From 3add6a965ed55d7c7751b7c30243010a37a83130 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 16 Oct 2023 20:12:40 -0400 Subject: [PATCH 1/7] Several crashes fixed and working Random! --- source/funkin/FreeplayState.hx | 64 ++++++++++++++++---- source/funkin/freeplayStuff/SongMenuItem.hx | 65 +++++++++++---------- 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 4e7674e93..c7d3b7a2f 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -511,16 +511,19 @@ class FreeplayState extends MusicBeatSubState // this creates a filter to return all the songs that start with a letter between those two var filterRegexp = new EReg("^[" + filterStuff.filterData + "].*", "i"); tempSongs = tempSongs.filter(str -> { + if (str == null) return true; // Random return filterRegexp.match(str.songName); }); case STARTSWITH: tempSongs = tempSongs.filter(str -> { + if (str == null) return true; // Random return str.songName.toLowerCase().startsWith(filterStuff.filterData); }); case ALL: // no filter! case FAVORITE: tempSongs = tempSongs.filter(str -> { + if (str == null) return true; // Random return str.isFav; }); default: @@ -531,7 +534,7 @@ class FreeplayState extends MusicBeatSubState var hsvShader:HSVShader = new HSVShader(); var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem); - randomCapsule.init(FlxG.width, 0, "Random"); + randomCapsule.init(FlxG.width, 0, null); randomCapsule.onConfirm = function() { capsuleOnConfirmRandom(randomCapsule); }; @@ -550,8 +553,7 @@ class FreeplayState extends MusicBeatSubState var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem); - funnyMenu.init(FlxG.width, 0, tempSongs[i].songName); - if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter); + funnyMenu.init(FlxG.width, 0, tempSongs[i]); funnyMenu.onConfirm = function() { capsuleOnConfirmDefault(funnyMenu); }; @@ -917,11 +919,29 @@ class FreeplayState extends MusicBeatSubState } } - function capsuleOnConfirmRandom(cap:SongMenuItem):Void + function capsuleOnConfirmRandom(randomCapsule:SongMenuItem):Void { trace("RANDOM SELECTED"); busy = true; + + var availableSongCapsules:Array = grpCapsules.members.filter(function(cap:SongMenuItem) { + // Dead capsules are ones which were removed from the list when changing filters. + return cap.alive && cap.songData != null; + }); + + trace('Available songs: ${availableSongCapsules.map(function(cap) { + return cap.songData.songName; + })}'); + + var targetSong:SongMenuItem = FlxG.random.getObject(availableSongCapsules); + + // Seeing if I can do an animation... + curSelected = grpCapsules.members.indexOf(targetSong); + changeSelection(0); // Trigger an update. + + // Act like we hit Confirm on that song. + capsuleOnConfirmDefault(targetSong); } function capsuleOnConfirmDefault(cap:SongMenuItem):Void @@ -930,8 +950,19 @@ class FreeplayState extends MusicBeatSubState PlayStatePlaylist.isStoryMode = false; - var songId:String = cap.songTitle.toLowerCase(); - var targetSong:Song = SongRegistry.instance.fetchEntry(songId); + if (cap.songData == null) + { + trace('[WARN] Failure while trying to load song!'); + busy = false; + return; + } + + var targetSong:Null = SongRegistry.instance.fetchEntry(cap.songData.songId); + if (targetSong == null) + { + trace('[WARN] Could not retrieve song ${targetSong}.'); + } + var targetDifficulty:String = switch (curDifficulty) { case 0: @@ -956,7 +987,7 @@ class FreeplayState extends MusicBeatSubState targetCharacter = 'pico'; } - PlayStatePlaylist.campaignId = songs[curSelected].levelId; + PlayStatePlaylist.campaignId = cap.songData.levelId; // Visual and audio effects. FlxG.sound.play(Paths.sound('confirmMenu')); @@ -967,7 +998,7 @@ class FreeplayState extends MusicBeatSubState targetSong.cacheCharts(true); new FlxTimer().start(1, function(tmr:FlxTimer) { - Paths.setCurrentLevel(songs[curSelected].levelId); + Paths.setCurrentLevel(cap.songData.levelId); LoadingState.loadAndSwitchState(new PlayState( { targetSong: targetSong, @@ -983,6 +1014,8 @@ class FreeplayState extends MusicBeatSubState FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName)); + var prevSelected = curSelected; + curSelected += change; if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; @@ -999,10 +1032,10 @@ class FreeplayState extends MusicBeatSubState default: 'normal'; }; - var daSong = songs[curSelected]; - if (daSong != null) + var daSongCapsule = grpCapsules.members[curSelected]; + if (daSongCapsule.songData != null) { - var songScore:SaveScoreData = Save.get().getSongScore(daSong.songId, targetDifficulty); + var songScore:SaveScoreData = Save.get().getSongScore(daSongCapsule.songData.songId, targetDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore?.accuracy ?? 0.0; } @@ -1031,6 +1064,15 @@ class FreeplayState extends MusicBeatSubState FlxG.sound.playMusic(Paths.music('freeplay/freeplayRandom'), 0); FlxG.sound.music.fadeIn(2, 0, 0.8); } + else + { + // TODO: Stream the instrumental of the selected song? + if (prevSelected == 0) + { + FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); + FlxG.sound.music.fadeIn(2, 0, 0.8); + } + } grpCapsules.members[curSelected].selected = true; } } diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 5fd7eb576..06de92886 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -1,5 +1,6 @@ package funkin.freeplayStuff; +import funkin.FreeplayState.FreeplaySongData; import funkin.shaderslmfao.HSVShader; import funkin.shaderslmfao.GaussianBlurShader; import flixel.group.FlxGroup; @@ -19,9 +20,13 @@ class SongMenuItem extends FlxSpriteGroup var pixelIcon:FlxSprite; - public var selected(default, set):Bool; + /** + * Modify this by calling `init()` + * If `null`, assume this SongMenuItem is for the "Random Song" option. + */ + public var songData(default, null):Null = null; - public var songTitle:String = "Test"; + public var selected(default, set):Bool; public var songText:CapsuleText; public var favIcon:FlxSprite; @@ -45,12 +50,10 @@ class SongMenuItem extends FlxSpriteGroup public var hsvShader(default, set):HSVShader; - public function new(x:Float, y:Float, song:String, ?character:String) + public function new(x:Float, y:Float) { super(x, y); - this.songTitle = song; - capsule = new FlxSprite(); capsule.frames = Paths.getSparrowAtlas('freeplay/freeplayCapsule'); capsule.animation.addByPrefix('selected', 'mp3 capsule w backing0', 24); @@ -86,7 +89,7 @@ class SongMenuItem extends FlxSpriteGroup ranking.x -= 10; } - songText = new CapsuleText(capsule.width * 0.26, 45, songTitle, Std.int(40 * realScaled)); + songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled)); add(songText); grpHide.add(songText); @@ -97,8 +100,6 @@ class SongMenuItem extends FlxSpriteGroup add(pixelIcon); grpHide.add(pixelIcon); - if (character != null) setCharacter(character); - favIcon = new FlxSprite(400, 40); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.animation.addByPrefix('fav', "favorite heart", 24, false); @@ -144,18 +145,21 @@ class SongMenuItem extends FlxSpriteGroup if (value) textAppear(); - selectedAlpha(); + updateSelected(); } - public function init(x:Float, y:Float, song:String, ?character:String) + public function init(x:Float, y:Float, songData:Null) { this.x = x; this.y = y; - this.songTitle = song; - songText.text = this.songTitle; - if (character != null) setCharacter(character); + this.songData = songData; - selected = selected; + // Update capsule text. + songText.text = songData?.songName ?? 'Random'; + // Update capsule character. + if (songData?.songCharacter != null) setCharacter(songData.songCharacter); + // Update opacity, offsets, etc. + updateSelected(); } /** @@ -169,6 +173,7 @@ class SongMenuItem extends FlxSpriteGroup trace(char); + // TODO: Put this in the character metadata where it belongs. switch (char) { case "monster-christmas": @@ -244,7 +249,7 @@ class SongMenuItem extends FlxSpriteGroup { visible = true; capsule.alpha = 1; - selectedAlpha(); + updateSelected(); doLerp = true; doJumpIn = false; doJumpOut = false; @@ -319,24 +324,22 @@ class SongMenuItem extends FlxSpriteGroup return (index * ((height * realScaled) + 10)) + 120; } - /** - * Merely a helper function to call set_selected, to make sure that the alpha is correct on the rankings/selections - */ - public function selectedAlpha():Void - { - selected = selected; - } - function set_selected(value:Bool):Bool { // cute one liners, lol! - diffGrayscale.setAmount(value ? 0 : 0.8); - songText.alpha = value ? 1 : 0.6; - songText.blurredText.visible = value ? true : false; - capsule.offset.x = value ? 0 : -5; - capsule.animation.play(value ? "selected" : "unselected"); - ranking.alpha = value ? 1 : 0.7; - ranking.color = value ? 0xFFFFFFFF : 0xFFAAAAAA; - return value; + selected = value; + updateSelected(); + return selected; + } + + function updateSelected():Void + { + diffGrayscale.setAmount(this.selected ? 0 : 0.8); + songText.alpha = this.selected ? 1 : 0.6; + songText.blurredText.visible = this.selected ? true : false; + capsule.offset.x = this.selected ? 0 : -5; + capsule.animation.play(this.selected ? "selected" : "unselected"); + ranking.alpha = this.selected ? 1 : 0.7; + ranking.color = this.selected ? 0xFFFFFFFF : 0xFFAAAAAA; } } From acce2ac8110afa6dd538850e39ca34d6420b9c06 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 25 Oct 2023 01:12:46 -0400 Subject: [PATCH 2/7] Move ScriptEventType to its own module. --- source/funkin/MusicBeatState.hx | 14 +- source/funkin/MusicBeatSubState.hx | 4 +- source/funkin/modding/events/ScriptEvent.hx | 263 +---------------- .../modding/events/ScriptEventDispatcher.hx | 62 ++-- .../funkin/modding/events/ScriptEventType.hx | 271 ++++++++++++++++++ source/funkin/modding/module/ModuleHandler.hx | 8 +- source/funkin/play/Countdown.hx | 12 +- source/funkin/play/character/CharacterData.hx | 2 +- .../play/cutscene/dialogue/Conversation.hx | 18 +- .../dialogue/ConversationDebugState.hx | 2 +- source/funkin/play/stage/Stage.hx | 2 +- .../ui/debug/charting/ChartEditorState.hx | 20 +- .../ui/haxeui/components/CharacterPlayer.hx | 8 +- 13 files changed, 351 insertions(+), 335 deletions(-) create mode 100644 source/funkin/modding/events/ScriptEventType.hx diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 9a986a8b5..0c1694189 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -117,7 +117,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function stepHit():Bool { - var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(ScriptEventType.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -128,7 +128,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function beatHit():Bool { - var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -148,7 +148,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler override function startOutro(onComplete:() -> Void):Void { - var event = new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_BEGIN, null, true); + var event = new StateChangeScriptEvent(ScriptEventType.STATE_CHANGE_BEGIN, null, true); dispatchEvent(event); @@ -164,7 +164,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public override function openSubState(targetSubState:FlxSubState):Void { - var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_BEGIN, targetSubState, true); + var event = new SubStateScriptEvent(ScriptEventType.SUBSTATE_OPEN_BEGIN, targetSubState, true); dispatchEvent(event); @@ -175,12 +175,12 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function onOpenSubStateComplete(targetState:FlxSubState):Void { - dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_END, targetState, true)); + dispatchEvent(new SubStateScriptEvent(ScriptEventType.SUBSTATE_OPEN_END, targetState, true)); } public override function closeSubState():Void { - var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_BEGIN, this.subState, true); + var event = new SubStateScriptEvent(ScriptEventType.SUBSTATE_CLOSE_BEGIN, this.subState, true); dispatchEvent(event); @@ -191,6 +191,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function onCloseSubStateComplete(targetState:FlxSubState):Void { - dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_END, targetState, true)); + dispatchEvent(new SubStateScriptEvent(ScriptEventType.SUBSTATE_CLOSE_END, targetState, true)); } } diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/MusicBeatSubState.hx index 31d1bd14c..287a77a6c 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -96,7 +96,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function stepHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(ScriptEventType.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -112,7 +112,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function beatHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 586a6206c..1ab3f357a 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -10,265 +10,12 @@ import funkin.play.notes.NoteDirection; import openfl.events.EventType; import openfl.events.KeyboardEvent; -typedef ScriptEventType = EventType; - /** * This is a base class for all events that are issued to scripted classes. * It can be used to identify the type of event called, store data, and cancel event propagation. */ class ScriptEvent { - /** - * Called when the relevant object is created. - * Keep in mind that the constructor may be called before the object is needed, - * for the purposes of caching data or otherwise. - * - * This event is not cancelable. - */ - public static inline final CREATE:ScriptEventType = 'CREATE'; - - /** - * Called when the relevant object is destroyed. - * This should perform relevant cleanup to ensure good performance. - * - * This event is not cancelable. - */ - public static inline final DESTROY:ScriptEventType = 'DESTROY'; - - /** - * Called when the relevent object is added to the game state. - * This assumes all data is loaded and ready to go. - * - * This event is not cancelable. - */ - public static inline final ADDED:ScriptEventType = 'ADDED'; - - /** - * Called during the update function. - * This is called every frame, so be careful! - * - * This event is not cancelable. - */ - public static inline final UPDATE:ScriptEventType = 'UPDATE'; - - /** - * Called when the player moves to pause the game. - * - * This event IS cancelable! Canceling the event will prevent the game from pausing. - */ - public static inline final PAUSE:ScriptEventType = 'PAUSE'; - - /** - * Called when the player moves to unpause the game while paused. - * - * This event IS cancelable! Canceling the event will prevent the game from resuming. - */ - public static inline final RESUME:ScriptEventType = 'RESUME'; - - /** - * Called once per step in the song. This happens 4 times per measure. - * - * This event is not cancelable. - */ - public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT'; - - /** - * Called once per step in the song. This happens 16 times per measure. - * - * This event is not cancelable. - */ - public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT'; - - /** - * Called when a character hits a note. - * Important information such as judgement/timing, note data, player/opponent, etc. are all provided. - * - * This event IS cancelable! Canceling this event prevents the note from being hit, - * and will likely result in a miss later. - */ - public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT'; - - /** - * Called when a character misses a note. - * Important information such as note data, player/opponent, etc. are all provided. - * - * This event IS cancelable! Canceling this event prevents the note from being considered missed, - * avoiding a combo break and lost health. - */ - public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS'; - - /** - * Called when a character presses a note when there was none there, causing them to lose health. - * Important information such as direction pressed, etc. are all provided. - * - * This event IS cancelable! Canceling this event prevents the note from being considered missed, - * avoiding lost health/score and preventing the miss animation. - */ - public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS'; - - /** - * Called when a song event is reached in the chart. - * - * This event IS cancelable! Cancelling this event prevents the event from being triggered, - * thus blocking its normal functionality. - */ - public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT'; - - /** - * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin. - * - * This event is not cancelable. - */ - public static inline final SONG_START:ScriptEventType = 'SONG_START'; - - /** - * Called when the song ends. This happens as the instrumental and vocals end. - * - * This event is not cancelable. - */ - public static inline final SONG_END:ScriptEventType = 'SONG_END'; - - /** - * Called when the countdown begins. This occurs before the song starts. - * - * This event IS cancelable! Canceling this event will prevent the countdown from starting. - * - The song will not start until you call Countdown.performCountdown() later. - * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it. - */ - public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START'; - - /** - * Called when a step of the countdown happens. - * Includes information about what step of the countdown was hit. - * - * This event IS cancelable! Canceling this event will pause the countdown. - * - The countdown will not resume until you call PlayState.resumeCountdown(). - */ - public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP'; - - /** - * Called when the countdown is done but just before the song starts. - * - * This event is not cancelable. - */ - public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END'; - - /** - * Called before the game over screen triggers and the death animation plays. - * - * This event is not cancelable. - */ - public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER'; - - /** - * Called after the player presses a key to restart the game. - * This can happen from the pause menu or the game over screen. - * - * This event IS cancelable! Canceling this event will prevent the game from restarting. - */ - public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY'; - - /** - * Called when the player pushes down any key on the keyboard. - * - * This event is not cancelable. - */ - public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN'; - - /** - * Called when the player releases a key on the keyboard. - * - * This event is not cancelable. - */ - public static inline final KEY_UP:ScriptEventType = 'KEY_UP'; - - /** - * Called when the game has finished loading the notes from JSON. - * This allows modders to mutate the notes before they are used in the song. - * - * This event is not cancelable. - */ - public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED'; - - /** - * Called when the game is about to switch the current FlxState. - * - * This event is not cancelable. - */ - public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN'; - - /** - * Called when the game has finished switching the current FlxState. - * - * This event is not cancelable. - */ - public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END'; - - /** - * Called when the game is about to open a new FlxSubState. - * - * This event is not cancelable. - */ - public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN'; - - /** - * Called when the game has finished opening a new FlxSubState. - * - * This event is not cancelable. - */ - public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END'; - - /** - * Called when the game is about to close the current FlxSubState. - * - * This event is not cancelable. - */ - public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN'; - - /** - * Called when the game has finished closing the current FlxSubState. - * - * This event is not cancelable. - */ - public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END'; - - /** - * Called when the game starts a conversation. - * - * This event is not cancelable. - */ - public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START'; - - /** - * Called to display the next line of conversation. - * - * This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line. - * - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation. - */ - public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE'; - - /** - * Called to skip scrolling the current line of conversation. - * - * This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line. - * - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing. - */ - public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE'; - - /** - * Called to skip the conversation. - * - * This event IS cancelable! Canceling this event will prevent the conversation from skipping. - */ - public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP'; - - /** - * Called when the game ends a conversation. - * - * This event is not cancelable. - */ - public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END'; - /** * If true, the behavior associated with this event can be prevented. * For example, cancelling COUNTDOWN_START should prevent the countdown from starting, @@ -411,7 +158,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void { - super(ScriptEvent.NOTE_GHOST_MISS, true); + super(ScriptEventType.NOTE_GHOST_MISS, true); this.dir = dir; this.hasPossibleNotes = hasPossibleNotes; this.healthChange = healthChange; @@ -439,7 +186,7 @@ class SongEventScriptEvent extends ScriptEvent public function new(event:funkin.data.song.SongData.SongEventData):Void { - super(ScriptEvent.SONG_EVENT, true); + super(ScriptEventType.SONG_EVENT, true); this.event = event; } @@ -462,7 +209,7 @@ class UpdateScriptEvent extends ScriptEvent public function new(elapsed:Float):Void { - super(ScriptEvent.UPDATE, false); + super(ScriptEventType.UPDATE, false); this.elapsed = elapsed; } @@ -591,7 +338,7 @@ class SongLoadScriptEvent extends ScriptEvent public function new(id:String, difficulty:String, notes:Array):Void { - super(ScriptEvent.SONG_LOADED, false); + super(ScriptEventType.SONG_LOADED, false); this.id = id; this.difficulty = difficulty; this.notes = notes; @@ -660,7 +407,7 @@ class PauseScriptEvent extends ScriptEvent public function new(gitaroo:Bool):Void { - super(ScriptEvent.PAUSE, true); + super(ScriptEventType.PAUSE, true); this.gitaroo = gitaroo; } } diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index 5e3e32a46..5a746d064 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -23,13 +23,13 @@ class ScriptEventDispatcher // IScriptedClass switch (event.type) { - case ScriptEvent.CREATE: + case ScriptEventType.CREATE: target.onCreate(event); return; - case ScriptEvent.DESTROY: + case ScriptEventType.DESTROY: target.onDestroy(event); return; - case ScriptEvent.UPDATE: + case ScriptEventType.UPDATE: target.onUpdate(cast event); return; } @@ -39,7 +39,7 @@ class ScriptEventDispatcher var t:IStateStageProp = cast(target, IStateStageProp); switch (event.type) { - case ScriptEvent.ADDED: + case ScriptEventType.ADDED: t.onAdd(cast event); return; } @@ -50,19 +50,19 @@ class ScriptEventDispatcher var t:IDialogueScriptedClass = cast(target, IDialogueScriptedClass); switch (event.type) { - case ScriptEvent.DIALOGUE_START: + case ScriptEventType.DIALOGUE_START: t.onDialogueStart(cast event); return; - case ScriptEvent.DIALOGUE_LINE: + case ScriptEventType.DIALOGUE_LINE: t.onDialogueLine(cast event); return; - case ScriptEvent.DIALOGUE_COMPLETE_LINE: + case ScriptEventType.DIALOGUE_COMPLETE_LINE: t.onDialogueCompleteLine(cast event); return; - case ScriptEvent.DIALOGUE_SKIP: + case ScriptEventType.DIALOGUE_SKIP: t.onDialogueSkip(cast event); return; - case ScriptEvent.DIALOGUE_END: + case ScriptEventType.DIALOGUE_END: t.onDialogueEnd(cast event); return; } @@ -73,52 +73,52 @@ class ScriptEventDispatcher var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass); switch (event.type) { - case ScriptEvent.NOTE_HIT: + case ScriptEventType.NOTE_HIT: t.onNoteHit(cast event); return; - case ScriptEvent.NOTE_MISS: + case ScriptEventType.NOTE_MISS: t.onNoteMiss(cast event); return; - case ScriptEvent.NOTE_GHOST_MISS: + case ScriptEventType.NOTE_GHOST_MISS: t.onNoteGhostMiss(cast event); return; - case ScriptEvent.SONG_BEAT_HIT: + case ScriptEventType.SONG_BEAT_HIT: t.onBeatHit(cast event); return; - case ScriptEvent.SONG_STEP_HIT: + case ScriptEventType.SONG_STEP_HIT: t.onStepHit(cast event); return; - case ScriptEvent.SONG_START: + case ScriptEventType.SONG_START: t.onSongStart(event); return; - case ScriptEvent.SONG_END: + case ScriptEventType.SONG_END: t.onSongEnd(event); return; - case ScriptEvent.SONG_RETRY: + case ScriptEventType.SONG_RETRY: t.onSongRetry(event); return; - case ScriptEvent.GAME_OVER: + case ScriptEventType.GAME_OVER: t.onGameOver(event); return; - case ScriptEvent.PAUSE: + case ScriptEventType.PAUSE: t.onPause(cast event); return; - case ScriptEvent.RESUME: + case ScriptEventType.RESUME: t.onResume(event); return; - case ScriptEvent.SONG_EVENT: + case ScriptEventType.SONG_EVENT: t.onSongEvent(cast event); return; - case ScriptEvent.COUNTDOWN_START: + case ScriptEventType.COUNTDOWN_START: t.onCountdownStart(cast event); return; - case ScriptEvent.COUNTDOWN_STEP: + case ScriptEventType.COUNTDOWN_STEP: t.onCountdownStep(cast event); return; - case ScriptEvent.COUNTDOWN_END: + case ScriptEventType.COUNTDOWN_END: t.onCountdownEnd(cast event); return; - case ScriptEvent.SONG_LOADED: + case ScriptEventType.SONG_LOADED: t.onSongLoaded(cast event); return; } @@ -129,22 +129,22 @@ class ScriptEventDispatcher var t = cast(target, IStateChangingScriptedClass); switch (event.type) { - case ScriptEvent.STATE_CHANGE_BEGIN: + case ScriptEventType.STATE_CHANGE_BEGIN: t.onStateChangeBegin(cast event); return; - case ScriptEvent.STATE_CHANGE_END: + case ScriptEventType.STATE_CHANGE_END: t.onStateChangeEnd(cast event); return; - case ScriptEvent.SUBSTATE_OPEN_BEGIN: + case ScriptEventType.SUBSTATE_OPEN_BEGIN: t.onSubStateOpenBegin(cast event); return; - case ScriptEvent.SUBSTATE_OPEN_END: + case ScriptEventType.SUBSTATE_OPEN_END: t.onSubStateOpenEnd(cast event); return; - case ScriptEvent.SUBSTATE_CLOSE_BEGIN: + case ScriptEventType.SUBSTATE_CLOSE_BEGIN: t.onSubStateCloseBegin(cast event); return; - case ScriptEvent.SUBSTATE_CLOSE_END: + case ScriptEventType.SUBSTATE_CLOSE_END: t.onSubStateCloseEnd(cast event); return; } diff --git a/source/funkin/modding/events/ScriptEventType.hx b/source/funkin/modding/events/ScriptEventType.hx new file mode 100644 index 000000000..0385a19e9 --- /dev/null +++ b/source/funkin/modding/events/ScriptEventType.hx @@ -0,0 +1,271 @@ +package funkin.modding.events; + +enum abstract ScriptEventType(String) from String to String +{ + /** + * Called when the relevant object is created. + * Keep in mind that the constructor may be called before the object is needed, + * for the purposes of caching data or otherwise. + * + * This event is not cancelable. + */ + public static inline final CREATE:ScriptEventType = 'CREATE'; + + /** + * Called when the relevant object is destroyed. + * This should perform relevant cleanup to ensure good performance. + * + * This event is not cancelable. + */ + public static inline final DESTROY:ScriptEventType = 'DESTROY'; + + /** + * Called when the relevent object is added to the game state. + * This assumes all data is loaded and ready to go. + * + * This event is not cancelable. + */ + public static inline final ADDED:ScriptEventType = 'ADDED'; + + /** + * Called during the update function. + * This is called every frame, so be careful! + * + * This event is not cancelable. + */ + public static inline final UPDATE:ScriptEventType = 'UPDATE'; + + /** + * Called when the player moves to pause the game. + * + * This event IS cancelable! Canceling the event will prevent the game from pausing. + */ + public static inline final PAUSE:ScriptEventType = 'PAUSE'; + + /** + * Called when the player moves to unpause the game while paused. + * + * This event IS cancelable! Canceling the event will prevent the game from resuming. + */ + public static inline final RESUME:ScriptEventType = 'RESUME'; + + /** + * Called once per step in the song. This happens 4 times per measure. + * + * This event is not cancelable. + */ + public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT'; + + /** + * Called once per step in the song. This happens 16 times per measure. + * + * This event is not cancelable. + */ + public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT'; + + /** + * Called when a character hits a note. + * Important information such as judgement/timing, note data, player/opponent, etc. are all provided. + * + * This event IS cancelable! Canceling this event prevents the note from being hit, + * and will likely result in a miss later. + */ + public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT'; + + /** + * Called when a character misses a note. + * Important information such as note data, player/opponent, etc. are all provided. + * + * This event IS cancelable! Canceling this event prevents the note from being considered missed, + * avoiding a combo break and lost health. + */ + public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS'; + + /** + * Called when a character presses a note when there was none there, causing them to lose health. + * Important information such as direction pressed, etc. are all provided. + * + * This event IS cancelable! Canceling this event prevents the note from being considered missed, + * avoiding lost health/score and preventing the miss animation. + */ + public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS'; + + /** + * Called when a song event is reached in the chart. + * + * This event IS cancelable! Cancelling this event prevents the event from being triggered, + * thus blocking its normal functionality. + */ + public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT'; + + /** + * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin. + * + * This event is not cancelable. + */ + public static inline final SONG_START:ScriptEventType = 'SONG_START'; + + /** + * Called when the song ends. This happens as the instrumental and vocals end. + * + * This event is not cancelable. + */ + public static inline final SONG_END:ScriptEventType = 'SONG_END'; + + /** + * Called when the countdown begins. This occurs before the song starts. + * + * This event IS cancelable! Canceling this event will prevent the countdown from starting. + * - The song will not start until you call Countdown.performCountdown() later. + * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it. + */ + public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START'; + + /** + * Called when a step of the countdown happens. + * Includes information about what step of the countdown was hit. + * + * This event IS cancelable! Canceling this event will pause the countdown. + * - The countdown will not resume until you call PlayState.resumeCountdown(). + */ + public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP'; + + /** + * Called when the countdown is done but just before the song starts. + * + * This event is not cancelable. + */ + public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END'; + + /** + * Called before the game over screen triggers and the death animation plays. + * + * This event is not cancelable. + */ + public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER'; + + /** + * Called after the player presses a key to restart the game. + * This can happen from the pause menu or the game over screen. + * + * This event IS cancelable! Canceling this event will prevent the game from restarting. + */ + public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY'; + + /** + * Called when the player pushes down any key on the keyboard. + * + * This event is not cancelable. + */ + public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN'; + + /** + * Called when the player releases a key on the keyboard. + * + * This event is not cancelable. + */ + public static inline final KEY_UP:ScriptEventType = 'KEY_UP'; + + /** + * Called when the game has finished loading the notes from JSON. + * This allows modders to mutate the notes before they are used in the song. + * + * This event is not cancelable. + */ + public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED'; + + /** + * Called when the game is about to switch the current FlxState. + * + * This event is not cancelable. + */ + public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN'; + + /** + * Called when the game has finished switching the current FlxState. + * + * This event is not cancelable. + */ + public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END'; + + /** + * Called when the game is about to open a new FlxSubState. + * + * This event is not cancelable. + */ + public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN'; + + /** + * Called when the game has finished opening a new FlxSubState. + * + * This event is not cancelable. + */ + public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END'; + + /** + * Called when the game is about to close the current FlxSubState. + * + * This event is not cancelable. + */ + public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN'; + + /** + * Called when the game has finished closing the current FlxSubState. + * + * This event is not cancelable. + */ + public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END'; + + /** + * Called when the game starts a conversation. + * + * This event is not cancelable. + */ + public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START'; + + /** + * Called to display the next line of conversation. + * + * This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line. + * - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation. + */ + public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE'; + + /** + * Called to skip scrolling the current line of conversation. + * + * This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line. + * - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing. + */ + public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE'; + + /** + * Called to skip the conversation. + * + * This event IS cancelable! Canceling this event will prevent the conversation from skipping. + */ + public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP'; + + /** + * Called when the game ends a conversation. + * + * This event is not cancelable. + */ + public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END'; + + /** + * Allow for comparing `ScriptEventType` to `String`. + */ + @:op(A == B) private static inline function equals(a:ScriptEventType, b:String):Bool + { + return (a : String) == b; + } + + /** + * Allow for comparing `ScriptEventType` to `String`. + */ + @:op(A != B) private static inline function notEquals(a:ScriptEventType, b:String):Bool + { + return (a : String) != b; + } +} diff --git a/source/funkin/modding/module/ModuleHandler.hx b/source/funkin/modding/module/ModuleHandler.hx index 3cc7b7984..5777c0ccc 100644 --- a/source/funkin/modding/module/ModuleHandler.hx +++ b/source/funkin/modding/module/ModuleHandler.hx @@ -1,7 +1,7 @@ package funkin.modding.module; import funkin.util.SortUtil; -import funkin.modding.events.ScriptEvent.UpdateScriptEvent; +import funkin.modding.events.ScriptEventType.UpdateScriptEvent; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.module.Module; @@ -55,7 +55,7 @@ class ModuleHandler static function onStateSwitchComplete():Void { - callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true)); + callEvent(new StateChangeScriptEvent(ScriptEventType.STATE_CHANGE_END, FlxG.state, true)); } static function addToModuleCache(module:Module):Void @@ -119,7 +119,7 @@ class ModuleHandler { if (moduleCache != null) { - var event = new ScriptEvent(ScriptEvent.DESTROY, false); + var event = new ScriptEvent(ScriptEventType.DESTROY, false); // Note: Ignore stopPropagation() for (key => value in moduleCache) @@ -148,6 +148,6 @@ class ModuleHandler public static inline function callOnCreate():Void { - callEvent(new ScriptEvent(ScriptEvent.CREATE, false)); + callEvent(new ScriptEvent(ScriptEventType.CREATE, false)); } } diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 9796c7161..93b446ee9 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -6,7 +6,7 @@ import flixel.FlxSprite; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.module.ModuleHandler; import funkin.modding.events.ScriptEvent; -import funkin.modding.events.ScriptEvent.CountdownScriptEvent; +import funkin.modding.events.ScriptEventType.CountdownScriptEvent; import flixel.util.FlxTimer; class Countdown @@ -43,7 +43,7 @@ class Countdown Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5); // Handle onBeatHit events manually // @:privateAccess - // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, 0, 0)); // The timer function gets called based on the beat of the song. countdownTimer = new FlxTimer(); @@ -59,7 +59,7 @@ class Countdown // onBeatHit events are now properly dispatched by the Conductor even at negative timestamps, // so calling this is no longer necessary. - // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, 0, 0)); // Countdown graphic. showCountdownGraphic(countdownStep, isPixelStyle); @@ -94,11 +94,11 @@ class Countdown switch (index) { case BEFORE: - event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_START, index); + event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_START, index); case THREE | TWO | ONE | GO: // I didn't know you could use `|` in a switch/case block! - event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_STEP, index); + event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_STEP, index); case AFTER: - event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_END, index, false); + event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_END, index, false); default: return true; } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 8be9f25c7..e42c4e629 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -254,7 +254,7 @@ class CharacterDataParser char.debug = debug; // Call onCreate only in the fetchCharacter() function, not at application initialization. - ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE)); + ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEventType.CREATE)); return char; } diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 2b7db381c..42f2ead65 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -120,7 +120,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass this.alpha = 1.0; // Start the dialogue. - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, false)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, false)); } function setupMusic():Void @@ -214,7 +214,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass return; } - ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(ScriptEvent.CREATE, true)); + ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(ScriptEventType.CREATE, true)); currentSpeaker = nextSpeaker; currentSpeaker.zIndex = 200; @@ -258,7 +258,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass return; } - ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(ScriptEvent.CREATE, true)); + ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(ScriptEventType.CREATE, true)); currentDialogueBox = nextDialogueBox; currentDialogueBox.zIndex = 300; @@ -293,7 +293,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass public function startConversation():Void { - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, true)); } /** @@ -308,13 +308,13 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass switch (state) { case ConversationState.Start: - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, true)); case ConversationState.Opening: - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_COMPLETE_LINE, this, true)); case ConversationState.Speaking: - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_COMPLETE_LINE, this, true)); case ConversationState.Idle: - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_LINE, this, true)); case ConversationState.Ending: // Skip the outro. endOutro(); @@ -371,7 +371,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass */ public function skipConversation():Void { - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_SKIP, this, true)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_SKIP, this, true)); } static var outroTween:FlxTween; diff --git a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx index 4d7f74a58..8daaf8804 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx @@ -30,7 +30,7 @@ class ConversationDebugState extends MusicBeatState conversation.completeCallback = onConversationComplete; add(conversation); - ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(ScriptEvent.CREATE, false)); + ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(ScriptEventType.CREATE, false)); } function onConversationComplete():Void diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index d9875e456..ed302b9ae 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -402,7 +402,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass // Add the character to the scene. this.add(character); - ScriptEventDispatcher.callEvent(character, new ScriptEvent(ScriptEvent.ADDED, false)); + ScriptEventDispatcher.callEvent(character, new ScriptEvent(ScriptEventType.ADDED, false)); #if debug debugIconGroup.add(debugIcon); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 05173726f..5956877bf 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -697,8 +697,6 @@ class ChartEditorState extends HaxeUIState */ var opponentPreviewDirty:Bool = true; - var isInPlaytestMode:Bool = false; - /** * The list of command previously performed. Used for undoing previous actions. */ @@ -3584,13 +3582,13 @@ class ChartEditorState extends HaxeUIState { switch (event.type) { - case ScriptEvent.UPDATE: + case ScriptEventType.UPDATE: currentPlayerCharacterPlayer.onUpdate(cast event); - case ScriptEvent.SONG_BEAT_HIT: + case ScriptEventType.SONG_BEAT_HIT: currentPlayerCharacterPlayer.onBeatHit(cast event); - case ScriptEvent.SONG_STEP_HIT: + case ScriptEventType.SONG_STEP_HIT: currentPlayerCharacterPlayer.onStepHit(cast event); - case ScriptEvent.NOTE_HIT: + case ScriptEventType.NOTE_HIT: currentPlayerCharacterPlayer.onNoteHit(cast event); } } @@ -3599,13 +3597,13 @@ class ChartEditorState extends HaxeUIState { switch (event.type) { - case ScriptEvent.UPDATE: + case ScriptEventType.UPDATE: currentOpponentCharacterPlayer.onUpdate(cast event); - case ScriptEvent.SONG_BEAT_HIT: + case ScriptEventType.SONG_BEAT_HIT: currentOpponentCharacterPlayer.onBeatHit(cast event); - case ScriptEvent.SONG_STEP_HIT: + case ScriptEventType.SONG_STEP_HIT: currentOpponentCharacterPlayer.onStepHit(cast event); - case ScriptEvent.NOTE_HIT: + case ScriptEventType.NOTE_HIT: currentOpponentCharacterPlayer.onNoteHit(cast event); } } @@ -3921,7 +3919,7 @@ class ChartEditorState extends HaxeUIState var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault()); tempNote.noteData = noteData; tempNote.scrollFactor.set(0, 0); - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, tempNote, 1, true); + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, tempNote, 1, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! diff --git a/source/funkin/ui/haxeui/components/CharacterPlayer.hx b/source/funkin/ui/haxeui/components/CharacterPlayer.hx index 66b94bfa2..e71023683 100644 --- a/source/funkin/ui/haxeui/components/CharacterPlayer.hx +++ b/source/funkin/ui/haxeui/components/CharacterPlayer.hx @@ -1,9 +1,9 @@ package funkin.ui.haxeui.components; -import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent; -import funkin.modding.events.ScriptEvent.NoteScriptEvent; -import funkin.modding.events.ScriptEvent.SongTimeScriptEvent; -import funkin.modding.events.ScriptEvent.UpdateScriptEvent; +import funkin.modding.events.ScriptEventType.GhostMissNoteScriptEvent; +import funkin.modding.events.ScriptEventType.NoteScriptEvent; +import funkin.modding.events.ScriptEventType.SongTimeScriptEvent; +import funkin.modding.events.ScriptEventType.UpdateScriptEvent; import haxe.ui.core.IDataComponent; import funkin.play.character.BaseCharacter; import funkin.play.character.CharacterData.CharacterDataParser; From e14e6448ac4b082800677789a6df87378e29ffcc Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 25 Oct 2023 01:13:06 -0400 Subject: [PATCH 3/7] More event type refactors --- source/funkin/play/PlayState.hx | 28 +++++++++---------- .../play/cutscene/dialogue/Conversation.hx | 6 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 1d3480efe..53a076fc0 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -682,7 +682,7 @@ class PlayState extends MusicBeatSubState { if (!assertChartExists()) return; - dispatchEvent(new ScriptEvent(ScriptEvent.SONG_RETRY)); + dispatchEvent(new ScriptEvent(ScriptEventType.SONG_RETRY)); resetCamera(); @@ -867,7 +867,7 @@ class PlayState extends MusicBeatSubState deathCounter += 1; - dispatchEvent(new ScriptEvent(ScriptEvent.GAME_OVER)); + dispatchEvent(new ScriptEvent(ScriptEventType.GAME_OVER)); // Disable updates, preventing animations in the background from playing. persistentUpdate = false; @@ -994,7 +994,7 @@ class PlayState extends MusicBeatSubState { if (Std.isOfType(subState, PauseSubState)) { - var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true); + var event:ScriptEvent = new ScriptEvent(ScriptEventType.RESUME, true); dispatchEvent(event); @@ -1097,7 +1097,7 @@ class PlayState extends MusicBeatSubState if (this.currentStage != null) { remove(currentStage); - var event:ScriptEvent = new ScriptEvent(ScriptEvent.DESTROY, false); + var event:ScriptEvent = new ScriptEvent(ScriptEventType.DESTROY, false); ScriptEventDispatcher.callEvent(currentStage, event); currentStage = null; } @@ -1116,7 +1116,7 @@ class PlayState extends MusicBeatSubState super.debug_refreshModules(); - var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); + var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); ScriptEventDispatcher.callEvent(currentSong, event); } @@ -1332,7 +1332,7 @@ class PlayState extends MusicBeatSubState if (currentStage != null) { // Actually create and position the sprites. - var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); + var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); ScriptEventDispatcher.callEvent(currentStage, event); // Apply camera zoom level from stage data. @@ -1640,7 +1640,7 @@ class PlayState extends MusicBeatSubState add(currentConversation); refresh(); - var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); + var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); ScriptEventDispatcher.callEvent(currentConversation, event); } @@ -1664,7 +1664,7 @@ class PlayState extends MusicBeatSubState */ function startSong():Void { - dispatchEvent(new ScriptEvent(ScriptEvent.SONG_START)); + dispatchEvent(new ScriptEvent(ScriptEventType.SONG_START)); startingSong = false; @@ -1783,7 +1783,7 @@ class PlayState extends MusicBeatSubState // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, 0, true); + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, note, 0, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -1872,7 +1872,7 @@ class PlayState extends MusicBeatSubState { // Call an event to allow canceling the note miss. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, 0, true); + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_MISS, note, 0, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2021,7 +2021,7 @@ class PlayState extends MusicBeatSubState function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true); + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, note, Highscore.tallies.combo + 1, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2053,7 +2053,7 @@ class PlayState extends MusicBeatSubState // a MISS is when you let a note scroll past you!! Highscore.tallies.missed++; - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, Highscore.tallies.combo, true); + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_MISS, note, Highscore.tallies.combo, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; @@ -2385,7 +2385,7 @@ class PlayState extends MusicBeatSubState */ function endSong():Void { - dispatchEvent(new ScriptEvent(ScriptEvent.SONG_END)); + dispatchEvent(new ScriptEvent(ScriptEventType.SONG_END)); #if sys // spitter for ravy, teehee!! @@ -2593,7 +2593,7 @@ class PlayState extends MusicBeatSubState { remove(currentStage); currentStage.kill(); - dispatchEvent(new ScriptEvent(ScriptEvent.DESTROY, false)); + dispatchEvent(new ScriptEvent(ScriptEventType.DESTROY, false)); currentStage = null; } diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 42f2ead65..fa46c9252 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -405,7 +405,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass public function endOutro():Void { outroTween = null; - ScriptEventDispatcher.callEvent(this, new ScriptEvent(ScriptEvent.DESTROY, false)); + ScriptEventDispatcher.callEvent(this, new ScriptEvent(ScriptEventType.DESTROY, false)); } /** @@ -445,7 +445,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass if (currentDialogueEntry >= currentDialogueEntryCount) { - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_END, this, false)); } else { @@ -485,7 +485,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass propagateEvent(event); if (event.eventCanceled) return; - dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false)); + dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_END, this, false)); } public function onDialogueEnd(event:DialogueScriptEvent):Void From 8d875949b9f9b018139768b1c736d8eb96b368df Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 26 Oct 2023 05:45:53 -0400 Subject: [PATCH 4/7] Clean up several messy chunks of code, and add support for dragging selected notes. --- .../ui/debug/charting/ChartEditorState.hx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 5956877bf..5585f9fdf 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3582,13 +3582,13 @@ class ChartEditorState extends HaxeUIState { switch (event.type) { - case ScriptEventType.UPDATE: + case UPDATE: currentPlayerCharacterPlayer.onUpdate(cast event); - case ScriptEventType.SONG_BEAT_HIT: + case SONG_BEAT_HIT: currentPlayerCharacterPlayer.onBeatHit(cast event); - case ScriptEventType.SONG_STEP_HIT: + case SONG_STEP_HIT: currentPlayerCharacterPlayer.onStepHit(cast event); - case ScriptEventType.NOTE_HIT: + case NOTE_HIT: currentPlayerCharacterPlayer.onNoteHit(cast event); } } @@ -3597,13 +3597,13 @@ class ChartEditorState extends HaxeUIState { switch (event.type) { - case ScriptEventType.UPDATE: + case UPDATE: currentOpponentCharacterPlayer.onUpdate(cast event); - case ScriptEventType.SONG_BEAT_HIT: + case SONG_BEAT_HIT: currentOpponentCharacterPlayer.onBeatHit(cast event); - case ScriptEventType.SONG_STEP_HIT: + case SONG_STEP_HIT: currentOpponentCharacterPlayer.onStepHit(cast event); - case ScriptEventType.NOTE_HIT: + case NOTE_HIT: currentOpponentCharacterPlayer.onNoteHit(cast event); } } @@ -3919,7 +3919,7 @@ class ChartEditorState extends HaxeUIState var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault()); tempNote.noteData = noteData; tempNote.scrollFactor.set(0, 0); - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, tempNote, 1, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, tempNote, 1, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! From eec3ac3ced9ebab78ae9c0c207103264b5e221db Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 26 Oct 2023 05:46:22 -0400 Subject: [PATCH 5/7] Clean up several messy chunks of code, and add support for dragging notes and events. --- .vscode/settings.json | 34 +- assets | 2 +- docs/style-guide.md | 9 +- source/funkin/InitState.hx | 4 + source/funkin/MusicBeatState.hx | 14 +- source/funkin/MusicBeatSubState.hx | 4 +- source/funkin/data/song/SongData.hx | 119 +- source/funkin/data/song/SongDataUtils.hx | 8 +- source/funkin/import.hx | 2 + source/funkin/modding/events/ScriptEvent.hx | 10 +- .../modding/events/ScriptEventDispatcher.hx | 67 +- .../funkin/modding/events/ScriptEventType.hx | 66 +- source/funkin/modding/module/ModuleHandler.hx | 8 +- source/funkin/play/Countdown.hx | 12 +- source/funkin/play/PlayState.hx | 28 +- source/funkin/play/character/CharacterData.hx | 2 +- .../play/cutscene/dialogue/Conversation.hx | 24 +- .../dialogue/ConversationDebugState.hx | 2 +- source/funkin/play/song/Song.hx | 22 +- source/funkin/play/stage/Stage.hx | 3 +- .../ui/debug/charting/ChartEditorCommand.hx | 879 ---- .../ui/debug/charting/ChartEditorState.hx | 3532 +++++++++-------- .../charting/commands/AddEventsCommand.hx | 67 + .../charting/commands/AddNotesCommand.hx | 72 + .../charting/commands/ChartEditorCommand.hx | 30 + .../charting/commands/CutItemsCommand.hx | 68 + .../commands/DeselectAllItemsCommand.hx | 42 + .../charting/commands/DeselectItemsCommand.hx | 60 + .../commands/ExtendNoteLengthCommand.hx | 52 + .../charting/commands/FlipNotesCommand.hx | 59 + .../commands/InvertSelectedItemsCommand.hx | 43 + .../charting/commands/MoveEventsCommand.hx | 72 + .../charting/commands/MoveItemsCommand.hx | 96 + .../charting/commands/MoveNotesCommand.hx | 75 + .../charting/commands/PasteItemsCommand.hx | 99 + .../charting/commands/RemoveEventsCommand.hx | 60 + .../charting/commands/RemoveItemsCommand.hx | 69 + .../charting/commands/RemoveNotesCommand.hx | 63 + .../commands/SelectAllItemsCommand.hx | 42 + .../charting/commands/SelectItemsCommand.hx | 78 + .../commands/SetItemSelectionCommand.hx | 48 + .../commands/SwitchDifficultyCommand.hx | 45 + .../ChartEditorEventSprite.hx | 17 +- .../ChartEditorHoldNoteSprite.hx | 4 +- .../ChartEditorNotePreview.hx | 2 +- .../{ => components}/ChartEditorNoteSprite.hx | 53 +- .../ChartEditorSelectionSquareSprite.hx | 20 + .../{ => handlers}/ChartEditorAudioHandler.hx | 61 +- .../ChartEditorDialogHandler.hx | 248 +- .../ChartEditorImportExportHandler.hx | 22 +- .../{ => handlers}/ChartEditorThemeHandler.hx | 15 +- .../ChartEditorToolboxHandler.hx | 92 +- source/funkin/ui/debug/charting/import.hx | 10 + .../{ => util}/ChartEditorDropdowns.hx | 21 +- .../ui/haxeui/components/CharacterPlayer.hx | 8 +- source/funkin/util/Constants.hx | 13 +- source/funkin/util/assets/SoundUtil.hx | 23 + source/funkin/util/tools/FloatTools.hx | 15 + source/funkin/util/tools/IntTools.hx | 16 + 59 files changed, 3809 insertions(+), 2922 deletions(-) delete mode 100644 source/funkin/ui/debug/charting/ChartEditorCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/AddEventsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/AddNotesCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/ChartEditorCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/CutItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/DeselectAllItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/DeselectItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/ExtendNoteLengthCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/FlipNotesCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/InvertSelectedItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/MoveEventsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/MoveItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/MoveNotesCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/PasteItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/SelectAllItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx create mode 100644 source/funkin/ui/debug/charting/commands/SwitchDifficultyCommand.hx rename source/funkin/ui/debug/charting/{ => components}/ChartEditorEventSprite.hx (91%) rename source/funkin/ui/debug/charting/{ => components}/ChartEditorHoldNoteSprite.hx (97%) rename source/funkin/ui/debug/charting/{ => components}/ChartEditorNotePreview.hx (98%) rename source/funkin/ui/debug/charting/{ => components}/ChartEditorNoteSprite.hx (85%) create mode 100644 source/funkin/ui/debug/charting/components/ChartEditorSelectionSquareSprite.hx rename source/funkin/ui/debug/charting/{ => handlers}/ChartEditorAudioHandler.hx (75%) rename source/funkin/ui/debug/charting/{ => handlers}/ChartEditorDialogHandler.hx (95%) rename source/funkin/ui/debug/charting/{ => handlers}/ChartEditorImportExportHandler.hx (87%) rename source/funkin/ui/debug/charting/{ => handlers}/ChartEditorThemeHandler.hx (98%) rename source/funkin/ui/debug/charting/{ => handlers}/ChartEditorToolboxHandler.hx (92%) create mode 100644 source/funkin/ui/debug/charting/import.hx rename source/funkin/ui/debug/charting/{ => util}/ChartEditorDropdowns.hx (89%) create mode 100644 source/funkin/util/assets/SoundUtil.hx create mode 100644 source/funkin/util/tools/FloatTools.hx create mode 100644 source/funkin/util/tools/IntTools.hx diff --git a/.vscode/settings.json b/.vscode/settings.json index 80d2bf76a..92d49c3d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,7 +71,7 @@ "files.eol": "\n", "haxe.displayPort": "auto", - "haxe.enableCompilationServer": true, + "haxe.enableCompilationServer": false, "haxe.displayServer": { "arguments": ["-v"] }, @@ -97,15 +97,35 @@ "args": ["-debug"] }, { - "label": "Windows / Debug (DEBUG ASSETS)", - "target": "windows", - "args": ["-debug", "-DDEBUG_ASSETS"] - }, - { - "label": "Windows / Debug (ANIMATE)", + "label": "Windows / Debug (FlxAnimate Test)", "target": "windows", "args": ["-debug", "-DANIMATE"] }, + { + "label": "Windows / Debug (Straight to Freeplay)", + "target": "windows", + "args": ["-debug", "-DFREEPLAY"] + }, + { + "label": "Windows / Debug (Straight to Play - Bopeebo Normal)", + "target": "windows", + "args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"] + }, + { + "label": "Windows / Debug (Straight to Chart Editor)", + "target": "windows", + "args": ["-debug", "-DCHARTING"] + }, + { + "label": "Windows / Debug (Straight to Animation Editor)", + "target": "windows", + "args": ["-debug", "-DANIMDEBUG"] + }, + { + "label": "Windows / Debug (Latency Test)", + "target": "windows", + "args": ["-debug", "-DLATENCY"] + }, { "label": "HTML5 / Debug", "target": "html5", diff --git a/assets b/assets index ef79a6cf1..be9d790af 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit ef79a6cf1ae3dcbd86a5b798f8117a6c692c0156 +Subproject commit be9d790af9c6f1f5e3afc7aed2b1d5c21823bc20 diff --git a/docs/style-guide.md b/docs/style-guide.md index 71ae844c4..1131cca2b 100644 --- a/docs/style-guide.md +++ b/docs/style-guide.md @@ -24,7 +24,7 @@ Example: ``` /** * Finds the largest deviation from the desired time inside this VoicesGroup. - * + * * @param targetTime The time to check against. * If none is provided, it checks the time of all members against the first member of this VoicesGroup. * @return The largest deviation from the target time found. @@ -52,3 +52,10 @@ import sys.io.File; #end ``` +## Argument Formatting + +[Optional arguments](https://haxe.org/manual/types-function-optional-arguments.html) and [default arguments](https://haxe.org/manual/types-function-default-values.html) should be mutually exclusive and not used together! + +For example, `myFunction(?input:Int)` should be used if you want the argument to be a `Null` whose value is `null` if no value is passed, and `myFunction(input:Int = 0)` should be used if you want the argument to be an `Int`, whose value is `0` if no value is passed. + +Using both at the same time is considered valid by Haxe, but `myFunction(?input:Int = 0)` results in a `Null` whose value defaults to 0 anyway, so it's never null, but it's annotated as nullable! The biggest consequence of this is that it makes null safety more annoying. diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index ecfa32eb3..5299a3aa0 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -266,6 +266,10 @@ class InitState extends FlxState return; } + // Load and cache the song's charts. + // TODO: Do this in the loading state. + songData.cacheCharts(true); + LoadingState.loadAndSwitchState(new funkin.play.PlayState( { targetSong: songData, diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 0c1694189..9861c48c7 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -117,7 +117,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function stepHit():Bool { - var event = new SongTimeScriptEvent(ScriptEventType.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -128,7 +128,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function beatHit():Bool { - var event = new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -148,7 +148,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler override function startOutro(onComplete:() -> Void):Void { - var event = new StateChangeScriptEvent(ScriptEventType.STATE_CHANGE_BEGIN, null, true); + var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true); dispatchEvent(event); @@ -164,7 +164,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public override function openSubState(targetSubState:FlxSubState):Void { - var event = new SubStateScriptEvent(ScriptEventType.SUBSTATE_OPEN_BEGIN, targetSubState, true); + var event = new SubStateScriptEvent(SUBSTATE_OPEN_BEGIN, targetSubState, true); dispatchEvent(event); @@ -175,12 +175,12 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function onOpenSubStateComplete(targetState:FlxSubState):Void { - dispatchEvent(new SubStateScriptEvent(ScriptEventType.SUBSTATE_OPEN_END, targetState, true)); + dispatchEvent(new SubStateScriptEvent(SUBSTATE_OPEN_END, targetState, true)); } public override function closeSubState():Void { - var event = new SubStateScriptEvent(ScriptEventType.SUBSTATE_CLOSE_BEGIN, this.subState, true); + var event = new SubStateScriptEvent(SUBSTATE_CLOSE_BEGIN, this.subState, true); dispatchEvent(event); @@ -191,6 +191,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function onCloseSubStateComplete(targetState:FlxSubState):Void { - dispatchEvent(new SubStateScriptEvent(ScriptEventType.SUBSTATE_CLOSE_END, targetState, true)); + dispatchEvent(new SubStateScriptEvent(SUBSTATE_CLOSE_END, targetState, true)); } } diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/MusicBeatSubState.hx index 287a77a6c..53fe19bdd 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -96,7 +96,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function stepHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(ScriptEventType.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); @@ -112,7 +112,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl */ public function beatHit():Bool { - var event:ScriptEvent = new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); + var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep); dispatchEvent(event); diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 88993e519..783f52a64 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -447,7 +447,7 @@ class SongChartData } } -class SongEventData +class SongEventDataRaw { /** * The timestamp of the event. The timestamp is in the format of the song's time format. @@ -503,40 +503,57 @@ class SongEventData return _stepTime = Conductor.getTimeInSteps(this.time); } +} + +/** + * Wrap SongEventData in an abstract so we can overload operators. + */ +@:forward(time, event, value, activated, getStepTime) +abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw +{ + public function new(time:Float, event:String, value:Dynamic = null) + { + this = new SongEventDataRaw(time, event, value); + } public inline function getDynamic(key:String):Null { - return value == null ? null : Reflect.field(value, key); + return this.value == null ? null : Reflect.field(this.value, key); } public inline function getBool(key:String):Null { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); } public inline function getInt(key:String):Null { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); } public inline function getFloat(key:String):Null { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); } public inline function getString(key:String):String { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); } public inline function getArray(key:String):Array { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); } public inline function getBoolArray(key:String):Array { - return value == null ? null : cast Reflect.field(value, key); + return this.value == null ? null : cast Reflect.field(this.value, key); + } + + public function clone():SongEventData + { + return new SongEventData(this.time, this.event, this.value); } @:op(A == B) @@ -584,7 +601,7 @@ class SongEventData } } -class SongNoteData +class SongNoteDataRaw { /** * The timestamp of the note. The timestamp is in the format of the song's time format. @@ -655,6 +672,48 @@ class SongNoteData return _stepTime = Conductor.getTimeInSteps(this.time); } + @:jignored + var _stepLength:Null = null; + + /** + * @param force Set to `true` to force recalculation (good after BPM changes) + * @return The length of the hold note in steps, or `0` if this is not a hold note. + */ + public function getStepLength(force = false):Float + { + if (this.length <= 0) return 0.0; + + if (_stepLength != null && !force) return _stepLength; + + return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime(); + } + + public function setStepLength(value:Float):Void + { + if (value <= 0) + { + this.length = 0.0; + } + else + { + var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time; + this.length = lengthMs; + } + _stepLength = null; + } +} + +/** + * Wrap SongNoteData in an abstract so we can overload operators. + */ +@:forward +abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw +{ + public function new(time:Float, data:Int, length:Float = 0, kind:String = '') + { + this = new SongNoteDataRaw(time, data, length, kind); + } + /** * The direction of the note, if applicable. * Strips the strumline index from the data. @@ -668,7 +727,12 @@ class SongNoteData public function getDirectionName(strumlineSize:Int = 4):String { - switch (this.data % strumlineSize) + return SongNoteData.buildDirectionName(this.data, strumlineSize); + } + + public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String + { + switch (data % strumlineSize) { case 0: return 'Left'; @@ -705,36 +769,6 @@ class SongNoteData return getStrumlineIndex(strumlineSize) == 0; } - @:jignored - var _stepLength:Null = null; - - /** - * @param force Set to `true` to force recalculation (good after BPM changes) - * @return The length of the hold note in steps, or `0` if this is not a hold note. - */ - public function getStepLength(force = false):Float - { - if (this.length <= 0) return 0.0; - - if (_stepLength != null && !force) return _stepLength; - - return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime(); - } - - public function setStepLength(value:Float):Void - { - if (value <= 0) - { - this.length = 0.0; - } - else - { - var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time; - this.length = lengthMs; - } - _stepLength = null; - } - @:jignored public var isHoldNote(get, never):Bool; @@ -797,6 +831,11 @@ class SongNoteData return this.time <= other.time; } + public function clone():SongNoteData + { + return new SongNoteData(this.time, this.data, this.length, this.kind); + } + /** * Produces a string representation suitable for debugging. */ diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 3ff3943c6..984af18fa 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -66,8 +66,14 @@ class SongDataUtils var result = notes.filter(function(note:SongNoteData):Bool { for (x in subtrahend) + { + // The currently iterated note is in the subtrahend array. // SongNoteData's == operation has been overridden so that this will work. - if (x == note) return false; + if (x == note) + { + return false; + } + } return true; }); diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 1c3a0fdb4..8c7124da0 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -12,7 +12,9 @@ using Lambda; using StringTools; using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; +using funkin.util.tools.FloatTools; using funkin.util.tools.Int64Tools; +using funkin.util.tools.IntTools; using funkin.util.tools.IteratorTools; using funkin.util.tools.MapTools; using funkin.util.tools.SongEventDataArrayTools; diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 1ab3f357a..18f934aee 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -158,7 +158,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void { - super(ScriptEventType.NOTE_GHOST_MISS, true); + super(NOTE_GHOST_MISS, true); this.dir = dir; this.hasPossibleNotes = hasPossibleNotes; this.healthChange = healthChange; @@ -186,7 +186,7 @@ class SongEventScriptEvent extends ScriptEvent public function new(event:funkin.data.song.SongData.SongEventData):Void { - super(ScriptEventType.SONG_EVENT, true); + super(SONG_EVENT, true); this.event = event; } @@ -209,7 +209,7 @@ class UpdateScriptEvent extends ScriptEvent public function new(elapsed:Float):Void { - super(ScriptEventType.UPDATE, false); + super(UPDATE, false); this.elapsed = elapsed; } @@ -338,7 +338,7 @@ class SongLoadScriptEvent extends ScriptEvent public function new(id:String, difficulty:String, notes:Array):Void { - super(ScriptEventType.SONG_LOADED, false); + super(SONG_LOADED, false); this.id = id; this.difficulty = difficulty; this.notes = notes; @@ -407,7 +407,7 @@ class PauseScriptEvent extends ScriptEvent public function new(gitaroo:Bool):Void { - super(ScriptEventType.PAUSE, true); + super(PAUSE, true); this.gitaroo = gitaroo; } } diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index 5a746d064..f5d797ea4 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -23,15 +23,16 @@ class ScriptEventDispatcher // IScriptedClass switch (event.type) { - case ScriptEventType.CREATE: + case CREATE: target.onCreate(event); return; - case ScriptEventType.DESTROY: + case DESTROY: target.onDestroy(event); return; - case ScriptEventType.UPDATE: + case UPDATE: target.onUpdate(cast event); return; + default: // Continue; } if (Std.isOfType(target, IStateStageProp)) @@ -39,9 +40,10 @@ class ScriptEventDispatcher var t:IStateStageProp = cast(target, IStateStageProp); switch (event.type) { - case ScriptEventType.ADDED: + case ADDED: t.onAdd(cast event); return; + default: // Continue; } } @@ -50,21 +52,22 @@ class ScriptEventDispatcher var t:IDialogueScriptedClass = cast(target, IDialogueScriptedClass); switch (event.type) { - case ScriptEventType.DIALOGUE_START: + case DIALOGUE_START: t.onDialogueStart(cast event); return; - case ScriptEventType.DIALOGUE_LINE: + case DIALOGUE_LINE: t.onDialogueLine(cast event); return; - case ScriptEventType.DIALOGUE_COMPLETE_LINE: + case DIALOGUE_COMPLETE_LINE: t.onDialogueCompleteLine(cast event); return; - case ScriptEventType.DIALOGUE_SKIP: + case DIALOGUE_SKIP: t.onDialogueSkip(cast event); return; - case ScriptEventType.DIALOGUE_END: + case DIALOGUE_END: t.onDialogueEnd(cast event); return; + default: // Continue; } } @@ -73,54 +76,55 @@ class ScriptEventDispatcher var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass); switch (event.type) { - case ScriptEventType.NOTE_HIT: + case NOTE_HIT: t.onNoteHit(cast event); return; - case ScriptEventType.NOTE_MISS: + case NOTE_MISS: t.onNoteMiss(cast event); return; - case ScriptEventType.NOTE_GHOST_MISS: + case NOTE_GHOST_MISS: t.onNoteGhostMiss(cast event); return; - case ScriptEventType.SONG_BEAT_HIT: + case SONG_BEAT_HIT: t.onBeatHit(cast event); return; - case ScriptEventType.SONG_STEP_HIT: + case SONG_STEP_HIT: t.onStepHit(cast event); return; - case ScriptEventType.SONG_START: + case SONG_START: t.onSongStart(event); return; - case ScriptEventType.SONG_END: + case SONG_END: t.onSongEnd(event); return; - case ScriptEventType.SONG_RETRY: + case SONG_RETRY: t.onSongRetry(event); return; - case ScriptEventType.GAME_OVER: + case GAME_OVER: t.onGameOver(event); return; - case ScriptEventType.PAUSE: + case PAUSE: t.onPause(cast event); return; - case ScriptEventType.RESUME: + case RESUME: t.onResume(event); return; - case ScriptEventType.SONG_EVENT: + case SONG_EVENT: t.onSongEvent(cast event); return; - case ScriptEventType.COUNTDOWN_START: + case COUNTDOWN_START: t.onCountdownStart(cast event); return; - case ScriptEventType.COUNTDOWN_STEP: + case COUNTDOWN_STEP: t.onCountdownStep(cast event); return; - case ScriptEventType.COUNTDOWN_END: + case COUNTDOWN_END: t.onCountdownEnd(cast event); return; - case ScriptEventType.SONG_LOADED: + case SONG_LOADED: t.onSongLoaded(cast event); return; + default: // Continue; } } @@ -129,24 +133,25 @@ class ScriptEventDispatcher var t = cast(target, IStateChangingScriptedClass); switch (event.type) { - case ScriptEventType.STATE_CHANGE_BEGIN: + case STATE_CHANGE_BEGIN: t.onStateChangeBegin(cast event); return; - case ScriptEventType.STATE_CHANGE_END: + case STATE_CHANGE_END: t.onStateChangeEnd(cast event); return; - case ScriptEventType.SUBSTATE_OPEN_BEGIN: + case SUBSTATE_OPEN_BEGIN: t.onSubStateOpenBegin(cast event); return; - case ScriptEventType.SUBSTATE_OPEN_END: + case SUBSTATE_OPEN_END: t.onSubStateOpenEnd(cast event); return; - case ScriptEventType.SUBSTATE_CLOSE_BEGIN: + case SUBSTATE_CLOSE_BEGIN: t.onSubStateCloseBegin(cast event); return; - case ScriptEventType.SUBSTATE_CLOSE_END: + case SUBSTATE_CLOSE_END: t.onSubStateCloseEnd(cast event); return; + default: // Continue; } } else diff --git a/source/funkin/modding/events/ScriptEventType.hx b/source/funkin/modding/events/ScriptEventType.hx index 0385a19e9..e06b5ad24 100644 --- a/source/funkin/modding/events/ScriptEventType.hx +++ b/source/funkin/modding/events/ScriptEventType.hx @@ -9,7 +9,7 @@ enum abstract ScriptEventType(String) from String to String * * This event is not cancelable. */ - public static inline final CREATE:ScriptEventType = 'CREATE'; + var CREATE = 'CREATE'; /** * Called when the relevant object is destroyed. @@ -17,7 +17,7 @@ enum abstract ScriptEventType(String) from String to String * * This event is not cancelable. */ - public static inline final DESTROY:ScriptEventType = 'DESTROY'; + var DESTROY = 'DESTROY'; /** * Called when the relevent object is added to the game state. @@ -25,7 +25,7 @@ enum abstract ScriptEventType(String) from String to String * * This event is not cancelable. */ - public static inline final ADDED:ScriptEventType = 'ADDED'; + var ADDED = 'ADDED'; /** * Called during the update function. @@ -33,35 +33,35 @@ enum abstract ScriptEventType(String) from String to String * * This event is not cancelable. */ - public static inline final UPDATE:ScriptEventType = 'UPDATE'; + var UPDATE = 'UPDATE'; /** * Called when the player moves to pause the game. * * This event IS cancelable! Canceling the event will prevent the game from pausing. */ - public static inline final PAUSE:ScriptEventType = 'PAUSE'; + var PAUSE = 'PAUSE'; /** * Called when the player moves to unpause the game while paused. * * This event IS cancelable! Canceling the event will prevent the game from resuming. */ - public static inline final RESUME:ScriptEventType = 'RESUME'; + var RESUME = 'RESUME'; /** * Called once per step in the song. This happens 4 times per measure. * * This event is not cancelable. */ - public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT'; + var SONG_BEAT_HIT = 'BEAT_HIT'; /** * Called once per step in the song. This happens 16 times per measure. * * This event is not cancelable. */ - public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT'; + var SONG_STEP_HIT = 'STEP_HIT'; /** * Called when a character hits a note. @@ -70,7 +70,7 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event prevents the note from being hit, * and will likely result in a miss later. */ - public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT'; + var NOTE_HIT = 'NOTE_HIT'; /** * Called when a character misses a note. @@ -79,7 +79,7 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event prevents the note from being considered missed, * avoiding a combo break and lost health. */ - public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS'; + var NOTE_MISS = 'NOTE_MISS'; /** * Called when a character presses a note when there was none there, causing them to lose health. @@ -88,7 +88,7 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event prevents the note from being considered missed, * avoiding lost health/score and preventing the miss animation. */ - public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS'; + var NOTE_GHOST_MISS = 'NOTE_GHOST_MISS'; /** * Called when a song event is reached in the chart. @@ -96,21 +96,21 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Cancelling this event prevents the event from being triggered, * thus blocking its normal functionality. */ - public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT'; + var SONG_EVENT = 'SONG_EVENT'; /** * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin. * * This event is not cancelable. */ - public static inline final SONG_START:ScriptEventType = 'SONG_START'; + var SONG_START = 'SONG_START'; /** * Called when the song ends. This happens as the instrumental and vocals end. * * This event is not cancelable. */ - public static inline final SONG_END:ScriptEventType = 'SONG_END'; + var SONG_END = 'SONG_END'; /** * Called when the countdown begins. This occurs before the song starts. @@ -119,7 +119,7 @@ enum abstract ScriptEventType(String) from String to String * - The song will not start until you call Countdown.performCountdown() later. * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it. */ - public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START'; + var COUNTDOWN_START = 'COUNTDOWN_START'; /** * Called when a step of the countdown happens. @@ -128,21 +128,21 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event will pause the countdown. * - The countdown will not resume until you call PlayState.resumeCountdown(). */ - public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP'; + var COUNTDOWN_STEP = 'COUNTDOWN_STEP'; /** * Called when the countdown is done but just before the song starts. * * This event is not cancelable. */ - public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END'; + var COUNTDOWN_END = 'COUNTDOWN_END'; /** * Called before the game over screen triggers and the death animation plays. * * This event is not cancelable. */ - public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER'; + var GAME_OVER = 'GAME_OVER'; /** * Called after the player presses a key to restart the game. @@ -150,21 +150,21 @@ enum abstract ScriptEventType(String) from String to String * * This event IS cancelable! Canceling this event will prevent the game from restarting. */ - public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY'; + var SONG_RETRY = 'SONG_RETRY'; /** * Called when the player pushes down any key on the keyboard. * * This event is not cancelable. */ - public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN'; + var KEY_DOWN = 'KEY_DOWN'; /** * Called when the player releases a key on the keyboard. * * This event is not cancelable. */ - public static inline final KEY_UP:ScriptEventType = 'KEY_UP'; + var KEY_UP = 'KEY_UP'; /** * Called when the game has finished loading the notes from JSON. @@ -172,56 +172,56 @@ enum abstract ScriptEventType(String) from String to String * * This event is not cancelable. */ - public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED'; + var SONG_LOADED = 'SONG_LOADED'; /** * Called when the game is about to switch the current FlxState. * * This event is not cancelable. */ - public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN'; + var STATE_CHANGE_BEGIN = 'STATE_CHANGE_BEGIN'; /** * Called when the game has finished switching the current FlxState. * * This event is not cancelable. */ - public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END'; + var STATE_CHANGE_END = 'STATE_CHANGE_END'; /** * Called when the game is about to open a new FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN'; + var SUBSTATE_OPEN_BEGIN = 'SUBSTATE_OPEN_BEGIN'; /** * Called when the game has finished opening a new FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END'; + var SUBSTATE_OPEN_END = 'SUBSTATE_OPEN_END'; /** * Called when the game is about to close the current FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN'; + var SUBSTATE_CLOSE_BEGIN = 'SUBSTATE_CLOSE_BEGIN'; /** * Called when the game has finished closing the current FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END'; + var SUBSTATE_CLOSE_END = 'SUBSTATE_CLOSE_END'; /** * Called when the game starts a conversation. * * This event is not cancelable. */ - public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START'; + var DIALOGUE_START = 'DIALOGUE_START'; /** * Called to display the next line of conversation. @@ -229,7 +229,7 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line. * - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation. */ - public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE'; + var DIALOGUE_LINE = 'DIALOGUE_LINE'; /** * Called to skip scrolling the current line of conversation. @@ -237,21 +237,21 @@ enum abstract ScriptEventType(String) from String to String * This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line. * - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing. */ - public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE'; + var DIALOGUE_COMPLETE_LINE = 'DIALOGUE_COMPLETE_LINE'; /** * Called to skip the conversation. * * This event IS cancelable! Canceling this event will prevent the conversation from skipping. */ - public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP'; + var DIALOGUE_SKIP = 'DIALOGUE_SKIP'; /** * Called when the game ends a conversation. * * This event is not cancelable. */ - public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END'; + var DIALOGUE_END = 'DIALOGUE_END'; /** * Allow for comparing `ScriptEventType` to `String`. diff --git a/source/funkin/modding/module/ModuleHandler.hx b/source/funkin/modding/module/ModuleHandler.hx index 5777c0ccc..4711e7419 100644 --- a/source/funkin/modding/module/ModuleHandler.hx +++ b/source/funkin/modding/module/ModuleHandler.hx @@ -1,7 +1,7 @@ package funkin.modding.module; import funkin.util.SortUtil; -import funkin.modding.events.ScriptEventType.UpdateScriptEvent; +import funkin.modding.events.ScriptEvent.UpdateScriptEvent; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.module.Module; @@ -55,7 +55,7 @@ class ModuleHandler static function onStateSwitchComplete():Void { - callEvent(new StateChangeScriptEvent(ScriptEventType.STATE_CHANGE_END, FlxG.state, true)); + callEvent(new StateChangeScriptEvent(STATE_CHANGE_END, FlxG.state, true)); } static function addToModuleCache(module:Module):Void @@ -119,7 +119,7 @@ class ModuleHandler { if (moduleCache != null) { - var event = new ScriptEvent(ScriptEventType.DESTROY, false); + var event = new ScriptEvent(DESTROY, false); // Note: Ignore stopPropagation() for (key => value in moduleCache) @@ -148,6 +148,6 @@ class ModuleHandler public static inline function callOnCreate():Void { - callEvent(new ScriptEvent(ScriptEventType.CREATE, false)); + callEvent(new ScriptEvent(CREATE, false)); } } diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 93b446ee9..d23574ce2 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -6,7 +6,7 @@ import flixel.FlxSprite; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.module.ModuleHandler; import funkin.modding.events.ScriptEvent; -import funkin.modding.events.ScriptEventType.CountdownScriptEvent; +import funkin.modding.events.ScriptEvent.CountdownScriptEvent; import flixel.util.FlxTimer; class Countdown @@ -43,7 +43,7 @@ class Countdown Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5); // Handle onBeatHit events manually // @:privateAccess - // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, 0, 0)); + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0)); // The timer function gets called based on the beat of the song. countdownTimer = new FlxTimer(); @@ -59,7 +59,7 @@ class Countdown // onBeatHit events are now properly dispatched by the Conductor even at negative timestamps, // so calling this is no longer necessary. - // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEventType.SONG_BEAT_HIT, 0, 0)); + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0)); // Countdown graphic. showCountdownGraphic(countdownStep, isPixelStyle); @@ -94,11 +94,11 @@ class Countdown switch (index) { case BEFORE: - event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_START, index); + event = new CountdownScriptEvent(COUNTDOWN_START, index); case THREE | TWO | ONE | GO: // I didn't know you could use `|` in a switch/case block! - event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_STEP, index); + event = new CountdownScriptEvent(COUNTDOWN_STEP, index); case AFTER: - event = new CountdownScriptEvent(ScriptEventType.COUNTDOWN_END, index, false); + event = new CountdownScriptEvent(COUNTDOWN_END, index, false); default: return true; } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 53a076fc0..4542b9f98 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -682,7 +682,7 @@ class PlayState extends MusicBeatSubState { if (!assertChartExists()) return; - dispatchEvent(new ScriptEvent(ScriptEventType.SONG_RETRY)); + dispatchEvent(new ScriptEvent(SONG_RETRY)); resetCamera(); @@ -867,7 +867,7 @@ class PlayState extends MusicBeatSubState deathCounter += 1; - dispatchEvent(new ScriptEvent(ScriptEventType.GAME_OVER)); + dispatchEvent(new ScriptEvent(GAME_OVER)); // Disable updates, preventing animations in the background from playing. persistentUpdate = false; @@ -994,7 +994,7 @@ class PlayState extends MusicBeatSubState { if (Std.isOfType(subState, PauseSubState)) { - var event:ScriptEvent = new ScriptEvent(ScriptEventType.RESUME, true); + var event:ScriptEvent = new ScriptEvent(RESUME, true); dispatchEvent(event); @@ -1097,7 +1097,7 @@ class PlayState extends MusicBeatSubState if (this.currentStage != null) { remove(currentStage); - var event:ScriptEvent = new ScriptEvent(ScriptEventType.DESTROY, false); + var event:ScriptEvent = new ScriptEvent(DESTROY, false); ScriptEventDispatcher.callEvent(currentStage, event); currentStage = null; } @@ -1116,7 +1116,7 @@ class PlayState extends MusicBeatSubState super.debug_refreshModules(); - var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); + var event:ScriptEvent = new ScriptEvent(CREATE, false); ScriptEventDispatcher.callEvent(currentSong, event); } @@ -1332,7 +1332,7 @@ class PlayState extends MusicBeatSubState if (currentStage != null) { // Actually create and position the sprites. - var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); + var event:ScriptEvent = new ScriptEvent(CREATE, false); ScriptEventDispatcher.callEvent(currentStage, event); // Apply camera zoom level from stage data. @@ -1640,7 +1640,7 @@ class PlayState extends MusicBeatSubState add(currentConversation); refresh(); - var event:ScriptEvent = new ScriptEvent(ScriptEventType.CREATE, false); + var event:ScriptEvent = new ScriptEvent(CREATE, false); ScriptEventDispatcher.callEvent(currentConversation, event); } @@ -1664,7 +1664,7 @@ class PlayState extends MusicBeatSubState */ function startSong():Void { - dispatchEvent(new ScriptEvent(ScriptEventType.SONG_START)); + dispatchEvent(new ScriptEvent(SONG_START)); startingSong = false; @@ -1783,7 +1783,7 @@ class PlayState extends MusicBeatSubState // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, note, 0, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, 0, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -1872,7 +1872,7 @@ class PlayState extends MusicBeatSubState { // Call an event to allow canceling the note miss. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_MISS, note, 0, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, 0, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2021,7 +2021,7 @@ class PlayState extends MusicBeatSubState function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_HIT, note, Highscore.tallies.combo + 1, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, Highscore.tallies.combo + 1, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2053,7 +2053,7 @@ class PlayState extends MusicBeatSubState // a MISS is when you let a note scroll past you!! Highscore.tallies.missed++; - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEventType.NOTE_MISS, note, Highscore.tallies.combo, true); + var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; @@ -2385,7 +2385,7 @@ class PlayState extends MusicBeatSubState */ function endSong():Void { - dispatchEvent(new ScriptEvent(ScriptEventType.SONG_END)); + dispatchEvent(new ScriptEvent(SONG_END)); #if sys // spitter for ravy, teehee!! @@ -2593,7 +2593,7 @@ class PlayState extends MusicBeatSubState { remove(currentStage); currentStage.kill(); - dispatchEvent(new ScriptEvent(ScriptEventType.DESTROY, false)); + dispatchEvent(new ScriptEvent(DESTROY, false)); currentStage = null; } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index e42c4e629..abe8bf992 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -254,7 +254,7 @@ class CharacterDataParser char.debug = debug; // Call onCreate only in the fetchCharacter() function, not at application initialization. - ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEventType.CREATE)); + ScriptEventDispatcher.callEvent(char, new ScriptEvent(CREATE)); return char; } diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index fa46c9252..46acf3f37 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -120,7 +120,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass this.alpha = 1.0; // Start the dialogue. - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, false)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false)); } function setupMusic():Void @@ -214,7 +214,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass return; } - ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(ScriptEventType.CREATE, true)); + ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true)); currentSpeaker = nextSpeaker; currentSpeaker.zIndex = 200; @@ -258,7 +258,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass return; } - ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(ScriptEventType.CREATE, true)); + ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true)); currentDialogueBox = nextDialogueBox; currentDialogueBox.zIndex = 300; @@ -293,7 +293,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass public function startConversation():Void { - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true)); } /** @@ -308,13 +308,13 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass switch (state) { case ConversationState.Start: - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_START, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true)); case ConversationState.Opening: - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_COMPLETE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true)); case ConversationState.Speaking: - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_COMPLETE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true)); case ConversationState.Idle: - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_LINE, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_LINE, this, true)); case ConversationState.Ending: // Skip the outro. endOutro(); @@ -371,7 +371,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass */ public function skipConversation():Void { - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_SKIP, this, true)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true)); } static var outroTween:FlxTween; @@ -405,7 +405,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass public function endOutro():Void { outroTween = null; - ScriptEventDispatcher.callEvent(this, new ScriptEvent(ScriptEventType.DESTROY, false)); + ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false)); } /** @@ -445,7 +445,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass if (currentDialogueEntry >= currentDialogueEntryCount) { - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_END, this, false)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false)); } else { @@ -485,7 +485,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass propagateEvent(event); if (event.eventCanceled) return; - dispatchEvent(new DialogueScriptEvent(ScriptEventType.DIALOGUE_END, this, false)); + dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false)); } public function onDialogueEnd(event:DialogueScriptEvent):Void diff --git a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx index 8daaf8804..70ac011a2 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx @@ -30,7 +30,7 @@ class ConversationDebugState extends MusicBeatState conversation.completeCallback = onConversationComplete; add(conversation); - ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(ScriptEventType.CREATE, false)); + ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false)); } function onConversationComplete():Void diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 60b8b9864..9562ef2ca 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -47,8 +47,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry; - final _metadata:Array; - + // key = variation id, value = metadata + final _metadata:Map; final variations:Array; final difficulties:Map; @@ -62,7 +62,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry 0) return _metadata[0]?.songName ?? DEFAULT_SONGNAME; + if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.songName ?? DEFAULT_SONGNAME; return DEFAULT_SONGNAME; } @@ -71,7 +71,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry 0) return _metadata[0]?.artist ?? DEFAULT_ARTIST; + if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.artist ?? DEFAULT_ARTIST; return DEFAULT_ARTIST; } @@ -88,7 +88,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry _data]; variations.clear(); variations.push(Constants.DEFAULT_VARIATION); @@ -100,9 +100,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry { - return _metadata; + return _metadata.values(); } /** @@ -147,10 +147,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry; - var appendToSelection:Bool; - - public function new(notes:Array, appendToSelection:Bool = false) - { - this.notes = notes; - this.appendToSelection = appendToSelection; - } - - public function execute(state:ChartEditorState):Void - { - for (note in notes) - { - state.currentSongChartNoteData.push(note); - } - - if (appendToSelection) - { - state.currentNoteSelection = state.currentNoteSelection.concat(notes); - } - else - { - state.currentNoteSelection = notes; - state.currentEventSelection = []; - } - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); - state.currentNoteSelection = []; - state.currentEventSelection = []; - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - if (notes.length == 1) - { - var dir:String = notes[0].getDirectionName(); - return 'Add $dir Note'; - } - - return 'Add ${notes.length} Notes'; - } -} - -@:nullSafety -class RemoveNotesCommand implements ChartEditorCommand -{ - var notes:Array; - - public function new(notes:Array) - { - this.notes = notes; - } - - public function execute(state:ChartEditorState):Void - { - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); - state.currentNoteSelection = []; - state.currentEventSelection = []; - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - for (note in notes) - { - state.currentSongChartNoteData.push(note); - } - state.currentNoteSelection = notes; - state.currentEventSelection = []; - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - if (notes.length == 1 && notes[0] != null) - { - var dir:String = notes[0].getDirectionName(); - return 'Remove $dir Note'; - } - - return 'Remove ${notes.length} Notes'; - } -} - -/** - * Appends one or more items to the selection. - */ -@:nullSafety -class SelectItemsCommand implements ChartEditorCommand -{ - var notes:Array; - var events:Array; - - public function new(notes:Array, events:Array) - { - this.notes = notes; - this.events = events; - } - - public function execute(state:ChartEditorState):Void - { - for (note in this.notes) - { - state.currentNoteSelection.push(note); - } - - for (event in this.events) - { - state.currentEventSelection.push(event); - } - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes); - state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events); - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - var len:Int = notes.length + events.length; - - if (notes.length == 0) - { - if (events.length == 1) - { - return 'Select Event'; - } - else - { - return 'Select ${events.length} Events'; - } - } - else if (events.length == 0) - { - if (notes.length == 1) - { - return 'Select Note'; - } - else - { - return 'Select ${notes.length} Notes'; - } - } - - return 'Select ${len} Items'; - } -} - -@:nullSafety -class AddEventsCommand implements ChartEditorCommand -{ - var events:Array; - var appendToSelection:Bool; - - public function new(events:Array, appendToSelection:Bool = false) - { - this.events = events; - this.appendToSelection = appendToSelection; - } - - public function execute(state:ChartEditorState):Void - { - for (event in events) - { - state.currentSongChartEventData.push(event); - } - - if (appendToSelection) - { - state.currentEventSelection = state.currentEventSelection.concat(events); - } - else - { - state.currentNoteSelection = []; - state.currentEventSelection = events; - } - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events); - - state.currentNoteSelection = []; - state.currentEventSelection = []; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - var len:Int = events.length; - return 'Add $len Events'; - } -} - -@:nullSafety -class RemoveEventsCommand implements ChartEditorCommand -{ - var events:Array; - - public function new(events:Array) - { - this.events = events; - } - - public function execute(state:ChartEditorState):Void - { - state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events); - state.currentEventSelection = []; - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - for (event in events) - { - state.currentSongChartEventData.push(event); - } - state.currentEventSelection = events; - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - if (events.length == 1 && events[0] != null) - { - return 'Remove Event'; - } - - return 'Remove ${events.length} Events'; - } -} - -@:nullSafety -class RemoveItemsCommand implements ChartEditorCommand -{ - var notes:Array; - var events:Array; - - public function new(notes:Array, events:Array) - { - this.notes = notes; - this.events = events; - } - - public function execute(state:ChartEditorState):Void - { - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); - state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events); - - state.currentNoteSelection = []; - state.currentEventSelection = []; - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - for (note in notes) - { - state.currentSongChartNoteData.push(note); - } - - for (event in events) - { - state.currentSongChartEventData.push(event); - } - - state.currentNoteSelection = notes; - state.currentEventSelection = events; - - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - return 'Remove ${notes.length + events.length} Items'; - } -} - -@:nullSafety -class SwitchDifficultyCommand implements ChartEditorCommand -{ - var prevDifficulty:String; - var newDifficulty:String; - var prevVariation:String; - var newVariation:String; - - public function new(prevDifficulty:String, newDifficulty:String, prevVariation:String, newVariation:String) - { - this.prevDifficulty = prevDifficulty; - this.newDifficulty = newDifficulty; - this.prevVariation = prevVariation; - this.newVariation = newVariation; - } - - public function execute(state:ChartEditorState):Void - { - state.selectedVariation = newVariation != null ? newVariation : prevVariation; - state.selectedDifficulty = newDifficulty != null ? newDifficulty : prevDifficulty; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.selectedVariation = prevVariation != null ? prevVariation : newVariation; - state.selectedDifficulty = prevDifficulty != null ? prevDifficulty : newDifficulty; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - return 'Switch Difficulty'; - } -} - -@:nullSafety -class DeselectItemsCommand implements ChartEditorCommand -{ - var notes:Array; - var events:Array; - - public function new(notes:Array, events:Array) - { - this.notes = notes; - this.events = events; - } - - public function execute(state:ChartEditorState):Void - { - state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes); - state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events); - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - for (note in this.notes) - { - state.currentNoteSelection.push(note); - } - - for (event in this.events) - { - state.currentEventSelection.push(event); - } - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - var noteCount = notes.length + events.length; - - if (noteCount == 1) - { - var dir:String = notes[0].getDirectionName(); - return 'Deselect $dir Items'; - } - - return 'Deselect ${noteCount} Items'; - } -} - -/** - * Sets the selection rather than appends it. - * Deselects any notes that are not in the new selection. - */ -@:nullSafety -class SetItemSelectionCommand implements ChartEditorCommand -{ - var notes:Array; - var events:Array; - var previousNoteSelection:Array; - var previousEventSelection:Array; - - public function new(notes:Array, events:Array, previousNoteSelection:Array, - previousEventSelection:Array) - { - this.notes = notes; - this.events = events; - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } - - public function execute(state:ChartEditorState):Void - { - state.currentNoteSelection = notes; - state.currentEventSelection = events; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.currentNoteSelection = previousNoteSelection; - state.currentEventSelection = previousEventSelection; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - return 'Select ${notes.length} Items'; - } -} - -@:nullSafety -class SelectAllItemsCommand implements ChartEditorCommand -{ - var previousNoteSelection:Array; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } - - public function execute(state:ChartEditorState):Void - { - state.currentNoteSelection = state.currentSongChartNoteData; - state.currentEventSelection = state.currentSongChartEventData; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.currentNoteSelection = previousNoteSelection; - state.currentEventSelection = previousEventSelection; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - return 'Select All Items'; - } -} - -@:nullSafety -class InvertSelectedItemsCommand implements ChartEditorCommand -{ - var previousNoteSelection:Array; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } - - public function execute(state:ChartEditorState):Void - { - state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection); - state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection); - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.currentNoteSelection = previousNoteSelection; - state.currentEventSelection = previousEventSelection; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - return 'Invert Selected Items'; - } -} - -@:nullSafety -class DeselectAllItemsCommand implements ChartEditorCommand -{ - var previousNoteSelection:Array; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection; - this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection; - } - - public function execute(state:ChartEditorState):Void - { - state.currentNoteSelection = []; - state.currentEventSelection = []; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function undo(state:ChartEditorState):Void - { - state.currentNoteSelection = previousNoteSelection; - state.currentEventSelection = previousEventSelection; - - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - } - - public function toString():String - { - return 'Deselect All Items'; - } -} - -@:nullSafety -class CutItemsCommand implements ChartEditorCommand -{ - var notes:Array; - var events:Array; - - public function new(notes:Array, events:Array) - { - this.notes = notes; - this.events = events; - } - - public function execute(state:ChartEditorState):Void - { - // Copy the notes. - SongDataUtils.writeItemsToClipboard( - { - notes: SongDataUtils.buildNoteClipboard(notes), - events: SongDataUtils.buildEventClipboard(events) - }); - - // Delete the notes. - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); - state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events); - state.currentNoteSelection = []; - state.currentEventSelection = []; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes); - state.currentSongChartEventData = state.currentSongChartEventData.concat(events); - - state.currentNoteSelection = notes; - state.currentEventSelection = events; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - state.sortChartData(); - } - - public function toString():String - { - var len:Int = notes.length + events.length; - - if (notes.length == 0) return 'Cut $len Events to Clipboard'; - else if (events.length == 0) return 'Cut $len Notes to Clipboard'; - else - return 'Cut $len Items to Clipboard'; - } -} - -@:nullSafety -class FlipNotesCommand implements ChartEditorCommand -{ - var notes:Array = []; - var flippedNotes:Array = []; - - public function new(notes:Array) - { - this.notes = notes; - this.flippedNotes = SongDataUtils.flipNotes(notes); - } - - public function execute(state:ChartEditorState):Void - { - // Delete the notes. - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); - - // Add the flipped notes. - state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes); - - state.currentNoteSelection = flippedNotes; - state.currentEventSelection = []; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes); - state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes); - - state.currentNoteSelection = notes; - state.currentEventSelection = []; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - var len:Int = notes.length; - return 'Flip $len Notes'; - } -} - -@:nullSafety -class PasteItemsCommand implements ChartEditorCommand -{ - var targetTimestamp:Float; - // Notes we added with this command, for undo. - var addedNotes:Array = []; - var addedEvents:Array = []; - - public function new(targetTimestamp:Float) - { - this.targetTimestamp = targetTimestamp; - } - - public function execute(state:ChartEditorState):Void - { - var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard(); - - if (currentClipboard.valid != true) - { - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Failed to Paste', - body: 'Could not parse clipboard contents.', - type: NotificationType.Error, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end - return; - } - - trace(currentClipboard.notes); - - addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp)); - addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp)); - - state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes); - state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents); - state.currentNoteSelection = addedNotes.copy(); - state.currentEventSelection = addedEvents.copy(); - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Paste Successful', - body: 'Successfully pasted clipboard contents.', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end - } - - public function undo(state:ChartEditorState):Void - { - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes); - state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents); - state.currentNoteSelection = []; - state.currentEventSelection = []; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard(); - - var len:Int = currentClipboard.notes.length + currentClipboard.events.length; - - if (currentClipboard.notes.length == 0) return 'Paste $len Events'; - else if (currentClipboard.events.length == 0) return 'Paste $len Notes'; - else - return 'Paste $len Items'; - } -} - -@:nullSafety -class ExtendNoteLengthCommand implements ChartEditorCommand -{ - var note:SongNoteData; - var oldLength:Float; - var newLength:Float; - - public function new(note:SongNoteData, newLength:Float) - { - this.note = note; - this.oldLength = note.length; - this.newLength = newLength; - } - - public function execute(state:ChartEditorState):Void - { - note.length = newLength; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function undo(state:ChartEditorState):Void - { - ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo')); - - note.length = oldLength; - - state.saveDataDirty = true; - state.noteDisplayDirty = true; - state.notePreviewDirty = true; - - state.sortChartData(); - } - - public function toString():String - { - return 'Extend Note Length'; - } -} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 5585f9fdf..8c18271d9 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,22 +1,14 @@ package funkin.ui.debug.charting; -import funkin.play.stage.StageData; -import funkin.play.character.CharacterData.CharacterDataParser; -import funkin.play.character.CharacterData; -import flixel.system.FlxAssets.FlxSoundAsset; -import flixel.math.FlxMath; -import haxe.ui.components.TextField; -import haxe.ui.components.DropDown; -import haxe.ui.components.NumberStepper; -import haxe.ui.containers.Frame; import flixel.addons.display.FlxSliceSprite; import flixel.addons.display.FlxTiledSprite; +import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxSprite; import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; -import flixel.addons.transition.FlxTransitionableState; import flixel.input.keyboard.FlxKey; +import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.sound.FlxSound; @@ -29,41 +21,59 @@ import flixel.util.FlxTimer; import funkin.audio.visualize.PolygonSpectogram; import funkin.audio.VoicesGroup; import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.notestyle.NoteStyleRegistry; +import funkin.data.song.SongData.SongChartData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongDataUtils; import funkin.input.Cursor; import funkin.input.TurboKeyHandler; import funkin.modding.events.ScriptEvent; import funkin.play.character.BaseCharacter.CharacterType; +import funkin.play.character.CharacterData; import funkin.play.HealthIcon; import funkin.play.notes.NoteSprite; -import funkin.play.notes.Strumline; import funkin.play.PlayState; import funkin.play.song.Song; -import funkin.data.song.SongData.SongChartData; -import funkin.data.song.SongRegistry; -import funkin.data.song.SongData.SongEventData; -import funkin.data.song.SongData.SongMetadata; -import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongCharacterData; -import funkin.data.song.SongDataUtils; -import funkin.ui.debug.charting.ChartEditorCommand; -import funkin.ui.debug.charting.ChartEditorCommand; -import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme; -import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode; +import funkin.play.stage.StageData; +import funkin.ui.debug.charting.commands.AddEventsCommand; +import funkin.ui.debug.charting.commands.AddNotesCommand; +import funkin.ui.debug.charting.commands.ChartEditorCommand; +import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.DeselectAllItemsCommand; +import funkin.ui.debug.charting.commands.DeselectItemsCommand; +import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; +import funkin.ui.debug.charting.commands.FlipNotesCommand; +import funkin.ui.debug.charting.commands.InvertSelectedItemsCommand; +import funkin.ui.debug.charting.commands.MoveEventsCommand; +import funkin.ui.debug.charting.commands.MoveItemsCommand; +import funkin.ui.debug.charting.commands.MoveNotesCommand; +import funkin.ui.debug.charting.commands.PasteItemsCommand; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; +import funkin.ui.debug.charting.commands.RemoveItemsCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; +import funkin.ui.debug.charting.commands.SelectAllItemsCommand; +import funkin.ui.debug.charting.commands.SelectItemsCommand; +import funkin.ui.debug.charting.commands.SetItemSelectionCommand; +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.ChartEditorSelectionSquareSprite; import funkin.ui.haxeui.components.CharacterPlayer; import funkin.ui.haxeui.HaxeUIState; import funkin.util.Constants; -import funkin.util.DateUtil; -import funkin.util.FileUtil; -import funkin.util.SerializerUtil; import funkin.util.SortUtil; import funkin.util.WindowUtil; import haxe.DynamicAccess; import haxe.io.Bytes; -import haxe.io.Path; +import haxe.ui.components.DropDown; import haxe.ui.components.Label; +import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; +import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; +import haxe.ui.containers.Frame; import haxe.ui.containers.menus.MenuItem; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; @@ -73,9 +83,7 @@ import haxe.ui.events.DragEvent; import haxe.ui.events.UIEvent; import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationType; -import openfl.Assets; import openfl.display.BitmapData; -import openfl.geom.Rectangle; using Lambda; @@ -88,14 +96,6 @@ using Lambda; * @author MasterEric */ @:nullSafety -// Give other classes access to private instance fields -@:allow(funkin.ui.debug.charting.ChartEditorCommand) -@:allow(funkin.ui.debug.charting.ChartEditorDropdowns) -@:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) -@:allow(funkin.ui.debug.charting.ChartEditorThemeHandler) -@:allow(funkin.ui.debug.charting.ChartEditorAudioHandler) -@:allow(funkin.ui.debug.charting.ChartEditorImportExportHandler) -@:allow(funkin.ui.debug.charting.ChartEditorToolboxHandler) class ChartEditorState extends HaxeUIState { /** @@ -103,129 +103,223 @@ class ChartEditorState extends HaxeUIState */ // ============================== // XML Layouts - static final CHART_EDITOR_LAYOUT:String = Paths.ui('chart-editor/main-view'); + public static final CHART_EDITOR_LAYOUT:String = Paths.ui('chart-editor/main-view'); - static final CHART_EDITOR_NOTIFBAR_LAYOUT:String = Paths.ui('chart-editor/components/notifbar'); - static final CHART_EDITOR_PLAYBARHEAD_LAYOUT:String = Paths.ui('chart-editor/components/playbar-head'); + public static final CHART_EDITOR_NOTIFBAR_LAYOUT:String = Paths.ui('chart-editor/components/notifbar'); + public static final CHART_EDITOR_PLAYBARHEAD_LAYOUT:String = Paths.ui('chart-editor/components/playbar-head'); - static final CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:String = Paths.ui('chart-editor/toolbox/tools'); - static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); - static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); - static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); - static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); - static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); - static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); + 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_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); + public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); + public static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); + public static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); // Validation - static final SUPPORTED_MUSIC_FORMATS:Array = ['ogg']; + public static final SUPPORTED_MUSIC_FORMATS:Array = ['ogg']; + + // Layout /** * The base grid size for the chart editor. */ public static final GRID_SIZE:Int = 40; + /** + * The width of the scroll area. + */ public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = 12; + /** + * The height of the playhead, in pixels. + */ public static final PLAYHEAD_HEIGHT:Int = Std.int(GRID_SIZE / 8); + /** + * The width of the border between grid squares, where the crosshair changes from "Place Notes" to "Select Notes". + */ public static final GRID_SELECTION_BORDER_WIDTH:Int = 6; + /** + * The height of the menu bar in the layout. + */ + public static final MENU_BAR_HEIGHT:Int = 32; + + /** + * The height of the playbar in the layout. + */ + public static final PLAYBAR_HEIGHT:Int = 48; + + /** + * The amount of padding between the menu bar and the chart grid when fully scrolled up. + */ + public static final GRID_TOP_PAD:Int = 8; + + // Colors + // Background color tint. + public static final CURSOR_COLOR:FlxColor = 0xE0FFFFFF; + public static final PREVIEW_BG_COLOR:FlxColor = 0xFF303030; + public static final PLAYHEAD_SCROLL_AREA_COLOR:FlxColor = 0xFF682B2F; + public static final SPECTROGRAM_COLOR:FlxColor = 0xFFFF0000; + public static final PLAYHEAD_COLOR:FlxColor = 0xC0BD0231; + + // Timings + + /** + * Duration, in seconds, for the scroll easing animation. + */ + public static final SCROLL_EASE_DURATION:Float = 0.2; + + // Other + /** * Number of notes in each player's strumline. */ public static final STRUMLINE_SIZE:Int = 4; - /** - * The height of the menu bar in the layout. - */ - static final MENU_BAR_HEIGHT:Int = 32; - - /** - * The height of the playbar in the layout. - */ - static final PLAYBAR_HEIGHT:Int = 48; - - /** - * Duration to wait before autosaving the chart. - */ - static final AUTOSAVE_TIMER_DELAY:Float = 60.0 * 5.0; - - /** - * The amount of padding between the menu bar and the chart grid when fully scrolled up. - */ - static final GRID_TOP_PAD:Int = 8; - - /** - * Duration, in milliseconds, until toast notifications are automatically hidden. - */ - static final NOTIFICATION_DISMISS_TIME:Int = 5000; - - /** - * Duration, in seconds, for the scroll easing animation. - */ - static final SCROLL_EASE_DURATION:Float = 0.2; - - // UI Element Colors - // Background color tint. - static final CURSOR_COLOR:FlxColor = 0xE0FFFFFF; - static final PREVIEW_BG_COLOR:FlxColor = 0xFF303030; - static final PLAYHEAD_SCROLL_AREA_COLOR:FlxColor = 0xFF682B2F; - static final SPECTROGRAM_COLOR:FlxColor = 0xFFFF0000; - static final PLAYHEAD_COLOR:FlxColor = 0xC0BD0231; - /** * How many pixels far the user needs to move the mouse before the cursor is considered to be dragged rather than clicked. */ - static final DRAG_THRESHOLD:Float = 16.0; + public static final DRAG_THRESHOLD:Float = 16.0; /** - * Types of notes you can snap to. + * Precisions of notes you can snap to. */ - static final SNAP_QUANTS:Array = [4, 8, 12, 16, 20, 24, 32, 48, 64, 96, 192]; + public static final SNAP_QUANTS:Array = [4, 8, 12, 16, 20, 24, 32, 48, 64, 96, 192]; - static final BASE_QUANT:Int = 16; + /** + * The default note snapping value. + */ + public static final BASE_QUANT:Int = 16; + + /** + * The index of thet default note snapping value in the `SNAP_QUANTS` array. + */ + public static final BASE_QUANT_INDEX:Int = 3; /** * INSTANCE DATA */ // ============================== + // Song Length /** - * The internal index of what note snapping value is in use. - * Increment to make placement more preceise and decrement to make placement less precise. + * The length of the current instrumental, in milliseconds. */ - var noteSnapQuantIndex:Int = 3; // default is 16 + @:isVar var songLengthInMs(get, set):Float = 0; - /** - * The current note snapping value. - * For example, `32` when snapping to 32nd notes. - */ - public var noteSnapQuant(get, never):Int; - - function get_noteSnapQuant():Int + function get_songLengthInMs():Float { - return SNAP_QUANTS[noteSnapQuantIndex]; + if (songLengthInMs <= 0) return 1000; + return songLengthInMs; + } + + function set_songLengthInMs(value:Float):Float + { + this.songLengthInMs = value; + + // Make sure playhead doesn't go outside the song. + if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; + + return this.songLengthInMs; } /** - * The ratio of the current note snapping value to the default. - * For example, `32` becomes `0.5` when snapping to 16th notes. + * The length of the current instrumental, converted to steps. + * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. */ - public var noteSnapRatio(get, never):Float; + var songLengthInSteps(get, set):Float; - function get_noteSnapRatio():Float + function get_songLengthInSteps():Float { - return BASE_QUANT / noteSnapQuant; + return Conductor.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); + return value; } /** - * scrollPosition is the current position in the song, in pixels. + * The length of the current instrumental, in PIXELS. + * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. + */ + var songLengthInPixels(get, set):Int; + + function get_songLengthInPixels():Int + { + return Std.int(songLengthInSteps * GRID_SIZE); + } + + function set_songLengthInPixels(value:Int):Int + { + songLengthInSteps = value / GRID_SIZE; + return value; + } + + // Scroll Position + + /** + * The relative scroll position in the song, in pixels. * One pixel is 1/40 of 1 step, and 1/160 of 1 beat. */ var scrollPositionInPixels(default, set):Float = -1.0; + function set_scrollPositionInPixels(value:Float):Float + { + if (value < 0) + { + // If we're scrolling up, and we hit the top, + // but the playhead is in the middle, move the playhead up. + if (playheadPositionInPixels > 0) + { + var amount:Float = scrollPositionInPixels - value; + playheadPositionInPixels -= amount; + } + + value = 0; + } + + if (value > songLengthInPixels) value = songLengthInPixels; + + if (value == scrollPositionInPixels) return value; + + // Difference in pixels. + var diff:Float = value - scrollPositionInPixels; + + this.scrollPositionInPixels = value; + + // Move the grid sprite to the correct position. + if (gridTiledSprite != null && gridPlayheadScrollArea != null) + { + if (isViewDownscroll) + { + gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayheadScrollArea.y = gridTiledSprite.y; + } + else + { + gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayheadScrollArea.y = gridTiledSprite.y; + } + } + + // Move the rendered notes to the correct position. + renderedNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedHoldNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedEvents.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedSelectionSquares.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + // Offset the selection box start position, if we are dragging. + if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff; + // Update the note preview viewport box. + setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); + return this.scrollPositionInPixels; + } + /** - * scrollPosition, converted to steps. + * The relative scroll position in the song, converted to steps. * NOT dependant on BPM, because the size of a grid square does not change with BPM. */ var scrollPositionInSteps(get, set):Float; @@ -242,7 +336,7 @@ class ChartEditorState extends HaxeUIState } /** - * scrollPosition, converted to milliseconds. + * The relative scroll position in the song, converted to milliseconds. * DEPENDANT on BPM, because the duration of a grid square changes with BPM. */ var scrollPositionInMs(get, set):Float; @@ -258,11 +352,13 @@ class ChartEditorState extends HaxeUIState return value; } + // Playhead (on the grid) + /** - * The position of the playhead, in pixels, relative to the scrollPosition. - * 0 means playhead is at the top of the grid. - * 40 means the playhead is 1 grid length below the base position. - * -40 means the playhead is 1 grid length above the base position. + * The position of the playhead, in pixels, relative to the `scrollPositionInPixels`. + * `0` means playhead is at the top of the grid. + * `40` means the playhead is 1 grid length below the base position. + * `-40` means the playhead is 1 grid length above the base position. */ var playheadPositionInPixels(default, set):Float = 0.0; @@ -314,77 +410,7 @@ class ChartEditorState extends HaxeUIState return value; } - /** - * songLength, in milliseconds. - */ - @:isVar var songLengthInMs(get, set):Float = 0; - - function get_songLengthInMs():Float - { - if (songLengthInMs <= 0) return 1000; - return songLengthInMs; - } - - function set_songLengthInMs(value:Float):Float - { - this.songLengthInMs = value; - - // Make sure playhead doesn't go outside the song. - if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; - - return this.songLengthInMs; - } - - /** - * songLength, converted to steps. - * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. - */ - var songLengthInSteps(get, set):Float; - - function get_songLengthInSteps():Float - { - return Conductor.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); - return value; - } - - /** - * This is the song's length in PIXELS, same format as scrollPosition. - * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. - */ - var songLengthInPixels(get, set):Int; - - function get_songLengthInPixels():Int - { - return Std.int(songLengthInSteps * GRID_SIZE); - } - - function set_songLengthInPixels(value:Int):Int - { - songLengthInSteps = value / GRID_SIZE; - return value; - } - - /** - * The current theme used by the editor. - * Dictates the appearance of many UI elements. - * Currently hardcoded to just Light and Dark. - */ - var currentTheme(default, set):ChartEditorTheme = ChartEditorTheme.Light; - - function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme - { - if (value == null || value == currentTheme) return currentTheme; - - currentTheme = value; - ChartEditorThemeHandler.updateTheme(this); - return value; - } + // Playbar (at the bottom) /** * Whether a skip button has been pressed on the playbar, and which one. @@ -404,47 +430,62 @@ class ChartEditorState extends HaxeUIState */ var playbarHeadDraggingWasPlaying:Bool = false; + // Tools Status + /** * The note kind to use for notes being placed in the chart. Defaults to `''`. */ var selectedNoteKind:String = ''; /** - * The note kind to use for notes being placed in the chart. Defaults to `''`. + * The event type to use for events being placed in the chart. Defaults to `''`. */ var selectedEventKind:String = 'FocusCamera'; /** - * The note data as a struct. + * The event data to use for events being placed in the chart. */ var selectedEventData:DynamicAccess = {}; /** - * Whether to play a metronome sound while the playhead is moving. + * The internal index of what note snapping value is in use. + * Increment to make placement more preceise and decrement to make placement less precise. */ - var isMetronomeEnabled:Bool = true; + var noteSnapQuantIndex:Int = BASE_QUANT_INDEX; /** - * Use the tool window to affect how the user interacts with the program. + * The current note snapping value. + * For example, `32` when snapping to 32nd notes. */ - var currentToolMode:ChartEditorToolMode = ChartEditorToolMode.Select; + var noteSnapQuant(get, never):Int; + + function get_noteSnapQuant():Int + { + return SNAP_QUANTS[noteSnapQuantIndex]; + } /** - * The character sprite in the Player Preview window. - * `null` until accessed. + * The ratio of the current note snapping value to the default. + * For example, `32` becomes `0.5` when snapping to 16th notes. */ - var currentPlayerCharacterPlayer:Null = null; + var noteSnapRatio(get, never):Float; - /** - * The character sprite in the Opponent Preview window. - * `null` until accessed. - */ - var currentOpponentCharacterPlayer:Null = null; + function get_noteSnapRatio():Float + { + return BASE_QUANT / noteSnapQuant; + } /** * The currently selected live input style. */ - var currentLiveInputStyle:LiveInputStyle = LiveInputStyle.None; + var currentLiveInputStyle:ChartEditorLiveInputStyle = None; + + /** + * If true, playtesting a chart will skip to the current playhead position. + */ + var playtestStartTime:Bool = false; + + // Visuals /** * Whether the current view is in downscroll mode. @@ -467,33 +508,38 @@ class ChartEditorState extends HaxeUIState } /** - * If true, playtesting a chart will skip to the current playhead position. + * The current theme used by the editor. + * Dictates the appearance of many UI elements. + * Currently hardcoded to just Light and Dark. */ - var playtestStartTime:Bool = false; + var currentTheme(default, set):ChartEditorTheme = ChartEditorTheme.Light; - /** - * Whether hitsounds are enabled for at least one character. - */ - var hitsoundsEnabled(get, never):Bool; - - function get_hitsoundsEnabled():Bool + function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme { - return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; + if (value == null || value == currentTheme) return currentTheme; + + currentTheme = value; + this.updateTheme(); + return value; } /** - * Whether hitsounds are enabled for the player. + * The character sprite in the Player Preview window. + * `null` until accessed. */ - var hitsoundsEnabledPlayer:Bool = true; + var currentPlayerCharacterPlayer:Null = null; /** - * Whether hitsounds are enabled for the opponent. + * The character sprite in the Opponent Preview window. + * `null` until accessed. */ - var hitsoundsEnabledOpponent:Bool = true; + var currentOpponentCharacterPlayer:Null = null; + + // HaxeUI /** * Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI. - * If so, ignore mouse events underneath. + * If so, ignore mouse events underneath as well as certain key events. */ var isCursorOverHaxeUI(get, never):Bool; @@ -513,107 +559,143 @@ class ChartEditorState extends HaxeUIState /** * Set by ChartEditorDialogHandler, used to prevent background interaction while the dialog is open. */ - public var isHaxeUIDialogOpen:Bool = false; + var isHaxeUIDialogOpen:Bool = false; /** - * The variation ID for the difficulty which is currently being edited. + * The Dialog components representing the currently available tool windows. + * Dialogs are retained here even when collapsed or hidden. */ - var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION; + var activeToolboxes:Map = new Map(); + + // Audio /** - * Setter called when we are switching variations. - * We will likely need to switch instrumentals as well. + * Whether to play a metronome sound while the playhead is moving. */ - function set_selectedVariation(value:String):String - { - // Don't update if we're already on the variation. - if (selectedVariation == value) return selectedVariation; - selectedVariation = value; - - // Make sure view is updated when the variation changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - - switchToCurrentInstrumental(); - - return selectedVariation; - } + var isMetronomeEnabled:Bool = true; /** - * The difficulty ID for the difficulty which is currently being edited. + * Whether hitsounds are enabled for the player. */ - var selectedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; - - function set_selectedDifficulty(value:String):String - { - selectedDifficulty = value; - - // Make sure view is updated when the difficulty changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - - // Make sure the difficulty we selected is in the list of difficulties. - currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty); - - return selectedDifficulty; - } + var hitsoundsEnabledPlayer:Bool = true; /** - * The instrumental ID which is currently selected. + * Whether hitsounds are enabled for the opponent. */ - var currentInstrumentalId(get, set):String; - - function get_currentInstrumentalId():String - { - var instId:Null = currentSongMetadata.playData.characters.instrumental; - if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation; - return instId; - } - - function set_currentInstrumentalId(value:String):String - { - return currentSongMetadata.playData.characters.instrumental = value; - } + var hitsoundsEnabledOpponent:Bool = true; /** - * The character ID for the character which is currently selected. + * Whether hitsounds are enabled for at least one character. */ - var selectedCharacter(default, set):String = Constants.DEFAULT_CHARACTER; + var hitsoundsEnabled(get, never):Bool; - function set_selectedCharacter(value:String):String + function get_hitsoundsEnabled():Bool { - selectedCharacter = value; - - // Make sure view is updated when the character changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - - return selectedCharacter; + return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; } + // Auto-save + /** - * Whether the user is currently in Pattern Mode. - * This overrides the chart editor's normal behavior. + * A timer used to auto-save the chart after a period of inactivity. */ - var isInPatternMode(default, set):Bool = false; + var autoSaveTimer:Null = null; - function set_isInPatternMode(value:Bool):Bool - { - isInPatternMode = value; + // Scrolling - // Make sure view is updated when we change modes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - this.scrollPositionInPixels = 0; + /** + * Whether the user's last mouse click was on the playhead scroll area. + */ + var gridPlayheadScrollAreaPressed:Bool = false; - return isInPatternMode; - } + /** + * Where the user's last mouse click was on the note preview scroll area. + * `null` if the user isn't clicking on the note preview. + */ + var notePreviewScrollAreaStartPos:Null = null; - var currentPattern:String = ''; + /** + * The current process that is lerping the scroll position. + * Used to cancel the previous lerp if the user scrolls again. + */ + var currentScrollEase:Null; + + // Note Placement + + /** + * The SongNoteData which is currently being placed. + * `null` if the user isn't currently placing a note. + * As the user drags, we will update this note's sustain length, and finalize the note when they release. + */ + var currentPlaceNoteData:Null = null; + + // Note Movement + + /** + * The note sprite we are currently moving, if any. + */ + var dragTargetNote:Null = null; + + /** + * The song event sprite we are currently moving, if any. + */ + var dragTargetEvent:Null = null; + + /** + * The amount of vertical steps the note sprite has moved by since the user started dragging. + */ + var dragTargetCurrentStep:Float = 0; + + /** + * The amount of horizontal columns the note sprite has moved by since the user started dragging. + */ + var dragTargetCurrentColumn:Int = 0; + + // Hold Note Dragging + + /** + * The current length of the hold note we are dragging, in steps. + * Play a sound when this value changes. + */ + var dragLengthCurrent:Float = 0; + + /** + * Flip-flop to alternate between two stretching sounds. + */ + var stretchySounds:Bool = false; + + // Selection + + /** + * The notes which are currently in the user's selection. + */ + var currentNoteSelection:Array = []; + + /** + * The events which are currently in the user's selection. + */ + var currentEventSelection:Array = []; + + /** + * The position where the user clicked to start a selection. + * `null` if the user isn't currently selecting anything. + * The selection box extends from this point to the current mouse position. + */ + var selectionBoxStartPos:Null = null; + + // History + + /** + * The list of command previously performed. Used for undoing previous actions. + */ + var undoHistory:Array = []; + + /** + * The list of commands that have been undone. Used for redoing previous actions. + */ + var redoHistory:Array = []; + + // Dirty Flags /** * Whether the note display render group has been modified and needs to be updated. @@ -652,7 +734,7 @@ class ChartEditorState extends HaxeUIState if (value) { // Start the auto-save timer. - autoSaveTimer = new FlxTimer().start(AUTOSAVE_TIMER_DELAY, (_) -> autoSave()); + autoSaveTimer = new FlxTimer().start(Constants.AUTOSAVE_TIMER_DELAY_SEC, (_) -> autoSave()); } else { @@ -668,11 +750,6 @@ class ChartEditorState extends HaxeUIState return saveDataDirty = value; } - /** - * A timer used to auto-save the chart after a period of inactivity. - */ - var autoSaveTimer:Null = null; - /** * Whether the difficulty tree view in the toolbox has been modified and needs to be updated. * This happens when we add/remove difficulties. @@ -698,14 +775,11 @@ class ChartEditorState extends HaxeUIState var opponentPreviewDirty:Bool = true; /** - * The list of command previously performed. Used for undoing previous actions. + * Whether the undo/redo histories have changed since the last time the UI was updated. */ - var undoHistory:Array = []; + var commandHistoryDirty:Bool = true; - /** - * The list of commands that have been undone. Used for redoing previous actions. - */ - var redoHistory:Array = []; + // Input /** * Handler used to track how long the user has been holding the undo keybind. @@ -747,52 +821,6 @@ class ChartEditorState extends HaxeUIState */ var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN); - /** - * Whether the undo/redo histories have changed since the last time the UI was updated. - */ - var commandHistoryDirty:Bool = true; - - /** - * The notes which are currently in the user's selection. - */ - var currentNoteSelection:Array = []; - - /** - * The events which are currently in the user's selection. - */ - var currentEventSelection:Array = []; - - /** - * The position where the user clicked to start a selection. - * `null` if the user isn't currently selecting anything. - * The selection box extends from this point to the current mouse position. - */ - var selectionBoxStartPos:Null = null; - - /** - * Whether the user's last mouse click was on the playhead scroll area. - */ - var gridPlayheadScrollAreaPressed:Bool = false; - - /** - * Where the user's last mouse click was on the note preview scroll area. - * `null` if the user isn't clicking on the note preview. - */ - var notePreviewScrollAreaStartPos:Null = null; - - /** - * The SongNoteData which is currently being placed. - * `null` if the user isn't currently placing a note. - * As the user drags, we will update this note's sustain length. - */ - var currentPlaceNoteData:Null = null; - - /** - * The Dialog components representing the currently available tool windows. - * Dialogs are retained here even when collapsed or hidden. - */ - var activeToolboxes:Map = new Map(); - /** * AUDIO AND SOUND DATA */ @@ -801,7 +829,7 @@ class ChartEditorState extends HaxeUIState /** * The chill audio track that plays when you open the Chart Editor. */ - public var welcomeMusic:FlxSound = new FlxSound(); + var welcomeMusic:FlxSound = new FlxSound(); /** * The audio track for the instrumental. @@ -1005,7 +1033,7 @@ class ChartEditorState extends HaxeUIState return currentSongChartData.events = value; } - public var currentSongNoteStyle(get, set):String; + var currentSongNoteStyle(get, set):String; function get_currentSongNoteStyle():String { @@ -1081,10 +1109,67 @@ class ChartEditorState extends HaxeUIState } /** - * SIGNALS + * The variation ID for the difficulty which is currently being edited. */ - // ============================== - // public var onDifficultyChange(default, never):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION; + + /** + * Setter called when we are switching variations. + * We will likely need to switch instrumentals as well. + */ + function set_selectedVariation(value:String):String + { + // Don't update if we're already on the variation. + if (selectedVariation == value) return selectedVariation; + selectedVariation = value; + + // Make sure view is updated when the variation changes. + noteDisplayDirty = true; + notePreviewDirty = true; + notePreviewViewportBoundsDirty = true; + + switchToCurrentInstrumental(); + + return selectedVariation; + } + + /** + * The difficulty ID for the difficulty which is currently being edited. + */ + var selectedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + + function set_selectedDifficulty(value:String):String + { + selectedDifficulty = value; + + // Make sure view is updated when the difficulty changes. + noteDisplayDirty = true; + notePreviewDirty = true; + notePreviewViewportBoundsDirty = true; + + // Make sure the difficulty we selected is in the list of difficulties. + currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty); + + return selectedDifficulty; + } + + /** + * The instrumental ID which is currently selected. + */ + var currentInstrumentalId(get, set):String; + + function get_currentInstrumentalId():String + { + var instId:Null = currentSongMetadata.playData.characters.instrumental; + if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation; + return instId; + } + + function set_currentInstrumentalId(value:String):String + { + return currentSongMetadata.playData.characters.instrumental = value; + } + /** * RENDER OBJECTS */ @@ -1121,6 +1206,9 @@ class ChartEditorState extends HaxeUIState */ var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup(); + /** + * The sprite for the scroll area under + */ var gridPlayheadScrollArea:Null = null; /** @@ -1201,12 +1289,6 @@ class ChartEditorState extends HaxeUIState */ var playbarNoteSnap:Null