mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 10:05:41 -05:00
Merge branch 'feature/chart-editor-bpm' of https://github.com/ninjamuffin99/Funkin-secret into feature/chart-editor-bpm
This commit is contained in:
commit
da465ff46d
28 changed files with 2195 additions and 601 deletions
2
.github/actions/setup-haxeshit/action.yml
vendored
2
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -24,5 +24,5 @@ runs:
|
||||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git
|
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git
|
||||||
haxelib version
|
haxelib version
|
||||||
haxelib --global install hmm
|
haxelib --global install hmm
|
||||||
haxelib --global run hmm install
|
haxelib --global run hmm install --quiet
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
@ -181,6 +181,14 @@
|
||||||
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
||||||
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
||||||
</section>
|
</section>
|
||||||
|
<section if="debug">
|
||||||
|
<!--
|
||||||
|
Use the parent assets folder rather than the exported one
|
||||||
|
No more will we accidentally undo our changes!
|
||||||
|
TODO: Add a thing to disable this on builds meant for itch.io.
|
||||||
|
-->
|
||||||
|
<haxedef name="REDIRECT_ASSETS_FOLDER" />
|
||||||
|
</section>
|
||||||
<!-- <prebuild haxe="trace('prebuilding');"/> -->
|
<!-- <prebuild haxe="trace('prebuilding');"/> -->
|
||||||
<!-- <postbuild haxe="art/Postbuild.hx"/> -->
|
<!-- <postbuild haxe="art/Postbuild.hx"/> -->
|
||||||
<!-- <config:ios allow-provisioning-updates="true" team-id="" /> -->
|
<!-- <config:ios allow-provisioning-updates="true" team-id="" /> -->
|
||||||
|
|
35
hmm.json
35
hmm.json
|
@ -4,21 +4,21 @@
|
||||||
"name": "discord_rpc",
|
"name": "discord_rpc",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "2d83fa8",
|
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel",
|
"name": "flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "32cee07",
|
"ref": "32cee07a0e5f21e590a4b21234603b2cd5898b10",
|
||||||
"url": "https://github.com/EliteMasterEric/flixel"
|
"url": "https://github.com/EliteMasterEric/flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-addons",
|
"name": "flixel-addons",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "f107166",
|
"ref": "f107166de3e830648e8fbf3da5526d4b94aa7dfc",
|
||||||
"url": "https://github.com/EliteMasterEric/flixel-addons"
|
"url": "https://github.com/EliteMasterEric/flixel-addons"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
"name": "flxanimate",
|
"name": "flxanimate",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "a913635",
|
"ref": "a9136359271cae6ea3016b7fd9023c5c42562933",
|
||||||
"url": "https://github.com/ninjamuffin99/flxanimate"
|
"url": "https://github.com/ninjamuffin99/flxanimate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42,23 +42,16 @@
|
||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "4b927f5",
|
"ref": "3590c94858fc6dbcf9b4d522cd644ad571269677",
|
||||||
"url": "https://github.com/haxeui/haxeui-core/"
|
"url": "https://github.com/haxeui/haxeui-core/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "haxeui-flixel",
|
"name": "haxeui-flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "999fadd",
|
"ref": "999faddf862d8a1584ae3794d932c55e94fc65cc",
|
||||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "hmm",
|
|
||||||
"type": "git",
|
|
||||||
"dir": null,
|
|
||||||
"ref": "3ef9522",
|
|
||||||
"url": "https://github.com/steviegt6/hmm"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "hscript",
|
"name": "hscript",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
|
@ -66,8 +59,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxCodec",
|
"name": "hxCodec",
|
||||||
"type": "haxelib",
|
"type": "git",
|
||||||
"version": "3.0.1"
|
"dir": null,
|
||||||
|
"ref": "c8c47e706ad82a423783006ed901b6d93c89a421",
|
||||||
|
"url": "https://github.com/polybiusproxy/hxCodec"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp",
|
"name": "hxcpp",
|
||||||
|
@ -93,21 +88,21 @@
|
||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "2447ae6",
|
"ref": "acb0334c59bd4618f3c0277584d524ed0b288b5f",
|
||||||
"url": "https://github.com/elitemastereric/lime"
|
"url": "https://github.com/EliteMasterEric/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "d33d489",
|
"ref": "d33d489a137ff8fdece4994cf1302f0b6334ed08",
|
||||||
"url": "https://github.com/EliteMasterEric/openfl"
|
"url": "https://github.com/EliteMasterEric/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "6594dd8",
|
"ref": "631a3637f30997e47cd37bbab3cb6a75636a4b2a",
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -118,7 +113,7 @@
|
||||||
{
|
{
|
||||||
"name": "tink_json",
|
"name": "tink_json",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": null
|
"version": "0.11.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -901,6 +901,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:haxe.warning("-WDeprecated")
|
||||||
override function switchTo(nextState:FlxState):Bool
|
override function switchTo(nextState:FlxState):Bool
|
||||||
{
|
{
|
||||||
clearDaCache(songs[curSelected].songName);
|
clearDaCache(songs[curSelected].songName);
|
||||||
|
|
|
@ -29,7 +29,9 @@ class PolymodHandler
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
*/
|
*/
|
||||||
static final MOD_FOLDER = "mods";
|
static final MOD_FOLDER:String = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../example_mods" #elseif REDIRECT_ASSETS_FOLDER "../../../../example_mods" #else "mods" #end;
|
||||||
|
|
||||||
|
static final CORE_FOLDER:Null<String> = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../assets" #elseif REDIRECT_ASSETS_FOLDER "../../../../assets" #else null #end;
|
||||||
|
|
||||||
public static function createModRoot()
|
public static function createModRoot()
|
||||||
{
|
{
|
||||||
|
@ -202,9 +204,10 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
assetLibraryPaths: [
|
assetLibraryPaths: [
|
||||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
"default" => "preload", "shared" => "", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2", "week3" => "week3",
|
||||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
"week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||||
]
|
],
|
||||||
|
coreAssetRedirect: CORE_FOLDER,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -639,6 +639,12 @@ class PlayState extends MusicBeatState
|
||||||
|
|
||||||
currentStage.resetStage();
|
currentStage.resetStage();
|
||||||
|
|
||||||
|
playerStrumline.vwooshNotes();
|
||||||
|
opponentStrumline.vwooshNotes();
|
||||||
|
|
||||||
|
playerStrumline.clean();
|
||||||
|
opponentStrumline.clean();
|
||||||
|
|
||||||
// Delete all notes and reset the arrays.
|
// Delete all notes and reset the arrays.
|
||||||
regenNoteData();
|
regenNoteData();
|
||||||
|
|
||||||
|
@ -966,6 +972,7 @@ class PlayState extends MusicBeatState
|
||||||
* This function is called whenever Flixel switches switching to a new FlxState.
|
* This function is called whenever Flixel switches switching to a new FlxState.
|
||||||
* @return Whether to actually switch to the new state.
|
* @return Whether to actually switch to the new state.
|
||||||
*/
|
*/
|
||||||
|
@:haxe.warning("-WDeprecated")
|
||||||
override function switchTo(nextState:FlxState):Bool
|
override function switchTo(nextState:FlxState):Bool
|
||||||
{
|
{
|
||||||
var result:Bool = super.switchTo(nextState);
|
var result:Bool = super.switchTo(nextState);
|
||||||
|
|
|
@ -58,6 +58,7 @@ class BaseCharacter extends Bopper
|
||||||
*/
|
*/
|
||||||
public var dropNoteCounts(default, null):Array<Int>;
|
public var dropNoteCounts(default, null):Array<Int>;
|
||||||
|
|
||||||
|
@:allow(funkin.ui.animDebugShit.DebugBoundingState)
|
||||||
final _data:CharacterData;
|
final _data:CharacterData;
|
||||||
final singTimeSec:Float;
|
final singTimeSec:Float;
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,9 @@ class Strumline extends FlxSpriteGroup
|
||||||
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
||||||
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
||||||
|
|
||||||
|
var notesVwoosh:FlxTypedSpriteGroup<NoteSprite>;
|
||||||
|
var holdNotesVwoosh:FlxTypedSpriteGroup<SustainTrail>;
|
||||||
|
|
||||||
final noteStyle:NoteStyle;
|
final noteStyle:NoteStyle;
|
||||||
|
|
||||||
var noteData:Array<SongNoteData> = [];
|
var noteData:Array<SongNoteData> = [];
|
||||||
|
@ -76,10 +79,18 @@ class Strumline extends FlxSpriteGroup
|
||||||
this.holdNotes.zIndex = 20;
|
this.holdNotes.zIndex = 20;
|
||||||
this.add(this.holdNotes);
|
this.add(this.holdNotes);
|
||||||
|
|
||||||
|
this.holdNotesVwoosh = new FlxTypedSpriteGroup<SustainTrail>();
|
||||||
|
this.holdNotesVwoosh.zIndex = 21;
|
||||||
|
this.add(this.holdNotesVwoosh);
|
||||||
|
|
||||||
this.notes = new FlxTypedSpriteGroup<NoteSprite>();
|
this.notes = new FlxTypedSpriteGroup<NoteSprite>();
|
||||||
this.notes.zIndex = 30;
|
this.notes.zIndex = 30;
|
||||||
this.add(this.notes);
|
this.add(this.notes);
|
||||||
|
|
||||||
|
this.notesVwoosh = new FlxTypedSpriteGroup<NoteSprite>();
|
||||||
|
this.notesVwoosh.zIndex = 31;
|
||||||
|
this.add(this.notesVwoosh);
|
||||||
|
|
||||||
this.noteHoldCovers = new FlxTypedSpriteGroup<NoteHoldCover>(0, 0, 4);
|
this.noteHoldCovers = new FlxTypedSpriteGroup<NoteHoldCover>(0, 0, 4);
|
||||||
this.noteHoldCovers.zIndex = 40;
|
this.noteHoldCovers.zIndex = 40;
|
||||||
this.add(this.noteHoldCovers);
|
this.add(this.noteHoldCovers);
|
||||||
|
@ -201,6 +212,54 @@ class Strumline extends FlxSpriteGroup
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this when resetting the playstate.
|
||||||
|
*/
|
||||||
|
public function vwooshNotes():Void
|
||||||
|
{
|
||||||
|
for (note in notes.members)
|
||||||
|
{
|
||||||
|
if (note == null) continue;
|
||||||
|
if (!note.alive) continue;
|
||||||
|
|
||||||
|
notes.remove(note);
|
||||||
|
notesVwoosh.add(note);
|
||||||
|
|
||||||
|
var targetY:Float = FlxG.height + note.y;
|
||||||
|
if (PreferencesMenu.getPref('downscroll')) targetY = 0 - note.height;
|
||||||
|
FlxTween.tween(note, {y: targetY}, 0.5,
|
||||||
|
{
|
||||||
|
ease: FlxEase.expoIn,
|
||||||
|
onComplete: function(twn) {
|
||||||
|
note.kill();
|
||||||
|
notesVwoosh.remove(note, true);
|
||||||
|
note.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (holdNote in holdNotes.members)
|
||||||
|
{
|
||||||
|
if (holdNote == null) continue;
|
||||||
|
if (!holdNote.alive) continue;
|
||||||
|
|
||||||
|
holdNotes.remove(holdNote);
|
||||||
|
holdNotesVwoosh.add(holdNote);
|
||||||
|
|
||||||
|
var targetY:Float = FlxG.height + holdNote.y;
|
||||||
|
if (PreferencesMenu.getPref('downscroll')) targetY = 0 - holdNote.height;
|
||||||
|
FlxTween.tween(holdNote, {y: targetY}, 0.5,
|
||||||
|
{
|
||||||
|
ease: FlxEase.expoIn,
|
||||||
|
onComplete: function(twn) {
|
||||||
|
holdNote.kill();
|
||||||
|
holdNotesVwoosh.remove(holdNote, true);
|
||||||
|
holdNote.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a note's strumTime, calculate its Y position relative to the strumline.
|
* For a note's strumTime, calculate its Y position relative to the strumline.
|
||||||
* NOTE: Assumes Conductor and PlayState are both initialized.
|
* NOTE: Assumes Conductor and PlayState are both initialized.
|
||||||
|
@ -396,6 +455,38 @@ class Strumline extends FlxSpriteGroup
|
||||||
return heldKeys[dir];
|
return heldKeys[dir];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song is reset.
|
||||||
|
* Removes any special animations and the like.
|
||||||
|
* Doesn't reset the notes from the chart, that's handled by the PlayState.
|
||||||
|
*/
|
||||||
|
public function clean():Void
|
||||||
|
{
|
||||||
|
for (note in notes.members)
|
||||||
|
{
|
||||||
|
if (note == null) continue;
|
||||||
|
killNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (holdNote in holdNotes.members)
|
||||||
|
{
|
||||||
|
if (holdNote == null) continue;
|
||||||
|
holdNote.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (splash in noteSplashes)
|
||||||
|
{
|
||||||
|
if (splash == null) continue;
|
||||||
|
splash.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cover in noteHoldCovers)
|
||||||
|
{
|
||||||
|
if (cover == null) continue;
|
||||||
|
cover.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||||
{
|
{
|
||||||
this.notes.clear();
|
this.notes.clear();
|
||||||
|
@ -423,6 +514,7 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
public function killNote(note:NoteSprite):Void
|
public function killNote(note:NoteSprite):Void
|
||||||
{
|
{
|
||||||
|
if (note == null) return;
|
||||||
note.visible = false;
|
note.visible = false;
|
||||||
notes.remove(note, false);
|
notes.remove(note, false);
|
||||||
note.kill();
|
note.kill();
|
||||||
|
|
|
@ -289,6 +289,20 @@ class SustainTrail extends FlxSprite
|
||||||
missedNote = false;
|
missedNote = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override function revive():Void
|
||||||
|
{
|
||||||
|
super.revive();
|
||||||
|
|
||||||
|
strumTime = 0;
|
||||||
|
noteDirection = 0;
|
||||||
|
sustainLength = 0;
|
||||||
|
fullSustainLength = 0;
|
||||||
|
noteData = null;
|
||||||
|
|
||||||
|
hitNote = false;
|
||||||
|
missedNote = false;
|
||||||
|
}
|
||||||
|
|
||||||
override public function destroy():Void
|
override public function destroy():Void
|
||||||
{
|
{
|
||||||
vertices = null;
|
vertices = null;
|
||||||
|
|
|
@ -47,7 +47,7 @@ class Song implements IPlayStateScriptedClass
|
||||||
difficultyIds = [];
|
difficultyIds = [];
|
||||||
difficulties = new Map<String, SongDifficulty>();
|
difficulties = new Map<String, SongDifficulty>();
|
||||||
|
|
||||||
_metadata = SongDataParser.parseSongMetadata(songId);
|
_metadata = SongDataParser.loadSongMetadata(songId);
|
||||||
if (_metadata == null || _metadata.length == 0)
|
if (_metadata == null || _metadata.length == 0)
|
||||||
{
|
{
|
||||||
throw 'Could not find song data for songId: $songId';
|
throw 'Could not find song data for songId: $songId';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package funkin.play.song;
|
package funkin.play.song;
|
||||||
|
|
||||||
import funkin.modding.events.ScriptEvent;
|
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
import flixel.util.typeLimit.OneOfTwo;
|
import flixel.util.typeLimit.OneOfTwo;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
@ -120,12 +120,21 @@ class SongDataParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the song IDs available to the game.
|
||||||
|
* @return The list of song IDs.
|
||||||
|
*/
|
||||||
public static function listSongIds():Array<String>
|
public static function listSongIds():Array<String>
|
||||||
{
|
{
|
||||||
return songCache.keys().array();
|
return songCache.keys().array();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function parseSongMetadata(songId:String):Array<SongMetadata>
|
/**
|
||||||
|
* Loads the song metadata for a particular song.
|
||||||
|
* @param songId The ID of the song to load.
|
||||||
|
* @return The song metadata for each variation, or an empty array if the song was not found.
|
||||||
|
*/
|
||||||
|
public static function loadSongMetadata(songId:String):Array<SongMetadata>
|
||||||
{
|
{
|
||||||
var result:Array<SongMetadata> = [];
|
var result:Array<SongMetadata> = [];
|
||||||
|
|
||||||
|
@ -147,19 +156,13 @@ class SongDataParser
|
||||||
|
|
||||||
result.push(songMetadata);
|
result.push(songMetadata);
|
||||||
|
|
||||||
var variations = songMetadata.playData.songVariations;
|
var variations:Array<String> = songMetadata.playData.songVariations;
|
||||||
|
|
||||||
for (variation in variations)
|
for (variation in variations)
|
||||||
{
|
{
|
||||||
var variationJsonStr:String = loadSongMetadataFile(songId, variation);
|
var variationRawJson:String = loadSongMetadataFile(songId, variation);
|
||||||
var variationJsonData:Dynamic = null;
|
var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationRawJson, '${songId}_${variation}');
|
||||||
try
|
variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}_${variation}');
|
||||||
{
|
|
||||||
variationJsonData = Json.parse(variationJsonStr);
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationJsonData, '${songId}-${variation}');
|
|
||||||
variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}-${variation}');
|
|
||||||
if (variationSongMetadata != null)
|
if (variationSongMetadata != null)
|
||||||
{
|
{
|
||||||
variationSongMetadata.variation = variation;
|
variationSongMetadata.variation = variation;
|
||||||
|
@ -176,7 +179,7 @@ class SongDataParser
|
||||||
|
|
||||||
var rawJson:String = Assets.getText(songMetadataFilePath).trim();
|
var rawJson:String = Assets.getText(songMetadataFilePath).trim();
|
||||||
|
|
||||||
while (!rawJson.endsWith("}"))
|
while (!rawJson.endsWith('}') && rawJson.length > 0)
|
||||||
{
|
{
|
||||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
}
|
}
|
||||||
|
@ -214,7 +217,7 @@ class SongDataParser
|
||||||
return rawJson;
|
return rawJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function parseSongChartData(songId:String, variation:String = ""):SongChartData
|
public static function parseSongChartData(songId:String, variation:String = ''):SongChartData
|
||||||
{
|
{
|
||||||
var rawJson:String = loadSongChartDataFile(songId, variation);
|
var rawJson:String = loadSongChartDataFile(songId, variation);
|
||||||
var jsonData:Dynamic = null;
|
var jsonData:Dynamic = null;
|
||||||
|
@ -222,7 +225,11 @@ class SongDataParser
|
||||||
{
|
{
|
||||||
jsonData = Json.parse(rawJson);
|
jsonData = Json.parse(rawJson);
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e)
|
||||||
|
{
|
||||||
|
trace('Failed to parse song chart data: ${songId} (${variation})');
|
||||||
|
trace(e);
|
||||||
|
}
|
||||||
|
|
||||||
var songChartData:SongChartData = SongMigrator.migrateSongChartData(jsonData, songId);
|
var songChartData:SongChartData = SongMigrator.migrateSongChartData(jsonData, songId);
|
||||||
songChartData = SongValidator.validateSongChartData(songChartData, songId);
|
songChartData = SongValidator.validateSongChartData(songChartData, songId);
|
||||||
|
@ -242,7 +249,7 @@ class SongDataParser
|
||||||
|
|
||||||
var rawJson:String = Assets.getText(songChartDataFilePath).trim();
|
var rawJson:String = Assets.getText(songChartDataFilePath).trim();
|
||||||
|
|
||||||
while (!rawJson.endsWith("}"))
|
while (!rawJson.endsWith('}') && rawJson.length > 0)
|
||||||
{
|
{
|
||||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
}
|
}
|
||||||
|
@ -310,7 +317,7 @@ abstract SongMetadata(RawSongMetadata)
|
||||||
|
|
||||||
public function clone(?newVariation:String = null):SongMetadata
|
public function clone(?newVariation:String = null):SongMetadata
|
||||||
{
|
{
|
||||||
var result = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
|
var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
|
||||||
result.version = this.version;
|
result.version = this.version;
|
||||||
result.timeFormat = this.timeFormat;
|
result.timeFormat = this.timeFormat;
|
||||||
result.divisions = this.divisions;
|
result.divisions = this.divisions;
|
||||||
|
@ -391,12 +398,12 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
*/
|
*/
|
||||||
public var time(get, set):Float;
|
public var time(get, set):Float;
|
||||||
|
|
||||||
public function get_time():Float
|
function get_time():Float
|
||||||
{
|
{
|
||||||
return this.t;
|
return this.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_time(value:Float):Float
|
function set_time(value:Float):Float
|
||||||
{
|
{
|
||||||
return this.t = value;
|
return this.t = value;
|
||||||
}
|
}
|
||||||
|
@ -406,7 +413,7 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
*/
|
*/
|
||||||
public var stepTime(get, never):Float;
|
public var stepTime(get, never):Float;
|
||||||
|
|
||||||
public function get_stepTime():Float
|
function get_stepTime():Float
|
||||||
{
|
{
|
||||||
return Conductor.getTimeInSteps(abstract.time);
|
return Conductor.getTimeInSteps(abstract.time);
|
||||||
}
|
}
|
||||||
|
@ -416,12 +423,12 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
*/
|
*/
|
||||||
public var data(get, set):Int;
|
public var data(get, set):Int;
|
||||||
|
|
||||||
public function get_data():Int
|
function get_data():Int
|
||||||
{
|
{
|
||||||
return this.d;
|
return this.d;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_data(value:Int):Int
|
function set_data(value:Int):Int
|
||||||
{
|
{
|
||||||
return this.d = value;
|
return this.d = value;
|
||||||
}
|
}
|
||||||
|
@ -524,7 +531,7 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
return this.k;
|
return this.k;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_kind(value:String):String
|
function set_kind(value:String):String
|
||||||
{
|
{
|
||||||
if (value == 'normal' || value == '') value = null;
|
if (value == 'normal' || value == '') value = null;
|
||||||
return this.k = value;
|
return this.k = value;
|
||||||
|
@ -628,55 +635,55 @@ abstract SongEventData(RawSongEventData)
|
||||||
|
|
||||||
public var time(get, set):Float;
|
public var time(get, set):Float;
|
||||||
|
|
||||||
public function get_time():Float
|
function get_time():Float
|
||||||
{
|
{
|
||||||
return this.t;
|
return this.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_time(value:Float):Float
|
function set_time(value:Float):Float
|
||||||
{
|
{
|
||||||
return this.t = value;
|
return this.t = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var stepTime(get, never):Float;
|
public var stepTime(get, never):Float;
|
||||||
|
|
||||||
public function get_stepTime():Float
|
function get_stepTime():Float
|
||||||
{
|
{
|
||||||
return Conductor.getTimeInSteps(abstract.time);
|
return Conductor.getTimeInSteps(abstract.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public var event(get, set):String;
|
public var event(get, set):String;
|
||||||
|
|
||||||
public function get_event():String
|
function get_event():String
|
||||||
{
|
{
|
||||||
return this.e;
|
return this.e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_event(value:String):String
|
function set_event(value:String):String
|
||||||
{
|
{
|
||||||
return this.e = value;
|
return this.e = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var value(get, set):Dynamic;
|
public var value(get, set):Dynamic;
|
||||||
|
|
||||||
public function get_value():Dynamic
|
function get_value():Dynamic
|
||||||
{
|
{
|
||||||
return this.v;
|
return this.v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_value(value:Dynamic):Dynamic
|
function set_value(value:Dynamic):Dynamic
|
||||||
{
|
{
|
||||||
return this.v = value;
|
return this.v = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var activated(get, set):Bool;
|
public var activated(get, set):Bool;
|
||||||
|
|
||||||
public function get_activated():Bool
|
function get_activated():Bool
|
||||||
{
|
{
|
||||||
return this.a;
|
return this.a;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_activated(value:Bool):Bool
|
function set_activated(value:Bool):Bool
|
||||||
{
|
{
|
||||||
return this.a = value;
|
return this.a = value;
|
||||||
}
|
}
|
||||||
|
@ -755,7 +762,7 @@ abstract SongEventData(RawSongEventData)
|
||||||
|
|
||||||
abstract SongPlayableChar(RawSongPlayableChar)
|
abstract SongPlayableChar(RawSongPlayableChar)
|
||||||
{
|
{
|
||||||
public function new(girlfriend:String, opponent:String, inst:String = "")
|
public function new(girlfriend:String, opponent:String, inst:String = '')
|
||||||
{
|
{
|
||||||
this =
|
this =
|
||||||
{
|
{
|
||||||
|
@ -767,36 +774,36 @@ abstract SongPlayableChar(RawSongPlayableChar)
|
||||||
|
|
||||||
public var girlfriend(get, set):String;
|
public var girlfriend(get, set):String;
|
||||||
|
|
||||||
public function get_girlfriend():String
|
function get_girlfriend():String
|
||||||
{
|
{
|
||||||
return this.g;
|
return this.g;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_girlfriend(value:String):String
|
function set_girlfriend(value:String):String
|
||||||
{
|
{
|
||||||
return this.g = value;
|
return this.g = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var opponent(get, set):String;
|
public var opponent(get, set):String;
|
||||||
|
|
||||||
public function get_opponent():String
|
function get_opponent():String
|
||||||
{
|
{
|
||||||
return this.o;
|
return this.o;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_opponent(value:String):String
|
function set_opponent(value:String):String
|
||||||
{
|
{
|
||||||
return this.o = value;
|
return this.o = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var inst(get, set):String;
|
public var inst(get, set):String;
|
||||||
|
|
||||||
public function get_inst():String
|
function get_inst():String
|
||||||
{
|
{
|
||||||
return this.i;
|
return this.i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_inst(value:String):String
|
function set_inst(value:String):String
|
||||||
{
|
{
|
||||||
return this.i = value;
|
return this.i = value;
|
||||||
}
|
}
|
||||||
|
@ -842,6 +849,35 @@ abstract SongChartData(RawSongChartData)
|
||||||
|
|
||||||
return (result == 0.0) ? 1.0 : result;
|
return (result == 0.0) ? 1.0 : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setScrollSpeed(value:Float, diff:String = 'default'):Float
|
||||||
|
{
|
||||||
|
return this.scrollSpeed.set(diff, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNotes(diff:String):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
var result:Array<SongNoteData> = this.notes.get(diff);
|
||||||
|
|
||||||
|
if (result == null && diff != 'normal') return getNotes('normal');
|
||||||
|
|
||||||
|
return (result == null) ? [] : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNotes(value:Array<SongNoteData>, diff:String):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return this.notes.set(diff, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvents():Array<SongEventData>
|
||||||
|
{
|
||||||
|
return this.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEvents(value:Array<SongEventData>):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return this.events = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef RawSongTimeChange =
|
typedef RawSongTimeChange =
|
||||||
|
@ -902,12 +938,12 @@ abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange
|
||||||
|
|
||||||
public var timeStamp(get, set):Float;
|
public var timeStamp(get, set):Float;
|
||||||
|
|
||||||
public function get_timeStamp():Float
|
function get_timeStamp():Float
|
||||||
{
|
{
|
||||||
return this.t;
|
return this.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeStamp(value:Float):Float
|
function set_timeStamp(value:Float):Float
|
||||||
{
|
{
|
||||||
return this.t = value;
|
return this.t = value;
|
||||||
}
|
}
|
||||||
|
@ -926,43 +962,43 @@ abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange
|
||||||
|
|
||||||
public var bpm(get, set):Float;
|
public var bpm(get, set):Float;
|
||||||
|
|
||||||
public function get_bpm():Float
|
function get_bpm():Float
|
||||||
{
|
{
|
||||||
return this.bpm;
|
return this.bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_bpm(value:Float):Float
|
function set_bpm(value:Float):Float
|
||||||
{
|
{
|
||||||
return this.bpm = value;
|
return this.bpm = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var timeSignatureNum(get, set):Int;
|
public var timeSignatureNum(get, set):Int;
|
||||||
|
|
||||||
public function get_timeSignatureNum():Int
|
function get_timeSignatureNum():Int
|
||||||
{
|
{
|
||||||
return this.n;
|
return this.n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeSignatureNum(value:Int):Int
|
function set_timeSignatureNum(value:Int):Int
|
||||||
{
|
{
|
||||||
return this.n = value;
|
return this.n = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var timeSignatureDen(get, set):Int;
|
public var timeSignatureDen(get, set):Int;
|
||||||
|
|
||||||
public function get_timeSignatureDen():Int
|
function get_timeSignatureDen():Int
|
||||||
{
|
{
|
||||||
return this.d;
|
return this.d;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeSignatureDen(value:Int):Int
|
function set_timeSignatureDen(value:Int):Int
|
||||||
{
|
{
|
||||||
return this.d = value;
|
return this.d = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var beatTuplets(get, set):Array<Int>;
|
public var beatTuplets(get, set):Array<Int>;
|
||||||
|
|
||||||
public function get_beatTuplets():Array<Int>
|
function get_beatTuplets():Array<Int>
|
||||||
{
|
{
|
||||||
if (Std.isOfType(this.bt, Int))
|
if (Std.isOfType(this.bt, Int))
|
||||||
{
|
{
|
||||||
|
@ -974,7 +1010,7 @@ abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_beatTuplets(value:Array<Int>):Array<Int>
|
function set_beatTuplets(value:Array<Int>):Array<Int>
|
||||||
{
|
{
|
||||||
return this.bt = value;
|
return this.bt = value;
|
||||||
}
|
}
|
||||||
|
@ -982,7 +1018,7 @@ abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange
|
||||||
|
|
||||||
enum abstract SongTimeFormat(String) from String to String
|
enum abstract SongTimeFormat(String) from String to String
|
||||||
{
|
{
|
||||||
var TICKS = "ticks";
|
var TICKS = 'ticks';
|
||||||
var FLOAT = "float";
|
var FLOAT = 'float';
|
||||||
var MILLISECONDS = "ms";
|
var MILLISECONDS = 'ms';
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,9 +101,11 @@ class SongDataUtils
|
||||||
*
|
*
|
||||||
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||||
*/
|
*/
|
||||||
public static function buildNoteClipboard(notes:Array<SongNoteData>):Array<SongNoteData>
|
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int = null):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
|
if (notes.length == 0) return notes;
|
||||||
|
if (timeOffset == null) timeOffset = -Std.int(notes[0].time);
|
||||||
|
return offsetSongNoteData(sortNotes(notes), timeOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,9 +113,11 @@ class SongDataUtils
|
||||||
*
|
*
|
||||||
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
||||||
*/
|
*/
|
||||||
public static function buildEventClipboard(events:Array<SongEventData>):Array<SongEventData>
|
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int = null):Array<SongEventData>
|
||||||
{
|
{
|
||||||
return offsetSongEventData(sortEvents(events), -Std.int(events[0].time));
|
if (events.length == 0) return events;
|
||||||
|
if (timeOffset == null) timeOffset = -Std.int(events[0].time);
|
||||||
|
return offsetSongEventData(sortEvents(events), timeOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package funkin.play.song;
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.play.song.formats.FNFLegacy;
|
||||||
import funkin.play.song.SongData.SongChartData;
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
import funkin.play.song.SongData.SongMetadata;
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import funkin.play.song.SongData.SongPlayableChar;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
|
|
||||||
class SongMigrator
|
class SongMigrator
|
||||||
|
@ -11,13 +15,22 @@ class SongMigrator
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the SongMigrator class.
|
* and adding migration to the SongMigrator class.
|
||||||
*/
|
*/
|
||||||
public static final CHART_VERSION:String = "2.0.0";
|
public static final CHART_VERSION:String = '2.0.0';
|
||||||
|
|
||||||
public static final CHART_VERSION_RULE:String = "2.0.x";
|
/**
|
||||||
|
* Version rule for which chart versions are compatible with the current version.
|
||||||
|
*/
|
||||||
|
public static final CHART_VERSION_RULE:String = '2.0.x';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate song data from an older chart version to the current version.
|
||||||
|
* @param jsonData The song metadata to migrate.
|
||||||
|
* @param songId The ID of the song (only used for error reporting).
|
||||||
|
* @return The migrated song metadata, or null if the migration failed.
|
||||||
|
*/
|
||||||
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
||||||
{
|
{
|
||||||
if (jsonData.version)
|
if (jsonData.version != null)
|
||||||
{
|
{
|
||||||
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||||
{
|
{
|
||||||
|
@ -32,10 +45,11 @@ class SongMigrator
|
||||||
trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
||||||
switch (jsonData.version)
|
switch (jsonData.version)
|
||||||
{
|
{
|
||||||
// TODO: Add migration functions as cases here.
|
case '1.0.0':
|
||||||
|
return migrateSongMetadataFromLegacy(jsonData);
|
||||||
default:
|
default:
|
||||||
// Unknown version.
|
trace('Song (${songId}) has unknown metadata version (${jsonData.version}), assuming FNF Legacy.');
|
||||||
trace('Song (${songId}) unknown metadata version: ${jsonData.version}');
|
return migrateSongMetadataFromLegacy(jsonData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +60,12 @@ class SongMigrator
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate song chart data from an older chart version to the current version.
|
||||||
|
* @param jsonData The song chart data to migrate.
|
||||||
|
* @param songId The ID of the song (only used for error reporting).
|
||||||
|
* @return The migrated song chart data, or null if the migration failed.
|
||||||
|
*/
|
||||||
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
||||||
{
|
{
|
||||||
if (jsonData.version)
|
if (jsonData.version)
|
||||||
|
@ -76,4 +96,161 @@ class SongMigrator
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate song metadata from FNF Legacy chart version to the current version.
|
||||||
|
* @param jsonData The song metadata to migrate.
|
||||||
|
* @param songId The ID of the song (only used for error reporting).
|
||||||
|
* @return The migrated song metadata, or null if the migration failed.
|
||||||
|
*/
|
||||||
|
public static function migrateSongMetadataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongMetadata
|
||||||
|
{
|
||||||
|
trace('Migrating song metadata from FNF Legacy.');
|
||||||
|
|
||||||
|
var songData:FNFLegacy = cast jsonData;
|
||||||
|
|
||||||
|
var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default');
|
||||||
|
|
||||||
|
var hadError:Bool = false;
|
||||||
|
|
||||||
|
// Set generatedBy string for debugging.
|
||||||
|
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Set the song's BPM.
|
||||||
|
songMetadata.timeChanges[0].bpm = songData.song.bpm;
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace("Couldn't parse BPM!");
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Set the song's stage.
|
||||||
|
songMetadata.playData.stage = songData.song.stageDefault;
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace("Couldn't parse stage!");
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Set's the song's name.
|
||||||
|
songMetadata.songName = songData.song.song;
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace("Couldn't parse song name!");
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
songMetadata.playData.difficulties = [];
|
||||||
|
if (songData.song != null && songData.song.notes != null)
|
||||||
|
{
|
||||||
|
if (Std.isOfType(songData.song.notes, Array))
|
||||||
|
{
|
||||||
|
// One difficulty of notes.
|
||||||
|
songMetadata.playData.difficulties.push(difficulty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Multiple difficulties of notes.
|
||||||
|
var songNoteDataDynamic:haxe.DynamicAccess<Dynamic> = cast songData.song.notes;
|
||||||
|
for (difficultyKey in songNoteDataDynamic.keys())
|
||||||
|
{
|
||||||
|
songMetadata.playData.difficulties.push(difficultyKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace("Couldn't parse difficulties!");
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
songMetadata.playData.songVariations = [];
|
||||||
|
|
||||||
|
// Set the song's song variations.
|
||||||
|
songMetadata.playData.playableChars = {};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Reflect.setField(songMetadata.playData.playableChars, songData.song.player1, new SongPlayableChar('', songData.song.player2));
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace("Couldn't parse characters!");
|
||||||
|
hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return songMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate song chart data from FNF Legacy chart version to the current version.
|
||||||
|
* @param jsonData The song data to migrate.
|
||||||
|
* @param songId The ID of the song (only used for error reporting).
|
||||||
|
* @param difficulty The difficulty to migrate.
|
||||||
|
* @return The migrated song chart data, or null if the migration failed.
|
||||||
|
*/
|
||||||
|
public static function migrateSongChartDataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongChartData
|
||||||
|
{
|
||||||
|
trace('Migrating song chart data from FNF Legacy.');
|
||||||
|
|
||||||
|
var songData:FNFLegacy = cast jsonData;
|
||||||
|
|
||||||
|
var songChartData:SongChartData = new SongChartData(1.0, [], []);
|
||||||
|
|
||||||
|
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
|
||||||
|
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes));
|
||||||
|
songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes), difficulty);
|
||||||
|
songChartData.setScrollSpeed(songData.song.speed, difficulty);
|
||||||
|
|
||||||
|
return songChartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function migrateSongNoteDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
var songNotes:Array<SongNoteData> = [];
|
||||||
|
|
||||||
|
for (section in sections)
|
||||||
|
{
|
||||||
|
// Skip empty sections.
|
||||||
|
if (section.sectionNotes.length == 0) continue;
|
||||||
|
|
||||||
|
for (note in section.sectionNotes)
|
||||||
|
{
|
||||||
|
songNotes.push(new SongNoteData(note.time, note.getData(section.mustHitSection), note.length, note.kind));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return songNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function migrateSongEventDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongEventData>
|
||||||
|
{
|
||||||
|
var songEvents:Array<SongEventData> = [];
|
||||||
|
|
||||||
|
var lastSectionWasMustHit:Null<Bool> = null;
|
||||||
|
for (section in sections)
|
||||||
|
{
|
||||||
|
// Skip empty sections.
|
||||||
|
if (section.sectionNotes.length == 0) continue;
|
||||||
|
|
||||||
|
if (section.mustHitSection != lastSectionWasMustHit)
|
||||||
|
{
|
||||||
|
lastSectionWasMustHit = section.mustHitSection;
|
||||||
|
|
||||||
|
var firstNote:LegacyNote = section.sectionNotes[0];
|
||||||
|
|
||||||
|
songEvents.push(new SongEventData(firstNote.time, 'FocusCamera', {char: section.mustHitSection ? 0 : 1}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return songEvents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,9 +82,9 @@ class SongSerializer
|
||||||
* Save a SongChartData object as a JSON file to an automatically generated path.
|
* Save a SongChartData object as a JSON file to an automatically generated path.
|
||||||
* Works great on HTML5 and desktop.
|
* Works great on HTML5 and desktop.
|
||||||
*/
|
*/
|
||||||
public static function exportSongChartData(data:SongChartData)
|
public static function exportSongChartData(data:SongChartData, songId:String)
|
||||||
{
|
{
|
||||||
var path = 'chart.json';
|
var path = '${songId}-chart.json';
|
||||||
exportSongChartDataAs(path, data);
|
exportSongChartDataAs(path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +92,9 @@ class SongSerializer
|
||||||
* Save a SongMetadata object as a JSON file to an automatically generated path.
|
* Save a SongMetadata object as a JSON file to an automatically generated path.
|
||||||
* Works great on HTML5 and desktop.
|
* Works great on HTML5 and desktop.
|
||||||
*/
|
*/
|
||||||
public static function exportSongMetadata(data:SongMetadata)
|
public static function exportSongMetadata(data:SongMetadata, songId:String)
|
||||||
{
|
{
|
||||||
var path = 'metadata.json';
|
var path = '${songId}-metadata.json';
|
||||||
exportSongMetadataAs(path, data);
|
exportSongMetadataAs(path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
131
source/funkin/play/song/formats/FNFLegacy.hx
Normal file
131
source/funkin/play/song/formats/FNFLegacy.hx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package funkin.play.song.formats;
|
||||||
|
|
||||||
|
typedef FNFLegacy =
|
||||||
|
{
|
||||||
|
var song:LegacySongData;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef LegacySongData =
|
||||||
|
{
|
||||||
|
var player1:String; // Boyfriend
|
||||||
|
var player2:String; // Opponent
|
||||||
|
|
||||||
|
var speed:Float;
|
||||||
|
var stageDefault:String;
|
||||||
|
var bpm:Float;
|
||||||
|
var notes:Array<LegacyNoteSection>;
|
||||||
|
var song:String; // Song name
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef LegacyScrollSpeeds =
|
||||||
|
{
|
||||||
|
var easy:Float;
|
||||||
|
var normal:Float;
|
||||||
|
var hard:Float;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef LegacyNoteData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The easy difficulty.
|
||||||
|
*/
|
||||||
|
var ?easy:Array<LegacyNoteSection>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The normal difficulty.
|
||||||
|
*/
|
||||||
|
var ?normal:Array<LegacyNoteSection>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hard difficulty.
|
||||||
|
*/
|
||||||
|
var ?hard:Array<LegacyNoteSection>;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef LegacyNoteSection =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Whether the section is a must-hit section.
|
||||||
|
* If true, 0-3 are boyfriends notes, 4-7 are opponents notes.
|
||||||
|
* If false, 0-3 are opponents notes, 4-7 are boyfriends notes.
|
||||||
|
*/
|
||||||
|
var mustHitSection:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of note data:
|
||||||
|
* - Direction
|
||||||
|
* - Time (ms)
|
||||||
|
* - Sustain Duration (ms)
|
||||||
|
* - Note kind (true = "alt", or string)
|
||||||
|
*/
|
||||||
|
var sectionNotes:Array<LegacyNote>;
|
||||||
|
|
||||||
|
var typeOfSection:Int;
|
||||||
|
var lengthInSteps:Int;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes in the old format are stored as an Array<Dynamic>
|
||||||
|
*/
|
||||||
|
abstract LegacyNote(Array<Dynamic>)
|
||||||
|
{
|
||||||
|
public var time(get, set):Float;
|
||||||
|
|
||||||
|
function get_time():Float
|
||||||
|
{
|
||||||
|
return this[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_time(value:Float):Float
|
||||||
|
{
|
||||||
|
return this[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var data(get, set):Int;
|
||||||
|
|
||||||
|
function get_data():Int
|
||||||
|
{
|
||||||
|
return this[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_data(value:Int):Int
|
||||||
|
{
|
||||||
|
return this[1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(mustHitSection:Bool):Int
|
||||||
|
{
|
||||||
|
if (mustHitSection) return this[1];
|
||||||
|
|
||||||
|
return (this[1] + 4) % 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var length(get, set):Float;
|
||||||
|
|
||||||
|
function get_length():Float
|
||||||
|
{
|
||||||
|
if (this.length < 3) return 0.0;
|
||||||
|
return this[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_length(value:Float):Float
|
||||||
|
{
|
||||||
|
return this[2] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var kind(get, set):String;
|
||||||
|
|
||||||
|
function get_kind():String
|
||||||
|
{
|
||||||
|
if (this.length < 4) return 'normal';
|
||||||
|
|
||||||
|
if (Std.isOfType(this[3], Bool)) return this[3] ? 'alt' : 'normal';
|
||||||
|
|
||||||
|
return this[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_kind(value:String):String
|
||||||
|
{
|
||||||
|
return this[3] = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
return globalOffsets = value;
|
return globalOffsets = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:allow(funkin.ui.animDebugShit.DebugBoundingState)
|
||||||
var animOffsets(default, set):Array<Float> = [0, 0];
|
var animOffsets(default, set):Array<Float> = [0, 0];
|
||||||
|
|
||||||
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package funkin.ui.animDebugShit;
|
package funkin.ui.animDebugShit;
|
||||||
|
|
||||||
|
import funkin.util.SerializerUtil;
|
||||||
|
import funkin.play.character.CharacterData;
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
|
@ -32,6 +34,9 @@ import openfl.net.FileReference;
|
||||||
import openfl.net.URLLoader;
|
import openfl.net.URLLoader;
|
||||||
import openfl.net.URLRequest;
|
import openfl.net.URLRequest;
|
||||||
import openfl.utils.ByteArray;
|
import openfl.utils.ByteArray;
|
||||||
|
import funkin.input.Cursor;
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
|
|
||||||
using flixel.util.FlxSpriteUtil;
|
using flixel.util.FlxSpriteUtil;
|
||||||
|
|
||||||
|
@ -71,7 +76,7 @@ class DebugBoundingState extends FlxState
|
||||||
{
|
{
|
||||||
Paths.setCurrentLevel('week1');
|
Paths.setCurrentLevel('week1');
|
||||||
|
|
||||||
var str = Paths.xml('ui/offset-editor-view');
|
var str = Paths.xml('ui/animation-editor/offset-editor-view');
|
||||||
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
||||||
|
|
||||||
// uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
// uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
||||||
|
@ -109,6 +114,8 @@ class DebugBoundingState extends FlxState
|
||||||
initSpritesheetView();
|
initSpritesheetView();
|
||||||
initOffsetView();
|
initOffsetView();
|
||||||
|
|
||||||
|
Cursor.show();
|
||||||
|
|
||||||
uiStuff.cameras = [hudCam];
|
uiStuff.cameras = [hudCam];
|
||||||
|
|
||||||
add(uiStuff);
|
add(uiStuff);
|
||||||
|
@ -124,7 +131,7 @@ class DebugBoundingState extends FlxState
|
||||||
spriteSheetView = new FlxGroup();
|
spriteSheetView = new FlxGroup();
|
||||||
add(spriteSheetView);
|
add(spriteSheetView);
|
||||||
|
|
||||||
var tex = Paths.getSparrowAtlas('characters/temp');
|
var tex = Paths.getSparrowAtlas('characters/BOYFRIEND');
|
||||||
// tex.frames[0].uv
|
// tex.frames[0].uv
|
||||||
|
|
||||||
bf = new FlxSprite();
|
bf = new FlxSprite();
|
||||||
|
@ -238,11 +245,15 @@ class DebugBoundingState extends FlxState
|
||||||
txtOffsetShit.cameras = [hudCam];
|
txtOffsetShit.cameras = [hudCam];
|
||||||
offsetView.add(txtOffsetShit);
|
offsetView.add(txtOffsetShit);
|
||||||
|
|
||||||
animDropDownMenu = new FlxUIDropDownMenu(630, 20, FlxUIDropDownMenu.makeStrIdLabelArray(['weed'], true));
|
animDropDownMenu = new FlxUIDropDownMenu(0, 0, FlxUIDropDownMenu.makeStrIdLabelArray(['weed'], true));
|
||||||
animDropDownMenu.cameras = [hudCam];
|
animDropDownMenu.cameras = [hudCam];
|
||||||
|
// Move to bottom right corner
|
||||||
|
animDropDownMenu.x = FlxG.width - animDropDownMenu.width - 20;
|
||||||
|
animDropDownMenu.y = FlxG.height - animDropDownMenu.height - 20;
|
||||||
offsetView.add(animDropDownMenu);
|
offsetView.add(animDropDownMenu);
|
||||||
|
|
||||||
var characters:Array<String> = CoolUtil.coolTextFile(Paths.txt('characterList'));
|
var characters:Array<String> = CharacterDataParser.listCharacterIds();
|
||||||
|
characters.sort(SortUtil.alphabetically);
|
||||||
|
|
||||||
var charDropdown:DropDown = cast uiStuff.findComponent('characterDropdown');
|
var charDropdown:DropDown = cast uiStuff.findComponent('characterDropdown');
|
||||||
for (char in characters)
|
for (char in characters)
|
||||||
|
@ -264,19 +275,16 @@ class DebugBoundingState extends FlxState
|
||||||
{
|
{
|
||||||
if (FlxG.mouse.justPressed)
|
if (FlxG.mouse.justPressed)
|
||||||
{
|
{
|
||||||
mouseOffset.set(FlxG.mouse.x - -swagChar.offset.x, FlxG.mouse.y - -swagChar.offset.y);
|
mouseOffset.set(FlxG.mouse.x - -swagChar.animOffsets[0], FlxG.mouse.y - -swagChar.animOffsets[1]);
|
||||||
// oldPos.set(swagChar.offset.x, swagChar.offset.y);
|
|
||||||
// oldPos.set(FlxG.mouse.x, FlxG.mouse.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.mouse.pressed)
|
if (FlxG.mouse.pressed)
|
||||||
{
|
{
|
||||||
swagChar.offset.x = (FlxG.mouse.x - mouseOffset.x) * -1;
|
swagChar.animOffsets = [(FlxG.mouse.x - mouseOffset.x) * -1, (FlxG.mouse.y - mouseOffset.y) * -1];
|
||||||
swagChar.offset.y = (FlxG.mouse.y - mouseOffset.y) * -1;
|
|
||||||
|
|
||||||
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, [Std.int(swagChar.offset.x), Std.int(swagChar.offset.y)]);
|
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, swagChar.animOffsets);
|
||||||
|
|
||||||
txtOffsetShit.text = 'Offset: ' + swagChar.offset;
|
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,6 +299,11 @@ class DebugBoundingState extends FlxState
|
||||||
swagText.text = str + ": " + Std.string(value);
|
swagText.text = str + ": " + Std.string(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearInfo()
|
||||||
|
{
|
||||||
|
txtGrp.clear();
|
||||||
|
}
|
||||||
|
|
||||||
function checkLibrary(library:String)
|
function checkLibrary(library:String)
|
||||||
{
|
{
|
||||||
trace(Assets.hasLibrary(library));
|
trace(Assets.hasLibrary(library));
|
||||||
|
@ -320,7 +333,7 @@ class DebugBoundingState extends FlxState
|
||||||
{
|
{
|
||||||
var lv:DropDown = cast uiStuff.findComponent("swapper");
|
var lv:DropDown = cast uiStuff.findComponent("swapper");
|
||||||
lv.selectedIndex = 1;
|
lv.selectedIndex = 1;
|
||||||
curView = OFFSETSHIT;
|
curView = ANIMATIONS;
|
||||||
if (swagChar != null)
|
if (swagChar != null)
|
||||||
{
|
{
|
||||||
FlxG.camera.focusOn(swagChar.getMidpoint());
|
FlxG.camera.focusOn(swagChar.getMidpoint());
|
||||||
|
@ -334,7 +347,7 @@ class DebugBoundingState extends FlxState
|
||||||
spriteSheetView.visible = true;
|
spriteSheetView.visible = true;
|
||||||
offsetView.visible = false;
|
offsetView.visible = false;
|
||||||
offsetView.active = false;
|
offsetView.active = false;
|
||||||
case OFFSETSHIT:
|
case ANIMATIONS:
|
||||||
spriteSheetView.visible = false;
|
spriteSheetView.visible = false;
|
||||||
offsetView.visible = true;
|
offsetView.visible = true;
|
||||||
offsetView.active = true;
|
offsetView.active = true;
|
||||||
|
@ -344,6 +357,8 @@ class DebugBoundingState extends FlxState
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
|
if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
|
||||||
|
|
||||||
|
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||||
|
|
||||||
CoolUtil.mouseCamDrag();
|
CoolUtil.mouseCamDrag();
|
||||||
CoolUtil.mouseWheelZoom();
|
CoolUtil.mouseWheelZoom();
|
||||||
|
|
||||||
|
@ -364,14 +379,14 @@ class DebugBoundingState extends FlxState
|
||||||
+ 1);
|
+ 1);
|
||||||
else
|
else
|
||||||
animDropDownMenu.selectedId = Std.string(0);
|
animDropDownMenu.selectedId = Std.string(0);
|
||||||
animDropDownMenu.callback(animDropDownMenu.selectedId);
|
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||||
}
|
}
|
||||||
if (FlxG.keys.justPressed.LBRACKET || FlxG.keys.justPressed.Q)
|
if (FlxG.keys.justPressed.LBRACKET || FlxG.keys.justPressed.Q)
|
||||||
{
|
{
|
||||||
if (Std.parseInt(animDropDownMenu.selectedId) - 1 >= 0) animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId) - 1);
|
if (Std.parseInt(animDropDownMenu.selectedId) - 1 >= 0) animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId) - 1);
|
||||||
else
|
else
|
||||||
animDropDownMenu.selectedId = Std.string(animDropDownMenu.length - 1);
|
animDropDownMenu.selectedId = Std.string(animDropDownMenu.length - 1);
|
||||||
animDropDownMenu.callback(animDropDownMenu.selectedId);
|
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboards controls for general WASD "movement"
|
// Keyboards controls for general WASD "movement"
|
||||||
|
@ -379,16 +394,29 @@ class DebugBoundingState extends FlxState
|
||||||
// and then it's just played and updated from the animDropDownMenu callback, which is set in the loadAnimShit() function probabbly
|
// and then it's just played and updated from the animDropDownMenu callback, which is set in the loadAnimShit() function probabbly
|
||||||
if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S || FlxG.keys.justPressed.D || FlxG.keys.justPressed.A)
|
if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S || FlxG.keys.justPressed.D || FlxG.keys.justPressed.A)
|
||||||
{
|
{
|
||||||
var missShit:String = '';
|
var suffix:String = '';
|
||||||
|
var targetLabel:String = '';
|
||||||
|
|
||||||
if (FlxG.keys.pressed.SHIFT) missShit = 'miss';
|
if (FlxG.keys.pressed.SHIFT) suffix = 'miss';
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.W) animDropDownMenu.selectedLabel = 'singUP' + missShit;
|
if (FlxG.keys.justPressed.W) targetLabel = 'singUP$suffix';
|
||||||
if (FlxG.keys.justPressed.S) animDropDownMenu.selectedLabel = 'singDOWN' + missShit;
|
if (FlxG.keys.justPressed.S) targetLabel = 'singDOWN$suffix';
|
||||||
if (FlxG.keys.justPressed.A) animDropDownMenu.selectedLabel = 'singLEFT' + missShit;
|
if (FlxG.keys.justPressed.A) targetLabel = 'singLEFT$suffix';
|
||||||
if (FlxG.keys.justPressed.D) animDropDownMenu.selectedLabel = 'singRIGHT' + missShit;
|
if (FlxG.keys.justPressed.D) targetLabel = 'singRIGHT$suffix';
|
||||||
|
|
||||||
animDropDownMenu.callback(animDropDownMenu.selectedId);
|
if (targetLabel != animDropDownMenu.selectedLabel)
|
||||||
|
{
|
||||||
|
// Play the new animation if the IDs are the different.
|
||||||
|
// Override the onion skin.
|
||||||
|
animDropDownMenu.selectedLabel = targetLabel;
|
||||||
|
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Replay the current animation if the IDs are the same.
|
||||||
|
// Don't override the onion skin.
|
||||||
|
playCharacterAnimation(animDropDownMenu.selectedId, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.F)
|
if (FlxG.keys.justPressed.F)
|
||||||
|
@ -400,16 +428,16 @@ class DebugBoundingState extends FlxState
|
||||||
if (FlxG.keys.justPressed.SPACE)
|
if (FlxG.keys.justPressed.SPACE)
|
||||||
{
|
{
|
||||||
animDropDownMenu.selectedLabel = 'idle';
|
animDropDownMenu.selectedLabel = 'idle';
|
||||||
animDropDownMenu.callback(animDropDownMenu.selectedId);
|
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playback the animation
|
// Playback the animation
|
||||||
if (FlxG.keys.justPressed.ENTER) animDropDownMenu.callback(animDropDownMenu.selectedId);
|
if (FlxG.keys.justPressed.ENTER) playCharacterAnimation(animDropDownMenu.selectedId, false);
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN)
|
if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN)
|
||||||
{
|
{
|
||||||
var animName = animDropDownMenu.selectedLabel;
|
var animName = animDropDownMenu.selectedLabel;
|
||||||
var coolValues:Array<Float> = swagChar.animationOffsets.get(animName);
|
var coolValues:Array<Float> = swagChar.animationOffsets.get(animName).copy();
|
||||||
|
|
||||||
var multiplier:Int = 5;
|
var multiplier:Int = 5;
|
||||||
|
|
||||||
|
@ -431,6 +459,13 @@ class DebugBoundingState extends FlxState
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.ESCAPE)
|
if (FlxG.keys.justPressed.ESCAPE)
|
||||||
|
{
|
||||||
|
var outputString = FlxG.keys.pressed.CONTROL ? buildOutputStringOld() : buildOutputStringNew();
|
||||||
|
saveOffsets(outputString, FlxG.keys.pressed.CONTROL ? swagChar.characterId + "Offsets.txt" : swagChar.characterId + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOutputStringOld():String
|
||||||
{
|
{
|
||||||
var outputString:String = "";
|
var outputString:String = "";
|
||||||
|
|
||||||
|
@ -440,8 +475,21 @@ class DebugBoundingState extends FlxState
|
||||||
}
|
}
|
||||||
|
|
||||||
outputString.trim();
|
outputString.trim();
|
||||||
saveOffsets(outputString);
|
|
||||||
|
return outputString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildOutputStringNew():String
|
||||||
|
{
|
||||||
|
var charData:CharacterData = Reflect.copy(swagChar._data);
|
||||||
|
|
||||||
|
for (charDataAnim in charData.animations)
|
||||||
|
{
|
||||||
|
var animName:String = charDataAnim.name;
|
||||||
|
charDataAnim.offsets = swagChar.animationOffsets.get(animName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SerializerUtil.toJSON(charData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var swagChar:BaseCharacter;
|
var swagChar:BaseCharacter;
|
||||||
|
@ -466,35 +514,51 @@ class DebugBoundingState extends FlxState
|
||||||
generateOutlines(swagChar.frames.frames);
|
generateOutlines(swagChar.frames.frames);
|
||||||
bf.pixels = swagChar.pixels;
|
bf.pixels = swagChar.pixels;
|
||||||
|
|
||||||
var animThing:Array<String> = [];
|
clearInfo();
|
||||||
|
addInfo(swagChar._data.assetPath, "");
|
||||||
|
addInfo('Width', bf.width);
|
||||||
|
addInfo('Height', bf.height);
|
||||||
|
|
||||||
|
characterAnimNames = [];
|
||||||
|
|
||||||
for (i in swagChar.animationOffsets.keys())
|
for (i in swagChar.animationOffsets.keys())
|
||||||
{
|
{
|
||||||
animThing.push(i);
|
characterAnimNames.push(i);
|
||||||
trace(i);
|
trace(i);
|
||||||
trace(swagChar.animationOffsets[i]);
|
trace(swagChar.animationOffsets[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
animDropDownMenu.setData(FlxUIDropDownMenu.makeStrIdLabelArray(animThing, true));
|
animDropDownMenu.setData(FlxUIDropDownMenu.makeStrIdLabelArray(characterAnimNames, true));
|
||||||
animDropDownMenu.callback = function(str:String) {
|
animDropDownMenu.callback = function(str:String) {
|
||||||
|
playCharacterAnimation(str, true);
|
||||||
|
};
|
||||||
|
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||||
|
dropDownSetup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private var characterAnimNames:Array<String>;
|
||||||
|
|
||||||
|
function playCharacterAnimation(str:String, setOnionSkin:Bool = true)
|
||||||
|
{
|
||||||
|
if (setOnionSkin)
|
||||||
|
{
|
||||||
// clears the canvas
|
// clears the canvas
|
||||||
onionSkinChar.pixels.fillRect(new Rectangle(0, 0, FlxG.width * 2, FlxG.height * 2), 0x00000000);
|
onionSkinChar.pixels.fillRect(new Rectangle(0, 0, FlxG.width * 2, FlxG.height * 2), 0x00000000);
|
||||||
|
|
||||||
onionSkinChar.stamp(swagChar, Std.int(swagChar.x - swagChar.offset.x), Std.int(swagChar.y - swagChar.offset.y));
|
onionSkinChar.stamp(swagChar, Std.int(swagChar.x), Std.int(swagChar.y));
|
||||||
onionSkinChar.alpha = 0.6;
|
onionSkinChar.alpha = 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
var animName = animThing[Std.parseInt(str)];
|
var animName = characterAnimNames[Std.parseInt(str)];
|
||||||
swagChar.playAnimation(animName, true); // trace();
|
swagChar.playAnimation(animName, true); // trace();
|
||||||
trace(swagChar.animationOffsets.get(animName));
|
trace(swagChar.animationOffsets.get(animName));
|
||||||
|
|
||||||
txtOffsetShit.text = 'Offset: ' + swagChar.offset;
|
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||||
};
|
|
||||||
dropDownSetup = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _file:FileReference;
|
var _file:FileReference;
|
||||||
|
|
||||||
function saveOffsets(saveString:String)
|
function saveOffsets(saveString:String, fileName:String)
|
||||||
{
|
{
|
||||||
if ((saveString != null) && (saveString.length > 0))
|
if ((saveString != null) && (saveString.length > 0))
|
||||||
{
|
{
|
||||||
|
@ -502,7 +566,7 @@ class DebugBoundingState extends FlxState
|
||||||
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
||||||
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
||||||
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
||||||
_file.save(saveString, swagChar.characterId + "Offsets.txt");
|
_file.save(saveString,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,5 +606,5 @@ class DebugBoundingState extends FlxState
|
||||||
enum abstract ANIMDEBUGVIEW(String)
|
enum abstract ANIMDEBUGVIEW(String)
|
||||||
{
|
{
|
||||||
var SPRITESHEET;
|
var SPRITESHEET;
|
||||||
var OFFSETSHIT;
|
var ANIMATIONS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.play.character.CharacterData;
|
||||||
|
import funkin.util.Constants;
|
||||||
|
import funkin.util.SerializerUtil;
|
||||||
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.character.BaseCharacter;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
|
import funkin.play.song.SongMigrator;
|
||||||
|
import funkin.play.song.SongValidator;
|
||||||
import funkin.play.song.SongData.SongDataParser;
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import funkin.play.song.SongData.SongPlayableChar;
|
import funkin.play.song.SongData.SongPlayableChar;
|
||||||
import funkin.play.song.SongData.SongTimeChange;
|
import funkin.play.song.SongData.SongTimeChange;
|
||||||
|
import funkin.util.FileUtil;
|
||||||
import haxe.io.Path;
|
import haxe.io.Path;
|
||||||
import haxe.ui.components.Button;
|
import haxe.ui.components.Button;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
|
@ -41,6 +49,9 @@ class ChartEditorDialogHandler
|
||||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup');
|
static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup');
|
||||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals');
|
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_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry');
|
||||||
|
static final CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart');
|
||||||
|
static final CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-entry');
|
||||||
|
static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart');
|
||||||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,9 +83,140 @@ class ChartEditorDialogHandler
|
||||||
//
|
//
|
||||||
// Create Song Wizard
|
// Create Song Wizard
|
||||||
//
|
//
|
||||||
|
openCreateSongWizard(state, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkImportChartLegacy:Link = dialog.findComponent('splashImportChartLegacy', Link);
|
||||||
|
linkImportChartLegacy.onClick = function(_event) {
|
||||||
|
// Hide the welcome dialog
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
|
||||||
|
// Open the "Import Chart" dialog
|
||||||
|
openImportChartWizard(state, 'legacy', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttonBrowse:Button = dialog.findComponent('splashBrowse', Button);
|
||||||
|
buttonBrowse.onClick = function(_event) {
|
||||||
|
// Hide the welcome dialog
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
|
||||||
|
// Open the "Open Chart" dialog
|
||||||
|
openBrowseWizard(state, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||||
|
|
||||||
|
var songList:Array<String> = SongDataParser.listSongIds();
|
||||||
|
songList.sort(SortUtil.alphabetically);
|
||||||
|
|
||||||
|
for (targetSongId in songList)
|
||||||
|
{
|
||||||
|
var songData:Song = SongDataParser.fetchSong(targetSongId);
|
||||||
|
|
||||||
|
if (songData == null) continue;
|
||||||
|
|
||||||
|
var songName:String = songData.getDifficulty().songName;
|
||||||
|
|
||||||
|
var linkTemplateSong:Link = new Link();
|
||||||
|
linkTemplateSong.text = songName;
|
||||||
|
linkTemplateSong.onClick = function(_event) {
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
|
||||||
|
// Load song from template
|
||||||
|
state.loadSongAsTemplate(targetSongId);
|
||||||
|
}
|
||||||
|
|
||||||
|
splashTemplateContainer.addComponent(linkTemplateSong);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the wizard for opening an existing chart from individual files.
|
||||||
|
* @param state
|
||||||
|
* @param closable
|
||||||
|
*/
|
||||||
|
public static function openBrowseWizard(state:ChartEditorState, closable:Bool):Void
|
||||||
|
{
|
||||||
|
// Open the "Open Chart" wizard
|
||||||
|
// Step 1. Open Chart
|
||||||
|
var openChartDialog:Dialog = openChartDialog(state);
|
||||||
|
openChartDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 2. Upload instrumental
|
||||||
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
|
uploadInstDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 3. Upload Vocals
|
||||||
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
|
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
state.postLoadInstrumental();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function openImportChartWizard(state:ChartEditorState, format:String, closable:Bool):Void
|
||||||
|
{
|
||||||
|
// Open the "Open Chart" wizard
|
||||||
|
// Step 1. Open Chart
|
||||||
|
var openChartDialog:Dialog = openImportChartDialog(state, format);
|
||||||
|
openChartDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 2. Upload instrumental
|
||||||
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
|
uploadInstDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 3. Upload Vocals
|
||||||
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
|
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
state.postLoadInstrumental();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function openCreateSongWizard(state:ChartEditorState, closable:Bool):Void
|
||||||
|
{
|
||||||
// Step 1. Upload Instrumental
|
// Step 1. Upload Instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, false);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(_event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
@ -104,34 +246,6 @@ class ChartEditorDialogHandler
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
|
||||||
|
|
||||||
var songList:Array<String> = SongDataParser.listSongIds();
|
|
||||||
songList.sort(SortUtil.alphabetical);
|
|
||||||
|
|
||||||
for (targetSongId in songList)
|
|
||||||
{
|
|
||||||
var songData:Song = SongDataParser.fetchSong(targetSongId);
|
|
||||||
|
|
||||||
if (songData == null) continue;
|
|
||||||
|
|
||||||
var songName:String = songData.getDifficulty().songName;
|
|
||||||
|
|
||||||
var linkTemplateSong:Link = new Link();
|
|
||||||
linkTemplateSong.text = songName;
|
|
||||||
linkTemplateSong.onClick = function(_event) {
|
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
|
||||||
|
|
||||||
// Load song from template
|
|
||||||
state.loadSongAsTemplate(targetSongId);
|
|
||||||
}
|
|
||||||
|
|
||||||
splashTemplateContainer.addComponent(linkTemplateSong);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and opens a dialog where the user uploads an instrumental for the current song.
|
* Builds and opens a dialog where the user uploads an instrumental for the current song.
|
||||||
* @param state The current chart editor state.
|
* @param state The current chart editor state.
|
||||||
|
@ -216,11 +330,20 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext))
|
||||||
|
{
|
||||||
|
'File format (${path.ext}) not supported for instrumental track (${path.file}.${path.ext})';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
'Failed to load instrumental track (${path.file}.${path.ext})';
|
||||||
|
}
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Failure',
|
title: 'Failure',
|
||||||
body: 'Failed to load instrumental track (${path.file}.${path.ext})',
|
body: message,
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -420,12 +543,6 @@ class ChartEditorDialogHandler
|
||||||
moveCharGroup(event.data.id);
|
moveCharGroup(event.data.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
// Find the next available player character.
|
|
||||||
trace(charGroupPlayer.dataSource.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
|
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
|
||||||
charGroupOpponent.onChange = function(event:UIEvent) {
|
charGroupOpponent.onChange = function(event:UIEvent) {
|
||||||
charData.opponent = event.data.id;
|
charData.opponent = event.data.id;
|
||||||
|
@ -483,8 +600,8 @@ class ChartEditorDialogHandler
|
||||||
for (charKey in charIdsForVocals)
|
for (charKey in charIdsForVocals)
|
||||||
{
|
{
|
||||||
trace('Adding vocal upload for character ${charKey}');
|
trace('Adding vocal upload for character ${charKey}');
|
||||||
var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey);
|
var charMetadata:CharacterData = CharacterDataParser.fetchCharacterData(charKey);
|
||||||
var charName:String = charMetadata.characterName;
|
var charName:String = charMetadata != null ? charMetadata.name : charKey;
|
||||||
|
|
||||||
var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||||
|
|
||||||
|
@ -511,11 +628,20 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext))
|
||||||
|
{
|
||||||
|
'File format (${path.ext}) not supported for vocal track (${path.file}.${path.ext})';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
'Failed to load vocal track (${path.file}.${path.ext})';
|
||||||
|
}
|
||||||
|
|
||||||
// Vocals failed to load.
|
// Vocals failed to load.
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Failure',
|
title: 'Failure',
|
||||||
body: 'Failed to load vocal track (${path.file}.${path.ext})',
|
body: message,
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -552,6 +678,284 @@ class ChartEditorDialogHandler
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and opens a dialog where the user upload the JSON files for a 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.
|
||||||
|
*/
|
||||||
|
@:haxe.warning('-WVarInit')
|
||||||
|
public static function openChartDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||||
|
{
|
||||||
|
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT, true, closable);
|
||||||
|
|
||||||
|
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||||
|
buttonCancel.onClick = function(_event) {
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chartContainerA:Component = dialog.findComponent('chartContainerA');
|
||||||
|
var chartContainerB:Component = dialog.findComponent('chartContainerB');
|
||||||
|
|
||||||
|
var songMetadata:Map<String, SongMetadata> = [];
|
||||||
|
var songChartData:Map<String, SongChartData> = [];
|
||||||
|
|
||||||
|
var buttonContinue:Button = dialog.findComponent('dialogContinue', Button);
|
||||||
|
buttonContinue.onClick = function(_event) {
|
||||||
|
state.loadSong(songMetadata, songChartData);
|
||||||
|
|
||||||
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
var onDropFileMetadataVariation:String->Label->String->Void;
|
||||||
|
var onClickMetadataVariation:String->Label->UIEvent->Void;
|
||||||
|
var onDropFileChartDataVariation:String->Label->String->Void;
|
||||||
|
var onClickChartDataVariation:String->Label->UIEvent->Void;
|
||||||
|
|
||||||
|
var constructVariationEntries:Array<String>->Void = function(variations:Array<String>) {
|
||||||
|
// Clear the chart container.
|
||||||
|
while (chartContainerB.getComponentAt(0) != null)
|
||||||
|
{
|
||||||
|
chartContainerB.removeComponent(chartContainerB.getComponentAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an entry for -chart.json.
|
||||||
|
var songDefaultChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT);
|
||||||
|
var songDefaultChartDataEntryLabel:Label = songDefaultChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||||
|
songDefaultChartDataEntryLabel.text = 'Drag and drop <song>-chart.json file, or click to browse.';
|
||||||
|
|
||||||
|
songDefaultChartDataEntry.onClick = onClickChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel);
|
||||||
|
addDropHandler(songDefaultChartDataEntry, onDropFileChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel));
|
||||||
|
chartContainerB.addComponent(songDefaultChartDataEntry);
|
||||||
|
|
||||||
|
for (variation in variations)
|
||||||
|
{
|
||||||
|
// Build entries for -metadata-<variation>.json.
|
||||||
|
var songVariationMetadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT);
|
||||||
|
var songVariationMetadataEntryLabel:Label = songVariationMetadataEntry.findComponent('chartEntryLabel', Label);
|
||||||
|
songVariationMetadataEntryLabel.text = 'Drag and drop <song>-metadata-${variation}.json file, or click to browse.';
|
||||||
|
|
||||||
|
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
|
||||||
|
addDropHandler(songVariationMetadataEntry, onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel));
|
||||||
|
chartContainerB.addComponent(songVariationMetadataEntry);
|
||||||
|
|
||||||
|
// Build entries for -chart-<variation>.json.
|
||||||
|
var songVariationChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT);
|
||||||
|
var songVariationChartDataEntryLabel:Label = songVariationChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||||
|
songVariationChartDataEntryLabel.text = 'Drag and drop <song>-chart-${variation}.json file, or click to browse.';
|
||||||
|
|
||||||
|
songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel);
|
||||||
|
addDropHandler(songVariationChartDataEntry, onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel));
|
||||||
|
chartContainerB.addComponent(songVariationChartDataEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropFileMetadataVariation = function(variation:String, label:Label, pathStr:String) {
|
||||||
|
var path:Path = new Path(pathStr);
|
||||||
|
trace('Dropped JSON file (${path})');
|
||||||
|
|
||||||
|
var songMetadataJson:Dynamic = FileUtil.readJSONFromPath(path.toString());
|
||||||
|
var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import');
|
||||||
|
songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import');
|
||||||
|
|
||||||
|
songMetadata.set(variation, songMetadataVariation);
|
||||||
|
|
||||||
|
// Tell the user the load was successful.
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded metadata file (${path.file}.${path.ext})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
|
||||||
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||||
|
|
||||||
|
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickMetadataVariation = function(variation:String, label:Label, _event:UIEvent) {
|
||||||
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
||||||
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
||||||
|
if (selectedFile != null)
|
||||||
|
{
|
||||||
|
trace('Selected file: ' + selectedFile.name);
|
||||||
|
|
||||||
|
var songMetadataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes);
|
||||||
|
var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import');
|
||||||
|
songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import');
|
||||||
|
songMetadataVariation.variation = variation;
|
||||||
|
|
||||||
|
songMetadata.set(variation, songMetadataVariation);
|
||||||
|
|
||||||
|
// Tell the user the load was successful.
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded metadata file (${selectedFile.name})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
|
||||||
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
|
|
||||||
|
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropFileChartDataVariation = function(variation:String, label:Label, pathStr:String) {
|
||||||
|
var path:Path = new Path(pathStr);
|
||||||
|
trace('Dropped JSON file (${path})');
|
||||||
|
|
||||||
|
var songChartDataJson:Dynamic = FileUtil.readJSONFromPath(path.toString());
|
||||||
|
var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import');
|
||||||
|
songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import');
|
||||||
|
|
||||||
|
songChartData.set(variation, songChartDataVariation);
|
||||||
|
|
||||||
|
// Tell the user the load was successful.
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded chart data file (${path.file}.${path.ext})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
|
||||||
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickChartDataVariation = function(variation:String, label:Label, _event:UIEvent) {
|
||||||
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
||||||
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
||||||
|
if (selectedFile != null)
|
||||||
|
{
|
||||||
|
trace('Selected file: ' + selectedFile.name);
|
||||||
|
|
||||||
|
var songChartDataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes);
|
||||||
|
var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import');
|
||||||
|
songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import');
|
||||||
|
|
||||||
|
songChartData.set(variation, songChartDataVariation);
|
||||||
|
|
||||||
|
// Tell the user the load was successful.
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded chart data file (${selectedFile.name})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
|
||||||
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT);
|
||||||
|
var metadataEntryLabel:Label = metadataEntry.findComponent('chartEntryLabel', Label);
|
||||||
|
metadataEntryLabel.text = 'Drag and drop <song>-metadata.json file, or click to browse.';
|
||||||
|
|
||||||
|
metadataEntry.onClick = onClickMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel);
|
||||||
|
addDropHandler(metadataEntry, onDropFileMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel));
|
||||||
|
|
||||||
|
chartContainerA.addComponent(metadataEntry);
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and opens a dialog where the user can import a chart from an existing file format.
|
||||||
|
* @param state The current chart editor state.
|
||||||
|
* @param format The format to import from.
|
||||||
|
* @param closable
|
||||||
|
* @return Dialog
|
||||||
|
*/
|
||||||
|
public static function openImportChartDialog(state:ChartEditorState, format:String, ?closable:Bool = true):Dialog
|
||||||
|
{
|
||||||
|
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT, true, closable);
|
||||||
|
|
||||||
|
var prettyFormat:String = switch (format)
|
||||||
|
{
|
||||||
|
case 'legacy': 'FNF Legacy';
|
||||||
|
default: 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileFilter = switch (format)
|
||||||
|
{
|
||||||
|
case 'legacy': {label: 'JSON Data File (.json)', extension: 'json'};
|
||||||
|
default: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.title = 'Import Chart - ${prettyFormat}';
|
||||||
|
|
||||||
|
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||||
|
|
||||||
|
buttonCancel.onClick = function(_event) {
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
var importBox:Box = dialog.findComponent('importBox', Box);
|
||||||
|
|
||||||
|
importBox.onMouseOver = function(_event) {
|
||||||
|
importBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
|
Cursor.cursorMode = Pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
importBox.onMouseOut = function(_event) {
|
||||||
|
importBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
|
Cursor.cursorMode = Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var onDropFile:String->Void;
|
||||||
|
|
||||||
|
importBox.onClick = function(_event) {
|
||||||
|
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', [fileFilter], function(selectedFile:SelectedFileInfo) {
|
||||||
|
if (selectedFile != null)
|
||||||
|
{
|
||||||
|
trace('Selected file: ' + selectedFile.fullPath);
|
||||||
|
var selectedFileJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes);
|
||||||
|
var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson);
|
||||||
|
var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson);
|
||||||
|
|
||||||
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||||
|
|
||||||
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded chart file (${selectedFile.name})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropFile = function(pathStr:String) {
|
||||||
|
var path:Path = new Path(pathStr);
|
||||||
|
var selectedFileJson:Dynamic = FileUtil.readJSONFromPath(path.toString());
|
||||||
|
var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson);
|
||||||
|
var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson);
|
||||||
|
|
||||||
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||||
|
|
||||||
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Loaded chart file (${path.file}.${path.ext})',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addDropHandler(importBox, onDropFile);
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
|
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
|
||||||
*
|
*
|
||||||
|
@ -571,6 +975,8 @@ class ChartEditorDialogHandler
|
||||||
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Dialog
|
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Dialog
|
||||||
{
|
{
|
||||||
var dialog:Dialog = cast state.buildComponent(key);
|
var dialog:Dialog = cast state.buildComponent(key);
|
||||||
|
if (dialog == null) return null;
|
||||||
|
|
||||||
dialog.destroyOnClose = true;
|
dialog.destroyOnClose = true;
|
||||||
dialog.closable = closable;
|
dialog.closable = closable;
|
||||||
dialog.showDialog(modal);
|
dialog.showDialog(modal);
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ChartEditorEventSprite extends FlxSprite
|
||||||
/**
|
/**
|
||||||
* The image used for all song events. Cached for performance.
|
* The image used for all song events. Cached for performance.
|
||||||
*/
|
*/
|
||||||
var eventGraphic:BitmapData;
|
static var eventSpriteBasic:BitmapData;
|
||||||
|
|
||||||
public function new(parent:ChartEditorState)
|
public function new(parent:ChartEditorState)
|
||||||
{
|
{
|
||||||
|
|
144
source/funkin/ui/debug/charting/ChartEditorNotePreview.hx
Normal file
144
source/funkin/ui/debug/charting/ChartEditorNotePreview.hx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import flixel.math.FlxMath;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import flixel.util.FlxSpriteUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the note scrollbar preview in the chart editor.
|
||||||
|
*/
|
||||||
|
class ChartEditorNotePreview extends FlxSprite
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Constants
|
||||||
|
//
|
||||||
|
static final NOTE_WIDTH:Int = 5;
|
||||||
|
static final NOTE_HEIGHT:Int = 1;
|
||||||
|
static final WIDTH:Int = NOTE_WIDTH * 9;
|
||||||
|
|
||||||
|
static final BG_COLOR:FlxColor = FlxColor.GRAY;
|
||||||
|
static final LEFT_COLOR:FlxColor = 0xFFFF22AA;
|
||||||
|
static final DOWN_COLOR:FlxColor = 0xFF00EEFF;
|
||||||
|
static final UP_COLOR:FlxColor = 0xFF00CC00;
|
||||||
|
static final RIGHT_COLOR:FlxColor = 0xFFCC1111;
|
||||||
|
static final EVENT_COLOR:FlxColor = 0xFF111111;
|
||||||
|
|
||||||
|
var previewHeight:Int;
|
||||||
|
|
||||||
|
public function new(height:Int)
|
||||||
|
{
|
||||||
|
super(0, 0);
|
||||||
|
this.previewHeight = height;
|
||||||
|
buildBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the initial sprite for the preview.
|
||||||
|
*/
|
||||||
|
function buildBackground():Void
|
||||||
|
{
|
||||||
|
makeGraphic(WIDTH, 0, BG_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase all notes from the preview.
|
||||||
|
*/
|
||||||
|
public function erase():Void
|
||||||
|
{
|
||||||
|
drawRect(0, 0, WIDTH, previewHeight, BG_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single note to the preview.
|
||||||
|
* @param note The data for the note.
|
||||||
|
* @param songLengthInMs The total length of the song in milliseconds.
|
||||||
|
*/
|
||||||
|
public function addNote(note:SongNoteData, songLengthInMs:Int):Void
|
||||||
|
{
|
||||||
|
var noteDir:Int = note.getDirection();
|
||||||
|
var mustHit:Bool = note.getStrumlineIndex() == 0;
|
||||||
|
drawNote(noteDir, mustHit, Std.int(note.time), songLengthInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a song event to the preview.
|
||||||
|
* @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
|
||||||
|
{
|
||||||
|
drawNote(-1, false, Std.int(event.time), songLengthInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an array of notes to the preview.
|
||||||
|
* @param notes The data for the notes.
|
||||||
|
* @param songLengthInMs The total length of the song in milliseconds.
|
||||||
|
*/
|
||||||
|
public function addNotes(notes:Array<SongNoteData>, songLengthInMs:Int):Void
|
||||||
|
{
|
||||||
|
for (note in notes)
|
||||||
|
{
|
||||||
|
addNote(note, songLengthInMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an array of events to the preview.
|
||||||
|
* @param events The data for the events.
|
||||||
|
* @param songLengthInMs The total length of the song in milliseconds.
|
||||||
|
*/
|
||||||
|
public function addEvents(events:Array<SongEventData>, songLengthInMs:Int):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
addEvent(event, songLengthInMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a note on the preview.
|
||||||
|
* @param dir Note data.
|
||||||
|
* @param mustHit False if opponent, true if player.
|
||||||
|
* @param strumTimeInMs Time in milliseconds to strum the note.
|
||||||
|
* @param songLengthInMs Length of the song in milliseconds.
|
||||||
|
*/
|
||||||
|
function drawNote(dir:Int, mustHit:Bool, strumTimeInMs:Int, songLengthInMs:Int):Void
|
||||||
|
{
|
||||||
|
var color:FlxColor = switch (dir)
|
||||||
|
{
|
||||||
|
case 0: LEFT_COLOR;
|
||||||
|
case 1: DOWN_COLOR;
|
||||||
|
case 2: UP_COLOR;
|
||||||
|
case 3: RIGHT_COLOR;
|
||||||
|
default: EVENT_COLOR;
|
||||||
|
};
|
||||||
|
|
||||||
|
var noteX:Float = NOTE_WIDTH * dir;
|
||||||
|
if (mustHit) noteX += NOTE_WIDTH * 4;
|
||||||
|
if (dir == -1) noteX = NOTE_WIDTH * 8;
|
||||||
|
|
||||||
|
var noteY:Float = FlxMath.remapToRange(strumTimeInMs, 0, songLengthInMs, 0, previewHeight);
|
||||||
|
|
||||||
|
drawRect(noteX, noteY, NOTE_WIDTH, NOTE_HEIGHT, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
function eraseNote(dir:Int, mustHit:Bool, strumTimeInMs:Int, songLengthInMs:Int):Void
|
||||||
|
{
|
||||||
|
var noteX:Float = NOTE_WIDTH * dir;
|
||||||
|
if (mustHit) noteX += NOTE_WIDTH * 4;
|
||||||
|
if (dir == -1) noteX = NOTE_WIDTH * 8;
|
||||||
|
|
||||||
|
var noteY:Float = FlxMath.remapToRange(strumTimeInMs, 0, songLengthInMs, 0, previewHeight);
|
||||||
|
|
||||||
|
drawRect(noteX, noteY, NOTE_WIDTH, NOTE_HEIGHT, BG_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function drawRect(noteX:Float, noteY:Float, width:Int, height:Int, color:FlxColor):Void
|
||||||
|
{
|
||||||
|
FlxSpriteUtil.drawRect(this, noteX, noteY, width, height, color);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -26,7 +26,7 @@ class ChartEditorThemeHandler
|
||||||
// An enum of typedefs or something?
|
// An enum of typedefs or something?
|
||||||
// ================================
|
// ================================
|
||||||
static final BACKGROUND_COLOR_LIGHT:FlxColor = 0xFF673AB7;
|
static final BACKGROUND_COLOR_LIGHT:FlxColor = 0xFF673AB7;
|
||||||
static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF673AB7;
|
static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF361E60;
|
||||||
|
|
||||||
// Color 1 of the grid pattern. Alternates with Color 2.
|
// Color 1 of the grid pattern. Alternates with Color 2.
|
||||||
static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6;
|
static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6;
|
||||||
|
@ -43,13 +43,11 @@ class ChartEditorThemeHandler
|
||||||
// Vertical divider between characters.
|
// Vertical divider between characters.
|
||||||
static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
|
static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
|
||||||
static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
||||||
// static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = 2;
|
|
||||||
static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
|
static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
|
||||||
|
|
||||||
// Horizontal divider between measures.
|
// Horizontal divider between measures.
|
||||||
static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
|
static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
|
||||||
static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
||||||
// static final GRID_MEASURE_DIVIDER_WIDTH:Float = 2;
|
|
||||||
static final GRID_MEASURE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
|
static final GRID_MEASURE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
|
||||||
|
|
||||||
// Border on the square highlighting selected notes.
|
// Border on the square highlighting selected notes.
|
||||||
|
@ -66,6 +64,12 @@ class ChartEditorThemeHandler
|
||||||
static final PLAYHEAD_BLOCK_BORDER_COLOR:FlxColor = 0xFF9D0011;
|
static final PLAYHEAD_BLOCK_BORDER_COLOR:FlxColor = 0xFF9D0011;
|
||||||
static final PLAYHEAD_BLOCK_FILL_COLOR:FlxColor = 0xFFBD0231;
|
static final PLAYHEAD_BLOCK_FILL_COLOR:FlxColor = 0xFFBD0231;
|
||||||
|
|
||||||
|
static final TOTAL_COLUMN_COUNT:Int = ChartEditorState.STRUMLINE_SIZE * 2 + 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the theme is changed, this function updates all of the UI elements to match the new theme.
|
||||||
|
* @param state The ChartEditorState to update.
|
||||||
|
*/
|
||||||
public static function updateTheme(state:ChartEditorState):Void
|
public static function updateTheme(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
updateBackground(state);
|
updateBackground(state);
|
||||||
|
@ -73,6 +77,10 @@ class ChartEditorThemeHandler
|
||||||
updateSelectionSquare(state);
|
updateSelectionSquare(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the tint of the background sprite to match the current theme.
|
||||||
|
* @param state The ChartEditorState to update.
|
||||||
|
*/
|
||||||
static function updateBackground(state:ChartEditorState):Void
|
static function updateBackground(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.menuBG.color = switch (state.currentTheme)
|
state.menuBG.color = switch (state.currentTheme)
|
||||||
|
@ -85,7 +93,7 @@ class ChartEditorThemeHandler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the checkerboard background image of the chart editor, and adds dividing lines to it.
|
* Builds the checkerboard background image of the chart editor, and adds dividing lines to it.
|
||||||
* @param dark Whether to draw the grid in a dark color instead of a light one.
|
* @param state The ChartEditorState to update.
|
||||||
*/
|
*/
|
||||||
static function updateGridBitmap(state:ChartEditorState):Void
|
static function updateGridBitmap(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
@ -107,8 +115,8 @@ class ChartEditorThemeHandler
|
||||||
|
|
||||||
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
|
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
|
||||||
// This gets reused to fill the screen.
|
// This gets reused to fill the screen.
|
||||||
var gridWidth:Int = Std.int(ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2 + 1));
|
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.stepsPerMeasure);
|
||||||
state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1, gridColor2);
|
state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1, gridColor2);
|
||||||
|
|
||||||
// Selection borders
|
// Selection borders
|
||||||
|
@ -143,7 +151,7 @@ class ChartEditorThemeHandler
|
||||||
selectionBorderColor);
|
selectionBorderColor);
|
||||||
|
|
||||||
// Selection borders across the middle.
|
// Selection borders across the middle.
|
||||||
for (i in 1...(ChartEditorState.STRUMLINE_SIZE * 2 + 1))
|
for (i in 1...TOTAL_COLUMN_COUNT)
|
||||||
{
|
{
|
||||||
state.gridBitmap.fillRect(new Rectangle((ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0,
|
state.gridBitmap.fillRect(new Rectangle((ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0,
|
||||||
ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height),
|
ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height),
|
||||||
|
@ -167,7 +175,7 @@ class ChartEditorThemeHandler
|
||||||
// Divider at top
|
// Divider at top
|
||||||
state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
|
state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
|
||||||
// Divider at bottom
|
// Divider at bottom
|
||||||
var dividerLineBY = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2);
|
var dividerLineBY:Float = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2);
|
||||||
state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
|
state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
|
||||||
|
|
||||||
// Draw dividers between the strumlines.
|
// Draw dividers between the strumlines.
|
||||||
|
@ -180,10 +188,10 @@ class ChartEditorThemeHandler
|
||||||
};
|
};
|
||||||
|
|
||||||
// Divider at 1 * (Strumline Size)
|
// Divider at 1 * (Strumline Size)
|
||||||
var dividerLineAX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
var dividerLineAX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
||||||
state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
||||||
// Divider at 2 * (Strumline Size)
|
// Divider at 2 * (Strumline Size)
|
||||||
var dividerLineBX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
var dividerLineBX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
||||||
state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
||||||
|
|
||||||
if (state.gridTiledSprite != null)
|
if (state.gridTiledSprite != null)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
import haxe.ui.data.ArrayDataSource;
|
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.play.event.SongEvent;
|
import funkin.play.event.SongEvent;
|
||||||
import funkin.play.event.SongEventData;
|
import funkin.play.event.SongEventData;
|
||||||
|
@ -12,15 +11,17 @@ import haxe.ui.components.CheckBox;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
import haxe.ui.components.Label;
|
import haxe.ui.components.Label;
|
||||||
import haxe.ui.components.NumberStepper;
|
import haxe.ui.components.NumberStepper;
|
||||||
import haxe.ui.components.NumberStepper;
|
|
||||||
import haxe.ui.components.Slider;
|
import haxe.ui.components.Slider;
|
||||||
import haxe.ui.components.TextField;
|
import haxe.ui.components.TextField;
|
||||||
import haxe.ui.containers.dialogs.Dialog;
|
|
||||||
import haxe.ui.containers.Box;
|
import haxe.ui.containers.Box;
|
||||||
import haxe.ui.containers.Frame;
|
|
||||||
import haxe.ui.containers.Grid;
|
import haxe.ui.containers.Grid;
|
||||||
import haxe.ui.containers.Group;
|
import haxe.ui.containers.Group;
|
||||||
|
import haxe.ui.containers.VBox;
|
||||||
|
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||||
|
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||||
|
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||||
import haxe.ui.core.Component;
|
import haxe.ui.core.Component;
|
||||||
|
import haxe.ui.data.ArrayDataSource;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,18 +33,26 @@ enum ChartEditorToolMode
|
||||||
Place;
|
Place;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static functions which handle building themed UI elements for a provided ChartEditorState.
|
||||||
|
*/
|
||||||
class ChartEditorToolboxHandler
|
class ChartEditorToolboxHandler
|
||||||
{
|
{
|
||||||
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
||||||
{
|
{
|
||||||
if (shown) showToolbox(state, id);
|
if (shown)
|
||||||
|
{
|
||||||
|
showToolbox(state, id);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
hideToolbox(state, id);
|
hideToolbox(state, id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function showToolbox(state:ChartEditorState, id:String)
|
public static function showToolbox(state:ChartEditorState, id:String):Void
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
var toolbox:CollapsibleDialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
if (toolbox == null) toolbox = initToolbox(state, id);
|
if (toolbox == null) toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
|
@ -59,7 +68,7 @@ class ChartEditorToolboxHandler
|
||||||
|
|
||||||
public static function hideToolbox(state:ChartEditorState, id:String):Void
|
public static function hideToolbox(state:ChartEditorState, id:String):Void
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
var toolbox:CollapsibleDialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
if (toolbox == null) toolbox = initToolbox(state, id);
|
if (toolbox == null) toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
|
@ -73,13 +82,27 @@ class ChartEditorToolboxHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function minimizeToolbox(state:ChartEditorState, id:String):Void {}
|
public static function minimizeToolbox(state:ChartEditorState, id:String):Void
|
||||||
|
|
||||||
public static function maximizeToolbox(state:ChartEditorState, id:String):Void {}
|
|
||||||
|
|
||||||
public static function initToolbox(state:ChartEditorState, id:String):Dialog
|
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = null;
|
var toolbox:CollapsibleDialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
|
if (toolbox == null) return;
|
||||||
|
|
||||||
|
toolbox.minimized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maximizeToolbox(state:ChartEditorState, id:String):Void
|
||||||
|
{
|
||||||
|
var toolbox:CollapsibleDialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
|
if (toolbox == null) return;
|
||||||
|
|
||||||
|
toolbox.minimized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initToolbox(state:ChartEditorState, id:String):CollapsibleDialog
|
||||||
|
{
|
||||||
|
var toolbox:CollapsibleDialog = null;
|
||||||
switch (id)
|
switch (id)
|
||||||
{
|
{
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||||
|
@ -95,9 +118,9 @@ class ChartEditorToolboxHandler
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:
|
||||||
toolbox = buildToolboxCharactersLayout(state);
|
toolbox = buildToolboxCharactersLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||||
toolbox = buildToolboxPlayerPreviewLayout(state);
|
toolbox = null; // buildToolboxPlayerPreviewLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
||||||
toolbox = buildToolboxOpponentPreviewLayout(state);
|
toolbox = null; // buildToolboxOpponentPreviewLayout(state);
|
||||||
default:
|
default:
|
||||||
// This happens if you try to load an unknown layout.
|
// This happens if you try to load an unknown layout.
|
||||||
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
|
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
|
||||||
|
@ -114,9 +137,15 @@ class ChartEditorToolboxHandler
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getToolbox(state:ChartEditorState, id:String):Dialog
|
/**
|
||||||
|
* Retrieve a toolbox by its layout's asset ID.
|
||||||
|
* @param state The ChartEditorState instance.
|
||||||
|
* @param id The asset ID of the toolbox layout.
|
||||||
|
* @return The toolbox.
|
||||||
|
*/
|
||||||
|
public static function getToolbox(state:ChartEditorState, id:String):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
var toolbox:CollapsibleDialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
// Initialize the toolbox without showing it.
|
// Initialize the toolbox without showing it.
|
||||||
if (toolbox == null) toolbox = initToolbox(state, id);
|
if (toolbox == null) toolbox = initToolbox(state, id);
|
||||||
|
@ -124,9 +153,9 @@ class ChartEditorToolboxHandler
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxToolsLayout(state:ChartEditorState):Dialog
|
static function buildToolboxToolsLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -134,15 +163,15 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 50;
|
toolbox.x = 50;
|
||||||
toolbox.y = 50;
|
toolbox.y = 50;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxTools', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxTools', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group);
|
var toolsGroup:Group = toolbox.findComponent('toolboxToolsGroup', Group);
|
||||||
|
|
||||||
if (toolsGroup == null) return null;
|
if (toolsGroup == null) return null;
|
||||||
|
|
||||||
toolsGroup.onChange = (event:UIEvent) -> {
|
toolsGroup.onChange = function(event:UIEvent) {
|
||||||
switch (event.target.id)
|
switch (event.target.id)
|
||||||
{
|
{
|
||||||
case 'toolboxToolsGroupSelect':
|
case 'toolboxToolsGroupSelect':
|
||||||
|
@ -157,9 +186,9 @@ class ChartEditorToolboxHandler
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog
|
static function buildToolboxNoteDataLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -167,16 +196,16 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 75;
|
toolbox.x = 75;
|
||||||
toolbox.y = 100;
|
toolbox.y = 100;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown);
|
var toolboxNotesNoteKind:DropDown = toolbox.findComponent('toolboxNotesNoteKind', DropDown);
|
||||||
var toolboxNotesCustomKindLabel:Label = toolbox.findComponent("toolboxNotesCustomKindLabel", Label);
|
var toolboxNotesCustomKindLabel:Label = toolbox.findComponent('toolboxNotesCustomKindLabel', Label);
|
||||||
var toolboxNotesCustomKind:TextField = toolbox.findComponent("toolboxNotesCustomKind", TextField);
|
var toolboxNotesCustomKind:TextField = toolbox.findComponent('toolboxNotesCustomKind', TextField);
|
||||||
|
|
||||||
toolboxNotesNoteKind.onChange = (event:UIEvent) -> {
|
toolboxNotesNoteKind.onChange = function(event:UIEvent) {
|
||||||
var isCustom = (event.data.id == '~CUSTOM~');
|
var isCustom:Bool = (event.data.id == '~CUSTOM~');
|
||||||
|
|
||||||
if (isCustom)
|
if (isCustom)
|
||||||
{
|
{
|
||||||
|
@ -194,16 +223,16 @@ class ChartEditorToolboxHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toolboxNotesCustomKind.onChange = (event:UIEvent) -> {
|
toolboxNotesCustomKind.onChange = function(event:UIEvent) {
|
||||||
state.selectedNoteKind = toolboxNotesCustomKind.text;
|
state.selectedNoteKind = toolboxNotesCustomKind.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog
|
static function buildToolboxEventDataLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -211,12 +240,12 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 100;
|
toolbox.x = 100;
|
||||||
toolbox.y = 150;
|
toolbox.y = 150;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolboxEventsEventKind:DropDown = toolbox.findComponent("toolboxEventsEventKind", DropDown);
|
var toolboxEventsEventKind:DropDown = toolbox.findComponent('toolboxEventsEventKind', DropDown);
|
||||||
var toolboxEventsDataGrid:Grid = toolbox.findComponent("toolboxEventsDataGrid", Grid);
|
var toolboxEventsDataGrid:Grid = toolbox.findComponent('toolboxEventsDataGrid', Grid);
|
||||||
|
|
||||||
toolboxEventsEventKind.dataSource = new ArrayDataSource();
|
toolboxEventsEventKind.dataSource = new ArrayDataSource();
|
||||||
|
|
||||||
|
@ -227,7 +256,7 @@ class ChartEditorToolboxHandler
|
||||||
toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id});
|
toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id});
|
||||||
}
|
}
|
||||||
|
|
||||||
toolboxEventsEventKind.onChange = (event:UIEvent) -> {
|
toolboxEventsEventKind.onChange = function(event:UIEvent) {
|
||||||
var eventType:String = event.data.value;
|
var eventType:String = event.data.value;
|
||||||
|
|
||||||
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
|
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
|
||||||
|
@ -281,9 +310,9 @@ class ChartEditorToolboxHandler
|
||||||
numberStepper.value = field.defaultValue;
|
numberStepper.value = field.defaultValue;
|
||||||
input = numberStepper;
|
input = numberStepper;
|
||||||
case BOOL:
|
case BOOL:
|
||||||
var checkBox = new CheckBox();
|
var checkBox:CheckBox = new CheckBox();
|
||||||
checkBox.id = field.name;
|
checkBox.id = field.name;
|
||||||
checkBox.selected = field.defaultValue == true;
|
checkBox.selected = field.defaultValue;
|
||||||
input = checkBox;
|
input = checkBox;
|
||||||
case ENUM:
|
case ENUM:
|
||||||
var dropDown:DropDown = new DropDown();
|
var dropDown:DropDown = new DropDown();
|
||||||
|
@ -293,7 +322,7 @@ class ChartEditorToolboxHandler
|
||||||
// Add entries to the dropdown.
|
// Add entries to the dropdown.
|
||||||
for (optionName in field.keys.keys())
|
for (optionName in field.keys.keys())
|
||||||
{
|
{
|
||||||
var optionValue = field.keys.get(optionName);
|
var optionValue:String = field.keys.get(optionName);
|
||||||
trace('$optionName : $optionValue');
|
trace('$optionName : $optionValue');
|
||||||
dropDown.dataSource.add({value: optionValue, text: optionName});
|
dropDown.dataSource.add({value: optionValue, text: optionName});
|
||||||
}
|
}
|
||||||
|
@ -314,7 +343,7 @@ class ChartEditorToolboxHandler
|
||||||
|
|
||||||
target.addComponent(input);
|
target.addComponent(input);
|
||||||
|
|
||||||
input.onChange = (event:UIEvent) -> {
|
input.onChange = function(event:UIEvent) {
|
||||||
trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${event.target.value}');
|
trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${event.target.value}');
|
||||||
|
|
||||||
if (event.target.value == null) state.selectedEventData.remove(event.target.id);
|
if (event.target.value == null) state.selectedEventData.remove(event.target.id);
|
||||||
|
@ -324,9 +353,9 @@ class ChartEditorToolboxHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxDifficultyLayout(state:ChartEditorState):Dialog
|
static function buildToolboxDifficultyLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -334,36 +363,36 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 125;
|
toolbox.x = 125;
|
||||||
toolbox.y = 200;
|
toolbox.y = 200;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:UIEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var difficultyToolboxSaveMetadata:Button = toolbox.findComponent("difficultyToolboxSaveMetadata", Button);
|
var difficultyToolboxSaveMetadata:Button = toolbox.findComponent('difficultyToolboxSaveMetadata', Button);
|
||||||
var difficultyToolboxSaveChart:Button = toolbox.findComponent("difficultyToolboxSaveChart", Button);
|
var difficultyToolboxSaveChart:Button = toolbox.findComponent('difficultyToolboxSaveChart', Button);
|
||||||
var difficultyToolboxSaveAll:Button = toolbox.findComponent("difficultyToolboxSaveAll", Button);
|
var difficultyToolboxSaveAll:Button = toolbox.findComponent('difficultyToolboxSaveAll', Button);
|
||||||
var difficultyToolboxLoadMetadata:Button = toolbox.findComponent("difficultyToolboxLoadMetadata", Button);
|
var difficultyToolboxLoadMetadata:Button = toolbox.findComponent('difficultyToolboxLoadMetadata', Button);
|
||||||
var difficultyToolboxLoadChart:Button = toolbox.findComponent("difficultyToolboxLoadChart", Button);
|
var difficultyToolboxLoadChart:Button = toolbox.findComponent('difficultyToolboxLoadChart', Button);
|
||||||
|
|
||||||
difficultyToolboxSaveMetadata.onClick = (event:UIEvent) -> {
|
difficultyToolboxSaveMetadata.onClick = function(event:UIEvent) {
|
||||||
SongSerializer.exportSongMetadata(state.currentSongMetadata);
|
SongSerializer.exportSongMetadata(state.currentSongMetadata, state.currentSongId);
|
||||||
};
|
};
|
||||||
|
|
||||||
difficultyToolboxSaveChart.onClick = (event:UIEvent) -> {
|
difficultyToolboxSaveChart.onClick = function(event:UIEvent) {
|
||||||
SongSerializer.exportSongChartData(state.currentSongChartData);
|
SongSerializer.exportSongChartData(state.currentSongChartData, state.currentSongId);
|
||||||
};
|
};
|
||||||
|
|
||||||
difficultyToolboxSaveAll.onClick = (event:UIEvent) -> {
|
difficultyToolboxSaveAll.onClick = function(event:UIEvent) {
|
||||||
state.exportAllSongData();
|
state.exportAllSongData();
|
||||||
};
|
};
|
||||||
|
|
||||||
difficultyToolboxLoadMetadata.onClick = (event:UIEvent) -> {
|
difficultyToolboxLoadMetadata.onClick = function(event:UIEvent) {
|
||||||
// Replace metadata for current variation.
|
// Replace metadata for current variation.
|
||||||
SongSerializer.importSongMetadataAsync(function(songMetadata) {
|
SongSerializer.importSongMetadataAsync(function(songMetadata) {
|
||||||
state.currentSongMetadata = songMetadata;
|
state.currentSongMetadata = songMetadata;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
difficultyToolboxLoadChart.onClick = (event:UIEvent) -> {
|
difficultyToolboxLoadChart.onClick = function(event:UIEvent) {
|
||||||
// Replace chart data for current variation.
|
// Replace chart data for current variation.
|
||||||
SongSerializer.importSongChartDataAsync(function(songChartData) {
|
SongSerializer.importSongChartDataAsync(function(songChartData) {
|
||||||
state.currentSongChartData = songChartData;
|
state.currentSongChartData = songChartData;
|
||||||
|
@ -376,9 +405,9 @@ class ChartEditorToolboxHandler
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxMetadataLayout(state:ChartEditorState):Dialog
|
static function buildToolboxMetadataLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -386,13 +415,13 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 150;
|
toolbox.x = 150;
|
||||||
toolbox.y = 250;
|
toolbox.y = 250;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:UIEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputSongName:TextField = toolbox.findComponent('inputSongName', TextField);
|
var inputSongName:TextField = toolbox.findComponent('inputSongName', TextField);
|
||||||
inputSongName.onChange = (event:UIEvent) -> {
|
inputSongName.onChange = function(event:UIEvent) {
|
||||||
var valid = event.target.text != null && event.target.text != "";
|
var valid:Bool = event.target.text != null && event.target.text != '';
|
||||||
|
|
||||||
if (valid)
|
if (valid)
|
||||||
{
|
{
|
||||||
|
@ -404,10 +433,11 @@ class ChartEditorToolboxHandler
|
||||||
state.currentSongMetadata.songName = null;
|
state.currentSongMetadata.songName = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inputSongName.value = state.currentSongMetadata.songName;
|
||||||
|
|
||||||
var inputSongArtist:TextField = toolbox.findComponent('inputSongArtist', TextField);
|
var inputSongArtist:TextField = toolbox.findComponent('inputSongArtist', TextField);
|
||||||
inputSongArtist.onChange = (event:UIEvent) -> {
|
inputSongArtist.onChange = function(event:UIEvent) {
|
||||||
var valid = event.target.text != null && event.target.text != "";
|
var valid:Bool = event.target.text != null && event.target.text != '';
|
||||||
|
|
||||||
if (valid)
|
if (valid)
|
||||||
{
|
{
|
||||||
|
@ -419,28 +449,31 @@ class ChartEditorToolboxHandler
|
||||||
state.currentSongMetadata.artist = null;
|
state.currentSongMetadata.artist = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inputSongArtist.value = state.currentSongMetadata.artist;
|
||||||
|
|
||||||
var inputStage:DropDown = toolbox.findComponent('inputStage', DropDown);
|
var inputStage:DropDown = toolbox.findComponent('inputStage', DropDown);
|
||||||
inputStage.onChange = (event:UIEvent) -> {
|
inputStage.onChange = function(event:UIEvent) {
|
||||||
var valid = event.data != null && event.data.id != null;
|
var valid:Bool = event.data != null && event.data.id != null;
|
||||||
|
|
||||||
if (valid)
|
if (valid)
|
||||||
{
|
{
|
||||||
state.currentSongMetadata.playData.stage = event.data.id;
|
state.currentSongMetadata.playData.stage = event.data.id;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inputStage.value = state.currentSongMetadata.playData.stage;
|
||||||
|
|
||||||
var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown);
|
var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown);
|
||||||
inputNoteSkin.onChange = (event:UIEvent) -> {
|
inputNoteSkin.onChange = function(event:UIEvent) {
|
||||||
if (event.data.id == null) return;
|
if (event.data.id == null) return;
|
||||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||||
};
|
};
|
||||||
|
inputNoteSkin.value = state.currentSongMetadata.playData.noteSkin;
|
||||||
|
|
||||||
var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper);
|
var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper);
|
||||||
inputBPM.onChange = (event:UIEvent) -> {
|
inputBPM.onChange = function(event:UIEvent) {
|
||||||
if (event.value == null || event.value <= 0) return;
|
if (event.value == null || event.value <= 0) return;
|
||||||
|
|
||||||
var timeChanges = state.currentSongMetadata.timeChanges;
|
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||||
if (timeChanges == null || timeChanges.length == 0)
|
if (timeChanges == null || timeChanges.length == 0)
|
||||||
{
|
{
|
||||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||||
|
@ -454,28 +487,30 @@ class ChartEditorToolboxHandler
|
||||||
|
|
||||||
state.currentSongMetadata.timeChanges = timeChanges;
|
state.currentSongMetadata.timeChanges = timeChanges;
|
||||||
};
|
};
|
||||||
|
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
||||||
|
|
||||||
var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider);
|
var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider);
|
||||||
inputScrollSpeed.onChange = (event:UIEvent) -> {
|
inputScrollSpeed.onChange = function(event:UIEvent) {
|
||||||
var valid = event.target.value != null && event.target.value > 0;
|
var valid:Bool = event.target.value != null && event.target.value > 0;
|
||||||
|
|
||||||
if (valid)
|
if (valid)
|
||||||
{
|
{
|
||||||
inputScrollSpeed.removeClass('invalid-value');
|
inputScrollSpeed.removeClass('invalid-value');
|
||||||
state.currentSongChartData.scrollSpeed = event.target.value;
|
state.currentSongChartScrollSpeed = event.target.value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state.currentSongChartData.scrollSpeed = null;
|
state.currentSongChartScrollSpeed = 1.0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inputScrollSpeed.value = state.currentSongChartData.scrollSpeed;
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxCharactersLayout(state:ChartEditorState):Dialog
|
static function buildToolboxCharactersLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -483,16 +518,16 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 175;
|
toolbox.x = 175;
|
||||||
toolbox.y = 300;
|
toolbox.y = 300;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Dialog
|
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -500,23 +535,23 @@ class ChartEditorToolboxHandler
|
||||||
toolbox.x = 200;
|
toolbox.x = 200;
|
||||||
toolbox.y = 350;
|
toolbox.y = 350;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false);
|
state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
||||||
// TODO: We need to implement character swapping in ChartEditorState.
|
// TODO: We need to implement character swapping in ChartEditorState.
|
||||||
charPlayer.loadCharacter('bf');
|
charPlayer.loadCharacter('bf');
|
||||||
// charPlayer.setScale(0.5);
|
charPlayer.characterType = CharacterType.BF;
|
||||||
charPlayer.setCharacterType(CharacterType.BF);
|
|
||||||
charPlayer.flip = true;
|
charPlayer.flip = true;
|
||||||
|
charPlayer.targetScale = 0.5;
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):Dialog
|
static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):CollapsibleDialog
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
if (toolbox == null) return null;
|
||||||
|
|
||||||
|
@ -531,9 +566,9 @@ class ChartEditorToolboxHandler
|
||||||
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
||||||
// TODO: We need to implement character swapping in ChartEditorState.
|
// TODO: We need to implement character swapping in ChartEditorState.
|
||||||
charPlayer.loadCharacter('dad');
|
charPlayer.loadCharacter('dad');
|
||||||
// charPlayer.setScale(0.5);
|
charPlayer.characterType = CharacterType.DAD;
|
||||||
charPlayer.setCharacterType(CharacterType.DAD);
|
|
||||||
charPlayer.flip = false;
|
charPlayer.flip = false;
|
||||||
|
charPlayer.targetScale = 0.5;
|
||||||
|
|
||||||
return toolbox;
|
return toolbox;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
package funkin.ui.haxeui.components;
|
package funkin.ui.haxeui.components;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent;
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||||
import flixel.graphics.frames.FlxFramesCollection;
|
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||||
import flixel.math.FlxRect;
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import haxe.ui.core.IDataComponent;
|
||||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.character.BaseCharacter;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import haxe.ui.containers.Box;
|
import haxe.ui.containers.Box;
|
||||||
import haxe.ui.core.Component;
|
import haxe.ui.core.Component;
|
||||||
import haxe.ui.core.IDataComponent;
|
|
||||||
import haxe.ui.data.DataSource;
|
|
||||||
import haxe.ui.events.AnimationEvent;
|
import haxe.ui.events.AnimationEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
|
||||||
import haxe.ui.geom.Size;
|
import haxe.ui.geom.Size;
|
||||||
import haxe.ui.layouts.DefaultLayout;
|
import haxe.ui.layouts.DefaultLayout;
|
||||||
import haxe.ui.styles.Style;
|
|
||||||
import openfl.Assets;
|
|
||||||
|
|
||||||
private typedef AnimationInfo =
|
typedef AnimationInfo =
|
||||||
{
|
{
|
||||||
var name:String;
|
var name:String;
|
||||||
var prefix:String;
|
var prefix:String;
|
||||||
|
@ -29,6 +23,10 @@ private typedef AnimationInfo =
|
||||||
var flipY:Null<Bool>; // default false
|
var flipY:Null<Bool>; // default false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variant of SparrowPlayer which loads a BaseCharacter instead.
|
||||||
|
* This allows it to play appropriate animations based on song events.
|
||||||
|
*/
|
||||||
@:composite(Layout)
|
@:composite(Layout)
|
||||||
class CharacterPlayer extends Box
|
class CharacterPlayer extends Box
|
||||||
{
|
{
|
||||||
|
@ -37,7 +35,7 @@ class CharacterPlayer extends Box
|
||||||
public function new(?defaultToBf:Bool = true)
|
public function new(?defaultToBf:Bool = true)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this._overrideSkipTransformChildren = false;
|
_overrideSkipTransformChildren = false;
|
||||||
|
|
||||||
if (defaultToBf)
|
if (defaultToBf)
|
||||||
{
|
{
|
||||||
|
@ -45,52 +43,39 @@ class CharacterPlayer extends Box
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _charId:String;
|
|
||||||
|
|
||||||
public var charId(get, set):String;
|
public var charId(get, set):String;
|
||||||
|
|
||||||
function get_charId():String
|
function get_charId():String
|
||||||
{
|
{
|
||||||
return _charId;
|
return character.characterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_charId(value:String):String
|
function set_charId(value:String):String
|
||||||
{
|
{
|
||||||
_charId = value;
|
loadCharacter(value);
|
||||||
loadCharacter(_charId);
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var _redispatchLoaded:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure
|
public var charName(get, null):String;
|
||||||
var _redispatchStart:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure
|
|
||||||
|
|
||||||
public override function onReady()
|
function get_charName():String
|
||||||
{
|
{
|
||||||
super.onReady();
|
return character.characterName;
|
||||||
|
|
||||||
invalidateComponentLayout();
|
|
||||||
|
|
||||||
if (_redispatchLoaded)
|
|
||||||
{
|
|
||||||
_redispatchLoaded = false;
|
|
||||||
dispatch(new AnimationEvent(AnimationEvent.LOADED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_redispatchStart)
|
// possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
|
||||||
{
|
var _redispatchLoaded:Bool = false;
|
||||||
_redispatchStart = false;
|
// possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
|
||||||
dispatch(new AnimationEvent(AnimationEvent.START));
|
var _redispatchStart:Bool = false;
|
||||||
}
|
var _characterLoaded:Bool = false;
|
||||||
|
|
||||||
parentComponent._overrideSkipTransformChildren = false;
|
/**
|
||||||
}
|
* Loads a character by ID.
|
||||||
|
* @param id The ID of the character to load.
|
||||||
public function loadCharacter(id:String)
|
*/
|
||||||
|
public function loadCharacter(id:String):Void
|
||||||
{
|
{
|
||||||
if (id == null)
|
if (id == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
|
@ -99,32 +84,24 @@ class CharacterPlayer extends Box
|
||||||
character = null;
|
character = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newCharacter:BaseCharacter = CharacterDataParser.fetchCharacter(id);
|
// Prevent script issues by fetching with debug=true.
|
||||||
|
var newCharacter:BaseCharacter = CharacterDataParser.fetchCharacter(id, true);
|
||||||
if (newCharacter == null)
|
if (newCharacter == null) return; // Fail if character doesn't exist.
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Assign character.
|
||||||
character = newCharacter;
|
character = newCharacter;
|
||||||
if (_characterType != null)
|
|
||||||
{
|
|
||||||
character.characterType = _characterType;
|
|
||||||
}
|
|
||||||
if (flip)
|
|
||||||
{
|
|
||||||
character.flipX = !character.flipX;
|
|
||||||
}
|
|
||||||
|
|
||||||
character.scale.x *= _scale;
|
// Set character properties.
|
||||||
character.scale.y *= _scale;
|
if (characterType != null) character.characterType = characterType;
|
||||||
|
if (flip) character.flipX = !character.flipX;
|
||||||
|
if (targetScale != 1.0) character.setScale(targetScale);
|
||||||
|
|
||||||
character.animation.callback = function(name:String = "", frameNumber:Int = -1, frameIndex:Int = -1) {
|
character.animation.callback = function(name:String = '', frameNumber:Int = -1, frameIndex:Int = -1) {
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
character.onAnimationFrame(name, frameNumber, frameIndex);
|
character.onAnimationFrame(name, frameNumber, frameIndex);
|
||||||
dispatch(new AnimationEvent(AnimationEvent.FRAME));
|
dispatch(new AnimationEvent(AnimationEvent.FRAME));
|
||||||
};
|
};
|
||||||
character.animation.finishCallback = function(name:String = "") {
|
character.animation.finishCallback = function(name:String = '') {
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
character.onAnimationFinished(name);
|
character.onAnimationFinished(name);
|
||||||
dispatch(new AnimationEvent(AnimationEvent.END));
|
dispatch(new AnimationEvent(AnimationEvent.END));
|
||||||
|
@ -143,28 +120,15 @@ class CharacterPlayer extends Box
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override function repositionChildren()
|
/**
|
||||||
|
* The character type (such as BF, Dad, GF, etc).
|
||||||
|
*/
|
||||||
|
public var characterType(default, set):CharacterType;
|
||||||
|
|
||||||
|
function set_characterType(value:CharacterType):CharacterType
|
||||||
{
|
{
|
||||||
super.repositionChildren();
|
if (character != null) character.characterType = value;
|
||||||
|
return characterType = value;
|
||||||
@:privateAccess
|
|
||||||
var animOffsets = character.animOffsets;
|
|
||||||
|
|
||||||
character.x = this.screenX + ((this.width / 2) - (character.frameWidth / 2));
|
|
||||||
character.x -= animOffsets[0];
|
|
||||||
character.y = this.screenY + ((this.height / 2) - (character.frameHeight / 2));
|
|
||||||
character.y -= animOffsets[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
var _characterType:CharacterType;
|
|
||||||
|
|
||||||
public function setCharacterType(value:CharacterType)
|
|
||||||
{
|
|
||||||
_characterType = value;
|
|
||||||
if (character != null)
|
|
||||||
{
|
|
||||||
character.characterType = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var flip(default, set):Bool;
|
public var flip(default, set):Bool;
|
||||||
|
@ -181,89 +145,133 @@ class CharacterPlayer extends Box
|
||||||
return flip = value;
|
return flip = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var _scale:Float = 1.0;
|
public var targetScale(default, set):Float = 1.0;
|
||||||
|
|
||||||
public function setScale(value)
|
function set_targetScale(value:Float):Float
|
||||||
{
|
{
|
||||||
_scale = value;
|
if (value == targetScale) return value;
|
||||||
|
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
character.scale.x *= _scale;
|
character.setScale(value);
|
||||||
character.scale.y *= _scale;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onUpdate(event:UpdateScriptEvent)
|
return targetScale = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFrame(name:String, frameNumber:Int, frameIndex:Int):Void
|
||||||
|
{
|
||||||
|
dispatch(new AnimationEvent(AnimationEvent.FRAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFinish(name:String):Void
|
||||||
|
{
|
||||||
|
dispatch(new AnimationEvent(AnimationEvent.END));
|
||||||
|
}
|
||||||
|
|
||||||
|
override function repositionChildren():Void
|
||||||
|
{
|
||||||
|
super.repositionChildren();
|
||||||
|
character.x = this.screenX;
|
||||||
|
character.y = this.screenY;
|
||||||
|
|
||||||
|
// Apply animation offsets, so the character is positioned correctly based on the animation.
|
||||||
|
@:privateAccess var animOffsets:Array<Float> = character.animOffsets;
|
||||||
|
|
||||||
|
character.x -= animOffsets[0] * targetScale * (flip ? -1 : 1);
|
||||||
|
character.y -= animOffsets[1] * targetScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an update event is hit in the song.
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
|
public function onUpdate(event:UpdateScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onUpdate(event);
|
if (character != null) character.onUpdate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an beat is hit in the song
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onBeatHit(event);
|
if (character != null) character.onBeatHit(event);
|
||||||
|
|
||||||
this.repositionChildren();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a step is hit in the song
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void
|
public function onStepHit(event:SongTimeScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onStepHit(event);
|
if (character != null) character.onStepHit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note is hit in the song
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void
|
public function onNoteHit(event:NoteScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onNoteHit(event);
|
if (character != null) character.onNoteHit(event);
|
||||||
|
|
||||||
this.repositionChildren();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note is missed in the song
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void
|
public function onNoteMiss(event:NoteScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onNoteMiss(event);
|
if (character != null) character.onNoteMiss(event);
|
||||||
|
|
||||||
this.repositionChildren();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a key is pressed but no note is hit in the song
|
||||||
|
* Used to play character animations.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onNoteGhostMiss(event);
|
if (character != null) character.onNoteGhostMiss(event);
|
||||||
|
|
||||||
this.repositionChildren();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@:access(funkin.ui.haxeui.components.CharacterPlayer)
|
@:access(funkin.ui.haxeui.components.CharacterPlayer)
|
||||||
private class Layout extends DefaultLayout
|
private class Layout extends DefaultLayout
|
||||||
{
|
{
|
||||||
public override function repositionChildren()
|
public override function resizeChildren():Void
|
||||||
{
|
{
|
||||||
var player = cast(_component, CharacterPlayer);
|
super.resizeChildren();
|
||||||
var sprite:BaseCharacter = player.character;
|
|
||||||
if (sprite == null)
|
var player:CharacterPlayer = cast(_component, CharacterPlayer);
|
||||||
|
var character:BaseCharacter = player.character;
|
||||||
|
if (character == null)
|
||||||
{
|
{
|
||||||
return super.repositionChildren();
|
return super.resizeChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
@:privateAccess
|
character.cornerPosition.set(0, 0);
|
||||||
var animOffsets = sprite.animOffsets;
|
// character.setGraphicSize(Std.int(innerWidth), Std.int(innerHeight));
|
||||||
|
|
||||||
sprite.x = _component.screenLeft + ((_component.width / 2) - (sprite.frameWidth / 2));
|
|
||||||
sprite.x += animOffsets[0];
|
|
||||||
sprite.y = _component.screenTop + ((_component.height / 2) - (sprite.frameHeight / 2));
|
|
||||||
sprite.y += animOffsets[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function calcAutoSize(exclusions:Array<Component> = null):Size
|
public override function calcAutoSize(exclusions:Array<Component> = null):Size
|
||||||
{
|
{
|
||||||
var player = cast(_component, CharacterPlayer);
|
var player:CharacterPlayer = cast(_component, CharacterPlayer);
|
||||||
var sprite = player.character;
|
var character:BaseCharacter = player.character;
|
||||||
if (sprite == null)
|
if (character == null)
|
||||||
{
|
{
|
||||||
return super.calcAutoSize(exclusions);
|
return super.calcAutoSize(exclusions);
|
||||||
}
|
}
|
||||||
var size = new Size();
|
var size:Size = new Size();
|
||||||
size.width = sprite.frameWidth + paddingLeft + paddingRight;
|
size.width = character.width + paddingLeft + paddingRight;
|
||||||
size.height = sprite.frameHeight + paddingTop + paddingBottom;
|
size.height = character.height + paddingTop + paddingBottom;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,9 +173,33 @@ class Constants
|
||||||
public static final MS_PER_SEC:Float = 1000;
|
public static final MS_PER_SEC:Float = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of steps in one beat.
|
* The number of microseconds in a millisecond.
|
||||||
*
|
*/
|
||||||
* Each beat represents ONE quarter note, so one step is one sixteenth note!
|
public static final US_PER_MS:Int = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of microseconds in a second.
|
||||||
|
*/
|
||||||
|
public static final US_PER_SEC:Int = US_PER_MS * MS_PER_SEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a microsecond.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_US:Int = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a millisecond.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_MS:Int = NS_PER_US * US_PER_MS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a second.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of steps in a beat.
|
||||||
|
* One step is one 16th note and one beat is one quarter note.
|
||||||
*/
|
*/
|
||||||
public static final STEPS_PER_BEAT:Int = 4;
|
public static final STEPS_PER_BEAT:Int = 4;
|
||||||
|
|
||||||
|
@ -284,6 +308,13 @@ class Constants
|
||||||
*/
|
*/
|
||||||
public static final COUNTDOWN_VOLUME:Float = 0.6;
|
public static final COUNTDOWN_VOLUME:Float = 0.6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The horizontal offset of the strumline from the left edge of the screen.
|
||||||
|
*/
|
||||||
public static final STRUMLINE_X_OFFSET:Float = 48;
|
public static final STRUMLINE_X_OFFSET:Float = 48;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The vertical offset of the strumline from the top edge of the screen.
|
||||||
|
*/
|
||||||
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,9 @@ class FileUtil
|
||||||
?dialogTitle:String):Bool
|
?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = convertTypeFilter(typeFilter);
|
var filter:String = convertTypeFilter(typeFilter);
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
|
@ -54,9 +54,9 @@ class FileUtil
|
||||||
?dialogTitle:String):Bool
|
?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = convertTypeFilter(typeFilter);
|
var filter:String = convertTypeFilter(typeFilter);
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
|
@ -81,9 +81,9 @@ class FileUtil
|
||||||
?dialogTitle:String):Bool
|
?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = convertTypeFilter(typeFilter);
|
var filter:String = convertTypeFilter(typeFilter);
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSelect != null) fileDialog.onSelectMultiple.add(onSelect);
|
if (onSelect != null) fileDialog.onSelectMultiple.add(onSelect);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
|
@ -109,9 +109,9 @@ class FileUtil
|
||||||
?dialogTitle:String):Bool
|
?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = convertTypeFilter(typeFilter);
|
var filter:String = convertTypeFilter(typeFilter);
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
if (onSelect != null) fileDialog.onSelect.add(onSelect);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
|
@ -136,29 +136,29 @@ class FileUtil
|
||||||
public static function openFile(?typeFilter:Array<FileFilter>, ?onOpen:Bytes->Void, ?onCancel:Void->Void, ?defaultPath:String, ?dialogTitle:String):Bool
|
public static function openFile(?typeFilter:Array<FileFilter>, ?onOpen:Bytes->Void, ?onCancel:Void->Void, ?defaultPath:String, ?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = convertTypeFilter(typeFilter);
|
var filter:String = convertTypeFilter(typeFilter);
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onOpen != null) fileDialog.onOpen.add(onOpen);
|
if (onOpen != null) fileDialog.onOpen.add(onOpen);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
fileDialog.open(filter, defaultPath, dialogTitle);
|
fileDialog.open(filter, defaultPath, dialogTitle);
|
||||||
return true;
|
return true;
|
||||||
#elseif html5
|
#elseif html5
|
||||||
var onFileLoaded = function(event) {
|
var onFileLoaded:Event->Void = function(event) {
|
||||||
var loadedFileRef:FileReference = event.target;
|
var loadedFileRef:FileReference = event.target;
|
||||||
trace('Loaded file: ' + loadedFileRef.name);
|
trace('Loaded file: ' + loadedFileRef.name);
|
||||||
onOpen(loadedFileRef.data);
|
onOpen(loadedFileRef.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
var onFileSelected = function(event) {
|
var onFileSelected:Event->Void = function(event) {
|
||||||
var selectedFileRef:FileReference = event.target;
|
var selectedFileRef:FileReference = event.target;
|
||||||
trace('Selected file: ' + selectedFileRef.name);
|
trace('Selected file: ' + selectedFileRef.name);
|
||||||
selectedFileRef.addEventListener(Event.COMPLETE, onFileLoaded);
|
selectedFileRef.addEventListener(Event.COMPLETE, onFileLoaded);
|
||||||
selectedFileRef.load();
|
selectedFileRef.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileRef = new FileReference();
|
var fileRef:FileReference = new FileReference();
|
||||||
fileRef.addEventListener(Event.SELECT, onFileSelected);
|
fileRef.addEventListener(Event.SELECT, onFileSelected);
|
||||||
fileRef.browse(typeFilter);
|
fileRef.browse(typeFilter);
|
||||||
return true;
|
return true;
|
||||||
|
@ -177,18 +177,18 @@ class FileUtil
|
||||||
public static function saveFile(data:Bytes, ?onSave:String->Void, ?onCancel:Void->Void, ?defaultFileName:String, ?dialogTitle:String):Bool
|
public static function saveFile(data:Bytes, ?onSave:String->Void, ?onCancel:Void->Void, ?defaultFileName:String, ?dialogTitle:String):Bool
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
var filter = defaultFileName != null ? Path.extension(defaultFileName) : null;
|
var filter:String = defaultFileName != null ? Path.extension(defaultFileName) : null;
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSave != null) fileDialog.onSelect.add(onSave);
|
if (onSave != null) fileDialog.onSelect.add(onSave);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
fileDialog.save(data, filter, defaultFileName, dialogTitle);
|
fileDialog.save(data, filter, defaultFileName, dialogTitle);
|
||||||
return true;
|
return true;
|
||||||
#elseif html5
|
#elseif html5
|
||||||
var filter = defaultFileName != null ? Path.extension(defaultFileName) : null;
|
var filter:String = defaultFileName != null ? Path.extension(defaultFileName) : null;
|
||||||
|
|
||||||
var fileDialog = new FileDialog();
|
var fileDialog:FileDialog = new FileDialog();
|
||||||
if (onSave != null) fileDialog.onSave.add(onSave);
|
if (onSave != null) fileDialog.onSave.add(onSave);
|
||||||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ class FileUtil
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
// Prompt the user for a directory, then write all of the files to there.
|
// Prompt the user for a directory, then write all of the files to there.
|
||||||
var onSelectDir = function(targetPath:String) {
|
var onSelectDir:String->Void = function(targetPath:String):Void {
|
||||||
var paths:Array<String> = [];
|
var paths:Array<String> = [];
|
||||||
for (resource in resources)
|
for (resource in resources)
|
||||||
{
|
{
|
||||||
|
@ -230,7 +230,7 @@ class FileUtil
|
||||||
writeBytesToPath(filePath, resource.data, force ? Force : Skip);
|
writeBytesToPath(filePath, resource.data, force ? Force : Skip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e:Dynamic)
|
catch (_)
|
||||||
{
|
{
|
||||||
trace('Failed to write file (probably already exists): $filePath' + filePath);
|
trace('Failed to write file (probably already exists): $filePath' + filePath);
|
||||||
continue;
|
continue;
|
||||||
|
@ -240,7 +240,7 @@ class FileUtil
|
||||||
onSaveAll(paths);
|
onSaveAll(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
browseForDirectory(null, onSelectDir, onCancel, defaultPath, "Choose directory to save all files to...");
|
browseForDirectory(null, onSelectDir, onCancel, defaultPath, 'Choose directory to save all files to...');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elseif html5
|
#elseif html5
|
||||||
|
@ -260,14 +260,14 @@ class FileUtil
|
||||||
?force:Bool = false):Bool
|
?force:Bool = false):Bool
|
||||||
{
|
{
|
||||||
// Create a ZIP file.
|
// Create a ZIP file.
|
||||||
var zipBytes = createZIPFromEntries(resources);
|
var zipBytes:Bytes = createZIPFromEntries(resources);
|
||||||
|
|
||||||
var onSave = function(path:String) {
|
var onSave:String->Void = function(path:String) {
|
||||||
onSave([path]);
|
onSave([path]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prompt the user to save the ZIP file.
|
// Prompt the user to save the ZIP file.
|
||||||
saveFile(zipBytes, onSave, onCancel, defaultPath, "Save files as ZIP...");
|
saveFile(zipBytes, onSave, onCancel, defaultPath, 'Save files as ZIP...');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ class FileUtil
|
||||||
{
|
{
|
||||||
#if desktop
|
#if desktop
|
||||||
// Create a ZIP file.
|
// Create a ZIP file.
|
||||||
var zipBytes = createZIPFromEntries(resources);
|
var zipBytes:Bytes = createZIPFromEntries(resources);
|
||||||
|
|
||||||
// Write the ZIP.
|
// Write the ZIP.
|
||||||
writeBytesToPath(path, zipBytes, force ? Force : Skip);
|
writeBytesToPath(path, zipBytes, force ? Force : Skip);
|
||||||
|
@ -293,13 +293,70 @@ class FileUtil
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read string file contents directly from a given path.
|
||||||
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @return The file contents.
|
||||||
|
*/
|
||||||
|
public static function readStringFromPath(path:String):String
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
return sys.io.File.getContent(path);
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes file contents directly from a given path.
|
||||||
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @return The file contents.
|
||||||
|
*/
|
||||||
|
public static function readBytesFromPath(path:String):Bytes
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
return Bytes.ofString(sys.io.File.getContent(path));
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read JSON file contents directly from a given path.
|
||||||
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @return The JSON data.
|
||||||
|
*/
|
||||||
|
public static function readJSONFromPath(path:String):Dynamic
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SerializerUtil.fromJSON(sys.io.File.getContent(path));
|
||||||
|
}
|
||||||
|
catch (ex)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write string file contents directly to a given path.
|
* Write string file contents directly to a given path.
|
||||||
* Only works on desktop.
|
* Only works on desktop.
|
||||||
*
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @param data The string to write.
|
||||||
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
||||||
*/
|
*/
|
||||||
public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip)
|
public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip):Void
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
createDirIfNotExists(Path.directory(path));
|
createDirIfNotExists(Path.directory(path));
|
||||||
|
@ -336,9 +393,11 @@ class FileUtil
|
||||||
* Write byte file contents directly to a given path.
|
* Write byte file contents directly to a given path.
|
||||||
* Only works on desktop.
|
* Only works on desktop.
|
||||||
*
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @param data The bytes to write.
|
||||||
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
* @param mode Whether to Force, Skip, or Ask to overwrite an existing file.
|
||||||
*/
|
*/
|
||||||
public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip)
|
public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip):Void
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
createDirIfNotExists(Path.directory(path));
|
createDirIfNotExists(Path.directory(path));
|
||||||
|
@ -374,8 +433,11 @@ class FileUtil
|
||||||
/**
|
/**
|
||||||
* Write string file contents directly to the end of a file at the given path.
|
* Write string file contents directly to the end of a file at the given path.
|
||||||
* Only works on desktop.
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @param data The string to append.
|
||||||
*/
|
*/
|
||||||
public static function appendStringToPath(path:String, data:String)
|
public static function appendStringToPath(path:String, data:String):Void
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
sys.io.File.append(path, false).writeString(data);
|
sys.io.File.append(path, false).writeString(data);
|
||||||
|
@ -387,8 +449,10 @@ class FileUtil
|
||||||
/**
|
/**
|
||||||
* Create a directory if it doesn't already exist.
|
* Create a directory if it doesn't already exist.
|
||||||
* Only works on desktop.
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @param dir The path to the directory.
|
||||||
*/
|
*/
|
||||||
public static function createDirIfNotExists(dir:String)
|
public static function createDirIfNotExists(dir:String):Void
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
if (!sys.FileSystem.exists(dir))
|
if (!sys.FileSystem.exists(dir))
|
||||||
|
@ -404,6 +468,8 @@ class FileUtil
|
||||||
/**
|
/**
|
||||||
* Get the path to a temporary directory we can use for writing files.
|
* Get the path to a temporary directory we can use for writing files.
|
||||||
* Only works on desktop.
|
* Only works on desktop.
|
||||||
|
*
|
||||||
|
* @return The path to the temporary directory.
|
||||||
*/
|
*/
|
||||||
public static function getTempDir():String
|
public static function getTempDir():String
|
||||||
{
|
{
|
||||||
|
@ -421,9 +487,11 @@ class FileUtil
|
||||||
if (path != null) break;
|
if (path != null) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tempDir = Path.join([path, 'funkin/']);
|
tempDir = Path.join([path, 'funkin/']);
|
||||||
|
return tempDir;
|
||||||
#else
|
#else
|
||||||
return tempDir = '/tmp/funkin/';
|
tempDir = '/tmp/funkin/';
|
||||||
|
return tempDir;
|
||||||
#end
|
#end
|
||||||
#else
|
#else
|
||||||
return null;
|
return null;
|
||||||
|
@ -438,9 +506,9 @@ class FileUtil
|
||||||
*/
|
*/
|
||||||
public static function createZIPFromEntries(entries:Array<Entry>):Bytes
|
public static function createZIPFromEntries(entries:Array<Entry>):Bytes
|
||||||
{
|
{
|
||||||
var o = new haxe.io.BytesOutput();
|
var o:haxe.io.BytesOutput = new haxe.io.BytesOutput();
|
||||||
|
|
||||||
var zipWriter = new haxe.zip.Writer(o);
|
var zipWriter:haxe.zip.Writer = new haxe.zip.Writer(o);
|
||||||
zipWriter.write(entries.list());
|
zipWriter.write(entries.list());
|
||||||
|
|
||||||
return o.getBytes();
|
return o.getBytes();
|
||||||
|
@ -455,8 +523,20 @@ class FileUtil
|
||||||
*/
|
*/
|
||||||
public static function makeZIPEntry(name:String, content:String):Entry
|
public static function makeZIPEntry(name:String, content:String):Entry
|
||||||
{
|
{
|
||||||
var data = haxe.io.Bytes.ofString(content, UTF8);
|
var data:Bytes = haxe.io.Bytes.ofString(content, UTF8);
|
||||||
|
|
||||||
|
return makeZIPEntryFromBytes(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ZIP file entry from a file name and its string contents.
|
||||||
|
*
|
||||||
|
* @param name The name of the file. You can use slashes to create subdirectories.
|
||||||
|
* @param data The byte data of the file.
|
||||||
|
* @return The resulting entry.
|
||||||
|
*/
|
||||||
|
public static function makeZIPEntryFromBytes(name:String, data:haxe.io.Bytes):Entry
|
||||||
|
{
|
||||||
return {
|
return {
|
||||||
fileName: name,
|
fileName: name,
|
||||||
fileSize: data.length,
|
fileSize: data.length,
|
||||||
|
@ -474,15 +554,15 @@ class FileUtil
|
||||||
|
|
||||||
static function convertTypeFilter(typeFilter:Array<FileFilter>):String
|
static function convertTypeFilter(typeFilter:Array<FileFilter>):String
|
||||||
{
|
{
|
||||||
var filter = null;
|
var filter:String = null;
|
||||||
if (typeFilter != null)
|
if (typeFilter != null)
|
||||||
{
|
{
|
||||||
var filters = [];
|
var filters:Array<String> = [];
|
||||||
for (type in typeFilter)
|
for (type in typeFilter)
|
||||||
{
|
{
|
||||||
filters.push(StringTools.replace(StringTools.replace(type.extension, "*.", ""), ";", ","));
|
filters.push(StringTools.replace(StringTools.replace(type.extension, '*.', ''), ';', ','));
|
||||||
}
|
}
|
||||||
filter = filters.join(";");
|
filter = filters.join(';');
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
|
|
|
@ -43,9 +43,18 @@ class SerializerUtil
|
||||||
* Convert a JSON byte array to a Haxe object.
|
* Convert a JSON byte array to a Haxe object.
|
||||||
*/
|
*/
|
||||||
public static function fromJSONBytes(input:Bytes):Dynamic
|
public static function fromJSONBytes(input:Bytes):Dynamic
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return Json.parse(input.toString());
|
return Json.parse(input.toString());
|
||||||
}
|
}
|
||||||
|
catch (e:Dynamic)
|
||||||
|
{
|
||||||
|
trace('An error occurred while parsing JSON from byte data');
|
||||||
|
trace(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize how certain types are serialized when converting to JSON.
|
* Customize how certain types are serialized when converting to JSON.
|
||||||
|
|
|
@ -31,9 +31,12 @@ class SortUtil
|
||||||
/**
|
/**
|
||||||
* Sort predicate for sorting strings alphabetically.
|
* Sort predicate for sorting strings alphabetically.
|
||||||
*/
|
*/
|
||||||
public static function alphabetical(a:String, b:String):Int
|
public static function alphabetically(a:String, b:String)
|
||||||
{
|
{
|
||||||
|
a = a.toUpperCase();
|
||||||
|
b = b.toUpperCase();
|
||||||
|
|
||||||
// Sort alphabetically. Yes that's how this works.
|
// Sort alphabetically. Yes that's how this works.
|
||||||
return a > b ? 1 : -1;
|
return a == b ? 0 : a > b ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue