mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-06-17 23:21:43 +00:00
Scripted modules are stable but missing some features.
This commit is contained in:
parent
49b2907a30
commit
0cc4f8a982
6
example_mods/.gitignore
vendored
6
example_mods/.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
./tricky
|
||||
./enaSkin
|
||||
# Exclude any experimental mods that my have been added.
|
||||
/*
|
||||
!introMod
|
||||
!testing123
|
6
example_mods/introMod/README.md
Normal file
6
example_mods/introMod/README.md
Normal file
|
@ -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.
|
|
@ -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);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,12 +48,12 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
{
|
||||
// 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<FlxSprite>
|
|||
|
||||
avgVel *= 10000000;
|
||||
|
||||
volumes[i] += avgVel - (FlxG.elapsed * (volumes[i] * 50));
|
||||
volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
|
||||
|
||||
var animFrame:Int = Std.int(volumes[i]);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Note>;
|
||||
|
||||
public var id(default, null):String;
|
||||
|
||||
public var difficulty(default, null):String;
|
||||
|
||||
function set_notes(notes:Array<Note>):Array<Note>
|
||||
{
|
||||
this.notes = notes;
|
||||
return this.notes;
|
||||
}
|
||||
|
||||
public function new(id:String, difficulty:String, notes:Array<Note>):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)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IScriptedClass>, event:ScriptEvent):Void
|
||||
{
|
||||
if (targets == null || event == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Std.isOfType(targets, Array))
|
||||
{
|
||||
var t = cast(targets, Array<Dynamic>);
|
||||
if (t.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (target in targets)
|
||||
{
|
||||
var t:IScriptedClass = cast target;
|
||||
if (t == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
callEvent(t, event);
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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<String, Module> = new Map<String, Module>();
|
||||
|
||||
/**
|
||||
* Whether modules start active by default.
|
||||
*/
|
||||
static final DEFAULT_STARTACTIVE:Bool = true;
|
||||
static var modulePriorityOrder:Array<String> = [];
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Note>;
|
||||
|
||||
/**
|
||||
* 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<String>;
|
||||
var startedCountdown:Bool = false;
|
||||
var talking:Bool = true;
|
||||
var songScore:Int = 0;
|
||||
var doof:DialogueBox;
|
||||
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
|
||||
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<Dynamic> = [];
|
||||
|
||||
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<String> = ["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<String> = [
|
||||
"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());
|
||||
}
|
||||
|
||||
|
|
6
source/funkin/play/Scoring.hx
Normal file
6
source/funkin/play/Scoring.hx
Normal file
|
@ -0,0 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
/**
|
||||
* A static class which holds any functions related to scoring.
|
||||
*/
|
||||
class Scoring {}
|
|
@ -169,7 +169,7 @@ class Strumline extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
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:
|
||||
|
|
102
source/funkin/play/VanillaCutscenes.hx
Normal file
102
source/funkin/play/VanillaCutscenes.hx
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
16
source/funkin/util/IteratorTools.hx
Normal file
16
source/funkin/util/IteratorTools.hx
Normal file
|
@ -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<T>(iterator:Iterator<T>):Array<T>
|
||||
{
|
||||
return [for (i in iterator) i];
|
||||
}
|
||||
}
|
3
source/funkin/util/macro/HookableMacro.hx
Normal file
3
source/funkin/util/macro/HookableMacro.hx
Normal file
|
@ -0,0 +1,3 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
class HookableMacro {}
|
|
@ -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() {}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package modding.module;
|
||||
|
||||
import modding.module.ModuleEvent;
|
||||
import modding.module.ModuleEvent.UpdateModuleEvent;
|
||||
|
||||
class ModuleHandler
|
||||
{
|
||||
static final moduleCache:Map<String, Module> = new Map<String, Module>();
|
||||
|
||||
/**
|
||||
* 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<String> = 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ;)
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package modding.module;
|
||||
|
||||
import openfl.events.EventType;
|
||||
|
||||
typedef ModuleEventType = EventType<ModuleEvent>;
|
||||
|
||||
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)';
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package modding.module.events;
|
Loading…
Reference in a new issue