From 9209bac02c1f89431876dcda56eb068c6cae381c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 14 Feb 2024 03:27:34 -0500 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 11/27] 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 44623071cd62821a8d2708b1664a7b4ba84c1015 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 18 Feb 2024 03:02:36 -0500 Subject: [PATCH 12/27] 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 db428a3e3637ff3a633ffcf31c056c4053b299b0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 19 Feb 2024 23:09:23 -0500 Subject: [PATCH 13/27] 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 d888fb860d91d544a5d21864b9ddfccec576c8d0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 20 Feb 2024 13:37:53 -0500 Subject: [PATCH 14/27] 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 15/27] 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 539b688055a75aa706c631c8c0fc13ec6cf782b6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 18:55:24 -0500 Subject: [PATCH 16/27] 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 01ed1730f4338ea99ad6c472689780867a91cff1 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 22 Feb 2024 20:56:41 -0500 Subject: [PATCH 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 90360de0d01c72b552a87096fdb0549ebc76d443 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 26 Feb 2024 19:03:04 -0500 Subject: [PATCH 25/27] 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 26/27] 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 ddeac1db15858037eed160eaa32427bd20fa29fb Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 22:02:04 -0500 Subject: [PATCH 27/27] 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