diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index dcf5fd0a7..cb5e68f61 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index 8968471e3..17c3cc026 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/.vscode/settings.json b/.vscode/settings.json index 80d2bf76a..92d49c3d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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", diff --git a/Project.xml b/Project.xml index 9fad26fd7..46ba7f155 100644 --- a/Project.xml +++ b/Project.xml @@ -162,7 +162,10 @@ + + +
diff --git a/assets b/assets index 8e8aeb064..fb7120cf3 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8e8aeb06472ca294c569818cbefb1bb3dfce7854 +Subproject commit fb7120cf30d7accda049409b68d8daa0e1e7650f diff --git a/docs/style-guide.md b/docs/style-guide.md index 71ae844c4..1131cca2b 100644 --- a/docs/style-guide.md +++ b/docs/style-guide.md @@ -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` 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` 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. diff --git a/hmm.json b/hmm.json index 1d9e51d69..778b85604 100644 --- a/hmm.json +++ b/hmm.json @@ -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" }, { diff --git a/source/Main.hx b/source/Main.hx index 726c1fdbf..5fbb6747b 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -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(); } } diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index b79ae0fc4..10bf505f0 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -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? diff --git a/source/funkin/CoolUtil.hx b/source/funkin/CoolUtil.hx deleted file mode 100644 index d07bb4e22..000000000 --- a/source/funkin/CoolUtil.hx +++ /dev/null @@ -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 - { - var daList:Array = []; - - var swagArray:Array = 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 - { - var dumbArray:Array = []; - 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); - } -} diff --git a/source/funkin/DialogueBox.hx b/source/funkin/DialogueBox.hx deleted file mode 100644 index 68d330dbe..000000000 --- a/source/funkin/DialogueBox.hx +++ /dev/null @@ -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 = []; - - // 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) - { - 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 = dialogueList[0].split(':'); - curCharacter = splitName[1]; - dialogueList[0] = dialogueList[0].substr(splitName[1].length + 2).trim(); - } -} diff --git a/source/funkin/FlxSwf.hx b/source/funkin/FlxSwf.hx deleted file mode 100644 index d37343894..000000000 --- a/source/funkin/FlxSwf.hx +++ /dev/null @@ -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 -{ - public function new() - { - super(); - type = FlxDrawItemType.TILES; - } - - override function render(camera:FlxCamera) - { - super.render(camera); - } -} diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index 3c9fd82e4..2c18ffa2d 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -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(); diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index fbde22e1b..13bcd306e 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -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, diff --git a/source/funkin/MenuCharacter.hx b/source/funkin/MenuCharacter.hx deleted file mode 100644 index 3e05b9e9f..000000000 --- a/source/funkin/MenuCharacter.hx +++ /dev/null @@ -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(); - } -} diff --git a/source/funkin/NoteSplash.hx b/source/funkin/NoteSplash.hx deleted file mode 100644 index 81b35b36d..000000000 --- a/source/funkin/NoteSplash.hx +++ /dev/null @@ -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); - } -} diff --git a/source/funkin/Options.hx b/source/funkin/Options.hx deleted file mode 100644 index bc8a98570..000000000 --- a/source/funkin/Options.hx +++ /dev/null @@ -1,6 +0,0 @@ -package funkin; - -class Options -{ - public static var masterVolume:Float = 1; -} diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 07a15dae1..e0212e573 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -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; diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx index e97cfe384..2e3e0e425 100644 --- a/source/funkin/PlayerSettings.hx +++ b/source/funkin/PlayerSettings.hx @@ -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; diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 7e3c3c6d7..6b0911ede 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -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 { diff --git a/source/funkin/TankCutscene.hx b/source/funkin/TankCutscene.hx deleted file mode 100644 index 4bc7349ad..000000000 --- a/source/funkin/TankCutscene.hx +++ /dev/null @@ -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); - } -} diff --git a/source/funkin/Discord.hx b/source/funkin/api/discord/Discord.hx similarity index 98% rename from source/funkin/Discord.hx rename to source/funkin/api/discord/Discord.hx index d2cf12535..a4d65684e 100644 --- a/source/funkin/Discord.hx +++ b/source/funkin/api/discord/Discord.hx @@ -1,4 +1,4 @@ -package funkin; +package funkin.api.discord; import Sys.sleep; #if discord_rpc diff --git a/source/funkin/api/newgrounds/NGUtil.hx b/source/funkin/api/newgrounds/NGUtil.hx index ba7d5f916..c8289fc46 100644 --- a/source/funkin/api/newgrounds/NGUtil.hx +++ b/source/funkin/api/newgrounds/NGUtil.hx @@ -241,15 +241,3 @@ class NGUtil } #end } - -enum ConnectionResult -{ - /** Log in successful */ - Success; - - /** Could not login */ - Fail(msg:String); - - /** User cancelled the login */ - Cancelled; -} diff --git a/source/funkin/NGio.hx b/source/funkin/api/newgrounds/NGio.hx similarity index 99% rename from source/funkin/NGio.hx rename to source/funkin/api/newgrounds/NGio.hx index e5f60c8b5..e505bdedf 100644 --- a/source/funkin/NGio.hx +++ b/source/funkin/api/newgrounds/NGio.hx @@ -1,4 +1,4 @@ -package funkin; +package funkin.api.newgrounds; #if newgrounds import flixel.util.FlxSignal; diff --git a/source/funkin/api/newgrounds/README.md b/source/funkin/api/newgrounds/README.md index f61e1b0fd..09534fb71 100644 --- a/source/funkin/api/newgrounds/README.md +++ b/source/funkin/api/newgrounds/README.md @@ -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. \ No newline at end of file + such as writing high scores or posting achievements. diff --git a/source/funkin/audiovis/ABot.hx b/source/funkin/audio/visualize/ABot.hx similarity index 85% rename from source/funkin/audiovis/ABot.hx rename to source/funkin/audio/visualize/ABot.hx index 11c123fb2..0b2ec619e 100644 --- a/source/funkin/audiovis/ABot.hx +++ b/source/funkin/audio/visualize/ABot.hx @@ -1,4 +1,4 @@ -package funkin.audiovis; +package funkin.audio.visualize; import flixel.FlxSprite; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; diff --git a/source/funkin/audiovis/ABotVis.hx b/source/funkin/audio/visualize/ABotVis.hx similarity index 96% rename from source/funkin/audiovis/ABotVis.hx rename to source/funkin/audio/visualize/ABotVis.hx index 060bddcf7..681287808 100644 --- a/source/funkin/audiovis/ABotVis.hx +++ b/source/funkin/audio/visualize/ABotVis.hx @@ -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 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)); }; diff --git a/source/funkin/audio/visualize/PolygonSpectogram.hx b/source/funkin/audio/visualize/PolygonSpectogram.hx index 6b7e280ec..604bc910b 100644 --- a/source/funkin/audio/visualize/PolygonSpectogram.hx +++ b/source/funkin/audio/visualize/PolygonSpectogram.hx @@ -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; diff --git a/source/funkin/audiovis/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx similarity index 98% rename from source/funkin/audiovis/SpectogramSprite.hx rename to source/funkin/audio/visualize/SpectogramSprite.hx index c4f8234eb..63d0fcd2e 100644 --- a/source/funkin/audiovis/SpectogramSprite.hx +++ b/source/funkin/audio/visualize/SpectogramSprite.hx @@ -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; diff --git a/source/funkin/audiovis/VisShit.hx b/source/funkin/audio/visualize/VisShit.hx similarity index 93% rename from source/funkin/audiovis/VisShit.hx rename to source/funkin/audio/visualize/VisShit.hx index ee531a977..5bfb8c7c5 100644 --- a/source/funkin/audiovis/VisShit.hx +++ b/source/funkin/audio/visualize/VisShit.hx @@ -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 }; diff --git a/source/funkin/audiovis/dsp/Complex.hx b/source/funkin/audio/visualize/dsp/Complex.hx similarity index 98% rename from source/funkin/audiovis/dsp/Complex.hx rename to source/funkin/audio/visualize/dsp/Complex.hx index 523549e99..37861bcc3 100644 --- a/source/funkin/audiovis/dsp/Complex.hx +++ b/source/funkin/audio/visualize/dsp/Complex.hx @@ -1,4 +1,4 @@ -package funkin.audiovis.dsp; +package funkin.audio.visualize.dsp; /** Complex number representation. diff --git a/source/funkin/audiovis/dsp/FFT.hx b/source/funkin/audio/visualize/dsp/FFT.hx similarity index 97% rename from source/funkin/audiovis/dsp/FFT.hx rename to source/funkin/audio/visualize/dsp/FFT.hx index d1d99140e..dc75acb81 100644 --- a/source/funkin/audiovis/dsp/FFT.hx +++ b/source/funkin/audio/visualize/dsp/FFT.hx @@ -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() diff --git a/source/funkin/audiovis/dsp/OffsetArray.hx b/source/funkin/audio/visualize/dsp/OffsetArray.hx similarity index 98% rename from source/funkin/audiovis/dsp/OffsetArray.hx rename to source/funkin/audio/visualize/dsp/OffsetArray.hx index bd066a727..c8a5c27c3 100644 --- a/source/funkin/audiovis/dsp/OffsetArray.hx +++ b/source/funkin/audio/visualize/dsp/OffsetArray.hx @@ -1,4 +1,4 @@ -package funkin.audiovis.dsp; +package funkin.audio.visualize.dsp; /** A view into an Array with an indexing offset. diff --git a/source/funkin/audiovis/dsp/Signal.hx b/source/funkin/audio/visualize/dsp/Signal.hx similarity index 98% rename from source/funkin/audiovis/dsp/Signal.hx rename to source/funkin/audio/visualize/dsp/Signal.hx index 1f7cc6114..4557dc199 100644 --- a/source/funkin/audiovis/dsp/Signal.hx +++ b/source/funkin/audio/visualize/dsp/Signal.hx @@ -1,4 +1,4 @@ -package funkin.audiovis.dsp; +package funkin.audio.visualize.dsp; using Lambda; diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventData.hx index 831a53fbd..7a167b031 100644 --- a/source/funkin/data/event/SongEventData.hx +++ b/source/funkin/data/event/SongEventData.hx @@ -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, + /** * 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. */ diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index c0bd26332..e79e1a3f4 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 = 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 = 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. */ diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 3ff3943c6..b149a8855 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -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, desc:Bool = false):Array + { + // 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. */ diff --git a/source/funkin/shaderslmfao/AngleMask.hx b/source/funkin/graphics/shaders/AngleMask.hx similarity index 96% rename from source/funkin/shaderslmfao/AngleMask.hx rename to source/funkin/graphics/shaders/AngleMask.hx index b9188201b..30e508a58 100644 --- a/source/funkin/shaderslmfao/AngleMask.hx +++ b/source/funkin/graphics/shaders/AngleMask.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/BlendModeEffect.hx b/source/funkin/graphics/shaders/BlendModeEffect.hx similarity index 95% rename from source/funkin/shaderslmfao/BlendModeEffect.hx rename to source/funkin/graphics/shaders/BlendModeEffect.hx index 8fe98f70a..bf2246795 100644 --- a/source/funkin/shaderslmfao/BlendModeEffect.hx +++ b/source/funkin/graphics/shaders/BlendModeEffect.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.util.FlxColor; import openfl.display.ShaderParameter; diff --git a/source/funkin/shaderslmfao/BlendModesShader.hx b/source/funkin/graphics/shaders/BlendModesShader.hx similarity index 92% rename from source/funkin/shaderslmfao/BlendModesShader.hx rename to source/funkin/graphics/shaders/BlendModesShader.hx index 6807a65c0..acd2c1586 100644 --- a/source/funkin/shaderslmfao/BlendModesShader.hx +++ b/source/funkin/graphics/shaders/BlendModesShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.addons.display.FlxRuntimeShader; import funkin.Paths; diff --git a/source/funkin/shaderslmfao/ColorSwap.hx b/source/funkin/graphics/shaders/ColorSwap.hx similarity index 99% rename from source/funkin/shaderslmfao/ColorSwap.hx rename to source/funkin/graphics/shaders/ColorSwap.hx index 2c1f5664b..1be4d5429 100644 --- a/source/funkin/shaderslmfao/ColorSwap.hx +++ b/source/funkin/graphics/shaders/ColorSwap.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxColor; diff --git a/source/funkin/shaderslmfao/GaussianBlurShader.hx b/source/funkin/graphics/shaders/GaussianBlurShader.hx similarity index 93% rename from source/funkin/shaderslmfao/GaussianBlurShader.hx rename to source/funkin/graphics/shaders/GaussianBlurShader.hx index ad472ac31..81167655b 100644 --- a/source/funkin/shaderslmfao/GaussianBlurShader.hx +++ b/source/funkin/graphics/shaders/GaussianBlurShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.addons.display.FlxRuntimeShader; import funkin.Paths; diff --git a/source/funkin/shaderslmfao/Grayscale.hx b/source/funkin/graphics/shaders/Grayscale.hx similarity index 92% rename from source/funkin/shaderslmfao/Grayscale.hx rename to source/funkin/graphics/shaders/Grayscale.hx index 016d64b46..6673ace24 100644 --- a/source/funkin/shaderslmfao/Grayscale.hx +++ b/source/funkin/graphics/shaders/Grayscale.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.addons.display.FlxRuntimeShader; import funkin.Paths; diff --git a/source/funkin/shaderslmfao/HSVShader.hx b/source/funkin/graphics/shaders/HSVShader.hx similarity index 96% rename from source/funkin/shaderslmfao/HSVShader.hx rename to source/funkin/graphics/shaders/HSVShader.hx index 066a49c96..733bbca7f 100644 --- a/source/funkin/shaderslmfao/HSVShader.hx +++ b/source/funkin/graphics/shaders/HSVShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.addons.display.FlxRuntimeShader; import funkin.Paths; diff --git a/source/funkin/shaderslmfao/LeftMaskShader.hx b/source/funkin/graphics/shaders/LeftMaskShader.hx similarity index 97% rename from source/funkin/shaderslmfao/LeftMaskShader.hx rename to source/funkin/graphics/shaders/LeftMaskShader.hx index e921a7f2b..f82a5c208 100644 --- a/source/funkin/shaderslmfao/LeftMaskShader.hx +++ b/source/funkin/graphics/shaders/LeftMaskShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.math.FlxRect; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/MultiplyShader.hx b/source/funkin/graphics/shaders/MultiplyShader.hx similarity index 94% rename from source/funkin/shaderslmfao/MultiplyShader.hx rename to source/funkin/graphics/shaders/MultiplyShader.hx index 2868982a2..5fe95f04e 100644 --- a/source/funkin/shaderslmfao/MultiplyShader.hx +++ b/source/funkin/graphics/shaders/MultiplyShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/OverlayBlend.hx b/source/funkin/graphics/shaders/OverlayBlend.hx similarity index 97% rename from source/funkin/shaderslmfao/OverlayBlend.hx rename to source/funkin/graphics/shaders/OverlayBlend.hx index 8845a3b55..e44f3152a 100644 --- a/source/funkin/shaderslmfao/OverlayBlend.hx +++ b/source/funkin/graphics/shaders/OverlayBlend.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/PureColor.hx b/source/funkin/graphics/shaders/PureColor.hx similarity index 96% rename from source/funkin/shaderslmfao/PureColor.hx rename to source/funkin/graphics/shaders/PureColor.hx index 767a29d0d..1d2216a8c 100644 --- a/source/funkin/shaderslmfao/PureColor.hx +++ b/source/funkin/graphics/shaders/PureColor.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxColor; diff --git a/source/funkin/shaderslmfao/ScreenWipeShader.hx b/source/funkin/graphics/shaders/ScreenWipeShader.hx similarity index 98% rename from source/funkin/shaderslmfao/ScreenWipeShader.hx rename to source/funkin/graphics/shaders/ScreenWipeShader.hx index 1aeb069ba..bc45f0ef6 100644 --- a/source/funkin/shaderslmfao/ScreenWipeShader.hx +++ b/source/funkin/graphics/shaders/ScreenWipeShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/StrokeShader.hx b/source/funkin/graphics/shaders/StrokeShader.hx similarity index 98% rename from source/funkin/shaderslmfao/StrokeShader.hx rename to source/funkin/graphics/shaders/StrokeShader.hx index 38dc41636..fd133ac0a 100644 --- a/source/funkin/shaderslmfao/StrokeShader.hx +++ b/source/funkin/graphics/shaders/StrokeShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxColor; diff --git a/source/funkin/shaderslmfao/TitleOutline.hx b/source/funkin/graphics/shaders/TitleOutline.hx similarity index 98% rename from source/funkin/shaderslmfao/TitleOutline.hx rename to source/funkin/graphics/shaders/TitleOutline.hx index 9a849f795..db60fc3ae 100644 --- a/source/funkin/shaderslmfao/TitleOutline.hx +++ b/source/funkin/graphics/shaders/TitleOutline.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/WaveShader.hx b/source/funkin/graphics/shaders/WaveShader.hx similarity index 90% rename from source/funkin/shaderslmfao/WaveShader.hx rename to source/funkin/graphics/shaders/WaveShader.hx index 89171b089..8738cc405 100644 --- a/source/funkin/shaderslmfao/WaveShader.hx +++ b/source/funkin/graphics/shaders/WaveShader.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.system.FlxAssets.FlxShader; diff --git a/source/funkin/shaderslmfao/WiggleEffectRuntime.hx b/source/funkin/graphics/shaders/WiggleEffectRuntime.hx similarity index 98% rename from source/funkin/shaderslmfao/WiggleEffectRuntime.hx rename to source/funkin/graphics/shaders/WiggleEffectRuntime.hx index 23f93b672..a04941df5 100644 --- a/source/funkin/shaderslmfao/WiggleEffectRuntime.hx +++ b/source/funkin/graphics/shaders/WiggleEffectRuntime.hx @@ -1,4 +1,4 @@ -package funkin.shaderslmfao; +package funkin.graphics.shaders; import flixel.addons.display.FlxRuntimeShader; import openfl.Assets; diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 1c3a0fdb4..5ca6b03db 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -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; diff --git a/source/funkin/Controls.hx b/source/funkin/input/Controls.hx similarity index 99% rename from source/funkin/Controls.hx rename to source/funkin/input/Controls.hx index 9372c4dc6..d74502e6f 100644 --- a/source/funkin/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -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; +} diff --git a/source/funkin/input/Cursor.hx b/source/funkin/input/Cursor.hx index edd9e70f3..c609c9e30 100644 --- a/source/funkin/input/Cursor.hx +++ b/source/funkin/input/Cursor.hx @@ -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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 diff --git a/source/funkin/input/TurboKeyHandler.hx b/source/funkin/input/TurboKeyHandler.hx index 099d373b4..36b8e2087 100644 --- a/source/funkin/input/TurboKeyHandler.hx +++ b/source/funkin/input/TurboKeyHandler.hx @@ -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, ?delay:Float = DEFAULT_DELAY, - ?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler + public static overload inline extern function build(inputKeys:Array, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler { return new TurboKeyHandler(inputKeys, delay, interval); } diff --git a/source/funkin/modding/base/ScriptedMusicBeatState.hx b/source/funkin/modding/base/ScriptedMusicBeatState.hx index 782e4d0b8..6dc6826c4 100644 --- a/source/funkin/modding/base/ScriptedMusicBeatState.hx +++ b/source/funkin/modding/base/ScriptedMusicBeatState.hx @@ -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 {} diff --git a/source/funkin/modding/base/ScriptedMusicBeatSubState.hx b/source/funkin/modding/base/ScriptedMusicBeatSubState.hx index 7dab3d7dd..79c98ea3f 100644 --- a/source/funkin/modding/base/ScriptedMusicBeatSubState.hx +++ b/source/funkin/modding/base/ScriptedMusicBeatSubState.hx @@ -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 {} diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 586a6206c..18f934aee 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -10,265 +10,12 @@ import funkin.play.notes.NoteDirection; import openfl.events.EventType; import openfl.events.KeyboardEvent; -typedef ScriptEventType = EventType; - /** * 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):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; } } diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index 5e3e32a46..f5d797ea4 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -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 diff --git a/source/funkin/modding/events/ScriptEventType.hx b/source/funkin/modding/events/ScriptEventType.hx new file mode 100644 index 000000000..e06b5ad24 --- /dev/null +++ b/source/funkin/modding/events/ScriptEventType.hx @@ -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; + } +} diff --git a/source/funkin/modding/module/ModuleHandler.hx b/source/funkin/modding/module/ModuleHandler.hx index 3cc7b7984..4711e7419 100644 --- a/source/funkin/modding/module/ModuleHandler.hx +++ b/source/funkin/modding/module/ModuleHandler.hx @@ -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)); } } diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 9796c7161..d23574ce2 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -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; } diff --git a/source/funkin/play/Fighter.hx b/source/funkin/play/Fighter.hx deleted file mode 100644 index 691d21b83..000000000 --- a/source/funkin/play/Fighter.hx +++ /dev/null @@ -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 = [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; -} diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index c5d9b4b0b..6eb53e2d5 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -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; /** diff --git a/source/funkin/GitarooPause.hx b/source/funkin/play/GitarooPause.hx similarity index 96% rename from source/funkin/GitarooPause.hx rename to source/funkin/play/GitarooPause.hx index a4dc766be..dbfbf5961 100644 --- a/source/funkin/GitarooPause.hx +++ b/source/funkin/play/GitarooPause.hx @@ -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 { diff --git a/source/funkin/PauseSubState.hx b/source/funkin/play/PauseSubState.hx similarity index 97% rename from source/funkin/PauseSubState.hx rename to source/funkin/play/PauseSubState.hx index 2ce9abf65..f5555b66e 100644 --- a/source/funkin/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -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': diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 9a126e509..cce424b06 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -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; } diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 3f7231c2a..507fa1236 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -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 diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 588b5663d..7ad0892f6 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -58,7 +58,7 @@ class BaseCharacter extends Bopper */ public var dropNoteCounts(default, null):Array; - @:allow(funkin.ui.animDebugShit.DebugBoundingState) + @:allow(funkin.ui.debug.anim.DebugBoundingState) final _data:CharacterData; final singTimeSec:Float; diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 8be9f25c7..abe8bf992 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -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; } diff --git a/source/funkin/ComboMilestone.hx b/source/funkin/play/components/ComboMilestone.hx similarity index 98% rename from source/funkin/ComboMilestone.hx rename to source/funkin/play/components/ComboMilestone.hx index 79e454c44..54d1438f1 100644 --- a/source/funkin/ComboMilestone.hx +++ b/source/funkin/play/components/ComboMilestone.hx @@ -1,4 +1,4 @@ -package funkin; +package funkin.play.components; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; diff --git a/source/funkin/play/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx similarity index 98% rename from source/funkin/play/HealthIcon.hx rename to source/funkin/play/components/HealthIcon.hx index 958933df8..0d90df5a0 100644 --- a/source/funkin/play/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -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(); } diff --git a/source/funkin/ui/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx similarity index 99% rename from source/funkin/ui/PopUpStuff.hx rename to source/funkin/play/components/PopUpStuff.hx index 75fc87c8b..38a6ec15a 100644 --- a/source/funkin/ui/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -1,4 +1,4 @@ -package funkin.ui; +package funkin.play.components; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; diff --git a/source/funkin/ui/TallyCounter.hx b/source/funkin/play/components/TallyCounter.hx similarity index 94% rename from source/funkin/ui/TallyCounter.hx rename to source/funkin/play/components/TallyCounter.hx index 72857671e..77e6ef4ec 100644 --- a/source/funkin/ui/TallyCounter.hx +++ b/source/funkin/play/components/TallyCounter.hx @@ -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 { diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 2b7db381c..46acf3f37 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -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 diff --git a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx index 4d7f74a58..13697b9f4 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx @@ -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 diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 60b995c06..369a4144a 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -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; /** diff --git a/source/funkin/play/notes/SustainTrail.hx b/source/funkin/play/notes/SustainTrail.hx index f55799828..ab4bf5f16 100644 --- a/source/funkin/play/notes/SustainTrail.hx +++ b/source/funkin/play/notes/SustainTrail.hx @@ -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 diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 90920a710..b48eda224 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -47,8 +47,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry; - final _metadata:Array; - + // key = variation id, value = metadata + final _metadata:Map; final variations:Array; final difficulties:Map; @@ -62,7 +62,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry 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 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 _data]; variations.clear(); variations.push(Constants.DEFAULT_VARIATION); @@ -100,11 +100,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry { - return _metadata; + return _metadata.values(); } /** @@ -149,10 +149,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = [0, 0]; public var originalPosition:FlxPoint = new FlxPoint(0, 0); diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 89b85d14c..c8cb8ce66 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -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); diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index dcf7f9f0d..efb3ee623 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -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. diff --git a/source/funkin/Alphabet.hx b/source/funkin/ui/Alphabet.hx similarity index 97% rename from source/funkin/Alphabet.hx rename to source/funkin/ui/Alphabet.hx index 45e9a2aee..66b95f5b8 100644 --- a/source/funkin/Alphabet.hx +++ b/source/funkin/ui/Alphabet.hx @@ -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); diff --git a/source/funkin/ui/AtlasMenuList.hx b/source/funkin/ui/AtlasMenuList.hx index 028a00eef..efff6da93 100644 --- a/source/funkin/ui/AtlasMenuList.hx +++ b/source/funkin/ui/AtlasMenuList.hx @@ -38,7 +38,7 @@ class AtlasMenuList extends MenuTypedList /** * A menu list item which uses single texture atlas. */ -class AtlasMenuItem extends MenuItem +class AtlasMenuItem extends MenuListItem { var atlas:FlxAtlasFrames; diff --git a/source/funkin/MenuItem.hx b/source/funkin/ui/MenuItem.hx similarity index 93% rename from source/funkin/MenuItem.hx rename to source/funkin/ui/MenuItem.hx index 261092a1a..ba5cc066b 100644 --- a/source/funkin/MenuItem.hx +++ b/source/funkin/ui/MenuItem.hx @@ -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; diff --git a/source/funkin/ui/MenuList.hx b/source/funkin/ui/MenuList.hx index f1de8d40e..3ffe3c330 100644 --- a/source/funkin/ui/MenuList.hx +++ b/source/funkin/ui/MenuList.hx @@ -6,7 +6,7 @@ import flixel.group.FlxGroup; import flixel.math.FlxPoint; import flixel.util.FlxSignal; -class MenuTypedList extends FlxTypedGroup +class MenuTypedList extends FlxTypedGroup { public var selectedIndex(default, null) = 0; public var selectedItem(get, never):T; @@ -206,7 +206,7 @@ class MenuTypedList extends FlxTypedGroup } } -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 extends MenuItem +class MenuTypedItem extends MenuListItem { public var label(default, set):T; diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx similarity index 77% rename from source/funkin/MusicBeatState.hx rename to source/funkin/ui/MusicBeatState.hx index 9a986a8b5..4e0e19d5e 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -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)); } } diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx similarity index 93% rename from source/funkin/MusicBeatSubState.hx rename to source/funkin/ui/MusicBeatSubState.hx index 31d1bd14c..64df6ee71 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -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); diff --git a/source/funkin/SwagCamera.hx b/source/funkin/ui/SwagCamera.hx similarity index 87% rename from source/funkin/SwagCamera.hx rename to source/funkin/ui/SwagCamera.hx index 386eaea62..70791d38f 100644 --- a/source/funkin/SwagCamera.hx +++ b/source/funkin/ui/SwagCamera.hx @@ -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); } } } diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 7ef4cb238..ef02a802e 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -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'); } diff --git a/source/funkin/MemoryCounter.hx b/source/funkin/ui/debug/MemoryCounter.hx similarity index 97% rename from source/funkin/MemoryCounter.hx rename to source/funkin/ui/debug/MemoryCounter.hx index 658febe59..312d853e7 100644 --- a/source/funkin/MemoryCounter.hx +++ b/source/funkin/ui/debug/MemoryCounter.hx @@ -1,4 +1,4 @@ -package funkin; +package funkin.ui.debug; import openfl.text.TextFormat; import openfl.system.System; diff --git a/source/funkin/ui/animDebugShit/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx similarity index 98% rename from source/funkin/ui/animDebugShit/DebugBoundingState.hx rename to source/funkin/ui/debug/anim/DebugBoundingState.hx index 297c44e8e..4e06913b4 100644 --- a/source/funkin/ui/animDebugShit/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -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; diff --git a/source/funkin/ui/animDebugShit/FlxAnimateTest.hx b/source/funkin/ui/debug/anim/FlxAnimateTest.hx similarity index 94% rename from source/funkin/ui/animDebugShit/FlxAnimateTest.hx rename to source/funkin/ui/debug/anim/FlxAnimateTest.hx index 738e109ef..c83d2c370 100644 --- a/source/funkin/ui/animDebugShit/FlxAnimateTest.hx +++ b/source/funkin/ui/debug/anim/FlxAnimateTest.hx @@ -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. diff --git a/source/funkin/ui/debug/charting/ChartEditorCommand.hx b/source/funkin/ui/debug/charting/ChartEditorCommand.hx deleted file mode 100644 index 98ca810d3..000000000 --- a/source/funkin/ui/debug/charting/ChartEditorCommand.hx +++ /dev/null @@ -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; - var appendToSelection:Bool; - - public function new(notes:Array, 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; - - public function new(notes:Array) - { - 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; - var events:Array; - - public function new(notes:Array, events:Array) - { - 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; - var appendToSelection:Bool; - - public function new(events:Array, 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; - - public function new(events:Array) - { - 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; - var events:Array; - - public function new(notes:Array, events:Array) - { - 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; - var events:Array; - - public function new(notes:Array, events:Array) - { - 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; - var events:Array; - var previousNoteSelection:Array; - var previousEventSelection:Array; - - public function new(notes:Array, events:Array, previousNoteSelection:Array, - previousEventSelection:Array) - { - 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; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - 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; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - 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; - var previousEventSelection:Array; - - public function new(?previousNoteSelection:Array, ?previousEventSelection:Array) - { - 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; - var events:Array; - - public function new(notes:Array, events:Array) - { - 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 = []; - var flippedNotes:Array = []; - - public function new(notes:Array) - { - 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 = []; - var addedEvents:Array = []; - - 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'; - } -} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 7babd6038..aa5372327 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -17,41 +17,65 @@ import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.tweens.misc.VarTween; import flixel.util.FlxColor; +import funkin.ui.mainmenu.MainMenuState; import flixel.util.FlxSort; import flixel.util.FlxTimer; import funkin.audio.visualize.PolygonSpectogram; import funkin.audio.VoicesGroup; import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongMetadata; import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongDataUtils; -import funkin.data.song.SongRegistry; import funkin.input.Cursor; import funkin.input.TurboKeyHandler; import funkin.modding.events.ScriptEvent; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; -import funkin.play.HealthIcon; +import funkin.play.components.HealthIcon; import funkin.play.notes.NoteSprite; -import funkin.play.notes.Strumline; import funkin.play.PlayState; import funkin.play.song.Song; +import funkin.data.song.SongData.SongChartData; +import funkin.data.song.SongRegistry; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongCharacterData; +import funkin.data.song.SongDataUtils; +import funkin.ui.debug.charting.commands.ChartEditorCommand; +import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler; import funkin.play.stage.StageData; import funkin.save.Save; -import funkin.ui.debug.charting.ChartEditorCommand; -import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme; -import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode; +import funkin.ui.debug.charting.commands.AddEventsCommand; +import funkin.ui.debug.charting.commands.AddNotesCommand; +import funkin.ui.debug.charting.commands.ChartEditorCommand; +import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.DeselectAllItemsCommand; +import funkin.ui.debug.charting.commands.DeselectItemsCommand; +import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand; +import funkin.ui.debug.charting.commands.FlipNotesCommand; +import funkin.ui.debug.charting.commands.InvertSelectedItemsCommand; +import funkin.ui.debug.charting.commands.MoveEventsCommand; +import funkin.ui.debug.charting.commands.MoveItemsCommand; +import funkin.ui.debug.charting.commands.MoveNotesCommand; +import funkin.ui.debug.charting.commands.PasteItemsCommand; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; +import funkin.ui.debug.charting.commands.RemoveItemsCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; +import funkin.ui.debug.charting.commands.SelectAllItemsCommand; +import funkin.ui.debug.charting.commands.SelectItemsCommand; +import funkin.ui.debug.charting.commands.SetItemSelectionCommand; +import funkin.ui.debug.charting.components.ChartEditorEventSprite; +import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite; +import funkin.ui.debug.charting.components.ChartEditorNotePreview; +import funkin.ui.debug.charting.components.ChartEditorNoteSprite; +import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite; import funkin.ui.haxeui.components.CharacterPlayer; -import funkin.ui.haxeui.components.FunkinMenuItem; import funkin.ui.haxeui.HaxeUIState; import funkin.util.Constants; -import funkin.util.DateUtil; -import funkin.util.FileUtil; -import funkin.util.SerializerUtil; import funkin.util.SortUtil; import funkin.util.WindowUtil; import haxe.DynamicAccess; @@ -65,17 +89,18 @@ import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.Frame; import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import haxe.ui.core.Component; import haxe.ui.core.Screen; import haxe.ui.events.DragEvent; import haxe.ui.events.UIEvent; +import haxe.ui.focus.FocusManager; import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationType; -import openfl.Assets; import openfl.display.BitmapData; -import openfl.geom.Rectangle; +import funkin.util.FileUtil; using Lambda; @@ -88,14 +113,6 @@ using Lambda; * @author MasterEric */ @:nullSafety -// Give other classes access to private instance fields -@:allow(funkin.ui.debug.charting.ChartEditorCommand) -@:allow(funkin.ui.debug.charting.ChartEditorDropdowns) -@:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) -@:allow(funkin.ui.debug.charting.ChartEditorThemeHandler) -@:allow(funkin.ui.debug.charting.ChartEditorAudioHandler) -@:allow(funkin.ui.debug.charting.ChartEditorImportExportHandler) -@:allow(funkin.ui.debug.charting.ChartEditorToolboxHandler) class ChartEditorState extends HaxeUIState { /** @@ -103,129 +120,223 @@ class ChartEditorState extends HaxeUIState */ // ============================== // XML Layouts - static final CHART_EDITOR_LAYOUT:String = Paths.ui('chart-editor/main-view'); + public static final CHART_EDITOR_LAYOUT:String = Paths.ui('chart-editor/main-view'); - static final CHART_EDITOR_NOTIFBAR_LAYOUT:String = Paths.ui('chart-editor/components/notifbar'); - static final CHART_EDITOR_PLAYBARHEAD_LAYOUT:String = Paths.ui('chart-editor/components/playbar-head'); + public static final CHART_EDITOR_NOTIFBAR_LAYOUT:String = Paths.ui('chart-editor/components/notifbar'); + public static final CHART_EDITOR_PLAYBARHEAD_LAYOUT:String = Paths.ui('chart-editor/components/playbar-head'); - static final CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:String = Paths.ui('chart-editor/toolbox/tools'); - static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); - static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); - static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); - static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); - static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); - static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); + public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); + public static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); + public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); + public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); + public static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); + public static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); // Validation - static final SUPPORTED_MUSIC_FORMATS:Array = ['ogg']; + public static final SUPPORTED_MUSIC_FORMATS:Array = ['ogg']; + + // Layout /** * The base grid size for the chart editor. */ public static final GRID_SIZE:Int = 40; + /** + * The width of the scroll area. + */ public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = 12; + /** + * The height of the playhead, in pixels. + */ public static final PLAYHEAD_HEIGHT:Int = Std.int(GRID_SIZE / 8); + /** + * The width of the border between grid squares, where the crosshair changes from "Place Notes" to "Select Notes". + */ public static final GRID_SELECTION_BORDER_WIDTH:Int = 6; + /** + * The height of the menu bar in the layout. + */ + public static final MENU_BAR_HEIGHT:Int = 32; + + /** + * The height of the playbar in the layout. + */ + public static final PLAYBAR_HEIGHT:Int = 48; + + /** + * The amount of padding between the menu bar and the chart grid when fully scrolled up. + */ + public static final GRID_TOP_PAD:Int = 8; + + // Colors + // Background color tint. + public static final CURSOR_COLOR:FlxColor = 0xE0FFFFFF; + public static final PREVIEW_BG_COLOR:FlxColor = 0xFF303030; + public static final PLAYHEAD_SCROLL_AREA_COLOR:FlxColor = 0xFF682B2F; + public static final SPECTROGRAM_COLOR:FlxColor = 0xFFFF0000; + public static final PLAYHEAD_COLOR:FlxColor = 0xC0BD0231; + + // Timings + + /** + * Duration, in seconds, for the scroll easing animation. + */ + public static final SCROLL_EASE_DURATION:Float = 0.2; + + // Other + /** * Number of notes in each player's strumline. */ public static final STRUMLINE_SIZE:Int = 4; - /** - * The height of the menu bar in the layout. - */ - static final MENU_BAR_HEIGHT:Int = 32; - - /** - * The height of the playbar in the layout. - */ - static final PLAYBAR_HEIGHT:Int = 48; - - /** - * Duration to wait before autosaving the chart. - */ - static final AUTOSAVE_TIMER_DELAY:Float = 60.0 * 5.0; - - /** - * The amount of padding between the menu bar and the chart grid when fully scrolled up. - */ - static final GRID_TOP_PAD:Int = 8; - - /** - * Duration, in milliseconds, until toast notifications are automatically hidden. - */ - static final NOTIFICATION_DISMISS_TIME:Int = 5000; - - /** - * Duration, in seconds, for the scroll easing animation. - */ - static final SCROLL_EASE_DURATION:Float = 0.2; - - // UI Element Colors - // Background color tint. - static final CURSOR_COLOR:FlxColor = 0xE0FFFFFF; - static final PREVIEW_BG_COLOR:FlxColor = 0xFF303030; - static final PLAYHEAD_SCROLL_AREA_COLOR:FlxColor = 0xFF682B2F; - static final SPECTROGRAM_COLOR:FlxColor = 0xFFFF0000; - static final PLAYHEAD_COLOR:FlxColor = 0xC0BD0231; - /** * How many pixels far the user needs to move the mouse before the cursor is considered to be dragged rather than clicked. */ - static final DRAG_THRESHOLD:Float = 16.0; + public static final DRAG_THRESHOLD:Float = 16.0; /** - * Types of notes you can snap to. + * Precisions of notes you can snap to. */ - static final SNAP_QUANTS:Array = [4, 8, 12, 16, 20, 24, 32, 48, 64, 96, 192]; + public static final SNAP_QUANTS:Array = [4, 8, 12, 16, 20, 24, 32, 48, 64, 96, 192]; - static final BASE_QUANT:Int = 16; + /** + * The default note snapping value. + */ + public static final BASE_QUANT:Int = 16; + + /** + * The index of thet default note snapping value in the `SNAP_QUANTS` array. + */ + public static final BASE_QUANT_INDEX:Int = 3; /** * INSTANCE DATA */ // ============================== + // Song Length /** - * The internal index of what note snapping value is in use. - * Increment to make placement more preceise and decrement to make placement less precise. + * The length of the current instrumental, in milliseconds. */ - var noteSnapQuantIndex:Int = 3; // default is 16 + @:isVar var songLengthInMs(get, set):Float = 0; - /** - * The current note snapping value. - * For example, `32` when snapping to 32nd notes. - */ - public var noteSnapQuant(get, never):Int; - - function get_noteSnapQuant():Int + function get_songLengthInMs():Float { - return SNAP_QUANTS[noteSnapQuantIndex]; + if (songLengthInMs <= 0) return 1000; + return songLengthInMs; + } + + function set_songLengthInMs(value:Float):Float + { + this.songLengthInMs = value; + + // Make sure playhead doesn't go outside the song. + if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; + + return this.songLengthInMs; } /** - * The ratio of the current note snapping value to the default. - * For example, `32` becomes `0.5` when snapping to 16th notes. + * The length of the current instrumental, converted to steps. + * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. */ - public var noteSnapRatio(get, never):Float; + var songLengthInSteps(get, set):Float; - function get_noteSnapRatio():Float + function get_songLengthInSteps():Float { - return BASE_QUANT / noteSnapQuant; + return Conductor.getTimeInSteps(songLengthInMs); + } + + function set_songLengthInSteps(value:Float):Float + { + // Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first. + songLengthInMs = Conductor.getStepTimeInMs(value); + return value; } /** - * scrollPosition is the current position in the song, in pixels. + * The length of the current instrumental, in PIXELS. + * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. + */ + var songLengthInPixels(get, set):Int; + + function get_songLengthInPixels():Int + { + return Std.int(songLengthInSteps * GRID_SIZE); + } + + function set_songLengthInPixels(value:Int):Int + { + songLengthInSteps = value / GRID_SIZE; + return value; + } + + // Scroll Position + + /** + * The relative scroll position in the song, in pixels. * One pixel is 1/40 of 1 step, and 1/160 of 1 beat. */ var scrollPositionInPixels(default, set):Float = -1.0; + function set_scrollPositionInPixels(value:Float):Float + { + if (value < 0) + { + // If we're scrolling up, and we hit the top, + // but the playhead is in the middle, move the playhead up. + if (playheadPositionInPixels > 0) + { + var amount:Float = scrollPositionInPixels - value; + playheadPositionInPixels -= amount; + } + + value = 0; + } + + if (value > songLengthInPixels) value = songLengthInPixels; + + if (value == scrollPositionInPixels) return value; + + // Difference in pixels. + var diff:Float = value - scrollPositionInPixels; + + this.scrollPositionInPixels = value; + + // Move the grid sprite to the correct position. + if (gridTiledSprite != null && gridPlayheadScrollArea != null) + { + if (isViewDownscroll) + { + gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayheadScrollArea.y = gridTiledSprite.y; + } + else + { + gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD); + gridPlayheadScrollArea.y = gridTiledSprite.y; + } + } + + // Move the rendered notes to the correct position. + renderedNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedHoldNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedEvents.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + renderedSelectionSquares.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0); + // Offset the selection box start position, if we are dragging. + if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff; + // Update the note preview viewport box. + setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); + return this.scrollPositionInPixels; + } + /** - * scrollPosition, converted to steps. + * The relative scroll position in the song, converted to steps. * NOT dependant on BPM, because the size of a grid square does not change with BPM. */ var scrollPositionInSteps(get, set):Float; @@ -242,7 +353,7 @@ class ChartEditorState extends HaxeUIState } /** - * scrollPosition, converted to milliseconds. + * The relative scroll position in the song, converted to milliseconds. * DEPENDANT on BPM, because the duration of a grid square changes with BPM. */ var scrollPositionInMs(get, set):Float; @@ -258,11 +369,13 @@ class ChartEditorState extends HaxeUIState return value; } + // Playhead (on the grid) + /** - * The position of the playhead, in pixels, relative to the scrollPosition. - * 0 means playhead is at the top of the grid. - * 40 means the playhead is 1 grid length below the base position. - * -40 means the playhead is 1 grid length above the base position. + * The position of the playhead, in pixels, relative to the `scrollPositionInPixels`. + * `0` means playhead is at the top of the grid. + * `40` means the playhead is 1 grid length below the base position. + * `-40` means the playhead is 1 grid length above the base position. */ var playheadPositionInPixels(default, set):Float = 0.0; @@ -314,77 +427,7 @@ class ChartEditorState extends HaxeUIState return value; } - /** - * songLength, in milliseconds. - */ - @:isVar var songLengthInMs(get, set):Float = 0; - - function get_songLengthInMs():Float - { - if (songLengthInMs <= 0) return 1000; - return songLengthInMs; - } - - function set_songLengthInMs(value:Float):Float - { - this.songLengthInMs = value; - - // Make sure playhead doesn't go outside the song. - if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs; - - return this.songLengthInMs; - } - - /** - * songLength, converted to steps. - * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. - */ - var songLengthInSteps(get, set):Float; - - function get_songLengthInSteps():Float - { - return Conductor.getTimeInSteps(songLengthInMs); - } - - function set_songLengthInSteps(value:Float):Float - { - // Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first. - songLengthInMs = Conductor.getStepTimeInMs(value); - return value; - } - - /** - * This is the song's length in PIXELS, same format as scrollPosition. - * Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does. - */ - var songLengthInPixels(get, set):Int; - - function get_songLengthInPixels():Int - { - return Std.int(songLengthInSteps * GRID_SIZE); - } - - function set_songLengthInPixels(value:Int):Int - { - songLengthInSteps = value / GRID_SIZE; - return value; - } - - /** - * The current theme used by the editor. - * Dictates the appearance of many UI elements. - * Currently hardcoded to just Light and Dark. - */ - var currentTheme(default, set):ChartEditorTheme = ChartEditorTheme.Light; - - function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme - { - if (value == null || value == currentTheme) return currentTheme; - - currentTheme = value; - ChartEditorThemeHandler.updateTheme(this); - return value; - } + // Playbar (at the bottom) /** * Whether a skip button has been pressed on the playbar, and which one. @@ -404,47 +447,62 @@ class ChartEditorState extends HaxeUIState */ var playbarHeadDraggingWasPlaying:Bool = false; + // Tools Status + /** * The note kind to use for notes being placed in the chart. Defaults to `''`. */ var selectedNoteKind:String = ''; /** - * The note kind to use for notes being placed in the chart. Defaults to `''`. + * The event type to use for events being placed in the chart. Defaults to `''`. */ var selectedEventKind:String = 'FocusCamera'; /** - * The note data as a struct. + * The event data to use for events being placed in the chart. */ var selectedEventData:DynamicAccess = {}; /** - * Whether to play a metronome sound while the playhead is moving. + * The internal index of what note snapping value is in use. + * Increment to make placement more preceise and decrement to make placement less precise. */ - var isMetronomeEnabled:Bool = true; + var noteSnapQuantIndex:Int = BASE_QUANT_INDEX; /** - * Use the tool window to affect how the user interacts with the program. + * The current note snapping value. + * For example, `32` when snapping to 32nd notes. */ - var currentToolMode:ChartEditorToolMode = ChartEditorToolMode.Select; + var noteSnapQuant(get, never):Int; + + function get_noteSnapQuant():Int + { + return SNAP_QUANTS[noteSnapQuantIndex]; + } /** - * The character sprite in the Player Preview window. - * `null` until accessed. + * The ratio of the current note snapping value to the default. + * For example, `32` becomes `0.5` when snapping to 16th notes. */ - var currentPlayerCharacterPlayer:Null = null; + var noteSnapRatio(get, never):Float; - /** - * The character sprite in the Opponent Preview window. - * `null` until accessed. - */ - var currentOpponentCharacterPlayer:Null = null; + function get_noteSnapRatio():Float + { + return BASE_QUANT / noteSnapQuant; + } /** * The currently selected live input style. */ - var currentLiveInputStyle:LiveInputStyle = LiveInputStyle.None; + var currentLiveInputStyle:ChartEditorLiveInputStyle = None; + + /** + * If true, playtesting a chart will skip to the current playhead position. + */ + var playtestStartTime:Bool = false; + + // Visuals /** * Whether the current view is in downscroll mode. @@ -467,20 +525,64 @@ class ChartEditorState extends HaxeUIState } /** - * If true, playtesting a chart will skip to the current playhead position. + * The current theme used by the editor. + * Dictates the appearance of many UI elements. + * Currently hardcoded to just Light and Dark. */ - var playtestStartTime:Bool = false; + var currentTheme(default, set):ChartEditorTheme = ChartEditorTheme.Light; + + function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme + { + if (value == null || value == currentTheme) return currentTheme; + + currentTheme = value; + this.updateTheme(); + return value; + } /** - * Whether hitsounds are enabled for at least one character. + * The character sprite in the Player Preview window. + * `null` until accessed. */ - var hitsoundsEnabled(get, never):Bool; + var currentPlayerCharacterPlayer:Null = null; - function get_hitsoundsEnabled():Bool + /** + * The character sprite in the Opponent Preview window. + * `null` until accessed. + */ + var currentOpponentCharacterPlayer:Null = null; + + // HaxeUI + + /** + * Whether the user is focused on an input in the Haxe UI, and inputs are being fed into it. + * If the user clicks off the input, focus will leave. + */ + var isHaxeUIFocused(get, never):Bool; + + function get_isHaxeUIFocused():Bool { - return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; + return FocusManager.instance.focus != null; } + /** + * Set by ChartEditorDialogHandler, used to prevent background interaction while the dialog is open. + */ + var isHaxeUIDialogOpen:Bool = false; + + /** + * The Dialog components representing the currently available tool windows. + * Dialogs are retained here even when collapsed or hidden. + */ + var activeToolboxes:Map = new Map(); + + // Audio + + /** + * Whether to play a metronome sound while the playhead is moving. + */ + var isMetronomeEnabled:Bool = true; + /** * Whether hitsounds are enabled for the player. */ @@ -492,107 +594,117 @@ class ChartEditorState extends HaxeUIState var hitsoundsEnabledOpponent:Bool = true; /** - * Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI. - * If so, ignore mouse events underneath. + * Whether hitsounds are enabled for at least one character. */ - var isCursorOverHaxeUI(get, never):Bool; + var hitsoundsEnabled(get, never):Bool; - function get_isCursorOverHaxeUI():Bool + function get_hitsoundsEnabled():Bool { - return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY); + return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent; } - var isCursorOverHaxeUIButton(get, never):Bool; - - function get_isCursorOverHaxeUIButton():Bool - { - return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY, haxe.ui.components.Button) - || Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY, haxe.ui.components.Link); - } + // Auto-save /** - * Set by ChartEditorDialogHandler, used to prevent background interaction while the dialog is open. + * A timer used to auto-save the chart after a period of inactivity. */ - public var isHaxeUIDialogOpen:Bool = false; + var autoSaveTimer:Null = null; + + // Scrolling /** - * The variation ID for the difficulty which is currently being edited. + * Whether the user's last mouse click was on the playhead scroll area. */ - var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION; + var gridPlayheadScrollAreaPressed:Bool = false; /** - * Setter called when we are switching variations. - * We will likely need to switch instrumentals as well. + * Where the user's last mouse click was on the note preview scroll area. + * `null` if the user isn't clicking on the note preview. */ - function set_selectedVariation(value:String):String - { - // Don't update if we're already on the variation. - if (selectedVariation == value) return selectedVariation; - selectedVariation = value; - - // Make sure view is updated when the variation changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - - switchToCurrentInstrumental(); - - return selectedVariation; - } + var notePreviewScrollAreaStartPos:Null = null; /** - * The difficulty ID for the difficulty which is currently being edited. + * The current process that is lerping the scroll position. + * Used to cancel the previous lerp if the user scrolls again. */ - var selectedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + var currentScrollEase:Null; - function set_selectedDifficulty(value:String):String - { - selectedDifficulty = value; - - // Make sure view is updated when the difficulty changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - - // Make sure the difficulty we selected is in the list of difficulties. - currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty); - - return selectedDifficulty; - } + // Note Placement /** - * The instrumental ID which is currently selected. + * The SongNoteData which is currently being placed. + * `null` if the user isn't currently placing a note. + * As the user drags, we will update this note's sustain length, and finalize the note when they release. */ - var currentInstrumentalId(get, set):String; + var currentPlaceNoteData:Null = null; - function get_currentInstrumentalId():String - { - var instId:Null = currentSongMetadata.playData.characters.instrumental; - if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation; - return instId; - } - - function set_currentInstrumentalId(value:String):String - { - return currentSongMetadata.playData.characters.instrumental = value; - } + // Note Movement /** - * The character ID for the character which is currently selected. + * The note sprite we are currently moving, if any. */ - var selectedCharacter(default, set):String = Constants.DEFAULT_CHARACTER; + var dragTargetNote:Null = null; - function set_selectedCharacter(value:String):String - { - selectedCharacter = value; + /** + * The song event sprite we are currently moving, if any. + */ + var dragTargetEvent:Null = null; - // Make sure view is updated when the character changes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; + /** + * The amount of vertical steps the note sprite has moved by since the user started dragging. + */ + var dragTargetCurrentStep:Float = 0; - return selectedCharacter; - } + /** + * The amount of horizontal columns the note sprite has moved by since the user started dragging. + */ + var dragTargetCurrentColumn:Int = 0; + + // Hold Note Dragging + + /** + * The current length of the hold note we are dragging, in steps. + * Play a sound when this value changes. + */ + var dragLengthCurrent:Float = 0; + + /** + * Flip-flop to alternate between two stretching sounds. + */ + var stretchySounds:Bool = false; + + // Selection + + /** + * The notes which are currently in the user's selection. + */ + var currentNoteSelection:Array = []; + + /** + * The events which are currently in the user's selection. + */ + var currentEventSelection:Array = []; + + /** + * The position where the user clicked to start a selection. + * `null` if the user isn't currently selecting anything. + * The selection box extends from this point to the current mouse position. + */ + var selectionBoxStartPos:Null = null; + + // History + + /** + * The list of command previously performed. Used for undoing previous actions. + */ + var undoHistory:Array = []; + + /** + * The list of commands that have been undone. Used for redoing previous actions. + */ + var redoHistory:Array = []; + + // Dirty Flags /** * Whether the note display render group has been modified and needs to be updated. @@ -631,7 +743,7 @@ class ChartEditorState extends HaxeUIState if (value) { // Start the auto-save timer. - autoSaveTimer = new FlxTimer().start(AUTOSAVE_TIMER_DELAY, (_) -> autoSave()); + autoSaveTimer = new FlxTimer().start(Constants.AUTOSAVE_TIMER_DELAY_SEC, (_) -> autoSave()); } else { @@ -649,11 +761,6 @@ class ChartEditorState extends HaxeUIState return saveDataDirty; } - /** - * A timer used to auto-save the chart after a period of inactivity. - */ - var autoSaveTimer:Null = null; - /** * Whether the difficulty tree view in the toolbox has been modified and needs to be updated. * This happens when we add/remove difficulties. @@ -678,17 +785,12 @@ class ChartEditorState extends HaxeUIState */ var opponentPreviewDirty:Bool = true; - var isInPlaytestMode:Bool = false; - /** - * The list of command previously performed. Used for undoing previous actions. + * Whether the undo/redo histories have changed since the last time the UI was updated. */ - var undoHistory:Array = []; + var commandHistoryDirty:Bool = true; - /** - * The list of commands that have been undone. Used for redoing previous actions. - */ - var redoHistory:Array = []; + // Input /** * Handler used to track how long the user has been holding the undo keybind. @@ -730,52 +832,6 @@ class ChartEditorState extends HaxeUIState */ var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN); - /** - * Whether the undo/redo histories have changed since the last time the UI was updated. - */ - var commandHistoryDirty:Bool = true; - - /** - * The notes which are currently in the user's selection. - */ - var currentNoteSelection:Array = []; - - /** - * The events which are currently in the user's selection. - */ - var currentEventSelection:Array = []; - - /** - * The position where the user clicked to start a selection. - * `null` if the user isn't currently selecting anything. - * The selection box extends from this point to the current mouse position. - */ - var selectionBoxStartPos:Null = null; - - /** - * Whether the user's last mouse click was on the playhead scroll area. - */ - var gridPlayheadScrollAreaPressed:Bool = false; - - /** - * Where the user's last mouse click was on the note preview scroll area. - * `null` if the user isn't clicking on the note preview. - */ - var notePreviewScrollAreaStartPos:Null = null; - - /** - * The SongNoteData which is currently being placed. - * `null` if the user isn't currently placing a note. - * As the user drags, we will update this note's sustain length. - */ - var currentPlaceNoteData:Null = null; - - /** - * The Dialog components representing the currently available tool windows. - * Dialogs are retained here even when collapsed or hidden. - */ - var activeToolboxes:Map = new Map(); - /** * AUDIO AND SOUND DATA */ @@ -784,7 +840,7 @@ class ChartEditorState extends HaxeUIState /** * The chill audio track that plays when you open the Chart Editor. */ - public var welcomeMusic:FlxSound = new FlxSound(); + var welcomeMusic:FlxSound = new FlxSound(); /** * The audio track for the instrumental. @@ -988,7 +1044,7 @@ class ChartEditorState extends HaxeUIState return currentSongChartData.events = value; } - public var currentSongNoteStyle(get, set):String; + var currentSongNoteStyle(get, set):String; function get_currentSongNoteStyle():String { @@ -1064,10 +1120,67 @@ class ChartEditorState extends HaxeUIState } /** - * SIGNALS + * The variation ID for the difficulty which is currently being edited. */ - // ============================== - // public var onDifficultyChange(default, never):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION; + + /** + * Setter called when we are switching variations. + * We will likely need to switch instrumentals as well. + */ + function set_selectedVariation(value:String):String + { + // Don't update if we're already on the variation. + if (selectedVariation == value) return selectedVariation; + selectedVariation = value; + + // Make sure view is updated when the variation changes. + noteDisplayDirty = true; + notePreviewDirty = true; + notePreviewViewportBoundsDirty = true; + + switchToCurrentInstrumental(); + + return selectedVariation; + } + + /** + * The difficulty ID for the difficulty which is currently being edited. + */ + var selectedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + + function set_selectedDifficulty(value:String):String + { + selectedDifficulty = value; + + // Make sure view is updated when the difficulty changes. + noteDisplayDirty = true; + notePreviewDirty = true; + notePreviewViewportBoundsDirty = true; + + // Make sure the difficulty we selected is in the list of difficulties. + currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty); + + return selectedDifficulty; + } + + /** + * The instrumental ID which is currently selected. + */ + var currentInstrumentalId(get, set):String; + + function get_currentInstrumentalId():String + { + var instId:Null = currentSongMetadata.playData.characters.instrumental; + if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation; + return instId; + } + + function set_currentInstrumentalId(value:String):String + { + return currentSongMetadata.playData.characters.instrumental = value; + } + /** * RENDER OBJECTS */ @@ -1104,6 +1217,9 @@ class ChartEditorState extends HaxeUIState */ var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup(); + /** + * The sprite for the scroll area under + */ var gridPlayheadScrollArea:Null = null; /** @@ -1172,7 +1288,7 @@ class ChartEditorState extends HaxeUIState /** * The item in the menubar to save the currently opened chart. */ - var menubarItemSaveChart:Null = null; + var menubarItemSaveChart:Null = null; /** * The playbar head slider. @@ -1194,12 +1310,6 @@ class ChartEditorState extends HaxeUIState */ var playbarNoteSnap:Null