Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENHANCEMENT + BUGFIX] small script event/module overhaul #4172

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/funkin/InitState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class InitState extends FlxState

ModuleHandler.buildModuleCallbacks();
ModuleHandler.loadModuleCache();
ModuleHandler.callOnCreate();
ModuleHandler.callOnGameInit();

funkin.input.Cursor.hide();

Expand Down
28 changes: 28 additions & 0 deletions source/funkin/modding/IScriptedClass.hx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ interface IBPMSyncedScriptedClass extends IScriptedClass
*/
interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedScriptedClass
{
/**
* Called after `PlayState` creation, right before the countdown starts.
*/
public function onPlayStateCreate(event:ScriptEvent):Void;

/**
* Called immediately before `PlayState` cleanup.
*/
public function onPlayStateClose(event:ScriptEvent):Void;

/**
* Called when the game is paused.
* Has properties to set whether the pause easter egg will happen,
Expand Down Expand Up @@ -186,3 +196,21 @@ interface IDialogueScriptedClass extends IScriptedClass
public function onDialogueSkip(event:DialogueScriptEvent):Void;
public function onDialogueEnd(event:DialogueScriptEvent):Void;
}

/**
* Defines a set of callbacks available to scripted classes which are global and not tied to a specific state.
*/
interface IGlobalScriptedClass extends IPlayStateScriptedClass extends IStateChangingScriptedClass
{
/**
* Called when the game is first initialized, before the title screen appears.
* This event is only called once.
*/
public function onGameInit(event:ScriptEvent):Void;

/**
* Called when the game is closed for any reason.
* This event is only called once.
*/
public function onGameClose(event:ScriptEvent):Void;
}
22 changes: 21 additions & 1 deletion source/funkin/modding/events/ScriptEventDispatcher.hx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package funkin.modding.events;

import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.IScriptedClass;

/**
Expand Down Expand Up @@ -114,6 +113,12 @@ class ScriptEventDispatcher
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
switch (event.type)
{
case PLAYSTATE_CREATE:
t.onPlayStateCreate(event);
return;
case PLAYSTATE_CLOSE:
t.onPlayStateClose(event);
return;
case NOTE_GHOST_MISS:
t.onNoteGhostMiss(cast event);
return;
Expand Down Expand Up @@ -186,6 +191,21 @@ class ScriptEventDispatcher
default: // Continue;
}
}

if (Std.isOfType(target, IGlobalScriptedClass))
{
var t = cast(target, IGlobalScriptedClass);
switch (event.type)
{
case GAME_INIT:
t.onGameInit(event);
return;
case GAME_CLOSE:
t.onGameClose(event);
return;
default: // Continue;
}
}
else
{
// Prevent "NO HELPER error."
Expand Down
30 changes: 30 additions & 0 deletions source/funkin/modding/events/ScriptEventType.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ package funkin.modding.events;

enum abstract ScriptEventType(String) from String to String
{
/**
* Called when the game is first initialized, before the title screen appears.
* This event is only called once.
*
* This event is not cancelable.
*/
var GAME_INIT = 'GAME_INIT';

/**
* Called when the game is closed for any reason.
* This event is only called once.
*
* This event is not cancelable.
*/
var GAME_CLOSE = 'GAME_CLOSE';

/**
* Called when the relevant object is created.
* Keep in mind that the constructor may be called before the object is needed,
Expand Down Expand Up @@ -35,6 +51,20 @@ enum abstract ScriptEventType(String) from String to String
*/
var UPDATE = 'UPDATE';

/**
* Called after `PlayState` creation, right before the countdown starts.
*
* This event is not cancelable.
*/
var PLAYSTATE_CREATE = 'PLAYSTATE_CREATE';

/**
* Called immediately before `PlayState` cleanup.
*
* This event is not cancelable.
*/
var PLAYSTATE_CLOSE = 'PLAYSTATE_CLOSE';

/**
* Called when the player moves to pause the game.
*
Expand Down
27 changes: 21 additions & 6 deletions source/funkin/modding/module/Module.hx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package funkin.modding.module;

import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
import funkin.modding.IScriptedClass.IGlobalScriptedClass;
import funkin.modding.events.ScriptEvent;

/**
* A module is a scripted class which receives all events without requiring a specific context.
* You may have the module active at all times, or only when another script enables it.
*/
class Module implements IPlayStateScriptedClass implements IStateChangingScriptedClass
class Module implements IGlobalScriptedClass
{
/**
* Whether the module is currently active.
Expand Down Expand Up @@ -60,19 +59,35 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
public function onScriptEvent(event:ScriptEvent) {}

/**
* Called when the module is first created.
* This happens before the title screen appears!
* Called when the game is first initialized, before the title screen appears.
* This happens only once, immediately after the module's first `onCreate` event.
*/
public function onGameInit(event:ScriptEvent) {}

/**
* Called when the game is closed for any reason.
* This happens only once, immediately before the module's last `onDestroy` event.
*/
public function onGameClose(event:ScriptEvent) {}

/**
* Called when the module is created.
* This happens when the game is first initialized or after a mod reload.
*/
public function onCreate(event:ScriptEvent) {}

/**
* Called when a module is destroyed.
* This currently only happens when reloading modules with F5.
* This happens when reloading modules with F5 or when the game is closed.
*/
public function onDestroy(event:ScriptEvent) {}

public function onUpdate(event:UpdateScriptEvent) {}

public function onPlayStateCreate(event:ScriptEvent) {}

public function onPlayStateClose(event:ScriptEvent) {}

public function onPause(event:PauseScriptEvent) {}

public function onResume(event:ScriptEvent) {}
Expand Down
27 changes: 21 additions & 6 deletions source/funkin/modding/module/ModuleHandler.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.module.Module;
import funkin.modding.module.ScriptedModule;
import funkin.util.WindowUtil;

/**
* Utility functions for loading and manipulating active modules.
Expand Down Expand Up @@ -46,11 +47,15 @@ class ModuleHandler
reorderModuleCache();

trace("[MODULEHANDLER] Module cache loaded.");

callEvent(new ScriptEvent(CREATE, false), true);
}

public static function buildModuleCallbacks():Void
{
FlxG.signals.postStateSwitch.add(onStateSwitchComplete);

WindowUtil.windowExit.add(callOnGameClose);
}

static function onStateSwitchComplete():Void
Expand Down Expand Up @@ -122,31 +127,41 @@ class ModuleHandler
var event = new ScriptEvent(DESTROY, false);

// Note: Ignore stopPropagation()
for (key => value in moduleCache)
for (i in 0...modulePriorityOrder.length)
{
ScriptEventDispatcher.callEvent(value, event);
var moduleId = modulePriorityOrder[modulePriorityOrder.length - 1 - i];
var module:Module = moduleCache.get(moduleId);
if (module != null)
{
ScriptEventDispatcher.callEvent(module, event);
}
}

moduleCache.clear();
modulePriorityOrder = [];
}
}

public static function callEvent(event:ScriptEvent):Void
public static function callEvent(event:ScriptEvent, force:Bool = false):Void
{
for (moduleId in modulePriorityOrder)
{
var module:Module = moduleCache.get(moduleId);
// The module needs to be active to receive events.
if (module != null && module.active)
if (module != null && (module.active || force))
{
ScriptEventDispatcher.callEvent(module, event);
}
}
}

public static inline function callOnCreate():Void
public static inline function callOnGameInit():Void
{
callEvent(new ScriptEvent(GAME_INIT, false));
}

public static inline function callOnGameClose(exitCode:Int):Void
{
callEvent(new ScriptEvent(CREATE, false));
callEvent(new ScriptEvent(GAME_CLOSE, false));
}
}
18 changes: 15 additions & 3 deletions source/funkin/play/PlayState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ class PlayState extends MusicBeatSubState

FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);

// Dispatch the `PlayState` create event.
dispatchEvent(new ScriptEvent(PLAYSTATE_CREATE, false));

// The song is loaded and in the process of starting.
// This gets set back to false when the chart actually starts.
startingSong = true;
Expand Down Expand Up @@ -1168,7 +1171,7 @@ class PlayState extends MusicBeatSubState
// Modules should get the first chance to cancel the event.

// super.dispatchEvent(event) dispatches event to module scripts.
super.dispatchEvent(event);
if (event.type != DESTROY) super.dispatchEvent(event);

// Dispatch event to note kind scripts
NoteKindManager.callEvent(event);
Expand Down Expand Up @@ -3115,11 +3118,18 @@ class PlayState extends MusicBeatSubState
super.close();
}

private var cleanupDone:Bool = false;

/**
* Perform necessary cleanup before leaving the PlayState.
*/
* Perform necessary cleanup before leaving the PlayState.
*/
function performCleanup():Void
{
if (cleanupDone) return;

// Dispatch the `PlayState` close event.
dispatchEvent(new ScriptEvent(PLAYSTATE_CLOSE, false));

// If the camera is being tweened, stop it.
cancelAllCameraTweens();

Expand Down Expand Up @@ -3172,6 +3182,8 @@ class PlayState extends MusicBeatSubState

// Clear the static reference to this state.
instance = null;

cleanupDone = true;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions source/funkin/play/song/Song.hx
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta

public function destroy():Void {}

public function onPlayStateCreate(event:ScriptEvent) {}

public function onPlayStateClose(event:ScriptEvent) {}

public function onPause(event:PauseScriptEvent):Void {};

public function onResume(event:ScriptEvent):Void {};
Expand Down
4 changes: 4 additions & 0 deletions source/funkin/play/stage/Bopper.hx
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
return output;
}

public function onPlayStateCreate(event:ScriptEvent) {}

public function onPlayStateClose(event:ScriptEvent) {}

public function onPause(event:PauseScriptEvent) {}

public function onResume(event:ScriptEvent) {}
Expand Down
4 changes: 4 additions & 0 deletions source/funkin/play/stage/Stage.hx
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
}
}

public function onPlayStateCreate(event:ScriptEvent) {}

public function onPlayStateClose(event:ScriptEvent) {}

public function onPause(event:PauseScriptEvent) {}

public function onResume(event:ScriptEvent) {}
Expand Down
Loading