From 9209bac02c1f89431876dcda56eb068c6cae381c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 14 Feb 2024 03:27:34 -0500 Subject: [PATCH 001/172] Port improved AtlasSprite from char-select-rebase --- .../graphics/adobeanimate/FlxAtlasSprite.hx | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index ae7a5708c..fe024e2f5 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -18,7 +18,7 @@ class FlxAtlasSprite extends FlxAnimate // ?OnComplete:Void -> Void, ShowPivot: #if debug false #else false #end, Antialiasing: true, - ScrollFactor: new FlxPoint(1, 1), + ScrollFactor: null, // Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset }; @@ -55,8 +55,8 @@ class FlxAtlasSprite extends FlxAnimate */ public function listAnimations():Array { - // return this.anim.getFrameLabels(); - return [""]; + return this.anim.getFrameLabels(); + // return [""]; } /** @@ -82,8 +82,10 @@ class FlxAtlasSprite extends FlxAnimate * @param restart Whether to restart the animation if it is already playing. * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing */ - public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false):Void + public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void { + if (loop == null) loop = false; + // Skip if not allowed to play animations. if ((!canPlayOtherAnims && !ignoreOther)) return; @@ -110,15 +112,14 @@ class FlxAtlasSprite extends FlxAnimate return; } - // Stop the current animation if it is playing. - // This includes removing existing frame callbacks. - if (this.currentAnimation != null) this.stopAnimation(); - - // Add a callback to ensure `onAnimationFinish` is dispatched. - addFrameCallback(getNextFrameLabel(id), function() { - trace('Animation finished: ' + id); - onAnimationFinish.dispatch(id); - }); + anim.callback = function(_, frame:Int) { + if (frame == (anim.getFrameLabel(id).duration - 1) + anim.getFrameLabel(id).index) + { + if (loop) playAnimation(id, true, false, true); + else + onAnimationFinish.dispatch(id); + } + }; // Prevent other animations from playing if `ignoreOther` is true. if (ignoreOther) canPlayOtherAnims = false; @@ -128,6 +129,11 @@ class FlxAtlasSprite extends FlxAnimate this.currentAnimation = id; } + override public function update(elapsed:Float) + { + super.update(elapsed); + } + /** * Stops the current animation. */ From c896300b63627625ed14017d83ff5475f07d074a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 17:23:43 -0500 Subject: [PATCH 002/172] NoteData stringifies nicer now. --- source/funkin/data/song/SongData.hx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 73ecbce14..8a07f9fb1 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1016,6 +1016,12 @@ class SongNoteDataRaw implements ICloneable { return new SongNoteDataRaw(this.time, this.data, this.length, this.kind); } + + public function toString():String + { + return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' + + (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); + } } /** From 04b73dac9f4d1cfb034064f7978743b5380105fd Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 17:23:57 -0500 Subject: [PATCH 003/172] Always force debug version in VSCode. --- .vscode/settings.json | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d1f488f7..8455fde93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -93,57 +93,61 @@ { "label": "Windows / Debug", "target": "windows", - "args": ["-debug"] + "args": ["-debug", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (FlxAnimate Test)", "target": "windows", - "args": ["-debug", "-DANIMATE"] + "args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Straight to Freeplay)", "target": "windows", - "args": ["-debug", "-DFREEPLAY"] + "args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Straight to Play - Bopeebo Normal)", "target": "windows", - "args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"] + "args": [ + "-debug", + "-DSONG=bopeebo -DDIFFICULTY=normal", + "-DFORCE_DEBUG_VERSION" + ] }, { "label": "Windows / Debug (Conversation Test)", "target": "windows", - "args": ["-debug", "-DDIALOGUE"] + "args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Straight to Chart Editor)", "target": "windows", - "args": ["-debug", "-DCHARTING"] + "args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Straight to Animation Editor)", "target": "windows", - "args": ["-debug", "-DANIMDEBUG"] + "args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Latency Test)", "target": "windows", - "args": ["-debug", "-DLATENCY"] + "args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"] }, { "label": "Windows / Debug (Waveform Test)", "target": "windows", - "args": ["-debug", "-DWAVEFORM"] + "args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"] }, { "label": "HTML5 / Debug", "target": "html5", - "args": ["-debug"] + "args": ["-debug", "-DFORCE_DEBUG_VERSION"] }, { "label": "HTML5 / Debug (Watch)", "target": "html5", - "args": ["-debug", "-watch"] + "args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"] } ], "cmake.configureOnOpen": false, From 5ec093926335400f502ced25733c71108e317fd3 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 17:25:28 -0500 Subject: [PATCH 004/172] Bunch of changes to NoteScriptEvent and death logic --- source/funkin/modding/events/ScriptEvent.hx | 7 ++++ source/funkin/play/GameOverSubState.hx | 24 ++++++++++--- source/funkin/play/PlayState.hx | 36 ++++++++++--------- source/funkin/play/character/CharacterData.hx | 8 ++++- source/funkin/play/stage/Stage.hx | 3 ++ 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 18f934aee..68265a103 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -106,12 +106,19 @@ class NoteScriptEvent extends ScriptEvent */ public var playSound(default, default):Bool; + /** + * A multiplier to the health gained or lost from this note. + * This affects both hits and misses. Remember that max health is 2.00. + */ + public var healthMulti:Float; + public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void { super(type, cancelable); this.note = note; this.comboCount = comboCount; this.playSound = true; + this.healthMulti = 1.0; } public override function toString():String diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 74b39417e..62c3409b7 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -4,16 +4,17 @@ import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; import flixel.sound.FlxSound; -import funkin.ui.story.StoryMenuState; import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.graphics.FunkinSprite; -import funkin.ui.MusicBeatSubState; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; +import funkin.play.character.BaseCharacter; import funkin.play.PlayState; import funkin.ui.freeplay.FreeplayState; -import funkin.play.character.BaseCharacter; +import funkin.ui.MusicBeatSubState; +import funkin.ui.story.StoryMenuState; +import openfl.utils.Assets; /** * A substate which renders over the PlayState when the player dies. @@ -148,6 +149,12 @@ class GameOverSubState extends MusicBeatSubState Conductor.instance.update(0); } + public function resetCameraZoom():Void + { + // Apply camera zoom level from stage data. + FlxG.camera.zoom = PlayState?.instance?.currentStage?.camZoom ?? 1.0; + } + var hasStartedAnimation:Bool = false; override function update(elapsed:Float) @@ -295,7 +302,7 @@ class GameOverSubState extends MusicBeatSubState * Starts the death music at the appropriate volume. * @param startingVolume */ - function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void + public function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void { var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix); if (isEnding) @@ -320,7 +327,14 @@ class GameOverSubState extends MusicBeatSubState public static function playBlueBalledSFX() { blueballed = true; - FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); + if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix))) + { + FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); + } + else + { + FlxG.log.error('Missing blue ball sound effect: ' + Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); + } } var playingJeffQuote:Bool = false; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index be4fab254..4b9349648 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -903,6 +903,7 @@ class PlayState extends MusicBeatSubState { FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } + FlxG.watch.addQuick('health', health); // TODO: Add a song event for Handle GF dance speed. @@ -1390,8 +1391,7 @@ class PlayState extends MusicBeatSubState var event:ScriptEvent = new ScriptEvent(CREATE, false); ScriptEventDispatcher.callEvent(currentStage, event); - // Apply camera zoom level from stage data. - defaultCameraZoom = currentStage.camZoom; + resetCameraZoom(); // Add the stage to the scene. this.add(currentStage); @@ -1407,6 +1407,12 @@ class PlayState extends MusicBeatSubState } } + public function resetCameraZoom():Void + { + // Apply camera zoom level from stage data. + defaultCameraZoom = currentStage.camZoom; + } + /** * Generates the character sprites and adds them to the stage. */ @@ -1960,7 +1966,7 @@ class PlayState extends MusicBeatSubState // Judge the miss. // NOTE: This is what handles the scoring. trace('Missed note! ${note.noteData}'); - onNoteMiss(note); + onNoteMiss(note, event.playSound, event.healthMulti); note.handledMiss = true; } @@ -2111,7 +2117,7 @@ class PlayState extends MusicBeatSubState // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - popUpScore(note, input); + popUpScore(note, input, event.healthMulti); if (note.isHoldNote && note.holdNoteSprite != null) { @@ -2125,15 +2131,11 @@ class PlayState extends MusicBeatSubState * Called when a note leaves the screen and is considered missed by the player. * @param note */ - function onNoteMiss(note:NoteSprite):Void + function onNoteMiss(note:NoteSprite, playSound:Bool = false, healthLossMulti:Float = 1.0):Void { - // a MISS is when you let a note scroll past you!! - var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true); - dispatchEvent(event); - // Calling event.cancelEvent() skips all the other logic! Neat! - if (event.eventCanceled) return; + // If we are here, we already CALLED the onNoteMiss script hook! - health -= Constants.HEALTH_MISS_PENALTY; + health -= Constants.HEALTH_MISS_PENALTY * healthLossMulti; songScore -= 10; if (!isPracticeMode) @@ -2183,7 +2185,7 @@ class PlayState extends MusicBeatSubState Highscore.tallies.combo = comboPopUps.displayCombo(0); } - if (event.playSound) + if (playSound) { vocals.playerVolume = 0; FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); @@ -2310,7 +2312,7 @@ class PlayState extends MusicBeatSubState /** * Handles health, score, and rating popups when a note is hit. */ - function popUpScore(daNote:NoteSprite, input:PreciseInputEvent):Void + function popUpScore(daNote:NoteSprite, input:PreciseInputEvent, healthGainMulti:Float = 1.0):Void { vocals.playerVolume = 1; @@ -2341,19 +2343,19 @@ class PlayState extends MusicBeatSubState { case 'sick': Highscore.tallies.sick += 1; - health += Constants.HEALTH_SICK_BONUS; + health += Constants.HEALTH_SICK_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; case 'good': Highscore.tallies.good += 1; - health += Constants.HEALTH_GOOD_BONUS; + health += Constants.HEALTH_GOOD_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; case 'bad': Highscore.tallies.bad += 1; - health += Constants.HEALTH_BAD_BONUS; + health += Constants.HEALTH_BAD_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; case 'shit': Highscore.tallies.shit += 1; - health += Constants.HEALTH_SHIT_BONUS; + health += Constants.HEALTH_SHIT_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 69e3ca48e..f3c7d7613 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -305,9 +305,15 @@ class CharacterDataParser icon = "darnell"; case "senpai-angry": icon = "senpai"; + case "tankman" | "tankman-atlas": + icon = "tankmen"; } - return Paths.image("freeplay/icons/" + icon + "pixel"); + var path = Paths.image("freeplay/icons/" + icon + "pixel"); + if (Assets.exists(path)) return path; + + // TODO: Hardcode some additional behavior or a fallback. + return null; } /** diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index af5765b25..8b47eff2b 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -397,15 +397,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements this.characters.set('bf', character); charData = _data.characters.bf; character.flipX = !character.getDataFlipX(); + character.name = 'bf'; character.initHealthIcon(false); case GF: this.characters.set('gf', character); charData = _data.characters.gf; character.flipX = character.getDataFlipX(); + character.name = 'gf'; case DAD: this.characters.set('dad', character); charData = _data.characters.dad; character.flipX = character.getDataFlipX(); + character.name = 'dad'; character.initHealthIcon(true); default: this.characters.set(character.characterId, character); From 72d623bdf6179b4ffc9eea58ec6253531b183eac Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 17:26:02 -0500 Subject: [PATCH 005/172] Don't prettier format FlxAnimate JSONs --- .prettierignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..c92ea0bae --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +# Ignore artifacts +export + +# Ignore all asset files (including FlxAnimate JSONs) +assets + +# Don't ignore data files +!assets/preload/data From a9bcc492bc6eeb58208bd2597f321cd6aec27460 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 17:26:10 -0500 Subject: [PATCH 006/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 1f00d2413..6c657fc8f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1f00d24134231433180affecc67a617d54169ffa +Subproject commit 6c657fc8f6537500a98ebcd04bb7ce3f9b69a322 From d6b3e2a9cf4aab093a129aef13ebd93294d6f929 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Fri, 16 Feb 2024 00:07:16 -0800 Subject: [PATCH 007/172] Fix `FunkinSound` not resuming after focus `FunkingSound.onFocus` was checking `_shouldPlay` before resuming, but this would always be false, causing the sound to not resume when tabbing out and back into the game. --- source/funkin/audio/FunkinSound.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index e7ce68d08..c1d51800b 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -186,7 +186,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocus():Void { - if (!_alreadyPaused && this._shouldPlay) + if (!_alreadyPaused) { resume(); } From 77ff261be16ab6fecb5c4e5e02db58becf5dffa9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 16 Feb 2024 13:08:34 -0500 Subject: [PATCH 008/172] Make functions non-inline so they work on HScript --- source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index fe024e2f5..2329a2791 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -55,6 +55,7 @@ class FlxAtlasSprite extends FlxAnimate */ public function listAnimations():Array { + if (this.anim == null) return []; return this.anim.getFrameLabels(); // return [""]; } @@ -152,22 +153,22 @@ class FlxAtlasSprite extends FlxAnimate frameLabel.add(callback); } - inline function goToFrameLabel(label:String):Void + function goToFrameLabel(label:String):Void { this.anim.goToFrameLabel(label); } - inline function getNextFrameLabel(label:String):String + function getNextFrameLabel(label:String):String { return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length]; } - inline function getLabelIndex(label:String):Int + function getLabelIndex(label:String):Int { return listAnimations().indexOf(label); } - inline function goToFrameIndex(index:Int):Void + function goToFrameIndex(index:Int):Void { this.anim.curFrame = index; } From 8a9a7f3b97cd2afc84ce2898f4a8cb8a986e660f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 17 Feb 2024 02:13:11 -0500 Subject: [PATCH 009/172] Additional chart editor fixes. --- source/funkin/audio/waveform/WaveformData.hx | 2 ++ source/funkin/data/song/SongData.hx | 6 ++++-- .../debug/charting/commands/SelectItemsCommand.hx | 4 ++-- .../charting/commands/SetItemSelectionCommand.hx | 4 ++-- .../toolboxes/ChartEditorFreeplayToolbox.hx | 6 +++--- .../charting/toolboxes/ChartEditorOffsetsToolbox.hx | 13 +++++-------- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/source/funkin/audio/waveform/WaveformData.hx b/source/funkin/audio/waveform/WaveformData.hx index b82d141e7..1f649b472 100644 --- a/source/funkin/audio/waveform/WaveformData.hx +++ b/source/funkin/audio/waveform/WaveformData.hx @@ -187,6 +187,8 @@ class WaveformData */ public function merge(that:WaveformData):WaveformData { + if (that == null) return this.clone(); + var result = this.clone([]); for (channelIndex in 0...this.channels) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 4deba7088..cc568ec66 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -110,7 +110,8 @@ class SongMetadata implements ICloneable */ public function serialize(pretty:Bool = true):String { - var writer = new json2object.JsonWriter(); + var ignoreNullOptionals = true; + var writer = new json2object.JsonWriter(ignoreNullOptionals); // I believe @:jignored should be iggnored by the writer? // var output = this.clone(); // output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer. @@ -597,7 +598,8 @@ class SongChartData implements ICloneable */ public function serialize(pretty:Bool = true):String { - var writer = new json2object.JsonWriter(); + var ignoreNullOptionals = true; + var writer = new json2object.JsonWriter(ignoreNullOptionals); return writer.write(this, pretty ? ' ' : null); } diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index 88f73cfed..891ac9ebd 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -34,7 +34,7 @@ class SelectItemsCommand implements ChartEditorCommand } // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event. - if (this.notes.length == 0 && this.events.length >= 1) + if (this.notes.length == 0 && this.events.length == 1) { var eventSelected = this.events[0]; @@ -60,7 +60,7 @@ class SelectItemsCommand implements ChartEditorCommand } // If we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note. - if (this.events.length == 0 && this.notes.length >= 1) + if (this.events.length == 0 && this.notes.length == 1) { var noteSelected = this.notes[0]; diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 5cc89e137..0b540dbeb 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -31,7 +31,7 @@ class SetItemSelectionCommand implements ChartEditorCommand state.currentEventSelection = events; // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event. - if (this.notes.length == 0 && this.events.length >= 1) + if (this.notes.length == 0 && this.events.length == 1) { var eventSelected = this.events[0]; @@ -57,7 +57,7 @@ class SetItemSelectionCommand implements ChartEditorCommand } // IF we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note. - if (this.events.length == 0 && this.notes.length >= 1) + if (this.events.length == 0 && this.notes.length == 1) { var noteSelected = this.notes[0]; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx index c384e7a6d..1432c9205 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx @@ -289,10 +289,10 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox // Build player waveform. // waveformMusic.waveform.forceUpdate = true; var perfStart = haxe.Timer.stamp(); - var waveformData1 = playerVoice.waveformData; - var waveformData2 = opponentVoice?.waveformData ?? playerVoice.waveformData; // this null check is for songs that only have 1 vocals file! + var waveformData1 = playerVoice?.waveformData; + var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file! var waveformData3 = chartEditorState.audioInstTrack.waveformData; - var waveformData = waveformData1.merge(waveformData2).merge(waveformData3); + var waveformData = waveformData3.merge(waveformData1).merge(waveformData2); trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds'); waveformMusic.waveform.waveformData = waveformData; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx index fd9209294..af1d75444 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx @@ -270,24 +270,21 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox // Build player waveform. // waveformPlayer.waveform.forceUpdate = true; - waveformPlayer.waveform.waveformData = playerVoice.waveformData; + waveformPlayer.waveform.waveformData = playerVoice?.waveformData; // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it. - waveformPlayer.waveform.duration = playerVoice.length / Constants.MS_PER_SEC; + waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000) / Constants.MS_PER_SEC; // Build opponent waveform. // waveformOpponent.waveform.forceUpdate = true; // note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor // so we null check - if (opponentVoice != null) - { - waveformOpponent.waveform.waveformData = opponentVoice.waveformData; - waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC; - } + waveformOpponent.waveform.waveformData = opponentVoice?.waveformData; + waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000) / Constants.MS_PER_SEC; // Build instrumental waveform. // waveformInstrumental.waveform.forceUpdate = true; waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData; - waveformInstrumental.waveform.duration = instTrack.length / Constants.MS_PER_SEC; + waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000) / Constants.MS_PER_SEC; addOffsetsToAudioPreview(); } From d535a3f5475c6a31e774287ee22d09aa26fa035d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 17 Feb 2024 02:13:19 -0500 Subject: [PATCH 010/172] Update haxeui --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 26cb0d0b4..700b42dfe 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "8a7846b", + "ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "e9f880522e27134b29df4067f82df7d7e5237b70", + "ref": "63a906a6148958dbfde8c7b48d90b0693767fd95", "url": "https://github.com/haxeui/haxeui-flixel" }, { From da7cb559bb7a55f46c5c8643ac24ad79ac2b7152 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 17 Feb 2024 02:13:29 -0500 Subject: [PATCH 011/172] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 6c657fc8f..75ac8ec25 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6c657fc8f6537500a98ebcd04bb7ce3f9b69a322 +Subproject commit 75ac8ec2564c9a56e8282b0853091ecd8b4f2dfd From d020697bc70aa090607acb35046db8cf4ea836f4 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 17 Feb 2024 05:57:49 -0500 Subject: [PATCH 012/172] asset submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 75ac8ec25..6e7942c43 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 75ac8ec2564c9a56e8282b0853091ecd8b4f2dfd +Subproject commit 6e7942c4376bbfaf3b652f7f1aa1a32a1a0d24a5 From 44623071cd62821a8d2708b1664a7b4ba84c1015 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 18 Feb 2024 03:02:36 -0500 Subject: [PATCH 013/172] Redo event stuff for abot and game over audio logic --- source/funkin/audio/FunkinSound.hx | 1 + source/funkin/data/event/SongEventRegistry.hx | 6 +- source/funkin/data/song/SongData.hx | 28 +++---- source/funkin/data/song/SongDataUtils.hx | 2 +- source/funkin/modding/events/ScriptEvent.hx | 8 +- source/funkin/play/GameOverSubState.hx | 83 ++++++++++++++++--- .../ui/debug/charting/ChartEditorState.hx | 6 +- .../charting/commands/SelectItemsCommand.hx | 4 +- .../commands/SetItemSelectionCommand.hx | 4 +- .../components/ChartEditorEventSprite.hx | 4 +- .../toolboxes/ChartEditorEventDataToolbox.hx | 4 +- 11 files changed, 104 insertions(+), 46 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index e7ce68d08..0e6bd6893 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -128,6 +128,7 @@ class FunkinSound extends FlxSound implements ICloneable function fixMaxVolume():Void { + return; #if lime_openal // This code is pretty fragile, it reaches through 5 layers of private access. @:privateAccess diff --git a/source/funkin/data/event/SongEventRegistry.hx b/source/funkin/data/event/SongEventRegistry.hx index dc5589813..8732e3b98 100644 --- a/source/funkin/data/event/SongEventRegistry.hx +++ b/source/funkin/data/event/SongEventRegistry.hx @@ -108,8 +108,8 @@ class SongEventRegistry public static function handleEvent(data:SongEventData):Void { - var eventType:String = data.event; - var eventHandler:SongEvent = eventCache.get(eventType); + var eventKind:String = data.eventKind; + var eventHandler:SongEvent = eventCache.get(eventKind); if (eventHandler != null) { @@ -117,7 +117,7 @@ class SongEventRegistry } else { - trace('WARNING: No event handler for event with id: ${eventType}'); + trace('WARNING: No event handler for event with kind: ${eventKind}'); } data.activated = true; diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index cc568ec66..24febea86 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -650,7 +650,7 @@ class SongEventDataRaw implements ICloneable * Custom events can be added by scripts with the `ScriptedSongEvent` class. */ @:alias("e") - public var event:String; + public var eventKind:String; /** * The data for the event. @@ -670,10 +670,10 @@ class SongEventDataRaw implements ICloneable @:jignored public var activated:Bool = false; - public function new(time:Float, event:String, value:Dynamic = null) + public function new(time:Float, eventKind:String, value:Dynamic = null) { this.time = time; - this.event = event; + this.eventKind = eventKind; this.value = value; } @@ -689,19 +689,19 @@ class SongEventDataRaw implements ICloneable public function clone():SongEventDataRaw { - return new SongEventDataRaw(this.time, this.event, this.value); + return new SongEventDataRaw(this.time, this.eventKind, this.value); } } /** * Wrap SongEventData in an abstract so we can overload operators. */ -@:forward(time, event, value, activated, getStepTime, clone) +@:forward(time, eventKind, value, activated, getStepTime, clone) abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw { - public function new(time:Float, event:String, value:Dynamic = null) + public function new(time:Float, eventKind:String, value:Dynamic = null) { - this = new SongEventDataRaw(time, event, value); + this = new SongEventDataRaw(time, eventKind, value); } public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic @@ -728,12 +728,12 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR public inline function getHandler():Null { - return SongEventRegistry.getEvent(this.event); + return SongEventRegistry.getEvent(this.eventKind); } public inline function getSchema():Null { - return SongEventRegistry.getEventSchema(this.event); + return SongEventRegistry.getEventSchema(this.eventKind); } public inline function getDynamic(key:String):Null @@ -786,7 +786,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR var eventHandler = getHandler(); var eventSchema = getSchema(); - if (eventSchema == null) return 'Unknown Event: ${this.event}'; + if (eventSchema == null) return 'Unknown Event: ${this.eventKind}'; var result = '${eventHandler.getTitle()}'; @@ -811,19 +811,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR public function clone():SongEventData { - return new SongEventData(this.time, this.event, this.value); + return new SongEventData(this.time, this.eventKind, this.value); } @:op(A == B) public function op_equals(other:SongEventData):Bool { - return this.time == other.time && this.event == other.event && this.value == other.value; + return this.time == other.time && this.eventKind == other.eventKind && this.value == other.value; } @:op(A != B) public function op_notEquals(other:SongEventData):Bool { - return this.time != other.time || this.event != other.event || this.value != other.value; + return this.time != other.time || this.eventKind != other.eventKind || this.value != other.value; } @:op(A > B) @@ -855,7 +855,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR */ public function toString():String { - return 'SongEventData(${this.time}ms, ${this.event}: ${this.value})'; + return 'SongEventData(${this.time}ms, ${this.eventKind}: ${this.value})'; } } diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 7f3b01eb4..c93c5379a 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -47,7 +47,7 @@ class SongDataUtils public static function offsetSongEventData(events:Array, offset:Float):Array { return events.map(function(event:SongEventData):SongEventData { - return new SongEventData(event.time + offset, event.event, event.value); + return new SongEventData(event.time + offset, event.eventKind, event.value); }); } diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 68265a103..5d522e3ae 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -189,17 +189,17 @@ class SongEventScriptEvent extends ScriptEvent * The note associated with this event. * You cannot replace it, but you can edit it. */ - public var event(default, null):funkin.data.song.SongData.SongEventData; + public var eventData(default, null):funkin.data.song.SongData.SongEventData; - public function new(event:funkin.data.song.SongData.SongEventData):Void + public function new(eventData:funkin.data.song.SongData.SongEventData):Void { super(SONG_EVENT, true); - this.event = event; + this.eventData = eventData; } public override function toString():String { - return 'SongEventScriptEvent(event=' + event + ')'; + return 'SongEventScriptEvent(event=' + eventData + ')'; } } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 62c3409b7..b7e92d10f 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -4,6 +4,7 @@ import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; import flixel.sound.FlxSound; +import funkin.audio.FunkinSound; import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.graphics.FunkinSprite; @@ -64,7 +65,7 @@ class GameOverSubState extends MusicBeatSubState /** * The music playing in the background of the state. */ - var gameOverMusic:FlxSound = new FlxSound(); + var gameOverMusic:Null = null; /** * Whether the player has confirmed and prepared to restart the level. @@ -72,6 +73,11 @@ class GameOverSubState extends MusicBeatSubState */ var isEnding:Bool = false; + /** + * Whether the death music is on its first loop. + */ + var isStarting:Bool = true; + var isChartingMode:Bool = false; var transparent:Bool; @@ -141,10 +147,6 @@ class GameOverSubState extends MusicBeatSubState // Set up the audio // - // Prepare the game over music. - FlxG.sound.list.add(gameOverMusic); - gameOverMusic.stop(); - // The conductor now represents the BPM of the game over music. Conductor.instance.update(0); } @@ -223,7 +225,7 @@ class GameOverSubState extends MusicBeatSubState } } - if (gameOverMusic.playing) + if (gameOverMusic != null && gameOverMusic.playing) { // Match the conductor to the music. // This enables the stepHit and beatHit events. @@ -298,24 +300,71 @@ class GameOverSubState extends MusicBeatSubState ScriptEventDispatcher.callEvent(boyfriend, event); } + /** + * Rather than hardcoding stuff, we look for the presence of a music file + * with the given suffix, and strip it down until we find one that's valid. + */ + function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null + { + var basePath = 'gameplay/gameover/gameOver'; + if (starting) basePath += 'Start'; + else if (ending) basePath += 'End'; + + var musicPath = Paths.music(basePath + suffix); + while (!Assets.exists(musicPath) && suffix.length > 0) + { + suffix = suffix.split('-').slice(0, -1).join('-'); + musicPath = Paths.music(basePath + suffix); + } + if (!Assets.exists(musicPath)) return null; + trace('Resolved music path: ' + musicPath); + return musicPath; + } + /** * Starts the death music at the appropriate volume. * @param startingVolume */ - public function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void + public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void { - var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix); - if (isEnding) + var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding); + var onComplete = null; + if (isStarting) { - musicPath = Paths.music('gameplay/gameover/gameOverEnd' + musicSuffix); + if (musicPath == null) + { + isStarting = false; + musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding); + } + else + { + isStarting = false; + onComplete = function() { + // We need to force to ensure that the non-starting music plays. + startDeathMusic(1.0, true); + }; + } } - if (!gameOverMusic.playing || force) + + if (musicPath == null) { - gameOverMusic.loadEmbedded(musicPath); + trace('Could not find game over music!'); + return; + } + else if (gameOverMusic == null || !gameOverMusic.playing || force) + { + if (gameOverMusic != null) gameOverMusic.stop(); + gameOverMusic = FunkinSound.load(musicPath); gameOverMusic.volume = startingVolume; - gameOverMusic.looped = !isEnding; + gameOverMusic.looped = !(isEnding || isStarting); + gameOverMusic.onComplete = onComplete; gameOverMusic.play(); } + else + { + @:privateAccess + trace('Music already playing! ${gameOverMusic?._label}'); + } } static var blueballed:Bool = false; @@ -358,6 +407,14 @@ class GameOverSubState extends MusicBeatSubState }); } + public override function destroy() + { + super.destroy(); + if (gameOverMusic != null) gameOverMusic.stop(); + gameOverMusic = null; + instance = null; + } + public override function toString():String { return "GameOverSubState"; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 48a6e70c9..e9748a4ba 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3417,7 +3417,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Update the event sprite's position. eventSprite.updateEventPosition(renderedEvents); // Update the sprite's graphic. TODO: Is this inefficient? - eventSprite.playAnimation(eventSprite.eventData.event); + eventSprite.playAnimation(eventSprite.eventData.eventKind); } else { @@ -4678,9 +4678,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null); - if (eventKindToPlace != eventData.event) + if (eventKindToPlace != eventData.eventKind) { - eventData.event = eventKindToPlace; + eventData.eventKind = eventKindToPlace; } eventData.time = cursorSnappedMs; diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index 891ac9ebd..423295f1a 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -38,7 +38,7 @@ class SelectItemsCommand implements ChartEditorCommand { var eventSelected = this.events[0]; - state.eventKindToPlace = eventSelected.event; + state.eventKindToPlace = eventSelected.eventKind; // This code is here to parse event data that's not built as a struct for some reason. // TODO: Clean this up or get rid of it. @@ -46,7 +46,7 @@ class SelectItemsCommand implements ChartEditorCommand var defaultKey = null; if (eventSchema == null) { - trace('[WARNING] Event schema not found for event ${eventSelected.event}.'); + trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.'); } else { diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 0b540dbeb..46fcca87c 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -35,7 +35,7 @@ class SetItemSelectionCommand implements ChartEditorCommand { var eventSelected = this.events[0]; - state.eventKindToPlace = eventSelected.event; + state.eventKindToPlace = eventSelected.eventKind; // This code is here to parse event data that's not built as a struct for some reason. // TODO: Clean this up or get rid of it. @@ -43,7 +43,7 @@ class SetItemSelectionCommand implements ChartEditorCommand var defaultKey = null; if (eventSchema == null) { - trace('[WARNING] Event schema not found for event ${eventSelected.event}.'); + trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.'); } else { diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index e3dae37cf..f680095d7 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -133,7 +133,7 @@ class ChartEditorEventSprite extends FlxSprite public function playAnimation(?name:String):Void { - if (name == null) name = eventData?.event ?? DEFAULT_EVENT; + if (name == null) name = eventData?.eventKind ?? DEFAULT_EVENT; var correctedName = correctAnimationName(name); this.animation.play(correctedName); @@ -160,7 +160,7 @@ class ChartEditorEventSprite extends FlxSprite else { this.visible = true; - playAnimation(value.event); + playAnimation(value.eventKind); this.eventData = value; // Update the position to match the note data. updateEventPosition(); diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 7b163ad3d..ec46e1f85 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -90,7 +90,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox // Edit the event data of any selected events. for (event in chartEditorState.currentEventSelection) { - event.event = chartEditorState.eventKindToPlace; + event.eventKind = chartEditorState.eventKindToPlace; event.value = chartEditorState.eventDataToPlace; } chartEditorState.saveDataDirty = true; @@ -255,7 +255,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox { for (event in chartEditorState.currentEventSelection) { - event.event = chartEditorState.eventKindToPlace; + event.eventKind = chartEditorState.eventKindToPlace; event.value = chartEditorState.eventDataToPlace; } chartEditorState.saveDataDirty = true; From dd0632d55e2ce61d3e40d7830e89eec99ea00cc1 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 19 Feb 2024 16:38:40 -0700 Subject: [PATCH 014/172] Send assets submodule to the woods --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 75ac8ec25..e76470d66 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 75ac8ec2564c9a56e8282b0853091ecd8b4f2dfd +Subproject commit e76470d66794bf3796f7ace84f2121b698e37a30 From 7d7cf32f447aab78621e874d9facf0c15ee3f0ce Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 19 Feb 2024 18:18:32 -0700 Subject: [PATCH 015/172] Chart editor grid now updates when changing the song BPM. HItsound volumes converted to two separate sliders. --- source/funkin/save/Save.hx | 71 +++++---------- .../ui/debug/charting/ChartEditorState.hx | 88 +++++++++---------- .../commands/ChangeStartingBPMCommand.hx | 4 + .../components/ChartEditorMeasureTicks.hx | 8 +- 4 files changed, 76 insertions(+), 95 deletions(-) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 5bbded231..ff5e5bfb8 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -106,8 +106,8 @@ abstract Save(RawSaveData) playtestStartTime: false, downscroll: false, metronomeVolume: 1.0, - hitsoundsEnabledPlayer: true, - hitsoundsEnabledOpponent: true, + hitsoundVolumePlayer: 1.0, + hitsoundVolumeOpponent: 1.0, themeMusic: true }, }; @@ -300,55 +300,38 @@ abstract Save(RawSaveData) return this.optionsChartEditor.metronomeVolume; } - public var chartEditorHitsoundVolume(get, set):Float; + public var chartEditorHitsoundVolumePlayer(get, set):Float; - function get_chartEditorHitsoundVolume():Float + function get_chartEditorHitsoundVolumePlayer():Float { - if (this.optionsChartEditor.hitsoundVolume == null) this.optionsChartEditor.hitsoundVolume = 1.0; + if (this.optionsChartEditor.hitsoundVolumePlayer == null) this.optionsChartEditor.hitsoundVolumePlayer = 1.0; - return this.optionsChartEditor.hitsoundVolume; + return this.optionsChartEditor.hitsoundVolumePlayer; } - function set_chartEditorHitsoundVolume(value:Float):Float + function set_chartEditorHitsoundVolumePlayer(value:Float):Float { // Set and apply. - this.optionsChartEditor.hitsoundVolume = value; + this.optionsChartEditor.hitsoundVolumePlayer = value; flush(); - return this.optionsChartEditor.hitsoundVolume; + return this.optionsChartEditor.hitsoundVolumePlayer; } - public var chartEditorHitsoundsEnabledPlayer(get, set):Bool; + public var chartEditorHitsoundVolumeOpponent(get, set):Float; - function get_chartEditorHitsoundsEnabledPlayer():Bool + function get_chartEditorHitsoundVolumeOpponent():Float { - if (this.optionsChartEditor.hitsoundsEnabledPlayer == null) this.optionsChartEditor.hitsoundsEnabledPlayer = true; + if (this.optionsChartEditor.hitsoundVolumeOpponent == null) this.optionsChartEditor.hitsoundVolumeOpponent = 1.0; - return this.optionsChartEditor.hitsoundsEnabledPlayer; + return this.optionsChartEditor.hitsoundVolumeOpponent; } - function set_chartEditorHitsoundsEnabledPlayer(value:Bool):Bool + function set_chartEditorHitsoundVolumeOpponent(value:Float):Float { // Set and apply. - this.optionsChartEditor.hitsoundsEnabledPlayer = value; + this.optionsChartEditor.hitsoundVolumeOpponent = value; flush(); - return this.optionsChartEditor.hitsoundsEnabledPlayer; - } - - public var chartEditorHitsoundsEnabledOpponent(get, set):Bool; - - function get_chartEditorHitsoundsEnabledOpponent():Bool - { - if (this.optionsChartEditor.hitsoundsEnabledOpponent == null) this.optionsChartEditor.hitsoundsEnabledOpponent = true; - - return this.optionsChartEditor.hitsoundsEnabledOpponent; - } - - function set_chartEditorHitsoundsEnabledOpponent(value:Bool):Bool - { - // Set and apply. - this.optionsChartEditor.hitsoundsEnabledOpponent = value; - flush(); - return this.optionsChartEditor.hitsoundsEnabledOpponent; + return this.optionsChartEditor.hitsoundVolumeOpponent; } public var chartEditorThemeMusic(get, set):Bool; @@ -990,10 +973,16 @@ typedef SaveDataChartEditorOptions = var ?metronomeVolume:Float; /** - * Hitsound volume in the Chart Editor. + * Hitsound volume (player) in the Chart Editor. * @default `1.0` */ - var ?hitsoundVolume:Float; + var ?hitsoundVolumePlayer:Float; + + /** + * Hitsound volume (opponent) in the Chart Editor. + * @default `1.0` + */ + var ?hitsoundVolumeOpponent:Float; /** * If true, playtest songs from the current position in the Chart Editor. @@ -1001,18 +990,6 @@ typedef SaveDataChartEditorOptions = */ var ?playtestStartTime:Bool; - /** - * Player note hit sounds in the Chart Editor. - * @default `true` - */ - var ?hitsoundsEnabledPlayer:Bool; - - /** - * Opponent note hit sounds in the Chart Editor. - * @default `true` - */ - var ?hitsoundsEnabledOpponent:Bool; - /** * Theme music in the Chart Editor. * @default `true` diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 48a6e70c9..a00c1681b 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -310,10 +310,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { this.songLengthInMs = value; - // Make sure playhead doesn't go outside the song. - if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; - - onSongLengthChanged(); + updateGridHeight(); return this.songLengthInMs; } @@ -704,19 +701,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var metronomeVolume:Float = 1.0; /** - * The volume to play hitsounds at. + * The volume to play the player's hitsounds at. */ - var hitsoundVolume:Float = 1.0; + var hitsoundVolumePlayer:Float = 1.0; /** - * Whether hitsounds are enabled for the player. + * The volume to play the opponent's hitsounds at. */ - var hitsoundsEnabledPlayer:Bool = true; - - /** - * Whether hitsounds are enabled for the opponent. - */ - var hitsoundsEnabledOpponent:Bool = true; + var hitsoundVolumeOpponent:Float = 1.0; /** * Whether hitsounds are enabled for at least one character. @@ -725,7 +717,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_hitsoundsEnabled():Bool { - return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; + return hitsoundVolumePlayer + hitsoundVolumeOpponent > 0; } // Auto-save @@ -1757,30 +1749,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var menubarItemVolumeMetronome:Slider; - /** - * The `Audio -> Enable Player Hitsounds` menu checkbox. - */ - var menubarItemPlayerHitsounds:MenuCheckBox; - - /** - * The `Audio -> Enable Opponent Hitsounds` menu checkbox. - */ - var menubarItemOpponentHitsounds:MenuCheckBox; - /** * The `Audio -> Play Theme Music` menu checkbox. */ var menubarItemThemeMusic:MenuCheckBox; /** - * The `Audio -> Hitsound Volume` label. + * The `Audio -> Player Hitsound Volume` label. */ - var menubarLabelVolumeHitsounds:Label; + var menubarLabelVolumeHitsoundPlayer:Label; /** - * The `Audio -> Hitsound Volume` slider. + * The `Audio -> Enemy Hitsound Volume` label. */ - var menubarItemVolumeHitsounds:Slider; + var menubarLabelVolumeHitsoundOpponent:Label; + + /** + * The `Audio -> Player Hitsound Volume` slider. + */ + var menubarItemVolumeHitsoundPlayer:Slider; + + /** + * The `Audio -> Enemy Hitsound Volume` slider. + */ + var menubarItemVolumeHitsoundOpponent:Slider; /** * The `Audio -> Instrumental Volume` label. @@ -2187,9 +2179,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState playtestStartTime = save.chartEditorPlaytestStartTime; currentTheme = save.chartEditorTheme; metronomeVolume = save.chartEditorMetronomeVolume; - hitsoundVolume = save.chartEditorHitsoundVolume; - hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer; - hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent; + hitsoundVolumePlayer = save.chartEditorHitsoundVolumePlayer; + hitsoundVolumePlayer = save.chartEditorHitsoundVolumeOpponent; this.welcomeMusic.active = save.chartEditorThemeMusic; // audioInstTrack.volume = save.chartEditorInstVolume; @@ -2217,9 +2208,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState save.chartEditorPlaytestStartTime = playtestStartTime; save.chartEditorTheme = currentTheme; save.chartEditorMetronomeVolume = metronomeVolume; - save.chartEditorHitsoundVolume = hitsoundVolume; - save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer; - save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent; + save.chartEditorHitsoundVolumePlayer = hitsoundVolumePlayer; + save.chartEditorHitsoundVolumeOpponent = hitsoundVolumeOpponent; save.chartEditorThemeMusic = this.welcomeMusic.active; // save.chartEditorInstVolume = audioInstTrack.volume; @@ -2912,24 +2902,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState }; menubarItemVolumeMetronome.value = Std.int(metronomeVolume * 100); - menubarItemPlayerHitsounds.onChange = event -> hitsoundsEnabledPlayer = event.value; - menubarItemPlayerHitsounds.selected = hitsoundsEnabledPlayer; - - menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value; - menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent; - menubarItemThemeMusic.onChange = event -> { this.welcomeMusic.active = event.value; fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); }; menubarItemThemeMusic.selected = this.welcomeMusic.active; - menubarItemVolumeHitsound.onChange = event -> { + menubarItemVolumeHitsoundPlayer.onChange = event -> { var volume:Float = event.value.toFloat() / 100.0; - hitsoundVolume = volume; - menubarLabelVolumeHitsound.text = 'Hitsound - ${Std.int(event.value)}%'; + hitsoundVolumePlayer = volume; + menubarLabelVolumeHitsoundPlayer.text = 'Player - ${Std.int(event.value)}%'; }; - menubarItemVolumeHitsound.value = Std.int(hitsoundVolume * 100); + menubarItemVolumeHitsoundPlayer.value = Std.int(hitsoundVolumePlayer * 100); + + menubarItemVolumeHitsoundOpponent.onChange = event -> { + var volume:Float = event.value.toFloat() / 100.0; + hitsoundVolumeOpponent = volume; + menubarLabelVolumeHitsoundOpponent.text = 'Enemy - ${Std.int(event.value)}%'; + }; + menubarItemVolumeHitsoundOpponent.value = Std.int(hitsoundVolumeOpponent * 100); menubarItemVolumeInstrumental.onChange = event -> { var volume:Float = event.value.toFloat() / 100.0; @@ -5511,8 +5502,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent); } - function onSongLengthChanged():Void + public function updateGridHeight():Void { + // Make sure playhead doesn't go outside the song after we update the grid height. + if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; + if (gridTiledSprite != null) { gridTiledSprite.height = songLengthInPixels; @@ -5939,9 +5933,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState switch (noteData.getStrumlineIndex()) { case 0: // Player - if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume); + if (hitsoundVolumePlayer > 0) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolumePlayer); case 1: // Opponent - if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume); + if (hitsoundVolumeOpponent > 0) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolumeOpponent); } } } diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx index bd832fab3..a3728f337 100644 --- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx @@ -40,6 +40,8 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.scrollPositionInPixels = 0; Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + + state.updateGridHeight(); } public function undo(state:ChartEditorState):Void @@ -62,6 +64,8 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.scrollPositionInPixels = 0; Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + + state.updateGridHeight(); } public function shouldAddToHistory(state:ChartEditorState):Bool diff --git a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx index 1a76d1e22..2d8b62cbd 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx @@ -58,9 +58,15 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.instance.stepsPerMeasure) + 1; var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure; - measureNumber.text = '${measureNumberInViewport + 1}'; measureNumber.y = measureNumberPosition + this.y; + // Show the measure number only if it isn't beneath the end of the note grid. + // Using measureNumber + 1 because the cut-off bar at the bottom is technically a bar, but it looks bad if a measure number shows up there. + if ((measureNumberInViewport + 1) < chartEditorState.songLengthInSteps / Conductor.instance.stepsPerMeasure) + measureNumber.text = '${measureNumberInViewport + 1}'; + else + measureNumber.text = ''; + // trace(measureNumber.text + ' at ' + measureNumber.y); } From 7524885c816b2bc28e431e430041cf35735e325f Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 19 Feb 2024 18:18:46 -0700 Subject: [PATCH 016/172] Assets submod go brrr --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index e76470d66..36d153d69 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit e76470d66794bf3796f7ace84f2121b698e37a30 +Subproject commit 36d153d69cfd84675b8575e547def6b792cca545 From 85b3441a031aaeb08c30f8b8cd1f1dbf8e6c90ad Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 19 Feb 2024 21:40:52 -0500 Subject: [PATCH 017/172] tiny levelitems positioning --- source/funkin/ui/story/StoryMenuState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index bd7a05f91..9012f3672 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -433,7 +433,7 @@ class StoryMenuState extends MusicBeatState { var item:LevelTitle = levelTitles.members[index]; - item.targetY = (index - currentIndex) * 120 + 480; + item.targetY = (index - currentIndex) * 125 + 480; if (index == currentIndex) { From ef39382fea0b3ec78680a699e78b0088b99b7244 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 19 Feb 2024 21:49:05 -0500 Subject: [PATCH 018/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 36d153d69..c8f320e89 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 36d153d69cfd84675b8575e547def6b792cca545 +Subproject commit c8f320e89f65f9e6242354b25f04aac2c07cceeb From db428a3e3637ff3a633ffcf31c056c4053b299b0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 19 Feb 2024 23:09:23 -0500 Subject: [PATCH 019/172] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 75ac8ec25..573ccf59f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 75ac8ec2564c9a56e8282b0853091ecd8b4f2dfd +Subproject commit 573ccf59f3ab5d2333d395810ef82195e8456467 From ef6a5c033a6e7e01afc8fa89f16430cc0ba31237 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 19 Feb 2024 23:48:14 -0500 Subject: [PATCH 020/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 36d153d69..c8f320e89 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 36d153d69cfd84675b8575e547def6b792cca545 +Subproject commit c8f320e89f65f9e6242354b25f04aac2c07cceeb From 2cfeb2c94d09d1303acd3d185c0d69143ec50284 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 20 Feb 2024 00:29:11 -0500 Subject: [PATCH 021/172] anim editor dropdown fix --- .../ui/debug/anim/DebugBoundingState.hx | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx index 5561a9dcd..46a095a65 100644 --- a/source/funkin/ui/debug/anim/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -40,6 +40,7 @@ import openfl.utils.ByteArray; import funkin.input.Cursor; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.util.SortUtil; +import haxe.ui.core.Screen; using flixel.util.FlxSpriteUtil; @@ -75,21 +76,19 @@ class DebugBoundingState extends FlxState var uiStuff:Component; + var haxeUIFocused(get, default):Bool = false; + + function get_haxeUIFocused():Bool + { + // get the screen position, according to the HUD camera, temp default to FlxG.camera juuust in case? + var hudMousePos:FlxPoint = FlxG.mouse.getScreenPosition(hudCam ?? FlxG.camera); + return Screen.instance.hasSolidComponentUnderPoint(hudMousePos.x, hudMousePos.y); + } + override function create() { Paths.setCurrentLevel('week1'); - var str = Paths.xml('ui/animation-editor/offset-editor-view'); - uiStuff = RuntimeComponentBuilder.fromAsset(str); - - // uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET; - var dropdown:DropDown = cast uiStuff.findComponent("swapper"); - dropdown.onChange = function(e:UIEvent) { - trace(e.type); - curView = cast e.data.curView; - trace(e.data); - // trace(e.data); - }; // lv. // lv.onChange = function(e:UIEvent) // { @@ -106,23 +105,40 @@ class DebugBoundingState extends FlxState hudCam = new FlxCamera(); hudCam.bgColor.alpha = 0; - FlxG.cameras.add(hudCam, false); - bg = FlxGridOverlay.create(10, 10); // bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.GREEN); bg.scrollFactor.set(); add(bg); - initSpritesheetView(); - initOffsetView(); + // we are setting this as the default draw camera only temporarily, to trick haxeui + FlxG.cameras.add(hudCam); - Cursor.show(); + var str = Paths.xml('ui/animation-editor/offset-editor-view'); + uiStuff = RuntimeComponentBuilder.fromAsset(str); + + // uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET; + var dropdown:DropDown = cast uiStuff.findComponent("swapper"); + dropdown.onChange = function(e:UIEvent) { + trace(e.type); + curView = cast e.data.curView; + trace(e.data); + // trace(e.data); + }; uiStuff.cameras = [hudCam]; add(uiStuff); + // sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf + FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true); + FlxG.cameras.setDefaultDrawTarget(hudCam, false); + + initSpritesheetView(); + initOffsetView(); + + Cursor.show(); + super.create(); } @@ -359,7 +375,7 @@ class DebugBoundingState extends FlxState offsetView.visible = true; offsetView.active = true; offsetControls(); - mouseOffsetMovement(); + if (!haxeUIFocused) mouseOffsetMovement(); } if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible; @@ -367,7 +383,7 @@ class DebugBoundingState extends FlxState if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); MouseUtil.mouseCamDrag(); - MouseUtil.mouseWheelZoom(); + if (!haxeUIFocused) MouseUtil.mouseWheelZoom(); // bg.scale.x = FlxG.camera.zoom; // bg.scale.y = FlxG.camera.zoom; From d888fb860d91d544a5d21864b9ddfccec576c8d0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 20 Feb 2024 13:37:53 -0500 Subject: [PATCH 022/172] Remove support for >100% audio since it didn't actually boost the gain. --- source/funkin/audio/FunkinSound.hx | 15 +-------------- .../charting/handlers/ChartEditorAudioHandler.hx | 4 +--- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 0e6bd6893..ba157ed8e 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -23,7 +23,7 @@ import openfl.utils.AssetType; @:nullSafety class FunkinSound extends FlxSound implements ICloneable { - static final MAX_VOLUME:Float = 2.0; + static final MAX_VOLUME:Float = 1.0; static var cache(default, null):FlxTypedGroup = new FlxTypedGroup(); @@ -40,7 +40,6 @@ class FunkinSound extends FlxSound implements ICloneable override function set_volume(value:Float):Float { // Uncap the volume. - fixMaxVolume(); _volume = FlxMath.bound(value, 0.0, MAX_VOLUME); updateTransform(); return _volume; @@ -126,18 +125,6 @@ class FunkinSound extends FlxSound implements ICloneable return this; } - function fixMaxVolume():Void - { - return; - #if lime_openal - // This code is pretty fragile, it reaches through 5 layers of private access. - @:privateAccess - var handle = this?._channel?.__source?.__backend?.handle; - if (handle == null) return; - lime.media.openal.AL.sourcef(handle, lime.media.openal.AL.MAX_GAIN, MAX_VOLUME); - #end - } - public override function play(forceRestart:Bool = false, startTime:Float = 0, ?endTime:Float):FunkinSound { if (!exists) return this; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 363dc1567..5e3ffeb42 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -299,16 +299,14 @@ class ChartEditorAudioHandler */ public static function playSound(_state:ChartEditorState, path:String, volume:Float = 1.0):Void { - var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound(); var asset:Null = FlxG.sound.cache(path); if (asset == null) { trace('WARN: Failed to play sound $path, asset not found.'); return; } - snd.loadEmbedded(asset); + var snd:FunkinSound = FunkinSound.load(asset); snd.autoDestroy = true; - FlxG.sound.list.add(snd); snd.play(true); snd.volume = volume; } From 907d9150c03e0b887a75b15163a8affe2eff1c07 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 21 Feb 2024 17:10:18 -0500 Subject: [PATCH 023/172] Add additional memory utilities and logging. --- .gitignore | 1 + source/funkin/ui/debug/MemoryCounter.hx | 3 +- source/funkin/util/MemoryUtil.hx | 113 +++++++++++++++++++++ source/funkin/util/logging/CrashHandler.hx | 8 ++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 source/funkin/util/MemoryUtil.hx diff --git a/.gitignore b/.gitignore index b2fe731ea..34a0c5590 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_STORE .haxelib/ +.vs/ APIStuff.hx dump/ export/ diff --git a/source/funkin/ui/debug/MemoryCounter.hx b/source/funkin/ui/debug/MemoryCounter.hx index 312d853e7..b25b55645 100644 --- a/source/funkin/ui/debug/MemoryCounter.hx +++ b/source/funkin/ui/debug/MemoryCounter.hx @@ -1,5 +1,6 @@ package funkin.ui.debug; +import funkin.util.MemoryUtil; import openfl.text.TextFormat; import openfl.system.System; import openfl.text.TextField; @@ -35,7 +36,7 @@ class MemoryCounter extends TextField @:noCompletion #if !flash override #end function __enterFrame(deltaTime:Float):Void { - var mem:Float = Math.round(System.totalMemory / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; + var mem:Float = Math.round(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; if (mem > memPeak) memPeak = mem; diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx new file mode 100644 index 000000000..6b5f7deea --- /dev/null +++ b/source/funkin/util/MemoryUtil.hx @@ -0,0 +1,113 @@ +package funkin.util; + +/** + * Utilities for working with the garbage collector. + * + * HXCPP is built on Immix. + * HTML5 builds use the browser's built-in mark-and-sweep and JS has no APIs to interact with it. + * @see https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/immix/ + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management + * @see https://betterprogramming.pub/deep-dive-into-garbage-collection-in-javascript-6881610239a + * @see https://github.com/HaxeFoundation/hxcpp/blob/master/docs/build_xml/Defines.md + * @see cpp.vm.Gc + */ +class MemoryUtil +{ + public static function buildGCInfo():String + { + #if cpp + var result = "HXCPP-Immix:"; + result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; + result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; + result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; + result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_LARGE)} bytes'; + result += '\n- HXCPP Debugger: ${#if HXCPP_DEBUGGER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Generational Mode: ${#if HXCPP_GC_GENERATIONAL 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_MOVING 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_DYNAMIC_SIZE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_BIG_BLOCKS 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Debug Link: ${#if HXCPP_DEBUG_LINK 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace: ${#if HXCPP_STACK_TRACE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace Line Numbers: ${#if HXCPP_STACK_LINE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Pointer Validation: ${#if HXCPP_CHECK_POINTER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Profiler: ${#if HXCPP_PROFILER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Local Telemetry: ${#if HXCPP_TELEMETRY 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}'; + result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; + #elseif js + var result = "JS-MNS:"; + result += '\n- Memory Used: ${getMemoryUsed()} bytes'; + #else + var result = "Unknown GC"; + #end + + return result; + } + + /** + * Calculate the total memory usage of the program, in bytes. + * @return Int + */ + public static function getMemoryUsed():Int + { + #if cpp + // There is also Gc.MEM_INFO_RESERVED, MEM_INFO_CURRENT, and MEM_INFO_LARGE. + return cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE); + #else + return openfl.system.System.totalMemory; + #end + } + + /** + * Enable garbage collection if it was previously disabled. + */ + public static function enable():Void + { + #if cpp + cpp.vm.Gc.enable(true); + #else + throw "Not implemented!"; + #end + } + + /** + * Disable garbage collection entirely. + */ + public static function disable():Void + { + #if cpp + cpp.vm.Gc.enable(false); + #else + throw "Not implemented!"; + #end + } + + /** + * Manually perform garbage collection once. + * Should only be called from the main thread. + * @param major `true` to perform major collection, whatever that means. + */ + public static function collect(major:Bool = false):Void + { + #if cpp + cpp.vm.Gc.run(major); + #else + throw "Not implemented!"; + #end + } + + /** + * Perform major garbage collection repeatedly until less than 16kb of memory is freed in one operation. + * Should only be called from the main thread. + * + * NOTE: This is DIFFERENT from actual compaction, + */ + public static function compact():Void + { + #if cpp + cpp.vm.Gc.compact(); + #else + throw "Not implemented!"; + #end + } +} diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx index ad5983e52..93d566710 100644 --- a/source/funkin/util/logging/CrashHandler.hx +++ b/source/funkin/util/logging/CrashHandler.hx @@ -125,6 +125,14 @@ class CrashHandler fullContents += '=====================\n'; + fullContents += '\n'; + + fullContents += MemoryUtil.buildGCInfo(); + + fullContents += '\n\n'; + + fullContents += '=====================\n'; + fullContents += 'Haxelibs: \n'; for (lib in Constants.LIBRARY_VERSIONS) From d95232dda00160fa7a95a9270e626ff18b0fc531 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 00:05:56 -0500 Subject: [PATCH 024/172] Add .vs to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b2fe731ea..34a0c5590 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_STORE .haxelib/ +.vs/ APIStuff.hx dump/ export/ From 744e2f95bd228ba5f2772b20adc83fde046e09ec Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 21 Feb 2024 17:10:18 -0500 Subject: [PATCH 025/172] Add additional memory utilities and logging. --- source/funkin/ui/debug/MemoryCounter.hx | 3 +- source/funkin/util/MemoryUtil.hx | 113 +++++++++++++++++++++ source/funkin/util/logging/CrashHandler.hx | 8 ++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 source/funkin/util/MemoryUtil.hx diff --git a/source/funkin/ui/debug/MemoryCounter.hx b/source/funkin/ui/debug/MemoryCounter.hx index 312d853e7..b25b55645 100644 --- a/source/funkin/ui/debug/MemoryCounter.hx +++ b/source/funkin/ui/debug/MemoryCounter.hx @@ -1,5 +1,6 @@ package funkin.ui.debug; +import funkin.util.MemoryUtil; import openfl.text.TextFormat; import openfl.system.System; import openfl.text.TextField; @@ -35,7 +36,7 @@ class MemoryCounter extends TextField @:noCompletion #if !flash override #end function __enterFrame(deltaTime:Float):Void { - var mem:Float = Math.round(System.totalMemory / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; + var mem:Float = Math.round(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; if (mem > memPeak) memPeak = mem; diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx new file mode 100644 index 000000000..6b5f7deea --- /dev/null +++ b/source/funkin/util/MemoryUtil.hx @@ -0,0 +1,113 @@ +package funkin.util; + +/** + * Utilities for working with the garbage collector. + * + * HXCPP is built on Immix. + * HTML5 builds use the browser's built-in mark-and-sweep and JS has no APIs to interact with it. + * @see https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/immix/ + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management + * @see https://betterprogramming.pub/deep-dive-into-garbage-collection-in-javascript-6881610239a + * @see https://github.com/HaxeFoundation/hxcpp/blob/master/docs/build_xml/Defines.md + * @see cpp.vm.Gc + */ +class MemoryUtil +{ + public static function buildGCInfo():String + { + #if cpp + var result = "HXCPP-Immix:"; + result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; + result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; + result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; + result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_LARGE)} bytes'; + result += '\n- HXCPP Debugger: ${#if HXCPP_DEBUGGER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Generational Mode: ${#if HXCPP_GC_GENERATIONAL 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_MOVING 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_DYNAMIC_SIZE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_BIG_BLOCKS 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Debug Link: ${#if HXCPP_DEBUG_LINK 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace: ${#if HXCPP_STACK_TRACE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace Line Numbers: ${#if HXCPP_STACK_LINE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Pointer Validation: ${#if HXCPP_CHECK_POINTER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Profiler: ${#if HXCPP_PROFILER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Local Telemetry: ${#if HXCPP_TELEMETRY 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}'; + result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; + #elseif js + var result = "JS-MNS:"; + result += '\n- Memory Used: ${getMemoryUsed()} bytes'; + #else + var result = "Unknown GC"; + #end + + return result; + } + + /** + * Calculate the total memory usage of the program, in bytes. + * @return Int + */ + public static function getMemoryUsed():Int + { + #if cpp + // There is also Gc.MEM_INFO_RESERVED, MEM_INFO_CURRENT, and MEM_INFO_LARGE. + return cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE); + #else + return openfl.system.System.totalMemory; + #end + } + + /** + * Enable garbage collection if it was previously disabled. + */ + public static function enable():Void + { + #if cpp + cpp.vm.Gc.enable(true); + #else + throw "Not implemented!"; + #end + } + + /** + * Disable garbage collection entirely. + */ + public static function disable():Void + { + #if cpp + cpp.vm.Gc.enable(false); + #else + throw "Not implemented!"; + #end + } + + /** + * Manually perform garbage collection once. + * Should only be called from the main thread. + * @param major `true` to perform major collection, whatever that means. + */ + public static function collect(major:Bool = false):Void + { + #if cpp + cpp.vm.Gc.run(major); + #else + throw "Not implemented!"; + #end + } + + /** + * Perform major garbage collection repeatedly until less than 16kb of memory is freed in one operation. + * Should only be called from the main thread. + * + * NOTE: This is DIFFERENT from actual compaction, + */ + public static function compact():Void + { + #if cpp + cpp.vm.Gc.compact(); + #else + throw "Not implemented!"; + #end + } +} diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx index ad5983e52..93d566710 100644 --- a/source/funkin/util/logging/CrashHandler.hx +++ b/source/funkin/util/logging/CrashHandler.hx @@ -125,6 +125,14 @@ class CrashHandler fullContents += '=====================\n'; + fullContents += '\n'; + + fullContents += MemoryUtil.buildGCInfo(); + + fullContents += '\n\n'; + + fullContents += '=====================\n'; + fullContents += 'Haxelibs: \n'; for (lib in Constants.LIBRARY_VERSIONS) From 0b49a88cdd5157e8da51fb77c97796bf82061be7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 01:47:35 -0500 Subject: [PATCH 026/172] Move the crash keybind into a global plugin. --- source/funkin/InitState.hx | 3 +- .../ui/debug/charting/ChartEditorState.hx | 8 ---- source/funkin/util/logging/CrashHandler.hx | 10 +++++ .../funkin/util/plugins/ForceCrashPlugin.hx | 37 +++++++++++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 source/funkin/util/plugins/ForceCrashPlugin.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0ad3c19b8..0a59fb70b 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -202,8 +202,9 @@ class InitState extends FlxState // // Plugins provide a useful interface for globally active Flixel objects, // that receive update events regardless of the current state. - // TODO: Move Module behavior to a Flixel plugin. + // TODO: Move scripted Module behavior to a Flixel plugin. funkin.util.plugins.EvacuateDebugPlugin.initialize(); + funkin.util.plugins.ForceCrashPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); funkin.util.plugins.ScreenshotPlugin.initialize(); funkin.util.plugins.VolumePlugin.initialize(); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a00c1681b..75352c21d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5258,14 +5258,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // F1 = Open Help if (FlxG.keys.justPressed.F1) this.openUserGuideDialog(); - - // DEBUG KEYBIND: Ctrl + Alt + Shift + L = Crash the game. - #if debug - if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L) - { - throw "DEBUG: Crashing the chart editor!"; - } - #end } function handleQuickWatch():Void diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx index 93d566710..ef293486d 100644 --- a/source/funkin/util/logging/CrashHandler.hx +++ b/source/funkin/util/logging/CrashHandler.hx @@ -133,6 +133,16 @@ class CrashHandler fullContents += '=====================\n'; + fullContents += '\n'; + + fullContents += 'Flixel Current State: ${Type.getClassName(Type.getClass(FlxG.state))}\n'; + + fullContents += '\n'; + + fullContents += '=====================\n'; + + fullContents += '\n'; + fullContents += 'Haxelibs: \n'; for (lib in Constants.LIBRARY_VERSIONS) diff --git a/source/funkin/util/plugins/ForceCrashPlugin.hx b/source/funkin/util/plugins/ForceCrashPlugin.hx new file mode 100644 index 000000000..a2c8c0a21 --- /dev/null +++ b/source/funkin/util/plugins/ForceCrashPlugin.hx @@ -0,0 +1,37 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which forcibly crashes the application. + * TODO: Should we disable this in release builds? + */ +class ForceCrashPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ForceCrashPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + // Ctrl + Alt + Shift + L = Crash the game for debugging purposes + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L) + { + // TODO: Make this message 87% funnier. + throw "DEBUG: Crashing the game via debug keybind!"; + } + } + + public override function destroy():Void + { + super.destroy(); + } +} From 402a84831d4f2b8b76677d346645876051af90b9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 02:18:24 -0500 Subject: [PATCH 027/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index c8f320e89..cb122820d 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c8f320e89f65f9e6242354b25f04aac2c07cceeb +Subproject commit cb122820d4f533a542fe27875202d57294600bbc From 818e80ed74233ef33224284c4238c30c71221fa1 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 03:15:06 -0500 Subject: [PATCH 028/172] Add a screenshot sound provided by Isaac. --- assets | 2 +- source/funkin/util/plugins/ScreenshotPlugin.hx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/assets b/assets index c8f320e89..14abe753a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c8f320e89f65f9e6242354b25f04aac2c07cceeb +Subproject commit 14abe753a2af62879f7ebaf717331297ff7d7a81 diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index 16d0c7244..d7e8109b8 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -13,6 +13,7 @@ import flixel.util.FlxSignal; import flixel.util.FlxTimer; import funkin.graphics.FunkinSprite; import funkin.input.Cursor; +import funkin.audio.FunkinSound; import openfl.display.Bitmap; import openfl.display.Sprite; import openfl.display.BitmapData; @@ -162,7 +163,7 @@ class ScreenshotPlugin extends FlxBasic final CAMERA_FLASH_DURATION = 0.25; /** - * Visual (and audio?) feedback when a screenshot is taken. + * Visual and audio feedback when a screenshot is taken. */ function showCaptureFeedback():Void { @@ -171,6 +172,9 @@ class ScreenshotPlugin extends FlxBasic flashSpr.addChild(flashBitmap); FlxG.stage.addChild(flashSpr); FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)}); + + // Play a sound (auto-play is true). + FunkinSound.load(Paths.sound('screenshot'), 1.0, false, true, true); } static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. From 201c81cfefbb3f45a21fac03b1734ac6e61175f0 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 22 Feb 2024 18:51:18 -0500 Subject: [PATCH 029/172] changed keybind to ctrl+shift+L --- source/funkin/util/plugins/ForceCrashPlugin.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/util/plugins/ForceCrashPlugin.hx b/source/funkin/util/plugins/ForceCrashPlugin.hx index a2c8c0a21..e8094eb3c 100644 --- a/source/funkin/util/plugins/ForceCrashPlugin.hx +++ b/source/funkin/util/plugins/ForceCrashPlugin.hx @@ -22,8 +22,8 @@ class ForceCrashPlugin extends FlxBasic { super.update(elapsed); - // Ctrl + Alt + Shift + L = Crash the game for debugging purposes - if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L) + // Ctrl + Shift + L = Crash the game for debugging purposes + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.pressed.L) { // TODO: Make this message 87% funnier. throw "DEBUG: Crashing the game via debug keybind!"; From 539b688055a75aa706c631c8c0fc13ec6cf782b6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 18:55:24 -0500 Subject: [PATCH 030/172] Exploration in expanding FunkinSprite for optimization --- assets | 2 +- source/funkin/graphics/FunkinSprite.hx | 122 +++++++++++++++++- source/funkin/play/Countdown.hx | 3 +- source/funkin/play/GitarooPause.hx | 12 +- source/funkin/play/PauseSubState.hx | 3 +- source/funkin/play/PlayState.hx | 25 ++-- source/funkin/play/ResultState.hx | 19 +-- source/funkin/play/components/HealthIcon.hx | 5 +- source/funkin/play/components/PopUpStuff.hx | 26 ++-- source/funkin/play/notes/NoteSprite.hx | 3 +- .../funkin/play/notes/notestyle/NoteStyle.hx | 6 + source/funkin/play/stage/Stage.hx | 6 +- source/funkin/ui/freeplay/DJBoyfriend.hx | 2 - source/funkin/ui/freeplay/FreeplayFlames.hx | 2 +- source/funkin/ui/freeplay/FreeplayScore.hx | 2 +- source/funkin/ui/freeplay/FreeplayState.hx | 8 +- source/funkin/ui/transition/LoadingState.hx | 10 +- 17 files changed, 197 insertions(+), 59 deletions(-) diff --git a/assets b/assets index 03f544a7b..ffbf73c76 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 03f544a7b42fed43c521cb596e487ad4ae129576 +Subproject commit ffbf73c76860a2747eb11eeed14099e186700956 diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index 487aaac34..6c8ce1308 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -6,6 +6,8 @@ import flixel.graphics.FlxGraphic; /** * An FlxSprite with additional functionality. + * - A more efficient method for creating solid color sprites. + * - TODO: Better cache handling for textures. */ class FunkinSprite extends FlxSprite { @@ -18,19 +20,135 @@ class FunkinSprite extends FlxSprite super(x, y); } + /** + * Create a new FunkinSprite with a static texture. + * @param x The starting X position. + * @param y The starting Y position. + * @param key The key of the texture to load. + * @return The new FunkinSprite. + */ + public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite + { + var sprite = new FunkinSprite(x, y); + sprite.loadTexture(key); + return sprite; + } + + /** + * Create a new FunkinSprite with a Sparrow atlas animated texture. + * @param x The starting X position. + * @param y The starting Y position. + * @param key The key of the texture to load. + * @return The new FunkinSprite. + */ + public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite + { + var sprite = new FunkinSprite(x, y); + sprite.loadSparrow(key); + return sprite; + } + + /** + * Create a new FunkinSprite with a Packer atlas animated texture. + * @param x The starting X position. + * @param y The starting Y position. + * @param key The key of the texture to load. + * @return The new FunkinSprite. + */ + public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite + { + var sprite = new FunkinSprite(x, y); + sprite.loadPacker(key); + return sprite; + } + + /** + * Load a static image as the sprite's texture. + * @param key The key of the texture to load. + * @return This sprite, for chaining. + */ + public function loadTexture(key:String):FunkinSprite + { + if (!isTextureCached(key)) FlxG.log.warn('Texture not cached, may experience stuttering! $key'); + + loadGraphic(key); + + return this; + } + + /** + * Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture. + * @param key The key of the texture to load. + * @return This sprite, for chaining. + */ + public function loadSparrow(key:String):FunkinSprite + { + var graphicKey = Paths.image(key); + if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey'); + + this.frames = Paths.getSparrowAtlas(key); + + return this; + } + + /** + * Load an animated texture (Packer atlas spritesheet) as the sprite's texture. + * @param key The key of the texture to load. + * @return This sprite, for chaining. + */ + public function loadPacker(key:String):FunkinSprite + { + var graphicKey = Paths.image(key); + if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey'); + + this.frames = Paths.getPackerAtlas(key); + + return this; + } + + public static function isTextureCached(key:String):Bool + { + return FlxG.bitmap.get(key) != null; + } + + public static function cacheTexture(key:String):Void + { + var graphic = flixel.graphics.FlxGraphic.fromAssetKey(key, false, null, true); + if (graphic == null) + { + FlxG.log.warn('Failed to cache graphic: $key'); + } + else + { + trace('Successfully cached graphic: $key'); + } + } + + public static function cacheSparrow(key:String):Void + { + cacheTexture(Paths.image(key)); + } + + public static function cachePacker(key:String):Void + { + cacheTexture(Paths.image(key)); + } + /** * Acts similarly to `makeGraphic`, but with improved memory usage, - * at the expense of not being able to paint onto the sprite. + * at the expense of not being able to paint onto the resulting sprite. * * @param width The target width of the sprite. * @param height The target height of the sprite. * @param color The color to fill the sprite with. + * @return This sprite, for chaining. */ public function makeSolidColor(width:Int, height:Int, color:FlxColor = FlxColor.WHITE):FunkinSprite { + // Create a tiny solid color graphic and scale it up to the desired size. var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}'); frames = graphic.imageFrame; - scale.set(width / 2, height / 2); + scale.set(width / 2.0, height / 2.0); updateHitbox(); return this; diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 5b7ce9fc2..38e8986ef 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -3,6 +3,7 @@ package funkin.play; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.FlxSprite; +import funkin.graphics.FunkinSprite; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.module.ModuleHandler; import funkin.modding.events.ScriptEvent; @@ -214,7 +215,7 @@ class Countdown if (spritePath == null) return; - var countdownSprite:FlxSprite = new FlxSprite(0, 0).loadGraphic(Paths.image(spritePath)); + var countdownSprite:FunkinSprite = FunkinSprite.create(Paths.image(spritePath)); countdownSprite.scrollFactor.set(0, 0); if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE)); diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx index edeb4229c..1ed9dcf3b 100644 --- a/source/funkin/play/GitarooPause.hx +++ b/source/funkin/play/GitarooPause.hx @@ -3,6 +3,7 @@ package funkin.play; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; import funkin.play.PlayState; +import funkin.graphics.FunkinSprite; import funkin.ui.MusicBeatState; import flixel.addons.transition.FlxTransitionableState; import funkin.ui.mainmenu.MainMenuState; @@ -27,25 +28,22 @@ class GitarooPause extends MusicBeatState { if (FlxG.sound.music != null) FlxG.sound.music.stop(); - var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('pauseAlt/pauseBG')); + var bg:FunkinSprite = FunkinSprite.create(Paths.image('pauseAlt/pauseBG')); add(bg); - var bf:FlxSprite = new FlxSprite(0, 30); - bf.frames = Paths.getSparrowAtlas('pauseAlt/bfLol'); + var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol'); bf.animation.addByPrefix('lol', "funnyThing", 13); bf.animation.play('lol'); add(bf); bf.screenCenter(X); - replayButton = new FlxSprite(FlxG.width * 0.28, FlxG.height * 0.7); - replayButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI'); + replayButton = FunkinSprite.createSparrow(FlxG.width * 0.28, FlxG.height * 0.7, 'pauseAlt/pauseUI'); replayButton.animation.addByPrefix('selected', 'bluereplay', 0, false); replayButton.animation.appendByPrefix('selected', 'yellowreplay'); replayButton.animation.play('selected'); add(replayButton); - cancelButton = new FlxSprite(FlxG.width * 0.58, replayButton.y); - cancelButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI'); + cancelButton = FunkinSprite.createSparrow(FlxG.width * 0.58, replayButton.y, 'pauseAlt/pauseUI'); cancelButton.animation.addByPrefix('selected', 'bluecancel', 0, false); cancelButton.animation.appendByPrefix('selected', 'cancelyellow'); cancelButton.animation.play('selected'); diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 023b8d5be..1ae96268d 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -13,6 +13,7 @@ import flixel.util.FlxColor; import funkin.play.PlayState; import funkin.data.song.SongRegistry; import funkin.ui.Alphabet; +import funkin.graphics.FunkinSprite; class PauseSubState extends MusicBeatSubState { @@ -72,7 +73,7 @@ class PauseSubState extends MusicBeatSubState FlxG.sound.list.add(pauseMusic); - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + bg = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK); bg.alpha = 0; bg.scrollFactor.set(); add(bg); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5033dd45b..bde68461b 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -10,12 +10,14 @@ import flixel.addons.transition.Transition; import flixel.addons.transition.Transition; import flixel.FlxCamera; import flixel.FlxObject; -import flixel.FlxSprite; import flixel.FlxState; +import funkin.graphics.FunkinSprite; import flixel.FlxSubState; +import funkin.graphics.FunkinSprite; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; +import funkin.graphics.FunkinSprite; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; @@ -213,7 +215,7 @@ class PlayState extends MusicBeatSubState * The current gameplay camera will always follow this object. Tween its position to move the camera smoothly. * * It needs to be an object in the scene for the camera to be configured to follow it. - * We optionally make this an FlxSprite so we can draw a debug graphic with it. + * We optionally make this a sprite so we can draw a debug graphic with it. */ public var cameraFollowPoint:FlxObject; @@ -400,7 +402,7 @@ class PlayState extends MusicBeatSubState * The background image used for the health bar. * Emma says the image is slightly skewed so I'm leaving it as an image instead of a `createGraphic`. */ - public var healthBarBG:FlxSprite; + public var healthBarBG:FunkinSprite; /** * The health icon representing the player. @@ -568,12 +570,15 @@ class PlayState extends MusicBeatSubState if (!assertChartExists()) return; + // TODO: Add something to toggle this on! if (false) { // Displays the camera follow point as a sprite for debug purposes. - cameraFollowPoint = new FlxSprite(0, 0).makeGraphic(8, 8, 0xFF00FF00); + var cameraFollowPoint = new FunkinSprite(0, 0); + cameraFollowPoint.makeSolidColor(8, 8, 0xFF00FF00); cameraFollowPoint.visible = false; cameraFollowPoint.zIndex = 1000000; + this.cameraFollowPoint = cameraFollowPoint; } else { @@ -1349,7 +1354,7 @@ class PlayState extends MusicBeatSubState function initHealthBar():Void { var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9; - healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar')); + healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar')); healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(0, 0); add(healthBarBG); @@ -1383,7 +1388,7 @@ class PlayState extends MusicBeatSubState function initMinimalMode():Void { // Create the green background. - var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + var menuBG = FunkinSprite.create(Paths.image('menuDesat')); menuBG.color = 0xFF4CAF50; menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); menuBG.updateHitbox(); @@ -2623,10 +2628,10 @@ class PlayState extends MusicBeatSubState // TODO: Softcode this cutscene. if (currentSong.id == 'eggnog') { - var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, - -FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); - blackShit.scrollFactor.set(); - add(blackShit); + var blackBG:FunkinSprite = new FunkinSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom); + blackBG.makeSolidColor(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); + blackBG.scrollFactor.set(); + add(blackBG); camHUD.visible = false; isInCutscene = true; diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 9ffeefcfd..223043c28 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -4,6 +4,7 @@ import funkin.ui.story.StoryMenuState; import funkin.graphics.adobeanimate.FlxAtlasSprite; import flixel.FlxBasic; import flixel.FlxSprite; +import funkin.graphics.FunkinSprite; import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxBitmapFont; import flixel.group.FlxGroup.FlxTypedGroup; @@ -96,8 +97,7 @@ class ResultState extends MusicBeatSubState bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce! }; - var gf:FlxSprite = new FlxSprite(500, 300); - gf.frames = Paths.getSparrowAtlas('resultScreen/resultGirlfriendGOOD'); + var gf:FlxSprite = FunkinSprite.createSparrow(500, 300, 'resultScreen/resultGirlfriendGOOD'); gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false); gf.visible = false; gf.animation.finishCallback = _ -> { @@ -105,8 +105,7 @@ class ResultState extends MusicBeatSubState }; add(gf); - var boyfriend:FlxSprite = new FlxSprite(640, -200); - boyfriend.frames = Paths.getSparrowAtlas('resultScreen/resultBoyfriendGOOD'); + var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD'); boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false); boyfriend.visible = false; boyfriend.animation.finishCallback = function(_) { @@ -115,8 +114,7 @@ class ResultState extends MusicBeatSubState add(boyfriend); - var soundSystem:FlxSprite = new FlxSprite(-15, -180); - soundSystem.frames = Paths.getSparrowAtlas("resultScreen/soundSystem"); + var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem'); soundSystem.animation.addByPrefix("idle", "sound system", 24, false); soundSystem.visible = false; new FlxTimer().start(0.4, _ -> { @@ -162,20 +160,17 @@ class ResultState extends MusicBeatSubState FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5}); add(blackTopBar); - var resultsAnim:FlxSprite = new FlxSprite(-200, -10); - resultsAnim.frames = Paths.getSparrowAtlas("resultScreen/results"); + var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results"); resultsAnim.animation.addByPrefix("result", "results", 24, false); resultsAnim.animation.play("result"); add(resultsAnim); - var ratingsPopin:FlxSprite = new FlxSprite(-150, 120); - ratingsPopin.frames = Paths.getSparrowAtlas("resultScreen/ratingsPopin"); + var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin"); ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false); ratingsPopin.visible = false; add(ratingsPopin); - var scorePopin:FlxSprite = new FlxSprite(-180, 520); - scorePopin.frames = Paths.getSparrowAtlas("resultScreen/scorePopin"); + var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin"); scorePopin.animation.addByPrefix("score", "tally score", 24, false); scorePopin.visible = false; add(scorePopin); diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index 420a4fdc4..419c5b3ea 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -6,6 +6,7 @@ import flixel.math.FlxMath; import flixel.math.FlxPoint; import funkin.play.character.CharacterData.CharacterDataParser; import openfl.utils.Assets; +import funkin.graphics.FunkinSprite; import funkin.util.MathUtil; /** @@ -26,7 +27,7 @@ import funkin.util.MathUtil; * @author MasterEric */ @:nullSafety -class HealthIcon extends FlxSprite +class HealthIcon extends FunkinSprite { /** * The character this icon is representing. @@ -408,7 +409,7 @@ class HealthIcon extends FlxSprite if (!isLegacyStyle) { - frames = Paths.getSparrowAtlas('icons/icon-$charId'); + loadSparrow('icons/icon-$charId'); loadAnimationNew(); } diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 9553856a9..593a7333e 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -3,6 +3,7 @@ package funkin.play.components; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.tweens.FlxTween; +import funkin.graphics.FunkinSprite; import funkin.play.PlayState; class PopUpStuff extends FlxTypedGroup @@ -14,17 +15,18 @@ class PopUpStuff extends FlxTypedGroup public function displayRating(daRating:String) { + var perfStart:Float = Sys.time(); + if (daRating == null) daRating = "good"; - var rating:FlxSprite = new FlxSprite(0, 0); - rating.scrollFactor.set(0.2, 0.2); - - rating.zIndex = 1000; var ratingPath:String = daRating; if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel"; - rating.loadGraphic(Paths.image(ratingPath)); + var rating:FunkinSprite = FunkinSprite.create(0, 0, Paths.image(ratingPath)); + rating.scrollFactor.set(0.2, 0.2); + + rating.zIndex = 1000; rating.x = FlxG.width * 0.50; rating.x -= FlxG.camera.scroll.x * 0.2; // make sure rating is visible lol! @@ -61,10 +63,16 @@ class PopUpStuff extends FlxTypedGroup }, startDelay: Conductor.instance.beatLengthMs * 0.001 }); + + var perfEnd:Float = Sys.time(); + + trace("displayRating took: " + (perfEnd - perfStart)); } public function displayCombo(?combo:Int = 0):Int { + var perfStart:Float = Sys.time(); + if (combo == null) combo = 0; var pixelShitPart1:String = ""; @@ -75,7 +83,7 @@ class PopUpStuff extends FlxTypedGroup pixelShitPart1 = 'weeb/pixelUI/'; pixelShitPart2 = '-pixel'; } - var comboSpr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); + var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); comboSpr.y = FlxG.camera.height * 0.4 + 80; comboSpr.x = FlxG.width * 0.50; comboSpr.x -= FlxG.camera.scroll.x * 0.2; @@ -129,8 +137,7 @@ class PopUpStuff extends FlxTypedGroup var daLoop:Int = 1; for (i in seperatedScore) { - var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2)); - numScore.y = comboSpr.y; + var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2)); if (PlayState.instance.currentStageId.startsWith('school')) { @@ -163,6 +170,9 @@ class PopUpStuff extends FlxTypedGroup daLoop++; } + var perfEnd:Float = Sys.time(); + trace("displayCombo took: " + (perfEnd - perfStart)); + return combo; } } diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index 0368b18e9..45862b26d 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -4,9 +4,10 @@ import funkin.data.song.SongData.SongNoteData; import funkin.play.notes.notestyle.NoteStyle; import flixel.graphics.frames.FlxAtlasFrames; import flixel.FlxSprite; +import funkin.graphics.FunkinSprite; import funkin.graphics.shaders.HSVShader; -class NoteSprite extends FlxSprite +class NoteSprite extends FunkinSprite { static final DIRECTION_COLORS:Array = ['purple', 'blue', 'green', 'red']; diff --git a/source/funkin/play/notes/notestyle/NoteStyle.hx b/source/funkin/play/notes/notestyle/NoteStyle.hx index 34c1ce9c3..f560396e8 100644 --- a/source/funkin/play/notes/notestyle/NoteStyle.hx +++ b/source/funkin/play/notes/notestyle/NoteStyle.hx @@ -4,6 +4,7 @@ import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxFramesCollection; import funkin.data.animation.AnimationData; import funkin.data.IRegistryEntry; +import funkin.graphics.FunkinSprite; import funkin.data.notestyle.NoteStyleData; import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry; @@ -100,6 +101,11 @@ class NoteStyle implements IRegistryEntry function buildNoteFrames(force:Bool = false):FlxAtlasFrames { + if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath()))) + { + FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}'); + } + if (noteFrames != null && !force) return noteFrames; noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary()); diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 8b47eff2b..c20202245 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -185,9 +185,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements switch (dataProp.animType) { case 'packer': - propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath); + propSprite.loadPacker(dataProp.assetPath); default: // 'sparrow' - propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath); + propSprite.loadSparrow(dataProp.assetPath); } } else if (isSolidColor) @@ -209,7 +209,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements else { // Initalize static sprite. - propSprite.loadGraphic(Paths.image(dataProp.assetPath)); + propSprite.loadTexture(Paths.image(dataProp.assetPath)); // Disables calls to update() for a performance boost. propSprite.active = false; diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx index 2417cdf9a..9d37fe2c1 100644 --- a/source/funkin/ui/freeplay/DJBoyfriend.hx +++ b/source/funkin/ui/freeplay/DJBoyfriend.hx @@ -156,8 +156,6 @@ class DJBoyfriend extends FlxAtlasSprite function setupAnimations():Void { - // frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk')); - // animation.addByPrefix('intro', "boyfriend dj intro", 24, false); addOffset('boyfriend dj intro', 8, 3); diff --git a/source/funkin/ui/freeplay/FreeplayFlames.hx b/source/funkin/ui/freeplay/FreeplayFlames.hx index a116fb813..c20d85898 100644 --- a/source/funkin/ui/freeplay/FreeplayFlames.hx +++ b/source/funkin/ui/freeplay/FreeplayFlames.hx @@ -23,7 +23,7 @@ class FreeplayFlames extends FlxSpriteGroup { var flame:FlxSprite = new FlxSprite(flameX + (flameSpreadX * i), flameY + (flameSpreadY * i)); flame.frames = Paths.getSparrowAtlas("freeplay/freeplayFlame"); - flame.animation.addByPrefix("flame", "fire loop", FlxG.random.int(23, 25), false); + flame.animation.addByPrefix("flame", "fire loop full instance 1", FlxG.random.int(23, 25), false); flame.animation.play("flame"); flame.visible = false; flameCount = 0; diff --git a/source/funkin/ui/freeplay/FreeplayScore.hx b/source/funkin/ui/freeplay/FreeplayScore.hx index e266efca1..413b182e0 100644 --- a/source/funkin/ui/freeplay/FreeplayScore.hx +++ b/source/funkin/ui/freeplay/FreeplayScore.hx @@ -111,7 +111,7 @@ class ScoreNum extends FlxSprite for (i in 0...10) { var stringNum:String = numToString[i]; - animation.addByPrefix(stringNum, stringNum, 24, false); + animation.addByPrefix(stringNum, '$stringNum DIGITAL', 24, false); } this.digit = initDigit; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 39cab8759..669354345 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -226,17 +226,17 @@ class FreeplayState extends MusicBeatSubState trace(FlxG.camera.initialZoom); trace(FlxCamera.defaultZoom); - var pinkBack:FlxSprite = new FlxSprite().loadGraphic(Paths.image('freeplay/pinkBack')); + var pinkBack:FunkinSprite = FunkinSprite.create(Paths.image('freeplay/pinkBack')); pinkBack.color = 0xFFffd4e9; // sets it to pink! pinkBack.x -= pinkBack.width; FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); add(pinkBack); - var orangeBackShit:FlxSprite = new FlxSprite(84, 440).makeGraphic(Std.int(pinkBack.width), 75, 0xFFfeda00); + var orangeBackShit:FunkinSprite = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFfeda00); add(orangeBackShit); - var alsoOrangeLOL:FlxSprite = new FlxSprite(0, orangeBackShit.y).makeGraphic(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], @@ -462,7 +462,7 @@ class FreeplayState extends MusicBeatSubState var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); - fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 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(); diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index 86f443d1d..e893b0cec 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -44,11 +44,10 @@ class LoadingState extends MusicBeatState override function create():Void { - var bg:FlxSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d); + var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d); add(bg); - funkay = new FlxSprite(); - funkay.loadGraphic(Paths.image('funkay')); + funkay = FunkinSprite.create(Paths.image('funkay')); funkay.setGraphicSize(0, FlxG.height); funkay.updateHitbox(); add(funkay); @@ -209,6 +208,11 @@ class LoadingState extends MusicBeatState params.targetSong.cacheCharts(true); } + // TODO: This is a hack! Redo this later when we have a proper asset caching system. + FunkinSprite.cacheTexture(Paths.image('combo')); + FunkinSprite.cacheTexture(Paths.image('healthBar')); + FunkinSprite.cacheTexture(Paths.image('menuDesat')); + FlxG.switchState(playStateCtor); #end } From 1f67bd23bfd2388c0b9376db75b205b542dbd74f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 22 Feb 2024 19:01:26 -0500 Subject: [PATCH 031/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 14abe753a..cb0fbb56b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 14abe753a2af62879f7ebaf717331297ff7d7a81 +Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096 From c6e6772dcf6e7831c02615701f11e3b49d9ef93f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 19:07:15 -0500 Subject: [PATCH 032/172] Update assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index cb122820d..cb0fbb56b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit cb122820d4f533a542fe27875202d57294600bbc +Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096 From 01ed1730f4338ea99ad6c472689780867a91cff1 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 20:56:41 -0500 Subject: [PATCH 033/172] Fix some issues with cutscenes. --- Project.xml | 2 +- source/funkin/Paths.hx | 14 ++++++++++++++ source/funkin/play/cutscene/VideoCutscene.hx | 19 +++++++++++++++---- source/funkin/ui/freeplay/FreeplayState.hx | 1 + 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Project.xml b/Project.xml index c58153575..c368dacef 100644 --- a/Project.xml +++ b/Project.xml @@ -108,7 +108,7 @@ - + diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index e0212e573..6006939be 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -16,6 +16,20 @@ class Paths currentLevel = name.toLowerCase(); } + public static function stripLibrary(path:String):String + { + var parts = path.split(':'); + if (parts.length < 2) return path; + return parts[1]; + } + + public static function getLibrary(path:String):String + { + var parts = path.split(':'); + if (parts.length < 2) return "preload"; + return parts[0]; + } + static function getPath(file:String, type:AssetType, library:Null) { if (library != null) return getLibraryPath(file, library); diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 934919b65..df31accb2 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -36,6 +36,8 @@ class VideoCutscene return; } + var rawFilePath = Paths.stripLibrary(filePath); + // Trigger the cutscene. Don't play the song in the background. PlayState.instance.isInCutscene = true; PlayState.instance.camHUD.visible = false; @@ -49,10 +51,10 @@ class VideoCutscene #if html5 playVideoHTML5(filePath); - #end - - #if hxCodec - playVideoNative(filePath); + #elseif hxCodec + playVideoNative(rawFilePath); + #else + throw "No video support for this platform!"; #end } @@ -110,6 +112,15 @@ class VideoCutscene PlayState.instance.refresh(); vid.play(filePath, false); + + // Resize videos bigger or smaller than the screen. + vid.bitmap.onTextureSetup.add(() -> { + vid.setGraphicSize(FlxG.width, FlxG.height); + vid.updateHitbox(); + vid.x = 0; + vid.y = 0; + // vid.scale.set(0.5, 0.5); + }); } else { diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 669354345..e4a6b96d8 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -7,6 +7,7 @@ import flixel.addons.ui.FlxInputText; import flixel.FlxCamera; import flixel.FlxGame; import flixel.FlxSprite; +import funkin.graphics.FunkinSprite; import flixel.FlxState; import flixel.group.FlxGroup; import flixel.group.FlxGroup.FlxTypedGroup; From 50e208cf436445c26f9399b7dd44ad842930d7b8 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 20:56:50 -0500 Subject: [PATCH 034/172] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index ffbf73c76..24bf8d3c1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit ffbf73c76860a2747eb11eeed14099e186700956 +Subproject commit 24bf8d3c13532ee06923818d04cd44699d6be952 From ddfb0c6a61fe445a880229fda99b8c2c6700d662 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 23:37:52 -0500 Subject: [PATCH 035/172] Working on more asset caching improvements. --- source/funkin/graphics/FunkinSprite.hx | 61 +++++++++++++++++++ source/funkin/play/components/PopUpStuff.hx | 9 ++- source/funkin/ui/transition/LoadingState.hx | 26 +++++++- .../funkin/ui/transition/StickerSubState.hx | 5 +- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index 6c8ce1308..f47b4138a 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -11,6 +11,18 @@ import flixel.graphics.FlxGraphic; */ class FunkinSprite extends FlxSprite { + /** + * An internal list of all the textures cached with `cacheTexture`. + * This excludes any temporary textures like those from `FlxText` or `makeSolidColor`. + */ + static var currentCachedTextures:Map = []; + + /** + * An internal list of textures that were cached in the previous state. + * We don't know whether we want to keep them cached or not. + */ + static var previousCachedTextures:Map = []; + /** * @param x Starting X position * @param y Starting Y position @@ -113,6 +125,19 @@ class FunkinSprite extends FlxSprite public static function cacheTexture(key:String):Void { + // We don't want to cache the same texture twice. + if (currentCachedTextures.exists(key)) return; + + if (previousCachedTextures.exists(key)) + { + // Move the graphic from the previous cache to the current cache. + var graphic = previousCachedTextures.get(key); + previousCachedTextures.remove(key); + currentCachedTextures.set(key, graphic); + return; + } + + // Else, texture is currently uncached. var graphic = flixel.graphics.FlxGraphic.fromAssetKey(key, false, null, true); if (graphic == null) { @@ -121,6 +146,8 @@ class FunkinSprite extends FlxSprite else { trace('Successfully cached graphic: $key'); + graphic.persist = true; + currentCachedTextures.set(key, graphic); } } @@ -134,6 +161,40 @@ class FunkinSprite extends FlxSprite cacheTexture(Paths.image(key)); } + /** + * Call this, then `cacheTexture` to keep the textures we still need, then `purgeCache` to remove the textures that we won't be using anymore. + */ + public static function preparePurgeCache():Void + { + previousCachedTextures = currentCachedTextures; + currentCachedTextures = []; + } + + public static function purgeCache():Void + { + // Everything that is in previousCachedTextures but not in currentCachedTextures should be destroyed. + for (graphicKey in previousCachedTextures.keys()) + { + var graphic = previousCachedTextures.get(graphicKey); + FlxG.bitmap.remove(graphic); + graphic.destroy(); + previousCachedTextures.remove(graphicKey); + } + } + + static function isGraphicCached(graphic:FlxGraphic):Bool + { + if (graphic == null) return false; + var result = FlxG.bitmap.get(graphic.key); + if (result == null) return false; + if (result != graphic) + { + FlxG.log.warn('Cached graphic does not match original: ${graphic.key}'); + return false; + } + return true; + } + /** * Acts similarly to `makeGraphic`, but with improved memory usage, * at the expense of not being able to paint onto the resulting sprite. diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 593a7333e..88ffa468c 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -15,7 +15,9 @@ class PopUpStuff extends FlxTypedGroup public function displayRating(daRating:String) { + #if sys var perfStart:Float = Sys.time(); + #end if (daRating == null) daRating = "good"; @@ -64,14 +66,17 @@ class PopUpStuff extends FlxTypedGroup startDelay: Conductor.instance.beatLengthMs * 0.001 }); + #if sys var perfEnd:Float = Sys.time(); - trace("displayRating took: " + (perfEnd - perfStart)); + #end } public function displayCombo(?combo:Int = 0):Int { + #if sys var perfStart:Float = Sys.time(); + #end if (combo == null) combo = 0; @@ -170,8 +175,10 @@ class PopUpStuff extends FlxTypedGroup daLoop++; } + #if sys var perfEnd:Float = Sys.time(); trace("displayCombo took: " + (perfEnd - perfStart)); + #end return combo; } diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index e893b0cec..3a41340a6 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -3,6 +3,7 @@ package funkin.ui.transition; import flixel.FlxSprite; import flixel.math.FlxMath; import flixel.tweens.FlxEase; +import funkin.graphics.FunkinSprite; import flixel.tweens.FlxTween; import flixel.util.FlxTimer; import funkin.graphics.shaders.ScreenWipeShader; @@ -208,10 +209,31 @@ class LoadingState extends MusicBeatState params.targetSong.cacheCharts(true); } - // TODO: This is a hack! Redo this later when we have a proper asset caching system. + // TODO: This section is a hack! Redo this later when we have a proper asset caching system. + FunkinSprite.preparePurgeCache(); FunkinSprite.cacheTexture(Paths.image('combo')); FunkinSprite.cacheTexture(Paths.image('healthBar')); FunkinSprite.cacheTexture(Paths.image('menuDesat')); + FunkinSprite.cacheTexture(Paths.image('combo')); + FunkinSprite.cacheTexture(Paths.image('num0')); + FunkinSprite.cacheTexture(Paths.image('num1')); + FunkinSprite.cacheTexture(Paths.image('num2')); + FunkinSprite.cacheTexture(Paths.image('num3')); + FunkinSprite.cacheTexture(Paths.image('num4')); + FunkinSprite.cacheTexture(Paths.image('num5')); + FunkinSprite.cacheTexture(Paths.image('num6')); + FunkinSprite.cacheTexture(Paths.image('num7')); + FunkinSprite.cacheTexture(Paths.image('num8')); + FunkinSprite.cacheTexture(Paths.image('num9')); + FunkinSprite.cacheTexture(Paths.image('ready', 'shared')); + FunkinSprite.cacheTexture(Paths.image('set', 'shared')); + FunkinSprite.cacheTexture(Paths.image('go', 'shared')); + FunkinSprite.cacheTexture(Paths.image('sick', 'shared')); + FunkinSprite.cacheTexture(Paths.image('good', 'shared')); + FunkinSprite.cacheTexture(Paths.image('bad', 'shared')); + FunkinSprite.cacheTexture(Paths.image('shit', 'shared')); + FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: Remove + FunkinSprite.purgeCache(); FlxG.switchState(playStateCtor); #end @@ -358,7 +380,7 @@ class MultiCallback public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2) { - var screenShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("shaderTransitionStuff/coolDots")); + var screenShit:FunkinSprite = FunkinSprite.create(Paths.image("shaderTransitionStuff/coolDots")); var screenWipeShit:ScreenWipeShader = new ScreenWipeShader(); screenWipeShit.funnyShit.input = screenShit.pixels; diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx index e94eed7d5..fda7d1d5d 100644 --- a/source/funkin/ui/transition/StickerSubState.hx +++ b/source/funkin/ui/transition/StickerSubState.hx @@ -3,6 +3,7 @@ package funkin.ui.transition; import flixel.FlxSprite; import haxe.Json; import lime.utils.Assets; +import funkin.graphics.FunkinSprite; // import flxtyped group import funkin.ui.MusicBeatSubState; import funkin.ui.story.StoryMenuState; @@ -301,14 +302,14 @@ class StickerSubState extends MusicBeatSubState } } -class StickerSprite extends FlxSprite +class StickerSprite extends FunkinSprite { public var timing:Float = 0; public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void { super(x, y); - loadGraphic(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName)); + loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName)); updateHitbox(); scrollFactor.set(); } From e349b0bb49775fac832b4d094eec6c7f19f3747f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 23 Feb 2024 00:16:11 -0500 Subject: [PATCH 036/172] New pre-caching techniques should reduce stuttering on Weekend 1. --- source/funkin/play/PlayState.hx | 5 ----- source/funkin/play/notes/NoteSplash.hx | 3 +++ source/funkin/play/notes/notestyle/NoteStyle.hx | 5 +++-- source/funkin/ui/transition/LoadingState.hx | 13 ++++++++++++- source/funkin/ui/transition/StickerSubState.hx | 4 ++++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index bde68461b..934e0b403 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2281,11 +2281,6 @@ class PlayState extends MusicBeatSubState if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible; #end - // Eject button - if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); - - if (FlxG.keys.justPressed.F5) debug_refreshModules(); - // Open the stage editor overlaying the current state. if (controls.DEBUG_STAGE) { diff --git a/source/funkin/play/notes/NoteSplash.hx b/source/funkin/play/notes/NoteSplash.hx index 0ff8076c8..2411e5615 100644 --- a/source/funkin/play/notes/NoteSplash.hx +++ b/source/funkin/play/notes/NoteSplash.hx @@ -35,6 +35,7 @@ class NoteSplash extends FlxSprite */ function setup():Void { + if (frameCollection?.parent?.isDestroyed ?? false) frameCollection = null; if (frameCollection == null) preloadFrames(); this.frames = frameCollection; @@ -75,6 +76,8 @@ class NoteSplash extends FlxSprite this.playAnimation('splash${variant}Right'); } + if (animation.curAnim == null) return; + // Vary the speed of the animation a bit. animation.curAnim.frameRate = FRAMERATE_DEFAULT + FlxG.random.int(-FRAMERATE_VARIANCE, FRAMERATE_VARIANCE); diff --git a/source/funkin/play/notes/notestyle/NoteStyle.hx b/source/funkin/play/notes/notestyle/NoteStyle.hx index f560396e8..d0cc09f6a 100644 --- a/source/funkin/play/notes/notestyle/NoteStyle.hx +++ b/source/funkin/play/notes/notestyle/NoteStyle.hx @@ -106,6 +106,9 @@ class NoteStyle implements IRegistryEntry FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}'); } + // Purge the note frames if the cached atlas is invalid. + if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null; + if (noteFrames != null && !force) return noteFrames; noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary()); @@ -115,8 +118,6 @@ class NoteStyle implements IRegistryEntry throw 'Could not load note frames for note style: $id'; } - noteFrames.parent.persist = true; - return noteFrames; } diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index 3a41340a6..5f755872f 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -225,6 +225,10 @@ class LoadingState extends MusicBeatState FunkinSprite.cacheTexture(Paths.image('num7')); FunkinSprite.cacheTexture(Paths.image('num8')); FunkinSprite.cacheTexture(Paths.image('num9')); + FunkinSprite.cacheTexture(Paths.image('notes', 'shared')); + FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared')); + FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared')); + FunkinSprite.cacheTexture(Paths.image('NOTE_hold_assets')); FunkinSprite.cacheTexture(Paths.image('ready', 'shared')); FunkinSprite.cacheTexture(Paths.image('set', 'shared')); FunkinSprite.cacheTexture(Paths.image('go', 'shared')); @@ -232,7 +236,14 @@ class LoadingState extends MusicBeatState FunkinSprite.cacheTexture(Paths.image('good', 'shared')); FunkinSprite.cacheTexture(Paths.image('bad', 'shared')); FunkinSprite.cacheTexture(Paths.image('shit', 'shared')); - FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: Remove + FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this + + // FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above! + // FunkinSprite.cacheAllCharacterTextures(player) + // FunkinSprite.cacheAllCharacterTextures(girlfriend) + // FunkinSprite.cacheAllCharacterTextures(opponent) + // FunkinSprite.cacheAllStageTextures(stage) + FunkinSprite.purgeCache(); FlxG.switchState(playStateCtor); diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx index fda7d1d5d..40fce6f7d 100644 --- a/source/funkin/ui/transition/StickerSubState.hx +++ b/source/funkin/ui/transition/StickerSubState.hx @@ -246,6 +246,10 @@ class StickerSubState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; + // TODO: Rework this asset caching stuff + FunkinSprite.preparePurgeCache(); + FunkinSprite.purgeCache(); + // I think this grabs the screen and puts it under the stickers? // Leaving this commented out rather than stripping it out because it's cool... /* From eb4a21f2d5eca92ebdcb2e107c976e41002da32a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 23 Feb 2024 03:16:41 -0500 Subject: [PATCH 037/172] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 499bb17c1..7d031153c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 499bb17c15620ca9140f8254029f05bb7c96f1f4 +Subproject commit 7d031153cf073e9d49ab59d7c72956cf4a68bcda From fb9fd572102466cd4a1c5829e6af3197c107ee2e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 23 Feb 2024 03:16:51 -0500 Subject: [PATCH 038/172] Improve json parsing error handling. --- source/funkin/data/BaseRegistry.hx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx index 0ccbe2f18..62a7eb0f7 100644 --- a/source/funkin/data/BaseRegistry.hx +++ b/source/funkin/data/BaseRegistry.hx @@ -61,7 +61,16 @@ abstract class BaseRegistry & Constructible = null; + try + { + entry = createScriptedEntry(entryCls); + } + catch (e:Dynamic) + { + log('Failed to create scripted entry (${entryCls})'); + continue; + } if (entry != null) { @@ -196,6 +205,11 @@ abstract class BaseRegistry & Constructible { + if (version == null) + { + throw '[${registryId}] Entry ${id} could not be JSON-parsed or does not have a parseable version.'; + } + // If a version rule is not specified, do not check against it. if (versionRule == null || VersionUtil.validateVersion(version, versionRule)) { From 4168962fecd4129c5335146b9c1b64222ab1c2b2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 23 Feb 2024 03:19:34 -0500 Subject: [PATCH 039/172] Revert "Fix `FunkinSound` not resuming after focus" This reverts commit d6b3e2a9cf4aab093a129aef13ebd93294d6f929. --- source/funkin/audio/FunkinSound.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 1059185cf..ba157ed8e 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -174,7 +174,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocus():Void { - if (!_alreadyPaused) + if (!_alreadyPaused && this._shouldPlay) { resume(); } From 66c91d8b3eb7f52ba66437d984a39b0ef4d7907f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 23 Feb 2024 03:23:00 -0500 Subject: [PATCH 040/172] Sort the chart editor note kind dropdown. --- source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index b26082f98..26015161b 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -147,6 +147,8 @@ class ChartEditorDropdowns dropDown.dataSource.add(value); } + dropDown.dataSource.sort('id', ASCENDING); + return returnValue; } From 85cb20e23daf71cf9e0ba260ae7b6e639f8c2914 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 26 Feb 2024 15:42:56 -0500 Subject: [PATCH 041/172] renames urls in gitmodules to current updated names of the repos --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 17c3cc026..903904103 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,10 @@ [submodule "assets"] path = assets - url = https://github.com/FunkinCrew/Funkin-history-rewrite-assets + url = https://github.com/FunkinCrew/Funkin-Assets-secret branch = master update = merge [submodule "art"] path = art - url = https://github.com/FunkinCrew/Funkin-history-rewrite-art + url = https://github.com/FunkinCrew/Funkin-Art-secret branch = master update = merge From 90360de0d01c72b552a87096fdb0549ebc76d443 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 26 Feb 2024 19:03:04 -0500 Subject: [PATCH 042/172] Working Blazin cutscene and fixed time travel --- assets | 2 +- source/funkin/data/event/SongEventRegistry.hx | 23 ++++ source/funkin/play/PlayState.hx | 124 ++++++++++++------ source/funkin/play/cutscene/VideoCutscene.hx | 50 ++++++- 4 files changed, 155 insertions(+), 44 deletions(-) diff --git a/assets b/assets index 7d031153c..1b0e09750 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7d031153cf073e9d49ab59d7c72956cf4a68bcda +Subproject commit 1b0e097508ec694012043a4c059885b05a569e2a diff --git a/source/funkin/data/event/SongEventRegistry.hx b/source/funkin/data/event/SongEventRegistry.hx index 8732e3b98..9b0163557 100644 --- a/source/funkin/data/event/SongEventRegistry.hx +++ b/source/funkin/data/event/SongEventRegistry.hx @@ -148,6 +148,29 @@ class SongEventRegistry }); } + /** + * The currentTime has jumped far ahead or back. + * If we moved back in time, we need to reset all the events in that space. + * If we moved forward in time, we need to skip all the events in that space. + */ + public static function handleSkippedEvents(events:Array, currentTime:Float):Void + { + for (event in events) + { + // Deactivate future events. + if (event.time > currentTime) + { + event.activated = false; + } + + // Skip past events. + if (event.time < currentTime) + { + event.activated = true; + } + } + } + /** * Reset activation of all the provided events. */ diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 934e0b403..5bbf83e17 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -987,8 +987,21 @@ class PlayState extends MusicBeatSubState } } + processSongEvents(); + + // Handle keybinds. + processInputQueue(); + if (!isInCutscene && !disableKeys) debugKeyShit(); + if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed); + + // Moving notes into position is now done by Strumline.update(). + processNotes(elapsed); + } + + function processSongEvents():Void + { // Query and activate song events. - // TODO: Check that these work even when songPosition is less than 0. + // TODO: Check that these work appropriately even when songPosition is less than 0, to play events during countdown. if (songEvents != null && songEvents.length > 0) { var songEventsToActivate:Array = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition); @@ -998,8 +1011,9 @@ class PlayState extends MusicBeatSubState trace('Found ${songEventsToActivate.length} event(s) to activate.'); for (event in songEventsToActivate) { - // If an event is trying to play, but it's over 5 seconds old, skip it. - if (event.time - Conductor.instance.songPosition < -5000) + // If an event is trying to play, but it's over 1 second old, skip it. + var eventAge:Float = Conductor.instance.songPosition - event.time; + if (eventAge > 1000) { event.activated = true; continue; @@ -1015,14 +1029,6 @@ class PlayState extends MusicBeatSubState } } } - - // Handle keybinds. - processInputQueue(); - if (!isInCutscene && !disableKeys) debugKeyShit(); - if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed); - - // Moving notes into position is now done by Strumline.update(). - processNotes(elapsed); } public override function dispatchEvent(event:ScriptEvent):Void @@ -1761,7 +1767,7 @@ class PlayState extends MusicBeatSubState currentChart.playInst(1.0, false); } - FlxG.sound.music.onComplete = endSong; + FlxG.sound.music.onComplete = endSong.bind(false); // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; @@ -2041,6 +2047,7 @@ class PlayState extends MusicBeatSubState } } + // Respawns notes that were b playerStrumline.handleSkippedNotes(); opponentStrumline.handleSkippedNotes(); } @@ -2303,7 +2310,7 @@ class PlayState extends MusicBeatSubState #if (debug || FORCE_DEBUG_VERSION) // 1: End the song immediately. - if (FlxG.keys.justPressed.ONE) endSong(); + if (FlxG.keys.justPressed.ONE) endSong(true); // 2: Gain 10% health. if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX; @@ -2497,16 +2504,35 @@ class PlayState extends MusicBeatSubState if (skipHeldTimer >= 1.5) { - VideoCutscene.finishVideo(); + skipVideoCutscene(); } } /** - * End the song. Handle saving high scores and transitioning to the results screen. + * Handle logic for actually skipping a video cutscene after it has been held. */ - function endSong():Void + function skipVideoCutscene():Void { - dispatchEvent(new ScriptEvent(SONG_END)); + VideoCutscene.finishVideo(); + } + + /** + * End the song. Handle saving high scores and transitioning to the results screen. + * + * Broadcasts an `onSongEnd` event, which can be cancelled to prevent the song from ending (for a cutscene or something). + * Remember to call `endSong` again when the song should actually end! + * @param rightGoddamnNow If true, don't play the fancy animation where you zoom onto Girlfriend. Used after a cutscene. + */ + public function endSong(rightGoddamnNow:Bool = false):Void + { + FlxG.sound.music.volume = 0; + vocals.volume = 0; + mayPauseGame = false; + + // Check if any events want to prevent the song from ending. + var event = new ScriptEvent(SONG_END, true); + dispatchEvent(event); + if (event.eventCanceled) return; #if sys // spitter for ravy, teehee!! @@ -2516,9 +2542,7 @@ class PlayState extends MusicBeatSubState #end deathCounter = 0; - mayPauseGame = false; - FlxG.sound.music.volume = 0; - vocals.volume = 0; + if (currentSong != null && currentSong.validScore) { // crackhead double thingie, sets whether was new highscore, AND saves the song! @@ -2605,7 +2629,14 @@ class PlayState extends MusicBeatSubState } else { - moveToResultsScreen(); + if (rightGoddamnNow) + { + moveToResultsScreen(); + } + else + { + zoomIntoResultsScreen(); + } } } else @@ -2663,7 +2694,14 @@ class PlayState extends MusicBeatSubState } else { - moveToResultsScreen(); + if (rightGoddamnNow) + { + moveToResultsScreen(); + } + else + { + zoomIntoResultsScreen(); + } } } } @@ -2717,9 +2755,9 @@ class PlayState extends MusicBeatSubState } /** - * Play the camera zoom animation and move to the results screen. + * Play the camera zoom animation and then move to the results screen once it's done. */ - function moveToResultsScreen():Void + function zoomIntoResultsScreen():Void { trace('WENT TO RESULTS SCREEN!'); @@ -2773,22 +2811,30 @@ class PlayState extends MusicBeatSubState { ease: FlxEase.expoIn, onComplete: function(_) { - persistentUpdate = false; - vocals.stop(); - camHUD.alpha = 1; - var res:ResultState = new ResultState( - { - storyMode: PlayStatePlaylist.isStoryMode, - title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'), - tallies: Highscore.tallies, - }); - res.camera = camHUD; - openSubState(res); + moveToResultsScreen(); } }); }); } + /** + * Move to the results screen right goddamn now. + */ + function moveToResultsScreen():Void + { + persistentUpdate = false; + vocals.stop(); + camHUD.alpha = 1; + var res:ResultState = new ResultState( + { + storyMode: PlayStatePlaylist.isStoryMode, + title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'), + tallies: Highscore.tallies, + }); + res.camera = camHUD; + openSubState(res); + } + /** * Pauses music and vocals easily. */ @@ -2818,14 +2864,18 @@ class PlayState extends MusicBeatSubState */ function changeSection(sections:Int):Void { - FlxG.sound.music.pause(); + // FlxG.sound.music.pause(); - var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections); + var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections); var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps); + // Don't go back in time to before the song started. + targetTimeMs = Math.max(0, targetTimeMs); + FlxG.sound.music.time = targetTimeMs; handleSkippedNotes(); + SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition); // regenNoteData(FlxG.sound.music.time); Conductor.instance.update(FlxG.sound.music.time); diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index df31accb2..75e69bf04 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite; class VideoCutscene { static var blackScreen:FlxSprite; + static var cutsceneType:CutsceneType; + + #if html5 + static var vid:FlxVideo; + #end + #if hxCodec + static var vid:FlxVideoSprite; + #end /** * Play a video cutscene. * TODO: Currently this is hardcoded to start the countdown after the video is done. * @param path The path to the video file. Use Paths.file(path) to get the correct path. + * @param cutseneType The type of cutscene to play, determines what the game does after. Defaults to `CutsceneType.STARTING`. */ - public static function play(filePath:String):Void + public static function play(filePath:String, ?cutsceneType:CutsceneType = STARTING):Void { if (PlayState.instance == null) return; @@ -49,6 +58,8 @@ class VideoCutscene blackScreen.cameras = [PlayState.instance.camCutscene]; PlayState.instance.add(blackScreen); + VideoCutscene.cutsceneType = cutsceneType; + #if html5 playVideoHTML5(filePath); #elseif hxCodec @@ -68,8 +79,6 @@ class VideoCutscene } #if html5 - static var vid:FlxVideo; - static function playVideoHTML5(filePath:String):Void { // Video displays OVER the FlxState. @@ -94,8 +103,6 @@ class VideoCutscene #end #if hxCodec - static var vid:FlxVideoSprite; - static function playVideoNative(filePath:String):Void { // Video displays OVER the FlxState. @@ -129,10 +136,17 @@ class VideoCutscene } #end + /** + * Finish the active video cutscene. Done when the video is finished or when the player skips the cutscene. + * @param transitionTime The duration of the transition to the next state. Defaults to 0.5 seconds (this time is always used when cancelling the video). + * @param finishCutscene The callback to call when the transition is finished. + */ public static function finishVideo(?transitionTime:Float = 0.5):Void { trace('ALERT: Finish video cutscene called!'); + var cutsceneType:CutsceneType = VideoCutscene.cutsceneType; + #if html5 if (vid != null) { @@ -168,8 +182,32 @@ class VideoCutscene { ease: FlxEase.quadInOut, onComplete: function(twn:FlxTween) { - PlayState.instance.startCountdown(); + onCutsceneFinish(cutsceneType); } }); } + + /** + * The default callback used when a cutscene is finished. + * You can specify your own callback when calling `VideoCutscene#play()`. + */ + static function onCutsceneFinish(cutsceneType:CutsceneType):Void + { + switch (cutsceneType) + { + case CutsceneType.STARTING: + PlayState.instance.startCountdown(); + case CutsceneType.ENDING: + PlayState.instance.endSong(true); // true = right goddamn now + case CutsceneType.MIDSONG: + throw "Not implemented!"; + } + } +} + +enum CutsceneType +{ + STARTING; // The default cutscene type. Starts the countdown after the video is done. + MIDSONG; // TODO: Implement this! + ENDING; // Ends the song after the video is done. } From d8fdf45ddac201c68cde476cf30050ab14146966 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 26 Feb 2024 21:07:22 -0500 Subject: [PATCH 043/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 1b0e09750..f8c259584 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1b0e097508ec694012043a4c059885b05a569e2a +Subproject commit f8c2595844eff9375b522f117bfdadbdc6728c49 From 12a48b9b86df7cf7265ddd9d7834440cad1f351c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 26 Feb 2024 23:18:56 -0500 Subject: [PATCH 044/172] Work in progress on revamped, more extensible pause menu. --- .../tweens/misc/BackgroundColorTween.hx | 66 ++ source/funkin/import.hx | 1 + source/funkin/play/PauseSubState.hx | 574 ++++++++++-------- source/funkin/util/tools/FlxTweenTools.hx | 25 + 4 files changed, 408 insertions(+), 258 deletions(-) create mode 100644 source/flixel/tweens/misc/BackgroundColorTween.hx create mode 100644 source/funkin/util/tools/FlxTweenTools.hx diff --git a/source/flixel/tweens/misc/BackgroundColorTween.hx b/source/flixel/tweens/misc/BackgroundColorTween.hx new file mode 100644 index 000000000..b8b2d993f --- /dev/null +++ b/source/flixel/tweens/misc/BackgroundColorTween.hx @@ -0,0 +1,66 @@ +package flixel.tweens.misc; + +/** + * Tweens the background color of a state. + * Tweens the red, green, blue, and/or alpha values of the color. + * + * @see `flixel.tweens.misc.ColorTween` for something that operates on sprites! + */ +class BackgroundColorTween extends FlxTween +{ + public var color(default, null):FlxColor; + + var startColor:FlxColor; + var endColor:FlxColor; + + /** + * State object whose color to tween + */ + public var targetState(default, null):FlxState; + + /** + * Clean up references + */ + override public function destroy() + { + super.destroy(); + targetState = null; + } + + /** + * Tweens the color to a new color and an alpha to a new alpha. + * + * @param duration Duration of the tween. + * @param fromColor Start color. + * @param toColor End color. + * @param targetState Optional sprite object whose color to tween. + * @return The tween for chaining. + */ + public function tween(duration:Float, fromColor:FlxColor, toColor:FlxColor, ?targetState:FlxSprite):ColorTween + { + this.color = startColor = fromColor; + this.endColor = toColor; + this.duration = duration; + this.targetState = targetState; + this.start(); + return this; + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + color = FlxColor.interpolate(startColor, endColor, scale); + + if (targetState != null) + { + targetState.bgColor = color; + // Alpha should apply inherently. + // targetState.alpha = color.alphaFloat; + } + } + + override function isTweenOf(object:Dynamic, ?field:String):Bool + { + return targetState == object && (field == null || field == "color"); + } +} diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 02055d4ed..23b588044 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -15,6 +15,7 @@ using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.DynamicTools; using funkin.util.tools.FloatTools; +using funkin.util.tools.FlxTweenTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; using funkin.util.tools.IteratorTools; diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 023b8d5be..8300ac936 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -1,312 +1,370 @@ package funkin.play; -import funkin.play.PlayStatePlaylist; -import flixel.FlxSprite; -import flixel.addons.transition.FlxTransitionableState; -import flixel.group.FlxGroup.FlxTypedGroup; -import funkin.ui.MusicBeatSubState; -import flixel.sound.FlxSound; -import flixel.text.FlxText; -import flixel.tweens.FlxEase; -import flixel.tweens.FlxTween; -import flixel.util.FlxColor; -import funkin.play.PlayState; -import funkin.data.song.SongRegistry; -import funkin.ui.Alphabet; +typedef PauseSubStateParams = +{ + ?mode:PauseMode, +}; +/** + * The menu displayed when the Play State is paused. + */ class PauseSubState extends MusicBeatSubState { - var grpMenuShit:FlxTypedGroup; - - final pauseOptionsBase:Array = [ - 'Resume', - 'Restart Song', - 'Change Difficulty', - 'Toggle Practice Mode', - 'Exit to Menu' + static final PAUSE_MENU_ENTRIES_STANDARD = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Song', callback: restartPlayState}, + {text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)}, + {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> (PlayState.instance?.isPracticeMode ?? true)}, + {text: 'Exit to Menu', callback: quitToMenu}, ]; - final pauseOptionsCharting:Array = ['Resume', 'Restart Song', 'Exit to Chart Editor']; - final pauseOptionsDifficultyBase:Array = ['BACK']; + static final PAUSE_MENU_ENTRIES_CHARTING = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Song', callback: restartPlayState}, + {text: 'Return to Chart Editor', callback: quitToChartEditor}, + ]; - var pauseOptionsDifficulty:Array = []; // AUTO-POPULATED + static final PAUSE_MENU_ENTRIES_DIFFICULTY = [ + {text: 'Back', callback: switchMode.bind(_, Standard)} + // Other entries are added dynamically. + ]; - var menuItems:Array = []; - var curSelected:Int = 0; + static final PAUSE_MENU_ENTRIES_CUTSCENE = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Cutscene', callback: restartCutscene}, + {text: 'Skip Cutscene', callback: skipCutscene}, + {text: 'Exit to Menu', callback: quitToMenu}, + ]; - var pauseMusic:FlxSound; + static final MUSIC_FADE_IN_TIME:Float = 50; + static final MUSIC_FINAL_VOLUME:Float = 0.5; - var practiceText:FlxText; + public static var musicSuffix:String = ''; - public var exitingToMenu:Bool = false; + // Status + var menuEntries:Array; + var currentEntry:Int = 0; + var currentMode:PauseMode; + var allowInput:Bool = true; - var bg:FlxSprite; - var metaDataGrp:FlxTypedGroup; + // Graphics + var metadata:FlxTypedGroup; + var metadataPractice:FlxText; + var menuEntryText:FlxTypedGroup; - var isChartingMode:Bool; + // Audio + var pauseMusic:FunkinSound; - public function new(isChartingMode:Bool = false) + public function new(?params:PauseSubStateParams) { super(); + this.currentMode = params?.mode ?? PauseMode.Standard; - this.isChartingMode = isChartingMode; - - menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; - var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); - trace('DIFFICULTIES: ${difficultiesInVariation}'); - - pauseOptionsDifficulty = difficultiesInVariation.map(function(item:String):String { - return item.toUpperCase(); - }).concat(pauseOptionsDifficultyBase); - - if (PlayStatePlaylist.campaignId == 'week6') - { - pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true); - } - else - { - pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true); - } - pauseMusic.volume = 0; - pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); - - FlxG.sound.list.add(pauseMusic); - - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - bg.alpha = 0; - bg.scrollFactor.set(); - add(bg); - - metaDataGrp = new FlxTypedGroup(); - add(metaDataGrp); - - var levelInfo:FlxText = new FlxText(20, 15, 0, '', 32); - if (PlayState.instance.currentChart != null) - { - levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; - } - levelInfo.scrollFactor.set(); - levelInfo.setFormat(Paths.font('vcr.ttf'), 32); - levelInfo.updateHitbox(); - metaDataGrp.add(levelInfo); - - var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, '', 32); - levelDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); - levelDifficulty.scrollFactor.set(); - levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32); - levelDifficulty.updateHitbox(); - metaDataGrp.add(levelDifficulty); - - var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, '', 32); - deathCounter.text = 'Blue balled: ${PlayState.instance.deathCounter}'; - FlxG.watch.addQuick('totalNotesHit', Highscore.tallies.totalNotesHit); - FlxG.watch.addQuick('totalNotes', Highscore.tallies.totalNotes); - deathCounter.scrollFactor.set(); - deathCounter.setFormat(Paths.font('vcr.ttf'), 32); - deathCounter.updateHitbox(); - metaDataGrp.add(deathCounter); - - practiceText = new FlxText(20, 15 + 64 + 32, 0, 'PRACTICE MODE', 32); - practiceText.scrollFactor.set(); - practiceText.setFormat(Paths.font('vcr.ttf'), 32); - practiceText.updateHitbox(); - practiceText.x = FlxG.width - (practiceText.width + 20); - practiceText.visible = PlayState.instance.isPracticeMode; - metaDataGrp.add(practiceText); - - levelDifficulty.alpha = 0; - levelInfo.alpha = 0; - deathCounter.alpha = 0; - - levelInfo.x = FlxG.width - (levelInfo.width + 20); - levelDifficulty.x = FlxG.width - (levelDifficulty.width + 20); - deathCounter.x = FlxG.width - (deathCounter.width + 20); - - FlxTween.tween(bg, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); - FlxTween.tween(levelInfo, {alpha: 1, y: 20}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.3}); - FlxTween.tween(levelDifficulty, {alpha: 1, y: levelDifficulty.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.5}); - FlxTween.tween(deathCounter, {alpha: 1, y: deathCounter.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.7}); - - grpMenuShit = new FlxTypedGroup(); - add(grpMenuShit); - - regenMenu(); - - // cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + this.bgColor = FlxColor.TRANSPARENT; // Transparent, fades into black later. } - function regenMenu():Void + public override function create():Void { - while (grpMenuShit.members.length > 0) - { - grpMenuShit.remove(grpMenuShit.members[0], true); - } + super.create(); - for (i in 0...menuItems.length) - { - var songText:Alphabet = new Alphabet(0, (70 * i) + 30, menuItems[i], true, false); - songText.isMenuItem = true; - songText.targetY = i; - grpMenuShit.add(songText); - } + startPauseMusic(); - curSelected = 0; - changeSelection(); + buildMetadata(); + + transitionIn(); } - override function update(elapsed:Float):Void + public override function update(elapsed:Float):Void { - if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed; - super.update(elapsed); handleInputs(); } + function startPauseMusic():Void + { + pauseMusic = FunkinSound.load(Paths.music('breakfast-pixel'), true, true); + + // Start playing at a random point in the song. + pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); + pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); + } + + /** + * Render the metadata in the top right. + */ + function buildMetadata():Void + { + metadata = new FlxTypedGroup(); + add(metadata); + + var metadataSong:FlxText = new FlxText(20, 15, 0, 'Song Name - Artist'); + metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + if (PlayState.instance?.currentChart != null) + { + metadataSong.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; + } + metadata.add(metadataSong); + + var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, 0, 'Difficulty'); + metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + if (PlayState.instance?.currentDifficulty != null) + { + metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); + } + metadata.add(metadataDifficulty); + + var metadataDeaths:FlxText = new FlxText(20, 15 + 64, 0, '0 Blue Balls'); + metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + metadata.add(metadataDeaths); + + metadataPractice = new FlxText(20, 15 + 96, 0, 'PRACTICE MODE'); + metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + metadata.add(metadataPractice); + } + + function regenerateMenu(?targetMode:PauseMode):Void + { + var previousMode:PauseMode = this.currentMode; + this.currentMode = targetMode ?? this.currentMode; + this.currentEntry = 0; + + menuEntryText.clear(); + + // Choose the correct menu entries. + switch (this.currentMode) + { + case PauseMode.Standard: + currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); + case PauseMode.Charting: + currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); + case PauseMode.Difficulty: + // Prepend the difficulties. + var entries:Array = []; + if (PlayState.instance.currentChart != null) + { + var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); + trace('DIFFICULTIES: ${difficultiesInVariation}'); + for (difficulty in difficultiesInVariation) + { + difficulties.push({text: difficulty.toTitleCase(), callback: () -> changeDifficulty(this, difficulty)}); + } + } + + // Add the back button. + currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Cutscene: + currentMenuEntries = PAUSE_MENU_ENTRIES_CUTSCENE.clone(); + } + + // Render out the entries depending on the mode. + for (entryIndex in 0...entries) + { + var entry:PauseMenuEntry = entries[entryIndex]; + + // Remove entries that should be hidden. + if (entry.filter != null && !entry.filter()) currentMenuEntries.remove(entry); + + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.alpha = 0; + menuEntryText.add(text); + + entry.sprite = text; + } + + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + changeSelection(); + } + + function transitionIn():Void + { + FlxTween.globalManager.bgColor(this, 0.4, FlxColor.fromRGB(0, 0, 0, 0.0), FlxColor.fromRGB(0, 0, 0, 0.6), {ease: FlxEase.quartInOut}); + + // Animate each element a little bit downwards. + var delay:Float = 0.3; + for (child in metadata.members) + { + FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); + delay += 0.2; + } + } + function handleInputs():Void { - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; - var accepted = controls.ACCEPT; + if (!allowInput) return; + + if (controls.UI_UP_P) + { + changeSelection(-1); + } + if (controls.UI_DOWN_P) + { + changeSelection(1); + } + if (controls.PAUSE) + { + resume(this); + } + if (controls.ACCEPT) + { + menuEntries[currentEntry].callback(this); + } #if (debug || FORCE_DEBUG_VERSION) // to pause the game and get screenshots easy, press H on pause menu! if (FlxG.keys.justPressed.H) { - bg.visible = !bg.visible; - grpMenuShit.visible = !grpMenuShit.visible; - metaDataGrp.visible = !metaDataGrp.visible; + var visible = !metaDataGrp.visible; + metadata = visible; + menuEntryText = visible; + this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black } #end - - if (!exitingToMenu) - { - if (upP) - { - changeSelection(-1); - } - if (downP) - { - changeSelection(1); - } - - var androidPause:Bool = false; - - #if android - androidPause = FlxG.android.justPressed.BACK; - #end - - if (androidPause) close(); - - if (accepted) - { - var daSelected:String = menuItems[curSelected]; - - switch (daSelected) - { - case 'Resume': - close(); - - case 'Change Difficulty': - menuItems = pauseOptionsDifficulty; - regenMenu(); - - case 'Toggle Practice Mode': - PlayState.instance.isPracticeMode = true; - practiceText.visible = PlayState.instance.isPracticeMode; - - case 'Restart Song': - PlayState.instance.needsReset = true; - close(); - - case 'Exit to Menu': - exitingToMenu = true; - PlayState.instance.deathCounter = 0; - - for (item in grpMenuShit.members) - { - item.targetY = -3; - item.alpha = 0.6; - } - - FlxTransitionableState.skipNextTransIn = true; - FlxTransitionableState.skipNextTransOut = true; - - if (PlayStatePlaylist.isStoryMode) - { - PlayStatePlaylist.reset(); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); - } - else - { - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); - } - - case 'Exit to Chart Editor': - this.close(); - if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! - PlayState.instance.close(); // This only works because PlayState is a substate! - - case 'BACK': - menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; - regenMenu(); - - default: - if (pauseOptionsDifficulty.contains(daSelected)) - { - PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); - - // Reset campaign score when changing difficulty - // So if you switch difficulty on the last song of a week you get a really low overall score. - PlayStatePlaylist.campaignScore = 0; - PlayStatePlaylist.campaignDifficulty = daSelected.toLowerCase(); - PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty; - - PlayState.instance.needsReset = true; - - close(); - } - else - { - trace('[WARN] Unhandled pause menu option: ${daSelected}'); - } - } - } - - if (FlxG.keys.justPressed.J) - { - // for reference later! - // PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxKey.J, null); - } - } - } - - override function destroy():Void - { - pauseMusic.destroy(); - - super.destroy(); } function changeSelection(change:Int = 0):Void { FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); - curSelected += change; + currentEntry += change; - if (curSelected < 0) curSelected = menuItems.length - 1; - if (curSelected >= menuItems.length) curSelected = 0; + if (currentEntry < 0) currentEntry = menuEntries.length - 1; + if (currentEntry >= menuEntries.length) currentEntry = 0; - for (index => item in grpMenuShit.members) + for (entryIndex in 0...menuEntries.length) { - item.targetY = index - curSelected; + var isCurrent:Bool = entryIndex == currentEntry; - item.alpha = 0.6; + var entry:PauseMenuEntry = menuEntries[entryIndex]; + var text:AtlasText = entry.sprite; - if (item.targetY == 0) - { - item.alpha = 1; - } + // Set the transparency. + text.alpha = isCurrent ? 1.0 : 0.6; + + // Set the position. + var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; + var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); + FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); } } + + // =============== + // Menu Callbacks + // =============== + static function resume(state:PauseSubState):Void + { + state.close(); + } + + static function switchMode(state:PauseSubState, targetMode:PauseMode):Void + { + state.regenerateMenu(targetMode); + } + + static function changeDifficulty(state:PauseSubState, difficulty:String):Void + { + PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); + + // Reset campaign score when changing difficulty + // So if you switch difficulty on the last song of a week you get a really low overall score. + PlayStatePlaylist.campaignScore = 0; + PlayStatePlaylist.campaignDifficulty = difficulty; + PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty; + + PlayState.instance.needsReset = true; + + state.close(); + } + + static function restartPlayState(state:PauseSubState):Void + { + PlayState.instance.needsReset = true; + state.close(); + } + + static function enablePracticeMode(state:PauseSubState):Void + { + if (PlayState.instance == null) return; + + PlayState.instance.isPracticeMode = true; + regenerateMenu(); + } + + static function quitToMenu(state:PauseSubState):Void + { + state.allowInput = false; + + PlayState.instance.deathCounter = 0; + + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; + + if (PlayStatePlaylist.isStoryMode) + { + PlayStatePlaylist.reset(); + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); + } + else + { + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); + } + } + + static function quitToChartEditor(state:PauseSubState):Void + { + state.close(); + if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! + PlayState.instance.close(); // This only works because PlayState is a substate! + } + + /** + * Reset the pause configuration to the default. + */ + public static function reset():Void + { + musicSuffix = ''; + } } + +/** + * Which set of options the pause menu should display. + */ +enum PauseMode +{ + /** + * The menu displayed when the player pauses the game during a song. + */ + Standard; + + /** + * The menu displayed when the player pauses the game during a song while in charting mode. + */ + Charting; + + /** + * The menu displayed when the player moves to change the game's difficulty. + */ + Difficulty; + + /** + * The menu displayed when the player pauses the game during a cutscene. + */ + Cutscene; +} + +typedef PauseMenuEntry = +{ + var text:String; + var callback:PauseSubState->Void; + + var ?sprite:AtlasText; + + /** + * If this returns true, the entry will be displayed. If it returns false, the entry will be hidden. + */ + var ?filter:Void->Bool; +}; diff --git a/source/funkin/util/tools/FlxTweenTools.hx b/source/funkin/util/tools/FlxTweenTools.hx new file mode 100644 index 000000000..0860af64b --- /dev/null +++ b/source/funkin/util/tools/FlxTweenTools.hx @@ -0,0 +1,25 @@ +package funkin.util.tools; + +import flixel.tweens.FlxTween; +import flixel.tweens.FlxTween.FlxTweenManager; + +class FlxTweenTools +{ + /** + * Tween the background color of a FlxState. + * @param globalManager `flixel.tweens.FlxTween.globalManager` + * @param targetState The FlxState to tween the background color of. + * @param duration The duration of the tween. + * @param fromColor The starting color. + * @param toColor The ending color. + * @param options The options for the tween. + * @return The tween. + */ + public static function bgColor(globalManager:FlxTweenManager, targetState:FlxState, duration:Float = 1.0, fromColor:FlxColor, toColor:FlxColor, + ?options:TweenOptions):BackgroundColorTween + { + var tween = new BackgroundColorTween(options, this); + tween.tween(duration, fromColor, toColor, targetState); + globalManager.add(tween); + } +} From ddeac1db15858037eed160eaa32427bd20fa29fb Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 22:02:04 -0500 Subject: [PATCH 045/172] deubbershish... --- source/funkin/InitState.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0a59fb70b..33674439d 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -146,12 +146,11 @@ class InitState extends FlxState #end // Make errors and warnings less annoying. - #if FORCE_DEBUG_VERSION + // Forcing this always since I have never been happy to have the debugger to pop up LogStyle.ERROR.openConsole = false; LogStyle.ERROR.errorSound = null; LogStyle.WARNING.openConsole = false; LogStyle.WARNING.errorSound = null; - #end // // FLIXEL TRANSITIONS From 5dafd5409794aeb28089132cee507595dd0c44aa Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 22:09:09 -0500 Subject: [PATCH 046/172] update submodules, and add change from other PR --- .gitmodules | 4 ---- assets | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 903904103..452c0089b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,6 @@ [submodule "assets"] path = assets url = https://github.com/FunkinCrew/Funkin-Assets-secret - branch = master - update = merge [submodule "art"] path = art url = https://github.com/FunkinCrew/Funkin-Art-secret - branch = master - update = merge diff --git a/assets b/assets index cb0fbb56b..f8c259584 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096 +Subproject commit f8c2595844eff9375b522f117bfdadbdc6728c49 From 6da1dd57ad77a2a25273c78f7bc9be64c3f0ed2d Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 22:27:50 -0500 Subject: [PATCH 047/172] Update build-shit.yml --- .github/workflows/build-shit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 8ea3b16f3..388670a01 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -39,7 +39,7 @@ jobs: build-dir: export/release/html5/bin target: html5 create-nightly-win: - runs-on: windows-latest + runs-on: [self-hosted, windows] steps: - name: get token from gh app uses: actions/create-github-app-token@v1 From 5db3e006d6635a7e83cb847630786bcf18604c60 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 23:48:29 -0500 Subject: [PATCH 048/172] asset submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index f8c259584..742384647 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit f8c2595844eff9375b522f117bfdadbdc6728c49 +Subproject commit 742384647b778392f8688f17a3fc16bf4ae9f3ee From b9594ad812e11534c2d77e5fce08a20c8c8d748e Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 28 Feb 2024 00:07:52 -0500 Subject: [PATCH 049/172] asset submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 742384647..84b157429 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 742384647b778392f8688f17a3fc16bf4ae9f3ee +Subproject commit 84b1574294e7a7af21adf1a0e4894c88431ca43d From 13d6ba378e03c0f34470aeef5b38e2a32b4c143f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 00:19:08 -0500 Subject: [PATCH 050/172] Work in progress on fixing Week 6 --- .../tweens/misc/BackgroundColorTween.hx | 66 ------ source/funkin/InitState.hx | 3 + source/funkin/audio/FunkinSound.hx | 5 + source/funkin/audio/SoundGroup.hx | 5 +- source/funkin/graphics/video/FlxVideo.hx | 9 + source/funkin/import.hx | 1 - source/funkin/input/Controls.hx | 80 +++---- source/funkin/play/PauseSubState.hx | 219 +++++++++++++----- source/funkin/play/PlayState.hx | 95 +++----- source/funkin/play/cutscene/VideoCutscene.hx | 18 ++ .../play/cutscene/dialogue/Conversation.hx | 65 +++--- .../play/cutscene/dialogue/DialogueBox.hx | 41 +++- .../funkin/play/cutscene/dialogue/Speaker.hx | 17 ++ source/funkin/play/song/Song.hx | 4 +- source/funkin/save/Save.hx | 6 - .../save/migrator/RawSaveData_v1_0_0.hx | 1 - .../funkin/save/migrator/SaveDataMigrator.hx | 2 - source/funkin/ui/MusicBeatSubState.hx | 4 + .../debug/dialogue/ConversationDebugState.hx | 9 - source/funkin/ui/options/ControlsMenu.hx | 2 +- source/funkin/util/plugins/MemoryGCPlugin.hx | 37 +++ source/funkin/util/tools/FlxTweenTools.hx | 25 -- 22 files changed, 399 insertions(+), 315 deletions(-) delete mode 100644 source/flixel/tweens/misc/BackgroundColorTween.hx create mode 100644 source/funkin/util/plugins/MemoryGCPlugin.hx delete mode 100644 source/funkin/util/tools/FlxTweenTools.hx diff --git a/source/flixel/tweens/misc/BackgroundColorTween.hx b/source/flixel/tweens/misc/BackgroundColorTween.hx deleted file mode 100644 index b8b2d993f..000000000 --- a/source/flixel/tweens/misc/BackgroundColorTween.hx +++ /dev/null @@ -1,66 +0,0 @@ -package flixel.tweens.misc; - -/** - * Tweens the background color of a state. - * Tweens the red, green, blue, and/or alpha values of the color. - * - * @see `flixel.tweens.misc.ColorTween` for something that operates on sprites! - */ -class BackgroundColorTween extends FlxTween -{ - public var color(default, null):FlxColor; - - var startColor:FlxColor; - var endColor:FlxColor; - - /** - * State object whose color to tween - */ - public var targetState(default, null):FlxState; - - /** - * Clean up references - */ - override public function destroy() - { - super.destroy(); - targetState = null; - } - - /** - * Tweens the color to a new color and an alpha to a new alpha. - * - * @param duration Duration of the tween. - * @param fromColor Start color. - * @param toColor End color. - * @param targetState Optional sprite object whose color to tween. - * @return The tween for chaining. - */ - public function tween(duration:Float, fromColor:FlxColor, toColor:FlxColor, ?targetState:FlxSprite):ColorTween - { - this.color = startColor = fromColor; - this.endColor = toColor; - this.duration = duration; - this.targetState = targetState; - this.start(); - return this; - } - - override function update(elapsed:Float):Void - { - super.update(elapsed); - color = FlxColor.interpolate(startColor, endColor, scale); - - if (targetState != null) - { - targetState.bgColor = color; - // Alpha should apply inherently. - // targetState.alpha = color.alphaFloat; - } - } - - override function isTweenOf(object:Dynamic, ?field:String):Bool - { - return targetState == object && (field == null || field == "color"); - } -} diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0a59fb70b..b08710c33 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -203,6 +203,9 @@ class InitState extends FlxState // Plugins provide a useful interface for globally active Flixel objects, // that receive update events regardless of the current state. // TODO: Move scripted Module behavior to a Flixel plugin. + #if debug + funkin.util.plugins.MemoryGCPlugin.initialize(); + #end funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ForceCrashPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index e7ce68d08..704407c14 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -25,6 +25,9 @@ class FunkinSound extends FlxSound implements ICloneable { static final MAX_VOLUME:Float = 2.0; + /** + * Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible! + */ static var cache(default, null):FlxTypedGroup = new FlxTypedGroup(); public var muted(default, set):Bool = false; @@ -264,6 +267,8 @@ class FunkinSound extends FlxSound implements ICloneable { var sound:FunkinSound = cache.recycle(construct); + // Load the sound. + // Sets `exists = true` as a side effect. sound.loadEmbedded(embeddedSound, looped, autoDestroy, onComplete); if (embeddedSound is String) diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx index 0e81a0901..a26537c2a 100644 --- a/source/funkin/audio/SoundGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -80,8 +80,9 @@ class SoundGroup extends FlxTypedGroup // We have to play, then pause the sound to set the time, // else the sound will restart immediately when played. - result.play(true, 0.0); - result.pause(); + // TODO: Past me experienced that issue but present me didn't? Investigate. + // result.play(true, 0.0); + // result.pause(); result.time = this.time; result.onComplete = function() { diff --git a/source/funkin/graphics/video/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx index e95d7caa6..66829af00 100644 --- a/source/funkin/graphics/video/FlxVideo.hx +++ b/source/funkin/graphics/video/FlxVideo.hx @@ -40,6 +40,15 @@ class FlxVideo extends FlxBasic netStream.play(videoPath); } + public function restartVideo():Void + { + // Seek to the beginning of the video. + if (netStream != null) + { + netStream.seek(0); + } + } + public function finishVideo():Void { netStream.dispose(); diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 23b588044..02055d4ed 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -15,7 +15,6 @@ using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.DynamicTools; using funkin.util.tools.FloatTools; -using funkin.util.tools.FlxTweenTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; using funkin.util.tools.IteratorTools; diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index c4760cf5f..0857678d0 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -58,12 +58,11 @@ class Controls extends FlxActionSet var _back = new FlxActionDigital(Action.BACK); var _pause = new FlxActionDigital(Action.PAUSE); var _reset = new FlxActionDigital(Action.RESET); + var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _cutscene_advance = new FlxActionDigital(Action.CUTSCENE_ADVANCE); - var _cutscene_skip = new FlxActionDigital(Action.CUTSCENE_SKIP); var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU); var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART); var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE); - var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _volume_up = new FlxActionDigital(Action.VOLUME_UP); var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN); var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE); @@ -208,16 +207,21 @@ class Controls extends FlxActionSet inline function get_PAUSE() return _pause.check(); + public var RESET(get, never):Bool; + + inline function get_RESET() + return _reset.check(); + + public var SCREENSHOT(get, never):Bool; + + inline function get_SCREENSHOT() + return _screenshot.check(); + public var CUTSCENE_ADVANCE(get, never):Bool; inline function get_CUTSCENE_ADVANCE() return _cutscene_advance.check(); - public var CUTSCENE_SKIP(get, never):Bool; - - inline function get_CUTSCENE_SKIP() - return _cutscene_skip.check(); - public var DEBUG_MENU(get, never):Bool; inline function get_DEBUG_MENU() @@ -233,11 +237,6 @@ class Controls extends FlxActionSet inline function get_DEBUG_STAGE() return _debug_stage.check(); - public var SCREENSHOT(get, never):Bool; - - inline function get_SCREENSHOT() - return _screenshot.check(); - public var VOLUME_UP(get, never):Bool; inline function get_VOLUME_UP() @@ -253,11 +252,6 @@ class Controls extends FlxActionSet inline function get_VOLUME_MUTE() return _volume_mute.check(); - public var RESET(get, never):Bool; - - inline function get_RESET() - return _reset.check(); - public function new(name, scheme:KeyboardScheme = null) { super(name); @@ -289,16 +283,15 @@ class Controls extends FlxActionSet add(_accept); add(_back); add(_pause); + add(_reset); + add(_screenshot); add(_cutscene_advance); - add(_cutscene_skip); add(_debug_menu); add(_debug_chart); add(_debug_stage); - add(_screenshot); add(_volume_up); add(_volume_down); add(_volume_mute); - add(_reset); for (action in digitalActions) byName[action.name] = action; @@ -383,12 +376,11 @@ class Controls extends FlxActionSet case BACK: _back; case PAUSE: _pause; case RESET: _reset; + case SCREENSHOT: _screenshot; case CUTSCENE_ADVANCE: _cutscene_advance; - case CUTSCENE_SKIP: _cutscene_skip; case DEBUG_MENU: _debug_menu; case DEBUG_CHART: _debug_chart; case DEBUG_STAGE: _debug_stage; - case SCREENSHOT: _screenshot; case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; @@ -449,26 +441,24 @@ class Controls extends FlxActionSet func(_back, JUST_PRESSED); case PAUSE: func(_pause, JUST_PRESSED); + case RESET: + func(_reset, JUST_PRESSED); + case SCREENSHOT: + func(_screenshot, JUST_PRESSED); case CUTSCENE_ADVANCE: func(_cutscene_advance, JUST_PRESSED); - case CUTSCENE_SKIP: - func(_cutscene_skip, PRESSED); case DEBUG_MENU: func(_debug_menu, JUST_PRESSED); case DEBUG_CHART: func(_debug_chart, JUST_PRESSED); case DEBUG_STAGE: func(_debug_stage, JUST_PRESSED); - case SCREENSHOT: - func(_screenshot, JUST_PRESSED); case VOLUME_UP: func(_volume_up, JUST_PRESSED); case VOLUME_DOWN: func(_volume_down, JUST_PRESSED); case VOLUME_MUTE: func(_volume_mute, JUST_PRESSED); - case RESET: - func(_reset, JUST_PRESSED); } } @@ -654,13 +644,12 @@ class Controls extends FlxActionSet bindKeys(Control.ACCEPT, getDefaultKeybinds(scheme, Control.ACCEPT)); bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK)); bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE)); + bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); + bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE)); - bindKeys(Control.CUTSCENE_SKIP, getDefaultKeybinds(scheme, Control.CUTSCENE_SKIP)); bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE)); - bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); - bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); @@ -683,16 +672,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [Z, SPACE, ENTER]; case Control.BACK: return [X, BACKSPACE, ESCAPE]; case Control.PAUSE: return [P, ENTER, ESCAPE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.CUTSCENE_ADVANCE: return [Z, ENTER]; - case Control.CUTSCENE_SKIP: return [P, ESCAPE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; - case Control.RESET: return [R]; } case Duo(true): switch (control) { @@ -707,16 +695,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [G, Z]; case Control.BACK: return [H, X]; case Control.PAUSE: return [ONE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.CUTSCENE_ADVANCE: return [G, Z]; - case Control.CUTSCENE_SKIP: return [ONE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; - case Control.RESET: return [R]; } case Duo(false): switch (control) { @@ -731,16 +718,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [ENTER]; case Control.BACK: return [ESCAPE]; case Control.PAUSE: return [ONE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.CUTSCENE_ADVANCE: return [ENTER]; - case Control.CUTSCENE_SKIP: return [ONE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; - case Control.RESET: return [R]; } default: // Fallthrough. @@ -843,15 +829,14 @@ class Controls extends FlxActionSet Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), + Control.RESET => getDefaultGamepadBinds(Control.RESET), // Control.SCREENSHOT => [], // Control.VOLUME_UP => [RIGHT_SHOULDER], // Control.VOLUME_DOWN => [LEFT_SHOULDER], // Control.VOLUME_MUTE => [RIGHT_TRIGGER], Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE), - Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP), // Control.DEBUG_MENU // Control.DEBUG_CHART - Control.RESET => getDefaultGamepadBinds(Control.RESET) ]); } @@ -868,15 +853,14 @@ class Controls extends FlxActionSet case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; case Control.PAUSE: return [START]; + case Control.RESET: return [RIGHT_SHOULDER]; case Control.SCREENSHOT: return []; case Control.VOLUME_UP: return []; case Control.VOLUME_DOWN: return []; case Control.VOLUME_MUTE: return []; case Control.CUTSCENE_ADVANCE: return [A]; - case Control.CUTSCENE_SKIP: return [START]; case Control.DEBUG_MENU: return []; case Control.DEBUG_CHART: return []; - case Control.RESET: return [RIGHT_SHOULDER]; default: // Fallthrough. } @@ -1228,14 +1212,13 @@ enum Control UI_RIGHT; UI_DOWN; RESET; + SCREENSHOT; ACCEPT; BACK; PAUSE; // CUTSCENE CUTSCENE_ADVANCE; - CUTSCENE_SKIP; // SCREENSHOT - SCREENSHOT; // VOLUME VOLUME_UP; VOLUME_DOWN; @@ -1279,15 +1262,14 @@ abstract Action(String) to String from String var BACK = "back"; var PAUSE = "pause"; var RESET = "reset"; + // SCREENSHOT + var SCREENSHOT = "screenshot"; // CUTSCENE var CUTSCENE_ADVANCE = "cutscene_advance"; - var CUTSCENE_SKIP = "cutscene_skip"; // VOLUME var VOLUME_UP = "volume_up"; var VOLUME_DOWN = "volume_down"; var VOLUME_MUTE = "volume_mute"; - // SCREENSHOT - var SCREENSHOT = "screenshot"; // DEBUG var DEBUG_MENU = "debug_menu"; var DEBUG_CHART = "debug_chart"; diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 8300ac936..4f2a76e19 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -1,5 +1,24 @@ package funkin.play; +import flixel.addons.transition.FlxTransitionableState; +import flixel.FlxG; +import flixel.util.FlxTimer; +import flixel.FlxSprite; +import flixel.group.FlxSpriteGroup; +import flixel.math.FlxMath; +import flixel.text.FlxText; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import funkin.audio.FunkinSound; +import funkin.data.song.SongRegistry; +import funkin.graphics.FunkinSprite; +import funkin.play.cutscene.VideoCutscene; +import funkin.play.PlayState; +import funkin.ui.AtlasText; +import funkin.ui.MusicBeatSubState; +import funkin.ui.transition.StickerSubState; + typedef PauseSubStateParams = { ?mode:PauseMode, @@ -10,29 +29,36 @@ typedef PauseSubStateParams = */ class PauseSubState extends MusicBeatSubState { - static final PAUSE_MENU_ENTRIES_STANDARD = [ + static final PAUSE_MENU_ENTRIES_STANDARD:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)}, - {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> (PlayState.instance?.isPracticeMode ?? true)}, + {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> !(PlayState.instance?.isPracticeMode ?? false)}, {text: 'Exit to Menu', callback: quitToMenu}, ]; - static final PAUSE_MENU_ENTRIES_CHARTING = [ + static final PAUSE_MENU_ENTRIES_CHARTING:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Return to Chart Editor', callback: quitToChartEditor}, ]; - static final PAUSE_MENU_ENTRIES_DIFFICULTY = [ + static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array = [ {text: 'Back', callback: switchMode.bind(_, Standard)} // Other entries are added dynamically. ]; - static final PAUSE_MENU_ENTRIES_CUTSCENE = [ + static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array = [ {text: 'Resume', callback: resume}, - {text: 'Restart Cutscene', callback: restartCutscene}, - {text: 'Skip Cutscene', callback: skipCutscene}, + {text: 'Restart Cutscene', callback: restartVideoCutscene}, + {text: 'Skip Cutscene', callback: skipVideoCutscene}, + {text: 'Exit to Menu', callback: quitToMenu}, + ]; + + static final PAUSE_MENU_ENTRIES_CONVERSATION:Array = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Dialogue', callback: restartConversation}, + {text: 'Skip Dialogue', callback: skipConversation}, {text: 'Exit to Menu', callback: quitToMenu}, ]; @@ -42,15 +68,21 @@ class PauseSubState extends MusicBeatSubState public static var musicSuffix:String = ''; // Status - var menuEntries:Array; + var currentMenuEntries:Array; var currentEntry:Int = 0; var currentMode:PauseMode; - var allowInput:Bool = true; + + /** + * Disallow input until the transition in is complete! + * This prevents the pause menu from immediately closing. + */ + public var allowInput:Bool = false; // Graphics - var metadata:FlxTypedGroup; + var background:FunkinSprite; + var metadata:FlxTypedSpriteGroup; var metadataPractice:FlxText; - var menuEntryText:FlxTypedGroup; + var menuEntryText:FlxTypedSpriteGroup; // Audio var pauseMusic:FunkinSound; @@ -58,9 +90,7 @@ class PauseSubState extends MusicBeatSubState public function new(?params:PauseSubStateParams) { super(); - this.currentMode = params?.mode ?? PauseMode.Standard; - - this.bgColor = FlxColor.TRANSPARENT; // Transparent, fades into black later. + this.currentMode = params?.mode ?? Standard; } public override function create():Void @@ -69,8 +99,16 @@ class PauseSubState extends MusicBeatSubState startPauseMusic(); + buildBackground(); + buildMetadata(); + menuEntryText = new FlxTypedSpriteGroup(); + menuEntryText.scrollFactor.set(0, 0); + add(menuEntryText); + + regenerateMenu(); + transitionIn(); } @@ -81,47 +119,69 @@ class PauseSubState extends MusicBeatSubState handleInputs(); } + public override function destroy():Void + { + super.destroy(); + pauseMusic.stop(); + } + function startPauseMusic():Void { - pauseMusic = FunkinSound.load(Paths.music('breakfast-pixel'), true, true); + pauseMusic = FunkinSound.load(Paths.music('breakfast$musicSuffix'), true, true); // Start playing at a random point in the song. pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); } + function buildBackground():Void + { + // Using state.bgColor causes bugs! + background = new FunkinSprite(0, 0); + background.makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK); + background.alpha = 0.0; + background.scrollFactor.set(0, 0); + background.updateHitbox(); + add(background); + } + /** * Render the metadata in the top right. */ function buildMetadata():Void { - metadata = new FlxTypedGroup(); + metadata = new FlxTypedSpriteGroup(); + metadata.scrollFactor.set(0, 0); add(metadata); - var metadataSong:FlxText = new FlxText(20, 15, 0, 'Song Name - Artist'); + var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist'); metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentChart != null) { - metadataSong.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; + metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; } + metadataSong.scrollFactor.set(0, 0); metadata.add(metadataSong); - var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, 0, 'Difficulty'); + var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: '); metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentDifficulty != null) { metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); } + metadataDifficulty.scrollFactor.set(0, 0); metadata.add(metadataDifficulty); - var metadataDeaths:FlxText = new FlxText(20, 15 + 64, 0, '0 Blue Balls'); + var metadataDeaths:FlxText = new FlxText(20, 15 + 64, FlxG.width - 40, '0 Blue Balls'); metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + metadataDeaths.scrollFactor.set(0, 0); metadata.add(metadataDeaths); - metadataPractice = new FlxText(20, 15 + 96, 0, 'PRACTICE MODE'); + metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE'); metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + metadataPractice.scrollFactor.set(0, 0); metadata.add(metadataPractice); } @@ -149,30 +209,45 @@ class PauseSubState extends MusicBeatSubState trace('DIFFICULTIES: ${difficultiesInVariation}'); for (difficulty in difficultiesInVariation) { - difficulties.push({text: difficulty.toTitleCase(), callback: () -> changeDifficulty(this, difficulty)}); + entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); } } // Add the back button. currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Conversation: + currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); case PauseMode.Cutscene: - currentMenuEntries = PAUSE_MENU_ENTRIES_CUTSCENE.clone(); + currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); } // Render out the entries depending on the mode. - for (entryIndex in 0...entries) + var entryIndex:Int = 0; + var toRemove = []; + for (entry in currentMenuEntries) { - var entry:PauseMenuEntry = entries[entryIndex]; + if (entry == null || (entry.filter != null && !entry.filter())) + { + // Remove entries that should be hidden. + toRemove.push(entry); + } + else + { + // Handle visible entries. + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.scrollFactor.set(0, 0); + text.alpha = 0; + menuEntryText.add(text); - // Remove entries that should be hidden. - if (entry.filter != null && !entry.filter()) currentMenuEntries.remove(entry); + entry.sprite = text; - var yPos:Float = 70 * entryIndex + 30; - var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); - text.alpha = 0; - menuEntryText.add(text); - - entry.sprite = text; + entryIndex++; + } + } + for (entry in toRemove) + { + currentMenuEntries.remove(entry); } metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; @@ -182,15 +257,19 @@ class PauseSubState extends MusicBeatSubState function transitionIn():Void { - FlxTween.globalManager.bgColor(this, 0.4, FlxColor.fromRGB(0, 0, 0, 0.0), FlxColor.fromRGB(0, 0, 0, 0.6), {ease: FlxEase.quartInOut}); + FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); // Animate each element a little bit downwards. - var delay:Float = 0.3; + var delay:Float = 0.1; for (child in metadata.members) { FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); - delay += 0.2; + delay += 0.05; } + + new FlxTimer().start(0.2, (_) -> { + allowInput = true; + }); } function handleInputs():Void @@ -205,22 +284,24 @@ class PauseSubState extends MusicBeatSubState { changeSelection(1); } - if (controls.PAUSE) - { - resume(this); - } + if (controls.ACCEPT) { - menuEntries[currentEntry].callback(this); + currentMenuEntries[currentEntry].callback(this); + } + else if (controls.PAUSE) + { + resume(this); } #if (debug || FORCE_DEBUG_VERSION) // to pause the game and get screenshots easy, press H on pause menu! if (FlxG.keys.justPressed.H) { - var visible = !metaDataGrp.visible; - metadata = visible; - menuEntryText = visible; + var visible = !metadata.visible; + + metadata.visible = visible; + menuEntryText.visible = visible; this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black } #end @@ -232,14 +313,14 @@ class PauseSubState extends MusicBeatSubState currentEntry += change; - if (currentEntry < 0) currentEntry = menuEntries.length - 1; - if (currentEntry >= menuEntries.length) currentEntry = 0; + if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1; + if (currentEntry >= currentMenuEntries.length) currentEntry = 0; - for (entryIndex in 0...menuEntries.length) + for (entryIndex in 0...currentMenuEntries.length) { var isCurrent:Bool = entryIndex == currentEntry; - var entry:PauseMenuEntry = menuEntries[entryIndex]; + var entry:PauseMenuEntry = currentMenuEntries[entryIndex]; var text:AtlasText = entry.sprite; // Set the transparency. @@ -248,6 +329,7 @@ class PauseSubState extends MusicBeatSubState // Set the position. var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); + trace(targetY); FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); } } @@ -291,7 +373,35 @@ class PauseSubState extends MusicBeatSubState if (PlayState.instance == null) return; PlayState.instance.isPracticeMode = true; - regenerateMenu(); + state.regenerateMenu(); + } + + static function restartVideoCutscene(state:PauseSubState):Void + { + VideoCutscene.restartVideo(); + state.close(); + } + + static function skipVideoCutscene(state:PauseSubState):Void + { + VideoCutscene.finishVideo(); + state.close(); + } + + static function restartConversation(state:PauseSubState):Void + { + if (PlayState.instance?.currentConversation == null) return; + + PlayState.instance.currentConversation.resetConversation(); + state.close(); + } + + static function skipConversation(state:PauseSubState):Void + { + if (PlayState.instance?.currentConversation == null) return; + + PlayState.instance.currentConversation.skipConversation(); + state.close(); } static function quitToMenu(state:PauseSubState):Void @@ -306,11 +416,11 @@ class PauseSubState extends MusicBeatSubState if (PlayStatePlaylist.isStoryMode) { PlayStatePlaylist.reset(); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); + state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); } else { - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); + state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); } } @@ -351,7 +461,12 @@ enum PauseMode Difficulty; /** - * The menu displayed when the player pauses the game during a cutscene. + * The menu displayed when the player pauses the game during a conversation. + */ + Conversation; + + /** + * The menu displayed when the player pauses the game during a video cutscene. */ Cutscene; } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 1dbba5b54..ca2344b0a 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -314,6 +314,11 @@ class PlayState extends MusicBeatSubState **/ var inputReleaseQueue:Array = []; + /** + * If we just unpaused the game, we shouldn't be able to pause again for one frame. + */ + var justUnpaused:Bool = false; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -442,11 +447,6 @@ class PlayState extends MusicBeatSubState */ var comboPopUps:PopUpStuff; - /** - * The circular sprite that appears while the user is holding down the Skip Cutscene button. - */ - var skipTimer:FlxPieDial; - /** * PROPERTIES */ @@ -474,7 +474,7 @@ class PlayState extends MusicBeatSubState if (!Std.isOfType(this.subState, PauseSubState)) return false; var pauseSubState:PauseSubState = cast this.subState; - return pauseSubState.exitingToMenu; + return !pauseSubState.allowInput; } /** @@ -629,14 +629,6 @@ class PlayState extends MusicBeatSubState comboPopUps.cameras = [camHUD]; add(comboPopUps); - // The little dial that shows up when you hold the Skip Cutscene key. - skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24); - skipTimer.amount = 0; - skipTimer.zIndex = 1000; - add(skipTimer); - // Renders only in video cutscene mode. - skipTimer.cameras = [camCutscene]; - #if discord_rpc // Initialize Discord Rich Presence. initDiscord(); @@ -752,6 +744,8 @@ class PlayState extends MusicBeatSubState public override function update(elapsed:Float):Void { + // TOTAL: 9.42% CPU Time when profiled in VS 2019. + if (criticalFailure) return; super.update(elapsed); @@ -853,7 +847,7 @@ class PlayState extends MusicBeatSubState #end // Attempt to pause the game. - if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame) + if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame && !justUnpaused) { var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000)); @@ -888,12 +882,12 @@ class PlayState extends MusicBeatSubState boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); } - var pauseSubState:FlxSubState = new PauseSubState(isChartingMode); + var pauseSubState:FlxSubState = new PauseSubState({mode: isChartingMode ? Charting : Standard}); FlxTransitionableSubState.skipNextTransIn = true; FlxTransitionableSubState.skipNextTransOut = true; - openSubState(pauseSubState); pauseSubState.camera = camHUD; + openSubState(pauseSubState); // boyfriendPos.put(); // TODO: Why is this here? } @@ -1017,6 +1011,8 @@ class PlayState extends MusicBeatSubState // Moving notes into position is now done by Strumline.update(). processNotes(elapsed); + + justUnpaused = false; } public override function dispatchEvent(event:ScriptEvent):Void @@ -1105,6 +1101,8 @@ class PlayState extends MusicBeatSubState DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); } #end + + justUnpaused = true; } else if (Std.isOfType(subState, Transition)) { @@ -2444,58 +2442,39 @@ class PlayState extends MusicBeatSubState */ function handleCutsceneKeys(elapsed:Float):Void { + if (isGamePaused) return; + if (currentConversation != null) { - if (controls.CUTSCENE_ADVANCE) currentConversation?.advanceConversation(); - - if (controls.CUTSCENE_SKIP) + // Pause/unpause may conflict with advancing the conversation! + if (controls.CUTSCENE_ADVANCE && !justUnpaused) { - currentConversation?.trySkipConversation(elapsed); + currentConversation?.advanceConversation(); } - else + else if (controls.PAUSE && !justUnpaused) { - currentConversation?.trySkipConversation(-1); + var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); + + persistentUpdate = false; + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; + pauseSubState.camera = camCutscene; + openSubState(pauseSubState); } } else if (VideoCutscene.isPlaying()) { // This is a video cutscene. - - if (controls.CUTSCENE_SKIP) + if (controls.PAUSE && !justUnpaused) { - trySkipVideoCutscene(elapsed); + var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); + + persistentUpdate = false; + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; + pauseSubState.camera = camCutscene; + openSubState(pauseSubState); } - else - { - trySkipVideoCutscene(-1); - } - } - } - - /** - * Handle logic for the skip timer. - * If the skip button is being held, pass the amount of time elapsed since last game update. - * If the skip button has been released, pass a negative number. - */ - function trySkipVideoCutscene(elapsed:Float):Void - { - if (skipTimer == null || skipTimer.animation == null) return; - - if (elapsed < 0) - { - skipHeldTimer = 0.0; - } - else - { - skipHeldTimer += elapsed; - } - - skipTimer.visible = skipHeldTimer >= 0.05; - skipTimer.amount = Math.min(skipHeldTimer / 1.5, 1.0); - - if (skipHeldTimer >= 1.5) - { - VideoCutscene.finishVideo(); } } @@ -2694,7 +2673,7 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.pause(); if (vocals != null) { - vocals.pause(); + vocals.destroy(); remove(vocals); } } diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 934919b65..a883a528a 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -118,6 +118,24 @@ class VideoCutscene } #end + public static function restartVideo():Void + { + #if html5 + if (vid != null) + { + vid.restartVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + // Seek to the start of the video. + vid.bitmap.time = 0; + } + #end + } + public static function finishVideo(?transitionTime:Float = 0.5):Void { trace('ALERT: Finish video cutscene called!'); diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index dc3fd8c8a..150f6bf6f 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -31,10 +31,6 @@ import funkin.data.dialogue.DialogueBoxRegistry; */ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry { - static final CONVERSATION_SKIP_TIMER:Float = 1.5; - - var skipHeldTimer:Float = 0.0; - /** * The ID of the conversation. */ @@ -105,8 +101,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl var currentDialogueBox:DialogueBox; - var skipTimer:FlxPieDial; - public function new(id:String) { super(); @@ -124,8 +118,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { // Reset the progress in the dialogue. currentDialogueEntry = 0; + currentDialogueLine = 0; this.state = ConversationState.Start; - this.alpha = 1.0; // Start the dialogue. dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false)); @@ -153,6 +147,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl function setupBackdrop():Void { + if (backdrop != null) + { + backdrop.destroy(); + backdrop = null; + } + backdrop = new FunkinSprite(0, 0); if (_data.backdrop == null) return; @@ -181,12 +181,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl refresh(); } - function setupSkipTimer():Void - { - add(skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24)); - skipTimer.amount = 0; - } - public override function update(elapsed:Float):Void { super.update(elapsed); @@ -201,8 +195,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl // Skip the next steps if the current speaker is already displayed. if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return; - var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId); - if (currentSpeaker != null) { remove(currentSpeaker); @@ -210,6 +202,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentSpeaker = null; } + var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId); + if (nextSpeaker == null) { if (nextSpeakerId == null) @@ -222,6 +216,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl } return; } + if (!nextSpeaker.alive) nextSpeaker.revive(); ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true)); @@ -266,6 +261,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl trace('Dialogue box could not be retrieved.'); return; } + if (!nextDialogueBox.alive) nextDialogueBox.revive(); ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true)); @@ -347,29 +343,21 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentDialogueEntry = 0; this.state = ConversationState.Start; - advanceConversation(); - } + if (outroTween != null) outroTween.cancel(); // Canc + outroTween = null; - public function trySkipConversation(elapsed:Float):Void - { - if (skipTimer == null || skipTimer.animation == null) return; + this.alpha = 0.0; + if (this.music != null) this.music.stop(); + this.music = null; - if (elapsed < 0) - { - skipHeldTimer = 0.0; - } - else - { - skipHeldTimer += elapsed; - } + if (currentSpeaker != null) currentSpeaker.kill(); + currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + currentDialogueBox = null; + if (backdrop != null) backdrop.kill(); + backdrop = null; - skipTimer.visible = skipHeldTimer >= 0.05; - skipTimer.amount = Math.min(skipHeldTimer / CONVERSATION_SKIP_TIMER, 1.0); - - if (skipHeldTimer >= CONVERSATION_SKIP_TIMER) - { - skipConversation(); - } + startConversation(); } /** @@ -425,7 +413,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl // Fade in the music and backdrop. setupMusic(); setupBackdrop(); - setupSkipTimer(); // Advance the conversation. state = ConversationState.Opening; @@ -554,12 +541,16 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl if (this.music != null) this.music.stop(); this.music = null; - this.skipTimer = null; if (currentSpeaker != null) currentSpeaker.kill(); + remove(currentSpeaker); currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + remove(currentDialogueBox); currentDialogueBox = null; + if (backdrop != null) backdrop.kill(); + remove(backdrop); backdrop = null; this.clear(); diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx index 6f8a0086a..eb603a352 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx @@ -8,6 +8,7 @@ import flixel.text.FlxText; import flixel.addons.text.FlxTypeText; import funkin.util.assets.FlxAnimationUtil; import funkin.modding.events.ScriptEvent; +import funkin.audio.FunkinSound; import funkin.modding.IScriptedClass.IDialogueScriptedClass; import flixel.util.FlxColor; import funkin.data.dialogue.DialogueBoxData; @@ -111,9 +112,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.y = 0; this.alpha = 1; - this.boxSprite = new FlxSprite(0, 0); - add(this.boxSprite); - loadSpritesheet(); loadAnimations(); @@ -122,6 +120,15 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple function loadSpritesheet():Void { + if (this.boxSprite != null) + { + remove(this.boxSprite); + this.boxSprite = null; + } + + this.boxSprite = new FlxSprite(0, 0); + add(this.boxSprite); + trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}'); var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath); @@ -190,6 +197,31 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.boxSprite.updateHitbox(); } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function kill():Void + { + super.kill(); + if (this.boxSprite != null) + { + this.boxSprite.kill(); + this.boxSprite = null; + } + if (this.textDisplay != null) + { + this.textDisplay.kill(); + this.textDisplay = null; + } + this.clear(); + } + + public override function revive():Void + { + super.revive(); + } + function loadAnimations():Void { trace('[DIALOGUE BOX] Loading ${_data.animations.length} animations for ${id}'); @@ -246,7 +278,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW, FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false); textDisplay.borderSize = _data.text.shadowWidth ?? 2; - textDisplay.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)]; + // TODO: Add an option to configure this. + textDisplay.sounds = [FunkinSound.load(Paths.sound('pixelText'), 0.6)]; textDisplay.completeCallback = onTypingComplete; diff --git a/source/funkin/play/cutscene/dialogue/Speaker.hx b/source/funkin/play/cutscene/dialogue/Speaker.hx index d5bffd7b0..0d59854a7 100644 --- a/source/funkin/play/cutscene/dialogue/Speaker.hx +++ b/source/funkin/play/cutscene/dialogue/Speaker.hx @@ -106,6 +106,23 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe loadAnimations(); } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function kill():Void + { + super.kill(); + } + + public override function revive():Void + { + super.revive(); + + loadSpritesheet(); + loadAnimations(); + } + function loadSpritesheet():Void { trace('[SPEAKER] Loading spritesheet ${_data.assetPath} for ${id}'); diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 970aebc57..105e6db43 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -614,9 +614,9 @@ class SongDifficulty } // Add player vocals. - if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(Assets.getSound(voiceList[0]))); + if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(voiceList[0])); // Add opponent vocals. - if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(Assets.getSound(voiceList[1]))); + if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(voiceList[1])); // Add additional vocals. if (voiceList.length > 2) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index ff5e5bfb8..6246dcb58 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -893,12 +893,6 @@ typedef SaveControlsData = */ var ?CUTSCENE_ADVANCE:Array; - /** - * Keybind for skipping a cutscene. - * @default `Escape` - */ - var ?CUTSCENE_SKIP:Array; - /** * Keybind for increasing volume. * @default `Plus` diff --git a/source/funkin/save/migrator/RawSaveData_v1_0_0.hx b/source/funkin/save/migrator/RawSaveData_v1_0_0.hx index b71102cce..a516f944a 100644 --- a/source/funkin/save/migrator/RawSaveData_v1_0_0.hx +++ b/source/funkin/save/migrator/RawSaveData_v1_0_0.hx @@ -35,7 +35,6 @@ typedef SaveControlsData_v1_0_0 = var ?ACCEPT:Array; var ?BACK:Array; var ?CUTSCENE_ADVANCE:Array; - var ?CUTSCENE_SKIP:Array; var ?NOTE_DOWN:Array; var ?NOTE_LEFT:Array; var ?NOTE_RIGHT:Array; diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index 92bee4ceb..f995660f7 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -272,7 +272,6 @@ class SaveDataMigrator ACCEPT: controlsData?.keys?.ACCEPT ?? null, BACK: controlsData?.keys?.BACK ?? null, CUTSCENE_ADVANCE: controlsData?.keys?.CUTSCENE_ADVANCE ?? null, - CUTSCENE_SKIP: controlsData?.keys?.CUTSCENE_SKIP ?? null, NOTE_DOWN: controlsData?.keys?.NOTE_DOWN ?? null, NOTE_LEFT: controlsData?.keys?.NOTE_LEFT ?? null, NOTE_RIGHT: controlsData?.keys?.NOTE_RIGHT ?? null, @@ -293,7 +292,6 @@ class SaveDataMigrator ACCEPT: controlsData?.pad?.ACCEPT ?? null, BACK: controlsData?.pad?.BACK ?? null, CUTSCENE_ADVANCE: controlsData?.pad?.CUTSCENE_ADVANCE ?? null, - CUTSCENE_SKIP: controlsData?.pad?.CUTSCENE_SKIP ?? null, NOTE_DOWN: controlsData?.pad?.NOTE_DOWN ?? null, NOTE_LEFT: controlsData?.pad?.NOTE_LEFT ?? null, NOTE_RIGHT: controlsData?.pad?.NOTE_RIGHT ?? null, diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 2c8970357..dc742874f 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -51,6 +51,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler override function update(elapsed:Float):Void { + // 3.59% CPU Usage (100% is FlxTypedGroup#update() and most of that is updating each member.) super.update(elapsed); // Emergency exit button. @@ -61,8 +62,11 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler // Display Conductor info in the watch window. FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); + + // 0.09% CPU Usage? Conductor.watchQuick(); + // 4.31% CPU Usage dispatchEvent(new UpdateScriptEvent(elapsed)); } diff --git a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx index 33a6f365a..abda3d3b1 100644 --- a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx +++ b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx @@ -62,15 +62,6 @@ class ConversationDebugState extends MusicBeatState if (conversation != null) { if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation(); - - if (controls.CUTSCENE_SKIP) - { - conversation.trySkipConversation(elapsed); - } - else - { - conversation.trySkipConversation(-1); - } } } } diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index bda071846..62ae4b1a9 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -27,7 +27,7 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page static var controlGroups:Array> = [ [NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT], [UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK], - [CUTSCENE_ADVANCE, CUTSCENE_SKIP], + [CUTSCENE_ADVANCE], [VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE], [DEBUG_MENU, DEBUG_CHART] ]; diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx new file mode 100644 index 000000000..3df861eb5 --- /dev/null +++ b/source/funkin/util/plugins/MemoryGCPlugin.hx @@ -0,0 +1,37 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection. + */ +class MemoryGCPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new MemoryGCPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.INSERT) + { + var perfStart:Float = Sys.time(); + funkin.util.MemoryUtil.collect(true); + var perfEnd:Float = Sys.time(); + trace("Memory GC took " + (perfEnd - perfStart) + " seconds"); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/tools/FlxTweenTools.hx b/source/funkin/util/tools/FlxTweenTools.hx deleted file mode 100644 index 0860af64b..000000000 --- a/source/funkin/util/tools/FlxTweenTools.hx +++ /dev/null @@ -1,25 +0,0 @@ -package funkin.util.tools; - -import flixel.tweens.FlxTween; -import flixel.tweens.FlxTween.FlxTweenManager; - -class FlxTweenTools -{ - /** - * Tween the background color of a FlxState. - * @param globalManager `flixel.tweens.FlxTween.globalManager` - * @param targetState The FlxState to tween the background color of. - * @param duration The duration of the tween. - * @param fromColor The starting color. - * @param toColor The ending color. - * @param options The options for the tween. - * @return The tween. - */ - public static function bgColor(globalManager:FlxTweenManager, targetState:FlxState, duration:Float = 1.0, fromColor:FlxColor, toColor:FlxColor, - ?options:TweenOptions):BackgroundColorTween - { - var tween = new BackgroundColorTween(options, this); - tween.tween(duration, fromColor, toColor, targetState); - globalManager.add(tween); - } -} From 4da3729058720d183d63127f0e4e9c062452cfaf Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 00:20:06 -0500 Subject: [PATCH 051/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index cb0fbb56b..e0b13c91c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096 +Subproject commit e0b13c91c2c39c877d9cd4fdc6acc90ec982cfdf From 858d8edf7aba344539317754e0e3e7dbefacb4e0 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Tue, 27 Feb 2024 23:29:40 -0700 Subject: [PATCH 052/172] 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 cecddbc2ee12c31641cbc38c660f16a46ec70bcb Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 03:01:20 -0500 Subject: [PATCH 053/172] Fixed issues with audio in Week 6 after skipping the cutscene. --- source/funkin/audio/FunkinSound.hx | 7 ++- source/funkin/play/PlayState.hx | 40 ++++++++------ .../play/cutscene/dialogue/Conversation.hx | 52 ++++++++++++++----- .../play/cutscene/dialogue/DialogueBox.hx | 10 ++-- .../funkin/play/cutscene/dialogue/Speaker.hx | 3 ++ source/funkin/play/song/Song.hx | 4 +- .../debug/dialogue/ConversationDebugState.hx | 34 ++++++++++-- source/funkin/util/plugins/WatchPlugin.hx | 3 ++ 8 files changed, 114 insertions(+), 39 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 704407c14..2630a806a 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -189,10 +189,14 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocus():Void { - if (!_alreadyPaused && this._shouldPlay) + if (!_alreadyPaused) { resume(); } + else + { + trace('Not resuming audio on focus!'); + } } /** @@ -200,6 +204,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocusLost():Void { + trace('Focus lost, pausing audio!'); _alreadyPaused = _paused; pause(); } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ca2344b0a..bb9e05638 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -770,8 +770,7 @@ class PlayState extends MusicBeatSubState inputSpitter = []; // Reset music properly. - - FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instance.instrumentalOffset); + FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; FlxG.sound.music.pause(); if (!overrideMusic) @@ -1054,7 +1053,10 @@ class PlayState extends MusicBeatSubState if (FlxG.sound.music != null) { musicPausedBySubState = FlxG.sound.music.playing; - FlxG.sound.music.pause(); + if (musicPausedBySubState) + { + FlxG.sound.music.pause(); + } if (vocals != null) vocals.pause(); } @@ -1082,7 +1084,7 @@ class PlayState extends MusicBeatSubState // Resume if (musicPausedBySubState) { - FlxG.sound.music.play(FlxG.sound.music.time); + FlxG.sound.music.play(); } if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); @@ -1284,12 +1286,6 @@ class PlayState extends MusicBeatSubState public override function destroy():Void { - if (currentConversation != null) - { - remove(currentConversation); - currentConversation.kill(); - } - performCleanup(); super.destroy(); @@ -1708,6 +1704,7 @@ class PlayState extends MusicBeatSubState currentConversation = ConversationRegistry.instance.fetchEntry(conversationId); if (currentConversation == null) return; + if (!currentConversation.alive) currentConversation.revive(); currentConversation.completeCallback = onConversationComplete; currentConversation.cameras = [camCutscene]; @@ -1725,8 +1722,13 @@ class PlayState extends MusicBeatSubState function onConversationComplete():Void { isInCutscene = false; - remove(currentConversation); - currentConversation = null; + + if (currentConversation != null) + { + currentConversation.kill(); + remove(currentConversation); + currentConversation = null; + } if (startingSong && !isInCountdown) { @@ -1751,12 +1753,14 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.onComplete = endSong; // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. - FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; + FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset); + + // I am going insane. + FlxG.sound.music.volume = 1.0; + FlxG.sound.music.fadeTween.cancel(); trace('Playing vocals...'); add(vocals); - - FlxG.sound.music.play(FlxG.sound.music.time); vocals.play(); resyncVocals(); @@ -2657,6 +2661,12 @@ class PlayState extends MusicBeatSubState */ function performCleanup():Void { + if (currentConversation != null) + { + remove(currentConversation); + currentConversation.kill(); + } + if (currentChart != null) { // TODO: Uncache the song. diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 150f6bf6f..a85a47c49 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -150,6 +150,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl if (backdrop != null) { backdrop.destroy(); + remove(backdrop); backdrop = null; } @@ -193,7 +194,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl var nextSpeakerId:String = currentDialogueEntryData.speaker; // Skip the next steps if the current speaker is already displayed. - if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return; + if ((currentSpeaker != null && currentSpeaker.alive) && nextSpeakerId == currentSpeaker.id) return; if (currentSpeaker != null) { @@ -244,8 +245,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { var nextDialogueBoxId:String = currentDialogueEntryData?.box; - // Skip the next steps if the current speaker is already displayed. - if (currentDialogueBox != null && nextDialogueBoxId == currentDialogueBox.id) return; + // Skip the next steps if the current dialogue box is already displayed. + if ((currentDialogueBox != null && currentDialogueBox.alive) && nextDialogueBoxId == currentDialogueBox.id) return; if (currentDialogueBox != null) { @@ -343,18 +344,25 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentDialogueEntry = 0; this.state = ConversationState.Start; - if (outroTween != null) outroTween.cancel(); // Canc + if (outroTween != null) + { + outroTween.cancel(); + } outroTween = null; - this.alpha = 0.0; if (this.music != null) this.music.stop(); this.music = null; if (currentSpeaker != null) currentSpeaker.kill(); + remove(currentSpeaker); currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + remove(currentDialogueBox); currentDialogueBox = null; - if (backdrop != null) backdrop.kill(); + + if (backdrop != null) backdrop.destroy(); + remove(backdrop); backdrop = null; startConversation(); @@ -371,7 +379,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true)); } - static var outroTween:FlxTween; + var outroTween:FlxTween; public function startOutro():Void { @@ -399,7 +407,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl public function endOutro():Void { - outroTween = null; ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false)); } @@ -534,10 +541,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { propagateEvent(event); - if (outroTween != null) outroTween.cancel(); // Canc + if (outroTween != null) + { + outroTween.cancel(); + } outroTween = null; - this.alpha = 0.0; if (this.music != null) this.music.stop(); this.music = null; @@ -549,7 +558,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl remove(currentDialogueBox); currentDialogueBox = null; - if (backdrop != null) backdrop.kill(); + if (backdrop != null) backdrop.destroy(); remove(backdrop); backdrop = null; @@ -569,16 +578,27 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl */ function propagateEvent(event:ScriptEvent):Void { - if (this.currentDialogueBox != null) + if (this.currentDialogueBox != null && this.currentDialogueBox.exists) { ScriptEventDispatcher.callEvent(this.currentDialogueBox, event); } - if (this.currentSpeaker != null) + if (this.currentSpeaker != null && this.currentSpeaker.exists) { ScriptEventDispatcher.callEvent(this.currentSpeaker, event); } } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function revive():Void + { + super.revive(); + this.alpha = 1; + this.visible = true; + } + /** * Calls `kill()` on the group's members and then on the group itself. * You can revive this group later via `revive()` after this. @@ -590,6 +610,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl exists = false; _skipTransformChildren = false; if (group != null) group.kill(); + + if (outroTween != null) + { + outroTween.cancel(); + outroTween = null; + } } public override function toString():String diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx index eb603a352..fe4f13be0 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx @@ -4,7 +4,7 @@ import flixel.FlxSprite; import funkin.data.IRegistryEntry; import flixel.group.FlxSpriteGroup; import flixel.graphics.frames.FlxFramesCollection; -import flixel.text.FlxText; +import funkin.graphics.FunkinSprite; import flixel.addons.text.FlxTypeText; import funkin.util.assets.FlxAnimationUtil; import funkin.modding.events.ScriptEvent; @@ -126,8 +126,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.boxSprite = null; } - this.boxSprite = new FlxSprite(0, 0); - add(this.boxSprite); + this.boxSprite = new FunkinSprite(0, 0); trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}'); @@ -153,6 +152,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.flipY = _data.flipY; this.globalOffsets = _data.offsets; this.setScale(_data.scale); + + add(this.boxSprite); } public function setText(newText:String):Void @@ -220,6 +221,9 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple public override function revive():Void { super.revive(); + + this.visible = true; + this.alpha = 1.0; } function loadAnimations():Void diff --git a/source/funkin/play/cutscene/dialogue/Speaker.hx b/source/funkin/play/cutscene/dialogue/Speaker.hx index 0d59854a7..f848d79c8 100644 --- a/source/funkin/play/cutscene/dialogue/Speaker.hx +++ b/source/funkin/play/cutscene/dialogue/Speaker.hx @@ -119,6 +119,9 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe { super.revive(); + this.visible = true; + this.alpha = 1.0; + loadSpritesheet(); loadAnimations(); } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 105e6db43..61f83d1ed 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -502,11 +502,11 @@ class SongDifficulty } } - public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void + public function playInst(volume:Float = 1.0, looped:Bool = false):Void { var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : ''; - FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped); + FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true); // Workaround for a bug where FlxG.sound.music.update() was being called twice. FlxG.sound.list.remove(FlxG.sound.music); diff --git a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx index abda3d3b1..f165865c4 100644 --- a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx +++ b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx @@ -36,11 +36,24 @@ class ConversationDebugState extends MusicBeatState public override function create():Void { - conversation = ConversationRegistry.instance.fetchEntry(conversationId); - conversation.completeCallback = onConversationComplete; - add(conversation); + super.create(); + startConversation(); + } - ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false)); + function startConversation():Void + { + if (conversation != null) return; + + conversation = ConversationRegistry.instance.fetchEntry(conversationId); + if (conversation == null) return; + if (!conversation.alive) conversation.revive(); + + conversation.zIndex = 1000; + add(conversation); + refresh(); + + var event:ScriptEvent = new ScriptEvent(CREATE, false); + ScriptEventDispatcher.callEvent(conversation, event); } function onConversationComplete():Void @@ -61,7 +74,18 @@ class ConversationDebugState extends MusicBeatState if (conversation != null) { - if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation(); + if (controls.CUTSCENE_ADVANCE) + { + conversation.advanceConversation(); + } + else if (controls.PAUSE) + { + conversation.kill(); + remove(conversation); + conversation = null; + + FlxG.switchState(() -> new ConversationDebugState()); + } } } } diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx index defd9797d..a03115820 100644 --- a/source/funkin/util/plugins/WatchPlugin.hx +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -35,7 +35,10 @@ class WatchPlugin extends FlxBasic FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); + + FlxG.watch.addQuick("musicLength", FlxG.sound?.music?.length ?? 0.0); FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0); + FlxG.watch.addQuick("bpm", Conductor.instance.bpm); FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); From 8683900922507d028e336e377482a7a812f04934 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 03:53:36 -0500 Subject: [PATCH 054/172] Fixes to cutscene pausing. --- source/funkin/graphics/video/FlxVideo.hx | 17 ++++++++ source/funkin/play/PauseSubState.hx | 28 +++++++++++-- source/funkin/play/PlayState.hx | 2 + source/funkin/play/cutscene/VideoCutscene.hx | 42 +++++++++++++++++++- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/source/funkin/graphics/video/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx index 66829af00..5e178efb3 100644 --- a/source/funkin/graphics/video/FlxVideo.hx +++ b/source/funkin/graphics/video/FlxVideo.hx @@ -40,6 +40,23 @@ class FlxVideo extends FlxBasic netStream.play(videoPath); } + public function pauseVideo():Void + { + if (netStream != null) + { + netStream.pause(); + } + } + + public function resumeVideo():Void + { + // Resume playing the video. + if (netStream != null) + { + netStream.resume(); + } + } + public function restartVideo():Void { // Seek to the beginning of the video. diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 4f2a76e19..afed43b7d 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -82,6 +82,7 @@ class PauseSubState extends MusicBeatSubState var background:FunkinSprite; var metadata:FlxTypedSpriteGroup; var metadataPractice:FlxText; + var metadataDeaths:FlxText; var menuEntryText:FlxTypedSpriteGroup; // Audio @@ -172,9 +173,8 @@ class PauseSubState extends MusicBeatSubState metadataDifficulty.scrollFactor.set(0, 0); metadata.add(metadataDifficulty); - var metadataDeaths:FlxText = new FlxText(20, 15 + 64, FlxG.width - 40, '0 Blue Balls'); + metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls'); metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); - metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; metadataDeaths.scrollFactor.set(0, 0); metadata.add(metadataDeaths); @@ -183,6 +183,8 @@ class PauseSubState extends MusicBeatSubState metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; metadataPractice.scrollFactor.set(0, 0); metadata.add(metadataPractice); + + updateMetadataText(); } function regenerateMenu(?targetMode:PauseMode):Void @@ -250,11 +252,28 @@ class PauseSubState extends MusicBeatSubState currentMenuEntries.remove(entry); } - metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + updateMetadataText(); changeSelection(); } + function updateMetadataText():Void + { + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + switch (this.currentMode) + { + case Standard | Difficulty: + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + case Charting: + metadataDeaths.text = 'Chart Editor Preview'; + case Conversation: + metadataDeaths.text = 'Dialogue Paused'; + case Cutscene: + metadataDeaths.text = 'Video Paused'; + } + } + function transitionIn():Void { FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); @@ -339,6 +358,9 @@ class PauseSubState extends MusicBeatSubState // =============== static function resume(state:PauseSubState):Void { + // Resume a paused video if it exists. + VideoCutscene.resumeVideo(); + state.close(); } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d4c0a965a..efe9cd04a 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2480,6 +2480,8 @@ class PlayState extends MusicBeatSubState // This is a video cutscene. if (controls.PAUSE && !justUnpaused) { + VideoCutscene.pauseVideo(); + var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); persistentUpdate = false; diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 8786a12b2..2d31b0a28 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -112,6 +112,7 @@ class VideoCutscene { vid.zIndex = 0; vid.bitmap.onEndReached.add(finishVideo.bind(0.5)); + vid.autoPause = false; vid.cameras = [PlayState.instance.camCutscene]; @@ -136,7 +137,7 @@ class VideoCutscene } #end - public static function restartVideo():Void + public static function restartVideo(resume:Bool = true):Void { #if html5 if (vid != null) @@ -150,6 +151,45 @@ class VideoCutscene { // Seek to the start of the video. vid.bitmap.time = 0; + if (resume) + { + // Resume the video if it was paused. + vid.resume(); + } + } + #end + } + + public static function pauseVideo():Void + { + #if html5 + if (vid != null) + { + vid.pauseVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + vid.pause(); + } + #end + } + + public static function resumeVideo():Void + { + #if html5 + if (vid != null) + { + vid.resumeVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + vid.resume(); } #end } From 2252040241a8db150c5e0b78f2dfece09c13a5ab Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 14:51:39 -0500 Subject: [PATCH 055/172] Fix custom pause music in Week 6, improve documentation --- assets | 2 +- source/funkin/play/PauseSubState.hx | 458 +++++++++++++----- source/funkin/play/PlayState.hx | 10 +- .../play/cutscene/dialogue/Conversation.hx | 16 + 4 files changed, 369 insertions(+), 117 deletions(-) diff --git a/assets b/assets index 7b9959492..1abf0f921 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7b9959492306af796204f88f7b6dcaf3e0d0c702 +Subproject commit 1abf0f92177529d542181e4f3e0b07c5eceed5e8 diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index afed43b7d..4165d5d2a 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -19,8 +19,14 @@ import funkin.ui.AtlasText; import funkin.ui.MusicBeatSubState; import funkin.ui.transition.StickerSubState; +/** + * Parameters for initializing the PauseSubState. + */ typedef PauseSubStateParams = { + /** + * Which mode to start in. Dictates what entries are displayed. + */ ?mode:PauseMode, }; @@ -29,6 +35,13 @@ typedef PauseSubStateParams = */ class PauseSubState extends MusicBeatSubState { + // =============== + // Constants + // =============== + + /** + * Pause menu entries for when the game is paused during a song. + */ static final PAUSE_MENU_ENTRIES_STANDARD:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, @@ -37,17 +50,26 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; + /** + * Pause menu entries for when the game is paused in the Chart Editor preview. + */ static final PAUSE_MENU_ENTRIES_CHARTING:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Return to Chart Editor', callback: quitToChartEditor}, ]; + /** + * Pause menu entries for when the user selects "Change Difficulty". + */ static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array = [ {text: 'Back', callback: switchMode.bind(_, Standard)} // Other entries are added dynamically. ]; + /** + * Pause menu entries for when the game is paused during a video cutscene. + */ static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Cutscene', callback: restartVideoCutscene}, @@ -55,6 +77,9 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; + /** + * Pause menu entries for when the game is paused during a conversation. + */ static final PAUSE_MENU_ENTRIES_CONVERSATION:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Dialogue', callback: restartConversation}, @@ -62,38 +87,105 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; - static final MUSIC_FADE_IN_TIME:Float = 50; - static final MUSIC_FINAL_VOLUME:Float = 0.5; - - public static var musicSuffix:String = ''; - - // Status - var currentMenuEntries:Array; - var currentEntry:Int = 0; - var currentMode:PauseMode; + /** + * Duration for the music to fade in when the pause menu is opened. + */ + static final MUSIC_FADE_IN_TIME:Float = 5; /** - * Disallow input until the transition in is complete! - * This prevents the pause menu from immediately closing. + * The final volume for the music when the pause menu is opened. + */ + static final MUSIC_FINAL_VOLUME:Float = 0.75; + + /** + * Defines which pause music to use. + */ + public static var musicSuffix:String = ''; + + /** + * Reset the pause configuration to the default. + */ + public static function reset():Void + { + musicSuffix = ''; + } + + // =============== + // Status Variables + // =============== + + /** + * Disallow input until transitions are complete! + * This prevents the pause menu from immediately closing when opened, among other things. */ public var allowInput:Bool = false; - // Graphics + /** + * The entries currently displayed in the pause menu. + */ + var currentMenuEntries:Array; + + /** + * The index of `currentMenuEntries` that is currently selected. + */ + var currentEntry:Int = 0; + + /** + * The mode that the pause menu is currently in. + */ + var currentMode:PauseMode; + + // =============== + // Graphics Variables + // =============== + + /** + * The semi-transparent black background that appears when the game is paused. + */ var background:FunkinSprite; + + /** + * The metadata displayed in the top right. + */ var metadata:FlxTypedSpriteGroup; + + /** + * A text object that displays the current practice mode status. + */ var metadataPractice:FlxText; + + /** + * A text object that displays the current death count. + */ var metadataDeaths:FlxText; + + /** + * The actual text objects for the menu entries. + */ var menuEntryText:FlxTypedSpriteGroup; - // Audio + // =============== + // Audio Variables + // =============== var pauseMusic:FunkinSound; + // =============== + // Constructor + // =============== + public function new(?params:PauseSubStateParams) { super(); this.currentMode = params?.mode ?? Standard; } + // =============== + // Lifecycle Functions + // =============== + + /** + * Called when the state is first loaded. + */ public override function create():Void { super.create(); @@ -104,15 +196,15 @@ class PauseSubState extends MusicBeatSubState buildMetadata(); - menuEntryText = new FlxTypedSpriteGroup(); - menuEntryText.scrollFactor.set(0, 0); - add(menuEntryText); - regenerateMenu(); transitionIn(); } + /** + * Called every frame. + * @param elapsed The time elapsed since the last frame, in seconds. + */ public override function update(elapsed:Float):Void { super.update(elapsed); @@ -120,21 +212,40 @@ class PauseSubState extends MusicBeatSubState handleInputs(); } + /** + * Called when the state is closed. + */ public override function destroy():Void { super.destroy(); pauseMusic.stop(); } + // =============== + // Initialization Functions + // =============== + + /** + * Play the pause music. + */ function startPauseMusic():Void { - pauseMusic = FunkinSound.load(Paths.music('breakfast$musicSuffix'), true, true); + var pauseMusicPath:String = Paths.music('breakfast$musicSuffix'); + pauseMusic = FunkinSound.load(pauseMusicPath, true, true); + + if (pauseMusic == null) + { + FlxG.log.warn('Could not play pause music: ${pauseMusicPath} does not exist!'); + } // Start playing at a random point in the song. pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); } + /** + * Render the semi-transparent black background. + */ function buildBackground():Void { // Using state.bgColor causes bugs! @@ -187,93 +298,9 @@ class PauseSubState extends MusicBeatSubState updateMetadataText(); } - function regenerateMenu(?targetMode:PauseMode):Void - { - var previousMode:PauseMode = this.currentMode; - this.currentMode = targetMode ?? this.currentMode; - this.currentEntry = 0; - - menuEntryText.clear(); - - // Choose the correct menu entries. - switch (this.currentMode) - { - case PauseMode.Standard: - currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); - case PauseMode.Charting: - currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); - case PauseMode.Difficulty: - // Prepend the difficulties. - var entries:Array = []; - if (PlayState.instance.currentChart != null) - { - var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); - trace('DIFFICULTIES: ${difficultiesInVariation}'); - for (difficulty in difficultiesInVariation) - { - entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); - } - } - - // Add the back button. - currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); - case PauseMode.Conversation: - currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); - case PauseMode.Cutscene: - currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); - } - - // Render out the entries depending on the mode. - var entryIndex:Int = 0; - var toRemove = []; - for (entry in currentMenuEntries) - { - if (entry == null || (entry.filter != null && !entry.filter())) - { - // Remove entries that should be hidden. - toRemove.push(entry); - } - else - { - // Handle visible entries. - var yPos:Float = 70 * entryIndex + 30; - var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); - text.scrollFactor.set(0, 0); - text.alpha = 0; - menuEntryText.add(text); - - entry.sprite = text; - - entryIndex++; - } - } - for (entry in toRemove) - { - currentMenuEntries.remove(entry); - } - - updateMetadataText(); - - changeSelection(); - } - - function updateMetadataText():Void - { - metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; - - switch (this.currentMode) - { - case Standard | Difficulty: - metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; - case Charting: - metadataDeaths.text = 'Chart Editor Preview'; - case Conversation: - metadataDeaths.text = 'Dialogue Paused'; - case Cutscene: - metadataDeaths.text = 'Video Paused'; - } - } - + /** + * Perform additional animations to transition the pause menu in when it is first displayed. + */ function transitionIn():Void { FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); @@ -291,6 +318,13 @@ class PauseSubState extends MusicBeatSubState }); } + // =============== + // Input Handling + // =============== + + /** + * Process user inputs every frame. + */ function handleInputs():Void { if (!allowInput) return; @@ -326,6 +360,10 @@ class PauseSubState extends MusicBeatSubState #end } + /** + * Move the current selection up or down. + * @param change The amount to change the selection by, with sign indicating direction. + */ function changeSelection(change:Int = 0):Void { FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); @@ -353,9 +391,148 @@ class PauseSubState extends MusicBeatSubState } } + // =============== + // Menu Functions + // =============== + + /** + * Clear the current menu entries and regenerate them based on the current mode. + * @param targetMode Optionally specify a mode to switch to before regenerating the menu. + */ + function regenerateMenu(?targetMode:PauseMode):Void + { + // If targetMode is null, keep the current mode. + if (targetMode == null) targetMode = this.currentMode; + + var previousMode:PauseMode = this.currentMode; + this.currentMode = targetMode; + + resetSelection(); + chooseMenuEntries(); + clearAndAddMenuEntries(); + updateMetadataText(); + changeSelection(); + } + + /** + * Reset the current selection to the first entry. + */ + function resetSelection():Void + { + this.currentEntry = 0; + } + + /** + * Select which menu entries to display based on the current mode. + */ + function chooseMenuEntries():Void + { + // Choose the correct menu entries. + // NOTE: We clone the arrays to prevent modifications to the arrays from affecting the original. + switch (this.currentMode) + { + case PauseMode.Standard: + currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); + case PauseMode.Charting: + currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); + case PauseMode.Difficulty: + // Prepend the difficulties. + var entries:Array = []; + if (PlayState.instance.currentChart != null) + { + var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); + trace('DIFFICULTIES: ${difficultiesInVariation}'); + for (difficulty in difficultiesInVariation) + { + entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); + } + } + + // Add the back button. + currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Conversation: + currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); + case PauseMode.Cutscene: + currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); + } + } + + /** + * Clear the `menuEntryText` group and render the current menu entries to it. + * We first create the `menuEntryText` group if it doesn't already exist. + */ + function clearAndAddMenuEntries():Void + { + if (menuEntryText == null) + { + menuEntryText = new FlxTypedSpriteGroup(); + menuEntryText.scrollFactor.set(0, 0); + add(menuEntryText); + } + menuEntryText.clear(); + + // Render out the entries depending on the mode. + var entryIndex:Int = 0; + var toRemove = []; + for (entry in currentMenuEntries) + { + if (entry == null || (entry.filter != null && !entry.filter())) + { + // Remove entries that should be hidden. + toRemove.push(entry); + } + else + { + // Handle visible entries. + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.scrollFactor.set(0, 0); + text.alpha = 0; + menuEntryText.add(text); + + entry.sprite = text; + + entryIndex++; + } + } + for (entry in toRemove) + { + currentMenuEntries.remove(entry); + } + } + + // =============== + // Metadata Functions + // =============== + + /** + * Update the values for the metadata text in the top right. + */ + function updateMetadataText():Void + { + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + switch (this.currentMode) + { + case Standard | Difficulty: + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + case Charting: + metadataDeaths.text = 'Chart Editor Preview'; + case Conversation: + metadataDeaths.text = 'Dialogue Paused'; + case Cutscene: + metadataDeaths.text = 'Video Paused'; + } + } + // =============== // Menu Callbacks // =============== + + /** + * Close the pause menu and resume the game. + * @param state The current PauseSubState. + */ static function resume(state:PauseSubState):Void { // Resume a paused video if it exists. @@ -364,11 +541,22 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Switch the pause menu to the indicated mode. + * Create a callback from this using `.bind(_, targetMode)`. + * @param state The current PauseSubState. + * @param targetMode The mode to switch to. + */ static function switchMode(state:PauseSubState, targetMode:PauseMode):Void { state.regenerateMenu(targetMode); } + /** + * Switch the game's difficulty to the indicated difficulty, then resume the game. + * @param state The current PauseSubState. + * @param difficulty The difficulty to switch to. + */ static function changeDifficulty(state:PauseSubState, difficulty:String):Void { PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); @@ -384,12 +572,20 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Restart the current level, then resume the game. + * @param state The current PauseSubState. + */ static function restartPlayState(state:PauseSubState):Void { PlayState.instance.needsReset = true; state.close(); } + /** + * Force the game into practice mode, then update the pause menu. + * @param state The current PauseSubState. + */ static function enablePracticeMode(state:PauseSubState):Void { if (PlayState.instance == null) return; @@ -398,18 +594,30 @@ class PauseSubState extends MusicBeatSubState state.regenerateMenu(); } + /** + * Restart the paused video cutscene, then resume the game. + * @param state The current PauseSubState. + */ static function restartVideoCutscene(state:PauseSubState):Void { VideoCutscene.restartVideo(); state.close(); } + /** + * Skip the paused video cutscene, then resume the game. + * @param state The current PauseSubState. + */ static function skipVideoCutscene(state:PauseSubState):Void { VideoCutscene.finishVideo(); state.close(); } + /** + * Restart the paused conversation, then resume the game. + * @param state The current PauseSubState. + */ static function restartConversation(state:PauseSubState):Void { if (PlayState.instance?.currentConversation == null) return; @@ -418,6 +626,10 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Skip the paused conversation, then resume the game. + * @param state The current PauseSubState. + */ static function skipConversation(state:PauseSubState):Void { if (PlayState.instance?.currentConversation == null) return; @@ -426,6 +638,10 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Quit the game and return to the main menu. + * @param state The current PauseSubState. + */ static function quitToMenu(state:PauseSubState):Void { state.allowInput = false; @@ -446,20 +662,16 @@ class PauseSubState extends MusicBeatSubState } } + /** + * Quit the game and return to the chart editor. + * @param state The current PauseSubState. + */ static function quitToChartEditor(state:PauseSubState):Void { state.close(); if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! PlayState.instance.close(); // This only works because PlayState is a substate! } - - /** - * Reset the pause configuration to the default. - */ - public static function reset():Void - { - musicSuffix = ''; - } } /** @@ -493,15 +705,31 @@ enum PauseMode Cutscene; } +/** + * Represents a single entry in the pause menu. + */ typedef PauseMenuEntry = { + /** + * The text to display for this entry. + * TODO: Implement localization. + */ var text:String; - var callback:PauseSubState->Void; - var ?sprite:AtlasText; + /** + * The callback to execute when the user selects this entry. + */ + var callback:PauseSubState->Void; /** * If this returns true, the entry will be displayed. If it returns false, the entry will be hidden. */ var ?filter:Void->Bool; + + // Instance-specific properties + + /** + * The text object currently displaying this entry. + */ + var ?sprite:AtlasText; }; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index efe9cd04a..e00d4db48 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1099,6 +1099,11 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.play(); } + if (currentConversation != null) + { + currentConversation.resumeMusic(); + } + if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -2462,10 +2467,12 @@ class PlayState extends MusicBeatSubState // Pause/unpause may conflict with advancing the conversation! if (controls.CUTSCENE_ADVANCE && !justUnpaused) { - currentConversation?.advanceConversation(); + currentConversation.advanceConversation(); } else if (controls.PAUSE && !justUnpaused) { + currentConversation.pauseMusic(); + var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); persistentUpdate = false; @@ -2740,6 +2747,7 @@ class PlayState extends MusicBeatSubState } GameOverSubState.reset(); + PauseSubState.reset(); // Clear the static reference to this state. instance = null; diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index a85a47c49..f865f3b7b 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -145,6 +145,22 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl music.play(); } + public function pauseMusic():Void + { + if (music != null) + { + music.pause(); + } + } + + public function resumeMusic():Void + { + if (music != null) + { + music.resume(); + } + } + function setupBackdrop():Void { if (backdrop != null) From 1d70ff95f9d2073d8ab761a6bb26771ffc73baa7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 21:19:21 -0500 Subject: [PATCH 056/172] Several chart editor fixes (more precise playback speed, live input mode loads properly from save) --- assets | 2 +- source/funkin/play/PlayState.hx | 13 +++++++++--- source/funkin/play/components/PopUpStuff.hx | 18 +++-------------- .../ui/debug/charting/ChartEditorState.hx | 20 +++++++++++++------ 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/assets b/assets index 84b157429..a1c3d095a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 84b1574294e7a7af21adf1a0e4894c88431ca43d +Subproject commit a1c3d095a3c16db88c010267d1a7f9695506c66c diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5bbf83e17..07cf6d352 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -631,8 +631,9 @@ class PlayState extends MusicBeatSubState // Initialize the judgements and combo meter. comboPopUps = new PopUpStuff(); - comboPopUps.cameras = [camHUD]; + comboPopUps.zIndex = 900; add(comboPopUps); + comboPopUps.cameras = [camHUD]; // The little dial that shows up when you hold the Skip Cutscene key. skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24); @@ -1277,6 +1278,7 @@ class PlayState extends MusicBeatSubState { var animShit:ComboMilestone = new ComboMilestone(-100, 300, Highscore.tallies.combo); animShit.scrollFactor.set(0.6, 0.6); + animShit.zIndex = 1100; animShit.cameras = [camHUD]; add(animShit); @@ -1363,18 +1365,21 @@ class PlayState extends MusicBeatSubState healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar')); healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(0, 0); + healthBarBG.zIndex = 800; add(healthBarBG); healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this, 'healthLerp', 0, 2); healthBar.scrollFactor.set(); healthBar.createFilledBar(Constants.COLOR_HEALTH_BAR_RED, Constants.COLOR_HEALTH_BAR_GREEN); + healthBar.zIndex = 801; add(healthBar); // The score text below the health bar. scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, '', 20); scoreText.setFormat(Paths.font('vcr.ttf'), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.scrollFactor.set(); + scoreText.zIndex = 802; add(scoreText); // Move the health bar to the HUD camera. @@ -1488,6 +1493,7 @@ class PlayState extends MusicBeatSubState iconP2 = new HealthIcon('dad', 1); iconP2.y = healthBar.y - (iconP2.height / 2); dad.initHealthIcon(true); // Apply the character ID here + iconP2.zIndex = 850; add(iconP2); iconP2.cameras = [camHUD]; @@ -1507,6 +1513,7 @@ class PlayState extends MusicBeatSubState iconP1 = new HealthIcon('bf', 0); iconP1.y = healthBar.y - (iconP1.height / 2); boyfriend.initHealthIcon(false); // Apply the character ID here + iconP1.zIndex = 850; add(iconP1); iconP1.cameras = [camHUD]; @@ -1574,13 +1581,13 @@ class PlayState extends MusicBeatSubState playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style // playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; - playerStrumline.zIndex = 200; + playerStrumline.zIndex = 1001; playerStrumline.cameras = [camHUD]; // Position the opponent strumline on the left half of the screen opponentStrumline.x = Constants.STRUMLINE_X_OFFSET; opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; - opponentStrumline.zIndex = 100; + opponentStrumline.zIndex = 1000; opponentStrumline.cameras = [camHUD]; if (!PlayStatePlaylist.isStoryMode) diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 88ffa468c..faab5e4dc 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -5,6 +5,7 @@ import flixel.group.FlxGroup.FlxTypedGroup; import flixel.tweens.FlxTween; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; +import flixel.util.FlxDirection; class PopUpStuff extends FlxTypedGroup { @@ -30,14 +31,7 @@ class PopUpStuff extends FlxTypedGroup rating.zIndex = 1000; rating.x = FlxG.width * 0.50; - rating.x -= FlxG.camera.scroll.x * 0.2; - // make sure rating is visible lol! - // if (rating.x < FlxG.camera.scroll.x) - // rating.x = FlxG.camera.scroll.x; - // else if (rating.x > FlxG.camera.scroll.x + FlxG.camera.width - rating.width) - // rating.x = FlxG.camera.scroll.x + FlxG.camera.width - rating.width; - - // FlxG.camera.scroll.y + + // rating.x -= FlxG.camera.scroll.x * 0.2; rating.y = FlxG.camera.height * 0.4 - 60; rating.acceleration.y = 550; rating.velocity.y -= FlxG.random.int(140, 175); @@ -91,13 +85,7 @@ class PopUpStuff extends FlxTypedGroup var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); comboSpr.y = FlxG.camera.height * 0.4 + 80; comboSpr.x = FlxG.width * 0.50; - comboSpr.x -= FlxG.camera.scroll.x * 0.2; - // make sure combo is visible lol! - // 194 fits 4 combo digits - // if (comboSpr.x < FlxG.camera.scroll.x + 194) - // comboSpr.x = FlxG.camera.scroll.x + 194; - // else if (comboSpr.x > FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width) - // comboSpr.x = FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width; + // comboSpr.x -= FlxG.camera.scroll.x * 0.2; comboSpr.acceleration.y = 600; comboSpr.velocity.y -= 150; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 768ea9e43..e46779483 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2838,12 +2838,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menuBarItemInputStyleNone.onClick = function(event:UIEvent) { currentLiveInputStyle = None; }; + menuBarItemInputStyleNone.selected = currentLiveInputStyle == None; menuBarItemInputStyleNumberKeys.onClick = function(event:UIEvent) { currentLiveInputStyle = NumberKeys; }; + menuBarItemInputStyleNumberKeys.selected = currentLiveInputStyle == NumberKeys; menuBarItemInputStyleWASD.onClick = function(event:UIEvent) { - currentLiveInputStyle = WASD; + currentLiveInputStyle = WASDKeys; }; + menuBarItemInputStyleWASD.selected = currentLiveInputStyle == WASDKeys; menubarItemAbout.onClick = _ -> this.openAboutDialog(); menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true); @@ -2942,7 +2945,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemPlaybackSpeed.onChange = event -> { var pitch:Float = (event.value.toFloat() * 2.0) / 100.0; - pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25. + pitch = Math.floor(pitch / 0.05) * 0.05; // Round to nearest 5% + pitch = Math.max(0.05, Math.min(2.0, pitch)); // Clamp to 5% to 200% #if FLX_PITCH if (audioInstTrack != null) audioInstTrack.pitch = pitch; audioVocalTrackGroup.pitch = pitch; @@ -4933,7 +4937,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Place notes at the playhead. switch (currentLiveInputStyle) { - case ChartEditorLiveInputStyle.WASD: + case ChartEditorLiveInputStyle.WASDKeys: if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4); if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5); if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6); @@ -5236,6 +5240,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } // Would bind Ctrl+A and Ctrl+D here, but they are already bound to Select All and Select None. } + else + { + trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).'); + } } /** @@ -6125,7 +6133,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } /** - * Available input modes for the chart editor state. + * Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds. */ enum ChartEditorLiveInputStyle { @@ -6140,9 +6148,9 @@ enum ChartEditorLiveInputStyle NumberKeys; /** - * WASD to place notes on opponent's side, arrow keys to place notes on player's side. + * WASD to place notes on opponent's side, Arrow keys to place notes on player's side. */ - WASD; + WASDKeys; } typedef ChartEditorParams = From 0139d5c4f6d941264622e7849c907d4e252cdeca Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 01:19:32 -0500 Subject: [PATCH 057/172] Fix a crash on atlas characters. --- source/funkin/play/character/AnimateAtlasCharacter.hx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index f9dc18119..418982bef 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -191,7 +191,11 @@ class AnimateAtlasCharacter extends BaseCharacter _skipTransformChildren = true; super.kill(); _skipTransformChildren = false; - this.mainSprite.kill(); + if (this.mainSprite != null) + { + this.mainSprite.kill(); + this.mainSprite = null; + } } /** From 1102c483fd265126be77c72ee46619990dc2aea9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 01:19:51 -0500 Subject: [PATCH 058/172] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a1c3d095a..8b914574f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a1c3d095a3c16db88c010267d1a7f9695506c66c +Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 From 2e012d54a43872d8bdb40688fe19f960e35a8a24 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 02:26:29 -0500 Subject: [PATCH 059/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 1abf0f921..8b914574f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1abf0f92177529d542181e4f3e0b07c5eceed5e8 +Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 From 0193c1ddeaf78e0c1018098a98ce34f9a4fb7f2a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 02:50:04 -0500 Subject: [PATCH 060/172] Update hxCodec to add autoFocus --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 700b42dfe..6ec068c52 100644 --- a/hmm.json +++ b/hmm.json @@ -73,7 +73,7 @@ "name": "hxCodec", "type": "git", "dir": null, - "ref": "c8c47e706ad82a423783006ed901b6d93c89a421", + "ref": "387e1665d6feb5762358134f168e6ebfe46acec8", "url": "https://github.com/polybiusproxy/hxCodec" }, { From aab050d757c2617e80a2649a07023087f24d7587 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 13:44:53 -0500 Subject: [PATCH 061/172] trolled use FunkinCrew for hxcodec in hmm.json --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 6ec068c52..836b01c9b 100644 --- a/hmm.json +++ b/hmm.json @@ -74,7 +74,7 @@ "type": "git", "dir": null, "ref": "387e1665d6feb5762358134f168e6ebfe46acec8", - "url": "https://github.com/polybiusproxy/hxCodec" + "url": "https://github.com/FunkinCrew/hxCodec" }, { "name": "hxcpp", From 88f57cf52078c3111288722a86e26ff9a7d1017f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 13:53:09 -0500 Subject: [PATCH 062/172] pause menu tween polish --- source/funkin/play/PauseSubState.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 4165d5d2a..2b705ea9e 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -303,14 +303,14 @@ class PauseSubState extends MusicBeatSubState */ function transitionIn():Void { - FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); + FlxTween.tween(background, {alpha: 0.6}, 0.8, {ease: FlxEase.quartOut}); // Animate each element a little bit downwards. var delay:Float = 0.1; for (child in metadata.members) { - FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); - delay += 0.05; + FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay}); + delay += 0.1; } new FlxTimer().start(0.2, (_) -> { @@ -387,7 +387,8 @@ class PauseSubState extends MusicBeatSubState var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); trace(targetY); - FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); + FlxTween.globalManager.cancelTweensOf(text); + FlxTween.tween(text, {x: targetX, y: targetY}, 0.33, {ease: FlxEase.quartOut}); } } From 684eb919b59badab5d6367c644be82b5c07e8d3d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 18:49:20 -0500 Subject: [PATCH 063/172] Make the playback rate option explicit so it doesn't break in the future. --- source/funkin/play/PlayState.hx | 20 +++++++++++++++++-- .../ui/debug/charting/ChartEditorState.hx | 5 +++++ source/funkin/ui/freeplay/FreeplayState.hx | 6 ++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 8b7dee87e..f6bb463e7 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -97,7 +97,7 @@ typedef PlayStateParams = ?targetDifficulty:String, /** * The variation to play on. - * @default `Constants.DEFAULT_VARIATION` . + * @default `Constants.DEFAULT_VARIATION` */ ?targetVariation:String, /** @@ -118,8 +118,14 @@ typedef PlayStateParams = ?minimalMode:Bool, /** * If specified, the game will jump to the specified timestamp after the countdown ends. + * @default `0.0` */ ?startTimestamp:Float, + /** + * If specified, the game will play the song with the given speed. + * @default `1.0` for 100% speed. + */ + ?playbackRate:Float, /** * If specified, the game will not load the instrumental or vocal tracks, * and must be loaded externally. @@ -210,6 +216,12 @@ class PlayState extends MusicBeatSubState */ public var startTimestamp:Float = 0.0; + /** + * Play back the song at this speed. + * @default `1.0` for normal speed. + */ + public var playbackRate:Float = 1.0; + /** * An empty FlxObject contained in the scene. * The current gameplay camera will always follow this object. Tween its position to move the camera smoothly. @@ -550,6 +562,7 @@ class PlayState extends MusicBeatSubState isPracticeMode = params.practiceMode ?? false; isMinimalMode = params.minimalMode ?? false; startTimestamp = params.startTimestamp ?? 0.0; + playbackRate = params.playbackRate ?? 1.0; overrideMusic = params.overrideMusic ?? false; previousCameraFollowPoint = params.cameraFollowPoint; @@ -777,6 +790,7 @@ class PlayState extends MusicBeatSubState // Reset music properly. FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; + FlxG.sound.music.pitch = playbackRate; FlxG.sound.music.pause(); if (!overrideMusic) @@ -1783,14 +1797,16 @@ class PlayState extends MusicBeatSubState // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset); + FlxG.sound.music.pitch = playbackRate; // I am going insane. FlxG.sound.music.volume = 1.0; - FlxG.sound.music.fadeTween.cancel(); + if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel(); trace('Playing vocals...'); add(vocals); vocals.play(); + vocals.pitch = playbackRate; resyncVocals(); #if discord_rpc diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index e46779483..2f1764ecd 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5308,6 +5308,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var startTimestamp:Float = 0; if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; + var playbackRate:Float = (menubarItemPlaybackSpeed.value.toFloat() * 2.0) / 100.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + var targetSong:Song; try { @@ -5357,6 +5361,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState practiceMode: playtestPracticeMode, minimalMode: minimal, startTimestamp: startTimestamp, + playbackRate: playbackRate, overrideMusic: true, }); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index e4a6b96d8..a1799481e 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1143,6 +1143,12 @@ class FreeplayState extends MusicBeatSubState targetSong: targetSong, targetDifficulty: targetDifficulty, targetVariation: targetVariation, + // TODO: Make this an option! + // startTimestamp: 0.0, + // TODO: Make this an option! + // playbackRate: 0.5, + practiceMode: false, + minimalMode: false, }, true); }); } From c50e7abb74307a79c19b6f88a12343c1fdb4062f Mon Sep 17 00:00:00 2001 From: FabsTheFabs Date: Fri, 1 Mar 2024 05:33:24 +0000 Subject: [PATCH 064/172] fixed Background inconsistency in mall stage --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 8b914574f..2e6341548 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 +Subproject commit 2e6341548e0f3541948b48a05093cbb82114ba94 From e7c8c941ca95a807975473357be725d962d8ab65 Mon Sep 17 00:00:00 2001 From: FabsTheFabs Date: Fri, 1 Mar 2024 05:41:54 +0000 Subject: [PATCH 065/172] added girlfriend back to winter horrorland --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 2e6341548..54f915ea2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2e6341548e0f3541948b48a05093cbb82114ba94 +Subproject commit 54f915ea2698a300f06712a5880990f932e6d632 From 0294ea0b7923f182d99be8a3906354d3b6394c70 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 1 Mar 2024 08:13:06 -0500 Subject: [PATCH 066/172] Implemented FlxAnimate characters into Blazin'. --- assets | 2 +- source/funkin/data/BaseRegistry.hx | 1 + source/funkin/data/stage/StageData.hx | 11 ++ .../graphics/adobeanimate/FlxAtlasSprite.hx | 11 +- source/funkin/modding/events/ScriptEvent.hx | 33 +++++- source/funkin/play/GameOverSubState.hx | 11 ++ source/funkin/play/PauseSubState.hx | 1 - source/funkin/play/PlayState.hx | 103 ++++++++++-------- .../play/character/AnimateAtlasCharacter.hx | 36 +++++- source/funkin/play/character/BaseCharacter.hx | 15 ++- source/funkin/play/character/CharacterData.hx | 7 ++ .../funkin/play/cutscene/dialogue/Speaker.hx | 8 +- source/funkin/play/stage/Bopper.hx | 8 +- source/funkin/play/stage/Stage.hx | 31 +++++- 14 files changed, 205 insertions(+), 73 deletions(-) diff --git a/assets b/assets index 8b914574f..874f7de39 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 +Subproject commit 874f7de39ee2dfe2ffe4c02edf701d36f2a393fd diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx index 62a7eb0f7..2df0c18f0 100644 --- a/source/funkin/data/BaseRegistry.hx +++ b/source/funkin/data/BaseRegistry.hx @@ -240,6 +240,7 @@ abstract class BaseRegistry & Constructible { + // We enforce that T is Constructible to ensure this is valid. return new T(id); } diff --git a/source/funkin/data/stage/StageData.hx b/source/funkin/data/stage/StageData.hx index cb914007f..22b883c75 100644 --- a/source/funkin/data/stage/StageData.hx +++ b/source/funkin/data/stage/StageData.hx @@ -32,18 +32,21 @@ class StageData bf: { zIndex: 0, + scale: 1, position: [0, 0], cameraOffsets: [-100, -100] }, dad: { zIndex: 0, + scale: 1, position: [0, 0], cameraOffsets: [100, -100] }, gf: { zIndex: 0, + scale: 1, position: [0, 0], cameraOffsets: [0, 0] } @@ -114,6 +117,7 @@ typedef StageDataProp = @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) @:optional + @:default(Left(1.0)) var scale:haxe.ds.Either>; /** @@ -190,6 +194,13 @@ typedef StageDataCharacter = @:default([0, 0]) var position:Array; + /** + * The scale to render the character at. + */ + @:optional + @:default(1) + var scale:Float; + /** * The camera offsets to apply when focusing on the character on this stage. * @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index 2329a2791..90965847e 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -82,6 +82,8 @@ class FlxAtlasSprite extends FlxAnimate * @param id A string ID of the animation to play. * @param restart Whether to restart the animation if it is already playing. * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing + * @param loop Whether to loop the animation + * NOTE: `loop` and `ignoreOther` are not compatible with each other! */ public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void { @@ -116,9 +118,14 @@ class FlxAtlasSprite extends FlxAnimate anim.callback = function(_, frame:Int) { if (frame == (anim.getFrameLabel(id).duration - 1) + anim.getFrameLabel(id).index) { - if (loop) playAnimation(id, true, false, true); + if (loop) + { + playAnimation(id, true, false, true); + } else + { onAnimationFinish.dispatch(id); + } } }; @@ -177,6 +184,6 @@ class FlxAtlasSprite extends FlxAnimate { canPlayOtherAnims = true; this.currentAnimation = null; - this.anim.stop(); + this.anim.pause(); } } diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 5d522e3ae..0d424a281 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -107,18 +107,18 @@ class NoteScriptEvent extends ScriptEvent public var playSound(default, default):Bool; /** - * A multiplier to the health gained or lost from this note. + * The health gained or lost from this note. * This affects both hits and misses. Remember that max health is 2.00. */ - public var healthMulti:Float; + public var healthChange:Float; - public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void + public function new(type:ScriptEventType, note:NoteSprite, healthChange:Float, comboCount:Int = 0, cancelable:Bool = false):Void { super(type, cancelable); this.note = note; this.comboCount = comboCount; this.playSound = true; - this.healthMulti = 1.0; + this.healthChange = healthChange; } public override function toString():String @@ -127,6 +127,31 @@ class NoteScriptEvent extends ScriptEvent } } +class HitNoteScriptEvent extends NoteScriptEvent +{ + /** + * The judgement the player received for hitting the note. + */ + public var judgement:String; + + /** + * The score the player received for hitting the note. + */ + public var score:Int; + + public function new(note:NoteSprite, healthChange:Float, score:Int, judgement:String, comboCount:Int = 0):Void + { + super(NOTE_HIT, note, healthChange, comboCount, true); + this.score = score; + this.judgement = judgement; + } + + public override function toString():String + { + return 'HitNoteScriptEvent(note=' + note + ', comboCount=' + comboCount + ', judgement=' + judgement + ', score=' + score + ')'; + } +} + /** * An event that is fired when you press a key with no note present. */ diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index b7e92d10f..d5132e160 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -12,6 +12,7 @@ import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.character.BaseCharacter; import funkin.play.PlayState; +import funkin.util.MathUtil; import funkin.ui.freeplay.FreeplayState; import funkin.ui.MusicBeatSubState; import funkin.ui.story.StoryMenuState; @@ -82,6 +83,9 @@ class GameOverSubState extends MusicBeatSubState var transparent:Bool; + final CAMERA_ZOOM_DURATION:Float = 0.5; + var targetCameraZoom:Float = 1.0; + public function new(params:GameOverParams) { super(); @@ -142,6 +146,7 @@ class GameOverSubState extends MusicBeatSubState FlxG.camera.target = null; FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); + targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom(); // // Set up the audio @@ -177,6 +182,9 @@ class GameOverSubState extends MusicBeatSubState } } + // Smoothly lerp the camera + FlxG.camera.zoom = MathUtil.smoothLerp(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION); + // // Handle user inputs. // @@ -286,6 +294,9 @@ class GameOverSubState extends MusicBeatSubState remove(boyfriend); PlayState.instance.currentStage.addCharacter(boyfriend, BF); + // Snap reset the camera which may have changed because of the player character data. + resetCameraZoom(); + // Close the substate. close(); }); diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 2b705ea9e..03681ce13 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -386,7 +386,6 @@ class PauseSubState extends MusicBeatSubState // Set the position. var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); - trace(targetY); FlxTween.globalManager.cancelTweensOf(text); FlxTween.tween(text, {x: targetX, y: targetY}, 0.33, {ease: FlxEase.quartOut}); } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index f6bb463e7..5cb2e20f3 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -927,7 +927,7 @@ class PlayState extends MusicBeatSubState camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } - if (currentStage != null) + if (currentStage != null && currentStage.getBoyfriend() != null) { FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } @@ -1498,17 +1498,17 @@ class PlayState extends MusicBeatSubState if (dad != null) { dad.characterType = CharacterType.DAD; - } - // - // OPPONENT HEALTH ICON - // - iconP2 = new HealthIcon('dad', 1); - iconP2.y = healthBar.y - (iconP2.height / 2); - dad.initHealthIcon(true); // Apply the character ID here - iconP2.zIndex = 850; - add(iconP2); - iconP2.cameras = [camHUD]; + // + // OPPONENT HEALTH ICON + // + iconP2 = new HealthIcon('dad', 1); + iconP2.y = healthBar.y - (iconP2.height / 2); + dad.initHealthIcon(true); // Apply the character ID here + iconP2.zIndex = 850; + add(iconP2); + iconP2.cameras = [camHUD]; + } // // BOYFRIEND @@ -1518,17 +1518,17 @@ class PlayState extends MusicBeatSubState if (boyfriend != null) { boyfriend.characterType = CharacterType.BF; - } - // - // PLAYER HEALTH ICON - // - iconP1 = new HealthIcon('bf', 0); - iconP1.y = healthBar.y - (iconP1.height / 2); - boyfriend.initHealthIcon(false); // Apply the character ID here - iconP1.zIndex = 850; - add(iconP1); - iconP1.cameras = [camHUD]; + // + // PLAYER HEALTH ICON + // + iconP1 = new HealthIcon('bf', 0); + iconP1.y = healthBar.y - (iconP1.height / 2); + boyfriend.initHealthIcon(false); // Apply the character ID here + iconP1.zIndex = 850; + add(iconP1); + iconP1.cameras = [camHUD]; + } // // ADD CHARACTERS TO SCENE @@ -2016,7 +2016,7 @@ class PlayState extends MusicBeatSubState { // Call an event to allow canceling the note miss. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, 0, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, -Constants.HEALTH_MISS_PENALTY, 0, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2025,7 +2025,7 @@ class PlayState extends MusicBeatSubState // Judge the miss. // NOTE: This is what handles the scoring. trace('Missed note! ${note.noteData}'); - onNoteMiss(note, event.playSound, event.healthMulti); + onNoteMiss(note, event.playSound, event.healthChange); note.handledMiss = true; } @@ -2171,13 +2171,41 @@ class PlayState extends MusicBeatSubState function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { - var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, Highscore.tallies.combo + 1, true); + // Calculate the input latency (do this as late as possible). + // trace('Compare: ${PreciseInputManager.getCurrentTimestamp()} - ${input.timestamp}'); + var inputLatencyNs:Int64 = PreciseInputManager.getCurrentTimestamp() - input.timestamp; + var inputLatencyMs:Float = inputLatencyNs.toFloat() / Constants.NS_PER_MS; + // trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); + + // Get the offset and compensate for input latency. + // Round inward (trim remainder) for consistency. + var noteDiff:Int = Std.int(Conductor.instance.songPosition - note.noteData.time - inputLatencyMs); + + var score = Scoring.scoreNote(noteDiff, PBOT1); + var daRating = Scoring.judgeNote(noteDiff, PBOT1); + + var healthChange = 0.0; + switch (daRating) + { + case 'sick': + healthChange = Constants.HEALTH_SICK_BONUS; + case 'good': + healthChange = Constants.HEALTH_GOOD_BONUS; + case 'bad': + healthChange = Constants.HEALTH_BAD_BONUS; + case 'shit': + healthChange = Constants.HEALTH_SHIT_BONUS; + } + + // Send the note hit event. + var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, Highscore.tallies.combo + 1); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - popUpScore(note, input, event.healthMulti); + // Display the combo meter and add the calculation to the score. + popUpScore(note, event.score, event.judgement, event.healthChange); if (note.isHoldNote && note.holdNoteSprite != null) { @@ -2191,11 +2219,11 @@ class PlayState extends MusicBeatSubState * Called when a note leaves the screen and is considered missed by the player. * @param note */ - function onNoteMiss(note:NoteSprite, playSound:Bool = false, healthLossMulti:Float = 1.0):Void + function onNoteMiss(note:NoteSprite, playSound:Bool = false, healthChange:Float):Void { // If we are here, we already CALLED the onNoteMiss script hook! - health -= Constants.HEALTH_MISS_PENALTY * healthLossMulti; + health += healthChange; songScore -= 10; if (!isPracticeMode) @@ -2367,23 +2395,10 @@ class PlayState extends MusicBeatSubState /** * Handles health, score, and rating popups when a note is hit. */ - function popUpScore(daNote:NoteSprite, input:PreciseInputEvent, healthGainMulti:Float = 1.0):Void + function popUpScore(daNote:NoteSprite, score:Int, daRating:String, healthChange:Float):Void { vocals.playerVolume = 1; - // Calculate the input latency (do this as late as possible). - // trace('Compare: ${PreciseInputManager.getCurrentTimestamp()} - ${input.timestamp}'); - var inputLatencyNs:Int64 = PreciseInputManager.getCurrentTimestamp() - input.timestamp; - var inputLatencyMs:Float = inputLatencyNs.toFloat() / Constants.NS_PER_MS; - // trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); - - // Get the offset and compensate for input latency. - // Round inward (trim remainder) for consistency. - var noteDiff:Int = Std.int(Conductor.instance.songPosition - daNote.noteData.time - inputLatencyMs); - - var score = Scoring.scoreNote(noteDiff, PBOT1); - var daRating = Scoring.judgeNote(noteDiff, PBOT1); - if (daRating == 'miss') { // If daRating is 'miss', that means we made a mistake and should not continue. @@ -2398,22 +2413,20 @@ class PlayState extends MusicBeatSubState { case 'sick': Highscore.tallies.sick += 1; - health += Constants.HEALTH_SICK_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; case 'good': Highscore.tallies.good += 1; - health += Constants.HEALTH_GOOD_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; case 'bad': Highscore.tallies.bad += 1; - health += Constants.HEALTH_BAD_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; case 'shit': Highscore.tallies.shit += 1; - health += Constants.HEALTH_SHIT_BONUS * healthGainMulti; isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; } + health += healthChange; + if (isComboBreak) { // Break the combo, but don't increment tallies.misses. diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index 418982bef..f180bd457 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -77,6 +77,7 @@ class AnimateAtlasCharacter extends BaseCharacter var atlasSprite:FlxAtlasSprite = loadAtlasSprite(); setSprite(atlasSprite); + loadAnimations(); super.onCreate(event); @@ -86,10 +87,21 @@ class AnimateAtlasCharacter extends BaseCharacter { if ((!canPlayOtherAnims && !ignoreOther)) return; - currentAnimation = name; - var prefix:String = getAnimationData(name).prefix; - if (prefix == null) prefix = name; - this.mainSprite.playAnimation(prefix, restart, ignoreOther); + var correctName = correctAnimationName(name); + if (correctName == null) return; + + var animData = getAnimationData(correctName); + currentAnimation = correctName; + var prefix:String = animData.prefix; + if (prefix == null) prefix = correctName; + var loop:Bool = animData.looped; + + this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop); + } + + public override function hasAnimation(name:String):Bool + { + return getAnimationData(name) != null; } function loadAtlasSprite():FlxAtlasSprite @@ -114,7 +126,11 @@ class AnimateAtlasCharacter extends BaseCharacter } else { + // Make the game hold on the last frame. this.mainSprite.cleanupAnimation(prefix); + + // Fallback to idle! + // playAnimation('idle', true, false); } } @@ -140,14 +156,24 @@ class AnimateAtlasCharacter extends BaseCharacter function loadAnimations():Void { - trace('[ATLASCHAR] Loading ${_data.animations.length} animations for ${characterId}'); + trace('[ATLASCHAR] Attempting to load ${_data.animations.length} animations for ${characterId}'); var animData:Array = cast _data.animations; for (anim in animData) { + // Validate the animation before adding. + var prefix = anim.prefix; + if (!this.mainSprite.hasAnimation(prefix)) + { + FlxG.log.warn('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}'); + continue; + } animations.set(anim.name, anim); + trace('[ATLASCHAR] - Successfully loaded animation ${anim.name} to ${characterId}'); } + + trace('[ATLASCHAR] Loaded ${animations.size()} animations for ${characterId}'); } public override function getCurrentAnimation():String diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 390864148..bfba3715a 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -60,7 +60,7 @@ class BaseCharacter extends Bopper @:allow(funkin.ui.debug.anim.DebugBoundingState) final _data:CharacterData; - final singTimeSec:Float; + final singTimeSteps:Float; /** * The offset between the corner of the sprite and the origin of the sprite (at the character's feet). @@ -180,7 +180,7 @@ class BaseCharacter extends Bopper { this.characterName = _data.name; this.name = _data.name; - this.singTimeSec = _data.singTime; + this.singTimeSteps = _data.singTime; this.globalOffsets = _data.offsets; this.flipX = _data.flipX; } @@ -193,6 +193,11 @@ class BaseCharacter extends Bopper return _data.death?.cameraOffsets ?? [0.0, 0.0]; } + public function getDeathCameraZoom():Float + { + return _data.death?.cameraZoom ?? 1.0; + } + /** * Gets the value of flipX from the character data. * `!getFlipX()` is the direction Boyfriend should face. @@ -367,9 +372,9 @@ class BaseCharacter extends Bopper // This lets you add frames to the end of the sing animation to ease back into the idle! holdTimer += event.elapsed; - var singTimeSec:Float = singTimeSec * (Conductor.instance.beatLengthMs * 0.001); // x beats, to ms. + var singTimeSec:Float = singTimeSteps * (Conductor.instance.stepLengthMs / Constants.MS_PER_SEC); // x beats, to ms. - if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss + if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss??? // Without this check here, the player character would only play the `sing` animation // for one beat, as opposed to holding it as long as the player is holding the button. @@ -378,7 +383,7 @@ class BaseCharacter extends Bopper FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec); if (holdTimer > singTimeSec && shouldStopSinging) { - // trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation'); + trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation'); holdTimer = 0; dance(true); } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index f3c7d7613..23710274c 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -744,4 +744,11 @@ typedef DeathData = * @default [0, 0] */ var ?cameraOffsets:Array; + + /** + * The amount to zoom the camera by while focusing on this character as they die. + * Value is a multiplier of the default camera zoom for the stage. + * @default 1.0 + */ + var ?cameraZoom:Float; } diff --git a/source/funkin/play/cutscene/dialogue/Speaker.hx b/source/funkin/play/cutscene/dialogue/Speaker.hx index f848d79c8..0d29481c4 100644 --- a/source/funkin/play/cutscene/dialogue/Speaker.hx +++ b/source/funkin/play/cutscene/dialogue/Speaker.hx @@ -218,25 +218,25 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe // If the animation exists, we're good. if (hasAnimation(name)) return name; - trace('[BOPPER] Animation "$name" does not exist!'); + FlxG.log.notice('Speaker tried to play animation "$name" that does not exist, stripping suffixes...'); // Attempt to strip a `-alt` suffix, if it exists. if (name.lastIndexOf('-') != -1) { var correctName = name.substring(0, name.lastIndexOf('-')); - trace('[BOPPER] Attempting to fallback to "$correctName"'); + FlxG.log.notice('Speaker tried to play animation "$name" that does not exist, stripping suffixes...'); return correctAnimationName(correctName); } else { if (name != 'idle') { - trace('[BOPPER] Attempting to fallback to "idle"'); + FlxG.log.warn('Speaker tried to play animation "$name" that does not exist, fallback to idle...'); return correctAnimationName('idle'); } else { - trace('[BOPPER] Failing animation playback.'); + FlxG.log.error('Speaker tried to play animation "idle" that does not exist! This is bad!'); return null; } } diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 1bc0632f9..7974900d8 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -236,25 +236,25 @@ class Bopper extends StageProp implements IPlayStateScriptedClass // If the animation exists, we're good. if (hasAnimation(name)) return name; - trace('[BOPPER] Animation "$name" does not exist!'); + FlxG.log.notice('Bopper tried to play animation "$name" that does not exist, stripping suffixes...'); // Attempt to strip a `-alt` suffix, if it exists. if (name.lastIndexOf('-') != -1) { var correctName = name.substring(0, name.lastIndexOf('-')); - trace('[BOPPER] Attempting to fallback to "$correctName"'); + FlxG.log.notice('Bopper tried to play animation "$name" that does not exist, stripping suffixes...'); return correctAnimationName(correctName); } else { if (name != 'idle') { - trace('[BOPPER] Attempting to fallback to "idle"'); + FlxG.log.warn('Bopper tried to play animation "$name" that does not exist, fallback to idle...'); return correctAnimationName('idle'); } else { - trace('[BOPPER] Failing animation playback.'); + FlxG.log.error('Bopper tried to play animation "idle" that does not exist! This is bad!'); return null; } } diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index c20202245..32c0509a5 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -110,6 +110,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements getBoyfriend().resetCharacter(true); // Reapply the camera offsets. var charData = _data.characters.bf; + getBoyfriend().scale.set(charData.scale, charData.scale); getBoyfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; getBoyfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; } @@ -122,6 +123,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements getGirlfriend().resetCharacter(true); // Reapply the camera offsets. var charData = _data.characters.gf; + getGirlfriend().scale.set(charData.scale, charData.scale); getGirlfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; getGirlfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; } @@ -130,6 +132,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements getDad().resetCharacter(true); // Reapply the camera offsets. var charData = _data.characters.dad; + getDad().scale.set(charData.scale, charData.scale); getDad().cameraFocusPoint.x += charData.cameraOffsets[0]; getDad().cameraFocusPoint.y += charData.cameraOffsets[1]; } @@ -226,7 +229,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements switch (dataProp.scale) { case Left(value): - propSprite.scale.set(value); + propSprite.scale.set(value, value); case Right(values): propSprite.scale.set(values[0], values[1]); @@ -435,6 +438,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements character.originalPosition.y = character.y + character.animOffsets[1]; } + character.scale.set(charData.scale, charData.scale); character.cameraFocusPoint.x += charData.cameraOffsets[0]; character.cameraFocusPoint.y += charData.cameraOffsets[1]; @@ -637,7 +641,30 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements */ public function dispatchToCharacters(event:ScriptEvent):Void { - for (characterId in characters.keys()) + var charList = this.characters.keys().array(); + + // Dad, then BF, then GF, in that order. + + if (charList.contains('dad')) + { + dispatchToCharacter('dad', event); + charList.remove('dad'); + } + + if (charList.contains('bf')) + { + dispatchToCharacter('bf', event); + charList.remove('bf'); + } + + if (charList.contains('gf')) + { + dispatchToCharacter('gf', event); + charList.remove('gf'); + } + + // Then the rest of the characters, if any. + for (characterId in charList) { dispatchToCharacter(characterId, event); } From f8481a0efec637cdc6f9c44d526cedfdbb696297 Mon Sep 17 00:00:00 2001 From: fabs <60322424+FabsTheFabs@users.noreply.github.com> Date: Sat, 2 Mar 2024 18:56:44 +0000 Subject: [PATCH 067/172] fixed pico game over loop from playing a duplicate track (#356) * fixed pico game over loop from playing a duplicate track that persists in menus * assets submod --------- Co-authored-by: Cameron Taylor --- assets | 2 +- source/funkin/play/GameOverSubState.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 54f915ea2..b152ad838 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 54f915ea2698a300f06712a5880990f932e6d632 +Subproject commit b152ad83868ff77307abb24893ddd5f74ed324d7 diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index b7e92d10f..857cda524 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -338,8 +338,8 @@ class GameOverSubState extends MusicBeatSubState } else { - isStarting = false; onComplete = function() { + isStarting = false; // We need to force to ensure that the non-starting music plays. startDeathMusic(1.0, true); }; From f7a3d43e54764ada2ad5d4c16d5192135fe897e2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 2 Mar 2024 22:46:13 -0500 Subject: [PATCH 068/172] Fixes to animation looping and holds --- .vscode/settings.json | 1 + assets | 2 +- .../graphics/adobeanimate/FlxAtlasSprite.hx | 6 ++- .../play/character/AnimateAtlasCharacter.hx | 27 +++++++++-- source/funkin/play/notes/NoteSprite.hx | 46 ++++++++++++++----- source/funkin/play/notes/Strumline.hx | 1 - .../ui/debug/charting/ChartEditorState.hx | 2 +- .../charting/util/ChartEditorDropdowns.hx | 36 ++++++++++----- .../funkin/ui/debug/latency/LatencyState.hx | 2 +- source/funkin/ui/options/ColorsMenu.hx | 2 +- 10 files changed, 91 insertions(+), 34 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ec86904ea..87ed06aed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,6 +71,7 @@ "haxe.displayPort": "auto", "haxe.enableCompilationServer": false, + "haxe.enableServerView": true, "haxe.displayServer": { "arguments": ["-v"] }, diff --git a/assets b/assets index 874f7de39..dcfda23b5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 874f7de39ee2dfe2ffe4c02edf701d36f2a393fd +Subproject commit dcfda23b549f84a2314d3b676972af237f321ac7 diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index 90965847e..9a2af8913 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -116,7 +116,9 @@ class FlxAtlasSprite extends FlxAnimate } anim.callback = function(_, frame:Int) { - if (frame == (anim.getFrameLabel(id).duration - 1) + anim.getFrameLabel(id).index) + var offset = loop ? 0 : -1; + + if (frame == (anim.getFrameLabel(id).duration + offset) + anim.getFrameLabel(id).index) { if (loop) { @@ -183,7 +185,7 @@ class FlxAtlasSprite extends FlxAnimate public function cleanupAnimation(_:String):Void { canPlayOtherAnims = true; - this.currentAnimation = null; + // this.currentAnimation = null; this.anim.pause(); } } diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index f180bd457..9e7aa98bf 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -46,7 +46,8 @@ class AnimateAtlasCharacter extends BaseCharacter var _skipTransformChildren:Bool = false; var animations:Map = new Map(); - var currentAnimation:String; + var currentAnimName:Null = null; + var animFinished:Bool = false; public function new(id:String) { @@ -88,15 +89,21 @@ class AnimateAtlasCharacter extends BaseCharacter if ((!canPlayOtherAnims && !ignoreOther)) return; var correctName = correctAnimationName(name); - if (correctName == null) return; + if (correctName == null) + { + trace('Could not find Atlas animation: ' + name); + return; + } var animData = getAnimationData(correctName); - currentAnimation = correctName; + currentAnimName = correctName; var prefix:String = animData.prefix; if (prefix == null) prefix = correctName; var loop:Bool = animData.looped; this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop); + + animFinished = false; } public override function hasAnimation(name:String):Bool @@ -104,6 +111,15 @@ class AnimateAtlasCharacter extends BaseCharacter return getAnimationData(name) != null; } + /** + * Returns true if the animation has finished playing. + * Never true if animation is configured to loop. + */ + public override function isAnimationFinished():Bool + { + return animFinished; + } + function loadAtlasSprite():FlxAtlasSprite { trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.'); @@ -128,6 +144,8 @@ class AnimateAtlasCharacter extends BaseCharacter { // Make the game hold on the last frame. this.mainSprite.cleanupAnimation(prefix); + // currentAnimName = null; + animFinished = true; // Fallback to idle! // playAnimation('idle', true, false); @@ -178,7 +196,8 @@ class AnimateAtlasCharacter extends BaseCharacter public override function getCurrentAnimation():String { - return this.mainSprite.getCurrentAnimation(); + // return this.mainSprite.getCurrentAnimation(); + return currentAnimName; } function getAnimationData(name:String = null):AnimateAtlasAnimation diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index 45862b26d..b16b88466 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -16,26 +16,53 @@ class NoteSprite extends FunkinSprite var hsvShader:HSVShader; /** - * The time at which the note should be hit, in milliseconds. + * The strum time at which the note should be hit, in milliseconds. */ - public var strumTime(default, set):Float; + public var strumTime(get, set):Float; + + function get_strumTime():Float + { + return this.noteData?.time ?? 0.0; + } function set_strumTime(value:Float):Float { - this.strumTime = value; - return this.strumTime; + if (this.noteData == null) return value; + return this.noteData.time = value; + } + + /** + * The length for which the note should be held, in milliseconds. + * Defaults to 0 for single notes. + */ + public var length(get, set):Float; + + function get_length():Float + { + return this.noteData?.length ?? 0.0; + } + + function set_length(value:Float):Float + { + if (this.noteData == null) return value; + return this.noteData.length = value; } /** * An extra attribute for the note. * For example, whether the note is an "alt" note, or whether it has custom behavior on hit. */ - public var kind(default, set):String; + public var kind(get, set):Null; + + function get_kind():Null + { + return this.noteData?.kind; + } function set_kind(value:String):String { - this.kind = value; - return this.kind; + if (this.noteData == null) return value; + return this.noteData.kind = value; } /** @@ -100,16 +127,13 @@ class NoteSprite extends FunkinSprite */ public var handledMiss:Bool; - public function new(noteStyle:NoteStyle, strumTime:Float = 0, direction:Int = 0) + public function new(noteStyle:NoteStyle, direction:Int = 0) { super(0, -9999); - this.strumTime = strumTime; this.direction = direction; this.hsvShader = new HSVShader(); - if (this.strumTime < 0) this.strumTime = 0; - setupNoteGraphic(noteStyle); // Disables the update() function for performance. diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 5fdd3945f..190aa3ee0 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -659,7 +659,6 @@ class Strumline extends FlxSpriteGroup if (noteSprite != null) { - noteSprite.strumTime = note.time; noteSprite.direction = note.getDirection(); noteSprite.noteData = note; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 2f1764ecd..a662ce2bd 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5308,7 +5308,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var startTimestamp:Float = 0; if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; - var playbackRate:Float = (menubarItemPlaybackSpeed.value.toFloat() * 2.0) / 100.0; + var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index 26015161b..4ec5101f7 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -117,18 +117,30 @@ class ChartEditorDropdowns "ugh" => "Ugh (Week 7)", "hehPrettyGood" => "Heh, Pretty Good (Week 7)", // Weekend 1 - "weekend-1-lightcan" => "Light Can (2hot)", - "weekend-1-kickcan" => "Kick Can (2hot)", - "weekend-1-kneecan" => "Knee Can (2hot)", - "weekend-1-cockgun" => "Cock Gun (2hot)", - "weekend-1-firegun" => "Fire Gun (2hot)", - "weekend-1-punchlow" => "Punch Low (Blazin)", - "weekend-1-punchhigh" => "Punch High (Blazin)", - "weekend-1-punchlowblocked" => "Punch Low Blocked (Blazin)", - "weekend-1-punchhighblocked" => "Punch High Blocked (Blazin)", - "weekend-1-dodgelow" => "Dodge Low (Blazin)", - "weekend-1-blockhigh" => "Block High (Blazin)", - "weekend-1-fakeout" => "Fakeout (Blazin)", + "weekend-1-punchhigh" => "Punch High (Blazin')", + "weekend-1-punchhighdodged" => "Punch High (Dodge) (Blazin')", + "weekend-1-punchhighblocked" => "Punch High (Block) (Blazin')", + "weekend-1-punchhighspin" => "Punch High (Spin) (Blazin')", + "weekend-1-punchlow" => "Punch Low (Blazin')", + "weekend-1-punchlowdodged" => "Punch Low (Dodge) (Blazin')", + "weekend-1-punchlowblocked" => "Punch Low (Block) (Blazin')", + "weekend-1-punchlowspin" => "Punch High (Spin) (Blazin')", + "weekend-1-picouppercutprep" => "Pico Uppercut (Prep) (Blazin')", + "weekend-1-picouppercut" => "Pico Uppercut (Blazin')", + "weekend-1-blockhigh" => "Block High (Blazin')", + "weekend-1-blocklow" => "Dodge High (Blazin')", + "weekend-1-blockspin" => "Block High (Spin) (Blazin')", + "weekend-1-dodgehigh" => "Block Low (Blazin')", + "weekend-1-dodgelow" => "Dodge Low (Blazin')", + "weekend-1-dodgespin" => "Dodge High (Spin) (Blazin')", + "weekend-1-hithigh" => "Hit High (Blazin')", + "weekend-1-hitlow" => "Hit Low (Blazin')", + "weekend-1-hitspin" => "Hit High (Spin) (Blazin')", + "weekend-1-darnelluppercutprep" => "Darnell Uppercut (Prep) (Blazin')", + "weekend-1-darnelluppercut" => "Darnell Uppercut (Blazin')", + "weekend-1-idle" => "Idle (Blazin')", + "weekend-1-fakeout" => "Fakeout (Blazin')", + "weekend-1-reversefakeout" => "Reverse Fakeout (Blazin')", ]; public static function populateDropdownWithNoteKinds(dropDown:DropDown, startingKindId:String):DropDownEntry diff --git a/source/funkin/ui/debug/latency/LatencyState.hx b/source/funkin/ui/debug/latency/LatencyState.hx index 70ef97fd0..9ebd29537 100644 --- a/source/funkin/ui/debug/latency/LatencyState.hx +++ b/source/funkin/ui/debug/latency/LatencyState.hx @@ -130,7 +130,7 @@ class LatencyState extends MusicBeatSubState for (i in 0...32) { - var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), Conductor.instance.beatLengthMs * i); + var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault()); noteGrp.add(note); } diff --git a/source/funkin/ui/options/ColorsMenu.hx b/source/funkin/ui/options/ColorsMenu.hx index 928f74ba8..146384564 100644 --- a/source/funkin/ui/options/ColorsMenu.hx +++ b/source/funkin/ui/options/ColorsMenu.hx @@ -23,7 +23,7 @@ class ColorsMenu extends Page for (i in 0...4) { - var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), 0, i); + var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), i); note.x = (100 * i) + i; note.screenCenter(Y); From 761a3d53df744fc9e403061f8fd81369d609db47 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 2 Mar 2024 23:23:52 -0500 Subject: [PATCH 069/172] Taunt improvements. --- assets | 2 +- source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assets b/assets index dcfda23b5..e32bc0bbc 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit dcfda23b549f84a2314d3b676972af237f321ac7 +Subproject commit e32bc0bbcecef4120e4272bc94f952801be06ec9 diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index 4ec5101f7..b53de174e 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -140,7 +140,9 @@ class ChartEditorDropdowns "weekend-1-darnelluppercut" => "Darnell Uppercut (Blazin')", "weekend-1-idle" => "Idle (Blazin')", "weekend-1-fakeout" => "Fakeout (Blazin')", - "weekend-1-reversefakeout" => "Reverse Fakeout (Blazin')", + "weekend-1-taunt" => "Taunt (If Fakeout) (Blazin')", + "weekend-1-tauntforce" => "Taunt (Forced) (Blazin')", + "weekend-1-reversefakeout" => "Fakeout (Reverse) (Blazin')", ]; public static function populateDropdownWithNoteKinds(dropDown:DropDown, startingKindId:String):DropDownEntry From 0a19c7a8cb18753ece1625d8f5f4f4af325a4b6e Mon Sep 17 00:00:00 2001 From: Hazel Date: Sun, 3 Mar 2024 04:49:27 +0000 Subject: [PATCH 070/172] fix(ci), feat(ci), refactor: i don't need sleep, i need answers (#357) * hx the codec * fix(ci,html5): use haxe.Timer instead of Sys.time * refactor(compat): use haxe.Timer instead of Sys.time(), introduce TimerUtil to reduce code dupe * fix: redundant types * refactor(style): use TimerTools in place of haxe.Timer * refactor: consistent timer code * feat: build timings * refactor(ci): cleanup ci configs * sigh * sigh, 2 * fix: haxelib deleterepo does not silently fail * retrigger ci * verbose output * debug info after haxelib gti * force haxelib git override * more debug info * force bash * at least haxelib is consistent now * fix the runners first, then do that * update ci-haxe * it is time? * deleterepo may fail * finishing touches --- .github/actions/setup-haxeshit/action.yml | 39 ++++-- .github/workflows/build-shit.yml | 113 +++++++----------- .gitignore | 1 + source/Postbuild.hx | 30 ++++- source/Prebuild.hx | 15 ++- source/funkin/InitState.hx | 7 +- .../audio/visualize/SpectogramSprite.hx | 1 - source/funkin/audio/visualize/VisShit.hx | 1 - .../audio/waveform/WaveformDataParser.hx | 7 +- source/funkin/play/components/PopUpStuff.hx | 21 +--- .../handlers/ChartEditorAudioHandler.hx | 47 ++++---- .../toolboxes/ChartEditorFreeplayToolbox.hx | 19 +-- source/funkin/util/plugins/MemoryGCPlugin.hx | 6 +- source/funkin/util/tools/FloatTools.hx | 9 ++ source/funkin/util/tools/TimerTools.hx | 30 +++++ 15 files changed, 205 insertions(+), 141 deletions(-) create mode 100644 source/funkin/util/tools/TimerTools.hx diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index 9c1fae0b1..236d29944 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -3,21 +3,46 @@ description: "sets up haxe shit, using HMM!" runs: using: "composite" steps: - - uses: funkincrew/ci-haxe@v3 + - name: Install Haxe lol + uses: funkincrew/ci-haxe@v3.1.0 with: haxe-version: 4.3.3 - name: Config haxelib run: | - haxelib config + haxelib --never install haxelib 4.1.0 --global + haxelib --never deleterepo || true + haxelib --never newrepo + echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV" + haxelib --never git haxelib https://github.com/HaxeFoundation/haxelib.git master shell: bash - - name: Installing Haxe lol + - name: Gather debug info + run: | + cat << EOF >> "$GITHUB_STEP_SUMMARY" + ## haxe + - version: \`$(haxe -version)\` + - exe: \`$(which haxe)\` + ## haxelib + - version: \`$(haxelib version)\` + - exe: \`$(which haxelib)\` + - path: \`$HAXEPATH\` + ### local + - config: \`$(haxelib config)\` + - path: \`$(haxelib path haxelib || true)\` + ### global + - config: \`$(haxelib config --global)\` + - path: \`$(haxelib path haxelib --global || true)\` + ### system + - version: \`$(haxelib --system version)\` + - local: \`$(haxelib --system config)\` + - global: \`$(haxelib --system config --global)\` + EOF + shell: bash + - name: Install hmm + # hmm only supports global installs run: | - haxe -version - haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git master - haxelib version haxelib --global install hmm shell: bash - - name: dependency install cache + - name: Restore cached dependencies id: cache-hmm uses: actions/cache@v4 with: diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 388670a01..5a1f5609a 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -8,71 +8,80 @@ jobs: runs-on: [self-hosted, linux] container: ubuntu:23.10 steps: - - name: prepare container + - name: Install tools missing in container run: | apt update - apt install sudo git curl unzip -y - git config --global --add safe.directory $GITHUB_WORKSPACE - - name: get token from gh app + apt install -y sudo git curl unzip + - name: Fix git config on posix runner + run: | + git config --global --add safe.directory ${{ github.workspace }} + - name: Get checkout token uses: actions/create-github-app-token@v1 id: app_token with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PEM }} owner: ${{ github.repository_owner }} - - name: checkout repo + - name: Checkout repo uses: funkincrew/ci-checkout@v6 with: submodules: 'recursive' token: ${{ steps.app_token.outputs.token }} - - uses: ./.github/actions/setup-haxeshit - - name: gather game dependencies + - name: Install Haxe, dependencies + uses: ./.github/actions/setup-haxeshit + - name: Install native dependencies run: | - sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev - - name: build game + apt install -y \ + libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \ + libgl-dev libgl1-mesa-dev \ + libasound2-dev + - name: Build game run: | haxelib run lime build html5 -release --times -DGITHUB_BUILD - ls - - uses: ./.github/actions/upload-itch + - name: Upload build artifacts + uses: ./.github/actions/upload-itch with: butler-key: ${{ secrets.BUTLER_API_KEY}} build-dir: export/release/html5/bin target: html5 create-nightly-win: runs-on: [self-hosted, windows] + defaults: + run: + shell: bash steps: - - name: get token from gh app + - name: Get checkout token uses: actions/create-github-app-token@v1 id: app_token with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PEM }} owner: ${{ github.repository_owner }} - - name: checkout repo + - name: Checkout repo uses: funkincrew/ci-checkout@v6 with: submodules: 'recursive' token: ${{ steps.app_token.outputs.token }} - - uses: ./.github/actions/setup-haxeshit - - name: Make HXCPP cache dir + - name: Install Haxe, dependencies + uses: ./.github/actions/setup-haxeshit + - name: Setup build cache run: | - mkdir -p ${{ runner.temp }}\hxcpp_cache + mkdir -p ${{ runner.temp }}/hxcpp_cache - name: Restore build cache id: cache-build-win uses: actions/cache@v4 with: path: | - .haxelib export - ${{ runner.temp }}\hxcpp_cache - key: ${{ runner.os }}-build-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }} - - name: build game + ${{ runner.temp }}/hxcpp_cache + key: ${{ runner.os }}-build-win-${{ github.ref_name }} + - name: Build game run: | - haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD - dir + haxelib run lime build windows -v -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD env: HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache" - - uses: ./.github/actions/upload-itch + - name: Upload build artifacts + uses: ./.github/actions/upload-itch with: butler-key: ${{ secrets.BUTLER_API_KEY }} build-dir: export/release/windows/bin @@ -80,78 +89,42 @@ jobs: create-nightly-mac: runs-on: [self-hosted, macos] steps: - - name: prepare container + - name: Fix git config on posix runner run: | - git config --global --add safe.directory $GITHUB_WORKSPACE - - name: get token from gh app + git config --global --add safe.directory ${{ github.workspace }} + - name: Get checkout token uses: actions/create-github-app-token@v1 id: app_token with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PEM }} owner: ${{ github.repository_owner }} - - name: checkout repo + - name: Checkout repo uses: funkincrew/ci-checkout@v6 with: submodules: 'recursive' token: ${{ steps.app_token.outputs.token }} - - uses: ./.github/actions/setup-haxeshit - - name: Make HXCPP cache dir + - name: Install Haxe, dependencies + uses: ./.github/actions/setup-haxeshit + - name: Setup build cache run: | mkdir -p ${{ runner.temp }}/hxcpp_cache - - name: restore build cache + - name: Restore build cache id: cache-build-win uses: actions/cache@v4 with: path: | - .haxelib export ${{ runner.temp }}/hxcpp_cache - key: ${{ runner.os }}-build-mac-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }} + key: ${{ runner.os }}-build-mac-${{ github.ref_name }} - name: Build game run: | haxelib run lime build macos -release --times -DGITHUB_BUILD - ls env: HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache" - - uses: ./.github/actions/upload-itch + - name: Upload build artifacts + uses: ./.github/actions/upload-itch with: butler-key: ${{ secrets.BUTLER_API_KEY}} build-dir: export/release/macos/bin target: macos - -# test-unit-win: -# needs: create-nightly-win -# runs-on: windows-latest -# steps: -# - name: get token from gh app -# uses: actions/create-github-app-token@v1 -# id: app_token -# with: -# app-id: ${{ vars.APP_ID }} -# private-key: ${{ secrets.APP_PEM }} -# owner: ${{ github.repository_owner }} -# - name: checkout repo -# uses: funkincrew/ci-checkout@v6 -# with: -# submodules: 'recursive' -# token: ${{ steps.app_token.outputs.token }} -# - name: Make HXCPP cache dir -# run: | -# mkdir -p ${{ runner.temp }}\hxcpp_cache -# - name: Restore build cache -# id: cache-build-win -# uses: actions/cache@v4 -# with: -# path: | -# .haxelib -# export -# ${{ runner.temp }}\hxcpp_cache -# key: ${{ runner.os }}-test-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }} -# - uses: ./.github/actions/setup-haxeshit -# - name: Run unit tests -# run: | -# cd ./tests/unit/ -# ./start-win-native.bat -# env: -# HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache" diff --git a/.gitignore b/.gitignore index 34a0c5590..87fd97fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dump/ export/ RECOVER_*.fla shitAudio/ +.build_time diff --git a/source/Postbuild.hx b/source/Postbuild.hx index d48b670a4..f1827c4ab 100644 --- a/source/Postbuild.hx +++ b/source/Postbuild.hx @@ -1,11 +1,37 @@ package source; // Yeah, I know... +import sys.FileSystem; +import sys.io.File; + class Postbuild { + static inline final buildTimeFile = '.build_time'; + static function main() { - trace('Postbuild'); + printBuildTime(); + } - // TODO: Maybe put a 'Build took X seconds' message here? + static function printBuildTime() + { + // get buildEnd before fs operations since they are blocking + var end:Float = Sys.time(); + if (FileSystem.exists(buildTimeFile)) + { + var fi = File.read(buildTimeFile); + var start:Float = fi.readDouble(); + fi.close(); + + sys.FileSystem.deleteFile(buildTimeFile); + + var buildTime = roundToTwoDecimals(end - start); + + trace('Build took: ${buildTime} seconds'); + } + } + + private static function roundToTwoDecimals(value:Float):Float + { + return Math.round(value * 100) / 100; } } diff --git a/source/Prebuild.hx b/source/Prebuild.hx index 63782fc56..18a5e2076 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -1,9 +1,22 @@ package source; // Yeah, I know... +import sys.io.File; + class Prebuild { + static inline final buildTimeFile = '.build_time'; + static function main() { - trace('Prebuild'); + saveBuildTime(); + trace('Building...'); + } + + static function saveBuildTime() + { + var fo = File.write(buildTimeFile); + var now = Sys.time(); + fo.writeDouble(now); + fo.close(); } } diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 7dc20b385..2368d09f2 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -30,6 +30,7 @@ import funkin.modding.module.ModuleHandler; import funkin.ui.title.TitleState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; +import funkin.util.tools.TimerTools; import funkin.ui.transition.LoadingState; #if discord_rpc import Discord.DiscordClient; @@ -219,7 +220,7 @@ class InitState extends FlxState // NOTE: Registries must be imported and not referenced with fully qualified names, // to ensure build macros work properly. trace('Parsing game data...'); - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry. SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); @@ -236,9 +237,7 @@ class InitState extends FlxState ModuleHandler.loadModuleCache(); ModuleHandler.callOnCreate(); - var perfEnd = haxe.Timer.stamp(); - - trace('Parsing game data took ${Math.floor((perfEnd - perfStart) * 1000)}ms.'); + trace('Parsing game data took: ${TimerTools.ms(perfStart)}'); } /** diff --git a/source/funkin/audio/visualize/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx index b4e024a4c..470dbf7fe 100644 --- a/source/funkin/audio/visualize/SpectogramSprite.hx +++ b/source/funkin/audio/visualize/SpectogramSprite.hx @@ -10,7 +10,6 @@ import flixel.util.FlxColor; import funkin.audio.visualize.PolygonSpectogram.VISTYPE; import funkin.audio.visualize.VisShit.CurAudioInfo; import funkin.audio.visualize.dsp.FFT; -import haxe.Timer; import lime.system.ThreadPool; import lime.utils.Int16Array; diff --git a/source/funkin/audio/visualize/VisShit.hx b/source/funkin/audio/visualize/VisShit.hx index 5bfb8c7c5..204ced1e1 100644 --- a/source/funkin/audio/visualize/VisShit.hx +++ b/source/funkin/audio/visualize/VisShit.hx @@ -3,7 +3,6 @@ package funkin.audio.visualize; import flixel.math.FlxMath; import flixel.sound.FlxSound; import funkin.audio.visualize.dsp.FFT; -import haxe.Timer; import lime.system.ThreadPool; import lime.utils.Int16Array; import funkin.util.MathUtil; diff --git a/source/funkin/audio/waveform/WaveformDataParser.hx b/source/funkin/audio/waveform/WaveformDataParser.hx index 54a142f6a..c667f2002 100644 --- a/source/funkin/audio/waveform/WaveformDataParser.hx +++ b/source/funkin/audio/waveform/WaveformDataParser.hx @@ -1,5 +1,7 @@ package funkin.audio.waveform; +import funkin.util.tools.TimerTools; + class WaveformDataParser { static final INT16_MAX:Int = 32767; @@ -71,7 +73,7 @@ class WaveformDataParser var outputData:Array = []; - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); for (pointIndex in 0...outputPointCount) { @@ -108,8 +110,7 @@ class WaveformDataParser var outputDataLength:Int = Std.int(outputData.length / channels / 2); var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData); - var perfEnd = haxe.Timer.stamp(); - trace('[WAVEFORM] Interpreted audio buffer in ${perfEnd - perfStart} seconds.'); + trace('[WAVEFORM] Interpreted audio buffer in ${TimerTools.seconds(perfStart)}.'); return result; } diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index faab5e4dc..0fe50f513 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -3,9 +3,10 @@ package funkin.play.components; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.tweens.FlxTween; +import flixel.util.FlxDirection; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; -import flixel.util.FlxDirection; +import funkin.util.tools.TimerTools; class PopUpStuff extends FlxTypedGroup { @@ -16,9 +17,7 @@ class PopUpStuff extends FlxTypedGroup public function displayRating(daRating:String) { - #if sys - var perfStart:Float = Sys.time(); - #end + var perfStart:Float = TimerTools.start(); if (daRating == null) daRating = "good"; @@ -60,17 +59,12 @@ class PopUpStuff extends FlxTypedGroup startDelay: Conductor.instance.beatLengthMs * 0.001 }); - #if sys - var perfEnd:Float = Sys.time(); - trace("displayRating took: " + (perfEnd - perfStart)); - #end + trace('displayRating took: ${TimerTools.seconds(perfStart)}'); } public function displayCombo(?combo:Int = 0):Int { - #if sys - var perfStart:Float = Sys.time(); - #end + var perfStart:Float = TimerTools.start(); if (combo == null) combo = 0; @@ -163,10 +157,7 @@ class PopUpStuff extends FlxTypedGroup daLoop++; } - #if sys - var perfEnd:Float = Sys.time(); - trace("displayCombo took: " + (perfEnd - perfStart)); - #end + trace('displayCombo took: ${TimerTools.seconds(perfStart)}'); return combo; } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 5e3ffeb42..889f5764f 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -7,6 +7,7 @@ import funkin.audio.FunkinSound; import funkin.play.character.BaseCharacter.CharacterType; import funkin.util.FileUtil; import funkin.util.assets.SoundUtil; +import funkin.util.tools.TimerTools; import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformDataParser; import funkin.audio.waveform.WaveformSprite; @@ -128,41 +129,41 @@ class ChartEditorAudioHandler public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool { - var perfA = haxe.Timer.stamp(); + var perfA:Float = TimerTools.start(); var result:Bool = playInstrumental(state, instId); if (!result) return false; - var perfB = haxe.Timer.stamp(); + var perfB:Float = TimerTools.start(); stopExistingVocals(state); - var perfC = haxe.Timer.stamp(); + var perfC:Float = TimerTools.start(); result = playVocals(state, BF, playerId, instId); - var perfD = haxe.Timer.stamp(); + var perfD:Float = TimerTools.start(); // if (!result) return false; result = playVocals(state, DAD, opponentId, instId); // if (!result) return false; - var perfE = haxe.Timer.stamp(); + var perfE:Float = TimerTools.start(); state.hardRefreshOffsetsToolbox(); - var perfF = haxe.Timer.stamp(); + var perfF:Float = TimerTools.start(); state.hardRefreshFreeplayToolbox(); - var perfG = haxe.Timer.stamp(); + var perfG:Float = TimerTools.start(); - trace('Switched to instrumental in ${perfB - perfA} seconds.'); - trace('Stopped existing vocals in ${perfC - perfB} seconds.'); - trace('Played BF vocals in ${perfD - perfC} seconds.'); - trace('Played DAD vocals in ${perfE - perfD} seconds.'); - trace('Hard refreshed offsets toolbox in ${perfF - perfE} seconds.'); - trace('Hard refreshed freeplay toolbox in ${perfG - perfF} seconds.'); + trace('Switched to instrumental in ${TimerTools.seconds(perfA, perfB)}.'); + trace('Stopped existing vocals in ${TimerTools.seconds(perfB, perfC)}.'); + trace('Played BF vocals in ${TimerTools.seconds(perfC, perfD)}.'); + trace('Played DAD vocals in ${TimerTools.seconds(perfD, perfE)}.'); + trace('Hard refreshed offsets toolbox in ${TimerTools.seconds(perfE, perfF)}.'); + trace('Hard refreshed freeplay toolbox in ${TimerTools.seconds(perfF, perfG)}.'); return true; } @@ -174,10 +175,9 @@ class ChartEditorAudioHandler { if (instId == '') instId = 'default'; var instTrackData:Null = state.audioInstTrackData.get(instId); - var perfA = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData); - var perfB = haxe.Timer.stamp(); - trace('Built instrumental track in ${perfB - perfA} seconds.'); + trace('Built instrumental track in ${TimerTools.seconds(perfStart)} seconds.'); if (instTrack == null) return false; stopExistingInstrumental(state); @@ -205,10 +205,9 @@ class ChartEditorAudioHandler { var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; var vocalTrackData:Null = state.audioVocalTrackData.get(trackId); - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData); - var perfEnd = haxe.Timer.stamp(); - trace('Built vocal track in ${perfEnd - perfStart} seconds.'); + trace('Built vocal track in ${TimerTools.seconds(perfStart)}.'); if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup(); @@ -219,10 +218,9 @@ class ChartEditorAudioHandler case BF: state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); var waveformData:Null = vocalTrack.waveformData; - var perfEnd = haxe.Timer.stamp(); - trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.'); + trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); if (waveformData != null) { @@ -246,10 +244,9 @@ class ChartEditorAudioHandler case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); var waveformData:Null = vocalTrack.waveformData; - var perfEnd = haxe.Timer.stamp(); - trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.'); + trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); if (waveformData != null) { diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx index 1432c9205..28d435c54 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx @@ -1,21 +1,22 @@ package funkin.ui.debug.charting.toolboxes; +import flixel.addons.display.FlxTiledSprite; +import flixel.math.FlxMath; import funkin.audio.SoundGroup; +import funkin.audio.waveform.WaveformDataParser; +import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand; +import funkin.ui.haxeui.components.WaveformPlayer; +import funkin.ui.freeplay.FreeplayState; +import funkin.util.tools.TimerTools; +import haxe.ui.backend.flixel.components.SpriteWrapper; import haxe.ui.components.Button; import haxe.ui.components.HorizontalSlider; import haxe.ui.components.Label; -import flixel.addons.display.FlxTiledSprite; -import flixel.math.FlxMath; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; -import haxe.ui.backend.flixel.components.SpriteWrapper; -import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand; -import funkin.ui.haxeui.components.WaveformPlayer; -import funkin.audio.waveform.WaveformDataParser; import haxe.ui.containers.VBox; import haxe.ui.containers.Absolute; import haxe.ui.containers.ScrollView; -import funkin.ui.freeplay.FreeplayState; import haxe.ui.containers.Frame; import haxe.ui.core.Screen; import haxe.ui.events.DragEvent; @@ -288,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox // Build player waveform. // waveformMusic.waveform.forceUpdate = true; - var perfStart = haxe.Timer.stamp(); + var perfStart:Float = TimerTools.start(); var waveformData1 = playerVoice?.waveformData; var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file! var waveformData3 = chartEditorState.audioInstTrack.waveformData; var waveformData = waveformData3.merge(waveformData1).merge(waveformData2); - trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds'); + trace('Waveform data merging took: ${TimerTools.seconds(perfStart)}'); waveformMusic.waveform.waveformData = waveformData; // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it. diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx index 3df861eb5..67a4fe18e 100644 --- a/source/funkin/util/plugins/MemoryGCPlugin.hx +++ b/source/funkin/util/plugins/MemoryGCPlugin.hx @@ -1,6 +1,7 @@ package funkin.util.plugins; import flixel.FlxBasic; +import funkin.util.tools.TimerTools; /** * A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection. @@ -23,10 +24,9 @@ class MemoryGCPlugin extends FlxBasic if (FlxG.keys.justPressed.INSERT) { - var perfStart:Float = Sys.time(); + var perfStart:Float = TimerTools.start(); funkin.util.MemoryUtil.collect(true); - var perfEnd:Float = Sys.time(); - trace("Memory GC took " + (perfEnd - perfStart) + " seconds"); + trace('Memory GC took: ${TimerTools.seconds(perfStart)}'); } } diff --git a/source/funkin/util/tools/FloatTools.hx b/source/funkin/util/tools/FloatTools.hx index e07ae5cb9..e34110490 100644 --- a/source/funkin/util/tools/FloatTools.hx +++ b/source/funkin/util/tools/FloatTools.hx @@ -12,4 +12,13 @@ class FloatTools { return Math.max(min, Math.min(max, value)); } + + /** + Round a float to a certain number of decimal places. + **/ + public static function round(number:Float, ?precision = 2):Float + { + number *= Math.pow(10, precision); + return Math.round(number) / Math.pow(10, precision); + } } diff --git a/source/funkin/util/tools/TimerTools.hx b/source/funkin/util/tools/TimerTools.hx new file mode 100644 index 000000000..5322ada92 --- /dev/null +++ b/source/funkin/util/tools/TimerTools.hx @@ -0,0 +1,30 @@ +package funkin.util.tools; + +import funkin.util.tools.FloatTools; +import haxe.Timer; + +class TimerTools +{ + public static function start():Float + { + return Timer.stamp(); + } + + private static function took(start:Float, ?end:Float):Float + { + var endOrNow:Float = end != null ? end : Timer.stamp(); + return endOrNow - start; + } + + public static function seconds(start:Float, ?end:Float, ?precision = 2):String + { + var seconds:Float = FloatTools.round(took(start, end), precision); + return '${seconds} seconds'; + } + + public static function ms(start:Float, ?end:Float):String + { + var seconds:Float = took(start, end); + return '${seconds * 1000} ms'; + } +} From c36ea962dc9d78fb1328ba717f6d78b53fb559e4 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 3 Mar 2024 01:40:58 -0500 Subject: [PATCH 071/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index e32bc0bbc..2d694cdfd 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit e32bc0bbcecef4120e4272bc94f952801be06ec9 +Subproject commit 2d694cdfd90b9cc516b3e41a552212dd9e6d037b From bdb0fe6b51a8ef6ead098fd286d041a154d682c0 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Mon, 4 Mar 2024 02:08:38 -0800 Subject: [PATCH 072/172] Bump assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b152ad838..9a55f8b24 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b152ad83868ff77307abb24893ddd5f74ed324d7 +Subproject commit 9a55f8b24bd41a8b8f03302b4f155e18b97774ff From fcce3454543a5d5073b6887c40c55443939ac9a9 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Mon, 4 Mar 2024 02:26:38 -0800 Subject: [PATCH 073/172] Avoid null reference error with music --- source/funkin/play/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 8b7dee87e..dd507963c 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1786,7 +1786,7 @@ class PlayState extends MusicBeatSubState // I am going insane. FlxG.sound.music.volume = 1.0; - FlxG.sound.music.fadeTween.cancel(); + FlxG.sound.music.fadeTween?.cancel(); trace('Playing vocals...'); add(vocals); From 5557dbf28fe490419e0db12bbc25982ff9bddc1f Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Mon, 4 Mar 2024 02:59:10 -0800 Subject: [PATCH 074/172] Avoid unpausing music when debugger is toggled Workaround a Flixel issue where sounds can unpause after toggling the Flixel debugger with F2, caused by the debugger throwing a spurious onFocus event without an onFocusLost event. --- source/funkin/audio/FunkinSound.hx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 06ba48e81..cf28ed08e 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -90,6 +90,11 @@ class FunkinSound extends FlxSound implements ICloneable */ var _label:String = "unknown"; + /** + * Whether we received a focus lost event. + */ + var _lostFocus:Bool = false; + public function new() { super(); @@ -177,7 +182,10 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocus():Void { - if (!_alreadyPaused) + // Flixel can sometimes toss spurious `onFocus` events, e.g. if the Flixel debugger is toggled + // on and off. We only want to resume the sound if we actually lost focus, and if we weren't + // already paused before we lost focus. + if (_lostFocus && !_alreadyPaused) { resume(); } @@ -185,6 +193,7 @@ class FunkinSound extends FlxSound implements ICloneable { trace('Not resuming audio on focus!'); } + _lostFocus = false; } /** @@ -193,6 +202,7 @@ class FunkinSound extends FlxSound implements ICloneable override function onFocusLost():Void { trace('Focus lost, pausing audio!'); + _lostFocus = true; _alreadyPaused = _paused; pause(); } From 5d00f57bfb9704f1ced5b86afb126d1fd388c038 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Mon, 4 Mar 2024 03:43:17 -0800 Subject: [PATCH 075/172] Properly handle pausing sounds with negative startTime --- source/funkin/audio/FunkinSound.hx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index cf28ed08e..a1e14d705 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -172,8 +172,18 @@ class FunkinSound extends FlxSound implements ICloneable public override function pause():FunkinSound { - super.pause(); - this._shouldPlay = false; + if (_shouldPlay) + { + // This sound will eventually play, but is still at a negative timestamp. + // Manually set the paused flag to ensure proper focus/unfocus behavior. + _shouldPlay = false; + _paused = true; + active = false; + } + else + { + super.pause(); + } return this; } @@ -211,7 +221,10 @@ class FunkinSound extends FlxSound implements ICloneable { if (this._time < 0) { - this._shouldPlay = true; + // Sound with negative timestamp, restart the timer. + _shouldPlay = true; + _paused = false; + active = true; } else { From 73cf6b9fd2ec287be794ddeeb1c09587db9ae32f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 16:37:42 -0500 Subject: [PATCH 076/172] Rewrite save data to fix scripted story levels. Add a "pre-transition death delay" --- assets | 2 +- source/funkin/PlayerSettings.hx | 12 +- source/funkin/Preferences.hx | 28 +- source/funkin/api/newgrounds/NGUtil.hx | 12 +- source/funkin/api/newgrounds/NGio.hx | 12 +- source/funkin/modding/PolymodHandler.hx | 4 +- source/funkin/play/PlayState.hx | 53 ++- source/funkin/play/character/BaseCharacter.hx | 5 + source/funkin/play/character/CharacterData.hx | 6 + source/funkin/save/Save.hx | 349 ++++++++++-------- .../funkin/save/migrator/SaveDataMigrator.hx | 10 +- .../ui/debug/charting/ChartEditorState.hx | 8 +- source/funkin/ui/freeplay/FreeplayState.hx | 4 +- source/funkin/ui/story/StoryMenuState.hx | 2 +- 14 files changed, 298 insertions(+), 209 deletions(-) diff --git a/assets b/assets index 2d694cdfd..8b8086ac0 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2d694cdfd90b9cc516b3e41a552212dd9e6d037b +Subproject commit 8b8086ac0ff072e23996828f19a05f064dafe5cf diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx index 2e3e0e425..5a9351bba 100644 --- a/source/funkin/PlayerSettings.hx +++ b/source/funkin/PlayerSettings.hx @@ -84,9 +84,9 @@ class PlayerSettings function addKeyboard():Void { var useDefault = true; - if (Save.get().hasControls(id, Keys)) + if (Save.instance.hasControls(id, Keys)) { - var keyControlData = Save.get().getControls(id, Keys); + var keyControlData = Save.instance.getControls(id, Keys); trace("keyControlData: " + haxe.Json.stringify(keyControlData)); useDefault = false; controls.fromSaveData(keyControlData, Keys); @@ -112,9 +112,9 @@ class PlayerSettings function addGamepad(gamepad:FlxGamepad) { var useDefault = true; - if (Save.get().hasControls(id, Gamepad(gamepad.id))) + if (Save.instance.hasControls(id, Gamepad(gamepad.id))) { - var padControlData = Save.get().getControls(id, Gamepad(gamepad.id)); + var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id)); trace("padControlData: " + haxe.Json.stringify(padControlData)); useDefault = false; controls.addGamepadWithSaveData(gamepad.id, padControlData); @@ -141,7 +141,7 @@ class PlayerSettings if (keyData != null) { trace("saving key data: " + haxe.Json.stringify(keyData)); - Save.get().setControls(id, Keys, keyData); + Save.instance.setControls(id, Keys, keyData); } if (controls.gamepadsAdded.length > 0) @@ -150,7 +150,7 @@ class PlayerSettings if (padData != null) { trace("saving pad data: " + haxe.Json.stringify(padData)); - Save.get().setControls(id, Gamepad(controls.gamepadsAdded[0]), padData); + Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData); } } } diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 039a4c285..60c7a996a 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -15,12 +15,12 @@ class Preferences static function get_naughtyness():Bool { - return Save.get().options.naughtyness; + return Save.instance.options.naughtyness; } static function set_naughtyness(value:Bool):Bool { - var save = Save.get(); + var save = Save.instance; save.options.naughtyness = value; save.flush(); return value; @@ -34,12 +34,12 @@ class Preferences static function get_downscroll():Bool { - return Save.get().options.downscroll; + return Save.instance.options.downscroll; } static function set_downscroll(value:Bool):Bool { - var save = Save.get(); + var save = Save.instance; save.options.downscroll = value; save.flush(); return value; @@ -53,12 +53,12 @@ class Preferences static function get_flashingLights():Bool { - return Save.get().options.flashingLights; + return Save.instance.options.flashingLights; } static function set_flashingLights(value:Bool):Bool { - var save = Save.get(); + var save = Save.instance; save.options.flashingLights = value; save.flush(); return value; @@ -72,12 +72,12 @@ class Preferences static function get_zoomCamera():Bool { - return Save.get().options.zoomCamera; + return Save.instance.options.zoomCamera; } static function set_zoomCamera(value:Bool):Bool { - var save = Save.get(); + var save = Save.instance; save.options.zoomCamera = value; save.flush(); return value; @@ -91,17 +91,17 @@ class Preferences static function get_debugDisplay():Bool { - return Save.get().options.debugDisplay; + return Save.instance.options.debugDisplay; } static function set_debugDisplay(value:Bool):Bool { - if (value != Save.get().options.debugDisplay) + if (value != Save.instance.options.debugDisplay) { toggleDebugDisplay(value); } - var save = Save.get(); + var save = Save.instance; save.options.debugDisplay = value; save.flush(); return value; @@ -115,14 +115,14 @@ class Preferences static function get_autoPause():Bool { - return Save.get().options.autoPause; + return Save.instance.options.autoPause; } static function set_autoPause(value:Bool):Bool { - if (value != Save.get().options.autoPause) FlxG.autoPause = value; + if (value != Save.instance.options.autoPause) FlxG.autoPause = value; - var save = Save.get(); + var save = Save.instance; save.options.autoPause = value; save.flush(); return value; diff --git a/source/funkin/api/newgrounds/NGUtil.hx b/source/funkin/api/newgrounds/NGUtil.hx index c8289fc46..e9902a798 100644 --- a/source/funkin/api/newgrounds/NGUtil.hx +++ b/source/funkin/api/newgrounds/NGUtil.hx @@ -86,10 +86,10 @@ class NGUtil #end var onSessionFail:Error->Void = null; - if (sessionId == null && Save.get().ngSessionId != null) + if (sessionId == null && Save.instance.ngSessionId != null) { trace("using stored session id"); - sessionId = Save.get().ngSessionId; + sessionId = Save.instance.ngSessionId; onSessionFail = function(error) savedSessionFailed = true; } #end @@ -159,8 +159,8 @@ class NGUtil static function onNGLogin():Void { trace('logged in! user:${NG.core.user.name}'); - Save.get().ngSessionId = NG.core.sessionId; - Save.get().flush(); + Save.instance.ngSessionId = NG.core.sessionId; + Save.instance.flush(); // Load medals then call onNGMedalFetch() NG.core.requestMedals(onNGMedalFetch); @@ -174,8 +174,8 @@ class NGUtil { NG.core.logOut(); - Save.get().ngSessionId = null; - Save.get().flush(); + Save.instance.ngSessionId = null; + Save.instance.flush(); } // --- MEDALS diff --git a/source/funkin/api/newgrounds/NGio.hx b/source/funkin/api/newgrounds/NGio.hx index e505bdedf..c1f8ad3ba 100644 --- a/source/funkin/api/newgrounds/NGio.hx +++ b/source/funkin/api/newgrounds/NGio.hx @@ -86,10 +86,10 @@ class NGio #end var onSessionFail:Error->Void = null; - if (sessionId == null && Save.get().ngSessionId != null) + if (sessionId == null && Save.instance.ngSessionId != null) { trace("using stored session id"); - sessionId = Save.get().ngSessionId; + sessionId = Save.instance.ngSessionId; onSessionFail = function(error) savedSessionFailed = true; } #end @@ -159,8 +159,8 @@ class NGio static function onNGLogin():Void { trace('logged in! user:${NG.core.user.name}'); - Save.get().ngSessionId = NG.core.sessionId; - Save.get().flush(); + Save.instance.ngSessionId = NG.core.sessionId; + Save.instance.flush(); // Load medals then call onNGMedalFetch() NG.core.requestMedals(onNGMedalFetch); @@ -174,8 +174,8 @@ class NGio { NG.core.logOut(); - Save.get().ngSessionId = null; - Save.get().flush(); + Save.instance.ngSessionId = null; + Save.instance.flush(); } // --- MEDALS diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index f1e82aee9..889f63073 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -61,7 +61,7 @@ class PolymodHandler createModRoot(); trace("Initializing Polymod (using configured mods)..."); - loadModsById(Save.get().enabledModIds); + loadModsById(Save.instance.enabledModIds); } /** @@ -236,7 +236,7 @@ class PolymodHandler public static function getEnabledMods():Array { - var modIds = Save.get().enabledModIds; + var modIds = Save.instance.enabledModIds; var modMetadata = getAllMods(); var enabledMods = []; for (item in modMetadata) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5cb2e20f3..5421f1d2d 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -282,6 +282,12 @@ class PlayState extends MusicBeatSubState */ public var isPracticeMode:Bool = false; + /** + * Whether the player has dropped below zero health, + * and we are just waiting for an animation to play out before transitioning. + */ + public var isPlayerDying:Bool = false; + /** * In Minimal Mode, the stage and characters are not loaded and a standard background is used. */ @@ -785,6 +791,7 @@ class PlayState extends MusicBeatSubState persistentDraw = true; startingSong = true; + isPlayerDying = false; inputSpitter = []; @@ -953,7 +960,7 @@ class PlayState extends MusicBeatSubState } #end - if (health <= Constants.HEALTH_MIN && !isPracticeMode) + if (health <= Constants.HEALTH_MIN && !isPracticeMode && !isPlayerDying) { vocals.pause(); FlxG.sound.music.pause(); @@ -979,20 +986,30 @@ class PlayState extends MusicBeatSubState } #end - var gameOverSubState = new GameOverSubState( - { - isChartingMode: isChartingMode, - transparent: persistentDraw + isPlayerDying = true; + + var deathPreTransitionDelay = currentStage?.getBoyfriend()?.getDeathPreTransitionDelay() ?? 0.0; + if (deathPreTransitionDelay > 0) + { + new FlxTimer().start(deathPreTransitionDelay, function(_) { + moveToGameOver(); }); - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; - openSubState(gameOverSubState); + } + else + { + // Transition immediately. + moveToGameOver(); + } #if discord_rpc // Game Over doesn't get his own variable because it's only used here DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); #end } + else if (isPlayerDying) + { + // Wait up. + } } processSongEvents(); @@ -1008,6 +1025,18 @@ class PlayState extends MusicBeatSubState justUnpaused = false; } + function moveToGameOver():Void + { + var gameOverSubState = new GameOverSubState( + { + isChartingMode: isChartingMode, + transparent: persistentDraw + }); + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; + openSubState(gameOverSubState); + } + function processSongEvents():Void { // Query and activate song events. @@ -2592,9 +2621,9 @@ class PlayState extends MusicBeatSubState accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, }; - if (Save.get().isSongHighScore(currentSong.id, currentDifficulty, data)) + if (Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data)) { - Save.get().setSongScore(currentSong.id, currentDifficulty, data); + Save.instance.setSongScore(currentSong.id, currentDifficulty, data); #if newgrounds NGio.postScore(score, currentSong.id); #end @@ -2642,9 +2671,9 @@ class PlayState extends MusicBeatSubState accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, }; - if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) + if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) { - Save.get().setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); + Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); #if newgrounds NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}'); #end diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index bfba3715a..cf5311bdc 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -198,6 +198,11 @@ class BaseCharacter extends Bopper return _data.death?.cameraZoom ?? 1.0; } + public function getDeathPreTransitionDelay():Float + { + return _data.death?.preTransitionDelay ?? 0.0; + } + /** * Gets the value of flipX from the character data. * `!getFlipX()` is the direction Boyfriend should face. diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 23710274c..56d7b7793 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -751,4 +751,10 @@ typedef DeathData = * @default 1.0 */ var ?cameraZoom:Float; + + /** + * Impose a delay between when the character reaches `0` health and when the death animation plays. + * @default 0.0 + */ + var ?preTransitionDelay:Float; } diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 6246dcb58..73ba8efa0 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -11,8 +11,7 @@ import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme; import thx.semver.Version; @:nullSafety -@:forward(volume, mute) -abstract Save(RawSaveData) +class Save { // Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null. public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2"; @@ -25,6 +24,20 @@ abstract Save(RawSaveData) static final SAVE_PATH_LEGACY:String = 'ninjamuffin99'; static final SAVE_NAME_LEGACY:String = 'funkin'; + public static var instance(get, never):Save; + static var _instance:Null = null; + + static function get_instance():Save + { + if (_instance == null) + { + _instance = new Save(FlxG.save.data); + } + return _instance; + } + + var data:RawSaveData; + public static function load():Void { trace("[SAVE] Loading save..."); @@ -33,84 +46,85 @@ abstract Save(RawSaveData) loadFromSlot(1); } - public static function get():Save - { - return FlxG.save.data; - } - /** * Constructing a new Save will load the default values. */ - public function new() + public function new(data:RawSaveData) { - this = - { - version: Save.SAVE_DATA_VERSION, + this.data = data; - volume: 1.0, - mute: false, + if (this.data == null) data = Save.getDefault(); + } - api: - { - newgrounds: - { - sessionId: null, - } - }, - scores: - { - // No saved scores. - levels: [], - songs: [], - }, - options: - { - // Reasonable defaults. - naughtyness: true, - downscroll: false, - flashingLights: true, - zoomCamera: true, - debugDisplay: false, - autoPause: true, + public static function getDefault():RawSaveData + { + return { + version: Save.SAVE_DATA_VERSION, - controls: - { - // Leave controls blank so defaults are loaded. - p1: - { - keyboard: {}, - gamepad: {}, - }, - p2: - { - keyboard: {}, - gamepad: {}, - }, - }, - }, + volume: 1.0, + mute: false, - mods: - { - // No mods enabled. - enabledMods: [], - modOptions: [], - }, + api: + { + newgrounds: + { + sessionId: null, + } + }, + scores: + { + // No saved scores. + levels: [], + songs: [], + }, + options: + { + // Reasonable defaults. + naughtyness: true, + downscroll: false, + flashingLights: true, + zoomCamera: true, + debugDisplay: false, + autoPause: true, - optionsChartEditor: - { - // Reasonable defaults. - previousFiles: [], - noteQuant: 3, - chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None, - theme: ChartEditorTheme.Light, - playtestStartTime: false, - downscroll: false, - metronomeVolume: 1.0, - hitsoundVolumePlayer: 1.0, - hitsoundVolumeOpponent: 1.0, - themeMusic: true - }, - }; + controls: + { + // Leave controls blank so defaults are loaded. + p1: + { + keyboard: {}, + gamepad: {}, + }, + p2: + { + keyboard: {}, + gamepad: {}, + }, + }, + }, + + mods: + { + // No mods enabled. + enabledMods: [], + modOptions: [], + }, + + optionsChartEditor: + { + // Reasonable defaults. + previousFiles: [], + noteQuant: 3, + chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None, + theme: ChartEditorTheme.Light, + playtestStartTime: false, + downscroll: false, + metronomeVolume: 1.0, + hitsoundVolumePlayer: 1.0, + hitsoundVolumeOpponent: 1.0, + themeMusic: true + }, + }; } /** @@ -120,7 +134,7 @@ abstract Save(RawSaveData) function get_options():SaveDataOptions { - return this.options; + return data.options; } /** @@ -130,7 +144,7 @@ abstract Save(RawSaveData) function get_modOptions():Map { - return this.mods.modOptions; + return data.mods.modOptions; } /** @@ -140,232 +154,232 @@ abstract Save(RawSaveData) function get_ngSessionId():Null { - return this.api.newgrounds.sessionId; + return data.api.newgrounds.sessionId; } function set_ngSessionId(value:Null):Null { - this.api.newgrounds.sessionId = value; + data.api.newgrounds.sessionId = value; flush(); - return this.api.newgrounds.sessionId; + return data.api.newgrounds.sessionId; } public var enabledModIds(get, set):Array; function get_enabledModIds():Array { - return this.mods.enabledMods; + return data.mods.enabledMods; } function set_enabledModIds(value:Array):Array { - this.mods.enabledMods = value; + data.mods.enabledMods = value; flush(); - return this.mods.enabledMods; + return data.mods.enabledMods; } public var chartEditorPreviousFiles(get, set):Array; function get_chartEditorPreviousFiles():Array { - if (this.optionsChartEditor.previousFiles == null) this.optionsChartEditor.previousFiles = []; + if (data.optionsChartEditor.previousFiles == null) data.optionsChartEditor.previousFiles = []; - return this.optionsChartEditor.previousFiles; + return data.optionsChartEditor.previousFiles; } function set_chartEditorPreviousFiles(value:Array):Array { // Set and apply. - this.optionsChartEditor.previousFiles = value; + data.optionsChartEditor.previousFiles = value; flush(); - return this.optionsChartEditor.previousFiles; + return data.optionsChartEditor.previousFiles; } public var chartEditorHasBackup(get, set):Bool; function get_chartEditorHasBackup():Bool { - if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = false; + if (data.optionsChartEditor.hasBackup == null) data.optionsChartEditor.hasBackup = false; - return this.optionsChartEditor.hasBackup; + return data.optionsChartEditor.hasBackup; } function set_chartEditorHasBackup(value:Bool):Bool { // Set and apply. - this.optionsChartEditor.hasBackup = value; + data.optionsChartEditor.hasBackup = value; flush(); - return this.optionsChartEditor.hasBackup; + return data.optionsChartEditor.hasBackup; } public var chartEditorNoteQuant(get, set):Int; function get_chartEditorNoteQuant():Int { - if (this.optionsChartEditor.noteQuant == null) this.optionsChartEditor.noteQuant = 3; + if (data.optionsChartEditor.noteQuant == null) data.optionsChartEditor.noteQuant = 3; - return this.optionsChartEditor.noteQuant; + return data.optionsChartEditor.noteQuant; } function set_chartEditorNoteQuant(value:Int):Int { // Set and apply. - this.optionsChartEditor.noteQuant = value; + data.optionsChartEditor.noteQuant = value; flush(); - return this.optionsChartEditor.noteQuant; + return data.optionsChartEditor.noteQuant; } public var chartEditorLiveInputStyle(get, set):ChartEditorLiveInputStyle; function get_chartEditorLiveInputStyle():ChartEditorLiveInputStyle { - if (this.optionsChartEditor.chartEditorLiveInputStyle == null) this.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None; + if (data.optionsChartEditor.chartEditorLiveInputStyle == null) data.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None; - return this.optionsChartEditor.chartEditorLiveInputStyle; + return data.optionsChartEditor.chartEditorLiveInputStyle; } function set_chartEditorLiveInputStyle(value:ChartEditorLiveInputStyle):ChartEditorLiveInputStyle { // Set and apply. - this.optionsChartEditor.chartEditorLiveInputStyle = value; + data.optionsChartEditor.chartEditorLiveInputStyle = value; flush(); - return this.optionsChartEditor.chartEditorLiveInputStyle; + return data.optionsChartEditor.chartEditorLiveInputStyle; } public var chartEditorDownscroll(get, set):Bool; function get_chartEditorDownscroll():Bool { - if (this.optionsChartEditor.downscroll == null) this.optionsChartEditor.downscroll = false; + if (data.optionsChartEditor.downscroll == null) data.optionsChartEditor.downscroll = false; - return this.optionsChartEditor.downscroll; + return data.optionsChartEditor.downscroll; } function set_chartEditorDownscroll(value:Bool):Bool { // Set and apply. - this.optionsChartEditor.downscroll = value; + data.optionsChartEditor.downscroll = value; flush(); - return this.optionsChartEditor.downscroll; + return data.optionsChartEditor.downscroll; } public var chartEditorPlaytestStartTime(get, set):Bool; function get_chartEditorPlaytestStartTime():Bool { - if (this.optionsChartEditor.playtestStartTime == null) this.optionsChartEditor.playtestStartTime = false; + if (data.optionsChartEditor.playtestStartTime == null) data.optionsChartEditor.playtestStartTime = false; - return this.optionsChartEditor.playtestStartTime; + return data.optionsChartEditor.playtestStartTime; } function set_chartEditorPlaytestStartTime(value:Bool):Bool { // Set and apply. - this.optionsChartEditor.playtestStartTime = value; + data.optionsChartEditor.playtestStartTime = value; flush(); - return this.optionsChartEditor.playtestStartTime; + return data.optionsChartEditor.playtestStartTime; } public var chartEditorTheme(get, set):ChartEditorTheme; function get_chartEditorTheme():ChartEditorTheme { - if (this.optionsChartEditor.theme == null) this.optionsChartEditor.theme = ChartEditorTheme.Light; + if (data.optionsChartEditor.theme == null) data.optionsChartEditor.theme = ChartEditorTheme.Light; - return this.optionsChartEditor.theme; + return data.optionsChartEditor.theme; } function set_chartEditorTheme(value:ChartEditorTheme):ChartEditorTheme { // Set and apply. - this.optionsChartEditor.theme = value; + data.optionsChartEditor.theme = value; flush(); - return this.optionsChartEditor.theme; + return data.optionsChartEditor.theme; } public var chartEditorMetronomeVolume(get, set):Float; function get_chartEditorMetronomeVolume():Float { - if (this.optionsChartEditor.metronomeVolume == null) this.optionsChartEditor.metronomeVolume = 1.0; + if (data.optionsChartEditor.metronomeVolume == null) data.optionsChartEditor.metronomeVolume = 1.0; - return this.optionsChartEditor.metronomeVolume; + return data.optionsChartEditor.metronomeVolume; } function set_chartEditorMetronomeVolume(value:Float):Float { // Set and apply. - this.optionsChartEditor.metronomeVolume = value; + data.optionsChartEditor.metronomeVolume = value; flush(); - return this.optionsChartEditor.metronomeVolume; + return data.optionsChartEditor.metronomeVolume; } public var chartEditorHitsoundVolumePlayer(get, set):Float; function get_chartEditorHitsoundVolumePlayer():Float { - if (this.optionsChartEditor.hitsoundVolumePlayer == null) this.optionsChartEditor.hitsoundVolumePlayer = 1.0; + if (data.optionsChartEditor.hitsoundVolumePlayer == null) data.optionsChartEditor.hitsoundVolumePlayer = 1.0; - return this.optionsChartEditor.hitsoundVolumePlayer; + return data.optionsChartEditor.hitsoundVolumePlayer; } function set_chartEditorHitsoundVolumePlayer(value:Float):Float { // Set and apply. - this.optionsChartEditor.hitsoundVolumePlayer = value; + data.optionsChartEditor.hitsoundVolumePlayer = value; flush(); - return this.optionsChartEditor.hitsoundVolumePlayer; + return data.optionsChartEditor.hitsoundVolumePlayer; } public var chartEditorHitsoundVolumeOpponent(get, set):Float; function get_chartEditorHitsoundVolumeOpponent():Float { - if (this.optionsChartEditor.hitsoundVolumeOpponent == null) this.optionsChartEditor.hitsoundVolumeOpponent = 1.0; + if (data.optionsChartEditor.hitsoundVolumeOpponent == null) data.optionsChartEditor.hitsoundVolumeOpponent = 1.0; - return this.optionsChartEditor.hitsoundVolumeOpponent; + return data.optionsChartEditor.hitsoundVolumeOpponent; } function set_chartEditorHitsoundVolumeOpponent(value:Float):Float { // Set and apply. - this.optionsChartEditor.hitsoundVolumeOpponent = value; + data.optionsChartEditor.hitsoundVolumeOpponent = value; flush(); - return this.optionsChartEditor.hitsoundVolumeOpponent; + return data.optionsChartEditor.hitsoundVolumeOpponent; } public var chartEditorThemeMusic(get, set):Bool; function get_chartEditorThemeMusic():Bool { - if (this.optionsChartEditor.themeMusic == null) this.optionsChartEditor.themeMusic = true; + if (data.optionsChartEditor.themeMusic == null) data.optionsChartEditor.themeMusic = true; - return this.optionsChartEditor.themeMusic; + return data.optionsChartEditor.themeMusic; } function set_chartEditorThemeMusic(value:Bool):Bool { // Set and apply. - this.optionsChartEditor.themeMusic = value; + data.optionsChartEditor.themeMusic = value; flush(); - return this.optionsChartEditor.themeMusic; + return data.optionsChartEditor.themeMusic; } public var chartEditorPlaybackSpeed(get, set):Float; function get_chartEditorPlaybackSpeed():Float { - if (this.optionsChartEditor.playbackSpeed == null) this.optionsChartEditor.playbackSpeed = 1.0; + if (data.optionsChartEditor.playbackSpeed == null) data.optionsChartEditor.playbackSpeed = 1.0; - return this.optionsChartEditor.playbackSpeed; + return data.optionsChartEditor.playbackSpeed; } function set_chartEditorPlaybackSpeed(value:Float):Float { // Set and apply. - this.optionsChartEditor.playbackSpeed = value; + data.optionsChartEditor.playbackSpeed = value; flush(); - return this.optionsChartEditor.playbackSpeed; + return data.optionsChartEditor.playbackSpeed; } /** @@ -377,11 +391,11 @@ abstract Save(RawSaveData) */ public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null { - var level = this.scores.levels.get(levelId); + var level = data.scores.levels.get(levelId); if (level == null) { level = []; - this.scores.levels.set(levelId, level); + data.scores.levels.set(levelId, level); } return level.get(difficultyId); @@ -392,11 +406,11 @@ abstract Save(RawSaveData) */ public function setLevelScore(levelId:String, difficultyId:String, score:SaveScoreData):Void { - var level = this.scores.levels.get(levelId); + var level = data.scores.levels.get(levelId); if (level == null) { level = []; - this.scores.levels.set(levelId, level); + data.scores.levels.set(levelId, level); } level.set(difficultyId, score); @@ -405,11 +419,11 @@ abstract Save(RawSaveData) public function isLevelHighScore(levelId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool { - var level = this.scores.levels.get(levelId); + var level = data.scores.levels.get(levelId); if (level == null) { level = []; - this.scores.levels.set(levelId, level); + data.scores.levels.set(levelId, level); } var currentScore = level.get(difficultyId); @@ -448,11 +462,11 @@ abstract Save(RawSaveData) */ public function getSongScore(songId:String, difficultyId:String = 'normal'):Null { - var song = this.scores.songs.get(songId); + var song = data.scores.songs.get(songId); if (song == null) { song = []; - this.scores.songs.set(songId, song); + data.scores.songs.set(songId, song); } return song.get(difficultyId); } @@ -462,11 +476,11 @@ abstract Save(RawSaveData) */ public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void { - var song = this.scores.songs.get(songId); + var song = data.scores.songs.get(songId); if (song == null) { song = []; - this.scores.songs.set(songId, song); + data.scores.songs.set(songId, song); } song.set(difficultyId, score); @@ -482,11 +496,11 @@ abstract Save(RawSaveData) */ public function isSongHighScore(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool { - var song = this.scores.songs.get(songId); + var song = data.scores.songs.get(songId); if (song == null) { song = []; - this.scores.songs.set(songId, song); + data.scores.songs.set(songId, song); } var currentScore = song.get(difficultyId); @@ -527,9 +541,9 @@ abstract Save(RawSaveData) switch (inputType) { case Keys: - return (playerId == 0) ? this.options.controls.p1.keyboard : this.options.controls.p2.keyboard; + return (playerId == 0) ? data.options.controls.p1.keyboard : data.options.controls.p2.keyboard; case Gamepad(_): - return (playerId == 0) ? this.options.controls.p1.gamepad : this.options.controls.p2.gamepad; + return (playerId == 0) ? data.options.controls.p1.gamepad : data.options.controls.p2.gamepad; } } @@ -547,20 +561,20 @@ abstract Save(RawSaveData) case Keys: if (playerId == 0) { - this.options.controls.p1.keyboard = controls; + data.options.controls.p1.keyboard = controls; } else { - this.options.controls.p2.keyboard = controls; + data.options.controls.p2.keyboard = controls; } case Gamepad(_): if (playerId == 0) { - this.options.controls.p1.gamepad = controls; + data.options.controls.p1.gamepad = controls; } else { - this.options.controls.p2.gamepad = controls; + data.options.controls.p2.gamepad = controls; } } @@ -581,6 +595,36 @@ abstract Save(RawSaveData) } } + /** + * The user's current volume setting. + */ + public var volume(get, set):Float; + + function get_volume():Float + { + return data.volume; + } + + function set_volume(value:Float):Float + { + return data.volume = value; + } + + /** + * Whether the user's volume is currently muted. + */ + public var mute(get, set):Bool; + + function get_mute():Bool + { + return data.mute; + } + + function set_mute(value:Bool):Bool + { + return data.mute = value; + } + /** * Call this to make sure the save data is written to disk. */ @@ -606,17 +650,22 @@ abstract Save(RawSaveData) if (legacySaveData != null) { trace('[SAVE] Found legacy save data, converting...'); - FlxG.save.mergeData(SaveDataMigrator.migrateFromLegacy(legacySaveData)); + var gameSave = SaveDataMigrator.migrate(legacySaveData); + @:privateAccess + FlxG.save.mergeData(gameSave.data); + } + else + { + trace('[SAVE] No legacy save data found.'); } } else { trace('[SAVE] Loaded save data.'); - FlxG.save.mergeData(SaveDataMigrator.migrate(FlxG.save.data)); + @:privateAccess + var gameSave = SaveDataMigrator.migrate(FlxG.save.data); + FlxG.save.mergeData(gameSave.data); } - - trace('[SAVE] Done loading save data.'); - trace(FlxG.save.data); } static function fetchLegacySaveData():Null diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index f995660f7..00637d52a 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -19,21 +19,21 @@ class SaveDataMigrator { trace('[SAVE] No version found in save data! Returning blank data.'); trace(inputData); - return new Save(); + return new Save(Save.getDefault()); } else { if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE)) { - // Simply cast the structured data. - var save:Save = inputData; + // Simply import the structured data. + var save:Save = new Save(inputData); return save; } else { trace('[SAVE] Invalid save data version! Returning blank data.'); trace(inputData); - return new Save(); + return new Save(Save.getDefault()); } } } @@ -45,7 +45,7 @@ class SaveDataMigrator { var inputSaveData:RawSaveData_v1_0_0 = cast inputData; - var result:Save = new Save(); + var result:Save = new Save(Save.getDefault()); result.volume = inputSaveData.volume; result.mute = inputSaveData.mute; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a662ce2bd..78e73bf27 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -920,12 +920,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_shouldShowBackupAvailableDialog():Bool { - return Save.get().chartEditorHasBackup; + return Save.instance.chartEditorHasBackup; } function set_shouldShowBackupAvailableDialog(value:Bool):Bool { - return Save.get().chartEditorHasBackup = value; + return Save.instance.chartEditorHasBackup = value; } /** @@ -2163,7 +2163,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function loadPreferences():Void { - var save:Save = Save.get(); + var save:Save = Save.instance; if (previousWorkingFilePaths[0] == null) { @@ -2191,7 +2191,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function writePreferences(hasBackup:Bool):Void { - var save:Save = Save.get(); + var save:Save = Save.instance; // Can't use filter() because of null safety checking! var filteredWorkingFilePaths:Array = []; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index a1799481e..50f85571b 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1001,7 +1001,7 @@ class FreeplayState extends MusicBeatSubState var daSong = songs[curSelected]; if (daSong != null) { - var songScore:SaveScoreData = Save.get().getSongScore(songs[curSelected].songId, currentDifficulty); + var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore?.accuracy ?? 0.0; rememberedDifficulty = currentDifficulty; @@ -1189,7 +1189,7 @@ class FreeplayState extends MusicBeatSubState var daSongCapsule = grpCapsules.members[curSelected]; if (daSongCapsule.songData != null) { - var songScore:SaveScoreData = Save.get().getSongScore(daSongCapsule.songData.songId, currentDifficulty); + var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore?.accuracy ?? 0.0; diffIdsCurrent = daSongCapsule.songData.songDifficulties; diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 9012f3672..ba1d2ed21 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -649,7 +649,7 @@ class StoryMenuState extends MusicBeatState tracklistText.screenCenter(X); tracklistText.x -= FlxG.width * 0.35; - var levelScore:Null = Save.get().getLevelScore(currentLevelId, currentDifficultyId); + var levelScore:Null = Save.instance.getLevelScore(currentLevelId, currentDifficultyId); highScore = levelScore?.score ?? 0; // levelScore.accuracy } From 02cb4b5002b5e666614cea518627a9f6544c84a7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 16:42:20 -0500 Subject: [PATCH 077/172] Update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 8b8086ac0..7e043d6d8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8b8086ac0ff072e23996828f19a05f064dafe5cf +Subproject commit 7e043d6d82160fb8d977b99c94c57cb7ea862c19 From 72044c572b7170eab00f3c29253e12255a1dc62a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 17:25:16 -0500 Subject: [PATCH 078/172] Update assets --- .prettierignore | 8 +++++--- assets | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.prettierignore b/.prettierignore index c92ea0bae..657ff3812 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,8 +1,10 @@ # Ignore artifacts export -# Ignore all asset files (including FlxAnimate JSONs) -assets +# Ignore all JSONS in the images folder (including FlxAnimate JSONs) +assets/preload/images +assets/shared/images # Don't ignore data files -!assets/preload/data +# TODO: These don't work. +!assets/preload/data/ diff --git a/assets b/assets index 7e043d6d8..3f7223465 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7e043d6d82160fb8d977b99c94c57cb7ea862c19 +Subproject commit 3f7223465924dff3af2e189af281b07397a87577 From 8ddf1ceb236f1ff25ea6835c9e50d860f2a0e9d7 Mon Sep 17 00:00:00 2001 From: FabsTheFabs Date: Mon, 4 Mar 2024 22:33:51 +0000 Subject: [PATCH 079/172] Update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b152ad838..6eeb023a2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b152ad83868ff77307abb24893ddd5f74ed324d7 +Subproject commit 6eeb023a2273de712f44ff569995ac9b5774e9ef From 37d2d4eb4d34d4ba83e8ba46b6196c8d5062383b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 4 Mar 2024 18:10:30 -0500 Subject: [PATCH 080/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 3f7223465..d0fe6c223 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 3f7223465924dff3af2e189af281b07397a87577 +Subproject commit d0fe6c22328b2bd82bd3f18fe32f09bc4b0ce57a From 7f4c36c8f45f39efbda97cc38fc3fa936ea7ba58 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 4 Mar 2024 19:03:43 -0500 Subject: [PATCH 081/172] some checkstyle stuff --- checkstyle.json | 26 ++----------- hxformat.json | 61 +++++++++++++++---------------- source/funkin/play/ResultState.hx | 28 +++++++------- 3 files changed, 47 insertions(+), 68 deletions(-) diff --git a/checkstyle.json b/checkstyle.json index 445f82b42..a5e4465b5 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -23,10 +23,10 @@ }, { "props": { - "allowSingleArgParens": false, - "allowReturn": false, - "allowFunction": false, - "allowCurlyBody": false + "allowSingleArgParens": true, + "allowReturn": true, + "allowFunction": true, + "allowCurlyBody": true }, "type": "ArrowFunction" }, @@ -64,10 +64,6 @@ }, "type": "CodeSimilarity" }, - { - "props": {}, - "type": "CommentedOutCode" - }, { "props": { "policy": "aligned", @@ -352,13 +348,6 @@ "props": {}, "type": "MultipleVariableDeclarations" }, - { - "props": { - "allowSingleLineStatement": true, - "tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"] - }, - "type": "NeedBraces" - }, { "props": { "max": 3, @@ -558,13 +547,6 @@ }, "type": "Spacing" }, - { - "props": { - "policy": "onlySingle", - "allowException": true - }, - "type": "StringLiteral" - }, { "props": { "format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)" diff --git a/hxformat.json b/hxformat.json index cacbbf810..88a528e34 100644 --- a/hxformat.json +++ b/hxformat.json @@ -1,31 +1,30 @@ -{ - "disableFormatting": false, - - "indentation": { - "character": " ", - "tabWidth": 2, - "indentCaseLabels": true - }, - - "lineEnds": { - "anonFunctionCurly": { - "emptyCurly": "break", - "leftCurly": "after", - "rightCurly": "both" - }, - "leftCurly": "both", - "rightCurly": "both" - }, - - "sameLine": { - "ifBody": "same", - "ifElse": "next", - "doWhile": "next", - "tryBody": "next", - "tryCatch": "next" - }, - - "whitespace": { - "switchPolicy": "around" - } -} +{ + "disableFormatting": false, + + "indentation": { + "character": " ", + "tabWidth": 2, + "indentCaseLabels": true + }, + "lineEnds": { + "anonFunctionCurly": { + "emptyCurly": "break", + "leftCurly": "after", + "rightCurly": "both" + }, + "leftCurly": "both", + "rightCurly": "both" + }, + + "sameLine": { + "ifBody": "same", + "ifElse": "next", + "doWhile": "next", + "tryBody": "next", + "tryCatch": "next" + }, + + "whitespace": { + "switchPolicy": "around" + } +} diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 223043c28..bd9a08589 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -2,27 +2,25 @@ package funkin.play; import funkin.ui.story.StoryMenuState; import funkin.graphics.adobeanimate.FlxAtlasSprite; -import flixel.FlxBasic; import flixel.FlxSprite; import funkin.graphics.FunkinSprite; -import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxBitmapFont; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxPoint; import funkin.ui.MusicBeatSubState; import flixel.math.FlxRect; import flixel.text.FlxBitmapText; -import flixel.text.FlxText; import flixel.tweens.FlxEase; import funkin.ui.freeplay.FreeplayState; import flixel.tweens.FlxTween; -import flixel.util.FlxColor; import flixel.util.FlxGradient; import flixel.util.FlxTimer; import funkin.graphics.shaders.LeftMaskShader; import funkin.play.components.TallyCounter; -import flxanimate.FlxAnimate.Settings; +/** + * The state for the results screen after a song or week is finished. + */ class ResultState extends MusicBeatSubState { final params:ResultsStateParams; @@ -31,8 +29,8 @@ class ResultState extends MusicBeatSubState var songName:FlxBitmapText; var difficulty:FlxSprite; - var maskShaderSongName = new LeftMaskShader(); - var maskShaderDifficulty = new LeftMaskShader(); + var maskShaderSongName:LeftMaskShader = new LeftMaskShader(); + var maskShaderDifficulty:LeftMaskShader = new LeftMaskShader(); public function new(params:ResultsStateParams) { @@ -50,22 +48,22 @@ class ResultState extends MusicBeatSubState else resultsVariation = NORMAL; - var loops = resultsVariation != SHIT; + var loops:Bool = resultsVariation != SHIT; FlxG.sound.playMusic(Paths.music("results" + resultsVariation), 1, loops); // TEMP-ish, just used to sorta "cache" the 3000x3000 image! - var cacheBullShit = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem")); + var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem")); add(cacheBullShit); - var dumb = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin")); + var dumb:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin")); add(dumb); var bg:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFECC5C, 0xFFFDC05C], 90); bg.scrollFactor.set(); add(bg); - var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFffeb69, 0xFFffe66a], 90); + var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90); bgFlash.scrollFactor.set(); bgFlash.visible = false; add(bgFlash); @@ -202,7 +200,7 @@ class ResultState extends MusicBeatSubState var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5); ratingGrp.add(tallyGood); - var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xffE6CF8A); + var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xFFE6CF8A); ratingGrp.add(tallyBad); var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A); @@ -274,13 +272,13 @@ class ResultState extends MusicBeatSubState super.create(); } - function timerThenSongName() + function timerThenSongName():Void { movingSongStuff = false; difficulty.x = 555; - var diffYTween = 122; + var diffYTween:Float = 122; difficulty.y = -difficulty.height; FlxTween.tween(difficulty, {y: diffYTween}, 0.5, {ease: FlxEase.quartOut, startDelay: 0.8}); @@ -310,7 +308,7 @@ class ResultState extends MusicBeatSubState // maskShaderSongName.frameUV = songName.frame.uv; } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { // maskShaderSongName.swagSprX = songName.x; maskShaderDifficulty.swagSprX = difficulty.x; From d2b124efcaf3a644ad3af889517b031752ac4ccc Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 4 Mar 2024 20:47:23 -0500 Subject: [PATCH 082/172] some highscore tallies debugging stuf in progress --- checkstyle.json | 26 +++++--------------------- source/funkin/Highscore.hx | 8 +++++++- source/funkin/InitState.hx | 5 +++-- source/funkin/data/song/SongData.hx | 2 ++ source/funkin/play/PlayState.hx | 11 +++++++---- source/funkin/play/ResultState.hx | 4 ++++ source/funkin/util/TrackerUtil.hx | 23 +++++++++++++++++++++++ 7 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 source/funkin/util/TrackerUtil.hx diff --git a/checkstyle.json b/checkstyle.json index a5e4465b5..dae5a33a9 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -76,7 +76,7 @@ "props": { "ignoreExtern": true, "format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", - "tokens": [] + "tokens": ["INLINE"] }, "type": "ConstantName" }, @@ -174,13 +174,7 @@ "fieldType": "BOTH", "requireReturn": true, "ignoreOverride": true, - "tokens": [ - "ABSTRACT_DEF", - "CLASS_DEF", - "ENUM_DEF", - "INTERFACE_DEF", - "TYPEDEF_DEF" - ], + "tokens": ["ABSTRACT_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF"], "modifier": "PUBLIC", "excludeNames": ["new", "toString"] }, @@ -194,10 +188,6 @@ }, "type": "FileLength" }, - { - "props": {}, - "type": "Final" - }, { "props": { "option": "upperCase" @@ -364,7 +354,7 @@ }, { "props": { - "max": 1 + "max": 4 }, "type": "NestedIfDepth" }, @@ -376,7 +366,7 @@ }, { "props": { - "option": "nullDefault" + "option": "questionMark" }, "type": "NullableParameter" }, @@ -577,13 +567,7 @@ }, { "props": { - "tokens": [ - "ABSTRACT_DEF", - "CLASS_DEF", - "ENUM_DEF", - "INTERFACE_DEF", - "TYPEDEF_DEF" - ] + "tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"] }, "type": "TypeDocComment" }, diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index 24b65832b..44b0e0f74 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -1,10 +1,16 @@ package funkin; +import flixel.FlxG; + /** * A core class which handles tracking score and combo for the current song. */ class Highscore { + /** + * Keeps track of notes hit for the current song / week, + * and how accurate you were with each note (bad, missed, shit, etc.) + */ public static var tallies:Tallies = new Tallies(); } @@ -51,7 +57,7 @@ typedef RawTallies = var totalNotesHit:Int; /** - * How many notes PASSED BY AND/OR HIT!!! + * How many notes in the current chart */ var totalNotes:Int; } diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 2368d09f2..9ecf66ec7 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -32,6 +32,7 @@ import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; import funkin.util.tools.TimerTools; import funkin.ui.transition.LoadingState; +import funkin.util.TrackerUtil; #if discord_rpc import Discord.DiscordClient; #end @@ -67,7 +68,7 @@ class InitState extends FlxState /** * Setup a bunch of important Flixel stuff. */ - function setupShit() + function setupShit():Void { // // GAME SETUP @@ -95,7 +96,7 @@ class InitState extends FlxState #if (debug || FORCE_DEBUG_VERSION) // Disable using ~ to open the console (we use that for the Editor menu) FlxG.debugger.toggleKeys = [F2]; - + TrackerUtil.initTrackers(); // Adds an additional Close Debugger button. // This big obnoxious white button is for MOBILE, so that you can press it // easily with your finger when debug bullshit pops up during testing lol! diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 24febea86..938859ff2 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -106,6 +106,8 @@ class SongMetadata implements ICloneable /** * Serialize this SongMetadata into a JSON string. + * @param pretty Whether the JSON should be big ol string (false), + * or formatted with tabs (true) * @return The JSON string. */ public function serialize(pretty:Bool = true):String diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d090b4f8a..6e8f22908 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1732,7 +1732,6 @@ class PlayState extends MusicBeatSubState if (strumTime < startTime) continue; // Skip notes that are before the start time. var noteData:Int = songNote.getDirection(); - var playerNote:Bool = true; if (noteData > 3) playerNote = false; @@ -1741,6 +1740,8 @@ class PlayState extends MusicBeatSubState { case 0: playerNoteData.push(songNote); + // increment totalNotes for total possible notes able to be hit by the player + Highscore.tallies.totalNotes++; case 1: opponentNoteData.push(songNote); } @@ -2457,6 +2458,8 @@ class PlayState extends MusicBeatSubState health += healthChange; + FlxG.watch.addQuick("COMBO: ", Highscore.tallies.combo); + if (isComboBreak) { // Break the combo, but don't increment tallies.misses. @@ -2617,9 +2620,9 @@ class PlayState extends MusicBeatSubState combo: Highscore.tallies.combo, maxCombo: Highscore.tallies.maxCombo, totalNotesHit: Highscore.tallies.totalNotesHit, - totalNotes: Highscore.tallies.totalNotes, + totalNotes: currentChart.notes.length, }, - accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, + accuracy: Highscore.tallies.totalNotesHit / currentChart.notes.length, }; if (Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data)) @@ -2669,7 +2672,7 @@ class PlayState extends MusicBeatSubState totalNotesHit: 0, totalNotes: 0, }, - accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, + accuracy: Highscore.tallies.totalNotesHit / currentChart.notes.length, }; if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index bd9a08589..f77c3fc6b 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -186,6 +186,10 @@ class ResultState extends MusicBeatSubState var ratingGrp:FlxTypedGroup = new FlxTypedGroup(); add(ratingGrp); + /** + * NOTE: We display how many notes were HIT, not how many notes there were in total. + * + */ var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit); ratingGrp.add(totalHit); diff --git a/source/funkin/util/TrackerUtil.hx b/source/funkin/util/TrackerUtil.hx new file mode 100644 index 000000000..b8ed0c995 --- /dev/null +++ b/source/funkin/util/TrackerUtil.hx @@ -0,0 +1,23 @@ +package funkin.util; + +// This may or may not already be imported via imports.hx... +import flixel.system.debug.watch.Tracker; +import flixel.FlxG; + +/** + * Utility class that helps manage adding profiles to the flixel debugger tracker + */ +class TrackerUtil +{ + /** + * Adds profiles to the debugger to help track certain game objects. + * NOTE: This isn't the full list of profiles made, as sometimes they're made + * on in various places in code for random debugging purposes! + * Might be good to put them all here though! + */ + public static function initTrackers():Void + { + Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"])); + FlxG.console.registerClass(Highscore); + } +} From fa72fa44b1f5ba4f19ad276cfbafcfd1819bb4dd Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 4 Mar 2024 21:18:40 -0500 Subject: [PATCH 083/172] tallies for story mode --- source/funkin/Highscore.hx | 24 +++++++++++++++++++++++- source/funkin/play/PlayState.hx | 10 +++++++--- source/funkin/ui/story/StoryMenuState.hx | 4 +++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index 44b0e0f74..e2987443d 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -8,10 +8,32 @@ import flixel.FlxG; class Highscore { /** - * Keeps track of notes hit for the current song / week, + * Keeps track of notes hit for the current song * and how accurate you were with each note (bad, missed, shit, etc.) */ public static var tallies:Tallies = new Tallies(); + + /** + * Keeps track of notes hit for the current WEEK / level + * for use with storymode, or likely any other "playlist" esque option + */ + public static var talliesLevel:Tallies = new Tallies(); + + public static function combineTallies(tally1:Tallies, tally2:Tallies):Tallies + { + var combinedTally:Tallies = new Tallies(); + combinedTally.combo = tally1.combo + tally2.combo; + combinedTally.missed = tally1.missed + tally2.missed; + combinedTally.shit = tally1.shit + tally2.shit; + combinedTally.bad = tally1.bad + tally2.bad; + combinedTally.good = tally1.good + tally2.good; + combinedTally.sick = tally1.sick + tally2.sick; + combinedTally.totalNotes = tally1.totalNotes + tally2.totalNotes; + combinedTally.totalNotesHit = tally1.totalNotesHit + tally2.totalNotesHit; + combinedTally.maxCombo = tally1.maxCombo + tally2.maxCombo; + + return combinedTally; + } } @:forward diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 6e8f22908..04a18fb95 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2458,8 +2458,6 @@ class PlayState extends MusicBeatSubState health += healthChange; - FlxG.watch.addQuick("COMBO: ", Highscore.tallies.combo); - if (isComboBreak) { // Break the combo, but don't increment tallies.misses. @@ -2625,6 +2623,9 @@ class PlayState extends MusicBeatSubState accuracy: Highscore.tallies.totalNotesHit / currentChart.notes.length, }; + // adds current song data into the tallies for the level (story levels) + Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel); + if (Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data)) { Save.instance.setSongScore(currentSong.id, currentDifficulty, data); @@ -2893,11 +2894,14 @@ class PlayState extends MusicBeatSubState persistentUpdate = false; vocals.stop(); camHUD.alpha = 1; + + var talliesToUse:Tallies = PlayStatePlaylist.isStoryMode ? Highscore.talliesLevel : Highscore.tallies; + var res:ResultState = new ResultState( { storyMode: PlayStatePlaylist.isStoryMode, title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'), - tallies: Highscore.tallies, + tallies: talliesToUse, }); res.camera = camHUD; openSubState(res); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index ba1d2ed21..59e1bbdc1 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -519,7 +519,7 @@ class StoryMenuState extends MusicBeatState } } - function selectLevel() + function selectLevel():Void { if (!currentLevel.isUnlocked()) { @@ -554,6 +554,8 @@ class StoryMenuState extends MusicBeatState PlayStatePlaylist.campaignTitle = currentLevel.getTitle(); PlayStatePlaylist.campaignDifficulty = currentDifficultyId; + Highscore.talliesLevel = new funkin.Highscore.Tallies(); + new FlxTimer().start(1, function(tmr:FlxTimer) { FlxTransitionableState.skipNextTransIn = false; FlxTransitionableState.skipNextTransOut = false; From d0f81add959c5418e3dbeb5c61fc933afb0dad51 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 21:19:24 -0500 Subject: [PATCH 084/172] Fix a bug where Chart Editor Playtest destroys the vocals and crashes --- source/funkin/play/PlayState.hx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d090b4f8a..d7cc1493c 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2787,14 +2787,19 @@ class PlayState extends MusicBeatSubState // TODO: Uncache the song. } - if (!overrideMusic) + if (overrideMusic) { - // Stop the music. + // Stop the music. Do NOT destroy it, something still references it! FlxG.sound.music.pause(); - if (vocals != null) vocals.stop(); + if (vocals != null) + { + vocals.pause(); + remove(vocals); + } } else { + // Stop and destroy the music. FlxG.sound.music.pause(); if (vocals != null) { From acc75c5c5c46d5f7ca8a00a98f32d85fc318279a Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 4 Mar 2024 21:25:56 -0500 Subject: [PATCH 085/172] use totalNotes instead of currentChart.notes.length --- source/funkin/play/PlayState.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 04a18fb95..b41c29c83 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2618,9 +2618,9 @@ class PlayState extends MusicBeatSubState combo: Highscore.tallies.combo, maxCombo: Highscore.tallies.maxCombo, totalNotesHit: Highscore.tallies.totalNotesHit, - totalNotes: currentChart.notes.length, + totalNotes: Highscore.tallies.totalNotes, }, - accuracy: Highscore.tallies.totalNotesHit / currentChart.notes.length, + accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, }; // adds current song data into the tallies for the level (story levels) @@ -2673,7 +2673,7 @@ class PlayState extends MusicBeatSubState totalNotesHit: 0, totalNotes: 0, }, - accuracy: Highscore.tallies.totalNotesHit / currentChart.notes.length, + accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, }; if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) From 08fb8be4191922a1a13a33ad9d5ebb4c722fd599 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 22:22:19 -0500 Subject: [PATCH 086/172] Redo combine tally implementation --- source/funkin/Highscore.hx | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index e2987443d..996e2367e 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -1,7 +1,5 @@ package funkin; -import flixel.FlxG; - /** * A core class which handles tracking score and combo for the current song. */ @@ -19,18 +17,27 @@ class Highscore */ public static var talliesLevel:Tallies = new Tallies(); - public static function combineTallies(tally1:Tallies, tally2:Tallies):Tallies + /** + * Produces a new Tallies object which represents the sum of two existing Tallies + * @param newTally The first tally + * @param baseTally The second tally + * @return The combined tally + */ + public static function combineTallies(newTally:Tallies, baseTally:Tallies):Tallies { var combinedTally:Tallies = new Tallies(); - combinedTally.combo = tally1.combo + tally2.combo; - combinedTally.missed = tally1.missed + tally2.missed; - combinedTally.shit = tally1.shit + tally2.shit; - combinedTally.bad = tally1.bad + tally2.bad; - combinedTally.good = tally1.good + tally2.good; - combinedTally.sick = tally1.sick + tally2.sick; - combinedTally.totalNotes = tally1.totalNotes + tally2.totalNotes; - combinedTally.totalNotesHit = tally1.totalNotesHit + tally2.totalNotesHit; - combinedTally.maxCombo = tally1.maxCombo + tally2.maxCombo; + combinedTally.missed = newTally.missed + baseTally.missed; + combinedTally.shit = newTally.shit + baseTally.shit; + combinedTally.bad = newTally.bad + baseTally.bad; + combinedTally.good = newTally.good + baseTally.good; + combinedTally.sick = newTally.sick + baseTally.sick; + combinedTally.totalNotes = newTally.totalNotes + baseTally.totalNotes; + combinedTally.totalNotesHit = newTally.totalNotesHit + baseTally.totalNotesHit; + + // Current combo = use most recent. + combinedTally.combo = newTally.combo; + // Max combo = use maximum value. + combinedTally.maxCombo = Std.int(Math.max(newTally.maxCombo, baseTally.maxCombo)); return combinedTally; } @@ -57,6 +64,9 @@ abstract Tallies(RawTallies) } } +/** + * A structure object containing the data for highscore tallies. + */ typedef RawTallies = { var combo:Int; From 07524c8ee8662c075e605a8706bb0f2f231baa88 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 4 Mar 2024 22:42:02 -0500 Subject: [PATCH 087/172] Eric's modifications to checkstyle.json --- checkstyle.json | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/checkstyle.json b/checkstyle.json index dae5a33a9..d41a6d617 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -64,6 +64,10 @@ }, "type": "CodeSimilarity" }, + { + "props": {}, + "type": "CommentedOutCode" + }, { "props": { "policy": "aligned", @@ -76,7 +80,7 @@ "props": { "ignoreExtern": true, "format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", - "tokens": ["INLINE"] + "tokens": ["INLINE", "NOTINLINE"] }, "type": "ConstantName" }, @@ -338,6 +342,13 @@ "props": {}, "type": "MultipleVariableDeclarations" }, + { + "props": { + "allowSingleLineStatement": true, + "tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"] + }, + "type": "NeedBraces" + }, { "props": { "max": 3, @@ -375,7 +386,6 @@ "oldFunctionTypePolicy": "none", "unaryOpPolicy": "none", "intervalOpPolicy": "none", - "newFunctionTypePolicy": "around", "ternaryOpPolicy": "around", "boolOpPolicy": "around", @@ -537,6 +547,13 @@ }, "type": "Spacing" }, + { + "props": { + "policy": "onlySingle", + "allowException": true + }, + "type": "StringLiteral" + }, { "props": { "format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)" @@ -559,12 +576,6 @@ "props": {}, "type": "TrailingWhitespace" }, - { - "props": { - "ignoreEnumAbstractValues": true - }, - "type": "Type" - }, { "props": { "tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"] @@ -596,12 +607,6 @@ }, "type": "UnusedLocalVar" }, - { - "props": { - "severity": "IGNORE" - }, - "type": "VariableInitialisation" - }, { "props": { "typeHintPolicy": "enforce_all", From d9cf097e460de497a7a6754c85eade23b39b9aa3 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 4 Mar 2024 20:57:21 -0700 Subject: [PATCH 088/172] 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 ae5f48c29cf86fa1c9691eb1c6d854a5c1712d6b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 5 Mar 2024 00:22:29 -0500 Subject: [PATCH 089/172] Fix issues with story mode colors breaking --- source/funkin/ui/story/Level.hx | 11 +++++++++-- source/funkin/ui/story/LevelProp.hx | 14 ++++++++++++-- source/funkin/ui/story/StoryMenuState.hx | 7 ++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/source/funkin/ui/story/Level.hx b/source/funkin/ui/story/Level.hx index c93ad41a6..b548b7b1e 100644 --- a/source/funkin/ui/story/Level.hx +++ b/source/funkin/ui/story/Level.hx @@ -201,8 +201,15 @@ class Level implements IRegistryEntry if (existingProp != null) { existingProp.propData = propData; - existingProp.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex; - existingProp.visible = true; + if (existingProp.propData == null) + { + existingProp.visible = false; + } + else + { + existingProp.visible = true; + existingProp.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex; + } } else { diff --git a/source/funkin/ui/story/LevelProp.hx b/source/funkin/ui/story/LevelProp.hx index 5af383de9..b126f0243 100644 --- a/source/funkin/ui/story/LevelProp.hx +++ b/source/funkin/ui/story/LevelProp.hx @@ -11,11 +11,11 @@ class LevelProp extends Bopper function set_propData(value:LevelPropData):LevelPropData { // Only reset the prop if the asset path has changed. - if (propData == null || value.assetPath != this.propData.assetPath) + if (propData == null || value?.assetPath != propData?.assetPath) { this.visible = (value != null); this.propData = value; - danceEvery = this.propData.danceEvery; + danceEvery = this.propData?.danceEvery ?? 0; applyData(); } @@ -35,6 +35,16 @@ class LevelProp extends Bopper function applyData():Void { + if (propData == null) + { + this.visible = false; + return; + } + else + { + this.visible = true; + } + var isAnimated:Bool = propData.animations.length > 0; if (isAnimated) { diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index ba1d2ed21..8b477feee 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -141,10 +141,10 @@ class StoryMenuState extends MusicBeatState persistentUpdate = persistentDraw = true; - updateData(); - rememberSelection(); + updateData(); + // Explicitly define the background color. this.bgColor = FlxColor.BLACK; @@ -403,7 +403,8 @@ class StoryMenuState extends MusicBeatState function hasModdedLevels():Bool { - return LevelRegistry.instance.listModdedLevelIds().length > 0; + return false; + // return LevelRegistry.instance.listModdedLevelIds().length > 0; } /** From 9212ea9c90dad5902a9e4c4a93b162897647e0d5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 5 Mar 2024 02:29:44 -0500 Subject: [PATCH 090/172] Script fixes for 2hot explosions breaking Polymod --- assets | 2 +- hmm.json | 2 +- source/funkin/data/level/LevelData.hx | 22 +++- source/funkin/modding/PolymodHandler.hx | 130 ++++++++++++++++-------- source/funkin/ui/story/Level.hx | 51 ++++++++-- 5 files changed, 152 insertions(+), 55 deletions(-) diff --git a/assets b/assets index 55c602f2a..14b86f436 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 55c602f2adbbd84de541ea86e5e646c4d2a1df0b +Subproject commit 14b86f4369fddf61eb76116139eb33fa8f6e92d0 diff --git a/hmm.json b/hmm.json index 836b01c9b..cbd5fea30 100644 --- a/hmm.json +++ b/hmm.json @@ -146,7 +146,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "d5a3b8995f64d20b95f844454e8c3b38c3d3a9fa", + "ref": "be712450e5d3ba446008884921bb56873b299a64", "url": "https://github.com/larsiusprime/polymod" }, { diff --git a/source/funkin/data/level/LevelData.hx b/source/funkin/data/level/LevelData.hx index 843389cae..f5e58ae16 100644 --- a/source/funkin/data/level/LevelData.hx +++ b/source/funkin/data/level/LevelData.hx @@ -17,7 +17,7 @@ typedef LevelData = var version:String; /** - * The title of the week, as seen in the top corner. + * The title of the level, as seen in the top corner. */ var name:String; @@ -27,21 +27,35 @@ typedef LevelData = @:jcustomparse(funkin.data.DataParse.stringNotEmpty) var titleAsset:String; + /** + * The props to display over the colored background. + * In the base game this is usually Boyfriend and the opponent. + */ @:default([]) var props:Array; - @:default(["bopeebo"]) + + /** + * The list of song IDs included in this level. + */ + @:default(['bopeebo']) var songs:Array; - @:default("#F9CF51") + + /** + * The background for the level behind the props. + */ + @:default('#F9CF51') @:optional var background:String; } +/** + * Data for a single prop for a story mode level. + */ typedef LevelPropData = { /** * The image to use for the prop. May optionally be a sprite sheet. */ - // @:jcustomparse(funkin.data.DataParse.stringNotEmpty) var assetPath:String; /** diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 889f63073..b1c6b511a 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -1,24 +1,25 @@ package funkin.modding; -import funkin.util.macro.ClassMacro; -import funkin.modding.module.ModuleHandler; -import funkin.data.song.SongData; -import funkin.data.stage.StageData; -import polymod.Polymod; -import polymod.backends.PolymodAssets.PolymodAssetType; -import polymod.format.ParseRules.TextFileFormat; -import funkin.data.event.SongEventRegistry; -import funkin.data.stage.StageRegistry; -import funkin.util.FileUtil; -import funkin.data.level.LevelRegistry; -import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.dialogue.ConversationRegistry; import funkin.data.dialogue.DialogueBoxRegistry; import funkin.data.dialogue.SpeakerRegistry; +import funkin.data.event.SongEventRegistry; +import funkin.data.level.LevelRegistry; +import funkin.data.notestyle.NoteStyleRegistry; +import funkin.data.song.SongRegistry; +import funkin.data.stage.StageRegistry; +import funkin.modding.module.ModuleHandler; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.save.Save; -import funkin.data.song.SongRegistry; +import funkin.util.FileUtil; +import funkin.util.macro.ClassMacro; +import polymod.backends.PolymodAssets.PolymodAssetType; +import polymod.format.ParseRules.TextFileFormat; +import polymod.Polymod; +/** + * A class for interacting with Polymod, the atomic modding framework for Haxe. + */ class PolymodHandler { /** @@ -27,16 +28,33 @@ class PolymodHandler * Bug fixes increment the patch version, new features increment the minor version. * Changes that break old mods increment the major version. */ - static final API_VERSION:String = "0.1.0"; + static final API_VERSION:String = '0.1.0'; /** * Where relative to the executable that mods are located. */ - static final MOD_FOLDER:String = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../example_mods" #elseif REDIRECT_ASSETS_FOLDER "../../../../example_mods" #else "mods" #end; + static final MOD_FOLDER:String = + #if (REDIRECT_ASSETS_FOLDER && macos) + '../../../../../../../example_mods' + #elseif REDIRECT_ASSETS_FOLDER + '../../../../example_mods' + #else + 'mods' + #end; - static final CORE_FOLDER:Null = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../assets" #elseif REDIRECT_ASSETS_FOLDER "../../../../assets" #else null #end; + static final CORE_FOLDER:Null = + #if (REDIRECT_ASSETS_FOLDER && macos) + '../../../../../../../assets' + #elseif REDIRECT_ASSETS_FOLDER + '../../../../assets' + #else + null + #end; - public static function createModRoot() + /** + * If the mods folder doesn't exist, create it. + */ + public static function createModRoot():Void { FileUtil.createDirIfNotExists(MOD_FOLDER); } @@ -44,40 +62,44 @@ class PolymodHandler /** * Loads the game with ALL mods enabled with Polymod. */ - public static function loadAllMods() + public static function loadAllMods():Void { // Create the mod root if it doesn't exist. createModRoot(); - trace("Initializing Polymod (using all mods)..."); + trace('Initializing Polymod (using all mods)...'); loadModsById(getAllModIds()); } /** * Loads the game with configured mods enabled with Polymod. */ - public static function loadEnabledMods() + public static function loadEnabledMods():Void { // Create the mod root if it doesn't exist. createModRoot(); - trace("Initializing Polymod (using configured mods)..."); + trace('Initializing Polymod (using configured mods)...'); loadModsById(Save.instance.enabledModIds); } /** * Loads the game without any mods enabled with Polymod. */ - public static function loadNoMods() + public static function loadNoMods():Void { // Create the mod root if it doesn't exist. createModRoot(); // We still need to configure the debug print calls etc. - trace("Initializing Polymod (using no mods)..."); + trace('Initializing Polymod (using no mods)...'); loadModsById([]); } - public static function loadModsById(ids:Array) + /** + * Load all the mods with the given ids. + * @param ids The ORDERED list of mod ids to load. + */ + public static function loadModsById(ids:Array):Void { if (ids.length == 0) { @@ -90,7 +112,7 @@ class PolymodHandler buildImports(); - var loadedModList = polymod.Polymod.init( + var loadedModList:Array = polymod.Polymod.init( { // Root directory for all mods. modRoot: MOD_FOLDER, @@ -142,30 +164,40 @@ class PolymodHandler } #if debug - var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE); + var fileList:Array = Polymod.listModFiles(PolymodAssetType.IMAGE); trace('Installed mods have replaced ${fileList.length} images.'); for (item in fileList) + { trace(' * $item'); + } fileList = Polymod.listModFiles(PolymodAssetType.TEXT); trace('Installed mods have added/replaced ${fileList.length} text files.'); for (item in fileList) + { trace(' * $item'); + } fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC); trace('Installed mods have replaced ${fileList.length} music files.'); for (item in fileList) + { trace(' * $item'); + } fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND); trace('Installed mods have replaced ${fileList.length} sound files.'); for (item in fileList) + { trace(' * $item'); + } fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC); trace('Installed mods have replaced ${fileList.length} generic audio files.'); for (item in fileList) + { trace(' * $item'); + } #end } @@ -183,21 +215,21 @@ class PolymodHandler for (cls in ClassMacro.listClassesInPackage('polymod')) { if (cls == null) continue; - var className = Type.getClassName(cls); + var className:String = Type.getClassName(cls); Polymod.blacklistImport(className); } } static function buildParseRules():polymod.format.ParseRules { - var output = polymod.format.ParseRules.getDefault(); + var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault(); // Ensure TXT files have merge support. - output.addType("txt", TextFileFormat.LINES); + output.addType('txt', TextFileFormat.LINES); // Ensure script files have merge support. - output.addType("hscript", TextFileFormat.PLAINTEXT); - output.addType("hxs", TextFileFormat.PLAINTEXT); - output.addType("hxc", TextFileFormat.PLAINTEXT); - output.addType("hx", TextFileFormat.PLAINTEXT); + output.addType('hscript', TextFileFormat.PLAINTEXT); + output.addType('hxs', TextFileFormat.PLAINTEXT); + output.addType('hxc', TextFileFormat.PLAINTEXT); + output.addType('hx', TextFileFormat.PLAINTEXT); // You can specify the format of a specific file, with file extension. // output.addFile("data/introText.txt", TextFileFormat.LINES) @@ -208,17 +240,21 @@ class PolymodHandler { return { assetLibraryPaths: [ - "default" => "preload", "shared" => "shared", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2", - "week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1", + 'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'tutorial' => 'tutorial', 'week1' => 'week1', 'week2' => 'week2', + 'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1', ], coreAssetRedirect: CORE_FOLDER, } } + /** + * Retrieve a list of metadata for ALL installed mods, including disabled mods. + * @return An array of mod metadata + */ public static function getAllMods():Array { trace('Scanning the mods folder...'); - var modMetadata = Polymod.scan( + var modMetadata:Array = Polymod.scan( { modRoot: MOD_FOLDER, apiVersionRule: API_VERSION, @@ -228,17 +264,25 @@ class PolymodHandler return modMetadata; } + /** + * Retrieve a list of ALL mod IDs, including disabled mods. + * @return An array of mod IDs + */ public static function getAllModIds():Array { - var modIds = [for (i in getAllMods()) i.id]; + var modIds:Array = [for (i in getAllMods()) i.id]; return modIds; } + /** + * Retrieve a list of metadata for all enabled mods. + * @return An array of mod metadata + */ public static function getEnabledMods():Array { - var modIds = Save.instance.enabledModIds; - var modMetadata = getAllMods(); - var enabledMods = []; + var modIds:Array = Save.instance.enabledModIds; + var modMetadata:Array = getAllMods(); + var enabledMods:Array = []; for (item in modMetadata) { if (modIds.indexOf(item.id) != -1) @@ -249,7 +293,11 @@ class PolymodHandler return enabledMods; } - public static function forceReloadAssets() + /** + * Clear and reload from disk all data assets. + * Useful for "hot reloading" for fast iteration! + */ + public static function forceReloadAssets():Void { // Forcibly clear scripts so that scripts can be edited. ModuleHandler.clearModuleCache(); diff --git a/source/funkin/ui/story/Level.hx b/source/funkin/ui/story/Level.hx index b548b7b1e..626fb8e52 100644 --- a/source/funkin/ui/story/Level.hx +++ b/source/funkin/ui/story/Level.hx @@ -51,6 +51,7 @@ class Level implements IRegistryEntry /** * Retrieve the title of the level for display on the menu. + * @return Title of the level as a string */ public function getTitle():String { @@ -58,16 +59,21 @@ class Level implements IRegistryEntry return _data.name; } + /** + * Construct the title graphic for the level. + * @return The constructed graphic as a sprite. + */ public function buildTitleGraphic():FlxSprite { - var result = new FlxSprite().loadGraphic(Paths.image(_data.titleAsset)); + var result:FlxSprite = new FlxSprite().loadGraphic(Paths.image(_data.titleAsset)); return result; } /** * Get the list of songs in this level, as an array of names, for display on the menu. - * @return Array + * @param difficulty The difficulty of the level being displayed + * @return The display names of the songs in this level */ public function getSongDisplayNames(difficulty:String):Array { @@ -88,7 +94,9 @@ class Level implements IRegistryEntry /** * Whether this level is unlocked. If not, it will be greyed out on the menu and have a lock icon. - * TODO: Change this behavior in a later release. + * Override this in a script. + * @default `true` + * @return Whether this level is unlocked */ public function isUnlocked():Bool { @@ -97,6 +105,9 @@ class Level implements IRegistryEntry /** * Whether this level is visible. If not, it will not be shown on the menu at all. + * Override this in a script. + * @default `true` + * @return Whether this level is visible in the menu */ public function isVisible():Bool { @@ -106,6 +117,7 @@ class Level implements IRegistryEntry /** * Build a sprite for the background of the level. * Can be overriden by ScriptedLevel. Not used if `isBackgroundSimple` returns true. + * @return The constructed sprite */ public function buildBackground():FlxSprite { @@ -124,6 +136,7 @@ class Level implements IRegistryEntry /** * Returns true if the background is a solid color. * If you have a ScriptedLevel with a fancy background, you may want to override this to false. + * @return Whether the background is a simple color */ public function isBackgroundSimple():Bool { @@ -133,30 +146,36 @@ class Level implements IRegistryEntry /** * Returns true if the background is a solid color. * If you have a ScriptedLevel with a fancy background, you may want to override this to false. + * @return The background as a simple color. May not be valid if `isBackgroundSimple` returns false. */ public function getBackgroundColor():FlxColor { return FlxColor.fromString(_data.background); } + /** + * The list of difficulties the player can select from for this level. + * @return The difficulty IDs. + */ public function getDifficulties():Array { var difficulties:Array = []; - var songList = getSongs(); + var songList:Array = getSongs(); var firstSongId:String = songList[0]; var firstSong:Song = SongRegistry.instance.fetchEntry(firstSongId); if (firstSong != null) { - // Don't display alternate characters in Story Mode. - for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, "erect"])) + // Don't display alternate characters in Story Mode. Only show `default` and `erect` variations. + for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, 'erect'])) { difficulties.push(difficulty); } } + // Sort in a specific order! Fall back to alphabetical. difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST)); // Filter to only include difficulties that are present in all songs @@ -169,7 +188,7 @@ class Level implements IRegistryEntry for (difficulty in difficulties) { - if (!song.hasDifficulty(difficulty, [Constants.DEFAULT_VARIATION, "erect"])) + if (!song.hasDifficulty(difficulty, [Constants.DEFAULT_VARIATION, 'erect'])) { difficulties.remove(difficulty); } @@ -181,6 +200,11 @@ class Level implements IRegistryEntry return difficulties; } + /** + * Build the props for display over the colored background. + * @param existingProps The existing prop sprites, if any. + * @return The constructed prop sprites + */ public function buildProps(?existingProps:Array):Array { var props:Array = existingProps == null ? [] : [for (x in existingProps) x]; @@ -189,11 +213,13 @@ class Level implements IRegistryEntry var hiddenProps:Array = props.splice(_data.props.length - 1, props.length - 1); for (hiddenProp in hiddenProps) + { hiddenProp.visible = false; + } for (propIndex in 0..._data.props.length) { - var propData = _data.props[propIndex]; + var propData:LevelPropData = _data.props[propIndex]; // Attempt to reuse the `LevelProp` object. // This prevents animations from resetting. @@ -224,6 +250,10 @@ class Level implements IRegistryEntry return props; } + /** + * Called when the level is destroyed. + * TODO: Document when this gets called + */ public function destroy():Void {} public function toString():String @@ -231,6 +261,11 @@ class Level implements IRegistryEntry return 'Level($id)'; } + /** + * Retrieve and parse the JSON data for a level by ID. + * @param id The ID of the level + * @return The parsed level data, or null if not found or invalid + */ static function _fetchData(id:String):Null { return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id)); From 6db5d10a55932cb817d429b950313d4578096554 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 5 Mar 2024 19:35:51 -0500 Subject: [PATCH 091/172] jenny assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 14b86f436..69ebdb6a7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 14b86f4369fddf61eb76116139eb33fa8f6e92d0 +Subproject commit 69ebdb6a7aa57b6762ce509243679ab959615120 From 059e1c0e13c9560edbe3454e6cf7ae9be8b3886a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 5 Mar 2024 21:48:04 -0500 Subject: [PATCH 092/172] Implement onNoteIncoming script event. --- assets | 2 +- source/funkin/modding/IScriptedClass.hx | 28 ++++++++++--------- .../modding/events/ScriptEventDispatcher.hx | 16 +++++++++-- .../funkin/modding/events/ScriptEventType.hx | 7 +++++ source/funkin/modding/module/Module.hx | 4 ++- source/funkin/play/PlayState.hx | 13 +++++++-- source/funkin/play/character/BaseCharacter.hx | 2 +- source/funkin/play/notes/Strumline.hx | 7 +++++ source/funkin/play/song/Song.hx | 4 ++- source/funkin/play/stage/Bopper.hx | 4 ++- source/funkin/play/stage/Stage.hx | 4 ++- .../ui/debug/charting/ChartEditorState.hx | 2 +- .../ui/haxeui/components/CharacterPlayer.hx | 8 +++++- 13 files changed, 76 insertions(+), 25 deletions(-) diff --git a/assets b/assets index 69ebdb6a7..518349369 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 69ebdb6a7aa57b6762ce509243679ab959615120 +Subproject commit 518349369ea3237504e8100c901313185bb5b80f diff --git a/source/funkin/modding/IScriptedClass.hx b/source/funkin/modding/IScriptedClass.hx index b009aea41..5f2ff2b9e 100644 --- a/source/funkin/modding/IScriptedClass.hx +++ b/source/funkin/modding/IScriptedClass.hx @@ -56,7 +56,20 @@ interface IStateStageProp extends IScriptedClass */ interface INoteScriptedClass extends IScriptedClass { - public function onNoteHit(event:NoteScriptEvent):Void; + /** + * Called when a note enters the field of view and approaches the strumline. + */ + public function onNoteIncoming(event:NoteScriptEvent):Void; + + /** + * Called when EITHER player hits a note. + * Query the note attached to the event to determine if it was hit by the player or CPU. + */ + public function onNoteHit(event:HitNoteScriptEvent):Void; + + /** + * Called when EITHER player (usually the player) misses a note. + */ public function onNoteMiss(event:NoteScriptEvent):Void; } @@ -73,7 +86,7 @@ interface INoteScriptedClass extends IScriptedClass /** * Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State. */ -interface IPlayStateScriptedClass extends IScriptedClass +interface IPlayStateScriptedClass extends INoteScriptedClass { /** * Called when the game is paused. @@ -113,17 +126,6 @@ interface IPlayStateScriptedClass extends IScriptedClass */ public function onSongRetry(event:ScriptEvent):Void; - /** - * Called when EITHER player hits a note. - * Query the note attached to the event to determine if it was hit by the player or CPU. - */ - public function onNoteHit(event:NoteScriptEvent):Void; - - /** - * Called when EITHER player (usually the player) misses a note. - */ - public function onNoteMiss(event:NoteScriptEvent):Void; - /** * Called when the player presses a key when no note is on the strumline. */ diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index f5d797ea4..fd58d0fad 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -71,17 +71,29 @@ class ScriptEventDispatcher } } - if (Std.isOfType(target, IPlayStateScriptedClass)) + if (Std.isOfType(target, INoteScriptedClass)) { - var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass); + var t:INoteScriptedClass = cast(target, INoteScriptedClass); switch (event.type) { + case NOTE_INCOMING: + t.onNoteIncoming(cast event); + return; case NOTE_HIT: t.onNoteHit(cast event); return; case NOTE_MISS: t.onNoteMiss(cast event); return; + default: // Continue; + } + } + + if (Std.isOfType(target, IPlayStateScriptedClass)) + { + var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass); + switch (event.type) + { case NOTE_GHOST_MISS: t.onNoteGhostMiss(cast event); return; diff --git a/source/funkin/modding/events/ScriptEventType.hx b/source/funkin/modding/events/ScriptEventType.hx index e06b5ad24..eeeb8ef29 100644 --- a/source/funkin/modding/events/ScriptEventType.hx +++ b/source/funkin/modding/events/ScriptEventType.hx @@ -63,6 +63,13 @@ enum abstract ScriptEventType(String) from String to String */ var SONG_STEP_HIT = 'STEP_HIT'; + /** + * Called when a note comes on screen and starts approaching the strumline. + * + * This event is not cancelable. + */ + var NOTE_INCOMING = 'NOTE_INCOMING'; + /** * Called when a character hits a note. * Important information such as judgement/timing, note data, player/opponent, etc. are all provided. diff --git a/source/funkin/modding/module/Module.hx b/source/funkin/modding/module/Module.hx index f50c9936a..be9b7146b 100644 --- a/source/funkin/modding/module/Module.hx +++ b/source/funkin/modding/module/Module.hx @@ -83,7 +83,9 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte public function onGameOver(event:ScriptEvent) {} - public function onNoteHit(event:NoteScriptEvent) {} + public function onNoteIncoming(event:NoteScriptEvent) {} + + public function onNoteHit(event:HitNoteScriptEvent) {} public function onNoteMiss(event:NoteScriptEvent) {} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index e7ad326d2..5564c46c2 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1614,8 +1614,10 @@ class PlayState extends MusicBeatSubState var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); - playerStrumline = new Strumline(noteStyle, true); + playerStrumline = new Strumline(noteStyle, !isBotPlayMode); + playerStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); opponentStrumline = new Strumline(noteStyle, false); + opponentStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); add(playerStrumline); add(opponentStrumline); @@ -1751,6 +1753,13 @@ class PlayState extends MusicBeatSubState opponentStrumline.applyNoteData(opponentNoteData); } + function onStrumlineNoteIncoming(noteSprite:NoteSprite):Void + { + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_INCOMING, noteSprite, 0, false); + + dispatchEvent(event); + } + /** * Prepares to start the countdown. * Ends any running cutscenes, creates the strumlines, and starts the countdown. @@ -1942,7 +1951,7 @@ class PlayState extends MusicBeatSubState // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, 0, true); + var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index cf5311bdc..d39f19b76 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -485,7 +485,7 @@ class BaseCharacter extends Bopper * Every time a note is hit, check if the note is from the same strumline. * If it is, then play the sing animation. */ - public override function onNoteHit(event:NoteScriptEvent) + public override function onNoteHit(event:HitNoteScriptEvent) { super.onNoteHit(event); diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 190aa3ee0..1ba5dcfc5 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -1,5 +1,6 @@ package funkin.play.notes; +import flixel.util.FlxSignal.FlxTypedSignal; import flixel.FlxG; import funkin.play.notes.notestyle.NoteStyle; import flixel.group.FlxSpriteGroup; @@ -49,6 +50,8 @@ class Strumline extends FlxSpriteGroup public var holdNotes:FlxTypedSpriteGroup; + public var onNoteIncoming:FlxTypedSignalVoid>; + var strumlineNotes:FlxTypedSpriteGroup; var noteSplashes:FlxTypedSpriteGroup; var noteHoldCovers:FlxTypedSpriteGroup; @@ -106,6 +109,8 @@ class Strumline extends FlxSpriteGroup this.refresh(); + this.onNoteIncoming = new FlxTypedSignalVoid>(); + for (i in 0...KEY_COUNT) { var child:StrumlineNote = new StrumlineNote(noteStyle, isPlayer, DIRECTIONS[i]); @@ -311,6 +316,8 @@ class Strumline extends FlxSpriteGroup } nextNoteIndex = noteIndex + 1; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow. + + onNoteIncoming.dispatch(noteSprite); } // Update rendering of notes. diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 61f83d1ed..3997692c2 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -364,7 +364,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry Date: Tue, 5 Mar 2024 22:27:07 -0500 Subject: [PATCH 093/172] Implement a botplay checkbox in the chart editor --- assets | 2 +- source/funkin/play/PlayState.hx | 71 +++++++++++++++++-- source/funkin/play/notes/Strumline.hx | 4 ++ .../ui/debug/charting/ChartEditorState.hx | 6 ++ .../handlers/ChartEditorToolboxHandler.hx | 9 +++ source/funkin/ui/freeplay/FreeplayState.hx | 8 +-- 6 files changed, 88 insertions(+), 12 deletions(-) diff --git a/assets b/assets index 69ebdb6a7..51b02f0d4 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 69ebdb6a7aa57b6762ce509243679ab959615120 +Subproject commit 51b02f0d47e5b34bf8589065c092953c10c5040d diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index e7ad326d2..46c09090d 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -111,6 +111,11 @@ typedef PlayStateParams = * @default `false` */ ?practiceMode:Bool, + /** + * Whether the song should start in Bot Play Mode. + * @default `false` + */ + ?botPlayMode:Bool, /** * Whether the song should be in minimal mode. * @default `false` @@ -282,6 +287,12 @@ class PlayState extends MusicBeatSubState */ public var isPracticeMode:Bool = false; + /** + * Whether the game is currently in Bot Play Mode. + * If true, player will not lose gain or lose score from notes. + */ + public var isBotPlayMode:Bool = false; + /** * Whether the player has dropped below zero health, * and we are just waiting for an animation to play out before transitioning. @@ -566,6 +577,7 @@ class PlayState extends MusicBeatSubState if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; if (params.targetVariation != null) currentVariation = params.targetVariation; isPracticeMode = params.practiceMode ?? false; + isBotPlayMode = params.botPlayMode ?? false; isMinimalMode = params.minimalMode ?? false; startTimestamp = params.startTimestamp ?? 0.0; playbackRate = params.playbackRate ?? 1.0; @@ -1614,7 +1626,7 @@ class PlayState extends MusicBeatSubState var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); - playerStrumline = new Strumline(noteStyle, true); + playerStrumline = new Strumline(noteStyle, !isBotPlayMode); opponentStrumline = new Strumline(noteStyle, false); add(playerStrumline); add(opponentStrumline); @@ -1876,7 +1888,14 @@ class PlayState extends MusicBeatSubState function updateScoreText():Void { // TODO: Add functionality for modules to update the score text. - scoreText.text = 'Score:' + songScore; + if (isBotPlayMode) + { + scoreText.text = 'Bot Play Enabled'; + } + else + { + scoreText.text = 'Score:' + songScore; + } } /** @@ -1884,7 +1903,14 @@ class PlayState extends MusicBeatSubState */ function updateHealthBar():Void { - healthLerp = FlxMath.lerp(healthLerp, health, 0.15); + if (isBotPlayMode) + { + healthLerp = Constants.HEALTH_MAX; + } + else + { + healthLerp = FlxMath.lerp(healthLerp, health, 0.15); + } } /** @@ -1928,13 +1954,16 @@ class PlayState extends MusicBeatSubState if (Conductor.instance.songPosition > hitWindowEnd) { - if (note.hasMissed) continue; + if (note.hasMissed || note.hasBeenHit) continue; note.tooEarly = false; note.mayHit = false; note.hasMissed = true; - if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true; + if (note.holdNoteSprite != null) + { + note.holdNoteSprite.missedNote = true; + } } else if (Conductor.instance.songPosition > hitWindowCenter) { @@ -2021,10 +2050,38 @@ class PlayState extends MusicBeatSubState if (Conductor.instance.songPosition > hitWindowEnd) { + if (note.hasMissed || note.hasBeenHit) continue; note.tooEarly = false; note.mayHit = false; note.hasMissed = true; - if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true; + if (note.holdNoteSprite != null) + { + note.holdNoteSprite.missedNote = true; + } + } + else if (isBotPlayMode && Conductor.instance.songPosition > hitWindowCenter) + { + if (note.hasBeenHit) continue; + + // We call onHitNote to play the proper animations, + // but not goodNoteHit! This means zero score and zero notes hit for the results screen! + + // Call an event to allow canceling the note hit. + // NOTE: This is what handles the character animations! + var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0); + dispatchEvent(event); + + // Calling event.cancelEvent() skips all the other logic! Neat! + if (event.eventCanceled) continue; + + // Command the bot to hit the note on time. + // NOTE: This is what handles the strumline and cleaning up the note itself! + playerStrumline.hitNote(note); + + if (note.holdNoteSprite != null) + { + playerStrumline.playNoteHoldCover(note.holdNoteSprite); + } } else if (Conductor.instance.songPosition > hitWindowStart) { @@ -2069,7 +2126,7 @@ class PlayState extends MusicBeatSubState if (holdNote == null || !holdNote.alive) continue; // While the hold note is being hit, and there is length on the hold note... - if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) + if (!isBotPlayMode && holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) { // Grant the player health. health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 190aa3ee0..efe1c707a 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -38,6 +38,10 @@ class Strumline extends FlxSpriteGroup return FlxG.height / 0.45; } + /** + * Whether this strumline is controlled by the player's inputs. + * False means it's controlled by the opponent or Bot Play. + */ public var isPlayer:Bool; /** diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78e73bf27..85a2396b9 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -592,6 +592,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var playtestPracticeMode:Bool = false; + /** + * If true, playtesting a chart will make the computer do it for you! + */ + var playtestBotPlayMode:Bool = false; + /** * Enables or disables the "debugger" popup that appears when you run into a flixel error. */ @@ -5359,6 +5364,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState targetDifficulty: selectedDifficulty, targetVariation: selectedVariation, practiceMode: playtestPracticeMode, + botPlayMode: playtestBotPlayMode, minimalMode: minimal, startTimestamp: startTimestamp, playbackRate: playbackRate, diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index f32cc2bfb..3b32edf5d 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -299,6 +299,15 @@ class ChartEditorToolboxHandler state.playtestStartTime = checkboxStartTime.selected; }; + var checkboxBotPlay:Null = toolbox.findComponent('playtestBotPlayCheckbox', CheckBox); + if (checkboxBotPlay == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestBotPlayCheckbox component.'; + + checkboxBotPlay.selected = state.playtestBotPlayMode; + + checkboxBotPlay.onClick = _ -> { + state.playtestBotPlayMode = checkboxBotPlay.selected; + }; + var checkboxDebugger:Null = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox); if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.'; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 50f85571b..45f9a4d27 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1143,12 +1143,12 @@ class FreeplayState extends MusicBeatSubState targetSong: targetSong, targetDifficulty: targetDifficulty, targetVariation: targetVariation, - // TODO: Make this an option! - // startTimestamp: 0.0, - // TODO: Make this an option! - // playbackRate: 0.5, practiceMode: false, minimalMode: false, + // TODO: Make these an option! It's currently only accessible via chart editor. + // startTimestamp: 0.0, + // playbackRate: 0.5, + // botPlayMode: true, }, true); }); } From 607b5757fdc6f558c75eedfd1b7fee8d478a1e85 Mon Sep 17 00:00:00 2001 From: FabsTheFabs Date: Wed, 6 Mar 2024 05:05:03 +0000 Subject: [PATCH 094/172] added decimal point to waveform duration --- .../debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx index af1d75444..bd2fd8ba3 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx @@ -272,19 +272,19 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox // waveformPlayer.waveform.forceUpdate = true; waveformPlayer.waveform.waveformData = playerVoice?.waveformData; // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it. - waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000) / Constants.MS_PER_SEC; + waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000.0) / Constants.MS_PER_SEC; // Build opponent waveform. // waveformOpponent.waveform.forceUpdate = true; // note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor // so we null check waveformOpponent.waveform.waveformData = opponentVoice?.waveformData; - waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000) / Constants.MS_PER_SEC; + waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000.0) / Constants.MS_PER_SEC; // Build instrumental waveform. // waveformInstrumental.waveform.forceUpdate = true; waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData; - waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000) / Constants.MS_PER_SEC; + waveformInstrumental.waveform.duration = (instTrack?.lenth ?? 1000.0) / Constants.MS_PER_SEC; addOffsetsToAudioPreview(); } From 6bc1eb7278dab1e01f32125026c33cf1cc67a6ea Mon Sep 17 00:00:00 2001 From: FabsTheFabs Date: Wed, 6 Mar 2024 05:06:57 +0000 Subject: [PATCH 095/172] typo oops --- .../ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx index bd2fd8ba3..46721edfa 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorOffsetsToolbox.hx @@ -284,7 +284,7 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox // Build instrumental waveform. // waveformInstrumental.waveform.forceUpdate = true; waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData; - waveformInstrumental.waveform.duration = (instTrack?.lenth ?? 1000.0) / Constants.MS_PER_SEC; + waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000.0) / Constants.MS_PER_SEC; addOffsetsToAudioPreview(); } From 67ede14685ad6199cc84cdab6230603c9bfb71cf Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 6 Mar 2024 02:17:45 -0500 Subject: [PATCH 096/172] raindrop assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 095e91fb3..bd718379f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 095e91fb33dad70a5b51e37542e335cedd025d09 +Subproject commit bd718379f18ea9562f5260a5e41e20720ebe9e9b From a516e9199f714a2be5488d2ee4d063c3430f3c80 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Tue, 5 Mar 2024 23:21:57 -0800 Subject: [PATCH 097/172] Remove DynamicTools; fix pause menu on HTML5 Calls intended for `ArrayTools.clone` were being routed to `DynamicTools.clone` due to the order of `using` statements in `imports.hx`. This caused the pause menu to break due to arrays becoming fubar (missing length property). Using `DynamicTools` is a little dangerous, so remove it in favor of calling `Reflect.copy` directly. --- source/funkin/import.hx | 1 - .../funkin/ui/debug/charting/ChartEditorState.hx | 4 ++-- source/funkin/util/tools/DynamicTools.hx | 14 -------------- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 source/funkin/util/tools/DynamicTools.hx diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 02055d4ed..250de99cb 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -13,7 +13,6 @@ using Lambda; using StringTools; using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; -using funkin.util.tools.DynamicTools; using funkin.util.tools.FloatTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index b9e412b36..0e1aa4f4d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -4530,14 +4530,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone()); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, Reflect.copy(eventDataToPlace)); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone()); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, Reflect.copy(noteKindToPlace)); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); diff --git a/source/funkin/util/tools/DynamicTools.hx b/source/funkin/util/tools/DynamicTools.hx deleted file mode 100644 index 47501ea22..000000000 --- a/source/funkin/util/tools/DynamicTools.hx +++ /dev/null @@ -1,14 +0,0 @@ -package funkin.util.tools; - -class DynamicTools -{ - /** - * Creates a full clone of the input `Dynamic`. Only guaranteed to work on anonymous structures. - * @param input The `Dynamic` to clone. - * @return A clone of the input `Dynamic`. - */ - public static function clone(input:Dynamic):Dynamic - { - return Reflect.copy(input); - } -} From b2d3fe17d7b7af1e0d21786d357f12815a1aa498 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 6 Mar 2024 02:56:10 -0500 Subject: [PATCH 098/172] fun lil pitch effect on result scren --- source/funkin/play/ResultState.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index f77c3fc6b..a78d61583 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -347,6 +347,10 @@ class ResultState extends MusicBeatSubState if (controls.PAUSE) { + FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8); + FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, {onComplete: _ -> { + FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4); + }}); if (params.storyMode) { openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker))); From 7a9bff248e3351f051884ef01d7e3b083491000a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 6 Mar 2024 12:24:25 -0500 Subject: [PATCH 099/172] Fix an issue with array.clone() on HTML5 --- source/funkin/import.hx | 2 +- source/funkin/util/tools/DynamicAccessTools.hx | 16 ++++++++++++++++ source/funkin/util/tools/DynamicTools.hx | 14 -------------- 3 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 source/funkin/util/tools/DynamicAccessTools.hx delete mode 100644 source/funkin/util/tools/DynamicTools.hx diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 02055d4ed..66c3470ff 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -13,7 +13,7 @@ using Lambda; using StringTools; using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; -using funkin.util.tools.DynamicTools; +using funkin.util.tools.DynamicAccessTools; using funkin.util.tools.FloatTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; diff --git a/source/funkin/util/tools/DynamicAccessTools.hx b/source/funkin/util/tools/DynamicAccessTools.hx new file mode 100644 index 000000000..1c83ce039 --- /dev/null +++ b/source/funkin/util/tools/DynamicAccessTools.hx @@ -0,0 +1,16 @@ +package funkin.util.tools; + +import haxe.DynamicAccess; + +class DynamicAccessTools +{ + /** + * Creates a full clone of the input `DynamicAccess`. + * @param input The `Dynamic` to clone. + * @return A clone of the input `Dynamic`. + */ + public static function clone(input:DynamicAccess):DynamicAccess + { + return Reflect.copy(input); + } +} diff --git a/source/funkin/util/tools/DynamicTools.hx b/source/funkin/util/tools/DynamicTools.hx deleted file mode 100644 index 47501ea22..000000000 --- a/source/funkin/util/tools/DynamicTools.hx +++ /dev/null @@ -1,14 +0,0 @@ -package funkin.util.tools; - -class DynamicTools -{ - /** - * Creates a full clone of the input `Dynamic`. Only guaranteed to work on anonymous structures. - * @param input The `Dynamic` to clone. - * @return A clone of the input `Dynamic`. - */ - public static function clone(input:Dynamic):Dynamic - { - return Reflect.copy(input); - } -} From 2fa1d18dce85a6127148406d5f6822279e48bfc6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 6 Mar 2024 12:29:54 -0500 Subject: [PATCH 100/172] Fix build --- source/funkin/ui/debug/charting/ChartEditorState.hx | 2 +- source/funkin/util/tools/DynamicAccessTools.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78e73bf27..191f3cb15 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -4532,7 +4532,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone()); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); diff --git a/source/funkin/util/tools/DynamicAccessTools.hx b/source/funkin/util/tools/DynamicAccessTools.hx index 1c83ce039..14b9a6c68 100644 --- a/source/funkin/util/tools/DynamicAccessTools.hx +++ b/source/funkin/util/tools/DynamicAccessTools.hx @@ -9,7 +9,7 @@ class DynamicAccessTools * @param input The `Dynamic` to clone. * @return A clone of the input `Dynamic`. */ - public static function clone(input:DynamicAccess):DynamicAccess + public static function clone(input:DynamicAccess):DynamicAccess { return Reflect.copy(input); } From f671cc856902713618d270137592a4bb0a606348 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 6 Mar 2024 14:13:48 -0500 Subject: [PATCH 101/172] Remove DynamicAccessTools entirely. --- source/funkin/import.hx | 1 - .../funkin/ui/debug/charting/ChartEditorState.hx | 2 +- source/funkin/util/tools/DynamicAccessTools.hx | 16 ---------------- 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 source/funkin/util/tools/DynamicAccessTools.hx diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 66c3470ff..250de99cb 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -13,7 +13,6 @@ using Lambda; using StringTools; using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; -using funkin.util.tools.DynamicAccessTools; using funkin.util.tools.FloatTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 191f3cb15..29d7ddf97 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -4525,7 +4525,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone()); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.copy()); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } diff --git a/source/funkin/util/tools/DynamicAccessTools.hx b/source/funkin/util/tools/DynamicAccessTools.hx deleted file mode 100644 index 14b9a6c68..000000000 --- a/source/funkin/util/tools/DynamicAccessTools.hx +++ /dev/null @@ -1,16 +0,0 @@ -package funkin.util.tools; - -import haxe.DynamicAccess; - -class DynamicAccessTools -{ - /** - * Creates a full clone of the input `DynamicAccess`. - * @param input The `Dynamic` to clone. - * @return A clone of the input `Dynamic`. - */ - public static function clone(input:DynamicAccess):DynamicAccess - { - return Reflect.copy(input); - } -} From 332a81ec72a36af65757bf66a640d38e88f4ae77 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 6 Mar 2024 17:22:11 -0500 Subject: [PATCH 102/172] No longer miss notes during ending cutscene --- source/funkin/play/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ab693ef46..a6e4b4632 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1032,7 +1032,7 @@ class PlayState extends MusicBeatSubState if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed); // Moving notes into position is now done by Strumline.update(). - processNotes(elapsed); + if (!isInCutscene) processNotes(elapsed); justUnpaused = false; } From aff7bbb4e9bd6d8307e1d0ef79d88cee035c7a40 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 6 Mar 2024 17:22:22 -0500 Subject: [PATCH 103/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 095e91fb3..bf61ea3c9 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 095e91fb33dad70a5b51e37542e335cedd025d09 +Subproject commit bf61ea3c9b56bc82c36976d5aa8052bc16ff1b4e From 69b28ca42c5f8f0be4052f09df05f3e782de2bc6 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Wed, 6 Mar 2024 22:48:44 -0800 Subject: [PATCH 104/172] Add #if FLX_DEBUG in TrackerUtil `Tracker.addProfile` only exists when FLX_DEBUG is set, so add this conditional check to fix non-debug builds. --- source/funkin/util/TrackerUtil.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/util/TrackerUtil.hx b/source/funkin/util/TrackerUtil.hx index b8ed0c995..ffe374c5f 100644 --- a/source/funkin/util/TrackerUtil.hx +++ b/source/funkin/util/TrackerUtil.hx @@ -17,7 +17,9 @@ class TrackerUtil */ public static function initTrackers():Void { + #if FLX_DEBUG Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"])); FlxG.console.registerClass(Highscore); + #end } } From b0abef0d527dcb90060a1b142fa4f4ed393a5ba2 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Wed, 6 Mar 2024 23:37:50 -0800 Subject: [PATCH 105/172] Use correct resource URL when loading videos On HTML5, `VideoCutscene` was not stripping the library prefix from the video file path, causing the video to fail to load. Fixes FPIQ-281. --- source/funkin/play/cutscene/VideoCutscene.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 2d31b0a28..ff56e0919 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -61,7 +61,7 @@ class VideoCutscene VideoCutscene.cutsceneType = cutsceneType; #if html5 - playVideoHTML5(filePath); + playVideoHTML5(rawFilePath); #elseif hxCodec playVideoNative(rawFilePath); #else From 427e4810ad768879e5585915abb50f7ec5f43c7a Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 7 Mar 2024 03:57:16 -0500 Subject: [PATCH 106/172] faster bf processing... --- source/funkin/util/logging/AnsiTrace.hx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/funkin/util/logging/AnsiTrace.hx b/source/funkin/util/logging/AnsiTrace.hx index c8d27b86f..9fdc19e1b 100644 --- a/source/funkin/util/logging/AnsiTrace.hx +++ b/source/funkin/util/logging/AnsiTrace.hx @@ -52,7 +52,12 @@ class AnsiTrace public static function traceBF() { #if sys - if (colorSupported) Sys.println(ansiBF.join("\n")); + if (colorSupported) + { + for (line in ansiBF) + Sys.stdout().writeString(line + "\n"); + Sys.stdout().flush(); + } #end } From c385b7887f83d4bf5309c1aaa16ea32e4058b312 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 7 Mar 2024 15:41:00 -0500 Subject: [PATCH 107/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index bf61ea3c9..3510a2459 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit bf61ea3c9b56bc82c36976d5aa8052bc16ff1b4e +Subproject commit 3510a245986914ff72736f121347cf4a11db0b35 From a6c44412c50e2dc8ef663f9349bf3e08fde13e38 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 7 Mar 2024 23:20:02 -0500 Subject: [PATCH 108/172] Exclude WAV files from builds. --- Project.xml | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Project.xml b/Project.xml index c368dacef..99c46ef9f 100644 --- a/Project.xml +++ b/Project.xml @@ -22,8 +22,8 @@ - - + +
@@ -53,28 +53,28 @@
- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + -
+
- - + + + + + @@ -216,9 +219,9 @@ --> - +
- +
diff --git a/assets b/assets index fe8c987eb..a9c472d5d 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit fe8c987eb846ceb73b8518879b506111aaccdf80 +Subproject commit a9c472d5d3ae1729dc109ab50c2f9824dbb9e5e7 diff --git a/source/funkin/audio/visualize/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx index 470dbf7fe..636c0726a 100644 --- a/source/funkin/audio/visualize/SpectogramSprite.hx +++ b/source/funkin/audio/visualize/SpectogramSprite.hx @@ -4,7 +4,6 @@ import flixel.FlxSprite; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.math.FlxMath; import flixel.math.FlxPoint; -import flixel.math.FlxVector; import flixel.sound.FlxSound; import flixel.util.FlxColor; import funkin.audio.visualize.PolygonSpectogram.VISTYPE; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 970f021ac..b84c68f8d 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -808,7 +808,8 @@ class ChartEditorDialogHandler } songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel); #if FILE_DROP_SUPPORTED - addDropHandler(songVariationMetadataEntry, onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel)); + state.addDropHandler({component: songVariationMetadataEntry, handler: onDropFileMetadataVariation.bind(variation) + .bind(songVariationMetadataEntryLabel)}); #end chartContainerB.addComponent(songVariationMetadataEntry); @@ -832,7 +833,11 @@ class ChartEditorDialogHandler } songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel); #if FILE_DROP_SUPPORTED - addDropHandler(songVariationChartDataEntry, onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel)); + state.addDropHandler( + { + component: songVariationChartDataEntry, + handler: onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel) + }); #end chartContainerB.addComponent(songVariationChartDataEntry); } diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx index 9d37fe2c1..55f43d2ef 100644 --- a/source/funkin/ui/freeplay/DJBoyfriend.hx +++ b/source/funkin/ui/freeplay/DJBoyfriend.hx @@ -4,7 +4,7 @@ import flixel.FlxSprite; import flixel.util.FlxSignal; import funkin.util.assets.FlxAnimationUtil; import funkin.graphics.adobeanimate.FlxAtlasSprite; -import flixel.system.FlxSound; +import flixel.sound.FlxSound; import flixel.util.FlxTimer; import funkin.audio.FlxStreamSound; From b9ab38e0b9fb4d9738518d6c978397bf9f9e9206 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 13 Mar 2024 18:47:15 -0700 Subject: [PATCH 134/172] timertools -> timerutil fix --- source/funkin/InitState.hx | 6 +-- .../audio/waveform/WaveformDataParser.hx | 6 +-- source/funkin/play/components/PopUpStuff.hx | 10 ++--- .../handlers/ChartEditorAudioHandler.hx | 44 +++++++++---------- .../toolboxes/ChartEditorFreeplayToolbox.hx | 6 +-- source/funkin/util/TimerUtil.hx | 4 +- source/funkin/util/plugins/MemoryGCPlugin.hx | 6 +-- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 9ecf66ec7..8c6af4e51 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -30,7 +30,7 @@ import funkin.modding.module.ModuleHandler; import funkin.ui.title.TitleState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import funkin.ui.transition.LoadingState; import funkin.util.TrackerUtil; #if discord_rpc @@ -221,7 +221,7 @@ class InitState extends FlxState // NOTE: Registries must be imported and not referenced with fully qualified names, // to ensure build macros work properly. trace('Parsing game data...'); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry. SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); @@ -238,7 +238,7 @@ class InitState extends FlxState ModuleHandler.loadModuleCache(); ModuleHandler.callOnCreate(); - trace('Parsing game data took: ${TimerTools.ms(perfStart)}'); + trace('Parsing game data took: ${TimerUtil.ms(perfStart)}'); } /** diff --git a/source/funkin/audio/waveform/WaveformDataParser.hx b/source/funkin/audio/waveform/WaveformDataParser.hx index c667f2002..5aa54d744 100644 --- a/source/funkin/audio/waveform/WaveformDataParser.hx +++ b/source/funkin/audio/waveform/WaveformDataParser.hx @@ -1,6 +1,6 @@ package funkin.audio.waveform; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; class WaveformDataParser { @@ -73,7 +73,7 @@ class WaveformDataParser var outputData:Array = []; - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); for (pointIndex in 0...outputPointCount) { @@ -110,7 +110,7 @@ class WaveformDataParser var outputDataLength:Int = Std.int(outputData.length / channels / 2); var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData); - trace('[WAVEFORM] Interpreted audio buffer in ${TimerTools.seconds(perfStart)}.'); + trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.'); return result; } diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 0fe50f513..69ce40140 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -6,7 +6,7 @@ import flixel.tweens.FlxTween; import flixel.util.FlxDirection; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; class PopUpStuff extends FlxTypedGroup { @@ -17,7 +17,7 @@ class PopUpStuff extends FlxTypedGroup public function displayRating(daRating:String) { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); if (daRating == null) daRating = "good"; @@ -59,12 +59,12 @@ class PopUpStuff extends FlxTypedGroup startDelay: Conductor.instance.beatLengthMs * 0.001 }); - trace('displayRating took: ${TimerTools.seconds(perfStart)}'); + trace('displayRating took: ${TimerUtil.seconds(perfStart)}'); } public function displayCombo(?combo:Int = 0):Int { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); if (combo == null) combo = 0; @@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup daLoop++; } - trace('displayCombo took: ${TimerTools.seconds(perfStart)}'); + trace('displayCombo took: ${TimerUtil.seconds(perfStart)}'); return combo; } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 889f5764f..1e1d165f3 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -7,7 +7,7 @@ import funkin.audio.FunkinSound; import funkin.play.character.BaseCharacter.CharacterType; import funkin.util.FileUtil; import funkin.util.assets.SoundUtil; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformDataParser; import funkin.audio.waveform.WaveformSprite; @@ -129,41 +129,41 @@ class ChartEditorAudioHandler public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool { - var perfA:Float = TimerTools.start(); + var perfA:Float = TimerUtil.start(); var result:Bool = playInstrumental(state, instId); if (!result) return false; - var perfB:Float = TimerTools.start(); + var perfB:Float = TimerUtil.start(); stopExistingVocals(state); - var perfC:Float = TimerTools.start(); + var perfC:Float = TimerUtil.start(); result = playVocals(state, BF, playerId, instId); - var perfD:Float = TimerTools.start(); + var perfD:Float = TimerUtil.start(); // if (!result) return false; result = playVocals(state, DAD, opponentId, instId); // if (!result) return false; - var perfE:Float = TimerTools.start(); + var perfE:Float = TimerUtil.start(); state.hardRefreshOffsetsToolbox(); - var perfF:Float = TimerTools.start(); + var perfF:Float = TimerUtil.start(); state.hardRefreshFreeplayToolbox(); - var perfG:Float = TimerTools.start(); + var perfG:Float = TimerUtil.start(); - trace('Switched to instrumental in ${TimerTools.seconds(perfA, perfB)}.'); - trace('Stopped existing vocals in ${TimerTools.seconds(perfB, perfC)}.'); - trace('Played BF vocals in ${TimerTools.seconds(perfC, perfD)}.'); - trace('Played DAD vocals in ${TimerTools.seconds(perfD, perfE)}.'); - trace('Hard refreshed offsets toolbox in ${TimerTools.seconds(perfE, perfF)}.'); - trace('Hard refreshed freeplay toolbox in ${TimerTools.seconds(perfF, perfG)}.'); + trace('Switched to instrumental in ${TimerUtil.seconds(perfA, perfB)}.'); + trace('Stopped existing vocals in ${TimerUtil.seconds(perfB, perfC)}.'); + trace('Played BF vocals in ${TimerUtil.seconds(perfC, perfD)}.'); + trace('Played DAD vocals in ${TimerUtil.seconds(perfD, perfE)}.'); + trace('Hard refreshed offsets toolbox in ${TimerUtil.seconds(perfE, perfF)}.'); + trace('Hard refreshed freeplay toolbox in ${TimerUtil.seconds(perfF, perfG)}.'); return true; } @@ -175,9 +175,9 @@ class ChartEditorAudioHandler { if (instId == '') instId = 'default'; var instTrackData:Null = state.audioInstTrackData.get(instId); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData); - trace('Built instrumental track in ${TimerTools.seconds(perfStart)} seconds.'); + trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.'); if (instTrack == null) return false; stopExistingInstrumental(state); @@ -205,9 +205,9 @@ class ChartEditorAudioHandler { var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; var vocalTrackData:Null = state.audioVocalTrackData.get(trackId); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData); - trace('Built vocal track in ${TimerTools.seconds(perfStart)}.'); + trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.'); if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup(); @@ -218,9 +218,9 @@ class ChartEditorAudioHandler case BF: state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData:Null = vocalTrack.waveformData; - trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); + trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.'); if (waveformData != null) { @@ -244,9 +244,9 @@ class ChartEditorAudioHandler case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData:Null = vocalTrack.waveformData; - trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); + trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.'); if (waveformData != null) { diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx index 28d435c54..c65781259 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx @@ -7,7 +7,7 @@ import funkin.audio.waveform.WaveformDataParser; import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand; import funkin.ui.haxeui.components.WaveformPlayer; import funkin.ui.freeplay.FreeplayState; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import haxe.ui.backend.flixel.components.SpriteWrapper; import haxe.ui.components.Button; import haxe.ui.components.HorizontalSlider; @@ -289,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox // Build player waveform. // waveformMusic.waveform.forceUpdate = true; - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData1 = playerVoice?.waveformData; var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file! var waveformData3 = chartEditorState.audioInstTrack.waveformData; var waveformData = waveformData3.merge(waveformData1).merge(waveformData2); - trace('Waveform data merging took: ${TimerTools.seconds(perfStart)}'); + trace('Waveform data merging took: ${TimerUtil.seconds(perfStart)}'); waveformMusic.waveform.waveformData = waveformData; // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it. diff --git a/source/funkin/util/TimerUtil.hx b/source/funkin/util/TimerUtil.hx index 5322ada92..caf49341b 100644 --- a/source/funkin/util/TimerUtil.hx +++ b/source/funkin/util/TimerUtil.hx @@ -1,9 +1,9 @@ -package funkin.util.tools; +package funkin.util; import funkin.util.tools.FloatTools; import haxe.Timer; -class TimerTools +class TimerUtil { public static function start():Float { diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx index 67a4fe18e..4b89fa637 100644 --- a/source/funkin/util/plugins/MemoryGCPlugin.hx +++ b/source/funkin/util/plugins/MemoryGCPlugin.hx @@ -1,7 +1,7 @@ package funkin.util.plugins; import flixel.FlxBasic; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; /** * A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection. @@ -24,9 +24,9 @@ class MemoryGCPlugin extends FlxBasic if (FlxG.keys.justPressed.INSERT) { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); funkin.util.MemoryUtil.collect(true); - trace('Memory GC took: ${TimerTools.seconds(perfStart)}'); + trace('Memory GC took: ${TimerUtil.seconds(perfStart)}'); } } From a6fecd05e05d555b1769a43a7c38b28d9d324e1c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 13 Mar 2024 18:58:18 -0700 Subject: [PATCH 135/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b71005291..b498f7f75 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b71005291132a09043cabb59511d9316a21039ca +Subproject commit b498f7f7569af24c25c88836d087f93529c2c6be From b5bc63fecaf6674f60d744de7cf02e82b0536f4f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 13 Mar 2024 21:27:09 -0700 Subject: [PATCH 136/172] use isMinimalMode --- source/funkin/play/event/FocusCameraSongEvent.hx | 2 +- source/funkin/play/event/ZoomCameraSongEvent.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 4ea6fa8c0..625b9cb7a 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -58,7 +58,7 @@ class FocusCameraSongEvent extends SongEvent if (PlayState.instance == null || PlayState.instance.currentStage == null) return; // Does nothing if we are minimal mode. - if (PlayState.instance.minimalMode) return; + if (PlayState.instance.isMinimalMode) return; var posX:Null = data.getFloat('x'); if (posX == null) posX = 0.0; diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 3a903a4ff..d1ce97e40 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -58,7 +58,7 @@ class ZoomCameraSongEvent extends SongEvent if (PlayState.instance == null || PlayState.instance.currentStage == null) return; // Does nothing if we are minimal mode. - if (PlayState.instance.minimalMode) return; + if (PlayState.instance.isMinimalMode) return; var zoom:Null = data.getFloat('zoom'); if (zoom == null) zoom = 1.0; From a21ce20e4056e64b180b2a49e522f4461b84f351 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 13 Mar 2024 21:59:40 -0700 Subject: [PATCH 137/172] assets submod merge --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 64586275f..f71741990 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 64586275f5e47960242faa687f9a26e52fcf2f9a +Subproject commit f7174199004f77eef8eaecfa81cd9307fe020699 From 0d2167d330b9a6a5e0720b3b8210d0ddda614c5f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 14 Mar 2024 03:08:51 -0700 Subject: [PATCH 138/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a9c472d5d..0e2c5bf21 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a9c472d5d3ae1729dc109ab50c2f9824dbb9e5e7 +Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 From 3aada18a5998f18c745034910db16eb8c4aa816d Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 14 Mar 2024 04:03:33 -0700 Subject: [PATCH 139/172] 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 140/172] 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 141/172] 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 142/172] 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 143/172] 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 144/172] 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 f31634351bb1443c4858b690204ecd813869fc28 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 15 Mar 2024 16:45:18 -0400 Subject: [PATCH 145/172] Fix an issue where stage character scale was overriding base character scale. --- source/funkin/play/character/BaseCharacter.hx | 9 +++- source/funkin/play/stage/Stage.hx | 54 ++++++++++--------- .../util/plugins/ReloadAssetsDebugPlugin.hx | 4 ++ 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index d39f19b76..2796f8123 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -193,6 +193,11 @@ class BaseCharacter extends Bopper return _data.death?.cameraOffsets ?? [0.0, 0.0]; } + public function getBaseScale():Float + { + return _data.scale; + } + public function getDeathCameraZoom():Float { return _data.death?.cameraZoom ?? 1.0; @@ -260,8 +265,8 @@ class BaseCharacter extends Bopper } /** - * Set the sprite scale to the appropriate value. - * @param scale + * Set the character's sprite scale to the appropriate value. + * @param scale The desired scale. */ public function setScale(scale:Null):Void { diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 56026469a..db42b0dd3 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -109,10 +109,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements { getBoyfriend().resetCharacter(true); // Reapply the camera offsets. - var charData = _data.characters.bf; - getBoyfriend().scale.set(charData.scale, charData.scale); - getBoyfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; - getBoyfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; + var stageCharData:StageDataCharacter = _data.characters.bf; + var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale; + getBoyfriend().setScale(finalScale); + getBoyfriend().cameraFocusPoint.x += stageCharData.cameraOffsets[0]; + getBoyfriend().cameraFocusPoint.y += stageCharData.cameraOffsets[1]; } else { @@ -122,19 +123,21 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements { getGirlfriend().resetCharacter(true); // Reapply the camera offsets. - var charData = _data.characters.gf; - getGirlfriend().scale.set(charData.scale, charData.scale); - getGirlfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; - getGirlfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; + var stageCharData:StageDataCharacter = _data.characters.gf; + var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale; + getGirlfriend().setScale(finalScale); + getGirlfriend().cameraFocusPoint.x += stageCharData.cameraOffsets[0]; + getGirlfriend().cameraFocusPoint.y += stageCharData.cameraOffsets[1]; } if (getDad() != null) { getDad().resetCharacter(true); // Reapply the camera offsets. - var charData = _data.characters.dad; - getDad().scale.set(charData.scale, charData.scale); - getDad().cameraFocusPoint.x += charData.cameraOffsets[0]; - getDad().cameraFocusPoint.y += charData.cameraOffsets[1]; + var stageCharData:StageDataCharacter = _data.characters.dad; + var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale; + getDad().setScale(finalScale); + getDad().cameraFocusPoint.x += stageCharData.cameraOffsets[0]; + getDad().cameraFocusPoint.y += stageCharData.cameraOffsets[1]; } // Reset positions of named props. @@ -393,23 +396,23 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements #end // Apply position and z-index. - var charData:StageDataCharacter = null; + var stageCharData:StageDataCharacter = null; switch (charType) { case BF: this.characters.set('bf', character); - charData = _data.characters.bf; + stageCharData = _data.characters.bf; character.flipX = !character.getDataFlipX(); character.name = 'bf'; character.initHealthIcon(false); case GF: this.characters.set('gf', character); - charData = _data.characters.gf; + stageCharData = _data.characters.gf; character.flipX = character.getDataFlipX(); character.name = 'gf'; case DAD: this.characters.set('dad', character); - charData = _data.characters.dad; + stageCharData = _data.characters.dad; character.flipX = character.getDataFlipX(); character.name = 'dad'; character.initHealthIcon(true); @@ -421,15 +424,15 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements // This ensures positioning is based on the idle animation. character.resetCharacter(true); - if (charData != null) + if (stageCharData != null) { - character.zIndex = charData.zIndex; + character.zIndex = stageCharData.zIndex; // Start with the per-stage character position. // Subtracting the origin ensures characters are positioned relative to their feet. // Subtracting the global offset allows positioning on a per-character basis. - character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0]; - character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1]; + character.x = stageCharData.position[0] - character.characterOrigin.x + character.globalOffsets[0]; + character.y = stageCharData.position[1] - character.characterOrigin.y + character.globalOffsets[1]; @:privateAccess(funkin.play.stage.Bopper) { @@ -438,16 +441,17 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements character.originalPosition.y = character.y + character.animOffsets[1]; } - character.scale.set(charData.scale, charData.scale); - character.cameraFocusPoint.x += charData.cameraOffsets[0]; - character.cameraFocusPoint.y += charData.cameraOffsets[1]; + var finalScale = character.getBaseScale() * stageCharData.scale; + character.setScale(finalScale); // Don't use scale.set for characters! + character.cameraFocusPoint.x += stageCharData.cameraOffsets[0]; + character.cameraFocusPoint.y += stageCharData.cameraOffsets[1]; #if debug // Draw the debug icon at the character's feet. if (charType == BF || charType == DAD) { - debugIcon.x = charData.position[0]; - debugIcon.y = charData.position[1]; + debugIcon.x = stageCharData.position[0]; + debugIcon.y = stageCharData.position[1]; debugIcon2.x = character.x; debugIcon2.y = character.y; } diff --git a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx index a43317cce..f69609531 100644 --- a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx +++ b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx @@ -22,7 +22,11 @@ class ReloadAssetsDebugPlugin extends FlxBasic { super.update(elapsed); + #if html5 + if (FlxG.keys.justPressed.FIVE && FlxG.keys.pressed.SHIFT) + #else if (FlxG.keys.justPressed.F5) + #end { funkin.modding.PolymodHandler.forceReloadAssets(); From 494a3c9e86dbc6ddb71a193488c3023c26558d9a Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Sat, 16 Mar 2024 08:38:10 -0700 Subject: [PATCH 146/172] 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 874a2d9e86adec6d606ad13a8e29ecbab63880a5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 16 Mar 2024 19:24:05 -0400 Subject: [PATCH 147/172] Formatting cleanup in assets folder --- .prettierignore | 1 + assets | 2 +- tests/unit/assets/shared/images/arrows.xml | 53 ++++++++++------------ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/.prettierignore b/.prettierignore index 657ff3812..2e132c06f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ export # Ignore all JSONS in the images folder (including FlxAnimate JSONs) assets/preload/images assets/shared/images +assets/weekend1/images # Don't ignore data files # TODO: These don't work. diff --git a/assets b/assets index 0e2c5bf21..8d393de26 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 8d393de26ecbbabcc8096258458df9eea8cb387f diff --git a/tests/unit/assets/shared/images/arrows.xml b/tests/unit/assets/shared/images/arrows.xml index 8f3355462..96a73a388 100644 --- a/tests/unit/assets/shared/images/arrows.xml +++ b/tests/unit/assets/shared/images/arrows.xml @@ -1,32 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + From 3a35be916b0b544ef0c3f6fdcdc56685c653a05d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 16 Mar 2024 22:20:22 -0400 Subject: [PATCH 148/172] A bunch of checkstyle fixes --- checkstyle.json | 2 +- source/Main.hx | 27 +- source/Postbuild.hx | 19 +- source/Prebuild.hx | 13 +- .../transition/FlxTransitionableSubState.hx | 234 ------------------ source/funkin/Conductor.hx | 64 +++-- source/funkin/InitState.hx | 37 +-- source/funkin/Paths.hx | 60 ++--- source/funkin/PlayerSettings.hx | 45 +++- source/funkin/Preferences.hx | 15 +- source/funkin/Preloader.hx | 4 +- source/funkin/play/PlayState.hx | 20 +- source/funkin/ui/MusicBeatSubState.hx | 2 +- source/funkin/ui/mainmenu/MainMenuState.hx | 6 +- source/funkin/util/MathUtil.hx | 27 +- source/funkin/util/MemoryUtil.hx | 14 +- source/funkin/util/MouseUtil.hx | 3 + source/funkin/util/PlatformUtil.hx | 8 +- source/funkin/util/SortUtil.hx | 50 +++- source/funkin/util/TimerUtil.hx | 21 +- source/funkin/util/TrackerUtil.hx | 2 +- source/funkin/util/VersionUtil.hx | 13 +- source/funkin/util/WindowUtil.hx | 12 +- source/haxe/ui/backend/flixel/UIStateBase.hx | 3 + .../haxe/ui/backend/flixel/UISubStateBase.hx | 3 + 25 files changed, 310 insertions(+), 394 deletions(-) delete mode 100644 source/flixel/addons/transition/FlxTransitionableSubState.hx diff --git a/checkstyle.json b/checkstyle.json index d41a6d617..5300d94ad 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -79,7 +79,7 @@ { "props": { "ignoreExtern": true, - "format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", + "format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", "tokens": ["INLINE", "NOTINLINE"] }, "type": "ConstantName" diff --git a/source/Main.hx b/source/Main.hx index a40fda29d..ee29eaf70 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -11,9 +11,11 @@ import openfl.display.Sprite; import openfl.events.Event; import openfl.Lib; import openfl.media.Video; -import funkin.util.CLIUtil; import openfl.net.NetStream; +/** + * The main class which initializes HaxeFlixel and starts the game in its initial state. + */ class Main extends Sprite { var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom). @@ -76,26 +78,27 @@ class Main extends Sprite var netStream:NetStream; var overlay:Sprite; + /** + * A frame counter displayed at the top left. + */ public static var fpsCounter:FPS; + + /** + * A RAM counter displayed at the top left. + */ public static var memoryCounter:MemoryCounter; function setupGame():Void { - /** - * The `zoom` argument of FlxGame was removed in the dev branch of Flixel, - * since it was considered confusing and unintuitive. - * If you want to change how the game scales when you resize the window, - * you can use `FlxG.scaleMode`. - * -Eric - */ - initHaxeUI(); + // addChild gets called by the user settings code. fpsCounter = new FPS(10, 3, 0xFFFFFF); - // addChild(fpsCounter); // Handled by Preferences.init + #if !html5 + // addChild gets called by the user settings code. + // TODO: disabled on HTML5 (todo: find another method that works?) memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF); - // addChild(memoryCounter); #end // George recommends binding the save before FlxGame is created. @@ -105,6 +108,8 @@ class Main extends Sprite #if hxcpp_debug_server trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.'); + #else + trace('hxcpp_debug_server is disabled! This build does not support debugging.'); #end } diff --git a/source/Postbuild.hx b/source/Postbuild.hx index f1827c4ab..405d0400b 100644 --- a/source/Postbuild.hx +++ b/source/Postbuild.hx @@ -3,34 +3,37 @@ package source; // Yeah, I know... import sys.FileSystem; import sys.io.File; +/** + * A script which executes after the game is built. + */ class Postbuild { - static inline final buildTimeFile = '.build_time'; + static inline final BUILD_TIME_FILE:String = '.build_time'; - static function main() + static function main():Void { printBuildTime(); } - static function printBuildTime() + static function printBuildTime():Void { // get buildEnd before fs operations since they are blocking var end:Float = Sys.time(); - if (FileSystem.exists(buildTimeFile)) + if (FileSystem.exists(BUILD_TIME_FILE)) { - var fi = File.read(buildTimeFile); + var fi:sys.io.FileInput = File.read(BUILD_TIME_FILE); var start:Float = fi.readDouble(); fi.close(); - sys.FileSystem.deleteFile(buildTimeFile); + sys.FileSystem.deleteFile(BUILD_TIME_FILE); - var buildTime = roundToTwoDecimals(end - start); + var buildTime:Float = roundToTwoDecimals(end - start); trace('Build took: ${buildTime} seconds'); } } - private static function roundToTwoDecimals(value:Float):Float + static function roundToTwoDecimals(value:Float):Float { return Math.round(value * 100) / 100; } diff --git a/source/Prebuild.hx b/source/Prebuild.hx index 18a5e2076..6856c414c 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -2,20 +2,23 @@ package source; // Yeah, I know... import sys.io.File; +/** + * A script which executes before the game is built. + */ class Prebuild { - static inline final buildTimeFile = '.build_time'; + static inline final BUILD_TIME_FILE:String = '.build_time'; - static function main() + static function main():Void { saveBuildTime(); trace('Building...'); } - static function saveBuildTime() + static function saveBuildTime():Void { - var fo = File.write(buildTimeFile); - var now = Sys.time(); + var fo:sys.io.FileOutput = File.write(BUILD_TIME_FILE); + var now:Float = Sys.time(); fo.writeDouble(now); fo.close(); } diff --git a/source/flixel/addons/transition/FlxTransitionableSubState.hx b/source/flixel/addons/transition/FlxTransitionableSubState.hx deleted file mode 100644 index ab416adbc..000000000 --- a/source/flixel/addons/transition/FlxTransitionableSubState.hx +++ /dev/null @@ -1,234 +0,0 @@ -package flixel.addons.transition; - -import flixel.FlxSubState; -import flixel.addons.transition.FlxTransitionableState; - -/** - * A `FlxSubState` which can perform visual transitions - * - * Usage: - * - * First, extend `FlxTransitionableSubState` as ie, `FooState`. - * - * Method 1: - * - * ```haxe - * var in:TransitionData = new TransitionData(...); // add your data where "..." is - * var out:TransitionData = new TransitionData(...); - * - * FlxG.switchState(() -> new FooState(in,out)); - * ``` - * - * Method 2: - * - * ```haxe - * FlxTransitionableSubState.defaultTransIn = new TransitionData(...); - * FlxTransitionableSubState.defaultTransOut = new TransitionData(...); - * - * FlxG.switchState(() -> new FooState()); - * ``` - */ -class FlxTransitionableSubState extends FlxSubState -{ - // global default transitions for ALL states, used if transIn/transOut are null - public static var defaultTransIn(get, set):TransitionData; - - static function get_defaultTransIn():TransitionData - { - return FlxTransitionableState.defaultTransIn; - } - - static function set_defaultTransIn(value:TransitionData):TransitionData - { - return FlxTransitionableState.defaultTransIn = value; - } - - public static var defaultTransOut(get, set):TransitionData; - - static function get_defaultTransOut():TransitionData - { - return FlxTransitionableState.defaultTransOut; - } - - static function set_defaultTransOut(value:TransitionData):TransitionData - { - return FlxTransitionableState.defaultTransOut = value; - } - - public static var skipNextTransIn(get, set):Bool; - - static function get_skipNextTransIn():Bool - { - return FlxTransitionableState.skipNextTransIn; - } - - static function set_skipNextTransIn(value:Bool):Bool - { - return FlxTransitionableState.skipNextTransIn = value; - } - - public static var skipNextTransOut(get, set):Bool; - - static function get_skipNextTransOut():Bool - { - return FlxTransitionableState.skipNextTransOut; - } - - static function set_skipNextTransOut(value:Bool):Bool - { - return FlxTransitionableState.skipNextTransOut = value; - } - - // beginning & ending transitions for THIS state: - public var transIn:TransitionData; - public var transOut:TransitionData; - - public var hasTransIn(get, never):Bool; - public var hasTransOut(get, never):Bool; - - /** - * Create a state with the ability to do visual transitions - * @param TransIn Plays when the state begins - * @param TransOut Plays when the state ends - */ - public function new(?TransIn:TransitionData, ?TransOut:TransitionData) - { - transIn = TransIn; - transOut = TransOut; - - if (transIn == null && defaultTransIn != null) - { - transIn = defaultTransIn; - } - if (transOut == null && defaultTransOut != null) - { - transOut = defaultTransOut; - } - super(); - } - - override function destroy():Void - { - super.destroy(); - transIn = null; - transOut = null; - _onExit = null; - } - - override function create():Void - { - super.create(); - transitionIn(); - } - - override function startOutro(onOutroComplete:() -> Void) - { - if (!hasTransOut) onOutroComplete(); - else if (!_exiting) - { - // play the exit transition, and when it's done call FlxG.switchState - _exiting = true; - transitionOut(onOutroComplete); - - if (skipNextTransOut) - { - skipNextTransOut = false; - finishTransOut(); - } - } - } - - /** - * Starts the in-transition. Can be called manually at any time. - */ - public function transitionIn():Void - { - if (transIn != null && transIn.type != NONE) - { - if (skipNextTransIn) - { - skipNextTransIn = false; - if (finishTransIn != null) - { - finishTransIn(); - } - return; - } - - var _trans = createTransition(transIn); - - _trans.setStatus(FULL); - openSubState(_trans); - - _trans.finishCallback = finishTransIn; - _trans.start(OUT); - } - } - - /** - * Starts the out-transition. Can be called manually at any time. - */ - public function transitionOut(?OnExit:Void->Void):Void - { - _onExit = OnExit; - if (hasTransOut) - { - var _trans = createTransition(transOut); - - _trans.setStatus(EMPTY); - openSubState(_trans); - - _trans.finishCallback = finishTransOut; - _trans.start(IN); - } - else - { - _onExit(); - } - } - - var transOutFinished:Bool = false; - - var _exiting:Bool = false; - var _onExit:Void->Void; - - function get_hasTransIn():Bool - { - return transIn != null && transIn.type != NONE; - } - - function get_hasTransOut():Bool - { - return transOut != null && transOut.type != NONE; - } - - function createTransition(data:TransitionData):Transition - { - return switch (data.type) - { - case TILES: new Transition(data); - case FADE: new Transition(data); - default: null; - } - } - - function finishTransIn() - { - closeSubState(); - } - - function finishTransOut() - { - transOutFinished = true; - - if (!_exiting) - { - closeSubState(); - } - - if (_onExit != null) - { - _onExit(); - } - } -} diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 05c23108f..01147ce31 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -3,7 +3,6 @@ package funkin; import funkin.util.Constants; import flixel.util.FlxSignal; import flixel.math.FlxMath; -import funkin.play.song.Song.SongDifficulty; import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongDataUtils; @@ -136,6 +135,9 @@ class Conductor return beatLengthMs / timeSignatureNumerator; } + /** + * The numerator for the current time signature (the `3` in `3/4`). + */ public var timeSignatureNumerator(get, never):Int; function get_timeSignatureNumerator():Int @@ -145,6 +147,9 @@ class Conductor return currentTimeChange.timeSignatureNum; } + /** + * The denominator for the current time signature (the `4` in `3/4`). + */ public var timeSignatureDenominator(get, never):Int; function get_timeSignatureDenominator():Int @@ -245,7 +250,7 @@ class Conductor * WARNING: Avoid this for things like setting the BPM of the title screen music, * you should have a metadata file for it instead. */ - public function forceBPM(?bpm:Float = null) + public function forceBPM(?bpm:Float):Void { if (bpm != null) { @@ -253,7 +258,7 @@ class Conductor } else { - // trace('[CONDUCTOR] Resetting BPM to default'); + trace('[CONDUCTOR] Resetting BPM to default'); } this.bpmOverride = bpm; @@ -266,7 +271,7 @@ class Conductor * @param songPosition The current position in the song in milliseconds. * Leave blank to use the FlxG.sound.music position. */ - public function update(?songPos:Float) + public function update(?songPos:Float):Void { if (songPos == null) { @@ -274,9 +279,9 @@ class Conductor songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0; } - var oldMeasure = this.currentMeasure; - var oldBeat = this.currentBeat; - var oldStep = this.currentStep; + var oldMeasure:Float = this.currentMeasure; + var oldBeat:Float = this.currentBeat; + var oldStep:Float = this.currentStep; // Set the song position we are at (for purposes of calculating note positions, etc). this.songPosition = songPos; @@ -338,39 +343,43 @@ class Conductor } } - public function mapTimeChanges(songTimeChanges:Array) + /** + * Apply the `SongTimeChange` data from the song metadata to this Conductor. + * @param songTimeChanges The SongTimeChanges. + */ + public function mapTimeChanges(songTimeChanges:Array):Void { timeChanges = []; // Sort in place just in case it's out of order. SongDataUtils.sortTimeChanges(songTimeChanges); - for (currentTimeChange in songTimeChanges) + for (songTimeChange in songTimeChanges) { // TODO: Maybe handle this different? // Do we care about BPM at negative timestamps? // Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`. - if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0; + if (songTimeChange.timeStamp < 0.0) songTimeChange.timeStamp = 0.0; - if (currentTimeChange.timeStamp <= 0.0) + if (songTimeChange.timeStamp <= 0.0) { - currentTimeChange.beatTime = 0.0; + songTimeChange.beatTime = 0.0; } else { // Calculate the beat time of this timestamp. - currentTimeChange.beatTime = 0.0; + songTimeChange.beatTime = 0.0; - if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0) + if (songTimeChange.timeStamp > 0.0 && timeChanges.length > 0) { var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1]; - currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime - + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), + songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime + + ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), 4); } } - timeChanges.push(currentTimeChange); + timeChanges.push(songTimeChange); } if (timeChanges.length > 0) @@ -384,6 +393,8 @@ class Conductor /** * Given a time in milliseconds, return a time in steps. + * @param ms The time in milliseconds. + * @return The time in steps. */ public function getTimeInSteps(ms:Float):Float { @@ -413,7 +424,7 @@ class Conductor var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs; - resultStep += resultFractionalStep; // Math.floor(); + resultStep += resultFractionalStep; return resultStep; } @@ -421,6 +432,8 @@ class Conductor /** * Given a time in steps and fractional steps, return a time in milliseconds. + * @param stepTime The time in steps. + * @return The time in milliseconds. */ public function getStepTimeInMs(stepTime:Float):Float { @@ -457,6 +470,8 @@ class Conductor /** * Given a time in beats and fractional beats, return a time in milliseconds. + * @param beatTime The time in beats. + * @return The time in milliseconds. */ public function getBeatTimeInMs(beatTime:Float):Float { @@ -491,13 +506,16 @@ class Conductor } } + /** + * Add variables of the current Conductor instance to the Flixel debugger. + */ public static function watchQuick():Void { - FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); - FlxG.watch.addQuick("bpm", Conductor.instance.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); + FlxG.watch.addQuick('songPosition', Conductor.instance.songPosition); + FlxG.watch.addQuick('bpm', Conductor.instance.bpm); + FlxG.watch.addQuick('currentMeasureTime', Conductor.instance.currentMeasureTime); + FlxG.watch.addQuick('currentBeatTime', Conductor.instance.currentBeatTime); + FlxG.watch.addQuick('currentStepTime', Conductor.instance.currentStepTime); } /** diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index a9e8dbffa..401147ee4 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -12,7 +12,6 @@ import flixel.math.FlxRect; import flixel.FlxSprite; import flixel.system.debug.log.LogStyle; import flixel.util.FlxColor; -import funkin.ui.options.PreferencesMenu; import funkin.util.macro.MacroUtil; import funkin.util.WindowUtil; import funkin.play.PlayStatePlaylist; @@ -31,7 +30,6 @@ import funkin.ui.title.TitleState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; import funkin.util.TimerUtil; -import funkin.ui.transition.LoadingState; import funkin.util.TrackerUtil; #if discord_rpc import Discord.DiscordClient; @@ -169,8 +167,9 @@ class InitState extends FlxState SpeakerRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries(); - // TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers. - CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry. + // TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors. + // Move it to use a BaseRegistry. + CharacterDataParser.loadCharacterCache(); ModuleHandler.buildModuleCallbacks(); ModuleHandler.loadModuleCache(); @@ -188,25 +187,35 @@ class InitState extends FlxState */ function startGame():Void { - #if SONG // -DSONG=bopeebo + #if SONG + // -DSONG=bopeebo startSong(defineSong(), defineDifficulty()); - #elseif LEVEL // -DLEVEL=week1 -DDIFFICULTY=hard + #elseif LEVEL + // -DLEVEL=week1 -DDIFFICULTY=hard startLevel(defineLevel(), defineDifficulty()); - #elseif FREEPLAY // -DFREEPLAY + #elseif FREEPLAY + // -DFREEPLAY FlxG.switchState(() -> new funkin.ui.freeplay.FreeplayState()); - #elseif DIALOGUE // -DDIALOGUE + #elseif DIALOGUE + // -DDIALOGUE FlxG.switchState(() -> new funkin.ui.debug.dialogue.ConversationDebugState()); - #elseif ANIMATE // -DANIMATE + #elseif ANIMATE + // -DANIMATE FlxG.switchState(() -> new funkin.ui.debug.anim.FlxAnimateTest()); - #elseif WAVEFORM // -DWAVEFORM + #elseif WAVEFORM + // -DWAVEFORM FlxG.switchState(() -> new funkin.ui.debug.WaveformTestState()); - #elseif CHARTING // -DCHARTING + #elseif CHARTING + // -DCHARTING FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState()); - #elseif STAGEBUILD // -DSTAGEBUILD + #elseif STAGEBUILD + // -DSTAGEBUILD FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState()); - #elseif ANIMDEBUG // -DANIMDEBUG + #elseif ANIMDEBUG + // -DANIMDEBUG FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState()); - #elseif LATENCY // -DLATENCY + #elseif LATENCY + // -DLATENCY FlxG.switchState(() -> new funkin.LatencyState()); #else startGameNormally(); diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 6006939be..d2c4833f2 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -11,144 +11,144 @@ class Paths { static var currentLevel:String; - static public function setCurrentLevel(name:String) + public static function setCurrentLevel(name:String):Void { currentLevel = name.toLowerCase(); } public static function stripLibrary(path:String):String { - var parts = path.split(':'); + var parts:Array = path.split(':'); if (parts.length < 2) return path; return parts[1]; } public static function getLibrary(path:String):String { - var parts = path.split(':'); - if (parts.length < 2) return "preload"; + var parts:Array = path.split(':'); + if (parts.length < 2) return 'preload'; return parts[0]; } - static function getPath(file:String, type:AssetType, library:Null) + static function getPath(file:String, type:AssetType, library:Null):String { if (library != null) return getLibraryPath(file, library); if (currentLevel != null) { - var levelPath = getLibraryPathForce(file, currentLevel); + var levelPath:String = getLibraryPathForce(file, currentLevel); if (OpenFlAssets.exists(levelPath, type)) return levelPath; } - var levelPath = getLibraryPathForce(file, "shared"); + var levelPath:String = getLibraryPathForce(file, 'shared'); if (OpenFlAssets.exists(levelPath, type)) return levelPath; return getPreloadPath(file); } - static public function getLibraryPath(file:String, library = "preload") + public static function getLibraryPath(file:String, library = 'preload'):String { - return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library); + return if (library == 'preload' || library == 'default') getPreloadPath(file); else getLibraryPathForce(file, library); } - inline static function getLibraryPathForce(file:String, library:String) + static inline function getLibraryPathForce(file:String, library:String):String { return '$library:assets/$library/$file'; } - inline static function getPreloadPath(file:String) + static inline function getPreloadPath(file:String):String { return 'assets/$file'; } - inline static public function file(file:String, type:AssetType = TEXT, ?library:String) + public static function file(file:String, type:AssetType = TEXT, ?library:String):String { return getPath(file, type, library); } - public static inline function animateAtlas(path:String, ?library:String) + public static function animateAtlas(path:String, ?library:String):String { return getLibraryPath('images/$path', library); } - inline static public function txt(key:String, ?library:String) + public static function txt(key:String, ?library:String):String { return getPath('data/$key.txt', TEXT, library); } - inline static public function frag(key:String, ?library:String) + public static function frag(key:String, ?library:String):String { return getPath('shaders/$key.frag', TEXT, library); } - inline static public function vert(key:String, ?library:String) + public static function vert(key:String, ?library:String):String { return getPath('shaders/$key.vert', TEXT, library); } - inline static public function xml(key:String, ?library:String) + public static function xml(key:String, ?library:String):String { return getPath('data/$key.xml', TEXT, library); } - inline static public function json(key:String, ?library:String) + public static function json(key:String, ?library:String):String { return getPath('data/$key.json', TEXT, library); } - static public function sound(key:String, ?library:String) + public static function sound(key:String, ?library:String):String { return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library); } - inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) + public static function soundRandom(key:String, min:Int, max:Int, ?library:String):String { return sound(key + FlxG.random.int(min, max), library); } - inline static public function music(key:String, ?library:String) + public static function music(key:String, ?library:String):String { return getPath('music/$key.${Constants.EXT_SOUND}', MUSIC, library); } - inline static public function videos(key:String, ?library:String) + public static function videos(key:String, ?library:String):String { return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library); } - inline static public function voices(song:String, ?suffix:String = '') + public static function voices(song:String, ?suffix:String = ''):String { - if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files + if (suffix == null) suffix = ''; // no suffix, for a sorta backwards compatibility with older-ish voice files return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}'; } - inline static public function inst(song:String, ?suffix:String = '') + public static function inst(song:String, ?suffix:String = ''):String { return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}'; } - inline static public function image(key:String, ?library:String) + public static function image(key:String, ?library:String):String { return getPath('images/$key.png', IMAGE, library); } - inline static public function font(key:String) + public static function font(key:String):String { return 'assets/fonts/$key'; } - inline static public function ui(key:String, ?library:String) + public static function ui(key:String, ?library:String):String { return xml('ui/$key', library); } - static public function getSparrowAtlas(key:String, ?library:String) + public static function getSparrowAtlas(key:String, ?library:String):FlxAtlasFrames { return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library)); } - inline static public function getPackerAtlas(key:String, ?library:String) + public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames { return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library)); } diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx index 5a9351bba..b4c871c29 100644 --- a/source/funkin/PlayerSettings.hx +++ b/source/funkin/PlayerSettings.hx @@ -13,6 +13,7 @@ import flixel.util.FlxSignal; */ class PlayerSettings { + // TODO: Finish implementation of second player. public static var numPlayers(default, null) = 0; public static var numAvatars(default, null) = 0; public static var player1(default, null):PlayerSettings; @@ -21,12 +22,21 @@ class PlayerSettings public static var onAvatarAdd(default, null) = new FlxTypedSignalVoid>(); public static var onAvatarRemove(default, null) = new FlxTypedSignalVoid>(); + /** + * The player number associated with this settings object. + */ public var id(default, null):Int; + /** + * The controls handler for this player. + */ public var controls(default, null):Controls; /** * Return the PlayerSettings for the given player number, or `null` if that player isn't active. + * + * @param id The player number this represents. + * @return The PlayerSettings for the given player number, or `null` if that player isn't active. */ public static function get(id:Int):Null { @@ -38,6 +48,9 @@ class PlayerSettings }; } + /** + * Initialize the PlayerSettings singletons for each player. + */ public static function init():Void { if (player1 == null) @@ -56,22 +69,30 @@ class PlayerSettings } } - public static function reset() + /** + * Forcibly destroy the PlayerSettings singletons for each player. + */ + public static function reset():Void { player1 = null; player2 = null; numPlayers = 0; } - static function onGamepadAdded(gamepad:FlxGamepad) + /** + * Callback invoked when a gamepad is added. + * @param gamepad The gamepad that was added. + */ + static function onGamepadAdded(gamepad:FlxGamepad):Void { + // TODO: Make this detect and handle multiple players player1.addGamepad(gamepad); } /** * @param id The player number this represents. This was refactored to START AT `1`. */ - private function new(id:Int) + function new(id:Int) { trace('loading player settings for id: $id'); @@ -83,11 +104,11 @@ class PlayerSettings function addKeyboard():Void { - var useDefault = true; + var useDefault:Bool = true; if (Save.instance.hasControls(id, Keys)) { var keyControlData = Save.instance.getControls(id, Keys); - trace("keyControlData: " + haxe.Json.stringify(keyControlData)); + trace('Loading keyboard control scheme from user save'); useDefault = false; controls.fromSaveData(keyControlData, Keys); } @@ -98,7 +119,7 @@ class PlayerSettings if (useDefault) { - trace("Loading default keyboard control scheme"); + trace('Loading default keyboard control scheme'); controls.setKeyboardScheme(Solo); } @@ -109,13 +130,13 @@ class PlayerSettings * Called after an FlxGamepad has been detected. * @param gamepad The gamepad that was detected. */ - function addGamepad(gamepad:FlxGamepad) + function addGamepad(gamepad:FlxGamepad):Void { var useDefault = true; if (Save.instance.hasControls(id, Gamepad(gamepad.id))) { var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id)); - trace("padControlData: " + haxe.Json.stringify(padControlData)); + trace('Loading gamepad control scheme from user save'); useDefault = false; controls.addGamepadWithSaveData(gamepad.id, padControlData); } @@ -126,7 +147,7 @@ class PlayerSettings if (useDefault) { - trace("Loading gamepad control scheme"); + trace('Loading default gamepad control scheme'); controls.addDefaultGamepad(gamepad.id); } PreciseInputManager.instance.initializeButtons(controls, gamepad); @@ -135,12 +156,12 @@ class PlayerSettings /** * Save this player's controls to the game's persistent save. */ - public function saveControls() + public function saveControls():Void { var keyData = controls.createSaveData(Keys); if (keyData != null) { - trace("saving key data: " + haxe.Json.stringify(keyData)); + trace('Saving keyboard control scheme to user save'); Save.instance.setControls(id, Keys, keyData); } @@ -149,7 +170,7 @@ class PlayerSettings var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0])); if (padData != null) { - trace("saving pad data: " + haxe.Json.stringify(padData)); + trace('Saving gamepad control scheme to user save'); Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData); } } diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 60c7a996a..d8066c581 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -20,7 +20,7 @@ class Preferences static function set_naughtyness(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.naughtyness = value; save.flush(); return value; @@ -39,7 +39,7 @@ class Preferences static function set_downscroll(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.downscroll = value; save.flush(); return value; @@ -58,7 +58,7 @@ class Preferences static function set_flashingLights(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.flashingLights = value; save.flush(); return value; @@ -77,7 +77,7 @@ class Preferences static function set_zoomCamera(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.zoomCamera = value; save.flush(); return value; @@ -122,15 +122,20 @@ class Preferences { if (value != Save.instance.options.autoPause) FlxG.autoPause = value; - var save = Save.instance; + var save:Save = Save.instance; save.options.autoPause = value; save.flush(); return value; } + /** + * Loads the user's preferences from the save data and apply them. + */ public static function init():Void { + // Apply the autoPause setting (enables automatic pausing on focus lost). FlxG.autoPause = Preferences.autoPause; + // Apply the debugDisplay setting (enables the FPS and RAM display). toggleDebugDisplay(Preferences.debugDisplay); } diff --git a/source/funkin/Preloader.hx b/source/funkin/Preloader.hx index 2a73d8199..157015ba6 100644 --- a/source/funkin/Preloader.hx +++ b/source/funkin/Preloader.hx @@ -3,7 +3,6 @@ package funkin; import flash.Lib; import flash.display.Bitmap; import flash.display.BitmapData; -import flash.display.BlendMode; import flash.display.Sprite; import flixel.system.FlxBasePreloader; import openfl.display.Sprite; @@ -12,7 +11,8 @@ import openfl.text.TextField; import openfl.text.TextFormat; import flixel.system.FlxAssets; -@:bitmap("art/preloaderArt.png") class LogoImage extends BitmapData {} +@:bitmap('art/preloaderArt.png') +class LogoImage extends BitmapData {} class Preloader extends FlxBasePreloader { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 984f27c26..2db3a27b9 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2,11 +2,7 @@ package funkin.play; import funkin.audio.FunkinSound; import flixel.addons.display.FlxPieDial; -import flixel.addons.display.FlxPieDial; import flixel.addons.transition.FlxTransitionableState; -import flixel.addons.transition.FlxTransitionableState; -import flixel.addons.transition.FlxTransitionableSubState; -import flixel.addons.transition.FlxTransitionableSubState; import flixel.addons.transition.Transition; import flixel.addons.transition.Transition; import flixel.FlxCamera; @@ -923,8 +919,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: isChartingMode ? Charting : Standard}); - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camHUD; openSubState(pauseSubState); // boyfriendPos.put(); // TODO: Why is this here? @@ -1045,8 +1041,8 @@ class PlayState extends MusicBeatSubState isChartingMode: isChartingMode, transparent: persistentDraw }); - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; openSubState(gameOverSubState); } @@ -2610,8 +2606,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); persistentUpdate = false; - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camCutscene; openSubState(pauseSubState); } @@ -2626,8 +2622,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); persistentUpdate = false; - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camCutscene; openSubState(pauseSubState); } diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index dc742874f..e9effdbee 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -1,6 +1,6 @@ package funkin.ui; -import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.FlxTransitionableState; import flixel.FlxSubState; import flixel.text.FlxText; import funkin.ui.mainmenu.MainMenuState; diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 1892bdec1..f4d48dba3 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -1,6 +1,6 @@ package funkin.ui.mainmenu; -import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.FlxTransitionableState; import funkin.ui.debug.DebugMenuSubState; import flixel.FlxObject; import flixel.FlxSprite; @@ -103,8 +103,8 @@ class MainMenuState extends MusicBeatState persistentDraw = true; persistentUpdate = false; // Freeplay has its own custom transition - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; openSubState(new FreeplayState()); }); diff --git a/source/funkin/util/MathUtil.hx b/source/funkin/util/MathUtil.hx index 5fed1d3e1..0e7c29d73 100644 --- a/source/funkin/util/MathUtil.hx +++ b/source/funkin/util/MathUtil.hx @@ -13,12 +13,24 @@ class MathUtil /** * Perform linear interpolation between the base and the target, based on the current framerate. + * @param base The starting value, when `progress <= 0`. + * @param target The ending value, when `progress >= 1`. + * @param ratio Value used to interpolate between `base` and `target`. + * + * @return The interpolated value. */ public static function coolLerp(base:Float, target:Float, ratio:Float):Float { return base + cameraLerp(ratio) * (target - base); } + /** + * Perform linear interpolation based on the current framerate. + * @param lerp Value used to interpolate between `base` and `target`. + * + * @return The interpolated value. + */ + @:deprecated('Use smoothLerp instead') public static function cameraLerp(lerp:Float):Float { return lerp * (FlxG.elapsed / (1 / 60)); @@ -30,26 +42,30 @@ class MathUtil * @param value The value to get the logarithm of. * @return `log_base(value)` */ - public static function logBase(base:Float, value:Float) + public static function logBase(base:Float, value:Float):Float { return Math.log(value) / Math.log(base); } /** - * @returns `2^x` + * Get the base-2 logarithm of a value. + * @param x value + * @return `2^x` */ - public static function exp2(x:Float) + public static function exp2(x:Float):Float { return Math.pow(2, x); } /** * Linearly interpolate between two values. + * * @param base The starting value, when `progress <= 0`. * @param target The ending value, when `progress >= 1`. * @param progress Value used to interpolate between `base` and `target`. + * @return The interpolated value. */ - public static function lerp(base:Float, target:Float, progress:Float) + public static function lerp(base:Float, target:Float, progress:Float):Float { return base + progress * (target - base); } @@ -62,9 +78,12 @@ 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 The interpolated value. */ public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float { + // An alternative algorithm which uses a separate half-life value: // var halfLife:Float = -duration / logBase(2, precision); // lerp(current, target, 1 - exp2(-elapsed / halfLife)); diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx index 6b5f7deea..f5935ed67 100644 --- a/source/funkin/util/MemoryUtil.hx +++ b/source/funkin/util/MemoryUtil.hx @@ -16,7 +16,7 @@ class MemoryUtil public static function buildGCInfo():String { #if cpp - var result = "HXCPP-Immix:"; + var result:String = 'HXCPP-Immix:'; result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; @@ -35,10 +35,10 @@ class MemoryUtil result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}'; result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; #elseif js - var result = "JS-MNS:"; + var result:String = 'JS-MNS:'; result += '\n- Memory Used: ${getMemoryUsed()} bytes'; #else - var result = "Unknown GC"; + var result:String = 'Unknown GC'; #end return result; @@ -66,7 +66,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.enable(true); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -78,7 +78,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.enable(false); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -92,7 +92,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.run(major); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -107,7 +107,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.compact(); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } } diff --git a/source/funkin/util/MouseUtil.hx b/source/funkin/util/MouseUtil.hx index c3f46819e..5248437a8 100644 --- a/source/funkin/util/MouseUtil.hx +++ b/source/funkin/util/MouseUtil.hx @@ -38,6 +38,9 @@ class MouseUtil } } + /** + * Increment the zoom level of the current camera by the mouse wheel scroll value. + */ public static function mouseWheelZoom():Void { if (FlxG.mouse.wheel != 0) FlxG.camera.zoom += FlxG.mouse.wheel * (0.1 * FlxG.camera.zoom); diff --git a/source/funkin/util/PlatformUtil.hx b/source/funkin/util/PlatformUtil.hx index eecf6a7ea..ae8c1dcee 100644 --- a/source/funkin/util/PlatformUtil.hx +++ b/source/funkin/util/PlatformUtil.hx @@ -16,9 +16,9 @@ class PlatformUtil #if mac return true; #elseif html5 - return js.Browser.window.navigator.platform.startsWith("Mac") - || js.Browser.window.navigator.platform.startsWith("iPad") - || js.Browser.window.navigator.platform.startsWith("iPhone"); + return js.Browser.window.navigator.platform.startsWith('Mac') + || js.Browser.window.navigator.platform.startsWith('iPad') + || js.Browser.window.navigator.platform.startsWith('iPhone'); #else return false; #end @@ -27,7 +27,7 @@ class PlatformUtil /** * Detects and returns the current host platform. * Always returns `HTML5` on web, regardless of the computer running that browser. - * Returns `null` if the platform could not be detected. + * @return The host platform, or `null` if the platform could not be detected. */ public static function detectHostPlatform():Null { diff --git a/source/funkin/util/SortUtil.hx b/source/funkin/util/SortUtil.hx index d87592c91..c5ac175be 100644 --- a/source/funkin/util/SortUtil.hx +++ b/source/funkin/util/SortUtil.hx @@ -27,29 +27,53 @@ class SortUtil /** * You can use this function in FlxTypedGroup.sort() to sort FlxObjects by their z-index values. * The value defaults to 0, but by assigning it you can easily rearrange objects as desired. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first FlxObject to compare. + * @param b The second FlxObject to compare. + * @return 1 if `a` has a higher z-index, -1 if `b` has a higher z-index. */ - public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int + public static inline function byZIndex(order:Int, a:FlxBasic, b:FlxBasic):Int { - if (Obj1 == null || Obj2 == null) return 0; - return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex); + if (a == null || b == null) return 0; + return FlxSort.byValues(order, a.zIndex, b.zIndex); } /** * Given two Notes, returns 1 or -1 based on whether `a` or `b` has an earlier strumtime. * * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Note to compare. + * @param b The second Note to compare. + * @return 1 if `a` has an earlier strumtime, -1 if `b` has an earlier strumtime. */ - public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite) + public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite):Int { return noteDataByTime(order, a.noteData, b.noteData); } - public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData) + /** + * Given two Note Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Event to compare. + * @param b The second Event to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. + */ + public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData):Int { return FlxSort.byValues(order, a.time, b.time); } - public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData) + /** + * Given two Event Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Event to compare. + * @param b The second Event to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. + */ + public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData):Int { return FlxSort.byValues(order, a.time, b.time); } @@ -58,8 +82,11 @@ class SortUtil * Given two FlxFrames, sort their names alphabetically. * * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Frame to compare. + * @param b The second Frame to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. */ - public static inline function byFrameName(a:FlxFrame, b:FlxFrame) + public static inline function byFrameName(a:FlxFrame, b:FlxFrame):Int { return alphabetically(a.name, b.name); } @@ -68,6 +95,7 @@ class SortUtil * Sort predicate for sorting strings alphabetically. * @param a The first string to compare. * @param b The second string to compare. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function alphabetically(a:String, b:String):Int { @@ -81,9 +109,11 @@ class SortUtil /** * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first. * Example usage: `array.sort(defaultThenAlphabetical.bind('test'))` will sort the array so that the string 'test' is first. + * + * @param defaultValue The value to prioritize. * @param a The first string to compare. * @param b The second string to compare. - * @param defaultValue The value to prioritize. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function defaultThenAlphabetically(defaultValue:String, a:String, b:String):Int { @@ -96,9 +126,11 @@ class SortUtil /** * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first. * Example usage: `array.sort(defaultsThenAlphabetical.bind(['test']))` will sort the array so that the string 'test' is first. + * + * @param defaultValues The values to prioritize. * @param a The first string to compare. * @param b The second string to compare. - * @param defaultValues The values to prioritize. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function defaultsThenAlphabetically(defaultValues:Array, a:String, b:String):Int { diff --git a/source/funkin/util/TimerUtil.hx b/source/funkin/util/TimerUtil.hx index caf49341b..eb2690e16 100644 --- a/source/funkin/util/TimerUtil.hx +++ b/source/funkin/util/TimerUtil.hx @@ -5,23 +5,42 @@ import haxe.Timer; class TimerUtil { + /** + * Store the current time. + */ public static function start():Float { return Timer.stamp(); } - private static function took(start:Float, ?end:Float):Float + /** + * Return the elapsed time. + */ + static function took(start:Float, ?end:Float):Float { var endOrNow:Float = end != null ? end : Timer.stamp(); return endOrNow - start; } + /** + * Return the elapsed time in seconds as a string. + * @param start The start time. + * @param end The end time. + * @param precision The number of decimal places to round to. + * @return The elapsed time in seconds as a string. + */ public static function seconds(start:Float, ?end:Float, ?precision = 2):String { var seconds:Float = FloatTools.round(took(start, end), precision); return '${seconds} seconds'; } + /** + * Return the elapsed time in milliseconds as a string. + * @param start The start time. + * @param end The end time. + * @return The elapsed time in milliseconds as a string. + */ public static function ms(start:Float, ?end:Float):String { var seconds:Float = took(start, end); diff --git a/source/funkin/util/TrackerUtil.hx b/source/funkin/util/TrackerUtil.hx index ffe374c5f..a2552f63e 100644 --- a/source/funkin/util/TrackerUtil.hx +++ b/source/funkin/util/TrackerUtil.hx @@ -18,7 +18,7 @@ class TrackerUtil public static function initTrackers():Void { #if FLX_DEBUG - Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"])); + Tracker.addProfile(new TrackerProfile(Highscore, ['tallies'])); FlxG.console.registerClass(Highscore); #end } diff --git a/source/funkin/util/VersionUtil.hx b/source/funkin/util/VersionUtil.hx index e8d3d3652..247ba19db 100644 --- a/source/funkin/util/VersionUtil.hx +++ b/source/funkin/util/VersionUtil.hx @@ -15,6 +15,9 @@ class VersionUtil /** * Checks that a given verison number satisisfies a given version rule. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. + * @param version The semantic version to validate. + * @param versionRule The version rule to validate against. + * @return `true` if the version satisfies the rule, `false` otherwise. */ public static function validateVersion(version:thx.semver.Version, versionRule:thx.semver.VersionRule):Bool { @@ -32,6 +35,9 @@ class VersionUtil /** * Checks that a given verison number satisisfies a given version rule. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. + * @param version The semantic version to validate. + * @param versionRule The version rule to validate against. + * @return `true` if the version satisfies the rule, `false` otherwise. */ public static function validateVersionStr(version:String, versionRule:String):Bool { @@ -56,7 +62,7 @@ class VersionUtil public static function getVersionFromJSON(input:Null):Null { if (input == null) return null; - var parsed = SerializerUtil.fromJSON(input); + var parsed:Dynamic = SerializerUtil.fromJSON(input); if (parsed == null) return null; if (parsed.version == null) return null; var versionStr:String = parsed.version; // Dynamic -> String cast @@ -64,6 +70,11 @@ class VersionUtil return version; } + /** + * Get and parse the semantic version from a JSON string. + * @param input The JSON string to parse. + * @return The semantic version, or null if it could not be parsed. + */ public static function parseVersion(input:Dynamic):Null { if (input == null) return null; diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 9f623c39d..f73fe11ce 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -24,7 +24,7 @@ class WindowUtil { #if CAN_OPEN_LINKS #if linux - Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]); + Sys.command('/usr/bin/xdg-open', [targetUrl, '&']); #else // This should work on Windows and HTML5. FlxG.openURL(targetUrl); @@ -42,7 +42,7 @@ class WindowUtil { #if CAN_OPEN_LINKS #if windows - Sys.command('explorer', [targetPath.replace("/", "\\")]); + Sys.command('explorer', [targetPath.replace('/', '\\')]); #elseif mac Sys.command('open', [targetPath]); #elseif linux @@ -61,9 +61,9 @@ class WindowUtil { #if CAN_OPEN_LINKS #if windows - Sys.command('explorer', ["/select," + targetPath.replace("/", "\\")]); + Sys.command('explorer', ['/select,' + targetPath.replace('/', '\\')]); #elseif mac - Sys.command('open', ["-R", targetPath]); + Sys.command('open', ['-R', targetPath]); #elseif linux // TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file. Sys.command('open', [targetPath]); @@ -82,7 +82,7 @@ class WindowUtil * Wires up FlxSignals that happen based on window activity. * For example, we can run a callback when the window is closed. */ - public static function initWindowEvents() + public static function initWindowEvents():Void { // onUpdate is called every frame just before rendering. @@ -95,7 +95,7 @@ class WindowUtil /** * Turns off that annoying "Report to Microsoft" dialog that pops up when the game crashes. */ - public static function disableCrashHandler() + public static function disableCrashHandler():Void { #if (cpp && windows) untyped __cpp__('SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);'); diff --git a/source/haxe/ui/backend/flixel/UIStateBase.hx b/source/haxe/ui/backend/flixel/UIStateBase.hx index 9f1a53722..38377206c 100644 --- a/source/haxe/ui/backend/flixel/UIStateBase.hx +++ b/source/haxe/ui/backend/flixel/UIStateBase.hx @@ -1,3 +1,6 @@ package haxe.ui.backend.flixel; +/** + * Override HaxeUI to use `MusicBeatState` instead of `FlxState`. + */ typedef UIStateBase = funkin.ui.MusicBeatState; diff --git a/source/haxe/ui/backend/flixel/UISubStateBase.hx b/source/haxe/ui/backend/flixel/UISubStateBase.hx index 306c9b633..52c9a7231 100644 --- a/source/haxe/ui/backend/flixel/UISubStateBase.hx +++ b/source/haxe/ui/backend/flixel/UISubStateBase.hx @@ -1,3 +1,6 @@ package haxe.ui.backend.flixel; +/** + * Override HaxeUI to use `MusicBeatSubState` instead of `FlxSubState`. + */ typedef UISubStateBase = funkin.ui.MusicBeatSubState; From a8ebdc5ee82e3cf0b04900756b104053e612c58e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 18 Mar 2024 22:27:19 -0400 Subject: [PATCH 149/172] Update Polymod to fix several bugs on web --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 42d17743f..b16576e66 100644 --- a/hmm.json +++ b/hmm.json @@ -146,7 +146,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "be712450e5d3ba446008884921bb56873b299a64", + "ref": "6d1bdf79b463ca0baa8471dfc6873ab7701c46ee", "url": "https://github.com/larsiusprime/polymod" }, { From a7780474feccce6e58c41f7339dc1837b3f53bf0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 00:28:25 -0400 Subject: [PATCH 150/172] Improve error handling for bad script superclasses --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index b16576e66..a75dee432 100644 --- a/hmm.json +++ b/hmm.json @@ -146,7 +146,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "6d1bdf79b463ca0baa8471dfc6873ab7701c46ee", + "ref": "5547763a22858a1f10939e082de421d587c862bf", "url": "https://github.com/larsiusprime/polymod" }, { From 4d1bcbc193b316715a650b38f54cfaf8de12a488 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 00:29:01 -0400 Subject: [PATCH 151/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 0e2c5bf21..6846ed8d2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 6846ed8d26b0ab531b758bb009d0ebcd515acb21 From 34cb3ceb24e9418b43a391b040801cee4f28a012 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 00:53:01 -0400 Subject: [PATCH 152/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 6846ed8d2..b272232aa 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6846ed8d26b0ab531b758bb009d0ebcd515acb21 +Subproject commit b272232aaf5a748db4b0dd5ac0cc98b72d4a636c From 42eec667532c330ed4e87ce7d3826a62f1ab5301 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 15:56:00 -0400 Subject: [PATCH 153/172] Rework HTML5 video cutscenes to properly support the pause menu and volume controls. --- source/funkin/audio/FunkinSound.hx | 24 +++- source/funkin/graphics/FunkinSprite.hx | 44 +++++++- .../graphics/framebuffer/FixedBitmapData.hx | 6 +- source/funkin/graphics/video/FlxVideo.hx | 105 +++++++++++++++--- 4 files changed, 153 insertions(+), 26 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 9efa6ed50..c64240909 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -1,11 +1,8 @@ package funkin.audio; -#if flash11 -import flash.media.Sound; -import flash.utils.ByteArray; -#end import flixel.sound.FlxSound; import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.util.FlxSignal.FlxTypedSignal; import flixel.system.FlxAssets.FlxSoundAsset; import funkin.util.tools.ICloneable; import funkin.data.song.SongData.SongMusicData; @@ -27,6 +24,25 @@ class FunkinSound extends FlxSound implements ICloneable { static final MAX_VOLUME:Float = 1.0; + /** + * An FlxSignal which is dispatched when the volume changes. + */ + public static var onVolumeChanged(get, never):FlxTypedSignalVoid>; + + static var _onVolumeChanged:NullVoid>> = null; + + static function get_onVolumeChanged():FlxTypedSignalVoid> + { + if (_onVolumeChanged == null) + { + _onVolumeChanged = new FlxTypedSignalVoid>(); + FlxG.sound.volumeHandler = function(volume:Float) { + _onVolumeChanged.dispatch(volume); + } + } + return _onVolumeChanged; + } + /** * Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible! */ diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index 03382f757..7ead7f1fb 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -3,6 +3,9 @@ package funkin.graphics; import flixel.FlxSprite; import flixel.util.FlxColor; import flixel.graphics.FlxGraphic; +import openfl.display3D.textures.TextureBase; +import funkin.graphics.framebuffer.FixedBitmapData; +import openfl.display.BitmapData; /** * An FlxSprite with additional functionality. @@ -41,7 +44,7 @@ class FunkinSprite extends FlxSprite */ public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite { - var sprite = new FunkinSprite(x, y); + var sprite:FunkinSprite = new FunkinSprite(x, y); sprite.loadTexture(key); return sprite; } @@ -55,7 +58,7 @@ class FunkinSprite extends FlxSprite */ public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite { - var sprite = new FunkinSprite(x, y); + var sprite:FunkinSprite = new FunkinSprite(x, y); sprite.loadSparrow(key); return sprite; } @@ -69,7 +72,7 @@ class FunkinSprite extends FlxSprite */ public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite { - var sprite = new FunkinSprite(x, y); + var sprite:FunkinSprite = new FunkinSprite(x, y); sprite.loadPacker(key); return sprite; } @@ -89,6 +92,30 @@ class FunkinSprite extends FlxSprite return this; } + /** + * Apply an OpenFL `BitmapData` to this sprite. + * @param input The OpenFL `BitmapData` to apply + * @return This sprite, for chaining + */ + public function loadBitmapData(input:BitmapData):FunkinSprite + { + loadGraphic(input); + + return this; + } + + /** + * Apply an OpenFL `TextureBase` to this sprite. + * @param input The OpenFL `TextureBase` to apply + * @return This sprite, for chaining + */ + public function loadTextureBase(input:TextureBase):FunkinSprite + { + var inputBitmap:FixedBitmapData = FixedBitmapData.fromTexture(input); + + return loadBitmapData(inputBitmap); + } + /** * Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture. * @param key The key of the texture to load. @@ -119,11 +146,20 @@ class FunkinSprite extends FlxSprite return this; } + /** + * Determine whether the texture with the given key is cached. + * @param key The key of the texture to check. + * @return Whether the texture is cached. + */ public static function isTextureCached(key:String):Bool { return FlxG.bitmap.get(key) != null; } + /** + * Ensure the texture with the given key is cached. + * @param key The key of the texture to cache. + */ public static function cacheTexture(key:String):Void { // We don't want to cache the same texture twice. @@ -139,7 +175,7 @@ class FunkinSprite extends FlxSprite } // Else, texture is currently uncached. - var graphic = flixel.graphics.FlxGraphic.fromAssetKey(key, false, null, true); + var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true); if (graphic == null) { FlxG.log.warn('Failed to cache graphic: $key'); diff --git a/source/funkin/graphics/framebuffer/FixedBitmapData.hx b/source/funkin/graphics/framebuffer/FixedBitmapData.hx index 00b39ce1c..4ffcbb867 100644 --- a/source/funkin/graphics/framebuffer/FixedBitmapData.hx +++ b/source/funkin/graphics/framebuffer/FixedBitmapData.hx @@ -32,11 +32,11 @@ class FixedBitmapData extends BitmapData public static function fromTexture(texture:TextureBase):FixedBitmapData { if (texture == null) return null; - final bitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0); - bitmapData.readable = false; + final bitmapData:FixedBitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0); + // bitmapData.readable = false; bitmapData.__texture = texture; bitmapData.__textureContext = texture.__textureContext; - bitmapData.image = null; + // bitmapData.image = null; return bitmapData; } } diff --git a/source/funkin/graphics/video/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx index 5e178efb3..a0fab9c09 100644 --- a/source/funkin/graphics/video/FlxVideo.hx +++ b/source/funkin/graphics/video/FlxVideo.hx @@ -1,45 +1,58 @@ package funkin.graphics.video; -import flixel.FlxBasic; -import flixel.FlxSprite; +import flixel.util.FlxColor; +import flixel.util.FlxSignal.FlxTypedSignal; +import funkin.audio.FunkinSound; +import openfl.display3D.textures.TextureBase; import openfl.events.NetStatusEvent; +import openfl.media.SoundTransform; import openfl.media.Video; import openfl.net.NetConnection; import openfl.net.NetStream; /** * Plays a video via a NetStream. Only works on HTML5. - * This does NOT replace hxCodec, nor does hxCodec replace this. hxCodec only works on desktop and does not work on HTML5! + * This does NOT replace hxCodec, nor does hxCodec replace this. + * hxCodec only works on desktop and does not work on HTML5! */ -class FlxVideo extends FlxBasic +class FlxVideo extends FunkinSprite { var video:Video; var netStream:NetStream; - - public var finishCallback:Void->Void; + var videoPath:String; /** - * Doesn't actually interact with Flixel shit, only just a pleasant to use class + * A callback to execute when the video finishes. */ + public var finishCallback:Void->Void; + public function new(videoPath:String) { super(); + this.videoPath = videoPath; + + makeGraphic(2, 2, FlxColor.TRANSPARENT); + video = new Video(); video.x = 0; video.y = 0; + video.alpha = 0; - FlxG.addChildBelowMouse(video); + FlxG.game.addChild(video); - var netConnection = new NetConnection(); + var netConnection:NetConnection = new NetConnection(); netConnection.connect(null); netStream = new NetStream(netConnection); - netStream.client = {onMetaData: client_onMetaData}; - netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus); + netStream.client = {onMetaData: onClientMetaData}; + netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetConnectionNetStatus); netStream.play(videoPath); } + /** + * Tell the FlxVideo to pause playback. + */ public function pauseVideo():Void { if (netStream != null) @@ -48,6 +61,9 @@ class FlxVideo extends FlxBasic } } + /** + * Tell the FlxVideo to resume if it is paused. + */ public function resumeVideo():Void { // Resume playing the video. @@ -57,6 +73,29 @@ class FlxVideo extends FlxBasic } } + var videoAvailable:Bool = false; + var frameTimer:Float; + + static final FRAME_RATE:Float = 60; + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (frameTimer >= (1 / FRAME_RATE)) + { + frameTimer = 0; + // TODO: We just draw the video buffer to the sprite 60 times a second. + // Can we copy the video buffer instead somehow? + pixels.draw(video); + } + + if (videoAvailable) frameTimer += elapsed; + } + + /** + * Tell the FlxVideo to seek to the beginning. + */ public function restartVideo():Void { // Seek to the beginning of the video. @@ -66,6 +105,9 @@ class FlxVideo extends FlxBasic } } + /** + * Tell the FlxVideo to end. + */ public function finishVideo():Void { netStream.dispose(); @@ -74,15 +116,48 @@ class FlxVideo extends FlxBasic if (finishCallback != null) finishCallback(); } - public function client_onMetaData(metaData:Dynamic) + public override function destroy():Void + { + if (netStream != null) + { + netStream.dispose(); + + if (FlxG.game.contains(video)) FlxG.game.removeChild(video); + } + + super.destroy(); + } + + /** + * Callback executed when the video stream loads. + * @param metaData The metadata of the video + */ + public function onClientMetaData(metaData:Dynamic):Void { video.attachNetStream(netStream); - video.width = FlxG.width; - video.height = FlxG.height; + onVideoReady(); } - function netConnection_onNetStatus(event:NetStatusEvent):Void + function onVideoReady():Void + { + video.width = FlxG.width; + video.height = FlxG.height; + + videoAvailable = true; + + FunkinSound.onVolumeChanged.add(onVolumeChanged); + onVolumeChanged(FlxG.sound.muted ? 0 : FlxG.sound.volume); + + makeGraphic(Std.int(video.width), Std.int(video.height), FlxColor.TRANSPARENT); + } + + function onVolumeChanged(volume:Float):Void + { + netStream.soundTransform = new SoundTransform(volume); + } + + function onNetConnectionNetStatus(event:NetStatusEvent):Void { if (event.info.code == 'NetStream.Play.Complete') finishVideo(); } From 50b17b3dcaba13894a827f75e2bc79493b6cd4ea Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 23:25:22 -0400 Subject: [PATCH 154/172] 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 566a271a634f29e50a91872a37974944be73df3a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Mar 2024 23:25:56 -0400 Subject: [PATCH 155/172] Fix several crashes when reaching a game over in minimal mode --- source/funkin/play/GameOverSubState.hx | 129 +++++++++++++++---------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 95304d762..70964bff0 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -97,14 +97,14 @@ class GameOverSubState extends MusicBeatSubState /** * Reset the game over configuration to the default. */ - public static function reset() + public static function reset():Void { - animationSuffix = ""; - musicSuffix = ""; - blueBallSuffix = ""; + animationSuffix = ''; + musicSuffix = ''; + blueBallSuffix = ''; } - override public function create() + override public function create():Void { if (instance != null) { @@ -130,23 +130,27 @@ 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.isDead = true; - add(boyfriend); - boyfriend.resetCharacter(); + if (PlayState.instance.isMinimalMode) {} + else + { + boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend.isDead = true; + add(boyfriend); + boyfriend.resetCharacter(); - // Assign a camera follow point to the boyfriend's position. - cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); - cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; - cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; - var offsets:Array = boyfriend.getDeathCameraOffsets(); - cameraFollowPoint.x += offsets[0]; - cameraFollowPoint.y += offsets[1]; - add(cameraFollowPoint); + // Assign a camera follow point to the boyfriend's position. + cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); + cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; + cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; + var offsets:Array = boyfriend.getDeathCameraOffsets(); + cameraFollowPoint.x += offsets[0]; + cameraFollowPoint.y += offsets[1]; + add(cameraFollowPoint); - FlxG.camera.target = null; - FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); - targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom(); + FlxG.camera.target = null; + FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); + targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom(); + } // // Set up the audio @@ -164,21 +168,29 @@ class GameOverSubState extends MusicBeatSubState var hasStartedAnimation:Bool = false; - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (!hasStartedAnimation) { hasStartedAnimation = true; - if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100)) + if (PlayState.instance.isMinimalMode) { - boyfriend.playAnimation('fakeoutDeath', true, false); + // Play the "blue balled" sound. May play a variant if one has been assigned. + playBlueBalledSFX(); } else { - boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over! - // Play the "blue balled" sound. May play a variant if one has been assigned. - playBlueBalledSFX(); + if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100)) + { + boyfriend.playAnimation('fakeoutDeath', true, false); + } + else + { + boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over! + // Play the "blue balled" sound. May play a variant if one has been assigned. + playBlueBalledSFX(); + } } } @@ -241,27 +253,34 @@ class GameOverSubState extends MusicBeatSubState } else { - // Music hasn't started yet. - switch (PlayStatePlaylist.campaignId) + if (PlayState.instance.isMinimalMode) { - // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. - // This will simplify the class and make it easier for mods to add death quotes. - case "week7": - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) - { - playingJeffQuote = true; - playJeffQuote(); - // Start music at lower volume - startDeathMusic(0.2, false); - boyfriend.playAnimation('deathLoop' + animationSuffix); - } - default: - // Start music at normal volume once the initial death animation finishes. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) - { - startDeathMusic(1.0, false); - boyfriend.playAnimation('deathLoop' + animationSuffix); - } + // startDeathMusic(1.0, false); + } + else + { + // Music hasn't started yet. + switch (PlayStatePlaylist.campaignId) + { + // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. + // This will simplify the class and make it easier for mods to add death quotes. + case 'week7': + if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) + { + playingJeffQuote = true; + playJeffQuote(); + // Start music at lower volume + startDeathMusic(0.2, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); + } + default: + // Start music at normal volume once the initial death animation finishes. + if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) + { + startDeathMusic(1.0, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); + } + } } } @@ -279,7 +298,11 @@ class GameOverSubState extends MusicBeatSubState isEnding = true; startDeathMusic(1.0, true); // isEnding changes this function's behavior. - boyfriend.playAnimation('deathConfirm' + animationSuffix, true); + if (PlayState.instance.isMinimalMode) {} + else + { + boyfriend.playAnimation('deathConfirm' + animationSuffix, true); + } // After the animation finishes... new FlxTimer().start(0.7, function(tmr:FlxTimer) { @@ -289,10 +312,14 @@ class GameOverSubState extends MusicBeatSubState FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); PlayState.instance.needsReset = true; - // Readd Boyfriend to the stage. - boyfriend.isDead = false; - remove(boyfriend); - PlayState.instance.currentStage.addCharacter(boyfriend, BF); + if (PlayState.instance.isMinimalMode) {} + else + { + // Readd Boyfriend to the stage. + boyfriend.isDead = false; + remove(boyfriend); + PlayState.instance.currentStage.addCharacter(boyfriend, BF); + } // Snap reset the camera which may have changed because of the player character data. resetCameraZoom(); From 36a9c29720f5a9c76316d2ab24461b8f54063aa7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 20 Mar 2024 14:37:24 -0400 Subject: [PATCH 156/172] 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 a93aa6c68d5ee30c41fce642a9e4f3ad1a38acbf Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 20 Mar 2024 15:40:33 -0700 Subject: [PATCH 157/172] submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b272232aa..86927859c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b272232aaf5a748db4b0dd5ac0cc98b72d4a636c +Subproject commit 86927859c8e73a9a0b44738a4b50ec97f38444e0 From 3975d34b70f53e8ff72d714f131184b0547ce004 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 20 Mar 2024 23:06:32 -0400 Subject: [PATCH 158/172] 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 159/172] 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 160/172] 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 161/172] 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 20:04:10 -0400 Subject: [PATCH 162/172] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 86927859c..4ce901125 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 86927859c8e73a9a0b44738a4b50ec97f38444e0 +Subproject commit 4ce90112505dd69a9943d7912944208f752c5206 From d96ea5e89520ac90ea5553b9bfa729b2c814b068 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 21 Mar 2024 18:51:18 -0700 Subject: [PATCH 163/172] 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 164/172] 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 165/172] 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 166/172] 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 167/172] 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 From 40d13619d363a7e47a23ff45adb6699b633b0629 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 22 Mar 2024 00:12:50 -0700 Subject: [PATCH 168/172] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 4ce901125..7cbe6ff4e 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4ce90112505dd69a9943d7912944208f752c5206 +Subproject commit 7cbe6ff4ed7d976e7c69d6677c4aa84988da0e8d From c9f4a0a86d92f2e30ef625bbd9fcc13107d1bd0f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 22 Mar 2024 15:29:58 -0400 Subject: [PATCH 169/172] Update polymod to fix several script-related crashes --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index a75dee432..0dfe88ded 100644 --- a/hmm.json +++ b/hmm.json @@ -146,7 +146,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "5547763a22858a1f10939e082de421d587c862bf", + "ref": "8553b800965f225bb14c7ab8f04bfa9cdec362ac", "url": "https://github.com/larsiusprime/polymod" }, { From 7d008e724765ae6e7f950fd6077cbc4e8aa4bbe7 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 22 Mar 2024 15:03:17 -0700 Subject: [PATCH 170/172] playstate camera fixie --- source/funkin/play/PlayState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a5152e727..759ba4742 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1550,6 +1550,7 @@ class PlayState extends MusicBeatSubState public function resetCameraZoom():Void { + if (PlayState.instance.isMinimalMode) return; // Apply camera zoom level from stage data. defaultCameraZoom = currentStage.camZoom; currentCameraZoom = defaultCameraZoom; From 83e8865a24611d515265d1159360dd7171420f2a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 22 Mar 2024 20:29:35 -0400 Subject: [PATCH 171/172] Make the ZoomCamera events use sensible defaults. --- .../funkin/play/event/ZoomCameraSongEvent.hx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index d1741a463..b913aebe7 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -52,6 +52,11 @@ class ZoomCameraSongEvent extends SongEvent super('ZoomCamera'); } + static final DEFAULT_ZOOM:Float = 1.0; + static final DEFAULT_DURATION:Float = 4.0; + static final DEFAULT_MODE:String = 'direct'; + static final DEFAULT_EASE:String = 'linear'; + public override function handleEvent(data:SongEventData):Void { // Does nothing if there is no PlayState camera or stage. @@ -60,25 +65,20 @@ class ZoomCameraSongEvent extends SongEvent // Does nothing if we are minimal mode. if (PlayState.instance.isMinimalMode) return; - var zoom:Null = data.getFloat('zoom'); - if (zoom == null) zoom = 1.0; + var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM; - var duration:Null = data.getFloat('duration'); - if (duration == null) duration = 4.0; + var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION; - var mode:Null = data.getString('mode'); - if (mode == null) mode = 'additive'; + var mode:String = data.getString('mode') ?? DEFAULT_MODE; + var isDirectMode:Bool = mode == 'direct'; - var ease:Null = data.getString('ease'); - if (ease == null) ease = 'linear'; - - var directMode:Bool = mode == 'direct'; + var ease:String = data.getString('ease') ?? DEFAULT_EASE; // If it's a string, check the value. switch (ease) { case 'INSTANT': - PlayState.instance.tweenCameraZoom(zoom, 0, directMode); + PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode); default: var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; @@ -89,7 +89,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - PlayState.instance.tweenCameraZoom(zoom, durSeconds, directMode, easeFunction); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, isDirectMode, easeFunction); } } @@ -130,7 +130,7 @@ class ZoomCameraSongEvent extends SongEvent { name: 'mode', title: 'Mode', - defaultValue: 'additive', + defaultValue: 'direct', type: SongEventFieldType.ENUM, keys: ['Additive' => 'additive', 'Direct' => 'direct'] }, From 01e83d4713144f5bbe607879861b48cd2d404f4c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 22 Mar 2024 20:21:09 -0700 Subject: [PATCH 172/172] the funkin crew presents --- source/funkin/ui/title/TitleState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 9dca759be..5cc7b8cc1 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -453,9 +453,9 @@ class TitleState extends MusicBeatState switch (i + 1) { case 1: - createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8r']); + createCoolText(['The', 'Funkin Crew Inc']); case 3: - addMoreText('present'); + addMoreText('presents'); case 4: deleteCoolText(); case 5: