Merge branch 'rewrite/master' into feature/chart-editor-open-recent

This commit is contained in:
Cameron Taylor 2023-11-15 01:10:26 -05:00
commit aecd587ef0
193 changed files with 4681 additions and 4266 deletions

View file

@ -13,7 +13,7 @@ runs:
- name: Installing Haxe lol
run: |
haxe -version
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git master
haxelib version
haxelib --global install hmm
shell: bash

3
.gitmodules vendored
View file

@ -2,6 +2,9 @@
path = assets
url = https://github.com/FunkinCrew/Funkin-history-rewrite-assets
branch = master
update = merge
[submodule "art"]
path = art
url = https://github.com/FunkinCrew/Funkin-history-rewrite-art
branch = master
update = merge

34
.vscode/settings.json vendored
View file

@ -71,7 +71,7 @@
"files.eol": "\n",
"haxe.displayPort": "auto",
"haxe.enableCompilationServer": true,
"haxe.enableCompilationServer": false,
"haxe.displayServer": {
"arguments": ["-v"]
},
@ -97,15 +97,35 @@
"args": ["-debug"]
},
{
"label": "Windows / Debug (DEBUG ASSETS)",
"target": "windows",
"args": ["-debug", "-DDEBUG_ASSETS"]
},
{
"label": "Windows / Debug (ANIMATE)",
"label": "Windows / Debug (FlxAnimate Test)",
"target": "windows",
"args": ["-debug", "-DANIMATE"]
},
{
"label": "Windows / Debug (Straight to Freeplay)",
"target": "windows",
"args": ["-debug", "-DFREEPLAY"]
},
{
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
"target": "windows",
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
},
{
"label": "Windows / Debug (Straight to Chart Editor)",
"target": "windows",
"args": ["-debug", "-DCHARTING"]
},
{
"label": "Windows / Debug (Straight to Animation Editor)",
"target": "windows",
"args": ["-debug", "-DANIMDEBUG"]
},
{
"label": "Windows / Debug (Latency Test)",
"target": "windows",
"args": ["-debug", "-DLATENCY"]
},
{
"label": "HTML5 / Debug",
"target": "html5",

View file

@ -162,7 +162,10 @@
<icon path="art/iconOG.png" />
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
<haxedef name="CAN_CHEAT" if="switch debug" />
<!-- I don't -->
<haxedef name="haxeui_no_mouse_reset" />
<!-- Clicking outside a dialog should deselect the current focused component. -->
<haxedef name="haxeui_focus_out_on_click" />
<!-- Skip the Intro -->
<section if="debug">
<!-- Starts the game at the specified week, at the first song -->

2
assets

@ -1 +1 @@
Subproject commit 8e8aeb06472ca294c569818cbefb1bb3dfce7854
Subproject commit fb7120cf30d7accda049409b68d8daa0e1e7650f

View file

@ -24,7 +24,7 @@ Example:
```
/**
* Finds the largest deviation from the desired time inside this VoicesGroup.
*
*
* @param targetTime The time to check against.
* If none is provided, it checks the time of all members against the first member of this VoicesGroup.
* @return The largest deviation from the target time found.
@ -52,3 +52,10 @@ import sys.io.File;
#end
```
## Argument Formatting
[Optional arguments](https://haxe.org/manual/types-function-optional-arguments.html) and [default arguments](https://haxe.org/manual/types-function-default-values.html) should be mutually exclusive and not used together!
For example, `myFunction(?input:Int)` should be used if you want the argument to be a `Null<Int>` whose value is `null` if no value is passed, and `myFunction(input:Int = 0)` should be used if you want the argument to be an `Int`, whose value is `0` if no value is passed.
Using both at the same time is considered valid by Haxe, but `myFunction(?input:Int = 0)` results in a `Null<Int>` whose value defaults to 0 anyway, so it's never null, but it's annotated as nullable! The biggest consequence of this is that it makes null safety more annoying.

View file

@ -49,14 +49,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "db6f81191abe386d891aca5a65c27ba6f8e10598",
"ref": "815e94dd5aa6cf09c5ddcef1666a54449ffde8dc",
"url": "https://github.com/haxeui/haxeui-core"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
"ref": "e10f51fe33b8d8d2dd3f21a0fd1d7c4d88d5d5c0",
"ref": "95c7d66e779626eabd6f48a1cd7aa7f9a503a7f3",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{

View file

@ -3,7 +3,7 @@ package;
import flixel.FlxGame;
import flixel.FlxState;
import funkin.util.logging.CrashHandler;
import funkin.MemoryCounter;
import funkin.ui.debug.MemoryCounter;
import funkin.save.Save;
import haxe.ui.Toolkit;
import openfl.display.FPS;
@ -111,5 +111,6 @@ class Main extends Sprite
Toolkit.init();
Toolkit.theme = 'dark'; // don't be cringe
Toolkit.autoScale = false;
funkin.input.Cursor.registerHaxeUICursors();
}
}

View file

@ -5,6 +5,7 @@ import flixel.util.FlxSignal;
import flixel.math.FlxMath;
import funkin.play.song.Song.SongDifficulty;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.SongDataUtils;
/**
* A core class which handles musical timing throughout the game,
@ -257,6 +258,9 @@ class Conductor
{
timeChanges = [];
// Sort in place just in case it's out of order.
SongDataUtils.sortTimeChanges(songTimeChanges);
for (currentTimeChange in songTimeChanges)
{
// TODO: Maybe handle this different?

View file

@ -1,129 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import funkin.play.PlayState;
import funkin.shaderslmfao.ScreenWipeShader;
import haxe.format.JsonParser;
import lime.math.Rectangle;
import lime.utils.Assets;
import openfl.filters.ShaderFilter;
class CoolUtil
{
public static function coolBaseLog(base:Float, fin:Float):Float
{
return Math.log(fin) / Math.log(base);
}
public static function coolTextFile(path:String):Array<String>
{
var daList:Array<String> = [];
var swagArray:Array<String> = Assets.getText(path).trim().split('\n');
for (item in swagArray)
{
// comment support in the quick lil text formats??? using //
if (!item.trim().startsWith('//')) daList.push(item);
}
for (i in 0...daList.length)
{
daList[i] = daList[i].trim();
}
return daList;
}
public static function numberArray(max:Int, ?min = 0):Array<Int>
{
var dumbArray:Array<Int> = [];
for (i in min...max)
{
dumbArray.push(i);
}
return dumbArray;
}
static var oldCamPos:FlxPoint = new FlxPoint();
static var oldMousePos:FlxPoint = new FlxPoint();
/**
* Used to be for general camera middle click dragging, now generalized for any click and drag type shit!
* Listen I don't make the rules here
* @param target what you want to be dragged, defaults to CAMERA SCROLL
* @param jusPres the "justPressed", should be a button of some sort
* @param pressed the "pressed", which should be the same button as `jusPres`
*/
public static function mouseCamDrag(?target:FlxPoint, ?jusPres:Bool, ?pressed:Bool):Void
{
if (target == null) target = FlxG.camera.scroll;
if (jusPres == null) jusPres = FlxG.mouse.justPressedMiddle;
if (pressed == null) pressed = FlxG.mouse.pressedMiddle;
if (jusPres)
{
oldCamPos.set(target.x, target.y);
oldMousePos.set(FlxG.mouse.screenX, FlxG.mouse.screenY);
}
if (pressed)
{
target.x = oldCamPos.x - (FlxG.mouse.screenX - oldMousePos.x);
target.y = oldCamPos.y - (FlxG.mouse.screenY - oldMousePos.y);
}
}
public static function mouseWheelZoom():Void
{
if (FlxG.mouse.wheel != 0) FlxG.camera.zoom += FlxG.mouse.wheel * (0.1 * FlxG.camera.zoom);
}
/**
Lerps camera, but accountsfor framerate shit?
Right now it's simply for use to change the followLerp variable of a camera during update
TODO LATER MAYBE:
Actually make and modify the scroll and lerp shit in it's own function
instead of solely relying on changing the lerp on the fly
*/
public static function camLerpShit(lerp:Float):Float
{
return lerp * (FlxG.elapsed / (1 / 60));
}
public static function coolSwitchState(state:FlxState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
{
var screenShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("shaderTransitionStuff/coolDots"));
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
screenWipeShit.funnyShit.input = screenShit.pixels;
FlxTween.tween(screenWipeShit, {daAlphaShit: 1}, time,
{
ease: FlxEase.quadInOut,
onComplete: function(twn) {
screenShit.destroy();
FlxG.switchState(new MainMenuState());
}
});
FlxG.camera.setFilters([new ShaderFilter(screenWipeShit)]);
}
/*
* frame dependant lerp kinda lol
*/
public static function coolLerp(base:Float, target:Float, ratio:Float):Float
{
return base + camLerpShit(ratio) * (target - base);
}
}

View file

@ -1,265 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.addons.text.FlxTypeText;
import flixel.group.FlxSpriteGroup;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.play.PlayState;
/**
* Handles dialog boxes and text, like the ones in Week 6.
*/
class DialogueBox extends FlxSpriteGroup
{
var box:FlxSprite;
var curCharacter:String = '';
var dialogue:Alphabet;
var dialogueList:Array<String> = [];
// SECOND DIALOGUE FOR THE PIXEL SHIT INSTEAD???
var swagDialogue:FlxTypeText;
var dropText:FlxText;
public var finishThing:Void->Void;
var portraitLeft:FlxSprite;
var portraitRight:FlxSprite;
var handSelect:FlxSprite;
var bgFade:FlxSprite;
public function new(talkingRight:Bool = true, ?dialogueList:Array<String>)
{
super();
switch (PlayState.instance.currentSong.id.toLowerCase())
{
case 'senpai':
FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
FlxG.sound.music.fadeIn(1, 0, 0.8);
case 'thorns':
FlxG.sound.playMusic(Paths.music('LunchboxScary'), 0);
FlxG.sound.music.fadeIn(1, 0, 0.8);
}
bgFade = new FlxSprite(-200, -200).makeGraphic(Std.int(FlxG.width * 1.3), Std.int(FlxG.height * 1.3), 0xFFB3DFD8);
bgFade.scrollFactor.set();
bgFade.alpha = 0;
add(bgFade);
new FlxTimer().start(0.83, function(tmr:FlxTimer) {
bgFade.alpha += (1 / 5) * 0.7;
if (bgFade.alpha > 0.7) bgFade.alpha = 0.7;
}, 5);
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 * Constants.PIXEL_ART_SCALE * 0.9));
portraitLeft.updateHitbox();
portraitLeft.scrollFactor.set();
add(portraitLeft);
portraitLeft.visible = false;
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 * Constants.PIXEL_ART_SCALE * 0.9));
portraitRight.updateHitbox();
portraitRight.scrollFactor.set();
add(portraitRight);
portraitRight.visible = false;
box = new FlxSprite(-20, 45);
var hasDialog:Bool = false;
switch (PlayState.instance.currentSong.id.toLowerCase())
{
case 'senpai':
hasDialog = true;
box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-pixel');
box.animation.addByPrefix('normalOpen', 'Text Box Appear', 24, false);
box.animation.addByIndices('normal', 'Text Box Appear', [4], '', 24);
case 'roses':
hasDialog = true;
FlxG.sound.play(Paths.sound('ANGRY_TEXT_BOX'));
box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-senpaiMad');
box.animation.addByPrefix('normalOpen', 'SENPAI ANGRY IMPACT SPEECH', 24, false);
box.animation.addByIndices('normal', 'SENPAI ANGRY IMPACT SPEECH', [4], '', 24);
case 'thorns':
hasDialog = true;
box.frames = Paths.getSparrowAtlas('weeb/pixelUI/dialogueBox-evil');
box.animation.addByPrefix('normalOpen', 'Spirit Textbox spawn', 24, false);
box.animation.addByIndices('normal', 'Spirit Textbox spawn', [11], '', 24);
var face:FlxSprite = new FlxSprite(320, 170).loadGraphic(Paths.image('weeb/spiritFaceForward'));
face.setGraphicSize(Std.int(face.width * 6));
add(face);
}
this.dialogueList = dialogueList;
if (!hasDialog) return;
box.animation.play('normalOpen');
box.setGraphicSize(Std.int(box.width * Constants.PIXEL_ART_SCALE * 0.9));
box.updateHitbox();
add(box);
box.screenCenter(X);
portraitLeft.screenCenter(X);
handSelect = new FlxSprite(1042, 590).loadGraphic(Paths.image('weeb/pixelUI/hand_textbox'));
handSelect.setGraphicSize(Std.int(handSelect.width * Constants.PIXEL_ART_SCALE * 0.9));
handSelect.updateHitbox();
handSelect.visible = false;
add(handSelect);
if (!talkingRight)
{
// box.flipX = true;
}
dropText = new FlxText(242, 502, Std.int(FlxG.width * 0.6), '', 32);
dropText.font = 'Pixel Arial 11 Bold';
dropText.color = 0xFFD89494;
add(dropText);
swagDialogue = new FlxTypeText(240, 500, Std.int(FlxG.width * 0.6), '', 32);
swagDialogue.font = 'Pixel Arial 11 Bold';
swagDialogue.color = 0xFF3F2021;
swagDialogue.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)];
add(swagDialogue);
dialogue = new Alphabet(0, 80, '', false, true);
// dialogue.x = 90;
// add(dialogue);
}
var dialogueOpened:Bool = false;
var dialogueStarted:Bool = false;
var dialogueEnded:Bool = false;
override function update(elapsed:Float):Void
{
// HARD CODING CUZ IM STUPDI
if (PlayState.instance.currentSong.id.toLowerCase() == 'roses') portraitLeft.visible = false;
if (PlayState.instance.currentSong.id.toLowerCase() == 'thorns')
{
portraitLeft.color = FlxColor.BLACK;
swagDialogue.color = FlxColor.WHITE;
dropText.color = FlxColor.BLACK;
}
dropText.text = swagDialogue.text;
if (box.animation.curAnim != null)
{
if (box.animation.curAnim.name == 'normalOpen' && box.animation.curAnim.finished)
{
box.animation.play('normal');
dialogueOpened = true;
}
}
if (dialogueOpened && !dialogueStarted)
{
startDialogue();
dialogueStarted = true;
}
if (FlxG.keys.justPressed.ANY && dialogueEnded)
{
remove(dialogue);
FlxG.sound.play(Paths.sound('clickText'), 0.8);
if (dialogueList[1] == null && dialogueList[0] != null)
{
if (!isEnding)
{
isEnding = true;
if (PlayState.instance.currentSong.id.toLowerCase() == 'senpai'
|| PlayState.instance.currentSong.id.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0);
new FlxTimer().start(0.2, function(tmr:FlxTimer) {
box.alpha -= 1 / 5;
bgFade.alpha -= 1 / 5 * 0.7;
portraitLeft.visible = false;
portraitRight.visible = false;
swagDialogue.alpha -= 1 / 5;
handSelect.alpha -= 1 / 5;
dropText.alpha = swagDialogue.alpha;
}, 5);
new FlxTimer().start(1.2, function(tmr:FlxTimer) {
finishThing();
kill();
});
}
}
else
{
dialogueList.remove(dialogueList[0]);
startDialogue();
}
}
else if (FlxG.keys.justPressed.ANY && dialogueStarted) swagDialogue.skip();
super.update(elapsed);
}
var isEnding:Bool = false;
function startDialogue():Void
{
cleanDialog();
// var theDialog:Alphabet = new Alphabet(0, 70, dialogueList[0], false, true);
// dialogue = theDialog;
// add(theDialog);
// swagDialogue.text = ;
swagDialogue.resetText(dialogueList[0]);
swagDialogue.start(0.04);
swagDialogue.completeCallback = function() {
trace('dialogue finish');
handSelect.visible = true;
dialogueEnded = true;
};
handSelect.visible = false;
dialogueEnded = false;
switch (curCharacter)
{
case 'dad':
portraitRight.visible = false;
if (!portraitLeft.visible)
{
portraitLeft.visible = true;
portraitLeft.animation.play('enter');
}
case 'bf':
portraitLeft.visible = false;
if (!portraitRight.visible)
{
portraitRight.visible = true;
portraitRight.animation.play('enter');
}
}
}
function cleanDialog():Void
{
var splitName:Array<String> = dialogueList[0].split(':');
curCharacter = splitName[1];
dialogueList[0] = dialogueList[0].substr(splitName[1].length + 2).trim();
}
}

View file

@ -1,43 +0,0 @@
package funkin;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.graphics.tile.FlxDrawBaseItem;
import openfl.display.MovieClip;
class FlxSwf extends FlxSprite
{
public var swf:MovieClip;
public function new()
{
super();
}
override function draw()
{
for (camera in cameras)
{
if (!camera.visible || !camera.exists) continue;
getScreenPosition(_point, camera).subtractPoint(offset);
// assume no render blit for now
// use camera.canvas
// camera.canvas.graphics.
}
}
}
class FlxDrawSwfItem extends FlxDrawBaseItem<FlxDrawSwfItem>
{
public function new()
{
super();
type = FlxDrawItemType.TILES;
}
override function render(camera:FlxCamera)
{
super.render(camera);
}
}

View file

@ -1,5 +1,8 @@
package funkin;
/**
* A core class which handles tracking score and combo for the current song.
*/
class Highscore
{
public static var tallies:Tallies = new Tallies();

View file

@ -1,6 +1,7 @@
package funkin;
import funkin.ui.debug.charting.ChartEditorState;
import funkin.ui.transition.LoadingState;
import flixel.FlxState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
@ -11,7 +12,7 @@ import flixel.math.FlxRect;
import flixel.FlxSprite;
import flixel.system.debug.log.LogStyle;
import flixel.util.FlxColor;
import funkin.ui.PreferencesMenu;
import funkin.ui.options.PreferencesMenu;
import funkin.util.macro.MacroUtil;
import funkin.util.WindowUtil;
import funkin.play.PlayStatePlaylist;
@ -29,11 +30,13 @@ import funkin.modding.module.ModuleHandler;
import funkin.ui.title.TitleState;
import funkin.util.CLIUtil;
import funkin.util.CLIUtil.CLIParams;
import funkin.ui.transition.LoadingState;
#if discord_rpc
import Discord.DiscordClient;
#end
/**
* A core class which performs initialization of the game.
* The initialization state has several functions:
* - Calls code to set up the game, including loading saves and parsing game data.
* - Chooses whether to start via debug or via launching normally.
@ -231,13 +234,13 @@ class InitState extends FlxState
#elseif FREEPLAY // -DFREEPLAY
FlxG.switchState(new FreeplayState());
#elseif ANIMATE // -DANIMATE
FlxG.switchState(new funkin.ui.animDebugShit.FlxAnimateTest());
FlxG.switchState(new funkin.ui.debug.anim.FlxAnimateTest());
#elseif CHARTING // -DCHARTING
FlxG.switchState(new funkin.ui.debug.charting.ChartEditorState());
#elseif STAGEBUILD // -DSTAGEBUILD
FlxG.switchState(new funkin.ui.stageBullshit.StageBuilderState());
FlxG.switchState(new funkin.ui.debug.stage.StageBuilderState());
#elseif ANIMDEBUG // -DANIMDEBUG
FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
FlxG.switchState(new funkin.ui.debug.anim.DebugBoundingState());
#elseif LATENCY // -DLATENCY
FlxG.switchState(new funkin.LatencyState());
#else
@ -282,6 +285,10 @@ class InitState extends FlxState
return;
}
// Load and cache the song's charts.
// TODO: Do this in the loading state.
songData.cacheCharts(true);
LoadingState.loadAndSwitchState(new funkin.play.PlayState(
{
targetSong: songData,

View file

@ -1,42 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
class MenuCharacter extends FlxSprite
{
public var character:String;
public function new(x:Float, character:String = 'bf')
{
super(x);
this.character = character;
var suffix:String = character;
if (character != "darnell" && character != "nene") suffix = "characters";
var tex = Paths.getSparrowAtlas('campaign_menu_UI_' + suffix);
frames = tex;
trace(character);
animation.addByPrefix('bf', "BF idle dance white", 24);
animation.addByPrefix('bfConfirm', 'BF HEY!!', 24, false);
animation.addByPrefix('gf', "GF Dancing Beat WHITE", 24);
animation.addByPrefix('dad', "Dad idle dance BLACK LINE", 24);
animation.addByPrefix('spooky', "spooky dance idle BLACK LINES", 24);
animation.addByPrefix('pico', "Pico Idle Dance", 24);
animation.addByPrefix('mom', "Mom Idle BLACK LINES", 24);
animation.addByPrefix('parents-christmas', "Parent Christmas Idle", 24);
animation.addByPrefix('senpai', "SENPAI idle Black Lines", 24);
animation.addByPrefix('tankman', "Tankman Menu BLACK", 24);
animation.addByPrefix('darnell', "Darnell Black Lines To Scale", 24);
animation.addByPrefix('nene', "Nene Black Lines To Scale", 24);
// Parent Christmas Idle
animation.play(character);
updateHitbox();
}
}

View file

@ -1,65 +0,0 @@
package funkin;
import flixel.FlxSprite;
import haxe.io.Path;
import flixel.graphics.frames.FlxAtlasFrames;
class NoteSplash extends FlxSprite
{
public function new(x:Float, y:Float, noteData:Int = 0):Void
{
super(x, y);
animation.addByPrefix('note0-0', 'note impact 1 purple', 24, false);
animation.addByPrefix('note1-0', 'note impact 1 blue', 24, false);
animation.addByPrefix('note2-0', 'note impact 1 green', 24, false);
animation.addByPrefix('note3-0', 'note impact 1 red', 24, false);
animation.addByPrefix('note0-1', 'note impact 2 purple', 24, false);
animation.addByPrefix('note1-1', 'note impact 2 blue', 24, false);
animation.addByPrefix('note2-1', 'note impact 2 green', 24, false);
animation.addByPrefix('note3-1', 'note impact 2 red', 24, false);
setupNoteSplash(x, y, noteData);
// alpha = 0.75;
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
if (animation.finished)
{
kill();
}
}
public static function buildSplashFrames(force:Bool = false):FlxAtlasFrames
{
// static variables inside functions are a cool of Haxe 4.3.0.
static var splashFrames:FlxAtlasFrames = null;
if (splashFrames != null && !force) return splashFrames;
splashFrames = Paths.getSparrowAtlas('noteSplashes');
splashFrames.parent.persist = true;
return splashFrames;
}
public function setupNoteSplash(x:Float, y:Float, noteData:Int = 0)
{
setPosition(x, y);
alpha = 0.6;
animation.play('note' + noteData + '-' + FlxG.random.int(0, 1), true);
animation.curAnim.frameRate = 24 + FlxG.random.int(-2, 2);
animation.finishCallback = function(name) {
kill();
};
updateHitbox();
offset.set(width * 0.3, height * 0.3);
}
}

View file

@ -1,6 +0,0 @@
package funkin;
class Options
{
public static var masterVolume:Float = 1;
}

View file

@ -4,6 +4,9 @@ import flixel.graphics.frames.FlxAtlasFrames;
import openfl.utils.AssetType;
import openfl.utils.Assets as OpenFlAssets;
/**
* A core class which handles determining asset paths.
*/
class Paths
{
static var currentLevel:String;

View file

@ -1,15 +1,16 @@
package funkin;
import funkin.save.Save;
import funkin.Controls;
import funkin.input.Controls;
import flixel.FlxCamera;
import funkin.input.PreciseInputManager;
import flixel.input.actions.FlxActionInput;
import flixel.input.gamepad.FlxGamepad;
import flixel.util.FlxSignal;
// import ui.DeviceManager;
// import props.Player;
/**
* A core class which represents the current player(s) and their controls and other configuration.
*/
class PlayerSettings
{
public static var numPlayers(default, null) = 0;

View file

@ -3,7 +3,7 @@ package funkin;
import funkin.save.Save;
/**
* A store of user-configurable, globally relevant values.
* A core class which provides a store of user-configurable, globally relevant values.
*/
class Preferences
{

View file

@ -1,27 +0,0 @@
package funkin;
import flixel.FlxSprite;
import flixel.sound.FlxSound;
class TankCutscene extends FlxSprite
{
public var startSyncAudio:FlxSound;
public function new(x:Float, y:Float)
{
super(x, y);
}
var startedPlayingSound:Bool = false;
override function update(elapsed:Float)
{
if (animation.curAnim.curFrame >= 1 && !startedPlayingSound)
{
startSyncAudio.play();
startedPlayingSound = true;
}
super.update(elapsed);
}
}

View file

@ -1,4 +1,4 @@
package funkin;
package funkin.api.discord;
import Sys.sleep;
#if discord_rpc

View file

@ -241,15 +241,3 @@ class NGUtil
}
#end
}
enum ConnectionResult
{
/** Log in successful */
Success;
/** Could not login */
Fail(msg:String);
/** User cancelled the login */
Cancelled;
}

View file

@ -1,4 +1,4 @@
package funkin;
package funkin.api.newgrounds;
#if newgrounds
import flixel.util.FlxSignal;

View file

@ -6,4 +6,4 @@ This package contains two main classes:
such as retrieving achievement status.
- `NGUnsafe` contains sensitive utility functions for interacting with the Newgrounds API.
- This includes any functions which scripts should not be able to use,
such as writing high scores or posting achievements.
such as writing high scores or posting achievements.

View file

@ -1,4 +1,4 @@
package funkin.audiovis;
package funkin.audio.visualize;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;

View file

@ -1,12 +1,13 @@
package funkin.audiovis;
package funkin.audio.visualize;
import funkin.audiovis.dsp.FFT;
import funkin.audio.visualize.dsp.FFT;
import flixel.FlxSprite;
import flixel.addons.plugin.taskManager.FlxTask;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.sound.FlxSound;
import funkin.util.MathUtil;
using Lambda;
@ -86,7 +87,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
for (i in 0...group.members.length)
{
var getSliceShit = function(s:Int) {
var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, CoolUtil.coolBaseLog(10, freqShit[0].length));
var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
return Math.round(Math.pow(10, powShit));
};

View file

@ -4,7 +4,7 @@ import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.sound.FlxSound;
import flixel.util.FlxColor;
import funkin.audiovis.VisShit;
import funkin.audio.visualize.VisShit;
import funkin.graphics.rendering.MeshRender;
import lime.utils.Int16Array;

View file

@ -1,4 +1,4 @@
package funkin.audiovis;
package funkin.audio.visualize;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
@ -8,8 +8,8 @@ import flixel.math.FlxVector;
import flixel.sound.FlxSound;
import flixel.util.FlxColor;
import funkin.audio.visualize.PolygonSpectogram.VISTYPE;
import funkin.audiovis.VisShit.CurAudioInfo;
import funkin.audiovis.dsp.FFT;
import funkin.audio.visualize.VisShit.CurAudioInfo;
import funkin.audio.visualize.dsp.FFT;
import haxe.Timer;
import lime.system.ThreadPool;
import lime.utils.Int16Array;

View file

@ -1,11 +1,12 @@
package funkin.audiovis;
package funkin.audio.visualize;
import flixel.math.FlxMath;
import flixel.sound.FlxSound;
import funkin.audiovis.dsp.FFT;
import funkin.audio.visualize.dsp.FFT;
import haxe.Timer;
import lime.system.ThreadPool;
import lime.utils.Int16Array;
import funkin.util.MathUtil;
using Lambda;
@ -42,7 +43,7 @@ class VisShit
// helpers, note that spectrum indexes suppose non-negative frequencies
final binSize = fs / fftN;
final indexToFreq = function(k:Int) {
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, CoolUtil.coolBaseLog(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, MathUtil.logBase(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
return 1.0 * (Math.pow(10, powShit)); // we need the `1.0` to avoid overflows
};

View file

@ -1,4 +1,4 @@
package funkin.audiovis.dsp;
package funkin.audio.visualize.dsp;
/**
Complex number representation.

View file

@ -1,9 +1,9 @@
package funkin.audiovis.dsp;
package funkin.audio.visualize.dsp;
import funkin.audiovis.dsp.Complex;
import funkin.audio.visualize.dsp.Complex;
using funkin.audiovis.dsp.OffsetArray;
using funkin.audiovis.dsp.Signal;
using funkin.audio.visualize.dsp.OffsetArray;
using funkin.audio.visualize.dsp.Signal;
// these are only used for testing, down in FFT.main()

View file

@ -1,4 +1,4 @@
package funkin.audiovis.dsp;
package funkin.audio.visualize.dsp;
/**
A view into an Array with an indexing offset.

View file

@ -1,4 +1,4 @@
package funkin.audiovis.dsp;
package funkin.audio.visualize.dsp;
using Lambda;

View file

@ -208,25 +208,32 @@ typedef SongEventSchemaField =
type:SongEventFieldType,
/**
* Used for ENUM values.
* Used only for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,
/**
* Used for INTEGER and FLOAT values.
* The minimum value that can be entered.
* @default No minimum
*/
?min:Float,
/**
* Used for INTEGER and FLOAT values.
* The maximum value that can be entered.
* @default No maximum
*/
?max:Float,
/**
* Used for INTEGER and FLOAT values.
* The step value that will be used when incrementing/decrementing the value.
* @default `0.1`
*/
?step:Float,
/**
* An optional default value for the field.
*/

View file

@ -453,7 +453,7 @@ class SongChartData
}
}
class SongEventData
class SongEventDataRaw
{
/**
* The timestamp of the event. The timestamp is in the format of the song's time format.
@ -509,40 +509,67 @@ class SongEventData
return _stepTime = Conductor.getTimeInSteps(this.time);
}
}
/**
* Wrap SongEventData in an abstract so we can overload operators.
*/
@:forward(time, event, value, activated, getStepTime)
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
{
public function new(time:Float, event:String, value:Dynamic = null)
{
this = new SongEventDataRaw(time, event, value);
}
public inline function getDynamic(key:String):Null<Dynamic>
{
return value == null ? null : Reflect.field(value, key);
return this.value == null ? null : Reflect.field(this.value, key);
}
public inline function getBool(key:String):Null<Bool>
{
return value == null ? null : cast Reflect.field(value, key);
return this.value == null ? null : cast Reflect.field(this.value, key);
}
public inline function getInt(key:String):Null<Int>
{
return value == null ? null : cast Reflect.field(value, key);
if (this.value == null) return null;
var result = Reflect.field(this.value, key);
if (result == null) return null;
if (Std.isOfType(result, Int)) return result;
if (Std.isOfType(result, String)) return Std.parseInt(cast result);
return cast result;
}
public inline function getFloat(key:String):Null<Float>
{
return value == null ? null : cast Reflect.field(value, key);
if (this.value == null) return null;
var result = Reflect.field(this.value, key);
if (result == null) return null;
if (Std.isOfType(result, Float)) return result;
if (Std.isOfType(result, String)) return Std.parseFloat(cast result);
return cast result;
}
public inline function getString(key:String):String
{
return value == null ? null : cast Reflect.field(value, key);
return this.value == null ? null : cast Reflect.field(this.value, key);
}
public inline function getArray(key:String):Array<Dynamic>
{
return value == null ? null : cast Reflect.field(value, key);
return this.value == null ? null : cast Reflect.field(this.value, key);
}
public inline function getBoolArray(key:String):Array<Bool>
{
return value == null ? null : cast Reflect.field(value, key);
return this.value == null ? null : cast Reflect.field(this.value, key);
}
public function clone():SongEventData
{
return new SongEventData(this.time, this.event, this.value);
}
@:op(A == B)
@ -590,7 +617,7 @@ class SongEventData
}
}
class SongNoteData
class SongNoteDataRaw
{
/**
* The timestamp of the note. The timestamp is in the format of the song's time format.
@ -661,6 +688,48 @@ class SongNoteData
return _stepTime = Conductor.getTimeInSteps(this.time);
}
@:jignored
var _stepLength:Null<Float> = null;
/**
* @param force Set to `true` to force recalculation (good after BPM changes)
* @return The length of the hold note in steps, or `0` if this is not a hold note.
*/
public function getStepLength(force = false):Float
{
if (this.length <= 0) return 0.0;
if (_stepLength != null && !force) return _stepLength;
return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
}
public function setStepLength(value:Float):Void
{
if (value <= 0)
{
this.length = 0.0;
}
else
{
var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
this.length = lengthMs;
}
_stepLength = null;
}
}
/**
* Wrap SongNoteData in an abstract so we can overload operators.
*/
@:forward
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
{
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
{
this = new SongNoteDataRaw(time, data, length, kind);
}
/**
* The direction of the note, if applicable.
* Strips the strumline index from the data.
@ -674,7 +743,12 @@ class SongNoteData
public function getDirectionName(strumlineSize:Int = 4):String
{
switch (this.data % strumlineSize)
return SongNoteData.buildDirectionName(this.data, strumlineSize);
}
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
{
switch (data % strumlineSize)
{
case 0:
return 'Left';
@ -711,36 +785,6 @@ class SongNoteData
return getStrumlineIndex(strumlineSize) == 0;
}
@:jignored
var _stepLength:Null<Float> = null;
/**
* @param force Set to `true` to force recalculation (good after BPM changes)
* @return The length of the hold note in steps, or `0` if this is not a hold note.
*/
public function getStepLength(force = false):Float
{
if (this.length <= 0) return 0.0;
if (_stepLength != null && !force) return _stepLength;
return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
}
public function setStepLength(value:Float):Void
{
if (value <= 0)
{
this.length = 0.0;
}
else
{
var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
this.length = lengthMs;
}
_stepLength = null;
}
@:jignored
public var isHoldNote(get, never):Bool;
@ -803,6 +847,11 @@ class SongNoteData
return this.time <= other.time;
}
public function clone():SongNoteData
{
return new SongNoteData(this.time, this.data, this.length, this.kind);
}
/**
* Produces a string representation suitable for debugging.
*/

View file

@ -3,6 +3,7 @@ package funkin.data.song;
import flixel.util.FlxSort;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongTimeChange;
import funkin.util.ClipboardUtil;
import funkin.util.SerializerUtil;
@ -66,8 +67,14 @@ class SongDataUtils
var result = notes.filter(function(note:SongNoteData):Bool {
for (x in subtrahend)
{
// The currently iterated note is in the subtrahend array.
// SongNoteData's == operation has been overridden so that this will work.
if (x == note) return false;
if (x == note)
{
return false;
}
}
return true;
});
@ -157,6 +164,18 @@ class SongDataUtils
return events;
}
/**
* Sort an array of notes by strum time.
*/
public static function sortTimeChanges(timeChanges:Array<SongTimeChange>, desc:Bool = false):Array<SongTimeChange>
{
// TODO: Modifies the array in place. Is this okay?
timeChanges.sort(function(a:SongTimeChange, b:SongTimeChange):Int {
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.timeStamp, b.timeStamp);
});
return timeChanges;
}
/**
* Serialize note and event data and write it to the clipboard.
*/

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.util.FlxColor;
import openfl.display.ShaderParameter;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.addons.display.FlxRuntimeShader;
import funkin.Paths;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxColor;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.addons.display.FlxRuntimeShader;
import funkin.Paths;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.addons.display.FlxRuntimeShader;
import funkin.Paths;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.addons.display.FlxRuntimeShader;
import funkin.Paths;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.math.FlxPoint;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxColor;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxColor;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.math.FlxPoint;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;

View file

@ -1,4 +1,4 @@
package funkin.shaderslmfao;
package funkin.graphics.shaders;
import flixel.addons.display.FlxRuntimeShader;
import openfl.Assets;

View file

@ -12,7 +12,10 @@ using Lambda;
using StringTools;
using funkin.util.tools.ArraySortTools;
using funkin.util.tools.ArrayTools;
using funkin.util.tools.DynamicTools;
using funkin.util.tools.FloatTools;
using funkin.util.tools.Int64Tools;
using funkin.util.tools.IntTools;
using funkin.util.tools.IteratorTools;
using funkin.util.tools.MapTools;
using funkin.util.tools.SongEventDataArrayTools;

View file

@ -1,5 +1,4 @@
package funkin;
package funkin.input;
import flixel.input.gamepad.FlxGamepad;
import flixel.util.FlxDirectionFlags;
@ -23,96 +22,14 @@ import flixel.util.FlxTimer;
import lime.ui.Haptic;
/**
* Since, in many cases multiple actions should use similar keys, we don't want the
* rebinding UI to list every action. ActionBinders are what the user percieves as
* an input so, for instance, they can't set jump-press and jump-release to different keys.
*/
enum Control
{
// List notes in order from left to right on gameplay screen.
NOTE_LEFT;
NOTE_DOWN;
NOTE_UP;
NOTE_RIGHT;
UI_UP;
UI_LEFT;
UI_RIGHT;
UI_DOWN;
RESET;
ACCEPT;
BACK;
PAUSE;
CUTSCENE_ADVANCE;
CUTSCENE_SKIP;
VOLUME_UP;
VOLUME_DOWN;
VOLUME_MUTE;
#if CAN_CHEAT
CHEAT;
#end
}
enum
abstract Action(String) to String from String
{
var UI_UP = "ui_up";
var UI_LEFT = "ui_left";
var UI_RIGHT = "ui_right";
var UI_DOWN = "ui_down";
var UI_UP_P = "ui_up-press";
var UI_LEFT_P = "ui_left-press";
var UI_RIGHT_P = "ui_right-press";
var UI_DOWN_P = "ui_down-press";
var UI_UP_R = "ui_up-release";
var UI_LEFT_R = "ui_left-release";
var UI_RIGHT_R = "ui_right-release";
var UI_DOWN_R = "ui_down-release";
var NOTE_UP = "note_up";
var NOTE_LEFT = "note_left";
var NOTE_RIGHT = "note_right";
var NOTE_DOWN = "note_down";
var NOTE_UP_P = "note_up-press";
var NOTE_LEFT_P = "note_left-press";
var NOTE_RIGHT_P = "note_right-press";
var NOTE_DOWN_P = "note_down-press";
var NOTE_UP_R = "note_up-release";
var NOTE_LEFT_R = "note_left-release";
var NOTE_RIGHT_R = "note_right-release";
var NOTE_DOWN_R = "note_down-release";
var ACCEPT = "accept";
var BACK = "back";
var PAUSE = "pause";
var CUTSCENE_ADVANCE = "cutscene_advance";
var CUTSCENE_SKIP = "cutscene_skip";
var VOLUME_UP = "volume_up";
var VOLUME_DOWN = "volume_down";
var VOLUME_MUTE = "volume_mute";
var RESET = "reset";
#if CAN_CHEAT
var CHEAT = "cheat";
#end
}
enum Device
{
Keys;
Gamepad(id:Int);
}
enum KeyboardScheme
{
Solo;
Duo(first:Bool);
None;
Custom;
}
/**
* A list of actions that a player would invoke via some input device.
* Uses FlxActions to funnel various inputs to a single action.
* A core class which handles receiving player input and interpreting it into game actions.
*/
class Controls extends FlxActionSet
{
/**
* A list of actions that a player would invoke via some input device.
* Uses FlxActions to funnel various inputs to a single action.
*/
var _ui_up = new FlxActionDigital(Action.UI_UP);
var _ui_left = new FlxActionDigital(Action.UI_LEFT);
var _ui_right = new FlxActionDigital(Action.UI_RIGHT);
@ -1241,3 +1158,88 @@ class FlxActionInputDigitalAndroid extends FlxActionInputDigital
}
}
#end
/**
* Since, in many cases multiple actions should use similar keys, we don't want the
* rebinding UI to list every action. ActionBinders are what the user percieves as
* an input so, for instance, they can't set jump-press and jump-release to different keys.
*/
enum Control
{
// List notes in order from left to right on gameplay screen.
NOTE_LEFT;
NOTE_DOWN;
NOTE_UP;
NOTE_RIGHT;
UI_UP;
UI_LEFT;
UI_RIGHT;
UI_DOWN;
RESET;
ACCEPT;
BACK;
PAUSE;
CUTSCENE_ADVANCE;
CUTSCENE_SKIP;
VOLUME_UP;
VOLUME_DOWN;
VOLUME_MUTE;
#if CAN_CHEAT
CHEAT;
#end
}
enum
abstract Action(String) to String from String
{
var UI_UP = "ui_up";
var UI_LEFT = "ui_left";
var UI_RIGHT = "ui_right";
var UI_DOWN = "ui_down";
var UI_UP_P = "ui_up-press";
var UI_LEFT_P = "ui_left-press";
var UI_RIGHT_P = "ui_right-press";
var UI_DOWN_P = "ui_down-press";
var UI_UP_R = "ui_up-release";
var UI_LEFT_R = "ui_left-release";
var UI_RIGHT_R = "ui_right-release";
var UI_DOWN_R = "ui_down-release";
var NOTE_UP = "note_up";
var NOTE_LEFT = "note_left";
var NOTE_RIGHT = "note_right";
var NOTE_DOWN = "note_down";
var NOTE_UP_P = "note_up-press";
var NOTE_LEFT_P = "note_left-press";
var NOTE_RIGHT_P = "note_right-press";
var NOTE_DOWN_P = "note_down-press";
var NOTE_UP_R = "note_up-release";
var NOTE_LEFT_R = "note_left-release";
var NOTE_RIGHT_R = "note_right-release";
var NOTE_DOWN_R = "note_down-release";
var ACCEPT = "accept";
var BACK = "back";
var PAUSE = "pause";
var CUTSCENE_ADVANCE = "cutscene_advance";
var CUTSCENE_SKIP = "cutscene_skip";
var VOLUME_UP = "volume_up";
var VOLUME_DOWN = "volume_down";
var VOLUME_MUTE = "volume_mute";
var RESET = "reset";
#if CAN_CHEAT
var CHEAT = "cheat";
#end
}
enum Device
{
Keys;
Gamepad(id:Int);
}
enum KeyboardScheme
{
Solo;
Duo(first:Bool);
None;
Custom;
}

View file

@ -1,5 +1,6 @@
package funkin.input;
import haxe.ui.backend.flixel.CursorHelper;
import openfl.utils.Assets;
import lime.app.Future;
import openfl.display.BitmapData;
@ -33,7 +34,7 @@ class Cursor
Cursor.cursorMode = null;
}
static final CURSOR_DEFAULT_PARAMS:CursorParams =
public static final CURSOR_DEFAULT_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-default.png",
scale: 1.0,
@ -42,7 +43,7 @@ class Cursor
};
static var assetCursorDefault:Null<BitmapData> = null;
static final CURSOR_CROSS_PARAMS:CursorParams =
public static final CURSOR_CROSS_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-cross.png",
scale: 1.0,
@ -51,7 +52,7 @@ class Cursor
};
static var assetCursorCross:Null<BitmapData> = null;
static final CURSOR_ERASER_PARAMS:CursorParams =
public static final CURSOR_ERASER_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-eraser.png",
scale: 1.0,
@ -60,7 +61,7 @@ class Cursor
};
static var assetCursorEraser:Null<BitmapData> = null;
static final CURSOR_GRABBING_PARAMS:CursorParams =
public static final CURSOR_GRABBING_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-grabbing.png",
scale: 1.0,
@ -69,7 +70,7 @@ class Cursor
};
static var assetCursorGrabbing:Null<BitmapData> = null;
static final CURSOR_HOURGLASS_PARAMS:CursorParams =
public static final CURSOR_HOURGLASS_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-hourglass.png",
scale: 1.0,
@ -78,7 +79,7 @@ class Cursor
};
static var assetCursorHourglass:Null<BitmapData> = null;
static final CURSOR_POINTER_PARAMS:CursorParams =
public static final CURSOR_POINTER_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-pointer.png",
scale: 1.0,
@ -87,7 +88,7 @@ class Cursor
};
static var assetCursorPointer:Null<BitmapData> = null;
static final CURSOR_TEXT_PARAMS:CursorParams =
public static final CURSOR_TEXT_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-text.png",
scale: 0.2,
@ -96,7 +97,7 @@ class Cursor
};
static var assetCursorText:Null<BitmapData> = null;
static final CURSOR_TEXT_VERTICAL_PARAMS:CursorParams =
public static final CURSOR_TEXT_VERTICAL_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-text-vertical.png",
scale: 0.2,
@ -105,7 +106,7 @@ class Cursor
};
static var assetCursorTextVertical:Null<BitmapData> = null;
static final CURSOR_ZOOM_IN_PARAMS:CursorParams =
public static final CURSOR_ZOOM_IN_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-zoom-in.png",
scale: 1.0,
@ -114,7 +115,7 @@ class Cursor
};
static var assetCursorZoomIn:Null<BitmapData> = null;
static final CURSOR_ZOOM_OUT_PARAMS:CursorParams =
public static final CURSOR_ZOOM_OUT_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-zoom-out.png",
scale: 1.0,
@ -123,7 +124,7 @@ class Cursor
};
static var assetCursorZoomOut:Null<BitmapData> = null;
static final CURSOR_CROSSHAIR_PARAMS:CursorParams =
public static final CURSOR_CROSSHAIR_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-crosshair.png",
scale: 1.0,
@ -132,7 +133,7 @@ class Cursor
};
static var assetCursorCrosshair:Null<BitmapData> = null;
static final CURSOR_CELL_PARAMS:CursorParams =
public static final CURSOR_CELL_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-cell.png",
scale: 1.0,
@ -500,6 +501,28 @@ class Cursor
{
trace("Failed to load cursor graphic for cursor mode " + cursorMode + ": " + error);
}
public static function registerHaxeUICursors():Void
{
CursorHelper.useCustomCursors = true;
registerHaxeUICursor('default', CURSOR_DEFAULT_PARAMS);
registerHaxeUICursor('cross', CURSOR_CROSS_PARAMS);
registerHaxeUICursor('eraser', CURSOR_ERASER_PARAMS);
registerHaxeUICursor('grabbing', CURSOR_GRABBING_PARAMS);
registerHaxeUICursor('hourglass', CURSOR_HOURGLASS_PARAMS);
registerHaxeUICursor('pointer', CURSOR_POINTER_PARAMS);
registerHaxeUICursor('text', CURSOR_TEXT_PARAMS);
registerHaxeUICursor('text-vertical', CURSOR_TEXT_VERTICAL_PARAMS);
registerHaxeUICursor('zoom-in', CURSOR_ZOOM_IN_PARAMS);
registerHaxeUICursor('zoom-out', CURSOR_ZOOM_OUT_PARAMS);
registerHaxeUICursor('crosshair', CURSOR_CROSSHAIR_PARAMS);
registerHaxeUICursor('cell', CURSOR_CELL_PARAMS);
}
public static function registerHaxeUICursor(id:String, params:CursorParams):Void
{
CursorHelper.registerCursor(id, params.graphic, params.scale, params.offsetX, params.offsetY);
}
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/cursor

View file

@ -108,8 +108,7 @@ class TurboKeyHandler extends FlxBasic
* @param repeatDelay How long to wait between repeats.
* @return A TurboKeyHandler
*/
public static overload inline extern function build(inputKeys:Array<FlxKey>, ?delay:Float = DEFAULT_DELAY,
?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
public static overload inline extern function build(inputKeys:Array<FlxKey>, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
{
return new TurboKeyHandler(inputKeys, delay, interval);
}

View file

@ -5,4 +5,4 @@ package funkin.modding.base;
* Create a scripted class that extends MusicBeatState to use this.
*/
@:hscriptClass
class ScriptedMusicBeatState extends funkin.MusicBeatState implements HScriptedClass {}
class ScriptedMusicBeatState extends funkin.ui.MusicBeatState implements HScriptedClass {}

View file

@ -5,4 +5,4 @@ package funkin.modding.base;
* Create a scripted class that extends MusicBeatSubState to use this.
*/
@:hscriptClass
class ScriptedMusicBeatSubState extends funkin.MusicBeatSubState implements HScriptedClass {}
class ScriptedMusicBeatSubState extends funkin.ui.MusicBeatSubState implements HScriptedClass {}

View file

@ -10,265 +10,12 @@ import funkin.play.notes.NoteDirection;
import openfl.events.EventType;
import openfl.events.KeyboardEvent;
typedef ScriptEventType = EventType<ScriptEvent>;
/**
* This is a base class for all events that are issued to scripted classes.
* It can be used to identify the type of event called, store data, and cancel event propagation.
*/
class ScriptEvent
{
/**
* Called when the relevant object is created.
* Keep in mind that the constructor may be called before the object is needed,
* for the purposes of caching data or otherwise.
*
* This event is not cancelable.
*/
public static inline final CREATE:ScriptEventType = 'CREATE';
/**
* Called when the relevant object is destroyed.
* This should perform relevant cleanup to ensure good performance.
*
* This event is not cancelable.
*/
public static inline final DESTROY:ScriptEventType = 'DESTROY';
/**
* Called when the relevent object is added to the game state.
* This assumes all data is loaded and ready to go.
*
* This event is not cancelable.
*/
public static inline final ADDED:ScriptEventType = 'ADDED';
/**
* Called during the update function.
* This is called every frame, so be careful!
*
* This event is not cancelable.
*/
public static inline final UPDATE:ScriptEventType = 'UPDATE';
/**
* Called when the player moves to pause the game.
*
* This event IS cancelable! Canceling the event will prevent the game from pausing.
*/
public static inline final PAUSE:ScriptEventType = 'PAUSE';
/**
* Called when the player moves to unpause the game while paused.
*
* This event IS cancelable! Canceling the event will prevent the game from resuming.
*/
public static inline final RESUME:ScriptEventType = 'RESUME';
/**
* Called once per step in the song. This happens 4 times per measure.
*
* This event is not cancelable.
*/
public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT';
/**
* Called once per step in the song. This happens 16 times per measure.
*
* This event is not cancelable.
*/
public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT';
/**
* Called when a character hits a note.
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
*
* This event IS cancelable! Canceling this event prevents the note from being hit,
* and will likely result in a miss later.
*/
public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT';
/**
* Called when a character misses a note.
* Important information such as note data, player/opponent, etc. are all provided.
*
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
* avoiding a combo break and lost health.
*/
public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS';
/**
* Called when a character presses a note when there was none there, causing them to lose health.
* Important information such as direction pressed, etc. are all provided.
*
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
* avoiding lost health/score and preventing the miss animation.
*/
public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS';
/**
* Called when a song event is reached in the chart.
*
* This event IS cancelable! Cancelling this event prevents the event from being triggered,
* thus blocking its normal functionality.
*/
public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT';
/**
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
*
* This event is not cancelable.
*/
public static inline final SONG_START:ScriptEventType = 'SONG_START';
/**
* Called when the song ends. This happens as the instrumental and vocals end.
*
* This event is not cancelable.
*/
public static inline final SONG_END:ScriptEventType = 'SONG_END';
/**
* Called when the countdown begins. This occurs before the song starts.
*
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
* - The song will not start until you call Countdown.performCountdown() later.
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
*/
public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START';
/**
* Called when a step of the countdown happens.
* Includes information about what step of the countdown was hit.
*
* This event IS cancelable! Canceling this event will pause the countdown.
* - The countdown will not resume until you call PlayState.resumeCountdown().
*/
public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP';
/**
* Called when the countdown is done but just before the song starts.
*
* This event is not cancelable.
*/
public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END';
/**
* Called before the game over screen triggers and the death animation plays.
*
* This event is not cancelable.
*/
public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER';
/**
* Called after the player presses a key to restart the game.
* This can happen from the pause menu or the game over screen.
*
* This event IS cancelable! Canceling this event will prevent the game from restarting.
*/
public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY';
/**
* Called when the player pushes down any key on the keyboard.
*
* This event is not cancelable.
*/
public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN';
/**
* Called when the player releases a key on the keyboard.
*
* This event is not cancelable.
*/
public static inline final KEY_UP:ScriptEventType = 'KEY_UP';
/**
* Called when the game has finished loading the notes from JSON.
* This allows modders to mutate the notes before they are used in the song.
*
* This event is not cancelable.
*/
public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED';
/**
* Called when the game is about to switch the current FlxState.
*
* This event is not cancelable.
*/
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN';
/**
* Called when the game has finished switching the current FlxState.
*
* This event is not cancelable.
*/
public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END';
/**
* Called when the game is about to open a new FlxSubState.
*
* This event is not cancelable.
*/
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN';
/**
* Called when the game has finished opening a new FlxSubState.
*
* This event is not cancelable.
*/
public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END';
/**
* Called when the game is about to close the current FlxSubState.
*
* This event is not cancelable.
*/
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN';
/**
* Called when the game has finished closing the current FlxSubState.
*
* This event is not cancelable.
*/
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END';
/**
* Called when the game starts a conversation.
*
* This event is not cancelable.
*/
public static inline final DIALOGUE_START:ScriptEventType = 'DIALOGUE_START';
/**
* Called to display the next line of conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line.
* - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation.
*/
public static inline final DIALOGUE_LINE:ScriptEventType = 'DIALOGUE_LINE';
/**
* Called to skip scrolling the current line of conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line.
* - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing.
*/
public static inline final DIALOGUE_COMPLETE_LINE:ScriptEventType = 'DIALOGUE_COMPLETE_LINE';
/**
* Called to skip the conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from skipping.
*/
public static inline final DIALOGUE_SKIP:ScriptEventType = 'DIALOGUE_SKIP';
/**
* Called when the game ends a conversation.
*
* This event is not cancelable.
*/
public static inline final DIALOGUE_END:ScriptEventType = 'DIALOGUE_END';
/**
* If true, the behavior associated with this event can be prevented.
* For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
@ -411,7 +158,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent
public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
{
super(ScriptEvent.NOTE_GHOST_MISS, true);
super(NOTE_GHOST_MISS, true);
this.dir = dir;
this.hasPossibleNotes = hasPossibleNotes;
this.healthChange = healthChange;
@ -439,7 +186,7 @@ class SongEventScriptEvent extends ScriptEvent
public function new(event:funkin.data.song.SongData.SongEventData):Void
{
super(ScriptEvent.SONG_EVENT, true);
super(SONG_EVENT, true);
this.event = event;
}
@ -462,7 +209,7 @@ class UpdateScriptEvent extends ScriptEvent
public function new(elapsed:Float):Void
{
super(ScriptEvent.UPDATE, false);
super(UPDATE, false);
this.elapsed = elapsed;
}
@ -591,7 +338,7 @@ class SongLoadScriptEvent extends ScriptEvent
public function new(id:String, difficulty:String, notes:Array<SongNoteData>):Void
{
super(ScriptEvent.SONG_LOADED, false);
super(SONG_LOADED, false);
this.id = id;
this.difficulty = difficulty;
this.notes = notes;
@ -660,7 +407,7 @@ class PauseScriptEvent extends ScriptEvent
public function new(gitaroo:Bool):Void
{
super(ScriptEvent.PAUSE, true);
super(PAUSE, true);
this.gitaroo = gitaroo;
}
}

View file

@ -23,15 +23,16 @@ class ScriptEventDispatcher
// IScriptedClass
switch (event.type)
{
case ScriptEvent.CREATE:
case CREATE:
target.onCreate(event);
return;
case ScriptEvent.DESTROY:
case DESTROY:
target.onDestroy(event);
return;
case ScriptEvent.UPDATE:
case UPDATE:
target.onUpdate(cast event);
return;
default: // Continue;
}
if (Std.isOfType(target, IStateStageProp))
@ -39,9 +40,10 @@ class ScriptEventDispatcher
var t:IStateStageProp = cast(target, IStateStageProp);
switch (event.type)
{
case ScriptEvent.ADDED:
case ADDED:
t.onAdd(cast event);
return;
default: // Continue;
}
}
@ -50,21 +52,22 @@ class ScriptEventDispatcher
var t:IDialogueScriptedClass = cast(target, IDialogueScriptedClass);
switch (event.type)
{
case ScriptEvent.DIALOGUE_START:
case DIALOGUE_START:
t.onDialogueStart(cast event);
return;
case ScriptEvent.DIALOGUE_LINE:
case DIALOGUE_LINE:
t.onDialogueLine(cast event);
return;
case ScriptEvent.DIALOGUE_COMPLETE_LINE:
case DIALOGUE_COMPLETE_LINE:
t.onDialogueCompleteLine(cast event);
return;
case ScriptEvent.DIALOGUE_SKIP:
case DIALOGUE_SKIP:
t.onDialogueSkip(cast event);
return;
case ScriptEvent.DIALOGUE_END:
case DIALOGUE_END:
t.onDialogueEnd(cast event);
return;
default: // Continue;
}
}
@ -73,54 +76,55 @@ class ScriptEventDispatcher
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
switch (event.type)
{
case ScriptEvent.NOTE_HIT:
case NOTE_HIT:
t.onNoteHit(cast event);
return;
case ScriptEvent.NOTE_MISS:
case NOTE_MISS:
t.onNoteMiss(cast event);
return;
case ScriptEvent.NOTE_GHOST_MISS:
case NOTE_GHOST_MISS:
t.onNoteGhostMiss(cast event);
return;
case ScriptEvent.SONG_BEAT_HIT:
case SONG_BEAT_HIT:
t.onBeatHit(cast event);
return;
case ScriptEvent.SONG_STEP_HIT:
case SONG_STEP_HIT:
t.onStepHit(cast event);
return;
case ScriptEvent.SONG_START:
case SONG_START:
t.onSongStart(event);
return;
case ScriptEvent.SONG_END:
case SONG_END:
t.onSongEnd(event);
return;
case ScriptEvent.SONG_RETRY:
case SONG_RETRY:
t.onSongRetry(event);
return;
case ScriptEvent.GAME_OVER:
case GAME_OVER:
t.onGameOver(event);
return;
case ScriptEvent.PAUSE:
case PAUSE:
t.onPause(cast event);
return;
case ScriptEvent.RESUME:
case RESUME:
t.onResume(event);
return;
case ScriptEvent.SONG_EVENT:
case SONG_EVENT:
t.onSongEvent(cast event);
return;
case ScriptEvent.COUNTDOWN_START:
case COUNTDOWN_START:
t.onCountdownStart(cast event);
return;
case ScriptEvent.COUNTDOWN_STEP:
case COUNTDOWN_STEP:
t.onCountdownStep(cast event);
return;
case ScriptEvent.COUNTDOWN_END:
case COUNTDOWN_END:
t.onCountdownEnd(cast event);
return;
case ScriptEvent.SONG_LOADED:
case SONG_LOADED:
t.onSongLoaded(cast event);
return;
default: // Continue;
}
}
@ -129,24 +133,25 @@ class ScriptEventDispatcher
var t = cast(target, IStateChangingScriptedClass);
switch (event.type)
{
case ScriptEvent.STATE_CHANGE_BEGIN:
case STATE_CHANGE_BEGIN:
t.onStateChangeBegin(cast event);
return;
case ScriptEvent.STATE_CHANGE_END:
case STATE_CHANGE_END:
t.onStateChangeEnd(cast event);
return;
case ScriptEvent.SUBSTATE_OPEN_BEGIN:
case SUBSTATE_OPEN_BEGIN:
t.onSubStateOpenBegin(cast event);
return;
case ScriptEvent.SUBSTATE_OPEN_END:
case SUBSTATE_OPEN_END:
t.onSubStateOpenEnd(cast event);
return;
case ScriptEvent.SUBSTATE_CLOSE_BEGIN:
case SUBSTATE_CLOSE_BEGIN:
t.onSubStateCloseBegin(cast event);
return;
case ScriptEvent.SUBSTATE_CLOSE_END:
case SUBSTATE_CLOSE_END:
t.onSubStateCloseEnd(cast event);
return;
default: // Continue;
}
}
else

View file

@ -0,0 +1,271 @@
package funkin.modding.events;
enum abstract ScriptEventType(String) from String to String
{
/**
* 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.
*/
var CREATE = 'CREATE';
/**
* Called when the relevant object is destroyed.
* This should perform relevant cleanup to ensure good performance.
*
* This event is not cancelable.
*/
var DESTROY = 'DESTROY';
/**
* Called when the relevent object is added to the game state.
* This assumes all data is loaded and ready to go.
*
* This event is not cancelable.
*/
var ADDED = 'ADDED';
/**
* Called during the update function.
* This is called every frame, so be careful!
*
* This event is not cancelable.
*/
var UPDATE = 'UPDATE';
/**
* Called when the player moves to pause the game.
*
* This event IS cancelable! Canceling the event will prevent the game from pausing.
*/
var PAUSE = '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.
*/
var RESUME = 'RESUME';
/**
* Called once per step in the song. This happens 4 times per measure.
*
* This event is not cancelable.
*/
var SONG_BEAT_HIT = 'BEAT_HIT';
/**
* Called once per step in the song. This happens 16 times per measure.
*
* This event is not cancelable.
*/
var SONG_STEP_HIT = '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.
*/
var NOTE_HIT = '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.
*/
var NOTE_MISS = 'NOTE_MISS';
/**
* Called when a character presses a note when there was none there, causing them to lose health.
* Important information such as direction pressed, etc. are all provided.
*
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
* avoiding lost health/score and preventing the miss animation.
*/
var NOTE_GHOST_MISS = 'NOTE_GHOST_MISS';
/**
* Called when a song event is reached in the chart.
*
* This event IS cancelable! Cancelling this event prevents the event from being triggered,
* thus blocking its normal functionality.
*/
var SONG_EVENT = 'SONG_EVENT';
/**
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
*
* This event is not cancelable.
*/
var SONG_START = 'SONG_START';
/**
* Called when the song ends. This happens as the instrumental and vocals end.
*
* This event is not cancelable.
*/
var SONG_END = 'SONG_END';
/**
* Called when the countdown begins. This occurs before the song starts.
*
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
* - The song will not start until you call Countdown.performCountdown() later.
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
*/
var COUNTDOWN_START = '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().
*/
var COUNTDOWN_STEP = 'COUNTDOWN_STEP';
/**
* Called when the countdown is done but just before the song starts.
*
* This event is not cancelable.
*/
var COUNTDOWN_END = 'COUNTDOWN_END';
/**
* Called before the game over screen triggers and the death animation plays.
*
* This event is not cancelable.
*/
var GAME_OVER = 'GAME_OVER';
/**
* Called after the player presses a key to restart the game.
* This can happen from the pause menu or the game over screen.
*
* This event IS cancelable! Canceling this event will prevent the game from restarting.
*/
var SONG_RETRY = 'SONG_RETRY';
/**
* Called when the player pushes down any key on the keyboard.
*
* This event is not cancelable.
*/
var KEY_DOWN = 'KEY_DOWN';
/**
* Called when the player releases a key on the keyboard.
*
* This event is not cancelable.
*/
var KEY_UP = '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.
*/
var SONG_LOADED = 'SONG_LOADED';
/**
* Called when the game is about to switch the current FlxState.
*
* This event is not cancelable.
*/
var STATE_CHANGE_BEGIN = 'STATE_CHANGE_BEGIN';
/**
* Called when the game has finished switching the current FlxState.
*
* This event is not cancelable.
*/
var STATE_CHANGE_END = 'STATE_CHANGE_END';
/**
* Called when the game is about to open a new FlxSubState.
*
* This event is not cancelable.
*/
var SUBSTATE_OPEN_BEGIN = 'SUBSTATE_OPEN_BEGIN';
/**
* Called when the game has finished opening a new FlxSubState.
*
* This event is not cancelable.
*/
var SUBSTATE_OPEN_END = 'SUBSTATE_OPEN_END';
/**
* Called when the game is about to close the current FlxSubState.
*
* This event is not cancelable.
*/
var SUBSTATE_CLOSE_BEGIN = 'SUBSTATE_CLOSE_BEGIN';
/**
* Called when the game has finished closing the current FlxSubState.
*
* This event is not cancelable.
*/
var SUBSTATE_CLOSE_END = 'SUBSTATE_CLOSE_END';
/**
* Called when the game starts a conversation.
*
* This event is not cancelable.
*/
var DIALOGUE_START = 'DIALOGUE_START';
/**
* Called to display the next line of conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from moving to the next line.
* - This event is called when the conversation starts, or when the user presses ACCEPT to advance the conversation.
*/
var DIALOGUE_LINE = 'DIALOGUE_LINE';
/**
* Called to skip scrolling the current line of conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from skipping to the next line.
* - This event is called when the user presses ACCEPT to advance the conversation while it is already advancing.
*/
var DIALOGUE_COMPLETE_LINE = 'DIALOGUE_COMPLETE_LINE';
/**
* Called to skip the conversation.
*
* This event IS cancelable! Canceling this event will prevent the conversation from skipping.
*/
var DIALOGUE_SKIP = 'DIALOGUE_SKIP';
/**
* Called when the game ends a conversation.
*
* This event is not cancelable.
*/
var DIALOGUE_END = 'DIALOGUE_END';
/**
* Allow for comparing `ScriptEventType` to `String`.
*/
@:op(A == B) private static inline function equals(a:ScriptEventType, b:String):Bool
{
return (a : String) == b;
}
/**
* Allow for comparing `ScriptEventType` to `String`.
*/
@:op(A != B) private static inline function notEquals(a:ScriptEventType, b:String):Bool
{
return (a : String) != b;
}
}

View file

@ -55,7 +55,7 @@ class ModuleHandler
static function onStateSwitchComplete():Void
{
callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true));
callEvent(new StateChangeScriptEvent(STATE_CHANGE_END, FlxG.state, true));
}
static function addToModuleCache(module:Module):Void
@ -119,7 +119,7 @@ class ModuleHandler
{
if (moduleCache != null)
{
var event = new ScriptEvent(ScriptEvent.DESTROY, false);
var event = new ScriptEvent(DESTROY, false);
// Note: Ignore stopPropagation()
for (key => value in moduleCache)
@ -148,6 +148,6 @@ class ModuleHandler
public static inline function callOnCreate():Void
{
callEvent(new ScriptEvent(ScriptEvent.CREATE, false));
callEvent(new ScriptEvent(CREATE, false));
}
}

View file

@ -43,7 +43,7 @@ class Countdown
Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
// Handle onBeatHit events manually
// @:privateAccess
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
// The timer function gets called based on the beat of the song.
countdownTimer = new FlxTimer();
@ -59,7 +59,7 @@ class Countdown
// onBeatHit events are now properly dispatched by the Conductor even at negative timestamps,
// so calling this is no longer necessary.
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
// Countdown graphic.
showCountdownGraphic(countdownStep, isPixelStyle);
@ -94,11 +94,11 @@ class Countdown
switch (index)
{
case BEFORE:
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_START, index);
event = new CountdownScriptEvent(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);
event = new CountdownScriptEvent(COUNTDOWN_STEP, index);
case AFTER:
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_END, index, false);
event = new CountdownScriptEvent(COUNTDOWN_END, index, false);
default:
return true;
}

View file

@ -1,68 +0,0 @@
package funkin.play;
import funkin.play.character.BaseCharacter;
import flixel.FlxSprite;
class Fighter extends BaseCharacter
{
public function new(?x:Float = 0, ?y:Float = 0, ?char:String = "pico-fighter")
{
super(char, Custom);
this.x = x;
this.y = y;
animation.finishCallback = function(anim:String) {
switch anim
{
case "punch low" | "punch high" | "block" | 'dodge':
dance(true);
}
};
}
public var actions:Array<ACTIONS> = [PUNCH, BLOCK, DODGE];
public function doSomething(?forceAction:ACTIONS)
{
var daAction:ACTIONS = FlxG.random.getObject(actions);
if (forceAction != null) daAction = forceAction;
switch (daAction)
{
case PUNCH:
punch();
case BLOCK:
block();
case DODGE:
dodge();
}
}
public var curAction:ACTIONS = DODGE;
function dodge()
{
playAnimation('dodge');
curAction = DODGE;
}
public function block()
{
playAnimation('block');
curAction = BLOCK;
}
public function punch()
{
curAction = PUNCH;
playAnimation('punch ' + (FlxG.random.bool() ? "low" : "high"));
}
}
enum ACTIONS
{
DODGE;
BLOCK;
PUNCH;
}

View file

@ -7,9 +7,11 @@ import flixel.sound.FlxSound;
import funkin.ui.story.StoryMenuState;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.ui.MusicBeatSubState;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState;
import funkin.ui.freeplay.FreeplayState;
import funkin.play.character.BaseCharacter;
/**

View file

@ -1,9 +1,11 @@
package funkin;
package funkin.play;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import funkin.play.PlayState;
import funkin.ui.MusicBeatState;
import flixel.addons.transition.FlxTransitionableState;
import funkin.ui.mainmenu.MainMenuState;
class GitarooPause extends MusicBeatState
{

View file

@ -1,9 +1,10 @@
package funkin;
package funkin.play;
import funkin.play.PlayStatePlaylist;
import flixel.FlxSprite;
import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup.FlxTypedGroup;
import funkin.ui.MusicBeatSubState;
import flixel.sound.FlxSound;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
@ -11,6 +12,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import funkin.play.PlayState;
import funkin.data.song.SongRegistry;
import funkin.ui.Alphabet;
class PauseSubState extends MusicBeatSubState
{
@ -231,11 +233,11 @@ class PauseSubState extends MusicBeatSubState
if (PlayStatePlaylist.isStoryMode)
{
openSubState(new funkin.ui.StickerSubState(null, STORY));
openSubState(new funkin.ui.transition.StickerSubState(null, STORY));
}
else
{
openSubState(new funkin.ui.StickerSubState(null, FREEPLAY));
openSubState(new funkin.ui.transition.StickerSubState(null, FREEPLAY));
}
case 'Exit to Chart Editor':

View file

@ -1,5 +1,6 @@
package funkin.play;
import funkin.ui.SwagCamera;
import flixel.addons.transition.FlxTransitionableSubState;
import funkin.ui.debug.charting.ChartEditorState;
import haxe.Int64;
@ -16,19 +17,24 @@ import flixel.FlxState;
import flixel.FlxSubState;
import flixel.input.keyboard.FlxKey;
import flixel.math.FlxMath;
import funkin.play.components.ComboMilestone;
import flixel.math.FlxPoint;
import funkin.play.components.HealthIcon;
import funkin.ui.MusicBeatSubState;
import flixel.math.FlxRect;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.ui.FlxBar;
import flixel.util.FlxColor;
import funkin.api.newgrounds.NGio;
import flixel.util.FlxTimer;
import funkin.audio.VoicesGroup;
import funkin.save.Save;
import funkin.Highscore.Tallies;
import funkin.input.PreciseInputManager;
import funkin.modding.events.ScriptEvent;
import funkin.ui.mainmenu.MainMenuState;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
@ -42,7 +48,6 @@ import funkin.play.notes.NoteDirection;
import funkin.play.notes.Strumline;
import funkin.play.notes.SustainTrail;
import funkin.play.scoring.Scoring;
import funkin.NoteSplash;
import funkin.play.song.Song;
import funkin.data.song.SongRegistry;
import funkin.data.song.SongData.SongEventData;
@ -50,9 +55,10 @@ import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongCharacterData;
import funkin.play.stage.Stage;
import funkin.play.stage.StageData.StageDataParser;
import funkin.ui.PopUpStuff;
import funkin.ui.PreferencesMenu;
import funkin.ui.stageBuildShit.StageOffsetSubState;
import funkin.ui.transition.LoadingState;
import funkin.play.components.PopUpStuff;
import funkin.ui.options.PreferencesMenu;
import funkin.ui.debug.stage.StageOffsetSubState;
import funkin.ui.story.StoryMenuState;
import funkin.util.SerializerUtil;
import funkin.util.SortUtil;
@ -510,8 +516,6 @@ class PlayState extends MusicBeatSubState
}
instance = this;
NoteSplash.buildSplashFrames();
if (!assertChartExists()) return;
if (false)
@ -682,7 +686,7 @@ class PlayState extends MusicBeatSubState
{
if (!assertChartExists()) return;
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_RETRY));
dispatchEvent(new ScriptEvent(SONG_RETRY));
resetCamera();
@ -700,6 +704,8 @@ class PlayState extends MusicBeatSubState
if (!overrideMusic)
{
// Stop the vocals if they already exist.
if (vocals != null) vocals.stop();
vocals = currentChart.buildVocals();
if (vocals.members.length == 0)
@ -867,7 +873,7 @@ class PlayState extends MusicBeatSubState
deathCounter += 1;
dispatchEvent(new ScriptEvent(ScriptEvent.GAME_OVER));
dispatchEvent(new ScriptEvent(GAME_OVER));
// Disable updates, preventing animations in the background from playing.
persistentUpdate = false;
@ -994,7 +1000,7 @@ class PlayState extends MusicBeatSubState
{
if (Std.isOfType(subState, PauseSubState))
{
var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true);
var event:ScriptEvent = new ScriptEvent(RESUME, true);
dispatchEvent(event);
@ -1097,7 +1103,7 @@ class PlayState extends MusicBeatSubState
if (this.currentStage != null)
{
remove(currentStage);
var event:ScriptEvent = new ScriptEvent(ScriptEvent.DESTROY, false);
var event:ScriptEvent = new ScriptEvent(DESTROY, false);
ScriptEventDispatcher.callEvent(currentStage, event);
currentStage = null;
}
@ -1116,7 +1122,7 @@ class PlayState extends MusicBeatSubState
super.debug_refreshModules();
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentSong, event);
}
@ -1332,7 +1338,7 @@ class PlayState extends MusicBeatSubState
if (currentStage != null)
{
// Actually create and position the sprites.
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentStage, event);
// Apply camera zoom level from stage data.
@ -1554,6 +1560,8 @@ class PlayState extends MusicBeatSubState
if (!overrideMusic)
{
// Stop the vocals if they already exist.
if (vocals != null) vocals.stop();
vocals = currentChart.buildVocals();
if (vocals.members.length == 0)
@ -1640,7 +1648,7 @@ class PlayState extends MusicBeatSubState
add(currentConversation);
refresh();
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentConversation, event);
}
@ -1664,7 +1672,7 @@ class PlayState extends MusicBeatSubState
*/
function startSong():Void
{
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_START));
dispatchEvent(new ScriptEvent(SONG_START));
startingSong = false;
@ -1783,7 +1791,7 @@ class PlayState extends MusicBeatSubState
// Call an event to allow canceling the note hit.
// NOTE: This is what handles the character animations!
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, 0, true);
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, 0, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
@ -1872,7 +1880,7 @@ class PlayState extends MusicBeatSubState
{
// Call an event to allow canceling the note miss.
// NOTE: This is what handles the character animations!
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, 0, true);
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, 0, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
@ -2021,7 +2029,7 @@ class PlayState extends MusicBeatSubState
function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void
{
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true);
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, Highscore.tallies.combo + 1, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
@ -2053,7 +2061,7 @@ class PlayState extends MusicBeatSubState
// a MISS is when you let a note scroll past you!!
Highscore.tallies.missed++;
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_MISS, note, Highscore.tallies.combo, true);
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return;
@ -2385,7 +2393,7 @@ class PlayState extends MusicBeatSubState
*/
function endSong():Void
{
dispatchEvent(new ScriptEvent(ScriptEvent.SONG_END));
dispatchEvent(new ScriptEvent(SONG_END));
#if sys
// spitter for ravy, teehee!!
@ -2593,7 +2601,7 @@ class PlayState extends MusicBeatSubState
{
remove(currentStage);
currentStage.kill();
dispatchEvent(new ScriptEvent(ScriptEvent.DESTROY, false));
dispatchEvent(new ScriptEvent(DESTROY, false));
currentStage = null;
}

View file

@ -8,16 +8,18 @@ import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxBitmapFont;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxPoint;
import funkin.ui.MusicBeatSubState;
import flixel.math.FlxRect;
import flixel.text.FlxBitmapText;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import funkin.ui.freeplay.FreeplayState;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxGradient;
import flixel.util.FlxTimer;
import funkin.shaderslmfao.LeftMaskShader;
import funkin.ui.TallyCounter;
import funkin.graphics.shaders.LeftMaskShader;
import funkin.play.components.TallyCounter;
import flxanimate.FlxAnimate.Settings;
class ResultState extends MusicBeatSubState

View file

@ -58,7 +58,7 @@ class BaseCharacter extends Bopper
*/
public var dropNoteCounts(default, null):Array<Int>;
@:allow(funkin.ui.animDebugShit.DebugBoundingState)
@:allow(funkin.ui.debug.anim.DebugBoundingState)
final _data:CharacterData;
final singTimeSec:Float;

View file

@ -254,7 +254,7 @@ class CharacterDataParser
char.debug = debug;
// Call onCreate only in the fetchCharacter() function, not at application initialization.
ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
ScriptEventDispatcher.callEvent(char, new ScriptEvent(CREATE));
return char;
}

View file

@ -1,4 +1,4 @@
package funkin;
package funkin.play.components;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;

View file

@ -1,4 +1,4 @@
package funkin.play;
package funkin.play.components;
import funkin.play.character.CharacterData;
import flixel.FlxSprite;
@ -6,6 +6,7 @@ import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import funkin.play.character.CharacterData.CharacterDataParser;
import openfl.utils.Assets;
import funkin.util.MathUtil;
/**
* This is a rework of the health icon with the following changes:
@ -201,19 +202,19 @@ class HealthIcon extends FlxSprite
if (this.width > this.height)
{
// Apply linear interpolation while accounting for frame rate.
var targetSize:Int = Std.int(CoolUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
setGraphicSize(targetSize, 0);
}
else
{
var targetSize:Int = Std.int(CoolUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
setGraphicSize(0, targetSize);
}
// Lerp the health icon back to its normal angle.
this.angle = CoolUtil.coolLerp(this.angle, 0, 0.15);
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
this.updateHitbox();
}

View file

@ -1,4 +1,4 @@
package funkin.ui;
package funkin.play.components;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;

View file

@ -1,4 +1,4 @@
package funkin.ui;
package funkin.play.components;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
@ -8,7 +8,7 @@ import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
/**
* Similar to ComboCounter, but it's not!
* Numerical counters used next to each judgement in the Results screen.
*/
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
{

View file

@ -120,7 +120,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
this.alpha = 1.0;
// Start the dialogue.
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, false));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false));
}
function setupMusic():Void
@ -214,7 +214,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
return;
}
ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(ScriptEvent.CREATE, true));
ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true));
currentSpeaker = nextSpeaker;
currentSpeaker.zIndex = 200;
@ -258,7 +258,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
return;
}
ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(ScriptEvent.CREATE, true));
ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true));
currentDialogueBox = nextDialogueBox;
currentDialogueBox.zIndex = 300;
@ -293,7 +293,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
public function startConversation():Void
{
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true));
}
/**
@ -308,13 +308,13 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
switch (state)
{
case ConversationState.Start:
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_START, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, true));
case ConversationState.Opening:
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true));
case ConversationState.Speaking:
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_COMPLETE_LINE, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_COMPLETE_LINE, this, true));
case ConversationState.Idle:
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_LINE, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_LINE, this, true));
case ConversationState.Ending:
// Skip the outro.
endOutro();
@ -371,7 +371,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
*/
public function skipConversation():Void
{
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_SKIP, this, true));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true));
}
static var outroTween:FlxTween;
@ -405,7 +405,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
public function endOutro():Void
{
outroTween = null;
ScriptEventDispatcher.callEvent(this, new ScriptEvent(ScriptEvent.DESTROY, false));
ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false));
}
/**
@ -445,7 +445,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
if (currentDialogueEntry >= currentDialogueEntryCount)
{
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false));
}
else
{
@ -485,7 +485,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
propagateEvent(event);
if (event.eventCanceled) return;
dispatchEvent(new DialogueScriptEvent(ScriptEvent.DIALOGUE_END, this, false));
dispatchEvent(new DialogueScriptEvent(DIALOGUE_END, this, false));
}
public function onDialogueEnd(event:DialogueScriptEvent):Void

View file

@ -4,6 +4,7 @@ import flixel.FlxState;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.events.ScriptEvent;
import flixel.util.FlxColor;
import funkin.ui.MusicBeatState;
/**
* A state with displays a conversation with no background.
@ -30,7 +31,7 @@ class ConversationDebugState extends MusicBeatState
conversation.completeCallback = onConversationComplete;
add(conversation);
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(ScriptEvent.CREATE, false));
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
}
function onConversationComplete():Void

View file

@ -12,7 +12,7 @@ import funkin.play.notes.NoteSplash;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.SustainTrail;
import funkin.data.song.SongData.SongNoteData;
import funkin.ui.PreferencesMenu;
import funkin.ui.options.PreferencesMenu;
import funkin.util.SortUtil;
/**

View file

@ -8,7 +8,7 @@ import flixel.FlxSprite;
import flixel.graphics.FlxGraphic;
import flixel.graphics.tile.FlxDrawTrianglesItem;
import flixel.math.FlxMath;
import funkin.ui.PreferencesMenu;
import funkin.ui.options.PreferencesMenu;
/**
* This is based heavily on the `FlxStrip` class. It uses `drawTriangles()` to clip a sustain note

View file

@ -47,8 +47,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
public final _data:Null<SongMetadata>;
final _metadata:Array<SongMetadata>;
// key = variation id, value = metadata
final _metadata:Map<String, SongMetadata>;
final variations:Array<String>;
final difficulties:Map<String, SongDifficulty>;
@ -62,7 +62,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
function get_songName():String
{
if (_data != null) return _data?.songName ?? DEFAULT_SONGNAME;
if (_metadata.length > 0) return _metadata[0]?.songName ?? DEFAULT_SONGNAME;
if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.songName ?? DEFAULT_SONGNAME;
return DEFAULT_SONGNAME;
}
@ -71,7 +71,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
function get_songArtist():String
{
if (_data != null) return _data?.artist ?? DEFAULT_ARTIST;
if (_metadata.length > 0) return _metadata[0]?.artist ?? DEFAULT_ARTIST;
if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.artist ?? DEFAULT_ARTIST;
return DEFAULT_ARTIST;
}
@ -88,7 +88,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
_data = _fetchData(id);
_metadata = _data == null ? [] : [_data];
_metadata = _data == null ? [] : [Constants.DEFAULT_VARIATION => _data];
variations.clear();
variations.push(Constants.DEFAULT_VARIATION);
@ -100,11 +100,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
variations.push(vari);
var variMeta = fetchVariationMetadata(id, vari);
if (variMeta != null) _metadata.push(variMeta);
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
}
}
if (_metadata.length == 0)
if (_metadata.size() == 0)
{
trace('[WARN] Could not find song data for songId: $id');
return;
@ -121,7 +121,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
result._metadata.clear();
for (meta in metadata)
result._metadata.push(meta);
result._metadata.set(meta.variation, meta);
result.variations.clear();
for (vari in variations)
@ -140,7 +140,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
public function getRawMetadata():Array<SongMetadata>
{
return _metadata;
return _metadata.values();
}
/**
@ -149,10 +149,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
function populateDifficulties():Void
{
if (_metadata == null || _metadata.length == 0) return;
if (_metadata == null || _metadata.size() == 0) return;
// Variations may have different artist, time format, generatedBy, etc.
for (metadata in _metadata)
for (metadata in _metadata.values())
{
if (metadata == null || metadata.playData == null) continue;

View file

@ -85,7 +85,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
return globalOffsets = value;
}
@:allow(funkin.ui.animDebugShit.DebugBoundingState)
@:allow(funkin.ui.debug.anim.DebugBoundingState)
var animOffsets(default, set):Array<Float> = [0, 0];
public var originalPosition:FlxPoint = new FlxPoint(0, 0);

View file

@ -7,6 +7,7 @@ import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxSort;
import funkin.modding.IScriptedClass;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventType;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.stage.StageData.StageDataCharacter;
@ -409,7 +410,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
// Add the character to the scene.
this.add(character);
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ScriptEvent.ADDED, false));
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ADDED, false));
#if debug
debugIconGroup.add(debugIcon);

View file

@ -1,11 +1,13 @@
package funkin.save;
import flixel.util.FlxSave;
import funkin.Controls.Device;
import funkin.save.migrator.SaveDataMigrator;
import thx.semver.Version;
import funkin.input.Controls.Device;
import funkin.save.migrator.RawSaveData_v1_0_0;
import funkin.save.migrator.SaveDataMigrator;
import funkin.ui.debug.charting.ChartEditorState.LiveInputStyle;
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
import thx.semver.Version;
@:nullSafety
@ -99,7 +101,7 @@ abstract Save(RawSaveData)
// Reasonable defaults.
previousFiles: [],
noteQuant: 3,
liveInputStyle: LiveInputStyle.None,
chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
theme: ChartEditorTheme.Light,
playtestStartTime: false,
downscroll: false,
@ -192,21 +194,21 @@ abstract Save(RawSaveData)
return this.optionsChartEditor.noteQuant;
}
public var chartEditorLiveInputStyle(get, set):LiveInputStyle;
public var chartEditorLiveInputStyle(get, set):ChartEditorLiveInputStyle;
function get_chartEditorLiveInputStyle():LiveInputStyle
function get_chartEditorLiveInputStyle():ChartEditorLiveInputStyle
{
if (this.optionsChartEditor.liveInputStyle == null) this.optionsChartEditor.liveInputStyle = LiveInputStyle.None;
if (this.optionsChartEditor.chartEditorLiveInputStyle == null) this.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
return this.optionsChartEditor.liveInputStyle;
return this.optionsChartEditor.chartEditorLiveInputStyle;
}
function set_chartEditorLiveInputStyle(value:LiveInputStyle):LiveInputStyle
function set_chartEditorLiveInputStyle(value:ChartEditorLiveInputStyle):ChartEditorLiveInputStyle
{
// Set and apply.
this.optionsChartEditor.liveInputStyle = value;
this.optionsChartEditor.chartEditorLiveInputStyle = value;
flush();
return this.optionsChartEditor.liveInputStyle;
return this.optionsChartEditor.chartEditorLiveInputStyle;
}
public var chartEditorDownscroll(get, set):Bool;
@ -938,9 +940,9 @@ typedef SaveDataChartEditorOptions =
/**
* Live input style in the Chart Editor.
* @default `LiveInputStyle.None`
* @default `ChartEditorLiveInputStyle.None`
*/
var ?liveInputStyle:LiveInputStyle;
var ?chartEditorLiveInputStyle:ChartEditorLiveInputStyle;
/**
* Theme in the Chart Editor.

View file

@ -1,9 +1,10 @@
package funkin;
package funkin.ui;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxMath;
import flixel.util.FlxTimer;
import funkin.util.MathUtil;
/**
* Loosley based on FlxTypeText lolol
@ -151,7 +152,6 @@ class Alphabet extends FlxSpriteGroup
if (AlphaCharacter.alphabet.indexOf(splitWords[loopNum].toLowerCase()) != -1
|| isNumber
|| isSymbol) // if (AlphaCharacter.alphabet.contains(splitWords[loopNum].toLowerCase()) || isNumber || isSymbol)
{
if (lastSprite != null && !xPosResetted)
{
@ -220,8 +220,8 @@ class Alphabet extends FlxSpriteGroup
{
var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3);
y = CoolUtil.coolLerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16);
x = CoolUtil.coolLerp(x, (targetY * 20) + 90, 0.16);
y = MathUtil.coolLerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16);
x = MathUtil.coolLerp(x, (targetY * 20) + 90, 0.16);
}
super.update(elapsed);

View file

@ -38,7 +38,7 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
/**
* A menu list item which uses single texture atlas.
*/
class AtlasMenuItem extends MenuItem
class AtlasMenuItem extends MenuListItem
{
var atlas:FlxAtlasFrames;

View file

@ -1,9 +1,10 @@
package funkin;
package funkin.ui;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxMath;
import funkin.util.MathUtil;
import flixel.util.FlxColor;
class MenuItem extends FlxSpriteGroup
@ -44,7 +45,7 @@ class MenuItem extends FlxSpriteGroup
override function update(elapsed:Float)
{
super.update(elapsed);
y = CoolUtil.coolLerp(y, (targetY * 120) + 480, 0.17);
y = MathUtil.coolLerp(y, (targetY * 120) + 480, 0.17);
if (isFlashing) flashingInt += 1;

View file

@ -6,7 +6,7 @@ import flixel.group.FlxGroup;
import flixel.math.FlxPoint;
import flixel.util.FlxSignal;
class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
{
public var selectedIndex(default, null) = 0;
public var selectedItem(get, never):T;
@ -206,7 +206,7 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
}
}
class MenuItem extends FlxSprite
class MenuListItem extends FlxSprite
{
public var callback:Void->Void;
public var name:String;
@ -261,7 +261,7 @@ class MenuItem extends FlxSprite
}
}
class MenuTypedItem<T:FlxSprite> extends MenuItem
class MenuTypedItem<T:FlxSprite> extends MenuListItem
{
public var label(default, set):T;

View file

@ -1,6 +1,7 @@
package funkin;
package funkin.ui;
import funkin.modding.IScriptedClass.IEventHandler;
import funkin.ui.mainmenu.MainMenuState;
import flixel.FlxState;
import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
@ -11,6 +12,7 @@ import funkin.modding.PolymodHandler;
import funkin.modding.events.ScriptEvent;
import funkin.modding.module.ModuleHandler;
import funkin.util.SortUtil;
import funkin.input.Controls;
/**
* MusicBeatState actually represents the core utility FlxState of the game.
@ -56,27 +58,45 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
Conductor.stepHit.remove(this.stepHit);
}
override function update(elapsed:Float)
function handleControls():Void
{
super.update(elapsed);
var isHaxeUIFocused:Bool = haxe.ui.focus.FocusManager.instance?.focus != null;
// Rebindable volume keys.
if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted();
else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1);
else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1);
if (!isHaxeUIFocused)
{
// Rebindable volume keys.
if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted();
else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1);
else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1);
}
}
function handleFunctionControls():Void
{
// Emergency exit button.
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
}
function handleQuickWatch():Void
{
// Display Conductor info in the watch window.
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
FlxG.watch.addQuick("bpm", Conductor.bpm);
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
}
override function update(elapsed:Float)
{
super.update(elapsed);
handleControls();
handleFunctionControls();
handleQuickWatch();
dispatchEvent(new UpdateScriptEvent(elapsed));
}
@ -117,7 +137,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function stepHit():Bool
{
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);
@ -128,7 +148,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function beatHit():Bool
{
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);
@ -148,7 +168,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
override function startOutro(onComplete:() -> Void):Void
{
var event = new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_BEGIN, null, true);
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
dispatchEvent(event);
@ -164,7 +184,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public override function openSubState(targetSubState:FlxSubState):Void
{
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_BEGIN, targetSubState, true);
var event = new SubStateScriptEvent(SUBSTATE_OPEN_BEGIN, targetSubState, true);
dispatchEvent(event);
@ -175,12 +195,12 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
function onOpenSubStateComplete(targetState:FlxSubState):Void
{
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_END, targetState, true));
dispatchEvent(new SubStateScriptEvent(SUBSTATE_OPEN_END, targetState, true));
}
public override function closeSubState():Void
{
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_BEGIN, this.subState, true);
var event = new SubStateScriptEvent(SUBSTATE_CLOSE_BEGIN, this.subState, true);
dispatchEvent(event);
@ -191,6 +211,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
function onCloseSubStateComplete(targetState:FlxSubState):Void
{
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_END, targetState, true));
dispatchEvent(new SubStateScriptEvent(SUBSTATE_CLOSE_END, targetState, true));
}
}

View file

@ -1,8 +1,9 @@
package funkin;
package funkin.ui;
import flixel.addons.transition.FlxTransitionableSubState;
import flixel.FlxSubState;
import flixel.text.FlxText;
import funkin.ui.mainmenu.MainMenuState;
import flixel.util.FlxColor;
import funkin.modding.events.ScriptEvent;
import funkin.modding.IScriptedClass.IEventHandler;
@ -10,6 +11,7 @@ import funkin.modding.module.ModuleHandler;
import funkin.modding.PolymodHandler;
import funkin.util.SortUtil;
import flixel.util.FlxSort;
import funkin.input.Controls;
/**
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
@ -96,7 +98,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function stepHit():Bool
{
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);
@ -112,7 +114,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function beatHit():Bool
{
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);

View file

@ -1,8 +1,9 @@
package funkin;
package funkin.ui;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.math.FlxPoint;
import funkin.util.MathUtil;
class SwagCamera extends FlxCamera
{
@ -92,10 +93,10 @@ class SwagCamera extends FlxCamera
else
{
// THIS THE PART THAT ACTUALLY MATTERS LOL
scroll.x = CoolUtil.coolLerp(scroll.x, _scrollTarget.x, followLerp);
scroll.y = CoolUtil.coolLerp(scroll.y, _scrollTarget.y, followLerp);
// scroll.x += (_scrollTarget.x - scroll.x) * CoolUtil.camLerpShit(followLerp);
// scroll.y += (_scrollTarget.y - scroll.y) * CoolUtil.camLerpShit(followLerp);
scroll.x = MathUtil.coolLerp(scroll.x, _scrollTarget.x, followLerp);
scroll.y = MathUtil.coolLerp(scroll.y, _scrollTarget.y, followLerp);
// scroll.x += (_scrollTarget.x - scroll.x) * MathUtil.cameraLerp(followLerp);
// scroll.y += (_scrollTarget.y - scroll.y) * MathUtil.cameraLerp(followLerp);
}
}
}

View file

@ -3,9 +3,10 @@ package funkin.ui.debug;
import flixel.math.FlxPoint;
import flixel.FlxObject;
import flixel.FlxSprite;
import funkin.MusicBeatSubState;
import funkin.ui.MusicBeatSubState;
import funkin.ui.TextMenuList;
import funkin.ui.debug.charting.ChartEditorState;
import funkin.ui.MusicBeatSubState;
class DebugMenuSubState extends MusicBeatSubState
{
@ -85,13 +86,13 @@ class DebugMenuSubState extends MusicBeatSubState
function openAnimationEditor()
{
FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
FlxG.switchState(new funkin.ui.debug.anim.DebugBoundingState());
trace('Animation Editor');
}
function testStickers()
{
openSubState(new funkin.ui.StickerSubState());
openSubState(new funkin.ui.transition.StickerSubState());
trace('opened stickers');
}

View file

@ -1,4 +1,4 @@
package funkin;
package funkin.ui.debug;
import openfl.text.TextFormat;
import openfl.system.System;

View file

@ -1,4 +1,4 @@
package funkin.ui.animDebugShit;
package funkin.ui.debug.anim;
import funkin.util.SerializerUtil;
import funkin.play.character.CharacterData;
@ -15,6 +15,7 @@ import flixel.math.FlxPoint;
import flixel.sound.FlxSound;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.util.MouseUtil;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
import funkin.play.character.BaseCharacter;
@ -25,6 +26,7 @@ import haxe.ui.components.DropDown;
import haxe.ui.core.Component;
import haxe.ui.events.ItemEvent;
import haxe.ui.events.UIEvent;
import funkin.ui.mainmenu.MainMenuState;
import lime.utils.Assets as LimeAssets;
import openfl.Assets;
import openfl.events.Event;
@ -32,6 +34,7 @@ import openfl.events.IOErrorEvent;
import openfl.geom.Rectangle;
import openfl.net.FileReference;
import openfl.net.URLLoader;
import funkin.ui.mainmenu.MainMenuState;
import openfl.net.URLRequest;
import openfl.utils.ByteArray;
import funkin.input.Cursor;
@ -363,8 +366,8 @@ class DebugBoundingState extends FlxState
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
CoolUtil.mouseCamDrag();
CoolUtil.mouseWheelZoom();
MouseUtil.mouseCamDrag();
MouseUtil.mouseWheelZoom();
// bg.scale.x = FlxG.camera.zoom;
// bg.scale.y = FlxG.camera.zoom;

View file

@ -1,7 +1,8 @@
package funkin.ui.animDebugShit;
package funkin.ui.debug.anim;
import flixel.FlxG;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.ui.MusicBeatState;
/**
* A simple test of FlxAnimate.

View file

@ -1,879 +0,0 @@
package funkin.ui.debug.charting;
import haxe.ui.notifications.NotificationType;
import haxe.ui.notifications.NotificationManager;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongDataUtils;
using Lambda;
/**
* Actions in the chart editor are backed by the Command pattern
* (see Bob Nystrom's book "Game Programming Patterns" for more info)
*
* To make a function compatible with the undo/redo history, create a new class
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
*/
interface ChartEditorCommand
{
/**
* Calling this function should perform the action that this command represents.
* @param state The ChartEditorState to perform the action on.
*/
public function execute(state:ChartEditorState):Void;
/**
* Calling this function should perform the inverse of the action that this command represents,
* effectively undoing the action.
* @param state The ChartEditorState to undo the action on.
*/
public function undo(state:ChartEditorState):Void;
/**
* Get a short description of the action (for the UI).
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.
*/
public function toString():String;
}
@:nullSafety
class AddNotesCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var appendToSelection:Bool;
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
{
this.notes = notes;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
{
for (note in notes)
{
state.currentSongChartNoteData.push(note);
}
if (appendToSelection)
{
state.currentNoteSelection = state.currentNoteSelection.concat(notes);
}
else
{
state.currentNoteSelection = notes;
state.currentEventSelection = [];
}
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentNoteSelection = [];
state.currentEventSelection = [];
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
if (notes.length == 1)
{
var dir:String = notes[0].getDirectionName();
return 'Add $dir Note';
}
return 'Add ${notes.length} Notes';
}
}
@:nullSafety
class RemoveNotesCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
public function new(notes:Array<SongNoteData>)
{
this.notes = notes;
}
public function execute(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentNoteSelection = [];
state.currentEventSelection = [];
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
for (note in notes)
{
state.currentSongChartNoteData.push(note);
}
state.currentNoteSelection = notes;
state.currentEventSelection = [];
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
if (notes.length == 1 && notes[0] != null)
{
var dir:String = notes[0].getDirectionName();
return 'Remove $dir Note';
}
return 'Remove ${notes.length} Notes';
}
}
/**
* Appends one or more items to the selection.
*/
@:nullSafety
class SelectItemsCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
for (note in this.notes)
{
state.currentNoteSelection.push(note);
}
for (event in this.events)
{
state.currentEventSelection.push(event);
}
// state.noteDisplayDirty = true;
// state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
// state.noteDisplayDirty = true;
// state.notePreviewDirty = true;
}
public function toString():String
{
var len:Int = notes.length + events.length;
if (notes.length == 0)
{
if (events.length == 1)
{
return 'Select Event';
}
else
{
return 'Select ${events.length} Events';
}
}
else if (events.length == 0)
{
if (notes.length == 1)
{
return 'Select Note';
}
else
{
return 'Select ${notes.length} Notes';
}
}
return 'Select ${len} Items';
}
}
@:nullSafety
class AddEventsCommand implements ChartEditorCommand
{
var events:Array<SongEventData>;
var appendToSelection:Bool;
public function new(events:Array<SongEventData>, appendToSelection:Bool = false)
{
this.events = events;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
{
for (event in events)
{
state.currentSongChartEventData.push(event);
}
if (appendToSelection)
{
state.currentEventSelection = state.currentEventSelection.concat(events);
}
else
{
state.currentNoteSelection = [];
state.currentEventSelection = events;
}
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var len:Int = events.length;
return 'Add $len Events';
}
}
@:nullSafety
class RemoveEventsCommand implements ChartEditorCommand
{
var events:Array<SongEventData>;
public function new(events:Array<SongEventData>)
{
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentEventSelection = [];
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
for (event in events)
{
state.currentSongChartEventData.push(event);
}
state.currentEventSelection = events;
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
if (events.length == 1 && events[0] != null)
{
return 'Remove Event';
}
return 'Remove ${events.length} Events';
}
}
@:nullSafety
class RemoveItemsCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentNoteSelection = [];
state.currentEventSelection = [];
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
for (note in notes)
{
state.currentSongChartNoteData.push(note);
}
for (event in events)
{
state.currentSongChartEventData.push(event);
}
state.currentNoteSelection = notes;
state.currentEventSelection = events;
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
return 'Remove ${notes.length + events.length} Items';
}
}
@:nullSafety
class SwitchDifficultyCommand implements ChartEditorCommand
{
var prevDifficulty:String;
var newDifficulty:String;
var prevVariation:String;
var newVariation:String;
public function new(prevDifficulty:String, newDifficulty:String, prevVariation:String, newVariation:String)
{
this.prevDifficulty = prevDifficulty;
this.newDifficulty = newDifficulty;
this.prevVariation = prevVariation;
this.newVariation = newVariation;
}
public function execute(state:ChartEditorState):Void
{
state.selectedVariation = newVariation != null ? newVariation : prevVariation;
state.selectedDifficulty = newDifficulty != null ? newDifficulty : prevDifficulty;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.selectedVariation = prevVariation != null ? prevVariation : newVariation;
state.selectedDifficulty = prevDifficulty != null ? prevDifficulty : newDifficulty;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Switch Difficulty';
}
}
@:nullSafety
class DeselectItemsCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
// state.noteDisplayDirty = true;
// state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
for (note in this.notes)
{
state.currentNoteSelection.push(note);
}
for (event in this.events)
{
state.currentEventSelection.push(event);
}
// state.noteDisplayDirty = true;
// state.notePreviewDirty = true;
}
public function toString():String
{
var noteCount = notes.length + events.length;
if (noteCount == 1)
{
var dir:String = notes[0].getDirectionName();
return 'Deselect $dir Items';
}
return 'Deselect ${noteCount} Items';
}
}
/**
* Sets the selection rather than appends it.
* Deselects any notes that are not in the new selection.
*/
@:nullSafety
class SetItemSelectionCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>,
previousEventSelection:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = notes;
state.currentEventSelection = events;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentNoteSelection = previousNoteSelection;
state.currentEventSelection = previousEventSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Select ${notes.length} Items';
}
}
@:nullSafety
class SelectAllItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = state.currentSongChartNoteData;
state.currentEventSelection = state.currentSongChartEventData;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentNoteSelection = previousNoteSelection;
state.currentEventSelection = previousEventSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Select All Items';
}
}
@:nullSafety
class InvertSelectedItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection);
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentNoteSelection = previousNoteSelection;
state.currentEventSelection = previousEventSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Invert Selected Items';
}
}
@:nullSafety
class DeselectAllItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentNoteSelection = previousNoteSelection;
state.currentEventSelection = previousEventSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Deselect All Items';
}
}
@:nullSafety
class CutItemsCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
// Copy the notes.
SongDataUtils.writeItemsToClipboard(
{
notes: SongDataUtils.buildNoteClipboard(notes),
events: SongDataUtils.buildEventClipboard(events)
});
// Delete the notes.
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
state.currentNoteSelection = notes;
state.currentEventSelection = events;
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var len:Int = notes.length + events.length;
if (notes.length == 0) return 'Cut $len Events to Clipboard';
else if (events.length == 0) return 'Cut $len Notes to Clipboard';
else
return 'Cut $len Items to Clipboard';
}
}
@:nullSafety
class FlipNotesCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData> = [];
var flippedNotes:Array<SongNoteData> = [];
public function new(notes:Array<SongNoteData>)
{
this.notes = notes;
this.flippedNotes = SongDataUtils.flipNotes(notes);
}
public function execute(state:ChartEditorState):Void
{
// Delete the notes.
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
// Add the flipped notes.
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
state.currentNoteSelection = flippedNotes;
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
state.currentNoteSelection = notes;
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var len:Int = notes.length;
return 'Flip $len Notes';
}
}
@:nullSafety
class PasteItemsCommand implements ChartEditorCommand
{
var targetTimestamp:Float;
// Notes we added with this command, for undo.
var addedNotes:Array<SongNoteData> = [];
var addedEvents:Array<SongEventData> = [];
public function new(targetTimestamp:Float)
{
this.targetTimestamp = targetTimestamp;
}
public function execute(state:ChartEditorState):Void
{
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
if (currentClipboard.valid != true)
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failed to Paste',
body: 'Could not parse clipboard contents.',
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
return;
}
trace(currentClipboard.notes);
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
state.currentNoteSelection = addedNotes.copy();
state.currentEventSelection = addedEvents.copy();
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Paste Successful',
body: 'Successfully pasted clipboard contents.',
type: NotificationType.Success,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
public function undo(state:ChartEditorState):Void
{
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
if (currentClipboard.notes.length == 0) return 'Paste $len Events';
else if (currentClipboard.events.length == 0) return 'Paste $len Notes';
else
return 'Paste $len Items';
}
}
@:nullSafety
class ExtendNoteLengthCommand implements ChartEditorCommand
{
var note:SongNoteData;
var oldLength:Float;
var newLength:Float;
public function new(note:SongNoteData, newLength:Float)
{
this.note = note;
this.oldLength = note.length;
this.newLength = newLength;
}
public function execute(state:ChartEditorState):Void
{
note.length = newLength;
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
note.length = oldLength;
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
return 'Extend Note Length';
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
package funkin.ui.debug.charting.commands;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongDataUtils;
/**
* Adds the given events to the current chart in the chart editor.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class AddEventsCommand implements ChartEditorCommand
{
var events:Array<SongEventData>;
var appendToSelection:Bool;
public function new(events:Array<SongEventData>, appendToSelection:Bool = false)
{
this.events = events;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
{
for (event in events)
{
state.currentSongChartEventData.push(event);
}
if (appendToSelection)
{
state.currentEventSelection = state.currentEventSelection.concat(events);
}
else
{
state.currentNoteSelection = [];
state.currentEventSelection = events;
}
state.playSound(Paths.sound('chartingSounds/noteLay'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var len:Int = events.length;
return 'Add $len Events';
}
}

View file

@ -0,0 +1,72 @@
package funkin.ui.debug.charting.commands;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongDataUtils;
/**
* Adds the given notes to the current chart in the chart editor.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class AddNotesCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var appendToSelection:Bool;
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
{
this.notes = notes;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
{
for (note in notes)
{
state.currentSongChartNoteData.push(note);
}
if (appendToSelection)
{
state.currentNoteSelection = state.currentNoteSelection.concat(notes);
}
else
{
state.currentNoteSelection = notes;
state.currentEventSelection = [];
}
state.playSound(Paths.sound('chartingSounds/noteLay'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentNoteSelection = [];
state.currentEventSelection = [];
state.playSound(Paths.sound('chartingSounds/undo'));
state.saveDataDirty = true;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
if (notes.length == 1)
{
var dir:String = notes[0].getDirectionName();
return 'Add $dir Note';
}
return 'Add ${notes.length} Notes';
}
}

View file

@ -0,0 +1,30 @@
package funkin.ui.debug.charting.commands;
/**
* Actions in the chart editor are backed by the Command pattern
* (see Bob Nystrom's book "Game Programming Patterns" for more info)
*
* To make a functionality compatible with the undo/redo history, create a new class
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
*/
interface ChartEditorCommand
{
/**
* Calling this function should perform the action that this command represents.
* @param state The ChartEditorState to perform the action on.
*/
public function execute(state:ChartEditorState):Void;
/**
* Calling this function should perform the inverse of the action that this command represents,
* effectively undoing the action. Assume that the original action was the last action performed.
* @param state The ChartEditorState to undo the action on.
*/
public function undo(state:ChartEditorState):Void;
/**
* Get a short description of the action (for the UI).
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.
*/
public function toString():String;
}

Some files were not shown because too many files have changed in this diff Show more