From 858d8edf7aba344539317754e0e3e7dbefacb4e0 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Tue, 27 Feb 2024 23:29:40 -0700 Subject: [PATCH 01/23] Cam tweening working! UI buggy af. To fix. --- source/funkin/play/PlayState.hx | 46 ++++++++++++- .../funkin/play/event/FocusCameraSongEvent.hx | 67 +++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5bbf83e17..a745be6cd 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -219,6 +219,12 @@ class PlayState extends MusicBeatSubState */ public var cameraFollowPoint:FlxObject; + /** + * 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; + /** * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. @@ -2847,15 +2853,51 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera():Void + public function resetCamera(?resetZoom:Bool = true, ?cancelFollowTween:Bool = true):Void { + // Cancel the follow tween if it's active. + if (cancelFollowTween && cameraFollowTween != null) + { + cameraFollowTween.cancel(); + } + FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); FlxG.camera.targetOffset.set(); - FlxG.camera.zoom = defaultCameraZoom; + + if (resetZoom) + { + FlxG.camera.zoom = defaultCameraZoom; + } + // Snap the camera to the follow point immediately. FlxG.camera.focusOn(cameraFollowPoint.getPosition()); } + /** + * Disables camera following and tweens the camera to the follow point manually. + */ + public function tweenCamera(?duration:Float, ?ease:NullFloat>):Void + { + // Cancel the current tween if it's active. + if (cameraFollowTween != null) + { + cameraFollowTween.cancel(); + } + + // Disable camera following for the duration of the tween. + FlxG.camera.target = null; + + // Follow tween! Caching it so we can cancel it later if needed. + var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5); + cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, duration, + { + ease: ease, + onComplete: function(_) { + resetCamera(false, false); // Re-enable camera following when the tween is complete. + } + }); + } + #if (debug || FORCE_DEBUG_VERSION) /** * Jumps forward or backward a number of sections in the song. diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 847df4a60..28a629f1a 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -1,5 +1,6 @@ package funkin.play.event; +import flixel.tweens.FlxEase; // Data from the chart import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; @@ -66,6 +67,13 @@ class FocusCameraSongEvent extends SongEvent if (char == null) char = cast data.value; + var useTween:Null = data.getBool('useTween'); + if (useTween == null) useTween = false; + var duration:Null = data.getFloat('duration'); + if (duration == null) duration = 4.0; + var ease:Null = data.getString('ease'); + if (ease == null) ease = 'linear'; + switch (char) { case -1: // Position @@ -114,6 +122,20 @@ class FocusCameraSongEvent extends SongEvent default: trace('Unknown camera focus: ' + data); } + + if (useTween) // always ends up false?? + { + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; + + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); + if (easeFunction == null) + { + trace('Invalid ease function: $ease'); + return; + } + + PlayState.instance.tweenCamera(durSeconds, easeFunction); + } } public override function getTitle():String @@ -155,6 +177,51 @@ class FocusCameraSongEvent extends SongEvent step: 10.0, type: SongEventFieldType.FLOAT, units: "px" + }, + { + name: 'useTween', + title: 'Use Tween', + type: SongEventFieldType.BOOL, + defaultValue: false + }, + { + name: 'duration', + title: 'Duration', + defaultValue: 4.0, + step: 0.5, + type: SongEventFieldType.FLOAT, + units: 'steps' + }, + { + name: 'ease', + title: 'Easing Type', + defaultValue: 'linear', + type: SongEventFieldType.ENUM, + keys: [ + 'Linear' => 'linear', + 'Instant' => 'INSTANT', + 'Quad In' => 'quadIn', + 'Quad Out' => 'quadOut', + 'Quad In/Out' => 'quadInOut', + 'Cube In' => 'cubeIn', + 'Cube Out' => 'cubeOut', + 'Cube In/Out' => 'cubeInOut', + 'Quart In' => 'quartIn', + 'Quart Out' => 'quartOut', + 'Quart In/Out' => 'quartInOut', + 'Quint In' => 'quintIn', + 'Quint Out' => 'quintOut', + 'Quint In/Out' => 'quintInOut', + 'Smooth Step In' => 'smoothStepIn', + 'Smooth Step Out' => 'smoothStepOut', + 'Smooth Step In/Out' => 'smoothStepInOut', + 'Sine In' => 'sineIn', + 'Sine Out' => 'sineOut', + 'Sine In/Out' => 'sineInOut', + 'Elastic In' => 'elasticIn', + 'Elastic Out' => 'elasticOut', + 'Elastic In/Out' => 'elasticInOut', + ] } ]); } From d9cf097e460de497a7a6754c85eade23b39b9aa3 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 4 Mar 2024 20:57:21 -0700 Subject: [PATCH 02/23] Fixed bools and associated checkboxes not updating properly. --- source/funkin/play/event/FocusCameraSongEvent.hx | 2 +- .../debug/charting/toolboxes/ChartEditorEventDataToolbox.hx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 28a629f1a..979b1ad7b 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -123,7 +123,7 @@ class FocusCameraSongEvent extends SongEvent trace('Unknown camera focus: ' + data); } - if (useTween) // always ends up false?? + if (useTween) { var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index ec46e1f85..50b341272 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -237,6 +237,11 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox { value = event.target.value.value; } + else if (field.type == BOOL) + { + var chk:CheckBox = cast event.target; + value = cast(chk.selected, Null); // Need to cast to nullable bool or the compiler will get mad. + } trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); From 21b895ab9735ca1c121026326ab5ab2a0c4c2e24 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Fri, 8 Mar 2024 14:50:26 -0700 Subject: [PATCH 03/23] Assets submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 55c602f2a..49c409b4c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 55c602f2adbbd84de541ea86e5e646c4d2a1df0b +Subproject commit 49c409b4c8321d8cd317787a78c479aaa64cb517 From d495420938ab4a4efad2c2c3d265352c165fe488 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sun, 10 Mar 2024 15:24:43 -0400 Subject: [PATCH 04/23] fixes capitalization in checkstyle settings --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 87ed06aed..ea93651ed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -85,7 +85,7 @@ }, "projectManager.git.baseFolders": ["./"], - "haxecheckstyle.sourceFolders": ["src", "Source"], + "haxecheckstyle.sourceFolders": ["src", "source"], "haxecheckstyle.externalSourceRoots": [], "haxecheckstyle.configurationFile": "checkstyle.json", "haxecheckstyle.codeSimilarityBufferSize": 100, From 6b8fb7dc77a9c11ecc5afa541e05d004c64aaca2 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Sun, 10 Mar 2024 16:35:41 -0700 Subject: [PATCH 05/23] Standardized camera zoom tweening to match camera follow tweening. Implemented methods to cancel tweens in necessary places. Start of pausing tweens when pausing the game (WIP). (CHANGES NOT TESTED EXPECT SOMETHING TO BREAK) --- source/funkin/play/GameOverSubState.hx | 9 +- source/funkin/play/PlayState.hx | 124 +++++++++++++++--- .../funkin/play/event/FocusCameraSongEvent.hx | 22 ++-- .../funkin/play/event/ZoomCameraSongEvent.hx | 8 +- .../ui/debug/stage/StageOffsetSubState.hx | 11 +- 5 files changed, 136 insertions(+), 38 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 95304d762..b3e815a41 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -119,6 +119,8 @@ class GameOverSubState extends MusicBeatSubState // Set up the visuals // + var playState = PlayState.instance; + // Add a black background to the screen. var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); // We make this transparent so that we can see the stage underneath during debugging, @@ -130,13 +132,16 @@ class GameOverSubState extends MusicBeatSubState // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState. // We can then play the character's `firstDeath` animation. - boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend = playState.currentStage.getBoyfriend(true); boyfriend.isDead = true; add(boyfriend); boyfriend.resetCharacter(); + // Cancel camera tweening if it's currently active. + playState.cancelAllCameraTweens(); + // Assign a camera follow point to the boyfriend's position. - cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); + cameraFollowPoint = new FlxObject(playState.cameraFollowPoint.x, playState.cameraFollowPoint.y, 1, 1); cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; var offsets:Array = boyfriend.getDeathCameraOffsets(); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3ce1f4948..0bd731bc6 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -242,6 +242,11 @@ class PlayState extends MusicBeatSubState */ public var cameraFollowTween:FlxTween; + /** + * An FlxTween that zooms the camera to the desired amount. + */ + public var cameraZoomTween:FlxTween; + /** * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. @@ -402,10 +407,12 @@ class PlayState extends MusicBeatSubState var startingSong:Bool = false; /** - * False if `FlxG.sound.music` + * Track if we currently have the music paused for a Pause substate, so we can unpause it when we return. */ var musicPausedBySubState:Bool = false; + var cameraFollowTweenPausedBySubState:Bool = false; // Idea FOR LATER: Store paused tweens in an array, so we know which ones to unpause when leaving the Pause substate. + /** * False until `create()` has completed. */ @@ -1126,14 +1133,24 @@ class PlayState extends MusicBeatSubState // Pause the music. if (FlxG.sound.music != null) { - musicPausedBySubState = FlxG.sound.music.playing; - if (musicPausedBySubState) + if (FlxG.sound.music.playing) { FlxG.sound.music.pause(); + musicPausedBySubState = true; } + + // Pause vocals. + // Not tracking that we've done this via a bool because vocal re-syncing involves pausing the vocals anyway. if (vocals != null) vocals.pause(); } + // Pause camera tweening. + if (cameraFollowTween != null && cameraFollowTween.active) + { + cameraFollowTween.active = false; + cameraFollowTweenPausedBySubState = true; + } + // Pause the countdown. Countdown.pauseCountdown(); } @@ -1155,10 +1172,18 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; - // Resume + // Resume music if we paused it. if (musicPausedBySubState) { FlxG.sound.music.play(); + musicPausedBySubState = false; + } + + // Resume camera tweening if we paused it. + if (cameraFollowTweenPausedBySubState) + { + cameraFollowTween.active = true; + cameraFollowTweenPausedBySubState = false; } if (currentConversation != null) @@ -1166,6 +1191,7 @@ class PlayState extends MusicBeatSubState currentConversation.resumeMusic(); } + // Re-sync vocals. if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -2852,6 +2878,9 @@ class PlayState extends MusicBeatSubState */ function performCleanup():Void { + // If the camera is being tweened, stop it. + cancelAllCameraTweens(); + if (currentConversation != null) { remove(currentConversation); @@ -2910,6 +2939,9 @@ class PlayState extends MusicBeatSubState // Stop camera zooming on beat. cameraZoomRate = 0; + // Cancel camera tweening if it's active. + cancelAllCameraTweens(); + // 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. @@ -2996,12 +3028,12 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera(?resetZoom:Bool = true, ?cancelFollowTween:Bool = true):Void + public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void { - // Cancel the follow tween if it's active. - if (cancelFollowTween && cameraFollowTween != null) + // Cancel camera tweens if any are active. + if (cancelTweens) { - cameraFollowTween.cancel(); + cancelAllCameraTweens(); } FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); @@ -3019,26 +3051,78 @@ class PlayState extends MusicBeatSubState /** * Disables camera following and tweens the camera to the follow point manually. */ - public function tweenCamera(?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToFollowPoint(?duration:Float, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. + cancelCameraFollowTween(); + + if (duration == 0) + { + // Instant movement. Just reset the camera to force it to the follow point. + resetCamera(false, false); + } + else + { + // Disable camera following for the duration of the tween. + FlxG.camera.target = null; + + // Follow tween! Caching it so we can cancel/pause it later if needed. + var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5); + cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, duration, + { + ease: ease, + onComplete: function(_) { + resetCamera(false, false); // Re-enable camera following when the tween is complete. + } + }); + } + } + + public function cancelCameraFollowTween() + { if (cameraFollowTween != null) { cameraFollowTween.cancel(); } + } - // Disable camera following for the duration of the tween. - FlxG.camera.target = null; + /** + * Tweens the camera zoom to the desired amount. Tweens defaultCameraZoom to avoid breaking camera bops. + */ + public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?ease:NullFloat>):Void + { + // Cancel the current tween if it's active. + cancelCameraZoomTween(); - // Follow tween! Caching it so we can cancel it later if needed. - var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5); - cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, duration, - { - ease: ease, - onComplete: function(_) { - resetCamera(false, false); // Re-enable camera following when the tween is complete. - } - }); + var targetZoom = zoom * FlxCamera.defaultZoom; + + if (duration == 0) + { + // Instant zoom. No tween needed. + defaultCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + } + } + + public function cancelCameraZoomTween() + { + if (cameraZoomTween != null) + { + cameraZoomTween.cancel(); + } + } + + /** + * Cancel all active camera tweens simultaneously. + */ + public function cancelAllCameraTweens() + { + cancelCameraFollowTween(); + cancelCameraZoomTween(); } #if (debug || FORCE_DEBUG_VERSION) diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 979b1ad7b..cd4366dd2 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -125,16 +125,22 @@ class FocusCameraSongEvent extends SongEvent if (useTween) { - var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; - - var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); - if (easeFunction == null) + switch (ease) { - trace('Invalid ease function: $ease'); - return; - } + case 'INSTANT': + PlayState.instance.tweenCameraToFollowPoint(0); + default: + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; - PlayState.instance.tweenCamera(durSeconds, easeFunction); + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); + if (easeFunction == null) + { + trace('Invalid ease function: $ease'); + return; + } + + PlayState.instance.tweenCameraToFollowPoint(durSeconds, easeFunction); + } } } diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 809130499..187664e97 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -69,9 +69,10 @@ class ZoomCameraSongEvent extends SongEvent switch (ease) { case 'INSTANT': - // Set the zoom. Use defaultCameraZoom to prevent breaking camera bops. - PlayState.instance.defaultCameraZoom = zoom * FlxCamera.defaultZoom; + PlayState.instance.tweenCameraZoom(zoom, 0); default: + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); if (easeFunction == null) { @@ -79,8 +80,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000), - {ease: easeFunction}); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, easeFunction); } } diff --git a/source/funkin/ui/debug/stage/StageOffsetSubState.hx b/source/funkin/ui/debug/stage/StageOffsetSubState.hx index e8a5d0a23..fa5056220 100644 --- a/source/funkin/ui/debug/stage/StageOffsetSubState.hx +++ b/source/funkin/ui/debug/stage/StageOffsetSubState.hx @@ -49,8 +49,11 @@ class StageOffsetSubState extends HaxeUISubState { super.create(); + var playState = PlayState.instance; + FlxG.mouse.visible = true; - PlayState.instance.pauseMusic(); + playState.pauseMusic(); + playState.cancelAllCameraTweens(); FlxG.camera.target = null; setupUIListeners(); @@ -63,8 +66,8 @@ class StageOffsetSubState extends HaxeUISubState // add(uiStuff); - PlayState.instance.persistentUpdate = true; - component.cameras = [PlayState.instance.camHUD]; + playState.persistentUpdate = true; + component.cameras = [playState.camHUD]; // uiStuff.cameras = [PlayState.instance.camHUD]; // btn.cameras = [PlayState.instance.camHUD]; @@ -72,7 +75,7 @@ class StageOffsetSubState extends HaxeUISubState var layerList:ListView = findComponent("prop-layers"); - for (thing in PlayState.instance.currentStage) + for (thing in playState.currentStage) { var prop:StageProp = cast thing; if (prop != null && prop.name != null) From 3aada18a5998f18c745034910db16eb8c4aa816d Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 14 Mar 2024 04:03:33 -0700 Subject: [PATCH 06/23] title screen volume fix --- source/funkin/ui/title/TitleState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 1c194d80d..9dca759be 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -223,6 +223,7 @@ class TitleState extends MusicBeatState var shouldFadeIn = (FlxG.sound.music == null); // Load music. Includes logic to handle BPM changes. FunkinSound.playMusic('freakyMenu', false, true); + FlxG.sound.music.volume = 0; // Fade from 0.0 to 0.7 over 4 seconds if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7); } From e3b09bc8396f23754a8f6fa3e191825a6b77e755 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 14 Mar 2024 20:41:31 -0400 Subject: [PATCH 07/23] Clean up the classic MILF script --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 0e2c5bf21..fff87b632 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit fff87b6320a1fe2b46538bb77abe06b5b20376e0 From 82383018f66147df8374212abd8b54d9cc8145cc Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 14 Mar 2024 19:27:07 -0700 Subject: [PATCH 08/23] funkin soundtray --- assets | 2 +- source/Main.hx | 9 +- source/funkin/ui/options/FunkinSoundTray.hx | 138 ++++++++++++++++++++ 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 source/funkin/ui/options/FunkinSoundTray.hx diff --git a/assets b/assets index 0e2c5bf21..223722892 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 2237228923c6bd35df1e68e3b2a13dfffd1c243d diff --git a/source/Main.hx b/source/Main.hx index a40fda29d..758edcc65 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -100,8 +100,15 @@ class Main extends Sprite // George recommends binding the save before FlxGame is created. Save.load(); + var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen); - addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen)); + // FlxG.game._customSoundTray wants just the class, it calls new from + // create() in there, which gets called when it's added to stage + // which is why it needs to be added before addChild(game) here + @:privateAccess + game._customSoundTray = funkin.ui.options.FunkinSoundTray; + + addChild(game); #if hxcpp_debug_server trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.'); diff --git a/source/funkin/ui/options/FunkinSoundTray.hx b/source/funkin/ui/options/FunkinSoundTray.hx new file mode 100644 index 000000000..31c38286d --- /dev/null +++ b/source/funkin/ui/options/FunkinSoundTray.hx @@ -0,0 +1,138 @@ +package funkin.ui.options; + +import flixel.system.ui.FlxSoundTray; +import flixel.tweens.FlxTween; +import flixel.system.FlxAssets; +import flixel.tweens.FlxEase; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.utils.Assets; +import funkin.util.MathUtil; + +/** + * Extends the default flixel soundtray, but with some art + * and lil polish! + * + * Gets added to the game in Main.hx, right after FlxGame is new'd + * since it's a Sprite rather than Flixel related object + */ +class FunkinSoundTray extends FlxSoundTray +{ + var graphicScale:Float = 0.30; + var lerpYPos:Float = 0; + + var volumeMaxSound:String; + + public function new() + { + // calls super, then removes all children to add our own + // graphics + super(); + removeChildren(); + + var bg:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/volumebox"))); + bg.scaleX = graphicScale; + bg.scaleY = graphicScale; + addChild(bg); + + y = -height; + visible = false; + + // clear the bars array entirely, it was initialized + // in the super class + _bars = []; + + // 1...11 due to how block named the assets, + // we are trying to get assets bars_1-10 + for (i in 1...11) + { + var bar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_" + i))); + bar.x = 10; + bar.y = 5; + bar.scaleX = graphicScale; + bar.scaleY = graphicScale; + addChild(bar); + _bars.push(bar); + } + + y = -height; + screenCenter(); + + volumeUpSound = Paths.sound("soundtray/Volup"); + volumeDownSound = Paths.sound("soundtray/Voldown"); + volumeMaxSound = Paths.sound("soundtray/VolMAX"); + + trace("Custom tray added!"); + } + + override public function update(MS:Float):Void + { + y = MathUtil.coolLerp(y, lerpYPos, 0.1); + + // Animate sound tray thing + if (_timer > 0) + { + _timer -= (MS / 1000); + } + else if (y > -height) + { + lerpYPos = -height; + + if (y <= -height) + { + visible = false; + active = false; + + #if FLX_SAVE + // Save sound preferences + if (FlxG.save.isBound) + { + FlxG.save.data.mute = FlxG.sound.muted; + FlxG.save.data.volume = FlxG.sound.volume; + FlxG.save.flush(); + } + #end + } + } + } + + /** + * Makes the little volume tray slide out. + * + * @param up Whether the volume is increasing. + */ + override public function show(up:Bool = false):Void + { + _timer = 1; + lerpYPos = 0; + visible = true; + active = true; + var globalVolume:Int = Math.round(FlxG.sound.volume * 10); + + if (FlxG.sound.muted) + { + globalVolume = 0; + } + + if (!silent) + { + var sound = up ? volumeUpSound : volumeDownSound; + + if (globalVolume == 10) sound = volumeMaxSound; + + if (sound != null) FlxG.sound.load(sound).play(); + } + + for (i in 0..._bars.length) + { + if (i < globalVolume) + { + _bars[i].visible = true; + } + else + { + _bars[i].visible = false; + } + } + } +} From d68ea0eb465c0405f20cde6e75b8a2667d756bb3 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 14 Mar 2024 19:40:07 -0700 Subject: [PATCH 09/23] lil polish --- source/funkin/ui/options/FunkinSoundTray.hx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/options/FunkinSoundTray.hx b/source/funkin/ui/options/FunkinSoundTray.hx index 31c38286d..4af94569b 100644 --- a/source/funkin/ui/options/FunkinSoundTray.hx +++ b/source/funkin/ui/options/FunkinSoundTray.hx @@ -38,6 +38,15 @@ class FunkinSoundTray extends FlxSoundTray y = -height; visible = false; + // makes an alpha'd version of all the bars (bar_10.png) + var backingBar:Bitmap = new Bitmap(Assets.getBitmapData(Paths.image("soundtray/bars_10"))); + backingBar.x = 10; + backingBar.y = 5; + backingBar.scaleX = graphicScale; + backingBar.scaleY = graphicScale; + addChild(backingBar); + backingBar.alpha = 0.4; + // clear the bars array entirely, it was initialized // in the super class _bars = []; @@ -76,7 +85,7 @@ class FunkinSoundTray extends FlxSoundTray } else if (y > -height) { - lerpYPos = -height; + lerpYPos = -height - 10; if (y <= -height) { @@ -104,7 +113,7 @@ class FunkinSoundTray extends FlxSoundTray override public function show(up:Bool = false):Void { _timer = 1; - lerpYPos = 0; + lerpYPos = 10; visible = true; active = true; var globalVolume:Int = Math.round(FlxG.sound.volume * 10); From 4866667e1c463bda8f6c5849985182c1b27b524d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 15 Mar 2024 02:25:34 -0400 Subject: [PATCH 10/23] ANNIHILATE M.I.L.F. custom gameplay --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index fff87b632..6fef3c78e 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit fff87b6320a1fe2b46538bb77abe06b5b20376e0 +Subproject commit 6fef3c78e7c2ec16746337081c91af4095602634 From 1541f0aa68478d0651c8ba2fccc181e75883e964 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Fri, 15 Mar 2024 01:52:22 -0700 Subject: [PATCH 11/23] Camera tween pausing/unpausing additions --- assets | 2 +- source/funkin/play/PlayState.hx | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/assets b/assets index 0e2c5bf21..223722892 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 2237228923c6bd35df1e68e3b2a13dfffd1c243d diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 7c0ed64b3..d6cb5bd47 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -412,7 +412,10 @@ class PlayState extends MusicBeatSubState */ var musicPausedBySubState:Bool = false; - var cameraFollowTweenPausedBySubState:Bool = false; // Idea FOR LATER: Store paused tweens in an array, so we know which ones to unpause when leaving the Pause substate. + /** + * Track any camera tweens we've paused for a Pause substate, so we can unpause them when we return. + */ + var cameraTweensPausedBySubState:List = new List(); /** * False until `create()` has completed. @@ -1145,11 +1148,17 @@ class PlayState extends MusicBeatSubState if (vocals != null) vocals.pause(); } - // Pause camera tweening. + // Pause camera tweening, and keep track of which tweens we pause. if (cameraFollowTween != null && cameraFollowTween.active) { cameraFollowTween.active = false; - cameraFollowTweenPausedBySubState = true; + cameraTweensPausedBySubState.add(cameraFollowTween); + } + + if (cameraZoomTween != null && cameraZoomTween.active) + { + cameraZoomTween.active = false; + cameraTweensPausedBySubState.add(cameraZoomTween); } // Pause the countdown. @@ -1180,12 +1189,12 @@ class PlayState extends MusicBeatSubState musicPausedBySubState = false; } - // Resume camera tweening if we paused it. - if (cameraFollowTweenPausedBySubState) + // Resume camera tweens if we paused any. + for (camTween in cameraTweensPausedBySubState) { - cameraFollowTween.active = true; - cameraFollowTweenPausedBySubState = false; + camTween.active = true; } + cameraTweensPausedBySubState.clear(); if (currentConversation != null) { From 494a3c9e86dbc6ddb71a193488c3023c26558d9a Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Sat, 16 Mar 2024 08:38:10 -0700 Subject: [PATCH 12/23] Bugfixes. New additive zoom mode for camera tweening. --- source/funkin/play/PlayState.hx | 64 ++++++++++++++----- .../funkin/play/event/ZoomCameraSongEvent.hx | 22 +++++-- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d6cb5bd47..a5152e727 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -255,14 +255,23 @@ class PlayState extends MusicBeatSubState public var previousCameraFollowPoint:FlxPoint = null; /** - * The current camera zoom level. - * - * The camera zoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. - * Defaults to 1.05 but may be larger or smaller depending on the current stage, - * and may be changed by the `ZoomCamera` song event. + * The current camera zoom level without any modifiers applied. + */ + public var currentCameraZoom:Float = FlxCamera.defaultZoom * 1.05; + + /** + * currentCameraZoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. + * Defaults to 1.05, but may be larger or smaller depending on the current stage. + * Tweened via the `ZoomCamera` song event in direct mode. */ public var defaultCameraZoom:Float = FlxCamera.defaultZoom * 1.05; + /** + * Camera zoom applied on top of currentCameraZoom. + * Tweened via the `ZoomCamera` song event in additive mode. + */ + public var additiveCameraZoom:Float = 0; + /** * The current HUD camera zoom level. * @@ -959,7 +968,9 @@ class PlayState extends MusicBeatSubState // Lerp the camera zoom towards the target level. if (subState == null) { - FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95); + currentCameraZoom = FlxMath.lerp(defaultCameraZoom, currentCameraZoom, 0.95); + FlxG.camera.zoom = currentCameraZoom + additiveCameraZoom; + camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } @@ -1349,7 +1360,7 @@ class PlayState extends MusicBeatSubState if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0) { // Zoom camera in (1.5%) - FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom; + currentCameraZoom += cameraZoomIntensity * defaultCameraZoom; // Hud zooms double (3%) camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom; } @@ -1541,6 +1552,11 @@ class PlayState extends MusicBeatSubState { // Apply camera zoom level from stage data. defaultCameraZoom = currentStage.camZoom; + currentCameraZoom = defaultCameraZoom; + FlxG.camera.zoom = currentCameraZoom; + + // Reset additive zoom. + additiveCameraZoom = 0; } /** @@ -3051,7 +3067,7 @@ class PlayState extends MusicBeatSubState if (resetZoom) { - FlxG.camera.zoom = defaultCameraZoom; + resetCameraZoom(); } // Snap the camera to the follow point immediately. @@ -3097,24 +3113,40 @@ class PlayState extends MusicBeatSubState } /** - * Tweens the camera zoom to the desired amount. Tweens defaultCameraZoom to avoid breaking camera bops. + * Tweens the camera zoom to the desired amount. */ - public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?directMode:Bool, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraZoomTween(); var targetZoom = zoom * FlxCamera.defaultZoom; - if (duration == 0) + if (directMode) // Direct mode: Tween defaultCameraZoom for basic "smooth" zooms. { - // Instant zoom. No tween needed. - defaultCameraZoom = targetZoom; + if (duration == 0) + { + // Instant zoom. No tween needed. + defaultCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + } } - else + else // Additive mode: Tween additiveCameraZoom for ease-based zooms. { - // Zoom tween! Caching it so we can cancel/pause it later if needed. - cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + if (duration == 0) + { + // Instant zoom. No tween needed. + additiveCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {additiveCameraZoom: targetZoom}, duration, {ease: ease}); + } } } diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index b9b634ffe..d1741a463 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -62,17 +62,23 @@ class ZoomCameraSongEvent extends SongEvent var zoom:Null = data.getFloat('zoom'); if (zoom == null) zoom = 1.0; + var duration:Null = data.getFloat('duration'); if (duration == null) duration = 4.0; + var mode:Null = data.getString('mode'); + if (mode == null) mode = 'additive'; + var ease:Null = data.getString('ease'); if (ease == null) ease = 'linear'; + var directMode:Bool = mode == 'direct'; + // If it's a string, check the value. switch (ease) { case 'INSTANT': - PlayState.instance.tweenCameraZoom(zoom, 0); + PlayState.instance.tweenCameraZoom(zoom, 0, directMode); default: var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; @@ -83,7 +89,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - PlayState.instance.tweenCameraZoom(zoom, durSeconds, easeFunction); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, directMode, easeFunction); } } @@ -96,8 +102,9 @@ class ZoomCameraSongEvent extends SongEvent * ``` * { * 'zoom': FLOAT, // Target zoom level. - * 'duration': FLOAT, // Optional duration in steps - * 'ease': ENUM, // Optional easing function + * 'duration': FLOAT, // Optional duration in steps. + * 'mode': ENUM, // Whether to set additive zoom or direct zoom. + * 'ease': ENUM, // Optional easing function. * } * @return SongEventSchema */ @@ -120,6 +127,13 @@ class ZoomCameraSongEvent extends SongEvent type: SongEventFieldType.FLOAT, units: 'steps' }, + { + name: 'mode', + title: 'Mode', + defaultValue: 'additive', + type: SongEventFieldType.ENUM, + keys: ['Additive' => 'additive', 'Direct' => 'direct'] + }, { name: 'ease', title: 'Easing Type', From 50b17b3dcaba13894a827f75e2bc79493b6cd4ea Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 23:25:22 -0400 Subject: [PATCH 13/23] Update 2hot, Lit Up, and Blazin to polished audio --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 0e2c5bf21..26e2b2c44 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 26e2b2c4419f6a39b4e7641f0f824117c03eb0b6 From 36a9c29720f5a9c76316d2ab24461b8f54063aa7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 20 Mar 2024 14:37:24 -0400 Subject: [PATCH 14/23] Work in progress on custom album art for freeplay --- assets | 2 +- source/funkin/InitState.hx | 2 + source/funkin/data/freeplay/AlbumData.hx | 36 ++ source/funkin/data/freeplay/AlbumRegistry.hx | 84 +++++ .../graphics/adobeanimate/FlxAtlasSprite.hx | 7 +- source/funkin/modding/PolymodHandler.hx | 2 + source/funkin/play/song/Song.hx | 20 ++ .../debug/dialogue/ConversationDebugState.hx | 1 + source/funkin/ui/freeplay/Album.hx | 89 +++++ source/funkin/ui/freeplay/AlbumRoll.hx | 173 ++++++++++ source/funkin/ui/freeplay/FreeplayState.hx | 314 +++++++++--------- source/funkin/ui/freeplay/ScriptedAlbum.hx | 9 + source/funkin/ui/freeplay/SongMenuItem.hx | 54 +-- source/funkin/util/Constants.hx | 11 +- 14 files changed, 619 insertions(+), 185 deletions(-) create mode 100644 source/funkin/data/freeplay/AlbumData.hx create mode 100644 source/funkin/data/freeplay/AlbumRegistry.hx create mode 100644 source/funkin/ui/freeplay/Album.hx create mode 100644 source/funkin/ui/freeplay/AlbumRoll.hx create mode 100644 source/funkin/ui/freeplay/ScriptedAlbum.hx diff --git a/assets b/assets index 0e2c5bf21..4e88fb2a5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 4e88fb2a50b284d92404af6afc95b9840d3cda8d diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index a9e8dbffa..8837b578d 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -24,6 +24,7 @@ import funkin.data.stage.StageRegistry; import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.SpeakerRegistry; +import funkin.data.freeplay.AlbumRegistry; import funkin.data.song.SongRegistry; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.modding.module.ModuleHandler; @@ -167,6 +168,7 @@ class InitState extends FlxState ConversationRegistry.instance.loadEntries(); DialogueBoxRegistry.instance.loadEntries(); SpeakerRegistry.instance.loadEntries(); + AlbumRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries(); // TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers. diff --git a/source/funkin/data/freeplay/AlbumData.hx b/source/funkin/data/freeplay/AlbumData.hx new file mode 100644 index 000000000..265a01fce --- /dev/null +++ b/source/funkin/data/freeplay/AlbumData.hx @@ -0,0 +1,36 @@ +package funkin.data.freeplay; + +/** + * A type definition for the data for an album of songs. + * It includes things like what graphics to display in Freeplay. + * @see https://lib.haxe.org/p/json2object/ + */ +typedef AlbumData = +{ + /** + * Semantic version for album data. + */ + public var version:String; + + /** + * Readable name of the album. + */ + public var name:String; + + /** + * Readable name of the artist(s) of the album. + */ + public var artists:Array; + + /** + * Asset key for the album art. + * The album art will be displayed in Freeplay. + */ + public var albumArtAsset:String; + + /** + * Asset key for the album title. + * The album title will be displayed below the album art in Freeplay. + */ + public var albumTitleAsset:String; +} diff --git a/source/funkin/data/freeplay/AlbumRegistry.hx b/source/funkin/data/freeplay/AlbumRegistry.hx new file mode 100644 index 000000000..78fba451b --- /dev/null +++ b/source/funkin/data/freeplay/AlbumRegistry.hx @@ -0,0 +1,84 @@ +package funkin.data.freeplay; + +import funkin.ui.freeplay.Album; +import funkin.data.freeplay.AlbumData; +import funkin.ui.freeplay.ScriptedAlbum; + +class AlbumRegistry extends BaseRegistry +{ + /** + * The current version string for the album data format. + * Handle breaking changes by incrementing this value + * and adding migration to the `migrateAlbumData()` function. + */ + public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.0'; + + public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x'; + + public static final instance:AlbumRegistry = new AlbumRegistry(); + + public function new() + { + super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE); + } + + /** + * Read, parse, and validate the JSON data and produce the corresponding data object. + * @param id The ID of the entry to load. + * @return The parsed data object. + */ + public function parseEntryData(id:String):Null + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser:json2object.JsonParser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + + switch (loadEntryFile(id)) + { + case {fileName: fileName, contents: contents}: + parser.fromJson(contents, fileName); + default: + return null; + } + + if (parser.errors.length > 0) + { + printErrors(parser.errors, id); + return null; + } + return parser.value; + } + + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + * @return The parsed data object. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String):Null + { + var parser:json2object.JsonParser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + + function createScriptedEntry(clsName:String):Album + { + return ScriptedAlbum.init(clsName, 'unknown'); + } + + function getScriptedClassNames():Array + { + return ScriptedAlbum.listScriptClasses(); + } +} diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index 9a2af8913..ec8c8413c 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -3,7 +3,9 @@ package funkin.graphics.adobeanimate; import flixel.util.FlxSignal.FlxTypedSignal; import flxanimate.FlxAnimate; import flxanimate.FlxAnimate.Settings; -import flixel.math.FlxPoint; +import flxanimate.frames.FlxAnimateFrames; +import openfl.display.BitmapData; +import openfl.utils.Assets; /** * A sprite which provides convenience functions for rendering a texture atlas with animations. @@ -19,6 +21,7 @@ class FlxAtlasSprite extends FlxAnimate ShowPivot: #if debug false #else false #end, Antialiasing: true, ScrollFactor: null, + OverrideGraphics: [], // Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset }; @@ -31,7 +34,7 @@ class FlxAtlasSprite extends FlxAnimate var canPlayOtherAnims:Bool = true; - public function new(x:Float, y:Float, path:String, ?settings:Settings) + public function new(x:Float, y:Float, ?path:String, ?settings:Settings) { if (settings == null) settings = SETTINGS; diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index b1c6b511a..a88476d4d 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -8,6 +8,7 @@ import funkin.data.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.song.SongRegistry; import funkin.data.stage.StageRegistry; +import funkin.data.freeplay.AlbumRegistry; import funkin.modding.module.ModuleHandler; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.save.Save; @@ -324,6 +325,7 @@ class PolymodHandler ConversationRegistry.instance.loadEntries(); DialogueBoxRegistry.instance.loadEntries(); SpeakerRegistry.instance.loadEntries(); + AlbumRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries(); CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry. ModuleHandler.loadModuleCache(); diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 1b7740408..d82b60de6 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -213,6 +213,26 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry + { + var result:Map = new Map(); + + for (difficultyId in difficulties.keys()) + { + var meta:Null = difficulties.get(difficultyId); + if (meta != null && meta.album != null) + { + result.set(difficultyId, meta.album); + } + } + + return result; + } + /** * Populate the difficulty data from the provided metadata. * Does not load chart data (that is triggered later when we want to play the song). diff --git a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx index f165865c4..c2edcca5a 100644 --- a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx +++ b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx @@ -11,6 +11,7 @@ import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.SpeakerData; import funkin.data.dialogue.SpeakerRegistry; +import funkin.data.freeplay.AlbumRegistry; import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.DialogueBox; import funkin.play.cutscene.dialogue.Speaker; diff --git a/source/funkin/ui/freeplay/Album.hx b/source/funkin/ui/freeplay/Album.hx new file mode 100644 index 000000000..7291c7357 --- /dev/null +++ b/source/funkin/ui/freeplay/Album.hx @@ -0,0 +1,89 @@ +package funkin.ui.freeplay; + +import funkin.data.freeplay.AlbumData; +import funkin.data.freeplay.AlbumRegistry; +import funkin.data.IRegistryEntry; +import flixel.graphics.FlxGraphic; + +/** + * A class representing the data for an album as displayed in Freeplay. + */ +class Album implements IRegistryEntry +{ + /** + * The internal ID for this album. + */ + public final id:String; + + /** + * The full data for an album. + */ + public final _data:AlbumData; + + public function new(id:String) + { + this.id = id; + this._data = _fetchData(id); + + if (_data == null) + { + throw 'Could not parse album data for id: $id'; + } + } + + /** + * Return the name of the album. + * @ + */ + public function getAlbumName():String + { + return _data.name; + } + + /** + * Return the artists of the album. + * @return The list of artists + */ + public function getAlbumArtists():Array + { + return _data.artists; + } + + /** + * Get the asset key for the album art. + * @return The asset key + */ + public function getAlbumArtAssetKey():String + { + return _data.albumArtAsset; + } + + /** + * Get the album art as a graphic, ready to apply to a sprite. + * @return The built graphic + */ + public function getAlbumArtGraphic():FlxGraphic + { + return FlxG.bitmap.add(Paths.image(getAlbumArtAssetKey())); + } + + /** + * Get the asset key for the album title. + */ + public function getAlbumTitleAssetKey():String + { + return _data.albumTitleAsset; + } + + public function toString():String + { + return 'Album($id)'; + } + + public function destroy():Void {} + + static function _fetchData(id:String):Null + { + return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id)); + } +} diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx new file mode 100644 index 000000000..f0c759666 --- /dev/null +++ b/source/funkin/ui/freeplay/AlbumRoll.hx @@ -0,0 +1,173 @@ +package funkin.ui.freeplay; + +import flixel.graphics.FlxGraphic; +import flixel.group.FlxSpriteGroup; +import flixel.util.FlxSort; +import funkin.data.freeplay.AlbumRegistry; +import funkin.graphics.adobeanimate.FlxAtlasSprite; +import funkin.graphics.FunkinSprite; +import funkin.util.SortUtil; + +/** + * The graphic for the album roll in the FreeplayState. + * Simply set `albumID` to fetch the required data and update the textures. + */ +class AlbumRoll extends FlxSpriteGroup +{ + /** + * The ID of the album to display. + * Modify this value to automatically update the album art and title. + */ + public var albumId(default, set):String; + + function set_albumId(value:String):String + { + if (this.albumId != value) + { + this.albumId = value; + updateAlbum(); + } + + return value; + } + + var albumArt:FlxAtlasSprite; + var albumTitle:FunkinSprite; + var difficultyStars:DifficultyStars; + + var albumData:Album; + + public function new() + { + super(); + + albumTitle = new FunkinSprite(947, 491); + albumTitle.visible = false; + albumTitle.zIndex = 200; + add(albumTitle); + + difficultyStars = new DifficultyStars(140, 39); + + difficultyStars.stars.visible = true; + albumTitle.visible = false; + // albumArtist.visible = false; + + // var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); + } + + /** + * Load the album data by ID and update the textures. + */ + function updateAlbum():Void + { + albumData = AlbumRegistry.instance.fetchEntry(albumId); + + if (albumData == null) + { + FlxG.log.warn('Could not find album data for album ID: ${albumId}'); + + return; + }; + + var albumArtGraphics:Array = [null, albumData.getAlbumArtGraphic()]; + + if (albumArt != null) + { + albumArt.visible = false; + albumArt.anim.stop(); + albumArt.destroy(); + remove(albumArt); + } + + // I wasn't able to get replacing to work properly on an existing object, + // so I just throw the old one in the trash and make a new one. + albumArt = new FlxAtlasSprite(640, 360, Paths.animateAtlas('freeplay/albumRoll'), + { + OverrideGraphics: albumArtGraphics, + }); + albumArt.zIndex = 100; + + playIntro(); + add(albumArt); + + albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey())); + + refresh(); + } + + public function refresh():Void + { + sort(SortUtil.byZIndex, FlxSort.ASCENDING); + } + + /** + * Apply exit movers for the album roll. + * @param exitMovers The exit movers to apply. + */ + public function applyExitMovers(exitMovers:FreeplayState.ExitMoverData):Void + { + exitMovers.set([albumArt], + { + x: FlxG.width, + speed: 0.4, + wait: 0 + }); + exitMovers.set([albumTitle], + { + x: FlxG.width, + speed: 0.2, + wait: 0.1 + }); + + /* + exitMovers.set([albumArtist], + { + x: FlxG.width * 1.1, + speed: 0.2, + wait: 0.2 + }); + */ + exitMovers.set([difficultyStars], + { + x: FlxG.width * 1.2, + speed: 0.2, + wait: 0.3 + }); + } + + /** + * Play the intro animation on the album art. + */ + public function playIntro():Void + { + albumArt.visible = true; + albumArt.anim.play(''); + albumArt.anim.onComplete = function() { + albumArt.anim.pause(); + }; + } + + public function setDifficultyStars(?difficulty:Int):Void + { + if (difficulty == null) return; + + difficultyStars.difficulty = difficulty; + } + + /** + * Make the album title graphic visible. + */ + public function showTitle():Void + { + albumTitle.visible = true; + } + + /** + * Make the album stars visible. + */ + public function showStars():Void + { + // albumArtist.visible = false; + difficultyStars.stars.visible = false; + } +} diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 7ade5a2a6..8d7ecb556 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1,19 +1,14 @@ package funkin.ui.freeplay; -import openfl.text.TextField; -import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.ui.FlxInputText; import flixel.FlxCamera; -import flixel.FlxGame; import flixel.FlxSprite; -import flixel.FlxState; import flixel.group.FlxGroup; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; -import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.text.FlxText; @@ -25,7 +20,6 @@ import flixel.util.FlxTimer; import funkin.audio.FunkinSound; import funkin.data.level.LevelRegistry; import funkin.data.song.SongRegistry; -import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.FunkinCamera; import funkin.graphics.FunkinSprite; import funkin.graphics.shaders.AngleMask; @@ -33,28 +27,16 @@ import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.PureColor; import funkin.graphics.shaders.StrokeShader; import funkin.input.Controls; -import funkin.input.Controls.Control; -import funkin.play.components.HealthIcon; -import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; import funkin.save.Save; import funkin.save.Save.SaveScoreData; import funkin.ui.AtlasText; -import funkin.ui.freeplay.BGScrollingText; -import funkin.ui.freeplay.DifficultyStars; -import funkin.ui.freeplay.DJBoyfriend; -import funkin.ui.freeplay.FreeplayScore; -import funkin.ui.freeplay.LetterSort; -import funkin.ui.freeplay.SongMenuItem; import funkin.ui.mainmenu.MainMenuState; -import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatSubState; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; import funkin.util.MathUtil; -import funkin.util.MathUtil; -import lime.app.Future; import lime.utils.Assets; /** @@ -65,6 +47,9 @@ typedef FreeplayStateParams = ?character:String, }; +/** + * The state for the freeplay menu, allowing the player to select any song to play. + */ class FreeplayState extends MusicBeatSubState { // @@ -120,30 +105,31 @@ class FreeplayState extends MusicBeatSubState var grpDifficulties:FlxTypedSpriteGroup; var coolColors:Array = [ - 0xff9271fd, - 0xff9271fd, - 0xff223344, + 0xFF9271FD, + 0xFF9271FD, + 0xFF223344, 0xFF941653, - 0xFFfc96d7, - 0xFFa0d1ff, - 0xffff78bf, - 0xfff6b604 + 0xFFFC96D7, + 0xFFA0D1FF, + 0xFFFF78BF, + 0xFFF6B604 ]; var grpSongs:FlxTypedGroup; var grpCapsules:FlxTypedGroup; var curCapsule:SongMenuItem; var curPlaying:Bool = false; - var ostName:FlxText; - var difficultyStars:DifficultyStars; var displayedVariations:Array; var dj:DJBoyfriend; + var ostName:FlxText; + var albumRoll:AlbumRoll; + var letterSort:LetterSort; var typing:FlxInputText; - var exitMovers:Map, MoveData> = new Map(); + var exitMovers:ExitMoverData = new Map(); var stickerSubState:StickerSubState; @@ -179,7 +165,7 @@ class FreeplayState extends MusicBeatSubState #if discord_rpc // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); + DiscordClient.changePresence('In the Menus', null); #end var isDebug:Bool = false; @@ -195,7 +181,7 @@ class FreeplayState extends MusicBeatSubState // TODO: This makes custom variations disappear from Freeplay. Figure out a better solution later. // Default character (BF) shows default and Erect variations. Pico shows only Pico variations. - displayedVariations = (currentCharacter == "bf") ? [Constants.DEFAULT_VARIATION, "erect"] : [currentCharacter]; + displayedVariations = (currentCharacter == 'bf') ? [Constants.DEFAULT_VARIATION, 'erect'] : [currentCharacter]; // programmatically adds the songs via LevelRegistry and SongRegistry for (levelId in LevelRegistry.instance.listBaseGameLevelIds()) @@ -205,7 +191,7 @@ class FreeplayState extends MusicBeatSubState var song:Song = SongRegistry.instance.fetchEntry(songId); // Only display songs which actually have available charts for the current character. - var availableDifficultiesForSong = song.listDifficulties(displayedVariations); + var availableDifficultiesForSong:Array = song.listDifficulties(displayedVariations); if (availableDifficultiesForSong.length == 0) continue; songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); @@ -226,16 +212,16 @@ class FreeplayState extends MusicBeatSubState trace(FlxCamera.defaultZoom); var pinkBack:FunkinSprite = FunkinSprite.create('freeplay/pinkBack'); - pinkBack.color = 0xFFffd4e9; // sets it to pink! + pinkBack.color = 0xFFFFD4E9; // sets it to pink! pinkBack.x -= pinkBack.width; FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); add(pinkBack); - var orangeBackShit:FunkinSprite = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFfeda00); + var orangeBackShit:FunkinSprite = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00); add(orangeBackShit); - var alsoOrangeLOL:FunkinSprite = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFffd400); + var alsoOrangeLOL:FunkinSprite = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400); add(alsoOrangeLOL); exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL], @@ -254,10 +240,10 @@ class FreeplayState extends MusicBeatSubState add(grpTxtScrolls); grpTxtScrolls.visible = false; - FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ["x", "y", "speed", "size"])); + FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ['x', 'y', 'speed', 'size'])); - var moreWays:BGScrollingText = new BGScrollingText(0, 160, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width, true, 43); - moreWays.funnyColor = 0xFFfff383; + var moreWays:BGScrollingText = new BGScrollingText(0, 160, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43); + moreWays.funnyColor = 0xFFFFF383; moreWays.speed = 6.8; grpTxtScrolls.add(moreWays); @@ -267,8 +253,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.4, }); - var funnyScroll:BGScrollingText = new BGScrollingText(0, 220, "BOYFRIEND", FlxG.width / 2, false, 60); - funnyScroll.funnyColor = 0xFFff9963; + var funnyScroll:BGScrollingText = new BGScrollingText(0, 220, 'BOYFRIEND', FlxG.width / 2, false, 60); + funnyScroll.funnyColor = 0xFFFF9963; funnyScroll.speed = -3.8; grpTxtScrolls.add(funnyScroll); @@ -280,7 +266,7 @@ class FreeplayState extends MusicBeatSubState wait: 0 }); - var txtNuts:BGScrollingText = new BGScrollingText(0, 285, "PROTECT YO NUTS", FlxG.width / 2, true, 43); + var txtNuts:BGScrollingText = new BGScrollingText(0, 285, 'PROTECT YO NUTS', FlxG.width / 2, true, 43); txtNuts.speed = 3.5; grpTxtScrolls.add(txtNuts); exitMovers.set([txtNuts], @@ -289,8 +275,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.4, }); - var funnyScroll2:BGScrollingText = new BGScrollingText(0, 335, "BOYFRIEND", FlxG.width / 2, false, 60); - funnyScroll2.funnyColor = 0xFFff9963; + var funnyScroll2:BGScrollingText = new BGScrollingText(0, 335, 'BOYFRIEND', FlxG.width / 2, false, 60); + funnyScroll2.funnyColor = 0xFFFF9963; funnyScroll2.speed = -3.8; grpTxtScrolls.add(funnyScroll2); @@ -300,8 +286,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.5, }); - var moreWays2:BGScrollingText = new BGScrollingText(0, 397, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width, true, 43); - moreWays2.funnyColor = 0xFFfff383; + var moreWays2:BGScrollingText = new BGScrollingText(0, 397, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43); + moreWays2.funnyColor = 0xFFFFF383; moreWays2.speed = 6.8; grpTxtScrolls.add(moreWays2); @@ -311,8 +297,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.4 }); - var funnyScroll3:BGScrollingText = new BGScrollingText(0, orangeBackShit.y + 10, "BOYFRIEND", FlxG.width / 2, 60); - funnyScroll3.funnyColor = 0xFFfea400; + var funnyScroll3:BGScrollingText = new BGScrollingText(0, orangeBackShit.y + 10, 'BOYFRIEND', FlxG.width / 2, 60); + funnyScroll3.funnyColor = 0xFFFEA400; funnyScroll3.speed = -3.8; grpTxtScrolls.add(funnyScroll3); @@ -328,8 +314,10 @@ class FreeplayState extends MusicBeatSubState x: -dj.width * 1.6, speed: 0.5 }); + // TODO: Replace this. - if (currentCharacter == "pico") dj.visible = false; + if (currentCharacter == 'pico') dj.visible = false; + add(dj); var bgDad:FlxSprite = new FlxSprite(pinkBack.width * 0.75, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad')); @@ -387,62 +375,23 @@ class FreeplayState extends MusicBeatSubState if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; } - // NOTE: This is an AtlasSprite because we use an animation to bring it into view. - // TODO: Add the ability to select the album graphic. - var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll")); - albumArt.visible = false; - add(albumArt); + albumRoll = new AlbumRoll(); + albumRoll.albumId = 'volume1'; + add(albumRoll); - exitMovers.set([albumArt], - { - x: FlxG.width, - speed: 0.4, - wait: 0 - }); - - var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1')); - var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); - difficultyStars = new DifficultyStars(140, 39); - - difficultyStars.stars.visible = false; - albumTitle.visible = false; - albumArtist.visible = false; - - exitMovers.set([albumTitle], - { - x: FlxG.width, - speed: 0.2, - wait: 0.1 - }); - - exitMovers.set([albumArtist], - { - x: FlxG.width * 1.1, - speed: 0.2, - wait: 0.2 - }); - exitMovers.set([difficultyStars], - { - x: FlxG.width * 1.2, - speed: 0.2, - wait: 0.3 - }); - - add(albumTitle); - add(albumArtist); - add(difficultyStars); + albumRoll.applyExitMovers(exitMovers); var overhangStuff:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 64, FlxColor.BLACK); overhangStuff.y -= overhangStuff.height; add(overhangStuff); FlxTween.tween(overhangStuff, {y: 0}, 0.3, {ease: FlxEase.quartOut}); - var fnfFreeplay:FlxText = new FlxText(8, 8, 0, "FREEPLAY", 48); - fnfFreeplay.font = "VCR OSD Mono"; + var fnfFreeplay:FlxText = new FlxText(8, 8, 0, 'FREEPLAY', 48); + fnfFreeplay.font = 'VCR OSD Mono'; fnfFreeplay.visible = false; - ostName = new FlxText(8, 8, FlxG.width - 8 - 8, "OFFICIAL OST", 48); - ostName.font = "VCR OSD Mono"; + ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48); + ostName.font = 'VCR OSD Mono'; ostName.alignment = RIGHT; ostName.visible = false; @@ -454,21 +403,21 @@ class FreeplayState extends MusicBeatSubState wait: 0 }); - var sillyStroke = new StrokeShader(0xFFFFFFFF, 2, 2); + var sillyStroke:StrokeShader = new StrokeShader(0xFFFFFFFF, 2, 2); fnfFreeplay.shader = sillyStroke; add(fnfFreeplay); add(ostName); var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); - fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore small instance 1", 24, false); + fnfHighscoreSpr.animation.addByPrefix('highscore', 'highscore small instance 1', 24, false); fnfHighscoreSpr.visible = false; fnfHighscoreSpr.setGraphicSize(0, Std.int(fnfHighscoreSpr.height * 1)); fnfHighscoreSpr.updateHitbox(); add(fnfHighscoreSpr); new FlxTimer().start(FlxG.random.float(12, 50), function(tmr) { - fnfHighscoreSpr.animation.play("highscore"); + fnfHighscoreSpr.animation.play('highscore'); tmr.time = FlxG.random.float(20, 60); }, 0); @@ -479,7 +428,7 @@ class FreeplayState extends MusicBeatSubState var clearBoxSprite:FlxSprite = new FlxSprite(1165, 65).loadGraphic(Paths.image('freeplay/clearBox')); add(clearBoxSprite); - txtCompletion = new AtlasText(1185, 87, "69", AtlasFont.FREEPLAY_CLEAR); + txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR); txtCompletion.visible = false; add(txtCompletion); @@ -496,9 +445,9 @@ class FreeplayState extends MusicBeatSubState letterSort.changeSelectionCallback = (str) -> { switch (str) { - case "fav": + case 'fav': generateSongList({filterType: FAVORITE}, true); - case "ALL": + case 'ALL': generateSongList(null, true); default: generateSongList({filterType: REGEXP, filterData: str}, true); @@ -514,25 +463,20 @@ class FreeplayState extends MusicBeatSubState dj.onIntroDone.add(function() { // when boyfriend hits dat shiii - albumArt.visible = true; - albumArt.anim.play(""); - albumArt.anim.onComplete = function() { - albumArt.anim.pause(); - }; + albumRoll.playIntro(); new FlxTimer().start(1, function(_) { - albumTitle.visible = true; + albumRoll.showTitle(); }); new FlxTimer().start(35 / 24, function(_) { - albumArtist.visible = true; - difficultyStars.stars.visible = true; + albumRoll.showStars(); }); FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut}); - var diffSelLeft = new DifficultySelector(20, grpDifficulties.y - 10, false, controls); - var diffSelRight = new DifficultySelector(325, grpDifficulties.y - 10, true, controls); + var diffSelLeft:DifficultySelector = new DifficultySelector(20, grpDifficulties.y - 10, false, controls); + var diffSelRight:DifficultySelector = new DifficultySelector(325, grpDifficulties.y - 10, true, controls); add(diffSelLeft); add(diffSelRight); @@ -562,7 +506,7 @@ class FreeplayState extends MusicBeatSubState }); }); - pinkBack.color = 0xFFffd863; + pinkBack.color = 0xFFFFD863; bgDad.visible = true; orangeBackShit.visible = true; alsoOrangeLOL.visible = true; @@ -571,9 +515,9 @@ class FreeplayState extends MusicBeatSubState generateSongList(null, false); - var swag:Alphabet = new Alphabet(1, 0, "swag"); + // var swag:Alphabet = new Alphabet(1, 0, 'swag'); - var funnyCam = new FunkinCamera(0, 0, FlxG.width, FlxG.height); + var funnyCam:FunkinCamera = new FunkinCamera(0, 0, FlxG.width, FlxG.height); funnyCam.bgColor = FlxColor.TRANSPARENT; FlxG.cameras.add(funnyCam); @@ -588,12 +532,20 @@ class FreeplayState extends MusicBeatSubState }); } + /** + * Given the current filter, rebuild the current song list. + * + * @param filterStuff A filter to apply to the song list (regex, startswith, all, favorite) + * @param force + */ public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void { curSelected = 1; for (cap in grpCapsules.members) + { cap.kill(); + } var tempSongs:Array = songs; @@ -604,7 +556,7 @@ class FreeplayState extends MusicBeatSubState case REGEXP: // filterStuff.filterData has a string with the first letter of the sorting range, and the second one // this creates a filter to return all the songs that start with a letter between those two - var filterRegexp = new EReg("^[" + filterStuff.filterData + "].*", "i"); + var filterRegexp:EReg = new EReg('^[' + filterStuff.filterData + '].*', 'i'); tempSongs = tempSongs.filter(str -> { if (str == null) return true; // Random return filterRegexp.match(str.songName); @@ -660,14 +612,19 @@ class FreeplayState extends MusicBeatSubState funnyMenu.favIcon.visible = tempSongs[i].isFav; funnyMenu.hsvShader = hsvShader; - if (i < 8) funnyMenu.initJumpIn(Math.min(i, 4), force); + if (i < 8) + { + funnyMenu.initJumpIn(Math.min(i, 4), force); + } else + { funnyMenu.forcePosition(); + } grpCapsules.add(funnyMenu); } - FlxG.console.registerFunction("changeSelection", changeSelection); + FlxG.console.registerFunction('changeSelection', changeSelection); rememberSelection(); @@ -699,7 +656,7 @@ class FreeplayState extends MusicBeatSubState { if (songs[curSelected] != null) { - var realShit = curSelected; + var realShit:Int = curSelected; songs[curSelected].isFav = !songs[curSelected].isFav; if (songs[curSelected].isFav) { @@ -708,7 +665,7 @@ class FreeplayState extends MusicBeatSubState ease: FlxEase.elasticOut, onComplete: _ -> { grpCapsules.members[realShit].favIcon.visible = true; - grpCapsules.members[realShit].favIcon.animation.play("fav"); + grpCapsules.members[realShit].favIcon.animation.play('fav'); } }); } @@ -772,9 +729,9 @@ class FreeplayState extends MusicBeatSubState { if (busy) return; - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; - var accepted = controls.ACCEPT; + var upP:Bool = controls.UI_UP_P; + var downP:Bool = controls.UI_DOWN_P; + var accepted:Bool = controls.ACCEPT; if (FlxG.onMobile) { @@ -786,14 +743,14 @@ class FreeplayState extends MusicBeatSubState } if (touch.pressed) { - var dx = initTouchPos.x - touch.screenX; - var dy = initTouchPos.y - touch.screenY; + var dx:Float = initTouchPos.x - touch.screenX; + var dy:Float = initTouchPos.y - touch.screenY; - var angle = Math.atan2(dy, dx); - var length = Math.sqrt(dx * dx + dy * dy); + var angle:Float = Math.atan2(dy, dx); + var length:Float = Math.sqrt(dx * dx + dy * dy); - FlxG.watch.addQuick("LENGTH", length); - FlxG.watch.addQuick("ANGLE", Math.round(FlxAngle.asDegrees(angle))); + FlxG.watch.addQuick('LENGTH', length); + FlxG.watch.addQuick('ANGLE', Math.round(FlxAngle.asDegrees(angle))); } } @@ -858,9 +815,14 @@ class FreeplayState extends MusicBeatSubState { spamTimer = 0; - if (controls.UI_UP) changeSelection(-1); + if (controls.UI_UP) + { + changeSelection(-1); + } else + { changeSelection(1); + } } } else if (spamTimer >= 0.9) spamming = true; @@ -904,7 +866,7 @@ class FreeplayState extends MusicBeatSubState { var newParams:FreeplayStateParams = { - character: currentCharacter == "bf" ? "pico" : "bf", + character: currentCharacter == 'bf' ? 'pico' : 'bf', }; openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(newParams, sticker))); } @@ -974,7 +936,7 @@ class FreeplayState extends MusicBeatSubState public override function destroy():Void { super.destroy(); - var daSong = songs[curSelected]; + var daSong:Null = songs[curSelected]; if (daSong != null) { clearDaCache(daSong.songName); @@ -985,7 +947,7 @@ class FreeplayState extends MusicBeatSubState { touchTimer = 0; - var currentDifficultyIndex = diffIdsCurrent.indexOf(currentDifficulty); + var currentDifficultyIndex:Int = diffIdsCurrent.indexOf(currentDifficulty); if (currentDifficultyIndex == -1) currentDifficultyIndex = diffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY); @@ -996,7 +958,7 @@ class FreeplayState extends MusicBeatSubState currentDifficulty = diffIdsCurrent[currentDifficultyIndex]; - var daSong = songs[curSelected]; + var daSong:Null = songs[curSelected]; if (daSong != null) { var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty); @@ -1060,11 +1022,12 @@ class FreeplayState extends MusicBeatSubState } // Set the difficulty star count on the right. - difficultyStars.difficulty = daSong?.songRating ?? difficultyStars.difficulty; // yay haxe 4.3 + albumRoll.setDifficultyStars(daSong?.songRating); + albumRoll.albumId = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID; } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) - function clearDaCache(actualSongTho:String) + function clearDaCache(actualSongTho:String):Void { for (song in songs) { @@ -1079,7 +1042,7 @@ class FreeplayState extends MusicBeatSubState function capsuleOnConfirmRandom(randomCapsule:SongMenuItem):Void { - trace("RANDOM SELECTED"); + trace('RANDOM SELECTED'); busy = true; letterSort.inputEnabled = false; @@ -1095,7 +1058,7 @@ class FreeplayState extends MusicBeatSubState if (availableSongCapsules.length == 0) { - trace("No songs available!"); + trace('No songs available!'); busy = false; letterSort.inputEnabled = true; FlxG.sound.play(Paths.sound('cancelMenu')); @@ -1167,24 +1130,23 @@ class FreeplayState extends MusicBeatSubState } // Set the difficulty star count on the right. - var daSong = songs[curSelected]; - difficultyStars.difficulty = daSong?.songRating ?? 0; + var daSong:Null = songs[curSelected]; + albumRoll.setDifficultyStars(daSong?.songRating ?? 0); } function changeSelection(change:Int = 0):Void { - // NGio.logEvent('Fresh'); FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName)); - var prevSelected = curSelected; + var prevSelected:Int = curSelected; curSelected += change; if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; if (curSelected >= grpCapsules.countLiving()) curSelected = 0; - var daSongCapsule = grpCapsules.members[curSelected]; + var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected]; if (daSongCapsule.songData != null) { var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); @@ -1235,6 +1197,9 @@ class FreeplayState extends MusicBeatSubState } } +/** + * The difficulty selector arrows to the left and right of the difficulty. + */ class DifficultySelector extends FlxSprite { var controls:Controls; @@ -1247,7 +1212,7 @@ class DifficultySelector extends FlxSprite this.controls = controls; frames = Paths.getSparrowAtlas('freeplay/freeplaySelector'); - animation.addByPrefix('shine', "arrow pointer loop", 24); + animation.addByPrefix('shine', 'arrow pointer loop', 24); animation.play('shine'); whiteShader = new PureColor(FlxColor.WHITE); @@ -1281,34 +1246,62 @@ class DifficultySelector extends FlxSprite } } +/** + * Structure for the current song filter. + */ typedef SongFilter = { var filterType:FilterType; var ?filterData:Dynamic; } +/** + * Possible types to use for the song filter. + */ enum abstract FilterType(String) { - var STARTSWITH; - var REGEXP; - var FAVORITE; - var ALL; + /** + * Filter to songs which start with a string + */ + public var STARTSWITH; + + /** + * Filter to songs which match a regular expression + */ + public var REGEXP; + + /** + * Filter to songs which are favorited + */ + public var FAVORITE; + + /** + * Filter to all songs + */ + public var ALL; } +/** + * Data about a specific song in the freeplay menu. + */ class FreeplaySongData { + /** + * Whether or not the song has been favorited. + */ public var isFav:Bool = false; var song:Song; - public var levelId(default, null):String = ""; - public var songId(default, null):String = ""; + public var levelId(default, null):String = ''; + public var songId(default, null):String = ''; public var songDifficulties(default, null):Array = []; - public var songName(default, null):String = ""; - public var songCharacter(default, null):String = ""; + public var songName(default, null):String = ''; + public var songCharacter(default, null):String = ''; public var songRating(default, null):Int = 0; + public var albumId(default, null):String = ''; public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; public var displayedVariations(default, null):Array = [Constants.DEFAULT_VARIATION]; @@ -1332,19 +1325,28 @@ class FreeplaySongData updateValues(displayedVariations); } - function updateValues(displayedVariations:Array):Void + function updateValues(variations:Array):Void { - this.songDifficulties = song.listDifficulties(displayedVariations); + this.songDifficulties = song.listDifficulties(variations); if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY; - var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, displayedVariations); + var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, variations); if (songDifficulty == null) return; this.songName = songDifficulty.songName; this.songCharacter = songDifficulty.characters.opponent; this.songRating = songDifficulty.difficultyRating; + this.albumId = songDifficulty.album; } } +/** + * The map storing information about the exit movers. + */ +typedef ExitMoverData = Map, MoveData>; + +/** + * The data for an exit mover. + */ typedef MoveData = { var ?x:Float; @@ -1353,8 +1355,14 @@ typedef MoveData = var ?wait:Float; } +/** + * The sprite for the difficulty + */ class DifficultySprite extends FlxSprite { + /** + * The difficulty id which this sprite represents. + */ public var difficultyId:String; public function new(diffId:String) diff --git a/source/funkin/ui/freeplay/ScriptedAlbum.hx b/source/funkin/ui/freeplay/ScriptedAlbum.hx new file mode 100644 index 000000000..737f97ad2 --- /dev/null +++ b/source/funkin/ui/freeplay/ScriptedAlbum.hx @@ -0,0 +1,9 @@ +package funkin.ui.freeplay; + +/** + * A script that can be tied to an Album. + * Create a scripted class that extends Album to use this. + * This allows you to customize how a specific album appears. + */ +@:hscriptClass +class ScriptedAlbum extends funkin.ui.freeplay.Album implements polymod.hscript.HScriptedClass {} diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 06d113468..c20d81328 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -65,25 +65,26 @@ class SongMenuItem extends FlxSpriteGroup var rank:String = FlxG.random.getObject(ranks); ranking = new FlxSprite(capsule.width * 0.84, 30); - ranking.loadGraphic(Paths.image("freeplay/ranks/" + rank)); + ranking.loadGraphic(Paths.image('freeplay/ranks/' + rank)); ranking.scale.x = ranking.scale.y = realScaled; - ranking.alpha = 0.75; + // ranking.alpha = 0.75; + ranking.visible = false; ranking.origin.set(capsule.origin.x - ranking.x, capsule.origin.y - ranking.y); add(ranking); grpHide.add(ranking); switch (rank) { - case "perfect": + case 'perfect': ranking.x -= 10; } grayscaleShader = new Grayscale(1); - diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRatings/diff00")); + diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image('freeplay/diffRatings/diff00')); diffRatingSprite.shader = grayscaleShader; - diffRatingSprite.visible = false; - add(diffRatingSprite); + // TODO: Readd once ratings are fully implemented + // add(diffRatingSprite); diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y); grpHide.add(diffRatingSprite); @@ -104,7 +105,7 @@ class SongMenuItem extends FlxSpriteGroup favIcon = new FlxSprite(400, 40); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); - favIcon.animation.addByPrefix('fav', "favorite heart", 24, false); + favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false); favIcon.animation.play('fav'); favIcon.setGraphicSize(50, 50); favIcon.visible = false; @@ -114,10 +115,11 @@ class SongMenuItem extends FlxSpriteGroup setVisibleGrp(false); } - function updateDifficultyRating(newRating:Int) + function updateDifficultyRating(newRating:Int):Void { var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}')); + diffRatingSprite.visible = false; } function set_hsvShader(value:HSVShader):HSVShader @@ -129,7 +131,7 @@ class SongMenuItem extends FlxSpriteGroup return value; } - function textAppear() + function textAppear():Void { songText.scale.x = 1.7; songText.scale.y = 0.2; @@ -144,7 +146,7 @@ class SongMenuItem extends FlxSpriteGroup }); } - function setVisibleGrp(value:Bool) + function setVisibleGrp(value:Bool):Void { for (spr in grpHide.members) { @@ -156,7 +158,7 @@ class SongMenuItem extends FlxSpriteGroup updateSelected(); } - public function init(?x:Float, ?y:Float, songData:Null) + public function init(?x:Float, ?y:Float, songData:Null):Void { if (x != null) this.x = x; if (y != null) this.y = y; @@ -176,7 +178,7 @@ class SongMenuItem extends FlxSpriteGroup * @param char The character ID used by this song. * If the character has no freeplay icon, a warning will be thrown and nothing will display. */ - public function setCharacter(char:String) + public function setCharacter(char:String):Void { var charPath:String = "freeplay/icons/"; @@ -186,18 +188,18 @@ class SongMenuItem extends FlxSpriteGroup // TODO: Also, can use CharacterDataParser.getCharPixelIconAsset() switch (char) { - case "monster-christmas": - charPath += "monsterpixel"; - case "mom-car": - charPath += "mommypixel"; - case "dad": - charPath += "daddypixel"; - case "darnell-blazin": - charPath += "darnellpixel"; - case "senpai-angry": - charPath += "senpaipixel"; + case 'monster-christmas': + charPath += 'monsterpixel'; + case 'mom-car': + charPath += 'mommypixel'; + case 'dad': + charPath += 'daddypixel'; + case 'darnell-blazin': + charPath += 'darnellpixel'; + case 'senpai-angry': + charPath += 'senpaipixel'; default: - charPath += char + "pixel"; + charPath += '${char}pixel'; } if (!openfl.utils.Assets.exists(Paths.image(charPath))) @@ -211,7 +213,7 @@ class SongMenuItem extends FlxSpriteGroup switch (char) { - case "parents-christmas": + case 'parents-christmas': pixelIcon.origin.x = 140; default: pixelIcon.origin.x = 100; @@ -262,7 +264,7 @@ class SongMenuItem extends FlxSpriteGroup var grpHide:FlxGroup; - public function forcePosition() + public function forcePosition():Void { visible = true; capsule.alpha = 1; @@ -287,7 +289,7 @@ class SongMenuItem extends FlxSpriteGroup setVisibleGrp(true); } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (doJumpIn) { diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 1005b312e..c9b99ed46 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -175,17 +175,22 @@ class Constants /** * The default name for songs. */ - public static final DEFAULT_SONGNAME:String = "Unknown"; + public static final DEFAULT_SONGNAME:String = 'Unknown'; /** * The default artist for songs. */ - public static final DEFAULT_ARTIST:String = "Unknown"; + public static final DEFAULT_ARTIST:String = 'Unknown'; /** * The default note style for songs. */ - public static final DEFAULT_NOTE_STYLE:String = "funkin"; + public static final DEFAULT_NOTE_STYLE:String = 'funkin'; + + /** + * The default album for songs in Freeplay. + */ + public static final DEFAULT_ALBUM_ID:String = 'volume1'; /** * The default timing format for songs. From 3975d34b70f53e8ff72d714f131184b0547ce004 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 20 Mar 2024 23:06:32 -0400 Subject: [PATCH 15/23] Fix scoring lerp issue on story menu. --- source/funkin/ui/story/StoryMenuState.hx | 2 +- source/funkin/util/MathUtil.hx | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 1f78eb375..60a6defdd 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -311,7 +311,7 @@ class StoryMenuState extends MusicBeatState { Conductor.instance.update(); - highScoreLerp = Std.int(MathUtil.coolLerp(highScoreLerp, highScore, 0.5)); + highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.5)); scoreText.text = 'LEVEL SCORE: ${Math.round(highScoreLerp)}'; diff --git a/source/funkin/util/MathUtil.hx b/source/funkin/util/MathUtil.hx index 5fed1d3e1..72c592e8b 100644 --- a/source/funkin/util/MathUtil.hx +++ b/source/funkin/util/MathUtil.hx @@ -62,12 +62,22 @@ class MathUtil * @param duration The total duration of the interpolation. Nominal duration until remaining distance is less than `precision`. * @param precision The target precision of the interpolation. Defaults to 1% of distance remaining. * @see https://twitter.com/FreyaHolmer/status/1757918211679650262 + * + * @return A value between the current value and the target value. */ public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float { // var halfLife:Float = -duration / logBase(2, precision); // lerp(current, target, 1 - exp2(-elapsed / halfLife)); - return lerp(current, target, 1 - Math.pow(precision, elapsed / duration)); + if (current == target) return target; + + var result:Float = lerp(current, target, 1 - Math.pow(precision, elapsed / duration)); + + // TODO: Is there a better way to ensure a lerp which actually reaches the target? + // Research a framerate-independent PID lerp. + if (Math.abs(result - target) < (precision * target)) result = target; + + return result; } } From 1f81e9282795806a856d614595751708aea9a70c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 21 Mar 2024 00:38:52 -0400 Subject: [PATCH 16/23] Scrapped the weird FlxAltasSprite stuff and just used a tween. Also fixed some bugs with story menu --- source/funkin/graphics/FunkinSprite.hx | 12 +++- .../graphics/adobeanimate/FlxAtlasSprite.hx | 1 - source/funkin/ui/freeplay/AlbumRoll.hx | 63 ++++++++++++------- source/funkin/ui/freeplay/FreeplayState.hx | 12 +--- source/funkin/ui/story/StoryMenuState.hx | 41 ++++++------ 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index 03382f757..d703a8ac0 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -3,6 +3,7 @@ package funkin.graphics; import flixel.FlxSprite; import flixel.util.FlxColor; import flixel.graphics.FlxGraphic; +import flixel.tweens.FlxTween; /** * An FlxSprite with additional functionality. @@ -217,7 +218,7 @@ class FunkinSprite extends FlxSprite } /** - * Ensure scale is applied when cloning a sprite. + * Ensure scale is applied when cloning a sprite.R * The default `clone()` method acts kinda weird TBH. * @return A clone of this sprite. */ @@ -230,4 +231,13 @@ class FunkinSprite extends FlxSprite return result; } + + public override function destroy():Void + { + frames = null; + // Cancel all tweens so they don't continue to run on a destroyed sprite. + // This prevents crashes. + FlxTween.cancelTweensOf(this); + super.destroy(); + } } diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index ec8c8413c..c5a3a3771 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -21,7 +21,6 @@ class FlxAtlasSprite extends FlxAnimate ShowPivot: #if debug false #else false #end, Antialiasing: true, ScrollFactor: null, - OverrideGraphics: [], // Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset }; diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx index f0c759666..507e0b662 100644 --- a/source/funkin/ui/freeplay/AlbumRoll.hx +++ b/source/funkin/ui/freeplay/AlbumRoll.hx @@ -1,12 +1,15 @@ package funkin.ui.freeplay; -import flixel.graphics.FlxGraphic; +import flixel.FlxSprite; import flixel.group.FlxSpriteGroup; import flixel.util.FlxSort; +import flixel.tweens.FlxTween; +import flixel.util.FlxTimer; +import flixel.tweens.FlxEase; import funkin.data.freeplay.AlbumRegistry; -import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.FunkinSprite; import funkin.util.SortUtil; +import openfl.utils.Assets; /** * The graphic for the album roll in the FreeplayState. @@ -31,10 +34,12 @@ class AlbumRoll extends FlxSpriteGroup return value; } - var albumArt:FlxAtlasSprite; + var albumArt:FunkinSprite; var albumTitle:FunkinSprite; var difficultyStars:DifficultyStars; + var _exitMovers:Null; + var albumData:Album; public function new() @@ -42,7 +47,7 @@ class AlbumRoll extends FlxSpriteGroup super(); albumTitle = new FunkinSprite(947, 491); - albumTitle.visible = false; + albumTitle.visible = true; albumTitle.zIndex = 200; add(albumTitle); @@ -69,28 +74,32 @@ class AlbumRoll extends FlxSpriteGroup return; }; - var albumArtGraphics:Array = [null, albumData.getAlbumArtGraphic()]; - if (albumArt != null) { + FlxTween.cancelTweensOf(albumArt); albumArt.visible = false; - albumArt.anim.stop(); albumArt.destroy(); remove(albumArt); } - // I wasn't able to get replacing to work properly on an existing object, - // so I just throw the old one in the trash and make a new one. - albumArt = new FlxAtlasSprite(640, 360, Paths.animateAtlas('freeplay/albumRoll'), - { - OverrideGraphics: albumArtGraphics, - }); + // Paths.animateAtlas('freeplay/albumRoll'), + albumArt = FunkinSprite.create(1500, 360, albumData.getAlbumArtAssetKey()); + albumArt.setGraphicSize(262, 262); // Magic number for size IG albumArt.zIndex = 100; playIntro(); add(albumArt); - albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey())); + applyExitMovers(); + + if (Assets.exists(Paths.image(albumData.getAlbumTitleAssetKey()))) + { + albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey())); + } + else + { + albumTitle.visible = false; + } refresh(); } @@ -104,8 +113,19 @@ class AlbumRoll extends FlxSpriteGroup * Apply exit movers for the album roll. * @param exitMovers The exit movers to apply. */ - public function applyExitMovers(exitMovers:FreeplayState.ExitMoverData):Void + public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData):Void { + if (exitMovers == null) + { + exitMovers = _exitMovers; + } + else + { + _exitMovers = exitMovers; + } + + if (exitMovers == null) return; + exitMovers.set([albumArt], { x: FlxG.width, @@ -141,10 +161,12 @@ class AlbumRoll extends FlxSpriteGroup public function playIntro():Void { albumArt.visible = true; - albumArt.anim.play(''); - albumArt.anim.onComplete = function() { - albumArt.anim.pause(); - }; + FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.quintOut}); + + albumTitle.visible = false; + new FlxTimer().start(0.75, function(_) { + showTitle(); + }); } public function setDifficultyStars(?difficulty:Int):Void @@ -154,9 +176,6 @@ class AlbumRoll extends FlxSpriteGroup difficultyStars.difficulty = difficulty; } - /** - * Make the album title graphic visible. - */ public function showTitle():Void { albumTitle.visible = true; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 8d7ecb556..fc11eec28 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -465,7 +465,7 @@ class FreeplayState extends MusicBeatSubState albumRoll.playIntro(); - new FlxTimer().start(1, function(_) { + new FlxTimer().start(0.75, function(_) { albumRoll.showTitle(); }); @@ -861,16 +861,6 @@ class FreeplayState extends MusicBeatSubState changeDiff(1); } - // TODO: DEBUG REMOVE THIS - if (FlxG.keys.justPressed.P) - { - var newParams:FreeplayStateParams = - { - character: currentCharacter == 'bf' ? 'pico' : 'bf', - }; - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(newParams, sticker))); - } - if (controls.BACK && !typing.hasFocus) { FlxTween.globalManager.clear(); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 60a6defdd..9ce110c73 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -1,37 +1,33 @@ package funkin.ui.story; -import funkin.ui.mainmenu.MainMenuState; -import funkin.save.Save; -import funkin.save.Save.SaveScoreData; -import openfl.utils.Assets; import flixel.addons.transition.FlxTransitionableState; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.text.FlxText; -import flixel.addons.transition.FlxTransitionableState; import flixel.tweens.FlxEase; -import funkin.graphics.FunkinSprite; -import funkin.ui.MusicBeatState; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; -import funkin.data.level.LevelRegistry; import funkin.audio.FunkinSound; +import funkin.data.level.LevelRegistry; +import funkin.data.song.SongRegistry; +import funkin.graphics.FunkinSprite; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; -import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; -import funkin.ui.mainmenu.MainMenuState; import funkin.play.song.Song; -import funkin.data.song.SongData.SongMusicData; -import funkin.data.song.SongRegistry; -import funkin.util.MathUtil; +import funkin.save.Save; +import funkin.save.Save.SaveScoreData; +import funkin.ui.mainmenu.MainMenuState; +import funkin.ui.MusicBeatState; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; +import funkin.util.MathUtil; +import openfl.utils.Assets; class StoryMenuState extends MusicBeatState { - static final DEFAULT_BACKGROUND_COLOR:FlxColor = FlxColor.fromString("#F9CF51"); + static final DEFAULT_BACKGROUND_COLOR:FlxColor = FlxColor.fromString('#F9CF51'); static final BACKGROUND_HEIGHT:Int = 400; var currentDifficultyId:String = 'normal'; @@ -166,25 +162,25 @@ class StoryMenuState extends MusicBeatState updateProps(); tracklistText = new FlxText(FlxG.width * 0.05, levelBackground.x + levelBackground.height + 100, 0, "Tracks", 32); - tracklistText.setFormat("VCR OSD Mono", 32); + tracklistText.setFormat('VCR OSD Mono', 32); tracklistText.alignment = CENTER; - tracklistText.color = 0xFFe55777; + tracklistText.color = 0xFFE55777; add(tracklistText); scoreText = new FlxText(10, 10, 0, 'HIGH SCORE: 42069420'); - scoreText.setFormat("VCR OSD Mono", 32); + scoreText.setFormat('VCR OSD Mono', 32); scoreText.zIndex = 1000; add(scoreText); modeText = new FlxText(10, 10, 0, 'Base Game Levels [TAB to switch]'); - modeText.setFormat("VCR OSD Mono", 32); + modeText.setFormat('VCR OSD Mono', 32); modeText.screenCenter(X); modeText.visible = hasModdedLevels(); modeText.zIndex = 1000; add(modeText); levelTitleText = new FlxText(FlxG.width * 0.7, 10, 0, 'LEVEL 1'); - levelTitleText.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT); + levelTitleText.setFormat('VCR OSD Mono', 32, FlxColor.WHITE, RIGHT); levelTitleText.alpha = 0.7; levelTitleText.zIndex = 1000; add(levelTitleText); @@ -217,7 +213,7 @@ class StoryMenuState extends MusicBeatState #if discord_rpc // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); + DiscordClient.changePresence('In the Menus', null); #end } @@ -307,7 +303,7 @@ class StoryMenuState extends MusicBeatState changeDifficulty(0); } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { Conductor.instance.update(); @@ -552,10 +548,13 @@ class StoryMenuState extends MusicBeatState FlxTransitionableState.skipNextTransIn = false; FlxTransitionableState.skipNextTransOut = false; + var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty); + LoadingState.loadPlayState( { targetSong: targetSong, targetDifficulty: PlayStatePlaylist.campaignDifficulty, + targetVariation: targetVariation }, true); }); } From 9db9f4492452544e021832313897fef9a26da763 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 21 Mar 2024 00:38:59 -0400 Subject: [PATCH 17/23] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 4e88fb2a5..75ca95883 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4e88fb2a50b284d92404af6afc95b9840d3cda8d +Subproject commit 75ca95883342ecc43ea0c8560b49cb736fd88fb2 From 105aca4707b30f19f45cab09f732f5cf522e3b0d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 21 Mar 2024 19:44:02 -0400 Subject: [PATCH 18/23] Fix an issue where hidden difficulties could end up in the difficulty list. --- source/funkin/play/song/Song.hx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 1b7740408..42266a6ae 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -367,11 +367,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry):Array + public function listDifficulties(?variationId:String, ?variationIds:Array, showHidden:Bool = false):Array { if (variationIds == null) variationIds = []; if (variationId != null) variationIds.push(variationId); @@ -387,6 +390,15 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry Date: Thu, 21 Mar 2024 18:51:18 -0700 Subject: [PATCH 19/23] submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 26e2b2c44..907015a5c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 26e2b2c4419f6a39b4e7641f0f824117c03eb0b6 +Subproject commit 907015a5cd8617d0febccd4a5237b2a25ff1df61 From 5e0de6d1ce9dcf1a9785b9b2b23b2e73dc008a9e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 21 Mar 2024 23:57:26 -0400 Subject: [PATCH 20/23] Fix some issues with events unintentionally sharing data after being edited via the toolbox. --- source/funkin/data/song/SongData.hx | 2 +- source/funkin/ui/debug/charting/ChartEditorState.hx | 9 +++++++++ .../debug/charting/commands/SetItemSelectionCommand.hx | 7 ++++++- .../charting/components/ChartEditorEventSprite.hx | 10 ++++++++-- .../charting/toolboxes/ChartEditorEventDataToolbox.hx | 7 ++++--- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 938859ff2..26380947a 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -706,7 +706,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR this = new SongEventDataRaw(time, eventKind, value); } - public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic + public function valueAsStruct(?defaultKey:String = "key"):Dynamic { if (this.value == null) return {}; if (Std.isOfType(this.value, Array)) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index c59a5abdb..bdc0d311e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -878,6 +878,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var noteDisplayDirty:Bool = true; + var noteTooltipsDirty:Bool = true; + /** * Whether the selected charactesr have been modified and the health icons need to be updated. */ @@ -1541,6 +1543,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Make sure view is updated when the variation changes. noteDisplayDirty = true; notePreviewDirty = true; + noteTooltipsDirty = true; notePreviewViewportBoundsDirty = true; switchToCurrentInstrumental(); @@ -1562,6 +1565,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Make sure view is updated when the difficulty changes. noteDisplayDirty = true; notePreviewDirty = true; + noteTooltipsDirty = true; notePreviewViewportBoundsDirty = true; // Make sure the difficulty we selected is in the list of difficulties. @@ -3663,8 +3667,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectionSquare.width = eventSprite.width; selectionSquare.height = eventSprite.height; } + + // Additional cleanup on notes. + if (noteTooltipsDirty) eventSprite.updateTooltipText(); } + noteTooltipsDirty = false; + // Sort the notes DESCENDING. This keeps the sustain behind the associated note. renderedNotes.sort(FlxSort.byY, FlxSort.DESCENDING); // TODO: .group.insertionSort() diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 46fcca87c..73cf80fa0 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -51,7 +51,12 @@ class SetItemSelectionCommand implements ChartEditorCommand } var eventData = eventSelected.valueAsStruct(defaultKey); - state.eventDataToPlace = eventData; + var eventDataClone = Reflect.copy(eventData); + + if (eventDataClone != null) + { + state.eventDataToPlace = eventDataClone; + } state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index f680095d7..c996079bc 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -164,8 +164,7 @@ class ChartEditorEventSprite extends FlxSprite this.eventData = value; // Update the position to match the note data. updateEventPosition(); - // Update the tooltip text. - this.tooltip.tipData = {text: this.eventData.buildTooltip()}; + updateTooltipText(); return this.eventData; } } @@ -188,6 +187,13 @@ class ChartEditorEventSprite extends FlxSprite this.updateTooltipPosition(); } + public function updateTooltipText():Void + { + if (this.eventData == null) return; + if (this.isGhost) return; + this.tooltip.tipData = {text: this.eventData.buildTooltip()}; + } + public function updateTooltipPosition():Void { // No tooltip for ghost sprites. diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 50b341272..f0949846d 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -258,14 +258,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox // Edit the event data of any existing events. if (!_initializing && chartEditorState.currentEventSelection.length > 0) { - for (event in chartEditorState.currentEventSelection) + for (songEvent in chartEditorState.currentEventSelection) { - event.eventKind = chartEditorState.eventKindToPlace; - event.value = chartEditorState.eventDataToPlace; + songEvent.eventKind = chartEditorState.eventKindToPlace; + songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); } chartEditorState.saveDataDirty = true; chartEditorState.noteDisplayDirty = true; chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; } } } From 433d4968da1245dfc6e5a132a1bcb41ed8b7d7dc Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 21 Mar 2024 23:32:07 -0700 Subject: [PATCH 21/23] change to elasticOut --- source/funkin/ui/freeplay/AlbumRoll.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx index 507e0b662..05bf01b76 100644 --- a/source/funkin/ui/freeplay/AlbumRoll.hx +++ b/source/funkin/ui/freeplay/AlbumRoll.hx @@ -161,7 +161,7 @@ class AlbumRoll extends FlxSpriteGroup public function playIntro():Void { albumArt.visible = true; - FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.quintOut}); + FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut}); albumTitle.visible = false; new FlxTimer().start(0.75, function(_) { From 80c938af4f704074a5e9c6a61f1410a772b7b6b6 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 21 Mar 2024 23:34:03 -0700 Subject: [PATCH 22/23] fix for the album roll to come in only once bf hand hits --- source/funkin/ui/freeplay/AlbumRoll.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx index 05bf01b76..a1e63c9a1 100644 --- a/source/funkin/ui/freeplay/AlbumRoll.hx +++ b/source/funkin/ui/freeplay/AlbumRoll.hx @@ -87,7 +87,7 @@ class AlbumRoll extends FlxSpriteGroup albumArt.setGraphicSize(262, 262); // Magic number for size IG albumArt.zIndex = 100; - playIntro(); + // playIntro(); add(albumArt); applyExitMovers(); From 5b5eb6f109cf17bb2dec799302b9bd30bfb42b4f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 21 Mar 2024 23:35:49 -0700 Subject: [PATCH 23/23] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 75ca95883..203bb6024 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 75ca95883342ecc43ea0c8560b49cb736fd88fb2 +Subproject commit 203bb60249e0a419d473eb6dc1763e62e29ee7fd