Funkin/source/funkin/modding/events/ScriptEvent.hx

666 lines
18 KiB
Haxe

package funkin.modding.events;
import funkin.data.song.SongData.SongNoteData;
import flixel.FlxState;
import flixel.FlxSubState;
import funkin.play.notes.NoteSprite;
import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.Countdown.CountdownStep;
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,
* until another script restarts it, or cancelling NOTE_HIT should cause the note to be missed.
*/
public var cancelable(default, null):Bool;
/**
* The type associated with the event.
*/
public var type(default, null):ScriptEventType;
/**
* Whether the event should continue to be triggered on additional targets.
*/
public var shouldPropagate(default, null):Bool;
/**
* Whether the event has been canceled by one of the scripts that received it.
*/
public var eventCanceled(default, null):Bool;
public function new(type:ScriptEventType, cancelable:Bool = false):Void
{
this.type = type;
this.cancelable = cancelable;
this.eventCanceled = false;
this.shouldPropagate = true;
}
/**
* Call this function on a cancelable event to cancel the associated behavior.
* For example, cancelling COUNTDOWN_START will prevent the countdown from starting.
*/
public function cancelEvent():Void
{
if (cancelable)
{
eventCanceled = true;
}
}
/**
* Cancel this event.
* This is an alias for cancelEvent() but I make this typo all the time.
*/
public function cancel():Void
{
cancelEvent();
}
/**
* Call this function to stop any other Scripteds from receiving the event.
*/
public function stopPropagation():Void
{
shouldPropagate = false;
}
public function toString():String
{
return 'ScriptEvent(type=$type, cancelable=$cancelable)';
}
}
/**
* SPECIFIC EVENTS
*/
/**
* An event that is fired associated with a specific note.
*/
class NoteScriptEvent extends ScriptEvent
{
/**
* The note associated with this event.
* You cannot replace it, but you can edit it.
*/
public var note(default, null):NoteSprite;
/**
* The combo count as it is with this event.
* Will be (combo) on miss events and (combo + 1) on hit events (the stored combo count won't update if the event is cancelled).
*/
public var comboCount(default, null):Int;
/**
* Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`).
*/
public var playSound(default, default):Bool;
public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void
{
super(type, cancelable);
this.note = note;
this.comboCount = comboCount;
this.playSound = true;
}
public override function toString():String
{
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ', comboCount=' + comboCount + ')';
}
}
/**
* An event that is fired when you press a key with no note present.
*/
class GhostMissNoteScriptEvent extends ScriptEvent
{
/**
* The direction that was mistakenly pressed.
*/
public var dir(default, null):NoteDirection;
/**
* Whether there was a note within judgement range when this ghost note was pressed.
*/
public var hasPossibleNotes(default, null):Bool;
/**
* How much health should be lost when this ghost note is pressed.
* Remember that max health is 2.00.
*/
public var healthChange(default, default):Float;
/**
* How much score should be lost when this ghost note is pressed.
*/
public var scoreChange(default, default):Int;
/**
* Whether to play the record scratch sound.
*/
public var playSound(default, default):Bool;
/**
* Whether to play the miss animation on the player.
*/
public var playAnim(default, default):Bool;
public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
{
super(ScriptEvent.NOTE_GHOST_MISS, true);
this.dir = dir;
this.hasPossibleNotes = hasPossibleNotes;
this.healthChange = healthChange;
this.scoreChange = scoreChange;
this.playSound = true;
this.playAnim = true;
}
public override function toString():String
{
return 'GhostMissNoteScriptEvent(dir=' + dir + ', hasPossibleNotes=' + hasPossibleNotes + ')';
}
}
/**
* An event that is fired when the song reaches an event.
*/
class SongEventScriptEvent extends ScriptEvent
{
/**
* The note associated with this event.
* You cannot replace it, but you can edit it.
*/
public var event(default, null):funkin.data.song.SongData.SongEventData;
public function new(event:funkin.data.song.SongData.SongEventData):Void
{
super(ScriptEvent.SONG_EVENT, true);
this.event = event;
}
public override function toString():String
{
return 'SongEventScriptEvent(event=' + event + ')';
}
}
/**
* An event that is fired during the update loop.
*/
class UpdateScriptEvent extends ScriptEvent
{
/**
* The note associated with this event.
* You cannot replace it, but you can edit it.
*/
public var elapsed(default, null):Float;
public function new(elapsed:Float):Void
{
super(ScriptEvent.UPDATE, false);
this.elapsed = elapsed;
}
public override function toString():String
{
return 'UpdateScriptEvent(elapsed=$elapsed)';
}
}
/**
* An event that is fired regularly during the song.
* May be on beat or on step.
*/
class SongTimeScriptEvent extends ScriptEvent
{
/**
* The current beat of the song.
*/
public var beat(default, null):Int;
/**
* The current step of the song.
*/
public var step(default, null):Int;
public function new(type:ScriptEventType, beat:Int, step:Int):Void
{
super(type, true);
this.beat = beat;
this.step = step;
}
public override function toString():String
{
return 'SongTimeScriptEvent(type=' + type + ', beat=' + beat + ', step=' + step + ')';
}
}
/**
* An event that is fired regularly during the song.
* May be on beat or on step.
*/
class CountdownScriptEvent extends ScriptEvent
{
/**
* The current step of the countdown.
*/
public var step(default, null):CountdownStep;
public function new(type:ScriptEventType, step:CountdownStep, cancelable:Bool = true):Void
{
super(type, cancelable);
this.step = step;
}
public override function toString():String
{
return 'CountdownScriptEvent(type=' + type + ', step=' + step + ')';
}
}
/**
* An event that is fired during a dialogue.
*/
class DialogueScriptEvent extends ScriptEvent
{
/**
* The dialogue being referenced by the event.
*/
public var conversation(default, null):Conversation;
public function new(type:ScriptEventType, conversation:Conversation, cancelable:Bool = true):Void
{
super(type, cancelable);
this.conversation = conversation;
}
public override function toString():String
{
return 'DialogueScriptEvent(type=$type, conversation=$conversation)';
}
}
/**
* An event that is fired when the player presses a key.
*/
class KeyboardInputScriptEvent extends ScriptEvent
{
/**
* The associated keyboard event.
*/
public var event(default, null):KeyboardEvent;
public function new(type:ScriptEventType, event:KeyboardEvent):Void
{
super(type, false);
this.event = event;
}
public override function toString():String
{
return 'KeyboardInputScriptEvent(type=' + type + ', event=' + event + ')';
}
}
/**
* An event that is fired once the song's chart has been parsed.
*/
class SongLoadScriptEvent extends ScriptEvent
{
/**
* The note associated with this event.
* You cannot replace it, but you can edit it.
*/
public var notes(default, set):Array<SongNoteData>;
public var id(default, null):String;
public var difficulty(default, null):String;
function set_notes(notes:Array<SongNoteData>):Array<SongNoteData>
{
this.notes = notes;
return this.notes;
}
public function new(id:String, difficulty:String, notes:Array<SongNoteData>):Void
{
super(ScriptEvent.SONG_LOADED, false);
this.id = id;
this.difficulty = difficulty;
this.notes = notes;
}
public override function toString():String
{
var noteStr = notes == null ? 'null' : 'Array(' + notes.length + ')';
return 'SongLoadScriptEvent(notes=$noteStr, id=$id, difficulty=$difficulty)';
}
}
/**
* An event that is fired when moving out of or into an FlxState.
*/
class StateChangeScriptEvent extends ScriptEvent
{
/**
* The state the game is moving into.
*/
public var targetState(default, null):FlxState;
public function new(type:ScriptEventType, targetState:FlxState, cancelable:Bool = false):Void
{
super(type, cancelable);
this.targetState = targetState;
}
public override function toString():String
{
return 'StateChangeScriptEvent(type=' + type + ', targetState=' + targetState + ')';
}
}
/**
* An event that is fired when moving out of or into an FlxSubState.
*/
class SubStateScriptEvent extends ScriptEvent
{
/**
* The state the game is moving into.
*/
public var targetState(default, null):FlxSubState;
public function new(type:ScriptEventType, targetState:FlxSubState, cancelable:Bool = false):Void
{
super(type, cancelable);
this.targetState = targetState;
}
public override function toString():String
{
return 'SubStateScriptEvent(type=' + type + ', targetState=' + targetState + ')';
}
}
/**
* An event which is called when the player attempts to pause the game.
*/
class PauseScriptEvent extends ScriptEvent
{
/**
* Whether to use the Gitaroo Man pause.
*/
public var gitaroo(default, default):Bool;
public function new(gitaroo:Bool):Void
{
super(ScriptEvent.PAUSE, true);
this.gitaroo = gitaroo;
}
}