From e2b0ed98dd0a48435d222a8442ff6364e7c057db Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 13 Dec 2023 23:11:19 -0500 Subject: [PATCH 001/129] Fix a bug where making one selection and then making a different one would leave the original highlighted --- .../ui/debug/charting/ChartEditorState.hx | 21 +++++--- .../components/ChartEditorNotePreview.hx | 17 +++++- source/funkin/util/tools/ArrayTools.hx | 52 +++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78de08fdf..b24bc1d95 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -753,15 +753,23 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function set_currentNoteSelection(value:Array):Array { + // This value is true if all elements of the current selection are also in the new selection. + var isSuperset:Bool = currentNoteSelection.isSubset(value); + var isEqual:Bool = currentNoteSelection.isEqualUnordered(value); + currentNoteSelection = value; - if (currentNoteSelection.length > 0) + if (!isEqual) { - notePreview.addNotes(currentNoteSelection, Std.int(songLengthInMs), true); - } - else - { - notePreviewDirty = true; + if (currentNoteSelection.length > 0 && isSuperset) + { + notePreview.addSelectedNotes(currentNoteSelection, Std.int(songLengthInMs)); + } + else + { + // The new selection removes elements from the old selection, so we have to redraw the note preview. + notePreviewDirty = true; + } } return currentNoteSelection; @@ -5295,6 +5303,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // TODO: Only update the notes that have changed. notePreview.erase(); notePreview.addNotes(currentSongChartNoteData, Std.int(songLengthInMs)); + notePreview.addSelectedNotes(currentNoteSelection, Std.int(songLengthInMs)); notePreview.addEvents(currentSongChartEventData, Std.int(songLengthInMs)); } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx index 09c99531d..598cbb544 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx @@ -80,11 +80,24 @@ class ChartEditorNotePreview extends FlxSprite * @param notes The data for the notes. * @param songLengthInMs The total length of the song in milliseconds. */ - public function addNotes(notes:Array, songLengthInMs:Int, ?isSelection:Bool = false):Void + public function addNotes(notes:Array, songLengthInMs:Int):Void { for (note in notes) { - addNote(note, songLengthInMs, isSelection); + addNote(note, songLengthInMs, false); + } + } + + /** + * Add an array of selected notes to the preview. + * @param notes The data for the notes. + * @param songLengthInMs The total length of the song in milliseconds. + */ + public function addSelectedNotes(notes:Array, songLengthInMs:Int):Void + { + for (note in notes) + { + addNote(note, songLengthInMs, true); } } diff --git a/source/funkin/util/tools/ArrayTools.hx b/source/funkin/util/tools/ArrayTools.hx index a88f8a861..925bb67a5 100644 --- a/source/funkin/util/tools/ArrayTools.hx +++ b/source/funkin/util/tools/ArrayTools.hx @@ -76,4 +76,56 @@ class ArrayTools while (array.length > 0) array.pop(); } + + /** + * Return true only if both arrays contain the same elements (possibly in a different order). + * @param a The first array to compare. + * @param b The second array to compare. + * @return Weather both arrays contain the same elements. + */ + public static function isEqualUnordered(a:Array, b:Array):Bool + { + if (a.length != b.length) return false; + for (element in a) + { + if (!b.contains(element)) return false; + } + for (element in b) + { + if (!a.contains(element)) return false; + } + return true; + } + + /** + * Returns true if `superset` contains all elements of `subset`. + * @param superset The array to query for each element. + * @param subset The array containing the elements to query for. + * @return Weather `superset` contains all elements of `subset`. + */ + public static function isSuperset(superset:Array, subset:Array):Bool + { + // Shortcuts. + if (subset.length == 0) return true; + if (subset.length > superset.length) return false; + + // Check each element. + for (element in subset) + { + if (!superset.contains(element)) return false; + } + return true; + } + + /** + * Returns true if `superset` contains all elements of `subset`. + * @param subset The array containing the elements to query for. + * @param superset The array to query for each element. + * @return Weather `superset` contains all elements of `subset`. + */ + public static function isSubset(subset:Array, superset:Array):Bool + { + // Switch it around. + return isSuperset(superset, subset); + } } From b3236e6134d5451fd8e269c1abb1fbf23368d74a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 14 Dec 2023 00:47:04 -0500 Subject: [PATCH 002/129] Fix a bug where modifying a copied template song's BPM in the chart editor would modify BPM in Freeplay. --- source/funkin/data/song/SongData.hx | 106 ++++++++++--- .../ui/debug/charting/ChartEditorState.hx | 140 ++++++++++-------- .../commands/ChangeStartingBPMCommand.hx | 10 ++ .../charting/commands/MoveItemsCommand.hx | 4 +- .../ChartEditorImportExportHandler.hx | 3 +- source/funkin/util/tools/ArrayTools.hx | 68 +++++++++ source/funkin/util/tools/ICloneable.hx | 10 ++ source/funkin/util/tools/MapTools.hx | 27 ++++ 8 files changed, 277 insertions(+), 91 deletions(-) create mode 100644 source/funkin/util/tools/ICloneable.hx diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 7886ada4f..fe4e66032 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -2,6 +2,7 @@ package funkin.data.song; import funkin.data.song.SongRegistry; import thx.semver.Version; +import funkin.util.tools.ICloneable; /** * Data containing information about a song. @@ -9,7 +10,7 @@ import thx.semver.Version; * Data which is only necessary in-game should be stored in the SongChartData. */ @:nullSafety -class SongMetadata +class SongMetadata implements ICloneable { /** * A semantic versioning string for the song data format. @@ -84,16 +85,16 @@ class SongMetadata * @param newVariation Set to a new variation ID to change the new metadata. * @return The cloned SongMetadata */ - public function clone(?newVariation:String = null):SongMetadata + public function clone():SongMetadata { - var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation); + var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.variation); result.version = this.version; result.timeFormat = this.timeFormat; result.divisions = this.divisions; - result.offsets = this.offsets; - result.timeChanges = this.timeChanges; + result.offsets = this.offsets.clone(); + result.timeChanges = this.timeChanges.deepClone(); result.looped = this.looped; - result.playData = this.playData; + result.playData = this.playData.clone(); result.generatedBy = this.generatedBy; return result; @@ -128,7 +129,7 @@ enum abstract SongTimeFormat(String) from String to String var MILLISECONDS = 'ms'; } -class SongTimeChange +class SongTimeChange implements ICloneable { public static final DEFAULT_SONGTIMECHANGE:SongTimeChange = new SongTimeChange(0, 100); @@ -195,6 +196,11 @@ class SongTimeChange this.beatTuplets = beatTuplets == null ? DEFAULT_BEAT_TUPLETS : beatTuplets; } + public function clone():SongTimeChange + { + return new SongTimeChange(this.timeStamp, this.bpm, this.timeSignatureNum, this.timeSignatureDen, this.beatTime, this.beatTuplets); + } + /** * Produces a string representation suitable for debugging. */ @@ -209,7 +215,7 @@ class SongTimeChange * These are intended to correct for issues with the chart, or with the song's audio (for example a 10ms delay before the song starts). * This is independent of the offsets applied in the user's settings, which are applied after these offsets and intended to correct for the user's hardware. */ -class SongOffsets +class SongOffsets implements ICloneable { /** * The offset, in milliseconds, to apply to the song's instrumental relative to the chart. @@ -279,6 +285,15 @@ class SongOffsets return value; } + public function clone():SongOffsets + { + var result:SongOffsets = new SongOffsets(this.instrumental); + result.altInstrumentals = this.altInstrumentals.clone(); + result.vocals = this.vocals.clone(); + + return result; + } + /** * Produces a string representation suitable for debugging. */ @@ -292,7 +307,7 @@ class SongOffsets * Metadata for a song only used for the music. * For example, the menu music. */ -class SongMusicData +class SongMusicData implements ICloneable { /** * A semantic versioning string for the song data format. @@ -346,13 +361,13 @@ class SongMusicData this.variation = variation == null ? Constants.DEFAULT_VARIATION : variation; } - public function clone(?newVariation:String = null):SongMusicData + public function clone():SongMusicData { - var result:SongMusicData = new SongMusicData(this.songName, this.artist, newVariation == null ? this.variation : newVariation); + var result:SongMusicData = new SongMusicData(this.songName, this.artist, this.variation); result.version = this.version; result.timeFormat = this.timeFormat; result.divisions = this.divisions; - result.timeChanges = this.timeChanges; + result.timeChanges = this.timeChanges.clone(); result.looped = this.looped; result.generatedBy = this.generatedBy; @@ -368,7 +383,7 @@ class SongMusicData } } -class SongPlayData +class SongPlayData implements ICloneable { /** * The variations this song has. The associated metadata files should exist. @@ -417,6 +432,20 @@ class SongPlayData ratings = new Map(); } + public function clone():SongPlayData + { + var result:SongPlayData = new SongPlayData(); + result.songVariations = this.songVariations.clone(); + result.difficulties = this.difficulties.clone(); + result.characters = this.characters.clone(); + result.stage = this.stage; + result.noteStyle = this.noteStyle; + result.ratings = this.ratings.clone(); + result.album = this.album; + + return result; + } + /** * Produces a string representation suitable for debugging. */ @@ -430,7 +459,7 @@ class SongPlayData * Information about the characters used in this variation of the song. * Create a new variation if you want to change the characters. */ -class SongCharacterData +class SongCharacterData implements ICloneable { @:optional @:default('') @@ -460,6 +489,14 @@ class SongCharacterData this.instrumental = instrumental; } + public function clone():SongCharacterData + { + var result:SongCharacterData = new SongCharacterData(this.player, this.girlfriend, this.opponent, this.instrumental); + result.altInstrumentals = this.altInstrumentals.clone(); + + return result; + } + /** * Produces a string representation suitable for debugging. */ @@ -469,7 +506,7 @@ class SongCharacterData } } -class SongChartData +class SongChartData implements ICloneable { @:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION) @:jcustomparse(funkin.data.DataParse.semverVersion) @@ -539,6 +576,24 @@ class SongChartData return writer.write(this, pretty ? ' ' : null); } + public function clone():SongChartData + { + // We have to manually perform the deep clone here because Map.deepClone() doesn't work. + var noteDataClone:Map> = new Map>(); + for (key in this.notes.keys()) + { + noteDataClone.set(key, this.notes.get(key).deepClone()); + } + var eventDataClone:Array = this.events.deepClone(); + + var result:SongChartData = new SongChartData(this.scrollSpeed.clone(), eventDataClone, noteDataClone); + result.version = this.version; + result.generatedBy = this.generatedBy; + result.variation = this.variation; + + return result; + } + /** * Produces a string representation suitable for debugging. */ @@ -548,7 +603,7 @@ class SongChartData } } -class SongEventDataRaw +class SongEventDataRaw implements ICloneable { /** * The timestamp of the event. The timestamp is in the format of the song's time format. @@ -604,12 +659,17 @@ class SongEventDataRaw return _stepTime = Conductor.getTimeInSteps(this.time); } + + public function clone():SongEventDataRaw + { + return new SongEventDataRaw(this.time, this.event, this.value); + } } /** * Wrap SongEventData in an abstract so we can overload operators. */ -@:forward(time, event, value, activated, getStepTime) +@:forward(time, event, value, activated, getStepTime, clone) abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw { public function new(time:Float, event:String, value:Dynamic = null) @@ -662,11 +722,6 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR return this.value == null ? null : cast Reflect.field(this.value, key); } - public function clone():SongEventData - { - return new SongEventData(this.time, this.event, this.value); - } - @:op(A == B) public function op_equals(other:SongEventData):Bool { @@ -712,7 +767,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR } } -class SongNoteDataRaw +class SongNoteDataRaw implements ICloneable { /** * The timestamp of the note. The timestamp is in the format of the song's time format. @@ -828,6 +883,11 @@ class SongNoteDataRaw } _stepLength = null; } + + public function clone():SongNoteDataRaw + { + return new SongNoteDataRaw(this.time, this.data, this.length, this.kind); + } } /** diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78de08fdf..c120bb9e4 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -860,6 +860,70 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return Save.get().chartEditorHasBackup = value; } + /** + * A list of previous working file paths. + * Also known as the "recent files" list. + * The first element is [null] if the current working file has not been saved anywhere yet. + */ + public var previousWorkingFilePaths(default, set):Array> = [null]; + + function set_previousWorkingFilePaths(value:Array>):Array> + { + // Called only when the WHOLE LIST is overridden. + previousWorkingFilePaths = value; + applyWindowTitle(); + populateOpenRecentMenu(); + applyCanQuickSave(); + return value; + } + + /** + * The current file path which the chart editor is working with. + * If `null`, the current chart has not been saved yet. + */ + public var currentWorkingFilePath(get, set):Null; + + function get_currentWorkingFilePath():Null + { + return previousWorkingFilePaths[0]; + } + + function set_currentWorkingFilePath(value:Null):Null + { + if (value == previousWorkingFilePaths[0]) return value; + + if (previousWorkingFilePaths.contains(null)) + { + // Filter all instances of `null` from the array. + previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null):Bool { + return x != null; + }); + } + + if (previousWorkingFilePaths.contains(value)) + { + // Move the path to the front of the list. + previousWorkingFilePaths.remove(value); + previousWorkingFilePaths.unshift(value); + } + else + { + // Add the path to the front of the list. + previousWorkingFilePaths.unshift(value); + } + + while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES) + { + // Remove the last path in the list. + previousWorkingFilePaths.pop(); + } + + populateOpenRecentMenu(); + applyWindowTitle(); + + return value; + } + /** * Whether the difficulty tree view in the toolbox has been modified and needs to be updated. * This happens when we add/remove difficulties. @@ -889,6 +953,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var commandHistoryDirty:Bool = true; + /** + * If true, we are currently in the process of quitting the chart editor. + * Skip any update functions as most of them will call a crash. + */ + var criticalFailure:Bool = false; + // Input /** @@ -1717,70 +1787,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var params:Null; - /** - * A list of previous working file paths. - * Also known as the "recent files" list. - * The first element is [null] if the current working file has not been saved anywhere yet. - */ - public var previousWorkingFilePaths(default, set):Array> = [null]; - - function set_previousWorkingFilePaths(value:Array>):Array> - { - // Called only when the WHOLE LIST is overridden. - previousWorkingFilePaths = value; - applyWindowTitle(); - populateOpenRecentMenu(); - applyCanQuickSave(); - return value; - } - - /** - * The current file path which the chart editor is working with. - * If `null`, the current chart has not been saved yet. - */ - public var currentWorkingFilePath(get, set):Null; - - function get_currentWorkingFilePath():Null - { - return previousWorkingFilePaths[0]; - } - - function set_currentWorkingFilePath(value:Null):Null - { - if (value == previousWorkingFilePaths[0]) return value; - - if (previousWorkingFilePaths.contains(null)) - { - // Filter all instances of `null` from the array. - previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null):Bool { - return x != null; - }); - } - - if (previousWorkingFilePaths.contains(value)) - { - // Move the path to the front of the list. - previousWorkingFilePaths.remove(value); - previousWorkingFilePaths.unshift(value); - } - else - { - // Add the path to the front of the list. - previousWorkingFilePaths.unshift(value); - } - - while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES) - { - // Remove the last path in the list. - previousWorkingFilePaths.pop(); - } - - populateOpenRecentMenu(); - applyWindowTitle(); - - return value; - } - public function new(?params:ChartEditorParams) { super(); @@ -2732,7 +2738,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public override function update(elapsed:Float):Void { // Override F4 behavior to include the autosave. - if (FlxG.keys.justPressed.F4) + if (FlxG.keys.justPressed.F4 && !criticalFailure) { quitChartEditor(); return; @@ -2741,6 +2747,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // dispatchEvent gets called here. super.update(elapsed); + if (criticalFailure) return; + // These ones happen even if the modal dialog is open. handleMusicPlayback(elapsed); handleNoteDisplay(); @@ -4516,6 +4524,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState FlxG.switchState(new MainMenuState()); resetWindowTitle(); + + criticalFailure = true; } /** diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx index 3c45c1168..d80dd7c38 100644 --- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx @@ -34,6 +34,11 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.currentSongMetadata.timeChanges = timeChanges; + state.noteDisplayDirty = true; + state.notePreviewDirty = true; + state.notePreviewViewportBoundsDirty = true; + state.scrollPositionInPixels = 0; + Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); } @@ -51,6 +56,11 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.currentSongMetadata.timeChanges = timeChanges; + state.noteDisplayDirty = true; + state.notePreviewDirty = true; + state.notePreviewViewportBoundsDirty = true; + state.scrollPositionInPixels = 0; + Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); } diff --git a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx index 2eedbbf03..53090a6cc 100644 --- a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx @@ -21,8 +21,8 @@ class MoveItemsCommand implements ChartEditorCommand public function new(notes:Array, events:Array, offset:Float, columns:Int) { // Clone the notes to prevent editing from affecting the history. - this.notes = [for (note in notes) note.clone()]; - this.events = [for (event in events) event.clone()]; + this.notes = notes.clone(); + this.events = events.clone(); this.offset = offset; this.columns = columns; this.movedNotes = []; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx index 0c8d6a205..d8c893d4d 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx @@ -43,7 +43,8 @@ class ChartEditorImportExportHandler var variation = (metadata.variation == null || metadata.variation == '') ? Constants.DEFAULT_VARIATION : metadata.variation; // Clone to prevent modifying the original. - var metadataClone:SongMetadata = metadata.clone(variation); + var metadataClone:SongMetadata = metadata.clone(); + metadataClone.variation = variation; if (metadataClone != null) songMetadata.set(variation, metadataClone); var chartData:Null = SongRegistry.instance.parseEntryChartData(songId, metadata.variation); diff --git a/source/funkin/util/tools/ArrayTools.hx b/source/funkin/util/tools/ArrayTools.hx index a88f8a861..0209cfc19 100644 --- a/source/funkin/util/tools/ArrayTools.hx +++ b/source/funkin/util/tools/ArrayTools.hx @@ -76,4 +76,72 @@ class ArrayTools while (array.length > 0) array.pop(); } + + /** + * Create a new array with all elements of the given array, to prevent modifying the original. + */ + public static function clone(array:Array):Array + { + return [for (element in array) element]; + } + + /** + * Create a new array with clones of all elements of the given array, to prevent modifying the original. + */ + public static function deepClone>(array:Array):Array + { + return [for (element in array) element.clone()]; + } + + /** + * Return true only if both arrays contain the same elements (possibly in a different order). + * @param a The first array to compare. + * @param b The second array to compare. + * @return Weather both arrays contain the same elements. + */ + public static function isEqualUnordered(a:Array, b:Array):Bool + { + if (a.length != b.length) return false; + for (element in a) + { + if (!b.contains(element)) return false; + } + for (element in b) + { + if (!a.contains(element)) return false; + } + return true; + } + + /** + * Returns true if `superset` contains all elements of `subset`. + * @param superset The array to query for each element. + * @param subset The array containing the elements to query for. + * @return Weather `superset` contains all elements of `subset`. + */ + public static function isSuperset(superset:Array, subset:Array):Bool + { + // Shortcuts. + if (subset.length == 0) return true; + if (subset.length > superset.length) return false; + + // Check each element. + for (element in subset) + { + if (!superset.contains(element)) return false; + } + return true; + } + + /** + * Returns true if `superset` contains all elements of `subset`. + * @param subset The array containing the elements to query for. + * @param superset The array to query for each element. + * @return Weather `superset` contains all elements of `subset`. + */ + public static function isSubset(subset:Array, superset:Array):Bool + { + // Switch it around. + return isSuperset(superset, subset); + } } diff --git a/source/funkin/util/tools/ICloneable.hx b/source/funkin/util/tools/ICloneable.hx new file mode 100644 index 000000000..33f19f167 --- /dev/null +++ b/source/funkin/util/tools/ICloneable.hx @@ -0,0 +1,10 @@ +package funkin.util.tools; + +/** + * Implement this on a class to enable `Array.deepClone()` to work on it. + * NOTE: T should be the type of the class that implements this interface. + */ +interface ICloneable +{ + public function clone():T; +} diff --git a/source/funkin/util/tools/MapTools.hx b/source/funkin/util/tools/MapTools.hx index 739c5efdb..1399fb791 100644 --- a/source/funkin/util/tools/MapTools.hx +++ b/source/funkin/util/tools/MapTools.hx @@ -25,6 +25,33 @@ class MapTools return [for (i in map.iterator()) i]; } + /** + * Create a new array with all elements of the given array, to prevent modifying the original. + */ + public static function clone(map:Map):Map + { + return map.copy(); + } + + /** + * Create a new array with clones of all elements of the given array, to prevent modifying the original. + */ + public static function deepClone>(map:Map):Map + { + // TODO: This function does NOT work. + throw "Not implemented"; + + /* + var newMap:Map = []; + // Replace each value with a clone of itself. + for (key in newMap.keys()) + { + newMap.set(key, newMap.get(key).clone()); + } + return newMap; + */ + } + /** * Return a list of keys from the map (as an array, rather than an iterator). * TODO: Rename this? From ca489b56094260a81fb360a5798905a5dabfef5f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 14 Dec 2023 16:56:20 -0500 Subject: [PATCH 003/129] Rewrite the Conductor to be a singleton instance. --- source/funkin/Conductor.hx | 214 +++++---- source/funkin/audio/visualize/ABotVis.hx | 2 +- .../audio/visualize/SpectogramSprite.hx | 8 +- source/funkin/data/song/SongData.hx | 8 +- source/funkin/play/Countdown.hx | 8 +- source/funkin/play/GameOverSubState.hx | 4 +- source/funkin/play/PlayState.hx | 98 ++--- source/funkin/play/character/BaseCharacter.hx | 2 +- .../funkin/play/components/ComboMilestone.hx | 2 +- source/funkin/play/components/PopUpStuff.hx | 6 +- .../funkin/play/event/ZoomCameraSongEvent.hx | 3 +- source/funkin/play/notes/Strumline.hx | 16 +- source/funkin/ui/MusicBeatState.hx | 16 +- source/funkin/ui/MusicBeatSubState.hx | 10 +- .../ui/debug/charting/ChartEditorState.hx | 153 ++++--- .../commands/ChangeStartingBPMCommand.hx | 4 +- .../charting/commands/MoveEventsCommand.hx | 2 +- .../charting/commands/MoveItemsCommand.hx | 4 +- .../charting/commands/MoveNotesCommand.hx | 2 +- .../charting/commands/PasteItemsCommand.hx | 4 +- .../handlers/ChartEditorAudioHandler.hx | 4 +- .../handlers/ChartEditorDialogHandler.hx | 4 +- .../ChartEditorImportExportHandler.hx | 34 +- .../handlers/ChartEditorThemeHandler.hx | 8 +- .../toolboxes/ChartEditorMetadataToolbox.hx | 4 +- .../funkin/ui/debug/latency/LatencyState.hx | 58 +-- source/funkin/ui/story/StoryMenuState.hx | 4 +- source/funkin/ui/title/TitleState.hx | 18 +- tests/unit/source/funkin/ConductorTest.hx | 414 +++++++++--------- 29 files changed, 587 insertions(+), 527 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index c531678ad..72706bbc1 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -28,29 +28,53 @@ class Conductor // 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second // 7/8 = 3.5 beats per measure = 14 steps per measure + /** + * The current instance of the Conductor. + * If one doesn't currently exist, a new one will be created. + * + * You can also do stuff like store a reference to the Conductor and pass it around or temporarily replace it, + * or have a second Conductor running at the same time, or other weird stuff like that if you need to. + */ + public static var instance:Conductor = new Conductor(); + + /** + * Signal fired when the current Conductor instance advances to a new measure. + */ + public static var measureHit(default, null):FlxSignal = new FlxSignal(); + + /** + * Signal fired when the current Conductor instance advances to a new beat. + */ + public static var beatHit(default, null):FlxSignal = new FlxSignal(); + + /** + * Signal fired when the current Conductor instance advances to a new step. + */ + public static var stepHit(default, null):FlxSignal = new FlxSignal(); + /** * The list of time changes in the song. * There should be at least one time change (at the beginning of the song) to define the BPM. */ - static var timeChanges:Array = []; + var timeChanges:Array = []; /** * The most recent time change for the current song position. */ - public static var currentTimeChange(default, null):SongTimeChange; + public var currentTimeChange(default, null):SongTimeChange; /** * The current position in the song in milliseconds. - * Update this every frame based on the audio position using `Conductor.update()`. + * Update this every frame based on the audio position using `Conductor.instance.update()`. */ - public static var songPosition(default, null):Float = 0; + public var songPosition(default, null):Float = 0; /** * Beats per minute of the current song at the current time. */ - public static var bpm(get, never):Float; + public var bpm(get, never):Float; - static function get_bpm():Float + function get_bpm():Float { if (bpmOverride != null) return bpmOverride; @@ -62,9 +86,9 @@ class Conductor /** * Beats per minute of the current song at the start time. */ - public static var startingBPM(get, never):Float; + public var startingBPM(get, never):Float; - static function get_startingBPM():Float + function get_startingBPM():Float { if (bpmOverride != null) return bpmOverride; @@ -78,14 +102,14 @@ class Conductor * The current value set by `forceBPM`. * If false, BPM is determined by time changes. */ - static var bpmOverride:Null = null; + var bpmOverride:Null = null; /** * Duration of a measure in milliseconds. Calculated based on bpm. */ - public static var measureLengthMs(get, never):Float; + public var measureLengthMs(get, never):Float; - static function get_measureLengthMs():Float + function get_measureLengthMs():Float { return beatLengthMs * timeSignatureNumerator; } @@ -93,9 +117,9 @@ class Conductor /** * Duration of a beat (quarter note) in milliseconds. Calculated based on bpm. */ - public static var beatLengthMs(get, never):Float; + public var beatLengthMs(get, never):Float; - static function get_beatLengthMs():Float + function get_beatLengthMs():Float { // Tied directly to BPM. return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC); @@ -104,25 +128,25 @@ class Conductor /** * Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm. */ - public static var stepLengthMs(get, never):Float; + public var stepLengthMs(get, never):Float; - static function get_stepLengthMs():Float + function get_stepLengthMs():Float { return beatLengthMs / timeSignatureNumerator; } - public static var timeSignatureNumerator(get, never):Int; + public var timeSignatureNumerator(get, never):Int; - static function get_timeSignatureNumerator():Int + function get_timeSignatureNumerator():Int { if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_NUM; return currentTimeChange.timeSignatureNum; } - public static var timeSignatureDenominator(get, never):Int; + public var timeSignatureDenominator(get, never):Int; - static function get_timeSignatureDenominator():Int + function get_timeSignatureDenominator():Int { if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_DEN; @@ -132,44 +156,44 @@ class Conductor /** * Current position in the song, in measures. */ - public static var currentMeasure(default, null):Int; + public var currentMeasure(default, null):Int; /** * Current position in the song, in beats. */ - public static var currentBeat(default, null):Int; + public var currentBeat(default, null):Int; /** * Current position in the song, in steps. */ - public static var currentStep(default, null):Int; + public var currentStep(default, null):Int; /** * Current position in the song, in measures and fractions of a measure. */ - public static var currentMeasureTime(default, null):Float; + public var currentMeasureTime(default, null):Float; /** * Current position in the song, in beats and fractions of a measure. */ - public static var currentBeatTime(default, null):Float; + public var currentBeatTime(default, null):Float; /** * Current position in the song, in steps and fractions of a step. */ - public static var currentStepTime(default, null):Float; + public var currentStepTime(default, null):Float; /** * An offset tied to the current chart file to compensate for a delay in the instrumental. */ - public static var instrumentalOffset:Float = 0; + public var instrumentalOffset:Float = 0; /** * The instrumental offset, in terms of steps. */ - public static var instrumentalOffsetSteps(get, never):Float; + public var instrumentalOffsetSteps(get, never):Float; - static function get_instrumentalOffsetSteps():Float + function get_instrumentalOffsetSteps():Float { var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator; @@ -179,19 +203,19 @@ class Conductor /** * An offset tied to the file format of the audio file being played. */ - public static var formatOffset:Float = 0; + public var formatOffset:Float = 0; /** * An offset set by the user to compensate for input lag. */ - public static var inputOffset:Float = 0; + public var inputOffset:Float = 0; /** * The number of beats in a measure. May be fractional depending on the time signature. */ - public static var beatsPerMeasure(get, never):Float; + public var beatsPerMeasure(get, never):Float; - static function get_beatsPerMeasure():Float + function get_beatsPerMeasure():Float { // NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure return stepsPerMeasure / Constants.STEPS_PER_BEAT; @@ -201,30 +225,15 @@ class Conductor * The number of steps in a measure. * TODO: I don't think this can be fractional? */ - public static var stepsPerMeasure(get, never):Int; + public var stepsPerMeasure(get, never):Int; - static function get_stepsPerMeasure():Int + function get_stepsPerMeasure():Int { // TODO: Is this always an integer? return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT); } - /** - * Signal fired when the Conductor advances to a new measure. - */ - public static var measureHit(default, null):FlxSignal = new FlxSignal(); - - /** - * Signal fired when the Conductor advances to a new beat. - */ - public static var beatHit(default, null):FlxSignal = new FlxSignal(); - - /** - * Signal fired when the Conductor advances to a new step. - */ - public static var stepHit(default, null):FlxSignal = new FlxSignal(); - - function new() {} + public function new() {} /** * Forcibly defines the current BPM of the song. @@ -235,7 +244,7 @@ class Conductor * WARNING: Avoid this for things like setting the BPM of the title screen music, * you should have a metadata file for it instead. */ - public static function forceBPM(?bpm:Float = null) + public function forceBPM(?bpm:Float = null) { if (bpm != null) { @@ -246,7 +255,7 @@ class Conductor // trace('[CONDUCTOR] Resetting BPM to default'); } - Conductor.bpmOverride = bpm; + this.bpmOverride = bpm; } /** @@ -256,29 +265,29 @@ class Conductor * @param songPosition The current position in the song in milliseconds. * Leave blank to use the FlxG.sound.music position. */ - public static function update(?songPosition:Float) + public function update(?songPos:Float) { - if (songPosition == null) + if (songPos == null) { // Take into account instrumental and file format song offsets. - songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0; + songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0; } - var oldMeasure = currentMeasure; - var oldBeat = currentBeat; - var oldStep = currentStep; + var oldMeasure = this.currentMeasure; + var oldBeat = this.currentBeat; + var oldStep = this.currentStep; // Set the song position we are at (for purposes of calculating note positions, etc). - Conductor.songPosition = songPosition; + this.songPosition = songPos; currentTimeChange = timeChanges[0]; - if (Conductor.songPosition > 0.0) + if (this.songPosition > 0.0) { for (i in 0...timeChanges.length) { - if (songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i]; + if (this.songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i]; - if (songPosition < timeChanges[i].timeStamp) break; + if (this.songPosition < timeChanges[i].timeStamp) break; } } @@ -286,45 +295,49 @@ class Conductor { trace('WARNING: Conductor is broken, timeChanges is empty.'); } - else if (currentTimeChange != null && Conductor.songPosition > 0.0) + else if (currentTimeChange != null && this.songPosition > 0.0) { // roundDecimal prevents representing 8 as 7.9999999 - currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); - currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; - currentMeasureTime = currentStepTime / stepsPerMeasure; - currentStep = Math.floor(currentStepTime); - currentBeat = Math.floor(currentBeatTime); - currentMeasure = Math.floor(currentMeasureTime); + this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); + this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; + this.currentMeasureTime = currentStepTime / stepsPerMeasure; + this.currentStep = Math.floor(currentStepTime); + this.currentBeat = Math.floor(currentBeatTime); + this.currentMeasure = Math.floor(currentMeasureTime); } else { // Assume a constant BPM equal to the forced value. - currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4); - currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; - currentMeasureTime = currentStepTime / stepsPerMeasure; - currentStep = Math.floor(currentStepTime); - currentBeat = Math.floor(currentBeatTime); - currentMeasure = Math.floor(currentMeasureTime); + this.currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4); + this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; + this.currentMeasureTime = currentStepTime / stepsPerMeasure; + this.currentStep = Math.floor(currentStepTime); + this.currentBeat = Math.floor(currentBeatTime); + this.currentMeasure = Math.floor(currentMeasureTime); } - // FlxSignals are really cool. - if (currentStep != oldStep) + // Only fire the signal if we are THE Conductor. + if (this == Conductor.instance) { - stepHit.dispatch(); - } + // FlxSignals are really cool. + if (currentStep != oldStep) + { + Conductor.stepHit.dispatch(); + } - if (currentBeat != oldBeat) - { - beatHit.dispatch(); - } + if (currentBeat != oldBeat) + { + Conductor.beatHit.dispatch(); + } - if (currentMeasure != oldMeasure) - { - measureHit.dispatch(); + if (currentMeasure != oldMeasure) + { + Conductor.measureHit.dispatch(); + } } } - public static function mapTimeChanges(songTimeChanges:Array) + public function mapTimeChanges(songTimeChanges:Array) { timeChanges = []; @@ -368,13 +381,13 @@ class Conductor } // Update currentStepTime - Conductor.update(Conductor.songPosition); + this.update(Conductor.instance.songPosition); } /** * Given a time in milliseconds, return a time in steps. */ - public static function getTimeInSteps(ms:Float):Float + public function getTimeInSteps(ms:Float):Float { if (timeChanges.length == 0) { @@ -411,7 +424,7 @@ class Conductor /** * Given a time in steps and fractional steps, return a time in milliseconds. */ - public static function getStepTimeInMs(stepTime:Float):Float + public function getStepTimeInMs(stepTime:Float):Float { if (timeChanges.length == 0) { @@ -447,7 +460,7 @@ class Conductor /** * Given a time in beats and fractional beats, return a time in milliseconds. */ - public static function getBeatTimeInMs(beatTime:Float):Float + public function getBeatTimeInMs(beatTime:Float):Float { if (timeChanges.length == 0) { @@ -480,13 +493,20 @@ class Conductor } } + public static function watchQuick():Void + { + FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); + FlxG.watch.addQuick("bpm", Conductor.instance.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); + } + + /** + * Reset the Conductor, replacing the current instance with a fresh one. + */ public static function reset():Void { - beatHit.removeAll(); - stepHit.removeAll(); - - mapTimeChanges([]); - forceBPM(null); - update(0); + Conductor.instance = new Conductor(); } } diff --git a/source/funkin/audio/visualize/ABotVis.hx b/source/funkin/audio/visualize/ABotVis.hx index 681287808..89b004df4 100644 --- a/source/funkin/audio/visualize/ABotVis.hx +++ b/source/funkin/audio/visualize/ABotVis.hx @@ -64,7 +64,7 @@ class ABotVis extends FlxTypedSpriteGroup if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples)); else - remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, vis.numSamples)); + remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples)); var fftSamples:Array = []; diff --git a/source/funkin/audio/visualize/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx index 63d0fcd2e..b4e024a4c 100644 --- a/source/funkin/audio/visualize/SpectogramSprite.hx +++ b/source/funkin/audio/visualize/SpectogramSprite.hx @@ -164,7 +164,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples)); else - remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples)); + remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples)); var fftSamples:Array = []; var i = remappedShit; @@ -235,15 +235,15 @@ class SpectogramSprite extends FlxTypedSpriteGroup if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples)); else { - if (curTime == Conductor.songPosition) + if (curTime == Conductor.instance.songPosition) { wavOptimiz = 3; return; // already did shit, so finishes function early } - curTime = Conductor.songPosition; + curTime = Conductor.instance.songPosition; - remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples)); + remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples)); } wavOptimiz = 8; diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 7886ada4f..7677ad637 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -602,7 +602,7 @@ class SongEventDataRaw { if (_stepTime != null && !force) return _stepTime; - return _stepTime = Conductor.getTimeInSteps(this.time); + return _stepTime = Conductor.instance.getTimeInSteps(this.time); } } @@ -796,7 +796,7 @@ class SongNoteDataRaw { if (_stepTime != null && !force) return _stepTime; - return _stepTime = Conductor.getTimeInSteps(this.time); + return _stepTime = Conductor.instance.getTimeInSteps(this.time); } @:jignored @@ -812,7 +812,7 @@ class SongNoteDataRaw if (_stepLength != null && !force) return _stepLength; - return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime(); + return _stepLength = Conductor.instance.getTimeInSteps(this.time + this.length) - getStepTime(); } public function setStepLength(value:Float):Void @@ -823,7 +823,7 @@ class SongNoteDataRaw } else { - var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time; + var lengthMs:Float = Conductor.instance.getStepTimeInMs(value) - this.time; this.length = lengthMs; } _stepLength = null; diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index d23574ce2..5b7ce9fc2 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -40,7 +40,7 @@ class Countdown stopCountdown(); PlayState.instance.isInCountdown = true; - Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5); + Conductor.instance.update(PlayState.instance.startTimestamp + Conductor.instance.beatLengthMs * -5); // Handle onBeatHit events manually // @:privateAccess // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0)); @@ -48,7 +48,7 @@ class Countdown // The timer function gets called based on the beat of the song. countdownTimer = new FlxTimer(); - countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) { + countdownTimer.start(Conductor.instance.beatLengthMs / 1000, function(tmr:FlxTimer) { if (PlayState.instance == null) { tmr.cancel(); @@ -158,7 +158,7 @@ class Countdown { stopCountdown(); // This will trigger PlayState.startSong() - Conductor.update(0); + Conductor.instance.update(0); // PlayState.isInCountdown = false; } @@ -225,7 +225,7 @@ class Countdown countdownSprite.screenCenter(); // Fade sprite in, then out, then destroy it. - FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.beatLengthMs / 1000, + FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000, { ease: FlxEase.cubeInOut, onComplete: function(twn:FlxTween) { diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 6eb53e2d5..ff79688f1 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -121,7 +121,7 @@ class GameOverSubState extends MusicBeatSubState gameOverMusic.stop(); // The conductor now represents the BPM of the game over music. - Conductor.update(0); + Conductor.instance.update(0); } var hasStartedAnimation:Bool = false; @@ -185,7 +185,7 @@ class GameOverSubState extends MusicBeatSubState { // Match the conductor to the music. // This enables the stepHit and beatHit events. - Conductor.update(gameOverMusic.time); + Conductor.instance.update(gameOverMusic.time); } else { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 81757bcae..bbe63199f 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -561,15 +561,15 @@ class PlayState extends MusicBeatSubState } // Prepare the Conductor. - Conductor.forceBPM(null); + Conductor.instance.forceBPM(null); if (currentChart.offsets != null) { - Conductor.instrumentalOffset = currentChart.offsets.getInstrumentalOffset(); + Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset(); } - Conductor.mapTimeChanges(currentChart.timeChanges); - Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp); + Conductor.instance.mapTimeChanges(currentChart.timeChanges); + Conductor.instance.update((Conductor.instance.beatLengthMs * -5) + startTimestamp); // The song is now loaded. We can continue to initialize the play state. initCameras(); @@ -734,7 +734,7 @@ class PlayState extends MusicBeatSubState // Reset music properly. - FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instrumentalOffset); + FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instance.instrumentalOffset); FlxG.sound.music.pause(); if (!overrideMusic) @@ -785,22 +785,22 @@ class PlayState extends MusicBeatSubState { if (isInCountdown) { - Conductor.update(Conductor.songPosition + elapsed * 1000); - if (Conductor.songPosition >= (startTimestamp)) startSong(); + Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000); + if (Conductor.instance.songPosition >= (startTimestamp)) startSong(); } } else { if (Constants.EXT_SOUND == 'mp3') { - Conductor.formatOffset = Constants.MP3_DELAY_MS; + Conductor.instance.formatOffset = Constants.MP3_DELAY_MS; } else { - Conductor.formatOffset = 0.0; + Conductor.instance.formatOffset = 0.0; } - Conductor.update(); // Normal conductor update. + Conductor.instance.update(); // Normal conductor update. } var androidPause:Bool = false; @@ -938,7 +938,7 @@ class PlayState extends MusicBeatSubState // TODO: Check that these work even when songPosition is less than 0. if (songEvents != null && songEvents.length > 0) { - var songEventsToActivate:Array = SongEventParser.queryEvents(songEvents, Conductor.songPosition); + var songEventsToActivate:Array = SongEventParser.queryEvents(songEvents, Conductor.instance.songPosition); if (songEventsToActivate.length > 0) { @@ -946,7 +946,7 @@ class PlayState extends MusicBeatSubState for (event in songEventsToActivate) { // If an event is trying to play, but it's over 5 seconds old, skip it. - if (event.time - Conductor.songPosition < -5000) + if (event.time - Conductor.instance.songPosition < -5000) { event.activated = true; continue; @@ -1048,7 +1048,7 @@ class PlayState extends MusicBeatSubState if (startTimer.finished) { DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, - currentSongLengthMs - Conductor.songPosition); + currentSongLengthMs - Conductor.instance.songPosition); } else { @@ -1072,12 +1072,12 @@ class PlayState extends MusicBeatSubState { if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) { - if (Conductor.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song + if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC, true, currentSongLengthMs - - Conductor.songPosition); + - Conductor.instance.songPosition); else DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); } @@ -1167,17 +1167,17 @@ class PlayState extends MusicBeatSubState if (!startingSong && FlxG.sound.music != null - && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset)) > 200 - || Math.abs(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset)) > 200)) + && (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200 + || Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200)) { trace("VOCALS NEED RESYNC"); - if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset)); - trace(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset)); + if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)); + trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)); resyncVocals(); } - if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.currentStep)); - if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.currentStep)); + if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep)); + if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep)); return true; } @@ -1198,14 +1198,14 @@ class PlayState extends MusicBeatSubState } // Only zoom camera if we are zoomed by less than 35%. - if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.currentBeat % cameraZoomRate == 0) + if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0) { // Zoom camera in (1.5%) FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom; // Hud zooms double (3%) camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom; } - // trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.currentBeat} % ${cameraZoomRate} == ${Conductor.currentBeat % cameraZoomRate}}'); + // trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}'); // That combo milestones that got spoiled that one time. // Comes with NEAT visual and audio effects. @@ -1218,13 +1218,13 @@ class PlayState extends MusicBeatSubState // TODO: Re-enable combo text (how to do this without sections?). // if (currentSong != null) // { - // shouldShowComboText = (Conductor.currentBeat % 8 == 7); - // var daSection = .getSong()[Std.int(Conductor.currentBeat / 16)]; + // shouldShowComboText = (Conductor.instance.currentBeat % 8 == 7); + // var daSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16)]; // shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection); // shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5); // - // var daNextSection = .getSong()[Std.int(Conductor.currentBeat / 16) + 1]; - // var isEndOfSong = .getSong().length < Std.int(Conductor.currentBeat / 16); + // var daNextSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16) + 1]; + // var isEndOfSong = .getSong().length < Std.int(Conductor.instance.currentBeat / 16); // shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection)); // } @@ -1237,7 +1237,7 @@ class PlayState extends MusicBeatSubState var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation - new FlxTimer().start(((Conductor.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) { + new FlxTimer().start(((Conductor.instance.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) { animShit.forceFinish(); }); } @@ -1272,10 +1272,10 @@ class PlayState extends MusicBeatSubState if (currentStage == null) return; // TODO: Add HEY! song events to Tutorial. - if (Conductor.currentBeat % 16 == 15 + if (Conductor.instance.currentBeat % 16 == 15 && currentStage.getDad().characterId == 'gf' - && Conductor.currentBeat > 16 - && Conductor.currentBeat < 48) + && Conductor.instance.currentBeat > 16 + && Conductor.instance.currentBeat < 48) { currentStage.getBoyfriend().playAnimation('hey', true); currentStage.getDad().playAnimation('cheer', true); @@ -1586,7 +1586,7 @@ class PlayState extends MusicBeatSubState trace('Song difficulty could not be loaded.'); } - // Conductor.forceBPM(currentChart.getStartingBPM()); + // Conductor.instance.forceBPM(currentChart.getStartingBPM()); if (!overrideMusic) { @@ -1717,7 +1717,7 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.onComplete = endSong; // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. - FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset; + FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; trace('Playing vocals...'); add(vocals); @@ -1733,7 +1733,7 @@ class PlayState extends MusicBeatSubState if (startTimestamp > 0) { - // FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset; + // FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; handleSkippedNotes(); } } @@ -1811,7 +1811,7 @@ class PlayState extends MusicBeatSubState var hitWindowCenter = note.strumTime; var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; - if (Conductor.songPosition > hitWindowEnd) + if (Conductor.instance.songPosition > hitWindowEnd) { if (note.hasMissed) continue; @@ -1821,7 +1821,7 @@ class PlayState extends MusicBeatSubState if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true; } - else if (Conductor.songPosition > hitWindowCenter) + else if (Conductor.instance.songPosition > hitWindowCenter) { if (note.hasBeenHit) continue; @@ -1842,7 +1842,7 @@ class PlayState extends MusicBeatSubState opponentStrumline.playNoteHoldCover(note.holdNoteSprite); } } - else if (Conductor.songPosition > hitWindowStart) + else if (Conductor.instance.songPosition > hitWindowStart) { if (note.hasBeenHit || note.hasMissed) continue; @@ -1888,14 +1888,14 @@ class PlayState extends MusicBeatSubState var hitWindowCenter = note.strumTime; var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; - if (Conductor.songPosition > hitWindowEnd) + if (Conductor.instance.songPosition > hitWindowEnd) { note.tooEarly = false; note.mayHit = false; note.hasMissed = true; if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true; } - else if (Conductor.songPosition > hitWindowStart) + else if (Conductor.instance.songPosition > hitWindowStart) { note.tooEarly = false; note.mayHit = true; @@ -1962,7 +1962,7 @@ class PlayState extends MusicBeatSubState if (note == null || note.hasBeenHit) continue; var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; - if (Conductor.songPosition > hitWindowEnd) + if (Conductor.instance.songPosition > hitWindowEnd) { // We have passed this note. // Flag the note for deletion without actually penalizing the player. @@ -2126,7 +2126,7 @@ class PlayState extends MusicBeatSubState { inputSpitter.push( { - t: Std.int(Conductor.songPosition), + t: Std.int(Conductor.instance.songPosition), d: indices[i], l: 20 }); @@ -2136,7 +2136,7 @@ class PlayState extends MusicBeatSubState { inputSpitter.push( { - t: Std.int(Conductor.songPosition), + t: Std.int(Conductor.instance.songPosition), d: -1, l: 20 }); @@ -2197,7 +2197,7 @@ class PlayState extends MusicBeatSubState { inputSpitter.push( { - t: Std.int(Conductor.songPosition), + t: Std.int(Conductor.instance.songPosition), d: indices[i], l: 20 }); @@ -2286,7 +2286,7 @@ class PlayState extends MusicBeatSubState // Get the offset and compensate for input latency. // Round inward (trim remainder) for consistency. - var noteDiff:Int = Std.int(Conductor.songPosition - daNote.noteData.time - inputLatencyMs); + var noteDiff:Int = Std.int(Conductor.instance.songPosition - daNote.noteData.time - inputLatencyMs); var score = Scoring.scoreNote(noteDiff, PBOT1); var daRating = Scoring.judgeNote(noteDiff, PBOT1); @@ -2341,7 +2341,7 @@ class PlayState extends MusicBeatSubState { inputSpitter.push( { - t: Std.int(Conductor.songPosition), + t: Std.int(Conductor.instance.songPosition), d: indices[i], l: 20 }); @@ -2351,7 +2351,7 @@ class PlayState extends MusicBeatSubState { inputSpitter.push( { - t: Std.int(Conductor.songPosition), + t: Std.int(Conductor.instance.songPosition), d: -1, l: 20 }); @@ -2750,15 +2750,15 @@ class PlayState extends MusicBeatSubState { FlxG.sound.music.pause(); - var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections); - var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps); + var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections); + var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps); FlxG.sound.music.time = targetTimeMs; handleSkippedNotes(); // regenNoteData(FlxG.sound.music.time); - Conductor.update(FlxG.sound.music.time); + Conductor.instance.update(FlxG.sound.music.time); resyncVocals(); } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 7ad0892f6..390864148 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -367,7 +367,7 @@ class BaseCharacter extends Bopper // This lets you add frames to the end of the sing animation to ease back into the idle! holdTimer += event.elapsed; - var singTimeSec:Float = singTimeSec * (Conductor.beatLengthMs * 0.001); // x beats, to ms. + var singTimeSec:Float = singTimeSec * (Conductor.instance.beatLengthMs * 0.001); // x beats, to ms. if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss diff --git a/source/funkin/play/components/ComboMilestone.hx b/source/funkin/play/components/ComboMilestone.hx index 54d1438f1..4119e45c2 100644 --- a/source/funkin/play/components/ComboMilestone.hx +++ b/source/funkin/play/components/ComboMilestone.hx @@ -40,7 +40,7 @@ class ComboMilestone extends FlxTypedSpriteGroup { if (onScreenTime < 0.9) { - new FlxTimer().start((Conductor.beatLengthMs / 1000) * 0.25, function(tmr) { + new FlxTimer().start((Conductor.instance.beatLengthMs / 1000) * 0.25, function(tmr) { forceFinish(); }); } diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 38a6ec15a..9553856a9 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -59,7 +59,7 @@ class PopUpStuff extends FlxTypedGroup remove(rating, true); rating.destroy(); }, - startDelay: Conductor.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001 }); } @@ -110,7 +110,7 @@ class PopUpStuff extends FlxTypedGroup remove(comboSpr, true); comboSpr.destroy(); }, - startDelay: Conductor.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001 }); var seperatedScore:Array = []; @@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup remove(numScore, true); numScore.destroy(); }, - startDelay: Conductor.beatLengthMs * 0.002 + startDelay: Conductor.instance.beatLengthMs * 0.002 }); daLoop++; diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 1ae76039e..4ad2ed390 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -79,7 +79,8 @@ class ZoomCameraSongEvent extends SongEvent return; } - FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000), {ease: easeFunction}); + FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000), + {ease: easeFunction}); } } diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 0145dee3f..2110be3ef 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -274,10 +274,10 @@ class Strumline extends FlxSpriteGroup static function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float { // Make the note move faster visually as it moves offscreen. - var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0; + var vwoosh:Float = (strumTime < Conductor.instance.songPosition) && vwoosh ? 2.0 : 1.0; var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; - return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1); + return Constants.PIXELS_PER_MS * (Conductor.instance.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1); } function updateNotes():Void @@ -285,8 +285,8 @@ class Strumline extends FlxSpriteGroup if (noteData.length == 0) return; var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0; - var hitWindowStart:Float = Conductor.songPosition - Constants.HIT_WINDOW_MS; - var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS; + var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS; + var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS; for (noteIndex in nextNoteIndex...noteData.length) { @@ -333,7 +333,7 @@ class Strumline extends FlxSpriteGroup { if (holdNote == null || !holdNote.alive) continue; - if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote) + if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote) { if (isPlayer && !isKeyHeld(holdNote.noteDirection)) { @@ -347,7 +347,7 @@ class Strumline extends FlxSpriteGroup var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8; - if (holdNote.missedNote && Conductor.songPosition >= renderWindowEnd) + if (holdNote.missedNote && Conductor.instance.songPosition >= renderWindowEnd) { // Hold note is offscreen, kill it. holdNote.visible = false; @@ -397,13 +397,13 @@ class Strumline extends FlxSpriteGroup holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2; } } - else if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote) + else if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote) { // Hold note is currently being hit, clip it off. holdConfirm(holdNote.noteDirection); holdNote.visible = true; - holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.songPosition; + holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.instance.songPosition; if (holdNote.sustainLength <= 10) { diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 077e9e495..848985563 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -83,13 +83,13 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function handleQuickWatch():Void { // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPosition", Conductor.songPosition); - FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); + FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); + FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + FlxG.watch.addQuick("bpm", Conductor.instance.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentBeatTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); } override function update(elapsed:Float) @@ -139,7 +139,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function stepHit():Bool { - var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); dispatchEvent(event); @@ -150,7 +150,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function beatHit():Bool { - var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); dispatchEvent(event); diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 9dd755b62..0fa55c234 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -65,12 +65,8 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl if (FlxG.keys.justPressed.F5) debug_refreshModules(); // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPosition", Conductor.songPosition); FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + Conductor.watchQuick(); dispatchEvent(new UpdateScriptEvent(elapsed)); } @@ -99,7 +95,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function stepHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); dispatchEvent(event); @@ -115,7 +111,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function beatHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); dispatchEvent(event); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78de08fdf..660d1a71a 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -21,6 +21,7 @@ import flixel.system.FlxAssets.FlxSoundAsset; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.tweens.misc.VarTween; +import haxe.ui.Toolkit; import flixel.util.FlxColor; import flixel.util.FlxSort; import flixel.util.FlxTimer; @@ -273,13 +274,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_songLengthInSteps():Float { - return Conductor.getTimeInSteps(songLengthInMs); + return Conductor.instance.getTimeInSteps(songLengthInMs); } function set_songLengthInSteps(value:Float):Float { - // Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first. - songLengthInMs = Conductor.getStepTimeInMs(value); + // Getting a reasonable result from setting songLengthInSteps requires that Conductor.instance.mapBPMChanges be called first. + songLengthInMs = Conductor.instance.getStepTimeInMs(value); return value; } @@ -393,12 +394,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_scrollPositionInMs():Float { - return Conductor.getStepTimeInMs(scrollPositionInSteps); + return Conductor.instance.getStepTimeInMs(scrollPositionInSteps); } function set_scrollPositionInMs(value:Float):Float { - scrollPositionInSteps = Conductor.getTimeInSteps(value); + scrollPositionInSteps = Conductor.instance.getTimeInSteps(value); return value; } @@ -452,13 +453,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_playheadPositionInMs():Float { if (audioVisGroup != null && audioVisGroup.playerVis != null) - audioVisGroup.playerVis.realtimeStartOffset = -Conductor.getStepTimeInMs(playheadPositionInSteps); - return Conductor.getStepTimeInMs(playheadPositionInSteps); + audioVisGroup.playerVis.realtimeStartOffset = -Conductor.instance.getStepTimeInMs(playheadPositionInSteps); + return Conductor.instance.getStepTimeInMs(playheadPositionInSteps); } function set_playheadPositionInMs(value:Float):Float { - playheadPositionInSteps = Conductor.getTimeInSteps(value); + playheadPositionInSteps = Conductor.instance.getTimeInSteps(value); if (audioVisGroup != null && audioVisGroup.playerVis != null) audioVisGroup.playerVis.realtimeStartOffset = -value; return value; @@ -2402,13 +2403,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } else { - Conductor.currentTimeChange.bpm += 1; + Conductor.instance.currentTimeChange.bpm += 1; this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); } } playbarBPM.onRightClick = _ -> { - Conductor.currentTimeChange.bpm -= 1; + Conductor.instance.currentTimeChange.bpm -= 1; this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); } @@ -2456,9 +2457,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemPaste.onClick = _ -> { var targetMs:Float = scrollPositionInMs + playheadPositionInMs; - var targetStep:Float = Conductor.getTimeInSteps(targetMs); + var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs); var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio; - var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep); + var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep); performCommand(new PasteItemsCommand(targetSnappedMs)); }; @@ -2639,10 +2640,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState saveDataDirty = false; } + var displayAutosavePopup:Bool = false; + /** * UPDATE FUNCTIONS */ - function autoSave():Void + function autoSave(?beforePlaytest:Bool = false):Void { var needsAutoSave:Bool = saveDataDirty; @@ -2660,13 +2663,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (needsAutoSave) { this.exportAllSongData(true, null); - var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); - this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [ - { - text: "Take Me There", - callback: openBackupsFolder, - } - ]); + if (beforePlaytest) + { + displayAutosavePopup = true; + } + else + { + displayAutosavePopup = false; + var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); + this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [ + { + text: "Take Me There", + callback: openBackupsFolder, + } + ]); + } } #end } @@ -2780,7 +2791,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying)) { - playMetronomeTick(Conductor.currentBeat % 4 == 0); + playMetronomeTick(Conductor.instance.currentBeat % 4 == 0); } return true; @@ -2796,8 +2807,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (audioInstTrack != null && audioInstTrack.isPlaying) { - if (healthIconDad != null) healthIconDad.onStepHit(Conductor.currentStep); - if (healthIconBF != null) healthIconBF.onStepHit(Conductor.currentStep); + if (healthIconDad != null) healthIconDad.onStepHit(Conductor.instance.currentStep); + if (healthIconBF != null) healthIconBF.onStepHit(Conductor.instance.currentStep); } // Updating these every step keeps it more accurate. @@ -2825,12 +2836,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState audioInstTrack.update(elapsed); // If the song starts 50ms in, make sure we start the song there. - if (Conductor.instrumentalOffset < 0) + if (Conductor.instance.instrumentalOffset < 0) { - if (audioInstTrack.time < -Conductor.instrumentalOffset) + if (audioInstTrack.time < -Conductor.instance.instrumentalOffset) { - trace('Resetting instrumental time to ${- Conductor.instrumentalOffset}ms'); - audioInstTrack.time = -Conductor.instrumentalOffset; + trace('Resetting instrumental time to ${- Conductor.instance.instrumentalOffset}ms'); + audioInstTrack.time = -Conductor.instance.instrumentalOffset; } } } @@ -2841,16 +2852,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat! - var oldStepTime:Float = Conductor.currentStepTime; - var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset; - Conductor.update(audioInstTrack.time); - handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset); + var oldStepTime:Float = Conductor.instance.currentStepTime; + var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset; + Conductor.instance.update(audioInstTrack.time); + handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); // Resync vocals. if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) { audioVocalTrackGroup.time = audioInstTrack.time; } - var diffStepTime:Float = Conductor.currentStepTime - oldStepTime; + var diffStepTime:Float = Conductor.instance.currentStepTime - oldStepTime; // Move the playhead. playheadPositionInPixels += diffStepTime * GRID_SIZE; @@ -2860,9 +2871,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Else, move the entire view. - var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset; - Conductor.update(audioInstTrack.time); - handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset); + var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset; + Conductor.instance.update(audioInstTrack.time); + handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); // Resync vocals. if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) { @@ -2871,7 +2882,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // We need time in fractional steps here to allow the song to actually play. // Also account for a potentially offset playhead. - scrollPositionInPixels = (Conductor.currentStepTime + Conductor.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels; + scrollPositionInPixels = (Conductor.instance.currentStepTime + Conductor.instance.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels; // DO NOT move song to scroll position here specifically. @@ -3000,8 +3011,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Let's try testing only notes within a certain range of the view area. // TODO: I don't think this messes up really long sustains, does it? - var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.measureLengthMs * 2); // Is 2 measures enough? - var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.measureLengthMs * 2); // Is 2 measures enough? + var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough? + var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough? // Add notes that are now visible. for (noteData in currentSongChartNoteData) @@ -3288,14 +3299,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // PAGE UP = Jump up to nearest measure if (pageUpKeyHandler.activated) { - var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure; + var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure; var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels; var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight; // If we would move less than one grid, instead move to the top of the previous measure. var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos); if (targetScrollAmount < GRID_SIZE) { - targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure; + targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure; } scrollAmount = targetScrollPosition - playheadPos; @@ -3304,21 +3315,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (playbarButtonPressed == 'playbarBack') { playbarButtonPressed = ''; - scrollAmount = -GRID_SIZE * 4 * Conductor.beatsPerMeasure; + scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure; shouldPause = true; } // PAGE DOWN = Jump down to nearest measure if (pageDownKeyHandler.activated) { - var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure; + var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure; var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels; var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight; // If we would move less than one grid, instead move to the top of the next measure. var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos); if (targetScrollAmount < GRID_SIZE) { - targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure; + targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure; } scrollAmount = targetScrollPosition - playheadPos; @@ -3327,7 +3338,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (playbarButtonPressed == 'playbarForward') { playbarButtonPressed = ''; - scrollAmount = GRID_SIZE * 4 * Conductor.beatsPerMeasure; + scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure; shouldPause = true; } @@ -3530,10 +3541,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // The song position of the cursor, in steps. var cursorFractionalStep:Float = cursorY / GRID_SIZE; - var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep); + var cursorMs:Float = Conductor.instance.getStepTimeInMs(cursorFractionalStep); // Round the cursor step to the nearest snap quant. var cursorSnappedStep:Float = Math.floor(cursorFractionalStep / noteSnapRatio) * noteSnapRatio; - var cursorSnappedMs:Float = Conductor.getStepTimeInMs(cursorSnappedStep); + var cursorSnappedMs:Float = Conductor.instance.getStepTimeInMs(cursorSnappedStep); // The direction value for the column at the cursor. var cursorGridPos:Int = Math.floor(cursorX / GRID_SIZE); @@ -3555,7 +3566,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // We released the mouse. Select the notes in the box. var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE; var cursorStepStart:Int = Math.floor(cursorFractionalStepStart); - var cursorMsStart:Float = Conductor.getStepTimeInMs(cursorStepStart); + var cursorMsStart:Float = Conductor.instance.getStepTimeInMs(cursorStepStart); var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE); var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE); @@ -3791,11 +3802,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var dragDistanceMs:Float = 0; if (dragTargetNote != null && dragTargetNote.noteData != null) { - dragDistanceMs = Conductor.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time; + dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time; } else if (dragTargetEvent != null && dragTargetEvent.eventData != null) { - dragDistanceMs = Conductor.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time; + dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time; } var dragDistanceColumns:Int = dragTargetCurrentColumn; @@ -3855,7 +3866,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { stepTime = dragTargetEvent.eventData.getStepTime(); } - var dragDistanceSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime; + var dragDistanceSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime; var data:Int = 0; var noteGridPos:Int = 0; if (dragTargetNote != null && dragTargetNote.noteData != null) @@ -3887,8 +3898,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Handle extending the note as you drag. var stepTime:Float = inline currentPlaceNoteData.getStepTime(); - var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs) - stepTime; - var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs; + var dragLengthSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs) - stepTime; + var dragLengthMs:Float = dragLengthSteps * Conductor.instance.stepLengthMs; var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE; if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null) @@ -4349,7 +4360,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent; } - var songPos:Float = Conductor.songPosition + Conductor.instrumentalOffset; + var songPos:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset; var songPosSeconds:String = Std.string(Math.floor((Math.abs(songPos) / 1000) % 60)).lpad('0', 2); var songPosMinutes:String = Std.string(Math.floor((Math.abs(songPos) / 1000) / 60)).lpad('0', 2); if (songPos < 0) songPosMinutes = '-' + songPosMinutes; @@ -4366,7 +4377,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState playbarNoteSnap.text = '1/${noteSnapQuant}'; playbarDifficulty.text = "Difficulty: " + selectedDifficulty.toTitleCase(); - playbarBPM.text = "BPM: " + Conductor.currentTimeChange.bpm; + playbarBPM.text = "BPM: " + Conductor.instance.currentTimeChange.bpm; } function handlePlayhead():Void @@ -4405,11 +4416,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels; var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio; var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep)); - var playheadPosSnappedMs:Float = playheadPosStep * Conductor.stepLengthMs * noteSnapRatio; + var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio; // Look for notes within 1 step of the playhead. var notesAtPos:Array = SongDataUtils.getNotesInTimeRange(currentSongChartNoteData, playheadPosSnappedMs, - playheadPosSnappedMs + Conductor.stepLengthMs * noteSnapRatio); + playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio); notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]); if (notesAtPos.length == 0) @@ -4606,9 +4617,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { var targetMs:Float = scrollPositionInMs + playheadPositionInMs; - var targetStep:Float = Conductor.getTimeInSteps(targetMs); + var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs); var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio; - var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep); + var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep); targetSnappedMs; } performCommand(new PasteItemsCommand(targetMs)); @@ -4748,7 +4759,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ function testSongInPlayState(minimal:Bool = false):Void { - autoSave(); + autoSave(true); stopWelcomeMusic(); @@ -4949,7 +4960,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Remove any notes past the end of the song. var songCutoffPointSteps:Float = songLengthInSteps - 0.1; - var songCutoffPointMs:Float = Conductor.getStepTimeInMs(songCutoffPointSteps); + var songCutoffPointMs:Float = Conductor.instance.getStepTimeInMs(songCutoffPointSteps); currentSongChartNoteData = SongDataUtils.clampSongNoteData(currentSongChartNoteData, 0.0, songCutoffPointMs); currentSongChartEventData = SongDataUtils.clampSongEventData(currentSongChartEventData, 0.0, songCutoffPointMs); @@ -5051,7 +5062,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var prevDifficulty = availableDifficulties[availableDifficulties.length - 1]; selectedDifficulty = prevDifficulty; - Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges); + Conductor.instance.mapTimeChanges(this.currentSongMetadata.timeChanges); refreshDifficultyTreeSelection(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); @@ -5112,9 +5123,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Update the songPosition in the audio tracks. if (audioInstTrack != null) { - audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instrumentalOffset; + audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset; // Update the songPosition in the Conductor. - Conductor.update(audioInstTrack.time); + Conductor.instance.update(audioInstTrack.time); if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = audioInstTrack.time; } @@ -5174,6 +5185,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.persistentUpdate = true; this.persistentDraw = true; + if (displayAutosavePopup) + { + displayAutosavePopup = false; + Toolkit.callLater(() -> { + var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); + this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [ + { + text: "Take Me There", + callback: openBackupsFolder, + } + ]); + }); + } + moveSongToScrollPosition(); fadeInWelcomeMusic(7, 10); @@ -5432,7 +5457,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState trace('ERROR: Instrumental track is null!'); } - this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset; + this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset; // Many things get reset when song length changes. healthIconsDirty = true; diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx index 3c45c1168..1ea681cb1 100644 --- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx @@ -34,7 +34,7 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.currentSongMetadata.timeChanges = timeChanges; - Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); } public function undo(state:ChartEditorState):Void @@ -51,7 +51,7 @@ class ChangeStartingBPMCommand implements ChartEditorCommand state.currentSongMetadata.timeChanges = timeChanges; - Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); } public function toString():String diff --git a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx index efe9c25d5..8331ed397 100644 --- a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx @@ -33,7 +33,7 @@ class MoveEventsCommand implements ChartEditorCommand { // Clone the notes to prevent editing from affecting the history. var resultEvent = event.clone(); - resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); + resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); movedEvents.push(resultEvent); } diff --git a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx index 2eedbbf03..087f29e82 100644 --- a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx @@ -41,7 +41,7 @@ class MoveItemsCommand implements ChartEditorCommand { // Clone the notes to prevent editing from affecting the history. var resultNote = note.clone(); - resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); + resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0, ChartEditorState.STRUMLINE_SIZE * 2 - 1)); @@ -52,7 +52,7 @@ class MoveItemsCommand implements ChartEditorCommand { // Clone the notes to prevent editing from affecting the history. var resultEvent = event.clone(); - resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); + resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); movedEvents.push(resultEvent); } diff --git a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx index 8bce747a1..0308d8fc8 100644 --- a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx @@ -34,7 +34,7 @@ class MoveNotesCommand implements ChartEditorCommand { // Clone the notes to prevent editing from affecting the history. var resultNote = note.clone(); - resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); + resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio))); resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0, ChartEditorState.STRUMLINE_SIZE * 2 - 1)); diff --git a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx index 75382da41..7e40bc49b 100644 --- a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx @@ -32,9 +32,9 @@ class PasteItemsCommand implements ChartEditorCommand return; } - var stepEndOfSong:Float = Conductor.getTimeInSteps(state.songLengthInMs); + var stepEndOfSong:Float = Conductor.instance.getTimeInSteps(state.songLengthInMs); var stepCutoff:Float = stepEndOfSong - 1.0; - var msCutoff:Float = Conductor.getStepTimeInMs(stepCutoff); + var msCutoff:Float = Conductor.instance.getStepTimeInMs(stepCutoff); addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp)); addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 272291a94..54347160a 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -182,7 +182,7 @@ class ChartEditorAudioHandler state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); state.audioVisGroup.addPlayerVis(vocalTrack); state.audioVisGroup.playerVis.x = 885; - state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; + state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.playerVis.detail = 1; @@ -193,7 +193,7 @@ class ChartEditorAudioHandler state.audioVisGroup.addOpponentVis(vocalTrack); state.audioVisGroup.opponentVis.x = 435; - state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; + state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.opponentVis.detail = 1; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 666b3656c..7e9ae1fd4 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -684,8 +684,8 @@ class ChartEditorDialogHandler state.songMetadata.set(targetVariation, newSongMetadata); - Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. - Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); state.difficultySelectDirty = true; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx index 0c8d6a205..4ae55d191 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx @@ -114,9 +114,9 @@ class ChartEditorImportExportHandler state.songMetadata = newSongMetadata; state.songChartData = newSongChartData; - Conductor.forceBPM(null); // Disable the forced BPM. - Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. - Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + Conductor.instance.forceBPM(null); // Disable the forced BPM. + Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); state.notePreviewDirty = true; state.notePreviewViewportBoundsDirty = true; @@ -413,16 +413,34 @@ class ChartEditorImportExportHandler ]); // We have to force write because the program will die before the save dialog is closed. trace('Force exporting to $targetPath...'); - FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode); - if (onSaveCb != null) onSaveCb(targetPath); + try + { + FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode); + // On success. + if (onSaveCb != null) onSaveCb(targetPath); + } + catch (e) + { + // On failure. + if (onCancelCb != null) onCancelCb(); + } } else { // Force write since we know what file the user wants to overwrite. trace('Force exporting to $targetPath...'); - FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode); - state.saveDataDirty = false; - if (onSaveCb != null) onSaveCb(targetPath); + try + { + // On success. + FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode); + state.saveDataDirty = false; + if (onSaveCb != null) onSaveCb(targetPath); + } + catch (e) + { + // On failure. + if (onCancelCb != null) onCancelCb(); + } } } else diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index 4197ebdd3..1625c53bc 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -125,7 +125,7 @@ class ChartEditorThemeHandler // 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall. // This gets reused to fill the screen. var gridWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); - var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure); + var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1, gridColor2); // Selection borders @@ -142,7 +142,7 @@ class ChartEditorThemeHandler selectionBorderColor); // Selection borders horizontally along the middle. - for (i in 1...(Conductor.stepsPerMeasure)) + for (i in 1...(Conductor.instance.stepsPerMeasure)) { state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), state.gridBitmap.width, ChartEditorState.GRID_SELECTION_BORDER_WIDTH), @@ -197,9 +197,9 @@ class ChartEditorThemeHandler }; // Selection borders horizontally in the middle. - for (i in 1...(Conductor.stepsPerMeasure)) + for (i in 1...(Conductor.instance.stepsPerMeasure)) { - if ((i % Conductor.beatsPerMeasure) == 0) + if ((i % Conductor.instance.beatsPerMeasure) == 0) { state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width, GRID_BEAT_DIVIDER_WIDTH), diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index e0ee4aca3..58aa69a3e 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -120,9 +120,9 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox if (event.value == null) return; state.currentInstrumentalOffset = event.value; - Conductor.instrumentalOffset = event.value; + Conductor.instance.instrumentalOffset = event.value; // Update song length. - state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset; + state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset; }; inputOffsetVocal.onChange = function(event:UIEvent) { diff --git a/source/funkin/ui/debug/latency/LatencyState.hx b/source/funkin/ui/debug/latency/LatencyState.hx index 18b0010b2..70ef97fd0 100644 --- a/source/funkin/ui/debug/latency/LatencyState.hx +++ b/source/funkin/ui/debug/latency/LatencyState.hx @@ -75,7 +75,7 @@ class LatencyState extends MusicBeatSubState // funnyStatsGraph.hi - Conductor.forceBPM(60); + Conductor.instance.forceBPM(60); noteGrp = new FlxTypedGroup(); add(noteGrp); @@ -91,14 +91,14 @@ class LatencyState extends MusicBeatSubState // // musSpec.visType = FREQUENCIES; // add(musSpec); - for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.beatLengthMs)) + for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.instance.beatLengthMs)) { - var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 15); + var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 15); beatTick.makeGraphic(2, 15); beatTick.alpha = 0.3; add(beatTick); - var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 26, 0, "swag"); + var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 26, 0, "swag"); offsetTxt.alpha = 0.5; diffGrp.add(offsetTxt); @@ -130,7 +130,7 @@ class LatencyState extends MusicBeatSubState for (i in 0...32) { - var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), Conductor.beatLengthMs * i); + var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), Conductor.instance.beatLengthMs * i); noteGrp.add(note); } @@ -146,9 +146,9 @@ class LatencyState extends MusicBeatSubState override function stepHit():Bool { - if (Conductor.currentStep % 4 == 2) + if (Conductor.instance.currentStep % 4 == 2) { - blocks.members[((Conductor.currentBeat % 8) + 1) % 8].alpha = 0.5; + blocks.members[((Conductor.instance.currentBeat % 8) + 1) % 8].alpha = 0.5; } return super.stepHit(); @@ -156,11 +156,11 @@ class LatencyState extends MusicBeatSubState override function beatHit():Bool { - if (Conductor.currentBeat % 8 == 0) blocks.forEach(blok -> { + if (Conductor.instance.currentBeat % 8 == 0) blocks.forEach(blok -> { blok.alpha = 0; }); - blocks.members[Conductor.currentBeat % 8].alpha = 1; + blocks.members[Conductor.instance.currentBeat % 8].alpha = 1; // block.visible = !block.visible; return super.beatHit(); @@ -192,17 +192,17 @@ class LatencyState extends MusicBeatSubState if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed; - Conductor.update(swagSong.getTimeWithDiff() - Conductor.inputOffset); - // Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp; + Conductor.instance.update(swagSong.getTimeWithDiff() - Conductor.instance.inputOffset); + // Conductor.instance.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp; - songPosVis.x = songPosToX(Conductor.songPosition); - songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.instrumentalOffset); - songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.inputOffset); + songPosVis.x = songPosToX(Conductor.instance.songPosition); + songVisFollowAudio.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.instrumentalOffset); + songVisFollowVideo.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.inputOffset); - offsetText.text = "INST Offset: " + Conductor.instrumentalOffset + "ms"; - offsetText.text += "\nINPUT Offset: " + Conductor.inputOffset + "ms"; - offsetText.text += "\ncurrentStep: " + Conductor.currentStep; - offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat; + offsetText.text = "INST Offset: " + Conductor.instance.instrumentalOffset + "ms"; + offsetText.text += "\nINPUT Offset: " + Conductor.instance.inputOffset + "ms"; + offsetText.text += "\ncurrentStep: " + Conductor.instance.currentStep; + offsetText.text += "\ncurrentBeat: " + Conductor.instance.currentBeat; var avgOffsetInput:Float = 0; @@ -221,24 +221,24 @@ class LatencyState extends MusicBeatSubState { if (FlxG.keys.justPressed.RIGHT) { - Conductor.instrumentalOffset += 1.0 * multiply; + Conductor.instance.instrumentalOffset += 1.0 * multiply; } if (FlxG.keys.justPressed.LEFT) { - Conductor.instrumentalOffset -= 1.0 * multiply; + Conductor.instance.instrumentalOffset -= 1.0 * multiply; } } else { if (FlxG.keys.justPressed.RIGHT) { - Conductor.inputOffset += 1.0 * multiply; + Conductor.instance.inputOffset += 1.0 * multiply; } if (FlxG.keys.justPressed.LEFT) { - Conductor.inputOffset -= 1.0 * multiply; + Conductor.instance.inputOffset -= 1.0 * multiply; } } @@ -250,7 +250,7 @@ class LatencyState extends MusicBeatSubState }*/ noteGrp.forEach(function(daNote:NoteSprite) { - daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.instrumentalOffset) - daNote.noteData.time) * 0.45); + daNote.y = (strumLine.y - ((Conductor.instance.songPosition - Conductor.instance.instrumentalOffset) - daNote.noteData.time) * 0.45); daNote.x = strumLine.x + 30; if (daNote.y < strumLine.y) daNote.alpha = 0.5; @@ -258,7 +258,7 @@ class LatencyState extends MusicBeatSubState if (daNote.y < 0 - daNote.height) { daNote.alpha = 1; - // daNote.data.strumTime += Conductor.beatLengthMs * 8; + // daNote.data.strumTime += Conductor.instance.beatLengthMs * 8; } }); @@ -267,14 +267,14 @@ class LatencyState extends MusicBeatSubState function generateBeatStuff() { - Conductor.update(swagSong.getTimeWithDiff()); + Conductor.instance.update(swagSong.getTimeWithDiff()); - var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length; - var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs); - getDiff -= Conductor.inputOffset; + var closestBeat:Int = Math.round(Conductor.instance.songPosition / Conductor.instance.beatLengthMs) % diffGrp.members.length; + var getDiff:Float = Conductor.instance.songPosition - (closestBeat * Conductor.instance.beatLengthMs); + getDiff -= Conductor.instance.inputOffset; // lil fix for end of song - if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length; + if (closestBeat == 0 && getDiff >= Conductor.instance.beatLengthMs * 2) getDiff -= FlxG.sound.music.length; trace("\tDISTANCE TO CLOSEST BEAT: " + getDiff + "ms"); trace("\tCLOSEST BEAT: " + closestBeat); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 456988873..a608d833a 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -238,7 +238,7 @@ class StoryMenuState extends MusicBeatState var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu'); if (freakyMenuMetadata != null) { - Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); + Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges); } FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); @@ -317,7 +317,7 @@ class StoryMenuState extends MusicBeatState override function update(elapsed:Float) { - Conductor.update(); + Conductor.instance.update(); highScoreLerp = Std.int(MathUtil.coolLerp(highScoreLerp, highScore, 0.5)); diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 7671bb336..bc44af073 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -221,7 +221,7 @@ class TitleState extends MusicBeatState var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu'); if (freakyMenuMetadata != null) { - Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); + Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges); } FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); FlxG.sound.music.fadeIn(4, 0, 0.7); @@ -256,7 +256,7 @@ class TitleState extends MusicBeatState if (FlxG.keys.pressed.DOWN) FlxG.sound.music.pitch -= 0.5 * elapsed; #end - Conductor.update(); + Conductor.instance.update(); /* if (FlxG.onMobile) { @@ -280,7 +280,7 @@ class TitleState extends MusicBeatState FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); } - if (FlxG.sound.music != null) Conductor.update(FlxG.sound.music.time); + if (FlxG.sound.music != null) Conductor.instance.update(FlxG.sound.music.time); if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen; // do controls.PAUSE | controls.ACCEPT instead? @@ -390,7 +390,7 @@ class TitleState extends MusicBeatState var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music); add(spec); - Conductor.forceBPM(190); + Conductor.instance.forceBPM(190); FlxG.camera.flash(FlxColor.WHITE, 1); FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); } @@ -442,13 +442,13 @@ class TitleState extends MusicBeatState if (!skippedIntro) { - // FlxG.log.add(Conductor.currentBeat); + // FlxG.log.add(Conductor.instance.currentBeat); // if the user is draggin the window some beats will // be missed so this is just to compensate - if (Conductor.currentBeat > lastBeat) + if (Conductor.instance.currentBeat > lastBeat) { // TODO: Why does it perform ALL the previous steps each beat? - for (i in lastBeat...Conductor.currentBeat) + for (i in lastBeat...Conductor.instance.currentBeat) { switch (i + 1) { @@ -483,11 +483,11 @@ class TitleState extends MusicBeatState } } } - lastBeat = Conductor.currentBeat; + lastBeat = Conductor.instance.currentBeat; } if (skippedIntro) { - if (cheatActive && Conductor.currentBeat % 2 == 0) swagShader.update(0.125); + if (cheatActive && Conductor.instance.currentBeat % 2 == 0) swagShader.update(0.125); if (logoBl != null && logoBl.animation != null) logoBl.animation.play('bump', true); diff --git a/tests/unit/source/funkin/ConductorTest.hx b/tests/unit/source/funkin/ConductorTest.hx index c65f3f297..a0cfedbab 100644 --- a/tests/unit/source/funkin/ConductorTest.hx +++ b/tests/unit/source/funkin/ConductorTest.hx @@ -31,23 +31,23 @@ class ConductorTest extends FunkinTest { // NOTE: Expected value comes first. - Assert.areEqual([], Conductor.timeChanges); - Assert.areEqual(null, Conductor.currentTimeChange); + Assert.areEqual([], Conductor.instance.timeChanges); + Assert.areEqual(null, Conductor.instance.currentTimeChange); - Assert.areEqual(0, Conductor.songPosition); - Assert.areEqual(Constants.DEFAULT_BPM, Conductor.bpm); - Assert.areEqual(null, Conductor.bpmOverride); + Assert.areEqual(0, Conductor.instance.songPosition); + Assert.areEqual(Constants.DEFAULT_BPM, Conductor.instance.bpm); + Assert.areEqual(null, Conductor.instance.bpmOverride); - Assert.areEqual(600, Conductor.beatLengthMs); + Assert.areEqual(600, Conductor.instance.beatLengthMs); - Assert.areEqual(4, Conductor.timeSignatureNumerator); - Assert.areEqual(4, Conductor.timeSignatureDenominator); + Assert.areEqual(4, Conductor.instance.timeSignatureNumerator); + Assert.areEqual(4, Conductor.instance.timeSignatureDenominator); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - Assert.areEqual(0.0, Conductor.currentStepTime); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + Assert.areEqual(0.0, Conductor.instance.currentStepTime); - Assert.areEqual(150, Conductor.stepLengthMs); + Assert.areEqual(150, Conductor.instance.stepLengthMs); } /** @@ -60,23 +60,23 @@ class ConductorTest extends FunkinTest var currentConductorState:Null = conductorState; Assert.isNotNull(currentConductorState); - Assert.areEqual(0, Conductor.songPosition); + Assert.areEqual(0, Conductor.instance.songPosition); step(); // 1 var BPM_100_STEP_TIME = 1 / 9; - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(1 / 9, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(1 / 9, Conductor.instance.currentStepTime); step(7); // 8 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 8, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(8 / 9, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 8, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(8 / 9, Conductor.instance.currentStepTime); Assert.areEqual(0, currentConductorState.beatsHit); Assert.areEqual(0, currentConductorState.stepsHit); @@ -88,10 +88,10 @@ class ConductorTest extends FunkinTest currentConductorState.beatsHit = 0; currentConductorState.stepsHit = 0; - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 9, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(1, Conductor.currentStep); - FunkinAssert.areNear(1.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 9, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(1, Conductor.instance.currentStep); + FunkinAssert.areNear(1.0, Conductor.instance.currentStepTime); step(35 - 9); // 35 @@ -100,10 +100,10 @@ class ConductorTest extends FunkinTest currentConductorState.beatsHit = 0; currentConductorState.stepsHit = 0; - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 35, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(3, Conductor.currentStep); - FunkinAssert.areNear(3.0 + 8 / 9, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 35, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(3, Conductor.instance.currentStep); + FunkinAssert.areNear(3.0 + 8 / 9, Conductor.instance.currentStepTime); step(); // 36 @@ -112,83 +112,83 @@ class ConductorTest extends FunkinTest currentConductorState.beatsHit = 0; currentConductorState.stepsHit = 0; - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 36, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(4, Conductor.currentStep); - FunkinAssert.areNear(4.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 36, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(4, Conductor.instance.currentStep); + FunkinAssert.areNear(4.0, Conductor.instance.currentStepTime); step(50 - 36); // 50 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 50, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(5, Conductor.currentStep); - FunkinAssert.areNear(5.555555, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 50, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(5, Conductor.instance.currentStep); + FunkinAssert.areNear(5.555555, Conductor.instance.currentStepTime); step(49); // 99 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 99, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(11, Conductor.currentStep); - FunkinAssert.areNear(11.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 99, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(11, Conductor.instance.currentStep); + FunkinAssert.areNear(11.0, Conductor.instance.currentStepTime); step(1); // 100 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 100, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(11, Conductor.currentStep); - FunkinAssert.areNear(11.111111, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 100, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(11, Conductor.instance.currentStep); + FunkinAssert.areNear(11.111111, Conductor.instance.currentStepTime); } @Test function testUpdateForcedBPM():Void { - Conductor.forceBPM(60); + Conductor.instance.forceBPM(60); - Assert.areEqual(0, Conductor.songPosition); + Assert.areEqual(0, Conductor.instance.songPosition); // 60 beats per minute = 1 beat per second // 1 beat per second = 1/60 beats per frame = 4/60 steps per frame step(); // Advances time 1/60 of 1 second. - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(4 / 60, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step step(14 - 1); // 14 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 14, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(1.0 - 4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 14, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(1.0 - 4 / 60, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step step(); // 15 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(1, Conductor.currentStep); - FunkinAssert.areNear(1.0, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(1, Conductor.instance.currentStep); + FunkinAssert.areNear(1.0, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step step(45 - 1); // 59 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(3, Conductor.currentStep); - FunkinAssert.areNear(4.0 - 4 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(3, Conductor.instance.currentStep); + FunkinAssert.areNear(4.0 - 4 / 60, Conductor.instance.currentStepTime); step(); // 60 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(4, Conductor.currentStep); - FunkinAssert.areNear(4.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(4, Conductor.instance.currentStep); + FunkinAssert.areNear(4.0, Conductor.instance.currentStepTime); step(); // 61 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(4, Conductor.currentStep); - FunkinAssert.areNear(4.0 + 4 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(4, Conductor.instance.currentStep); + FunkinAssert.areNear(4.0 + 4 / 60, Conductor.instance.currentStepTime); } @Test @@ -196,50 +196,50 @@ class ConductorTest extends FunkinTest { // Start the song with a BPM of 120. var songTimeChanges:Array = [new SongTimeChange(0, 120)]; - Conductor.mapTimeChanges(songTimeChanges); + Conductor.instance.mapTimeChanges(songTimeChanges); // All should be at 0. - FunkinAssert.areNear(0, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step + FunkinAssert.areNear(0, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step // 120 beats per minute = 2 beat per second // 2 beat per second = 2/60 beats per frame = 16/120 steps per frame step(); // Advances time 1/60 of 1 second. - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step step(15 - 1); // 15 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(2, Conductor.currentStep); - FunkinAssert.areNear(2.0, Conductor.currentStepTime); // 2/60 of 1 beat = 8/60 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(2, Conductor.instance.currentStep); + FunkinAssert.areNear(2.0, Conductor.instance.currentStepTime); // 2/60 of 1 beat = 8/60 of 1 step step(45 - 1); // 59 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(7, Conductor.currentStep); - FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(7, Conductor.instance.currentStep); + FunkinAssert.areNear(7.0 + 104 / 120, Conductor.instance.currentStepTime); step(); // 60 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime); step(); // 61 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime); } @Test @@ -247,57 +247,57 @@ class ConductorTest extends FunkinTest { // Start the song with a BPM of 120. var songTimeChanges:Array = [new SongTimeChange(0, 120), new SongTimeChange(3000, 90)]; - Conductor.mapTimeChanges(songTimeChanges); + Conductor.instance.mapTimeChanges(songTimeChanges); // All should be at 0. - FunkinAssert.areNear(0, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step + FunkinAssert.areNear(0, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step // 120 beats per minute = 2 beat per second // 2 beat per second = 2/60 beats per frame = 16/120 steps per frame step(); // Advances time 1/60 of 1 second. - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step step(60 - 1 - 1); // 59 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(7, Conductor.currentStep); - FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(7, Conductor.instance.currentStep); + FunkinAssert.areNear(7.0 + 104 / 120, Conductor.instance.currentStepTime); step(); // 60 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime); step(); // 61 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime); step(179 - 61); // 179 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition); - Assert.areEqual(5, Conductor.currentBeat); - Assert.areEqual(23, Conductor.currentStep); - FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.instance.songPosition); + Assert.areEqual(5, Conductor.instance.currentBeat); + Assert.areEqual(23, Conductor.instance.currentStep); + FunkinAssert.areNear(23.0 + 52 / 60, Conductor.instance.currentStepTime); step(); // 180 (3 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition); - Assert.areEqual(6, Conductor.currentBeat); - Assert.areEqual(24, Conductor.currentStep); - FunkinAssert.areNear(24.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.instance.songPosition); + Assert.areEqual(6, Conductor.instance.currentBeat); + Assert.areEqual(24, Conductor.instance.currentStep); + FunkinAssert.areNear(24.0, Conductor.instance.currentStepTime); step(); // 181 (3 + 1/60 seconds) // BPM has switched to 90! @@ -305,24 +305,24 @@ class ConductorTest extends FunkinTest // 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame // = 12/120 steps per frame - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition); - Assert.areEqual(6, Conductor.currentBeat); - Assert.areEqual(24, Conductor.currentStep); - FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.instance.songPosition); + Assert.areEqual(6, Conductor.instance.currentBeat); + Assert.areEqual(24, Conductor.instance.currentStep); + FunkinAssert.areNear(24.0 + 12 / 120, Conductor.instance.currentStepTime); step(59); // 240 (4 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition); - Assert.areEqual(7, Conductor.currentBeat); - Assert.areEqual(30, Conductor.currentStep); - FunkinAssert.areNear(30.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.instance.songPosition); + Assert.areEqual(7, Conductor.instance.currentBeat); + Assert.areEqual(30, Conductor.instance.currentStep); + FunkinAssert.areNear(30.0, Conductor.instance.currentStepTime); step(); // 241 (4 + 1/60 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition); - Assert.areEqual(7, Conductor.currentBeat); - Assert.areEqual(30, Conductor.currentStep); - FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.instance.songPosition); + Assert.areEqual(7, Conductor.instance.currentBeat); + Assert.areEqual(30, Conductor.instance.currentStep); + FunkinAssert.areNear(30.0 + 12 / 120, Conductor.instance.currentStepTime); } @Test @@ -334,63 +334,63 @@ class ConductorTest extends FunkinTest new SongTimeChange(3000, 90), new SongTimeChange(6000, 180) ]; - Conductor.mapTimeChanges(songTimeChanges); + Conductor.instance.mapTimeChanges(songTimeChanges); // Verify time changes. - Assert.areEqual(3, Conductor.timeChanges.length); - FunkinAssert.areNear(0, Conductor.timeChanges[0].beatTime); - FunkinAssert.areNear(6, Conductor.timeChanges[1].beatTime); - FunkinAssert.areNear(10.5, Conductor.timeChanges[2].beatTime); + Assert.areEqual(3, Conductor.instance.timeChanges.length); + FunkinAssert.areNear(0, Conductor.instance.timeChanges[0].beatTime); + FunkinAssert.areNear(6, Conductor.instance.timeChanges[1].beatTime); + FunkinAssert.areNear(10.5, Conductor.instance.timeChanges[2].beatTime); // All should be at 0. - FunkinAssert.areNear(0, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step + FunkinAssert.areNear(0, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step // 120 beats per minute = 2 beat per second // 2 beat per second = 2/60 beats per frame = 16/120 steps per frame step(); // Advances time 1/60 of 1 second. - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition); - Assert.areEqual(0, Conductor.currentBeat); - Assert.areEqual(0, Conductor.currentStep); - FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition); + Assert.areEqual(0, Conductor.instance.currentBeat); + Assert.areEqual(0, Conductor.instance.currentStep); + FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step step(60 - 1 - 1); // 59 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition); - Assert.areEqual(1, Conductor.currentBeat); - Assert.areEqual(7, Conductor.currentStep); - FunkinAssert.areNear(7 + 104 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition); + Assert.areEqual(1, Conductor.instance.currentBeat); + Assert.areEqual(7, Conductor.instance.currentStep); + FunkinAssert.areNear(7 + 104 / 120, Conductor.instance.currentStepTime); step(); // 60 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime); step(); // 61 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition); - Assert.areEqual(2, Conductor.currentBeat); - Assert.areEqual(8, Conductor.currentStep); - FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition); + Assert.areEqual(2, Conductor.instance.currentBeat); + Assert.areEqual(8, Conductor.instance.currentStep); + FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime); step(179 - 61); // 179 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition); - Assert.areEqual(5, Conductor.currentBeat); - Assert.areEqual(23, Conductor.currentStep); - FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.instance.songPosition); + Assert.areEqual(5, Conductor.instance.currentBeat); + Assert.areEqual(23, Conductor.instance.currentStep); + FunkinAssert.areNear(23.0 + 52 / 60, Conductor.instance.currentStepTime); step(); // 180 (3 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition); - Assert.areEqual(6, Conductor.currentBeat); - Assert.areEqual(24, Conductor.currentStep); // 23.999 => 24 - FunkinAssert.areNear(24.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.instance.songPosition); + Assert.areEqual(6, Conductor.instance.currentBeat); + Assert.areEqual(24, Conductor.instance.currentStep); // 23.999 => 24 + FunkinAssert.areNear(24.0, Conductor.instance.currentStepTime); step(); // 181 (3 + 1/60 seconds) // BPM has switched to 90! @@ -398,45 +398,45 @@ class ConductorTest extends FunkinTest // 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame // = 12/120 steps per frame - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition); - Assert.areEqual(6, Conductor.currentBeat); - Assert.areEqual(24, Conductor.currentStep); - FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.instance.songPosition); + Assert.areEqual(6, Conductor.instance.currentBeat); + Assert.areEqual(24, Conductor.instance.currentStep); + FunkinAssert.areNear(24.0 + 12 / 120, Conductor.instance.currentStepTime); step(60 - 1 - 1); // 240 (4 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 239, Conductor.songPosition); - Assert.areEqual(7, Conductor.currentBeat); - Assert.areEqual(29, Conductor.currentStep); - FunkinAssert.areNear(29.0 + 108 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 239, Conductor.instance.songPosition); + Assert.areEqual(7, Conductor.instance.currentBeat); + Assert.areEqual(29, Conductor.instance.currentStep); + FunkinAssert.areNear(29.0 + 108 / 120, Conductor.instance.currentStepTime); step(); // 240 (4 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition); - Assert.areEqual(7, Conductor.currentBeat); - Assert.areEqual(30, Conductor.currentStep); - FunkinAssert.areNear(30.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.instance.songPosition); + Assert.areEqual(7, Conductor.instance.currentBeat); + Assert.areEqual(30, Conductor.instance.currentStep); + FunkinAssert.areNear(30.0, Conductor.instance.currentStepTime); step(); // 241 (4 + 1/60 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition); - Assert.areEqual(7, Conductor.currentBeat); - Assert.areEqual(30, Conductor.currentStep); - FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.instance.songPosition); + Assert.areEqual(7, Conductor.instance.currentBeat); + Assert.areEqual(30, Conductor.instance.currentStep); + FunkinAssert.areNear(30.0 + 12 / 120, Conductor.instance.currentStepTime); step(359 - 241); // 359 (5 + 59/60 seconds) - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 359, Conductor.songPosition); - Assert.areEqual(10, Conductor.currentBeat); - Assert.areEqual(41, Conductor.currentStep); - FunkinAssert.areNear(41 + 108 / 120, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 359, Conductor.instance.songPosition); + Assert.areEqual(10, Conductor.instance.currentBeat); + Assert.areEqual(41, Conductor.instance.currentStep); + FunkinAssert.areNear(41 + 108 / 120, Conductor.instance.currentStepTime); step(); // 360 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 360, Conductor.songPosition); - Assert.areEqual(10, Conductor.currentBeat); - Assert.areEqual(42, Conductor.currentStep); // 41.999 - FunkinAssert.areNear(42.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 360, Conductor.instance.songPosition); + Assert.areEqual(10, Conductor.instance.currentBeat); + Assert.areEqual(42, Conductor.instance.currentStep); // 41.999 + FunkinAssert.areNear(42.0, Conductor.instance.currentStepTime); step(); // 361 // BPM has switched to 180! @@ -444,24 +444,24 @@ class ConductorTest extends FunkinTest // 3 beat per second = 3/60 beats per frame // = 12/60 steps per frame - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 361, Conductor.songPosition); - Assert.areEqual(10, Conductor.currentBeat); - Assert.areEqual(42, Conductor.currentStep); - FunkinAssert.areNear(42.0 + 12 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 361, Conductor.instance.songPosition); + Assert.areEqual(10, Conductor.instance.currentBeat); + Assert.areEqual(42, Conductor.instance.currentStep); + FunkinAssert.areNear(42.0 + 12 / 60, Conductor.instance.currentStepTime); step(); // 362 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 362, Conductor.songPosition); - Assert.areEqual(10, Conductor.currentBeat); - Assert.areEqual(42, Conductor.currentStep); - FunkinAssert.areNear(42.0 + 24 / 60, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 362, Conductor.instance.songPosition); + Assert.areEqual(10, Conductor.instance.currentBeat); + Assert.areEqual(42, Conductor.instance.currentStep); + FunkinAssert.areNear(42.0 + 24 / 60, Conductor.instance.currentStepTime); step(3); // 365 - FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 365, Conductor.songPosition); - Assert.areEqual(10, Conductor.currentBeat); - Assert.areEqual(43, Conductor.currentStep); // 42.999 => 42 - FunkinAssert.areNear(43.0, Conductor.currentStepTime); + FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 365, Conductor.instance.songPosition); + Assert.areEqual(10, Conductor.instance.currentBeat); + Assert.areEqual(43, Conductor.instance.currentStep); // 42.999 => 42 + FunkinAssert.areNear(43.0, Conductor.instance.currentStepTime); } } @@ -504,6 +504,6 @@ class ConductorState extends FlxState super.update(elapsed); // On each step, increment the Conductor as though the song was playing. - Conductor.update(Conductor.songPosition + elapsed * Constants.MS_PER_SEC); + Conductor.instance.update(Conductor.instance.songPosition + elapsed * Constants.MS_PER_SEC); } } From ba3bf4e846333561bc8a71cf52f963afde55ff1e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 14 Dec 2023 17:05:48 -0500 Subject: [PATCH 004/129] Update HaxeUI again --- assets | 2 +- hmm.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets b/assets index dfaf23dfa..54065ef5b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit dfaf23dfa11ff67be2eea9113a80ff5dc0040f76 +Subproject commit 54065ef5b7dec959aea478fd79b44e692e7c248a diff --git a/hmm.json b/hmm.json index a10eed1a6..28c2eeb3e 100644 --- a/hmm.json +++ b/hmm.json @@ -49,14 +49,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "032192e849cdb7d1070c0a3241c58ee555ffaccc", + "ref": "1e40adb6bf58b0eb6a5600f75face565b647ab36", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "d90758b229d05206400df867d333c79d9fdbd478", + "ref": "26b6bb132c92dfa9b77b4a61eaeda8f9a9efda98", "url": "https://github.com/haxeui/haxeui-flixel" }, { From e9ed7223b60ea482602bb9d46ccd3c9e2b605010 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 15 Dec 2023 02:29:32 -0500 Subject: [PATCH 005/129] no debugger --- assets | 2 +- source/funkin/ui/debug/charting/ChartEditorState.hx | 9 +++++++++ .../charting/handlers/ChartEditorToolboxHandler.hx | 10 ++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/assets b/assets index dfaf23dfa..c354795f7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit dfaf23dfa11ff67be2eea9113a80ff5dc0040f76 +Subproject commit c354795f7f560fa096b855c6e6bca745f77fa414 diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78de08fdf..9b2ffadde 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -124,6 +124,7 @@ import flixel.group.FlxGroup.FlxTypedGroup; import funkin.audio.visualize.PolygonVisGroup; import flixel.input.mouse.FlxMouseEvent; import flixel.text.FlxText; +import flixel.system.debug.log.LogStyle; using Lambda; @@ -544,6 +545,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var playtestPracticeMode:Bool = false; + /** + * Enables or disables the "debugger" popup that appears when you run into a flixel error. + */ + var enabledDebuggerPopup:Bool = true; + // Visuals /** @@ -4766,6 +4772,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return; } + LogStyle.WARNING.openConsole = enabledDebuggerPopup; + LogStyle.ERROR.openConsole = enabledDebuggerPopup; + // TODO: Rework asset system so we can remove this. switch (currentSongStage) { diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index a9a9c375d..98d04887d 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -488,6 +488,16 @@ class ChartEditorToolboxHandler state.playtestStartTime = checkboxStartTime.selected; }; + var checkboxDebugger:Null = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox); + + if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.'; + + state.enabledDebuggerPopup = checkboxDebugger.selected; + + checkboxDebugger.onClick = _ -> { + state.enabledDebuggerPopup = checkboxDebugger.selected; + }; + return toolbox; } From 9e27095659faa8a458516fc43fd6848653fe891a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:21:26 -0500 Subject: [PATCH 006/129] Update assets. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index c354795f7..6f17eb051 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c354795f7f560fa096b855c6e6bca745f77fa414 +Subproject commit 6f17eb051e2609d59a591d4e6eb78e37c6e90adb From 4ff5bd21df738f2b63e7679330afd56a0b53a975 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:23:42 -0500 Subject: [PATCH 007/129] Fixes to the FNF Legacy JSON parser --- source/funkin/data/DataParse.hx | 45 +++++++++++++++---- source/funkin/data/level/LevelRegistry.hx | 2 + .../data/notestyle/NoteStyleRegistry.hx | 2 + source/funkin/data/song/SongData.hx | 2 +- source/funkin/data/song/SongDataUtils.hx | 1 + source/funkin/data/song/SongRegistry.hx | 14 ++++++ .../data/song/importer/ChartManifestData.hx | 1 + .../data/song/importer/FNFLegacyData.hx | 3 +- .../data/song/importer/FNFLegacyImporter.hx | 22 ++++++++- .../source/funkin/data/BaseRegistryTest.hx | 2 + 10 files changed, 83 insertions(+), 11 deletions(-) diff --git a/source/funkin/data/DataParse.hx b/source/funkin/data/DataParse.hx index cbd168a61..49dde0198 100644 --- a/source/funkin/data/DataParse.hx +++ b/source/funkin/data/DataParse.hx @@ -178,7 +178,31 @@ class DataParse switch (json.value) { case JObject(fields): - return cast Tools.getValue(json); + var result:LegacyNoteSection = + { + mustHitSection: false, + sectionNotes: [], + }; + for (field in fields) + { + switch (field.name) + { + case 'sectionNotes': + result.sectionNotes = legacyNotes(field.value, field.name); + + case 'mustHitSection': + result.mustHitSection = Tools.getValue(field.value); + case 'typeOfSection': + result.typeOfSection = Tools.getValue(field.value); + case 'lengthInSteps': + result.lengthInSteps = Tools.getValue(field.value); + case 'changeBPM': + result.changeBPM = Tools.getValue(field.value); + case 'bpm': + result.bpm = Tools.getValue(field.value); + } + } + return result; default: throw 'Expected property $name to be an object, but it was ${json.value}.'; } @@ -189,7 +213,12 @@ class DataParse switch (json.value) { case JObject(fields): - return cast Tools.getValue(json); + var result = {}; + for (field in fields) + { + Reflect.setField(result, field.name, legacyNoteSectionArray(field.value, field.name)); + } + return result; default: throw 'Expected property $name to be an object, but it was ${json.value}.'; } @@ -211,13 +240,13 @@ class DataParse switch (json.value) { case JArray(values): - // var time:Null = values[0] == null ? null : Tools.getValue(values[0]); - // var data:Null = values[1] == null ? null : Tools.getValue(values[1]); - // var length:Null = values[2] == null ? null : Tools.getValue(values[2]); - // var alt:Null = values[3] == null ? null : Tools.getValue(values[3]); + var time:Null = values[0] == null ? null : Tools.getValue(values[0]); + var data:Null = values[1] == null ? null : Tools.getValue(values[1]); + var length:Null = values[2] == null ? null : Tools.getValue(values[2]); + var alt:Null = values[3] == null ? null : Tools.getValue(values[3]); - // return new LegacyNote(time, data, length, alt); - return null; + return new LegacyNote(time, data, length, alt); + // return null; default: throw 'Expected property $name to be a note, but it was ${json.value}.'; } diff --git a/source/funkin/data/level/LevelRegistry.hx b/source/funkin/data/level/LevelRegistry.hx index 75b0b11f6..b5c15de0f 100644 --- a/source/funkin/data/level/LevelRegistry.hx +++ b/source/funkin/data/level/LevelRegistry.hx @@ -30,6 +30,7 @@ class LevelRegistry extends BaseRegistry // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; switch (loadEntryFile(id)) { @@ -57,6 +58,7 @@ class LevelRegistry extends BaseRegistry public function parseEntryDataRaw(contents:String, ?fileName:String):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) diff --git a/source/funkin/data/notestyle/NoteStyleRegistry.hx b/source/funkin/data/notestyle/NoteStyleRegistry.hx index 4255a644b..ffb9bf490 100644 --- a/source/funkin/data/notestyle/NoteStyleRegistry.hx +++ b/source/funkin/data/notestyle/NoteStyleRegistry.hx @@ -35,6 +35,7 @@ class NoteStyleRegistry extends BaseRegistry // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; switch (loadEntryFile(id)) { @@ -62,6 +63,7 @@ class NoteStyleRegistry extends BaseRegistry public function parseEntryDataRaw(contents:String, ?fileName:String):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 7886ada4f..600871e2f 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -747,7 +747,7 @@ class SongNoteDataRaw /** * The kind of the note. * This can allow the note to include information used for custom behavior. - * Defaults to blank or `"normal"`. + * Defaults to blank or `Constants.DEFAULT_DIFFICULTY`. */ @:alias("k") @:default("normal") diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 4ae4b1426..309676884 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -230,6 +230,7 @@ class SongDataUtils trace('Read ${notesString.length} characters from clipboard.'); var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(notesString, 'clipboard'); if (parser.errors.length > 0) { diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 850654eb7..5a0835f57 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -126,6 +126,8 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + switch (loadEntryMetadataFile(id, variation)) { case {fileName: fileName, contents: contents}: @@ -147,6 +149,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -206,6 +209,8 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + switch (loadEntryMetadataFile(id, variation)) { case {fileName: fileName, contents: contents}: @@ -226,6 +231,8 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + switch (loadEntryMetadataFile(id, variation)) { case {fileName: fileName, contents: contents}: @@ -244,6 +251,7 @@ class SongRegistry extends BaseRegistry function parseEntryMetadataRaw_v2_1_0(contents:String, ?fileName:String = 'raw'):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -257,6 +265,7 @@ class SongRegistry extends BaseRegistry function parseEntryMetadataRaw_v2_0_0(contents:String, ?fileName:String = 'raw'):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -272,6 +281,8 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + switch (loadMusicDataFile(id, variation)) { case {fileName: fileName, contents: contents}: @@ -291,6 +302,7 @@ class SongRegistry extends BaseRegistry public function parseMusicDataRaw(contents:String, ?fileName:String = 'raw'):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -334,6 +346,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; switch (loadEntryChartFile(id, variation)) { @@ -356,6 +369,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, fileName); if (parser.errors.length > 0) diff --git a/source/funkin/data/song/importer/ChartManifestData.hx b/source/funkin/data/song/importer/ChartManifestData.hx index 0c7d2f0b0..dd0d28479 100644 --- a/source/funkin/data/song/importer/ChartManifestData.hx +++ b/source/funkin/data/song/importer/ChartManifestData.hx @@ -68,6 +68,7 @@ class ChartManifestData public static function deserialize(contents:String):Null { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(contents, 'manifest.json'); if (parser.errors.length > 0) diff --git a/source/funkin/data/song/importer/FNFLegacyData.hx b/source/funkin/data/song/importer/FNFLegacyData.hx index 5b75368c9..52380d344 100644 --- a/source/funkin/data/song/importer/FNFLegacyData.hx +++ b/source/funkin/data/song/importer/FNFLegacyData.hx @@ -19,7 +19,8 @@ class LegacySongData @:jcustomparse(funkin.data.DataParse.eitherLegacyScrollSpeeds) public var speed:Either; - public var stageDefault:String; + @:optional + public var stageDefault:Null; public var bpm:Float; @:jcustomparse(funkin.data.DataParse.eitherLegacyNoteData) diff --git a/source/funkin/data/song/importer/FNFLegacyImporter.hx b/source/funkin/data/song/importer/FNFLegacyImporter.hx index ee68513dc..ab2abda8e 100644 --- a/source/funkin/data/song/importer/FNFLegacyImporter.hx +++ b/source/funkin/data/song/importer/FNFLegacyImporter.hx @@ -14,6 +14,7 @@ class FNFLegacyImporter public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):FNFLegacyData { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = true; // Set to true to ignore extra variables that might be included in the JSON. parser.fromJson(input, fileName); if (parser.errors.length > 0) @@ -185,15 +186,34 @@ class FNFLegacyImporter return result; } + static final STRUMLINE_SIZE = 4; + static function migrateNoteSections(input:Array):Array { var result:Array = []; for (section in input) { + var mustHitSection = section.mustHitSection ?? false; for (note in section.sectionNotes) { - result.push(new SongNoteData(note.time, note.data, note.length, note.getKind())); + // Handle the dumb logic for mustHitSection. + var noteData = note.data; + + // Flip notes if mustHitSection is FALSE (not true lol). + if (!mustHitSection) + { + if (noteData >= STRUMLINE_SIZE) + { + noteData -= STRUMLINE_SIZE; + } + else + { + noteData += STRUMLINE_SIZE; + } + } + + result.push(new SongNoteData(note.time, noteData, note.length, note.getKind())); } } diff --git a/tests/unit/source/funkin/data/BaseRegistryTest.hx b/tests/unit/source/funkin/data/BaseRegistryTest.hx index 0be932d35..5f837ba97 100644 --- a/tests/unit/source/funkin/data/BaseRegistryTest.hx +++ b/tests/unit/source/funkin/data/BaseRegistryTest.hx @@ -156,6 +156,7 @@ class MyTypeRegistry extends BaseRegistry // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; switch (loadEntryFile(id)) { @@ -181,6 +182,7 @@ class MyTypeRegistry extends BaseRegistry // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; switch (loadEntryFile(id)) { From 328bb7b938d65406926cf268a2b47fd78fc60870 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:25:15 -0500 Subject: [PATCH 008/129] Fixes to Erect/Nightmare only --- source/funkin/input/Cursor.hx | 12 ++++++++++++ source/funkin/play/stage/StageData.hx | 1 + source/funkin/ui/debug/charting/ChartEditorState.hx | 6 ++++-- .../charting/handlers/ChartEditorDialogHandler.hx | 3 +++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/source/funkin/input/Cursor.hx b/source/funkin/input/Cursor.hx index b4bf43808..39f399465 100644 --- a/source/funkin/input/Cursor.hx +++ b/source/funkin/input/Cursor.hx @@ -34,6 +34,18 @@ class Cursor Cursor.cursorMode = null; } + public static inline function toggle():Void + { + if (FlxG.mouse.visible) + { + hide(); + } + else + { + show(); + } + } + public static final CURSOR_DEFAULT_PARAMS:CursorParams = { graphic: "assets/images/cursor/cursor-default.png", diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index d89995ef3..2d87dec31 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -164,6 +164,7 @@ class StageDataParser try { var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; parser.fromJson(rawJson, '$stageId.json'); if (parser.errors.length > 0) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index fa55750bf..afb0a114d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1010,7 +1010,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function get_availableDifficulties():Array { var m:Null = songMetadata.get(selectedVariation); - return m?.playData?.difficulties ?? []; + return m?.playData?.difficulties ?? [Constants.DEFAULT_DIFFICULTY]; } /** @@ -1069,7 +1069,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var result:Null = songChartData.get(selectedVariation); if (result == null) { - result = new SongChartData(["normal" => 1.0], [], ["normal" => []]); + result = new SongChartData([Constants.DEFAULT_DIFFICULTY => 1.0], [], [Constants.DEFAULT_DIFFICULTY => []]); songChartData.set(selectedVariation, result); } return result; @@ -1293,6 +1293,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function set_selectedDifficulty(value:String):String { + if (value == null) value = availableDifficulties[0] ?? Constants.DEFAULT_DIFFICULTY; + selectedDifficulty = value; // Make sure view is updated when the difficulty changes. diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 666b3656c..a595b8195 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -687,6 +687,9 @@ class ChartEditorDialogHandler Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + state.selectedVariation = Constants.DEFAULT_VARIATION; + state.selectedDifficulty = state.availableDifficulties[0]; + state.difficultySelectDirty = true; dialog.hideDialog(DialogButton.APPLY); From 3e65e7ecc59970d6060bd6f54f6097395d16f24a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:26:26 -0500 Subject: [PATCH 009/129] Fix for vocals not loading properly and not getting cleared properly. --- .../ui/debug/charting/ChartEditorState.hx | 23 +++++++++++++--- .../handlers/ChartEditorAudioHandler.hx | 27 ++++++++++--------- .../handlers/ChartEditorDialogHandler.hx | 14 +++------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index afb0a114d..8369e95b9 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2548,8 +2548,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemPlayPause.onClick = _ -> toggleAudioPlayback(); - menubarItemLoadInstrumental.onClick = _ -> this.openUploadInstDialog(true); - menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true); + menubarItemLoadInstrumental.onClick = _ -> { + var dialog = this.openUploadInstDialog(true); + // Ensure instrumental and vocals are reloaded properly. + dialog.onDialogClosed = function(_) { + this.isHaxeUIDialogOpen = false; + this.switchToCurrentInstrumental(); + this.postLoadInstrumental(); + } + }; + + menubarItemLoadVocals.onClick = _ -> { + var dialog = this.openUploadVocalsDialog(true); + // Ensure instrumental and vocals are reloaded properly. + dialog.onDialogClosed = function(_) { + this.isHaxeUIDialogOpen = false; + this.switchToCurrentInstrumental(); + this.postLoadInstrumental(); + } + }; menubarItemVolumeMetronome.onChange = event -> { var volume:Float = event.value.toFloat() / 100.0; @@ -4047,7 +4064,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } else { - // If we clicked and released outside the grid, do nothing. + // If we clicked and released outside the grid (or on HaxeUI), do nothing. } } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 272291a94..990ab41ae 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -28,11 +28,11 @@ class ChartEditorAudioHandler * @param instId The instrumental this vocal track will be for. * @return Success or failure. */ - public static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = ''):Bool + public static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = '', wipeFirst:Bool = false):Bool { #if sys var fileBytes:Bytes = sys.io.File.getBytes(path.toString()); - return loadVocalsFromBytes(state, fileBytes, charId, instId); + return loadVocalsFromBytes(state, fileBytes, charId, instId, wipeFirst); #else trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); return false; @@ -47,12 +47,12 @@ class ChartEditorAudioHandler * @param instId The instrumental this vocal track will be for. * @return Success or failure. */ - public static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = ''):Bool + public static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = '', wipeFirst:Bool = false):Bool { var trackData:Null = Assets.getBytes(path); if (trackData != null) { - return loadVocalsFromBytes(state, trackData, charId, instId); + return loadVocalsFromBytes(state, trackData, charId, instId, wipeFirst); } return false; } @@ -63,10 +63,12 @@ class ChartEditorAudioHandler * @param bytes The audio byte data. * @param charId The character this vocal track will be for. * @param instId The instrumental this vocal track will be for. + * @param wipeFirst Whether to wipe the existing vocal data before loading. */ - public static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = ''):Bool + public static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = '', wipeFirst:Bool = false):Bool { var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; + if (wipeFirst) wipeVocalData(state); state.audioVocalTrackData.set(trackId, bytes); return true; } @@ -78,11 +80,11 @@ class ChartEditorAudioHandler * @param instId The instrumental this vocal track will be for. * @return Success or failure. */ - public static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = ''):Bool + public static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = '', wipeFirst:Bool = false):Bool { #if sys var fileBytes:Bytes = sys.io.File.getBytes(path.toString()); - return loadInstFromBytes(state, fileBytes, instId); + return loadInstFromBytes(state, fileBytes, instId, wipeFirst); #else trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); return false; @@ -96,12 +98,12 @@ class ChartEditorAudioHandler * @param instId The instrumental this vocal track will be for. * @return Success or failure. */ - public static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = ''):Bool + public static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = '', wipeFirst:Bool = false):Bool { var trackData:Null = Assets.getBytes(path); if (trackData != null) { - return loadInstFromBytes(state, trackData, instId); + return loadInstFromBytes(state, trackData, instId, wipeFirst); } return false; } @@ -113,9 +115,10 @@ class ChartEditorAudioHandler * @param charId The character this vocal track will be for. * @param instId The instrumental this vocal track will be for. */ - public static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = ''):Bool + public static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = '', wipeFirst:Bool = false):Bool { if (instId == '') instId = 'default'; + if (wipeFirst) wipeInstrumentalData(state); state.audioInstTrackData.set(instId, bytes); return true; } @@ -127,9 +130,9 @@ class ChartEditorAudioHandler stopExistingVocals(state); result = playVocals(state, BF, playerId, instId); - if (!result) return false; + // if (!result) return false; result = playVocals(state, DAD, opponentId, instId); - if (!result) return false; + // if (!result) return false; return true; } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index a595b8195..2ede1a39f 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -758,14 +758,9 @@ class ChartEditorDialogHandler trace('Selected file: $pathStr'); var path:Path = new Path(pathStr); - if (!hasClearedVocals) + if (state.loadVocalsFromPath(path, charKey, instId, !hasClearedVocals)) { hasClearedVocals = true; - state.stopExistingVocals(); - } - - if (state.loadVocalsFromPath(path, charKey, instId)) - { // Tell the user the load was successful. state.success('Loaded Vocals', 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}'); #if FILE_DROP_SUPPORTED @@ -799,13 +794,10 @@ class ChartEditorDialogHandler if (selectedFile != null && selectedFile.bytes != null) { trace('Selected file: ' + selectedFile.name); - if (!hasClearedVocals) + + if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId, !hasClearedVocals)) { hasClearedVocals = true; - state.stopExistingVocals(); - } - if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId)) - { // Tell the user the load was successful. state.success('Loaded Vocals', 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}'); From bb0fb0281372c5843995a19cda7a9bba5545783f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:26:43 -0500 Subject: [PATCH 010/129] Fix to launching directly into Chart Editor --- source/funkin/Conductor.hx | 14 +++++++------- .../funkin/ui/debug/charting/ChartEditorState.hx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index c531678ad..7b34bffe2 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -37,7 +37,7 @@ class Conductor /** * The most recent time change for the current song position. */ - public static var currentTimeChange(default, null):SongTimeChange; + public static var currentTimeChange(default, null):Null; /** * The current position in the song in milliseconds. @@ -132,32 +132,32 @@ class Conductor /** * Current position in the song, in measures. */ - public static var currentMeasure(default, null):Int; + public static var currentMeasure(default, null):Int = 0; /** * Current position in the song, in beats. */ - public static var currentBeat(default, null):Int; + public static var currentBeat(default, null):Int = 0; /** * Current position in the song, in steps. */ - public static var currentStep(default, null):Int; + public static var currentStep(default, null):Int = 0; /** * Current position in the song, in measures and fractions of a measure. */ - public static var currentMeasureTime(default, null):Float; + public static var currentMeasureTime(default, null):Float = 0; /** * Current position in the song, in beats and fractions of a measure. */ - public static var currentBeatTime(default, null):Float; + public static var currentBeatTime(default, null):Float = 0; /** * Current position in the song, in steps and fractions of a step. */ - public static var currentStepTime(default, null):Float; + public static var currentStepTime(default, null):Float = 0; /** * An offset tied to the current chart file to compensate for a delay in the instrumental. diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 8369e95b9..de38a8fda 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -4387,7 +4387,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState playbarNoteSnap.text = '1/${noteSnapQuant}'; playbarDifficulty.text = "Difficulty: " + selectedDifficulty.toTitleCase(); - playbarBPM.text = "BPM: " + Conductor.currentTimeChange.bpm; + playbarBPM.text = "BPM: " + (Conductor.currentTimeChange?.bpm ?? 0.0); } function handlePlayhead():Void From ad02bf2ee0c4d0bc8437069ada3d19fd0ab156dc Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:27:58 -0500 Subject: [PATCH 011/129] Fix to GameOverSubstate exiting to Freeplay instead of Chart Editor --- source/funkin/play/GameOverSubState.hx | 24 ++++++++++++++++++++-- source/funkin/play/PlayState.hx | 5 ++++- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- source/funkin/ui/story/StoryMenuState.hx | 2 +- source/funkin/util/Constants.hx | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 6eb53e2d5..18e3e0280 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -64,9 +64,13 @@ class GameOverSubState extends MusicBeatSubState */ var isEnding:Bool = false; - public function new() + var isChartingMode:Bool = false; + + public function new(?params:GameOverParams) { super(); + + this.isChartingMode = params?.isChartingMode ?? false; } /** @@ -176,9 +180,20 @@ class GameOverSubState extends MusicBeatSubState // PlayState.seenCutscene = false; // old thing... gameOverMusic.stop(); - if (PlayStatePlaylist.isStoryMode) FlxG.switchState(new StoryMenuState()); + if (isChartingMode) + { + this.close(); + if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! + PlayState.instance.close(); // This only works because PlayState is a substate! + } + else if (PlayStatePlaylist.isStoryMode) + { + FlxG.switchState(new StoryMenuState()); + } else + { FlxG.switchState(new FreeplayState()); + } } if (gameOverMusic.playing) @@ -307,3 +322,8 @@ class GameOverSubState extends MusicBeatSubState }); } } + +typedef GameOverParams = +{ + var isChartingMode:Bool; +} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index e0932e756..c834e0abe 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -922,7 +922,10 @@ class PlayState extends MusicBeatSubState } #end - var gameOverSubState = new GameOverSubState(); + var gameOverSubState = new GameOverSubState( + { + isChartingMode: isChartingMode + }); FlxTransitionableSubState.skipNextTransIn = true; FlxTransitionableSubState.skipNextTransOut = true; openSubState(gameOverSubState); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 7c69804d9..f17c3d91e 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -97,7 +97,7 @@ class FreeplayState extends MusicBeatSubState var stickerSubState:StickerSubState; // - static var rememberedDifficulty:Null = "normal"; + static var rememberedDifficulty:Null = Constants.DEFAULT_DIFFICULTY; static var rememberedSongId:Null = null; public function new(?stickers:StickerSubState = null) diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 456988873..6e4cdacaf 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -106,7 +106,7 @@ class StoryMenuState extends MusicBeatState var stickerSubState:StickerSubState; static var rememberedLevelId:Null = null; - static var rememberedDifficulty:Null = "normal"; + static var rememberedDifficulty:Null = Constants.DEFAULT_DIFFICULTY; public function new(?stickers:StickerSubState = null) { diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index f8749567b..123267a49 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -123,6 +123,7 @@ class Constants /** * Default list of difficulties for charts. + * Assumes no Erect mode, etc. */ public static final DEFAULT_DIFFICULTY_LIST:Array = ['easy', 'normal', 'hard']; From 70b7de94aa672bd607583c5763bf6e2d8921369c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:28:13 -0500 Subject: [PATCH 012/129] Fix to Metadata toolbox causing crash when no GF :( --- .../toolboxes/ChartEditorMetadataToolbox.hx | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index 509aa5b07..bc9384cf3 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -183,17 +183,41 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox var LIMIT = 6; - var charDataOpponent:CharacterData = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.opponent); - buttonCharacterOpponent.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent); - buttonCharacterOpponent.text = charDataOpponent.name.length > LIMIT ? '${charDataOpponent.name.substr(0, LIMIT)}.' : '${charDataOpponent.name}'; + var charDataOpponent:Null = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.opponent); + if (charDataOpponent != null) + { + buttonCharacterOpponent.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent); + buttonCharacterOpponent.text = charDataOpponent.name.length > LIMIT ? '${charDataOpponent.name.substr(0, LIMIT)}.' : '${charDataOpponent.name}'; + } + else + { + buttonCharacterOpponent.icon = null; + buttonCharacterOpponent.text = "None"; + } - var charDataGirlfriend:CharacterData = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.girlfriend); - buttonCharacterGirlfriend.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend); - buttonCharacterGirlfriend.text = charDataGirlfriend.name.length > LIMIT ? '${charDataGirlfriend.name.substr(0, LIMIT)}.' : '${charDataGirlfriend.name}'; + var charDataGirlfriend:Null = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.girlfriend); + if (charDataGirlfriend != null) + { + buttonCharacterGirlfriend.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend); + buttonCharacterGirlfriend.text = charDataGirlfriend.name.length > LIMIT ? '${charDataGirlfriend.name.substr(0, LIMIT)}.' : '${charDataGirlfriend.name}'; + } + else + { + buttonCharacterGirlfriend.icon = null; + buttonCharacterGirlfriend.text = "None"; + } - var charDataPlayer:CharacterData = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.player); - buttonCharacterPlayer.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player); - buttonCharacterPlayer.text = charDataPlayer.name.length > LIMIT ? '${charDataPlayer.name.substr(0, LIMIT)}.' : '${charDataPlayer.name}'; + var charDataPlayer:Null = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.player); + if (charDataPlayer != null) + { + buttonCharacterPlayer.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player); + buttonCharacterPlayer.text = charDataPlayer.name.length > LIMIT ? '${charDataPlayer.name.substr(0, LIMIT)}.' : '${charDataPlayer.name}'; + } + else + { + buttonCharacterPlayer.icon = null; + buttonCharacterPlayer.text = "None"; + } } public static function build(chartEditorState:ChartEditorState):ChartEditorMetadataToolbox From f01535e43165e9e0f833d6b9c283480af5c26e9d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:28:24 -0500 Subject: [PATCH 013/129] Don't forget to update hmm! --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 96ee75bc1..8a903eed6 100644 --- a/hmm.json +++ b/hmm.json @@ -100,7 +100,7 @@ "name": "json2object", "type": "git", "dir": null, - "ref": "a0a78b60c41e47bae8bfa422488a199a58b4474e", + "ref": "a8c26f18463c98da32f744c214fe02273e1823fa", "url": "https://github.com/FunkinCrew/json2object" }, { From 8fea7eb73427994bda74e04b3193feb11a3d6389 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 01:57:39 -0500 Subject: [PATCH 014/129] Fix a bug where the background of the game over screen is pink --- source/funkin/play/GameOverSubState.hx | 16 +++++++++++++--- source/funkin/play/PlayState.hx | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 6eb53e2d5..dce2229e1 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -64,9 +64,13 @@ class GameOverSubState extends MusicBeatSubState */ var isEnding:Bool = false; - public function new() + var transparent:Bool; + + public function new(params:GameOverParams) { super(); + + transparent = params.transparent; } /** @@ -87,9 +91,10 @@ class GameOverSubState extends MusicBeatSubState // // Add a black background to the screen. - // We make this transparent so that we can see the stage underneath during debugging. var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - bg.alpha = 0.25; + // We make this transparent so that we can see the stage underneath during debugging, + // but it's normally opaque. + bg.alpha = transparent ? 0.25 : 1.0; bg.scrollFactor.set(); add(bg); @@ -307,3 +312,8 @@ class GameOverSubState extends MusicBeatSubState }); } } + +typedef GameOverParams = +{ + var transparent:Bool; +} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index e0932e756..85f60be4e 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -922,7 +922,10 @@ class PlayState extends MusicBeatSubState } #end - var gameOverSubState = new GameOverSubState(); + var gameOverSubState = new GameOverSubState( + { + transparent: persistentDraw, + }); FlxTransitionableSubState.skipNextTransIn = true; FlxTransitionableSubState.skipNextTransOut = true; openSubState(gameOverSubState); From 711253a2023046c97698f8ff7c893574b1513d8c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 19 Dec 2023 16:12:25 -0500 Subject: [PATCH 015/129] Update HaxeUI to latest --- hmm.json | 4 ++-- source/funkin/ui/haxeui/components/CharacterPlayer.hx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hmm.json b/hmm.json index 96ee75bc1..e31a405ae 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "7021f1fbab928268d9196a73e7f47461ca3c3e4d", + "ref": "c8e0cbfef33b955f009023c27c28b161e894b65e", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "26b6bb132c92dfa9b77b4a61eaeda8f9a9efda98", + "ref": "9a1abf4b450118e5222d7456024db31cc3b9f8d1", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/ui/haxeui/components/CharacterPlayer.hx b/source/funkin/ui/haxeui/components/CharacterPlayer.hx index 66b94bfa2..c7171fac7 100644 --- a/source/funkin/ui/haxeui/components/CharacterPlayer.hx +++ b/source/funkin/ui/haxeui/components/CharacterPlayer.hx @@ -35,7 +35,7 @@ class CharacterPlayer extends Box public function new(defaultToBf:Bool = true) { super(); - _overrideSkipTransformChildren = false; + // _overrideSkipTransformChildren = false; if (defaultToBf) { From a4c993f733859ac0d48336aaee2fdfad495fc219 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 23 Dec 2023 16:25:50 -0500 Subject: [PATCH 016/129] Update HaxeUI and Flixel --- hmm.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hmm.json b/hmm.json index e31a405ae..ddaf375c6 100644 --- a/hmm.json +++ b/hmm.json @@ -11,7 +11,7 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "9bdea914f3d0485b9b3ec158f28875b5ac95d476", + "ref": "a83738673e7edbf8acba3a1426af284dfe6719fe", "url": "https://github.com/FunkinCrew/flixel" }, { @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "c8e0cbfef33b955f009023c27c28b161e894b65e", + "ref": "e765a3e0b7a653823e8dec765e04623f27f573f8", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "9a1abf4b450118e5222d7456024db31cc3b9f8d1", + "ref": "7a517d561eff49d8123c128bf9f5c1123b84d014", "url": "https://github.com/haxeui/haxeui-flixel" }, { From 92e0022fedcba609bb1d5320c2cffc6d70708676 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 1 Jan 2024 19:13:51 -0500 Subject: [PATCH 017/129] derp comma! --- source/funkin/play/PlayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ea38d833f..3dcabf953 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -924,8 +924,8 @@ class PlayState extends MusicBeatSubState var gameOverSubState = new GameOverSubState( { - isChartingMode: isChartingMode - transparent: persistentDraw, + isChartingMode: isChartingMode, + transparent: persistentDraw }); FlxTransitionableSubState.skipNextTransIn = true; FlxTransitionableSubState.skipNextTransOut = true; From b1c933283055bfd4c0431f5b820840a18d848a52 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 2 Jan 2024 01:33:10 -0500 Subject: [PATCH 018/129] Fix an issue where hold notes early in a song would not render properly. --- source/funkin/play/notes/SustainTrail.hx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx index ab4bf5f16..7367b97af 100644 --- a/source/funkin/play/notes/SustainTrail.hx +++ b/source/funkin/play/notes/SustainTrail.hx @@ -149,9 +149,9 @@ class SustainTrail extends FlxSprite if (sustainLength == s) return s; height = sustainHeight(s, getScrollSpeed()); - // updateColorTransform(); + this.sustainLength = s; updateClipping(); - return sustainLength = s; + return this.sustainLength; } /** @@ -162,13 +162,15 @@ class SustainTrail extends FlxSprite public function updateClipping(songTime:Float = 0):Void { var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, height); - if (clipHeight == 0) + if (clipHeight <= 0.1) { visible = false; return; } else + { visible = true; + } var bottomHeight:Float = graphic.height * zoom * endOffset; var partHeight:Float = clipHeight - bottomHeight; From ddfecf2cfcf494a9f59abe4709eb3cb60bedafe0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 2 Jan 2024 14:07:07 -0500 Subject: [PATCH 019/129] Clean up difficulty label behavior --- assets | 2 +- .../ui/debug/charting/ChartEditorState.hx | 40 +++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/assets b/assets index 6f17eb051..9ecc4d26f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6f17eb051e2609d59a591d4e6eb78e37c6e90adb +Subproject commit 9ecc4d26fe6b26f31782cccfcd7331bd8a318ce1 diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index bf1b70d55..203bd1508 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2422,6 +2422,23 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); } + playbarDifficulty.onClick = _ -> { + if (FlxG.keys.pressed.CONTROL) + { + this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, true); + } + else + { + incrementDifficulty(-1); + this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); + } + } + + playbarDifficulty.onRightClick = _ -> { + incrementDifficulty(1); + this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); + } + // Add functionality to the menu items. // File @@ -2619,10 +2636,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarLabelPlaybackSpeed.text = 'Playback Speed - ${pitchDisplay}x'; } - playbarDifficulty.onClick = _ -> { - this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, true); - } - menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value); menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value); menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value); @@ -4394,8 +4407,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (playbarSongRemaining.value != songRemainingString) playbarSongRemaining.value = songRemainingString; playbarNoteSnap.text = '1/${noteSnapQuant}'; - playbarDifficulty.text = "Difficulty: " + selectedDifficulty.toTitleCase(); - playbarBPM.text = "BPM: " + (Conductor.currentTimeChange?.bpm ?? 0.0); + playbarDifficulty.text = '${selectedDifficulty.toTitleCase()}'; + // playbarBPM.text = 'BPM: ${(Conductor.currentTimeChange?.bpm ?? 0.0)}'; } function handlePlayhead():Void @@ -4750,16 +4763,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { super.handleQuickWatch(); - FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0); + FlxG.watch.addQuick('musicTime', audioInstTrack?.time); FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels); - FlxG.watch.addQuick("tapNotesRendered", renderedNotes.members.length); - FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes.members.length); - FlxG.watch.addQuick("eventsRendered", renderedEvents.members.length); - FlxG.watch.addQuick("notesSelected", currentNoteSelection.length); - FlxG.watch.addQuick("eventsSelected", currentEventSelection.length); + FlxG.watch.addQuick("tapNotesRendered", renderedNotes?.members?.length); + FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes?.members?.length); + FlxG.watch.addQuick("eventsRendered", renderedEvents?.members?.length); + FlxG.watch.addQuick("notesSelected", currentNoteSelection?.length); + FlxG.watch.addQuick("eventsSelected", currentEventSelection?.length); } function handlePostUpdate():Void @@ -5124,7 +5137,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } - this.success('Switch Difficulty', 'Switched difficulty to ${selectedDifficulty.toTitleCase()}'); + // Removed this notification because you can see your difficulty in the playbar now. + // this.success('Switch Difficulty', 'Switched difficulty to ${selectedDifficulty.toTitleCase()}'); } /** From 200f5702efb57fbca21f8d47edd8f31563b5f7e4 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 3 Jan 2024 02:51:52 -0500 Subject: [PATCH 020/129] disable note vwoosh, and fix gameover song not looping --- source/funkin/play/GameOverSubState.hx | 1 + source/funkin/play/notes/Strumline.hx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index d2ba83191..153fcc7ac 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -289,6 +289,7 @@ class GameOverSubState extends MusicBeatSubState { gameOverMusic.loadEmbedded(musicPath); gameOverMusic.volume = startingVolume; + gameOverMusic.looped = true; gameOverMusic.play(); } } diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 0145dee3f..948e9fa5b 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -274,7 +274,9 @@ class Strumline extends FlxSpriteGroup static function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float { // Make the note move faster visually as it moves offscreen. - var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0; + // var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0; + // ^^^ commented this out... do NOT make it move faster as it moves offscreen! + var vwoosh:Float = 1.0; var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1); From bbaa9aa4afc0101857a16e8e7430ac67e4bc1f1b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Jan 2024 19:53:17 -0500 Subject: [PATCH 021/129] Work in progress on context menus and song event editing. --- assets | 2 +- hmm.json | 4 +- source/funkin/InitState.hx | 7 + source/funkin/data/event/SongEventData.hx | 26 +- source/funkin/data/song/SongData.hx | 17 ++ source/funkin/ui/MusicBeatState.hx | 33 --- .../ui/debug/charting/ChartEditorState.hx | 144 +++++++--- .../charting/commands/SelectItemsCommand.hx | 10 + .../commands/SetItemSelectionCommand.hx | 10 + .../components/ChartEditorEventSprite.hx | 2 - .../ChartEditorBaseContextMenu.hx | 19 ++ .../ChartEditorDefaultContextMenu.hx | 14 + .../ChartEditorEventContextMenu.hx | 32 +++ .../ChartEditorNoteContextMenu.hx | 38 +++ .../ChartEditorSelectionContextMenu.hx | 58 ++++ .../handlers/ChartEditorContextMenuHandler.hx | 64 +++++ .../handlers/ChartEditorToolboxHandler.hx | 182 ++---------- source/funkin/ui/debug/charting/import.hx | 1 + .../toolboxes/ChartEditorEventDataToolbox.hx | 259 ++++++++++++++++++ .../toolboxes/ChartEditorMetadataToolbox.hx | 2 + .../util/plugins/EvacuateDebugPlugin.hx | 35 +++ source/funkin/util/plugins/README.md | 5 + .../util/plugins/ReloadAssetsDebugPlugin.hx | 38 +++ source/funkin/util/plugins/WatchPlugin.hx | 38 +++ 24 files changed, 804 insertions(+), 236 deletions(-) create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx create mode 100644 source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx create mode 100644 source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx create mode 100644 source/funkin/util/plugins/EvacuateDebugPlugin.hx create mode 100644 source/funkin/util/plugins/README.md create mode 100644 source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx create mode 100644 source/funkin/util/plugins/WatchPlugin.hx diff --git a/assets b/assets index 6f17eb051..23f85072c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6f17eb051e2609d59a591d4e6eb78e37c6e90adb +Subproject commit 23f85072c190373592a30aed137b034715623f28 diff --git a/hmm.json b/hmm.json index 57fbbb555..d461edd24 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "e765a3e0b7a653823e8dec765e04623f27f573f8", + "ref": "5086e59e7551d775ed4d1fb0188e31de22d1312b", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "7a517d561eff49d8123c128bf9f5c1123b84d014", + "ref": "2b9cff727999b53ed292b1675ac1c9089ac77600", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 13bcd306e..6c00b6f68 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -197,6 +197,13 @@ class InitState extends FlxState FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK]; #end + // + // FLIXEL PLUGINS + // + funkin.util.plugins.EvacuateDebugPlugin.initialize(); + funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); + funkin.util.plugins.WatchPlugin.initialize(); + // // GAME DATA PARSING // diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventData.hx index 7a167b031..522b9314e 100644 --- a/source/funkin/data/event/SongEventData.hx +++ b/source/funkin/data/event/SongEventData.hx @@ -240,4 +240,28 @@ typedef SongEventSchemaField = ?defaultValue:Dynamic, } -typedef SongEventSchema = Array; +@:forward +abstract SongEventSchema(SongEventSchemaRaw) +{ + public function new(?fields:Array) + { + this = fields; + } + + public function getByName(name:String):SongEventSchemaField + { + for (field in this) + { + if (field.name == name) return field; + } + + return null; + } + + public function getFirstField():SongEventSchemaField + { + return this[0]; + } +} + +typedef SongEventSchemaRaw = Array; diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 600871e2f..90a5f47c3 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -617,6 +617,23 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR this = new SongEventDataRaw(time, event, value); } + public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic + { + if (this.value == null) return {}; + // TODO: How to check if it's a dynamic struct? + if (Std.isOfType(this.value, Int) || Std.isOfType(this.value, String) || Std.isOfType(this.value, Float) || Std.isOfType(this.value, Bool) + || Std.isOfType(this.value, Array)) + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.value); + return cast result; + } + else + { + return cast this.value; + } + } + public inline function getDynamic(key:String):Null { return this.value == null ? null : Reflect.field(this.value, key); diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 077e9e495..f207d48c8 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -71,34 +71,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler } } - function handleFunctionControls():Void - { - // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState()); - - // This can now be used in EVERY STATE YAY! - if (FlxG.keys.justPressed.F5) debug_refreshModules(); - } - - function handleQuickWatch():Void - { - // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPosition", Conductor.songPosition); - FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); - FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); - } - override function update(elapsed:Float) { super.update(elapsed); handleControls(); - handleFunctionControls(); - handleQuickWatch(); dispatchEvent(new UpdateScriptEvent(elapsed)); } @@ -127,16 +104,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler ModuleHandler.callEvent(event); } - function debug_refreshModules() - { - PolymodHandler.forceReloadAssets(); - - this.destroy(); - - // Create a new instance of the current state, so old data is cleared. - FlxG.resetState(); - } - public function stepHit():Bool { var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index bf1b70d55..294c230d5 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -147,7 +147,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Layouts public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); - public static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); + public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties'); public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); @@ -489,17 +489,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The note kind to use for notes being placed in the chart. Defaults to `''`. */ - var selectedNoteKind:String = ''; + var noteKindToPlace:String = ''; /** * The event type to use for events being placed in the chart. Defaults to `''`. */ - var selectedEventKind:String = 'FocusCamera'; + var eventKindToPlace:String = 'FocusCamera'; /** * The event data to use for events being placed in the chart. */ - var selectedEventData:DynamicAccess = {}; + var eventDataToPlace:DynamicAccess = {}; /** * The internal index of what note snapping value is in use. @@ -1871,6 +1871,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Setup the onClick listeners for the UI after it's been created. setupUIListeners(); + setupContextMenu(); setupTurboKeyHandlers(); setupAutoSave(); @@ -2444,23 +2445,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemUndo.onClick = _ -> undoLastCommand(); menubarItemRedo.onClick = _ -> redoLastCommand(); menubarItemCopy.onClick = function(_) { - // Doesn't use a command because it's not undoable. - - // Calculate a single time offset for all the notes and events. - var timeOffset:Null = currentNoteSelection.length > 0 ? Std.int(currentNoteSelection[0].time) : null; - if (currentEventSelection.length > 0) - { - if (timeOffset == null || currentEventSelection[0].time < timeOffset) - { - timeOffset = Std.int(currentEventSelection[0].time); - } - } - - SongDataUtils.writeItemsToClipboard( - { - notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset), - events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset), - }); + copySelection(); }; menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)); @@ -2626,7 +2611,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value); menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value); menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value); - menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value); + menubarItemToggleToolboxEventData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT, event.value); menubarItemToggleToolboxPlaytestProperties.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT, event.value); menubarItemToggleToolboxPlayerPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value); menubarItemToggleToolboxOpponentPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value); @@ -2635,6 +2620,42 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // registerContextMenu(null, Paths.ui('chart-editor/context/test')); } + function setupContextMenu():Void + { + Screen.instance.registerEvent(MouseEvent.RIGHT_MOUSE_UP, function(e:MouseEvent) { + var xPos = e.screenX; + var yPos = e.screenY; + onContextMenu(xPos, yPos); + }); + } + + function onContextMenu(xPos:Float, yPos:Float) + { + trace('User right clicked to open menu at (${xPos}, ${yPos})'); + // this.openDefaultContextMenu(xPos, yPos); + } + + function copySelection():Void + { + // Doesn't use a command because it's not undoable. + + // Calculate a single time offset for all the notes and events. + var timeOffset:Null = currentNoteSelection.length > 0 ? Std.int(currentNoteSelection[0].time) : null; + if (currentEventSelection.length > 0) + { + if (timeOffset == null || currentEventSelection[0].time < timeOffset) + { + timeOffset = Std.int(currentEventSelection[0].time); + } + } + + SongDataUtils.writeItemsToClipboard( + { + notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset), + events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset), + }); + } + /** * Initialize TurboKeyHandlers and add them to the state (so `update()` is called) * We can then probe `keyHandler.activated` to see if the key combo's action should be taken. @@ -2812,6 +2833,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState playMetronomeTick(Conductor.currentBeat % 4 == 0); } + // Show the mouse cursor. + // Just throwing this somewhere convenient and infrequently called because sometimes Flixel's debug thing hides the cursor. + Cursor.show(); + return true; } @@ -3015,6 +3040,8 @@ 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); } else { @@ -3471,6 +3498,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // trace('shouldHandleCursor: $shouldHandleCursor'); + // TODO: TBH some of this should be using FlxMouseEventManager... + if (shouldHandleCursor) { // Over the course of this big conditional block, @@ -4054,14 +4083,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData.clone()); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone()); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind.clone()); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone()); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); @@ -4099,13 +4128,52 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (highlightedNote != null && highlightedNote.noteData != null) { // TODO: Handle the case of clicking on a sustain piece. - // Remove the note. - performCommand(new RemoveNotesCommand([highlightedNote.noteData])); + if (FlxG.keys.pressed.SHIFT) + { + // Shift + Right click opens the context menu. + // If we are clicking a large selection, open the Selection context menu, otherwise open the Note context menu. + var isHighlightedNoteSelected:Bool = isNoteSelected(highlightedNote.noteData); + var useSingleNoteContextMenu:Bool = (!isHighlightedNoteSelected) + || (isHighlightedNoteSelected && currentNoteSelection.length == 1); + // Show the context menu connected to the note. + if (useSingleNoteContextMenu) + { + this.openNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedNote.noteData); + } + else + { + this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + } + else + { + // Right click removes the note. + performCommand(new RemoveNotesCommand([highlightedNote.noteData])); + } } else if (highlightedEvent != null && highlightedEvent.eventData != null) { - // Remove the event. - performCommand(new RemoveEventsCommand([highlightedEvent.eventData])); + if (FlxG.keys.pressed.SHIFT) + { + // Shift + Right click opens the context menu. + // If we are clicking a large selection, open the Selection context menu, otherwise open the Event context menu. + var isHighlightedEventSelected:Bool = isEventSelected(highlightedEvent.eventData); + var useSingleEventContextMenu:Bool = (!isHighlightedEventSelected) + || (isHighlightedEventSelected && currentEventSelection.length == 1); + if (useSingleEventContextMenu) + { + this.openEventContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedEvent.eventData); + } + else + { + this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + } + else + { + // Right click removes the event. + performCommand(new RemoveEventsCommand([highlightedEvent.eventData])); + } } else { @@ -4126,11 +4194,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridGhostEvent == null) throw "ERROR: Tried to handle cursor, but gridGhostEvent is null! Check ChartEditorState.buildGrid()"; - var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, selectedEventKind, null); + var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null); - if (selectedEventKind != eventData.event) + if (eventKindToPlace != eventData.event) { - eventData.event = selectedEventKind; + eventData.event = eventKindToPlace; } eventData.time = cursorSnappedMs; @@ -4146,11 +4214,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()"; - var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind); + var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace); - if (cursorColumn != noteData.data || selectedNoteKind != noteData.kind) + if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind) { - noteData.kind = selectedNoteKind; + noteData.kind = noteKindToPlace; noteData.data = cursorColumn; gridGhostNote.playNoteAnimation(); } @@ -4443,7 +4511,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (notesAtPos.length == 0) { - var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, selectedNoteKind); + var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); } else @@ -4746,10 +4814,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState #end } - override function handleQuickWatch():Void + function handleQuickWatch():Void { - super.handleQuickWatch(); - FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0); FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); @@ -5487,6 +5553,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState cleanupAutoSave(); + this.closeAllMenus(); + // Hide the mouse cursor on other states. Cursor.hide(); diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index abe8b9e35..f59672646 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -33,6 +33,16 @@ class SelectItemsCommand implements ChartEditorCommand state.currentEventSelection.push(event); } + // 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) + { + var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; + var eventData = eventSelected.valueAsStruct(); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); + } + state.noteDisplayDirty = true; state.notePreviewDirty = true; } diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index a06aefabc..7dc344835 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -30,6 +30,16 @@ class SetItemSelectionCommand implements ChartEditorCommand state.currentNoteSelection = notes; 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) + { + var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; + var eventData = eventSelected.valueAsStruct(); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); + } + state.noteDisplayDirty = true; } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 4c9d91407..e5bbf0807 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -145,8 +145,6 @@ class ChartEditorEventSprite extends FlxSprite else { this.visible = true; - // Only play the animation if the event type has changed. - // if (this.eventData == null || this.eventData.event != value.event) playAnimation(value.event); this.eventData = value; // Update the position to match the note data. diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx new file mode 100644 index 000000000..f25f3ebb3 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx @@ -0,0 +1,19 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; + +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorBaseContextMenu extends Menu +{ + var chartEditorState:ChartEditorState; + + public function new(chartEditorState:ChartEditorState, xPos:Float = 0, yPos:Float = 0) + { + super(); + + this.chartEditorState = chartEditorState; + + this.left = xPos; + this.top = yPos; + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx new file mode 100644 index 000000000..9529cc2fd --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx @@ -0,0 +1,14 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.core.Screen; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/default.xml")) +class ChartEditorDefaultContextMenu extends ChartEditorBaseContextMenu +{ + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0) + { + super(chartEditorState2, xPos2, yPos2); + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx new file mode 100644 index 000000000..a79125b21 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx @@ -0,0 +1,32 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongEventData; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/event.xml")) +class ChartEditorEventContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuEdit:MenuItem; + var contextmenuDelete:MenuItem; + + var data:SongEventData; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongEventData) + { + super(chartEditorState2, xPos2, yPos2); + this.data = data; + + initialize(); + } + + function initialize() + { + contextmenuDelete.onClick = function(_) { + chartEditorState.performCommand(new RemoveEventsCommand([data])); + } + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx new file mode 100644 index 000000000..4bfab27e8 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx @@ -0,0 +1,38 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongNoteData; +import funkin.ui.debug.charting.commands.FlipNotesCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/note.xml")) +class ChartEditorNoteContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuFlip:MenuItem; + var contextmenuDelete:MenuItem; + + var data:SongNoteData; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongNoteData) + { + super(chartEditorState2, xPos2, yPos2); + this.data = data; + + initialize(); + } + + function initialize():Void + { + // NOTE: Remember to use commands here to ensure undo/redo works properly + contextmenuFlip.onClick = function(_) { + chartEditorState.performCommand(new FlipNotesCommand([data])); + } + + contextmenuDelete.onClick = function(_) { + chartEditorState.performCommand(new RemoveNotesCommand([data])); + } + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx new file mode 100644 index 000000000..feed9b689 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx @@ -0,0 +1,58 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; +import funkin.ui.debug.charting.commands.RemoveItemsCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/selection.xml")) +class ChartEditorSelectionContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuCut:MenuItem; + var contextmenuCopy:MenuItem; + var contextmenuPaste:MenuItem; + var contextmenuDelete:MenuItem; + var contextmenuFlip:MenuItem; + var contextmenuSelectAll:MenuItem; + var contextmenuSelectInverse:MenuItem; + var contextmenuSelectNone:MenuItem; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0) + { + super(chartEditorState2, xPos2, yPos2); + + initialize(); + } + + function initialize():Void + { + contextmenuCut.onClick = (_) -> { + chartEditorState.performCommand(new CutItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection)); + }; + contextmenuCopy.onClick = (_) -> { + chartEditorState.copySelection(); + }; + contextmenuFlip.onClick = (_) -> { + if (chartEditorState.currentNoteSelection.length > 0 && chartEditorState.currentEventSelection.length > 0) + { + chartEditorState.performCommand(new RemoveItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection)); + } + else if (chartEditorState.currentNoteSelection.length > 0) + { + chartEditorState.performCommand(new RemoveNotesCommand(chartEditorState.currentNoteSelection)); + } + else if (chartEditorState.currentEventSelection.length > 0) + { + chartEditorState.performCommand(new RemoveEventsCommand(chartEditorState.currentEventSelection)); + } + else + { + // Do nothing??? + } + }; + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx new file mode 100644 index 000000000..b914f4149 --- /dev/null +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx @@ -0,0 +1,64 @@ +package funkin.ui.debug.charting.handlers; + +import funkin.ui.debug.charting.contextmenus.ChartEditorDefaultContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorEventContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorNoteContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorSelectionContextMenu; +import haxe.ui.containers.menus.Menu; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongEventData; + +/** + * Handles context menus (the little menus that appear when you right click on stuff) for the new Chart Editor. + */ +@:nullSafety +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorContextMenuHandler +{ + static var existingMenus:Array = []; + + public static function openDefaultContextMenu(state:ChartEditorState, xPos:Float, yPos:Float) + { + displayMenu(state, new ChartEditorDefaultContextMenu(state, xPos, yPos)); + } + + public static function openSelectionContextMenu(state:ChartEditorState, xPos:Float, yPos:Float) + { + displayMenu(state, new ChartEditorSelectionContextMenu(state, xPos, yPos)); + } + + public static function openNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData) + { + displayMenu(state, new ChartEditorNoteContextMenu(state, xPos, yPos, data)); + } + + public static function openEventContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongEventData) + { + displayMenu(state, new ChartEditorEventContextMenu(state, xPos, yPos, data)); + } + + static function displayMenu(state:ChartEditorState, targetMenu:Menu) + { + // Close any existing menus + closeAllMenus(state); + + // Show the new menu + Screen.instance.addComponent(targetMenu); + existingMenus.push(targetMenu); + } + + public static function closeMenu(state:ChartEditorState, targetMenu:Menu) + { + // targetMenu.close(); + existingMenus.remove(targetMenu); + } + + public static function closeAllMenus(state:ChartEditorState) + { + for (existingMenu in existingMenus) + { + closeMenu(state, existingMenu); + } + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index a9a9c375d..08298eb66 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -23,6 +23,7 @@ import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.haxeui.components.CharacterPlayer; import funkin.util.FileUtil; import haxe.ui.components.Button; +import haxe.ui.data.ArrayDataSource; import haxe.ui.components.CheckBox; import haxe.ui.components.DropDown; import haxe.ui.components.HorizontalSlider; @@ -36,12 +37,12 @@ import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialog.DialogEvent; import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox; +import funkin.ui.debug.charting.toolboxes.ChartEditorEventDataToolbox; import haxe.ui.containers.Frame; import haxe.ui.containers.Grid; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import haxe.ui.core.Component; -import haxe.ui.data.ArrayDataSource; import haxe.ui.events.UIEvent; /** @@ -79,8 +80,9 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: onShowToolboxNoteData(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: - onShowToolboxEventData(state, toolbox); + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: + // TODO: Fix this. + cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onShowToolboxPlaytestProperties(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: @@ -119,7 +121,7 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: onHideToolboxNoteData(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: onHideToolboxEventData(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onHideToolboxPlaytestProperties(state, toolbox); @@ -195,7 +197,7 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: toolbox = buildToolboxNoteDataLayout(state); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: toolbox = buildToolboxEventDataLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: toolbox = buildToolboxPlaytestPropertiesLayout(state); @@ -283,19 +285,19 @@ class ChartEditorToolboxHandler toolboxNotesCustomKindLabel.hidden = false; toolboxNotesCustomKind.hidden = false; - state.selectedNoteKind = toolboxNotesCustomKind.text; + state.noteKindToPlace = toolboxNotesCustomKind.text; } else { toolboxNotesCustomKindLabel.hidden = true; toolboxNotesCustomKind.hidden = true; - state.selectedNoteKind = event.data.id; + state.noteKindToPlace = event.data.id; } } toolboxNotesCustomKind.onChange = function(event:UIEvent) { - state.selectedNoteKind = toolboxNotesCustomKind.text; + state.noteKindToPlace = toolboxNotesCustomKind.text; } return toolbox; @@ -305,159 +307,16 @@ class ChartEditorToolboxHandler static function onHideToolboxNoteData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxEventDataLayout(state:ChartEditorState):Null - { - var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT); - - if (toolbox == null) return null; - - // Starting position. - toolbox.x = 100; - toolbox.y = 150; - - toolbox.onDialogClosed = function(event:DialogEvent) { - state.menubarItemToggleToolboxEvents.selected = false; - } - - var toolboxEventsEventKind:Null = toolbox.findComponent('toolboxEventsEventKind', DropDown); - if (toolboxEventsEventKind == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsEventKind component.'; - var toolboxEventsDataGrid:Null = toolbox.findComponent('toolboxEventsDataGrid', Grid); - if (toolboxEventsDataGrid == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsDataGrid component.'; - - toolboxEventsEventKind.dataSource = new ArrayDataSource(); - - var songEvents:Array = SongEventParser.listEvents(); - - for (event in songEvents) - { - toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); - } - - toolboxEventsEventKind.onChange = function(event:UIEvent) { - var eventType:String = event.data.value; - - trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); - - state.selectedEventKind = eventType; - - var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); - - if (schema == null) - { - trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: $eventType'); - return; - } - - buildEventDataFormFromSchema(state, toolboxEventsDataGrid, schema); - } - toolboxEventsEventKind.value = state.selectedEventKind; - - return toolbox; - } - - static function onShowToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - - static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + + static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildEventDataFormFromSchema(state:ChartEditorState, target:Box, schema:SongEventSchema):Void - { - trace(schema); - // Clear the frame. - target.removeAllComponents(); - - state.selectedEventData = {}; - - for (field in schema) - { - if (field == null) continue; - - // Add a label. - var label:Label = new Label(); - label.text = field.title; - label.verticalAlign = "center"; - target.addComponent(label); - - var input:Component; - switch (field.type) - { - case INTEGER: - var numberStepper:NumberStepper = new NumberStepper(); - numberStepper.id = field.name; - numberStepper.step = field.step ?? 1.0; - numberStepper.min = field.min ?? 0.0; - numberStepper.max = field.max ?? 10.0; - if (field.defaultValue != null) numberStepper.value = field.defaultValue; - input = numberStepper; - case FLOAT: - var numberStepper:NumberStepper = new NumberStepper(); - numberStepper.id = field.name; - numberStepper.step = field.step ?? 0.1; - if (field.min != null) numberStepper.min = field.min; - if (field.max != null) numberStepper.max = field.max; - if (field.defaultValue != null) numberStepper.value = field.defaultValue; - input = numberStepper; - case BOOL: - var checkBox:CheckBox = new CheckBox(); - checkBox.id = field.name; - if (field.defaultValue != null) checkBox.selected = field.defaultValue; - input = checkBox; - case ENUM: - var dropDown:DropDown = new DropDown(); - dropDown.id = field.name; - dropDown.width = 200.0; - dropDown.dataSource = new ArrayDataSource(); - - if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; - - // Add entries to the dropdown. - - for (optionName in field.keys.keys()) - { - var optionValue:Null = field.keys.get(optionName); - trace('$optionName : $optionValue'); - dropDown.dataSource.add({value: optionValue, text: optionName}); - } - - dropDown.value = field.defaultValue; - - input = dropDown; - case STRING: - input = new TextField(); - input.id = field.name; - if (field.defaultValue != null) input.text = field.defaultValue; - default: - // Unknown type. Display a label so we know what it is. - input = new Label(); - input.id = field.name; - input.text = field.type; - } - - target.addComponent(input); - - input.onChange = function(event:UIEvent) { - var value = event.target.value; - if (field.type == ENUM) - { - value = event.target.value.value; - } - trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); - - if (value == null) - { - state.selectedEventData.remove(event.target.id); - } - else - { - state.selectedEventData.set(event.target.id, value); - } - } - } - } - static function buildToolboxPlaytestPropertiesLayout(state:ChartEditorState):Null { // fill with playtest properties @@ -576,8 +435,6 @@ class ChartEditorToolboxHandler trace('selected node: ${treeView.selectedNode}'); } - static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxMetadataLayout(state:ChartEditorState):Null { var toolbox:ChartEditorBaseToolbox = ChartEditorMetadataToolbox.build(state); @@ -587,7 +444,14 @@ class ChartEditorToolboxHandler return toolbox; } - static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function buildToolboxEventDataLayout(state:ChartEditorState):Null + { + var toolbox:ChartEditorBaseToolbox = ChartEditorEventDataToolbox.build(state); + + if (toolbox == null) return null; + + return toolbox; + } static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null { diff --git a/source/funkin/ui/debug/charting/import.hx b/source/funkin/ui/debug/charting/import.hx index 933eaa3a5..b0569e3bb 100644 --- a/source/funkin/ui/debug/charting/import.hx +++ b/source/funkin/ui/debug/charting/import.hx @@ -3,6 +3,7 @@ package funkin.ui.debug.charting; #if !macro // Apply handlers so they can be called as though they were functions in ChartEditorState using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler; +using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler; using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler; using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler; using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx new file mode 100644 index 000000000..7066fc913 --- /dev/null +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -0,0 +1,259 @@ +package funkin.ui.debug.charting.toolboxes; + +import funkin.play.character.BaseCharacter.CharacterType; +import funkin.play.character.CharacterData; +import funkin.play.stage.StageData; +import funkin.play.event.SongEvent; +import funkin.data.event.SongEventData.SongEventSchema; +import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; +import funkin.ui.debug.charting.util.ChartEditorDropdowns; +import haxe.ui.components.Button; +import haxe.ui.components.CheckBox; +import haxe.ui.components.DropDown; +import haxe.ui.components.HorizontalSlider; +import haxe.ui.components.Label; +import haxe.ui.components.NumberStepper; +import haxe.ui.components.Slider; +import haxe.ui.core.Component; +import funkin.data.event.SongEventData.SongEventParser; +import haxe.ui.components.TextField; +import haxe.ui.containers.Box; +import haxe.ui.containers.Frame; +import haxe.ui.events.UIEvent; +import haxe.ui.data.ArrayDataSource; +import haxe.ui.containers.Grid; +import haxe.ui.components.DropDown; +import haxe.ui.containers.Frame; + +/** + * The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM. + */ +// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros. +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/event-data.xml")) +class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox +{ + var toolboxEventsEventKind:DropDown; + var toolboxEventsDataFrame:Frame; + var toolboxEventsDataGrid:Grid; + + var _initializing:Bool = true; + + public function new(chartEditorState2:ChartEditorState) + { + super(chartEditorState2); + + initialize(); + + this.onDialogClosed = onClose; + + this._initializing = false; + } + + function onClose(event:UIEvent) + { + chartEditorState.menubarItemToggleToolboxEventData.selected = false; + } + + function initialize():Void + { + toolboxEventsEventKind.dataSource = new ArrayDataSource(); + + var songEvents:Array = SongEventParser.listEvents(); + + for (event in songEvents) + { + toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); + } + + toolboxEventsEventKind.onChange = function(event:UIEvent) { + var eventType:String = event.data.value; + + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); + + // Edit the event data to place. + chartEditorState.eventKindToPlace = eventType; + + var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); + + if (schema == null) + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: $eventType'); + return; + } + + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); + + if (!_initializing && chartEditorState.currentEventSelection.length > 0) + { + // Edit the event data of any selected events. + for (event in chartEditorState.currentEventSelection) + { + event.event = chartEditorState.eventKindToPlace; + event.value = chartEditorState.eventDataToPlace; + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + } + } + toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + } + + public override function refresh():Void + { + super.refresh(); + + toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + + for (pair in chartEditorState.eventDataToPlace.keyValueIterator()) + { + var fieldId:String = pair.key; + var value:Null = pair.value; + + var field:Component = toolboxEventsDataGrid.findComponent(fieldId); + + if (field == null) + { + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form.'; + } + else + { + switch (field) + { + case Std.isOfType(_, NumberStepper) => true: + var numberStepper:NumberStepper = cast field; + numberStepper.value = value; + case Std.isOfType(_, CheckBox) => true: + var checkBox:CheckBox = cast field; + checkBox.selected = value; + case Std.isOfType(_, DropDown) => true: + var dropDown:DropDown = cast field; + dropDown.value = value; + case Std.isOfType(_, TextField) => true: + var textField:TextField = cast field; + textField.text = value; + default: + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" is of unknown type "${Type.getClassName(Type.getClass(field))}".'; + } + } + } + } + + function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema):Void + { + trace(schema); + // Clear the frame. + target.removeAllComponents(); + + chartEditorState.eventDataToPlace = {}; + + for (field in schema) + { + if (field == null) continue; + + // Add a label for the data field. + var label:Label = new Label(); + label.text = field.title; + label.verticalAlign = "center"; + target.addComponent(label); + + // Add an input field for the data field. + var input:Component; + switch (field.type) + { + case INTEGER: + var numberStepper:NumberStepper = new NumberStepper(); + numberStepper.id = field.name; + numberStepper.step = field.step ?? 1.0; + numberStepper.min = field.min ?? 0.0; + numberStepper.max = field.max ?? 10.0; + if (field.defaultValue != null) numberStepper.value = field.defaultValue; + input = numberStepper; + case FLOAT: + var numberStepper:NumberStepper = new NumberStepper(); + numberStepper.id = field.name; + numberStepper.step = field.step ?? 0.1; + if (field.min != null) numberStepper.min = field.min; + if (field.max != null) numberStepper.max = field.max; + if (field.defaultValue != null) numberStepper.value = field.defaultValue; + input = numberStepper; + case BOOL: + var checkBox:CheckBox = new CheckBox(); + checkBox.id = field.name; + if (field.defaultValue != null) checkBox.selected = field.defaultValue; + input = checkBox; + case ENUM: + var dropDown:DropDown = new DropDown(); + dropDown.id = field.name; + dropDown.width = 200.0; + dropDown.dataSource = new ArrayDataSource(); + + if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; + + // Add entries to the dropdown. + + for (optionName in field.keys.keys()) + { + var optionValue:Null = field.keys.get(optionName); + trace('$optionName : $optionValue'); + dropDown.dataSource.add({value: optionValue, text: optionName}); + } + + dropDown.value = field.defaultValue; + + input = dropDown; + case STRING: + input = new TextField(); + input.id = field.name; + if (field.defaultValue != null) input.text = field.defaultValue; + default: + // Unknown type. Display a label that proclaims the type so we can debug it. + input = new Label(); + input.id = field.name; + input.text = field.type; + } + + target.addComponent(input); + + // Update the value of the event data. + input.onChange = function(event:UIEvent) { + var value = event.target.value; + if (field.type == ENUM) + { + value = event.target.value.value; + } + + trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); + + // Edit the event data to place. + if (value == null) + { + chartEditorState.eventDataToPlace.remove(event.target.id); + } + else + { + chartEditorState.eventDataToPlace.set(event.target.id, value); + } + + // Edit the event data of any existing events. + if (!_initializing && chartEditorState.currentEventSelection.length > 0) + { + for (event in chartEditorState.currentEventSelection) + { + event.event = chartEditorState.eventKindToPlace; + event.value = chartEditorState.eventDataToPlace; + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + } + } + } + } + + public static function build(chartEditorState:ChartEditorState):ChartEditorEventDataToolbox + { + return new ChartEditorEventDataToolbox(chartEditorState); + } +} diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index bc9384cf3..3f3c825ce 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -162,6 +162,8 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox public override function refresh():Void { + super.refresh(); + inputSongName.value = chartEditorState.currentSongMetadata.songName; inputSongArtist.value = chartEditorState.currentSongMetadata.artist; inputStage.value = chartEditorState.currentSongMetadata.playData.stage; diff --git a/source/funkin/util/plugins/EvacuateDebugPlugin.hx b/source/funkin/util/plugins/EvacuateDebugPlugin.hx new file mode 100644 index 000000000..1803c25ba --- /dev/null +++ b/source/funkin/util/plugins/EvacuateDebugPlugin.hx @@ -0,0 +1,35 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `F4` to immediately transition to the main menu. + * This is useful for debugging or if you get softlocked or something. + */ +class EvacuateDebugPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new EvacuateDebugPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(new funkin.ui.mainmenu.MainMenuState()); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/plugins/README.md b/source/funkin/util/plugins/README.md new file mode 100644 index 000000000..fe87d36e5 --- /dev/null +++ b/source/funkin/util/plugins/README.md @@ -0,0 +1,5 @@ +# funkin.util.plugins + +Flixel plugins are objects with `update()` functions that are called from every state. + +See: https://github.com/HaxeFlixel/flixel/blob/dev/flixel/system/frontEnds/PluginFrontEnd.hx diff --git a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx new file mode 100644 index 000000000..a43317cce --- /dev/null +++ b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx @@ -0,0 +1,38 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state. + * This is useful for hot reloading assets during development. + */ +class ReloadAssetsDebugPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ReloadAssetsDebugPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.F5) + { + funkin.modding.PolymodHandler.forceReloadAssets(); + + // Create a new instance of the current state, so old data is cleared. + FlxG.resetState(); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx new file mode 100644 index 000000000..5d718c2e0 --- /dev/null +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -0,0 +1,38 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to display several universally important values + * in the Flixel variable watch window. + */ +class WatchPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new WatchPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + FlxG.watch.addQuick("songPosition", Conductor.songPosition); + FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); + FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0); + FlxG.watch.addQuick("bpm", Conductor.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.currentMeasureTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + } + + public override function destroy():Void + { + super.destroy(); + } +} From 07bd2e44cd7f996cc0f2ec6ecc0426899cb944d5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Jan 2024 20:12:46 -0500 Subject: [PATCH 022/129] Fix a funny bug when restarting after game over --- source/funkin/play/GameOverSubState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 153fcc7ac..dadd5a3d9 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -289,7 +289,7 @@ class GameOverSubState extends MusicBeatSubState { gameOverMusic.loadEmbedded(musicPath); gameOverMusic.volume = startingVolume; - gameOverMusic.looped = true; + gameOverMusic.looped = !isEnding; gameOverMusic.play(); } } From edc6f85e214b88427f789c867f573cb970a36dde Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Jan 2024 21:10:14 -0500 Subject: [PATCH 023/129] Finish up event editing (selecting an existing event now shows its data in the event data toolbox) --- source/funkin/InitState.hx | 4 +- ...{SongEventData.hx => SongEventRegistry.hx} | 109 +-------------- source/funkin/data/event/SongEventSchema.hx | 125 ++++++++++++++++++ source/funkin/data/song/SongData.hx | 20 ++- source/funkin/modding/PolymodHandler.hx | 4 +- source/funkin/play/PlayState.hx | 8 +- .../funkin/play/event/FocusCameraSongEvent.hx | 8 +- .../play/event/PlayAnimationSongEvent.hx | 8 +- .../play/event/SetCameraBopSongEvent.hx | 8 +- source/funkin/play/event/SongEvent.hx | 2 +- .../funkin/play/event/ZoomCameraSongEvent.hx | 8 +- .../charting/commands/SelectItemsCommand.hx | 18 ++- .../commands/SetItemSelectionCommand.hx | 18 ++- .../components/ChartEditorEventSprite.hx | 6 +- .../handlers/ChartEditorToolboxHandler.hx | 2 +- .../toolboxes/ChartEditorEventDataToolbox.hx | 8 +- 16 files changed, 210 insertions(+), 146 deletions(-) rename source/funkin/data/event/{SongEventData.hx => SongEventRegistry.hx} (66%) create mode 100644 source/funkin/data/event/SongEventSchema.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 6c00b6f68..02b46c88c 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -19,7 +19,7 @@ import funkin.play.PlayStatePlaylist; import openfl.display.BitmapData; import funkin.data.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventRegistry; import funkin.play.cutscene.dialogue.ConversationDataParser; import funkin.play.cutscene.dialogue.DialogueBoxDataParser; import funkin.play.cutscene.dialogue.SpeakerDataParser; @@ -213,7 +213,7 @@ class InitState extends FlxState SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries(); - SongEventParser.loadEventCache(); + SongEventRegistry.loadEventCache(); ConversationDataParser.loadConversationCache(); DialogueBoxDataParser.loadDialogueBoxCache(); SpeakerDataParser.loadSpeakerCache(); diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventRegistry.hx similarity index 66% rename from source/funkin/data/event/SongEventData.hx rename to source/funkin/data/event/SongEventRegistry.hx index 522b9314e..dc5589813 100644 --- a/source/funkin/data/event/SongEventData.hx +++ b/source/funkin/data/event/SongEventRegistry.hx @@ -1,7 +1,7 @@ package funkin.data.event; import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventSchema; +import funkin.data.event.SongEventSchema; import funkin.data.song.SongData.SongEventData; import funkin.util.macro.ClassMacro; import funkin.play.event.ScriptedSongEvent; @@ -9,7 +9,7 @@ import funkin.play.event.ScriptedSongEvent; /** * This class statically handles the parsing of internal and scripted song event handlers. */ -class SongEventParser +class SongEventRegistry { /** * Every built-in event class must be added to this list. @@ -160,108 +160,3 @@ class SongEventParser } } } - -enum abstract SongEventFieldType(String) from String to String -{ - /** - * The STRING type will display as a text field. - */ - var STRING = "string"; - - /** - * The INTEGER type will display as a text field that only accepts numbers. - */ - var INTEGER = "integer"; - - /** - * The FLOAT type will display as a text field that only accepts numbers. - */ - var FLOAT = "float"; - - /** - * The BOOL type will display as a checkbox. - */ - var BOOL = "bool"; - - /** - * The ENUM type will display as a dropdown. - * Make sure to specify the `keys` field in the schema. - */ - var ENUM = "enum"; -} - -typedef SongEventSchemaField = -{ - /** - * The name of the property as it should be saved in the event data. - */ - name:String, - - /** - * The title of the field to display in the UI. - */ - title:String, - - /** - * The type of the field. - */ - type:SongEventFieldType, - - /** - * Used only for ENUM values. - * The key is the display name and the value is the actual value. - */ - ?keys:Map, - - /** - * Used for INTEGER and FLOAT values. - * The minimum value that can be entered. - * @default No minimum - */ - ?min:Float, - - /** - * Used for INTEGER and FLOAT values. - * The maximum value that can be entered. - * @default No maximum - */ - ?max:Float, - - /** - * Used for INTEGER and FLOAT values. - * The step value that will be used when incrementing/decrementing the value. - * @default `0.1` - */ - ?step:Float, - - /** - * An optional default value for the field. - */ - ?defaultValue:Dynamic, -} - -@:forward -abstract SongEventSchema(SongEventSchemaRaw) -{ - public function new(?fields:Array) - { - this = fields; - } - - public function getByName(name:String):SongEventSchemaField - { - for (field in this) - { - if (field.name == name) return field; - } - - return null; - } - - public function getFirstField():SongEventSchemaField - { - return this[0]; - } -} - -typedef SongEventSchemaRaw = Array; diff --git a/source/funkin/data/event/SongEventSchema.hx b/source/funkin/data/event/SongEventSchema.hx new file mode 100644 index 000000000..b5b2978d7 --- /dev/null +++ b/source/funkin/data/event/SongEventSchema.hx @@ -0,0 +1,125 @@ +package funkin.data.event; + +import funkin.play.event.SongEvent; +import funkin.data.event.SongEventSchema; +import funkin.data.song.SongData.SongEventData; +import funkin.util.macro.ClassMacro; +import funkin.play.event.ScriptedSongEvent; + +@:forward(name, tittlte, type, keys, min, max, step, defaultValue, iterator) +abstract SongEventSchema(SongEventSchemaRaw) +{ + public function new(?fields:Array) + { + this = fields; + } + + @:arrayAccess + public inline function getByName(name:String):SongEventSchemaField + { + for (field in this) + { + if (field.name == name) return field; + } + + return null; + } + + public function getFirstField():SongEventSchemaField + { + return this[0]; + } + + @:arrayAccess + public inline function get(key:Int) + { + return this[key]; + } + + @:arrayAccess + public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField + { + return this[k] = v; + } +} + +typedef SongEventSchemaRaw = Array; + +typedef SongEventSchemaField = +{ + /** + * The name of the property as it should be saved in the event data. + */ + name:String, + + /** + * The title of the field to display in the UI. + */ + title:String, + + /** + * The type of the field. + */ + type:SongEventFieldType, + + /** + * Used only for ENUM values. + * The key is the display name and the value is the actual value. + */ + ?keys:Map, + + /** + * Used for INTEGER and FLOAT values. + * The minimum value that can be entered. + * @default No minimum + */ + ?min:Float, + + /** + * Used for INTEGER and FLOAT values. + * The maximum value that can be entered. + * @default No maximum + */ + ?max:Float, + + /** + * Used for INTEGER and FLOAT values. + * The step value that will be used when incrementing/decrementing the value. + * @default `0.1` + */ + ?step:Float, + + /** + * An optional default value for the field. + */ + ?defaultValue:Dynamic, +} + +enum abstract SongEventFieldType(String) from String to String +{ + /** + * The STRING type will display as a text field. + */ + var STRING = "string"; + + /** + * The INTEGER type will display as a text field that only accepts numbers. + */ + var INTEGER = "integer"; + + /** + * The FLOAT type will display as a text field that only accepts numbers. + */ + var FLOAT = "float"; + + /** + * The BOOL type will display as a checkbox. + */ + var BOOL = "bool"; + + /** + * The ENUM type will display as a dropdown. + * Make sure to specify the `keys` field in the schema. + */ + var ENUM = "enum"; +} diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 90a5f47c3..de25f7f3e 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1,5 +1,7 @@ package funkin.data.song; +import funkin.data.event.SongEventRegistry; +import funkin.data.event.SongEventSchema; import funkin.data.song.SongRegistry; import thx.semver.Version; @@ -620,18 +622,28 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic { if (this.value == null) return {}; - // TODO: How to check if it's a dynamic struct? - if (Std.isOfType(this.value, Int) || Std.isOfType(this.value, String) || Std.isOfType(this.value, Float) || Std.isOfType(this.value, Bool) - || Std.isOfType(this.value, Array)) + if (Std.isOfType(this.value, Array)) { var result:haxe.DynamicAccess = {}; result.set(defaultKey, this.value); return cast result; } - else + else if (Reflect.isObject(this.value)) { + // We enter this case if the value is a struct. return cast this.value; } + else + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.value); + return cast result; + } + } + + public inline function getSchema():Null + { + return SongEventRegistry.getEventSchema(this.event); } public inline function getDynamic(key:String):Null diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 7716f0f02..b7ef07be5 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -8,7 +8,7 @@ import funkin.play.stage.StageData; import polymod.Polymod; import polymod.backends.PolymodAssets.PolymodAssetType; import polymod.format.ParseRules.TextFileFormat; -import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventRegistry; import funkin.util.FileUtil; import funkin.data.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; @@ -271,7 +271,7 @@ class PolymodHandler SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries(); - SongEventParser.loadEventCache(); + SongEventRegistry.loadEventCache(); ConversationDataParser.loadConversationCache(); DialogueBoxDataParser.loadDialogueBoxCache(); SpeakerDataParser.loadSpeakerCache(); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3dcabf953..dbf1b97e1 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -42,7 +42,7 @@ import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.ConversationDataParser; import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VideoCutscene; -import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventRegistry; import funkin.play.notes.NoteSprite; import funkin.play.notes.NoteDirection; import funkin.play.notes.Strumline; @@ -942,7 +942,7 @@ class PlayState extends MusicBeatSubState // TODO: Check that these work even when songPosition is less than 0. if (songEvents != null && songEvents.length > 0) { - var songEventsToActivate:Array = SongEventParser.queryEvents(songEvents, Conductor.songPosition); + var songEventsToActivate:Array = SongEventRegistry.queryEvents(songEvents, Conductor.songPosition); if (songEventsToActivate.length > 0) { @@ -961,7 +961,7 @@ class PlayState extends MusicBeatSubState // Calling event.cancelEvent() skips the event. Neat! if (!eventEvent.eventCanceled) { - SongEventParser.handleEvent(event); + SongEventRegistry.handleEvent(event); } } } @@ -1607,7 +1607,7 @@ class PlayState extends MusicBeatSubState // Reset song events. songEvents = currentChart.getEvents(); - SongEventParser.resetEvents(songEvents); + SongEventRegistry.resetEvents(songEvents); // Reset the notes on each strumline. var playerNoteData:Array = []; diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 5f63254b0..83c978ba8 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -5,8 +5,8 @@ import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; // Data from the event schema import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventSchema; -import funkin.data.event.SongEventData.SongEventFieldType; +import funkin.data.event.SongEventSchema; +import funkin.data.event.SongEventSchema.SongEventFieldType; /** * This class represents a handler for a type of song event. @@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: "char", title: "Character", @@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent step: 10.0, type: SongEventFieldType.FLOAT, } - ]; + ]); } } diff --git a/source/funkin/play/event/PlayAnimationSongEvent.hx b/source/funkin/play/event/PlayAnimationSongEvent.hx index 6bc625517..4e6669479 100644 --- a/source/funkin/play/event/PlayAnimationSongEvent.hx +++ b/source/funkin/play/event/PlayAnimationSongEvent.hx @@ -7,8 +7,8 @@ import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; // Data from the event schema import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventSchema; -import funkin.data.event.SongEventData.SongEventFieldType; +import funkin.data.event.SongEventSchema; +import funkin.data.event.SongEventSchema.SongEventFieldType; class PlayAnimationSongEvent extends SongEvent { @@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'target', title: 'Target', @@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent type: SongEventFieldType.BOOL, defaultValue: false } - ]; + ]); } } diff --git a/source/funkin/play/event/SetCameraBopSongEvent.hx b/source/funkin/play/event/SetCameraBopSongEvent.hx index 3cdeb9a67..d0e01346f 100644 --- a/source/funkin/play/event/SetCameraBopSongEvent.hx +++ b/source/funkin/play/event/SetCameraBopSongEvent.hx @@ -8,8 +8,8 @@ import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; // Data from the event schema import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventSchema; -import funkin.data.event.SongEventData.SongEventFieldType; +import funkin.data.event.SongEventSchema; +import funkin.data.event.SongEventSchema.SongEventFieldType; /** * This class represents a handler for configuring camera bop intensity and rate. @@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'intensity', title: 'Intensity', @@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent step: 1, type: SongEventFieldType.INTEGER, } - ]; + ]); } } diff --git a/source/funkin/play/event/SongEvent.hx b/source/funkin/play/event/SongEvent.hx index 36a886673..29b394c0e 100644 --- a/source/funkin/play/event/SongEvent.hx +++ b/source/funkin/play/event/SongEvent.hx @@ -1,7 +1,7 @@ package funkin.play.event; import funkin.data.song.SongData.SongEventData; -import funkin.data.event.SongEventData.SongEventSchema; +import funkin.data.event.SongEventSchema; /** * This class represents a handler for a type of song event. diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 1ae76039e..182cac108 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -8,8 +8,8 @@ import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; // Data from the event schema import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventFieldType; -import funkin.data.event.SongEventData.SongEventSchema; +import funkin.data.event.SongEventSchema; +import funkin.data.event.SongEventSchema.SongEventFieldType; /** * This class represents a handler for camera zoom events. @@ -99,7 +99,7 @@ class ZoomCameraSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'zoom', title: 'Zoom Level', @@ -145,6 +145,6 @@ class ZoomCameraSongEvent extends SongEvent 'Elastic In/Out' => 'elasticInOut', ] } - ]; + ]); } } diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index f59672646..49b2ba585 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -37,9 +37,25 @@ class SelectItemsCommand implements ChartEditorCommand if (this.notes.length == 0 && this.events.length >= 1) { var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; - var eventData = eventSelected.valueAsStruct(); + + // 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. + var eventSchema = eventSelected.getSchema(); + var defaultKey = null; + if (eventSchema == null) + { + trace('[WARNING] Event schema not found for event ${eventSelected.event}.'); + } + else + { + defaultKey = eventSchema.getFirstField()?.name; + } + var eventData = eventSelected.valueAsStruct(defaultKey); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 7dc344835..4725fd275 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -34,9 +34,25 @@ class SetItemSelectionCommand implements ChartEditorCommand if (this.notes.length == 0 && this.events.length >= 1) { var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; - var eventData = eventSelected.valueAsStruct(); + + // 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. + var eventSchema = eventSelected.getSchema(); + var defaultKey = null; + if (eventSchema == null) + { + trace('[WARNING] Event schema not found for event ${eventSelected.event}.'); + } + else + { + defaultKey = eventSchema.getFirstField()?.name; + } + var eventData = eventSelected.valueAsStruct(defaultKey); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index e5bbf0807..79bcd59af 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -1,6 +1,6 @@ package funkin.ui.debug.charting.components; -import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventRegistry; import flixel.graphics.frames.FlxAtlasFrames; import openfl.display.BitmapData; import openfl.utils.Assets; @@ -79,7 +79,7 @@ class ChartEditorEventSprite extends FlxSprite } // Push all the other events as frames. - for (eventName in SongEventParser.listEventIds()) + for (eventName in SongEventRegistry.listEventIds()) { var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName')); if (!exists) continue; // No graphic for this event. @@ -105,7 +105,7 @@ class ChartEditorEventSprite extends FlxSprite function buildAnimations():Void { - var eventNames:Array = [DEFAULT_EVENT].concat(SongEventParser.listEventIds()); + var eventNames:Array = [DEFAULT_EVENT].concat(SongEventRegistry.listEventIds()); for (eventName in eventNames) { this.animation.addByPrefix(eventName, '${eventName}0', 24, false); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index 08298eb66..ff18a6966 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -9,7 +9,7 @@ import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData; +import funkin.data.event.SongEventSchema; import funkin.data.song.SongData.SongTimeChange; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 7066fc913..480873bc5 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -4,7 +4,7 @@ import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; import funkin.play.stage.StageData; import funkin.play.event.SongEvent; -import funkin.data.event.SongEventData.SongEventSchema; +import funkin.data.event.SongEventSchema; import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import haxe.ui.components.Button; @@ -15,7 +15,7 @@ import haxe.ui.components.Label; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; import haxe.ui.core.Component; -import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventRegistry; import haxe.ui.components.TextField; import haxe.ui.containers.Box; import haxe.ui.containers.Frame; @@ -59,7 +59,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox { toolboxEventsEventKind.dataSource = new ArrayDataSource(); - var songEvents:Array = SongEventParser.listEvents(); + var songEvents:Array = SongEventRegistry.listEvents(); for (event in songEvents) { @@ -74,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox // Edit the event data to place. chartEditorState.eventKindToPlace = eventType; - var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); + var schema:SongEventSchema = SongEventRegistry.getEventSchema(eventType); if (schema == null) { From a35e0121f54d948d445dece61973fd80590c415b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Jan 2024 21:10:30 -0500 Subject: [PATCH 024/129] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 23f85072c..dec3191f2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 23f85072c190373592a30aed137b034715623f28 +Subproject commit dec3191f29b7e5cd488f1e8ec0bf8a760e02142f From 4a0a330eb428c84588ec919876e5679ad68db862 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 4 Jan 2024 08:20:34 -0500 Subject: [PATCH 025/129] Redo logic around note selection, add new buttons for selecting individual sides. --- assets | 2 +- source/funkin/data/song/SongDataUtils.hx | 2 +- .../ui/debug/charting/ChartEditorState.hx | 339 ++++++++++++------ .../charting/commands/AddEventsCommand.hx | 6 + .../charting/commands/AddNotesCommand.hx | 6 + .../commands/ChangeStartingBPMCommand.hx | 6 + .../charting/commands/ChartEditorCommand.hx | 11 + .../charting/commands/CopyItemsCommand.hx | 144 ++++++++ .../charting/commands/CutItemsCommand.hx | 6 + .../commands/DeselectAllItemsCommand.hx | 19 +- .../charting/commands/DeselectItemsCommand.hx | 21 +- .../commands/ExtendNoteLengthCommand.hx | 6 + .../charting/commands/FlipNotesCommand.hx | 6 + .../commands/InvertSelectedItemsCommand.hx | 20 +- .../charting/commands/MoveEventsCommand.hx | 6 + .../charting/commands/MoveItemsCommand.hx | 6 + .../charting/commands/MoveNotesCommand.hx | 6 + .../charting/commands/PasteItemsCommand.hx | 6 + .../charting/commands/RemoveEventsCommand.hx | 6 + .../charting/commands/RemoveItemsCommand.hx | 6 + .../charting/commands/RemoveNotesCommand.hx | 6 + .../commands/SelectAllItemsCommand.hx | 43 ++- .../charting/commands/SelectItemsCommand.hx | 12 +- .../commands/SetItemSelectionCommand.hx | 20 +- .../commands/SwitchDifficultyCommand.hx | 6 + .../components/ChartEditorEventSprite.hx | 4 +- .../components/ChartEditorNotePreview.hx | 17 +- .../handlers/ChartEditorAudioHandler.hx | 11 +- .../handlers/ChartEditorShortcutHandler.hx | 7 +- 29 files changed, 600 insertions(+), 156 deletions(-) create mode 100644 source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx diff --git a/assets b/assets index 9ecc4d26f..198c3ab87 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9ecc4d26fe6b26f31782cccfcd7331bd8a318ce1 +Subproject commit 198c3ab87e401e595be50814af0f667eb1be25e7 diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 309676884..275106f3a 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -273,7 +273,7 @@ class SongDataUtils } /** - * Filter a list of notes to only include notes whose data is within the given range. + * Filter a list of notes to only include notes whose data is within the given range, inclusive. */ public static function getNotesInDataRange(notes:Array, start:Int, end:Int):Array { diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 4f96fad69..16f83275b 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -61,6 +61,7 @@ import funkin.ui.debug.charting.commands.AddNotesCommand; import funkin.ui.debug.charting.commands.ChartEditorCommand; import funkin.ui.debug.charting.commands.ChartEditorCommand; import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.CopyItemsCommand; import funkin.ui.debug.charting.commands.DeselectAllItemsCommand; import funkin.ui.debug.charting.commands.DeselectItemsCommand; import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; @@ -103,6 +104,7 @@ import haxe.ui.components.Slider; import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.Frame; +import haxe.ui.containers.Box; import haxe.ui.containers.menus.Menu; import haxe.ui.containers.menus.MenuBar; import haxe.ui.containers.menus.MenuItem; @@ -190,10 +192,40 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ public static final PLAYBAR_HEIGHT:Int = 48; + /** + * The height of the note selection buttons above the grid. + */ + public static final NOTE_SELECT_BUTTON_HEIGHT:Int = 24; + /** * The amount of padding between the menu bar and the chart grid when fully scrolled up. */ - public static final GRID_TOP_PAD:Int = 8; + public static final GRID_TOP_PAD:Int = NOTE_SELECT_BUTTON_HEIGHT + 12; + + /** + * The initial vertical position of the chart grid. + */ + public static final GRID_INITIAL_Y_POS:Int = MENU_BAR_HEIGHT + GRID_TOP_PAD; + + /** + * The X position of the note preview area. + */ + public static final NOTE_PREVIEW_X_POS:Int = 350; + + /** + * The Y position of the note preview area. + */ + public static final NOTE_PREVIEW_Y_POS:Int = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 4; + + /** + * The X position of the note grid. + */ + public static var GRID_X_POS(get, never):Float; + + static function get_GRID_X_POS():Float + { + return FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; + } // Colors // Background color tint. @@ -338,21 +370,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { if (isViewDownscroll) { - gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS); gridPlayheadScrollArea.y = gridTiledSprite.y; } else { - gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS); gridPlayheadScrollArea.y = gridTiledSprite.y; if (audioVisGroup != null && audioVisGroup.playerVis != null) { - audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT); + audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); } if (audioVisGroup != null && audioVisGroup.opponentVis != null) { - audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT); + audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); } } } @@ -422,7 +454,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.playheadPositionInPixels = value; // Move the playhead sprite to the correct position. - gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS; return this.playheadPositionInPixels; } @@ -1602,6 +1634,24 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var playbarEnd:Button; + /** + * The button above the grid that selects all notes on the opponent's side. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectOpponent:Button; + + /** + * The button above the grid that selects all notes on the player's side. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectPlayer:Button; + + /** + * The button above the grid that selects all song events. + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectEvent:Button; + /** * RENDER OBJECTS */ @@ -2094,8 +2144,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridBitmap == null) throw 'ERROR: Tried to build grid, but gridBitmap is null! Check ChartEditorThemeHandler.updateTheme().'; gridTiledSprite = new FlxTiledSprite(gridBitmap, gridBitmap.width, 1000, false, true); - gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid. - gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar. + gridTiledSprite.x = GRID_X_POS; // Center the grid. + gridTiledSprite.y = GRID_INITIAL_Y_POS; // Push down to account for the menu bar. add(gridTiledSprite); gridTiledSprite.zIndex = 10; @@ -2127,8 +2177,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(gridPlayheadScrollArea); gridPlayheadScrollArea.setGraphicSize(PLAYHEAD_SCROLL_AREA_WIDTH, 3000); gridPlayheadScrollArea.updateHitbox(); - gridPlayheadScrollArea.x = gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH; - gridPlayheadScrollArea.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; + gridPlayheadScrollArea.x = GRID_X_POS - PLAYHEAD_SCROLL_AREA_WIDTH; + gridPlayheadScrollArea.y = GRID_INITIAL_Y_POS; gridPlayheadScrollArea.zIndex = 25; // The playhead that show the current position in the song. @@ -2136,8 +2186,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState gridPlayhead.zIndex = 30; var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2); - var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD; - gridPlayhead.setPosition(gridTiledSprite.x, playheadBaseYPos); + var playheadBaseYPos:Float = GRID_INITIAL_Y_POS; + gridPlayhead.setPosition(GRID_X_POS, playheadBaseYPos); var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR); playheadSprite.x = -PLAYHEAD_SCROLL_AREA_WIDTH; playheadSprite.y = 0; @@ -2168,10 +2218,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function buildNotePreview():Void { - var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - PLAYBAR_HEIGHT - GRID_TOP_PAD - GRID_TOP_PAD; - notePreview = new ChartEditorNotePreview(height); - notePreview.x = 350; - notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; + var playbarHeightWithPad = PLAYBAR_HEIGHT + 10; + var notePreviewHeight:Int = FlxG.height - NOTE_PREVIEW_Y_POS - playbarHeightWithPad; + notePreview = new ChartEditorNotePreview(notePreviewHeight); + notePreview.x = NOTE_PREVIEW_X_POS; + notePreview.y = NOTE_PREVIEW_Y_POS; add(notePreview); if (notePreviewViewport == null) throw 'ERROR: Tried to build note preview, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().'; @@ -2355,6 +2406,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(playbarHeadLayout); + // Little text that shows up when you copy something. txtCopyNotif = new FlxText(0, 0, 0, '', 24); txtCopyNotif.setBorderStyle(OUTLINE, 0xFF074809, 1); txtCopyNotif.color = 0xFF52FF77; @@ -2379,6 +2431,77 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.openCharacterDropdown(CharacterType.BF, true); } }); + + buttonSelectOpponent = new Button(); + buttonSelectOpponent.allowFocus = false; + buttonSelectOpponent.text = "Opponent"; // Default text. + buttonSelectOpponent.x = GRID_X_POS; + buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 8; + buttonSelectOpponent.width = GRID_SIZE * 4; + buttonSelectOpponent.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectOpponent.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection."; + buttonSelectOpponent.zIndex = 110; + add(buttonSelectOpponent); + + buttonSelectOpponent.onClick = (_) -> { + var notesToSelect:Array = currentSongChartNoteData; + notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, STRUMLINE_SIZE, STRUMLINE_SIZE * 2 - 1); + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand(notesToSelect, [])); + } + else + { + performCommand(new SetItemSelectionCommand(notesToSelect, [])); + } + } + + buttonSelectPlayer = new Button(); + buttonSelectPlayer.allowFocus = false; + buttonSelectPlayer.text = "Player"; // Default text. + buttonSelectPlayer.x = buttonSelectOpponent.x + buttonSelectOpponent.width; + buttonSelectPlayer.y = buttonSelectOpponent.y; + buttonSelectPlayer.width = GRID_SIZE * 4; + buttonSelectPlayer.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectPlayer.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection."; + buttonSelectPlayer.zIndex = 110; + add(buttonSelectPlayer); + + buttonSelectPlayer.onClick = (_) -> { + var notesToSelect:Array = currentSongChartNoteData; + notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, 0, STRUMLINE_SIZE - 1); + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand(notesToSelect, [])); + } + else + { + performCommand(new SetItemSelectionCommand(notesToSelect, [])); + } + } + + buttonSelectEvent = new Button(); + buttonSelectEvent.allowFocus = false; + buttonSelectEvent.icon = Paths.image('ui/chart-editor/events/Default'); + buttonSelectEvent.iconPosition = "top"; + buttonSelectEvent.x = buttonSelectPlayer.x + buttonSelectPlayer.width; + buttonSelectEvent.y = buttonSelectPlayer.y; + buttonSelectEvent.width = GRID_SIZE; + buttonSelectEvent.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectEvent.tooltip = "Click to set selection to all events.\nShift-click to add all events to selection."; + buttonSelectEvent.zIndex = 110; + add(buttonSelectEvent); + + buttonSelectEvent.onClick = (_) -> { + if (FlxG.keys.pressed.SHIFT) + { + performCommand(new SelectItemsCommand([], currentSongChartEventData)); + } + else + { + performCommand(new SetItemSelectionCommand([], currentSongChartEventData)); + } + } } /** @@ -2467,23 +2590,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemUndo.onClick = _ -> undoLastCommand(); menubarItemRedo.onClick = _ -> redoLastCommand(); menubarItemCopy.onClick = function(_) { - // Doesn't use a command because it's not undoable. - - // Calculate a single time offset for all the notes and events. - var timeOffset:Null = currentNoteSelection.length > 0 ? Std.int(currentNoteSelection[0].time) : null; - if (currentEventSelection.length > 0) - { - if (timeOffset == null || currentEventSelection[0].time < timeOffset) - { - timeOffset = Std.int(currentEventSelection[0].time); - } - } - - SongDataUtils.writeItemsToClipboard( - { - notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset), - events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset), - }); }; menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)); @@ -2521,11 +2627,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemFlipNotes.onClick = _ -> performCommand(new FlipNotesCommand(currentNoteSelection)); - menubarItemSelectAll.onClick = _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectAllNotes.onClick = _ -> performCommand(new SelectAllItemsCommand(true, false)); - menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectAllEvents.onClick = _ -> performCommand(new SelectAllItemsCommand(false, true)); - menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand()); + + menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand()); menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false); menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true); @@ -2989,7 +3097,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { if (holdNoteSprite == null || holdNoteSprite.noteData == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue; - if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD)) + if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT)) { holdNoteSprite.kill(); } @@ -3025,7 +3133,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Resolve an issue where dragging an event too far would cause it to be hidden. var isSelectedAndDragged = currentEventSelection.fastContains(eventSprite.eventData) && (dragTargetCurrentStep != 0); - if ((eventSprite.isEventVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD) + if ((eventSprite.isEventVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT) && currentSongChartEventData.fastContains(eventSprite.eventData)) || isSelectedAndDragged) { @@ -3569,7 +3677,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Clicked on the playhead scroll area. // Move the playhead to the cursor position. - this.playheadPositionInPixels = FlxG.mouse.screenY - MENU_BAR_HEIGHT - GRID_TOP_PAD; + this.playheadPositionInPixels = FlxG.mouse.screenY - (GRID_INITIAL_Y_POS); moveSongToScrollPosition(); // Cursor should be a grabby hand. @@ -3662,7 +3770,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Set the selection. - performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect, currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect)); } } else @@ -3675,7 +3783,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -3780,12 +3888,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (highlightedNote != null && highlightedNote.noteData != null) { // Click a note to select it. - performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [])); } else if (highlightedEvent != null && highlightedEvent.eventData != null) { // Click an event to select it. - performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData])); } else { @@ -3793,7 +3901,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -3808,7 +3916,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); if (shouldDeselect) { - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } } @@ -4049,7 +4157,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // If you click an unselected note, and aren't holding Control, deselect everything else. - performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [])); } } else if (highlightedEvent != null && highlightedEvent.eventData != null) @@ -4062,7 +4170,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // If you click an unselected event, and aren't holding Control, deselect everything else. - performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); + performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData])); } } else @@ -4324,7 +4432,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (currentSongMetadata.playData.characters.player != charPlayer.charId) { - if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player; + if (healthIconBF != null) + { + healthIconBF.characterId = currentSongMetadata.playData.characters.player; + } charPlayer.loadCharacter(currentSongMetadata.playData.characters.player); charPlayer.characterType = CharacterType.BF; @@ -4360,7 +4471,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (currentSongMetadata.playData.characters.opponent != charPlayer.charId) { - if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent; + if (healthIconDad != null) + { + healthIconDad.characterId = currentSongMetadata.playData.characters.opponent; + } charPlayer.loadCharacter(currentSongMetadata.playData.characters.opponent); charPlayer.characterType = CharacterType.DAD; @@ -4378,6 +4492,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + function handleSelectionButtons():Void + { + // Make sure buttons are never nudged out of the correct spot. + // TODO: Why do these even move in the first place? The camera never moves, LOL. + buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + buttonSelectPlayer.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + buttonSelectEvent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2; + } + /** * Handles display elements for the playbar at the bottom. */ @@ -4486,11 +4609,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState healthIconBF.size *= 0.5; // Make the icon smaller in Chart Editor. healthIconBF.flipX = !healthIconBF.flipX; // BF faces the other way. } + if (buttonSelectPlayer != null) + { + buttonSelectPlayer.text = charDataBF?.name ?? 'Player'; + } if (healthIconDad != null) { healthIconDad.configure(charDataDad?.healthIcon); healthIconDad.size *= 0.5; // Make the icon smaller in Chart Editor. } + if (buttonSelectOpponent != null) + { + buttonSelectOpponent.text = charDataDad?.name ?? 'Opponent'; + } healthIconsDirty = false; } @@ -4498,15 +4629,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (healthIconBF != null) { // Base X position to the right of the grid. - healthIconBF.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 45 - (healthIconBF.width / 2)); - healthIconBF.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconBF.height / 2)); + var xOffset = 45 - (healthIconBF.width / 2); + healthIconBF.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS + gridTiledSprite.width + xOffset); + var yOffset = 30 - (healthIconBF.height / 2); + healthIconBF.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; } // Visibly center the Dad health icon. if (healthIconDad != null) { - healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2)); - healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2)); + var xOffset = 45 + (healthIconDad.width / 2); + healthIconDad.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS - xOffset); + var yOffset = 30 - (healthIconDad.height / 2); + healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; } } @@ -4586,54 +4721,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // CTRL + C = Copy if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.C) { - if (currentNoteSelection.length > 0) - { - txtCopyNotif.visible = true; - txtCopyNotif.text = "Copied " + currentNoteSelection.length + " notes to clipboard"; - txtCopyNotif.x = FlxG.mouse.x - (txtCopyNotif.width / 2); - txtCopyNotif.y = FlxG.mouse.y - 16; - FlxTween.tween(txtCopyNotif, {y: txtCopyNotif.y - 32}, 0.5, - { - type: FlxTween.ONESHOT, - ease: FlxEase.quadOut, - onComplete: function(_) { - txtCopyNotif.visible = false; - } - }); - - for (note in renderedNotes.members) - { - if (isNoteSelected(note.noteData)) - { - FlxTween.globalManager.cancelTweensOf(note); - FlxTween.globalManager.cancelTweensOf(note.scale); - note.playNoteAnimation(); - var prevX:Float = note.scale.x; - var prevY:Float = note.scale.y; - - note.scale.x *= 1.2; - note.scale.y *= 1.2; - - note.angle = FlxG.random.bool() ? -10 : 10; - FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); - - FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7, - { - ease: FlxEase.elasticOut, - onComplete: function(_) { - note.playNoteAnimation(); - } - }); - } - } - } - - // We don't need a command for this since we can't undo it. - SongDataUtils.writeItemsToClipboard( - { - notes: SongDataUtils.buildNoteClipboard(currentNoteSelection), - events: SongDataUtils.buildEventClipboard(currentEventSelection), - }); + performCommand(new CopyItemsCommand(currentNoteSelection, currentEventSelection)); } // CTRL + X = Cut @@ -4694,25 +4782,50 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new FlipNotesCommand(currentNoteSelection)); } - // CTRL + A = Select All + // CTRL + A = Select All Notes if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.A) { // Select all items. - performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)); + if (FlxG.keys.pressed.ALT) + { + if (FlxG.keys.pressed.SHIFT) + { + // CTRL + ALT + SHIFT + A = Append All Events to Selection + performCommand(new SelectItemsCommand([], currentSongChartEventData)); + } + else + { + // CTRL + ALT + A = Set Selection to All Events + performCommand(new SelectAllItemsCommand(false, true)); + } + } + else + { + if (FlxG.keys.pressed.SHIFT) + { + // CTRL + SHIFT + A = Append All Notes to Selection + performCommand(new SelectItemsCommand(currentSongChartNoteData, [])); + } + else + { + // CTRL + A = Set Selection to All Notes + performCommand(new SelectAllItemsCommand(true, false)); + } + } } // CTRL + I = Select Inverse if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.I) { // Select unselected items and deselect selected items. - performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new InvertSelectedItemsCommand()); } // CTRL + D = Select None if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.D) { // Deselect all items. - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + performCommand(new DeselectAllItemsCommand()); } } @@ -4878,13 +4991,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState * Perform (or redo) a command, then add it to the undo stack. * * @param command The command to perform. - * @param purgeRedoStack If true, the redo stack will be cleared. + * @param purgeRedoStack If `true`, the redo stack will be cleared after performing the command. */ function performCommand(command:ChartEditorCommand, purgeRedoStack:Bool = true):Void { command.execute(this); - undoHistory.push(command); - commandHistoryDirty = true; + if (command.shouldAddToHistory(this)) + { + undoHistory.push(command); + commandHistoryDirty = true; + } if (purgeRedoStack) redoHistory = []; } @@ -4895,6 +5011,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function undoCommand(command:ChartEditorCommand):Void { command.undo(this); + // Note, if we are undoing a command, it should already be in the history, + // therefore we don't need to check `shouldAddToHistory(state)` redoHistory.push(command); commandHistoryDirty = true; } @@ -5515,6 +5633,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState @:privateAccess ChartEditorNoteSprite.noteFrameCollection = null; + + // Stop the music. + welcomeMusic.destroy(); + audioInstTrack.destroy(); + audioVocalTrackGroup.destroy(); } function applyCanQuickSave():Void diff --git a/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx b/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx index 9bf8ec3db..a878ee687 100644 --- a/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddEventsCommand.hx @@ -59,6 +59,12 @@ class AddEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { var len:Int = events.length; diff --git a/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx b/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx index ce4e73ea2..ea984c82d 100644 --- a/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddNotesCommand.hx @@ -59,6 +59,12 @@ class AddNotesCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0); + } + public function toString():String { if (notes.length == 1) diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx index 3c45c1168..5264623f6 100644 --- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx @@ -54,6 +54,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (targetBPM != previousBPM); + } + public function toString():String { return 'Change Starting BPM to ${targetBPM}'; diff --git a/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx b/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx index cfa169908..1fa86ad94 100644 --- a/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx @@ -6,6 +6,8 @@ package funkin.ui.debug.charting.commands; * * To make a functionality compatible with the undo/redo history, create a new class * that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())` + * + * NOTE: Make the constructor very simple, as it may be called without executing by the command palette. */ interface ChartEditorCommand { @@ -22,6 +24,15 @@ interface ChartEditorCommand */ public function undo(state:ChartEditorState):Void; + /** + * Return whether or not this command should be appended to the in the undo/redo history. + * Generally this should be true, it should only be false if the command is minor and non-destructive, + * like copying to the clipboard. + * + * Called after `execute()` is performed. + */ + public function shouldAddToHistory(state:ChartEditorState):Bool; + /** * Get a short description of the action (for the UI). * For example, return `Add Left Note` to display `Undo Add Left Note` in the menu. diff --git a/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx b/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx new file mode 100644 index 000000000..4361f867f --- /dev/null +++ b/source/funkin/ui/debug/charting/commands/CopyItemsCommand.hx @@ -0,0 +1,144 @@ +package funkin.ui.debug.charting.commands; + +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongDataUtils; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; + +/** + * Command that copies a given set of notes and song events to the clipboard, + * without deleting them from the chart editor. + */ +@:nullSafety +@:access(funkin.ui.debug.charting.ChartEditorState) +class CopyItemsCommand implements ChartEditorCommand +{ + var notes:Array; + var events:Array; + + public function new(notes:Array, events:Array) + { + this.notes = notes; + this.events = events; + } + + public function execute(state:ChartEditorState):Void + { + // Calculate a single time offset for all the notes and events. + var timeOffset:Null = state.currentNoteSelection.length > 0 ? Std.int(state.currentNoteSelection[0].time) : null; + if (state.currentEventSelection.length > 0) + { + if (timeOffset == null || state.currentEventSelection[0].time < timeOffset) + { + timeOffset = Std.int(state.currentEventSelection[0].time); + } + } + + SongDataUtils.writeItemsToClipboard( + { + notes: SongDataUtils.buildNoteClipboard(state.currentNoteSelection, timeOffset), + events: SongDataUtils.buildEventClipboard(state.currentEventSelection, timeOffset), + }); + + performVisuals(state); + } + + function performVisuals(state:ChartEditorState):Void + { + if (state.currentNoteSelection.length > 0) + { + // Display the "Copied Notes" text. + if (state.txtCopyNotif != null) + { + state.txtCopyNotif.visible = true; + state.txtCopyNotif.text = "Copied " + state.currentNoteSelection.length + " notes to clipboard"; + state.txtCopyNotif.x = FlxG.mouse.x - (state.txtCopyNotif.width / 2); + state.txtCopyNotif.y = FlxG.mouse.y - 16; + FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5, + { + type: FlxTween.ONESHOT, + ease: FlxEase.quadOut, + onComplete: function(_) { + state.txtCopyNotif.visible = false; + } + }); + } + + // Wiggle the notes. + for (note in state.renderedNotes.members) + { + if (state.isNoteSelected(note.noteData)) + { + FlxTween.globalManager.cancelTweensOf(note); + FlxTween.globalManager.cancelTweensOf(note.scale); + note.playNoteAnimation(); + var prevX:Float = note.scale.x; + var prevY:Float = note.scale.y; + + note.scale.x *= 1.2; + note.scale.y *= 1.2; + + note.angle = FlxG.random.bool() ? -10 : 10; + FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); + + FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7, + { + ease: FlxEase.elasticOut, + onComplete: function(_) { + note.playNoteAnimation(); + } + }); + } + } + + // Wiggle the events. + for (event in state.renderedEvents.members) + { + if (state.isEventSelected(event.eventData)) + { + FlxTween.globalManager.cancelTweensOf(event); + FlxTween.globalManager.cancelTweensOf(event.scale); + event.playAnimation(); + var prevX:Float = event.scale.x; + var prevY:Float = event.scale.y; + + event.scale.x *= 1.2; + event.scale.y *= 1.2; + + event.angle = FlxG.random.bool() ? -10 : 10; + FlxTween.tween(event, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut}); + + FlxTween.tween(event.scale, {"y": prevX, "x": prevY}, 0.7, + { + ease: FlxEase.elasticOut, + onComplete: function(_) { + event.playAnimation(); + } + }); + } + } + } + } + + public function undo(state:ChartEditorState):Void + { + // This command is not undoable. Do nothing. + } + + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is not undoable. Don't add it to the history. + return false; + } + + public function toString():String + { + var len:Int = notes.length + events.length; + + if (notes.length == 0) return 'Copy $len Events to Clipboard'; + else if (events.length == 0) return 'Copy $len Notes to Clipboard'; + else + return 'Copy $len Items to Clipboard'; + } +} diff --git a/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx b/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx index d0301b1ec..6cf674f80 100644 --- a/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/CutItemsCommand.hx @@ -56,6 +56,12 @@ class CutItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Always add it to the history. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx b/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx index cbde0ab3d..5bfef76cc 100644 --- a/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx @@ -10,17 +10,16 @@ import funkin.data.song.SongData.SongEventData; @:access(funkin.ui.debug.charting.ChartEditorState) class DeselectAllItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array; - var previousEventSelection:Array; + var previousNoteSelection:Array = []; + var previousEventSelection:Array = []; - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } + public function new() {} public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = []; state.currentEventSelection = []; @@ -35,6 +34,12 @@ class DeselectAllItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (previousNoteSelection.length > 0 || previousEventSelection.length > 0); + } + public function toString():String { return 'Deselect All Items'; diff --git a/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx index d679b5363..6a115a26a 100644 --- a/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx @@ -45,16 +45,27 @@ class DeselectItemsCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { - var noteCount = notes.length + events.length; + var isPlural = (notes.length + events.length) > 1; + var notesOnly = (notes.length > 0 && events.length == 0); + var eventsOnly = (notes.length == 0 && events.length > 0); - if (noteCount == 1) + if (notesOnly) { - var dir:String = notes[0].getDirectionName(); - return 'Deselect $dir Items'; + return 'Deselect ${notes.length} ${isPlural ? 'Notes' : 'Note'}'; + } + else if (eventsOnly) + { + return 'Deselect ${events.length} ${isPlural ? 'Events' : 'Event'}'; } - return 'Deselect ${noteCount} Items'; + return 'Deselect ${notes.length + events.length} Items'; } } diff --git a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx index 47da0dde5..2479feb0b 100644 --- a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx @@ -45,6 +45,12 @@ class ExtendNoteLengthCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (oldLength != newLength); + } + public function toString():String { return 'Extend Note Length'; diff --git a/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx b/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx index da8ec7fbc..f54ffed15 100644 --- a/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx @@ -51,6 +51,12 @@ class FlipNotesCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0); + } + public function toString():String { var len:Int = notes.length; diff --git a/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx b/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx index 6e37bcc03..d9a28f463 100644 --- a/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx @@ -12,19 +12,19 @@ import funkin.data.song.SongDataUtils; @:access(funkin.ui.debug.charting.ChartEditorState) class InvertSelectedItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array; - var previousEventSelection:Array; + var previousNoteSelection:Array = []; + var previousEventSelection:Array = []; - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } + public function new() {} public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection); state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection); + state.noteDisplayDirty = true; } @@ -36,6 +36,12 @@ class InvertSelectedItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (previousNoteSelection.length > 0 || previousEventSelection.length > 0); + } + public function toString():String { return 'Invert Selected Items'; diff --git a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx index efe9c25d5..a0368f908 100644 --- a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx @@ -65,6 +65,12 @@ class MoveEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { var len:Int = events.length; diff --git a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx index 2eedbbf03..4fa1e2f87 100644 --- a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx @@ -88,6 +88,12 @@ class MoveItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx index 8bce747a1..37ed61d72 100644 --- a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx @@ -67,6 +67,12 @@ class MoveNotesCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0); + } + public function toString():String { var len:Int = notes.length; diff --git a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx index 75382da41..bba6ae866 100644 --- a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx @@ -71,6 +71,12 @@ class PasteItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (addedNotes.length > 0 || addedEvents.length > 0); + } + public function toString():String { var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard(); diff --git a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx index 7e620c210..b4d913607 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx @@ -48,6 +48,12 @@ class RemoveEventsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (events.length > 0); + } + public function toString():String { if (events.length == 1 && events[0] != null) diff --git a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx index 77184209e..69317aff4 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx @@ -62,6 +62,12 @@ class RemoveItemsCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { return 'Remove ${notes.length + events.length} Items'; diff --git a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx index e189be83e..4811f831d 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx @@ -50,6 +50,12 @@ class RemoveNotesCommand implements ChartEditorCommand state.sortChartData(); } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0); + } + public function toString():String { if (notes.length == 1 && notes[0] != null) diff --git a/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx index e1a4dceaa..f550e044b 100644 --- a/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx @@ -10,19 +10,25 @@ import funkin.data.song.SongData.SongEventData; @:access(funkin.ui.debug.charting.ChartEditorState) class SelectAllItemsCommand implements ChartEditorCommand { - var previousNoteSelection:Array; - var previousEventSelection:Array; + var shouldSelectNotes:Bool; + var shouldSelectEvents:Bool; - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) + var previousNoteSelection:Array = []; + var previousEventSelection:Array = []; + + public function new(shouldSelectNotes:Bool, shouldSelectEvents:Bool) { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; + this.shouldSelectNotes = shouldSelectNotes; + this.shouldSelectEvents = shouldSelectEvents; } public function execute(state:ChartEditorState):Void { - state.currentNoteSelection = state.currentSongChartNoteData; - state.currentEventSelection = state.currentSongChartEventData; + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + + state.currentNoteSelection = shouldSelectNotes ? state.currentSongChartNoteData : []; + state.currentEventSelection = shouldSelectEvents ? state.currentSongChartEventData : []; state.noteDisplayDirty = true; } @@ -35,8 +41,29 @@ class SelectAllItemsCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (state.currentNoteSelection.length > 0 || state.currentEventSelection.length > 0); + } + public function toString():String { - return 'Select All Items'; + if (shouldSelectNotes && !shouldSelectEvents) + { + return 'Select All Notes'; + } + else if (shouldSelectEvents && !shouldSelectNotes) + { + return 'Select All Events'; + } + else if (shouldSelectNotes && shouldSelectEvents) + { + return 'Select All Notes and Events'; + } + else + { + return 'Select Nothing (Huh?)'; + } } } diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index abe8b9e35..17f45f946 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -15,10 +15,10 @@ class SelectItemsCommand implements ChartEditorCommand var notes:Array; var events:Array; - public function new(notes:Array, events:Array) + public function new(?notes:Array, ?events:Array) { - this.notes = notes; - this.events = events; + this.notes = notes ?? []; + this.events = events ?? []; } public function execute(state:ChartEditorState):Void @@ -46,6 +46,12 @@ class SelectItemsCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // This command is undoable. Add to the history if we actually performed an action. + return (notes.length > 0 || events.length > 0); + } + public function toString():String { var len:Int = notes.length + events.length; diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index a06aefabc..f9b4fb6d7 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -13,20 +13,20 @@ class SetItemSelectionCommand implements ChartEditorCommand { var notes:Array; var events:Array; - var previousNoteSelection:Array; - var previousEventSelection:Array; + var previousNoteSelection:Array = []; + var previousEventSelection:Array = []; - public function new(notes:Array, events:Array, previousNoteSelection:Array, - previousEventSelection:Array) + public function new(notes:Array, events:Array) { this.notes = notes; this.events = events; - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; } public function execute(state:ChartEditorState):Void { + this.previousNoteSelection = state.currentNoteSelection; + this.previousEventSelection = state.currentEventSelection; + state.currentNoteSelection = notes; state.currentEventSelection = events; @@ -41,8 +41,14 @@ class SetItemSelectionCommand implements ChartEditorCommand state.noteDisplayDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // Add to the history if we actually performed an action. + return (state.currentNoteSelection != previousNoteSelection && state.currentEventSelection != previousEventSelection); + } + public function toString():String { - return 'Select ${notes.length} Items'; + return 'Select ${notes.length + events.length} Items'; } } diff --git a/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx b/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx index 75e7e5afe..30c2edb61 100644 --- a/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx @@ -38,6 +38,12 @@ class SwitchDifficultyCommand implements ChartEditorCommand state.notePreviewDirty = true; } + public function shouldAddToHistory(state:ChartEditorState):Bool + { + // Add to the history if we actually performed an action. + return (prevVariation != newVariation || prevDifficulty != newDifficulty); + } + public function toString():String { return 'Switch Difficulty'; diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 4c9d91407..e0070cc7b 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -119,8 +119,10 @@ class ChartEditorEventSprite extends FlxSprite return DEFAULT_EVENT; } - public function playAnimation(name:String):Void + public function playAnimation(?name:String):Void { + if (name == null) name = eventData?.event ?? DEFAULT_EVENT; + var correctedName = correctAnimationName(name); this.animation.play(correctedName); refresh(); diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx index 598cbb544..8d9ec6743 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNotePreview.hx @@ -70,9 +70,9 @@ class ChartEditorNotePreview extends FlxSprite * @param event The data for the event. * @param songLengthInMs The total length of the song in milliseconds. */ - public function addEvent(event:SongEventData, songLengthInMs:Int):Void + public function addEvent(event:SongEventData, songLengthInMs:Int, ?isSelection:Bool = false):Void { - drawNote(-1, false, Std.int(event.time), songLengthInMs); + drawNote(-1, false, Std.int(event.time), songLengthInMs, isSelection); } /** @@ -114,6 +114,19 @@ class ChartEditorNotePreview extends FlxSprite } } + /** + * Add an array of selected events to the preview. + * @param events The data for the events. + * @param songLengthInMs The total length of the song in milliseconds. + */ + public function addSelectedEvents(events:Array, songLengthInMs:Int):Void + { + for (event in events) + { + addEvent(event, songLengthInMs, true); + } + } + /** * Draws a note on the preview. * @param dir Note data. diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 990ab41ae..d4fcc4638 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -185,9 +185,10 @@ class ChartEditorAudioHandler state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); state.audioVisGroup.addPlayerVis(vocalTrack); state.audioVisGroup.playerVis.x = 885; - state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; - state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; + state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time. + state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels. state.audioVisGroup.playerVis.detail = 1; + state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); state.audioVocalTrackGroup.playerVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); return true; @@ -195,10 +196,10 @@ class ChartEditorAudioHandler state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); state.audioVisGroup.addOpponentVis(vocalTrack); state.audioVisGroup.opponentVis.x = 435; - - state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; - state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; + state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time. + state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels. state.audioVisGroup.opponentVis.detail = 1; + state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); state.audioVocalTrackGroup.opponentVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx index f7105d2f7..62f1f4cbc 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorShortcutHandler.hx @@ -2,6 +2,10 @@ package funkin.ui.debug.charting.handlers; import funkin.util.PlatformUtil; +/** + * Handles modifying the shortcut text of menu items based on the current platform. + * On MacOS, `Ctrl`, `Alt`, and `Shift` are replaced with `⌘` (or `^`), `⌥`, and `⇧`, respectively. + */ @:access(funkin.ui.debug.charting.ChartEditorState) class ChartEditorShortcutHandler { @@ -18,7 +22,8 @@ class ChartEditorShortcutHandler state.menubarItemCopy.shortcutText = ctrlOrCmd('C'); state.menubarItemPaste.shortcutText = ctrlOrCmd('V'); - state.menubarItemSelectAll.shortcutText = ctrlOrCmd('A'); + state.menubarItemSelectAllNotes.shortcutText = ctrlOrCmd('A'); + state.menubarItemSelectAllEvents.shortcutText = ctrlOrCmd(alt('A')); state.menubarItemSelectInverse.shortcutText = ctrlOrCmd('I'); state.menubarItemSelectNone.shortcutText = ctrlOrCmd('D'); state.menubarItemSelectBeforeCursor.shortcutText = shift('Home'); From 336810b628631b0e1dcb67b25774bac1ce815395 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 4 Jan 2024 10:00:39 -0500 Subject: [PATCH 026/129] Tooltips when hovering over chart events --- hmm.json | 4 +- source/funkin/data/event/SongEventData.hx | 109 ++++++++++++++---- source/funkin/data/song/SongData.hx | 61 ++++++++++ .../funkin/play/event/FocusCameraSongEvent.hx | 4 +- .../play/event/PlayAnimationSongEvent.hx | 4 +- .../play/event/SetCameraBopSongEvent.hx | 4 +- .../funkin/play/event/ZoomCameraSongEvent.hx | 4 +- .../ui/debug/charting/ChartEditorState.hx | 3 +- .../components/ChartEditorEventSprite.hx | 42 ++++++- source/funkin/util/HaxeUIUtil.hx | 17 +++ 10 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 source/funkin/util/HaxeUIUtil.hx diff --git a/hmm.json b/hmm.json index 57fbbb555..be9e2dd26 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "e765a3e0b7a653823e8dec765e04623f27f573f8", + "ref": "67c5700e253ff8892589a95945a7799f34ae4df0", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "7a517d561eff49d8123c128bf9f5c1123b84d014", + "ref": "2b9cff727999b53ed292b1675ac1c9089ac77600", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventData.hx index 7a167b031..a4a41e3a0 100644 --- a/source/funkin/data/event/SongEventData.hx +++ b/source/funkin/data/event/SongEventData.hx @@ -161,35 +161,71 @@ class SongEventParser } } -enum abstract SongEventFieldType(String) from String to String +@:forward(name, title, type, keys, min, max, step, defaultValue, iterator) +abstract SongEventSchema(SongEventSchemaRaw) { - /** - * The STRING type will display as a text field. - */ - var STRING = "string"; + public function new(?fields:Array) + { + this = fields; + } - /** - * The INTEGER type will display as a text field that only accepts numbers. - */ - var INTEGER = "integer"; + @:arrayAccess + public function getByName(name:String):SongEventSchemaField + { + for (field in this) + { + if (field.name == name) return field; + } - /** - * The FLOAT type will display as a text field that only accepts numbers. - */ - var FLOAT = "float"; + return null; + } - /** - * The BOOL type will display as a checkbox. - */ - var BOOL = "bool"; + public function getFirstField():SongEventSchemaField + { + return this[0]; + } - /** - * The ENUM type will display as a dropdown. - * Make sure to specify the `keys` field in the schema. - */ - var ENUM = "enum"; + public function stringifyFieldValue(name:String, value:Dynamic):String + { + var field:SongEventSchemaField = getByName(name); + if (field == null) return 'Unknown'; + + switch (field.type) + { + case SongEventFieldType.STRING: + return Std.string(value); + case SongEventFieldType.INTEGER: + return Std.string(value); + case SongEventFieldType.FLOAT: + return Std.string(value); + case SongEventFieldType.BOOL: + return Std.string(value); + case SongEventFieldType.ENUM: + for (key in field.keys.keys()) + { + if (field.keys.get(key) == value) return key; + } + return Std.string(value); + default: + return 'Unknown'; + } + } + + @:arrayAccess + public inline function get(key:Int) + { + return this[key]; + } + + @:arrayAccess + public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField + { + return this[k] = v; + } } +typedef SongEventSchemaRaw = Array; + typedef SongEventSchemaField = { /** @@ -240,4 +276,31 @@ typedef SongEventSchemaField = ?defaultValue:Dynamic, } -typedef SongEventSchema = Array; +enum abstract SongEventFieldType(String) from String to String +{ + /** + * The STRING type will display as a text field. + */ + var STRING = "string"; + + /** + * The INTEGER type will display as a text field that only accepts numbers. + */ + var INTEGER = "integer"; + + /** + * The FLOAT type will display as a text field that only accepts numbers. + */ + var FLOAT = "float"; + + /** + * The BOOL type will display as a checkbox. + */ + var BOOL = "bool"; + + /** + * The ENUM type will display as a dropdown. + * Make sure to specify the `keys` field in the schema. + */ + var ENUM = "enum"; +} diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 600871e2f..de73cd957 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1,5 +1,8 @@ package funkin.data.song; +import funkin.play.event.SongEvent; +import funkin.data.event.SongEventData.SongEventParser; +import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.song.SongRegistry; import thx.semver.Version; @@ -617,6 +620,38 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR this = new SongEventDataRaw(time, event, value); } + public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic + { + if (this.value == null) return {}; + if (Std.isOfType(this.value, Array)) + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.value); + return cast result; + } + else if (Reflect.isObject(this.value)) + { + // We enter this case if the value is a struct. + return cast this.value; + } + else + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.value); + return cast result; + } + } + + public inline function getHandler():Null + { + return SongEventParser.getEvent(this.event); + } + + public inline function getSchema():Null + { + return SongEventParser.getEventSchema(this.event); + } + public inline function getDynamic(key:String):Null { return this.value == null ? null : Reflect.field(this.value, key); @@ -662,6 +697,32 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR return this.value == null ? null : cast Reflect.field(this.value, key); } + public function buildTooltip():String + { + var eventHandler = getHandler(); + var eventSchema = getSchema(); + + if (eventSchema == null) return 'Unknown Event: ${this.event}'; + + var result = '${eventHandler.getTitle()}'; + + var defaultKey = eventSchema.getFirstField()?.name; + var valueStruct:haxe.DynamicAccess = valueAsStruct(defaultKey); + + for (pair in valueStruct.keyValueIterator()) + { + var key = pair.key; + var value = pair.value; + + var title = eventSchema.getByName(key)?.title ?? 'UnknownField'; + var valueStr = eventSchema.stringifyFieldValue(key, value); + + result += '\n- ${title}: ${valueStr}'; + } + + return result; + } + public function clone():SongEventData { return new SongEventData(this.time, this.event, this.value); diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 5f63254b0..c91769eb5 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: "char", title: "Character", @@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent step: 10.0, type: SongEventFieldType.FLOAT, } - ]; + ]); } } diff --git a/source/funkin/play/event/PlayAnimationSongEvent.hx b/source/funkin/play/event/PlayAnimationSongEvent.hx index 6bc625517..0f611874b 100644 --- a/source/funkin/play/event/PlayAnimationSongEvent.hx +++ b/source/funkin/play/event/PlayAnimationSongEvent.hx @@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'target', title: 'Target', @@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent type: SongEventFieldType.BOOL, defaultValue: false } - ]; + ]); } } diff --git a/source/funkin/play/event/SetCameraBopSongEvent.hx b/source/funkin/play/event/SetCameraBopSongEvent.hx index 3cdeb9a67..7d5fd4699 100644 --- a/source/funkin/play/event/SetCameraBopSongEvent.hx +++ b/source/funkin/play/event/SetCameraBopSongEvent.hx @@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'intensity', title: 'Intensity', @@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent step: 1, type: SongEventFieldType.INTEGER, } - ]; + ]); } } diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 1ae76039e..9a361f71b 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -99,7 +99,7 @@ class ZoomCameraSongEvent extends SongEvent */ public override function getEventSchema():SongEventSchema { - return [ + return new SongEventSchema([ { name: 'zoom', title: 'Zoom Level', @@ -145,6 +145,6 @@ class ZoomCameraSongEvent extends SongEvent 'Elastic In/Out' => 'elasticInOut', ] } - ]; + ]); } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 4f96fad69..5c12e3408 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2113,7 +2113,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(gridGhostHoldNote); gridGhostHoldNote.zIndex = 11; - gridGhostEvent = new ChartEditorEventSprite(this); + gridGhostEvent = new ChartEditorEventSprite(this, true); gridGhostEvent.alpha = 0.6; gridGhostEvent.eventData = new SongEventData(-1, '', {}); gridGhostEvent.visible = false; @@ -3127,6 +3127,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Setting event data resets position relative to the grid so we fix that. eventSprite.x += renderedEvents.x; eventSprite.y += renderedEvents.y; + eventSprite.updateTooltipPosition(); } // Add hold notes that have been made visible (but not their parents) diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 4c9d91407..cc9acf344 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -11,6 +11,9 @@ import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxTileFrames; import flixel.math.FlxPoint; import funkin.data.song.SongData.SongEventData; +import haxe.ui.tooltips.ToolTipRegionOptions; +import funkin.util.HaxeUIUtil; +import haxe.ui.tooltips.ToolTipManager; /** * A sprite that can be used to display a song event in a chart. @@ -36,6 +39,13 @@ class ChartEditorEventSprite extends FlxSprite public var overrideStepTime(default, set):Null = null; + public var tooltip:ToolTipRegionOptions; + + /** + * Whether this sprite is a "ghost" sprite used when hovering to place a new event. + */ + public var isGhost:Bool = false; + function set_overrideStepTime(value:Null):Null { if (overrideStepTime == value) return overrideStepTime; @@ -45,12 +55,14 @@ class ChartEditorEventSprite extends FlxSprite return overrideStepTime; } - public function new(parent:ChartEditorState) + public function new(parent:ChartEditorState, isGhost:Bool = false) { super(); this.parentState = parent; + this.isGhost = isGhost; + this.tooltip = HaxeUIUtil.buildTooltip('N/A'); this.frames = buildFrames(); buildAnimations(); @@ -140,6 +152,7 @@ class ChartEditorEventSprite extends FlxSprite // Disown parent. MAKE SURE TO REVIVE BEFORE REUSING this.kill(); this.visible = false; + updateTooltipPosition(); return null; } else @@ -151,6 +164,8 @@ class ChartEditorEventSprite extends FlxSprite this.eventData = value; // Update the position to match the note data. updateEventPosition(); + // Update the tooltip text. + this.tooltip.tipData = {text: this.eventData.buildTooltip()}; return this.eventData; } } @@ -169,6 +184,31 @@ class ChartEditorEventSprite extends FlxSprite this.x += origin.x; this.y += origin.y; } + + this.updateTooltipPosition(); + } + + public function updateTooltipPosition():Void + { + // No tooltip for ghost sprites. + if (this.isGhost) return; + + if (this.eventData == null) + { + // Disable the tooltip. + ToolTipManager.instance.unregisterTooltipRegion(this.tooltip); + } + else + { + // Update the position. + this.tooltip.left = this.x; + this.tooltip.top = this.y; + this.tooltip.width = this.width; + this.tooltip.height = this.height; + + // Enable the tooltip. + ToolTipManager.instance.registerTooltipRegion(this.tooltip); + } } /** diff --git a/source/funkin/util/HaxeUIUtil.hx b/source/funkin/util/HaxeUIUtil.hx new file mode 100644 index 000000000..1ffd9cd40 --- /dev/null +++ b/source/funkin/util/HaxeUIUtil.hx @@ -0,0 +1,17 @@ +package funkin.util; + +import haxe.ui.tooltips.ToolTipRegionOptions; + +class HaxeUIUtil +{ + public static function buildTooltip(text:String, ?left:Float, ?top:Float, ?width:Float, ?height:Float):ToolTipRegionOptions + { + return { + tipData: {text: text}, + left: left ?? 0.0, + top: top ?? 0.0, + width: width ?? 0.0, + height: height ?? 0.0 + } + } +} From a3123cd64c00ee1c617328f3dd46bc6be7681de1 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 5 Jan 2024 02:35:41 -0500 Subject: [PATCH 027/129] Measure numbers next to ticks and support for initial time signature. --- assets | 2 +- source/Main.hx | 1 + .../ui/debug/charting/ChartEditorState.hx | 82 ++++++++++++------- .../components/ChartEditorMeasureTicks.hx | 71 ++++++++++++++++ .../handlers/ChartEditorAudioHandler.hx | 2 +- .../handlers/ChartEditorDialogHandler.hx | 1 + .../ChartEditorImportExportHandler.hx | 1 + .../handlers/ChartEditorThemeHandler.hx | 82 ++++++++++++++++--- .../toolboxes/ChartEditorMetadataToolbox.hx | 24 ++++++ 9 files changed, 225 insertions(+), 41 deletions(-) create mode 100644 source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx diff --git a/assets b/assets index 9ecc4d26f..d768f62af 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9ecc4d26fe6b26f31782cccfcd7331bd8a318ce1 +Subproject commit d768f62af4966066ebe123ea511046d90692248d diff --git a/source/Main.hx b/source/Main.hx index 5fbb6747b..86e520e69 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -112,5 +112,6 @@ class Main extends Sprite Toolkit.theme = 'dark'; // don't be cringe Toolkit.autoScale = false; funkin.input.Cursor.registerHaxeUICursors(); + haxe.ui.tooltips.ToolTipManager.defaultDelay = 200; } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 4f96fad69..569311e43 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -15,6 +15,7 @@ import flixel.group.FlxSpriteGroup; import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; import flixel.math.FlxPoint; +import flixel.graphics.FlxGraphic; import flixel.math.FlxRect; import flixel.sound.FlxSound; import flixel.system.FlxAssets.FlxSoundAsset; @@ -80,6 +81,7 @@ import funkin.ui.debug.charting.components.ChartEditorEventSprite; import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite; import funkin.ui.debug.charting.components.ChartEditorNotePreview; import funkin.ui.debug.charting.components.ChartEditorNoteSprite; +import funkin.ui.debug.charting.components.ChartEditorMeasureTicks; import funkin.ui.debug.charting.components.ChartEditorPlaybarHead; import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite; import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler; @@ -168,7 +170,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The width of the scroll area. */ - public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = 12; + public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = Std.int(GRID_SIZE); /** * The height of the playhead, in pixels. @@ -334,17 +336,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.scrollPositionInPixels = value; // Move the grid sprite to the correct position. - if (gridTiledSprite != null && gridPlayheadScrollArea != null) + if (gridTiledSprite != null && measureTicks != null) { if (isViewDownscroll) { gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); - gridPlayheadScrollArea.y = gridTiledSprite.y; + measureTicks.y = gridTiledSprite.y; } else { gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); - gridPlayheadScrollArea.y = gridTiledSprite.y; + measureTicks.y = gridTiledSprite.y; if (audioVisGroup != null && audioVisGroup.playerVis != null) { @@ -366,6 +368,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff; // Update the note preview viewport box. setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); + // Update the measure tick display. + if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0; return this.scrollPositionInPixels; } @@ -1630,23 +1634,28 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var notePreviewViewportBitmap:Null = null; + /**r + * The IMAGE used for the measure ticks. Updated by ChartEditorThemeHandler. + */ + var measureTickBitmap:Null = null; + /** * The tiled sprite used to display the grid. * The height is the length of the song, and scrolling is done by simply the sprite. */ var gridTiledSprite:Null = null; + /** + * The measure ticks area. Includes the numbers and the background sprite. + */ + var measureTicks:Null = null; + /** * The playhead representing the current position in the song. * Can move around on the grid independently of the view. */ var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup(); - /** - * The sprite for the scroll area under - */ - var gridPlayheadScrollArea:Null = null; - /** * A sprite used to indicate the note that will be placed on click. */ @@ -1868,6 +1877,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.updateTheme(); buildGrid(); + buildMeasureTicks(); buildNotePreview(); buildSelectionBox(); @@ -2122,20 +2132,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState buildNoteGroup(); - gridPlayheadScrollArea = new FlxSprite(0, 0); - gridPlayheadScrollArea.makeGraphic(10, 10, PLAYHEAD_SCROLL_AREA_COLOR); // Make it 10x10px and then scale it as needed. - add(gridPlayheadScrollArea); - gridPlayheadScrollArea.setGraphicSize(PLAYHEAD_SCROLL_AREA_WIDTH, 3000); - gridPlayheadScrollArea.updateHitbox(); - gridPlayheadScrollArea.x = gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH; - gridPlayheadScrollArea.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; - gridPlayheadScrollArea.zIndex = 25; - // The playhead that show the current position in the song. add(gridPlayhead); gridPlayhead.zIndex = 30; - var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2); + var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH); var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD; gridPlayhead.setPosition(gridTiledSprite.x, playheadBaseYPos); var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR); @@ -2166,11 +2167,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(audioVisGroup); } + function buildMeasureTicks():Void + { + measureTicks = new ChartEditorMeasureTicks(this); + var measureTicksWidth = (GRID_SIZE); + measureTicks.x = gridTiledSprite.x - measureTicksWidth; + measureTicks.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; + measureTicks.zIndex = 20; + + add(measureTicks); + } + function buildNotePreview():Void { var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - PLAYBAR_HEIGHT - GRID_TOP_PAD - GRID_TOP_PAD; notePreview = new ChartEditorNotePreview(height); - notePreview.x = 350; + notePreview.x = 320; notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; add(notePreview); @@ -2250,6 +2262,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState bounds.height = MIN_HEIGHT; } + trace('Note preview viewport bounds: ' + bounds.toString()); + return bounds; } @@ -2828,7 +2842,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying)) { - playMetronomeTick(Conductor.currentBeat % 4 == 0); + playMetronomeTick(Conductor.currentBeat % Conductor.beatsPerMeasure == 0); } return true; @@ -3533,7 +3547,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { scrollAnchorScreenPos = null; } - else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea) && !isCursorOverHaxeUI) + else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks) && !isCursorOverHaxeUI) { gridPlayheadScrollAreaPressed = true; } @@ -4211,7 +4225,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { targetCursorMode = Pointer; } - else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea)) + else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks)) { targetCursorMode = Pointer; } @@ -4505,7 +4519,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Visibly center the Dad health icon. if (healthIconDad != null) { - healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2)); + healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 75 - (healthIconDad.width / 2)); healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2)); } } @@ -4991,11 +5005,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function onSongLengthChanged():Void { - if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels; - if (gridPlayheadScrollArea != null) + if (gridTiledSprite != null) { - gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels); - gridPlayheadScrollArea.updateHitbox(); + gridTiledSprite.height = songLengthInPixels; + } + if (measureTicks != null) + { + measureTicks.setHeight(songLengthInPixels); } // Remove any notes past the end of the song. @@ -5103,6 +5119,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectedDifficulty = prevDifficulty; Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges); + updateTimeSignature(); refreshDifficultyTreeSelection(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); @@ -5242,6 +5259,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = vocalTargetVolume; } + function updateTimeSignature():Void + { + // Redo the grid bitmap to be 4/4. + this.updateTheme(); + gridTiledSprite.loadGraphic(gridBitmap); + measureTicks.reloadTickBitmap(); + } + /** * HAXEUI FUNCTIONS */ @@ -5354,6 +5379,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (notePreviewViewportBoundsDirty) { setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); + notePreviewViewportBoundsDirty = false; } } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx new file mode 100644 index 000000000..38ac30236 --- /dev/null +++ b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx @@ -0,0 +1,71 @@ +package funkin.ui.debug.charting.components; + +import flixel.FlxSprite; +import flixel.addons.display.FlxTiledSprite; +import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; +import flixel.text.FlxText; +import flixel.util.FlxColor; + +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorMeasureTicks extends FlxTypedSpriteGroup +{ + var chartEditorState:ChartEditorState; + + var tickTiledSprite:FlxTiledSprite; + var measureNumber:FlxText; + + override function set_y(value:Float):Float + { + var result = super.set_y(value); + + updateMeasureNumber(); + + return result; + } + + public function new(chartEditorState:ChartEditorState) + { + super(); + + this.chartEditorState = chartEditorState; + + tickTiledSprite = new FlxTiledSprite(chartEditorState.measureTickBitmap, chartEditorState.measureTickBitmap.width, 1000, false, true); + add(tickTiledSprite); + + measureNumber = new FlxText(0, 0, ChartEditorState.GRID_SIZE, "1"); + measureNumber.setFormat(Paths.font('vcr.ttf'), 20, FlxColor.WHITE); + measureNumber.borderStyle = FlxTextBorderStyle.OUTLINE; + measureNumber.borderColor = FlxColor.BLACK; + add(measureNumber); + } + + public function reloadTickBitmap():Void + { + tickTiledSprite.loadGraphic(chartEditorState.measureTickBitmap); + } + + /** + * At time of writing, we only have to manipulate one measure number because we can only see one measure at a time. + */ + function updateMeasureNumber() + { + if (measureNumber == null) return; + + var viewTopPosition = 0 - this.y; + var viewHeight = FlxG.height - ChartEditorState.MENU_BAR_HEIGHT - ChartEditorState.PLAYBAR_HEIGHT; + var viewBottomPosition = viewTopPosition + viewHeight; + + var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.stepsPerMeasure) + 1; + var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure; + + measureNumber.text = '${measureNumberInViewport + 1}'; + measureNumber.y = measureNumberPosition + this.y; + + // trace(measureNumber.text + ' at ' + measureNumber.y); + } + + public function setHeight(songLengthInPixels:Float):Void + { + tickTiledSprite.height = songLengthInPixels; + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 990ab41ae..4a45d454c 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -194,7 +194,7 @@ class ChartEditorAudioHandler case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); state.audioVisGroup.addOpponentVis(vocalTrack); - state.audioVisGroup.opponentVis.x = 435; + state.audioVisGroup.opponentVis.x = 405; state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 2ede1a39f..175b0460a 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -686,6 +686,7 @@ class ChartEditorDialogHandler Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + state.updateTimeSignature(); state.selectedVariation = Constants.DEFAULT_VARIATION; state.selectedDifficulty = state.availableDifficulties[0]; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx index 267d2208a..63f8e8c71 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorImportExportHandler.hx @@ -117,6 +117,7 @@ class ChartEditorImportExportHandler Conductor.forceBPM(null); // Disable the forced BPM. Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata. Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + state.updateTimeSignature(); state.notePreviewDirty = true; state.notePreviewViewportBoundsDirty = true; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index 4197ebdd3..020df566c 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -81,6 +81,7 @@ class ChartEditorThemeHandler { updateBackground(state); updateGridBitmap(state); + updateMeasureTicks(state); updateSelectionSquare(state); updateNotePreview(state); } @@ -207,9 +208,6 @@ class ChartEditorThemeHandler } } - // Divider at top - state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); - // Draw vertical dividers between the strumlines. var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme) @@ -233,6 +231,61 @@ class ChartEditorThemeHandler // Else, gridTiledSprite will be built later. } + static function updateMeasureTicks(state:ChartEditorState):Void + { + var measureTickWidth:Int = 6; + var beatTickWidth:Int = 4; + var stepTickWidth:Int = 2; + + // Draw the measure ticks. + var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares wide. + var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure); // 1 measure tall. + state.measureTickBitmap = new BitmapData(ticksWidth, ticksHeight, true); + state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK); + + // Draw the measure ticks. + state.measureTickBitmap.fillRect(new Rectangle(0, 0, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + var bottomTickY:Float = state.measureTickBitmap.height - (measureTickWidth / 2); + state.measureTickBitmap.fillRect(new Rectangle(0, bottomTickY, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + + // Draw the beat ticks. + var beatTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.beatsPerMeasure - (beatTickWidth / 2); + var beatTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.beatsPerMeasure - (beatTickWidth / 2); + var beatTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.beatsPerMeasure - (beatTickWidth / 2); + var beatTickLength:Float = state.measureTickBitmap.width * 2 / 3; + state.measureTickBitmap.fillRect(new Rectangle(0, beatTick2Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, beatTick3Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, beatTick4Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + + // Draw the step ticks. + // TODO: Make this a loop or something. + var stepTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick6Y:Float = state.measureTickBitmap.height * 5 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick7Y:Float = state.measureTickBitmap.height * 6 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick8Y:Float = state.measureTickBitmap.height * 7 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick10Y:Float = state.measureTickBitmap.height * 9 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick11Y:Float = state.measureTickBitmap.height * 10 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick12Y:Float = state.measureTickBitmap.height * 11 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick14Y:Float = state.measureTickBitmap.height * 13 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick15Y:Float = state.measureTickBitmap.height * 14 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick16Y:Float = state.measureTickBitmap.height * 15 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTickLength:Float = state.measureTickBitmap.width * 1 / 3; + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick2Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick3Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick4Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick6Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick7Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick8Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick10Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick11Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick12Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick14Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick15Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + state.measureTickBitmap.fillRect(new Rectangle(0, stepTick16Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + } + static function updateSelectionSquare(state:ChartEditorState):Void { var selectionSquareBorderColor:FlxColor = switch (state.currentTheme) @@ -289,14 +342,21 @@ class ChartEditorThemeHandler ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2), ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2)), viewportFillColor); - state.notePreviewViewport = new FlxSliceSprite(state.notePreviewViewportBitmap, - new FlxRect(SELECTION_SQUARE_BORDER_WIDTH - + 1, SELECTION_SQUARE_BORDER_WIDTH - + 1, ChartEditorState.GRID_SIZE - - (2 * SELECTION_SQUARE_BORDER_WIDTH + 2), - ChartEditorState.GRID_SIZE - - (2 * SELECTION_SQUARE_BORDER_WIDTH + 2)), - 32, 32); + if (state.notePreviewViewport != null) + { + state.notePreviewViewport.loadGraphic(state.notePreviewViewportBitmap); + } + else + { + state.notePreviewViewport = new FlxSliceSprite(state.notePreviewViewportBitmap, + new FlxRect(SELECTION_SQUARE_BORDER_WIDTH + + 1, SELECTION_SQUARE_BORDER_WIDTH + + 1, + ChartEditorState.GRID_SIZE + - (2 * SELECTION_SQUARE_BORDER_WIDTH + 2), ChartEditorState.GRID_SIZE + - (2 * SELECTION_SQUARE_BORDER_WIDTH + 2)), + 32, 32); + } } public static function buildPlayheadBlock():FlxSprite diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index bc9384cf3..3535f5113 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -116,6 +116,26 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox } }; + inputTimeSignature.onChange = function(event:UIEvent) { + var timeSignatureStr:String = event.data.text; + var timeSignature = timeSignatureStr.split('/'); + if (timeSignature.length != 2) return; + + var timeSignatureNum:Int = Std.parseInt(timeSignature[0]); + var timeSignatureDen:Int = Std.parseInt(timeSignature[1]); + + var previousTimeSignatureNum:Int = chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum; + var previousTimeSignatureDen:Int = chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen; + if (timeSignatureNum == previousTimeSignatureNum && timeSignatureDen == previousTimeSignatureDen) return; + + chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum = timeSignatureNum; + chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen = timeSignatureDen; + + trace('Time signature changed to ${timeSignatureNum}/${timeSignatureDen}'); + + chartEditorState.updateTimeSignature(); + }; + inputOffsetInst.onChange = function(event:UIEvent) { if (event.value == null) return; @@ -172,6 +192,10 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox frameVariation.text = 'Variation: ${chartEditorState.selectedVariation.toTitleCase()}'; frameDifficulty.text = 'Difficulty: ${chartEditorState.selectedDifficulty.toTitleCase()}'; + var currentTimeSignature = '${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum}/${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen}'; + trace('Setting time signature to ${currentTimeSignature}'); + inputTimeSignature.value = {id: currentTimeSignature, text: currentTimeSignature}; + var stageId:String = chartEditorState.currentSongMetadata.playData.stage; var stageData:Null = StageDataParser.parseStageData(stageId); if (inputStage != null) From 56aa6f1dd8f0826375ece7f925ff47c4ef4bbd7b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 15:29:15 -0500 Subject: [PATCH 028/129] submod update --- art | 2 +- assets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/art b/art index 1656bea53..03e7c2a23 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 1656bea5370c65879aaeb323e329f403c78071c5 +Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34 diff --git a/assets b/assets index 309e60cee..f8e22704a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 309e60ceea2c298e8bf34575359c74f64833479b +Subproject commit f8e22704a06b9f7763e795db19ba08cc90f66458 From ab316017f712be3a6b46f1b2f70ddde09b6e3b06 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 17:22:01 -0500 Subject: [PATCH 029/129] removed beatTime nullability? --- source/funkin/Conductor.hx | 29 +++++++++++++---------------- source/funkin/data/song/SongData.hx | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 09cdf7df4..05c23108f 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -352,24 +352,21 @@ class Conductor // Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`. if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0; - if (currentTimeChange.beatTime == null) + if (currentTimeChange.timeStamp <= 0.0) { - if (currentTimeChange.timeStamp <= 0.0) - { - currentTimeChange.beatTime = 0.0; - } - else - { - // Calculate the beat time of this timestamp. - currentTimeChange.beatTime = 0.0; + currentTimeChange.beatTime = 0.0; + } + else + { + // Calculate the beat time of this timestamp. + currentTimeChange.beatTime = 0.0; - if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0) - { - var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1]; - currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime - + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), - 4); - } + if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0) + { + var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1]; + currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime + + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), + 4); } } diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 98cee3f17..66874e5be 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -149,7 +149,7 @@ class SongTimeChange */ @:optional @:alias("b") - public var beatTime:Null; + public var beatTime:Float; /** * Quarter notes per minute (float). Cannot be empty in the first element of the list, From e25f3bc073c8b92000bd88003cd3691c13db041d Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:07:41 -0500 Subject: [PATCH 030/129] assets/art submomd --- art | 2 +- assets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/art b/art index 1656bea53..03e7c2a23 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 1656bea5370c65879aaeb323e329f403c78071c5 +Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34 diff --git a/assets b/assets index dec3191f2..4246be3aa 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit dec3191f29b7e5cd488f1e8ec0bf8a760e02142f +Subproject commit 4246be3aa353e43772760d02ae9ff262718dee06 From 97ea4b0891f9de979ac9869d3708a6abfc3824f9 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:23:15 -0500 Subject: [PATCH 031/129] re-add debug_refreshModules? --- source/funkin/ui/MusicBeatState.hx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 7d691c294..ffd065901 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -125,6 +125,16 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler ModuleHandler.callEvent(event); } + function debug_refreshModules() + { + PolymodHandler.forceReloadAssets(); + + this.destroy(); + + // Create a new instance of the current state, so old data is cleared. + FlxG.resetState(); + } + public function stepHit():Bool { var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep); From cc7577a333a05f803f937888c95d8f8f7a7770c8 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:25:54 -0500 Subject: [PATCH 032/129] fix conductor instance stuf on watchPlugin --- source/funkin/ui/MusicBeatState.hx | 12 ------------ source/funkin/util/plugins/WatchPlugin.hx | 12 ++++++------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index ffd065901..33333565f 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -80,18 +80,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler if (FlxG.keys.justPressed.F5) debug_refreshModules(); } - function handleQuickWatch():Void - { - // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); - FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); - FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.instance.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentBeatTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); - } - override function update(elapsed:Float) { super.update(elapsed); diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx index 5d718c2e0..17b2dd129 100644 --- a/source/funkin/util/plugins/WatchPlugin.hx +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -22,13 +22,13 @@ class WatchPlugin extends FlxBasic { super.update(elapsed); - FlxG.watch.addQuick("songPosition", Conductor.songPosition); - FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); + FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); + FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.currentMeasureTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + FlxG.watch.addQuick("bpm", Conductor.instance.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); } public override function destroy():Void From dcc79d56a3905c8e8278c7f6b361cfebc68c286f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:33:15 -0500 Subject: [PATCH 033/129] assets submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index d768f62af..b282f3431 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d768f62af4966066ebe123ea511046d90692248d +Subproject commit b282f3431c15b719222196813da98ab70839d3e5 From 350ce19783e05e686f61c6cb3577c020f5d66b54 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:37:13 -0500 Subject: [PATCH 034/129] conductor instnace small fix lol! --- source/funkin/ui/debug/charting/ChartEditorState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 14098b93b..1773a84fe 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2882,7 +2882,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying)) { - playMetronomeTick(Conductor.instance.currentBeat % Conductor.beatsPerMeasure == 0); + playMetronomeTick(Conductor.instance.currentBeat % Conductor.instance.beatsPerMeasure == 0); } // Show the mouse cursor. From ff88c53f7ccf38077bf0935c3526a19770755f20 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:38:20 -0500 Subject: [PATCH 035/129] steps per measure instance conductor fix --- .../components/ChartEditorMeasureTicks.hx | 4 +-- .../handlers/ChartEditorThemeHandler.hx | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx index 38ac30236..1a76d1e22 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx @@ -55,8 +55,8 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup var viewHeight = FlxG.height - ChartEditorState.MENU_BAR_HEIGHT - ChartEditorState.PLAYBAR_HEIGHT; var viewBottomPosition = viewTopPosition + viewHeight; - var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.stepsPerMeasure) + 1; - var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure; + var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.instance.stepsPerMeasure) + 1; + var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure; measureNumber.text = '${measureNumberInViewport + 1}'; measureNumber.y = measureNumberPosition + this.y; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index f6087c688..90f01f1b3 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -239,7 +239,7 @@ class ChartEditorThemeHandler // Draw the measure ticks. var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares wide. - var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure); // 1 measure tall. + var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); // 1 measure tall. state.measureTickBitmap = new BitmapData(ticksWidth, ticksHeight, true); state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK); @@ -259,18 +259,18 @@ class ChartEditorThemeHandler // Draw the step ticks. // TODO: Make this a loop or something. - var stepTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick6Y:Float = state.measureTickBitmap.height * 5 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick7Y:Float = state.measureTickBitmap.height * 6 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick8Y:Float = state.measureTickBitmap.height * 7 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick10Y:Float = state.measureTickBitmap.height * 9 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick11Y:Float = state.measureTickBitmap.height * 10 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick12Y:Float = state.measureTickBitmap.height * 11 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick14Y:Float = state.measureTickBitmap.height * 13 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick15Y:Float = state.measureTickBitmap.height * 14 / Conductor.stepsPerMeasure - (stepTickWidth / 2); - var stepTick16Y:Float = state.measureTickBitmap.height * 15 / Conductor.stepsPerMeasure - (stepTickWidth / 2); + var stepTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick6Y:Float = state.measureTickBitmap.height * 5 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick7Y:Float = state.measureTickBitmap.height * 6 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick8Y:Float = state.measureTickBitmap.height * 7 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick10Y:Float = state.measureTickBitmap.height * 9 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick11Y:Float = state.measureTickBitmap.height * 10 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick12Y:Float = state.measureTickBitmap.height * 11 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick14Y:Float = state.measureTickBitmap.height * 13 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick15Y:Float = state.measureTickBitmap.height * 14 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); + var stepTick16Y:Float = state.measureTickBitmap.height * 15 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); var stepTickLength:Float = state.measureTickBitmap.width * 1 / 3; state.measureTickBitmap.fillRect(new Rectangle(0, stepTick2Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); state.measureTickBitmap.fillRect(new Rectangle(0, stepTick3Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); From ac902c19c5de8b81d089e0122680caf25d84fe3d Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 5 Jan 2024 20:38:46 -0500 Subject: [PATCH 036/129] beats per measure conductor instance fix --- .../ui/debug/charting/handlers/ChartEditorThemeHandler.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index 90f01f1b3..d3aef4bfd 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -249,9 +249,9 @@ class ChartEditorThemeHandler state.measureTickBitmap.fillRect(new Rectangle(0, bottomTickY, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); // Draw the beat ticks. - var beatTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.beatsPerMeasure - (beatTickWidth / 2); - var beatTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.beatsPerMeasure - (beatTickWidth / 2); - var beatTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.beatsPerMeasure - (beatTickWidth / 2); + var beatTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2); + var beatTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2); + var beatTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2); var beatTickLength:Float = state.measureTickBitmap.width * 2 / 3; state.measureTickBitmap.fillRect(new Rectangle(0, beatTick2Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); state.measureTickBitmap.fillRect(new Rectangle(0, beatTick3Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); From 14df32d90860ad218dd4104d167a9b0cca3fc505 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 9 Jan 2024 14:48:20 -0500 Subject: [PATCH 037/129] Implement haxelib versions into crash logs --- Project.xml | 1 + source/funkin/util/Constants.hx | 7 ++- source/funkin/util/logging/CrashHandler.hx | 11 ++++ source/funkin/util/macro/HaxelibVersions.hx | 67 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 source/funkin/util/macro/HaxelibVersions.hx diff --git a/Project.xml b/Project.xml index e0677b026..a298c919f 100644 --- a/Project.xml +++ b/Project.xml @@ -111,6 +111,7 @@ + diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 123267a49..197fa28e8 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -70,7 +70,7 @@ class Constants public static final URL_KICKSTARTER:String = 'https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/'; /** - * GIT REPO DATA + * REPOSITORY DATA */ // ============================== @@ -86,6 +86,11 @@ class Constants public static final GIT_HASH:String = funkin.util.macro.GitCommit.getGitCommitHash(); #end + /** + * The current library versions, as provided by hmm. + */ + public static final LIBRARY_VERSIONS:Array = funkin.util.macro.HaxelibVersions.getLibraryVersions(); + /** * COLORS */ diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx index a21732048..e32ba2c42 100644 --- a/source/funkin/util/logging/CrashHandler.hx +++ b/source/funkin/util/logging/CrashHandler.hx @@ -123,6 +123,17 @@ class CrashHandler fullContents += '=====================\n'; + fullContents += 'Haxelibs: \n'; + + for (lib in Constants.LIBRARY_VERSIONS) + { + fullContents += '- ${lib}\n'; + } + + fullContents += '\n'; + + fullContents += '=====================\n'; + fullContents += '\n'; fullContents += message; diff --git a/source/funkin/util/macro/HaxelibVersions.hx b/source/funkin/util/macro/HaxelibVersions.hx new file mode 100644 index 000000000..f0317c397 --- /dev/null +++ b/source/funkin/util/macro/HaxelibVersions.hx @@ -0,0 +1,67 @@ +package funkin.util.macro; + +import haxe.io.Path; + +class HaxelibVersions +{ + public static macro function getLibraryVersions():haxe.macro.Expr.ExprOf> + { + #if !display + return macro $v{formatHmmData(readHmmData())}; + #else + // `#if display` is used for code completion. In this case returning an + // empty string is good enough; We don't want to call functions on every hint. + var commitHash:String = ""; + return macro $v{commitHashSplice}; + #end + } + + #if (debug && macro) + static function readHmmData():hmm.HmmConfig + { + return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow(); + } + + static function formatHmmData(hmmData:hmm.HmmConfig):Array + { + var result:Array = []; + + for (library in hmmData.dependencies) + { + switch (library) + { + case Haxelib(name, version): + result.push('${name} haxelib(${o(version)})'); + case Git(name, url, ref, dir): + result.push('${name} git(${url}/${o(dir, '')}:${o(ref)})'); + case Mercurial(name, url, ref, dir): + result.push('${name} mercurial(${url}/${o(dir, '')}:${o(ref)})'); + case Dev(name, path): + result.push('${name} dev(${path})'); + } + } + + return result; + } + + static function o(option:haxe.ds.Option, defaultValue:String = 'None'):String + { + switch (option) + { + case Some(value): + return value; + case None: + return defaultValue; + } + } + + static function readLibraryCurrentVersion(libraryName:String):String + { + var path = Path.join([Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.current']); + // This is compile time so we should always have Sys available. + var result = sys.io.File.getContent(path); + + return result; + } + #end +} From 27d5a6d5a5975d87e52d968969a18e76523aa819 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 9 Jan 2024 19:31:39 -0500 Subject: [PATCH 038/129] Update Project.xml --- Project.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.xml b/Project.xml index a298c919f..74da3b749 100644 --- a/Project.xml +++ b/Project.xml @@ -128,7 +128,7 @@ - + From 59999aa8fd6fa8367e326f1d29a6d1721ebc31a6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jan 2024 00:16:51 -0500 Subject: [PATCH 039/129] Fix an issue with release builds --- source/funkin/util/macro/HaxelibVersions.hx | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 source/funkin/util/macro/HaxelibVersions.hx diff --git a/source/funkin/util/macro/HaxelibVersions.hx b/source/funkin/util/macro/HaxelibVersions.hx new file mode 100644 index 000000000..1a4699bba --- /dev/null +++ b/source/funkin/util/macro/HaxelibVersions.hx @@ -0,0 +1,67 @@ +package funkin.util.macro; + +import haxe.io.Path; + +class HaxelibVersions +{ + public static macro function getLibraryVersions():haxe.macro.Expr.ExprOf> + { + #if !display + return macro $v{formatHmmData(readHmmData())}; + #else + // `#if display` is used for code completion. In this case returning an + // empty string is good enough; We don't want to call functions on every hint. + var commitHash:Array = []; + return macro $v{commitHash}; + #end + } + + #if (macro) + static function readHmmData():hmm.HmmConfig + { + return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow(); + } + + static function formatHmmData(hmmData:hmm.HmmConfig):Array + { + var result:Array = []; + + for (library in hmmData.dependencies) + { + switch (library) + { + case Haxelib(name, version): + result.push('${name} haxelib(${o(version)})'); + case Git(name, url, ref, dir): + result.push('${name} git(${url}/${o(dir, '')}:${o(ref)})'); + case Mercurial(name, url, ref, dir): + result.push('${name} mercurial(${url}/${o(dir, '')}:${o(ref)})'); + case Dev(name, path): + result.push('${name} dev(${path})'); + } + } + + return result; + } + + static function o(option:haxe.ds.Option, defaultValue:String = 'None'):String + { + switch (option) + { + case Some(value): + return value; + case None: + return defaultValue; + } + } + + static function readLibraryCurrentVersion(libraryName:String):String + { + var path = Path.join([Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.current']); + // This is compile time so we should always have Sys available. + var result = sys.io.File.getContent(path); + + return result; + } + #end +} From 043fb553f6b46fa3e4700777888cc3031cc82554 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jan 2024 00:20:00 -0500 Subject: [PATCH 040/129] Fix an issue causing an overflow error when using gamepad (WINDOWS ONLY) --- Project.xml | 5 +-- hmm.json | 2 +- source/funkin/play/PlayState.hx | 6 ++-- source/funkin/util/tools/Int64Tools.hx | 46 ++++++++++++++++---------- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Project.xml b/Project.xml index e0677b026..16af32868 100644 --- a/Project.xml +++ b/Project.xml @@ -111,6 +111,7 @@ + @@ -130,8 +131,8 @@ - - + diff --git a/hmm.json b/hmm.json index d461edd24..bc07b944d 100644 --- a/hmm.json +++ b/hmm.json @@ -107,7 +107,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "737b86f121cdc90358d59e2e527934f267c94a2c", + "ref": "17086ec7fa4535ddb54d01fdc00a318bb8ce6413", "url": "https://github.com/FunkinCrew/lime" }, { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 995797dd1..9f98d3b04 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2270,8 +2270,10 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; - trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); + // trace('Compare: ${PreciseInputManager.getCurrentTimestamp()} - ${input.timestamp}'); + var inputLatencyNs:Int64 = PreciseInputManager.getCurrentTimestamp() - input.timestamp; + var inputLatencyMs:Float = inputLatencyNs.toFloat() / Constants.NS_PER_MS; + // trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. // Round inward (trim remainder) for consistency. diff --git a/source/funkin/util/tools/Int64Tools.hx b/source/funkin/util/tools/Int64Tools.hx index 75448b36f..d53fa315d 100644 --- a/source/funkin/util/tools/Int64Tools.hx +++ b/source/funkin/util/tools/Int64Tools.hx @@ -1,32 +1,42 @@ package funkin.util.tools; +import haxe.Int64; + /** - * @see https://github.com/fponticelli/thx.core/blob/master/src/thx/Int64s.hx + * Why `haxe.Int64` doesn't have a built-in `toFloat` function is beyond me. */ class Int64Tools { - static var min = haxe.Int64.make(0x80000000, 0); - static var one = haxe.Int64.make(0, 1); - static var two = haxe.Int64.ofInt(2); - static var zero = haxe.Int64.make(0, 0); - static var ten = haxe.Int64.ofInt(10); + private inline static var MAX_32_PRECISION:Float = 4294967296.0; - public static function toFloat(i:haxe.Int64):Float + public static function fromFloat(f:Float):Int64 { - var isNegative = false; - if (i < 0) + var h = Std.int(f / MAX_32_PRECISION); + var l = Std.int(f); + return Int64.make(h, l); + } + + public static function toFloat(i:Int64):Float + { + var f:Float = Int64.getLow(i); + if (f < 0) f += MAX_32_PRECISION; + return (Int64.getHigh(i) * MAX_32_PRECISION + f); + } + + public static function isToIntSafe(i:Int64):Bool + { + return i.high != i.low >> 31; + } + + public static function toIntSafe(i:Int64):Int + { + try { - if (i < min) return -9223372036854775808.0; // most -ve value can't be made +ve - isNegative = true; - i = -i; + return Int64.toInt(i); } - var multiplier = 1.0, ret = 0.0; - for (_ in 0...64) + catch (e:Dynamic) { - if (haxe.Int64.and(i, one) != zero) ret += multiplier; - multiplier *= 2.0; - i = haxe.Int64.shr(i, 1); + throw 'Could not represent value "${Int64.toStr(i)}" as an integer.'; } - return (isNegative ? -1 : 1) * ret; } } From eea9ac1883360dbd934e1935534379f59f3abd05 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jan 2024 20:27:40 -0500 Subject: [PATCH 041/129] Update to include linux fix (???) --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index bc07b944d..cced726e2 100644 --- a/hmm.json +++ b/hmm.json @@ -107,7 +107,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "17086ec7fa4535ddb54d01fdc00a318bb8ce6413", + "ref": "fff39ba6fc64969cd51987ef7491d9345043dc5d", "url": "https://github.com/FunkinCrew/lime" }, { From 06964ce1e3ee82fa6041a9b27245146c0f5812af Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jan 2024 20:58:06 -0500 Subject: [PATCH 042/129] Fix HTML5 build issue --- source/funkin/ui/debug/charting/ChartEditorState.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 1773a84fe..851e3b2fa 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5333,6 +5333,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (displayAutosavePopup) { displayAutosavePopup = false; + #if sys Toolkit.callLater(() -> { var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [ @@ -5342,6 +5343,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } ]); }); + #else + // TODO: No auto-save on HTML5? + #end } moveSongToScrollPosition(); From fc12f956f60609c7c0dc8d17f979b0e6d8497918 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 10 Jan 2024 20:58:29 -0500 Subject: [PATCH 043/129] Fix alignment of character pixel icons in freeplay menu --- source/funkin/ui/freeplay/SongMenuItem.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 4e0772dfe..833187acb 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -94,7 +94,7 @@ class SongMenuItem extends FlxSpriteGroup add(songText); grpHide.add(songText); - pixelIcon = new FlxSprite(155, 15); + pixelIcon = new FlxSprite(160, 35); pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; From 462eece09a2a30d495e038264ea0b39d6a3a997f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 11 Jan 2024 00:30:00 -0500 Subject: [PATCH 044/129] Use proper difficulty rating data, proper clear percentage --- source/funkin/play/song/Song.hx | 6 + source/funkin/ui/AtlasText.hx | 1 + source/funkin/ui/freeplay/FreeplayState.hx | 144 +++++++++++++++++---- source/funkin/ui/freeplay/SongMenuItem.hx | 46 ++++--- 4 files changed, 150 insertions(+), 47 deletions(-) diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 9e5de6143..cde068f42 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -176,6 +176,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = null; + public function new(song:Song, diffId:String, variation:String) { this.song = song; diff --git a/source/funkin/ui/AtlasText.hx b/source/funkin/ui/AtlasText.hx index fea09de54..186d87c2a 100644 --- a/source/funkin/ui/AtlasText.hx +++ b/source/funkin/ui/AtlasText.hx @@ -274,4 +274,5 @@ enum abstract AtlasFont(String) from String to String { var DEFAULT = "default"; var BOLD = "bold"; + var FREEPLAY_CLEAR = "freeplay-clear"; } diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index f17c3d91e..cfe7f802a 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1,6 +1,5 @@ package funkin.ui.freeplay; -import funkin.input.Controls; import flash.text.TextField; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; @@ -23,33 +22,35 @@ import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; -import funkin.input.Controls.Control; import funkin.data.level.LevelRegistry; import funkin.data.song.SongRegistry; import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.shaders.AngleMask; import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.PureColor; -import funkin.util.MathUtil; import funkin.graphics.shaders.StrokeShader; +import funkin.input.Controls; +import funkin.input.Controls.Control; import funkin.play.components.HealthIcon; import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; import funkin.save.Save; import funkin.save.Save.SaveScoreData; +import funkin.ui.AtlasText; import funkin.ui.freeplay.BGScrollingText; import funkin.ui.freeplay.DifficultyStars; import funkin.ui.freeplay.DJBoyfriend; import funkin.ui.freeplay.FreeplayScore; import funkin.ui.freeplay.LetterSort; import funkin.ui.freeplay.SongMenuItem; +import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatSubState; -import funkin.ui.mainmenu.MainMenuState; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; import funkin.util.MathUtil; +import funkin.util.MathUtil; import lime.app.Future; import lime.utils.Assets; @@ -64,7 +65,7 @@ class FreeplayState extends MusicBeatSubState var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY; var fp:FreeplayScore; - var txtCompletion:FlxText; + var txtCompletion:AtlasText; var lerpCompletion:Float = 0; var intendedCompletion:Float = 0; var lerpScore:Float = 0; @@ -87,6 +88,8 @@ class FreeplayState extends MusicBeatSubState var grpCapsules:FlxTypedGroup; var curCapsule:SongMenuItem; var curPlaying:Bool = false; + var ostName:FlxText; + var difficultyStars:DifficultyStars; var dj:DJBoyfriend; @@ -150,15 +153,10 @@ class FreeplayState extends MusicBeatSubState for (songId in LevelRegistry.instance.parseEntryData(levelId).songs) { var song:Song = SongRegistry.instance.fetchEntry(songId); - var songBaseDifficulty:SongDifficulty = song.getDifficulty(Constants.DEFAULT_DIFFICULTY); - var songName = songBaseDifficulty.songName; - var songOpponent = songBaseDifficulty.characters.opponent; - var songDifficulties = song.listDifficulties(); + songs.push(new FreeplaySongData(levelId, songId, song)); - songs.push(new FreeplaySongData(songId, songName, levelId, songOpponent, songDifficulties)); - - for (difficulty in songDifficulties) + for (difficulty in song.listDifficulties()) { diffIdsTotal.pushUnique(difficulty); } @@ -334,6 +332,8 @@ class FreeplayState extends MusicBeatSubState if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; } + // NOTE: This is an AtlasSprite because we use an animation to bring it into view. + // TODO: Add the ability to select the album graphic. var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll")); albumArt.visible = false; add(albumArt); @@ -347,7 +347,7 @@ class FreeplayState extends MusicBeatSubState var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1')); var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); - var difficultyStars:DifficultyStars = new DifficultyStars(140, 39); + difficultyStars = new DifficultyStars(140, 39); difficultyStars.stars.visible = false; albumTitle.visible = false; @@ -382,10 +382,15 @@ class FreeplayState extends MusicBeatSubState add(overhangStuff); FlxTween.tween(overhangStuff, {y: 0}, 0.3, {ease: FlxEase.quartOut}); - var fnfFreeplay:FlxText = new FlxText(0, 12, 0, "FREEPLAY", 48); + var fnfFreeplay:FlxText = new FlxText(4, 10, 0, "FREEPLAY", 48); fnfFreeplay.font = "VCR OSD Mono"; fnfFreeplay.visible = false; + ostName = new FlxText(4, 10, FlxG.width - 4 - 4, "OFFICIAL OST", 48); + ostName.font = "VCR OSD Mono"; + ostName.alignment = RIGHT; + ostName.visible = false; + exitMovers.set([overhangStuff, fnfFreeplay], { y: -overhangStuff.height, @@ -398,7 +403,7 @@ class FreeplayState extends MusicBeatSubState fnfFreeplay.shader = sillyStroke; add(fnfFreeplay); - var fnfHighscoreSpr:FlxSprite = new FlxSprite(890, 70); + var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 24, false); fnfHighscoreSpr.visible = false; @@ -415,8 +420,10 @@ class FreeplayState extends MusicBeatSubState fp.visible = false; add(fp); - txtCompletion = new FlxText(1200, 77, 0, "0", 32); - txtCompletion.font = "VCR OSD Mono"; + var clearBoxSprite:FlxSprite = new FlxSprite(1165, 65).loadGraphic(Paths.image('freeplay/clearBox')); + add(clearBoxSprite); + + txtCompletion = new AtlasText(1185, 87, "69", AtlasFont.FREEPLAY_CLEAR); txtCompletion.visible = false; add(txtCompletion); @@ -674,9 +681,32 @@ class FreeplayState extends MusicBeatSubState lerpScore = MathUtil.coolLerp(lerpScore, intendedScore, 0.2); lerpCompletion = MathUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9); + if (Math.isNaN(lerpScore)) + { + lerpScore = intendedScore; + } + + if (Math.isNaN(lerpCompletion)) + { + lerpCompletion = intendedCompletion; + } + fp.updateScore(Std.int(lerpScore)); - txtCompletion.text = Math.floor(lerpCompletion * 100) + "%"; + txtCompletion.text = '${Math.floor(lerpCompletion * 100)}'; + + // Right align the completion percentage + switch (txtCompletion.text.length) + { + case 3: + txtCompletion.x = 1185 - 10; + case 2: + txtCompletion.x = 1185; + case 1: + txtCompletion.x = 1185 + 24; + default: + txtCompletion.x = 1185; + } handleInputs(elapsed); } @@ -913,6 +943,11 @@ class FreeplayState extends MusicBeatSubState intendedCompletion = 0.0; } + if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion)) + { + intendedCompletion = 0; + } + grpDifficulties.group.forEach(function(diffSprite) { diffSprite.visible = false; }); @@ -938,6 +973,27 @@ class FreeplayState extends MusicBeatSubState } } } + + if (change != 0) + { + // Update the song capsules to reflect the new difficulty info. + for (songCapsule in grpCapsules.members) + { + if (songCapsule == null) continue; + if (songCapsule.songData != null) + { + songCapsule.songData.currentDifficulty = currentDifficulty; + songCapsule.init(null, null, songCapsule.songData); + } + else + { + songCapsule.init(null, null, null); + } + } + } + + // Set the difficulty star count on the right. + difficultyStars.difficulty = daSong.songRating; } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) @@ -1046,6 +1102,10 @@ class FreeplayState extends MusicBeatSubState { currentDifficulty = rememberedDifficulty; } + + // Set the difficulty star count on the right. + var daSong = songs[curSelected]; + difficultyStars.difficulty = daSong?.songRating ?? 0; } function changeSelection(change:Int = 0) @@ -1176,19 +1236,47 @@ class FreeplaySongData { public var isFav:Bool = false; - public var songId:String = ""; - public var songName:String = ""; - public var levelId:String = ""; - public var songCharacter:String = ""; - public var songDifficulties:Array = []; + var song:Song; - public function new(songId:String, songName:String, levelId:String, songCharacter:String, songDifficulties:Array) + public var levelId(default, null):String = ""; + public var songId(default, null):String = ""; + + public var songDifficulties(default, null):Array = []; + + public var songName(default, null):String = ""; + public var songCharacter(default, null):String = ""; + public var songRating(default, null):Int = 0; + + public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + + function set_currentDifficulty(value:String):String + { + if (currentDifficulty == value) return value; + + currentDifficulty = value; + updateValues(); + return value; + } + + public function new(levelId:String, songId:String, song:Song) { - this.songId = songId; - this.songName = songName; this.levelId = levelId; - this.songCharacter = songCharacter; - this.songDifficulties = songDifficulties; + this.songId = songId; + this.song = song; + + updateValues(); + } + + function updateValues():Void + { + this.songDifficulties = song.listDifficulties(); + if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY; + + var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty); + if (songDifficulty == null) return; + this.songName = songDifficulty.songName; + this.songCharacter = songDifficulty.characters.opponent; + this.songRating = songDifficulty.difficultyRating; } } diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 833187acb..88b6aef3c 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -35,11 +35,6 @@ class SongMenuItem extends FlxSpriteGroup var ranks:Array = ["fail", "average", "great", "excellent", "perfect"]; - // lol... - var diffRanks:Array = [ - "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "14", "15" - ]; - public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; public var doJumpIn:Bool = false; @@ -47,10 +42,12 @@ class SongMenuItem extends FlxSpriteGroup public var doJumpOut:Bool = false; public var onConfirm:Void->Void; - public var diffGrayscale:Grayscale; + public var grayscaleShader:Grayscale; public var hsvShader(default, set):HSVShader; + var diffRatingSprite:FlxSprite; + public function new(x:Float, y:Float) { super(x, y); @@ -75,26 +72,30 @@ class SongMenuItem extends FlxSpriteGroup add(ranking); grpHide.add(ranking); - diffGrayscale = new Grayscale(1); - - var diffRank = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRankings/diff" + FlxG.random.getObject(diffRanks))); - diffRank.shader = diffGrayscale; - diffRank.visible = false; - add(diffRank); - diffRank.origin.set(capsule.origin.x - diffRank.x, capsule.origin.y - diffRank.y); - grpHide.add(diffRank); - switch (rank) { case "perfect": ranking.x -= 10; } + grayscaleShader = new Grayscale(1); + + diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRatings/diff00")); + diffRatingSprite.shader = grayscaleShader; + diffRatingSprite.visible = false; + add(diffRatingSprite); + diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y); + grpHide.add(diffRatingSprite); + songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled)); add(songText); grpHide.add(songText); + // TODO: Use value from metadata instead of random. + updateDifficultyRating(FlxG.random.int(0, 15)); + pixelIcon = new FlxSprite(160, 35); + pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; @@ -113,6 +114,12 @@ class SongMenuItem extends FlxSpriteGroup setVisibleGrp(false); } + function updateDifficultyRating(newRating:Int) + { + var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; + diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}')); + } + function set_hsvShader(value:HSVShader):HSVShader { this.hsvShader = value; @@ -149,16 +156,17 @@ class SongMenuItem extends FlxSpriteGroup updateSelected(); } - public function init(x:Float, y:Float, songData:Null) + public function init(?x:Float, ?y:Float, songData:Null) { - this.x = x; - this.y = y; + if (x != null) this.x = x; + if (y != null) this.y = y; this.songData = songData; // Update capsule text. songText.text = songData?.songName ?? 'Random'; // Update capsule character. if (songData?.songCharacter != null) setCharacter(songData.songCharacter); + updateDifficultyRating(songData?.songRating ?? 0); // Update opacity, offsets, etc. updateSelected(); } @@ -336,7 +344,7 @@ class SongMenuItem extends FlxSpriteGroup function updateSelected():Void { - diffGrayscale.setAmount(this.selected ? 0 : 0.8); + grayscaleShader.setAmount(this.selected ? 0 : 0.8); songText.alpha = this.selected ? 1 : 0.6; songText.blurredText.visible = this.selected ? true : false; capsule.offset.x = this.selected ? 0 : -5; From 315aeea998eb8945755f2d795bf10f1c5e3ab9ee Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 11 Jan 2024 00:31:18 -0500 Subject: [PATCH 045/129] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b282f3431..a92dc15de 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b282f3431c15b719222196813da98ab70839d3e5 +Subproject commit a92dc15de3a755d1ec7ec092dd057b2ff3dea0b4 From c05131e6a9192835e6e879bd6f2a42a6e1df1ad2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 11 Jan 2024 00:52:42 -0500 Subject: [PATCH 046/129] Additional freeplay cleanup --- source/funkin/ui/freeplay/FreeplayState.hx | 8 +++++--- source/funkin/ui/freeplay/SongMenuItem.hx | 9 ++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index cfe7f802a..dec199e98 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -382,16 +382,16 @@ class FreeplayState extends MusicBeatSubState add(overhangStuff); FlxTween.tween(overhangStuff, {y: 0}, 0.3, {ease: FlxEase.quartOut}); - var fnfFreeplay:FlxText = new FlxText(4, 10, 0, "FREEPLAY", 48); + var fnfFreeplay:FlxText = new FlxText(8, 8, 0, "FREEPLAY", 48); fnfFreeplay.font = "VCR OSD Mono"; fnfFreeplay.visible = false; - ostName = new FlxText(4, 10, FlxG.width - 4 - 4, "OFFICIAL OST", 48); + ostName = new FlxText(8, 8, FlxG.width - 8 - 8, "OFFICIAL OST", 48); ostName.font = "VCR OSD Mono"; ostName.alignment = RIGHT; ostName.visible = false; - exitMovers.set([overhangStuff, fnfFreeplay], + exitMovers.set([overhangStuff, fnfFreeplay, ostName], { y: -overhangStuff.height, x: 0, @@ -402,6 +402,7 @@ class FreeplayState extends MusicBeatSubState var sillyStroke = new StrokeShader(0xFFFFFFFF, 2, 2); fnfFreeplay.shader = sillyStroke; add(fnfFreeplay); + add(ostName); var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); @@ -492,6 +493,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(1 / 24, function(handShit) { fnfHighscoreSpr.visible = true; fnfFreeplay.visible = true; + ostName.visible = true; fp.visible = true; fp.updateScore(0); diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 88b6aef3c..06d113468 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -208,7 +208,14 @@ class SongMenuItem extends FlxSpriteGroup pixelIcon.loadGraphic(Paths.image(charPath)); pixelIcon.scale.x = pixelIcon.scale.y = 2; - pixelIcon.origin.x = 100; + + switch (char) + { + case "parents-christmas": + pixelIcon.origin.x = 140; + default: + pixelIcon.origin.x = 100; + } // pixelIcon.origin.x = capsule.origin.x; // pixelIcon.offset.x -= pixelIcon.origin.x; } From d19924ae4148d07b59a6b30284b9ac963d95fb98 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 12 Jan 2024 04:18:04 -0500 Subject: [PATCH 047/129] assets submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b282f3431..a3e2277e6 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b282f3431c15b719222196813da98ab70839d3e5 +Subproject commit a3e2277e6f12208f9a976b80883db67c54a2a897 From 025fd326bd5eb3378e390eb682875bd319891fc3 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 12 Jan 2024 06:13:34 -0500 Subject: [PATCH 048/129] Click and drag on a sustain to edit it. --- assets | 2 +- hmm.json | 4 +- source/funkin/data/song/SongData.hx | 36 +++- source/funkin/play/notes/SustainTrail.hx | 32 +++- .../ui/debug/charting/ChartEditorState.hx | 162 ++++++++++++++++-- .../commands/ExtendNoteLengthCommand.hx | 34 +++- .../components/ChartEditorHoldNoteSprite.hx | 37 +++- .../ChartEditorSelectionSquareSprite.hx | 21 ++- .../ChartEditorHoldNoteContextMenu.hx | 43 +++++ .../ChartEditorNoteContextMenu.hx | 5 + .../handlers/ChartEditorContextMenuHandler.hx | 18 ++ .../handlers/ChartEditorThemeHandler.hx | 2 +- 12 files changed, 358 insertions(+), 38 deletions(-) create mode 100644 source/funkin/ui/debug/charting/contextmenus/ChartEditorHoldNoteContextMenu.hx diff --git a/assets b/assets index b282f3431..7e31e86db 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b282f3431c15b719222196813da98ab70839d3e5 +Subproject commit 7e31e86dbeec3df5076895dedc62a45cc14d66e1 diff --git a/hmm.json b/hmm.json index d461edd24..8d05a7a2e 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "5086e59e7551d775ed4d1fb0188e31de22d1312b", + "ref": "2561076c5abeee0a60f3a2a65a8ecb7832a6a62a", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "2b9cff727999b53ed292b1675ac1c9089ac77600", + "ref": "9c8ab039524086f5a8c8f35b9fb14538b5bfba5d", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 1a726254f..708881429 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -826,7 +826,13 @@ class SongNoteDataRaw implements ICloneable @:alias("l") @:default(0) @:optional - public var length:Float; + public var length(default, set):Float; + + function set_length(value:Float):Float + { + _stepLength = null; + return length = value; + } /** * The kind of the note. @@ -883,6 +889,11 @@ class SongNoteDataRaw implements ICloneable return _stepTime = Conductor.instance.getTimeInSteps(this.time); } + /** + * The length of the note, if applicable, in steps. + * Calculated from the length and the BPM. + * Cached for performance. Set to `null` to recalculate. + */ @:jignored var _stepLength:Null = null; @@ -907,9 +918,14 @@ class SongNoteDataRaw implements ICloneable } else { - var lengthMs:Float = Conductor.instance.getStepTimeInMs(value) - this.time; + var endStep:Float = getStepTime() + value; + var endMs:Float = Conductor.instance.getStepTimeInMs(endStep); + var lengthMs:Float = endMs - this.time; + this.length = lengthMs; } + + // Recalculate the step length next time it's requested. _stepLength = null; } @@ -980,6 +996,10 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw @:op(A == B) public function op_equals(other:SongNoteData):Bool { + // Handle the case where one value is null. + if (this == null) return other == null; + if (other == null) return false; + if (this.kind == '') { if (other.kind != '' && other.kind != 'normal') return false; @@ -995,6 +1015,10 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw @:op(A != B) public function op_notEquals(other:SongNoteData):Bool { + // Handle the case where one value is null. + if (this == null) return other == null; + if (other == null) return false; + if (this.kind == '') { if (other.kind != '' && other.kind != 'normal') return true; @@ -1010,24 +1034,32 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw @:op(A > B) public function op_greaterThan(other:SongNoteData):Bool { + if (other == null) return false; + return this.time > other.time; } @:op(A < B) public function op_lessThan(other:SongNoteData):Bool { + if (other == null) return false; + return this.time < other.time; } @:op(A >= B) public function op_greaterThanOrEquals(other:SongNoteData):Bool { + if (other == null) return false; + return this.time >= other.time; } @:op(A <= B) public function op_lessThanOrEquals(other:SongNoteData):Bool { + if (other == null) return false; + return this.time <= other.time; } diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx index 7367b97af..4902afd49 100644 --- a/source/funkin/play/notes/SustainTrail.hx +++ b/source/funkin/play/notes/SustainTrail.hx @@ -82,6 +82,9 @@ class SustainTrail extends FlxSprite public var isPixel:Bool; + var graphicWidth:Float = 0; + var graphicHeight:Float = 0; + /** * Normally you would take strumTime:Float, noteData:Int, sustainLength:Float, parentNote:Note (?) * @param NoteData @@ -110,8 +113,8 @@ class SustainTrail extends FlxSprite zoom *= 0.7; // CALCULATE SIZE - width = graphic.width / 8 * zoom; // amount of notes * 2 - height = sustainHeight(sustainLength, getScrollSpeed()); + graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2 + graphicHeight = sustainHeight(sustainLength, getScrollSpeed()); // instead of scrollSpeed, PlayState.SONG.speed flipY = Preferences.downscroll; @@ -148,12 +151,21 @@ class SustainTrail extends FlxSprite if (sustainLength == s) return s; - height = sustainHeight(s, getScrollSpeed()); + graphicHeight = sustainHeight(s, getScrollSpeed()); this.sustainLength = s; updateClipping(); + updateHitbox(); return this.sustainLength; } + public override function updateHitbox():Void + { + width = graphicWidth; + height = graphicHeight; + offset.set(0, 0); + origin.set(width * 0.5, height * 0.5); + } + /** * Sets up new vertex and UV data to clip the trail. * If flipY is true, top and bottom bounds swap places. @@ -161,7 +173,7 @@ class SustainTrail extends FlxSprite */ public function updateClipping(songTime:Float = 0):Void { - var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, height); + var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, graphicHeight); if (clipHeight <= 0.1) { visible = false; @@ -178,10 +190,10 @@ class SustainTrail extends FlxSprite // ===HOLD VERTICES== // Top left vertices[0 * 2] = 0.0; // Inline with left side - vertices[0 * 2 + 1] = flipY ? clipHeight : height - clipHeight; + vertices[0 * 2 + 1] = flipY ? clipHeight : graphicHeight - clipHeight; // Top right - vertices[1 * 2] = width; + vertices[1 * 2] = graphicWidth; vertices[1 * 2 + 1] = vertices[0 * 2 + 1]; // Inline with top left vertex // Bottom left @@ -197,7 +209,7 @@ class SustainTrail extends FlxSprite } // Bottom right - vertices[3 * 2] = width; + vertices[3 * 2] = graphicWidth; vertices[3 * 2 + 1] = vertices[2 * 2 + 1]; // Inline with bottom left vertex // ===HOLD UVs=== @@ -233,7 +245,7 @@ class SustainTrail extends FlxSprite // Bottom left vertices[6 * 2] = vertices[2 * 2]; // Inline with left side - vertices[6 * 2 + 1] = flipY ? (graphic.height * (-bottomClip + endOffset) * zoom) : (height + graphic.height * (bottomClip - endOffset) * zoom); + vertices[6 * 2 + 1] = flipY ? (graphic.height * (-bottomClip + endOffset) * zoom) : (graphicHeight + graphic.height * (bottomClip - endOffset) * zoom); // Bottom right vertices[7 * 2] = vertices[3 * 2]; // Inline with right side @@ -277,6 +289,10 @@ class SustainTrail extends FlxSprite getScreenPosition(_point, camera).subtractPoint(offset); camera.drawTriangles(processedGraphic, vertices, indices, uvtData, null, _point, blend, true, antialiasing); } + + #if FLX_DEBUG + if (FlxG.debugger.drawDebug) drawDebug(); + #end } public override function kill():Void diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 1773a84fe..33bba450f 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -718,7 +718,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState * `null` if the user isn't currently placing a note. * As the user drags, we will update this note's sustain length, and finalize the note when they release. */ - var currentPlaceNoteData:Null = null; + var currentPlaceNoteData(default, set):Null = null; + + function set_currentPlaceNoteData(value:Null):Null + { + noteDisplayDirty = true; + + return currentPlaceNoteData = value; + } // Note Movement @@ -2270,7 +2277,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState bounds.height = MIN_HEIGHT; } - trace('Note preview viewport bounds: ' + bounds.toString()); + // trace('Note preview viewport bounds: ' + bounds.toString()); return bounds; } @@ -3047,8 +3054,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { if (holdNoteSprite == null || holdNoteSprite.noteData == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue; - if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD)) + if (holdNoteSprite.noteData == currentPlaceNoteData) { + // This hold note is for the note we are currently dragging. + // It will be displayed by gridGhostHoldNoteSprite instead. + holdNoteSprite.kill(); + } + else if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD)) + { + // This hold note is off-screen. + // Kill the hold note sprite and recycle it. holdNoteSprite.kill(); } else if (!currentSongChartNoteData.fastContains(holdNoteSprite.noteData) || holdNoteSprite.noteData.length == 0) @@ -3066,7 +3081,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { displayedHoldNoteData.push(holdNoteSprite.noteData); - // Update the event sprite's position. + // Update the event sprite's height and position. + // var holdNoteHeight = holdNoteSprite.noteData.getStepLength() * GRID_SIZE; + // holdNoteSprite.setHeightDirectly(holdNoteHeight); holdNoteSprite.updateHoldNotePosition(renderedNotes); } } @@ -3144,7 +3161,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState noteSprite.updateNotePosition(renderedNotes); // Add hold notes that are now visible (and not already displayed). - if (noteSprite.noteData != null && noteSprite.noteData.length > 0 && displayedHoldNoteData.indexOf(noteSprite.noteData) == -1) + if (noteSprite.noteData != null + && noteSprite.noteData.length > 0 + && displayedHoldNoteData.indexOf(noteSprite.noteData) == -1 + && noteSprite.noteData != currentPlaceNoteData) { var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this)); // trace('Creating new HoldNote... (${renderedHoldNotes.members.length})'); @@ -3157,6 +3177,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState holdNoteSprite.setHeightDirectly(noteLengthPixels); holdNoteSprite.updateHoldNotePosition(renderedHoldNotes); + + trace(holdNoteSprite.x + ', ' + holdNoteSprite.y + ', ' + holdNoteSprite.width + ', ' + holdNoteSprite.height); } } @@ -3195,6 +3217,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Is the note a hold note? if (noteData == null || noteData.length <= 0) continue; + // Is the note the one we are dragging? If so, ghostHoldNoteSprite will handle it. + if (noteData == currentPlaceNoteData) continue; + // Is the hold note rendered already? if (displayedHoldNoteData.indexOf(noteData) != -1) continue; @@ -3284,7 +3309,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectionSquare.x = noteSprite.x; selectionSquare.y = noteSprite.y; selectionSquare.width = GRID_SIZE; - selectionSquare.height = GRID_SIZE; + + var stepLength = noteSprite.noteData.getStepLength(); + selectionSquare.height = (stepLength <= 0) ? GRID_SIZE : ((stepLength + 1) * GRID_SIZE); } } @@ -3563,6 +3590,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var overlapsGrid:Bool = FlxG.mouse.overlaps(gridTiledSprite); + var overlapsRenderedNotes:Bool = FlxG.mouse.overlaps(renderedNotes); + var overlapsRenderedHoldNotes:Bool = FlxG.mouse.overlaps(renderedHoldNotes); + var overlapsRenderedEvents:Bool = FlxG.mouse.overlaps(renderedEvents); + // Cursor position relative to the grid. var cursorX:Float = FlxG.mouse.screenX - gridTiledSprite.x; var cursorY:Float = FlxG.mouse.screenY - gridTiledSprite.y; @@ -3804,12 +3835,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return event.alive && FlxG.mouse.overlaps(event); }); } + var highlightedHoldNote:Null = null; + if (highlightedNote == null && highlightedEvent == null) + { + highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool { + return holdNote.alive && FlxG.mouse.overlaps(holdNote); + }); + } if (FlxG.keys.pressed.CONTROL) { if (highlightedNote != null && highlightedNote.noteData != null) { - // TODO: Handle the case of clicking on a sustain piece. // Control click to select/deselect an individual note. if (isNoteSelected(highlightedNote.noteData)) { @@ -3832,6 +3869,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new SelectItemsCommand([], [highlightedEvent.eventData])); } } + else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) + { + // Control click to select/deselect an individual note. + if (isNoteSelected(highlightedNote.noteData)) + { + performCommand(new DeselectItemsCommand([highlightedHoldNote.noteData], [])); + } + else + { + performCommand(new SelectItemsCommand([highlightedHoldNote.noteData], [])); + } + } else { // Do nothing if you control-clicked on an empty space. @@ -3849,6 +3898,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Click an event to select it. performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); } + else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) + { + // Click a hold note to select it. + performCommand(new SetItemSelectionCommand([highlightedHoldNote.noteData], [], currentNoteSelection, currentEventSelection)); + } else { // Click on an empty space to deselect everything. @@ -4001,7 +4055,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var dragLengthMs:Float = dragLengthSteps * Conductor.instance.stepLengthMs; var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE; - if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null) + if (gridGhostHoldNote != null) { if (dragLengthSteps > 0) { @@ -4014,8 +4068,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } gridGhostHoldNote.visible = true; - gridGhostHoldNote.noteData = gridGhostNote.noteData; - gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); + gridGhostHoldNote.noteData = currentPlaceNoteData; + gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection(); gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true); @@ -4036,6 +4090,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Apply the new length. performCommand(new ExtendNoteLengthCommand(currentPlaceNoteData, dragLengthMs)); } + else + { + // Apply the new (zero) length if we are changing the length. + if (currentPlaceNoteData.length > 0) + { + this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI')); + performCommand(new ExtendNoteLengthCommand(currentPlaceNoteData, 0)); + } + } // Finished dragging. Release the note. currentPlaceNoteData = null; @@ -4068,6 +4131,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return event.alive && FlxG.mouse.overlaps(event); }); } + var highlightedHoldNote:Null = null; + if (highlightedNote == null && highlightedEvent == null) + { + highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool { + // If holdNote.alive is false, the holdNote is dead and awaiting recycling. + return holdNote.alive && FlxG.mouse.overlaps(holdNote); + }); + } if (FlxG.keys.pressed.CONTROL) { @@ -4094,6 +4165,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new SelectItemsCommand([], [highlightedEvent.eventData])); } } + else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) + { + if (isNoteSelected(highlightedNote.noteData)) + { + performCommand(new DeselectItemsCommand([highlightedHoldNote.noteData], [])); + } + else + { + performCommand(new SelectItemsCommand([highlightedHoldNote.noteData], [])); + } + } else { // Do nothing when control clicking nothing. @@ -4127,6 +4209,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection)); } } + else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) + { + // Clicked a hold note, start dragging TO EXTEND NOTE LENGTH. + currentPlaceNoteData = highlightedHoldNote.noteData; + } else { // Click a blank space to place a note and select it. @@ -4176,6 +4263,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return event.alive && FlxG.mouse.overlaps(event); }); } + var highlightedHoldNote:Null = null; + if (highlightedNote == null && highlightedEvent == null) + { + highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool { + // If holdNote.alive is false, the holdNote is dead and awaiting recycling. + return holdNote.alive && FlxG.mouse.overlaps(holdNote); + }); + } if (highlightedNote != null && highlightedNote.noteData != null) { @@ -4227,13 +4322,40 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new RemoveEventsCommand([highlightedEvent.eventData])); } } + else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null) + { + if (FlxG.keys.pressed.SHIFT) + { + // Shift + Right click opens the context menu. + // If we are clicking a large selection, open the Selection context menu, otherwise open the Note context menu. + var isHighlightedNoteSelected:Bool = isNoteSelected(highlightedHoldNote.noteData); + var useSingleNoteContextMenu:Bool = (!isHighlightedNoteSelected) + || (isHighlightedNoteSelected && currentNoteSelection.length == 1); + // Show the context menu connected to the note. + if (useSingleNoteContextMenu) + { + this.openHoldNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedHoldNote.noteData); + } + else + { + this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + } + else + { + // Right click removes hold from the note. + this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI')); + performCommand(new ExtendNoteLengthCommand(highlightedHoldNote.noteData, 0)); + } + } else { // Right clicked on nothing. } } - var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null; + var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null || overlapsRenderedNotes || overlapsRenderedHoldNotes + || overlapsRenderedEvents; // Handle grid cursor. if (!isCursorOverHaxeUI && overlapsGrid && !isOrWillSelect && !overlapsSelectionBorder && !gridPlayheadScrollAreaPressed) { @@ -4324,6 +4446,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { targetCursorMode = Crosshair; } + else if (overlapsRenderedNotes) + { + targetCursorMode = Pointer; + } + else if (overlapsRenderedHoldNotes) + { + targetCursorMode = Pointer; + } + else if (overlapsRenderedEvents) + { + targetCursorMode = Pointer; + } else if (overlapsGrid) { targetCursorMode = Cell; @@ -5042,7 +5176,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState throw "ERROR: Tried to build selection square, but selectionSquareBitmap is null! Check ChartEditorThemeHandler.updateSelectionSquare()"; // FlxG.bitmapLog.add(selectionSquareBitmap, "selectionSquareBitmap"); - var result = new ChartEditorSelectionSquareSprite(); + var result = new ChartEditorSelectionSquareSprite(this); result.loadGraphic(selectionSquareBitmap); return result; } @@ -5371,7 +5505,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * HAXEUI FUNCTIONS */ - // ==================== + // ================== /** * Set the currently selected item in the Difficulty tree view to the node representing the current difficulty. @@ -5462,7 +5596,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * STATIC FUNCTIONS */ - // ==================== + // ================== function handleNotePreview():Void { diff --git a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx index 47da0dde5..62ffe63b9 100644 --- a/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx @@ -13,17 +13,25 @@ class ExtendNoteLengthCommand implements ChartEditorCommand var note:SongNoteData; var oldLength:Float; var newLength:Float; + var unit:Unit; - public function new(note:SongNoteData, newLength:Float) + public function new(note:SongNoteData, newLength:Float, unit:Unit = MILLISECONDS) { this.note = note; this.oldLength = note.length; this.newLength = newLength; + this.unit = unit; } public function execute(state:ChartEditorState):Void { - note.length = newLength; + switch (unit) + { + case MILLISECONDS: + this.note.length = newLength; + case STEPS: + this.note.setStepLength(newLength); + } state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -36,7 +44,8 @@ class ExtendNoteLengthCommand implements ChartEditorCommand { state.playSound(Paths.sound('chartingSounds/undo')); - note.length = oldLength; + // Always use milliseconds for undoing + this.note.length = oldLength; state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -47,6 +56,23 @@ class ExtendNoteLengthCommand implements ChartEditorCommand public function toString():String { - return 'Extend Note Length'; + if (oldLength == 0) + { + return 'Add Hold to Note'; + } + else if (newLength == 0) + { + return 'Remove Hold from Note'; + } + else + { + return 'Extend Hold Note Length'; + } } } + +enum Unit +{ + MILLISECONDS; + STEPS; +} diff --git a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx index e5971db08..a7764907c 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx @@ -39,6 +39,17 @@ class ChartEditorHoldNoteSprite extends SustainTrail setup(); } + public override function updateHitbox():Void + { + // Expand the clickable hitbox to the full column width, then nudge to the left to re-center it. + width = ChartEditorState.GRID_SIZE; + height = graphicHeight; + + var xOffset = (ChartEditorState.GRID_SIZE - graphicWidth) / 2; + offset.set(-xOffset, 0); + origin.set(width * 0.5, height * 0.5); + } + /** * Set the height directly, to a value in pixels. * @param h The desired height in pixels. @@ -52,6 +63,23 @@ class ChartEditorHoldNoteSprite extends SustainTrail fullSustainLength = sustainLength; } + /** + * Call this to override how debug bounding boxes are drawn for this sprite. + */ + public override function drawDebugOnCamera(camera:flixel.FlxCamera):Void + { + if (!camera.visible || !camera.exists || !isOnScreen(camera)) return; + + var rect = getBoundingBox(camera); + trace('hold note bounding box: ' + rect.x + ', ' + rect.y + ', ' + rect.width + ', ' + rect.height); + + var gfx = beginDrawDebug(camera); + debugBoundingBoxColor = 0xffFF66FF; + gfx.lineStyle(2, color, 0.5); // thickness, color, alpha + gfx.drawRect(rect.x, rect.y, rect.width, rect.height); + endDrawDebug(camera); + } + function setup():Void { strumTime = 999999999; @@ -60,7 +88,9 @@ class ChartEditorHoldNoteSprite extends SustainTrail active = true; visible = true; alpha = 1.0; - width = graphic.width / 8 * zoom; // amount of notes * 2 + graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2 + + updateHitbox(); } public override function revive():Void @@ -154,7 +184,7 @@ class ChartEditorHoldNoteSprite extends SustainTrail } this.x += ChartEditorState.GRID_SIZE / 2; - this.x -= this.width / 2; + this.x -= this.graphicWidth / 2; this.y += ChartEditorState.GRID_SIZE / 2; @@ -163,5 +193,8 @@ class ChartEditorHoldNoteSprite extends SustainTrail this.x += origin.x; this.y += origin.y; } + + // Account for expanded clickable hitbox. + this.x += this.offset.x; } } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorSelectionSquareSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorSelectionSquareSprite.hx index 8f7c4aaec..14266b71a 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorSelectionSquareSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorSelectionSquareSprite.hx @@ -1,20 +1,33 @@ package funkin.ui.debug.charting.components; +import flixel.addons.display.FlxSliceSprite; import flixel.FlxSprite; -import funkin.data.song.SongData.SongNoteData; +import flixel.math.FlxRect; import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongNoteData; +import funkin.ui.debug.charting.handlers.ChartEditorThemeHandler; /** * A sprite that can be used to display a square over a selected note or event in the chart. * Designed to be used and reused efficiently. Has no gameplay functionality. */ -class ChartEditorSelectionSquareSprite extends FlxSprite +@:nullSafety +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorSelectionSquareSprite extends FlxSliceSprite { public var noteData:Null; public var eventData:Null; - public function new() + public function new(chartEditorState:ChartEditorState) { - super(); + super(chartEditorState.selectionSquareBitmap, + new FlxRect(ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + + 4, ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + + 4, + ChartEditorState.GRID_SIZE + - (2 * ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + 8), + ChartEditorState.GRID_SIZE + - (2 * ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + 8)), + 32, 32); } } diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorHoldNoteContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorHoldNoteContextMenu.hx new file mode 100644 index 000000000..9f58d2f03 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorHoldNoteContextMenu.hx @@ -0,0 +1,43 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongNoteData; +import funkin.ui.debug.charting.commands.FlipNotesCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; +import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/hold-note.xml")) +class ChartEditorHoldNoteContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuFlip:MenuItem; + var contextmenuDelete:MenuItem; + + var data:SongNoteData; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongNoteData) + { + super(chartEditorState2, xPos2, yPos2); + this.data = data; + + initialize(); + } + + function initialize():Void + { + // NOTE: Remember to use commands here to ensure undo/redo works properly + contextmenuFlip.onClick = function(_) { + chartEditorState.performCommand(new FlipNotesCommand([data])); + } + + contextmenuRemoveHold.onClick = function(_) { + chartEditorState.performCommand(new ExtendNoteLengthCommand(data, 0)); + } + + contextmenuDelete.onClick = function(_) { + chartEditorState.performCommand(new RemoveNotesCommand([data])); + } + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx index 4bfab27e8..66bf6f3ee 100644 --- a/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx @@ -6,6 +6,7 @@ import haxe.ui.core.Screen; import funkin.data.song.SongData.SongNoteData; import funkin.ui.debug.charting.commands.FlipNotesCommand; import funkin.ui.debug.charting.commands.RemoveNotesCommand; +import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; @:access(funkin.ui.debug.charting.ChartEditorState) @:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/note.xml")) @@ -31,6 +32,10 @@ class ChartEditorNoteContextMenu extends ChartEditorBaseContextMenu chartEditorState.performCommand(new FlipNotesCommand([data])); } + contextmenuAddHold.onClick = function(_) { + chartEditorState.performCommand(new ExtendNoteLengthCommand(data, 4, STEPS)); + } + contextmenuDelete.onClick = function(_) { chartEditorState.performCommand(new RemoveNotesCommand([data])); } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx index b914f4149..c1eea5379 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx @@ -2,6 +2,7 @@ package funkin.ui.debug.charting.handlers; import funkin.ui.debug.charting.contextmenus.ChartEditorDefaultContextMenu; import funkin.ui.debug.charting.contextmenus.ChartEditorEventContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorHoldNoteContextMenu; import funkin.ui.debug.charting.contextmenus.ChartEditorNoteContextMenu; import funkin.ui.debug.charting.contextmenus.ChartEditorSelectionContextMenu; import haxe.ui.containers.menus.Menu; @@ -23,16 +24,33 @@ class ChartEditorContextMenuHandler displayMenu(state, new ChartEditorDefaultContextMenu(state, xPos, yPos)); } + /** + * Opened when shift+right-clicking a selection of multiple items. + */ public static function openSelectionContextMenu(state:ChartEditorState, xPos:Float, yPos:Float) { displayMenu(state, new ChartEditorSelectionContextMenu(state, xPos, yPos)); } + /** + * Opened when shift+right-clicking a single note. + */ public static function openNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData) { displayMenu(state, new ChartEditorNoteContextMenu(state, xPos, yPos, data)); } + /** + * Opened when shift+right-clicking a single hold note. + */ + public static function openHoldNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData) + { + displayMenu(state, new ChartEditorHoldNoteContextMenu(state, xPos, yPos, data)); + } + + /** + * Opened when shift+right-clicking a single event. + */ public static function openEventContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongEventData) { displayMenu(state, new ChartEditorEventContextMenu(state, xPos, yPos, data)); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index d3aef4bfd..98bb5c2c8 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -52,7 +52,7 @@ class ChartEditorThemeHandler // Border on the square highlighting selected notes. static final SELECTION_SQUARE_BORDER_COLOR_LIGHT:FlxColor = 0xFF339933; static final SELECTION_SQUARE_BORDER_COLOR_DARK:FlxColor = 0xFF339933; - static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1; + public static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1; // Fill on the square highlighting selected notes. // Make sure this is transparent so you can see the notes underneath. From 1d1bab3b14933945876149fc509aa6950be2816c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 12 Jan 2024 06:14:22 -0500 Subject: [PATCH 049/129] assets merging --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a92dc15de..e56184c08 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a92dc15de3a755d1ec7ec092dd057b2ff3dea0b4 +Subproject commit e56184c0851e822136e3d254a51c89d54022938d From 25c10d205dae3727218b8f2e62fac7f3c352ab30 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 12 Jan 2024 06:35:41 -0500 Subject: [PATCH 050/129] Some positioning fixes --- source/funkin/ui/debug/charting/ChartEditorState.hx | 8 ++++---- .../ui/debug/charting/handlers/ChartEditorAudioHandler.hx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 64a2f19ed..dda779917 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -213,7 +213,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The X position of the note preview area. */ - public static final NOTE_PREVIEW_X_POS:Int = 350; + public static final NOTE_PREVIEW_X_POS:Int = 320; /** * The Y position of the note preview area. @@ -383,11 +383,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (audioVisGroup != null && audioVisGroup.playerVis != null) { - audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); + audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS - GRID_TOP_PAD); } if (audioVisGroup != null && audioVisGroup.opponentVis != null) { - audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS); + audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS - GRID_TOP_PAD); } } } @@ -4756,7 +4756,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Visibly center the Dad health icon. if (healthIconDad != null) { - var xOffset = 45 + (healthIconDad.width / 2); + var xOffset = 75 + (healthIconDad.width / 2); healthIconDad.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS - xOffset); var yOffset = 30 - (healthIconDad.height / 2); healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 0edba7357..d7fbd42c2 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -188,19 +188,19 @@ class ChartEditorAudioHandler state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.playerVis.detail = 1; - state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); + state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD); state.audioVocalTrackGroup.playerVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); return true; case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); state.audioVisGroup.addOpponentVis(vocalTrack); - state.audioVisGroup.opponentVis.x = 435; + state.audioVisGroup.opponentVis.x = 405; state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195; state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; state.audioVisGroup.opponentVis.detail = 1; - state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS); + state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD); state.audioVocalTrackGroup.opponentVoicesOffset = state.currentSongOffsets.getVocalOffset(charId); From 7f83daf23bf10098ebe47ffc7f73a6ef16bf885f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 12 Jan 2024 08:04:28 -0500 Subject: [PATCH 051/129] null fixy --- assets | 2 +- source/funkin/data/song/SongData.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index e56184c08..f1e42601b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit e56184c0851e822136e3d254a51c89d54022938d +Subproject commit f1e42601b6ea2026c6e2f4627c5738bfb8b7b524 diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 1a726254f..270195200 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -93,7 +93,7 @@ class SongMetadata implements ICloneable result.version = this.version; result.timeFormat = this.timeFormat; result.divisions = this.divisions; - result.offsets = this.offsets.clone(); + result.offsets = this.offsets != null ? this.offsets.clone() : new SongOffsets(); // if no song offsets found (aka null), so just create new ones result.timeChanges = this.timeChanges.deepClone(); result.looped = this.looped; result.playData = this.playData.clone(); From 9fe0c68256a33a5045e0070c245e17fe01aa1bf9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 12 Jan 2024 18:29:02 -0500 Subject: [PATCH 052/129] Add theme music toggle and extend fade in --- assets | 2 +- source/funkin/save/Save.hx | 28 +++++++++- .../ui/debug/charting/ChartEditorState.hx | 52 +++++++++++++++---- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/assets b/assets index f1e42601b..9d305889f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit f1e42601b6ea2026c6e2f4627c5738bfb8b7b524 +Subproject commit 9d305889f2e310afeddcda6a4be775eb630fb9ac diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 810d0fd93..1fbcc6c20 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -14,8 +14,8 @@ import thx.semver.Version; @:forward(volume, mute) abstract Save(RawSaveData) { - // Version 2.0.1 adds attributes to `optionsChartEditor`, that should return default values if they are null. - public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.1"; + // Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null. + public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2"; public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility. @@ -108,6 +108,7 @@ abstract Save(RawSaveData) metronomeVolume: 1.0, hitsoundsEnabledPlayer: true, hitsoundsEnabledOpponent: true, + themeMusic: true, instVolume: 1.0, voicesVolume: 1.0, playbackSpeed: 1.0, @@ -347,6 +348,23 @@ abstract Save(RawSaveData) return this.optionsChartEditor.hitsoundsEnabledOpponent; } + public var chartEditorThemeMusic(get, set):Bool; + + function get_chartEditorThemeMusic():Bool + { + if (this.optionsChartEditor.themeMusic == null) this.optionsChartEditor.themeMusic = true; + + return this.optionsChartEditor.themeMusic; + } + + function set_chartEditorThemeMusic(value:Bool):Bool + { + // Set and apply. + this.optionsChartEditor.themeMusic = value; + flush(); + return this.optionsChartEditor.themeMusic; + } + public var chartEditorInstVolume(get, set):Float; function get_chartEditorInstVolume():Float @@ -1027,6 +1045,12 @@ typedef SaveDataChartEditorOptions = */ var ?hitsoundsEnabledOpponent:Bool; + /** + * Theme music in the Chart Editor. + * @default `true` + */ + var ?themeMusic:Bool; + /** * Instrumental volume in the Chart Editor. * @default `1.0` diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 452e966d9..0853bf390 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -272,6 +272,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ public static final BASE_QUANT_INDEX:Int = 3; + /** + * The duration before the welcome music starts to fade back in after the user stops playing music in the chart editor. + */ + public static final WELCOME_MUSIC_FADE_IN_DELAY:Float = 30.0; + + /** + * The duration of the welcome music fade in. + */ + public static final WELCOME_MUSIC_FADE_IN_DURATION:Float = 10.0; + /** * INSTANCE DATA */ @@ -1636,6 +1646,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var menubarItemOpponentHitsounds:MenuCheckBox; + /** + * The `Audio -> Play Theme Music` menu checkbox. + */ + var menubarItemThemeMusic:MenuCheckBox; + /** * The `Audio -> Hitsound Volume` label. */ @@ -1921,6 +1936,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Set the z-index of the HaxeUI. this.root.zIndex = 100; + // Get rid of any music from the previous state. + if (FlxG.sound.music != null) FlxG.sound.music.stop(); + + // Play the welcome music. + setupWelcomeMusic(); + // Show the mouse cursor. Cursor.show(); @@ -1928,12 +1949,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState fixCamera(); - // Get rid of any music from the previous state. - if (FlxG.sound.music != null) FlxG.sound.music.stop(); - - // Play the welcome music. - setupWelcomeMusic(); - buildDefaultSongData(); buildBackground(); @@ -2027,6 +2042,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState hitsoundVolume = save.chartEditorHitsoundVolume; hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer; hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent; + this.welcomeMusic.active = save.chartEditorThemeMusic; // audioInstTrack.volume = save.chartEditorInstVolume; // audioInstTrack.pitch = save.chartEditorPlaybackSpeed; @@ -2056,6 +2072,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState save.chartEditorHitsoundVolume = hitsoundVolume; save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer; save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent; + save.chartEditorThemeMusic = this.welcomeMusic.active; // save.chartEditorInstVolume = audioInstTrack.volume; // save.chartEditorVoicesVolume = audioVocalTrackGroup.volume; @@ -2116,10 +2133,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function fadeInWelcomeMusic(?extraWait:Float = 0, ?fadeInTime:Float = 5):Void { + if (!this.welcomeMusic.active) + { + stopWelcomeMusic(); + return; + } + bgMusicTimer = new FlxTimer().start(extraWait, (_) -> { this.welcomeMusic.volume = 0; - this.welcomeMusic.play(); - this.welcomeMusic.fadeIn(fadeInTime, 0, 1.0); + if (this.welcomeMusic.active) + { + this.welcomeMusic.play(); + this.welcomeMusic.fadeIn(fadeInTime, 0, 1.0); + } }); } @@ -2750,6 +2776,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value; menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent; + menubarItemThemeMusic.onChange = event -> { + this.welcomeMusic.active = event.value; + fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); + }; + menubarItemThemeMusic.selected = this.welcomeMusic.active; + menubarItemVolumeHitsound.onChange = event -> { var volume:Float = event.value.toFloat() / 100.0; hitsoundVolume = volume; @@ -5619,7 +5651,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState moveSongToScrollPosition(); - fadeInWelcomeMusic(7, 10); + fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); // Reapply the volume. var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0; @@ -5855,7 +5887,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Pause stopAudioPlayback(); - fadeInWelcomeMusic(7, 10); + fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); } else { From 75dfed8229312185eb3d648dd2e4b8d41134344a Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 12 Jan 2024 19:50:13 -0500 Subject: [PATCH 053/129] -release fix --- .../ui/debug/charting/components/ChartEditorHoldNoteSprite.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx index a7764907c..c7f7747c0 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx @@ -63,6 +63,7 @@ class ChartEditorHoldNoteSprite extends SustainTrail fullSustainLength = sustainLength; } + #if FLX_DEBUG /** * Call this to override how debug bounding boxes are drawn for this sprite. */ @@ -79,6 +80,7 @@ class ChartEditorHoldNoteSprite extends SustainTrail gfx.drawRect(rect.x, rect.y, rect.width, rect.height); endDrawDebug(camera); } + #end function setup():Void { From 549d46117254b3c53d4f0798c6a4d7c0e501aafd Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 14 Jan 2024 06:46:58 -0500 Subject: [PATCH 054/129] Fix an issue where Song offsets would be null rather than zero. --- source/funkin/data/song/SongData.hx | 5 +++-- source/funkin/data/song/SongRegistry.hx | 9 +++++---- source/funkin/play/song/Song.hx | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 07ea38741..703ab406c 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -38,10 +38,11 @@ class SongMetadata implements ICloneable public var looped:Bool; /** - * Instrumental and vocal offsets. Optional, defaults to 0. + * Instrumental and vocal offsets. + * Defaults to an empty SongOffsets object. */ @:optional - public var offsets:SongOffsets; + public var offsets:Null; /** * Data relating to the song's gameplay. diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 5a0835f57..98b46c782 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -12,6 +12,7 @@ import funkin.util.VersionUtil; using funkin.data.song.migrator.SongDataMigrator; +@:nullSafety class SongRegistry extends BaseRegistry { /** @@ -31,7 +32,7 @@ class SongRegistry extends BaseRegistry public static final SONG_MUSIC_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; - public static var DEFAULT_GENERATEDBY(get, null):String; + public static var DEFAULT_GENERATEDBY(get, never):String; static function get_DEFAULT_GENERATEDBY():String { @@ -88,7 +89,7 @@ class SongRegistry extends BaseRegistry { try { - var entry:Song = createEntry(entryId); + var entry:Null = createEntry(entryId); if (entry != null) { trace(' Loaded entry data: ${entry}'); @@ -455,7 +456,7 @@ class SongRegistry extends BaseRegistry { variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var entryStr:Null = loadEntryMetadataFile(id, variation)?.contents; - var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr); + var entryVersion:Null = VersionUtil.getVersionFromJSON(entryStr); return entryVersion; } @@ -463,7 +464,7 @@ class SongRegistry extends BaseRegistry { variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var entryStr:Null = loadEntryChartFile(id, variation)?.contents; - var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr); + var entryVersion:Null = VersionUtil.getVersionFromJSON(entryStr); return entryVersion; } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index cde068f42..089eb6c92 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -174,7 +174,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry Date: Sun, 14 Jan 2024 06:47:17 -0500 Subject: [PATCH 055/129] Fix an issue with parsing enums in HScript. --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 67ba4d18d..b5db4b717 100644 --- a/hmm.json +++ b/hmm.json @@ -149,7 +149,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "80d1d309803c1b111866524f9769325e3b8b0b1b", + "ref": "cb11a95d0159271eb3587428cf4b9602e46dc469", "url": "https://github.com/larsiusprime/polymod" }, { From bf1107134924c6aaaf1b1ae1e69f394afed227c4 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sun, 14 Jan 2024 08:48:50 -0500 Subject: [PATCH 056/129] null check on daSong for difficultyStars --- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index dec199e98..de6484fd3 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -995,7 +995,7 @@ class FreeplayState extends MusicBeatSubState } // Set the difficulty star count on the right. - difficultyStars.difficulty = daSong.songRating; + difficultyStars.difficulty = daSong?.songRating ?? difficultyStars.difficulty; // yay haxe 4.3 } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) From 0f03021c209b9945c1046d4330d86a6c091de6a0 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sun, 14 Jan 2024 08:52:43 -0500 Subject: [PATCH 057/129] tooltip fix --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index b5db4b717..9a74ae54a 100644 --- a/hmm.json +++ b/hmm.json @@ -61,7 +61,7 @@ "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "9c8ab039524086f5a8c8f35b9fb14538b5bfba5d", + "ref": "4f1842e55a410014dd4a188b576b31019631493a", "url": "https://github.com/haxeui/haxeui-flixel" }, { From d53dbd67e3a943242317657e4af4c5c7ca52b35f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 14 Jan 2024 09:18:32 -0500 Subject: [PATCH 058/129] Rewrite to character animation, boyfriend death animation should be fixed --- assets | 2 +- source/funkin/audio/FunkinSound.hx | 20 +++ source/funkin/audio/SoundGroup.hx | 6 + .../play/character/MultiSparrowCharacter.hx | 149 ++++-------------- 4 files changed, 54 insertions(+), 123 deletions(-) diff --git a/assets b/assets index 9d305889f..d440b5937 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9d305889f2e310afeddcda6a4be775eb630fb9ac +Subproject commit d440b59376760ade87b922874f83dba8297ef22b diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 40293b0ce..278578fb3 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -107,6 +107,26 @@ class FunkinSound extends FlxSound return this; } + /** + * Called when the user clicks to focus on the window. + */ + override function onFocus():Void + { + if (!_alreadyPaused && this._shouldPlay) + { + resume(); + } + } + + /** + * Called when the user tabs away from the window. + */ + override function onFocusLost():Void + { + _alreadyPaused = _paused; + pause(); + } + public override function resume():FunkinSound { if (this._time < 0) diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx index 15c2296ca..528aaa80c 100644 --- a/source/funkin/audio/SoundGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -132,6 +132,12 @@ class SoundGroup extends FlxTypedGroup }); } + public override function destroy() + { + stop(); + super.destroy(); + } + /** * Remove all sounds from the group. */ diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx index 0fc07399c..48c5afb58 100644 --- a/source/funkin/play/character/MultiSparrowCharacter.hx +++ b/source/funkin/play/character/MultiSparrowCharacter.hx @@ -1,5 +1,6 @@ package funkin.play.character; +import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxFramesCollection; import funkin.modding.events.ScriptEvent; import funkin.util.assets.FlxAnimationUtil; @@ -7,35 +8,17 @@ import funkin.play.character.CharacterData.CharacterRenderType; /** * For some characters which use Sparrow atlases, the spritesheets need to be split - * into multiple files. This character renderer handles by showing the appropriate sprite. + * into multiple files. This character renderer concatenates these together into a single sprite. * * Examples in base game include BF Holding GF (most of the sprites are in one file * but the death animation is in a separate file). * Only example I can think of in mods is Tricky (which has a separate file for each animation). * - * BaseCharacter has game logic, SparrowCharacter has only rendering logic. + * BaseCharacter has game logic, MultiSparrowCharacter has only rendering logic. * KEEP THEM SEPARATE! - * - * TODO: Rewrite this to use a single frame collection. - * @see https://github.com/HaxeFlixel/flixel/issues/2587#issuecomment-1179620637 */ class MultiSparrowCharacter extends BaseCharacter { - /** - * The actual group which holds all spritesheets this character uses. - */ - var members:Map = new Map(); - - /** - * A map between animation names and what frame collection the animation should use. - */ - var animAssetPath:Map = new Map(); - - /** - * The current frame collection being used. - */ - var activeMember:String; - public function new(id:String) { super(id, CharacterRenderType.MultiSparrow); @@ -51,7 +34,7 @@ class MultiSparrowCharacter extends BaseCharacter function buildSprites():Void { - buildSpritesheets(); + buildSpritesheet(); buildAnimations(); if (_data.isPixel) @@ -66,95 +49,49 @@ class MultiSparrowCharacter extends BaseCharacter } } - function buildSpritesheets():Void + function buildSpritesheet():Void { - // TODO: This currently works by creating like 5 frame collections and switching between them. - // It would be better to refactor this to simply concatenate the frame collections together. - - // Build the list of asset paths to use. - // Ignore nulls and duplicates. - var assetList = [_data.assetPath]; + var assetList = []; for (anim in _data.animations) { if (anim.assetPath != null && !assetList.contains(anim.assetPath)) { assetList.push(anim.assetPath); } - animAssetPath.set(anim.name, anim.assetPath); } - // Load the Sparrow atlas for each path and store them in the members map. + var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath, 'shared'); + + if (texture == null) + { + trace('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}'); + } + else + { + trace('Creating multi-sparrow atlas: ${_data.assetPath}'); + texture.parent.destroyOnNoUse = false; + } + for (asset in assetList) { - var texture:FlxFramesCollection = Paths.getSparrowAtlas(asset, 'shared'); + var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset, 'shared'); // If we don't do this, the unused textures will be removed as soon as they're loaded. - if (texture == null) + if (subTexture == null) { - trace('Multi-Sparrow atlas could not load texture: ${asset}'); + trace('Multi-Sparrow atlas could not load subtexture: ${asset}'); } else { - trace('Adding multi-sparrow atlas: ${asset}'); - texture.parent.destroyOnNoUse = false; - members.set(asset, texture); + trace('Concatenating multi-sparrow atlas: ${asset}'); + subTexture.parent.destroyOnNoUse = false; } + + texture.addAtlas(subTexture); } - // Use the default frame collection to start. - loadFramesByAssetPath(_data.assetPath); - } - - /** - * Replace this sprite's animation frames with the ones at this asset path. - */ - function loadFramesByAssetPath(assetPath:String):Void - { - if (_data.assetPath == null) - { - trace('[ERROR] Multi-Sparrow character has no default asset path!'); - return; - } - if (assetPath == null) - { - // trace('Asset path is null, falling back to default. This is normal!'); - loadFramesByAssetPath(_data.assetPath); - return; - } - - if (this.activeMember == assetPath) - { - // trace('Already using this asset path: ${assetPath}'); - return; - } - - if (members.exists(assetPath)) - { - // Switch to a new set of sprites. - // trace('Loading frames from asset path: ${assetPath}'); - this.frames = members.get(assetPath); - this.activeMember = assetPath; - this.setScale(_data.scale); - } - else - { - trace('[WARN] MultiSparrow character ${characterId} could not find asset path: ${assetPath}'); - } - } - - /** - * Replace this sprite's animation frames with the ones needed to play this animation. - */ - function loadFramesByAnimName(animName) - { - if (animAssetPath.exists(animName)) - { - loadFramesByAssetPath(animAssetPath.get(animName)); - } - else - { - trace('[WARN] MultiSparrow character ${characterId} could not find animation: ${animName}'); - } + this.frames = texture; + this.setScale(_data.scale); } function buildAnimations() @@ -164,7 +101,6 @@ class MultiSparrowCharacter extends BaseCharacter // We need to swap to the proper frame collection before adding the animations, I think? for (anim in _data.animations) { - loadFramesByAnimName(anim.name); FlxAnimationUtil.addAtlasAnimation(this, anim); if (anim.offsets == null) @@ -187,37 +123,6 @@ class MultiSparrowCharacter extends BaseCharacter // unless we're forcing a new animation. if (!this.canPlayOtherAnims && !ignoreOther) return; - loadFramesByAnimName(name); super.playAnimation(name, restart, ignoreOther, reverse); } - - override function set_frames(value:FlxFramesCollection):FlxFramesCollection - { - // DISABLE THIS SO WE DON'T DESTROY OUR HARD WORK - // WE WILL MAKE SURE TO LOAD THE PROPER SPRITESHEET BEFORE PLAYING AN ANIM - // if (animation != null) - // { - // animation.destroyAnimations(); - // } - - if (value != null) - { - graphic = value.parent; - this.frames = value; - this.frame = value.getByIndex(0); - // this.numFrames = value.numFrames; - resetHelpers(); - this.bakedRotationAngle = 0; - this.animation.frameIndex = 0; - graphicLoaded(); - } - else - { - this.frames = null; - this.frame = null; - this.graphic = null; - } - - return this.frames; - } } From 3c4f5d596b96be71397e281d9b7193acb8b9f330 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sun, 14 Jan 2024 12:07:50 -0500 Subject: [PATCH 059/129] assets subjod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index d440b5937..9b9baa6f2 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d440b59376760ade87b922874f83dba8297ef22b +Subproject commit 9b9baa6f2b38dc9bd3354b350084f548e1e16c0f From 313fd55e985f1b3211d51581f41b03e05e9148cc Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 14:20:44 -0500 Subject: [PATCH 060/129] WIP --- source/funkin/play/PlayState.hx | 6 +++++- source/funkin/play/notes/Strumline.hx | 2 +- source/funkin/play/notes/SustainTrail.hx | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 9f98d3b04..c244fd60f 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1796,6 +1796,7 @@ class PlayState extends MusicBeatSubState { if (note == null) continue; + // TODO: Does this properly account for offsets? var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS; var hitWindowCenter = note.strumTime; var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; @@ -1865,7 +1866,10 @@ class PlayState extends MusicBeatSubState } // TODO: Potential penalty for dropping a hold note? - // if (holdNote.missedNote && !holdNote.handledMiss) { holdNote.handledMiss = true; } + if (holdNote.missedNote && !holdNote.handledMiss) + { + holdNote.handledMiss = true; + } } // Process notes on the player's side. diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index b312494cf..d56bb33ca 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -343,7 +343,7 @@ class Strumline extends FlxSpriteGroup playStatic(holdNote.noteDirection); holdNote.missedNote = true; holdNote.visible = true; - holdNote.alpha = 0.0; + holdNote.alpha = 0.0; // Completely hide the dropped hold note. } } diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx index 4902afd49..f5d444f61 100644 --- a/source/funkin/play/notes/SustainTrail.hx +++ b/source/funkin/play/notes/SustainTrail.hx @@ -47,6 +47,11 @@ class SustainTrail extends FlxSprite */ public var missedNote:Bool = false; + /** + * Set to `true` after handling additional logic for missing notes. + */ + public var handledMiss:Bool = false; + // maybe BlendMode.MULTIPLY if missed somehow, drawTriangles does not support! /** From 6f0f5ea6102d4646e0321d2f9927355151c623ad Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 15:33:28 -0500 Subject: [PATCH 061/129] Fix a crash when closing chart editor with no music playing --- source/funkin/ui/debug/charting/ChartEditorState.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 0853bf390..e98809ce8 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5951,9 +5951,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState ChartEditorNoteSprite.noteFrameCollection = null; // Stop the music. - welcomeMusic.destroy(); - audioInstTrack.destroy(); - audioVocalTrackGroup.destroy(); + if (welcomeMusic != null) welcomeMusic.destroy(); + if (audioInstTrack != null) audioInstTrack.destroy(); + if (audioVocalTrackGroup != null) audioVocalTrackGroup.destroy(); } function applyCanQuickSave():Void From 3366240eb5e02b17195dbeec86df328cd7048e6c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 18:21:49 -0500 Subject: [PATCH 062/129] Fix "Edit Event" menu item when right clicking chart events --- hmm.json | 4 ++-- .../charting/contextmenus/ChartEditorEventContextMenu.hx | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 9a74ae54a..d93ea0658 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "2561076c5abeee0a60f3a2a65a8ecb7832a6a62a", + "ref": "bb904f8b4b205755a310c23ff25219f9dcd62711", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "4f1842e55a410014dd4a188b576b31019631493a", + "ref": "1ec470c297afd7758a90dc9399aa1e3a4ea6ca0b", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx index a79125b21..d848f1435 100644 --- a/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx @@ -25,6 +25,10 @@ class ChartEditorEventContextMenu extends ChartEditorBaseContextMenu function initialize() { + contextmenuEdit.onClick = function(_) { + chartEditorState.showToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); + } + contextmenuDelete.onClick = function(_) { chartEditorState.performCommand(new RemoveEventsCommand([data])); } From e391c02015221908062401574c5f5e912ecfa4df Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 22:10:42 -0500 Subject: [PATCH 063/129] Polish note display visuals, and scrap Killer judgement. --- assets | 2 +- source/funkin/Highscore.hx | 2 - source/funkin/play/PlayState.hx | 83 +++++++++++++------ source/funkin/play/notes/NoteSprite.hx | 46 ++++++++++ source/funkin/play/notes/Strumline.hx | 23 +++-- source/funkin/play/notes/SustainTrail.hx | 1 + source/funkin/play/scoring/Scoring.hx | 4 +- source/funkin/save/Save.hx | 1 - .../funkin/save/migrator/SaveDataMigrator.hx | 6 -- source/funkin/util/Constants.hx | 6 ++ 10 files changed, 131 insertions(+), 43 deletions(-) diff --git a/assets b/assets index 9b9baa6f2..d094640f7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9b9baa6f2b38dc9bd3354b350084f548e1e16c0f +Subproject commit d094640f727a670a348b3579d11af5ff6a2ada3a diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index 2c18ffa2d..24b65832b 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -21,7 +21,6 @@ abstract Tallies(RawTallies) bad: 0, good: 0, sick: 0, - killer: 0, totalNotes: 0, totalNotesHit: 0, maxCombo: 0, @@ -43,7 +42,6 @@ typedef RawTallies = var bad:Int; var good:Int; var sick:Int; - var killer:Int; var maxCombo:Int; var isNewHighscore:Bool; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index c244fd60f..afb5400a3 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1865,17 +1865,30 @@ class PlayState extends MusicBeatSubState } } - // TODO: Potential penalty for dropping a hold note? if (holdNote.missedNote && !holdNote.handledMiss) { + // When the opponent drops a hold note. holdNote.handledMiss = true; + + // We dropped a hold note. + // Mute vocals and play miss animation, but don't penalize. + vocals.opponentVolume = 0; + currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); } } // Process notes on the player's side. for (note in playerStrumline.notes.members) { - if (note == null || note.hasBeenHit) continue; + if (note == null) continue; + + if (note.hasBeenHit) + { + note.tooEarly = false; + note.mayHit = false; + note.hasMissed = false; + continue; + } var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS; var hitWindowCenter = note.strumTime; @@ -1938,8 +1951,15 @@ class PlayState extends MusicBeatSubState songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed); } - // TODO: Potential penalty for dropping a hold note? - // if (holdNote.missedNote && !holdNote.handledMiss) { holdNote.handledMiss = true; } + if (holdNote.missedNote && !holdNote.handledMiss) + { + // The player dropped a hold note. + holdNote.handledMiss = true; + + // Mute vocals and play miss animation, but don't penalize. + vocals.playerVolume = 0; + currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true); + } } } @@ -2031,8 +2051,6 @@ class PlayState extends MusicBeatSubState trace('Hit note! ${targetNote.noteData}'); goodNoteHit(targetNote, input); - targetNote.visible = false; - targetNote.kill(); notesInDirection.remove(targetNote); // Play the strumline animation. @@ -2064,15 +2082,8 @@ class PlayState extends MusicBeatSubState // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - Highscore.tallies.combo++; - Highscore.tallies.totalNotesHit++; - - if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; - popUpScore(note, input); - playerStrumline.hitNote(note); - if (note.isHoldNote && note.holdNoteSprite != null) { playerStrumline.playNoteHoldCover(note.holdNoteSprite); @@ -2088,8 +2099,6 @@ class PlayState extends MusicBeatSubState function onNoteMiss(note:NoteSprite):Void { // a MISS is when you let a note scroll past you!! - Highscore.tallies.missed++; - var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2137,8 +2146,11 @@ class PlayState extends MusicBeatSubState } vocals.playerVolume = 0; + Highscore.tallies.missed++; + if (Highscore.tallies.combo != 0) { + // Break the combo. Highscore.tallies.combo = comboPopUps.displayCombo(0); } @@ -2286,29 +2298,54 @@ class PlayState extends MusicBeatSubState var score = Scoring.scoreNote(noteDiff, PBOT1); var daRating = Scoring.judgeNote(noteDiff, PBOT1); + if (daRating == 'miss') + { + // If daRating is 'miss', that means we made a mistake and should not continue. + trace('[WARNING] popUpScore judged a note as a miss!'); + // TODO: Remove this. + comboPopUps.displayRating('miss'); + return; + } + + // TODO: DEBUG OVERRIDE DON'T BE A DUMBASS + daRating = 'bad'; + + var isComboBreak = false; switch (daRating) { - case 'killer': - Highscore.tallies.killer += 1; - health += Constants.HEALTH_KILLER_BONUS; case 'sick': Highscore.tallies.sick += 1; health += Constants.HEALTH_SICK_BONUS; + isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; case 'good': Highscore.tallies.good += 1; health += Constants.HEALTH_GOOD_BONUS; + isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; case 'bad': Highscore.tallies.bad += 1; health += Constants.HEALTH_BAD_BONUS; + isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; case 'shit': Highscore.tallies.shit += 1; health += Constants.HEALTH_SHIT_BONUS; - case 'miss': - Highscore.tallies.missed += 1; - health -= Constants.HEALTH_MISS_PENALTY; + isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; } - if (daRating == "sick" || daRating == "killer") + if (isComboBreak) + { + // Break the combo, but don't increment tallies.misses. + Highscore.tallies.combo = comboPopUps.displayCombo(0); + } + else + { + Highscore.tallies.combo++; + Highscore.tallies.totalNotesHit++; + if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; + } + + playerStrumline.hitNote(daNote, !isComboBreak); + + if (daRating == "sick") { playerStrumline.playNoteSplash(daNote.noteData.getDirection()); } @@ -2444,7 +2481,6 @@ class PlayState extends MusicBeatSubState score: songScore, tallies: { - killer: Highscore.tallies.killer, sick: Highscore.tallies.sick, good: Highscore.tallies.good, bad: Highscore.tallies.bad, @@ -2495,7 +2531,6 @@ class PlayState extends MusicBeatSubState tallies: { // TODO: Sum up the values for the whole level! - killer: 0, sick: 0, good: 0, bad: 0, diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index e5c4786d3..0368b18e9 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -4,6 +4,7 @@ import funkin.data.song.SongData.SongNoteData; import funkin.play.notes.notestyle.NoteStyle; import flixel.graphics.frames.FlxAtlasFrames; import flixel.FlxSprite; +import funkin.graphics.shaders.HSVShader; class NoteSprite extends FlxSprite { @@ -11,6 +12,8 @@ class NoteSprite extends FlxSprite public var holdNoteSprite:SustainTrail; + var hsvShader:HSVShader; + /** * The time at which the note should be hit, in milliseconds. */ @@ -102,6 +105,8 @@ class NoteSprite extends FlxSprite this.strumTime = strumTime; this.direction = direction; + this.hsvShader = new HSVShader(); + if (this.strumTime < 0) this.strumTime = 0; setupNoteGraphic(noteStyle); @@ -116,16 +121,57 @@ class NoteSprite extends FlxSprite setGraphicSize(Strumline.STRUMLINE_SIZE); updateHitbox(); + + this.shader = hsvShader; + } + + #if FLX_DEBUG + /** + * Call this to override how debug bounding boxes are drawn for this sprite. + */ + public override function drawDebugOnCamera(camera:flixel.FlxCamera):Void + { + if (!camera.visible || !camera.exists || !isOnScreen(camera)) return; + + var gfx = beginDrawDebug(camera); + + var rect = getBoundingBox(camera); + trace('note sprite bounding box: ' + rect.x + ', ' + rect.y + ', ' + rect.width + ', ' + rect.height); + + gfx.lineStyle(2, 0xFFFF66FF, 0.5); // thickness, color, alpha + gfx.drawRect(rect.x, rect.y, rect.width, rect.height); + + gfx.lineStyle(2, 0xFFFFFF66, 0.5); // thickness, color, alpha + gfx.drawRect(rect.x, rect.y + rect.height / 2, rect.width, 1); + + endDrawDebug(camera); + } + #end + + public function desaturate():Void + { + this.hsvShader.saturation = 0.2; + } + + public function setHue(hue:Float):Void + { + this.hsvShader.hue = hue; } public override function revive():Void { super.revive(); + this.visible = true; + this.alpha = 1.0; this.active = false; this.tooEarly = false; this.hasBeenHit = false; this.mayHit = false; this.hasMissed = false; + + this.hsvShader.hue = 1.0; + this.hsvShader.saturation = 1.0; + this.hsvShader.value = 1.0; } public override function kill():Void diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index d56bb33ca..5fdd3945f 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -316,7 +316,7 @@ class Strumline extends FlxSpriteGroup // Update rendering of notes. for (note in notes.members) { - if (note == null || !note.alive || note.hasBeenHit) continue; + if (note == null || !note.alive) continue; var vwoosh:Bool = note.holdNoteSprite == null; // Set the note's position. @@ -384,10 +384,6 @@ class Strumline extends FlxSpriteGroup var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS; - trace('yOffset: ' + yOffset); - trace('holdNote.fullSustainLength: ' + holdNote.fullSustainLength); - trace('holdNote.sustainLength: ' + holdNote.sustainLength); - var vwoosh:Bool = false; if (Preferences.downscroll) @@ -531,11 +527,24 @@ class Strumline extends FlxSpriteGroup this.noteData.insertionSort(compareNoteData.bind(FlxSort.ASCENDING)); } - public function hitNote(note:NoteSprite):Void + /** + * @param note The note to hit. + * @param removeNote True to remove the note immediately, false to make it transparent and let it move offscreen. + */ + public function hitNote(note:NoteSprite, removeNote:Bool = true):Void { playConfirm(note.direction); note.hasBeenHit = true; - killNote(note); + + if (removeNote) + { + killNote(note); + } + else + { + note.alpha = 0.5; + note.desaturate(); + } if (note.holdNoteSprite != null) { diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx index f5d444f61..056a6a5a9 100644 --- a/source/funkin/play/notes/SustainTrail.hx +++ b/source/funkin/play/notes/SustainTrail.hx @@ -326,6 +326,7 @@ class SustainTrail extends FlxSprite hitNote = false; missedNote = false; + handledMiss = false; } override public function destroy():Void diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index 75d002cb5..edfb2cae7 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -158,8 +158,8 @@ class Scoring return switch (absTiming) { - case(_ < PBOT1_KILLER_THRESHOLD) => true: - 'killer'; + // case(_ < PBOT1_KILLER_THRESHOLD) => true: + // 'killer'; case(_ < PBOT1_SICK_THRESHOLD) => true: 'sick'; case(_ < PBOT1_GOOD_THRESHOLD) => true: diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1fbcc6c20..c68caad5f 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -792,7 +792,6 @@ typedef SaveScoreData = typedef SaveScoreTallyData = { - var killer:Int; var sick:Int; var good:Int; var bad:Int; diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index d5b23cfd9..92bee4ceb 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -120,7 +120,6 @@ class SaveDataMigrator accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, @@ -140,7 +139,6 @@ class SaveDataMigrator accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, @@ -160,7 +158,6 @@ class SaveDataMigrator accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, @@ -183,7 +180,6 @@ class SaveDataMigrator accuracy: 0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, @@ -209,7 +205,6 @@ class SaveDataMigrator accuracy: 0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, @@ -235,7 +230,6 @@ class SaveDataMigrator accuracy: 0, tallies: { - killer: 0, sick: 0, good: 0, bad: 0, diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 197fa28e8..1005b312e 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -363,6 +363,12 @@ class Constants */ public static final SCORE_HOLD_BONUS_PER_SECOND:Float = 250.0; + public static final JUDGEMENT_KILLER_COMBO_BREAK:Bool = false; + public static final JUDGEMENT_SICK_COMBO_BREAK:Bool = false; + public static final JUDGEMENT_GOOD_COMBO_BREAK:Bool = false; + public static final JUDGEMENT_BAD_COMBO_BREAK:Bool = true; + public static final JUDGEMENT_SHIT_COMBO_BREAK:Bool = true; + /** * FILE EXTENSIONS */ From 3d2e50906fc689188e692b65016c289b828b547c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 22:13:01 -0500 Subject: [PATCH 064/129] I was in fact a dumbass. --- source/funkin/play/PlayState.hx | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index afb5400a3..f7fc6e0f4 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2307,9 +2307,6 @@ class PlayState extends MusicBeatSubState return; } - // TODO: DEBUG OVERRIDE DON'T BE A DUMBASS - daRating = 'bad'; - var isComboBreak = false; switch (daRating) { From b4edcc4b3c974d15c787c344c162dfb3d80baba4 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 23:43:05 -0500 Subject: [PATCH 065/129] Reuse LevelProps (this fixes animations when switching weeks) --- source/funkin/ui/story/Level.hx | 23 ++++++--- source/funkin/ui/story/LevelProp.hx | 62 +++++++++++++++--------- source/funkin/ui/story/StoryMenuState.hx | 3 +- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/source/funkin/ui/story/Level.hx b/source/funkin/ui/story/Level.hx index e86241277..1b9252fde 100644 --- a/source/funkin/ui/story/Level.hx +++ b/source/funkin/ui/story/Level.hx @@ -180,9 +180,9 @@ class Level implements IRegistryEntry return difficulties; } - public function buildProps():Array + public function buildProps(?existingProps:Array):Array { - var props:Array = []; + var props:Array = existingProps == null ? [] : [for (x in existingProps) x]; if (_data.props.length == 0) return props; @@ -190,11 +190,22 @@ class Level implements IRegistryEntry { var propData = _data.props[propIndex]; - var propSprite:Null = LevelProp.build(propData); - if (propSprite == null) continue; + // Attempt to reuse the `LevelProp` object. + // This prevents animations from resetting. + var existingProp:Null = props[propIndex]; + if (existingProp != null) + { + existingProp.propData = propData; + existingProp.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex; + } + else + { + var propSprite:Null = LevelProp.build(propData); + if (propSprite == null) continue; - propSprite.x += FlxG.width * 0.25 * propIndex; - props.push(propSprite); + propSprite.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex; + props.push(propSprite); + } } return props; diff --git a/source/funkin/ui/story/LevelProp.hx b/source/funkin/ui/story/LevelProp.hx index 4dce7bfb3..5af383de9 100644 --- a/source/funkin/ui/story/LevelProp.hx +++ b/source/funkin/ui/story/LevelProp.hx @@ -6,9 +6,26 @@ import funkin.data.level.LevelData; class LevelProp extends Bopper { - public function new(danceEvery:Int) + public var propData(default, set):Null = null; + + function set_propData(value:LevelPropData):LevelPropData { - super(danceEvery); + // Only reset the prop if the asset path has changed. + if (propData == null || value.assetPath != this.propData.assetPath) + { + this.visible = (value != null); + this.propData = value; + danceEvery = this.propData.danceEvery; + applyData(); + } + + return this.propData; + } + + public function new(propData:LevelPropData) + { + super(propData.danceEvery); + this.propData = propData; } public function playConfirm():Void @@ -16,50 +33,51 @@ class LevelProp extends Bopper playAnimation('confirm', true, true); } - public static function build(propData:Null):Null + function applyData():Void { - if (propData == null) return null; - var isAnimated:Bool = propData.animations.length > 0; - var prop:LevelProp = new LevelProp(propData.danceEvery); - if (isAnimated) { // Initalize sprite frames. // Sparrow atlas only LEL. - prop.frames = Paths.getSparrowAtlas(propData.assetPath); + this.frames = Paths.getSparrowAtlas(propData.assetPath); } else { // Initalize static sprite. - prop.loadGraphic(Paths.image(propData.assetPath)); + this.loadGraphic(Paths.image(propData.assetPath)); // Disables calls to update() for a performance boost. - prop.active = false; + this.active = false; } - if (prop.frames == null || prop.frames.numFrames == 0) + if (this.frames == null || this.frames.numFrames == 0) { trace('ERROR: Could not build texture for level prop (${propData.assetPath}).'); - return null; + return; } var scale:Float = propData.scale * (propData.isPixel ? 6 : 1); - prop.scale.set(scale, scale); - prop.antialiasing = !propData.isPixel; - prop.alpha = propData.alpha; - prop.x = propData.offsets[0]; - prop.y = propData.offsets[1]; + this.scale.set(scale, scale); + this.antialiasing = !propData.isPixel; + this.alpha = propData.alpha; + this.x = propData.offsets[0]; + this.y = propData.offsets[1]; - FlxAnimationUtil.addAtlasAnimations(prop, propData.animations); + FlxAnimationUtil.addAtlasAnimations(this, propData.animations); for (propAnim in propData.animations) { - prop.setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]); + this.setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]); } - prop.dance(); - prop.animation.paused = true; + this.dance(); + this.animation.paused = true; + } - return prop; + public static function build(propData:Null):Null + { + if (propData == null) return null; + + return new LevelProp(propData); } } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index d6dd536f7..356757599 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -636,8 +636,7 @@ class StoryMenuState extends MusicBeatState function updateProps():Void { - levelProps.clear(); - for (prop in currentLevel.buildProps()) + for (prop in currentLevel.buildProps(levelProps.members)) { prop.zIndex = 1000; levelProps.add(prop); From 5ac4b14891fa81c73bfb60bc4e67d16d999be038 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 15 Jan 2024 23:51:37 -0500 Subject: [PATCH 066/129] Fix week 7 death animations. --- source/funkin/play/GameOverSubState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 137bf3905..732b22568 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -220,6 +220,7 @@ class GameOverSubState extends MusicBeatSubState playJeffQuote(); // Start music at lower volume startDeathMusic(0.2, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); } default: // Start music at normal volume once the initial death animation finishes. From 673f7f57d456b1471fea39d79d0e4763e30901fc Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 16 Jan 2024 00:03:30 -0500 Subject: [PATCH 067/129] Fix pink rim on low zoom levels. --- source/funkin/play/GameOverSubState.hx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 732b22568..443f742fa 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -94,11 +94,12 @@ class GameOverSubState extends MusicBeatSubState // // Add a black background to the screen. - var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + var bg = new FlxSprite().makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); // We make this transparent so that we can see the stage underneath during debugging, // but it's normally opaque. bg.alpha = transparent ? 0.25 : 1.0; bg.scrollFactor.set(); + bg.screenCenter(); add(bg); // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState. From 80c7bcdfdfaeec6a37d426c2b8b6ddb2b52b8d04 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 16 Jan 2024 16:49:15 -0500 Subject: [PATCH 068/129] Rewrite Stage data handling to use the Registry pattern, and add support for solid colors. --- source/funkin/InitState.hx | 5 +- source/funkin/data/character/TODO.md | 0 source/funkin/data/conversation/TODO.md | 0 source/funkin/data/dialogue/TODO.md | 0 source/funkin/data/level/LevelRegistry.hx | 4 +- source/funkin/data/song/SongRegistry.hx | 16 +- source/funkin/data/speaker/TODO.md | 0 source/funkin/data/stage/StageData.hx | 199 +++++++ source/funkin/data/stage/StageRegistry.hx | 103 ++++ source/funkin/graphics/FunkinSprite.hx | 53 ++ source/funkin/modding/PolymodHandler.hx | 5 +- source/funkin/play/GameOverSubState.hx | 3 +- source/funkin/play/PlayState.hx | 4 +- .../play/character/AnimateAtlasCharacter.hx | 3 +- source/funkin/play/stage/Stage.hx | 93 +-- source/funkin/play/stage/StageData.hx | 548 ------------------ source/funkin/play/stage/StageProp.hx | 4 +- .../ui/debug/charting/ChartEditorState.hx | 11 +- .../handlers/ChartEditorDialogHandler.hx | 2 +- .../handlers/ChartEditorToolboxHandler.hx | 6 +- .../toolboxes/ChartEditorEventDataToolbox.hx | 2 +- .../toolboxes/ChartEditorMetadataToolbox.hx | 10 +- .../charting/util/ChartEditorDropdowns.hx | 11 +- .../ui/debug/stage/StageOffsetSubState.hx | 14 +- source/funkin/ui/options/ControlsMenu.hx | 5 +- source/funkin/ui/story/StoryMenuState.hx | 3 +- source/funkin/ui/title/OutdatedSubState.hx | 2 +- source/funkin/ui/title/TitleState.hx | 4 +- source/funkin/ui/transition/LoadingState.hx | 5 +- 29 files changed, 484 insertions(+), 631 deletions(-) create mode 100644 source/funkin/data/character/TODO.md create mode 100644 source/funkin/data/conversation/TODO.md create mode 100644 source/funkin/data/dialogue/TODO.md create mode 100644 source/funkin/data/speaker/TODO.md create mode 100644 source/funkin/data/stage/StageData.hx create mode 100644 source/funkin/data/stage/StageRegistry.hx create mode 100644 source/funkin/graphics/FunkinSprite.hx delete mode 100644 source/funkin/play/stage/StageData.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 02b46c88c..c9198c3d4 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -20,11 +20,11 @@ import openfl.display.BitmapData; import funkin.data.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.event.SongEventRegistry; +import funkin.data.stage.StageRegistry; import funkin.play.cutscene.dialogue.ConversationDataParser; import funkin.play.cutscene.dialogue.DialogueBoxDataParser; import funkin.play.cutscene.dialogue.SpeakerDataParser; import funkin.data.song.SongRegistry; -import funkin.play.stage.StageData.StageDataParser; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.modding.module.ModuleHandler; import funkin.ui.title.TitleState; @@ -217,8 +217,9 @@ class InitState extends FlxState ConversationDataParser.loadConversationCache(); DialogueBoxDataParser.loadDialogueBoxCache(); SpeakerDataParser.loadSpeakerCache(); - StageDataParser.loadStageCache(); + StageRegistry.instance.loadEntries(); CharacterDataParser.loadCharacterCache(); + ModuleHandler.buildModuleCallbacks(); ModuleHandler.loadModuleCache(); diff --git a/source/funkin/data/character/TODO.md b/source/funkin/data/character/TODO.md new file mode 100644 index 000000000..e69de29bb diff --git a/source/funkin/data/conversation/TODO.md b/source/funkin/data/conversation/TODO.md new file mode 100644 index 000000000..e69de29bb diff --git a/source/funkin/data/dialogue/TODO.md b/source/funkin/data/dialogue/TODO.md new file mode 100644 index 000000000..e69de29bb diff --git a/source/funkin/data/level/LevelRegistry.hx b/source/funkin/data/level/LevelRegistry.hx index b5c15de0f..96712cba5 100644 --- a/source/funkin/data/level/LevelRegistry.hx +++ b/source/funkin/data/level/LevelRegistry.hx @@ -7,9 +7,9 @@ import funkin.ui.story.ScriptedLevel; class LevelRegistry extends BaseRegistry { /** - * The current version string for the stage data format. + * The current version string for the level data format. * Handle breaking changes by incrementing this value - * and adding migration to the `migrateStageData()` function. + * and adding migration to the `migrateLevelData()` function. */ public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.0"; diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 98b46c782..b772349bc 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -127,7 +127,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; switch (loadEntryMetadataFile(id, variation)) { @@ -150,7 +150,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -210,7 +210,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; switch (loadEntryMetadataFile(id, variation)) { @@ -232,7 +232,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; switch (loadEntryMetadataFile(id, variation)) { @@ -252,7 +252,7 @@ class SongRegistry extends BaseRegistry function parseEntryMetadataRaw_v2_1_0(contents:String, ?fileName:String = 'raw'):Null { var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -266,7 +266,7 @@ class SongRegistry extends BaseRegistry function parseEntryMetadataRaw_v2_0_0(contents:String, ?fileName:String = 'raw'):Null { var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; parser.fromJson(contents, fileName); if (parser.errors.length > 0) @@ -347,7 +347,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; switch (loadEntryChartFile(id, variation)) { @@ -370,7 +370,7 @@ class SongRegistry extends BaseRegistry variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; + parser.ignoreUnknownVariables = true; parser.fromJson(contents, fileName); if (parser.errors.length > 0) diff --git a/source/funkin/data/speaker/TODO.md b/source/funkin/data/speaker/TODO.md new file mode 100644 index 000000000..e69de29bb diff --git a/source/funkin/data/stage/StageData.hx b/source/funkin/data/stage/StageData.hx new file mode 100644 index 000000000..cb914007f --- /dev/null +++ b/source/funkin/data/stage/StageData.hx @@ -0,0 +1,199 @@ +package funkin.data.stage; + +import funkin.data.animation.AnimationData; + +@:nullSafety +class StageData +{ + /** + * The sematic version number of the stage data JSON format. + * Supports fancy comparisons like NPM does it's neat. + */ + @:default(funkin.data.stage.StageRegistry.STAGE_DATA_VERSION) + public var version:String; + + public var name:String = 'Unknown'; + public var props:Array = []; + public var characters:StageDataCharacters; + + @:default(1.0) + @:optional + public var cameraZoom:Null; + + public function new() + { + this.version = StageRegistry.STAGE_DATA_VERSION; + this.characters = makeDefaultCharacters(); + } + + function makeDefaultCharacters():StageDataCharacters + { + return { + bf: + { + zIndex: 0, + position: [0, 0], + cameraOffsets: [-100, -100] + }, + dad: + { + zIndex: 0, + position: [0, 0], + cameraOffsets: [100, -100] + }, + gf: + { + zIndex: 0, + position: [0, 0], + cameraOffsets: [0, 0] + } + }; + } + + /** + * Convert this StageData into a JSON string. + */ + public function serialize(pretty:Bool = true):String + { + var writer = new json2object.JsonWriter(); + return writer.write(this, pretty ? ' ' : null); + } +} + +typedef StageDataCharacters = +{ + var bf:StageDataCharacter; + var dad:StageDataCharacter; + var gf:StageDataCharacter; +}; + +typedef StageDataProp = +{ + /** + * The name of the prop for later lookup by scripts. + * Optional; if unspecified, the prop can't be referenced by scripts. + */ + @:optional + var name:String; + + /** + * The asset used to display the prop. + * NOTE: As of Stage data v1.0.1, you can also use a color here to create a rectangle, like "#ff0000". + * In this case, the `scale` property will be used to determine the size of the prop. + */ + var assetPath:String; + + /** + * The position of the prop as an [x, y] array of two floats. + */ + var position:Array; + + /** + * A number determining the stack order of the prop, relative to other props and the characters in the stage. + * Props with lower numbers render below those with higher numbers. + * This is just like CSS, it isn't hard. + * @default 0 + */ + @:optional + @:default(0) + var zIndex:Int; + + /** + * If set to true, anti-aliasing will be forcibly disabled on the sprite. + * This prevents blurry images on pixel-art levels. + * @default false + */ + @:optional + @:default(false) + var isPixel:Bool; + + /** + * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. + * Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory. + */ + @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) + @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) + @:optional + var scale:haxe.ds.Either>; + + /** + * The alpha of the prop, as a float. + * @default 1.0 + */ + @:optional + @:default(1.0) + var alpha:Float; + + /** + * If not zero, this prop will play an animation every X beats of the song. + * This requires animations to be defined. If `danceLeft` and `danceRight` are defined, + * they will alternated between, otherwise the `idle` animation will be used. + * + * @default 0 + */ + @:default(0) + @:optional + var danceEvery:Int; + + /** + * How much the prop scrolls relative to the camera. Used to create a parallax effect. + * Represented as an [x, y] array of two floats. + * [1, 1] means the prop moves 1:1 with the camera. + * [0.5, 0.5] means the prop half as much as the camera. + * [0, 0] means the prop is not moved. + * @default [0, 0] + */ + @:optional + @:default([0, 0]) + var scroll:Array; + + /** + * An optional array of animations which the prop can play. + * @default Prop has no animations. + */ + @:optional + @:default([]) + var animations:Array; + + /** + * If animations are used, this is the name of the animation to play first. + * @default Don't play an animation. + */ + @:optional + var startingAnimation:Null; + + /** + * The animation type to use. + * Options: "sparrow", "packer" + * @default "sparrow" + */ + @:default("sparrow") + @:optional + var animType:String; +}; + +typedef StageDataCharacter = +{ + /** + * A number determining the stack order of the character, relative to props and other characters in the stage. + * Again, just like CSS. + * @default 0 + */ + @:optional + @:default(0) + var zIndex:Int; + + /** + * The position to render the character at. + */ + @:optional + @:default([0, 0]) + var position:Array; + + /** + * The camera offsets to apply when focusing on the character on this stage. + * @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF + */ + @:optional + var cameraOffsets:Array; +}; diff --git a/source/funkin/data/stage/StageRegistry.hx b/source/funkin/data/stage/StageRegistry.hx new file mode 100644 index 000000000..b78292e5b --- /dev/null +++ b/source/funkin/data/stage/StageRegistry.hx @@ -0,0 +1,103 @@ +package funkin.data.stage; + +import funkin.data.stage.StageData; +import funkin.play.stage.Stage; +import funkin.play.stage.ScriptedStage; + +class StageRegistry extends BaseRegistry +{ + /** + * The current version string for the stage data format. + * Handle breaking changes by incrementing this value + * and adding migration to the `migrateStageData()` function. + */ + public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.1"; + + public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x"; + + public static final instance:StageRegistry = new StageRegistry(); + + public function new() + { + super('STAGE', 'stages', STAGE_DATA_VERSION_RULE); + } + + /** + * Read, parse, and validate the JSON data and produce the corresponding data object. + */ + public function parseEntryData(id:String):Null + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + + switch (loadEntryFile(id)) + { + case {fileName: fileName, contents: contents}: + parser.fromJson(contents, fileName); + default: + return null; + } + + if (parser.errors.length > 0) + { + printErrors(parser.errors, id); + return null; + } + return parser.value; + } + + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String):Null + { + var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + + function createScriptedEntry(clsName:String):Stage + { + return ScriptedStage.init(clsName, "unknown"); + } + + function getScriptedClassNames():Array + { + return ScriptedStage.listScriptClasses(); + } + + /** + * A list of all the stages from the base game, in order. + * TODO: Should this be hardcoded? + */ + public function listBaseGameStageIds():Array + { + return [ + "mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets", + "phillyBlazin", + ]; + } + + /** + * A list of all installed story weeks that are not from the base game. + */ + public function listModdedStageIds():Array + { + return listEntryIds().filter(function(id:String):Bool { + return listBaseGameStageIds().indexOf(id) == -1; + }); + } +} diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx new file mode 100644 index 000000000..487aaac34 --- /dev/null +++ b/source/funkin/graphics/FunkinSprite.hx @@ -0,0 +1,53 @@ +package funkin.graphics; + +import flixel.FlxSprite; +import flixel.util.FlxColor; +import flixel.graphics.FlxGraphic; + +/** + * An FlxSprite with additional functionality. + */ +class FunkinSprite extends FlxSprite +{ + /** + * @param x Starting X position + * @param y Starting Y position + */ + public function new(?x:Float = 0, ?y:Float = 0) + { + super(x, y); + } + + /** + * Acts similarly to `makeGraphic`, but with improved memory usage, + * at the expense of not being able to paint onto the 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. + */ + public function makeSolidColor(width:Int, height:Int, color:FlxColor = FlxColor.WHITE):FunkinSprite + { + var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}'); + frames = graphic.imageFrame; + scale.set(width / 2, height / 2); + updateHitbox(); + + return this; + } + + /** + * Ensure scale is applied when cloning a sprite. + * The default `clone()` method acts kinda weird TBH. + * @return A clone of this sprite. + */ + public override function clone():FunkinSprite + { + var result = new FunkinSprite(this.x, this.y); + result.frames = this.frames; + result.scale.set(this.scale.x, this.scale.y); + result.updateHitbox(); + + return result; + } +} diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index b7ef07be5..151e658b4 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -4,11 +4,12 @@ import funkin.util.macro.ClassMacro; import funkin.modding.module.ModuleHandler; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.data.song.SongData; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import polymod.Polymod; import polymod.backends.PolymodAssets.PolymodAssetType; import polymod.format.ParseRules.TextFileFormat; import funkin.data.event.SongEventRegistry; +import funkin.data.stage.StageRegistry; import funkin.util.FileUtil; import funkin.data.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; @@ -275,7 +276,7 @@ class PolymodHandler ConversationDataParser.loadConversationCache(); DialogueBoxDataParser.loadDialogueBoxCache(); SpeakerDataParser.loadSpeakerCache(); - StageDataParser.loadStageCache(); + StageRegistry.instance.loadEntries(); CharacterDataParser.loadCharacterCache(); ModuleHandler.loadModuleCache(); } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 137bf3905..cb190d23a 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -7,6 +7,7 @@ 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; @@ -94,7 +95,7 @@ class GameOverSubState extends MusicBeatSubState // // Add a black background to the screen. - var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); // We make this transparent so that we can see the stage underneath during debugging, // but it's normally opaque. bg.alpha = transparent ? 0.25 : 1.0; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 9f98d3b04..da525d4e0 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -50,11 +50,11 @@ import funkin.play.notes.SustainTrail; import funkin.play.scoring.Scoring; import funkin.play.song.Song; import funkin.data.song.SongRegistry; +import funkin.data.stage.StageRegistry; import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongCharacterData; import funkin.play.stage.Stage; -import funkin.play.stage.StageData.StageDataParser; import funkin.ui.transition.LoadingState; import funkin.play.components.PopUpStuff; import funkin.ui.options.PreferencesMenu; @@ -1353,7 +1353,7 @@ class PlayState extends MusicBeatSubState */ function loadStage(id:String):Void { - currentStage = StageDataParser.fetchStage(id); + currentStage = StageRegistry.instance.fetchEntry(id); if (currentStage != null) { diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index 3523ec994..f9dc18119 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -9,6 +9,7 @@ import flixel.math.FlxMath; import flixel.math.FlxPoint.FlxCallbackPoint; import flixel.math.FlxPoint; import flixel.math.FlxRect; +import funkin.graphics.FunkinSprite; import flixel.system.FlxAssets.FlxGraphicAsset; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; @@ -621,7 +622,7 @@ class AnimateAtlasCharacter extends BaseCharacter * This functionality isn't supported in SpriteGroup * @return this sprite group */ - public override function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite + public override function loadGraphicFromSprite(Sprite:FlxSprite):FunkinSprite { #if FLX_DEBUG throw "This function is not supported in FlxSpriteGroup"; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index c8cb8ce66..ac6c3705e 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -5,13 +5,16 @@ import flixel.group.FlxSpriteGroup; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxSort; +import flixel.util.FlxColor; import funkin.modding.IScriptedClass; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventType; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.character.BaseCharacter; -import funkin.play.stage.StageData.StageDataCharacter; -import funkin.play.stage.StageData.StageDataParser; +import funkin.data.IRegistryEntry; +import funkin.data.stage.StageData; +import funkin.data.stage.StageData.StageDataCharacter; +import funkin.data.stage.StageRegistry; import funkin.play.stage.StageProp; import funkin.util.SortUtil; import funkin.util.assets.FlxAnimationUtil; @@ -23,14 +26,25 @@ typedef StagePropGroup = FlxTypedSpriteGroup; * * A Stage is comprised of one or more props, each of which is a FlxSprite. */ -class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass +class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements IRegistryEntry { - public final stageId:String; - public final stageName:String; + public final id:String; - final _data:StageData; + public final _data:StageData; - public var camZoom:Float = 1.0; + public var stageName(get, never):String; + + function get_stageName():String + { + return _data?.name ?? 'Unknown'; + } + + public var camZoom(get, never):Float; + + function get_camZoom():Float + { + return _data?.cameraZoom ?? 1.0; + } var namedProps:Map = new Map(); var characters:Map = new Map(); @@ -41,21 +55,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * They're used to cache the data needed to build the stage, * then accessed and fleshed out when the stage needs to be built. * - * @param stageId + * @param id */ - public function new(stageId:String) + public function new(id:String) { super(); - this.stageId = stageId; - _data = StageDataParser.parseStageData(this.stageId); + this.id = id; + _data = _fetchData(id); + if (_data == null) { - throw 'Could not find stage data for stageId: $stageId'; - } - else - { - this.stageName = _data.name; + throw 'Could not find stage data for stage id: $id'; } } @@ -129,9 +140,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass */ function buildStage():Void { - trace('Building stage for display: ${this.stageId}'); - - this.camZoom = _data.cameraZoom; + trace('Building stage for display: ${this.id}'); this.debugIconGroup = new FlxSpriteGroup(); @@ -139,6 +148,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass { trace(' Placing prop: ${dataProp.name} (${dataProp.assetPath})'); + var isSolidColor = dataProp.assetPath.startsWith('#'); var isAnimated = dataProp.animations.length > 0; var propSprite:StageProp; @@ -162,6 +172,22 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath); } } + else if (isSolidColor) + { + var width:Int = 1; + var height:Int = 1; + switch (dataProp.scale) + { + case Left(value): + width = Std.int(value); + height = Std.int(value); + + case Right(values): + width = Std.int(values[0]); + height = Std.int(values[1]); + } + propSprite.makeSolidColor(width, height, FlxColor.fromString(dataProp.assetPath)); + } else { // Initalize static sprite. @@ -177,13 +203,16 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass continue; } - switch (dataProp.scale) + if (!isSolidColor) { - case Left(value): - propSprite.scale.set(value); + switch (dataProp.scale) + { + case Left(value): + propSprite.scale.set(value); - case Right(values): - propSprite.scale.set(values[0], values[1]); + case Right(values): + propSprite.scale.set(values[0], values[1]); + } } propSprite.updateHitbox(); @@ -195,15 +224,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass // If pixel, disable antialiasing. propSprite.antialiasing = !dataProp.isPixel; - switch (dataProp.scroll) - { - case Left(value): - propSprite.scrollFactor.x = value; - propSprite.scrollFactor.y = value; - case Right(values): - propSprite.scrollFactor.x = values[0]; - propSprite.scrollFactor.y = values[1]; - } + propSprite.scrollFactor.x = dataProp.scroll[0]; + propSprite.scrollFactor.y = dataProp.scroll[1]; propSprite.zIndex = dataProp.zIndex; @@ -731,6 +753,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass return Sprite; } + static function _fetchData(id:String):Null + { + return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id)); + } + public function onScriptEvent(event:ScriptEvent) {} public function onPause(event:PauseScriptEvent) {} diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx deleted file mode 100644 index 2d87dec31..000000000 --- a/source/funkin/play/stage/StageData.hx +++ /dev/null @@ -1,548 +0,0 @@ -package funkin.play.stage; - -import funkin.data.animation.AnimationData; -import funkin.play.stage.ScriptedStage; -import funkin.play.stage.Stage; -import funkin.util.VersionUtil; -import funkin.util.assets.DataAssets; -import haxe.Json; -import openfl.Assets; - -/** - * Contains utilities for loading and parsing stage data. - */ -class StageDataParser -{ - /** - * The current version string for the stage data format. - * Handle breaking changes by incrementing this value - * and adding migration to the `migrateStageData()` function. - */ - public static final STAGE_DATA_VERSION:String = "1.0.0"; - - /** - * The current version rule check for the stage data format. - */ - public static final STAGE_DATA_VERSION_RULE:String = "1.0.x"; - - static final stageCache:Map = new Map(); - - static final DEFAULT_STAGE_ID = 'UNKNOWN'; - - /** - * Parses and preloads the game's stage data and scripts when the game starts. - * - * If you want to force stages to be reloaded, you can just call this function again. - */ - public static function loadStageCache():Void - { - // Clear any stages that are cached if there were any. - clearStageCache(); - trace("Loading stage cache..."); - - // - // SCRIPTED STAGES - // - var scriptedStageClassNames:Array = ScriptedStage.listScriptClasses(); - trace(' Instantiating ${scriptedStageClassNames.length} scripted stages...'); - for (stageCls in scriptedStageClassNames) - { - var stage:Stage = ScriptedStage.init(stageCls, DEFAULT_STAGE_ID); - if (stage != null) - { - trace(' Loaded scripted stage: ${stage.stageName}'); - // Disable the rendering logic for stage until it's loaded. - // Note that kill() =/= destroy() - stage.kill(); - - // Then store it. - stageCache.set(stage.stageId, stage); - } - else - { - trace(' Failed to instantiate scripted stage class: ${stageCls}'); - } - } - - // - // UNSCRIPTED STAGES - // - var stageIdList:Array = DataAssets.listDataFilesInPath('stages/'); - var unscriptedStageIds:Array = stageIdList.filter(function(stageId:String):Bool { - return !stageCache.exists(stageId); - }); - trace(' Instantiating ${unscriptedStageIds.length} non-scripted stages...'); - for (stageId in unscriptedStageIds) - { - var stage:Stage; - try - { - stage = new Stage(stageId); - if (stage != null) - { - trace(' Loaded stage data: ${stage.stageName}'); - stageCache.set(stageId, stage); - } - } - catch (e) - { - trace(' An error occurred while loading stage data: ${stageId}'); - // Assume error was already logged. - continue; - } - } - - trace(' Successfully loaded ${Lambda.count(stageCache)} stages.'); - } - - public static function fetchStage(stageId:String):Null - { - if (stageCache.exists(stageId)) - { - trace('Successfully fetch stage: ${stageId}'); - var stage:Stage = stageCache.get(stageId); - stage.revive(); - return stage; - } - else - { - trace('Failed to fetch stage, not found in cache: ${stageId}'); - return null; - } - } - - static function clearStageCache():Void - { - if (stageCache != null) - { - for (stage in stageCache) - { - stage.destroy(); - } - stageCache.clear(); - } - } - - /** - * Load a stage's JSON file, parse its data, and return it. - * - * @param stageId The stage to load. - * @return The stage data, or null if validation failed. - */ - public static function parseStageData(stageId:String):Null - { - var rawJson:String = loadStageFile(stageId); - - var stageData:StageData = migrateStageData(rawJson, stageId); - - return validateStageData(stageId, stageData); - } - - public static function listStageIds():Array - { - return stageCache.keys().array(); - } - - static function loadStageFile(stagePath:String):String - { - var stageFilePath:String = Paths.json('stages/${stagePath}'); - var rawJson = Assets.getText(stageFilePath).trim(); - - while (!rawJson.endsWith("}")) - { - rawJson = rawJson.substr(0, rawJson.length - 1); - } - - return rawJson; - } - - static function migrateStageData(rawJson:String, stageId:String):Null - { - // If you update the stage data format in a breaking way, - // handle migration here by checking the `version` value. - - try - { - var parser = new json2object.JsonParser(); - parser.ignoreUnknownVariables = false; - parser.fromJson(rawJson, '$stageId.json'); - - if (parser.errors.length > 0) - { - trace('[STAGE] Failed to parse stage data'); - - for (error in parser.errors) - funkin.data.DataError.printError(error); - - return null; - } - return parser.value; - } - catch (e) - { - trace(' Error parsing data for stage: ${stageId}'); - trace(' ${e}'); - return null; - } - } - - static final DEFAULT_ANIMTYPE:String = "sparrow"; - static final DEFAULT_CAMERAZOOM:Float = 1.0; - static final DEFAULT_DANCEEVERY:Int = 0; - static final DEFAULT_ISPIXEL:Bool = false; - static final DEFAULT_NAME:String = "Untitled Stage"; - static final DEFAULT_OFFSETS:Array = [0, 0]; - static final DEFAULT_CAMERA_OFFSETS_BF:Array = [-100, -100]; - static final DEFAULT_CAMERA_OFFSETS_DAD:Array = [150, -100]; - static final DEFAULT_POSITION:Array = [0, 0]; - static final DEFAULT_SCALE:Float = 1.0; - static final DEFAULT_ALPHA:Float = 1.0; - static final DEFAULT_SCROLL:Array = [0, 0]; - static final DEFAULT_ZINDEX:Int = 0; - - static final DEFAULT_CHARACTER_DATA:StageDataCharacter = - { - zIndex: DEFAULT_ZINDEX, - position: DEFAULT_POSITION, - cameraOffsets: DEFAULT_OFFSETS, - } - - /** - * Set unspecified parameters to their defaults. - * If the parameter is mandatory, print an error message. - * @param id - * @param input - * @return The validated stage data - */ - static function validateStageData(id:String, input:StageData):Null - { - if (input == null) - { - trace('ERROR: Could not parse stage data for "${id}".'); - return null; - } - - if (input.version == null) - { - trace('ERROR: Could not load stage data for "$id": missing version'); - return null; - } - - if (!VersionUtil.validateVersionStr(input.version, STAGE_DATA_VERSION_RULE)) - { - trace('ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})'); - return null; - } - - if (input.name == null) - { - trace('WARN: Stage data for "$id" missing name'); - input.name = DEFAULT_NAME; - } - - if (input.cameraZoom == null) - { - input.cameraZoom = DEFAULT_CAMERAZOOM; - } - - if (input.props == null) - { - input.props = []; - } - - for (inputProp in input.props) - { - // It's fine for inputProp.name to be null - - if (inputProp.assetPath == null) - { - trace('ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"'); - return null; - } - - if (inputProp.position == null) - { - inputProp.position = DEFAULT_POSITION; - } - - if (inputProp.zIndex == null) - { - inputProp.zIndex = DEFAULT_ZINDEX; - } - - if (inputProp.isPixel == null) - { - inputProp.isPixel = DEFAULT_ISPIXEL; - } - - if (inputProp.danceEvery == null) - { - inputProp.danceEvery = DEFAULT_DANCEEVERY; - } - - if (inputProp.animType == null) - { - inputProp.animType = DEFAULT_ANIMTYPE; - } - - switch (inputProp.scale) - { - case null: - inputProp.scale = Right([DEFAULT_SCALE, DEFAULT_SCALE]); - case Left(value): - inputProp.scale = Right([value, value]); - case Right(_): - // Do nothing - } - - switch (inputProp.scroll) - { - case null: - inputProp.scroll = Right(DEFAULT_SCROLL); - case Left(value): - inputProp.scroll = Right([value, value]); - case Right(_): - // Do nothing - } - - if (inputProp.alpha == null) - { - inputProp.alpha = DEFAULT_ALPHA; - } - - if (inputProp.animations == null) - { - inputProp.animations = []; - } - - if (inputProp.animations.length == 0 && inputProp.startingAnimation != null) - { - trace('ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"'); - return null; - } - - for (inputAnimation in inputProp.animations) - { - if (inputAnimation.name == null) - { - trace('ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"'); - return null; - } - - if (inputAnimation.frameRate == null) - { - inputAnimation.frameRate = 24; - } - - if (inputAnimation.offsets == null) - { - inputAnimation.offsets = DEFAULT_OFFSETS; - } - - if (inputAnimation.looped == null) - { - inputAnimation.looped = true; - } - - if (inputAnimation.flipX == null) - { - inputAnimation.flipX = false; - } - - if (inputAnimation.flipY == null) - { - inputAnimation.flipY = false; - } - } - } - - if (input.characters == null) - { - trace('ERROR: Could not load stage data for "$id": missing characters'); - return null; - } - - if (input.characters.bf == null) - { - input.characters.bf = DEFAULT_CHARACTER_DATA; - } - if (input.characters.dad == null) - { - input.characters.dad = DEFAULT_CHARACTER_DATA; - } - if (input.characters.gf == null) - { - input.characters.gf = DEFAULT_CHARACTER_DATA; - } - - for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf]) - { - if (inputCharacter.position == null || inputCharacter.position.length != 2) - { - inputCharacter.position = [0, 0]; - } - } - - // All good! - return input; - } -} - -class StageData -{ - /** - * The sematic version number of the stage data JSON format. - * Supports fancy comparisons like NPM does it's neat. - */ - public var version:String; - - public var name:String; - public var cameraZoom:Null; - public var props:Array; - public var characters:StageDataCharacters; - - public function new() - { - this.version = StageDataParser.STAGE_DATA_VERSION; - } - - /** - * Convert this StageData into a JSON string. - */ - public function serialize(pretty:Bool = true):String - { - var writer = new json2object.JsonWriter(); - return writer.write(this, pretty ? ' ' : null); - } -} - -typedef StageDataCharacters = -{ - var bf:StageDataCharacter; - var dad:StageDataCharacter; - var gf:StageDataCharacter; -}; - -typedef StageDataProp = -{ - /** - * The name of the prop for later lookup by scripts. - * Optional; if unspecified, the prop can't be referenced by scripts. - */ - @:optional - var name:String; - - /** - * The asset used to display the prop. - */ - var assetPath:String; - - /** - * The position of the prop as an [x, y] array of two floats. - */ - var position:Array; - - /** - * A number determining the stack order of the prop, relative to other props and the characters in the stage. - * Props with lower numbers render below those with higher numbers. - * This is just like CSS, it isn't hard. - * @default 0 - */ - @:optional - @:default(0) - var zIndex:Int; - - /** - * If set to true, anti-aliasing will be forcibly disabled on the sprite. - * This prevents blurry images on pixel-art levels. - * @default false - */ - @:optional - @:default(false) - var isPixel:Bool; - - /** - * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. - * Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory. - */ - @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) - @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) - @:optional - var scale:haxe.ds.Either>; - - /** - * The alpha of the prop, as a float. - * @default 1.0 - */ - @:optional - @:default(1.0) - var alpha:Float; - - /** - * If not zero, this prop will play an animation every X beats of the song. - * This requires animations to be defined. If `danceLeft` and `danceRight` are defined, - * they will alternated between, otherwise the `idle` animation will be used. - * - * @default 0 - */ - @:default(0) - @:optional - var danceEvery:Int; - - /** - * How much the prop scrolls relative to the camera. Used to create a parallax effect. - * Represented as a float or as an [x, y] array of two floats. - * [1, 1] means the prop moves 1:1 with the camera. - * [0.5, 0.5] means the prop half as much as the camera. - * [0, 0] means the prop is not moved. - * @default [0, 0] - */ - @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) - @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) - @:optional - var scroll:haxe.ds.Either>; - - /** - * An optional array of animations which the prop can play. - * @default Prop has no animations. - */ - @:optional - var animations:Array; - - /** - * If animations are used, this is the name of the animation to play first. - * @default Don't play an animation. - */ - @:optional - var startingAnimation:Null; - - /** - * The animation type to use. - * Options: "sparrow", "packer" - * @default "sparrow" - */ - @:default("sparrow") - @:optional - var animType:String; -}; - -typedef StageDataCharacter = -{ - /** - * A number determining the stack order of the character, relative to props and other characters in the stage. - * Again, just like CSS. - * @default 0 - */ - var zIndex:Int; - - /** - * The position to render the character at. - */ - var position:Array; - - /** - * The camera offsets to apply when focusing on the character on this stage. - * @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF - */ - var cameraOffsets:Array; -}; diff --git a/source/funkin/play/stage/StageProp.hx b/source/funkin/play/stage/StageProp.hx index 4f67c5e4b..4d846162b 100644 --- a/source/funkin/play/stage/StageProp.hx +++ b/source/funkin/play/stage/StageProp.hx @@ -1,10 +1,10 @@ package funkin.play.stage; import funkin.modding.events.ScriptEvent; -import flixel.FlxSprite; +import funkin.graphics.FunkinSprite; import funkin.modding.IScriptedClass.IStateStageProp; -class StageProp extends FlxSprite implements IStateStageProp +class StageProp extends FunkinSprite implements IStateStageProp { /** * An internal name for this prop. diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 0853bf390..3090abad2 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -12,6 +12,7 @@ import flixel.FlxCamera; import flixel.FlxSprite; import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; +import funkin.graphics.FunkinSprite; import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; import flixel.math.FlxPoint; @@ -56,7 +57,7 @@ import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongDataUtils; import funkin.ui.debug.charting.commands.ChartEditorCommand; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import funkin.save.Save; import funkin.ui.debug.charting.commands.AddEventsCommand; import funkin.ui.debug.charting.commands.AddNotesCommand; @@ -2230,7 +2231,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2); var playheadBaseYPos:Float = GRID_INITIAL_Y_POS; gridPlayhead.setPosition(GRID_X_POS, playheadBaseYPos); - var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR); + var playheadSprite:FunkinSprite = new FunkinSprite().makeSolidColor(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR); playheadSprite.x = -PLAYHEAD_SCROLL_AREA_WIDTH; playheadSprite.y = 0; gridPlayhead.add(playheadSprite); @@ -5951,9 +5952,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState ChartEditorNoteSprite.noteFrameCollection = null; // Stop the music. - welcomeMusic.destroy(); - audioInstTrack.destroy(); - audioVocalTrackGroup.destroy(); + if (welcomeMusic != null) welcomeMusic.destroy(); + if (audioInstTrack != null) audioInstTrack.destroy(); + if (audioVocalTrackGroup != null) audioVocalTrackGroup.destroy(); } function applyCanQuickSave():Void diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 1e1a02974..32e9fef53 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -13,7 +13,7 @@ import funkin.play.character.BaseCharacter; import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.Song; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog; import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget; import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index ce1997968..1916f92c2 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -1,7 +1,6 @@ package funkin.ui.debug.charting.handlers; -import funkin.play.stage.StageData.StageDataParser; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import haxe.ui.components.HorizontalSlider; @@ -16,8 +15,7 @@ import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.event.SongEvent; import funkin.play.song.SongSerializer; -import funkin.play.stage.StageData; -import funkin.play.stage.StageData.StageDataParser; +import funkin.data.stage.StageData; import haxe.ui.RuntimeComponentBuilder; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.haxeui.components.CharacterPlayer; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 480873bc5..fbd1562b4 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -2,7 +2,7 @@ package funkin.ui.debug.charting.toolboxes; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import funkin.play.event.SongEvent; import funkin.data.event.SongEventSchema; import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index 98aa02151..06b20ed7c 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -2,7 +2,8 @@ package funkin.ui.debug.charting.toolboxes; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; +import funkin.data.stage.StageRegistry; import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import haxe.ui.components.Button; @@ -13,6 +14,7 @@ import haxe.ui.components.Label; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; import haxe.ui.components.TextField; +import funkin.play.stage.Stage; import haxe.ui.containers.Box; import haxe.ui.containers.Frame; import haxe.ui.events.UIEvent; @@ -199,11 +201,11 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox inputTimeSignature.value = {id: currentTimeSignature, text: currentTimeSignature}; var stageId:String = chartEditorState.currentSongMetadata.playData.stage; - var stageData:Null = StageDataParser.parseStageData(stageId); + var stage:Null = StageRegistry.instance.fetchEntry(stageId); if (inputStage != null) { - inputStage.value = (stageData != null) ? - {id: stageId, text: stageData.name} : + inputStage.value = (stage != null) ? + {id: stage.id, text: stage.stageName} : {id: "mainStage", text: "Main Stage"}; } diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index dfa0408d3..14c07440b 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -2,10 +2,11 @@ package funkin.ui.debug.charting.util; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; -import funkin.play.stage.StageData; -import funkin.play.stage.StageData.StageDataParser; +import funkin.data.stage.StageData; +import funkin.data.stage.StageRegistry; import funkin.play.character.CharacterData; import haxe.ui.components.DropDown; +import funkin.play.stage.Stage; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData.CharacterDataParser; @@ -60,16 +61,16 @@ class ChartEditorDropdowns { dropDown.dataSource.clear(); - var stageIds:Array = StageDataParser.listStageIds(); + var stageIds:Array = StageRegistry.instance.listEntryIds(); var returnValue:DropDownEntry = {id: "mainStage", text: "Main Stage"}; for (stageId in stageIds) { - var stage:Null = StageDataParser.parseStageData(stageId); + var stage:Null = StageRegistry.instance.fetchEntry(stageId); if (stage == null) continue; - var value = {id: stageId, text: stage.name}; + var value = {id: stage.id, text: stage.stageName}; if (startingStageId == stageId) returnValue = value; dropDown.dataSource.add(value); diff --git a/source/funkin/ui/debug/stage/StageOffsetSubState.hx b/source/funkin/ui/debug/stage/StageOffsetSubState.hx index 68546f1c7..e8a5d0a23 100644 --- a/source/funkin/ui/debug/stage/StageOffsetSubState.hx +++ b/source/funkin/ui/debug/stage/StageOffsetSubState.hx @@ -5,15 +5,17 @@ import flixel.input.mouse.FlxMouseEvent; import flixel.math.FlxPoint; import funkin.play.character.BaseCharacter; import funkin.play.PlayState; -import funkin.play.stage.StageData; +import funkin.data.stage.StageData; import funkin.play.stage.StageProp; import funkin.graphics.shaders.StrokeShader; import funkin.ui.haxeui.HaxeUISubState; import funkin.ui.debug.stage.StageEditorCommand; import funkin.util.SerializerUtil; +import funkin.data.stage.StageRegistry; import funkin.util.MouseUtil; import haxe.ui.containers.ListView; import haxe.ui.core.Component; +import funkin.graphics.FunkinSprite; import haxe.ui.events.UIEvent; import haxe.ui.RuntimeComponentBuilder; import openfl.events.Event; @@ -354,7 +356,13 @@ class StageOffsetSubState extends HaxeUISubState function prepStageStuff():String { - var stageLol:StageData = StageDataParser.parseStageData(PlayState.instance.currentStageId); + var stageLol:StageData = StageRegistry.instance.fetchEntry(PlayState.instance.currentStageId)?._data; + + if (stageLol == null) + { + FlxG.log.error("Stage not found in registry!"); + return ""; + } for (prop in stageLol.props) { @@ -378,6 +386,6 @@ class StageOffsetSubState extends HaxeUISubState stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x); stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y); - return SerializerUtil.toJSON(stageLol); + return stageLol.serialize(); } } diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index b83b54152..ea04e1208 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -8,6 +8,7 @@ import flixel.group.FlxGroup; import flixel.input.actions.FlxActionInput; import flixel.input.gamepad.FlxGamepadInputID; import flixel.input.keyboard.FlxKey; +import funkin.graphics.FunkinSprite; import funkin.input.Controls; import funkin.ui.AtlasText; import funkin.ui.MenuList; @@ -61,8 +62,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page if (FlxG.gamepads.numActiveGamepads > 0) { - var devicesBg:FlxSprite = new FlxSprite(); - devicesBg.makeGraphic(FlxG.width, 100, 0xFFFAFD6D); + var devicesBg:FunkinSprite = new FunkinSprite(); + devicesBg.makeSolidColor(FlxG.width, 100, 0xFFFAFD6D); add(devicesBg); deviceList = new TextMenuList(Horizontal, None); add(deviceList); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index d6dd536f7..a616fd46b 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -10,6 +10,7 @@ import flixel.group.FlxGroup.FlxTypedGroup; import flixel.text.FlxText; import flixel.addons.transition.FlxTransitionableState; import flixel.tweens.FlxEase; +import funkin.graphics.FunkinSprite; import funkin.ui.MusicBeatState; import flixel.tweens.FlxTween; import flixel.util.FlxColor; @@ -153,7 +154,7 @@ class StoryMenuState extends MusicBeatState updateBackground(); - var black:FlxSprite = new FlxSprite(levelBackground.x, 0).makeGraphic(FlxG.width, Std.int(400 + levelBackground.y), FlxColor.BLACK); + var black:FunkinSprite = new FunkinSprite(levelBackground.x, 0).makeSolidColor(FlxG.width, Std.int(400 + levelBackground.y), FlxColor.BLACK); black.zIndex = levelBackground.zIndex - 1; add(black); diff --git a/source/funkin/ui/title/OutdatedSubState.hx b/source/funkin/ui/title/OutdatedSubState.hx index d262fc4e4..012823541 100644 --- a/source/funkin/ui/title/OutdatedSubState.hx +++ b/source/funkin/ui/title/OutdatedSubState.hx @@ -15,7 +15,7 @@ class OutdatedSubState extends MusicBeatState override function create() { super.create(); - var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK); add(bg); var ver = "v" + Application.current.meta.get('version'); var txt:FlxText = new FlxText(0, 0, FlxG.width, diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index bc44af073..a5dcd6def 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -13,6 +13,7 @@ import funkin.audio.visualize.SpectogramSprite; import funkin.graphics.shaders.ColorSwap; import funkin.graphics.shaders.LeftMaskShader; import funkin.data.song.SongRegistry; +import funkin.graphics.FunkinSprite; import funkin.ui.MusicBeatState; import funkin.data.song.SongData.SongMusicData; import funkin.graphics.shaders.TitleOutline; @@ -118,7 +119,8 @@ class TitleState extends MusicBeatState persistentUpdate = true; - var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK); + bg.screenCenter(); add(bg); logoBl = new FlxSprite(-150, -100); diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index a223a4123..da9aeb28b 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -13,6 +13,7 @@ import funkin.play.song.Song.SongDifficulty; import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatState; import haxe.io.Path; +import funkin.graphics.FunkinSprite; import lime.app.Future; import lime.app.Promise; import lime.utils.AssetLibrary; @@ -42,7 +43,7 @@ class LoadingState extends MusicBeatState override function create():Void { - var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFFcaff4d); + var bg:FlxSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d); add(bg); funkay = new FlxSprite(); @@ -53,7 +54,7 @@ class LoadingState extends MusicBeatState funkay.scrollFactor.set(); funkay.screenCenter(); - loadBar = new FlxSprite(0, FlxG.height - 20).makeGraphic(FlxG.width, 10, 0xFFff16d2); + loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(FlxG.width, 10, 0xFFff16d2); loadBar.screenCenter(X); add(loadBar); From cd9035da199f3b114b84745785574ffb902e25fd Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 16 Jan 2024 17:08:41 -0500 Subject: [PATCH 069/129] Update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 9b9baa6f2..9e385784b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9b9baa6f2b38dc9bd3354b350084f548e1e16c0f +Subproject commit 9e385784b1d2f4332de0d696b1df655cfa269da0 From 19cd7da8ee39ec3b80702d154fcfcc87fa178802 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 17 Jan 2024 01:01:13 -0500 Subject: [PATCH 070/129] Fix an FlxDrawItems crash tied to FlxAnimate --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index d93ea0658..7e17f105c 100644 --- a/hmm.json +++ b/hmm.json @@ -11,7 +11,7 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "a83738673e7edbf8acba3a1426af284dfe6719fe", + "ref": "07c6018008801972d12275690fc144fcc22e3de6", "url": "https://github.com/FunkinCrew/flixel" }, { @@ -37,7 +37,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "d7c5621be742e2c98d523dfe5af7528835eaff1e", + "ref": "9bacdd6ea39f5e3a33b0f5dfb7bc583fe76060d4", "url": "https://github.com/FunkinCrew/flxanimate" }, { From e44b028946e5d98c5798ec5aee0502c54fbac968 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Wed, 17 Jan 2024 16:24:03 -0700 Subject: [PATCH 071/129] Added side sliders that alter the volume of vocals and hitsounds on player/opponent sides. --- assets | 2 +- .../ui/debug/charting/ChartEditorState.hx | 88 ++++++++++++++++++- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/assets b/assets index d094640f7..0e9019f0f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d094640f727a670a348b3579d11af5ff6a2ada3a +Subproject commit 0e9019f0fcb53f3e554604ea9a4e62d381873d1f diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index e98809ce8..5fa5308e2 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -104,6 +104,7 @@ import haxe.ui.components.Label; import haxe.ui.components.Button; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; +import haxe.ui.components.VerticalSlider; import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.Frame; @@ -720,6 +721,34 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; } + /** + * Sound multiplier for vocals and hitsounds on the player's side. + */ + var soundMultiplierPlayer(default, set):Float = 1.0; + + function set_soundMultiplierPlayer(value:Float):Float + { + soundMultiplierPlayer = value; + var vocalTargetVolume:Float = (menubarItemVolumeVocals.value ?? 100.0) / 100.0; + if (audioVocalTrackGroup != null) audioVocalTrackGroup.playerVolume = vocalTargetVolume * soundMultiplierPlayer; + + return soundMultiplierPlayer; + } + + /** + * Sound multiplier for vocals and hitsounds on the opponent's side. + */ + var soundMultiplierOpponent(default, set):Float = 1.0; + + function set_soundMultiplierOpponent(value:Float):Float + { + soundMultiplierOpponent = value; + var vocalTargetVolume:Float = (menubarItemVolumeVocals.value ?? 100.0) / 100.0; + if (audioVocalTrackGroup != null) audioVocalTrackGroup.opponentVolume = vocalTargetVolume * soundMultiplierOpponent; + + return soundMultiplierOpponent; + } + // Auto-save /** @@ -1749,6 +1778,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var buttonSelectEvent:Button; + /** + * The slider above the grid that sets the volume of the player's sounds. + * Constructed manually and added to the layout so we can control its position. + */ + var sliderVolumePlayer:Slider; + + /** + * The slider above the grid that sets the volume of the opponent's sounds. + * Constructed manually and added to the layout so we can control its position. + */ + var sliderVolumeOpponent:Slider; + /** * RENDER OBJECTS */ @@ -2557,6 +2598,37 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new SetItemSelectionCommand([], currentSongChartEventData)); } } + + function setupSideSlider(x, y):VerticalSlider + { + var slider = new VerticalSlider(); + slider.allowFocus = false; + slider.x = x; + slider.y = y; + slider.width = NOTE_SELECT_BUTTON_HEIGHT; + slider.height = GRID_SIZE * 4; + slider.pos = slider.max; + slider.tooltip = "Slide to set the volume of sounds on this side."; + slider.zIndex = 110; + slider.styleNames = "sideSlider"; + add(slider); + + return slider; + } + + var sliderY = GRID_INITIAL_Y_POS + 34; + sliderVolumeOpponent = setupSideSlider(GRID_X_POS - 64, sliderY); + sliderVolumePlayer = setupSideSlider(buttonSelectEvent.x + buttonSelectEvent.width, sliderY); + + sliderVolumePlayer.onChange = event -> { + var volume:Float = event.value.toFloat() / 100.0; + soundMultiplierPlayer = volume; + } + + sliderVolumeOpponent.onChange = event -> { + var volume:Float = event.value.toFloat() / 100.0; + soundMultiplierOpponent = volume; + } } /** @@ -2797,7 +2869,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemVolumeVocals.onChange = event -> { var volume:Float = event.value.toFloat() / 100.0; - if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume; + if (audioVocalTrackGroup != null) + { + audioVocalTrackGroup.playerVolume = volume * soundMultiplierPlayer; + audioVocalTrackGroup.opponentVolume = volume * soundMultiplierOpponent; + } menubarLabelVolumeVocals.text = 'Voices - ${Std.int(event.value)}%'; } @@ -5662,7 +5738,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState audioInstTrack.volume = instTargetVolume; audioInstTrack.onComplete = null; } - if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = vocalTargetVolume; + if (audioVocalTrackGroup != null) + { + audioVocalTrackGroup.playerVolume = vocalTargetVolume * soundMultiplierPlayer; + audioVocalTrackGroup.opponentVolume = vocalTargetVolume * soundMultiplierOpponent; + } } function updateTimeSignature():Void @@ -5864,9 +5944,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState switch (noteData.getStrumlineIndex()) { case 0: // Player - if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume); + if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume * soundMultiplierPlayer); case 1: // Opponent - if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume); + if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume * soundMultiplierOpponent); } } } From 027c2843f4958158e5ae74deca7499c2160ac91f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 17 Jan 2024 22:19:40 -0500 Subject: [PATCH 072/129] This bug took me like 4-5 hours of staring at code to fix i am going crazy graaaa --- .../ui/debug/charting/ChartEditorState.hx | 25 ++++++++++--------- .../handlers/ChartEditorThemeHandler.hx | 6 +++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index e98809ce8..5e8f112dd 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1958,7 +1958,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState buildGrid(); buildMeasureTicks(); buildNotePreview(); - buildSelectionBox(); buildAdditionalUI(); populateOpenRecentMenu(); @@ -2287,17 +2286,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); } - function buildSelectionBox():Void - { - if (selectionBoxSprite == null) throw 'ERROR: Tried to build selection box, but selectionBoxSprite is null! Check ChartEditorThemeHandler.updateTheme().'; - - selectionBoxSprite.scrollFactor.set(0, 0); - add(selectionBoxSprite); - selectionBoxSprite.zIndex = 30; - - setSelectionBoxBounds(); - } - function setSelectionBoxBounds(bounds:FlxRect = null):Void { if (selectionBoxSprite == null) @@ -2319,6 +2307,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + /** + * Automatically goes through and calls render on everything you added. + */ + override public function draw():Void + { + if (selectionBoxStartPos != null) + { + trace('selectionBoxSprite: ${selectionBoxSprite.visible} ${selectionBoxSprite.exists} ${this.members.contains(selectionBoxSprite)}'); + } + + super.draw(); + } + function calculateNotePreviewViewportBounds():FlxRect { var bounds:FlxRect = new FlxRect(); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index 98bb5c2c8..89fd4d5d3 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -317,6 +317,12 @@ class ChartEditorThemeHandler ChartEditorState.GRID_SIZE - (2 * SELECTION_SQUARE_BORDER_WIDTH + 8)), 32, 32); + + state.selectionBoxSprite.scrollFactor.set(0, 0); + state.selectionBoxSprite.zIndex = 30; + state.add(state.selectionBoxSprite); + + state.setSelectionBoxBounds(); } static function updateNotePreview(state:ChartEditorState):Void From a0c4499b03bb7c14fc2bf9bb0d78313617a54c28 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 17 Jan 2024 23:12:59 -0500 Subject: [PATCH 073/129] Fix a bug where replaying a level makes a pink screen --- source/funkin/play/PlayState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 20f19f714..cc9debf13 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1354,6 +1354,7 @@ class PlayState extends MusicBeatSubState function loadStage(id:String):Void { currentStage = StageRegistry.instance.fetchEntry(id); + currentStage.revive(); // Stages are killed and props destroyed when the PlayState is destroyed to save memory. if (currentStage != null) { From 26b761066303aa0fb1ab2e71689235ce9456baaa Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 17 Jan 2024 23:14:28 -0500 Subject: [PATCH 074/129] Fix an error with playable Pico death --- assets | 2 +- source/funkin/play/GameOverSubState.hx | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/assets b/assets index 9e385784b..d6be0e084 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9e385784b1d2f4332de0d696b1df655cfa269da0 +Subproject commit d6be0e084e4fda0416eca9ec7fe406af9b626e5c diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 88d9be7d4..36f72237e 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -23,6 +23,12 @@ import funkin.play.character.BaseCharacter; */ class GameOverSubState extends MusicBeatSubState { + /** + * The currently active GameOverSubState. + * There should be only one GameOverSubState in existance at a time, we can use a singleton. + */ + public static var instance:GameOverSubState = null; + /** * Which alternate animation on the character to use. * You can set this via script. @@ -88,6 +94,13 @@ class GameOverSubState extends MusicBeatSubState override public function create() { + if (instance != null) + { + // TODO: Do something in this case? IDK. + trace('WARNING: GameOverSubState instance already exists. This should not happen.'); + } + instance = this; + super.create(); // @@ -283,10 +296,10 @@ class GameOverSubState extends MusicBeatSubState */ function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void { - var musicPath = Paths.music('gameOver' + musicSuffix); + var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix); if (isEnding) { - musicPath = Paths.music('gameOverEnd' + musicSuffix); + musicPath = Paths.music('gameplay/gameover/gameOverEnd' + musicSuffix); } if (!gameOverMusic.playing || force) { @@ -306,7 +319,7 @@ class GameOverSubState extends MusicBeatSubState public static function playBlueBalledSFX() { blueballed = true; - FlxG.sound.play(Paths.sound('fnf_loss_sfx' + blueBallSuffix)); + FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); } var playingJeffQuote:Bool = false; @@ -329,6 +342,11 @@ class GameOverSubState extends MusicBeatSubState } }); } + + public override function toString():String + { + return "GameOverSubState"; + } } typedef GameOverParams = From 25fe2c0d39ea0c3bf371fee23c5f3b4bee5ab53b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 17 Jan 2024 23:53:23 -0500 Subject: [PATCH 075/129] Rewrite Upload Vocals dialog. --- assets | 2 +- .../dialogs/ChartEditorUploadChartDialog.hx | 1 + .../dialogs/ChartEditorUploadVocalsDialog.hx | 311 ++++++++++++++++++ .../handlers/ChartEditorDialogHandler.hx | 223 +++---------- 4 files changed, 364 insertions(+), 173 deletions(-) create mode 100644 source/funkin/ui/debug/charting/dialogs/ChartEditorUploadVocalsDialog.hx diff --git a/assets b/assets index 9e385784b..d0e0c9f94 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9e385784b1d2f4332de0d696b1df655cfa269da0 +Subproject commit d0e0c9f94961793a815f66c9a2875c99ef3b4c8c diff --git a/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadChartDialog.hx b/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadChartDialog.hx index 5b84148c6..17f047106 100644 --- a/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadChartDialog.hx +++ b/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadChartDialog.hx @@ -13,6 +13,7 @@ import haxe.ui.notifications.NotificationType; // @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros. @:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-chart.xml")) +@:access(funkin.ui.debug.charting.ChartEditorState) class ChartEditorUploadChartDialog extends ChartEditorBaseDialog { var dropHandlers:Array = []; diff --git a/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadVocalsDialog.hx b/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadVocalsDialog.hx new file mode 100644 index 000000000..537c7c36e --- /dev/null +++ b/source/funkin/ui/debug/charting/dialogs/ChartEditorUploadVocalsDialog.hx @@ -0,0 +1,311 @@ +package funkin.ui.debug.charting.dialogs; + +import funkin.input.Cursor; +import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget; +import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams; +import funkin.util.FileUtil; +import funkin.play.character.CharacterData; +import haxe.io.Path; +import haxe.ui.components.Button; +import haxe.ui.components.Label; +import haxe.ui.containers.dialogs.Dialog.DialogButton; +import haxe.ui.containers.dialogs.Dialog.DialogEvent; +import haxe.ui.containers.Box; +import haxe.ui.containers.dialogs.Dialogs; +import haxe.ui.core.Component; +import haxe.ui.notifications.NotificationManager; +import haxe.ui.notifications.NotificationType; + +// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros. + +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-vocals.xml")) +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog +{ + var dropHandlers:Array = []; + + var vocalContainer:Component; + var dialogCancel:Button; + var dialogNoVocals:Button; + var dialogContinue:Button; + + var charIds:Array; + var instId:String; + var hasClearedVocals:Bool = false; + + public function new(state2:ChartEditorState, charIds:Array, params2:DialogParams) + { + super(state2, params2); + + this.charIds = charIds; + this.instId = chartEditorState.currentInstrumentalId; + + dialogCancel.onClick = function(_) { + hideDialog(DialogButton.CANCEL); + } + + dialogNoVocals.onClick = function(_) { + // Dismiss + chartEditorState.wipeVocalData(); + hideDialog(DialogButton.APPLY); + }; + + dialogContinue.onClick = function(_) { + // Dismiss + hideDialog(DialogButton.APPLY); + }; + + buildDropHandlers(); + } + + function buildDropHandlers():Void + { + for (charKey in charIds) + { + trace('Adding vocal upload for character ${charKey}'); + + var charMetadata:Null = CharacterDataParser.fetchCharacterData(charKey); + var charName:String = charMetadata?.name ?? charKey; + + var vocalsEntry = new ChartEditorUploadVocalsEntry(charName); + + var dropHandler:DialogDropTarget = {component: vocalsEntry, handler: null}; + + var onDropFile:String->Void = function(pathStr:String) { + trace('Selected file: $pathStr'); + var path:Path = new Path(pathStr); + + if (chartEditorState.loadVocalsFromPath(path, charKey, this.instId, !this.hasClearedVocals)) + { + this.hasClearedVocals = true; + // Tell the user the load was successful. + chartEditorState.success('Loaded Vocals', 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${chartEditorState.selectedVariation}'); + #if FILE_DROP_SUPPORTED + vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; + #else + vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${path.file}.${path.ext}'; + #end + + dialogNoVocals.hidden = true; + chartEditorState.removeDropHandler(dropHandler); + } + else + { + trace('Failed to load vocal track (${path.file}.${path.ext})'); + + chartEditorState.error('Failed to Load Vocals', + 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${chartEditorState.selectedVariation})'); + + #if FILE_DROP_SUPPORTED + vocalsEntry.vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.'; + #else + vocalsEntry.vocalsEntryLabel.text = 'Click to browse for vocals for $charName.'; + #end + } + }; + + vocalsEntry.onClick = function(_event) { + Dialogs.openBinaryFile('Open $charName Vocals', [ + {label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) { + if (selectedFile != null && selectedFile.bytes != null) + { + trace('Selected file: ' + selectedFile.name); + + if (chartEditorState.loadVocalsFromBytes(selectedFile.bytes, charKey, this.instId, !this.hasClearedVocals)) + { + hasClearedVocals = true; + // Tell the user the load was successful. + chartEditorState.success('Loaded Vocals', + 'Loaded vocals for $charName (${selectedFile.name}), variation ${chartEditorState.selectedVariation}'); + + #if FILE_DROP_SUPPORTED + vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; + #else + vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${selectedFile.name}'; + #end + + dialogNoVocals.hidden = true; + } + else + { + trace('Failed to load vocal track (${selectedFile.fullPath})'); + + chartEditorState.error('Failed to Load Vocals', + 'Failed to load vocal track (${selectedFile.name}) for variation (${chartEditorState.selectedVariation})'); + + #if FILE_DROP_SUPPORTED + vocalsEntry.vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.'; + #else + vocalsEntry.vocalsEntryLabel.text = 'Click to browse for vocals for $charName.'; + #end + } + } + }); + } + + dropHandler.handler = onDropFile; + + // onDropFile + #if FILE_DROP_SUPPORTED + dropHandlers.push(dropHandler); + #end + + vocalContainer.addComponent(vocalsEntry); + } + } + + public static function build(state:ChartEditorState, charIds:Array, ?closable:Bool, ?modal:Bool):ChartEditorUploadVocalsDialog + { + var dialog = new ChartEditorUploadVocalsDialog(state, charIds, + { + closable: closable ?? false, + modal: modal ?? true + }); + + for (dropTarget in dialog.dropHandlers) + { + state.addDropHandler(dropTarget); + } + + dialog.showDialog(modal ?? true); + + return dialog; + } + + public override function onClose(event:DialogEvent):Void + { + super.onClose(event); + + if (event.button != DialogButton.APPLY && !this.closable) + { + // User cancelled the wizard! Back to the welcome dialog. + chartEditorState.openWelcomeDialog(this.closable); + } + + for (dropTarget in dropHandlers) + { + chartEditorState.removeDropHandler(dropTarget); + } + } + + public override function lock():Void + { + super.lock(); + this.dialogCancel.disabled = true; + } + + public override function unlock():Void + { + super.unlock(); + this.dialogCancel.disabled = false; + } + + /** + * Called when clicking the Upload Chart box. + */ + public function onClickChartBox():Void + { + if (this.locked) return; + + this.lock(); + // TODO / BUG: File filtering not working on mac finder dialog, so we don't use it for now + #if !mac + FileUtil.browseForBinaryFile('Open Chart', [FileUtil.FILE_EXTENSION_INFO_FNFC], onSelectFile, onCancelBrowse); + #else + FileUtil.browseForBinaryFile('Open Chart', null, onSelectFile, onCancelBrowse); + #end + } + + /** + * Called when a file is selected by dropping a file onto the Upload Chart box. + */ + function onDropFileChartBox(pathStr:String):Void + { + var path:Path = new Path(pathStr); + trace('Dropped file (${path})'); + + try + { + var result:Null> = ChartEditorImportExportHandler.loadFromFNFCPath(chartEditorState, path.toString()); + if (result != null) + { + chartEditorState.success('Loaded Chart', + result.length == 0 ? 'Loaded chart (${path.toString()})' : 'Loaded chart (${path.toString()})\n${result.join("\n")}'); + this.hideDialog(DialogButton.APPLY); + } + else + { + chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${path.toString()})'); + } + } + catch (err) + { + chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${path.toString()}): ${err}'); + } + } + + /** + * Called when a file is selected by the dialog displayed when clicking the Upload Chart box. + */ + function onSelectFile(selectedFile:SelectedFileInfo):Void + { + this.unlock(); + + if (selectedFile != null && selectedFile.bytes != null) + { + try + { + var result:Null> = ChartEditorImportExportHandler.loadFromFNFC(chartEditorState, selectedFile.bytes); + if (result != null) + { + chartEditorState.success('Loaded Chart', + result.length == 0 ? 'Loaded chart (${selectedFile.name})' : 'Loaded chart (${selectedFile.name})\n${result.join("\n")}'); + + if (selectedFile.fullPath != null) chartEditorState.currentWorkingFilePath = selectedFile.fullPath; + this.hideDialog(DialogButton.APPLY); + } + } + catch (err) + { + chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${selectedFile.name}): ${err}'); + } + } + } + + function onCancelBrowse():Void + { + this.unlock(); + } +} + +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-vocals-entry.xml")) +class ChartEditorUploadVocalsEntry extends Box +{ + public var vocalsEntryLabel:Label; + + var charName:String; + + public function new(charName:String) + { + super(); + + this.charName = charName; + + #if FILE_DROP_SUPPORTED + vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.'; + #else + vocalsEntryLabel.text = 'Click to browse for vocals for $charName.'; + #end + + this.onMouseOver = function(_event) { + // if (this.locked) return; + this.swapClass('upload-bg', 'upload-bg-hover'); + Cursor.cursorMode = Pointer; + } + + this.onMouseOut = function(_event) { + this.swapClass('upload-bg-hover', 'upload-bg'); + Cursor.cursorMode = Default; + } + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 32e9fef53..970f021ac 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -19,6 +19,7 @@ import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget; import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu; import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog; import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog; +import funkin.ui.debug.charting.dialogs.ChartEditorUploadVocalsDialog; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.util.Constants; import funkin.util.DateUtil; @@ -59,11 +60,8 @@ using Lambda; class ChartEditorDialogHandler { // Paths to HaxeUI layout files for each dialog. - static final CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-chart'); static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst'); static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata'); - static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals'); - static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts'); static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts-entry'); static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart'); @@ -105,6 +103,56 @@ class ChartEditorDialogHandler return dialog; } + /** + * Builds and opens a dialog letting the user browse for a chart file to open. + * @param state The current chart editor state. + * @param closable Whether the dialog can be closed by the user. + * @return The dialog that was opened. + */ + public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null + { + var dialog = ChartEditorUploadChartDialog.build(state, closable); + + dialog.zIndex = 1000; + state.isHaxeUIDialogOpen = true; + + return dialog; + } + + /** + * Builds and opens a dialog where the user uploads vocals for the current song. + * @param state The current chart editor state. + * @param closable Whether the dialog can be closed by the user. + * @return The dialog that was opened. + */ + public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog + { + var charData:SongCharacterData = state.currentSongMetadata.playData.characters; + + var hasClearedVocals:Bool = false; + + var charIdsForVocals:Array = [charData.player, charData.opponent]; + + var dialog = ChartEditorUploadVocalsDialog.build(state, charIdsForVocals, closable); + + dialog.zIndex = 1000; + state.isHaxeUIDialogOpen = true; + + return dialog; + } + + /** + * Builds and opens the dialog for selecting a character. + */ + public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null + { + var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition); + + menu.zIndex = 1000; + + return menu; + } + /** * Builds and opens a dialog letting the user know a backup is available, and prompting them to load it. */ @@ -186,22 +234,6 @@ class ChartEditorDialogHandler return dialog; } - /** - * Builds and opens a dialog letting the user browse for a chart file to open. - * @param state The current chart editor state. - * @param closable Whether the dialog can be closed by the user. - * @return The dialog that was opened. - */ - public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null - { - var dialog = ChartEditorUploadChartDialog.build(state, closable); - - dialog.zIndex = 1000; - state.isHaxeUIDialogOpen = true; - - return dialog; - } - /** * Open the wizard for opening an existing chart from individual files. * @param state @@ -288,15 +320,6 @@ class ChartEditorDialogHandler }; } - public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null - { - var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition); - - menu.zIndex = 1000; - - return menu; - } - public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void { // Step 1. Song Metadata @@ -699,150 +722,6 @@ class ChartEditorDialogHandler return dialog; } - /** - * Builds and opens a dialog where the user uploads vocals for the current song. - * @param state The current chart editor state. - * @param closable Whether the dialog can be closed by the user. - * @return The dialog that was opened. - */ - public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog - { - var instId:String = state.currentInstrumentalId; - var charIdsForVocals:Array = []; - - var charData:SongCharacterData = state.currentSongMetadata.playData.characters; - - var hasClearedVocals:Bool = false; - - charIdsForVocals.push(charData.player); - charIdsForVocals.push(charData.opponent); - - var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable); - if (dialog == null) throw 'Could not locate Upload Vocals dialog'; - - var dialogContainer:Null = dialog.findComponent('vocalContainer'); - if (dialogContainer == null) throw 'Could not locate vocalContainer in Upload Vocals dialog'; - - var buttonCancel:Null