mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-08-20 07:25:59 +00:00
Merge branch 'rewrite/master' into feature/chart-editor-new-font
This commit is contained in:
commit
cf63f7d6b6
2
.github/actions/setup-haxeshit/action.yml
vendored
2
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -13,7 +13,7 @@ runs:
|
|||
- name: Installing Haxe lol
|
||||
run: |
|
||||
haxe -version
|
||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development
|
||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib version
|
||||
haxelib --global install hmm
|
||||
shell: bash
|
||||
|
|
34
.vscode/settings.json
vendored
34
.vscode/settings.json
vendored
|
@ -71,7 +71,7 @@
|
|||
"files.eol": "\n",
|
||||
|
||||
"haxe.displayPort": "auto",
|
||||
"haxe.enableCompilationServer": true,
|
||||
"haxe.enableCompilationServer": false,
|
||||
"haxe.displayServer": {
|
||||
"arguments": ["-v"]
|
||||
},
|
||||
|
@ -97,15 +97,35 @@
|
|||
"args": ["-debug"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (DEBUG ASSETS)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DDEBUG_ASSETS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (ANIMATE)",
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMATE"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Freeplay)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFREEPLAY"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DCHARTING"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Latency Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DLATENCY"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit ef79a6cf1ae3dcbd86a5b798f8117a6c692c0156
|
||||
Subproject commit be9d790af9c6f1f5e3afc7aed2b1d5c21823bc20
|
|
@ -24,7 +24,7 @@ Example:
|
|||
```
|
||||
/**
|
||||
* Finds the largest deviation from the desired time inside this VoicesGroup.
|
||||
*
|
||||
*
|
||||
* @param targetTime The time to check against.
|
||||
* If none is provided, it checks the time of all members against the first member of this VoicesGroup.
|
||||
* @return The largest deviation from the target time found.
|
||||
|
@ -52,3 +52,10 @@ import sys.io.File;
|
|||
#end
|
||||
```
|
||||
|
||||
## Argument Formatting
|
||||
|
||||
[Optional arguments](https://haxe.org/manual/types-function-optional-arguments.html) and [default arguments](https://haxe.org/manual/types-function-default-values.html) should be mutually exclusive and not used together!
|
||||
|
||||
For example, `myFunction(?input:Int)` should be used if you want the argument to be a `Null<Int>` whose value is `null` if no value is passed, and `myFunction(input:Int = 0)` should be used if you want the argument to be an `Int`, whose value is `0` if no value is passed.
|
||||
|
||||
Using both at the same time is considered valid by Haxe, but `myFunction(?input:Int = 0)` results in a `Null<Int>` whose value defaults to 0 anyway, so it's never null, but it's annotated as nullable! The biggest consequence of this is that it makes null safety more annoying.
|
||||
|
|
|
@ -535,16 +535,19 @@ class FreeplayState extends MusicBeatSubState
|
|||
// this creates a filter to return all the songs that start with a letter between those two
|
||||
var filterRegexp = new EReg("^[" + filterStuff.filterData + "].*", "i");
|
||||
tempSongs = tempSongs.filter(str -> {
|
||||
if (str == null) return true; // Random
|
||||
return filterRegexp.match(str.songName);
|
||||
});
|
||||
case STARTSWITH:
|
||||
tempSongs = tempSongs.filter(str -> {
|
||||
if (str == null) return true; // Random
|
||||
return str.songName.toLowerCase().startsWith(filterStuff.filterData);
|
||||
});
|
||||
case ALL:
|
||||
// no filter!
|
||||
case FAVORITE:
|
||||
tempSongs = tempSongs.filter(str -> {
|
||||
if (str == null) return true; // Random
|
||||
return str.isFav;
|
||||
});
|
||||
default:
|
||||
|
@ -555,7 +558,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var hsvShader:HSVShader = new HSVShader();
|
||||
|
||||
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
randomCapsule.init(FlxG.width, 0, "Random");
|
||||
randomCapsule.init(FlxG.width, 0, null);
|
||||
randomCapsule.onConfirm = function() {
|
||||
capsuleOnConfirmRandom(randomCapsule);
|
||||
};
|
||||
|
@ -574,8 +577,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
|
||||
funnyMenu.init(FlxG.width, 0, tempSongs[i].songName);
|
||||
if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter);
|
||||
funnyMenu.init(FlxG.width, 0, tempSongs[i]);
|
||||
funnyMenu.onConfirm = function() {
|
||||
capsuleOnConfirmDefault(funnyMenu);
|
||||
};
|
||||
|
@ -946,11 +948,29 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
function capsuleOnConfirmRandom(cap:SongMenuItem):Void
|
||||
function capsuleOnConfirmRandom(randomCapsule:SongMenuItem):Void
|
||||
{
|
||||
trace("RANDOM SELECTED");
|
||||
|
||||
busy = true;
|
||||
|
||||
var availableSongCapsules:Array<SongMenuItem> = grpCapsules.members.filter(function(cap:SongMenuItem) {
|
||||
// Dead capsules are ones which were removed from the list when changing filters.
|
||||
return cap.alive && cap.songData != null;
|
||||
});
|
||||
|
||||
trace('Available songs: ${availableSongCapsules.map(function(cap) {
|
||||
return cap.songData.songName;
|
||||
})}');
|
||||
|
||||
var targetSong:SongMenuItem = FlxG.random.getObject(availableSongCapsules);
|
||||
|
||||
// Seeing if I can do an animation...
|
||||
curSelected = grpCapsules.members.indexOf(targetSong);
|
||||
changeSelection(0); // Trigger an update.
|
||||
|
||||
// Act like we hit Confirm on that song.
|
||||
capsuleOnConfirmDefault(targetSong);
|
||||
}
|
||||
|
||||
function capsuleOnConfirmDefault(cap:SongMenuItem):Void
|
||||
|
@ -959,8 +979,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
PlayStatePlaylist.isStoryMode = false;
|
||||
|
||||
var songId:String = cap.songTitle.toLowerCase();
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(cap.songData.songId);
|
||||
var targetDifficulty:String = currentDifficulty;
|
||||
|
||||
// TODO: Implement Pico into the interface properly.
|
||||
|
@ -970,7 +989,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
targetCharacter = 'pico';
|
||||
}
|
||||
|
||||
PlayStatePlaylist.campaignId = songs[curSelected].levelId;
|
||||
PlayStatePlaylist.campaignId = cap.songData.levelId;
|
||||
|
||||
// Visual and audio effects.
|
||||
FlxG.sound.play(Paths.sound('confirmMenu'));
|
||||
|
@ -981,7 +1000,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
targetSong.cacheCharts(true);
|
||||
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
Paths.setCurrentLevel(songs[curSelected].levelId);
|
||||
Paths.setCurrentLevel(cap.songData.levelId);
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
|
@ -1013,16 +1032,21 @@ class FreeplayState extends MusicBeatSubState
|
|||
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
|
||||
// FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName));
|
||||
|
||||
var prevSelected = curSelected;
|
||||
|
||||
curSelected += change;
|
||||
|
||||
if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1;
|
||||
if (curSelected >= grpCapsules.countLiving()) curSelected = 0;
|
||||
|
||||
var daSong = songs[curSelected];
|
||||
if (daSong != null)
|
||||
var daSongCapsule = grpCapsules.members[curSelected];
|
||||
if (daSongCapsule.songData != null)
|
||||
{
|
||||
diffIdsCurrent = daSong.songDifficulties;
|
||||
rememberedSongId = daSong.songId;
|
||||
var songScore:SaveScoreData = Save.get().getSongScore(daSongCapsule.songData.songId, currentDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||
diffIdsCurrent = daSongCapsule.songData.songDifficulties;
|
||||
rememberedSongId = daSongCapsule.songData.songId;
|
||||
changeDiff();
|
||||
}
|
||||
else
|
||||
|
@ -1054,7 +1078,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
// TODO: Try to stream the music?
|
||||
// TODO: Stream the instrumental of the selected song?
|
||||
if (prevSelected == 0)
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
}
|
||||
grpCapsules.members[curSelected].selected = true;
|
||||
}
|
||||
|
|
|
@ -266,6 +266,10 @@ class InitState extends FlxState
|
|||
return;
|
||||
}
|
||||
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
songData.cacheCharts(true);
|
||||
|
||||
LoadingState.loadAndSwitchState(new funkin.play.PlayState(
|
||||
{
|
||||
targetSong: songData,
|
||||
|
|
|
@ -117,7 +117,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
public function stepHit():Bool
|
||||
{
|
||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -128,7 +128,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
public function beatHit():Bool
|
||||
{
|
||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -148,7 +148,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
override function startOutro(onComplete:() -> Void):Void
|
||||
{
|
||||
var event = new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_BEGIN, null, true);
|
||||
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -164,7 +164,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
public override function openSubState(targetSubState:FlxSubState):Void
|
||||
{
|
||||
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_BEGIN, targetSubState, true);
|
||||
var event = new SubStateScriptEvent(SUBSTATE_OPEN_BEGIN, targetSubState, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -175,12 +175,12 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
function onOpenSubStateComplete(targetState:FlxSubState):Void
|
||||
{
|
||||
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_END, targetState, true));
|
||||
dispatchEvent(new SubStateScriptEvent(SUBSTATE_OPEN_END, targetState, true));
|
||||
}
|
||||
|
||||
public override function closeSubState():Void
|
||||
{
|
||||
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_BEGIN, this.subState, true);
|
||||
var event = new SubStateScriptEvent(SUBSTATE_CLOSE_BEGIN, this.subState, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -191,6 +191,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
|
||||
function onCloseSubStateComplete(targetState:FlxSubState):Void
|
||||
{
|
||||
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_END, targetState, true));
|
||||
dispatchEvent(new SubStateScriptEvent(SUBSTATE_CLOSE_END, targetState, true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
|
|||
*/
|
||||
public function stepHit():Bool
|
||||
{
|
||||
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -112,7 +112,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
|
|||
*/
|
||||
public function beatHit():Bool
|
||||
{
|
||||
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
|
|
@ -447,7 +447,7 @@ class SongChartData
|
|||
}
|
||||
}
|
||||
|
||||
class SongEventData
|
||||
class SongEventDataRaw
|
||||
{
|
||||
/**
|
||||
* The timestamp of the event. The timestamp is in the format of the song's time format.
|
||||
|
@ -503,40 +503,57 @@ class SongEventData
|
|||
|
||||
return _stepTime = Conductor.getTimeInSteps(this.time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, event, value, activated, getStepTime)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, event:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, event, value);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return value == null ? null : Reflect.field(value, key);
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBool(key:String):Null<Bool>
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getInt(key:String):Null<Int>
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getFloat(key:String):Null<Float>
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getString(key:String):String
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getArray(key:String):Array<Dynamic>
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBoolArray(key:String):Array<Bool>
|
||||
{
|
||||
return value == null ? null : cast Reflect.field(value, key);
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
return new SongEventData(this.time, this.event, this.value);
|
||||
}
|
||||
|
||||
@:op(A == B)
|
||||
|
@ -584,7 +601,7 @@ class SongEventData
|
|||
}
|
||||
}
|
||||
|
||||
class SongNoteData
|
||||
class SongNoteDataRaw
|
||||
{
|
||||
/**
|
||||
* The timestamp of the note. The timestamp is in the format of the song's time format.
|
||||
|
@ -655,6 +672,48 @@ class SongNoteData
|
|||
return _stepTime = Conductor.getTimeInSteps(this.time);
|
||||
}
|
||||
|
||||
@:jignored
|
||||
var _stepLength:Null<Float> = null;
|
||||
|
||||
/**
|
||||
* @param force Set to `true` to force recalculation (good after BPM changes)
|
||||
* @return The length of the hold note in steps, or `0` if this is not a hold note.
|
||||
*/
|
||||
public function getStepLength(force = false):Float
|
||||
{
|
||||
if (this.length <= 0) return 0.0;
|
||||
|
||||
if (_stepLength != null && !force) return _stepLength;
|
||||
|
||||
return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
|
||||
}
|
||||
|
||||
public function setStepLength(value:Float):Void
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
this.length = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
|
||||
this.length = lengthMs;
|
||||
}
|
||||
_stepLength = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongNoteData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward
|
||||
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
{
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
||||
{
|
||||
this = new SongNoteDataRaw(time, data, length, kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* The direction of the note, if applicable.
|
||||
* Strips the strumline index from the data.
|
||||
|
@ -668,7 +727,12 @@ class SongNoteData
|
|||
|
||||
public function getDirectionName(strumlineSize:Int = 4):String
|
||||
{
|
||||
switch (this.data % strumlineSize)
|
||||
return SongNoteData.buildDirectionName(this.data, strumlineSize);
|
||||
}
|
||||
|
||||
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
|
||||
{
|
||||
switch (data % strumlineSize)
|
||||
{
|
||||
case 0:
|
||||
return 'Left';
|
||||
|
@ -705,36 +769,6 @@ class SongNoteData
|
|||
return getStrumlineIndex(strumlineSize) == 0;
|
||||
}
|
||||
|
||||
@:jignored
|
||||
var _stepLength:Null<Float> = null;
|
||||
|
||||
/**
|
||||
* @param force Set to `true` to force recalculation (good after BPM changes)
|
||||
* @return The length of the hold note in steps, or `0` if this is not a hold note.
|
||||
*/
|
||||
public function getStepLength(force = false):Float
|
||||
{
|
||||
if (this.length <= 0) return 0.0;
|
||||
|
||||
if (_stepLength != null && !force) return _stepLength;
|
||||
|
||||
return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
|
||||
}
|
||||
|
||||
public function setStepLength(value:Float):Void
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
this.length = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
|
||||
this.length = lengthMs;
|
||||
}
|
||||
_stepLength = null;
|
||||
}
|
||||
|
||||
@:jignored
|
||||
public var isHoldNote(get, never):Bool;
|
||||
|
||||
|
@ -797,6 +831,11 @@ class SongNoteData
|
|||
return this.time <= other.time;
|
||||
}
|
||||
|
||||
public function clone():SongNoteData
|
||||
{
|
||||
return new SongNoteData(this.time, this.data, this.length, this.kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a string representation suitable for debugging.
|
||||
*/
|
||||
|
|
|
@ -66,8 +66,14 @@ class SongDataUtils
|
|||
|
||||
var result = notes.filter(function(note:SongNoteData):Bool {
|
||||
for (x in subtrahend)
|
||||
{
|
||||
// The currently iterated note is in the subtrahend array.
|
||||
// SongNoteData's == operation has been overridden so that this will work.
|
||||
if (x == note) return false;
|
||||
if (x == note)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.freeplayStuff;
|
||||
|
||||
import funkin.FreeplayState.FreeplaySongData;
|
||||
import funkin.shaderslmfao.HSVShader;
|
||||
import funkin.shaderslmfao.GaussianBlurShader;
|
||||
import flixel.group.FlxGroup;
|
||||
|
@ -19,9 +20,13 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
var pixelIcon:FlxSprite;
|
||||
|
||||
public var selected(default, set):Bool;
|
||||
/**
|
||||
* Modify this by calling `init()`
|
||||
* If `null`, assume this SongMenuItem is for the "Random Song" option.
|
||||
*/
|
||||
public var songData(default, null):Null<FreeplaySongData> = null;
|
||||
|
||||
public var songTitle:String = "Test";
|
||||
public var selected(default, set):Bool;
|
||||
|
||||
public var songText:CapsuleText;
|
||||
public var favIcon:FlxSprite;
|
||||
|
@ -45,12 +50,10 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
public var hsvShader(default, set):HSVShader;
|
||||
|
||||
public function new(x:Float, y:Float, song:String, ?character:String)
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.songTitle = song;
|
||||
|
||||
capsule = new FlxSprite();
|
||||
capsule.frames = Paths.getSparrowAtlas('freeplay/freeplayCapsule');
|
||||
capsule.animation.addByPrefix('selected', 'mp3 capsule w backing0', 24);
|
||||
|
@ -86,7 +89,7 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
ranking.x -= 10;
|
||||
}
|
||||
|
||||
songText = new CapsuleText(capsule.width * 0.26, 45, songTitle, Std.int(40 * realScaled));
|
||||
songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled));
|
||||
add(songText);
|
||||
grpHide.add(songText);
|
||||
|
||||
|
@ -97,8 +100,6 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
add(pixelIcon);
|
||||
grpHide.add(pixelIcon);
|
||||
|
||||
if (character != null) setCharacter(character);
|
||||
|
||||
favIcon = new FlxSprite(400, 40);
|
||||
favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart');
|
||||
favIcon.animation.addByPrefix('fav', "favorite heart", 24, false);
|
||||
|
@ -144,18 +145,21 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
if (value) textAppear();
|
||||
|
||||
selectedAlpha();
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
public function init(x:Float, y:Float, song:String, ?character:String)
|
||||
public function init(x:Float, y:Float, songData:Null<FreeplaySongData>)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.songTitle = song;
|
||||
songText.text = this.songTitle;
|
||||
if (character != null) setCharacter(character);
|
||||
this.songData = songData;
|
||||
|
||||
selected = selected;
|
||||
// Update capsule text.
|
||||
songText.text = songData?.songName ?? 'Random';
|
||||
// Update capsule character.
|
||||
if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
|
||||
// Update opacity, offsets, etc.
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,6 +173,7 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
trace(char);
|
||||
|
||||
// TODO: Put this in the character metadata where it belongs.
|
||||
switch (char)
|
||||
{
|
||||
case "monster-christmas":
|
||||
|
@ -244,7 +249,7 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
{
|
||||
visible = true;
|
||||
capsule.alpha = 1;
|
||||
selectedAlpha();
|
||||
updateSelected();
|
||||
doLerp = true;
|
||||
doJumpIn = false;
|
||||
doJumpOut = false;
|
||||
|
@ -319,24 +324,22 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
return (index * ((height * realScaled) + 10)) + 120;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merely a helper function to call set_selected, to make sure that the alpha is correct on the rankings/selections
|
||||
*/
|
||||
public function selectedAlpha():Void
|
||||
{
|
||||
selected = selected;
|
||||
}
|
||||
|
||||
function set_selected(value:Bool):Bool
|
||||
{
|
||||
// cute one liners, lol!
|
||||
diffGrayscale.setAmount(value ? 0 : 0.8);
|
||||
songText.alpha = value ? 1 : 0.6;
|
||||
songText.blurredText.visible = value ? true : false;
|
||||
capsule.offset.x = value ? 0 : -5;
|
||||
capsule.animation.play(value ? "selected" : "unselected");
|
||||
ranking.alpha = value ? 1 : 0.7;
|
||||
ranking.color = value ? 0xFFFFFFFF : 0xFFAAAAAA;
|
||||
return value;
|
||||
selected = value;
|
||||
updateSelected();
|
||||
return selected;
|
||||
}
|
||||
|
||||
function updateSelected():Void
|
||||
{
|
||||
diffGrayscale.setAmount(this.selected ? 0 : 0.8);
|
||||
songText.alpha = this.selected ? 1 : 0.6;
|
||||
songText.blurredText.visible = this.selected ? true : false;
|
||||
capsule.offset.x = this.selected ? 0 : -5;
|
||||
capsule.animation.play(this.selected ? "selected" : "unselected");
|
||||
ranking.alpha = this.selected ? 1 : 0.7;
|
||||
ranking.color = this.selected ? 0xFFFFFFFF : 0xFFAAAAAA;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ using Lambda;
|
|||
using StringTools;
|
||||
using funkin.util.tools.ArraySortTools;
|
||||
using funkin.util.tools.ArrayTools;
|
||||
using funkin.util.tools.FloatTools;
|
||||
using funkin.util.tools.Int64Tools;
|
||||
using funkin.util.tools.IntTools;
|
||||
using funkin.util.tools.IteratorTools;
|
||||
using funkin.util.tools.MapTools;
|
||||
using funkin.util.tools.SongEventDataArrayTools;
|
||||
|
|
|
@ -10,265 +10,12 @@ import funkin.play.notes.NoteDirection;
|
|||
import openfl.events.EventType;
|
||||
import openfl.events.KeyboardEvent;
|
||||
|
||||
typedef ScriptEventType = EventType<ScriptEvent>;
|
||||
|
||||
/**
|
||||
* This is a base class for all events that are issued to scripted classes.
|
||||
* It can be used to identify the type of event called, store data, and cancel event propagation.
|
||||
*/
|
||||
class ScriptEvent
|
||||
{
|
||||
/**
|
||||
* Called when the relevant object is created.
|
||||
* Keep in mind that the constructor may be called before the object is needed,
|
||||
* for the purposes of caching data or otherwise.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final CREATE:ScriptEventType = 'CREATE';
|
||||
|
||||
/**
|
||||
* Called when the relevant object is destroyed.
|
||||
* This should perform relevant cleanup to ensure good performance.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final DESTROY:ScriptEventType = 'DESTROY';
|
||||
|
||||
/**
|
||||
* Called when the relevent object is added to the game state.
|
||||
* This assumes all data is loaded and ready to go.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final ADDED:ScriptEventType = 'ADDED';
|
||||
|
||||
/**
|
||||
* Called during the update function.
|
||||
* This is called every frame, so be careful!
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final UPDATE:ScriptEventType = 'UPDATE';
|
||||
|
||||
/**
|
||||
* Called when the player moves to pause the game.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
||||
*/
|
||||
public static inline final PAUSE:ScriptEventType = 'PAUSE';
|
||||
|
||||
/**
|
||||
* Called when the player moves to unpause the game while paused.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
||||
*/
|
||||
public static inline final RESUME:ScriptEventType = 'RESUME';
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 4 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT';
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 16 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT';
|
||||
|
||||
/**
|
||||
* Called when a character hits a note.
|
||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
||||
* and will likely result in a miss later.
|
||||
*/
|
||||
public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT';
|
||||
|
||||
/**
|
||||
* Called when a character misses a note.
|
||||
* Important information such as note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding a combo break and lost health.
|
||||
*/
|
||||
public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS';
|
||||
|
||||
/**
|
||||
* Called when a character presses a note when there was none there, causing them to lose health.
|
||||
* Important information such as direction pressed, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding lost health/score and preventing the miss animation.
|
||||
*/
|
||||
public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS';
|
||||
|
||||
/**
|
||||
* Called when a song event is reached in the chart.
|
||||
*
|
||||
* This event IS cancelable! Cancelling this event prevents the event from being triggered,
|
||||
* thus blocking its normal functionality.
|
||||
*/
|
||||
public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT';
|
||||
|
||||
/**
|
||||
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_START:ScriptEventType = 'SONG_START';
|
||||
|
||||
/**
|
||||
* Called when the song ends. This happens as the instrumental and vocals end.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_END:ScriptEventType = 'SONG_END';
|
||||
|
||||
/**
|
||||
* Called when the countdown begins. This occurs before the song starts.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
|
||||
* - The song will not start until you call Countdown.performCountdown() later.
|
||||
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
|
||||
*/
|
||||
public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START';
|
||||
|
||||
/**
|
||||
* Called when a step of the countdown happens.
|
||||
* Includes information about what step of the countdown was hit.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will pause the countdown.
|
||||
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
||||
*/
|
||||
public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP';
|
||||
|
||||
/**
|
||||
* Called when the countdown is done but just before the song starts.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END';
|
||||
|
||||
/**
|
||||
* Called before the game over screen triggers and the death animation plays.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER';
|
||||
|
||||
/**
|
||||
* Called after the player presses a key to restart the game.
|
||||
* This can happen from the pause menu or the game over screen.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||
*/
|
||||
public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY';
|
||||
|
||||
/**
|
||||
* Called when the player pushes down any key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN';
|
||||
|
||||
/**
|
||||
* Called when the player releases a key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_UP:ScriptEventType = 'KEY_UP';
|
||||
|
||||
/**
|
||||
* Called when the game has finished loading the notes from JSON.
|
||||
* This allows modders to mutate the notes before they are used in the song.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED';
|
||||
|
||||
/**
|
||||
* Called when the game is about to switch the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished switching the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END';
|
||||
|
||||
/**
|
||||
* Called when the game is about to open a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished opening a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END';
|
||||
|
||||
/**
|
||||
* Called when the game is about to close the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished closing the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END';
|
||||
|
||||
/**
|
||||
* Called when the game starts a conversation.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START';
|
||||
|
||||
/**
|
||||
* Called to display the next line of conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line.
|
||||
* - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation.
|
||||
*/
|
||||
public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE';
|
||||
|
||||
/**
|
||||
* Called to skip scrolling the current line of conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line.
|
||||
* - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing.
|
||||
*/
|
||||
public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE';
|
||||
|
||||
/**
|
||||
* Called to skip the conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from skipping.
|
||||
*/
|
||||
public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP';
|
||||
|
||||
/**
|
||||
* Called when the game ends a conversation.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END';
|
||||
|
||||
/**
|
||||
* If true, the behavior associated with this event can be prevented.
|
||||
* For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
|
||||
|
@ -411,7 +158,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent
|
|||
|
||||
public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
|
||||
{
|
||||
super(ScriptEvent.NOTE_GHOST_MISS, true);
|
||||
super(NOTE_GHOST_MISS, true);
|
||||
this.dir = dir;
|
||||
this.hasPossibleNotes = hasPossibleNotes;
|
||||
this.healthChange = healthChange;
|
||||
|
@ -439,7 +186,7 @@ class SongEventScriptEvent extends ScriptEvent
|
|||
|
||||
public function new(event:funkin.data.song.SongData.SongEventData):Void
|
||||
{
|
||||
super(ScriptEvent.SONG_EVENT, true);
|
||||
super(SONG_EVENT, true);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
|
@ -462,7 +209,7 @@ class UpdateScriptEvent extends ScriptEvent
|
|||
|
||||
public function new(elapsed:Float):Void
|
||||
{
|
||||
super(ScriptEvent.UPDATE, false);
|
||||
super(UPDATE, false);
|
||||
this.elapsed = elapsed;
|
||||
}
|
||||
|
||||
|
@ -591,7 +338,7 @@ class SongLoadScriptEvent extends ScriptEvent
|
|||
|
||||
public function new(id:String, difficulty:String, notes:Array<SongNoteData>):Void
|
||||
{
|
||||
super(ScriptEvent.SONG_LOADED, false);
|
||||
super(SONG_LOADED, false);
|
||||
this.id = id;
|
||||
this.difficulty = difficulty;
|
||||
this.notes = notes;
|
||||
|
@ -660,7 +407,7 @@ class PauseScriptEvent extends ScriptEvent
|
|||
|
||||
public function new(gitaroo:Bool):Void
|
||||
{
|
||||
super(ScriptEvent.PAUSE, true);
|
||||
super(PAUSE, true);
|
||||
this.gitaroo = gitaroo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,16 @@ class ScriptEventDispatcher
|
|||
// IScriptedClass
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.CREATE:
|
||||
case CREATE:
|
||||
target.onCreate(event);
|
||||
return;
|
||||
case ScriptEvent.DESTROY:
|
||||
case DESTROY:
|
||||
target.onDestroy(event);
|
||||
return;
|
||||
case ScriptEvent.UPDATE:
|
||||
case UPDATE:
|
||||
target.onUpdate(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IStateStageProp))
|
||||
|
@ -39,9 +40,10 @@ class ScriptEventDispatcher
|
|||
var t:IStateStageProp = cast(target, IStateStageProp);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.ADDED:
|
||||
case ADDED:
|
||||
t.onAdd(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,21 +52,22 @@ class ScriptEventDispatcher
|
|||
var t:IDialogueScriptedClass = cast(target, IDialogueScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.DIALOGUE_START:
|
||||
case DIALOGUE_START:
|
||||
t.onDialogueStart(cast event);
|
||||
return;
|
||||
case ScriptEvent.DIALOGUE_LINE:
|
||||
case DIALOGUE_LINE:
|
||||
t.onDialogueLine(cast event);
|
||||
return;
|
||||
case ScriptEvent.DIALOGUE_COMPLETE_LINE:
|
||||
case DIALOGUE_COMPLETE_LINE:
|
||||
t.onDialogueCompleteLine(cast event);
|
||||
return;
|
||||
case ScriptEvent.DIALOGUE_SKIP:
|
||||
case DIALOGUE_SKIP:
|
||||
t.onDialogueSkip(cast event);
|
||||
return;
|
||||
case ScriptEvent.DIALOGUE_END:
|
||||
case DIALOGUE_END:
|
||||
t.onDialogueEnd(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,54 +76,55 @@ class ScriptEventDispatcher
|
|||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.NOTE_HIT:
|
||||
case NOTE_HIT:
|
||||
t.onNoteHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.NOTE_MISS:
|
||||
case NOTE_MISS:
|
||||
t.onNoteMiss(cast event);
|
||||
return;
|
||||
case ScriptEvent.NOTE_GHOST_MISS:
|
||||
case NOTE_GHOST_MISS:
|
||||
t.onNoteGhostMiss(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_BEAT_HIT:
|
||||
case SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_STEP_HIT:
|
||||
case SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_START:
|
||||
case SONG_START:
|
||||
t.onSongStart(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_END:
|
||||
case SONG_END:
|
||||
t.onSongEnd(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_RETRY:
|
||||
case SONG_RETRY:
|
||||
t.onSongRetry(event);
|
||||
return;
|
||||
case ScriptEvent.GAME_OVER:
|
||||
case GAME_OVER:
|
||||
t.onGameOver(event);
|
||||
return;
|
||||
case ScriptEvent.PAUSE:
|
||||
case PAUSE:
|
||||
t.onPause(cast event);
|
||||
return;
|
||||
case ScriptEvent.RESUME:
|
||||
case RESUME:
|
||||
t.onResume(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_EVENT:
|
||||
case SONG_EVENT:
|
||||
t.onSongEvent(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_START:
|
||||
case COUNTDOWN_START:
|
||||
t.onCountdownStart(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_STEP:
|
||||
case COUNTDOWN_STEP:
|
||||
t.onCountdownStep(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_END:
|
||||
case COUNTDOWN_END:
|
||||
t.onCountdownEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_LOADED:
|
||||
case SONG_LOADED:
|
||||
t.onSongLoaded(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,24 +133,25 @@ class ScriptEventDispatcher
|
|||
var t = cast(target, IStateChangingScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.STATE_CHANGE_BEGIN:
|
||||
case STATE_CHANGE_BEGIN:
|
||||
t.onStateChangeBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.STATE_CHANGE_END:
|
||||
case STATE_CHANGE_END:
|
||||
t.onStateChangeEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_OPEN_BEGIN:
|
||||
case SUBSTATE_OPEN_BEGIN:
|
||||
t.onSubStateOpenBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_OPEN_END:
|
||||
case SUBSTATE_OPEN_END:
|
||||
t.onSubStateOpenEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_CLOSE_BEGIN:
|
||||
case SUBSTATE_CLOSE_BEGIN:
|
||||
t.onSubStateCloseBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_CLOSE_END:
|
||||
case SUBSTATE_CLOSE_END:
|
||||
t.onSubStateCloseEnd(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
271
source/funkin/modding/events/ScriptEventType.hx
Normal file
271
source/funkin/modding/events/ScriptEventType.hx
Normal file
|
@ -0,0 +1,271 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
enum abstract ScriptEventType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* Called when the relevant object is created.
|
||||
* Keep in mind that the constructor may be called before the object is needed,
|
||||
* for the purposes of caching data or otherwise.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var CREATE = 'CREATE';
|
||||
|
||||
/**
|
||||
* Called when the relevant object is destroyed.
|
||||
* This should perform relevant cleanup to ensure good performance.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var DESTROY = 'DESTROY';
|
||||
|
||||
/**
|
||||
* Called when the relevent object is added to the game state.
|
||||
* This assumes all data is loaded and ready to go.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var ADDED = 'ADDED';
|
||||
|
||||
/**
|
||||
* Called during the update function.
|
||||
* This is called every frame, so be careful!
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var UPDATE = 'UPDATE';
|
||||
|
||||
/**
|
||||
* Called when the player moves to pause the game.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
||||
*/
|
||||
var PAUSE = 'PAUSE';
|
||||
|
||||
/**
|
||||
* Called when the player moves to unpause the game while paused.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
||||
*/
|
||||
var RESUME = 'RESUME';
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 4 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SONG_BEAT_HIT = 'BEAT_HIT';
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 16 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SONG_STEP_HIT = 'STEP_HIT';
|
||||
|
||||
/**
|
||||
* Called when a character hits a note.
|
||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
||||
* and will likely result in a miss later.
|
||||
*/
|
||||
var NOTE_HIT = 'NOTE_HIT';
|
||||
|
||||
/**
|
||||
* Called when a character misses a note.
|
||||
* Important information such as note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding a combo break and lost health.
|
||||
*/
|
||||
var NOTE_MISS = 'NOTE_MISS';
|
||||
|
||||
/**
|
||||
* Called when a character presses a note when there was none there, causing them to lose health.
|
||||
* Important information such as direction pressed, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding lost health/score and preventing the miss animation.
|
||||
*/
|
||||
var NOTE_GHOST_MISS = 'NOTE_GHOST_MISS';
|
||||
|
||||
/**
|
||||
* Called when a song event is reached in the chart.
|
||||
*
|
||||
* This event IS cancelable! Cancelling this event prevents the event from being triggered,
|
||||
* thus blocking its normal functionality.
|
||||
*/
|
||||
var SONG_EVENT = 'SONG_EVENT';
|
||||
|
||||
/**
|
||||
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SONG_START = 'SONG_START';
|
||||
|
||||
/**
|
||||
* Called when the song ends. This happens as the instrumental and vocals end.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SONG_END = 'SONG_END';
|
||||
|
||||
/**
|
||||
* Called when the countdown begins. This occurs before the song starts.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
|
||||
* - The song will not start until you call Countdown.performCountdown() later.
|
||||
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
|
||||
*/
|
||||
var COUNTDOWN_START = 'COUNTDOWN_START';
|
||||
|
||||
/**
|
||||
* Called when a step of the countdown happens.
|
||||
* Includes information about what step of the countdown was hit.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will pause the countdown.
|
||||
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
||||
*/
|
||||
var COUNTDOWN_STEP = 'COUNTDOWN_STEP';
|
||||
|
||||
/**
|
||||
* Called when the countdown is done but just before the song starts.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var COUNTDOWN_END = 'COUNTDOWN_END';
|
||||
|
||||
/**
|
||||
* Called before the game over screen triggers and the death animation plays.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var GAME_OVER = 'GAME_OVER';
|
||||
|
||||
/**
|
||||
* Called after the player presses a key to restart the game.
|
||||
* This can happen from the pause menu or the game over screen.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||
*/
|
||||
var SONG_RETRY = 'SONG_RETRY';
|
||||
|
||||
/**
|
||||
* Called when the player pushes down any key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var KEY_DOWN = 'KEY_DOWN';
|
||||
|
||||
/**
|
||||
* Called when the player releases a key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var KEY_UP = 'KEY_UP';
|
||||
|
||||
/**
|
||||
* Called when the game has finished loading the notes from JSON.
|
||||
* This allows modders to mutate the notes before they are used in the song.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SONG_LOADED = 'SONG_LOADED';
|
||||
|
||||
/**
|
||||
* Called when the game is about to switch the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var STATE_CHANGE_BEGIN = 'STATE_CHANGE_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished switching the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var STATE_CHANGE_END = 'STATE_CHANGE_END';
|
||||
|
||||
/**
|
||||
* Called when the game is about to open a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SUBSTATE_OPEN_BEGIN = 'SUBSTATE_OPEN_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished opening a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SUBSTATE_OPEN_END = 'SUBSTATE_OPEN_END';
|
||||
|
||||
/**
|
||||
* Called when the game is about to close the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SUBSTATE_CLOSE_BEGIN = 'SUBSTATE_CLOSE_BEGIN';
|
||||
|
||||
/**
|
||||
* Called when the game has finished closing the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var SUBSTATE_CLOSE_END = 'SUBSTATE_CLOSE_END';
|
||||
|
||||
/**
|
||||
* Called when the game starts a conversation.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var DIALOGUE_START = 'DIALOGUE_START';
|
||||
|
||||
/**
|
||||
* Called to display the next line of conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line.
|
||||
* - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation.
|
||||
*/
|
||||
var DIALOGUE_LINE = 'DIALOGUE_LINE';
|
||||
|
||||
/**
|
||||
* Called to skip scrolling the current line of conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line.
|
||||
* - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing.
|
||||
*/
|
||||
var DIALOGUE_COMPLETE_LINE = 'DIALOGUE_COMPLETE_LINE';
|
||||
|
||||
/**
|
||||
* Called to skip the conversation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the conversation from skipping.
|
||||
*/
|
||||
var DIALOGUE_SKIP = 'DIALOGUE_SKIP';
|
||||
|
||||
/**
|
||||
* Called when the game ends a conversation.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var DIALOGUE_END = 'DIALOGUE_END';
|
||||
|
||||
/**
|
||||
* Allow for comparing `ScriptEventType` to `String`.
|
||||
*/
|
||||
@:op(A == B) private static inline function equals(a:ScriptEventType, b:String):Bool
|
||||
{
|
||||
return (a : String) == b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for comparing `ScriptEventType` to `String`.
|
||||
*/
|
||||
@:op(A != B) private static inline function notEquals(a:ScriptEventType, b:String):Bool
|
||||
{
|
||||
return (a : String) != b;
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ class ModuleHandler
|
|||
|
||||
static function onStateSwitchComplete():Void
|
||||
{
|
||||
callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true));
|
||||
callEvent(new StateChangeScriptEvent(STATE_CHANGE_END, FlxG.state, true));
|
||||
}
|
||||
|
||||
static function addToModuleCache(module:Module):Void
|
||||
|
@ -119,7 +119,7 @@ class ModuleHandler
|
|||
{
|
||||
if (moduleCache != null)
|
||||
{
|
||||
var event = new ScriptEvent(ScriptEvent.DESTROY, false);
|
||||
var event = new ScriptEvent(DESTROY, false);
|
||||
|
||||
// Note: Ignore stopPropagation()
|
||||
for (key => value in moduleCache)
|
||||
|
@ -148,6 +148,6 @@ class ModuleHandler
|
|||
|
||||
public static inline function callOnCreate():Void
|
||||
{
|
||||
callEvent(new ScriptEvent(ScriptEvent.CREATE, false));
|
||||
callEvent(new ScriptEvent(CREATE, false));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class Countdown
|
|||
Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
|
||||
// Handle onBeatHit events manually
|
||||
// @:privateAccess
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// The timer function gets called based on the beat of the song.
|
||||
countdownTimer = new FlxTimer();
|
||||
|
@ -59,7 +59,7 @@ class Countdown
|
|||
|
||||
// onBeatHit events are now properly dispatched by the Conductor even at negative timestamps,
|
||||
// so calling this is no longer necessary.
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
|
@ -94,11 +94,11 @@ class Countdown
|
|||
switch (index)
|
||||
{
|
||||
case BEFORE:
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_START, index);
|
||||
event = new CountdownScriptEvent(COUNTDOWN_START, index);
|
||||
case THREE | TWO | ONE | GO: // I didn't know you could use `|` in a switch/case block!
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_STEP, index);
|
||||
event = new CountdownScriptEvent(COUNTDOWN_STEP, index);
|
||||
case AFTER:
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_END, index, false);
|
||||
event = new CountdownScriptEvent(COUNTDOWN_END, index, false);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -682,7 +682,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (!assertChartExists()) return;
|
||||
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_RETRY));
|
||||
dispatchEvent(new ScriptEvent(SONG_RETRY));
|
||||
|
||||
resetCamera();
|
||||
|
||||
|
@ -867,7 +867,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
deathCounter += 1;
|
||||
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.GAME_OVER));
|
||||
dispatchEvent(new ScriptEvent(GAME_OVER));
|
||||
|
||||
// Disable updates, preventing animations in the background from playing.
|
||||
persistentUpdate = false;
|
||||
|
@ -994,7 +994,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (Std.isOfType(subState, PauseSubState))
|
||||
{
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true);
|
||||
var event:ScriptEvent = new ScriptEvent(RESUME, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
|
@ -1097,7 +1097,7 @@ class PlayState extends MusicBeatSubState
|
|||
if (this.currentStage != null)
|
||||
{
|
||||
remove(currentStage);
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.DESTROY, false);
|
||||
var event:ScriptEvent = new ScriptEvent(DESTROY, false);
|
||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||
currentStage = null;
|
||||
}
|
||||
|
@ -1116,7 +1116,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
super.debug_refreshModules();
|
||||
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
|
||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||
ScriptEventDispatcher.callEvent(currentSong, event);
|
||||
}
|
||||
|
||||
|
@ -1332,7 +1332,7 @@ class PlayState extends MusicBeatSubState
|
|||
if (currentStage != null)
|
||||
{
|
||||
// Actually create and position the sprites.
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
|
||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||
|
||||
// Apply camera zoom level from stage data.
|
||||
|
@ -1640,7 +1640,7 @@ class PlayState extends MusicBeatSubState
|
|||
add(currentConversation);
|
||||
refresh();
|
||||
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
|
||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||
ScriptEventDispatcher.callEvent(currentConversation, event);
|
||||
}
|
||||
|
||||
|
@ -1664,7 +1664,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function startSong():Void
|
||||
{
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_START));
|
||||
dispatchEvent(new ScriptEvent(SONG_START));
|
||||
|
||||
startingSong = false;
|
||||
|
||||
|
@ -1783,7 +1783,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Call an event to allow canceling the note hit.
|
||||
// NOTE: This is what handles the character animations!
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, 0, true);
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, 0, true);
|
||||
dispatchEvent(event);
|
||||
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
|
@ -1872,7 +1872,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
// Call an event to allow canceling the note miss.
|
||||
// NOTE: This is what handles the character animations!
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, 0, true);
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, 0, true);
|
||||
dispatchEvent(event);
|
||||
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
|
@ -2021,7 +2021,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void
|
||||
{
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true);
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, Highscore.tallies.combo + 1, true);
|
||||
dispatchEvent(event);
|
||||
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
|
@ -2053,7 +2053,7 @@ class PlayState extends MusicBeatSubState
|
|||
// a MISS is when you let a note scroll past you!!
|
||||
Highscore.tallies.missed++;
|
||||
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, Highscore.tallies.combo, true);
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true);
|
||||
dispatchEvent(event);
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
if (event.eventCanceled) return;
|
||||
|
@ -2385,7 +2385,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function endSong():Void
|
||||
{
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_END));
|
||||
dispatchEvent(new ScriptEvent(SONG_END));
|
||||
|
||||
#if sys
|
||||
// spitter for ravy, teehee!!
|
||||
|
@ -2593,7 +2593,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
remove(currentStage);
|
||||
currentStage.kill();
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.DESTROY, false));
|
||||
dispatchEvent(new ScriptEvent(DESTROY, false));
|
||||
currentStage = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ class CharacterDataParser
|
|||
char.debug = debug;
|
||||
|
||||
// Call onCreate only in the fetchCharacter() function, not at application initialization.
|
||||
ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
|
||||
ScriptEventDispatcher.callEvent(char, new ScriptEvent(CREATE));
|
||||
|
||||
return char;
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
this.alpha = 1.0;
|
||||
|
||||
// Start the dialogue.
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, false));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false));
|
||||
}
|
||||
|
||||
function setupMusic():Void
|
||||
|
@ -214,7 +214,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
return;
|
||||
}
|
||||
|
||||
ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(ScriptEvent.CREATE, true));
|
||||
ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true));
|
||||
|
||||
currentSpeaker = nextSpeaker;
|
||||
currentSpeaker.zIndex = 200;
|
||||
|
@ -258,7 +258,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
return;
|
||||
}
|
||||
|
||||
ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(ScriptEvent.CREATE, true));
|
||||
ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true));
|
||||
|
||||
currentDialogueBox = nextDialogueBox;
|
||||
currentDialogueBox.zIndex = 300;
|
||||
|
@ -293,7 +293,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
public function startConversation():Void
|
||||
{
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,13 +308,13 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
switch (state)
|
||||
{
|
||||
case ConversationState.Start:
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true));
|
||||
case ConversationState.Opening:
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true));
|
||||
case ConversationState.Speaking:
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true));
|
||||
case ConversationState.Idle:
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_LINE, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_LINE, this, true));
|
||||
case ConversationState.Ending:
|
||||
// Skip the outro.
|
||||
endOutro();
|
||||
|
@ -371,7 +371,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
*/
|
||||
public function skipConversation():Void
|
||||
{
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_SKIP, this, true));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true));
|
||||
}
|
||||
|
||||
static var outroTween:FlxTween;
|
||||
|
@ -405,7 +405,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
public function endOutro():Void
|
||||
{
|
||||
outroTween = null;
|
||||
ScriptEventDispatcher.callEvent(this, new ScriptEvent(ScriptEvent.DESTROY, false));
|
||||
ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,7 +445,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
if (currentDialogueEntry >= currentDialogueEntryCount)
|
||||
{
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -485,7 +485,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
propagateEvent(event);
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false));
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false));
|
||||
}
|
||||
|
||||
public function onDialogueEnd(event:DialogueScriptEvent):Void
|
||||
|
|
|
@ -30,7 +30,7 @@ class ConversationDebugState extends MusicBeatState
|
|||
conversation.completeCallback = onConversationComplete;
|
||||
add(conversation);
|
||||
|
||||
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(ScriptEvent.CREATE, false));
|
||||
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
|
||||
}
|
||||
|
||||
function onConversationComplete():Void
|
||||
|
|
|
@ -47,8 +47,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*/
|
||||
public final _data:Null<SongMetadata>;
|
||||
|
||||
final _metadata:Array<SongMetadata>;
|
||||
|
||||
// key = variation id, value = metadata
|
||||
final _metadata:Map<String, SongMetadata>;
|
||||
final variations:Array<String>;
|
||||
final difficulties:Map<String, SongDifficulty>;
|
||||
|
||||
|
@ -62,7 +62,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
function get_songName():String
|
||||
{
|
||||
if (_data != null) return _data?.songName ?? DEFAULT_SONGNAME;
|
||||
if (_metadata.length > 0) return _metadata[0]?.songName ?? DEFAULT_SONGNAME;
|
||||
if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.songName ?? DEFAULT_SONGNAME;
|
||||
return DEFAULT_SONGNAME;
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
function get_songArtist():String
|
||||
{
|
||||
if (_data != null) return _data?.artist ?? DEFAULT_ARTIST;
|
||||
if (_metadata.length > 0) return _metadata[0]?.artist ?? DEFAULT_ARTIST;
|
||||
if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.artist ?? DEFAULT_ARTIST;
|
||||
return DEFAULT_ARTIST;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
|
||||
_data = _fetchData(id);
|
||||
|
||||
_metadata = _data == null ? [] : [_data];
|
||||
_metadata = _data == null ? [] : [Constants.DEFAULT_VARIATION => _data];
|
||||
|
||||
variations.clear();
|
||||
variations.push(Constants.DEFAULT_VARIATION);
|
||||
|
@ -100,9 +100,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
}
|
||||
|
||||
for (meta in fetchVariationMetadata(id))
|
||||
_metadata.push(meta);
|
||||
_metadata.set(meta.variation, meta);
|
||||
|
||||
if (_metadata.length == 0)
|
||||
if (_metadata.size() == 0)
|
||||
{
|
||||
trace('[WARN] Could not find song data for songId: $id');
|
||||
return;
|
||||
|
@ -119,7 +119,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
|
||||
result._metadata.clear();
|
||||
for (meta in metadata)
|
||||
result._metadata.push(meta);
|
||||
result._metadata.set(meta.variation, meta);
|
||||
|
||||
result.variations.clear();
|
||||
for (vari in variations)
|
||||
|
@ -138,7 +138,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
|
||||
public function getRawMetadata():Array<SongMetadata>
|
||||
{
|
||||
return _metadata;
|
||||
return _metadata.values();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,10 +147,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*/
|
||||
function populateDifficulties():Void
|
||||
{
|
||||
if (_metadata == null || _metadata.length == 0) return;
|
||||
if (_metadata == null || _metadata.size() == 0) return;
|
||||
|
||||
// Variations may have different artist, time format, generatedBy, etc.
|
||||
for (metadata in _metadata)
|
||||
for (metadata in _metadata.values())
|
||||
{
|
||||
if (metadata == null || metadata.playData == null) continue;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import flixel.system.FlxAssets.FlxShader;
|
|||
import flixel.util.FlxSort;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventType;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.stage.StageData.StageDataCharacter;
|
||||
|
@ -402,7 +403,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
// Add the character to the scene.
|
||||
this.add(character);
|
||||
|
||||
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ScriptEvent.ADDED, false));
|
||||
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ADDED, false));
|
||||
|
||||
#if debug
|
||||
debugIconGroup.add(debugIcon);
|
||||
|
|
|
@ -1,879 +0,0 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
using Lambda;
|
||||
|
||||
/**
|
||||
* Actions in the chart editor are backed by the Command pattern
|
||||
* (see Bob Nystrom's book "Game Programming Patterns" for more info)
|
||||
*
|
||||
* To make a function compatible with the undo/redo history, create a new class
|
||||
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
|
||||
*/
|
||||
interface ChartEditorCommand
|
||||
{
|
||||
/**
|
||||
* Calling this function should perform the action that this command represents.
|
||||
* @param state The ChartEditorState to perform the action on.
|
||||
*/
|
||||
public function execute(state:ChartEditorState):Void;
|
||||
|
||||
/**
|
||||
* Calling this function should perform the inverse of the action that this command represents,
|
||||
* effectively undoing the action.
|
||||
* @param state The ChartEditorState to undo the action on.
|
||||
*/
|
||||
public function undo(state:ChartEditorState):Void;
|
||||
|
||||
/**
|
||||
* Get a short description of the action (for the UI).
|
||||
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.
|
||||
*/
|
||||
public function toString():String;
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class AddNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var appendToSelection:Bool;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.appendToSelection = appendToSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
|
||||
if (appendToSelection)
|
||||
{
|
||||
state.currentNoteSelection = state.currentNoteSelection.concat(notes);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
}
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (notes.length == 1)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Add $dir Note';
|
||||
}
|
||||
|
||||
return 'Add ${notes.length} Notes';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class RemoveNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (notes.length == 1 && notes[0] != null)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Remove $dir Note';
|
||||
}
|
||||
|
||||
return 'Remove ${notes.length} Notes';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends one or more items to the selection.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SelectItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in this.notes)
|
||||
{
|
||||
state.currentNoteSelection.push(note);
|
||||
}
|
||||
|
||||
for (event in this.events)
|
||||
{
|
||||
state.currentEventSelection.push(event);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length + events.length;
|
||||
|
||||
if (notes.length == 0)
|
||||
{
|
||||
if (events.length == 1)
|
||||
{
|
||||
return 'Select Event';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Select ${events.length} Events';
|
||||
}
|
||||
}
|
||||
else if (events.length == 0)
|
||||
{
|
||||
if (notes.length == 1)
|
||||
{
|
||||
return 'Select Note';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Select ${notes.length} Notes';
|
||||
}
|
||||
}
|
||||
|
||||
return 'Select ${len} Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class AddEventsCommand implements ChartEditorCommand
|
||||
{
|
||||
var events:Array<SongEventData>;
|
||||
var appendToSelection:Bool;
|
||||
|
||||
public function new(events:Array<SongEventData>, appendToSelection:Bool = false)
|
||||
{
|
||||
this.events = events;
|
||||
this.appendToSelection = appendToSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
|
||||
if (appendToSelection)
|
||||
{
|
||||
state.currentEventSelection = state.currentEventSelection.concat(events);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = events;
|
||||
}
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = events.length;
|
||||
return 'Add $len Events';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class RemoveEventsCommand implements ChartEditorCommand
|
||||
{
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(events:Array<SongEventData>)
|
||||
{
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
state.currentEventSelection = [];
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
state.currentEventSelection = events;
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (events.length == 1 && events[0] != null)
|
||||
{
|
||||
return 'Remove Event';
|
||||
}
|
||||
|
||||
return 'Remove ${events.length} Events';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class RemoveItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Remove ${notes.length + events.length} Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class SwitchDifficultyCommand implements ChartEditorCommand
|
||||
{
|
||||
var prevDifficulty:String;
|
||||
var newDifficulty:String;
|
||||
var prevVariation:String;
|
||||
var newVariation:String;
|
||||
|
||||
public function new(prevDifficulty:String, newDifficulty:String, prevVariation:String, newVariation:String)
|
||||
{
|
||||
this.prevDifficulty = prevDifficulty;
|
||||
this.newDifficulty = newDifficulty;
|
||||
this.prevVariation = prevVariation;
|
||||
this.newVariation = newVariation;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.selectedVariation = newVariation != null ? newVariation : prevVariation;
|
||||
state.selectedDifficulty = newDifficulty != null ? newDifficulty : prevDifficulty;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.selectedVariation = prevVariation != null ? prevVariation : newVariation;
|
||||
state.selectedDifficulty = prevDifficulty != null ? prevDifficulty : newDifficulty;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Switch Difficulty';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class DeselectItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in this.notes)
|
||||
{
|
||||
state.currentNoteSelection.push(note);
|
||||
}
|
||||
|
||||
for (event in this.events)
|
||||
{
|
||||
state.currentEventSelection.push(event);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var noteCount = notes.length + events.length;
|
||||
|
||||
if (noteCount == 1)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Deselect $dir Items';
|
||||
}
|
||||
|
||||
return 'Deselect ${noteCount} Items';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection rather than appends it.
|
||||
* Deselects any notes that are not in the new selection.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SetItemSelectionCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>,
|
||||
previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Select ${notes.length} Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class SelectAllItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = state.currentSongChartNoteData;
|
||||
state.currentEventSelection = state.currentSongChartEventData;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Select All Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class InvertSelectedItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Invert Selected Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class DeselectAllItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Deselect All Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class CutItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
// Copy the notes.
|
||||
SongDataUtils.writeItemsToClipboard(
|
||||
{
|
||||
notes: SongDataUtils.buildNoteClipboard(notes),
|
||||
events: SongDataUtils.buildEventClipboard(events)
|
||||
});
|
||||
|
||||
// Delete the notes.
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length + events.length;
|
||||
|
||||
if (notes.length == 0) return 'Cut $len Events to Clipboard';
|
||||
else if (events.length == 0) return 'Cut $len Notes to Clipboard';
|
||||
else
|
||||
return 'Cut $len Items to Clipboard';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class FlipNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData> = [];
|
||||
var flippedNotes:Array<SongNoteData> = [];
|
||||
|
||||
public function new(notes:Array<SongNoteData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.flippedNotes = SongDataUtils.flipNotes(notes);
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
// Delete the notes.
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
|
||||
// Add the flipped notes.
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
|
||||
|
||||
state.currentNoteSelection = flippedNotes;
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length;
|
||||
return 'Flip $len Notes';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class PasteItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var targetTimestamp:Float;
|
||||
// Notes we added with this command, for undo.
|
||||
var addedNotes:Array<SongNoteData> = [];
|
||||
var addedEvents:Array<SongEventData> = [];
|
||||
|
||||
public function new(targetTimestamp:Float)
|
||||
{
|
||||
this.targetTimestamp = targetTimestamp;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||
|
||||
if (currentClipboard.valid != true)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failed to Paste',
|
||||
body: 'Could not parse clipboard contents.',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
}
|
||||
|
||||
trace(currentClipboard.notes);
|
||||
|
||||
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
||||
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
||||
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
|
||||
state.currentNoteSelection = addedNotes.copy();
|
||||
state.currentEventSelection = addedEvents.copy();
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Paste Successful',
|
||||
body: 'Successfully pasted clipboard contents.',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||
|
||||
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
|
||||
|
||||
if (currentClipboard.notes.length == 0) return 'Paste $len Events';
|
||||
else if (currentClipboard.events.length == 0) return 'Paste $len Notes';
|
||||
else
|
||||
return 'Paste $len Items';
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class ExtendNoteLengthCommand implements ChartEditorCommand
|
||||
{
|
||||
var note:SongNoteData;
|
||||
var oldLength:Float;
|
||||
var newLength:Float;
|
||||
|
||||
public function new(note:SongNoteData, newLength:Float)
|
||||
{
|
||||
this.note = note;
|
||||
this.oldLength = note.length;
|
||||
this.newLength = newLength;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
note.length = newLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
note.length = oldLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Extend Note Length';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
67
source/funkin/ui/debug/charting/commands/AddEventsCommand.hx
Normal file
67
source/funkin/ui/debug/charting/commands/AddEventsCommand.hx
Normal file
|
@ -0,0 +1,67 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Adds the given events to the current chart in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class AddEventsCommand implements ChartEditorCommand
|
||||
{
|
||||
var events:Array<SongEventData>;
|
||||
var appendToSelection:Bool;
|
||||
|
||||
public function new(events:Array<SongEventData>, appendToSelection:Bool = false)
|
||||
{
|
||||
this.events = events;
|
||||
this.appendToSelection = appendToSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
|
||||
if (appendToSelection)
|
||||
{
|
||||
state.currentEventSelection = state.currentEventSelection.concat(events);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = events;
|
||||
}
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = events.length;
|
||||
return 'Add $len Events';
|
||||
}
|
||||
}
|
72
source/funkin/ui/debug/charting/commands/AddNotesCommand.hx
Normal file
72
source/funkin/ui/debug/charting/commands/AddNotesCommand.hx
Normal file
|
@ -0,0 +1,72 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Adds the given notes to the current chart in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class AddNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var appendToSelection:Bool;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.appendToSelection = appendToSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
|
||||
if (appendToSelection)
|
||||
{
|
||||
state.currentNoteSelection = state.currentNoteSelection.concat(notes);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
}
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (notes.length == 1)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Add $dir Note';
|
||||
}
|
||||
|
||||
return 'Add ${notes.length} Notes';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
/**
|
||||
* Actions in the chart editor are backed by the Command pattern
|
||||
* (see Bob Nystrom's book "Game Programming Patterns" for more info)
|
||||
*
|
||||
* To make a functionality compatible with the undo/redo history, create a new class
|
||||
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
|
||||
*/
|
||||
interface ChartEditorCommand
|
||||
{
|
||||
/**
|
||||
* Calling this function should perform the action that this command represents.
|
||||
* @param state The ChartEditorState to perform the action on.
|
||||
*/
|
||||
public function execute(state:ChartEditorState):Void;
|
||||
|
||||
/**
|
||||
* Calling this function should perform the inverse of the action that this command represents,
|
||||
* effectively undoing the action. Assume that the original action was the last action performed.
|
||||
* @param state The ChartEditorState to undo the action on.
|
||||
*/
|
||||
public function undo(state:ChartEditorState):Void;
|
||||
|
||||
/**
|
||||
* Get a short description of the action (for the UI).
|
||||
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.
|
||||
*/
|
||||
public function toString():String;
|
||||
}
|
68
source/funkin/ui/debug/charting/commands/CutItemsCommand.hx
Normal file
68
source/funkin/ui/debug/charting/commands/CutItemsCommand.hx
Normal file
|
@ -0,0 +1,68 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Command that copies a given set of notes and song events to the clipboard,
|
||||
* and then deletes them from the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class CutItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
// Copy the notes.
|
||||
SongDataUtils.writeItemsToClipboard(
|
||||
{
|
||||
notes: SongDataUtils.buildNoteClipboard(notes),
|
||||
events: SongDataUtils.buildEventClipboard(events)
|
||||
});
|
||||
|
||||
// Delete the notes.
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length + events.length;
|
||||
|
||||
if (notes.length == 0) return 'Cut $len Events to Clipboard';
|
||||
else if (events.length == 0) return 'Cut $len Notes to Clipboard';
|
||||
else
|
||||
return 'Cut $len Items to Clipboard';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* Command that deselects all selected notes and events in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class DeselectAllItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Deselect All Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Command to deselect a specific set of notes and events in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class DeselectItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in this.notes)
|
||||
{
|
||||
state.currentNoteSelection.push(note);
|
||||
}
|
||||
|
||||
for (event in this.events)
|
||||
{
|
||||
state.currentEventSelection.push(event);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var noteCount = notes.length + events.length;
|
||||
|
||||
if (noteCount == 1)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Deselect $dir Items';
|
||||
}
|
||||
|
||||
return 'Deselect ${noteCount} Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* Command that modifies the length of a hold note in the chart editor.
|
||||
* If it is not a hold note, it will become one, and if it is already a hold note, its length will change.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ExtendNoteLengthCommand implements ChartEditorCommand
|
||||
{
|
||||
var note:SongNoteData;
|
||||
var oldLength:Float;
|
||||
var newLength:Float;
|
||||
|
||||
public function new(note:SongNoteData, newLength:Float)
|
||||
{
|
||||
this.note = note;
|
||||
this.oldLength = note.length;
|
||||
this.newLength = newLength;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
note.length = newLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
note.length = oldLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Extend Note Length';
|
||||
}
|
||||
}
|
59
source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx
Normal file
59
source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx
Normal file
|
@ -0,0 +1,59 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Command that flips a given array of notes from the player's side of the chart editor to the opponent's side, or vice versa.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class FlipNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData> = [];
|
||||
var flippedNotes:Array<SongNoteData> = [];
|
||||
|
||||
public function new(notes:Array<SongNoteData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.flippedNotes = SongDataUtils.flipNotes(notes);
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
// Delete the notes.
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
|
||||
// Add the flipped notes.
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
|
||||
|
||||
state.currentNoteSelection = flippedNotes;
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length;
|
||||
return 'Flip $len Notes';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Command to deselect all items that are currently selected in the chart editor,
|
||||
* then select all the items that were previously unselected.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class InvertSelectedItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Invert Selected Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Move the given events by the given offset and shift them by the given number of columns in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class MoveEventsCommand implements ChartEditorCommand
|
||||
{
|
||||
var events:Array<SongEventData>;
|
||||
var movedEvents:Array<SongEventData>;
|
||||
var offset:Float;
|
||||
|
||||
public function new(notes:Array<SongEventData>, offset:Float)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
this.events = [for (event in events) event.clone()];
|
||||
this.offset = offset;
|
||||
this.movedEvents = [];
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
movedEvents = [];
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
var resultEvent = event.clone();
|
||||
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||
|
||||
movedEvents.push(resultEvent);
|
||||
}
|
||||
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(movedEvents);
|
||||
state.currentEventSelection = movedEvents;
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, movedEvents);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = events.length;
|
||||
return 'Move $len Events';
|
||||
}
|
||||
}
|
96
source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx
Normal file
96
source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx
Normal file
|
@ -0,0 +1,96 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Move the given notes by the given offset and shift them by the given number of columns in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class MoveItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var movedNotes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
var movedEvents:Array<SongEventData>;
|
||||
var offset:Float;
|
||||
var columns:Int;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, offset:Float, columns:Int)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
this.notes = [for (note in notes) note.clone()];
|
||||
this.events = [for (event in events) event.clone()];
|
||||
this.offset = offset;
|
||||
this.columns = columns;
|
||||
this.movedNotes = [];
|
||||
this.movedEvents = [];
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
movedNotes = [];
|
||||
movedEvents = [];
|
||||
|
||||
for (note in notes)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
var resultNote = note.clone();
|
||||
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
|
||||
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
|
||||
|
||||
movedNotes.push(resultNote);
|
||||
}
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
var resultEvent = event.clone();
|
||||
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||
|
||||
movedEvents.push(resultEvent);
|
||||
}
|
||||
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(movedNotes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(movedEvents);
|
||||
state.currentNoteSelection = movedNotes;
|
||||
state.currentEventSelection = movedEvents;
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, movedNotes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, movedEvents);
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length + events.length;
|
||||
return 'Move $len Items';
|
||||
}
|
||||
}
|
75
source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx
Normal file
75
source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx
Normal file
|
@ -0,0 +1,75 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Move the given notes by the given offset and shift them by the given number of columns in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class MoveNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var movedNotes:Array<SongNoteData>;
|
||||
var offset:Float;
|
||||
var columns:Int;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, offset:Float, columns:Int)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
this.notes = [for (note in notes) note.clone()];
|
||||
this.offset = offset;
|
||||
this.columns = columns;
|
||||
this.movedNotes = [];
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
|
||||
movedNotes = [];
|
||||
|
||||
for (note in notes)
|
||||
{
|
||||
// Clone the notes to prevent editing from affecting the history.
|
||||
var resultNote = note.clone();
|
||||
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
|
||||
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
|
||||
|
||||
movedNotes.push(resultNote);
|
||||
}
|
||||
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(movedNotes);
|
||||
state.currentNoteSelection = movedNotes;
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, movedNotes);
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length;
|
||||
return 'Move $len Notes';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongDataUtils.SongClipboardItems;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
/**
|
||||
* A command which inserts the contents of the clipboard into the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class PasteItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var targetTimestamp:Float;
|
||||
// Notes we added with this command, for undo.
|
||||
var addedNotes:Array<SongNoteData> = [];
|
||||
var addedEvents:Array<SongEventData> = [];
|
||||
|
||||
public function new(targetTimestamp:Float)
|
||||
{
|
||||
this.targetTimestamp = targetTimestamp;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||
|
||||
if (currentClipboard.valid != true)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failed to Paste',
|
||||
body: 'Could not parse clipboard contents.',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
}
|
||||
|
||||
trace(currentClipboard.notes);
|
||||
|
||||
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
||||
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
||||
|
||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
|
||||
state.currentNoteSelection = addedNotes.copy();
|
||||
state.currentEventSelection = addedEvents.copy();
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Paste Successful',
|
||||
body: 'Successfully pasted clipboard contents.',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||
|
||||
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
|
||||
|
||||
if (currentClipboard.notes.length == 0) return 'Paste $len Events';
|
||||
else if (currentClipboard.events.length == 0) return 'Paste $len Notes';
|
||||
else
|
||||
return 'Paste $len Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Deletes the given events from the current chart in the chart editor.
|
||||
* Use only when ONLY events are being deleted.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class RemoveEventsCommand implements ChartEditorCommand
|
||||
{
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(events:Array<SongEventData>)
|
||||
{
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
state.currentEventSelection = events;
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (events.length == 1 && events[0] != null)
|
||||
{
|
||||
return 'Remove Event';
|
||||
}
|
||||
|
||||
return 'Remove ${events.length} Events';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Deletes the given notes and events from the current chart in the chart editor.
|
||||
* Use only when BOTH notes and events are being deleted.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class RemoveItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Remove ${notes.length + events.length} Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Deletes the given notes from the current chart in the chart editor.
|
||||
* Use only when ONLY notes are being deleted.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class RemoveNotesCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
state.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in notes)
|
||||
{
|
||||
state.currentSongChartNoteData.push(note);
|
||||
}
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
if (notes.length == 1 && notes[0] != null)
|
||||
{
|
||||
var dir:String = notes[0].getDirectionName();
|
||||
return 'Remove $dir Note';
|
||||
}
|
||||
|
||||
return 'Remove ${notes.length} Notes';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* Command to set the selection to all notes and events in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SelectAllItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = state.currentSongChartNoteData;
|
||||
state.currentEventSelection = state.currentSongChartEventData;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Select All Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Appends one or more items to the selection in the chart editor.
|
||||
* This does not deselect any items that are already selected, if any.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SelectItemsCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
for (note in this.notes)
|
||||
{
|
||||
state.currentNoteSelection.push(note);
|
||||
}
|
||||
|
||||
for (event in this.events)
|
||||
{
|
||||
state.currentEventSelection.push(event);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var len:Int = notes.length + events.length;
|
||||
|
||||
if (notes.length == 0)
|
||||
{
|
||||
if (events.length == 1)
|
||||
{
|
||||
return 'Select Event';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Select ${events.length} Events';
|
||||
}
|
||||
}
|
||||
else if (events.length == 0)
|
||||
{
|
||||
if (notes.length == 1)
|
||||
{
|
||||
return 'Select Note';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Select ${notes.length} Notes';
|
||||
}
|
||||
}
|
||||
|
||||
return 'Select ${len} Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* Command to set the current selection in the chart editor (rather than appending it).
|
||||
* Deselects any notes that are not in the new selection.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SetItemSelectionCommand implements ChartEditorCommand
|
||||
{
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
var previousNoteSelection:Array<SongNoteData>;
|
||||
var previousEventSelection:Array<SongEventData>;
|
||||
|
||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>,
|
||||
previousEventSelection:Array<SongEventData>)
|
||||
{
|
||||
this.notes = notes;
|
||||
this.events = events;
|
||||
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentNoteSelection = previousNoteSelection;
|
||||
state.currentEventSelection = previousEventSelection;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Select ${notes.length} Items';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
/**
|
||||
* Switch the current difficulty (and possibly variation) of the chart in the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SwitchDifficultyCommand implements ChartEditorCommand
|
||||
{
|
||||
var prevDifficulty:String;
|
||||
var newDifficulty:String;
|
||||
var prevVariation:String;
|
||||
var newVariation:String;
|
||||
|
||||
public function new(prevDifficulty:String, newDifficulty:String, prevVariation:String, newVariation:String)
|
||||
{
|
||||
this.prevDifficulty = prevDifficulty;
|
||||
this.newDifficulty = newDifficulty;
|
||||
this.prevVariation = prevVariation;
|
||||
this.newVariation = newVariation;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
state.selectedVariation = newVariation != null ? newVariation : prevVariation;
|
||||
state.selectedDifficulty = newDifficulty != null ? newDifficulty : prevDifficulty;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.selectedVariation = prevVariation != null ? prevVariation : newVariation;
|
||||
state.selectedDifficulty = prevDifficulty != null ? prevDifficulty : newDifficulty;
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Switch Difficulty';
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
|
@ -13,7 +13,7 @@ import flixel.math.FlxPoint;
|
|||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* A event sprite that can be used to display a song event in a chart.
|
||||
* A sprite that can be used to display a song event in a chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
@:nullSafety
|
||||
|
@ -34,6 +34,17 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
*/
|
||||
static var eventSpriteBasic:Null<BitmapData> = null;
|
||||
|
||||
public var overrideStepTime(default, set):Null<Float> = null;
|
||||
|
||||
function set_overrideStepTime(value:Null<Float>):Null<Float>
|
||||
{
|
||||
if (overrideStepTime == value) return overrideStepTime;
|
||||
|
||||
overrideStepTime = value;
|
||||
updateEventPosition();
|
||||
return overrideStepTime;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
{
|
||||
super();
|
||||
|
@ -146,7 +157,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
this.x = (ChartEditorState.STRUMLINE_SIZE * 2 + 1 - 1) * ChartEditorState.GRID_SIZE;
|
||||
|
||||
var stepTime:Float = inline eventData.getStepTime();
|
||||
var stepTime:Float = (overrideStepTime != null) ? overrideStepTime : eventData.getStepTime();
|
||||
this.y = stepTime * ChartEditorState.GRID_SIZE;
|
||||
|
||||
if (origin != null)
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
|
@ -11,7 +11,7 @@ import funkin.play.notes.SustainTrail;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* A hold note sprite that can be used to display a note in a chart.
|
||||
* A sprite that can be used to display the trail of a hold note in a chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
@:nullSafety
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -10,10 +10,11 @@ import flixel.math.FlxPoint;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* A note sprite that can be used to display a note in a chart.
|
||||
* A sprite that can be used to display a note in a chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorNoteSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
|
@ -37,6 +38,28 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
*/
|
||||
public var noteStyle(get, never):String;
|
||||
|
||||
public var overrideStepTime(default, set):Null<Float> = null;
|
||||
|
||||
function set_overrideStepTime(value:Null<Float>):Null<Float>
|
||||
{
|
||||
if (overrideStepTime == value) return overrideStepTime;
|
||||
|
||||
overrideStepTime = value;
|
||||
updateNotePosition();
|
||||
return overrideStepTime;
|
||||
}
|
||||
|
||||
public var overrideData(default, set):Null<Int> = null;
|
||||
|
||||
function set_overrideData(value:Null<Int>):Null<Int>
|
||||
{
|
||||
if (overrideData == value) return overrideData;
|
||||
|
||||
overrideData = value;
|
||||
playNoteAnimation();
|
||||
return overrideData;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
{
|
||||
super();
|
||||
|
@ -147,32 +170,15 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
{
|
||||
if (this.noteData == null) return;
|
||||
|
||||
var cursorColumn:Int = this.noteData.data;
|
||||
var cursorColumn:Int = (overrideData != null) ? overrideData : this.noteData.data;
|
||||
|
||||
if (cursorColumn < 0) cursorColumn = 0;
|
||||
if (cursorColumn >= (ChartEditorState.STRUMLINE_SIZE * 2 + 1))
|
||||
{
|
||||
cursorColumn = (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invert player and opponent columns.
|
||||
if (cursorColumn >= ChartEditorState.STRUMLINE_SIZE)
|
||||
{
|
||||
cursorColumn -= ChartEditorState.STRUMLINE_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorColumn += ChartEditorState.STRUMLINE_SIZE;
|
||||
}
|
||||
}
|
||||
cursorColumn = ChartEditorState.noteDataToGridColumn(cursorColumn);
|
||||
|
||||
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
|
||||
|
||||
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
||||
// noteData.getStepTime() returns a calculated value which accounts for BPM changes
|
||||
var stepTime:Float =
|
||||
inline this.noteData.getStepTime();
|
||||
var stepTime:Float = (overrideStepTime != null) ? overrideStepTime : noteData.getStepTime();
|
||||
if (stepTime >= 0)
|
||||
{
|
||||
this.y = stepTime * ChartEditorState.GRID_SIZE;
|
||||
|
@ -199,7 +205,8 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
var baseAnimationName:String = 'tap';
|
||||
|
||||
// Play the appropriate animation for the type, direction, and skin.
|
||||
var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle.toTitleCase()}';
|
||||
var dirName:String = overrideData != null ? SongNoteData.buildDirectionName(overrideData) : this.noteData.getDirectionName();
|
||||
var animationName:String = '${baseAnimationName}${dirName}${this.noteStyle.toTitleCase()}';
|
||||
|
||||
this.animation.play(animationName);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a square over a selected note or event in the chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
class ChartEditorSelectionSquareSprite extends FlxSprite
|
||||
{
|
||||
public var noteData:Null<SongNoteData>;
|
||||
public var eventData:Null<SongEventData>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.assets.SoundUtil;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Functions for loading audio for the chart editor.
|
||||
* Handlers split up the functionality of the Chart Editor into different classes based on focus to limit the amount of code in each class.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorDialogHandler)
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorImportExportHandler)
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorAudioHandler
|
||||
{
|
||||
/**
|
||||
|
@ -27,7 +26,7 @@ class ChartEditorAudioHandler
|
|||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = ''):Bool
|
||||
public static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
|
@ -46,7 +45,7 @@ class ChartEditorAudioHandler
|
|||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = ''):Bool
|
||||
public static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
|
@ -63,7 +62,7 @@ class ChartEditorAudioHandler
|
|||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = ''):Bool
|
||||
public static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
state.audioVocalTrackData.set(trackId, bytes);
|
||||
|
@ -77,7 +76,7 @@ class ChartEditorAudioHandler
|
|||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = ''):Bool
|
||||
public static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
|
@ -95,7 +94,7 @@ class ChartEditorAudioHandler
|
|||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = ''):Bool
|
||||
public static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
|
@ -112,7 +111,7 @@ class ChartEditorAudioHandler
|
|||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = ''):Bool
|
||||
public static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
state.audioInstTrackData.set(instId, bytes);
|
||||
|
@ -136,11 +135,11 @@ class ChartEditorAudioHandler
|
|||
/**
|
||||
* Tell the Chart Editor to select a specific instrumental track, that is already loaded.
|
||||
*/
|
||||
static function playInstrumental(state:ChartEditorState, instId:String = ''):Bool
|
||||
public static function playInstrumental(state:ChartEditorState, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var instTrack:Null<FlxSound> = buildFlxSoundFromBytes(instTrackData);
|
||||
var instTrack:Null<FlxSound> = SoundUtil.buildFlxSoundFromBytes(instTrackData);
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
|
@ -149,7 +148,7 @@ class ChartEditorAudioHandler
|
|||
return true;
|
||||
}
|
||||
|
||||
static function stopExistingInstrumental(state:ChartEditorState):Void
|
||||
public static function stopExistingInstrumental(state:ChartEditorState):Void
|
||||
{
|
||||
if (state.audioInstTrack != null)
|
||||
{
|
||||
|
@ -162,11 +161,11 @@ class ChartEditorAudioHandler
|
|||
/**
|
||||
* Tell the Chart Editor to select a specific vocal track, that is already loaded.
|
||||
*/
|
||||
static function playVocals(state:ChartEditorState, charType:CharacterType, charId:String, instId:String = ''):Bool
|
||||
public static function playVocals(state:ChartEditorState, charType:CharacterType, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var vocalTrack:Null<FlxSound> = buildFlxSoundFromBytes(vocalTrackData);
|
||||
var vocalTrack:Null<FlxSound> = SoundUtil.buildFlxSoundFromBytes(vocalTrackData);
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
|
||||
|
@ -190,7 +189,7 @@ class ChartEditorAudioHandler
|
|||
return false;
|
||||
}
|
||||
|
||||
static function stopExistingVocals(state:ChartEditorState):Void
|
||||
public static function stopExistingVocals(state:ChartEditorState):Void
|
||||
{
|
||||
if (state.audioVocalTrackGroup != null)
|
||||
{
|
||||
|
@ -203,7 +202,7 @@ class ChartEditorAudioHandler
|
|||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
||||
* @param path The path to the sound effect. Use `Paths` to build this.
|
||||
*/
|
||||
public static function playSound(path:String):Void
|
||||
public static function playSound(_state:ChartEditorState, path:String):Void
|
||||
{
|
||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||
|
@ -219,22 +218,11 @@ class ChartEditorAudioHandler
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert byte data into a playable sound.
|
||||
*
|
||||
* @param input The byte data.
|
||||
* @return The playable sound, or `null` if loading failed.
|
||||
* Create a list of ZIP file entries from the current loaded instrumental tracks in the chart eidtor.
|
||||
* @param state The chart editor state.
|
||||
* @return `Array<haxe.zip.Entry>`
|
||||
*/
|
||||
public static function buildFlxSoundFromBytes(input:Null<Bytes>):Null<FlxSound>
|
||||
{
|
||||
if (input == null) return null;
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
||||
var output:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
return output;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromInstrumentals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
public static function makeZIPEntriesFromInstrumentals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
|
@ -257,7 +245,12 @@ class ChartEditorAudioHandler
|
|||
return zipEntries;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromVocals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
/**
|
||||
* Create a list of ZIP file entries from the current loaded vocal tracks in the chart eidtor.
|
||||
* @param state The chart editor state.
|
||||
* @return `Array<haxe.zip.Entry>`
|
||||
*/
|
||||
public static function makeZIPEntriesFromVocals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import funkin.ui.haxeui.components.FunkinDropDown;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.data.song.importer.FNFLegacyData;
|
||||
import funkin.data.song.importer.FNFLegacyImporter;
|
||||
|
@ -15,6 +14,8 @@ import funkin.play.character.CharacterData;
|
|||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.ui.haxeui.components.FunkinDropDown;
|
||||
import funkin.ui.haxeui.components.FunkinLink;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.FileUtil;
|
||||
|
@ -47,8 +48,10 @@ using Lambda;
|
|||
* Handles dialogs for the new Chart Editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorDialogHandler
|
||||
{
|
||||
// Paths to HaxeUI layout files for each dialog.
|
||||
static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about');
|
||||
static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst');
|
||||
|
@ -67,7 +70,7 @@ class ChartEditorDialogHandler
|
|||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openAboutDialog(state:ChartEditorState):Null<Dialog>
|
||||
public static function openAboutDialog(state:ChartEditorState):Null<Dialog>
|
||||
{
|
||||
return openDialog(state, CHART_EDITOR_DIALOG_ABOUT_LAYOUT, true, true);
|
||||
}
|
||||
|
@ -158,7 +161,7 @@ class ChartEditorDialogHandler
|
|||
state.stopWelcomeMusic();
|
||||
|
||||
// Load song from template
|
||||
ChartEditorImportExportHandler.loadSongAsTemplate(state, targetSongId);
|
||||
state.loadSongAsTemplate(targetSongId);
|
||||
}
|
||||
|
||||
splashTemplateContainer.addComponent(linkTemplateSong);
|
||||
|
@ -402,7 +405,7 @@ class ChartEditorDialogHandler
|
|||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
||||
if (state.loadInstFromBytes(selectedFile.bytes, instId))
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
|
@ -410,7 +413,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -426,7 +429,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -437,7 +440,7 @@ class ChartEditorDialogHandler
|
|||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
if (ChartEditorAudioHandler.loadInstFromPath(state, path, instId))
|
||||
if (state.loadInstFromPath(path, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
|
@ -446,7 +449,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -472,7 +475,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: message,
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -483,66 +486,6 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
static var dropHandlers:Array<
|
||||
{
|
||||
component:Component,
|
||||
handler:(String->Void)
|
||||
}> = [];
|
||||
|
||||
/**
|
||||
* Add a callback for when a file is dropped on a component.
|
||||
*
|
||||
* On OS X you can’t drop on the application window, but rather only the app icon
|
||||
* (either in the dock while running or the icon on the hard drive) so this must be disabled
|
||||
* and UI updated appropriately.
|
||||
* @param component
|
||||
* @param handler
|
||||
*/
|
||||
static function addDropHandler(component:Component, handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile);
|
||||
|
||||
dropHandlers.push(
|
||||
{
|
||||
component: component,
|
||||
handler: handler
|
||||
});
|
||||
#else
|
||||
trace('addDropHandler not implemented for this platform');
|
||||
#end
|
||||
}
|
||||
|
||||
static function removeDropHandler(handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
FlxG.stage.window.onDropFile.remove(handler);
|
||||
#end
|
||||
}
|
||||
|
||||
static function clearDropHandlers():Void
|
||||
{
|
||||
#if desktop
|
||||
dropHandlers = [];
|
||||
FlxG.stage.window.onDropFile.remove(onDropFile);
|
||||
#end
|
||||
}
|
||||
|
||||
static function onDropFile(path:String):Void
|
||||
{
|
||||
// a VERY short timer to wait for the mouse position to update
|
||||
new FlxTimer().start(0.01, function(_) {
|
||||
for (handler in dropHandlers)
|
||||
{
|
||||
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
||||
{
|
||||
handler.handler(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM.
|
||||
* @param state The ChartEditorState instance.
|
||||
|
@ -722,7 +665,7 @@ class ChartEditorDialogHandler
|
|||
if (dialogNoVocals == null) throw 'Could not locate dialogNoVocals button in Upload Vocals dialog';
|
||||
dialogNoVocals.onClick = function(_event) {
|
||||
// Dismiss
|
||||
ChartEditorAudioHandler.stopExistingVocals(state);
|
||||
state.stopExistingVocals();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
|
@ -749,10 +692,10 @@ class ChartEditorDialogHandler
|
|||
if (!hasClearedVocals)
|
||||
{
|
||||
hasClearedVocals = true;
|
||||
ChartEditorAudioHandler.stopExistingVocals(state);
|
||||
state.stopExistingVocals();
|
||||
}
|
||||
|
||||
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey, instId))
|
||||
if (state.loadVocalsFromPath(path, charKey, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
|
@ -761,7 +704,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
#if FILE_DROP_SUPPORTED
|
||||
|
@ -784,7 +727,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -805,9 +748,9 @@ class ChartEditorDialogHandler
|
|||
if (!hasClearedVocals)
|
||||
{
|
||||
hasClearedVocals = true;
|
||||
ChartEditorAudioHandler.stopExistingVocals(state);
|
||||
state.stopExistingVocals();
|
||||
}
|
||||
if (ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey, instId))
|
||||
if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
|
@ -816,7 +759,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
#if FILE_DROP_SUPPORTED
|
||||
|
@ -837,7 +780,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -897,7 +840,7 @@ class ChartEditorDialogHandler
|
|||
var buttonContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||
if (buttonContinue == null) throw 'Could not locate dialogContinue button in Open Chart dialog';
|
||||
buttonContinue.onClick = function(_event) {
|
||||
ChartEditorImportExportHandler.loadSong(state, songMetadata, songChartData);
|
||||
state.loadSong(songMetadata, songChartData);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
|
@ -996,7 +939,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Could not parse metadata file version (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1014,7 +957,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Could not load metadata file (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1029,7 +972,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded metadata file (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -1061,7 +1004,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Could not parse metadata file version (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1081,7 +1024,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded metadata file (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -1102,7 +1045,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to load metadata file (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -1126,7 +1069,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Could not parse chart data file version (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1149,7 +1092,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded chart data file (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -1168,7 +1111,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to load chart data file (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -1193,7 +1136,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Could not parse chart data file version (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1216,7 +1159,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded chart data file (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
|
@ -1319,7 +1262,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Failure',
|
||||
body: 'Failed to parse FNF chart file (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
|
@ -1328,7 +1271,7 @@ class ChartEditorDialogHandler
|
|||
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(fnfLegacyData);
|
||||
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(fnfLegacyData);
|
||||
|
||||
ChartEditorImportExportHandler.loadSong(state, [Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
#if !mac
|
||||
|
@ -1337,7 +1280,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded chart file (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -1351,7 +1294,7 @@ class ChartEditorDialogHandler
|
|||
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(selectedFileData);
|
||||
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(selectedFileData);
|
||||
|
||||
ChartEditorImportExportHandler.loadSong(state, [Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
#if !mac
|
||||
|
@ -1360,7 +1303,7 @@ class ChartEditorDialogHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded chart file (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
};
|
||||
|
@ -1376,35 +1319,11 @@ class ChartEditorDialogHandler
|
|||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openUserGuideDialog(state:ChartEditorState):Null<Dialog>
|
||||
public static function openUserGuideDialog(state:ChartEditorState):Null<Dialog>
|
||||
{
|
||||
return openDialog(state, CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog from a given layout path.
|
||||
* @param modal Makes the background uninteractable while the dialog is open.
|
||||
* @param closable Hides the close button on the dialog, preventing it from being closed unless the user interacts with the dialog.
|
||||
*/
|
||||
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = cast state.buildComponent(key);
|
||||
if (dialog == null) return null;
|
||||
|
||||
dialog.destroyOnClose = true;
|
||||
dialog.closable = closable;
|
||||
dialog.showDialog(modal);
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
dialog.onDialogClosed = function(event:UIEvent) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
};
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user can add a new variation for a song.
|
||||
* @param state The current chart editor state.
|
||||
|
@ -1561,4 +1480,91 @@ class ChartEditorDialogHandler
|
|||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog from a given layout path.
|
||||
* @param modal Makes the background uninteractable while the dialog is open.
|
||||
* @param closable Hides the close button on the dialog, preventing it from being closed unless the user interacts with the dialog.
|
||||
*/
|
||||
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = cast state.buildComponent(key);
|
||||
if (dialog == null) return null;
|
||||
|
||||
dialog.destroyOnClose = true;
|
||||
dialog.closable = closable;
|
||||
dialog.showDialog(modal);
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
dialog.onDialogClosed = function(event:UIEvent) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
};
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
// ==========
|
||||
// DROP HANDLERS
|
||||
// ==========
|
||||
static var dropHandlers:Array<
|
||||
{
|
||||
component:Component,
|
||||
handler:(String->Void)
|
||||
}> = [];
|
||||
|
||||
/**
|
||||
* Add a callback for when a file is dropped on a component.
|
||||
*
|
||||
* On OS X you can’t drop on the application window, but rather only the app icon
|
||||
* (either in the dock while running or the icon on the hard drive) so this must be disabled
|
||||
* and UI updated appropriately.
|
||||
* @param component
|
||||
* @param handler
|
||||
*/
|
||||
static function addDropHandler(component:Component, handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile);
|
||||
|
||||
dropHandlers.push(
|
||||
{
|
||||
component: component,
|
||||
handler: handler
|
||||
});
|
||||
#else
|
||||
trace('addDropHandler not implemented for this platform');
|
||||
#end
|
||||
}
|
||||
|
||||
static function removeDropHandler(handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
FlxG.stage.window.onDropFile.remove(handler);
|
||||
#end
|
||||
}
|
||||
|
||||
static function clearDropHandlers():Void
|
||||
{
|
||||
#if desktop
|
||||
dropHandlers = [];
|
||||
FlxG.stage.window.onDropFile.remove(onDropFile);
|
||||
#end
|
||||
}
|
||||
|
||||
static function onDropFile(path:String):Void
|
||||
{
|
||||
// a VERY short timer to wait for the mouse position to update
|
||||
new FlxTimer().start(0.01, function(_) {
|
||||
for (handler in dropHandlers)
|
||||
{
|
||||
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
||||
{
|
||||
handler.handler(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import funkin.util.DateUtil;
|
||||
|
@ -16,7 +16,7 @@ import funkin.data.song.SongRegistry;
|
|||
* Contains functions for importing, loading, saving, and exporting charts.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorImportExportHandler
|
||||
{
|
||||
/**
|
||||
|
@ -50,18 +50,18 @@ class ChartEditorImportExportHandler
|
|||
|
||||
state.sortChartData();
|
||||
|
||||
state.clearVocals();
|
||||
state.stopExistingVocals();
|
||||
|
||||
var variations:Array<String> = state.availableVariations;
|
||||
for (variation in variations)
|
||||
{
|
||||
if (variation == Constants.DEFAULT_VARIATION)
|
||||
{
|
||||
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId));
|
||||
state.loadInstFromAsset(Paths.inst(songId));
|
||||
}
|
||||
else
|
||||
{
|
||||
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId, '-$variation'), variation);
|
||||
state.loadInstFromAsset(Paths.inst(songId, '-$variation'), variation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +75,12 @@ class ChartEditorImportExportHandler
|
|||
|
||||
if (voiceList.length == 2)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], diff.characters.opponent, instId);
|
||||
state.loadVocalsFromAsset(voiceList[0], diff.characters.player, instId);
|
||||
state.loadVocalsFromAsset(voiceList[1], diff.characters.opponent, instId);
|
||||
}
|
||||
else if (voiceList.length == 1)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||
state.loadVocalsFromAsset(voiceList[0], diff.characters.player, instId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ class ChartEditorImportExportHandler
|
|||
title: 'Success',
|
||||
body: 'Loaded song (${rawSongMetadata[0].songName})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
@ -169,8 +169,8 @@ class ChartEditorImportExportHandler
|
|||
}
|
||||
}
|
||||
|
||||
if (state.audioInstTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromInstrumentals(state));
|
||||
if (state.audioVocalTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromVocals(state));
|
||||
if (state.audioInstTrackData != null) zipEntries.concat(state.makeZIPEntriesFromInstrumentals());
|
||||
if (state.audioVocalTrackData != null) zipEntries.concat(state.makeZIPEntriesFromVocals());
|
||||
|
||||
trace('Exporting ${zipEntries.length} files to ZIP...');
|
||||
|
|
@ -1,26 +1,19 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.display.FlxSliceSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
/**
|
||||
* Available themes for the chart editor state.
|
||||
*/
|
||||
enum ChartEditorTheme
|
||||
{
|
||||
Light;
|
||||
Dark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static functions which handle building themed UI elements for a provided ChartEditorState.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorThemeHandler
|
||||
{
|
||||
// TODO: There's probably a better system of organization for these colors.
|
|
@ -1,53 +1,43 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import funkin.ui.haxeui.components.FunkinDropDown;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.ui.haxeui.components.FunkinDropDown;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.containers.Group;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.data.ArrayDataSource;
|
||||
import haxe.ui.events.UIEvent;
|
||||
|
||||
/**
|
||||
* Available tools for the chart editor state.
|
||||
*/
|
||||
enum ChartEditorToolMode
|
||||
{
|
||||
Select;
|
||||
Place;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static functions which handle building themed UI elements for a provided ChartEditorState.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorToolboxHandler
|
||||
{
|
||||
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
||||
|
@ -72,12 +62,10 @@ class ChartEditorToolboxHandler
|
|||
{
|
||||
toolbox.showDialog(false);
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/openWindow'));
|
||||
state.playSound(Paths.sound('chartingSounds/openWindow'));
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||
onShowToolboxTools(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
||||
onShowToolboxNoteData(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
||||
|
@ -111,12 +99,10 @@ class ChartEditorToolboxHandler
|
|||
{
|
||||
toolbox.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/exitWindow'));
|
||||
state.playSound(Paths.sound('chartingSounds/exitWindow'));
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||
onHideToolboxTools(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
||||
onHideToolboxNoteData(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
||||
|
@ -175,8 +161,6 @@ class ChartEditorToolboxHandler
|
|||
var toolbox:Null<CollapsibleDialog> = null;
|
||||
switch (id)
|
||||
{
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||
toolbox = buildToolboxToolsLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
||||
toolbox = buildToolboxNoteDataLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
||||
|
@ -223,44 +207,6 @@ class ChartEditorToolboxHandler
|
|||
return toolbox;
|
||||
}
|
||||
|
||||
static function buildToolboxToolsLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
// Starting position.
|
||||
toolbox.x = 50;
|
||||
toolbox.y = 50;
|
||||
|
||||
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxTools', false);
|
||||
}
|
||||
|
||||
var toolsGroup:Null<Group> = toolbox.findComponent('toolboxToolsGroup', Group);
|
||||
if (toolsGroup == null) throw 'ChartEditorToolboxHandler.buildToolboxToolsLayout() - Could not find toolboxToolsGroup component.';
|
||||
|
||||
if (toolsGroup == null) return null;
|
||||
|
||||
toolsGroup.onChange = function(event:UIEvent) {
|
||||
switch (event.target.id)
|
||||
{
|
||||
case 'toolboxToolsGroupSelect':
|
||||
state.currentToolMode = ChartEditorToolMode.Select;
|
||||
case 'toolboxToolsGroupPlace':
|
||||
state.currentToolMode = ChartEditorToolMode.Place;
|
||||
default:
|
||||
trace('ChartEditorToolboxHandler.buildToolboxToolsLayout() - Unknown toolbox tool selected: $event.target.id');
|
||||
}
|
||||
}
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
static function onShowToolboxTools(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onHideToolboxTools(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function buildToolboxNoteDataLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||
|
@ -483,11 +429,11 @@ class ChartEditorToolboxHandler
|
|||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxLoadChart component.';
|
||||
|
||||
difficultyToolboxAddVariation.onClick = function(_:UIEvent) {
|
||||
ChartEditorDialogHandler.openAddVariationDialog(state, true);
|
||||
state.openAddVariationDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxAddDifficulty.onClick = function(_:UIEvent) {
|
||||
ChartEditorDialogHandler.openAddDifficultyDialog(state, true);
|
||||
state.openAddDifficultyDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) {
|
10
source/funkin/ui/debug/charting/import.hx
Normal file
10
source/funkin/ui/debug/charting/import.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
#if !macro
|
||||
// Apply handlers so they can be called as though they were functions in ChartEditorState
|
||||
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
|
||||
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
|
||||
using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler;
|
||||
using funkin.ui.debug.charting.handlers.ChartEditorThemeHandler;
|
||||
using funkin.ui.debug.charting.handlers.ChartEditorToolboxHandler;
|
||||
#end
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.ui.debug.charting;
|
||||
package funkin.ui.debug.charting.util;
|
||||
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
@ -10,13 +10,16 @@ import funkin.play.character.BaseCharacter.CharacterType;
|
|||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
|
||||
/**
|
||||
* This class contains functions for populating dropdowns based on game data.
|
||||
* Functions for populating dropdowns based on game data.
|
||||
* These get used by both dialogs and toolboxes so they're in their own class to prevent "reaching over."
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(ChartEditorState)
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorDropdowns
|
||||
{
|
||||
/**
|
||||
* Populate a dropdown with a list of characters.
|
||||
*/
|
||||
public static function populateDropdownWithCharacters(dropDown:DropDown, charType:CharacterType, startingCharId:String):DropDownEntry
|
||||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
@ -50,6 +53,9 @@ class ChartEditorDropdowns
|
|||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a dropdown with a list of stages.
|
||||
*/
|
||||
public static function populateDropdownWithStages(dropDown:DropDown, startingStageId:String):DropDownEntry
|
||||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
@ -74,6 +80,9 @@ class ChartEditorDropdowns
|
|||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a dropdown with a list of note styles.
|
||||
*/
|
||||
public static function populateDropdownWithNoteStyles(dropDown:DropDown, startingStyleId:String):DropDownEntry
|
||||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
@ -98,6 +107,9 @@ class ChartEditorDropdowns
|
|||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a dropdown with a list of song variations.
|
||||
*/
|
||||
public static function populateDropdownWithVariations(dropDown:DropDown, state:ChartEditorState, includeNone:Bool = true):DropDownEntry
|
||||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
@ -122,6 +134,9 @@ class ChartEditorDropdowns
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in a dropdown.
|
||||
*/
|
||||
typedef DropDownEntry =
|
||||
{
|
||||
id:String,
|
|
@ -251,6 +251,16 @@ class Constants
|
|||
*/
|
||||
public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC;
|
||||
|
||||
/**
|
||||
* Duration, in milliseconds, until toast notifications are automatically hidden.
|
||||
*/
|
||||
public static final NOTIFICATION_DISMISS_TIME:Int = 5 * MS_PER_SEC;
|
||||
|
||||
/**
|
||||
* Duration to wait before autosaving the chart.
|
||||
*/
|
||||
public static final AUTOSAVE_TIMER_DELAY_SEC:Float = 5.0 * SECS_PER_MIN;
|
||||
|
||||
/**
|
||||
* Number of steps in a beat.
|
||||
* One step is one 16th note and one beat is one quarter note.
|
||||
|
@ -392,7 +402,8 @@ class Constants
|
|||
public static final GHOST_TAPPING:Bool = false;
|
||||
|
||||
/**
|
||||
* The separator between an asset library and the asset path.
|
||||
* The separator between an asset library and the asset path.
|
||||
|
||||
*/
|
||||
public static final LIBRARY_SEPARATOR:String = ':';
|
||||
|
||||
|
|
23
source/funkin/util/assets/SoundUtil.hx
Normal file
23
source/funkin/util/assets/SoundUtil.hx
Normal file
|
@ -0,0 +1,23 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import flixel.system.FlxSound;
|
||||
|
||||
class SoundUtil
|
||||
{
|
||||
/**
|
||||
* Convert byte data into a playable sound.
|
||||
*
|
||||
* @param input The byte data.
|
||||
* @return The playable sound, or `null` if loading failed.
|
||||
*/
|
||||
public static function buildFlxSoundFromBytes(input:Null<Bytes>):Null<FlxSound>
|
||||
{
|
||||
if (input == null) return null;
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
||||
var output:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
return output;
|
||||
}
|
||||
}
|
15
source/funkin/util/tools/FloatTools.hx
Normal file
15
source/funkin/util/tools/FloatTools.hx
Normal file
|
@ -0,0 +1,15 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
/**
|
||||
* Utilities for performing common math operations.
|
||||
*/
|
||||
class FloatTools
|
||||
{
|
||||
/**
|
||||
* Constrain a float between a minimum and maximum value.
|
||||
*/
|
||||
public static function clamp(value:Float, min:Float, max:Float):Float
|
||||
{
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
16
source/funkin/util/tools/IntTools.hx
Normal file
16
source/funkin/util/tools/IntTools.hx
Normal file
|
@ -0,0 +1,16 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
/**
|
||||
* Utilities for performing common math operations.
|
||||
*/
|
||||
class IntTools
|
||||
{
|
||||
/**
|
||||
* Constrain an integer between a minimum and maximum value.
|
||||
*/
|
||||
public static function clamp(value:Int, min:Int, max:Int):Int
|
||||
{
|
||||
// Don't use Math.min because it returns a Float.
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue