From 0cc4f8a98251716f4b4a514410a612ad72f069ed Mon Sep 17 00:00:00 2001 From: Eric Myllyoja Date: Sun, 13 Mar 2022 14:36:03 -0400 Subject: [PATCH] Scripted modules are stable but missing some features. --- example_mods/.gitignore | 6 +- example_mods/introMod/README.md | 6 + source/funkin/FreeplayState.hx | 25 +- source/funkin/MainMenuState.hx | 26 +- source/funkin/MusicBeatState.hx | 41 +- source/funkin/audiovis/ABotVis.hx | 6 +- source/funkin/charting/ChartingState.hx | 2 +- .../funkin/freeplayStuff/BGScrollingText.hx | 2 +- source/funkin/modding/IHook.hx | 2 + source/funkin/modding/IScriptedClass.hx | 3 + source/funkin/modding/events/ScriptEvent.hx | 74 ++- .../modding/events/ScriptEventDispatcher.hx | 15 +- source/funkin/modding/module/Module.hx | 27 +- source/funkin/modding/module/ModuleHandler.hx | 77 ++- source/funkin/play/Countdown.hx | 316 +++++++++- source/funkin/play/PlayState.hx | 572 +++++------------- source/funkin/play/Scoring.hx | 6 + source/funkin/play/Strumline.hx | 2 +- source/funkin/play/VanillaCutscenes.hx | 102 ++++ source/funkin/play/stage/Bopper.hx | 4 + source/funkin/play/stage/Stage.hx | 9 +- source/funkin/util/Constants.hx | 11 + source/funkin/util/IteratorTools.hx | 16 + source/funkin/util/macro/HookableMacro.hx | 3 + source/modding/module/Module.hx | 80 --- source/modding/module/ModuleHandler.hx | 79 --- source/modding/module/ScriptedModule.hx | 9 - source/modding/module/events/ModuleEvent.hx | 128 ---- .../modding/module/events/NoteModuleEvent.hx | 1 - 29 files changed, 847 insertions(+), 803 deletions(-) create mode 100644 example_mods/introMod/README.md create mode 100644 source/funkin/play/Scoring.hx create mode 100644 source/funkin/play/VanillaCutscenes.hx create mode 100644 source/funkin/util/IteratorTools.hx create mode 100644 source/funkin/util/macro/HookableMacro.hx delete mode 100644 source/modding/module/Module.hx delete mode 100644 source/modding/module/ModuleHandler.hx delete mode 100644 source/modding/module/ScriptedModule.hx delete mode 100644 source/modding/module/events/ModuleEvent.hx delete mode 100644 source/modding/module/events/NoteModuleEvent.hx diff --git a/example_mods/.gitignore b/example_mods/.gitignore index cd193dac6..d221d3f90 100644 --- a/example_mods/.gitignore +++ b/example_mods/.gitignore @@ -1,2 +1,4 @@ -./tricky -./enaSkin \ No newline at end of file +# Exclude any experimental mods that my have been added. +/* +!introMod +!testing123 \ No newline at end of file diff --git a/example_mods/introMod/README.md b/example_mods/introMod/README.md new file mode 100644 index 000000000..060eaba50 --- /dev/null +++ b/example_mods/introMod/README.md @@ -0,0 +1,6 @@ +# introMod + +This intro mod demonstrates two simple things: + +1. You can replace any game asset simply by placing a modded asset in the right spot. +2. You can append to text files simply by placing a text file in the right spot, but under the `_append` directory. \ No newline at end of file diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 882c7795f..c2d4c2680 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -238,9 +238,6 @@ class FreeplayState extends MusicBeatSubstate add(new DifficultySelector(20, grpDifficulties.y - 10, false, controls)); add(new DifficultySelector(325, grpDifficulties.y - 10, true, controls)); - var animShit:ComboCounter = new ComboCounter(100, 300, 1000000); - // add(animShit); - new FlxTimer().start(1 / 24, function(handShit) { fnfFreeplay.visible = true; @@ -388,7 +385,7 @@ class FreeplayState extends MusicBeatSubstate { if (FlxG.sound.music.volume < 0.7) { - FlxG.sound.music.volume += 0.5 * FlxG.elapsed; + FlxG.sound.music.volume += 0.5 * elapsed; } } @@ -435,7 +432,7 @@ class FreeplayState extends MusicBeatSubstate if (touchTimer >= 1.5) accepted = true; - touchTimer += FlxG.elapsed; + touchTimer += elapsed; var touch:FlxTouch = FlxG.touches.getFirst(); velTouch = Math.abs((touch.screenY - dyTouch)) / 50; @@ -472,24 +469,6 @@ class FreeplayState extends MusicBeatSubstate else { touchTimer = 0; - - /* if (velTouch >= 0) - { - trace(velTouch); - velTouch -= FlxG.elapsed; - - veloctiyLoopShit += velTouch; - - trace("VEL LOOP: " + veloctiyLoopShit); - - if (veloctiyLoopShit >= 30) - { - veloctiyLoopShit = 0; - changeSelection(1); - } - - // trace(velTouch); - }*/ } } diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx index 1f6bbf9da..d852ef397 100644 --- a/source/funkin/MainMenuState.hx +++ b/source/funkin/MainMenuState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.util.Constants; import funkin.modding.events.ScriptEvent.UpdateScriptEvent; import funkin.modding.module.ModuleHandler; import funkin.NGio; @@ -140,17 +141,21 @@ class MainMenuState extends MusicBeatState FlxG.camera.follow(camFollow, null, 0.06); // FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2); - var versionStr = 'v${Application.current.meta.get('version')}'; - versionStr += ' (secret week 8 build do not leak)'; + super.create(); - var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12); - versionShit.scrollFactor.set(); - versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - add(versionShit); + // This has to come AFTER! + this.leftWatermarkText.text = Constants.VERSION; + this.rightWatermarkText.text = "blablabla test"; + + // var versionStr = 'v${Application.current.meta.get('version')}'; + // versionStr += ' (secret week 8 build do not leak)'; + // + // var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12); + // versionShit.scrollFactor.set(); + // versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + // add(versionShit); // NG.core.calls.event.logEvent('swag').send(); - - super.create(); } override function closeSubState() @@ -299,7 +304,7 @@ class MainMenuState extends MusicBeatState if (FlxG.sound.music.volume < 0.8) { - FlxG.sound.music.volume += 0.5 * FlxG.elapsed; + FlxG.sound.music.volume += 0.5 * elapsed; } if (_exiting) @@ -310,9 +315,6 @@ class MainMenuState extends MusicBeatState FlxG.sound.play(Paths.sound('cancelMenu')); FlxG.switchState(new TitleState()); } - - var event:UpdateScriptEvent = new UpdateScriptEvent(elapsed); - ModuleHandler.callEvent(event); } } diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 570c0c677..9b961f176 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -1,5 +1,11 @@ package funkin; +import flixel.util.FlxColor; +import flixel.text.FlxText; +import cpp.abi.Abi; +import funkin.modding.events.ScriptEvent; +import funkin.modding.module.ModuleHandler; +import funkin.modding.events.ScriptEvent.UpdateScriptEvent; import funkin.Conductor.BPMChangeEvent; import flixel.FlxGame; import flixel.addons.transition.FlxTransitionableState; @@ -17,16 +23,23 @@ class MusicBeatState extends FlxUIState inline function get_controls():Controls return PlayerSettings.player1.controls; + public var leftWatermarkText:FlxText = null; + public var rightWatermarkText:FlxText = null; + override function create() { + super.create(); + if (transIn != null) trace('reg ' + transIn.region); - super.create(); + createWatermarkText(); } override function update(elapsed:Float) { + super.update(elapsed); + // everyStep(); var oldStep:Int = curStep; @@ -36,7 +49,31 @@ class MusicBeatState extends FlxUIState if (oldStep != curStep && curStep >= 0) stepHit(); - super.update(elapsed); + dispatchEvent(new UpdateScriptEvent(elapsed)); + } + + function createWatermarkText() + { + // Both have an xPos of 0, but a width equal to the full screen. + // The rightWatermarkText is right aligned, which puts the text in the correct spot. + leftWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12); + rightWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12); + + // 100,000 should be good enough. + leftWatermarkText.zIndex = 100000; + rightWatermarkText.zIndex = 100000; + leftWatermarkText.scrollFactor.set(0, 0); + rightWatermarkText.scrollFactor.set(0, 0); + leftWatermarkText.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + rightWatermarkText.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + + add(leftWatermarkText); + add(rightWatermarkText); + } + + function dispatchEvent(event:ScriptEvent) + { + ModuleHandler.callEvent(event); } private function updateBeat():Void diff --git a/source/funkin/audiovis/ABotVis.hx b/source/funkin/audiovis/ABotVis.hx index a05238e9f..07b1b51d3 100644 --- a/source/funkin/audiovis/ABotVis.hx +++ b/source/funkin/audiovis/ABotVis.hx @@ -48,12 +48,12 @@ class ABotVis extends FlxTypedSpriteGroup { // updateViz(); - updateFFT(); + updateFFT(elapsed); super.update(elapsed); } - function updateFFT() + function updateFFT(elapsed:Float) { if (vis.snd != null) { @@ -112,7 +112,7 @@ class ABotVis extends FlxTypedSpriteGroup avgVel *= 10000000; - volumes[i] += avgVel - (FlxG.elapsed * (volumes[i] * 50)); + volumes[i] += avgVel - (elapsed * (volumes[i] * 50)); var animFrame:Int = Std.int(volumes[i]); diff --git a/source/funkin/charting/ChartingState.hx b/source/funkin/charting/ChartingState.hx index e079cb3f3..e8aea9da1 100644 --- a/source/funkin/charting/ChartingState.hx +++ b/source/funkin/charting/ChartingState.hx @@ -903,7 +903,7 @@ class ChartingState extends MusicBeatState { if (FlxG.keys.pressed.W || FlxG.keys.pressed.S) { - var daTime:Float = 700 * FlxG.elapsed; + var daTime:Float = 700 * elapsed; if (FlxG.keys.pressed.CONTROL) daTime *= 0.2; diff --git a/source/funkin/freeplayStuff/BGScrollingText.hx b/source/funkin/freeplayStuff/BGScrollingText.hx index 39837e031..dcdd206bc 100644 --- a/source/funkin/freeplayStuff/BGScrollingText.hx +++ b/source/funkin/freeplayStuff/BGScrollingText.hx @@ -60,7 +60,7 @@ class BGScrollingText extends FlxSpriteGroup { for (txt in grpTexts.group) { - txt.x -= 1 * (speed * (FlxG.elapsed / (1 / 60))); + txt.x -= 1 * (speed * (elapsed / (1 / 60))); if (speed > 0) { diff --git a/source/funkin/modding/IHook.hx b/source/funkin/modding/IHook.hx index 51a4b1088..d9bc298b7 100644 --- a/source/funkin/modding/IHook.hx +++ b/source/funkin/modding/IHook.hx @@ -4,9 +4,11 @@ import polymod.hscript.HScriptable; /** * Functions annotated with @:hscript will call the relevant script. + * Functions annotated with @:hookable can be reassigned. */ @:hscript({ // ALL of these values are added to ALL scripts in the child classes. context: [FlxG, FlxSprite, Math, Paths, Std] }) +// @:autoBuild(funkin.util.macro.HookableMacro.build()) interface IHook extends HScriptable {} diff --git a/source/funkin/modding/IScriptedClass.hx b/source/funkin/modding/IScriptedClass.hx index 0445d2c32..9452d04db 100644 --- a/source/funkin/modding/IScriptedClass.hx +++ b/source/funkin/modding/IScriptedClass.hx @@ -23,6 +23,7 @@ interface IInputScriptedClass extends IScriptedClass { public function onKeyDown(event:KeyboardInputScriptEvent):Void; public function onKeyUp(event:KeyboardInputScriptEvent):Void; + // TODO: OnMouseDown, OnMouseUp, OnMouseMove } /** @@ -33,6 +34,7 @@ interface IPlayStateScriptedClass extends IScriptedClass public function onPause(event:ScriptEvent):Void; public function onResume(event:ScriptEvent):Void; + public function onSongLoaded(eent:SongLoadScriptEvent):Void; public function onSongStart(event:ScriptEvent):Void; public function onSongEnd(event:ScriptEvent):Void; public function onSongReset(event:ScriptEvent):Void; @@ -47,4 +49,5 @@ interface IPlayStateScriptedClass extends IScriptedClass public function onCountdownStart(event:CountdownScriptEvent):Void; public function onCountdownStep(event:CountdownScriptEvent):Void; + public function onCountdownEnd(event:CountdownScriptEvent):Void; } diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index c10a3c3b5..efc922ed5 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -108,8 +108,8 @@ class ScriptEvent * 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 PlayState.beginCountdown(). - * - Note that calling startCountdown() will trigger this event again, so be sure to add logic to ignore it. + * - 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"; @@ -122,6 +122,13 @@ class ScriptEvent */ 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 when the game over screen triggers and the death animation plays. * @@ -150,6 +157,14 @@ class ScriptEvent */ 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"; + /** * If true, the behavior associated with this event can be prevented. * For example, cancelling COUNTDOWN_BEGIN should prevent the countdown from starting, @@ -167,13 +182,16 @@ class ScriptEvent */ public var shouldPropagate(default, null):Bool; - @:noCompletion private var __eventCanceled: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.eventCanceled = false; this.shouldPropagate = true; } @@ -185,10 +203,16 @@ class ScriptEvent { if (cancelable) { - __eventCanceled = true; + eventCanceled = true; } } + public function cancel():Void + { + // This typo happens enough that I just added this. + cancelEvent(); + } + /** * Call this function to stop any other Scripteds from receiving the event. */ @@ -292,9 +316,9 @@ class CountdownScriptEvent extends ScriptEvent */ public var step(default, null):CountdownStep; - public function new(type:ScriptEventType, step:CountdownStep):Void + public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void { - super(type, false); + super(type, cancelable); this.step = step; } @@ -325,3 +349,39 @@ class KeyboardInputScriptEvent extends ScriptEvent 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; + + public var id(default, null):String; + + public var difficulty(default, null):String; + + function set_notes(notes:Array):Array + { + this.notes = notes; + return this.notes; + } + + public function new(id:String, difficulty:String, notes:Array):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)'; + } +} diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index 29ad736ab..c3df66170 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -11,6 +11,9 @@ class ScriptEventDispatcher { public static function callEvent(target:IScriptedClass, event:ScriptEvent):Void { + if (target == null || event == null) + return; + target.onScriptEvent(event); // If one target says to stop propagation, stop. @@ -85,6 +88,12 @@ class ScriptEventDispatcher case ScriptEvent.COUNTDOWN_STEP: t.onCountdownStep(cast event); return; + case ScriptEvent.COUNTDOWN_END: + t.onCountdownEnd(cast event); + return; + case ScriptEvent.SONG_LOADED: + t.onSongLoaded(cast event); + return; } } @@ -94,26 +103,20 @@ class ScriptEventDispatcher public static function callEventOnAllTargets(targets:Iterator, event:ScriptEvent):Void { if (targets == null || event == null) - { return; - } if (Std.isOfType(targets, Array)) { var t = cast(targets, Array); if (t.length == 0) - { return; - } } for (target in targets) { var t:IScriptedClass = cast target; if (t == null) - { continue; - } callEvent(t, event); diff --git a/source/funkin/modding/module/Module.hx b/source/funkin/modding/module/Module.hx index fda644890..87b933ccc 100644 --- a/source/funkin/modding/module/Module.hx +++ b/source/funkin/modding/module/Module.hx @@ -28,6 +28,22 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass public var moduleId(default, null):String = 'UNKNOWN'; + /** + * Determines the order in which modules receive events. + * You can modify this to change the order in which a given module receives events. + * + * Priority 1 is processed before Priority 1000, etc. + */ + public var priority(default, set):Int; + + function set_priority(value:Int):Int + { + this.priority = value; + @:privateAccess + ModuleHandler.reorderModuleCache(); + return value; + } + /** * Called when the module is initialized. * It may not be safe to reference other modules here since they may not be loaded yet. @@ -36,10 +52,11 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass * If false, the module will be inactive and must be enabled by another script, * such as a stage or another module. */ - public function new(moduleId:String, startActive:Bool) + public function new(moduleId:String, active:Bool = true, priority:Int = 1000):Void { this.moduleId = moduleId; - this.active = startActive; + this.active = active; + this.priority = priority; } public function toString() @@ -47,6 +64,8 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass return 'Module(' + this.moduleId + ')'; } + // TODO: Half of these aren't actually being called!!!!!!! + public function onScriptEvent(event:ScriptEvent) {} public function onCreate(event:ScriptEvent) {} @@ -84,4 +103,8 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass public function onCountdownStart(event:CountdownScriptEvent) {} public function onCountdownStep(event:CountdownScriptEvent) {} + + public function onCountdownEnd(event:CountdownScriptEvent) {} + + public function onSongLoaded(eent:SongLoadScriptEvent) {} } diff --git a/source/funkin/modding/module/ModuleHandler.hx b/source/funkin/modding/module/ModuleHandler.hx index 88fdfeb44..d5a296638 100644 --- a/source/funkin/modding/module/ModuleHandler.hx +++ b/source/funkin/modding/module/ModuleHandler.hx @@ -4,17 +4,15 @@ import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent.UpdateScriptEvent; +using funkin.util.IteratorTools; + /** * Utility functions for loading and manipulating active modules. */ class ModuleHandler { static final moduleCache:Map = new Map(); - - /** - * Whether modules start active by default. - */ - static final DEFAULT_STARTACTIVE:Bool = true; + static var modulePriorityOrder:Array = []; /** * Parses and preloads the game's stage data and scripts when the game starts. @@ -31,37 +29,92 @@ class ModuleHandler trace(' Instantiating ${scriptedModuleClassNames.length} modules...'); for (moduleCls in scriptedModuleClassNames) { - var module:Module = ScriptedModule.init(moduleCls, moduleCls, DEFAULT_STARTACTIVE); + var module:Module = ScriptedModule.init(moduleCls, moduleCls); if (module != null) { trace(' Loaded module: ${moduleCls}'); // Then store it. - moduleCache.set(module.moduleId, module); + addToModuleCache(module); } else { trace(' Failed to instantiate module: ${moduleCls}'); } } + reorderModuleCache(); trace("[MODULEHANDLER] Module cache loaded."); } + static function addToModuleCache(module:Module):Void + { + moduleCache.set(module.moduleId, module); + } + + static function reorderModuleCache():Void + { + modulePriorityOrder = moduleCache.keys().array(); + + modulePriorityOrder.sort(function(a:String, b:String):Int + { + var aModule:Module = moduleCache.get(a); + var bModule:Module = moduleCache.get(b); + + if (aModule.priority != bModule.priority) + { + return aModule.priority - bModule.priority; + } + else + { + // Sort alphabetically. Yes that's how this works. + return a > b ? 1 : -1; + } + }); + } + + public static function getModule(moduleId:String):Module + { + return moduleCache.get(moduleId); + } + + public static function activateModule(moduleId:String):Void + { + var module:Module = getModule(moduleId); + if (module != null) + { + module.active = true; + } + } + + public static function deactivateModule(moduleId:String):Void + { + var module:Module = getModule(moduleId); + if (module != null) + { + module.active = false; + } + } + public static function clearModuleCache():Void { if (moduleCache != null) { - // for (module in moduleCache) - // { - // module.destroy(); - // } moduleCache.clear(); + modulePriorityOrder = []; } } public static function callEvent(event:ScriptEvent):Void { - ScriptEventDispatcher.callEventOnAllTargets(moduleCache.iterator(), event); + for (moduleId in modulePriorityOrder) + { + var module:Module = moduleCache.get(moduleId); + // The module needs to be active to receive events. + if (module != null && module.active) + { + ScriptEventDispatcher.callEvent(module, event); + } + } } } diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index cdd5b01da..d6770b11a 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -1,11 +1,313 @@ package funkin.play; -enum abstract CountdownStep(String) from String to String +import funkin.util.Constants; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.FlxSprite; +import flixel.input.actions.FlxAction.FlxActionAnalog; +import cpp.abi.Abi; +import funkin.modding.events.ScriptEventDispatcher; +import funkin.modding.module.ModuleHandler; +import funkin.modding.events.ScriptEvent; +import funkin.modding.events.ScriptEvent.CountdownScriptEvent; +import flixel.util.FlxTimer; + +using StringTools; + +class Countdown { - var BEFORE = "BEFORE"; - var THREE = "THREE"; - var TWO = "TWO"; - var ONE = "ONE"; - var GO = "GO"; - var AFTER = "AFTER"; + /** + * The current step of the countdown. + */ + public static var countdownStep(default, null):CountdownStep = BEFORE; + + /** + * The currently running countdown. This will be null if there is no countdown running. + */ + static var countdownTimer:FlxTimer = null; + + /** + * Performs the countdown. + * Pauses the song, plays the countdown graphics/sound, and then starts the song. + * This will automatically stop and restart the countdown if it is already running. + */ + public static function performCountdown(isPixelStyle:Bool):Void + { + // Stop any existing countdown. + stopCountdown(); + + PlayState.isInCountdown = true; + Conductor.songPosition = Conductor.crochet * -5; + countdownStep = BEFORE; + + var cancelled:Bool = propagateCountdownEvent(countdownStep); + if (cancelled) + return; + + // The timer function gets called based on the beat of the song. + countdownTimer = new FlxTimer(); + + countdownTimer.start(Conductor.crochet / 1000, function(tmr:FlxTimer) + { + countdownStep = decrement(countdownStep); + + // Play the dance animations manually. + @:privateAccess + PlayState.instance.danceOnBeat(); + + // Countdown graphic. + showCountdownGraphic(countdownStep, isPixelStyle); + + // Countdown sound. + playCountdownSound(countdownStep, isPixelStyle); + + // Event handling bullshit. + var cancelled:Bool = propagateCountdownEvent(countdownStep); + + if (cancelled) + pauseCountdown(); + + if (countdownStep == AFTER) + { + stopCountdown(); + } + }, 6); // Before, 3, 2, 1, GO!, After + } + + /** + * @return TRUE if the event was cancelled. + */ + static function propagateCountdownEvent(index:CountdownStep):Bool + { + var event:ScriptEvent; + + switch (index) + { + case BEFORE: + event = new CountdownScriptEvent(ScriptEvent.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); + case AFTER: + event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_END, index, false); + default: + return true; + } + + // Stage + ScriptEventDispatcher.callEvent(PlayState.instance.currentStage, event); + + // Modules + ModuleHandler.callEvent(event); + + return event.eventCanceled; + } + + /** + * Pauses the countdown at the current step. You can start it up again later by calling resumeCountdown(). + * + * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. + */ + public static function pauseCountdown() + { + if (countdownTimer != null && !countdownTimer.finished) + { + countdownTimer.active = false; + } + } + + /** + * Resumes the countdown at the current step. Only makes sense if you called pauseCountdown() first. + * + * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. + */ + public static function resumeCountdown() + { + if (countdownTimer != null && !countdownTimer.finished) + { + countdownTimer.active = true; + } + } + + /** + * Stops the countdown at the current step. You will have to restart it again later. + * + * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event. + */ + public static function stopCountdown() + { + if (countdownTimer != null) + { + countdownTimer.cancel(); + countdownTimer.destroy(); + countdownTimer = null; + } + } + + /** + * Stops the current countdown, then starts the song for you. + */ + public static function skipCountdown() + { + stopCountdown(); + // This will trigger PlayState.startSong() + Conductor.songPosition = 0; + // PlayState.isInCountdown = false; + } + + /** + * Resets the countdown. Only works if it's already running. + */ + public static function resetCountdown() + { + if (countdownTimer != null) + { + countdownTimer.reset(); + } + } + + /** + * Retrieves the graphic to use for this step of the countdown. + * TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles? + * + * This is public so modules can do lol funny shit. + */ + public static function showCountdownGraphic(index:CountdownStep, isPixelStyle:Bool):Void + { + var spritePath:String = null; + + if (isPixelStyle) + { + switch (index) + { + case TWO: + spritePath = 'weeb/pixelUI/ready-pixel'; + case ONE: + spritePath = 'weeb/pixelUI/set-pixel'; + case GO: + spritePath = 'weeb/pixelUI/date-pixel'; + default: + // null + } + } + else + { + switch (index) + { + case TWO: + spritePath = 'ready'; + case ONE: + spritePath = 'set'; + case GO: + spritePath = 'go'; + default: + // null + } + } + + if (spritePath == null) + return; + + var countdownSprite:FlxSprite = new FlxSprite(0, 0).loadGraphic(Paths.image(spritePath)); + countdownSprite.scrollFactor.set(0, 0); + + if (isPixelStyle) + countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE)); + + countdownSprite.updateHitbox(); + countdownSprite.screenCenter(); + + // Fade sprite in, then out, then destroy it. + FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.crochet / 1000, { + ease: FlxEase.cubeInOut, + onComplete: function(twn:FlxTween) + { + countdownSprite.destroy(); + } + }); + + PlayState.instance.add(countdownSprite); + } + + /** + * Retrieves the sound file to use for this step of the countdown. + * TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles? + * + * This is public so modules can do lol funny shit. + */ + public static function playCountdownSound(index:CountdownStep, isPixelStyle:Bool):Void + { + var soundPath:String = null; + + if (isPixelStyle) + { + switch (index) + { + case THREE: + soundPath = 'intro3-pixel'; + case TWO: + soundPath = 'intro2-pixel'; + case ONE: + soundPath = 'intro1-pixel'; + case GO: + soundPath = 'introGo-pixel'; + default: + // null + } + } + else + { + switch (index) + { + case THREE: + soundPath = 'intro3'; + case TWO: + soundPath = 'intro2'; + case ONE: + soundPath = 'intro1'; + case GO: + soundPath = 'introGo'; + default: + // null + } + } + + if (soundPath == null) + return; + + FlxG.sound.play(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME); + } + + public static function decrement(step:CountdownStep):CountdownStep + { + switch (step) + { + case BEFORE: + return THREE; + case THREE: + return TWO; + case TWO: + return ONE; + case ONE: + return GO; + case GO: + return AFTER; + + default: + return AFTER; + } + } +} + +/** + * The countdown step. + * This can't be an enum abstract because scripts may need it. + */ +enum CountdownStep +{ + BEFORE; + THREE; + TWO; + ONE; + GO; + AFTER; } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 4391fe930..1efcaef11 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -73,12 +73,28 @@ class PlayState extends MusicBeatState */ public static var isPracticeMode:Bool = false; + /** + * Whether the game is currently in a cutscene, and gameplay should be stopped. + */ + public static var isInCutscene:Bool = false; + + /** + * Whether the game is currently in the countdown before the song resumes. + */ + public static var isInCountdown:Bool = false; + /** * The current "Blueball Counter" to display in the pause menu. * Resets when you beat a song or go back to the main menu. */ public static var deathCounter:Int = 0; + /** + * The default camera zoom level. The camera lerps back to this after zooming in. + * Defaults to 1.05 but may be larger or smaller depending on the current stage. + */ + public static var defaultCameraZoom:Float = 1.05; + /** * Used to persist the position of the `cameraFollowPosition` between resets. */ @@ -106,6 +122,12 @@ class PlayState extends MusicBeatState */ public var health:Float = 1; + /** + * An empty FlxObject contained in the scene. + * The current gameplay camera will be centered on this object. Tween its position to move the camera smoothly. + */ + public var cameraFollowPoint:FlxObject; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -117,12 +139,6 @@ class PlayState extends MusicBeatState */ private var inactiveNotes:Array; - /** - * An empty FlxObject contained in the scene. - * The current gameplay camera will be centered on this object. Tween its position to move the camera smoothly. - */ - private var cameraFollowPoint:FlxObject; - /** * An object which the strumline (and its notes) are positioned relative to. */ @@ -175,6 +191,16 @@ class PlayState extends MusicBeatState */ public var enemyStrumline:Strumline; + /** + * The camera which contains, and controls visibility of, the user interface elements. + */ + public var camHUD:FlxCamera; + + /** + * The camera which contains, and controls visibility of, the stage and characters. + */ + public var camGame:FlxCamera; + /** * PROPERTIES */ @@ -213,19 +239,14 @@ class PlayState extends MusicBeatState private var startingSong:Bool = false; private var iconP1:HealthIcon; private var iconP2:HealthIcon; - private var camHUD:FlxCamera; - private var camGame:FlxCamera; + var dialogue:Array; - var startedCountdown:Bool = false; var talking:Bool = true; var songScore:Int = 0; var doof:DialogueBox; var grpNoteSplashes:FlxTypedGroup; - var defaultCamZoom:Float = 1.05; - var inCutscene:Bool = false; var camPos:FlxPoint; var comboPopUps:PopUpStuff; - var startTimer:FlxTimer = new FlxTimer(); var perfectMode:Bool = false; var previousFrameTime:Int = 0; var songTime:Float = 0; @@ -254,15 +275,25 @@ class PlayState extends MusicBeatState // This state receives draw calls even when a substate is active. this.persistentDraw = true; + // Stop any pre-existing music. + if (FlxG.sound.music != null) + FlxG.sound.music.stop(); + + // Prepare the current song to be played. + FlxG.sound.cache(Paths.inst(currentSong.song)); + FlxG.sound.cache(Paths.voices(currentSong.song)); + Conductor.songPosition = -5000; + // Initialize stage stuff. + initCameras(); + if (currentSong == null) currentSong = SongLoad.loadFromJson('tutorial'); Conductor.mapBPMChanges(currentSong); Conductor.changeBPM(currentSong.bpm); - // dialogue init shit, just for week 5 really (for now...?) switch (currentSong.song.toLowerCase()) { case 'senpai': @@ -273,16 +304,6 @@ class PlayState extends MusicBeatState dialogue = CoolUtil.coolTextFile(Paths.txt('songs/thorns/thornsDialogue')); } - // Initialize stage stuff. - initCameras(); - - #if discord_rpc - initDiscord(); - #end - - initStage(); - initCharacters(); - if (dialogue != null) { doof = new DialogueBox(false, dialogue); @@ -291,7 +312,13 @@ class PlayState extends MusicBeatState doof.cameras = [camHUD]; } - // fake notesplash cache type deal so that it loads in the graphic? + // Once the song is loaded, we can continue and initialize the stage. + + initStage(); + initCharacters(); + #if discord_rpc + initDiscord(); + #end comboPopUps = new PopUpStuff(); add(comboPopUps); @@ -370,42 +397,15 @@ class PlayState extends MusicBeatState switch (currentSong.song.toLowerCase()) { case "winter-horrorland": - var blackScreen:FlxSprite = new FlxSprite(0, 0).makeGraphic(Std.int(FlxG.width * 2), Std.int(FlxG.height * 2), FlxColor.BLACK); - add(blackScreen); - blackScreen.scrollFactor.set(); - camHUD.visible = false; - - new FlxTimer().start(0.1, function(tmr:FlxTimer) - { - remove(blackScreen); - FlxG.sound.play(Paths.sound('Lights_Turn_On')); - cameraFollowPoint.y = -2050; - cameraFollowPoint.x += 200; - FlxG.camera.focusOn(cameraFollowPoint.getPosition()); - FlxG.camera.zoom = 1.5; - - new FlxTimer().start(0.8, function(tmr:FlxTimer) - { - camHUD.visible = true; - remove(blackScreen); - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, 2.5, { - ease: FlxEase.quadInOut, - onComplete: function(twn:FlxTween) - { - startCountdown(); - } - }); - }); - }); + VanillaCutscenes.playHorrorStartCutscene(); case 'senpai' | 'roses' | 'thorns': schoolIntro(doof); // doof is assumed to be non-null, lol! case 'ugh': - ughIntro(); + VanillaCutscenes.playUghCutscene(); case 'stress': - stressIntro(); + VanillaCutscenes.playStressCutscene(); case 'guns': - gunsIntro(); - + VanillaCutscenes.playGunsCutscene(); default: startCountdown(); } @@ -414,24 +414,19 @@ class PlayState extends MusicBeatState { startCountdown(); } + + this.leftWatermarkText.text = '${currentSong.song.toUpperCase()} - ${SongLoad.curDiff.toUpperCase()}'; + this.rightWatermarkText.text = Constants.VERSION; } /** - * Initializes the position of the camera. + * Initializes the game and HUD cameras. */ function initCameras() { - defaultCamZoom = FlxCamera.defaultZoom; + // Configure the default camera zoom level. + defaultCameraZoom = FlxCamera.defaultZoom * 1.05; - defaultCamZoom *= 1.05; - - if (FlxG.sound.music != null) - FlxG.sound.music.stop(); - - FlxG.sound.cache(Paths.inst(currentSong.song)); - FlxG.sound.cache(Paths.voices(currentSong.song)); - - // var gameCam:FlxCamera = FlxG.camera; camGame = new SwagCamera(); camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; @@ -590,99 +585,6 @@ class PlayState extends MusicBeatState } } - function ughIntro() - { - inCutscene = true; - - var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); - blackShit.scrollFactor.set(); - add(blackShit); - - #if html5 - var vid:FlxVideo = new FlxVideo('music/ughCutscene.mp4'); - vid.finishCallback = function() - { - remove(blackShit); - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - }; - #else - remove(blackShit); - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - #end - - FlxG.camera.zoom = defaultCamZoom * 1.2; - - cameraFollowPoint.x += 100; - cameraFollowPoint.y += 100; - - /* - FlxG.sound.playMusic(Paths.music('DISTORTO'), 0); - FlxG.sound.music.fadeIn(5, 0, 0.5); - - dad.visible = false; - var tankCutscene:TankCutscene = new TankCutscene(-20, 320); - tankCutscene.frames = Paths.getSparrowAtlas('cutsceneStuff/tankTalkSong1'); - tankCutscene.animation.addByPrefix('wellWell', 'TANK TALK 1 P1', 24, false); - tankCutscene.animation.addByPrefix('killYou', 'TANK TALK 1 P2', 24, false); - tankCutscene.animation.play('wellWell'); - tankCutscene.antialiasing = true; - gfCutsceneLayer.add(tankCutscene); - - camHUD.visible = false; - - FlxG.camera.zoom *= 1.2; - camFollow.y += 100; - - tankCutscene.startSyncAudio = FlxG.sound.load(Paths.sound('wellWellWell')); - - new FlxTimer().start(3, function(tmr:FlxTimer) - { - camFollow.x += 800; - camFollow.y += 100; - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom * 1.2}, 0.27, {ease: FlxEase.quadInOut}); - - new FlxTimer().start(1.5, function(bep:FlxTimer) - { - boyfriend.playAnim('singUP'); - // play sound - FlxG.sound.play(Paths.sound('bfBeep'), function() - { - boyfriend.playAnim('idle'); - }); - }); - - new FlxTimer().start(3, function(swaggy:FlxTimer) - { - camFollow.x -= 800; - camFollow.y -= 100; - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom * 1.2}, 0.5, {ease: FlxEase.quadInOut}); - tankCutscene.animation.play('killYou'); - FlxG.sound.play(Paths.sound('killYou')); - new FlxTimer().start(6.1, function(swagasdga:FlxTimer) - { - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - - FlxG.sound.music.fadeOut((Conductor.crochet / 1000) * 5, 0); - - new FlxTimer().start((Conductor.crochet / 1000) * 5, function(money:FlxTimer) - { - dad.visible = true; - gfCutsceneLayer.remove(tankCutscene); - }); - - cameraMovement(); - - startCountdown(); - camHUD.visible = true; - }); - }); - });*/ - } - /** * Removes any references to the current stage, then clears the stage cache, * then reloads all the stages. @@ -734,122 +636,13 @@ class PlayState extends MusicBeatState ScriptEventDispatcher.callEvent(currentStage, event); // Apply camera zoom. - defaultCamZoom *= currentStage.camZoom; + defaultCameraZoom *= currentStage.camZoom; // Add the stage to the scene. this.add(currentStage); } } - function gunsIntro() - { - inCutscene = true; - - var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); - blackShit.scrollFactor.set(); - add(blackShit); - - #if html5 - var vid:FlxVideo = new FlxVideo('music/gunsCutscene.mp4'); - vid.finishCallback = function() - { - remove(blackShit); - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - }; - #else - remove(blackShit); - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - #end - - /* camFollow.setPosition(camPos.x, camPos.y); - - camHUD.visible = false; - - FlxG.sound.playMusic(Paths.music('DISTORTO'), 0); - FlxG.sound.music.fadeIn(5, 0, 0.5); - - camFollow.y += 100; - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom * 1.3}, 4, {ease: FlxEase.quadInOut}); - - dad.visible = false; - var tankCutscene:TankCutscene = new TankCutscene(20, 320); - tankCutscene.frames = Paths.getSparrowAtlas('cutsceneStuff/tankTalkSong2'); - tankCutscene.animation.addByPrefix('tankyguy', 'TANK TALK 2', 24, false); - tankCutscene.animation.play('tankyguy'); - tankCutscene.antialiasing = true; - gfCutsceneLayer.add(tankCutscene); // add(); - - tankCutscene.startSyncAudio = FlxG.sound.load(Paths.sound('tankSong2')); - - new FlxTimer().start(4.1, function(ugly:FlxTimer) - { - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom * 1.4}, 0.4, {ease: FlxEase.quadOut}); - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom * 1.3}, 0.7, {ease: FlxEase.quadInOut, startDelay: 0.45}); - - gf.playAnim('sad'); - }); - - new FlxTimer().start(11, function(tmr:FlxTimer) - { - FlxG.sound.music.fadeOut((Conductor.crochet / 1000) * 5, 0); - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet * 5) / 1000, {ease: FlxEase.quartIn}); - startCountdown(); - new FlxTimer().start((Conductor.crochet * 25) / 1000, function(daTim:FlxTimer) - { - dad.visible = true; - gfCutsceneLayer.remove(tankCutscene); - }); - - camHUD.visible = true; - });*/ - } - - /** - * [ - * [0, function(){blah;}], - * [4.6, function(){blah;}], - * [25.1, function(){blah;}], - * [30.7, function(){blah;}] - * ] - * SOMETHING LIKE THIS - */ - // var cutsceneFunctions:Array = []; - - function stressIntro() - { - inCutscene = true; - - var blackShit:FlxSprite = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); - blackShit.scrollFactor.set(); - add(blackShit); - - #if html5 - var vid:FlxVideo = new FlxVideo('music/stressCutscene.mp4'); - vid.finishCallback = function() - { - remove(blackShit); - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - }; - #else - remove(blackShit); - - FlxTween.tween(FlxG.camera, {zoom: defaultCamZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); - startCountdown(); - cameraMovement(); - #end - } - function initDiscord():Void { #if discord_rpc @@ -920,7 +713,7 @@ class PlayState extends MusicBeatState { if (dialogueBox != null) { - inCutscene = true; + isInCutscene = true; if (currentSong.song.toLowerCase() == 'thorns') { @@ -962,93 +755,6 @@ class PlayState extends MusicBeatState }); } - function startCountdown():Void - { - inCutscene = false; - camHUD.visible = true; - - buildStrumlines(); - - talking = false; - - restartCountdownTimer(); - } - - function restartCountdownTimer():Void - { - startedCountdown = true; - - Conductor.songPosition = 0; - Conductor.songPosition -= Conductor.crochet * 5; - - var swagCounter:Int = 0; - - startTimer.start(Conductor.crochet / 1000, function(tmr:FlxTimer) - { - // this just based on beatHit stuff but compact - if (swagCounter % gfSpeed == 0) - currentStage.getGirlfriend().dance(); - if (swagCounter % 2 == 0) - { - if (currentStage.getBoyfriend().animation != null) - { - if (!currentStage.getBoyfriend().animation.curAnim.name.startsWith("sing")) - currentStage.getBoyfriend().playAnim('idle'); - } - - if (currentStage.getDad().animation != null) - { - if (!currentStage.getDad().animation.curAnim.name.startsWith("sing")) - currentStage.getDad().dance(); - } - } - else if (currentStage.getDad().curCharacter == 'spooky' && !currentStage.getDad().animation.curAnim.name.startsWith("sing")) - currentStage.getDad().dance(); - if (generatedMusic) - activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING); - - var introSprPaths:Array = ["ready", "set", "go"]; - var altSuffix:String = ""; - - if (currentStageId.startsWith("school")) - { - altSuffix = '-pixel'; - introSprPaths = ['weeb/pixelUI/ready-pixel', 'weeb/pixelUI/set-pixel', 'weeb/pixelUI/date-pixel']; - } - - var introSndPaths:Array = [ - "intro3" + altSuffix, "intro2" + altSuffix, - "intro1" + altSuffix, "introGo" + altSuffix - ]; - - if (swagCounter > 0) - readySetGo(introSprPaths[swagCounter - 1]); - FlxG.sound.play(Paths.sound(introSndPaths[swagCounter]), 0.6); - - swagCounter += 1; - }, 4); - } - - function readySetGo(path:String):Void - { - var spr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(path)); - spr.scrollFactor.set(); - - if (currentStageId.startsWith('school')) - spr.setGraphicSize(Std.int(spr.width * Constants.PIXEL_ART_SCALE)); - - spr.updateHitbox(); - spr.screenCenter(); - add(spr); - FlxTween.tween(spr, {y: spr.y += 100, alpha: 0}, Conductor.crochet / 1000, { - ease: FlxEase.cubeInOut, - onComplete: function(twn:FlxTween) - { - spr.destroy(); - } - }); - } - function startSong():Void { startingSong = false; @@ -1244,13 +950,11 @@ class PlayState extends MusicBeatState vocals.pause(); var event:ScriptEvent = new ScriptEvent(ScriptEvent.SONG_RESET, false); - ScriptEventDispatcher.callEvent(currentStage, event); - ModuleHandler.callEvent(event); FlxG.sound.music.time = 0; regenNoteData(); // loads the note data from start health = 1; - restartCountdownTimer(); + Countdown.performCountdown(currentStageId.startsWith('school')); needsReset = false; } @@ -1265,9 +969,9 @@ class PlayState extends MusicBeatState // do this BEFORE super.update() so songPosition is accurate if (startingSong) { - if (startedCountdown) + if (isInCountdown) { - Conductor.songPosition += FlxG.elapsed * 1000; + Conductor.songPosition += elapsed * 1000; if (Conductor.songPosition >= 0) startSong(); } @@ -1278,7 +982,6 @@ class PlayState extends MusicBeatState Conductor.offset = -13; // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM! Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; // 20 is THE MILLISECONDS?? - // Conductor.songPosition += FlxG.elapsed * 1000; if (!isGamePaused) { @@ -1290,11 +993,8 @@ class PlayState extends MusicBeatState { songTime = (songTime + Conductor.songPosition) / 2; Conductor.lastSongPos = Conductor.songPosition; - // Conductor.songPosition += FlxG.elapsed * 1000; - // trace('MISSED FRAME'); } } - // Conductor.lastSongPos = FlxG.sound.music.time; } var androidPause:Bool = false; @@ -1303,7 +1003,7 @@ class PlayState extends MusicBeatState androidPause = FlxG.android.justPressed.BACK; #end - if ((controls.PAUSE || androidPause) && startedCountdown && mayPauseGame) + if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame) { persistentUpdate = false; persistentDraw = true; @@ -1390,7 +1090,7 @@ class PlayState extends MusicBeatState if (camZooming) { - FlxG.camera.zoom = FlxMath.lerp(defaultCamZoom, FlxG.camera.zoom, 0.95); + FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95); camHUD.zoom = FlxMath.lerp(1 * FlxCamera.defaultZoom, camHUD.zoom, 0.95); } @@ -1413,7 +1113,7 @@ class PlayState extends MusicBeatState } } - if (!inCutscene && !_exiting) + if (!isInCutscene && !_exiting) { // RESET = Quick Game Over Screen if (controls.RESET) @@ -1566,12 +1266,8 @@ class PlayState extends MusicBeatState // TODO: Why the hell is the noteMiss logic in two different places? if (daNote.tooLate) { - if (currentStage != null) - { - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, daNote, true); - ScriptEventDispatcher.callEvent(currentStage, event); - ModuleHandler.callEvent(event); - } + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, daNote, true); + dispatchEvent(event); health -= 0.0775; vocals.volume = 0; killCombo(); @@ -1587,16 +1283,10 @@ class PlayState extends MusicBeatState }); } - if (!inCutscene) + if (!isInCutscene) keyShit(); - var event:UpdateScriptEvent = new UpdateScriptEvent(elapsed); - if (currentStage != null) - { - // We're using Eric's stage handler. - ScriptEventDispatcher.callEvent(currentStage, event); - } - ModuleHandler.callEvent(event); + dispatchEvent(new UpdateScriptEvent(elapsed)); } function applyClipRect(daNote:Note):Void @@ -1723,7 +1413,7 @@ class PlayState extends MusicBeatState blackShit.scrollFactor.set(); add(blackShit); camHUD.visible = false; - inCutscene = true; + isInCutscene = true; FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() { @@ -1787,11 +1477,12 @@ class PlayState extends MusicBeatState health += healthMulti; // TODO: Redo note hit logic to make sure this always gets called - if (currentStage != null) + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, true); + dispatchEvent(event); + + if (event.eventCanceled) { - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, true); - ScriptEventDispatcher.callEvent(currentStage, event); - ModuleHandler.callEvent(event); + // TODO: Do a thing! } if (isSick) @@ -1959,6 +1650,8 @@ class PlayState extends MusicBeatState } } + if (currentStage == null) + return; if (currentStage.getBoyfriend().holdTimer > Conductor.stepCrochet * 4 * 0.001 && !holdArray.contains(true)) { if (currentStage.getBoyfriend().animation != null @@ -2043,13 +1736,7 @@ class PlayState extends MusicBeatState resyncVocals(); } - if (currentStage != null) - { - // We're using Eric's stage handler. The stage should know that a step has been hit. - var event:SongTimeScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep); - ScriptEventDispatcher.callEvent(currentStage, event); - ModuleHandler.callEvent(event); - } + dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep)); } override function beatHit() @@ -2087,30 +1774,28 @@ class PlayState extends MusicBeatState } } + // Make the health icons bump (the update function causes them to lerp back down). iconP1.setGraphicSize(Std.int(iconP1.width + 30)); iconP2.setGraphicSize(Std.int(iconP2.width + 30)); - iconP1.updateHitbox(); iconP2.updateHitbox(); - var song = SongLoad.getSong(); - var step = Math.floor(curStep / 16); - if (curBeat % 8 == 7 - && song[step].mustHitSection - && combo > 5 - && song.length > step + 1 // GK: this fixes an error on week 1 where song[step + 1] was null - && !song[step + 1].mustHitSection) - { - var animShit:ComboCounter = new ComboCounter(-100, 300, combo); - animShit.scrollFactor.set(0.6, 0.6); + // Make the characters dance on the beat + danceOnBeat(); - var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation + // Call any relevant event handlers. + dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep)); + } - new FlxTimer().start(((Conductor.crochet / 1000) * 1.25) - frameShit, function(tmr) - { - animShit.forceFinish(); - }); - } + /** + * Handles characters dancing to the beat of the current song. + * + * TODO: Move some of this logic into `Bopper.hx` + */ + public function danceOnBeat() + { + if (currentStage == null) + return; if (curBeat % gfSpeed == 0) currentStage.getGirlfriend().dance(); @@ -2142,16 +1827,11 @@ class PlayState extends MusicBeatState currentStage.getBoyfriend().playAnim('hey', true); currentStage.getDad().playAnim('cheer', true); } - - if (currentStage != null) - { - // We're using Eric's stage handler. The stage should know that a beat has been hit. - var event:SongTimeScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep); - ScriptEventDispatcher.callEvent(currentStage, event); - ModuleHandler.callEvent(event); - } } + /** + * Constructs the strumlines for each player. + */ function buildStrumlines():Void { var strumlineStyle:StrumlineStyle = NORMAL; @@ -2169,12 +1849,14 @@ class PlayState extends MusicBeatState playerStrumline = new Strumline(0, strumlineStyle, 4); playerStrumline.offset = new FlxPoint(50 + FlxG.width / 2, strumlineYPos); + // Set the z-index so they don't appear in front of notes. playerStrumline.zIndex = 100; add(playerStrumline); playerStrumline.cameras = [camHUD]; enemyStrumline = new Strumline(1, strumlineStyle, 4); enemyStrumline.offset = new FlxPoint(50, strumlineYPos); + // Set the z-index so they don't appear in front of notes. enemyStrumline.zIndex = 100; add(enemyStrumline); enemyStrumline.cameras = [camHUD]; @@ -2202,11 +1884,17 @@ class PlayState extends MusicBeatState vocals.pause(); } - // Pause the restart timer. - if (!startTimer.finished) - startTimer.active = false; + // Pause the countdown. + Countdown.pauseCountdown(); } + var event:ScriptEvent = new ScriptEvent(ScriptEvent.PAUSE, true); + + dispatchEvent(event); + + if (event.eventCanceled) + return; + super.openSubState(subState); } @@ -2221,8 +1909,8 @@ class PlayState extends MusicBeatState if (FlxG.sound.music != null && !startingSong) resyncVocals(); - if (!startTimer.finished) - startTimer.active = true; + // Resume the countdown. + Countdown.resumeCountdown(); #if discord_rpc if (startTimer.finished) @@ -2233,9 +1921,41 @@ class PlayState extends MusicBeatState #end } + var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true); + + dispatchEvent(event); + + if (event.eventCanceled) + return; + super.closeSubState(); } + /** + * Prepares to start the countdown. + * Ends any running cutscenes, creates the strumlines, and starts the countdown. + */ + function startCountdown():Void + { + isInCutscene = false; + camHUD.visible = true; + talking = false; + + buildStrumlines(); + + Countdown.performCountdown(currentStageId.startsWith('school')); + } + + override function dispatchEvent(event:ScriptEvent):Void + { + ScriptEventDispatcher.callEvent(currentStage, event); + + // TODO: Dispatch event to song script + // TODO: Dispatch events to character scripts + + super.dispatchEvent(event); + } + /** * Updates the position and contents of the score display. */ @@ -2259,7 +1979,7 @@ class PlayState extends MusicBeatState function resetCamera():Void { FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); - FlxG.camera.zoom = defaultCamZoom; + FlxG.camera.zoom = defaultCameraZoom; FlxG.camera.focusOn(cameraFollowPoint.getPosition()); } diff --git a/source/funkin/play/Scoring.hx b/source/funkin/play/Scoring.hx new file mode 100644 index 000000000..7806cd8e4 --- /dev/null +++ b/source/funkin/play/Scoring.hx @@ -0,0 +1,6 @@ +package funkin.play; + +/** + * A static class which holds any functions related to scoring. + */ +class Scoring {} diff --git a/source/funkin/play/Strumline.hx b/source/funkin/play/Strumline.hx index b583b9aab..4254acb28 100644 --- a/source/funkin/play/Strumline.hx +++ b/source/funkin/play/Strumline.hx @@ -169,7 +169,7 @@ class Strumline extends FlxTypedGroup arrow.x += Note.swagWidth * arrow.ID; - // TODO: Seems weird that these are hardcoded... + // TODO: Seems weird that these are hardcoded like this... no XML? switch (Math.abs(arrow.ID)) { case 0: diff --git a/source/funkin/play/VanillaCutscenes.hx b/source/funkin/play/VanillaCutscenes.hx new file mode 100644 index 000000000..2d3d059a6 --- /dev/null +++ b/source/funkin/play/VanillaCutscenes.hx @@ -0,0 +1,102 @@ +package funkin.play; + +import flixel.util.FlxTimer; +import flixel.tweens.FlxTween; +import flixel.tweens.FlxTween; +import flixel.tweens.FlxEase; +import flixel.util.FlxColor; +import flixel.FlxSprite; + +/** + * Static methods for playing cutscenes in the PlayState. + * TODO: Softcode this shit!!!!!1! + */ +class VanillaCutscenes +{ + public static function playUghCutscene():Void + { + playVideoCutscene('music/ughCutscene.mp4'); + } + + public static function playGunsCutscene():Void + { + playVideoCutscene('music/gunsCutscene.mp4'); + } + + public static function playStressCutscene():Void + { + playVideoCutscene('music/stressCutscene.mp4'); + } + + static var blackScreen:FlxSprite; + + /** + * Plays a cutscene from a video file, then starts the countdown once the video is done. + * TODO: Cutscene is currently skipped on native platforms. + */ + static function playVideoCutscene(path:String):Void + { + PlayState.isInCutscene = true; + PlayState.instance.camHUD.visible = false; + + blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); + blackScreen.scrollFactor.set(0, 0); + PlayState.instance.add(blackScreen); + + #if html5 + vid:FlxVideo = new FlxVideo(path); + vid.finishCallback = finishVideoCutscene(); + #else + finishVideoCutscene(); + #end + } + + /** + * Does the cleanup to start the countdown after the video is done. + * Gets called immediately if the video can't be played. + */ + static function finishVideoCutscene():Void + { + PlayState.instance.remove(blackScreen); + blackScreen = null; + + FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut}); + Countdown.performCountdown(false); + @:privateAccess + PlayState.instance.cameraMovement(); + } + + public static function playHorrorStartCutscene() + { + PlayState.isInCutscene = true; + PlayState.instance.camHUD.visible = false; + + blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); + blackScreen.scrollFactor.set(0, 0); + PlayState.instance.add(blackScreen); + + new FlxTimer().start(0.1, function(tmr:FlxTimer) + { + PlayState.instance.remove(blackScreen); + FlxG.sound.play(Paths.sound('Lights_Turn_On')); + PlayState.instance.cameraFollowPoint.y = -2050; + PlayState.instance.cameraFollowPoint.x += 200; + FlxG.camera.focusOn(PlayState.instance.cameraFollowPoint.getPosition()); + FlxG.camera.zoom = 1.5; + + new FlxTimer().start(0.8, function(tmr:FlxTimer) + { + PlayState.instance.camHUD.visible = true; + PlayState.instance.remove(blackScreen); + blackScreen = null; + FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, 2.5, { + ease: FlxEase.quadInOut, + onComplete: function(twn:FlxTween) + { + Countdown.performCountdown(false); + } + }); + }); + }); + } +} diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index d73b38c56..ca1d26ce0 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -154,4 +154,8 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass public function onCountdownStart(event:CountdownScriptEvent) {} public function onCountdownStep(event:CountdownScriptEvent) {} + + public function onCountdownEnd(event:CountdownScriptEvent) {} + + public function onSongLoaded(eent:SongLoadScriptEvent) {} } diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index b5d77b8f0..6bbc48282 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -360,7 +360,10 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte // Override me in your scripted stage to perform custom behavior! // Make sure to call super.onBeatHit(curBeat) if you want to keep the boppers dancing. - ScriptEventDispatcher.callEventOnAllTargets(cast boppers, event); + for (bopper in boppers) + { + ScriptEventDispatcher.callEvent(bopper, event); + } } public function onScriptEvent(event:ScriptEvent) {} @@ -386,6 +389,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte public function onCountdownStep(event:CountdownScriptEvent) {} + public function onCountdownEnd(event:CountdownScriptEvent) {} + public function onKeyDown(event:KeyboardInputScriptEvent) {} public function onKeyUp(event:KeyboardInputScriptEvent) {} @@ -398,4 +403,6 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte public function onNoteHit(event:NoteScriptEvent) {} public function onNoteMiss(event:NoteScriptEvent) {} + + public function onSongLoaded(eent:SongLoadScriptEvent) {} } diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index d3e307d1d..f6c3b204f 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -1,5 +1,6 @@ package funkin.util; +import lime.app.Application; import flixel.util.FlxColor; class Constants @@ -11,4 +12,14 @@ class Constants public static final HEALTH_BAR_RED:FlxColor = 0xFFFF0000; public static final HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33; + + public static final COUNTDOWN_VOLUME = 0.6; + + public static final VERSION_SUFFIX = ' PROTOTYPE'; + public static var VERSION(get, null):String; + + static function get_VERSION():String + { + return 'v${Application.current.meta.get('version')}' + VERSION_SUFFIX; + } } diff --git a/source/funkin/util/IteratorTools.hx b/source/funkin/util/IteratorTools.hx new file mode 100644 index 000000000..259b75a09 --- /dev/null +++ b/source/funkin/util/IteratorTools.hx @@ -0,0 +1,16 @@ +package funkin.util; + +/** + * A static extension which provides utility functions for Iterators. + * + * For example, add `using IteratorTools` then call `iterator.array()`. + * + * @see https://haxe.org/manual/lf-static-extension.html + */ +class IteratorTools +{ + public static function array(iterator:Iterator):Array + { + return [for (i in iterator) i]; + } +} diff --git a/source/funkin/util/macro/HookableMacro.hx b/source/funkin/util/macro/HookableMacro.hx new file mode 100644 index 000000000..93d9545af --- /dev/null +++ b/source/funkin/util/macro/HookableMacro.hx @@ -0,0 +1,3 @@ +package funkin.util.macro; + +class HookableMacro {} diff --git a/source/modding/module/Module.hx b/source/modding/module/Module.hx deleted file mode 100644 index 51964a82b..000000000 --- a/source/modding/module/Module.hx +++ /dev/null @@ -1,80 +0,0 @@ -package modding.module; - -import modding.module.events.ModuleEvent; - -/** - * A module is an interface which provides for scripts to perform custom behavior - * without requiring a specific context. - * - * You may have the module active at all times, or only when another script enables it. - */ -class Module -{ - /** - * Whether the module is currently active. - */ - public var active(default, set):Bool = false; - - function set_active(value:Bool):Bool - { - this.active = value; - return value; - } - - public var moduleId(default, null):String = 'UNKNOWN'; - - /** - * Called when the module is initialized. - * It may not be safe to reference other modules here since they may not be loaded yet. - * - * @param startActive Whether to start with the module active. - * If false, the module will be inactive and must be enabled by another script, - * such as a stage or another module. - */ - public function new(moduleId:String, startActive:Bool) - { - this.moduleId = moduleId; - this.active = startActive; - } - - /** - * Called after the module was initialized, but before anything else. - * Other modules may still be uninitialized at this stage. - */ - public function onPostCreate() {} - - /** - * Called at the beginning of a song, before the countdown begins. - */ - public function onSongStart() {} - - /** - * Called at the end of a song, after the song fades out. - */ - public function onSongEnd() {} - - /** - * Called at the beginning of the countdown. - */ - public function onBeginCountdown(event:ModuleEvent) {} - - /** - * Called four times per section of a song. - */ - public function onSongBeat() {} - - /** - * Called sixteen times per section of a song. - */ - public function onSongStep() {} - - /** - * Called at the end of the `update()` loop. - * Be careful! Using this can have a significant impact on performance. - */ - public function onUpdate(event:UpdateModuleEvent) {} - - public function onNoteHit() {} - - public function onNoteMiss() {} -} diff --git a/source/modding/module/ModuleHandler.hx b/source/modding/module/ModuleHandler.hx deleted file mode 100644 index 1d6179951..000000000 --- a/source/modding/module/ModuleHandler.hx +++ /dev/null @@ -1,79 +0,0 @@ -package modding.module; - -import modding.module.ModuleEvent; -import modding.module.ModuleEvent.UpdateModuleEvent; - -class ModuleHandler -{ - static final moduleCache:Map = new Map(); - - /** - * Whether modules start active by default. - */ - static final DEFAULT_STARTACTIVE:Bool = true; - - /** - * Parses and preloads the game's stage data and scripts when the game starts. - * - * If you want to force stages to be reloaded, you can just call this function again. - */ - public static function loadModuleCache():Void - { - // Clear any stages that are cached if there were any. - clearModuleCache(); - trace("[MODULEHANDLER] Loading module cache..."); - - var scriptedModuleClassNames:Array = ScriptedModule.listScriptClasses(); - trace(' Instantiating ${scriptedModuleClassNames.length} modules...'); - for (moduleCls in scriptedModuleClassNames) - { - var module:Module = ScriptedModule.init(moduleCls, moduleCls, DEFAULT_STARTACTIVE); - if (module != null) - { - trace(' Loaded module: ${moduleCls}'); - - // Then store it. - moduleCache.set(module.moduleId, module); - } - else - { - trace(' Failed to instantiate module: ${moduleCls}'); - } - } - - trace("[MODULEHANDLER] Module cache loaded."); - - call_onPostCreate(); - } - - static function clearModuleCache():Void - { - if (moduleCache != null) - { - moduleCache.clear(); - } - } - - /** - * Calls onPostCreate on all modules. - */ - public static function call_onPostCreate():Void - { - for (module in moduleCache) - { - module.onPostCreate(); - } - } - - /** - * Calls onUpdate on all modules. - */ - public static function call_onUpdate(elapsed:Float):Void - { - var event = new UpdateModuleEvent(elapsed); - for (module in moduleCache) - { - module.onUpdate(event); - } - } -} diff --git a/source/modding/module/ScriptedModule.hx b/source/modding/module/ScriptedModule.hx deleted file mode 100644 index f311e08ca..000000000 --- a/source/modding/module/ScriptedModule.hx +++ /dev/null @@ -1,9 +0,0 @@ -package modding.module; - -import modding.IHook; - -@:hscriptClass -class ScriptedModule extends Module implements IHook -{ - // No body needed for this class, it's magic ;) -} diff --git a/source/modding/module/events/ModuleEvent.hx b/source/modding/module/events/ModuleEvent.hx deleted file mode 100644 index 892b28ab4..000000000 --- a/source/modding/module/events/ModuleEvent.hx +++ /dev/null @@ -1,128 +0,0 @@ -package modding.module; - -import openfl.events.EventType; - -typedef ModuleEventType = EventType; - -class ModuleEvent -{ - public static inline var SONG_START:ModuleEventType = "SONG_START"; - public static inline var SONG_END:ModuleEventType = "SONG_END"; - public static inline var COUNTDOWN_BEGIN:ModuleEventType = "COUNTDOWN_BEGIN"; - public static inline var COUNTDOWN_STEP:ModuleEventType = "COUNTDOWN_STEP"; - public static inline var SONG_BEAT_HIT:ModuleEventType = "SONG_BEAT_HIT"; - public static inline var SONG_STEP_HIT:ModuleEventType = "SONG_STEP_HIT"; - - public static inline var PAUSE:ModuleEventType = "PAUSE"; - public static inline var RESUME:ModuleEventType = "RESUME"; - public static inline var UPDATE:ModuleEventType = "UPDATE"; - - /** - * Note hit success, health gained, note data, player vs opponent, etc - * are all provided as event parameters. - * - * Event is cancelable, which will cause the press to be ignored and the note to be missed. - */ - public static inline var NOTE_HIT:ModuleEventType = "NOTE_HIT"; - - public static inline var NOTE_MISS:ModuleEventType = "NOTE_MISS"; - public static inline var GAME_OVER:ModuleEventType = "GAME_OVER"; - public static inline var RETRY:ModuleEventType = "RETRY"; - - /** - * If true, the behavior associated with this event can be prevented. - * For example, cancelling COUNTDOWN_BEGIN 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):ModuleEventType; - - @:noCompletion private var __eventCanceled:Bool; - @:noCompletion private var __shouldPropagate:Bool; - - public function new(type:ModuleEventType, 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_BEGIN will prevent the countdown from starting. - */ - public function cancelEvent():Void - { - if (cancelable) - { - __eventCanceled = true; - } - } - - /** - * Call this function to stop any other modules from receiving the event. - */ - public function stopPropagation():Void - { - __shouldPropagate = false; - } - - public function toString():String - { - return 'ModuleEvent(type=$type, cancelable=$cancelable)'; - } -} - -/** - * SPECIFIC EVENTS - */ -/** - * An event that is fired associated with a specific note. - */ -class NoteModuleEvent extends ModuleEvent -{ - /** - * The note associated with this event. - * You cannot replace it, but you can edit it. - */ - public var note(default, null):Note; - - public function new(type:ModuleEventType, note:Note, cancelable:Bool = false):Void - { - super(type, cancelable); - this.note = note; - } - - public override function toString():String - { - return 'NoteModuleEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ')'; - } -} - -/** - * An event that is fired during the update loop. - */ -class UpdateModuleEvent extends ModuleEvent -{ - /** - * 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(ModuleEvent.UPDATE, false); - this.elapsed = elapsed; - } - - public override function toString():String - { - return 'UpdateModuleEvent(elapsed=$elapsed)'; - } -} diff --git a/source/modding/module/events/NoteModuleEvent.hx b/source/modding/module/events/NoteModuleEvent.hx deleted file mode 100644 index 3e235064f..000000000 --- a/source/modding/module/events/NoteModuleEvent.hx +++ /dev/null @@ -1 +0,0 @@ -package modding.module.events;