mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-12-08 13:08:26 +00:00
Osu!Mania Importing
This commit is contained in:
parent
ba31f3611a
commit
0be42bf047
81
source/funkin/data/song/importer/OsuManiaData.hx
Normal file
81
source/funkin/data/song/importer/OsuManiaData.hx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package funkin.data.song.importer;
|
||||
|
||||
/**
|
||||
* Structure of a parsed Osu!Mania .osu file
|
||||
* Stuctured like a INI file format by CSV for HitObjects and more
|
||||
*/
|
||||
typedef OsuManiaData =
|
||||
{
|
||||
var General:
|
||||
{
|
||||
var PreviewTime:Int;
|
||||
};
|
||||
var Editor:
|
||||
{
|
||||
var DistanceSpacing:Float;
|
||||
var BeatDivisor:Int;
|
||||
var GridSize:Int;
|
||||
};
|
||||
var Metadata:
|
||||
{
|
||||
var Title:String;
|
||||
var TitleUnicode:String;
|
||||
var Artist:String;
|
||||
var ArtistUnicode:String;
|
||||
var Creator:String;
|
||||
var Version:String;
|
||||
};
|
||||
var Difficulty:
|
||||
{
|
||||
var OverallDifficulty:Float;
|
||||
var SliderMultiplier:Float;
|
||||
var SliderTickRate:Float;
|
||||
};
|
||||
var HitObjects:Array<ManiaHitObject>;
|
||||
var TimingPoints:Array<TimingPoint>;
|
||||
var Events:Array<Any>;
|
||||
}
|
||||
|
||||
class TimingPoint
|
||||
{
|
||||
public var time:Float;
|
||||
public var beatLength:Float;
|
||||
public var meter:Int;
|
||||
public var sampleSet:Int;
|
||||
public var sampleIndex:Int;
|
||||
public var volume:Int;
|
||||
public var uninherited:Int;
|
||||
public var effects:Int;
|
||||
public var bpm:Null<Float>;
|
||||
public var sv:Null<Float>;
|
||||
|
||||
public function new(time:Float, beatLength:Float, meter:Int, sampleSet:Int, sampleIndex:Int, volume:Int, uninherited:Int, effects:Int)
|
||||
{
|
||||
this.time = time;
|
||||
this.beatLength = beatLength;
|
||||
this.meter = meter;
|
||||
this.sampleSet = sampleSet;
|
||||
this.sampleIndex = sampleIndex;
|
||||
this.volume = volume;
|
||||
this.uninherited = uninherited;
|
||||
this.effects = effects;
|
||||
|
||||
// Derived values
|
||||
this.bpm = (uninherited == 1) ? (Math.round((60000 / beatLength) * 10) / 10) : null;
|
||||
this.sv = (uninherited == 0) ? (beatLength / 100) : null; // Just incase someone wants to add Scroll Velocity Support
|
||||
}
|
||||
}
|
||||
|
||||
class ManiaHitObject
|
||||
{
|
||||
public var time:Int;
|
||||
public var column:Int;
|
||||
public var holdDuration:Int;
|
||||
|
||||
public function new(time:Int, column:Int, holdDuration:Int)
|
||||
{
|
||||
this.time = time;
|
||||
this.column = column;
|
||||
this.holdDuration = holdDuration;
|
||||
}
|
||||
}
|
||||
185
source/funkin/data/song/importer/OsuManiaImporter.hx
Normal file
185
source/funkin/data/song/importer/OsuManiaImporter.hx
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package funkin.data.song.importer;
|
||||
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.importer.OsuManiaData;
|
||||
import funkin.data.song.importer.OsuManiaData.TimingPoint;
|
||||
import funkin.data.song.importer.OsuManiaData.ManiaHitObject;
|
||||
|
||||
class OsuManiaImporter
|
||||
{
|
||||
public static function parseOsuFile(osuContent:String):OsuManiaData
|
||||
{
|
||||
var lines:Array<String> = osuContent.split("\n");
|
||||
var result:Dynamic = {};
|
||||
var currentSection:String = null;
|
||||
|
||||
var nonCSVLikeSections = ["General", "Editor", "Metadata", "Difficulty"];
|
||||
|
||||
for (line in lines)
|
||||
{
|
||||
line = StringTools.trim(line);
|
||||
if (line == "" || StringTools.startsWith(line, "//")) continue;
|
||||
|
||||
// Section header like [General]
|
||||
var sectionRegex = ~/^\[(.+)\]$/;
|
||||
if (sectionRegex.match(line))
|
||||
{
|
||||
currentSection = sectionRegex.matched(1);
|
||||
if (nonCSVLikeSections.contains(currentSection))
|
||||
{
|
||||
Reflect.setField(result, currentSection, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
Reflect.setField(result, currentSection, []);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key-value pairs (INI style)
|
||||
if (currentSection != null && nonCSVLikeSections.contains(currentSection))
|
||||
{
|
||||
var parts:Array<String> = line.split(":");
|
||||
var key:String = StringTools.trim(parts.shift());
|
||||
var value:String = StringTools.trim(parts.join(":"));
|
||||
if (Reflect.field(result, currentSection) == null) Reflect.setField(result, currentSection, {});
|
||||
Reflect.setField(Reflect.field(result, currentSection), key, value);
|
||||
}
|
||||
// For CSV-like sections
|
||||
else if (currentSection != null)
|
||||
{
|
||||
var theArray:Array<String> = cast Reflect.field(result, currentSection);
|
||||
theArray.push(line);
|
||||
Reflect.setField(result, currentSection, theArray);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data The raw parsed JSON data to migrate, as a Dynamic.
|
||||
* @param difficulty
|
||||
* @return SongMetadata
|
||||
*/
|
||||
public static function migrateMetadata(songData:Dynamic, difficulty:String = 'normal'):SongMetadata
|
||||
{
|
||||
trace('Migrating song metadata from Osu!Mania.');
|
||||
|
||||
var songMetadata:SongMetadata = new SongMetadata('Import', songData.Metadata.ArtistUnicode ?? songData.Metadata.Artist ?? Constants.DEFAULT_ARTIST,
|
||||
songData.Metadata.Creator ?? Constants.DEFAULT_CHARTER, Constants.DEFAULT_VARIATION);
|
||||
|
||||
// Set generatedBy string for debugging.
|
||||
songMetadata.generatedBy = 'Chart Editor Import (Osu!Mania)';
|
||||
|
||||
songMetadata.playData.stage = 'mainStage';
|
||||
songMetadata.songName = songData.Metadata.TitleUnicode ?? songData.Metadata.Title ?? 'Import';
|
||||
songMetadata.playData.difficulties = [difficulty];
|
||||
|
||||
songMetadata.playData.songVariations = [];
|
||||
|
||||
songMetadata.timeChanges = rebuildTimeChanges(songData);
|
||||
|
||||
songMetadata.playData.characters = new SongCharacterData('bf', 'gf', 'dad');
|
||||
songMetadata.playData.ratings.set(difficulty, songData.Difficulty.OverallDifficulty ?? 0);
|
||||
|
||||
return songMetadata;
|
||||
}
|
||||
|
||||
static function rebuildTimeChanges(songData:Dynamic):Array<SongTimeChange>
|
||||
{
|
||||
var timings:Array<TimingPoint> = parseTimingPoints(songData.TimingPoints);
|
||||
var bpmPoints:Array<TimingPoint> = timings.filter((tp) -> tp.uninherited == 1);
|
||||
|
||||
var result:Array<SongTimeChange> = [];
|
||||
if (bpmPoints.length >= 1)
|
||||
{
|
||||
result.push(new SongTimeChange(0, bpmPoints[0].bpm ?? Constants.DEFAULT_BPM));
|
||||
|
||||
for (i in 1...bpmPoints.length)
|
||||
{
|
||||
var bpmPoint:TimingPoint = bpmPoints[i];
|
||||
|
||||
result.push(new SongTimeChange(bpmPoint.time, bpmPoint.bpm ?? Constants.DEFAULT_BPM));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length == 0)
|
||||
{
|
||||
result.push(new SongTimeChange(0, Constants.DEFAULT_BPM));
|
||||
trace("[WARN] No BPM points found, resulting to default BPM...");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function migrateChartData(songData:Dynamic, difficulty:String = 'normal'):SongChartData
|
||||
{
|
||||
trace('Migrating song chart data from Osu!Mania.');
|
||||
|
||||
// Osu!Mania doesn't have a scroll speed variable as its controlled by the player
|
||||
var songChartData:SongChartData = new SongChartData([difficulty => Constants.DEFAULT_SCROLLSPEED], [], [difficulty => []]);
|
||||
|
||||
var osuNotes:Array<ManiaHitObject> = parseManiaHitObjects(songData.HitObjects);
|
||||
songChartData.notes.set(difficulty, convertNotes(osuNotes));
|
||||
|
||||
songChartData.events = [];
|
||||
|
||||
return songChartData;
|
||||
}
|
||||
|
||||
static final STRUMLINE_SIZE = 4;
|
||||
|
||||
static function convertNotes(hitObjects:Array<ManiaHitObject>):Array<SongNoteData>
|
||||
{
|
||||
var result:Array<SongNoteData> = [];
|
||||
|
||||
for (hitObject in hitObjects)
|
||||
{
|
||||
result.push(new SongNoteData(hitObject.time, hitObject.column, hitObject.holdDuration ?? 0, ''));
|
||||
result.push(new SongNoteData(hitObject.time, hitObject.column + STRUMLINE_SIZE, hitObject.holdDuration ?? 0, ''));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function parseTimingPoints(timingLines:Array<String>):Array<TimingPoint>
|
||||
{
|
||||
return timingLines.map(function(line:String):TimingPoint {
|
||||
var parts = line.split(",");
|
||||
var time = Std.parseFloat(parts[0]);
|
||||
var beatLength = Std.parseFloat(parts[1]);
|
||||
var meter = Std.parseInt(parts[2]);
|
||||
var sampleSet = Std.parseInt(parts[3]);
|
||||
var sampleIndex = Std.parseInt(parts[4]);
|
||||
var volume = Std.parseInt(parts[5]);
|
||||
var uninherited = Std.parseInt(parts[6]);
|
||||
var effects = Std.parseInt(parts[7]);
|
||||
|
||||
return new TimingPoint(time, beatLength, meter, sampleSet, sampleIndex, volume, uninherited, effects);
|
||||
});
|
||||
}
|
||||
|
||||
static function parseManiaHitObjects(hitObjectsLines:Array<String>, ?columns:Int = 4):Array<ManiaHitObject>
|
||||
{
|
||||
return hitObjectsLines.map(function(line:String):ManiaHitObject {
|
||||
var parts = line.split(",");
|
||||
|
||||
var x:Int = Std.parseInt(parts[0]);
|
||||
var time:Int = Std.parseInt(parts[2]);
|
||||
var type:Int = Std.parseInt(parts[3]);
|
||||
var hasHold:Bool = (type & 128) == 128;
|
||||
|
||||
var noteD:Int = Std.int(x / (512 / columns));
|
||||
var holdEndTime:Null<Int> = hasHold ? Std.parseInt(parts[5].split(":")[0]) : null;
|
||||
|
||||
var holdDuration:Int = (holdEndTime != null) ? (holdEndTime - time) : 0;
|
||||
|
||||
return new ManiaHitObject(time, noteD, holdDuration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
|||
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
|
||||
#if FEATURE_FILE_DROP
|
||||
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}';
|
||||
|
|
@ -95,7 +95,7 @@ class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
|||
chartEditorState.error('Failed to Load Vocals',
|
||||
'Failed to load vocal track (${path.file}.${path.ext}) for variation (${chartEditorState.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
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.';
|
||||
|
|
@ -117,7 +117,7 @@ class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
|||
chartEditorState.success('Loaded Vocals',
|
||||
'Loaded vocals for $charName (${selectedFile.name}), variation ${chartEditorState.selectedVariation}');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
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}';
|
||||
|
|
@ -132,7 +132,7 @@ class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
|||
chartEditorState.error('Failed to Load Vocals',
|
||||
'Failed to load vocal track (${selectedFile.name}) for variation (${chartEditorState.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
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.';
|
||||
|
|
@ -145,7 +145,7 @@ class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
|||
dropHandler.handler = onDropFile;
|
||||
|
||||
// onDropFile
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
dropHandlers.push(dropHandler);
|
||||
#end
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ class ChartEditorUploadVocalsEntry extends Box
|
|||
|
||||
this.charName = charName;
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
|||
this.splashCreateFromSongErectOnly.onClick = _ -> onClickLinkCreateErectOnly();
|
||||
this.splashCreateFromSongBasicErect.onClick = _ -> onClickLinkCreateBasicErect();
|
||||
this.splashImportChartLegacy.onClick = _ -> onClickLinkImportChartLegacy();
|
||||
this.splashImportChartOsuMania.onClick = _ -> onClickLinkImportOsuMania();
|
||||
|
||||
// Add items to the Recent Charts list
|
||||
#if sys
|
||||
|
|
@ -242,5 +243,18 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
|||
// Open the "Import Chart" dialog
|
||||
chartEditorState.openImportChartWizard('legacy', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Import Chart: Osu! Mania" link in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickLinkImportOsuMania():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Open the "Import Chart" dialog
|
||||
chartEditorState.openImportChartWizard('osumania', false);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ package funkin.ui.debug.charting.handlers;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.data.song.importer.FNFLegacyData;
|
||||
import funkin.data.song.importer.FNFLegacyImporter;
|
||||
import funkin.data.song.importer.OsuManiaData;
|
||||
import funkin.data.song.importer.OsuManiaImporter;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
|
|
@ -780,7 +782,7 @@ class ChartEditorDialogHandler
|
|||
var songDefaultChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songDefaultChartDataEntryLabel:Null<Label> = songDefaultChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songDefaultChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
songDefaultChartDataEntryLabel.text = 'Drag and drop <song>-chart.json file, or click to browse.';
|
||||
#else
|
||||
songDefaultChartDataEntryLabel.text = 'Click to browse for <song>-chart.json file.';
|
||||
|
|
@ -800,7 +802,7 @@ class ChartEditorDialogHandler
|
|||
var songVariationMetadataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationMetadataEntryLabel:Null<Label> = songVariationMetadataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songVariationMetadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
songVariationMetadataEntryLabel.text = 'Drag and drop <song>-metadata-${variation}.json file, or click to browse.';
|
||||
#else
|
||||
songVariationMetadataEntryLabel.text = 'Click to browse for <song>-metadata-${variation}.json file.';
|
||||
|
|
@ -815,7 +817,7 @@ class ChartEditorDialogHandler
|
|||
Cursor.cursorMode = Default;
|
||||
}
|
||||
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
state.addDropHandler(
|
||||
{
|
||||
component: songVariationMetadataEntry,
|
||||
|
|
@ -828,7 +830,7 @@ class ChartEditorDialogHandler
|
|||
var songVariationChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationChartDataEntryLabel:Null<Label> = songVariationChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songVariationChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
songVariationChartDataEntryLabel.text = 'Drag and drop <song>-chart-${variation}.json file, or click to browse.';
|
||||
#else
|
||||
songVariationChartDataEntryLabel.text = 'Click to browse for <song>-chart-${variation}.json file.';
|
||||
|
|
@ -843,7 +845,7 @@ class ChartEditorDialogHandler
|
|||
Cursor.cursorMode = Default;
|
||||
}
|
||||
songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
state.addDropHandler(
|
||||
{
|
||||
component: songVariationChartDataEntry,
|
||||
|
|
@ -883,7 +885,7 @@ class ChartEditorDialogHandler
|
|||
// Tell the user the load was successful.
|
||||
state.success('Loaded Metadata', 'Loaded metadata file (${path.file}.${path.ext})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
#else
|
||||
label.text = 'Metadata file (click to browse)\n${path.file}.${path.ext}';
|
||||
|
|
@ -919,7 +921,7 @@ class ChartEditorDialogHandler
|
|||
// Tell the user the load was successful.
|
||||
state.success('Loaded Metadata', 'Loaded metadata file (${selectedFile.name})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
label.text = 'Metadata file (click to browse)\n${selectedFile.name}';
|
||||
|
|
@ -963,7 +965,7 @@ class ChartEditorDialogHandler
|
|||
// Tell the user the load was successful.
|
||||
state.success('Loaded Chart Data', 'Loaded chart data file (${path.file}.${path.ext})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
#else
|
||||
label.text = 'Chart data file (click to browse)\n${path.file}.${path.ext}';
|
||||
|
|
@ -1006,7 +1008,7 @@ class ChartEditorDialogHandler
|
|||
// Tell the user the load was successful.
|
||||
state.success('Loaded Chart Data', 'Loaded chart data file (${selectedFile.name})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
label.text = 'Chart data file (click to browse)\n${selectedFile.name}';
|
||||
|
|
@ -1020,7 +1022,7 @@ class ChartEditorDialogHandler
|
|||
var metadataEntryLabel:Null<Label> = metadataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (metadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
#if FEATURE_FILE_DROP
|
||||
metadataEntryLabel.text = 'Drag and drop <song>-metadata.json file, or click to browse.';
|
||||
#else
|
||||
metadataEntryLabel.text = 'Click to browse for <song>-metadata.json file.';
|
||||
|
|
@ -1057,27 +1059,45 @@ class ChartEditorDialogHandler
|
|||
var prettyFormat:String = switch (format)
|
||||
{
|
||||
case 'legacy': 'FNF Legacy';
|
||||
case 'osumania': 'Osu!Mania';
|
||||
default: 'Unknown';
|
||||
}
|
||||
|
||||
var fileFilter = switch (format)
|
||||
{
|
||||
case 'legacy':
|
||||
// TODO / BUG: File filtering not working on mac finder dialog, so we don't use it for now
|
||||
#if !mac
|
||||
[
|
||||
{label: 'JSON Data File (.json)', extension: 'json'}];
|
||||
#else
|
||||
[];
|
||||
#end
|
||||
case 'osumania':
|
||||
[
|
||||
{label: 'OSU! Beatmap File (.osu)', extension: 'osu'}];
|
||||
default: null;
|
||||
}
|
||||
|
||||
var fileExt = switch (format)
|
||||
{
|
||||
case 'osumania':
|
||||
"osu";
|
||||
|
||||
default: "json";
|
||||
}
|
||||
|
||||
dialog.title = 'Import Chart - ${prettyFormat}';
|
||||
|
||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
||||
|
||||
var importExtLabel:Null<Label> = dialog.findComponent('importLabel', Label);
|
||||
|
||||
if (importExtLabel != null)
|
||||
{
|
||||
#if FEATURE_FILE_DROP
|
||||
importExtLabel.text = 'Drag and drop a chart.$fileExt file, or click to browse.';
|
||||
#else
|
||||
importExtLabel.text = 'Click to browse for a chart.$fileExt file.';
|
||||
#end
|
||||
}
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
buttonCancel.onClick = function(_) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
|
|
@ -1098,49 +1118,76 @@ class ChartEditorDialogHandler
|
|||
|
||||
var onDropFile:String->Void;
|
||||
|
||||
importBox.onClick = function(_) {
|
||||
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', fileFilter ?? [], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.fullPath);
|
||||
var selectedFileTxt:String = selectedFile.bytes.toString();
|
||||
var fnfLegacyData:Null<FNFLegacyData> = FNFLegacyImporter.parseLegacyDataRaw(selectedFileTxt, selectedFile.fullPath);
|
||||
var onFileSelected:String->String->Void = (pathStr:String, content:String) -> {
|
||||
var path:Path = new Path(pathStr ?? "");
|
||||
trace('Selected file: ' + path.toString());
|
||||
|
||||
var songMetadata:Null<SongMetadata> = null;
|
||||
var songChartData:Null<SongChartData> = null;
|
||||
|
||||
if (path.ext != fileExt)
|
||||
{
|
||||
state.error('Failure', 'Given file extension ".${path.ext}" was not the requested extension ".$fileExt"');
|
||||
return;
|
||||
}
|
||||
|
||||
var loadedText = '';
|
||||
switch (format)
|
||||
{
|
||||
case 'legacy':
|
||||
var fnfLegacyData:Null<FNFLegacyData> = FNFLegacyImporter.parseLegacyDataRaw(content, path.toString());
|
||||
|
||||
if (fnfLegacyData == null)
|
||||
{
|
||||
state.error('Failure', 'Failed to parse FNF chart file (${selectedFile.name})');
|
||||
state.error('Failure', 'Failed to parse FNF chart file (${path.file}.${path.ext})');
|
||||
return;
|
||||
}
|
||||
|
||||
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(fnfLegacyData);
|
||||
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(fnfLegacyData);
|
||||
songMetadata = FNFLegacyImporter.migrateMetadata(fnfLegacyData);
|
||||
songChartData = FNFLegacyImporter.migrateChartData(fnfLegacyData);
|
||||
|
||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
loadedText = 'Loaded chart file';
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
state.success('Success', 'Loaded chart file (${selectedFile.name})');
|
||||
case 'osumania':
|
||||
var osuManiaData:Null<OsuManiaData> = OsuManiaImporter.parseOsuFile(content);
|
||||
|
||||
if (osuManiaData == null)
|
||||
{
|
||||
state.error('Failure', 'Failed to parse Osu!Mania beatmap file (${path.file}.${path.ext})');
|
||||
return;
|
||||
}
|
||||
|
||||
songMetadata = OsuManiaImporter.migrateMetadata(osuManiaData);
|
||||
songChartData = OsuManiaImporter.migrateChartData(osuManiaData);
|
||||
|
||||
loadedText = 'Loaded beatmap file';
|
||||
}
|
||||
|
||||
if (songMetadata == null || songMetadata == null)
|
||||
{
|
||||
state.error('Failure', 'Failed to load song (${path.file}.${path.ext})');
|
||||
return;
|
||||
}
|
||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
state.success('Success', '$loadedText (${path.file}.${path.ext})');
|
||||
};
|
||||
|
||||
importBox.onClick = function(_) {
|
||||
// TODO / BUG: File filtering not working on mac finder dialog, so we don't use it for now
|
||||
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', #if !mac fileFilter ?? [] #else [] #end, function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
@:nullSafety(Off)
|
||||
onFileSelected(selectedFile.fullPath, selectedFile.bytes.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
var selectedFileText:String = FileUtil.readStringFromPath(path.toString());
|
||||
var selectedFileData:Null<FNFLegacyData> = FNFLegacyImporter.parseLegacyDataRaw(selectedFileText, path.toString());
|
||||
|
||||
if (selectedFileData == null)
|
||||
{
|
||||
state.error('Failure', 'Failed to parse FNF chart file (${path.file}.${path.ext})');
|
||||
return;
|
||||
}
|
||||
|
||||
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(selectedFileData);
|
||||
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(selectedFileData);
|
||||
|
||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
state.success('Success', 'Loaded chart file (${path.file}.${path.ext})');
|
||||
var selectedFileText:String = FileUtil.readStringFromPath(pathStr);
|
||||
onFileSelected(pathStr, selectedFileText);
|
||||
};
|
||||
|
||||
state.addDropHandler({component: importBox, handler: onDropFile});
|
||||
|
|
@ -1326,11 +1373,11 @@ class ChartEditorDialogHandler
|
|||
{
|
||||
var dialog:Null<Dialog> = Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor",
|
||||
MessageBoxType.TYPE_YESNO, true, function(button:DialogButton) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (button == DialogButton.YES)
|
||||
{
|
||||
state.quitChartEditor();
|
||||
}
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
});
|
||||
|
||||
dialog.destroyOnClose = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue