mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-21 01:19:26 +00:00
Scripted module and script event systems, reworked strumlines.
This commit is contained in:
parent
fe109f878f
commit
49b2907a30
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.Section.SwagSection;
|
||||
|
@ -128,7 +129,7 @@ class Character extends FlxSprite
|
|||
|
||||
playAnim('danceRight');
|
||||
|
||||
setGraphicSize(Std.int(width * PlayState.daPixelZoom));
|
||||
setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE));
|
||||
updateHitbox();
|
||||
antialiasing = false;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.text.FlxTypeText;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
|
@ -38,7 +39,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
super();
|
||||
|
||||
switch (PlayState.SONG.song.toLowerCase())
|
||||
switch (PlayState.currentSong.song.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
|
||||
|
@ -63,7 +64,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitLeft = new FlxSprite(-20, 40);
|
||||
portraitLeft.frames = Paths.getSparrowAtlas('weeb/senpaiPortrait');
|
||||
portraitLeft.animation.addByPrefix('enter', 'Senpai Portrait Enter', 24, false);
|
||||
portraitLeft.setGraphicSize(Std.int(portraitLeft.width * PlayState.daPixelZoom * 0.9));
|
||||
portraitLeft.setGraphicSize(Std.int(portraitLeft.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
portraitLeft.updateHitbox();
|
||||
portraitLeft.scrollFactor.set();
|
||||
add(portraitLeft);
|
||||
|
@ -72,7 +73,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitRight = new FlxSprite(0, 40);
|
||||
portraitRight.frames = Paths.getSparrowAtlas('weeb/bfPortrait');
|
||||
portraitRight.animation.addByPrefix('enter', 'Boyfriend portrait enter', 24, false);
|
||||
portraitRight.setGraphicSize(Std.int(portraitRight.width * PlayState.daPixelZoom * 0.9));
|
||||
portraitRight.setGraphicSize(Std.int(portraitRight.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
portraitRight.updateHitbox();
|
||||
portraitRight.scrollFactor.set();
|
||||
add(portraitRight);
|
||||
|
@ -81,7 +82,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
box = new FlxSprite(-20, 45);
|
||||
|
||||
var hasDialog = false;
|
||||
switch (PlayState.SONG.song.toLowerCase())
|
||||
switch (PlayState.currentSong.song.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
hasDialog = true;
|
||||
|
@ -113,7 +114,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
return;
|
||||
|
||||
box.animation.play('normalOpen');
|
||||
box.setGraphicSize(Std.int(box.width * PlayState.daPixelZoom * 0.9));
|
||||
box.setGraphicSize(Std.int(box.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
box.updateHitbox();
|
||||
add(box);
|
||||
|
||||
|
@ -121,7 +122,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitLeft.screenCenter(X);
|
||||
|
||||
handSelect = new FlxSprite(1042, 590).loadGraphic(Paths.image('weeb/pixelUI/hand_textbox'));
|
||||
handSelect.setGraphicSize(Std.int(handSelect.width * PlayState.daPixelZoom * 0.9));
|
||||
handSelect.setGraphicSize(Std.int(handSelect.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
handSelect.updateHitbox();
|
||||
handSelect.visible = false;
|
||||
add(handSelect);
|
||||
|
@ -154,9 +155,9 @@ class DialogueBox extends FlxSpriteGroup
|
|||
override function update(elapsed:Float)
|
||||
{
|
||||
// HARD CODING CUZ IM STUPDI
|
||||
if (PlayState.SONG.song.toLowerCase() == 'roses')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'roses')
|
||||
portraitLeft.visible = false;
|
||||
if (PlayState.SONG.song.toLowerCase() == 'thorns')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'thorns')
|
||||
{
|
||||
portraitLeft.color = FlxColor.BLACK;
|
||||
swagDialogue.color = FlxColor.WHITE;
|
||||
|
@ -192,7 +193,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
isEnding = true;
|
||||
|
||||
if (PlayState.SONG.song.toLowerCase() == 'senpai' || PlayState.SONG.song.toLowerCase() == 'thorns')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'senpai' || PlayState.currentSong.song.toLowerCase() == 'thorns')
|
||||
FlxG.sound.music.fadeOut(2.2, 0);
|
||||
|
||||
new FlxTimer().start(0.2, function(tmr:FlxTimer)
|
||||
|
|
|
@ -538,7 +538,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
curDifficulty = 1;
|
||||
}*/
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
||||
PlayState.isStoryMode = false;
|
||||
PlayState.storyDifficulty = curDifficulty;
|
||||
// SongLoad.curDiff = Highscore.formatSong()
|
||||
|
|
|
@ -20,12 +20,12 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
|
||||
var gameOverMusic:FlxSound;
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
public function new()
|
||||
{
|
||||
gameOverMusic = new FlxSound();
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
|
||||
var daStage = PlayState.curStageId;
|
||||
var daStage = PlayState.instance.currentStageId;
|
||||
var daBf:String = '';
|
||||
switch (daStage)
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
daBf = 'bf';
|
||||
}
|
||||
|
||||
var daSong = PlayState.SONG.song.toLowerCase();
|
||||
var daSong = PlayState.currentSong.song.toLowerCase();
|
||||
|
||||
switch (daSong)
|
||||
{
|
||||
|
@ -48,7 +48,9 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
|
||||
Conductor.songPosition = 0;
|
||||
|
||||
bf = new Boyfriend(x, y, daBf);
|
||||
var bfXPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().x;
|
||||
var bfYPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().y;
|
||||
bf = new Boyfriend(bfXPos, bfYPos, daBf);
|
||||
add(bf);
|
||||
|
||||
camFollow = new FlxObject(bf.getGraphicMidpoint().x, bf.getGraphicMidpoint().y, 1, 1);
|
||||
|
@ -57,7 +59,7 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + stageSuffix));
|
||||
// Conductor.changeBPM(100);
|
||||
|
||||
switch (PlayState.SONG.player1)
|
||||
switch (PlayState.currentSong.player1)
|
||||
{
|
||||
case 'pico':
|
||||
stageSuffix = 'Pico';
|
||||
|
|
|
@ -37,7 +37,7 @@ class HealthIcon extends FlxSprite
|
|||
if (isOldIcon)
|
||||
changeIcon('bf-old');
|
||||
else
|
||||
changeIcon(PlayState.SONG.player1);
|
||||
changeIcon(PlayState.currentSong.player1);
|
||||
}
|
||||
|
||||
var pixelArrayFunny:Array<String> = CoolUtil.coolTextFile(Paths.file('images/icons/pixelIcons.txt'));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.charting.ChartingState;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
|
@ -122,6 +123,8 @@ class InitState extends FlxTransitionableState
|
|||
|
||||
StageDataParser.loadStageCache();
|
||||
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
#if song
|
||||
var song = getSong();
|
||||
|
||||
|
@ -186,7 +189,7 @@ class InitState extends FlxTransitionableState
|
|||
{
|
||||
var dif = getDif();
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(song, song);
|
||||
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
||||
PlayState.isStoryMode = isStoryMode;
|
||||
PlayState.storyDifficulty = dif;
|
||||
SongLoad.curDiff = switch (dif)
|
||||
|
|
|
@ -57,9 +57,9 @@ class LoadingState extends MusicBeatState
|
|||
callbacks = new MultiCallback(onLoad);
|
||||
var introComplete = callbacks.add("introComplete");
|
||||
checkLoadSong(getSongPath());
|
||||
if (PlayState.SONG.needsVoices)
|
||||
if (PlayState.currentSong.needsVoices)
|
||||
{
|
||||
var files = PlayState.SONG.voiceList;
|
||||
var files = PlayState.currentSong.voiceList;
|
||||
|
||||
if (files == null)
|
||||
files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally
|
||||
|
@ -173,12 +173,12 @@ class LoadingState extends MusicBeatState
|
|||
|
||||
static function getSongPath()
|
||||
{
|
||||
return Paths.inst(PlayState.SONG.song);
|
||||
return Paths.inst(PlayState.currentSong.song);
|
||||
}
|
||||
|
||||
static function getVocalPath(?suffix:String)
|
||||
{
|
||||
return Paths.voices(PlayState.SONG.song, suffix);
|
||||
return Paths.voices(PlayState.currentSong.song, suffix);
|
||||
}
|
||||
|
||||
inline static public function loadAndSwitchState(target:FlxState, stopMusic = false)
|
||||
|
@ -198,7 +198,7 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
#if NO_PRELOAD_ALL
|
||||
var loaded = isSoundLoaded(getSongPath())
|
||||
&& (!PlayState.SONG.needsVoices || isSoundLoaded(getVocalPath()))
|
||||
&& (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath()))
|
||||
&& isLibraryLoaded("shared");
|
||||
|
||||
if (!loaded)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.NGio;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -272,6 +274,8 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
|
@ -307,7 +311,8 @@ class MainMenuState extends MusicBeatState
|
|||
FlxG.switchState(new TitleState());
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
var event:UpdateScriptEvent = new UpdateScriptEvent(elapsed);
|
||||
ModuleHandler.callEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
|
@ -109,10 +110,8 @@ class Note extends FlxSprite
|
|||
|
||||
data.noteData = noteData;
|
||||
|
||||
var daStage:String = PlayState.curStageId;
|
||||
|
||||
// TODO: Make this logic more generic
|
||||
switch (daStage)
|
||||
switch (PlayState.instance.currentStageId)
|
||||
{
|
||||
case 'school' | 'schoolEvil':
|
||||
loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
@ -137,7 +136,7 @@ class Note extends FlxSprite
|
|||
animation.add('bluehold', [1]);
|
||||
}
|
||||
|
||||
setGraphicSize(Std.int(width * PlayState.daPixelZoom));
|
||||
setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE));
|
||||
updateHitbox();
|
||||
|
||||
default:
|
||||
|
@ -194,7 +193,7 @@ class Note extends FlxSprite
|
|||
|
||||
x -= width / 2;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
x += 30;
|
||||
|
||||
if (prevNote.isSustainNote)
|
||||
|
|
|
@ -51,7 +51,7 @@ class PauseSubState extends MusicBeatSubstate
|
|||
add(bg);
|
||||
|
||||
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
|
||||
levelInfo.text += PlayState.SONG.song;
|
||||
levelInfo.text += PlayState.currentSong.song;
|
||||
levelInfo.scrollFactor.set();
|
||||
levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
|
||||
levelInfo.updateHitbox();
|
||||
|
@ -76,7 +76,7 @@ class PauseSubState extends MusicBeatSubstate
|
|||
practiceText.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
practiceText.updateHitbox();
|
||||
practiceText.x = FlxG.width - (practiceText.width + 20);
|
||||
practiceText.visible = PlayState.practiceMode;
|
||||
practiceText.visible = PlayState.isPracticeMode;
|
||||
add(practiceText);
|
||||
|
||||
levelDifficulty.alpha = 0;
|
||||
|
@ -157,7 +157,7 @@ class PauseSubState extends MusicBeatSubstate
|
|||
case "Resume":
|
||||
close();
|
||||
case "EASY" | 'NORMAL' | "HARD":
|
||||
PlayState.SONG = SongLoad.loadFromJson(PlayState.SONG.song.toLowerCase(), PlayState.SONG.song.toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
|
||||
SongLoad.curDiff = daSelected.toLowerCase();
|
||||
|
||||
PlayState.storyDifficulty = curSelected;
|
||||
|
@ -167,8 +167,8 @@ class PauseSubState extends MusicBeatSubstate
|
|||
close();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
PlayState.practiceMode = !PlayState.practiceMode;
|
||||
practiceText.visible = PlayState.practiceMode;
|
||||
PlayState.isPracticeMode = !PlayState.isPracticeMode;
|
||||
practiceText.visible = PlayState.isPracticeMode;
|
||||
|
||||
case 'Change Difficulty':
|
||||
menuItems = difficultyChoices;
|
||||
|
|
|
@ -311,7 +311,7 @@ class StoryMenuState extends MusicBeatState
|
|||
PlayState.isStoryMode = true;
|
||||
selectedWeek = true;
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
||||
PlayState.storyWeek = curWeek;
|
||||
PlayState.campaignScore = 0;
|
||||
|
||||
|
|
|
@ -122,9 +122,9 @@ class ChartingState extends MusicBeatState
|
|||
curRenderedNotes = new FlxTypedGroup<Note>();
|
||||
curRenderedSustains = new FlxTypedGroup<FlxSprite>();
|
||||
|
||||
if (PlayState.SONG != null)
|
||||
if (PlayState.currentSong != null)
|
||||
{
|
||||
_song = SongLoad.songData = PlayState.SONG;
|
||||
_song = SongLoad.songData = PlayState.currentSong;
|
||||
trace("LOADED A PLAYSTATE SONGFILE");
|
||||
}
|
||||
else
|
||||
|
@ -814,7 +814,7 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
lastSection = curSection;
|
||||
|
||||
PlayState.SONG = _song;
|
||||
PlayState.currentSong = _song;
|
||||
|
||||
// JUST FOR DEBUG DARNELL STUFF, GENERALIZE THIS FOR BETTER LOADING ELSEWHERE TOO!
|
||||
PlayState.storyWeek = 8;
|
||||
|
@ -1462,13 +1462,13 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
function loadJson(song:String):Void
|
||||
{
|
||||
PlayState.SONG = SongLoad.loadFromJson(song.toLowerCase(), song.toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(song.toLowerCase(), song.toLowerCase());
|
||||
LoadingState.loadAndSwitchState(new ChartingState());
|
||||
}
|
||||
|
||||
function loadAutosave():Void
|
||||
{
|
||||
PlayState.SONG = FlxG.save.data.autosave;
|
||||
PlayState.currentSong = FlxG.save.data.autosave;
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package funkin.modding;
|
|||
import polymod.hscript.HScriptable;
|
||||
|
||||
/**
|
||||
* Add this interface to a class to make it a scriptable object.
|
||||
* Functions annotated with @:hscript will call the relevant script.
|
||||
*/
|
||||
@:hscript({
|
||||
|
|
50
source/funkin/modding/IScriptedClass.hx
Normal file
50
source/funkin/modding/IScriptedClass.hx
Normal file
|
@ -0,0 +1,50 @@
|
|||
package funkin.modding;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to all scripted classes.
|
||||
*
|
||||
* Includes events handling basic life cycle relevant to all scripted classes.
|
||||
*/
|
||||
interface IScriptedClass
|
||||
{
|
||||
public function onScriptEvent(event:ScriptEvent):Void;
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void;
|
||||
public function onDestroy(event:ScriptEvent):Void;
|
||||
public function onUpdate(event:UpdateScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve player input.
|
||||
*/
|
||||
interface IInputScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onKeyDown(event:KeyboardInputScriptEvent):Void;
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||
*/
|
||||
interface IPlayStateScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onPause(event:ScriptEvent):Void;
|
||||
public function onResume(event:ScriptEvent):Void;
|
||||
|
||||
public function onSongStart(event:ScriptEvent):Void;
|
||||
public function onSongEnd(event:ScriptEvent):Void;
|
||||
public function onSongReset(event:ScriptEvent):Void;
|
||||
public function onGameOver(event:ScriptEvent):Void;
|
||||
public function onGameRetry(event:ScriptEvent):Void;
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
||||
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
||||
}
|
327
source/funkin/modding/events/ScriptEvent.hx
Normal file
327
source/funkin/modding/events/ScriptEvent.hx
Normal file
|
@ -0,0 +1,327 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import funkin.play.Countdown.CountdownStep;
|
||||
import openfl.events.EventType;
|
||||
import openfl.events.KeyboardEvent;
|
||||
|
||||
typedef ScriptEventType = EventType<ScriptEvent>;
|
||||
|
||||
/**
|
||||
* This is a base class for all events that are issued to scripted classes.
|
||||
* It can be used to identify the type of event called, store data, and cancel event propagation.
|
||||
*/
|
||||
class ScriptEvent
|
||||
{
|
||||
/**
|
||||
* Called when the relevant object is created.
|
||||
* Keep in mind that the constructor may be called before the object is needed,
|
||||
* for the purposes of caching data or otherwise.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final CREATE:ScriptEventType = "CREATE";
|
||||
|
||||
/**
|
||||
* Called when the relevant object is destroyed.
|
||||
* This should perform relevant cleanup to ensure good performance.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final DESTROY:ScriptEventType = "DESTROY";
|
||||
|
||||
/**
|
||||
* Called during the update function.
|
||||
* This is called every frame, so be careful!
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final UPDATE:ScriptEventType = "UPDATE";
|
||||
|
||||
/**
|
||||
* Called when the player moves to pause the game.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
||||
*/
|
||||
public static inline final PAUSE:ScriptEventType = "PAUSE";
|
||||
|
||||
/**
|
||||
* Called when the player moves to unpause the game while paused.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
||||
*/
|
||||
public static inline final RESUME:ScriptEventType = "RESUME";
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 4 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT";
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 16 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT";
|
||||
|
||||
/**
|
||||
* Called when a character hits a note.
|
||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
||||
* and will likely result in a miss later.
|
||||
*/
|
||||
public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT";
|
||||
|
||||
/**
|
||||
* Called when a character misses a note.
|
||||
* Important information such as note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding a combo break and lost health.
|
||||
*/
|
||||
public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS";
|
||||
|
||||
/**
|
||||
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_START:ScriptEventType = "SONG_START";
|
||||
|
||||
/**
|
||||
* Called when the song ends. This happens as the instrumental and vocals end.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_END:ScriptEventType = "SONG_END";
|
||||
|
||||
/**
|
||||
* Called when the song is reset. This can happen from the pause menu or the game over screen.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_RESET:ScriptEventType = "SONG_RESET";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START";
|
||||
|
||||
/**
|
||||
* Called when a step of the countdown happens.
|
||||
* Includes information about what step of the countdown was hit.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will pause the countdown.
|
||||
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
||||
*/
|
||||
public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP";
|
||||
|
||||
/**
|
||||
* Called when the game over screen triggers and the death animation plays.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
||||
|
||||
/**
|
||||
* Called when the player presses a key to restart the game after the death animation.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||
*/
|
||||
public static inline final GAME_RETRY:ScriptEventType = "GAME_RETRY";
|
||||
|
||||
/**
|
||||
* Called when the player pushes down any key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN";
|
||||
|
||||
/**
|
||||
* Called when the player releases a key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_UP:ScriptEventType = "KEY_UP";
|
||||
|
||||
/**
|
||||
* 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):ScriptEventType;
|
||||
|
||||
/**
|
||||
* Whether the event should continue to be triggered on additional targets.
|
||||
*/
|
||||
public var shouldPropagate(default, null):Bool;
|
||||
|
||||
@:noCompletion private var __eventCanceled:Bool;
|
||||
|
||||
public function new(type:ScriptEventType, cancelable:Bool = false):Void
|
||||
{
|
||||
this.type = type;
|
||||
this.cancelable = cancelable;
|
||||
this.__eventCanceled = false;
|
||||
this.shouldPropagate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function on a cancelable event to cancel the associated behavior.
|
||||
* For example, cancelling COUNTDOWN_BEGIN will prevent the countdown from starting.
|
||||
*/
|
||||
public function cancelEvent():Void
|
||||
{
|
||||
if (cancelable)
|
||||
{
|
||||
__eventCanceled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to stop any other Scripteds from receiving the event.
|
||||
*/
|
||||
public function stopPropagation():Void
|
||||
{
|
||||
shouldPropagate = false;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'ScriptEvent(type=$type, cancelable=$cancelable)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SPECIFIC EVENTS
|
||||
*/
|
||||
/**
|
||||
* An event that is fired associated with a specific note.
|
||||
*/
|
||||
class NoteScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var note(default, null):Note;
|
||||
|
||||
public function new(type:ScriptEventType, note:Note, cancelable:Bool = false):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired during the update loop.
|
||||
*/
|
||||
class UpdateScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var elapsed(default, null):Float;
|
||||
|
||||
public function new(elapsed:Float):Void
|
||||
{
|
||||
super(ScriptEvent.UPDATE, false);
|
||||
this.elapsed = elapsed;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'UpdateScriptEvent(elapsed=$elapsed)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired regularly during the song.
|
||||
* May be on beat or on step.
|
||||
*/
|
||||
class SongTimeScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The current beat of the song.
|
||||
*/
|
||||
public var beat(default, null):Int;
|
||||
|
||||
/**
|
||||
* The current step of the song.
|
||||
*/
|
||||
public var step(default, null):Int;
|
||||
|
||||
public function new(type:ScriptEventType, beat:Int, step:Int):Void
|
||||
{
|
||||
super(type, false);
|
||||
this.beat = beat;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'SongTimeScriptEvent(type=' + type + ', beat=' + beat + ', step=' + step + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired regularly during the song.
|
||||
* May be on beat or on step.
|
||||
*/
|
||||
class CountdownScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The current step of the countdown.
|
||||
*/
|
||||
public var step(default, null):CountdownStep;
|
||||
|
||||
public function new(type:ScriptEventType, step:CountdownStep):Void
|
||||
{
|
||||
super(type, false);
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'CountdownScriptEvent(type=' + type + ', step=' + step + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when the player presses a key.
|
||||
*/
|
||||
class KeyboardInputScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The associated keyboard event.
|
||||
*/
|
||||
public var event(default, null):KeyboardEvent;
|
||||
|
||||
public function new(type:ScriptEventType, event:KeyboardEvent):Void
|
||||
{
|
||||
super(type, false);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'KeyboardInputScriptEvent(type=' + type + ', event=' + event + ')';
|
||||
}
|
||||
}
|
127
source/funkin/modding/events/ScriptEventDispatcher.hx
Normal file
127
source/funkin/modding/events/ScriptEventDispatcher.hx
Normal file
|
@ -0,0 +1,127 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IInputScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
|
||||
/**
|
||||
* Utility functions to assist with handling scripted classes.
|
||||
*/
|
||||
class ScriptEventDispatcher
|
||||
{
|
||||
public static function callEvent(target:IScriptedClass, event:ScriptEvent):Void
|
||||
{
|
||||
target.onScriptEvent(event);
|
||||
|
||||
// If one target says to stop propagation, stop.
|
||||
if (!event.shouldPropagate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// IScriptedClass
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.CREATE:
|
||||
target.onCreate(event);
|
||||
return;
|
||||
case ScriptEvent.DESTROY:
|
||||
target.onDestroy(event);
|
||||
return;
|
||||
case ScriptEvent.UPDATE:
|
||||
target.onUpdate(cast event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IInputScriptedClass))
|
||||
{
|
||||
var t = cast(target, IInputScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.KEY_DOWN:
|
||||
t.onKeyDown(cast event);
|
||||
return;
|
||||
case ScriptEvent.KEY_UP:
|
||||
t.onKeyUp(cast event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
{
|
||||
var t = cast(target, IPlayStateScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.NOTE_HIT:
|
||||
t.onNoteHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.NOTE_MISS:
|
||||
t.onNoteMiss(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_START:
|
||||
t.onSongStart(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_END:
|
||||
t.onSongEnd(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_RESET:
|
||||
t.onSongReset(event);
|
||||
return;
|
||||
case ScriptEvent.PAUSE:
|
||||
t.onPause(event);
|
||||
return;
|
||||
case ScriptEvent.RESUME:
|
||||
t.onResume(event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_START:
|
||||
t.onCountdownStart(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_STEP:
|
||||
t.onCountdownStep(cast event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw "No helper for event type: " + event.type;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// If one target says to stop propagation, stop.
|
||||
if (!event.shouldPropagate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
source/funkin/modding/module/Module.hx
Normal file
87
source/funkin/modding/module/Module.hx
Normal file
|
@ -0,0 +1,87 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.KeyboardInputScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IInputScriptedClass;
|
||||
|
||||
/**
|
||||
* A module is a scripted class which receives all events without requiring a specific context.
|
||||
* You may have the module active at all times, or only when another script enables it.
|
||||
*/
|
||||
class Module implements IInputScriptedClass implements IPlayStateScriptedClass
|
||||
{
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public function toString()
|
||||
{
|
||||
return 'Module(' + this.moduleId + ')';
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onCreate(event:ScriptEvent) {}
|
||||
|
||||
public function onDestroy(event:ScriptEvent) {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onKeyDown(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onPause(event:ScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onSongReset(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onGameRetry(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onBeatHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
}
|
67
source/funkin/modding/module/ModuleHandler.hx
Normal file
67
source/funkin/modding/module/ModuleHandler.hx
Normal file
|
@ -0,0 +1,67 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
|
||||
public static function clearModuleCache():Void
|
||||
{
|
||||
if (moduleCache != null)
|
||||
{
|
||||
// for (module in moduleCache)
|
||||
// {
|
||||
// module.destroy();
|
||||
// }
|
||||
moduleCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static function callEvent(event:ScriptEvent):Void
|
||||
{
|
||||
ScriptEventDispatcher.callEventOnAllTargets(moduleCache.iterator(), event);
|
||||
}
|
||||
}
|
9
source/funkin/modding/module/ScriptedModule.hx
Normal file
9
source/funkin/modding/module/ScriptedModule.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedModule extends Module implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
11
source/funkin/play/Countdown.hx
Normal file
11
source/funkin/play/Countdown.hx
Normal file
|
@ -0,0 +1,11 @@
|
|||
package funkin.play;
|
||||
|
||||
enum abstract CountdownStep(String) from String to String
|
||||
{
|
||||
var BEFORE = "BEFORE";
|
||||
var THREE = "THREE";
|
||||
var TWO = "TWO";
|
||||
var ONE = "ONE";
|
||||
var GO = "GO";
|
||||
var AFTER = "AFTER";
|
||||
}
|
File diff suppressed because it is too large
Load diff
245
source/funkin/play/Strumline.hx
Normal file
245
source/funkin/play/Strumline.hx
Normal file
|
@ -0,0 +1,245 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.Note.NoteColor;
|
||||
import funkin.Note.NoteDir;
|
||||
import funkin.Note.NoteType;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
|
||||
/**
|
||||
* A group controlling the individual notes of the strumline for a given player.
|
||||
*/
|
||||
class Strumline extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
public var offset(default, set):FlxPoint = new FlxPoint(0, 0);
|
||||
|
||||
function set_offset(value:FlxPoint):FlxPoint
|
||||
{
|
||||
this.offset = value;
|
||||
updatePositions();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the strumline.
|
||||
* Options are normal and pixel.
|
||||
*/
|
||||
var style:StrumlineStyle;
|
||||
|
||||
/**
|
||||
* The player this strumline belongs to.
|
||||
* 0 is Player 1, etc.
|
||||
*/
|
||||
var playerId:Int;
|
||||
|
||||
/**
|
||||
* The number of notes in the strumline.
|
||||
*/
|
||||
var size:Int;
|
||||
|
||||
public function new(playerId:Int = 0, style:StrumlineStyle = NORMAL, size:Int = 4)
|
||||
{
|
||||
super(0);
|
||||
this.playerId = playerId;
|
||||
this.style = style;
|
||||
this.size = size;
|
||||
|
||||
generateStrumline();
|
||||
}
|
||||
|
||||
function generateStrumline():Void
|
||||
{
|
||||
for (index in 0...size)
|
||||
{
|
||||
createStrumlineArrow(index);
|
||||
}
|
||||
}
|
||||
|
||||
function createStrumlineArrow(index:Int):Void
|
||||
{
|
||||
var arrow:FlxSprite = new FlxSprite(0, 0);
|
||||
|
||||
arrow.ID = index;
|
||||
|
||||
// Color changing for arrows is a WIP.
|
||||
/*
|
||||
var colorSwapShader:ColorSwap = new ColorSwap();
|
||||
colorSwapShader.update(Note.arrowColors[i]);
|
||||
arrow.shader = colorSwapShader;
|
||||
*/
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case NORMAL:
|
||||
createNormalNote(arrow);
|
||||
case PIXEL:
|
||||
createPixelNote(arrow);
|
||||
}
|
||||
|
||||
arrow.updateHitbox();
|
||||
arrow.scrollFactor.set();
|
||||
|
||||
arrow.animation.play('static');
|
||||
|
||||
applyFadeIn(arrow);
|
||||
|
||||
add(arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a small animation which moves the arrow down and fades it in.
|
||||
* Only plays at the start of Free Play songs I guess?
|
||||
* @param arrow The arrow to animate.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function applyFadeIn(arrow:FlxSprite):Void
|
||||
{
|
||||
if (!PlayState.isStoryMode)
|
||||
{
|
||||
arrow.y -= 10;
|
||||
arrow.alpha = 0;
|
||||
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * arrow.ID)});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the default note style to an arrow.
|
||||
* @param arrow The arrow to apply the style to.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function createNormalNote(arrow:FlxSprite):Void
|
||||
{
|
||||
arrow.frames = Paths.getSparrowAtlas('NOTE_assets');
|
||||
|
||||
arrow.animation.addByPrefix('green', 'arrowUP');
|
||||
arrow.animation.addByPrefix('blue', 'arrowDOWN');
|
||||
arrow.animation.addByPrefix('purple', 'arrowLEFT');
|
||||
arrow.animation.addByPrefix('red', 'arrowRIGHT');
|
||||
|
||||
arrow.setGraphicSize(Std.int(arrow.width * 0.7));
|
||||
arrow.antialiasing = true;
|
||||
|
||||
arrow.x += Note.swagWidth * arrow.ID;
|
||||
|
||||
switch (Math.abs(arrow.ID))
|
||||
{
|
||||
case 0:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 1');
|
||||
arrow.animation.addByPrefix('pressed', 'left press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'left confirm', 24, false);
|
||||
case 1:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 2');
|
||||
arrow.animation.addByPrefix('pressed', 'down press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'down confirm', 24, false);
|
||||
case 2:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 4');
|
||||
arrow.animation.addByPrefix('pressed', 'up press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'up confirm', 24, false);
|
||||
case 3:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 3');
|
||||
arrow.animation.addByPrefix('pressed', 'right press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'right confirm', 24, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the pixel note style to an arrow.
|
||||
* @param arrow The arrow to apply the style to.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function createPixelNote(arrow:FlxSprite):Void
|
||||
{
|
||||
arrow.loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
||||
arrow.animation.add('purplel', [4]);
|
||||
arrow.animation.add('blue', [5]);
|
||||
arrow.animation.add('green', [6]);
|
||||
arrow.animation.add('red', [7]);
|
||||
|
||||
arrow.setGraphicSize(Std.int(arrow.width * Constants.PIXEL_ART_SCALE));
|
||||
arrow.updateHitbox();
|
||||
|
||||
// Forcibly disable anti-aliasing on pixel graphics to stop blur.
|
||||
arrow.antialiasing = false;
|
||||
|
||||
arrow.x += Note.swagWidth * arrow.ID;
|
||||
|
||||
// TODO: Seems weird that these are hardcoded...
|
||||
switch (Math.abs(arrow.ID))
|
||||
{
|
||||
case 0:
|
||||
arrow.animation.add('static', [0]);
|
||||
arrow.animation.add('pressed', [4, 8], 12, false);
|
||||
arrow.animation.add('confirm', [12, 16], 24, false);
|
||||
case 1:
|
||||
arrow.animation.add('static', [1]);
|
||||
arrow.animation.add('pressed', [5, 9], 12, false);
|
||||
arrow.animation.add('confirm', [13, 17], 24, false);
|
||||
case 2:
|
||||
arrow.animation.add('static', [2]);
|
||||
arrow.animation.add('pressed', [6, 10], 12, false);
|
||||
arrow.animation.add('confirm', [14, 18], 12, false);
|
||||
case 3:
|
||||
arrow.animation.add('static', [3]);
|
||||
arrow.animation.add('pressed', [7, 11], 12, false);
|
||||
arrow.animation.add('confirm', [15, 19], 24, false);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePositions()
|
||||
{
|
||||
for (arrow in members)
|
||||
{
|
||||
arrow.x = Note.swagWidth * arrow.ID;
|
||||
arrow.x += offset.x;
|
||||
|
||||
arrow.y = 0;
|
||||
arrow.y += offset.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the arrow at the given position in the strumline.
|
||||
* @param index The index to retrieve.
|
||||
* @return The corresponding FlxSprite.
|
||||
*/
|
||||
public inline function getArrow(value:Int):FlxSprite
|
||||
{
|
||||
// members maintains the order that the arrows were added.
|
||||
return this.members[value];
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteType(value:NoteType):FlxSprite
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteDir(value:NoteDir):FlxSprite
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteColor(value:NoteColor):FlxSprite
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public static inline function getYPos():Int
|
||||
{
|
||||
return PreferencesMenu.getPref('downscroll') ? (FlxG.height - 150) : 50;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Unhardcode this and make it part of the note style system.
|
||||
*/
|
||||
enum StrumlineStyle
|
||||
{
|
||||
NORMAL;
|
||||
PIXEL;
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
/**
|
||||
* A Bopper is a stage prop which plays a dance animation.
|
||||
* Y'know, a thingie that bops. A bopper.
|
||||
*/
|
||||
class Bopper extends FlxSprite
|
||||
class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||
{
|
||||
/**
|
||||
* The bopper plays the dance animation once every `danceEvery` beats.
|
||||
|
@ -76,9 +82,9 @@ class Bopper extends FlxSprite
|
|||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
if (curBeat % danceEvery == 0)
|
||||
if (event.beat % danceEvery == 0)
|
||||
{
|
||||
dance();
|
||||
}
|
||||
|
@ -87,7 +93,7 @@ class Bopper extends FlxSprite
|
|||
/**
|
||||
* Called every `danceEvery` beats of the song.
|
||||
*/
|
||||
public function dance():Void
|
||||
function dance():Void
|
||||
{
|
||||
if (this.animation == null)
|
||||
{
|
||||
|
@ -116,4 +122,36 @@ class Bopper extends FlxSprite
|
|||
this.animation.play('idle$idleSuffix');
|
||||
}
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onCreate(event:ScriptEvent) {}
|
||||
|
||||
public function onDestroy(event:ScriptEvent) {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onPause(event:ScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onSongReset(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onGameRetry(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.KeyboardInputScriptEvent;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
|
@ -14,7 +19,7 @@ import funkin.util.SortUtil;
|
|||
*
|
||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||
*/
|
||||
class Stage extends FlxSpriteGroup implements IHook
|
||||
class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScriptedClass implements IInputScriptedClass
|
||||
{
|
||||
public final stageId:String;
|
||||
public final stageName:String;
|
||||
|
@ -50,16 +55,24 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the player is moving into the PlayState where the song will be played.
|
||||
*/
|
||||
public function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
buildStage();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default stage construction routine. Called when the stage is going to be played in.
|
||||
* Instantiates each prop and adds it to the stage, while setting its parameters.
|
||||
*/
|
||||
public function buildStage()
|
||||
function buildStage()
|
||||
{
|
||||
trace('Building stage for display: ${this.stageId}');
|
||||
|
||||
this.camZoom = _data.cameraZoom;
|
||||
// this.scrollFactor = new FlxPoint(1, 1);
|
||||
|
||||
for (dataProp in _data.props)
|
||||
{
|
||||
|
@ -162,8 +175,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
trace(' Prop placed.');
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,39 +211,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
trace('Stage sorted by z-index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stage and it's props (needs to be overridden with your own logic!)
|
||||
*/
|
||||
public function resetStage()
|
||||
{
|
||||
// Override me in your script to reset stage shit however you please!
|
||||
// also note: maybe add some default behaviour to reset stage stuff?
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that should get called every frame.
|
||||
*/
|
||||
public function onUpdate(elapsed:Float):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteHit(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteMiss(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the position and other properties of the soon-to-be child of this sprite group.
|
||||
* Private helper to avoid duplicate code in `add()` and `insert()`.
|
||||
|
@ -253,30 +231,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
clipRectTransform(sprite, clipRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public function onStepHit(curStep:Int):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
{
|
||||
// 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.
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
bopper.onBeatHit(curBeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the PlayState to add a character to the stage.
|
||||
*/
|
||||
|
@ -360,9 +314,11 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
/**
|
||||
* Perform cleanup for when you are leaving the level.
|
||||
*/
|
||||
public override function kill()
|
||||
public function onDestroy(event:ScriptEvent):Void
|
||||
{
|
||||
super.kill();
|
||||
// Make sure to call kill() when returning a stage to cache,
|
||||
// and destroy() only when performing a hard cache refresh.
|
||||
kill();
|
||||
|
||||
for (prop in this.namedProps)
|
||||
{
|
||||
|
@ -390,13 +346,56 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup for when you are destroying the stage
|
||||
* and removing all its data from cache.
|
||||
*
|
||||
* Call this ONLY when you are performing a hard cache clear.
|
||||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public override function destroy()
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
super.destroy();
|
||||
// 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);
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onPause(event:ScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
/**
|
||||
* Resets the stage and its props.
|
||||
*/
|
||||
public function onSongReset(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onGameRetry(event:ScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onKeyDown(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
/**
|
||||
* A function that should get called every frame.
|
||||
*/
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.ui;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
@ -22,7 +23,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var rating:FlxSprite = new FlxSprite();
|
||||
var ratingPath:String = daRating;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||
|
||||
rating.loadGraphic(Paths.image(ratingPath));
|
||||
|
@ -40,9 +41,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
add(rating);
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
rating.setGraphicSize(Std.int(rating.width * PlayState.daPixelZoom * 0.7));
|
||||
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -69,7 +70,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var pixelShitPart1:String = "";
|
||||
var pixelShitPart2:String = '';
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
pixelShitPart1 = 'weeb/pixelUI/';
|
||||
pixelShitPart2 = '-pixel';
|
||||
|
@ -90,9 +91,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
add(comboSpr);
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * PlayState.daPixelZoom * 0.7));
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -129,9 +130,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
|
||||
numScore.y = comboSpr.y;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
numScore.setGraphicSize(Std.int(numScore.width * PlayState.daPixelZoom));
|
||||
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
14
source/funkin/util/Constants.hx
Normal file
14
source/funkin/util/Constants.hx
Normal file
|
@ -0,0 +1,14 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class Constants
|
||||
{
|
||||
/**
|
||||
* The scale factor to use when increasing the size of pixel art graphics.
|
||||
*/
|
||||
public static final PIXEL_ART_SCALE = 6;
|
||||
|
||||
public static final HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
|
||||
public static final HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package funkin.util;
|
||||
|
||||
#if !macro
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.FlxObject;
|
||||
#end
|
||||
|
||||
class SortUtil
|
||||
|
@ -12,8 +11,18 @@ class SortUtil
|
|||
* You can use this function in FlxTypedGroup.sort() to sort FlxObjects by their z-index values.
|
||||
* The value defaults to 0, but by assigning it you can easily rearrange objects as desired.
|
||||
*/
|
||||
public static inline function byZIndex(Order:Int, Obj1:FlxObject, Obj2:FlxObject):Int
|
||||
public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int
|
||||
{
|
||||
return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two Notes, returns 1 or -1 based on whether `a` or `b` has an earlier strumtime.
|
||||
*
|
||||
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
|
||||
*/
|
||||
public static inline function byStrumtime(order:Int, a:Note, b:Note)
|
||||
{
|
||||
return FlxSort.byValues(order, a.data.strumTime, b.data.strumTime);
|
||||
}
|
||||
}
|
||||
|
|
80
source/modding/module/Module.hx
Normal file
80
source/modding/module/Module.hx
Normal file
|
@ -0,0 +1,80 @@
|
|||
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() {}
|
||||
}
|
79
source/modding/module/ModuleHandler.hx
Normal file
79
source/modding/module/ModuleHandler.hx
Normal file
|
@ -0,0 +1,79 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
9
source/modding/module/ScriptedModule.hx
Normal file
9
source/modding/module/ScriptedModule.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package modding.module;
|
||||
|
||||
import modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedModule extends Module implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
128
source/modding/module/events/ModuleEvent.hx
Normal file
128
source/modding/module/events/ModuleEvent.hx
Normal file
|
@ -0,0 +1,128 @@
|
|||
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
source/modding/module/events/NoteModuleEvent.hx
Normal file
1
source/modding/module/events/NoteModuleEvent.hx
Normal file
|
@ -0,0 +1 @@
|
|||
package modding.module.events;
|
Loading…
Reference in a new issue