This commit is contained in:
Lasercar 2025-04-05 06:47:42 +10:00 committed by GitHub
commit bb0e9148b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 142 additions and 101 deletions

View file

@ -335,7 +335,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
}
else
{
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
}
}
@ -348,7 +348,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
}
else
{
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
}
}

View file

@ -1,76 +0,0 @@
package funkin.play.song;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongMetadata;
import funkin.util.FileUtil;
import openfl.net.FileReference;
/**
* TODO: Refactor and remove this.
*/
class SongSerializer
{
/**
* Access a SongChartData JSON file from a specific path, then load it.
* @param path The file path to read from.
*/
public static function importSongChartDataSync(path:String):SongChartData
{
var fileData = FileUtil.readStringFromPath(path);
if (fileData == null) return null;
var songChartData:SongChartData = fileData.parseJSON();
return songChartData;
}
/**
* Access a SongMetadata JSON file from a specific path, then load it.
* @param path The file path to read from.
*/
public static function importSongMetadataSync(path:String):SongMetadata
{
var fileData = FileUtil.readStringFromPath(path);
if (fileData == null) return null;
var songMetadata:SongMetadata = fileData.parseJSON();
return songMetadata;
}
/**
* Prompt the user to browse for a SongChartData JSON file path, then load it.
* @param callback The function to call when the file is loaded.
*/
public static function importSongChartDataAsync(callback:SongChartData->Void):Void
{
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songChartData:SongChartData = data.parseJSON();
if (songChartData != null) callback(songChartData);
});
}
/**
* Prompt the user to browse for a SongMetadata JSON file path, then load it.
* @param callback The function to call when the file is loaded.
*/
public static function importSongMetadataAsync(callback:SongMetadata->Void):Void
{
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songMetadata:SongMetadata = data.parseJSON();
if (songMetadata != null) callback(songMetadata);
});
}
}

View file

@ -1294,6 +1294,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function set_currentSongChartData(value:SongChartData):SongChartData
{
songChartData.set(selectedVariation, value);
var variationMetadata:Null<SongMetadata> = songMetadata.get(selectedVariation);
if (variationMetadata != null)
{
// Add the difficulties to the metadata if they're new so that the editor properly loads them
// This is a silly way of getting the new difficulties but what other option do I have?
var keys:Array<String> = [for (x in songChartData.get(selectedVariation).scrollSpeed.keys()) x];
for (key in keys)
{
variationMetadata.playData.difficulties.pushUnique(key);
}
}
return value;
}
@ -3491,7 +3502,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// This sprite is off-screen or was deleted.
// Kill the note sprite and recycle it.
noteSprite.noteData = null;
noteSprite.kill();
}
}
// Sort the note data array, using an algorithm that is fast on nearly-sorted data.
@ -3509,7 +3520,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// It will be displayed by gridGhostHoldNoteSprite instead.
holdNoteSprite.kill();
}
else if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
else if (!holdNoteSprite.isHoldNoteVisible(viewAreaBottomPixels, viewAreaTopPixels))
{
// This hold note is off-screen.
// Kill the hold note sprite and recycle it.
@ -3533,7 +3544,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Update the event sprite's height and position.
// var holdNoteHeight = holdNoteSprite.noteData.getStepLength() * GRID_SIZE;
// holdNoteSprite.setHeightDirectly(holdNoteHeight);
holdNoteSprite.updateHoldNotePosition(renderedNotes);
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
}
}
// Sort the note data array, using an algorithm that is fast on nearly-sorted data.
@ -3549,7 +3560,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 - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT)
if ((eventSprite.isEventVisible(viewAreaBottomPixels, viewAreaTopPixels)
&& currentSongChartEventData.fastContains(eventSprite.eventData))
|| isSelectedAndDragged)
{
@ -3565,7 +3576,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// This event was deleted.
// Kill the event sprite and recycle it.
eventSprite.eventData = null;
eventSprite.kill();
}
}
// Sort the note data array, using an algorithm that is fast on nearly-sorted data.

View file

@ -37,7 +37,6 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;
state.scrollPositionInPixels = 0;
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
@ -61,7 +60,6 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;
state.scrollPositionInPixels = 0;
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);

View file

@ -1168,7 +1168,8 @@ class ChartEditorDialogHandler
var dialogNoteStyle:Null<DropDown> = dialog.findComponent('dialogNoteStyle', DropDown);
if (dialogNoteStyle == null) throw 'Could not locate dialogNoteStyle DropDown in Add Variation dialog';
dialogNoteStyle.value = state.currentSongMetadata.playData.noteStyle;
var startingValueNoteStyle = ChartEditorDropdowns.populateDropdownWithNoteStyles(dialogNoteStyle, state.currentSongMetadata.playData.noteStyle);
dialogNoteStyle.value = startingValueNoteStyle;
var dialogCharacterPlayer:Null<DropDown> = dialog.findComponent('dialogCharacterPlayer', DropDown);
if (dialogCharacterPlayer == null) throw 'Could not locate dialogCharacterPlayer DropDown in Add Variation dialog';
@ -1203,7 +1204,7 @@ class ChartEditorDialogHandler
var pendingVariation:SongMetadata = new SongMetadata(dialogSongName.text, dialogSongArtist.text, dialogVariationName.text.toLowerCase());
pendingVariation.playData.stage = dialogStage.value.id;
pendingVariation.playData.noteStyle = dialogNoteStyle.value;
pendingVariation.playData.noteStyle = dialogNoteStyle.value.id;
pendingVariation.timeChanges[0].bpm = dialogBPM.value;
state.songMetadata.set(pendingVariation.variation, pendingVariation);

View file

@ -113,7 +113,10 @@ class ChartEditorToolboxHandler
{
var toolbox:Null<ChartEditorBaseToolbox> = cast state.activeToolboxes.get(id);
if (toolbox == null) return;
if (toolbox == null)
{
toolbox = cast initToolbox(state, id);
}
if (toolbox != null)
{

View file

@ -1,12 +1,24 @@
package funkin.ui.debug.charting.toolboxes;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongRegistry;
import haxe.ui.components.Button;
import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.containers.dialogs.Dialog.DialogButton;
import funkin.data.song.SongData.SongMetadata;
import haxe.ui.components.DropDown;
import haxe.ui.components.HorizontalSlider;
import funkin.util.VersionUtil;
import funkin.util.FileUtil;
import openfl.net.FileReference;
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
import funkin.play.song.SongSerializer;
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.TreeView;
import haxe.ui.containers.TreeViewNode;
import haxe.ui.events.UIEvent;
@ -81,26 +93,96 @@ class ChartEditorDifficultyToolbox extends ChartEditorBaseToolbox
difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) {
var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : '';
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-metadata.json', chartEditorState.currentSongMetadata.serialize());
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-metadata.json', chartEditorState.currentSongMetadata.serialize(),
function(notification:String) {
switch (notification)
{
case "success":
chartEditorState.success("Saved Metadata", 'Successfully wrote file (${chartEditorState.currentSongId}$vari-metadata.json).');
case "info":
chartEditorState.info("Canceled Save Metadata", '(${chartEditorState.currentSongId}$vari-metadata.json)');
case "error":
chartEditorState.error("Failure", 'Failed to write file (${chartEditorState.currentSongId}$vari-metadata.json).');
}
});
};
difficultyToolboxSaveChart.onClick = function(_:UIEvent) {
var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : '';
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-chart.json', chartEditorState.currentSongChartData.serialize());
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-chart.json', chartEditorState.currentSongChartData.serialize(),
function(notification:String) {
switch (notification)
{
case "success":
chartEditorState.success("Saved Chart Data", 'Successfully wrote file (${chartEditorState.currentSongId}$vari-chart.json).');
case "info":
chartEditorState.info("Canceled Save Chart Data", '(${chartEditorState.currentSongId}$vari-chart.json)');
case "error":
chartEditorState.error("Failure", 'Failed to write file (${chartEditorState.currentSongId}$vari-chart.json).');
}
});
};
difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) {
// Replace metadata for current variation.
SongSerializer.importSongMetadataAsync(function(songMetadata) {
chartEditorState.currentSongMetadata = songMetadata;
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songMetadataVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(data);
var songMetadata:Null<SongMetadata> = null;
if (VersionUtil.validateVersion(songMetadataVersion,
SongRegistry.SONG_METADATA_VERSION_RULE)) songMetadata = SongRegistry.instance.parseEntryMetadataRawWithMigration(data, fileReference.name,
songMetadataVersion);
if (songMetadata != null)
{
chartEditorState.currentSongMetadata = songMetadata;
chartEditorState.healthIconsDirty = true;
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
chartEditorState.success('Replaced Metadata', 'Replaced metadata with file (${fileReference.name})');
}
else
{
chartEditorState.error('Failure', 'Failed to load metadata file (${fileReference.name})');
}
});
};
difficultyToolboxLoadChart.onClick = function(_:UIEvent) {
// Replace chart data for current variation.
SongSerializer.importSongChartDataAsync(function(songChartData) {
chartEditorState.currentSongChartData = songChartData;
chartEditorState.noteDisplayDirty = true;
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songChartDataVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(data);
var songChartData:Null<SongChartData> = null;
if (VersionUtil.validateVersion(songChartDataVersion,
SongRegistry.SONG_CHART_DATA_VERSION_RULE)) songChartData = SongRegistry.instance.parseEntryChartDataRawWithMigration(data, fileReference.name,
songChartDataVersion);
if (songChartData != null)
{
chartEditorState.currentSongChartData = songChartData;
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
updateTree();
refresh();
chartEditorState.success('Loaded Chart Data', 'Loaded chart data file (${fileReference.name})');
if (chartEditorState.currentNoteSelection != []) chartEditorState.currentNoteSelection = [];
if (chartEditorState.currentEventSelection != []) chartEditorState.currentEventSelection = [];
chartEditorState.noteDisplayDirty = true;
chartEditorState.notePreviewDirty = true;
chartEditorState.noteTooltipsDirty = true;
chartEditorState.notePreviewViewportBoundsDirty = true;
}
else
{
chartEditorState.error('Failure', 'Failed to load chart data file (${fileReference.name})');
}
});
};

View file

@ -3,6 +3,8 @@ package funkin.ui.debug.charting.toolboxes;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
import funkin.data.stage.StageRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
import haxe.ui.components.Button;
@ -112,8 +114,12 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
inputStage.value = startingValueStage;
inputNoteStyle.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
var valid:Bool = event.data != null && event.data.id != null;
if (valid)
{
chartEditorState.currentSongNoteStyle = event.data.id;
}
};
var startingValueNoteStyle = ChartEditorDropdowns.populateDropdownWithNoteStyles(inputNoteStyle, chartEditorState.currentSongMetadata.playData.noteStyle);
inputNoteStyle.value = startingValueNoteStyle;
@ -122,8 +128,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
if (event.value == null || event.value <= 0) return;
// Use a command so we can undo/redo this action.
var startingBPM = chartEditorState.currentSongMetadata.timeChanges[0].bpm;
if (event.value != startingBPM)
if (event.value != Conductor.instance.bpm)
{
chartEditorState.performCommand(new ChangeStartingBPMCommand(event.value));
}
@ -191,7 +196,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
inputSongArtist.value = chartEditorState.currentSongMetadata.artist;
inputSongCharter.value = chartEditorState.currentSongMetadata.charter;
inputStage.value = chartEditorState.currentSongMetadata.playData.stage;
inputNoteStyle.value = chartEditorState.currentSongMetadata.playData.noteStyle;
inputNoteStyle.value = chartEditorState.currentSongNoteStyle;
inputBPM.value = chartEditorState.currentSongMetadata.timeChanges[0].bpm;
inputDifficultyRating.value = chartEditorState.currentSongChartDifficultyRating;
inputScrollSpeed.value = chartEditorState.currentSongChartScrollSpeed;
@ -199,6 +204,11 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
frameVariation.text = 'Variation: ${chartEditorState.selectedVariation.toTitleCase()}';
frameDifficulty.text = 'Difficulty: ${chartEditorState.selectedDifficulty.toTitleCase()}';
if (chartEditorState.currentSongMetadata.timeChanges[0].bpm != Conductor.instance.bpm)
{
chartEditorState.performCommand(new ChangeStartingBPMCommand(chartEditorState.currentSongMetadata.timeChanges[0].bpm));
}
var currentTimeSignature = '${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum}/${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen}';
trace('Setting time signature to ${currentTimeSignature}');
inputTimeSignature.value = {id: currentTimeSignature, text: currentTimeSignature};
@ -212,6 +222,15 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
{id: "mainStage", text: "Main Stage"};
}
var noteStyleId:String = chartEditorState.currentSongNoteStyle;
var noteStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
if (inputNoteStyle != null)
{
inputNoteStyle.value = (noteStyle != null) ?
{id: noteStyle.id, text: noteStyle.getName()} :
{id: "Funkin", text: "Funkin'"};
}
var LIMIT = 6;
var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.opponent);

View file

@ -391,17 +391,20 @@ class FileUtil
/**
* Prompts the user to save a file to their computer.
*/
public static function writeFileReference(path:String, data:String)
public static function writeFileReference(path:String, data:String, callback:String->Void)
{
var file = new FileReference();
file.addEventListener(Event.COMPLETE, function(e:Event) {
trace('Successfully wrote file.');
callback("success");
});
file.addEventListener(Event.CANCEL, function(e:Event) {
trace('Cancelled writing file.');
callback("info");
});
file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent) {
trace('IO error writing file.');
callback("error");
});
file.save(data, path);
}