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<ScriptEvent>;
-
 /**
  * This is a base class for all events that are issued to scripted classes.
  * It can be used to identify the type of event called, store data, and cancel event propagation.
  */
 class ScriptEvent
 {
-  /**
-   * Called when the relevant object is created.
-   * Keep in mind that the constructor may be called before the object is needed,
-   * for the purposes of caching data or otherwise.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final CREATE:ScriptEventType = 'CREATE';
-
-  /**
-   * Called when the relevant object is destroyed.
-   * This should perform relevant cleanup to ensure good performance.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final DESTROY:ScriptEventType = 'DESTROY';
-
-  /**
-   * Called when the relevent object is added to the game state.
-   * This assumes all data is loaded and ready to go.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final ADDED:ScriptEventType = 'ADDED';
-
-  /**
-   * Called during the update function.
-   * This is called every frame, so be careful!
-   *
-   * This event is not cancelable.
-   */
-  public static inline final UPDATE:ScriptEventType = 'UPDATE';
-
-  /**
-   * Called when the player moves to pause the game.
-   *
-   * This event IS cancelable! Canceling the event will prevent the game from pausing.
-   */
-  public static inline final PAUSE:ScriptEventType = 'PAUSE';
-
-  /**
-   * Called when the player moves to unpause the game while paused.
-   *
-   * This event IS cancelable! Canceling the event will prevent the game from resuming.
-   */
-  public static inline final RESUME:ScriptEventType = 'RESUME';
-
-  /**
-   * Called once per step in the song. This happens 4 times per measure.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT';
-
-  /**
-   * Called once per step in the song. This happens 16 times per measure.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT';
-
-  /**
-   * Called when a character hits a note.
-   * Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
-   *
-   * This event IS cancelable! Canceling this event prevents the note from being hit,
-   *   and will likely result in a miss later.
-   */
-  public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT';
-
-  /**
-   * Called when a character misses a note.
-   * Important information such as note data, player/opponent, etc. are all provided.
-   *
-   * This event IS cancelable! Canceling this event prevents the note from being considered missed,
-   *   avoiding a combo break and lost health.
-   */
-  public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS';
-
-  /**
-   * Called when a character presses a note when there was none there, causing them to lose health.
-   * Important information such as direction pressed, etc. are all provided.
-   *
-   * This event IS cancelable! Canceling this event prevents the note from being considered missed,
-   *   avoiding lost health/score and preventing the miss animation.
-   */
-  public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS';
-
-  /**
-   * Called when a song event is reached in the chart.
-   *
-   * This event IS cancelable! Cancelling this event prevents the event from being triggered,
-   *   thus blocking its normal functionality.
-   */
-  public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT';
-
-  /**
-   * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SONG_START:ScriptEventType = 'SONG_START';
-
-  /**
-   * Called when the song ends. This happens as the instrumental and vocals end.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SONG_END:ScriptEventType = 'SONG_END';
-
-  /**
-   * Called when the countdown begins. This occurs before the song starts.
-   *
-   * This event IS cancelable! Canceling this event will prevent the countdown from starting.
-   * - The song will not start until you call Countdown.performCountdown() later.
-   * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
-   */
-  public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START';
-
-  /**
-   * Called when a step of the countdown happens.
-   * Includes information about what step of the countdown was hit.
-   *
-   * This event IS cancelable! Canceling this event will pause the countdown.
-   * - The countdown will not resume until you call PlayState.resumeCountdown().
-   */
-  public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP';
-
-  /**
-   * Called when the countdown is done but just before the song starts.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END';
-
-  /**
-   * Called before the game over screen triggers and the death animation plays.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER';
-
-  /**
-   * Called after the player presses a key to restart the game.
-   * This can happen from the pause menu or the game over screen.
-   *
-   * This event IS cancelable! Canceling this event will prevent the game from restarting.
-   */
-  public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY';
-
-  /**
-   * Called when the player pushes down any key on the keyboard.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN';
-
-  /**
-   * Called when the player releases a key on the keyboard.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final KEY_UP:ScriptEventType = 'KEY_UP';
-
-  /**
-   * Called when the game has finished loading the notes from JSON.
-   * This allows modders to mutate the notes before they are used in the song.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED';
-
-  /**
-   * Called when the game is about to switch the current FlxState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN';
-
-  /**
-   * Called when the game has finished switching the current FlxState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END';
-
-  /**
-   * Called when the game is about to open a new FlxSubState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN';
-
-  /**
-   * Called when the game has finished opening a new FlxSubState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END';
-
-  /**
-   * Called when the game is about to close the current FlxSubState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN';
-
-  /**
-   * Called when the game has finished closing the current FlxSubState.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END';
-
-  /**
-   * Called when the game starts a conversation.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START';
-
-  /**
-   * Called to display the next line of conversation.
-   *
-   * This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line.
-   * - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation.
-   */
-  public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE';
-
-  /**
-   * Called to skip scrolling the current line of conversation.
-   *
-   * This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line.
-   * - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing.
-   */
-  public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE';
-
-  /**
-   * Called to skip the conversation.
-   *
-   * This event IS cancelable! Canceling this event will prevent the conversation from skipping.
-   */
-  public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP';
-
-  /**
-   * Called when the game ends a conversation.
-   *
-   * This event is not cancelable.
-   */
-  public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END';
-
   /**
    * If true, the behavior associated with this event can be prevented.
    * For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
@@ -411,7 +158,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent
 
   public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
   {
-    super(ScriptEvent.NOTE_GHOST_MISS, true);
+    super(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<SongNoteData>):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;