diff --git a/Project.xml b/Project.xml
index e0677b026..74da3b749 100644
--- a/Project.xml
+++ b/Project.xml
@@ -111,6 +111,7 @@
+
@@ -127,7 +128,7 @@
-
+
diff --git a/art b/art
index 1656bea53..03e7c2a23 160000
--- a/art
+++ b/art
@@ -1 +1 @@
-Subproject commit 1656bea5370c65879aaeb323e329f403c78071c5
+Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34
diff --git a/assets b/assets
index 198c3ab87..a3e2277e6 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 198c3ab87e401e595be50814af0f667eb1be25e7
+Subproject commit a3e2277e6f12208f9a976b80883db67c54a2a897
diff --git a/hmm.json b/hmm.json
index 57fbbb555..d461edd24 100644
--- a/hmm.json
+++ b/hmm.json
@@ -54,14 +54,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
- "ref": "e765a3e0b7a653823e8dec765e04623f27f573f8",
+ "ref": "5086e59e7551d775ed4d1fb0188e31de22d1312b",
"url": "https://github.com/haxeui/haxeui-core"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
- "ref": "7a517d561eff49d8123c128bf9f5c1123b84d014",
+ "ref": "2b9cff727999b53ed292b1675ac1c9089ac77600",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{
diff --git a/source/Main.hx b/source/Main.hx
index 5fbb6747b..86e520e69 100644
--- a/source/Main.hx
+++ b/source/Main.hx
@@ -112,5 +112,6 @@ class Main extends Sprite
Toolkit.theme = 'dark'; // don't be cringe
Toolkit.autoScale = false;
funkin.input.Cursor.registerHaxeUICursors();
+ haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
}
}
diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx
index 7b34bffe2..05c23108f 100644
--- a/source/funkin/Conductor.hx
+++ b/source/funkin/Conductor.hx
@@ -11,6 +11,7 @@ import funkin.data.song.SongDataUtils;
* A core class which handles musical timing throughout the game,
* both in gameplay and in menus.
*/
+@:nullSafety
class Conductor
{
// onBeatHit is called every quarter note
@@ -28,29 +29,53 @@ class Conductor
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 7/8 = 3.5 beats per measure = 14 steps per measure
+ /**
+ * The current instance of the Conductor.
+ * If one doesn't currently exist, a new one will be created.
+ *
+ * You can also do stuff like store a reference to the Conductor and pass it around or temporarily replace it,
+ * or have a second Conductor running at the same time, or other weird stuff like that if you need to.
+ */
+ public static var instance:Conductor = new Conductor();
+
+ /**
+ * Signal fired when the current Conductor instance advances to a new measure.
+ */
+ public static var measureHit(default, null):FlxSignal = new FlxSignal();
+
+ /**
+ * Signal fired when the current Conductor instance advances to a new beat.
+ */
+ public static var beatHit(default, null):FlxSignal = new FlxSignal();
+
+ /**
+ * Signal fired when the current Conductor instance advances to a new step.
+ */
+ public static var stepHit(default, null):FlxSignal = new FlxSignal();
+
/**
* The list of time changes in the song.
* There should be at least one time change (at the beginning of the song) to define the BPM.
*/
- static var timeChanges:Array = [];
+ var timeChanges:Array = [];
/**
* The most recent time change for the current song position.
*/
- public static var currentTimeChange(default, null):Null;
+ public var currentTimeChange(default, null):Null;
/**
* The current position in the song in milliseconds.
- * Update this every frame based on the audio position using `Conductor.update()`.
+ * Update this every frame based on the audio position using `Conductor.instance.update()`.
*/
- public static var songPosition(default, null):Float = 0;
+ public var songPosition(default, null):Float = 0;
/**
* Beats per minute of the current song at the current time.
*/
- public static var bpm(get, never):Float;
+ public var bpm(get, never):Float;
- static function get_bpm():Float
+ function get_bpm():Float
{
if (bpmOverride != null) return bpmOverride;
@@ -62,9 +87,9 @@ class Conductor
/**
* Beats per minute of the current song at the start time.
*/
- public static var startingBPM(get, never):Float;
+ public var startingBPM(get, never):Float;
- static function get_startingBPM():Float
+ function get_startingBPM():Float
{
if (bpmOverride != null) return bpmOverride;
@@ -78,14 +103,14 @@ class Conductor
* The current value set by `forceBPM`.
* If false, BPM is determined by time changes.
*/
- static var bpmOverride:Null = null;
+ var bpmOverride:Null = null;
/**
* Duration of a measure in milliseconds. Calculated based on bpm.
*/
- public static var measureLengthMs(get, never):Float;
+ public var measureLengthMs(get, never):Float;
- static function get_measureLengthMs():Float
+ function get_measureLengthMs():Float
{
return beatLengthMs * timeSignatureNumerator;
}
@@ -93,9 +118,9 @@ class Conductor
/**
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
*/
- public static var beatLengthMs(get, never):Float;
+ public var beatLengthMs(get, never):Float;
- static function get_beatLengthMs():Float
+ function get_beatLengthMs():Float
{
// Tied directly to BPM.
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
@@ -104,25 +129,25 @@ class Conductor
/**
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
*/
- public static var stepLengthMs(get, never):Float;
+ public var stepLengthMs(get, never):Float;
- static function get_stepLengthMs():Float
+ function get_stepLengthMs():Float
{
return beatLengthMs / timeSignatureNumerator;
}
- public static var timeSignatureNumerator(get, never):Int;
+ public var timeSignatureNumerator(get, never):Int;
- static function get_timeSignatureNumerator():Int
+ function get_timeSignatureNumerator():Int
{
if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_NUM;
return currentTimeChange.timeSignatureNum;
}
- public static var timeSignatureDenominator(get, never):Int;
+ public var timeSignatureDenominator(get, never):Int;
- static function get_timeSignatureDenominator():Int
+ function get_timeSignatureDenominator():Int
{
if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_DEN;
@@ -132,44 +157,44 @@ class Conductor
/**
* Current position in the song, in measures.
*/
- public static var currentMeasure(default, null):Int = 0;
+ public var currentMeasure(default, null):Int = 0;
/**
* Current position in the song, in beats.
*/
- public static var currentBeat(default, null):Int = 0;
+ public var currentBeat(default, null):Int = 0;
/**
* Current position in the song, in steps.
*/
- public static var currentStep(default, null):Int = 0;
+ public var currentStep(default, null):Int = 0;
/**
* Current position in the song, in measures and fractions of a measure.
*/
- public static var currentMeasureTime(default, null):Float = 0;
+ public var currentMeasureTime(default, null):Float = 0;
/**
* Current position in the song, in beats and fractions of a measure.
*/
- public static var currentBeatTime(default, null):Float = 0;
+ public var currentBeatTime(default, null):Float = 0;
/**
* Current position in the song, in steps and fractions of a step.
*/
- public static var currentStepTime(default, null):Float = 0;
+ public var currentStepTime(default, null):Float = 0;
/**
* An offset tied to the current chart file to compensate for a delay in the instrumental.
*/
- public static var instrumentalOffset:Float = 0;
+ public var instrumentalOffset:Float = 0;
/**
* The instrumental offset, in terms of steps.
*/
- public static var instrumentalOffsetSteps(get, never):Float;
+ public var instrumentalOffsetSteps(get, never):Float;
- static function get_instrumentalOffsetSteps():Float
+ function get_instrumentalOffsetSteps():Float
{
var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator;
@@ -179,19 +204,19 @@ class Conductor
/**
* An offset tied to the file format of the audio file being played.
*/
- public static var formatOffset:Float = 0;
+ public var formatOffset:Float = 0;
/**
* An offset set by the user to compensate for input lag.
*/
- public static var inputOffset:Float = 0;
+ public var inputOffset:Float = 0;
/**
* The number of beats in a measure. May be fractional depending on the time signature.
*/
- public static var beatsPerMeasure(get, never):Float;
+ public var beatsPerMeasure(get, never):Float;
- static function get_beatsPerMeasure():Float
+ function get_beatsPerMeasure():Float
{
// NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
@@ -201,30 +226,15 @@ class Conductor
* The number of steps in a measure.
* TODO: I don't think this can be fractional?
*/
- public static var stepsPerMeasure(get, never):Int;
+ public var stepsPerMeasure(get, never):Int;
- static function get_stepsPerMeasure():Int
+ function get_stepsPerMeasure():Int
{
// TODO: Is this always an integer?
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
}
- /**
- * Signal fired when the Conductor advances to a new measure.
- */
- public static var measureHit(default, null):FlxSignal = new FlxSignal();
-
- /**
- * Signal fired when the Conductor advances to a new beat.
- */
- public static var beatHit(default, null):FlxSignal = new FlxSignal();
-
- /**
- * Signal fired when the Conductor advances to a new step.
- */
- public static var stepHit(default, null):FlxSignal = new FlxSignal();
-
- function new() {}
+ public function new() {}
/**
* Forcibly defines the current BPM of the song.
@@ -235,7 +245,7 @@ class Conductor
* WARNING: Avoid this for things like setting the BPM of the title screen music,
* you should have a metadata file for it instead.
*/
- public static function forceBPM(?bpm:Float = null)
+ public function forceBPM(?bpm:Float = null)
{
if (bpm != null)
{
@@ -246,7 +256,7 @@ class Conductor
// trace('[CONDUCTOR] Resetting BPM to default');
}
- Conductor.bpmOverride = bpm;
+ this.bpmOverride = bpm;
}
/**
@@ -256,29 +266,29 @@ class Conductor
* @param songPosition The current position in the song in milliseconds.
* Leave blank to use the FlxG.sound.music position.
*/
- public static function update(?songPosition:Float)
+ public function update(?songPos:Float)
{
- if (songPosition == null)
+ if (songPos == null)
{
// Take into account instrumental and file format song offsets.
- songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
+ songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
}
- var oldMeasure = currentMeasure;
- var oldBeat = currentBeat;
- var oldStep = currentStep;
+ var oldMeasure = this.currentMeasure;
+ var oldBeat = this.currentBeat;
+ var oldStep = this.currentStep;
// Set the song position we are at (for purposes of calculating note positions, etc).
- Conductor.songPosition = songPosition;
+ this.songPosition = songPos;
currentTimeChange = timeChanges[0];
- if (Conductor.songPosition > 0.0)
+ if (this.songPosition > 0.0)
{
for (i in 0...timeChanges.length)
{
- if (songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
+ if (this.songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
- if (songPosition < timeChanges[i].timeStamp) break;
+ if (this.songPosition < timeChanges[i].timeStamp) break;
}
}
@@ -286,45 +296,49 @@ class Conductor
{
trace('WARNING: Conductor is broken, timeChanges is empty.');
}
- else if (currentTimeChange != null && Conductor.songPosition > 0.0)
+ else if (currentTimeChange != null && this.songPosition > 0.0)
{
// roundDecimal prevents representing 8 as 7.9999999
- currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
- currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
- currentMeasureTime = currentStepTime / stepsPerMeasure;
- currentStep = Math.floor(currentStepTime);
- currentBeat = Math.floor(currentBeatTime);
- currentMeasure = Math.floor(currentMeasureTime);
+ this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
+ this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
+ this.currentMeasureTime = currentStepTime / stepsPerMeasure;
+ this.currentStep = Math.floor(currentStepTime);
+ this.currentBeat = Math.floor(currentBeatTime);
+ this.currentMeasure = Math.floor(currentMeasureTime);
}
else
{
// Assume a constant BPM equal to the forced value.
- currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4);
- currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
- currentMeasureTime = currentStepTime / stepsPerMeasure;
- currentStep = Math.floor(currentStepTime);
- currentBeat = Math.floor(currentBeatTime);
- currentMeasure = Math.floor(currentMeasureTime);
+ this.currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4);
+ this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
+ this.currentMeasureTime = currentStepTime / stepsPerMeasure;
+ this.currentStep = Math.floor(currentStepTime);
+ this.currentBeat = Math.floor(currentBeatTime);
+ this.currentMeasure = Math.floor(currentMeasureTime);
}
- // FlxSignals are really cool.
- if (currentStep != oldStep)
+ // Only fire the signal if we are THE Conductor.
+ if (this == Conductor.instance)
{
- stepHit.dispatch();
- }
+ // FlxSignals are really cool.
+ if (currentStep != oldStep)
+ {
+ Conductor.stepHit.dispatch();
+ }
- if (currentBeat != oldBeat)
- {
- beatHit.dispatch();
- }
+ if (currentBeat != oldBeat)
+ {
+ Conductor.beatHit.dispatch();
+ }
- if (currentMeasure != oldMeasure)
- {
- measureHit.dispatch();
+ if (currentMeasure != oldMeasure)
+ {
+ Conductor.measureHit.dispatch();
+ }
}
}
- public static function mapTimeChanges(songTimeChanges:Array)
+ public function mapTimeChanges(songTimeChanges:Array)
{
timeChanges = [];
@@ -338,24 +352,21 @@ class Conductor
// Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`.
if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0;
- if (currentTimeChange.beatTime == null)
+ if (currentTimeChange.timeStamp <= 0.0)
{
- if (currentTimeChange.timeStamp <= 0.0)
- {
- currentTimeChange.beatTime = 0.0;
- }
- else
- {
- // Calculate the beat time of this timestamp.
- currentTimeChange.beatTime = 0.0;
+ currentTimeChange.beatTime = 0.0;
+ }
+ else
+ {
+ // Calculate the beat time of this timestamp.
+ currentTimeChange.beatTime = 0.0;
- if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
- {
- var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
- currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
- + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
- 4);
- }
+ if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
+ {
+ var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
+ currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
+ 4);
}
}
@@ -368,13 +379,13 @@ class Conductor
}
// Update currentStepTime
- Conductor.update(Conductor.songPosition);
+ this.update(Conductor.instance.songPosition);
}
/**
* Given a time in milliseconds, return a time in steps.
*/
- public static function getTimeInSteps(ms:Float):Float
+ public function getTimeInSteps(ms:Float):Float
{
if (timeChanges.length == 0)
{
@@ -411,7 +422,7 @@ class Conductor
/**
* Given a time in steps and fractional steps, return a time in milliseconds.
*/
- public static function getStepTimeInMs(stepTime:Float):Float
+ public function getStepTimeInMs(stepTime:Float):Float
{
if (timeChanges.length == 0)
{
@@ -447,7 +458,7 @@ class Conductor
/**
* Given a time in beats and fractional beats, return a time in milliseconds.
*/
- public static function getBeatTimeInMs(beatTime:Float):Float
+ public function getBeatTimeInMs(beatTime:Float):Float
{
if (timeChanges.length == 0)
{
@@ -480,13 +491,20 @@ class Conductor
}
}
+ public static function watchQuick():Void
+ {
+ FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition);
+ FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
+ FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime);
+ FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
+ FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
+ }
+
+ /**
+ * Reset the Conductor, replacing the current instance with a fresh one.
+ */
public static function reset():Void
{
- beatHit.removeAll();
- stepHit.removeAll();
-
- mapTimeChanges([]);
- forceBPM(null);
- update(0);
+ Conductor.instance = new Conductor();
}
}
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 13bcd306e..02b46c88c 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -19,7 +19,7 @@ import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData;
import funkin.data.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
-import funkin.data.event.SongEventData.SongEventParser;
+import funkin.data.event.SongEventRegistry;
import funkin.play.cutscene.dialogue.ConversationDataParser;
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
import funkin.play.cutscene.dialogue.SpeakerDataParser;
@@ -197,6 +197,13 @@ class InitState extends FlxState
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
#end
+ //
+ // FLIXEL PLUGINS
+ //
+ funkin.util.plugins.EvacuateDebugPlugin.initialize();
+ funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
+ funkin.util.plugins.WatchPlugin.initialize();
+
//
// GAME DATA PARSING
//
@@ -206,7 +213,7 @@ class InitState extends FlxState
SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries();
- SongEventParser.loadEventCache();
+ SongEventRegistry.loadEventCache();
ConversationDataParser.loadConversationCache();
DialogueBoxDataParser.loadDialogueBoxCache();
SpeakerDataParser.loadSpeakerCache();
diff --git a/source/funkin/audio/visualize/ABotVis.hx b/source/funkin/audio/visualize/ABotVis.hx
index 681287808..89b004df4 100644
--- a/source/funkin/audio/visualize/ABotVis.hx
+++ b/source/funkin/audio/visualize/ABotVis.hx
@@ -64,7 +64,7 @@ class ABotVis extends FlxTypedSpriteGroup
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
else
- remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, vis.numSamples));
+ remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
var fftSamples:Array = [];
diff --git a/source/funkin/audio/visualize/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx
index 63d0fcd2e..b4e024a4c 100644
--- a/source/funkin/audio/visualize/SpectogramSprite.hx
+++ b/source/funkin/audio/visualize/SpectogramSprite.hx
@@ -164,7 +164,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
- remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
+ remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples));
var fftSamples:Array = [];
var i = remappedShit;
@@ -235,15 +235,15 @@ class SpectogramSprite extends FlxTypedSpriteGroup
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
{
- if (curTime == Conductor.songPosition)
+ if (curTime == Conductor.instance.songPosition)
{
wavOptimiz = 3;
return; // already did shit, so finishes function early
}
- curTime = Conductor.songPosition;
+ curTime = Conductor.instance.songPosition;
- remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
+ remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples));
}
wavOptimiz = 8;
diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventRegistry.hx
similarity index 71%
rename from source/funkin/data/event/SongEventData.hx
rename to source/funkin/data/event/SongEventRegistry.hx
index 7a167b031..dc5589813 100644
--- a/source/funkin/data/event/SongEventData.hx
+++ b/source/funkin/data/event/SongEventRegistry.hx
@@ -1,7 +1,7 @@
package funkin.data.event;
import funkin.play.event.SongEvent;
-import funkin.data.event.SongEventData.SongEventSchema;
+import funkin.data.event.SongEventSchema;
import funkin.data.song.SongData.SongEventData;
import funkin.util.macro.ClassMacro;
import funkin.play.event.ScriptedSongEvent;
@@ -9,7 +9,7 @@ import funkin.play.event.ScriptedSongEvent;
/**
* This class statically handles the parsing of internal and scripted song event handlers.
*/
-class SongEventParser
+class SongEventRegistry
{
/**
* Every built-in event class must be added to this list.
@@ -160,84 +160,3 @@ class SongEventParser
}
}
}
-
-enum abstract SongEventFieldType(String) from String to String
-{
- /**
- * The STRING type will display as a text field.
- */
- var STRING = "string";
-
- /**
- * The INTEGER type will display as a text field that only accepts numbers.
- */
- var INTEGER = "integer";
-
- /**
- * The FLOAT type will display as a text field that only accepts numbers.
- */
- var FLOAT = "float";
-
- /**
- * The BOOL type will display as a checkbox.
- */
- var BOOL = "bool";
-
- /**
- * The ENUM type will display as a dropdown.
- * Make sure to specify the `keys` field in the schema.
- */
- var ENUM = "enum";
-}
-
-typedef SongEventSchemaField =
-{
- /**
- * The name of the property as it should be saved in the event data.
- */
- name:String,
-
- /**
- * The title of the field to display in the UI.
- */
- title:String,
-
- /**
- * The type of the field.
- */
- type:SongEventFieldType,
-
- /**
- * Used only for ENUM values.
- * The key is the display name and the value is the actual value.
- */
- ?keys:Map,
-
- /**
- * Used for INTEGER and FLOAT values.
- * The minimum value that can be entered.
- * @default No minimum
- */
- ?min:Float,
-
- /**
- * Used for INTEGER and FLOAT values.
- * The maximum value that can be entered.
- * @default No maximum
- */
- ?max:Float,
-
- /**
- * Used for INTEGER and FLOAT values.
- * The step value that will be used when incrementing/decrementing the value.
- * @default `0.1`
- */
- ?step:Float,
-
- /**
- * An optional default value for the field.
- */
- ?defaultValue:Dynamic,
-}
-
-typedef SongEventSchema = Array;
diff --git a/source/funkin/data/event/SongEventSchema.hx b/source/funkin/data/event/SongEventSchema.hx
new file mode 100644
index 000000000..b5b2978d7
--- /dev/null
+++ b/source/funkin/data/event/SongEventSchema.hx
@@ -0,0 +1,125 @@
+package funkin.data.event;
+
+import funkin.play.event.SongEvent;
+import funkin.data.event.SongEventSchema;
+import funkin.data.song.SongData.SongEventData;
+import funkin.util.macro.ClassMacro;
+import funkin.play.event.ScriptedSongEvent;
+
+@:forward(name, tittlte, type, keys, min, max, step, defaultValue, iterator)
+abstract SongEventSchema(SongEventSchemaRaw)
+{
+ public function new(?fields:Array)
+ {
+ this = fields;
+ }
+
+ @:arrayAccess
+ public inline function getByName(name:String):SongEventSchemaField
+ {
+ for (field in this)
+ {
+ if (field.name == name) return field;
+ }
+
+ return null;
+ }
+
+ public function getFirstField():SongEventSchemaField
+ {
+ return this[0];
+ }
+
+ @:arrayAccess
+ public inline function get(key:Int)
+ {
+ return this[key];
+ }
+
+ @:arrayAccess
+ public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
+ {
+ return this[k] = v;
+ }
+}
+
+typedef SongEventSchemaRaw = Array;
+
+typedef SongEventSchemaField =
+{
+ /**
+ * The name of the property as it should be saved in the event data.
+ */
+ name:String,
+
+ /**
+ * The title of the field to display in the UI.
+ */
+ title:String,
+
+ /**
+ * The type of the field.
+ */
+ type:SongEventFieldType,
+
+ /**
+ * Used only for ENUM values.
+ * The key is the display name and the value is the actual value.
+ */
+ ?keys:Map,
+
+ /**
+ * Used for INTEGER and FLOAT values.
+ * The minimum value that can be entered.
+ * @default No minimum
+ */
+ ?min:Float,
+
+ /**
+ * Used for INTEGER and FLOAT values.
+ * The maximum value that can be entered.
+ * @default No maximum
+ */
+ ?max:Float,
+
+ /**
+ * Used for INTEGER and FLOAT values.
+ * The step value that will be used when incrementing/decrementing the value.
+ * @default `0.1`
+ */
+ ?step:Float,
+
+ /**
+ * An optional default value for the field.
+ */
+ ?defaultValue:Dynamic,
+}
+
+enum abstract SongEventFieldType(String) from String to String
+{
+ /**
+ * The STRING type will display as a text field.
+ */
+ var STRING = "string";
+
+ /**
+ * The INTEGER type will display as a text field that only accepts numbers.
+ */
+ var INTEGER = "integer";
+
+ /**
+ * The FLOAT type will display as a text field that only accepts numbers.
+ */
+ var FLOAT = "float";
+
+ /**
+ * The BOOL type will display as a checkbox.
+ */
+ var BOOL = "bool";
+
+ /**
+ * The ENUM type will display as a dropdown.
+ * Make sure to specify the `keys` field in the schema.
+ */
+ var ENUM = "enum";
+}
diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx
index 600871e2f..1a726254f 100644
--- a/source/funkin/data/song/SongData.hx
+++ b/source/funkin/data/song/SongData.hx
@@ -1,7 +1,10 @@
package funkin.data.song;
+import funkin.data.event.SongEventRegistry;
+import funkin.data.event.SongEventSchema;
import funkin.data.song.SongRegistry;
import thx.semver.Version;
+import funkin.util.tools.ICloneable;
/**
* Data containing information about a song.
@@ -9,7 +12,7 @@ import thx.semver.Version;
* Data which is only necessary in-game should be stored in the SongChartData.
*/
@:nullSafety
-class SongMetadata
+class SongMetadata implements ICloneable
{
/**
* A semantic versioning string for the song data format.
@@ -84,16 +87,16 @@ class SongMetadata
* @param newVariation Set to a new variation ID to change the new metadata.
* @return The cloned SongMetadata
*/
- public function clone(?newVariation:String = null):SongMetadata
+ public function clone():SongMetadata
{
- var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
+ var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.variation);
result.version = this.version;
result.timeFormat = this.timeFormat;
result.divisions = this.divisions;
- result.offsets = this.offsets;
- result.timeChanges = this.timeChanges;
+ result.offsets = this.offsets.clone();
+ result.timeChanges = this.timeChanges.deepClone();
result.looped = this.looped;
- result.playData = this.playData;
+ result.playData = this.playData.clone();
result.generatedBy = this.generatedBy;
return result;
@@ -128,7 +131,7 @@ enum abstract SongTimeFormat(String) from String to String
var MILLISECONDS = 'ms';
}
-class SongTimeChange
+class SongTimeChange implements ICloneable
{
public static final DEFAULT_SONGTIMECHANGE:SongTimeChange = new SongTimeChange(0, 100);
@@ -149,7 +152,7 @@ class SongTimeChange
*/
@:optional
@:alias("b")
- public var beatTime:Null;
+ public var beatTime:Float;
/**
* Quarter notes per minute (float). Cannot be empty in the first element of the list,
@@ -195,6 +198,11 @@ class SongTimeChange
this.beatTuplets = beatTuplets == null ? DEFAULT_BEAT_TUPLETS : beatTuplets;
}
+ public function clone():SongTimeChange
+ {
+ return new SongTimeChange(this.timeStamp, this.bpm, this.timeSignatureNum, this.timeSignatureDen, this.beatTime, this.beatTuplets);
+ }
+
/**
* Produces a string representation suitable for debugging.
*/
@@ -209,7 +217,7 @@ class SongTimeChange
* These are intended to correct for issues with the chart, or with the song's audio (for example a 10ms delay before the song starts).
* This is independent of the offsets applied in the user's settings, which are applied after these offsets and intended to correct for the user's hardware.
*/
-class SongOffsets
+class SongOffsets implements ICloneable
{
/**
* The offset, in milliseconds, to apply to the song's instrumental relative to the chart.
@@ -279,6 +287,15 @@ class SongOffsets
return value;
}
+ public function clone():SongOffsets
+ {
+ var result:SongOffsets = new SongOffsets(this.instrumental);
+ result.altInstrumentals = this.altInstrumentals.clone();
+ result.vocals = this.vocals.clone();
+
+ return result;
+ }
+
/**
* Produces a string representation suitable for debugging.
*/
@@ -292,7 +309,7 @@ class SongOffsets
* Metadata for a song only used for the music.
* For example, the menu music.
*/
-class SongMusicData
+class SongMusicData implements ICloneable
{
/**
* A semantic versioning string for the song data format.
@@ -346,13 +363,13 @@ class SongMusicData
this.variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
}
- public function clone(?newVariation:String = null):SongMusicData
+ public function clone():SongMusicData
{
- var result:SongMusicData = new SongMusicData(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
+ var result:SongMusicData = new SongMusicData(this.songName, this.artist, this.variation);
result.version = this.version;
result.timeFormat = this.timeFormat;
result.divisions = this.divisions;
- result.timeChanges = this.timeChanges;
+ result.timeChanges = this.timeChanges.clone();
result.looped = this.looped;
result.generatedBy = this.generatedBy;
@@ -368,7 +385,7 @@ class SongMusicData
}
}
-class SongPlayData
+class SongPlayData implements ICloneable
{
/**
* The variations this song has. The associated metadata files should exist.
@@ -417,6 +434,20 @@ class SongPlayData
ratings = new Map();
}
+ public function clone():SongPlayData
+ {
+ var result:SongPlayData = new SongPlayData();
+ result.songVariations = this.songVariations.clone();
+ result.difficulties = this.difficulties.clone();
+ result.characters = this.characters.clone();
+ result.stage = this.stage;
+ result.noteStyle = this.noteStyle;
+ result.ratings = this.ratings.clone();
+ result.album = this.album;
+
+ return result;
+ }
+
/**
* Produces a string representation suitable for debugging.
*/
@@ -430,7 +461,7 @@ class SongPlayData
* Information about the characters used in this variation of the song.
* Create a new variation if you want to change the characters.
*/
-class SongCharacterData
+class SongCharacterData implements ICloneable
{
@:optional
@:default('')
@@ -460,6 +491,14 @@ class SongCharacterData
this.instrumental = instrumental;
}
+ public function clone():SongCharacterData
+ {
+ var result:SongCharacterData = new SongCharacterData(this.player, this.girlfriend, this.opponent, this.instrumental);
+ result.altInstrumentals = this.altInstrumentals.clone();
+
+ return result;
+ }
+
/**
* Produces a string representation suitable for debugging.
*/
@@ -469,7 +508,7 @@ class SongCharacterData
}
}
-class SongChartData
+class SongChartData implements ICloneable
{
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@@ -539,6 +578,24 @@ class SongChartData
return writer.write(this, pretty ? ' ' : null);
}
+ public function clone():SongChartData
+ {
+ // We have to manually perform the deep clone here because Map.deepClone() doesn't work.
+ var noteDataClone:Map> = new Map>();
+ for (key in this.notes.keys())
+ {
+ noteDataClone.set(key, this.notes.get(key).deepClone());
+ }
+ var eventDataClone:Array = this.events.deepClone();
+
+ var result:SongChartData = new SongChartData(this.scrollSpeed.clone(), eventDataClone, noteDataClone);
+ result.version = this.version;
+ result.generatedBy = this.generatedBy;
+ result.variation = this.variation;
+
+ return result;
+ }
+
/**
* Produces a string representation suitable for debugging.
*/
@@ -548,7 +605,7 @@ class SongChartData
}
}
-class SongEventDataRaw
+class SongEventDataRaw implements ICloneable
{
/**
* The timestamp of the event. The timestamp is in the format of the song's time format.
@@ -602,14 +659,19 @@ class SongEventDataRaw
{
if (_stepTime != null && !force) return _stepTime;
- return _stepTime = Conductor.getTimeInSteps(this.time);
+ return _stepTime = Conductor.instance.getTimeInSteps(this.time);
+ }
+
+ public function clone():SongEventDataRaw
+ {
+ return new SongEventDataRaw(this.time, this.event, this.value);
}
}
/**
* Wrap SongEventData in an abstract so we can overload operators.
*/
-@:forward(time, event, value, activated, getStepTime)
+@:forward(time, event, value, activated, getStepTime, clone)
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
{
public function new(time:Float, event:String, value:Dynamic = null)
@@ -617,6 +679,33 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
this = new SongEventDataRaw(time, event, value);
}
+ public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic
+ {
+ if (this.value == null) return {};
+ if (Std.isOfType(this.value, Array))
+ {
+ var result:haxe.DynamicAccess = {};
+ result.set(defaultKey, this.value);
+ return cast result;
+ }
+ else if (Reflect.isObject(this.value))
+ {
+ // We enter this case if the value is a struct.
+ return cast this.value;
+ }
+ else
+ {
+ var result:haxe.DynamicAccess = {};
+ result.set(defaultKey, this.value);
+ return cast result;
+ }
+ }
+
+ public inline function getSchema():Null
+ {
+ return SongEventRegistry.getEventSchema(this.event);
+ }
+
public inline function getDynamic(key:String):Null
{
return this.value == null ? null : Reflect.field(this.value, key);
@@ -662,11 +751,6 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
return this.value == null ? null : cast Reflect.field(this.value, key);
}
- public function clone():SongEventData
- {
- return new SongEventData(this.time, this.event, this.value);
- }
-
@:op(A == B)
public function op_equals(other:SongEventData):Bool
{
@@ -712,7 +796,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
}
}
-class SongNoteDataRaw
+class SongNoteDataRaw implements ICloneable
{
/**
* The timestamp of the note. The timestamp is in the format of the song's time format.
@@ -796,7 +880,7 @@ class SongNoteDataRaw
{
if (_stepTime != null && !force) return _stepTime;
- return _stepTime = Conductor.getTimeInSteps(this.time);
+ return _stepTime = Conductor.instance.getTimeInSteps(this.time);
}
@:jignored
@@ -812,7 +896,7 @@ class SongNoteDataRaw
if (_stepLength != null && !force) return _stepLength;
- return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
+ return _stepLength = Conductor.instance.getTimeInSteps(this.time + this.length) - getStepTime();
}
public function setStepLength(value:Float):Void
@@ -823,11 +907,16 @@ class SongNoteDataRaw
}
else
{
- var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
+ var lengthMs:Float = Conductor.instance.getStepTimeInMs(value) - this.time;
this.length = lengthMs;
}
_stepLength = null;
}
+
+ public function clone():SongNoteDataRaw
+ {
+ return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
+ }
}
/**
diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx
index 7716f0f02..b7ef07be5 100644
--- a/source/funkin/modding/PolymodHandler.hx
+++ b/source/funkin/modding/PolymodHandler.hx
@@ -8,7 +8,7 @@ import funkin.play.stage.StageData;
import polymod.Polymod;
import polymod.backends.PolymodAssets.PolymodAssetType;
import polymod.format.ParseRules.TextFileFormat;
-import funkin.data.event.SongEventData.SongEventParser;
+import funkin.data.event.SongEventRegistry;
import funkin.util.FileUtil;
import funkin.data.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
@@ -271,7 +271,7 @@ class PolymodHandler
SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries();
- SongEventParser.loadEventCache();
+ SongEventRegistry.loadEventCache();
ConversationDataParser.loadConversationCache();
DialogueBoxDataParser.loadDialogueBoxCache();
SpeakerDataParser.loadSpeakerCache();
diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx
index d23574ce2..5b7ce9fc2 100644
--- a/source/funkin/play/Countdown.hx
+++ b/source/funkin/play/Countdown.hx
@@ -40,7 +40,7 @@ class Countdown
stopCountdown();
PlayState.instance.isInCountdown = true;
- Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
+ Conductor.instance.update(PlayState.instance.startTimestamp + Conductor.instance.beatLengthMs * -5);
// Handle onBeatHit events manually
// @:privateAccess
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
@@ -48,7 +48,7 @@ class Countdown
// The timer function gets called based on the beat of the song.
countdownTimer = new FlxTimer();
- countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) {
+ countdownTimer.start(Conductor.instance.beatLengthMs / 1000, function(tmr:FlxTimer) {
if (PlayState.instance == null)
{
tmr.cancel();
@@ -158,7 +158,7 @@ class Countdown
{
stopCountdown();
// This will trigger PlayState.startSong()
- Conductor.update(0);
+ Conductor.instance.update(0);
// PlayState.isInCountdown = false;
}
@@ -225,7 +225,7 @@ class Countdown
countdownSprite.screenCenter();
// Fade sprite in, then out, then destroy it.
- FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.beatLengthMs / 1000,
+ FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000,
{
ease: FlxEase.cubeInOut,
onComplete: function(twn:FlxTween) {
diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx
index dadd5a3d9..137bf3905 100644
--- a/source/funkin/play/GameOverSubState.hx
+++ b/source/funkin/play/GameOverSubState.hx
@@ -129,7 +129,7 @@ class GameOverSubState extends MusicBeatSubState
gameOverMusic.stop();
// The conductor now represents the BPM of the game over music.
- Conductor.update(0);
+ Conductor.instance.update(0);
}
var hasStartedAnimation:Bool = false;
@@ -204,7 +204,7 @@ class GameOverSubState extends MusicBeatSubState
{
// Match the conductor to the music.
// This enables the stepHit and beatHit events.
- Conductor.update(gameOverMusic.time);
+ Conductor.instance.update(gameOverMusic.time);
}
else
{
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 3dcabf953..995797dd1 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -42,7 +42,7 @@ import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.ConversationDataParser;
import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
-import funkin.data.event.SongEventData.SongEventParser;
+import funkin.data.event.SongEventRegistry;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.NoteDirection;
import funkin.play.notes.Strumline;
@@ -561,15 +561,15 @@ class PlayState extends MusicBeatSubState
}
// Prepare the Conductor.
- Conductor.forceBPM(null);
+ Conductor.instance.forceBPM(null);
if (currentChart.offsets != null)
{
- Conductor.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
+ Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
}
- Conductor.mapTimeChanges(currentChart.timeChanges);
- Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
+ Conductor.instance.mapTimeChanges(currentChart.timeChanges);
+ Conductor.instance.update((Conductor.instance.beatLengthMs * -5) + startTimestamp);
// The song is now loaded. We can continue to initialize the play state.
initCameras();
@@ -734,7 +734,7 @@ class PlayState extends MusicBeatSubState
// Reset music properly.
- FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instrumentalOffset);
+ FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instance.instrumentalOffset);
FlxG.sound.music.pause();
if (!overrideMusic)
@@ -785,22 +785,22 @@ class PlayState extends MusicBeatSubState
{
if (isInCountdown)
{
- Conductor.update(Conductor.songPosition + elapsed * 1000);
- if (Conductor.songPosition >= (startTimestamp)) startSong();
+ Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000);
+ if (Conductor.instance.songPosition >= (startTimestamp)) startSong();
}
}
else
{
if (Constants.EXT_SOUND == 'mp3')
{
- Conductor.formatOffset = Constants.MP3_DELAY_MS;
+ Conductor.instance.formatOffset = Constants.MP3_DELAY_MS;
}
else
{
- Conductor.formatOffset = 0.0;
+ Conductor.instance.formatOffset = 0.0;
}
- Conductor.update(); // Normal conductor update.
+ Conductor.instance.update(); // Normal conductor update.
}
var androidPause:Bool = false;
@@ -942,7 +942,7 @@ class PlayState extends MusicBeatSubState
// TODO: Check that these work even when songPosition is less than 0.
if (songEvents != null && songEvents.length > 0)
{
- var songEventsToActivate:Array = SongEventParser.queryEvents(songEvents, Conductor.songPosition);
+ var songEventsToActivate:Array = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition);
if (songEventsToActivate.length > 0)
{
@@ -950,7 +950,7 @@ class PlayState extends MusicBeatSubState
for (event in songEventsToActivate)
{
// If an event is trying to play, but it's over 5 seconds old, skip it.
- if (event.time - Conductor.songPosition < -5000)
+ if (event.time - Conductor.instance.songPosition < -5000)
{
event.activated = true;
continue;
@@ -961,7 +961,7 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips the event. Neat!
if (!eventEvent.eventCanceled)
{
- SongEventParser.handleEvent(event);
+ SongEventRegistry.handleEvent(event);
}
}
}
@@ -1052,7 +1052,7 @@ class PlayState extends MusicBeatSubState
if (startTimer.finished)
{
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
- currentSongLengthMs - Conductor.songPosition);
+ currentSongLengthMs - Conductor.instance.songPosition);
}
else
{
@@ -1076,12 +1076,12 @@ class PlayState extends MusicBeatSubState
{
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
{
- if (Conductor.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
+ if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
+ ' ('
+ storyDifficultyText
+ ')', iconRPC, true,
currentSongLengthMs
- - Conductor.songPosition);
+ - Conductor.instance.songPosition);
else
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
}
@@ -1154,17 +1154,17 @@ class PlayState extends MusicBeatSubState
if (!startingSong
&& FlxG.sound.music != null
- && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset)) > 200
- || Math.abs(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset)) > 200))
+ && (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200
+ || Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200))
{
trace("VOCALS NEED RESYNC");
- if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset));
- trace(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset));
+ if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
+ trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
resyncVocals();
}
- if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.currentStep));
- if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.currentStep));
+ if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
+ if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
return true;
}
@@ -1185,14 +1185,14 @@ class PlayState extends MusicBeatSubState
}
// Only zoom camera if we are zoomed by less than 35%.
- if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.currentBeat % cameraZoomRate == 0)
+ if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0)
{
// Zoom camera in (1.5%)
FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom;
// Hud zooms double (3%)
camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom;
}
- // trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.currentBeat} % ${cameraZoomRate} == ${Conductor.currentBeat % cameraZoomRate}}');
+ // trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}');
// That combo milestones that got spoiled that one time.
// Comes with NEAT visual and audio effects.
@@ -1205,13 +1205,13 @@ class PlayState extends MusicBeatSubState
// TODO: Re-enable combo text (how to do this without sections?).
// if (currentSong != null)
// {
- // shouldShowComboText = (Conductor.currentBeat % 8 == 7);
- // var daSection = .getSong()[Std.int(Conductor.currentBeat / 16)];
+ // shouldShowComboText = (Conductor.instance.currentBeat % 8 == 7);
+ // var daSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16)];
// shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection);
// shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5);
//
- // var daNextSection = .getSong()[Std.int(Conductor.currentBeat / 16) + 1];
- // var isEndOfSong = .getSong().length < Std.int(Conductor.currentBeat / 16);
+ // var daNextSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16) + 1];
+ // var isEndOfSong = .getSong().length < Std.int(Conductor.instance.currentBeat / 16);
// shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection));
// }
@@ -1224,7 +1224,7 @@ class PlayState extends MusicBeatSubState
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation
- new FlxTimer().start(((Conductor.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
+ new FlxTimer().start(((Conductor.instance.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
animShit.forceFinish();
});
}
@@ -1261,10 +1261,10 @@ class PlayState extends MusicBeatSubState
if (currentStage == null) return;
// TODO: Add HEY! song events to Tutorial.
- if (Conductor.currentBeat % 16 == 15
+ if (Conductor.instance.currentBeat % 16 == 15
&& currentStage.getDad().characterId == 'gf'
- && Conductor.currentBeat > 16
- && Conductor.currentBeat < 48)
+ && Conductor.instance.currentBeat > 16
+ && Conductor.instance.currentBeat < 48)
{
currentStage.getBoyfriend().playAnimation('hey', true);
currentStage.getDad().playAnimation('cheer', true);
@@ -1575,7 +1575,7 @@ class PlayState extends MusicBeatSubState
trace('Song difficulty could not be loaded.');
}
- // Conductor.forceBPM(currentChart.getStartingBPM());
+ // Conductor.instance.forceBPM(currentChart.getStartingBPM());
if (!overrideMusic)
{
@@ -1607,7 +1607,7 @@ class PlayState extends MusicBeatSubState
// Reset song events.
songEvents = currentChart.getEvents();
- SongEventParser.resetEvents(songEvents);
+ SongEventRegistry.resetEvents(songEvents);
// Reset the notes on each strumline.
var playerNoteData:Array = [];
@@ -1706,7 +1706,7 @@ class PlayState extends MusicBeatSubState
FlxG.sound.music.onComplete = endSong;
// A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
- FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
+ FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
trace('Playing vocals...');
add(vocals);
@@ -1722,7 +1722,7 @@ class PlayState extends MusicBeatSubState
if (startTimestamp > 0)
{
- // FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
+ // FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
handleSkippedNotes();
}
}
@@ -1800,7 +1800,7 @@ class PlayState extends MusicBeatSubState
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
- if (Conductor.songPosition > hitWindowEnd)
+ if (Conductor.instance.songPosition > hitWindowEnd)
{
if (note.hasMissed) continue;
@@ -1810,7 +1810,7 @@ class PlayState extends MusicBeatSubState
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
}
- else if (Conductor.songPosition > hitWindowCenter)
+ else if (Conductor.instance.songPosition > hitWindowCenter)
{
if (note.hasBeenHit) continue;
@@ -1831,7 +1831,7 @@ class PlayState extends MusicBeatSubState
opponentStrumline.playNoteHoldCover(note.holdNoteSprite);
}
}
- else if (Conductor.songPosition > hitWindowStart)
+ else if (Conductor.instance.songPosition > hitWindowStart)
{
if (note.hasBeenHit || note.hasMissed) continue;
@@ -1877,14 +1877,14 @@ class PlayState extends MusicBeatSubState
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
- if (Conductor.songPosition > hitWindowEnd)
+ if (Conductor.instance.songPosition > hitWindowEnd)
{
note.tooEarly = false;
note.mayHit = false;
note.hasMissed = true;
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
}
- else if (Conductor.songPosition > hitWindowStart)
+ else if (Conductor.instance.songPosition > hitWindowStart)
{
note.tooEarly = false;
note.mayHit = true;
@@ -1951,7 +1951,7 @@ class PlayState extends MusicBeatSubState
if (note == null || note.hasBeenHit) continue;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
- if (Conductor.songPosition > hitWindowEnd)
+ if (Conductor.instance.songPosition > hitWindowEnd)
{
// We have passed this note.
// Flag the note for deletion without actually penalizing the player.
@@ -2115,7 +2115,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
- t: Std.int(Conductor.songPosition),
+ t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@@ -2125,7 +2125,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
- t: Std.int(Conductor.songPosition),
+ t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
@@ -2186,7 +2186,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
- t: Std.int(Conductor.songPosition),
+ t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@@ -2275,7 +2275,7 @@ class PlayState extends MusicBeatSubState
// Get the offset and compensate for input latency.
// Round inward (trim remainder) for consistency.
- var noteDiff:Int = Std.int(Conductor.songPosition - daNote.noteData.time - inputLatencyMs);
+ var noteDiff:Int = Std.int(Conductor.instance.songPosition - daNote.noteData.time - inputLatencyMs);
var score = Scoring.scoreNote(noteDiff, PBOT1);
var daRating = Scoring.judgeNote(noteDiff, PBOT1);
@@ -2330,7 +2330,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
- t: Std.int(Conductor.songPosition),
+ t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@@ -2340,7 +2340,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
- t: Std.int(Conductor.songPosition),
+ t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
@@ -2739,15 +2739,15 @@ class PlayState extends MusicBeatSubState
{
FlxG.sound.music.pause();
- var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
- var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps);
+ var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
+ var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes();
// regenNoteData(FlxG.sound.music.time);
- Conductor.update(FlxG.sound.music.time);
+ Conductor.instance.update(FlxG.sound.music.time);
resyncVocals();
}
diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx
index 7ad0892f6..390864148 100644
--- a/source/funkin/play/character/BaseCharacter.hx
+++ b/source/funkin/play/character/BaseCharacter.hx
@@ -367,7 +367,7 @@ class BaseCharacter extends Bopper
// This lets you add frames to the end of the sing animation to ease back into the idle!
holdTimer += event.elapsed;
- var singTimeSec:Float = singTimeSec * (Conductor.beatLengthMs * 0.001); // x beats, to ms.
+ var singTimeSec:Float = singTimeSec * (Conductor.instance.beatLengthMs * 0.001); // x beats, to ms.
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss
diff --git a/source/funkin/play/components/ComboMilestone.hx b/source/funkin/play/components/ComboMilestone.hx
index 54d1438f1..4119e45c2 100644
--- a/source/funkin/play/components/ComboMilestone.hx
+++ b/source/funkin/play/components/ComboMilestone.hx
@@ -40,7 +40,7 @@ class ComboMilestone extends FlxTypedSpriteGroup
{
if (onScreenTime < 0.9)
{
- new FlxTimer().start((Conductor.beatLengthMs / 1000) * 0.25, function(tmr) {
+ new FlxTimer().start((Conductor.instance.beatLengthMs / 1000) * 0.25, function(tmr) {
forceFinish();
});
}
diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx
index 38a6ec15a..9553856a9 100644
--- a/source/funkin/play/components/PopUpStuff.hx
+++ b/source/funkin/play/components/PopUpStuff.hx
@@ -59,7 +59,7 @@ class PopUpStuff extends FlxTypedGroup
remove(rating, true);
rating.destroy();
},
- startDelay: Conductor.beatLengthMs * 0.001
+ startDelay: Conductor.instance.beatLengthMs * 0.001
});
}
@@ -110,7 +110,7 @@ class PopUpStuff extends FlxTypedGroup
remove(comboSpr, true);
comboSpr.destroy();
},
- startDelay: Conductor.beatLengthMs * 0.001
+ startDelay: Conductor.instance.beatLengthMs * 0.001
});
var seperatedScore:Array = [];
@@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup
remove(numScore, true);
numScore.destroy();
},
- startDelay: Conductor.beatLengthMs * 0.002
+ startDelay: Conductor.instance.beatLengthMs * 0.002
});
daLoop++;
diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx
index 5f63254b0..83c978ba8 100644
--- a/source/funkin/play/event/FocusCameraSongEvent.hx
+++ b/source/funkin/play/event/FocusCameraSongEvent.hx
@@ -5,8 +5,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
-import funkin.data.event.SongEventData.SongEventSchema;
-import funkin.data.event.SongEventData.SongEventFieldType;
+import funkin.data.event.SongEventSchema;
+import funkin.data.event.SongEventSchema.SongEventFieldType;
/**
* This class represents a handler for a type of song event.
@@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent
*/
public override function getEventSchema():SongEventSchema
{
- return [
+ return new SongEventSchema([
{
name: "char",
title: "Character",
@@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent
step: 10.0,
type: SongEventFieldType.FLOAT,
}
- ];
+ ]);
}
}
diff --git a/source/funkin/play/event/PlayAnimationSongEvent.hx b/source/funkin/play/event/PlayAnimationSongEvent.hx
index 6bc625517..4e6669479 100644
--- a/source/funkin/play/event/PlayAnimationSongEvent.hx
+++ b/source/funkin/play/event/PlayAnimationSongEvent.hx
@@ -7,8 +7,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
-import funkin.data.event.SongEventData.SongEventSchema;
-import funkin.data.event.SongEventData.SongEventFieldType;
+import funkin.data.event.SongEventSchema;
+import funkin.data.event.SongEventSchema.SongEventFieldType;
class PlayAnimationSongEvent extends SongEvent
{
@@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent
*/
public override function getEventSchema():SongEventSchema
{
- return [
+ return new SongEventSchema([
{
name: 'target',
title: 'Target',
@@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent
type: SongEventFieldType.BOOL,
defaultValue: false
}
- ];
+ ]);
}
}
diff --git a/source/funkin/play/event/SetCameraBopSongEvent.hx b/source/funkin/play/event/SetCameraBopSongEvent.hx
index 3cdeb9a67..d0e01346f 100644
--- a/source/funkin/play/event/SetCameraBopSongEvent.hx
+++ b/source/funkin/play/event/SetCameraBopSongEvent.hx
@@ -8,8 +8,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
-import funkin.data.event.SongEventData.SongEventSchema;
-import funkin.data.event.SongEventData.SongEventFieldType;
+import funkin.data.event.SongEventSchema;
+import funkin.data.event.SongEventSchema.SongEventFieldType;
/**
* This class represents a handler for configuring camera bop intensity and rate.
@@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent
*/
public override function getEventSchema():SongEventSchema
{
- return [
+ return new SongEventSchema([
{
name: 'intensity',
title: 'Intensity',
@@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent
step: 1,
type: SongEventFieldType.INTEGER,
}
- ];
+ ]);
}
}
diff --git a/source/funkin/play/event/SongEvent.hx b/source/funkin/play/event/SongEvent.hx
index 36a886673..29b394c0e 100644
--- a/source/funkin/play/event/SongEvent.hx
+++ b/source/funkin/play/event/SongEvent.hx
@@ -1,7 +1,7 @@
package funkin.play.event;
import funkin.data.song.SongData.SongEventData;
-import funkin.data.event.SongEventData.SongEventSchema;
+import funkin.data.event.SongEventSchema;
/**
* This class represents a handler for a type of song event.
diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx
index 1ae76039e..a35a12e1e 100644
--- a/source/funkin/play/event/ZoomCameraSongEvent.hx
+++ b/source/funkin/play/event/ZoomCameraSongEvent.hx
@@ -8,8 +8,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
-import funkin.data.event.SongEventData.SongEventFieldType;
-import funkin.data.event.SongEventData.SongEventSchema;
+import funkin.data.event.SongEventSchema;
+import funkin.data.event.SongEventSchema.SongEventFieldType;
/**
* This class represents a handler for camera zoom events.
@@ -79,7 +79,8 @@ class ZoomCameraSongEvent extends SongEvent
return;
}
- FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000), {ease: easeFunction});
+ FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000),
+ {ease: easeFunction});
}
}
@@ -99,7 +100,7 @@ class ZoomCameraSongEvent extends SongEvent
*/
public override function getEventSchema():SongEventSchema
{
- return [
+ return new SongEventSchema([
{
name: 'zoom',
title: 'Zoom Level',
@@ -145,6 +146,6 @@ class ZoomCameraSongEvent extends SongEvent
'Elastic In/Out' => 'elasticInOut',
]
}
- ];
+ ]);
}
}
diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx
index 948e9fa5b..b312494cf 100644
--- a/source/funkin/play/notes/Strumline.hx
+++ b/source/funkin/play/notes/Strumline.hx
@@ -279,7 +279,7 @@ class Strumline extends FlxSpriteGroup
var vwoosh:Float = 1.0;
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
- return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
+ return Constants.PIXELS_PER_MS * (Conductor.instance.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
}
function updateNotes():Void
@@ -287,8 +287,8 @@ class Strumline extends FlxSpriteGroup
if (noteData.length == 0) return;
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
- var hitWindowStart:Float = Conductor.songPosition - Constants.HIT_WINDOW_MS;
- var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS;
+ var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
+ var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
for (noteIndex in nextNoteIndex...noteData.length)
{
@@ -335,7 +335,7 @@ class Strumline extends FlxSpriteGroup
{
if (holdNote == null || !holdNote.alive) continue;
- if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote)
+ if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote)
{
if (isPlayer && !isKeyHeld(holdNote.noteDirection))
{
@@ -349,7 +349,7 @@ class Strumline extends FlxSpriteGroup
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
- if (holdNote.missedNote && Conductor.songPosition >= renderWindowEnd)
+ if (holdNote.missedNote && Conductor.instance.songPosition >= renderWindowEnd)
{
// Hold note is offscreen, kill it.
holdNote.visible = false;
@@ -399,13 +399,13 @@ class Strumline extends FlxSpriteGroup
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
}
}
- else if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote)
+ else if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote)
{
// Hold note is currently being hit, clip it off.
holdConfirm(holdNote.noteDirection);
holdNote.visible = true;
- holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.songPosition;
+ holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.instance.songPosition;
if (holdNote.sustainLength <= 10)
{
diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx
index 077e9e495..33333565f 100644
--- a/source/funkin/ui/MusicBeatState.hx
+++ b/source/funkin/ui/MusicBeatState.hx
@@ -80,25 +80,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
if (FlxG.keys.justPressed.F5) debug_refreshModules();
}
- function handleQuickWatch():Void
- {
- // Display Conductor info in the watch window.
- FlxG.watch.addQuick("songPosition", Conductor.songPosition);
- FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset);
- FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
- FlxG.watch.addQuick("bpm", Conductor.bpm);
- FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
- FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
- FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
- }
-
override function update(elapsed:Float)
{
super.update(elapsed);
handleControls();
- handleFunctionControls();
- handleQuickWatch();
dispatchEvent(new UpdateScriptEvent(elapsed));
}
@@ -139,7 +125,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function stepHit():Bool
{
- var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
+ var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
@@ -150,7 +136,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function beatHit():Bool
{
- var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
+ var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx
index 9dd755b62..0fa55c234 100644
--- a/source/funkin/ui/MusicBeatSubState.hx
+++ b/source/funkin/ui/MusicBeatSubState.hx
@@ -65,12 +65,8 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
if (FlxG.keys.justPressed.F5) debug_refreshModules();
// Display Conductor info in the watch window.
- FlxG.watch.addQuick("songPosition", Conductor.songPosition);
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
- FlxG.watch.addQuick("bpm", Conductor.bpm);
- FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
- FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
- FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
+ Conductor.watchQuick();
dispatchEvent(new UpdateScriptEvent(elapsed));
}
@@ -99,7 +95,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function stepHit():Bool
{
- var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
+ var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
@@ -115,7 +111,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function beatHit():Bool
{
- var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
+ var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 16f83275b..64a2f19ed 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -15,12 +15,14 @@ import flixel.group.FlxSpriteGroup;
import flixel.input.keyboard.FlxKey;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
+import flixel.graphics.FlxGraphic;
import flixel.math.FlxRect;
import flixel.sound.FlxSound;
import flixel.system.FlxAssets.FlxSoundAsset;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.tweens.misc.VarTween;
+import haxe.ui.Toolkit;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
@@ -81,6 +83,7 @@ import funkin.ui.debug.charting.components.ChartEditorEventSprite;
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
+import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite;
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
@@ -150,7 +153,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Layouts
public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata');
- public static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata');
+ public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata');
public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties');
public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata');
public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty');
@@ -170,7 +173,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
/**
* The width of the scroll area.
*/
- public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = 12;
+ public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = Std.int(GRID_SIZE);
/**
* The height of the playhead, in pixels.
@@ -306,13 +309,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_songLengthInSteps():Float
{
- return Conductor.getTimeInSteps(songLengthInMs);
+ return Conductor.instance.getTimeInSteps(songLengthInMs);
}
function set_songLengthInSteps(value:Float):Float
{
- // Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first.
- songLengthInMs = Conductor.getStepTimeInMs(value);
+ // Getting a reasonable result from setting songLengthInSteps requires that Conductor.instance.mapBPMChanges be called first.
+ songLengthInMs = Conductor.instance.getStepTimeInMs(value);
return value;
}
@@ -366,17 +369,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.scrollPositionInPixels = value;
// Move the grid sprite to the correct position.
- if (gridTiledSprite != null && gridPlayheadScrollArea != null)
+ if (gridTiledSprite != null && measureTicks != null)
{
if (isViewDownscroll)
{
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
- gridPlayheadScrollArea.y = gridTiledSprite.y;
+ measureTicks.y = gridTiledSprite.y;
}
else
{
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
- gridPlayheadScrollArea.y = gridTiledSprite.y;
+ measureTicks.y = gridTiledSprite.y;
if (audioVisGroup != null && audioVisGroup.playerVis != null)
{
@@ -398,6 +401,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff;
// Update the note preview viewport box.
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
+ // Update the measure tick display.
+ if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0;
return this.scrollPositionInPixels;
}
@@ -426,12 +431,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_scrollPositionInMs():Float
{
- return Conductor.getStepTimeInMs(scrollPositionInSteps);
+ return Conductor.instance.getStepTimeInMs(scrollPositionInSteps);
}
function set_scrollPositionInMs(value:Float):Float
{
- scrollPositionInSteps = Conductor.getTimeInSteps(value);
+ scrollPositionInSteps = Conductor.instance.getTimeInSteps(value);
return value;
}
@@ -485,13 +490,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_playheadPositionInMs():Float
{
if (audioVisGroup != null && audioVisGroup.playerVis != null)
- audioVisGroup.playerVis.realtimeStartOffset = -Conductor.getStepTimeInMs(playheadPositionInSteps);
- return Conductor.getStepTimeInMs(playheadPositionInSteps);
+ audioVisGroup.playerVis.realtimeStartOffset = -Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
+ return Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
}
function set_playheadPositionInMs(value:Float):Float
{
- playheadPositionInSteps = Conductor.getTimeInSteps(value);
+ playheadPositionInSteps = Conductor.instance.getTimeInSteps(value);
if (audioVisGroup != null && audioVisGroup.playerVis != null) audioVisGroup.playerVis.realtimeStartOffset = -value;
return value;
@@ -522,17 +527,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
/**
* The note kind to use for notes being placed in the chart. Defaults to `''`.
*/
- var selectedNoteKind:String = '';
+ var noteKindToPlace:String = '';
/**
* The event type to use for events being placed in the chart. Defaults to `''`.
*/
- var selectedEventKind:String = 'FocusCamera';
+ var eventKindToPlace:String = 'FocusCamera';
/**
* The event data to use for events being placed in the chart.
*/
- var selectedEventData:DynamicAccess = {};
+ var eventDataToPlace:DynamicAccess = {};
/**
* The internal index of what note snapping value is in use.
@@ -906,6 +911,70 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return Save.get().chartEditorHasBackup = value;
}
+ /**
+ * A list of previous working file paths.
+ * Also known as the "recent files" list.
+ * The first element is [null] if the current working file has not been saved anywhere yet.
+ */
+ public var previousWorkingFilePaths(default, set):Array> = [null];
+
+ function set_previousWorkingFilePaths(value:Array>):Array>
+ {
+ // Called only when the WHOLE LIST is overridden.
+ previousWorkingFilePaths = value;
+ applyWindowTitle();
+ populateOpenRecentMenu();
+ applyCanQuickSave();
+ return value;
+ }
+
+ /**
+ * The current file path which the chart editor is working with.
+ * If `null`, the current chart has not been saved yet.
+ */
+ public var currentWorkingFilePath(get, set):Null;
+
+ function get_currentWorkingFilePath():Null
+ {
+ return previousWorkingFilePaths[0];
+ }
+
+ function set_currentWorkingFilePath(value:Null):Null
+ {
+ if (value == previousWorkingFilePaths[0]) return value;
+
+ if (previousWorkingFilePaths.contains(null))
+ {
+ // Filter all instances of `null` from the array.
+ previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null):Bool {
+ return x != null;
+ });
+ }
+
+ if (previousWorkingFilePaths.contains(value))
+ {
+ // Move the path to the front of the list.
+ previousWorkingFilePaths.remove(value);
+ previousWorkingFilePaths.unshift(value);
+ }
+ else
+ {
+ // Add the path to the front of the list.
+ previousWorkingFilePaths.unshift(value);
+ }
+
+ while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES)
+ {
+ // Remove the last path in the list.
+ previousWorkingFilePaths.pop();
+ }
+
+ populateOpenRecentMenu();
+ applyWindowTitle();
+
+ return value;
+ }
+
/**
* Whether the difficulty tree view in the toolbox has been modified and needs to be updated.
* This happens when we add/remove difficulties.
@@ -935,6 +1004,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var commandHistoryDirty:Bool = true;
+ /**
+ * If true, we are currently in the process of quitting the chart editor.
+ * Skip any update functions as most of them will call a crash.
+ */
+ var criticalFailure:Bool = false;
+
// Input
/**
@@ -1680,23 +1755,28 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var notePreviewViewportBitmap:Null = null;
+ /**r
+ * The IMAGE used for the measure ticks. Updated by ChartEditorThemeHandler.
+ */
+ var measureTickBitmap:Null = null;
+
/**
* The tiled sprite used to display the grid.
* The height is the length of the song, and scrolling is done by simply the sprite.
*/
var gridTiledSprite:Null = null;
+ /**
+ * The measure ticks area. Includes the numbers and the background sprite.
+ */
+ var measureTicks:Null = null;
+
/**
* The playhead representing the current position in the song.
* Can move around on the grid independently of the view.
*/
var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup();
- /**
- * The sprite for the scroll area under
- */
- var gridPlayheadScrollArea:Null = null;
-
/**
* A sprite used to indicate the note that will be placed on click.
*/
@@ -1783,70 +1863,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var params:Null;
- /**
- * A list of previous working file paths.
- * Also known as the "recent files" list.
- * The first element is [null] if the current working file has not been saved anywhere yet.
- */
- public var previousWorkingFilePaths(default, set):Array> = [null];
-
- function set_previousWorkingFilePaths(value:Array>):Array>
- {
- // Called only when the WHOLE LIST is overridden.
- previousWorkingFilePaths = value;
- applyWindowTitle();
- populateOpenRecentMenu();
- applyCanQuickSave();
- return value;
- }
-
- /**
- * The current file path which the chart editor is working with.
- * If `null`, the current chart has not been saved yet.
- */
- public var currentWorkingFilePath(get, set):Null;
-
- function get_currentWorkingFilePath():Null
- {
- return previousWorkingFilePaths[0];
- }
-
- function set_currentWorkingFilePath(value:Null):Null
- {
- if (value == previousWorkingFilePaths[0]) return value;
-
- if (previousWorkingFilePaths.contains(null))
- {
- // Filter all instances of `null` from the array.
- previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null):Bool {
- return x != null;
- });
- }
-
- if (previousWorkingFilePaths.contains(value))
- {
- // Move the path to the front of the list.
- previousWorkingFilePaths.remove(value);
- previousWorkingFilePaths.unshift(value);
- }
- else
- {
- // Add the path to the front of the list.
- previousWorkingFilePaths.unshift(value);
- }
-
- while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES)
- {
- // Remove the last path in the list.
- previousWorkingFilePaths.pop();
- }
-
- populateOpenRecentMenu();
- applyWindowTitle();
-
- return value;
- }
-
public function new(?params:ChartEditorParams)
{
super();
@@ -1918,6 +1934,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.updateTheme();
buildGrid();
+ buildMeasureTicks();
buildNotePreview();
buildSelectionBox();
@@ -1927,6 +1944,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Setup the onClick listeners for the UI after it's been created.
setupUIListeners();
+ setupContextMenu();
setupTurboKeyHandlers();
setupAutoSave();
@@ -2172,15 +2190,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
buildNoteGroup();
- gridPlayheadScrollArea = new FlxSprite(0, 0);
- gridPlayheadScrollArea.makeGraphic(10, 10, PLAYHEAD_SCROLL_AREA_COLOR); // Make it 10x10px and then scale it as needed.
- add(gridPlayheadScrollArea);
- gridPlayheadScrollArea.setGraphicSize(PLAYHEAD_SCROLL_AREA_WIDTH, 3000);
- gridPlayheadScrollArea.updateHitbox();
- gridPlayheadScrollArea.x = GRID_X_POS - PLAYHEAD_SCROLL_AREA_WIDTH;
- gridPlayheadScrollArea.y = GRID_INITIAL_Y_POS;
- gridPlayheadScrollArea.zIndex = 25;
-
// The playhead that show the current position in the song.
add(gridPlayhead);
gridPlayhead.zIndex = 30;
@@ -2216,6 +2225,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(audioVisGroup);
}
+ function buildMeasureTicks():Void
+ {
+ measureTicks = new ChartEditorMeasureTicks(this);
+ var measureTicksWidth = (GRID_SIZE);
+ measureTicks.x = gridTiledSprite.x - measureTicksWidth;
+ measureTicks.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
+ measureTicks.zIndex = 20;
+
+ add(measureTicks);
+ }
+
function buildNotePreview():Void
{
var playbarHeightWithPad = PLAYBAR_HEIGHT + 10;
@@ -2301,6 +2321,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
bounds.height = MIN_HEIGHT;
}
+ trace('Note preview viewport bounds: ' + bounds.toString());
+
return bounds;
}
@@ -2541,13 +2563,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
else
{
- Conductor.currentTimeChange.bpm += 1;
+ Conductor.instance.currentTimeChange.bpm += 1;
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
playbarBPM.onRightClick = _ -> {
- Conductor.currentTimeChange.bpm -= 1;
+ Conductor.instance.currentTimeChange.bpm -= 1;
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
@@ -2590,14 +2612,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemUndo.onClick = _ -> undoLastCommand();
menubarItemRedo.onClick = _ -> redoLastCommand();
menubarItemCopy.onClick = function(_) {
+ copySelection();
};
menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection));
menubarItemPaste.onClick = _ -> {
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
- var targetStep:Float = Conductor.getTimeInSteps(targetMs);
+ var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
- var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
+ var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep);
performCommand(new PasteItemsCommand(targetSnappedMs));
};
@@ -2753,7 +2776,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value);
menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value);
menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value);
- menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value);
+ menubarItemToggleToolboxEventData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT, event.value);
menubarItemToggleToolboxPlaytestProperties.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT, event.value);
menubarItemToggleToolboxPlayerPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value);
menubarItemToggleToolboxOpponentPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value);
@@ -2762,6 +2785,42 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// registerContextMenu(null, Paths.ui('chart-editor/context/test'));
}
+ function setupContextMenu():Void
+ {
+ Screen.instance.registerEvent(MouseEvent.RIGHT_MOUSE_UP, function(e:MouseEvent) {
+ var xPos = e.screenX;
+ var yPos = e.screenY;
+ onContextMenu(xPos, yPos);
+ });
+ }
+
+ function onContextMenu(xPos:Float, yPos:Float)
+ {
+ trace('User right clicked to open menu at (${xPos}, ${yPos})');
+ // this.openDefaultContextMenu(xPos, yPos);
+ }
+
+ function copySelection():Void
+ {
+ // Doesn't use a command because it's not undoable.
+
+ // Calculate a single time offset for all the notes and events.
+ var timeOffset:Null = currentNoteSelection.length > 0 ? Std.int(currentNoteSelection[0].time) : null;
+ if (currentEventSelection.length > 0)
+ {
+ if (timeOffset == null || currentEventSelection[0].time < timeOffset)
+ {
+ timeOffset = Std.int(currentEventSelection[0].time);
+ }
+ }
+
+ SongDataUtils.writeItemsToClipboard(
+ {
+ notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset),
+ events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset),
+ });
+ }
+
/**
* Initialize TurboKeyHandlers and add them to the state (so `update()` is called)
* We can then probe `keyHandler.activated` to see if the key combo's action should be taken.
@@ -2793,10 +2852,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
saveDataDirty = false;
}
+ var displayAutosavePopup:Bool = false;
+
/**
* UPDATE FUNCTIONS
*/
- function autoSave():Void
+ function autoSave(?beforePlaytest:Bool = false):Void
{
var needsAutoSave:Bool = saveDataDirty;
@@ -2814,13 +2875,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (needsAutoSave)
{
this.exportAllSongData(true, null);
- var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
- this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
- {
- text: "Take Me There",
- callback: openBackupsFolder,
- }
- ]);
+ if (beforePlaytest)
+ {
+ displayAutosavePopup = true;
+ }
+ else
+ {
+ displayAutosavePopup = false;
+ var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
+ this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
+ {
+ text: "Take Me There",
+ callback: openBackupsFolder,
+ }
+ ]);
+ }
}
#end
}
@@ -2888,7 +2957,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
public override function update(elapsed:Float):Void
{
// Override F4 behavior to include the autosave.
- if (FlxG.keys.justPressed.F4)
+ if (FlxG.keys.justPressed.F4 && !criticalFailure)
{
quitChartEditor();
return;
@@ -2897,6 +2966,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// dispatchEvent gets called here.
super.update(elapsed);
+ if (criticalFailure) return;
+
// These ones happen even if the modal dialog is open.
handleMusicPlayback(elapsed);
handleNoteDisplay();
@@ -2936,9 +3007,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying))
{
- playMetronomeTick(Conductor.currentBeat % 4 == 0);
+ playMetronomeTick(Conductor.instance.currentBeat % Conductor.instance.beatsPerMeasure == 0);
}
+ // Show the mouse cursor.
+ // Just throwing this somewhere convenient and infrequently called because sometimes Flixel's debug thing hides the cursor.
+ Cursor.show();
+
return true;
}
@@ -2952,8 +3027,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (audioInstTrack != null && audioInstTrack.isPlaying)
{
- if (healthIconDad != null) healthIconDad.onStepHit(Conductor.currentStep);
- if (healthIconBF != null) healthIconBF.onStepHit(Conductor.currentStep);
+ if (healthIconDad != null) healthIconDad.onStepHit(Conductor.instance.currentStep);
+ if (healthIconBF != null) healthIconBF.onStepHit(Conductor.instance.currentStep);
}
// Updating these every step keeps it more accurate.
@@ -2981,12 +3056,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
audioInstTrack.update(elapsed);
// If the song starts 50ms in, make sure we start the song there.
- if (Conductor.instrumentalOffset < 0)
+ if (Conductor.instance.instrumentalOffset < 0)
{
- if (audioInstTrack.time < -Conductor.instrumentalOffset)
+ if (audioInstTrack.time < -Conductor.instance.instrumentalOffset)
{
- trace('Resetting instrumental time to ${- Conductor.instrumentalOffset}ms');
- audioInstTrack.time = -Conductor.instrumentalOffset;
+ trace('Resetting instrumental time to ${- Conductor.instance.instrumentalOffset}ms');
+ audioInstTrack.time = -Conductor.instance.instrumentalOffset;
}
}
}
@@ -2997,16 +3072,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
- var oldStepTime:Float = Conductor.currentStepTime;
- var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
- Conductor.update(audioInstTrack.time);
- handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
+ var oldStepTime:Float = Conductor.instance.currentStepTime;
+ var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
+ Conductor.instance.update(audioInstTrack.time);
+ handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
// Resync vocals.
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
{
audioVocalTrackGroup.time = audioInstTrack.time;
}
- var diffStepTime:Float = Conductor.currentStepTime - oldStepTime;
+ var diffStepTime:Float = Conductor.instance.currentStepTime - oldStepTime;
// Move the playhead.
playheadPositionInPixels += diffStepTime * GRID_SIZE;
@@ -3016,9 +3091,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
// Else, move the entire view.
- var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
- Conductor.update(audioInstTrack.time);
- handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
+ var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
+ Conductor.instance.update(audioInstTrack.time);
+ handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
// Resync vocals.
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
{
@@ -3027,7 +3102,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// We need time in fractional steps here to allow the song to actually play.
// Also account for a potentially offset playhead.
- scrollPositionInPixels = (Conductor.currentStepTime + Conductor.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels;
+ scrollPositionInPixels = (Conductor.instance.currentStepTime + Conductor.instance.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels;
// DO NOT move song to scroll position here specifically.
@@ -3142,6 +3217,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Update the event sprite's position.
eventSprite.updateEventPosition(renderedEvents);
+ // Update the sprite's graphic. TODO: Is this inefficient?
+ eventSprite.playAnimation(eventSprite.eventData.event);
}
else
{
@@ -3156,8 +3233,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Let's try testing only notes within a certain range of the view area.
// TODO: I don't think this messes up really long sustains, does it?
- var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.measureLengthMs * 2); // Is 2 measures enough?
- var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.measureLengthMs * 2); // Is 2 measures enough?
+ var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough?
+ var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough?
// Add notes that are now visible.
for (noteData in currentSongChartNoteData)
@@ -3444,14 +3521,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// PAGE UP = Jump up to nearest measure
if (pageUpKeyHandler.activated)
{
- var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
+ var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
// If we would move less than one grid, instead move to the top of the previous measure.
var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos);
if (targetScrollAmount < GRID_SIZE)
{
- targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure;
+ targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
@@ -3460,21 +3537,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarButtonPressed == 'playbarBack')
{
playbarButtonPressed = '';
- scrollAmount = -GRID_SIZE * 4 * Conductor.beatsPerMeasure;
+ scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
shouldPause = true;
}
// PAGE DOWN = Jump down to nearest measure
if (pageDownKeyHandler.activated)
{
- var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
+ var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
// If we would move less than one grid, instead move to the top of the next measure.
var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos);
if (targetScrollAmount < GRID_SIZE)
{
- targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure;
+ targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
@@ -3483,7 +3560,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarButtonPressed == 'playbarForward')
{
playbarButtonPressed = '';
- scrollAmount = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
+ scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
shouldPause = true;
}
@@ -3598,6 +3675,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// trace('shouldHandleCursor: $shouldHandleCursor');
+ // TODO: TBH some of this should be using FlxMouseEventManager...
+
if (shouldHandleCursor)
{
// Over the course of this big conditional block,
@@ -3641,7 +3720,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
scrollAnchorScreenPos = null;
}
- else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea) && !isCursorOverHaxeUI)
+ else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks) && !isCursorOverHaxeUI)
{
gridPlayheadScrollAreaPressed = true;
}
@@ -3686,10 +3765,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// The song position of the cursor, in steps.
var cursorFractionalStep:Float = cursorY / GRID_SIZE;
- var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep);
+ var cursorMs:Float = Conductor.instance.getStepTimeInMs(cursorFractionalStep);
// Round the cursor step to the nearest snap quant.
var cursorSnappedStep:Float = Math.floor(cursorFractionalStep / noteSnapRatio) * noteSnapRatio;
- var cursorSnappedMs:Float = Conductor.getStepTimeInMs(cursorSnappedStep);
+ var cursorSnappedMs:Float = Conductor.instance.getStepTimeInMs(cursorSnappedStep);
// The direction value for the column at the cursor.
var cursorGridPos:Int = Math.floor(cursorX / GRID_SIZE);
@@ -3711,7 +3790,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// We released the mouse. Select the notes in the box.
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
- var cursorMsStart:Float = Conductor.getStepTimeInMs(cursorStepStart);
+ var cursorMsStart:Float = Conductor.instance.getStepTimeInMs(cursorStepStart);
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
@@ -3947,11 +4026,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var dragDistanceMs:Float = 0;
if (dragTargetNote != null && dragTargetNote.noteData != null)
{
- dragDistanceMs = Conductor.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time;
+ dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time;
}
else if (dragTargetEvent != null && dragTargetEvent.eventData != null)
{
- dragDistanceMs = Conductor.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time;
+ dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time;
}
var dragDistanceColumns:Int = dragTargetCurrentColumn;
@@ -4011,7 +4090,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
stepTime = dragTargetEvent.eventData.getStepTime();
}
- var dragDistanceSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime;
+ var dragDistanceSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime;
var data:Int = 0;
var noteGridPos:Int = 0;
if (dragTargetNote != null && dragTargetNote.noteData != null)
@@ -4043,8 +4122,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Handle extending the note as you drag.
var stepTime:Float = inline currentPlaceNoteData.getStepTime();
- var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs) - stepTime;
- var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs;
+ var dragLengthSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs) - stepTime;
+ var dragLengthMs:Float = dragLengthSteps * Conductor.instance.stepLengthMs;
var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE;
if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null)
@@ -4181,14 +4260,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// Create an event and place it in the chart.
// TODO: Figure out configuring event data.
- var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData.clone());
+ var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone());
performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL));
}
else
{
// Create a note and place it in the chart.
- var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind.clone());
+ var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone());
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
@@ -4226,13 +4305,52 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (highlightedNote != null && highlightedNote.noteData != null)
{
// TODO: Handle the case of clicking on a sustain piece.
- // Remove the note.
- performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
+ if (FlxG.keys.pressed.SHIFT)
+ {
+ // Shift + Right click opens the context menu.
+ // If we are clicking a large selection, open the Selection context menu, otherwise open the Note context menu.
+ var isHighlightedNoteSelected:Bool = isNoteSelected(highlightedNote.noteData);
+ var useSingleNoteContextMenu:Bool = (!isHighlightedNoteSelected)
+ || (isHighlightedNoteSelected && currentNoteSelection.length == 1);
+ // Show the context menu connected to the note.
+ if (useSingleNoteContextMenu)
+ {
+ this.openNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedNote.noteData);
+ }
+ else
+ {
+ this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
+ }
+ }
+ else
+ {
+ // Right click removes the note.
+ performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
+ }
}
else if (highlightedEvent != null && highlightedEvent.eventData != null)
{
- // Remove the event.
- performCommand(new RemoveEventsCommand([highlightedEvent.eventData]));
+ if (FlxG.keys.pressed.SHIFT)
+ {
+ // Shift + Right click opens the context menu.
+ // If we are clicking a large selection, open the Selection context menu, otherwise open the Event context menu.
+ var isHighlightedEventSelected:Bool = isEventSelected(highlightedEvent.eventData);
+ var useSingleEventContextMenu:Bool = (!isHighlightedEventSelected)
+ || (isHighlightedEventSelected && currentEventSelection.length == 1);
+ if (useSingleEventContextMenu)
+ {
+ this.openEventContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedEvent.eventData);
+ }
+ else
+ {
+ this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
+ }
+ }
+ else
+ {
+ // Right click removes the event.
+ performCommand(new RemoveEventsCommand([highlightedEvent.eventData]));
+ }
}
else
{
@@ -4253,11 +4371,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridGhostEvent == null) throw "ERROR: Tried to handle cursor, but gridGhostEvent is null! Check ChartEditorState.buildGrid()";
- var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, selectedEventKind, null);
+ var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null);
- if (selectedEventKind != eventData.event)
+ if (eventKindToPlace != eventData.event)
{
- eventData.event = selectedEventKind;
+ eventData.event = eventKindToPlace;
}
eventData.time = cursorSnappedMs;
@@ -4273,11 +4391,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()";
- var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind);
+ var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace);
- if (cursorColumn != noteData.data || selectedNoteKind != noteData.kind)
+ if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind)
{
- noteData.kind = selectedNoteKind;
+ noteData.kind = noteKindToPlace;
noteData.data = cursorColumn;
gridGhostNote.playNoteAnimation();
}
@@ -4319,7 +4437,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetCursorMode = Pointer;
}
- else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
+ else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks))
{
targetCursorMode = Pointer;
}
@@ -4520,7 +4638,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent;
}
- var songPos:Float = Conductor.songPosition + Conductor.instrumentalOffset;
+ var songPos:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
var songPosSeconds:String = Std.string(Math.floor((Math.abs(songPos) / 1000) % 60)).lpad('0', 2);
var songPosMinutes:String = Std.string(Math.floor((Math.abs(songPos) / 1000) / 60)).lpad('0', 2);
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
@@ -4576,16 +4694,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
- var playheadPosSnappedMs:Float = playheadPosStep * Conductor.stepLengthMs * noteSnapRatio;
+ var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
// Look for notes within 1 step of the playhead.
var notesAtPos:Array = SongDataUtils.getNotesInTimeRange(currentSongChartNoteData, playheadPosSnappedMs,
- playheadPosSnappedMs + Conductor.stepLengthMs * noteSnapRatio);
+ playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
if (notesAtPos.length == 0)
{
- var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, selectedNoteKind);
+ var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace);
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
}
else
@@ -4699,6 +4817,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
FlxG.switchState(new MainMenuState());
resetWindowTitle();
+
+ criticalFailure = true;
}
/**
@@ -4742,9 +4862,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
- var targetStep:Float = Conductor.getTimeInSteps(targetMs);
+ var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
- var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
+ var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep);
targetSnappedMs;
}
performCommand(new PasteItemsCommand(targetMs));
@@ -4878,11 +4998,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
#end
}
- override function handleQuickWatch():Void
+ function handleQuickWatch():Void
{
- super.handleQuickWatch();
-
- FlxG.watch.addQuick('musicTime', audioInstTrack?.time);
+ FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0);
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels);
@@ -4909,7 +5027,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
function testSongInPlayState(minimal:Bool = false):Void
{
- autoSave();
+ autoSave(true);
stopWelcomeMusic();
@@ -5109,16 +5227,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function onSongLengthChanged():Void
{
- if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
- if (gridPlayheadScrollArea != null)
+ if (gridTiledSprite != null)
{
- gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
- gridPlayheadScrollArea.updateHitbox();
+ gridTiledSprite.height = songLengthInPixels;
+ }
+ if (measureTicks != null)
+ {
+ measureTicks.setHeight(songLengthInPixels);
}
// Remove any notes past the end of the song.
var songCutoffPointSteps:Float = songLengthInSteps - 0.1;
- var songCutoffPointMs:Float = Conductor.getStepTimeInMs(songCutoffPointSteps);
+ var songCutoffPointMs:Float = Conductor.instance.getStepTimeInMs(songCutoffPointSteps);
currentSongChartNoteData = SongDataUtils.clampSongNoteData(currentSongChartNoteData, 0.0, songCutoffPointMs);
currentSongChartEventData = SongDataUtils.clampSongEventData(currentSongChartEventData, 0.0, songCutoffPointMs);
@@ -5220,7 +5340,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var prevDifficulty = availableDifficulties[availableDifficulties.length - 1];
selectedDifficulty = prevDifficulty;
- Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges);
+ Conductor.instance.mapTimeChanges(this.currentSongMetadata.timeChanges);
+ updateTimeSignature();
refreshDifficultyTreeSelection();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
@@ -5282,9 +5403,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Update the songPosition in the audio tracks.
if (audioInstTrack != null)
{
- audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instrumentalOffset;
+ audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset;
// Update the songPosition in the Conductor.
- Conductor.update(audioInstTrack.time);
+ Conductor.instance.update(audioInstTrack.time);
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = audioInstTrack.time;
}
@@ -5344,6 +5465,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.persistentUpdate = true;
this.persistentDraw = true;
+ if (displayAutosavePopup)
+ {
+ displayAutosavePopup = false;
+ Toolkit.callLater(() -> {
+ var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
+ this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
+ {
+ text: "Take Me There",
+ callback: openBackupsFolder,
+ }
+ ]);
+ });
+ }
+
moveSongToScrollPosition();
fadeInWelcomeMusic(7, 10);
@@ -5360,6 +5495,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = vocalTargetVolume;
}
+ function updateTimeSignature():Void
+ {
+ // Redo the grid bitmap to be 4/4.
+ this.updateTheme();
+ gridTiledSprite.loadGraphic(gridBitmap);
+ measureTicks.reloadTickBitmap();
+ }
+
/**
* HAXEUI FUNCTIONS
*/
@@ -5472,6 +5615,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (notePreviewViewportBoundsDirty)
{
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
+ notePreviewViewportBoundsDirty = false;
}
}
@@ -5603,7 +5747,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('ERROR: Instrumental track is null!');
}
- this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
+ this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
// Many things get reset when song length changes.
healthIconsDirty = true;
@@ -5628,6 +5772,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
cleanupAutoSave();
+ this.closeAllMenus();
+
// Hide the mouse cursor on other states.
Cursor.hide();
diff --git a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx
index 5264623f6..bd832fab3 100644
--- a/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/ChangeStartingBPMCommand.hx
@@ -34,7 +34,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.currentSongMetadata.timeChanges = timeChanges;
- Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
+ state.noteDisplayDirty = true;
+ state.notePreviewDirty = true;
+ state.notePreviewViewportBoundsDirty = true;
+ state.scrollPositionInPixels = 0;
+
+ Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
}
public function undo(state:ChartEditorState):Void
@@ -51,7 +56,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.currentSongMetadata.timeChanges = timeChanges;
- Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
+ state.noteDisplayDirty = true;
+ state.notePreviewDirty = true;
+ state.notePreviewViewportBoundsDirty = true;
+ state.scrollPositionInPixels = 0;
+
+ Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
}
public function shouldAddToHistory(state:ChartEditorState):Bool
diff --git a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx
index a0368f908..ed50ad33e 100644
--- a/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx
@@ -33,7 +33,7 @@ class MoveEventsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultEvent = event.clone();
- resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
+ resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
movedEvents.push(resultEvent);
}
diff --git a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx
index 4fa1e2f87..f44cb973a 100644
--- a/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx
@@ -21,8 +21,8 @@ class MoveItemsCommand implements ChartEditorCommand
public function new(notes:Array, events:Array, offset:Float, columns:Int)
{
// Clone the notes to prevent editing from affecting the history.
- this.notes = [for (note in notes) note.clone()];
- this.events = [for (event in events) event.clone()];
+ this.notes = notes.clone();
+ this.events = events.clone();
this.offset = offset;
this.columns = columns;
this.movedNotes = [];
@@ -41,7 +41,7 @@ class MoveItemsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultNote = note.clone();
- resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
+ resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
@@ -52,7 +52,7 @@ class MoveItemsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultEvent = event.clone();
- resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
+ resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
movedEvents.push(resultEvent);
}
diff --git a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx
index 37ed61d72..51aeb5bbc 100644
--- a/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx
@@ -34,7 +34,7 @@ class MoveNotesCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultNote = note.clone();
- resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
+ resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
diff --git a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx
index bba6ae866..257db94b4 100644
--- a/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx
@@ -32,9 +32,9 @@ class PasteItemsCommand implements ChartEditorCommand
return;
}
- var stepEndOfSong:Float = Conductor.getTimeInSteps(state.songLengthInMs);
+ var stepEndOfSong:Float = Conductor.instance.getTimeInSteps(state.songLengthInMs);
var stepCutoff:Float = stepEndOfSong - 1.0;
- var msCutoff:Float = Conductor.getStepTimeInMs(stepCutoff);
+ var msCutoff:Float = Conductor.instance.getStepTimeInMs(stepCutoff);
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff);
diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx
index 17f45f946..d6c5beeac 100644
--- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx
@@ -33,6 +33,32 @@ class SelectItemsCommand implements ChartEditorCommand
state.currentEventSelection.push(event);
}
+ // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
+ if (this.notes.length == 0 && this.events.length >= 1)
+ {
+ var eventSelected = this.events[0];
+
+ state.eventKindToPlace = eventSelected.event;
+
+ // This code is here to parse event data that's not built as a struct for some reason.
+ // TODO: Clean this up or get rid of it.
+ var eventSchema = eventSelected.getSchema();
+ var defaultKey = null;
+ if (eventSchema == null)
+ {
+ trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
+ }
+ else
+ {
+ defaultKey = eventSchema.getFirstField()?.name;
+ }
+ var eventData = eventSelected.valueAsStruct(defaultKey);
+
+ state.eventDataToPlace = eventData;
+
+ state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
+ }
+
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx
index f9b4fb6d7..35a00e562 100644
--- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx
@@ -30,6 +30,32 @@ class SetItemSelectionCommand implements ChartEditorCommand
state.currentNoteSelection = notes;
state.currentEventSelection = events;
+ // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
+ if (this.notes.length == 0 && this.events.length >= 1)
+ {
+ var eventSelected = this.events[0];
+
+ state.eventKindToPlace = eventSelected.event;
+
+ // This code is here to parse event data that's not built as a struct for some reason.
+ // TODO: Clean this up or get rid of it.
+ var eventSchema = eventSelected.getSchema();
+ var defaultKey = null;
+ if (eventSchema == null)
+ {
+ trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
+ }
+ else
+ {
+ defaultKey = eventSchema.getFirstField()?.name;
+ }
+ var eventData = eventSelected.valueAsStruct(defaultKey);
+
+ state.eventDataToPlace = eventData;
+
+ state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
+ }
+
state.noteDisplayDirty = true;
}
diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx
index e0070cc7b..36c6b1d2f 100644
--- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx
+++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx
@@ -1,6 +1,6 @@
package funkin.ui.debug.charting.components;
-import funkin.data.event.SongEventData.SongEventParser;
+import funkin.data.event.SongEventRegistry;
import flixel.graphics.frames.FlxAtlasFrames;
import openfl.display.BitmapData;
import openfl.utils.Assets;
@@ -79,7 +79,7 @@ class ChartEditorEventSprite extends FlxSprite
}
// Push all the other events as frames.
- for (eventName in SongEventParser.listEventIds())
+ for (eventName in SongEventRegistry.listEventIds())
{
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
if (!exists) continue; // No graphic for this event.
@@ -105,7 +105,7 @@ class ChartEditorEventSprite extends FlxSprite
function buildAnimations():Void
{
- var eventNames:Array = [DEFAULT_EVENT].concat(SongEventParser.listEventIds());
+ var eventNames:Array = [DEFAULT_EVENT].concat(SongEventRegistry.listEventIds());
for (eventName in eventNames)
{
this.animation.addByPrefix(eventName, '${eventName}0', 24, false);
@@ -147,8 +147,6 @@ class ChartEditorEventSprite extends FlxSprite
else
{
this.visible = true;
- // Only play the animation if the event type has changed.
- // if (this.eventData == null || this.eventData.event != value.event)
playAnimation(value.event);
this.eventData = value;
// Update the position to match the note data.
diff --git a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx
new file mode 100644
index 000000000..1a76d1e22
--- /dev/null
+++ b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx
@@ -0,0 +1,71 @@
+package funkin.ui.debug.charting.components;
+
+import flixel.FlxSprite;
+import flixel.addons.display.FlxTiledSprite;
+import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+class ChartEditorMeasureTicks extends FlxTypedSpriteGroup
+{
+ var chartEditorState:ChartEditorState;
+
+ var tickTiledSprite:FlxTiledSprite;
+ var measureNumber:FlxText;
+
+ override function set_y(value:Float):Float
+ {
+ var result = super.set_y(value);
+
+ updateMeasureNumber();
+
+ return result;
+ }
+
+ public function new(chartEditorState:ChartEditorState)
+ {
+ super();
+
+ this.chartEditorState = chartEditorState;
+
+ tickTiledSprite = new FlxTiledSprite(chartEditorState.measureTickBitmap, chartEditorState.measureTickBitmap.width, 1000, false, true);
+ add(tickTiledSprite);
+
+ measureNumber = new FlxText(0, 0, ChartEditorState.GRID_SIZE, "1");
+ measureNumber.setFormat(Paths.font('vcr.ttf'), 20, FlxColor.WHITE);
+ measureNumber.borderStyle = FlxTextBorderStyle.OUTLINE;
+ measureNumber.borderColor = FlxColor.BLACK;
+ add(measureNumber);
+ }
+
+ public function reloadTickBitmap():Void
+ {
+ tickTiledSprite.loadGraphic(chartEditorState.measureTickBitmap);
+ }
+
+ /**
+ * At time of writing, we only have to manipulate one measure number because we can only see one measure at a time.
+ */
+ function updateMeasureNumber()
+ {
+ if (measureNumber == null) return;
+
+ var viewTopPosition = 0 - this.y;
+ var viewHeight = FlxG.height - ChartEditorState.MENU_BAR_HEIGHT - ChartEditorState.PLAYBAR_HEIGHT;
+ var viewBottomPosition = viewTopPosition + viewHeight;
+
+ var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.instance.stepsPerMeasure) + 1;
+ var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure;
+
+ measureNumber.text = '${measureNumberInViewport + 1}';
+ measureNumber.y = measureNumberPosition + this.y;
+
+ // trace(measureNumber.text + ' at ' + measureNumber.y);
+ }
+
+ public function setHeight(songLengthInPixels:Float):Void
+ {
+ tickTiledSprite.height = songLengthInPixels;
+ }
+}
diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx
new file mode 100644
index 000000000..f25f3ebb3
--- /dev/null
+++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx
@@ -0,0 +1,19 @@
+package funkin.ui.debug.charting.contextmenus;
+
+import haxe.ui.containers.menus.Menu;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+class ChartEditorBaseContextMenu extends Menu
+{
+ var chartEditorState:ChartEditorState;
+
+ public function new(chartEditorState:ChartEditorState, xPos:Float = 0, yPos:Float = 0)
+ {
+ super();
+
+ this.chartEditorState = chartEditorState;
+
+ this.left = xPos;
+ this.top = yPos;
+ }
+}
diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx
new file mode 100644
index 000000000..9529cc2fd
--- /dev/null
+++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx
@@ -0,0 +1,14 @@
+package funkin.ui.debug.charting.contextmenus;
+
+import haxe.ui.containers.menus.Menu;
+import haxe.ui.core.Screen;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/default.xml"))
+class ChartEditorDefaultContextMenu extends ChartEditorBaseContextMenu
+{
+ public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0)
+ {
+ super(chartEditorState2, xPos2, yPos2);
+ }
+}
diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx
new file mode 100644
index 000000000..a79125b21
--- /dev/null
+++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx
@@ -0,0 +1,32 @@
+package funkin.ui.debug.charting.contextmenus;
+
+import haxe.ui.containers.menus.Menu;
+import haxe.ui.containers.menus.MenuItem;
+import haxe.ui.core.Screen;
+import funkin.data.song.SongData.SongEventData;
+import funkin.ui.debug.charting.commands.RemoveEventsCommand;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/event.xml"))
+class ChartEditorEventContextMenu extends ChartEditorBaseContextMenu
+{
+ var contextmenuEdit:MenuItem;
+ var contextmenuDelete:MenuItem;
+
+ var data:SongEventData;
+
+ public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongEventData)
+ {
+ super(chartEditorState2, xPos2, yPos2);
+ this.data = data;
+
+ initialize();
+ }
+
+ function initialize()
+ {
+ contextmenuDelete.onClick = function(_) {
+ chartEditorState.performCommand(new RemoveEventsCommand([data]));
+ }
+ }
+}
diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx
new file mode 100644
index 000000000..4bfab27e8
--- /dev/null
+++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx
@@ -0,0 +1,38 @@
+package funkin.ui.debug.charting.contextmenus;
+
+import haxe.ui.containers.menus.Menu;
+import haxe.ui.containers.menus.MenuItem;
+import haxe.ui.core.Screen;
+import funkin.data.song.SongData.SongNoteData;
+import funkin.ui.debug.charting.commands.FlipNotesCommand;
+import funkin.ui.debug.charting.commands.RemoveNotesCommand;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/note.xml"))
+class ChartEditorNoteContextMenu extends ChartEditorBaseContextMenu
+{
+ var contextmenuFlip:MenuItem;
+ var contextmenuDelete:MenuItem;
+
+ var data:SongNoteData;
+
+ public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongNoteData)
+ {
+ super(chartEditorState2, xPos2, yPos2);
+ this.data = data;
+
+ initialize();
+ }
+
+ function initialize():Void
+ {
+ // NOTE: Remember to use commands here to ensure undo/redo works properly
+ contextmenuFlip.onClick = function(_) {
+ chartEditorState.performCommand(new FlipNotesCommand([data]));
+ }
+
+ contextmenuDelete.onClick = function(_) {
+ chartEditorState.performCommand(new RemoveNotesCommand([data]));
+ }
+ }
+}
diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx
new file mode 100644
index 000000000..feed9b689
--- /dev/null
+++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx
@@ -0,0 +1,58 @@
+package funkin.ui.debug.charting.contextmenus;
+
+import haxe.ui.containers.menus.Menu;
+import haxe.ui.containers.menus.MenuItem;
+import haxe.ui.core.Screen;
+import funkin.ui.debug.charting.commands.CutItemsCommand;
+import funkin.ui.debug.charting.commands.RemoveEventsCommand;
+import funkin.ui.debug.charting.commands.RemoveItemsCommand;
+import funkin.ui.debug.charting.commands.RemoveNotesCommand;
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/selection.xml"))
+class ChartEditorSelectionContextMenu extends ChartEditorBaseContextMenu
+{
+ var contextmenuCut:MenuItem;
+ var contextmenuCopy:MenuItem;
+ var contextmenuPaste:MenuItem;
+ var contextmenuDelete:MenuItem;
+ var contextmenuFlip:MenuItem;
+ var contextmenuSelectAll:MenuItem;
+ var contextmenuSelectInverse:MenuItem;
+ var contextmenuSelectNone:MenuItem;
+
+ public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0)
+ {
+ super(chartEditorState2, xPos2, yPos2);
+
+ initialize();
+ }
+
+ function initialize():Void
+ {
+ contextmenuCut.onClick = (_) -> {
+ chartEditorState.performCommand(new CutItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection));
+ };
+ contextmenuCopy.onClick = (_) -> {
+ chartEditorState.copySelection();
+ };
+ contextmenuFlip.onClick = (_) -> {
+ if (chartEditorState.currentNoteSelection.length > 0 && chartEditorState.currentEventSelection.length > 0)
+ {
+ chartEditorState.performCommand(new RemoveItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection));
+ }
+ else if (chartEditorState.currentNoteSelection.length > 0)
+ {
+ chartEditorState.performCommand(new RemoveNotesCommand(chartEditorState.currentNoteSelection));
+ }
+ else if (chartEditorState.currentEventSelection.length > 0)
+ {
+ chartEditorState.performCommand(new RemoveEventsCommand(chartEditorState.currentEventSelection));
+ }
+ else
+ {
+ // Do nothing???
+ }
+ };
+ }
+}
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
index d4fcc4638..0edba7357 100644
--- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
@@ -185,8 +185,8 @@ class ChartEditorAudioHandler
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
state.audioVisGroup.addPlayerVis(vocalTrack);
state.audioVisGroup.playerVis.x = 885;
- state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time.
- state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels.
+ state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
+ state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.playerVis.detail = 1;
state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS);
@@ -196,8 +196,9 @@ class ChartEditorAudioHandler
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
state.audioVisGroup.addOpponentVis(vocalTrack);
state.audioVisGroup.opponentVis.x = 435;
- state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time.
- state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels.
+
+ state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
+ state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.opponentVis.detail = 1;
state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS);
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx
new file mode 100644
index 000000000..b914f4149
--- /dev/null
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx
@@ -0,0 +1,64 @@
+package funkin.ui.debug.charting.handlers;
+
+import funkin.ui.debug.charting.contextmenus.ChartEditorDefaultContextMenu;
+import funkin.ui.debug.charting.contextmenus.ChartEditorEventContextMenu;
+import funkin.ui.debug.charting.contextmenus.ChartEditorNoteContextMenu;
+import funkin.ui.debug.charting.contextmenus.ChartEditorSelectionContextMenu;
+import haxe.ui.containers.menus.Menu;
+import haxe.ui.core.Screen;
+import funkin.data.song.SongData.SongNoteData;
+import funkin.data.song.SongData.SongEventData;
+
+/**
+ * Handles context menus (the little menus that appear when you right click on stuff) for the new Chart Editor.
+ */
+@:nullSafety
+@:access(funkin.ui.debug.charting.ChartEditorState)
+class ChartEditorContextMenuHandler
+{
+ static var existingMenus:Array