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/Project.xml b/Project.xml index 69400d8b1..46ba7f155 100644 --- a/Project.xml +++ b/Project.xml @@ -4,10 +4,7 @@ - - - + @@ -165,7 +162,10 @@ + + +
diff --git a/assets b/assets index c4543111e..482ef7658 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c4543111e7a31d7bde0e0fdc56636646abb8c963 +Subproject commit 482ef76582208a484a2f6450ce5ad9e278db08f8 diff --git a/hmm.json b/hmm.json index 22dd6e42f..778b85604 100644 --- a/hmm.json +++ b/hmm.json @@ -56,7 +56,7 @@ "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "9bd0b9e0fea40b8e06a89aac4949512d95064609", + "ref": "95c7d66e779626eabd6f48a1cd7aa7f9a503a7f3", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/Main.hx b/source/Main.hx index dffe666b7..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; @@ -11,6 +11,7 @@ import openfl.display.Sprite; import openfl.events.Event; import openfl.Lib; import openfl.media.Video; +import funkin.util.CLIUtil; import openfl.net.NetStream; class Main extends Sprite @@ -110,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 5299a3aa0..13bcd306e 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -1,5 +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; @@ -10,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; @@ -26,11 +28,15 @@ import funkin.play.stage.StageData.StageDataParser; import funkin.play.character.CharacterData.CharacterDataParser; 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. @@ -228,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 @@ -247,8 +253,21 @@ class InitState extends FlxState */ function startGameNormally():Void { - FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu')); - FlxG.switchState(new TitleState()); + var params:CLIParams = CLIUtil.processArgs(); + trace('Command line args: ${params}'); + + if (params.chart.shouldLoadChart) + { + FlxG.switchState(new ChartEditorState( + { + fnfcTargetPath: params.chart.chartPath, + })); + } + else + { + FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu')); + FlxG.switchState(new TitleState()); + } } /** 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/Preloader.hx b/source/funkin/Preloader.hx similarity index 93% rename from source/Preloader.hx rename to source/funkin/Preloader.hx index 3603d1a16..24015be05 100644 --- a/source/Preloader.hx +++ b/source/funkin/Preloader.hx @@ -1,4 +1,4 @@ -package; +package funkin; import flash.Lib; import flash.display.Bitmap; @@ -7,6 +7,7 @@ import flash.display.BlendMode; import flash.display.Sprite; import flixel.system.FlxBasePreloader; import openfl.display.Sprite; +import funkin.util.CLIUtil; @:bitmap("art/preloaderArt.png") class LogoImage extends BitmapData {} @@ -15,6 +16,8 @@ class Preloader extends FlxBasePreloader public function new(MinDisplayTime:Float = 0, ?AllowedURLs:Array) { super(MinDisplayTime, AllowedURLs); + + CLIUtil.resetWorkingDir(); // Bug fix for drag-and-drop. } var logo:Sprite; 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/DataParse.hx b/source/funkin/data/DataParse.hx index 64a53d2a4..cbd168a61 100644 --- a/source/funkin/data/DataParse.hx +++ b/source/funkin/data/DataParse.hx @@ -104,6 +104,22 @@ class DataParse } } + /** + * Parser which outputs a `Either>`. + */ + public static function eitherFloatOrFloats(json:Json, name:String):Null>> + { + switch (json.value) + { + case JNumber(f): + return Either.Left(Std.parseFloat(f)); + case JArray(fields): + return Either.Right(fields.map((field) -> cast Tools.getValue(field))); + default: + throw 'Expected property $name to be one or multiple floats, but it was ${json.value}.'; + } + } + /** * Parser which outputs a `Either`. * Used by the FNF legacy JSON importer. diff --git a/source/funkin/data/DataWrite.hx b/source/funkin/data/DataWrite.hx index 2f3a7632f..e277cb01c 100644 --- a/source/funkin/data/DataWrite.hx +++ b/source/funkin/data/DataWrite.hx @@ -3,11 +3,14 @@ package funkin.data; import funkin.util.SerializerUtil; import thx.semver.Version; import thx.semver.VersionRule; +import haxe.ds.Either; /** * `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON. * * Functions must be of the signature `(T) -> String`, where `T` is the type of the property. + * + * NOTE: Result must include quotation marks if the value is a string! json2object will not add them for you! */ class DataWrite { @@ -23,11 +26,12 @@ class DataWrite } /** + * * `@:jcustomwrite(funkin.data.DataWrite.semverVersion)` */ public static function semverVersion(value:Version):String { - return value.toString(); + return '"${value.toString()}"'; } /** @@ -35,6 +39,22 @@ class DataWrite */ public static function semverVersionRule(value:VersionRule):String { - return value.toString(); + return '"${value.toString()}"'; + } + + /** + * `@:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats)` + */ + public static function eitherFloatOrFloats(value:Null>>):String + { + switch (value) + { + case null: + return '${1.0}'; + case Left(inner): + return '$inner'; + case Right(inner): + return dynamicValue(inner); + } } } diff --git a/source/funkin/data/animation/AnimationData.hx b/source/funkin/data/animation/AnimationData.hx index 9765f784c..a0214096c 100644 --- a/source/funkin/data/animation/AnimationData.hx +++ b/source/funkin/data/animation/AnimationData.hx @@ -59,7 +59,10 @@ typedef UnnamedAnimationData = * The prefix for the frames of the animation as defined by the XML file. * This will may or may not differ from the `name` of the animation, * depending on how your animator organized their FLA or whatever. + * + * NOTE: For Sparrow animations, this is not optional, but for Packer animations it is. */ + @:optional var prefix:String; /** 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/notestyle/NoteStyleRegistry.hx b/source/funkin/data/notestyle/NoteStyleRegistry.hx index da45da5f2..4255a644b 100644 --- a/source/funkin/data/notestyle/NoteStyleRegistry.hx +++ b/source/funkin/data/notestyle/NoteStyleRegistry.hx @@ -15,8 +15,6 @@ class NoteStyleRegistry extends BaseRegistry public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x"; - public static final DEFAULT_NOTE_STYLE_ID:String = "funkin"; - public static final instance:NoteStyleRegistry = new NoteStyleRegistry(); public function new() @@ -26,7 +24,7 @@ class NoteStyleRegistry extends BaseRegistry public function fetchDefault():NoteStyle { - return fetchEntry(DEFAULT_NOTE_STYLE_ID); + return fetchEntry(Constants.DEFAULT_NOTE_STYLE); } /** diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 783f52a64..e79e1a3f4 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1,9 +1,13 @@ package funkin.data.song; -import flixel.util.typeLimit.OneOfTwo; import funkin.data.song.SongRegistry; import thx.semver.Version; +/** + * Data containing information about a song. + * It should contain all the data needed to display a song in the Freeplay menu, or to load the assets required to play its chart. + * Data which is only necessary in-game should be stored in the SongChartData. + */ @:nullSafety class SongMetadata { @@ -35,13 +39,11 @@ class SongMetadata */ public var playData:SongPlayData; - // @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) + @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) public var generatedBy:String; - // @:default(funkin.data.song.SongData.SongTimeFormat.MILLISECONDS) public var timeFormat:SongTimeFormat; - // @:default(funkin.data.song.SongData.SongTimeChange.DEFAULT_SONGTIMECHANGES) public var timeChanges:Array; /** @@ -64,7 +66,7 @@ class SongMetadata this.playData.difficulties = []; this.playData.characters = new SongCharacterData('bf', 'gf', 'dad'); this.playData.stage = 'mainStage'; - this.playData.noteSkin = 'funkin'; + this.playData.noteStyle = Constants.DEFAULT_NOTE_STYLE; this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; // Variation ID. this.variation = (variation == null) ? Constants.DEFAULT_VARIATION : variation; @@ -298,23 +300,27 @@ class SongPlayData /** * The note style used by this song. - * TODO: Rename to `noteStyle`? Renaming values is a breaking change to the metadata format. */ - public var noteSkin:String; + public var noteStyle:String; /** - * The difficulty rating for this song as displayed in Freeplay. - * TODO: Adding this is a non-breaking change to the metadata format. + * The difficulty ratings for this song as displayed in Freeplay. + * Key is a difficulty ID or `default`. */ - // public var rating:Int; + @:default(['default' => 1]) + public var ratings:Map; /** * The album ID for the album to display in Freeplay. - * TODO: Adding this is a non-breaking change to the metadata format. + * If `null`, display no album. */ - // public var album:String; + @:optional + public var album:Null; - public function new() {} + public function new() + { + ratings = new Map(); + } /** * Produces a string representation suitable for debugging. @@ -528,12 +534,22 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR public inline function getInt(key:String):Null { - return this.value == null ? null : cast Reflect.field(this.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 this.value == null ? null : cast Reflect.field(this.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 diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 984af18fa..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; @@ -163,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/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 889fca707..8e0f4577d 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -2,6 +2,7 @@ package funkin.data.song; import funkin.data.song.SongData; import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0; +import funkin.data.song.migrator.SongData_v2_1_0.SongMetadata_v2_1_0; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongMetadata; import funkin.play.song.ScriptedSong; @@ -18,9 +19,9 @@ class SongRegistry extends BaseRegistry * Handle breaking changes by incrementing this value * and adding migration to the `migrateStageData()` function. */ - public static final SONG_METADATA_VERSION:thx.semver.Version = "2.1.0"; + public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.0"; - public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.1.x"; + public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x"; public static final SONG_CHART_DATA_VERSION:thx.semver.Version = "2.0.0"; @@ -165,6 +166,10 @@ class SongRegistry extends BaseRegistry { return parseEntryMetadata(id, variation); } + else if (VersionUtil.validateVersion(version, "2.1.x")) + { + return parseEntryMetadata_v2_1_0(id, variation); + } else if (VersionUtil.validateVersion(version, "2.0.x")) { return parseEntryMetadata_v2_0_0(id, variation); @@ -182,6 +187,10 @@ class SongRegistry extends BaseRegistry { return parseEntryMetadataRaw(contents, fileName); } + else if (VersionUtil.validateVersion(version, "2.1.x")) + { + return parseEntryMetadataRaw_v2_1_0(contents, fileName); + } else if (VersionUtil.validateVersion(version, "2.0.x")) { return parseEntryMetadataRaw_v2_0_0(contents, fileName); @@ -192,12 +201,12 @@ class SongRegistry extends BaseRegistry } } - function parseEntryMetadata_v2_0_0(id:String, ?variation:String):Null + function parseEntryMetadata_v2_1_0(id:String, ?variation:String):Null { variation = variation == null ? Constants.DEFAULT_VARIATION : variation; - var parser = new json2object.JsonParser(); - switch (loadEntryMetadataFile(id)) + var parser = new json2object.JsonParser(); + switch (loadEntryMetadataFile(id, variation)) { case {fileName: fileName, contents: contents}: parser.fromJson(contents, fileName); @@ -209,6 +218,39 @@ class SongRegistry extends BaseRegistry printErrors(parser.errors, id); return null; } + return cleanMetadata(parser.value.migrate(), variation); + } + + function parseEntryMetadata_v2_0_0(id:String, ?variation:String):Null + { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + + var parser = new json2object.JsonParser(); + switch (loadEntryMetadataFile(id, variation)) + { + case {fileName: fileName, contents: contents}: + parser.fromJson(contents, fileName); + default: + return null; + } + if (parser.errors.length > 0) + { + printErrors(parser.errors, id); + return null; + } + return cleanMetadata(parser.value.migrate(), variation); + } + + function parseEntryMetadataRaw_v2_1_0(contents:String, ?fileName:String = 'raw'):Null + { + var parser = new json2object.JsonParser(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } return parser.value.migrate(); } diff --git a/source/funkin/data/song/importer/ChartManifestData.hx b/source/funkin/data/song/importer/ChartManifestData.hx new file mode 100644 index 000000000..0c7d2f0b0 --- /dev/null +++ b/source/funkin/data/song/importer/ChartManifestData.hx @@ -0,0 +1,84 @@ +package funkin.data.song.importer; + +/** + * A helper JSON blob found in `.fnfc` files. + */ +class ChartManifestData +{ + /** + * The current semantic version of the chart manifest data. + */ + public static final CHART_MANIFEST_DATA_VERSION:thx.semver.Version = "1.0.0"; + + @:default(funkin.data.song.importer.ChartManifestData.CHART_MANIFEST_DATA_VERSION) + @:jcustomparse(funkin.data.DataParse.semverVersion) + @:jcustomwrite(funkin.data.DataWrite.semverVersion) + public var version:thx.semver.Version; + + /** + * The internal song ID for this chart. + * The metadata and chart data file names are derived from this. + */ + public var songId:String; + + public function new(songId:String) + { + this.version = CHART_MANIFEST_DATA_VERSION; + this.songId = songId; + } + + public function getMetadataFileName(?variation:String):String + { + if (variation == null || variation == '') variation = Constants.DEFAULT_VARIATION; + + return '$songId-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.${Constants.EXT_DATA}'; + } + + public function getChartDataFileName(?variation:String):String + { + if (variation == null || variation == '') variation = Constants.DEFAULT_VARIATION; + + return '$songId-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.${Constants.EXT_DATA}'; + } + + public function getInstFileName(?variation:String):String + { + if (variation == null || variation == '') variation = Constants.DEFAULT_VARIATION; + + return 'Inst${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.${Constants.EXT_SOUND}'; + } + + public function getVocalsFileName(charId:String, ?variation:String):String + { + if (variation == null || variation == '') variation = Constants.DEFAULT_VARIATION; + + return 'Voices-$charId${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.${Constants.EXT_SOUND}'; + } + + /** + * Serialize this ChartManifestData into a JSON string. + * @return The JSON string. + */ + public function serialize(pretty:Bool = true):String + { + var writer = new json2object.JsonWriter(); + return writer.write(this, pretty ? ' ' : null); + } + + public static function deserialize(contents:String):Null + { + var parser = new json2object.JsonParser(); + parser.fromJson(contents, 'manifest.json'); + + if (parser.errors.length > 0) + { + trace('[ChartManifest] Failed to parse chart file manifest'); + + for (error in parser.errors) + DataError.printError(error); + + return null; + } + return parser.value; + } +} diff --git a/source/funkin/data/song/migrator/SongDataMigrator.hx b/source/funkin/data/song/migrator/SongDataMigrator.hx index b5e08c832..2603ab1f8 100644 --- a/source/funkin/data/song/migrator/SongDataMigrator.hx +++ b/source/funkin/data/song/migrator/SongDataMigrator.hx @@ -7,6 +7,8 @@ import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0; import funkin.data.song.migrator.SongData_v2_0_0.SongPlayData_v2_0_0; import funkin.data.song.migrator.SongData_v2_0_0.SongPlayableChar_v2_0_0; +using funkin.data.song.migrator.SongDataMigrator; // Does this even work lol? + /** * This class contains functions to migrate older data formats to the current one. * @@ -15,6 +17,48 @@ import funkin.data.song.migrator.SongData_v2_0_0.SongPlayableChar_v2_0_0; */ class SongDataMigrator { + public static overload extern inline function migrate(input:SongData_v2_1_0.SongMetadata_v2_1_0):SongMetadata + { + return migrate_SongMetadata_v2_1_0(input); + } + + public static function migrate_SongMetadata_v2_1_0(input:SongData_v2_1_0.SongMetadata_v2_1_0):SongMetadata + { + var result:SongMetadata = new SongMetadata(input.songName, input.artist, input.variation); + result.version = SongRegistry.SONG_METADATA_VERSION; + result.timeFormat = input.timeFormat; + result.divisions = input.divisions; + result.timeChanges = input.timeChanges; + result.looped = input.looped; + result.playData = input.playData.migrate(); + result.generatedBy = input.generatedBy; + + return result; + } + + public static overload extern inline function migrate(input:SongData_v2_1_0.SongPlayData_v2_1_0):SongPlayData + { + return migrate_SongPlayData_v2_1_0(input); + } + + public static function migrate_SongPlayData_v2_1_0(input:SongData_v2_1_0.SongPlayData_v2_1_0):SongPlayData + { + var result:SongPlayData = new SongPlayData(); + result.songVariations = input.songVariations; + result.difficulties = input.difficulties; + result.stage = input.stage; + result.characters = input.characters; + + // Renamed + result.noteStyle = input.noteSkin; + + // Added + result.ratings = ['default' => 1]; + result.album = null; + + return result; + } + public static overload extern inline function migrate(input:SongData_v2_0_0.SongMetadata_v2_0_0):SongMetadata { return migrate_SongMetadata_v2_0_0(input); @@ -23,12 +67,12 @@ class SongDataMigrator public static function migrate_SongMetadata_v2_0_0(input:SongData_v2_0_0.SongMetadata_v2_0_0):SongMetadata { var result:SongMetadata = new SongMetadata(input.songName, input.artist, input.variation); - result.version = input.version; + result.version = SongRegistry.SONG_METADATA_VERSION; result.timeFormat = input.timeFormat; result.divisions = input.divisions; result.timeChanges = input.timeChanges; result.looped = input.looped; - result.playData = migrate_SongPlayData_v2_0_0(input.playData); + result.playData = input.playData.migrate(); result.generatedBy = input.generatedBy; return result; @@ -45,7 +89,13 @@ class SongDataMigrator result.songVariations = input.songVariations; result.difficulties = input.difficulties; result.stage = input.stage; - result.noteSkin = input.noteSkin; + + // Added + result.ratings = ['default' => 1]; + result.album = null; + + // Renamed + result.noteStyle = input.noteSkin; // Fetch the first playable character and migrate it. var firstCharKey:Null = input.playableChars.size() == 0 ? null : input.playableChars.keys().array()[0]; diff --git a/source/funkin/data/song/migrator/SongData_v2_0_0.hx b/source/funkin/data/song/migrator/SongData_v2_0_0.hx index eeeed2f2b..62e3faf4c 100644 --- a/source/funkin/data/song/migrator/SongData_v2_0_0.hx +++ b/source/funkin/data/song/migrator/SongData_v2_0_0.hx @@ -42,6 +42,7 @@ class SongMetadata_v2_0_0 @:default(false) public var looped:Bool; + @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) public var generatedBy:String; public var timeFormat:SongData.SongTimeFormat; @@ -70,6 +71,13 @@ class SongPlayData_v2_0_0 */ public var playableChars:Map; + /** + * In metadata version `v2.2.0`, this was renamed to `noteStyle`. + */ + public var noteSkin:String; + + // In 2.2.0, the ratings value was added. + // In 2.2.0, the album value was added. // ========== // UNMODIFIED VALUES // ========== @@ -77,7 +85,6 @@ class SongPlayData_v2_0_0 public var difficulties:Array; public var stage:String; - public var noteSkin:String; public function new() {} diff --git a/source/funkin/data/song/migrator/SongData_v2_1_0.hx b/source/funkin/data/song/migrator/SongData_v2_1_0.hx new file mode 100644 index 000000000..57e4102d9 --- /dev/null +++ b/source/funkin/data/song/migrator/SongData_v2_1_0.hx @@ -0,0 +1,108 @@ +package funkin.data.song.migrator; + +import funkin.data.song.SongData; +import funkin.data.song.SongRegistry; +import thx.semver.Version; + +@:nullSafety +class SongMetadata_v2_1_0 +{ + // ========== + // MODIFIED VALUES + // =========== + + /** + * In metadata `v2.2.0`, `SongPlayData` was refactored. + */ + public var playData:SongPlayData_v2_1_0; + + // ========== + // UNMODIFIED VALUES + // ========== + @:jcustomparse(funkin.data.DataParse.semverVersion) + @:jcustomwrite(funkin.data.DataWrite.semverVersion) + public var version:Version; + + @:default("Unknown") + public var songName:String; + + @:default("Unknown") + public var artist:String; + + @:optional + @:default(96) + public var divisions:Null; // Optional field + + @:optional + @:default(false) + public var looped:Bool; + + @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) + public var generatedBy:String; + + public var timeFormat:SongData.SongTimeFormat; + + public var timeChanges:Array; + + /** + * Defaults to `Constants.DEFAULT_VARIATION`. Populated later. + */ + @:jignored + public var variation:String; + + public function new(songName:String, artist:String, ?variation:String) + { + this.version = SongRegistry.SONG_METADATA_VERSION; + this.songName = songName; + this.artist = artist; + this.timeFormat = 'ms'; + this.divisions = null; + this.timeChanges = [new SongTimeChange(0, 100)]; + this.looped = false; + this.playData = new SongPlayData_v2_1_0(); + this.playData.songVariations = []; + this.playData.difficulties = []; + this.playData.characters = new SongCharacterData('bf', 'gf', 'dad'); + this.playData.stage = 'mainStage'; + this.playData.noteSkin = 'funkin'; + this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; + // Variation ID. + this.variation = (variation == null) ? Constants.DEFAULT_VARIATION : variation; + } + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongMetadata[LEGACY:v2.1.0](${this.songName} by ${this.artist}, variation ${this.variation})'; + } +} + +class SongPlayData_v2_1_0 +{ + /** + * In `v2.2.0`, this value was renamed to `noteStyle`. + */ + public var noteSkin:String; + + // In 2.2.0, the ratings value was added. + // In 2.2.0, the album value was added. + // ========== + // UNMODIFIED VALUES + // ========== + public var songVariations:Array; + public var difficulties:Array; + public var characters:SongData.SongCharacterData; + public var stage:String; + + public function new() {} + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongPlayData[LEGACY:v2.1.0](${this.songVariations}, ${this.difficulties})'; + } +} 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 8c7124da0..5ca6b03db 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -12,6 +12,7 @@ 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; 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/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 856941e88..45a1373a9 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) @@ -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) @@ -1471,7 +1477,7 @@ class PlayState extends MusicBeatSubState { case 'school': 'pixel'; case 'schoolEvil': 'pixel'; - default: 'funkin'; + default: Constants.DEFAULT_NOTE_STYLE; } var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); @@ -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) @@ -2392,8 +2400,8 @@ class PlayState extends MusicBeatSubState #if sys // spitter for ravy, teehee!! - - var output = SerializerUtil.toJSON(inputSpitter); + var writer = new json2object.JsonWriter>(); + var output = writer.write(inputSpitter, ' '); sys.io.File.saveContent("./scores.json", output); #end 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/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/ConversationDebugState.hx b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx index 70ac011a2..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. 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 9562ef2ca..b48eda224 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -96,11 +96,13 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry + function fetchVariationMetadata(id:String, vari:String):Null { - var result:Array = []; - for (vari in variations) - { - var version:Null = SongRegistry.instance.fetchEntryMetadataVersion(id, vari); - if (version == null) continue; - var meta:Null = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version); - if (meta != null) result.push(meta); - } - return result; + var version:Null = SongRegistry.instance.fetchEntryMetadataVersion(id, vari); + if (version == null) return null; + var meta:Null = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version); + return meta; } } diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 187b5ec32..1bc0632f9 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -85,7 +85,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass return globalOffsets = value; } - @:allow(funkin.ui.animDebugShit.DebugBoundingState) + @:allow(funkin.ui.debug.anim.DebugBoundingState) var animOffsets(default, set):Array = [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 d7ba38e2a..c8cb8ce66 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -177,13 +177,13 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass continue; } - if (Std.isOfType(dataProp.scale, Array)) + switch (dataProp.scale) { - propSprite.scale.set(dataProp.scale[0], dataProp.scale[1]); - } - else - { - propSprite.scale.set(dataProp.scale); + case Left(value): + propSprite.scale.set(value); + + case Right(values): + propSprite.scale.set(values[0], values[1]); } propSprite.updateHitbox(); @@ -195,8 +195,15 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass // If pixel, disable antialiasing. propSprite.antialiasing = !dataProp.isPixel; - propSprite.scrollFactor.x = dataProp.scroll[0]; - propSprite.scrollFactor.y = dataProp.scroll[1]; + switch (dataProp.scroll) + { + case Left(value): + propSprite.scrollFactor.x = value; + propSprite.scrollFactor.y = value; + case Right(values): + propSprite.scrollFactor.x = values[0]; + propSprite.scrollFactor.y = values[1]; + } propSprite.zIndex = dataProp.zIndex; diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index c14e05aaf..29ca03b84 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -1,7 +1,6 @@ package funkin.play.stage; import funkin.data.animation.AnimationData; -import flixel.util.typeLimit.OneOfTwo; import funkin.play.stage.ScriptedStage; import funkin.play.stage.Stage; import funkin.util.VersionUtil; @@ -157,15 +156,26 @@ class StageDataParser return rawJson; } - static function migrateStageData(rawJson:String, stageId:String) + static function migrateStageData(rawJson:String, stageId:String):Null { // If you update the stage data format in a breaking way, // handle migration here by checking the `version` value. try { - var stageData:StageData = cast Json.parse(rawJson); - return stageData; + var parser = new json2object.JsonParser(); + parser.fromJson(rawJson, '$stageId.json'); + + if (parser.errors.length > 0) + { + trace('[STAGE] Failed to parse stage data'); + + for (error in parser.errors) + funkin.data.DataError.printError(error); + + return null; + } + return parser.value; } catch (e) { @@ -269,24 +279,29 @@ class StageDataParser inputProp.danceEvery = DEFAULT_DANCEEVERY; } - if (inputProp.scale == null) - { - inputProp.scale = DEFAULT_SCALE; - } - if (inputProp.animType == null) { inputProp.animType = DEFAULT_ANIMTYPE; } - if (Std.isOfType(inputProp.scale, Float)) + switch (inputProp.scale) { - inputProp.scale = [inputProp.scale, inputProp.scale]; + case null: + inputProp.scale = Right([DEFAULT_SCALE, DEFAULT_SCALE]); + case Left(value): + inputProp.scale = Right([value, value]); + case Right(_): + // Do nothing } - if (inputProp.scroll == null) + switch (inputProp.scroll) { - inputProp.scroll = DEFAULT_SCROLL; + case null: + inputProp.scroll = Right(DEFAULT_SCROLL); + case Left(value): + inputProp.scroll = Right([value, value]); + case Right(_): + // Do nothing } if (inputProp.alpha == null) @@ -294,11 +309,6 @@ class StageDataParser inputProp.alpha = DEFAULT_ALPHA; } - if (Std.isOfType(inputProp.scroll, Float)) - { - inputProp.scroll = [inputProp.scroll, inputProp.scroll]; - } - if (inputProp.animations == null) { inputProp.animations = []; @@ -392,23 +402,39 @@ class StageDataParser } } -typedef StageData = +class StageData { /** * The sematic version number of the stage data JSON format. * Supports fancy comparisons like NPM does it's neat. */ - var version:String; + public var version:String; - var name:String; - var cameraZoom:Null; - var props:Array; - var characters: - { - bf:StageDataCharacter, - dad:StageDataCharacter, - gf:StageDataCharacter, - }; + public var name:String; + public var cameraZoom:Null; + public var props:Array; + public var characters:StageDataCharacters; + + public function new() + { + this.version = StageDataParser.STAGE_DATA_VERSION; + } + + /** + * Convert this StageData into a JSON string. + */ + public function serialize(pretty:Bool = true):String + { + var writer = new json2object.JsonWriter(); + return writer.write(this, pretty ? ' ' : null); + } +} + +typedef StageDataCharacters = +{ + var bf:StageDataCharacter; + var dad:StageDataCharacter; + var gf:StageDataCharacter; }; typedef StageDataProp = @@ -417,6 +443,7 @@ typedef StageDataProp = * The name of the prop for later lookup by scripts. * Optional; if unspecified, the prop can't be referenced by scripts. */ + @:optional var name:String; /** @@ -435,27 +462,35 @@ typedef StageDataProp = * This is just like CSS, it isn't hard. * @default 0 */ - var zIndex:Null; + @:optional + @:default(0) + var zIndex:Int; /** * If set to true, anti-aliasing will be forcibly disabled on the sprite. * This prevents blurry images on pixel-art levels. * @default false */ - var isPixel:Null; + @:optional + @:default(false) + var isPixel:Bool; /** * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. * Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory. - * @default 1 */ - var scale:OneOfTwo>; + @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) + @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) + @:optional + var scale:haxe.ds.Either>; /** * The alpha of the prop, as a float. * @default 1.0 */ - var alpha:Null; + @:optional + @:default(1.0) + var alpha:Float; /** * If not zero, this prop will play an animation every X beats of the song. @@ -464,7 +499,9 @@ typedef StageDataProp = * * @default 0 */ - var danceEvery:Null; + @:default(0) + @:optional + var danceEvery:Int; /** * How much the prop scrolls relative to the camera. Used to create a parallax effect. @@ -474,25 +511,32 @@ typedef StageDataProp = * [0, 0] means the prop is not moved. * @default [0, 0] */ - var scroll:OneOfTwo>; + @:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats) + @:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats) + @:optional + var scroll:haxe.ds.Either>; /** * An optional array of animations which the prop can play. * @default Prop has no animations. */ + @:optional var animations:Array; /** * If animations are used, this is the name of the animation to play first. * @default Don't play an animation. */ - var startingAnimation:String; + @:optional + var startingAnimation:Null; /** * The animation type to use. * Options: "sparrow", "packer" * @default "sparrow" */ + @:default("sparrow") + @:optional var animType:String; }; @@ -503,16 +547,22 @@ typedef StageDataCharacter = * Again, just like CSS. * @default 0 */ - ?zIndex:Int, + @:optional + @:default(0) + var zIndex:Int; /** * The position to render the character at. */ - position:Array, + @:optional + @:default([0, 0]) + var position:Array; /** * The camera offsets to apply when focusing on the character on this stage. * @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF */ - cameraOffsets:Array, + @:optional + @:default([0, 0]) + var cameraOffsets:Array; }; diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 54b66605c..674c96fb6 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -3,7 +3,7 @@ package funkin.save; import flixel.util.FlxSave; import funkin.save.migrator.SaveDataMigrator; import thx.semver.Version; -import funkin.Controls.Device; +import funkin.input.Controls.Device; import funkin.save.migrator.RawSaveData_v1_0_0; @:nullSafety diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index e7b7c7583..d5b23cfd9 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -13,8 +13,7 @@ class SaveDataMigrator */ public static function migrate(inputData:Dynamic):Save { - // This deserializes directly into a `Version` object, not a `String`. - var version:Null = inputData?.version ?? null; + var version:Null = VersionUtil.parseVersion(inputData?.version ?? null); if (version == null) { @@ -24,7 +23,7 @@ class SaveDataMigrator } else { - if (VersionUtil.validateVersionStr(version, Save.SAVE_DATA_VERSION_RULE)) + if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE)) { // Simply cast the structured data. var save:Save = inputData; 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 88% rename from source/funkin/MusicBeatState.hx rename to source/funkin/ui/MusicBeatState.hx index 9861c48c7..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)); } diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx similarity index 98% rename from source/funkin/MusicBeatSubState.hx rename to source/funkin/ui/MusicBeatSubState.hx index 53fe19bdd..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. 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/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 366e446e5..382bab592 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -16,6 +16,7 @@ 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; @@ -31,7 +32,7 @@ import funkin.input.TurboKeyHandler; import funkin.modding.events.ScriptEvent; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; -import funkin.play.HealthIcon; +import funkin.play.components.HealthIcon; import funkin.play.notes.NoteSprite; import funkin.play.PlayState; import funkin.play.song.Song; @@ -90,6 +91,7 @@ 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.display.BitmapData; @@ -547,22 +549,14 @@ class ChartEditorState extends HaxeUIState // HaxeUI /** - * Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI. - * If so, ignore mouse events underneath as well as certain key events. + * 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 isCursorOverHaxeUI(get, never):Bool; + var isHaxeUIFocused(get, never):Bool; - function get_isCursorOverHaxeUI():Bool + function get_isHaxeUIFocused():Bool { - return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY); - } - - 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); + return FocusManager.instance.focus != null; } /** @@ -1046,17 +1040,17 @@ class ChartEditorState extends HaxeUIState function get_currentSongNoteStyle():String { - if (currentSongMetadata.playData.noteSkin == null) + if (currentSongMetadata.playData.noteStyle == null) { // Initialize to the default value if not set. - currentSongMetadata.playData.noteSkin = 'funkin'; + currentSongMetadata.playData.noteStyle = Constants.DEFAULT_NOTE_STYLE; } - return currentSongMetadata.playData.noteSkin; + return currentSongMetadata.playData.noteStyle; } function set_currentSongNoteStyle(value:String):String { - return currentSongMetadata.playData.noteSkin = value; + return currentSongMetadata.playData.noteStyle = value; } var currentSongStage(get, set):String; @@ -1326,10 +1320,22 @@ class ChartEditorState extends HaxeUIState */ // ============================== - public function new() + /** + * The params which were passed in when the Chart Editor was initialized. + */ + var params:Null; + + /** + * The current file path which the chart editor is working with. + */ + public var currentWorkingFilePath:Null; + + public function new(?params:ChartEditorParams) { // Load the HaxeUI XML file. super(CHART_EDITOR_LAYOUT); + + this.params = params; } public override function dispatchEvent(event:ScriptEvent):Void @@ -1410,7 +1416,33 @@ class ChartEditorState extends HaxeUIState refresh(); - this.openWelcomeDialog(false); + if (params != null && params.fnfcTargetPath != null) + { + // Chart editor was opened from the command line. Open the FNFC file now! + if (ChartEditorImportExportHandler.loadFromFNFCPath(this, params.fnfcTargetPath)) + { + // Don't open the welcome dialog! + + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart (${params.fnfcTargetPath})', + type: NotificationType.Success, + expiryMs: Constants.NOTIFICATION_DISMISS_TIME + }); + #end + } + else + { + // Song failed to load, open the Welcome dialog so we aren't in a broken state. + ChartEditorDialogHandler.openWelcomeDialog(this, false); + } + } + else + { + ChartEditorDialogHandler.openWelcomeDialog(this, false); + } } override function destroy():Void @@ -1772,11 +1804,15 @@ class ChartEditorState extends HaxeUIState noteSnapQuantIndex++; if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0; }); + addUIRightClickListener('playbarNoteSnap', function(_) { + noteSnapQuantIndex--; + if (noteSnapQuantIndex < 0) noteSnapQuantIndex = SNAP_QUANTS.length - 1; + }); // Add functionality to the menu items. addUIClickListener('menubarItemNewChart', _ -> this.openWelcomeDialog(true)); - addUIClickListener('menubarItemOpenChart', _ -> this.openBrowseWizard(true)); + addUIClickListener('menubarItemOpenChart', _ -> this.openBrowseFNFC(true)); addUIClickListener('menubarItemSaveChartAs', _ -> this.exportAllSongData()); addUIClickListener('menubarItemLoadInst', _ -> this.openUploadInstDialog(true)); addUIClickListener('menubarItemImportChart', _ -> this.openImportChartDialog('legacy', true)); @@ -1918,7 +1954,7 @@ class ChartEditorState extends HaxeUIState addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) { var volume:Float = (event?.value ?? 0) / 100.0; if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume; - vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%'; + vocalsVolumeLabel.text = 'Voices - ${Std.int(event.value)}%'; }); } @@ -2499,8 +2535,8 @@ class ChartEditorState extends HaxeUIState */ function handleScrollKeybinds():Void { - // Don't scroll when the cursor is over the UI, unless a playbar button (the << >> ones) is pressed. - if (isCursorOverHaxeUI && playbarButtonPressed == null) return; + // Don't scroll when the user is interacting with the UI, unless a playbar button (the << >> ones) is pressed. + if (isHaxeUIFocused && playbarButtonPressed == null) return; var scrollAmount:Float = 0; // Amount to scroll the grid. var playheadAmount:Float = 0; // Amount to scroll the playhead relative to the grid. @@ -2699,7 +2735,7 @@ class ChartEditorState extends HaxeUIState if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp")); // Note: If a menu is open in HaxeUI, don't handle cursor behavior. - var shouldHandleCursor:Bool = !isCursorOverHaxeUI + var shouldHandleCursor:Bool = !isHaxeUIFocused || (selectionBoxStartPos != null) || (dragTargetNote != null || dragTargetEvent != null); var eventColumn:Int = (STRUMLINE_SIZE * 2 + 1) - 1; @@ -3259,14 +3295,14 @@ class ChartEditorState extends HaxeUIState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData.clone()); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind.clone()); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); @@ -3786,7 +3822,7 @@ class ChartEditorState extends HaxeUIState // CTRL + O = Open Chart if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.O) { - this.openBrowseWizard(true); + this.openBrowseFNFC(true); } // CTRL + SHIFT + S = Save As @@ -3802,6 +3838,16 @@ class ChartEditorState extends HaxeUIState } } + @:nullSafety(Off) + function quitChartEditor():Void + { + autoSave(); + stopWelcomeMusic(); + // TODO: PR Flixel to make onComplete nullable. + if (audioInstTrack != null) audioInstTrack.onComplete = null; + FlxG.switchState(new MainMenuState()); + } + /** * Handle keybinds for edit menu items. */ @@ -3921,7 +3967,7 @@ class ChartEditorState extends HaxeUIState */ function handleTestKeybinds():Void { - if (!isHaxeUIDialogOpen && !isCursorOverHaxeUI && FlxG.keys.justPressed.ENTER) + if (!isHaxeUIDialogOpen && !isHaxeUIFocused && FlxG.keys.justPressed.ENTER) { var minimal = FlxG.keys.pressed.SHIFT; this.hideAllToolboxes(); @@ -3938,8 +3984,10 @@ class ChartEditorState extends HaxeUIState if (FlxG.keys.justPressed.F1) this.openUserGuideDialog(); } - function handleQuickWatch():Void + override function handleQuickWatch():Void { + super.handleQuickWatch(); + FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels); @@ -4093,16 +4141,6 @@ class ChartEditorState extends HaxeUIState #end } - /** - * Called when the user presses the Quit button. - */ - function quitChartEditor():Void - { - autoSave(); - stopWelcomeMusic(); - FlxG.switchState(new MainMenuState()); - } - /** * Called when the window is closed while we are in the chart editor. * @param exitCode The exit code of the window. @@ -4576,7 +4614,7 @@ class ChartEditorState extends HaxeUIState if (inputStage != null) inputStage.value = currentSongMetadata.playData.stage; var inputNoteStyle:Null = toolbox.findComponent('inputNoteStyle', DropDown); - if (inputNoteStyle != null) inputNoteStyle.value = currentSongMetadata.playData.noteSkin; + if (inputNoteStyle != null) inputNoteStyle.value = currentSongMetadata.playData.noteStyle; var inputBPM:Null = toolbox.findComponent('inputBPM', NumberStepper); if (inputBPM != null) inputBPM.value = currentSongMetadata.timeChanges[0].bpm; @@ -4720,6 +4758,14 @@ enum ChartEditorLiveInputStyle WASD; } +typedef ChartEditorParams = +{ + /** + * If non-null, load this song immediately instead of the welcome screen. + */ + var ?fnfcTargetPath:String; +}; + /** * Available themes for the chart editor state. */ diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 2bd719df2..4c9d91407 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -134,21 +134,25 @@ class ChartEditorEventSprite extends FlxSprite function set_eventData(value:Null):Null { - this.eventData = value; - - if (this.eventData == null) + if (value == null) { + this.eventData = null; // Disown parent. MAKE SURE TO REVIVE BEFORE REUSING this.kill(); + this.visible = false; + return null; + } + else + { + this.visible = true; + // Only play the animation if the event type has changed. + // if (this.eventData == null || this.eventData.event != value.event) + playAnimation(value.event); + this.eventData = value; + // Update the position to match the note data. + updateEventPosition(); return this.eventData; } - - this.visible = true; - playAnimation(this.eventData.event); - // Update the position to match the note data. - updateEventPosition(); - - return this.eventData; } public function updateEventPosition(?origin:FlxObject) diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 072004a43..5ea125eb4 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -258,7 +258,7 @@ class ChartEditorAudioHandler { var data:Null = state.audioVocalTrackData.get(key); if (data == null) continue; - zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-${key}.ogg', data)); + zipEntries.push(FileUtil.makeZIPEntryFromBytes('Voices-${key}.ogg', data)); } return zipEntries; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index 529707156..f5cbccff6 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -15,8 +15,6 @@ import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.Song; import funkin.play.stage.StageData; import funkin.ui.debug.charting.util.ChartEditorDropdowns; -import funkin.ui.haxeui.components.FunkinDropDown; -import funkin.ui.haxeui.components.FunkinLink; import funkin.util.Constants; import funkin.util.FileUtil; import funkin.util.SerializerUtil; @@ -54,12 +52,13 @@ class ChartEditorDialogHandler // Paths to HaxeUI layout files for each dialog. static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about'); static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome'); + static final CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-chart'); static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst'); static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); - static final CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart'); - static final CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-entry'); + static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts'); + static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts-entry'); static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart'); static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide'); static final CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-variation'); @@ -86,6 +85,11 @@ class ChartEditorDialogHandler var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable); if (dialog == null) throw 'Could not locate Welcome dialog'; + dialog.onDialogClosed = function(_event) { + // Called when the Welcome dialog is closed while it is closable. + state.stopWelcomeMusic(); + } + // Create New Song "Easy/Normal/Hard" var linkCreateBasic:Null = dialog.findComponent('splashCreateFromSongBasic', Link); if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog'; @@ -132,7 +136,7 @@ class ChartEditorDialogHandler state.stopWelcomeMusic(); // Open the "Open Chart" dialog - openBrowseWizard(state, false); + openBrowseFNFC(state, false); } var splashTemplateContainer:Null = dialog.findComponent('splashTemplateContainer', VBox); @@ -154,7 +158,7 @@ class ChartEditorDialogHandler continue; } - var linkTemplateSong:Link = new FunkinLink(); + var linkTemplateSong:Link = new Link(); linkTemplateSong.text = songName; linkTemplateSong.onClick = function(_event) { dialog.hideDialog(DialogButton.CANCEL); @@ -171,6 +175,126 @@ class ChartEditorDialogHandler return dialog; } + public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null + { + var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT, true, closable); + if (dialog == null) throw 'Could not locate Upload Chart dialog'; + + dialog.onDialogClosed = function(_event) { + if (_event.button == DialogButton.APPLY) + { + // Simply let the dialog close. + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + + var buttonCancel:Null