From 2a815e91dbb5a047a6180e9eb15e5637214a7ee8 Mon Sep 17 00:00:00 2001 From: Lasercar <64717068+Lasercar@users.noreply.github.com> Date: Tue, 17 Jun 2025 10:24:54 +1000 Subject: [PATCH 01/66] Charselect remember character (#4072) --- .../ui/charSelect/CharSelectSubState.hx | 78 +++++++++++++++---- source/funkin/ui/charSelect/Nametag.hx | 7 +- source/funkin/ui/freeplay/FreeplayState.hx | 18 +++-- source/funkin/ui/mainmenu/MainMenuState.hx | 5 +- source/funkin/ui/options/PreferencesMenu.hx | 2 +- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx index 87f08960c8e..c0892fb3d71 100644 --- a/source/funkin/ui/charSelect/CharSelectSubState.hx +++ b/source/funkin/ui/charSelect/CharSelectSubState.hx @@ -62,7 +62,8 @@ class CharSelectSubState extends MusicBeatSubState var chooseDipshit:FlxSprite; var dipshitBlur:FlxSprite; var transitionGradient:FlxSprite; - var curChar(default, set):String = "pico"; + var curChar(default, set):String = Constants.DEFAULT_CHARACTER; + var rememberedChar:String; var nametag:Nametag; var camFollow:FlxObject; var autoFollow:Bool = false; @@ -87,9 +88,10 @@ class CharSelectSubState extends MusicBeatSubState var bopInfo:FramesJSFLInfo; var blackScreen:FunkinSprite; - public function new() + public function new(?params:CharSelectSubStateParams) { super(); + rememberedChar = params?.character; loadAvailableCharacters(); } @@ -167,18 +169,39 @@ class CharSelectSubState extends MusicBeatSubState charLightGF.loadGraphic(Paths.image('charSelect/charLight')); add(charLightGF); - gfChill = new CharSelectGF(); - gfChill.switchGF("bf"); - add(gfChill); - - playerChillOut = new CharSelectPlayer(0, 0); - playerChillOut.switchChar("bf"); - playerChillOut.visible = false; - add(playerChillOut); + function setupPlayerChill(character:String) + { + gfChill = new CharSelectGF(); + gfChill.switchGF(character); + add(gfChill); + + playerChillOut = new CharSelectPlayer(0, 0); + playerChillOut.switchChar(character); + playerChillOut.visible = false; + add(playerChillOut); + + playerChill = new CharSelectPlayer(0, 0); + playerChill.switchChar(character); + add(playerChill); + } - playerChill = new CharSelectPlayer(0, 0); - playerChill.switchChar("bf"); - add(playerChill); + // I think I can do the character preselect thing here? This better work + // Edit: [UH-OH!] yes! It does! + if (rememberedChar != null && rememberedChar != Constants.DEFAULT_CHARACTER) + { + setupPlayerChill(rememberedChar); + for (pos => charId in availableChars) + { + if (charId == rememberedChar) + { + setCursorPosition(pos); + break; + } + } + @:bypassAccessor curChar = rememberedChar; + } + else + setupPlayerChill(Constants.DEFAULT_CHARACTER); var speakers:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas("charSelect/charSelectSpeakers")); speakers.anim.play(""); @@ -224,7 +247,7 @@ class CharSelectSubState extends MusicBeatSubState dipshitBacking.scrollFactor.set(); dipshitBlur.scrollFactor.set(); - nametag = new Nametag(); + nametag = new Nametag(curChar); add(nametag); nametag.scrollFactor.set(); @@ -1063,6 +1086,25 @@ class CharSelectSubState extends MusicBeatSubState return gridPosition; } + // Moved this code into a function because is now used twice + function setCursorPosition(index:Int) + { + var copy = 3; + var yThing = -1; + + while ((index + 1) > copy) + { + yThing++; + copy += 3; + } + + var xThing = (copy - index - 2) * -1; + + // Look, I'd write better code but I had better aneurysms, my bad - Cheems + cursorY = yThing; + cursorX = xThing; + } + function set_curChar(value:String):String { if (curChar == value) return value; @@ -1113,3 +1155,11 @@ class CharSelectSubState extends MusicBeatSubState return value; } } + +/** + * Parameters used to initialize the CharSelectSubState. + */ +typedef CharSelectSubStateParams = +{ + ?character:String, // ?fromFreeplaySelect:Bool, +}; diff --git a/source/funkin/ui/charSelect/Nametag.hx b/source/funkin/ui/charSelect/Nametag.hx index 5bdb0e7e837..3bc51b2944f 100644 --- a/source/funkin/ui/charSelect/Nametag.hx +++ b/source/funkin/ui/charSelect/Nametag.hx @@ -10,14 +10,17 @@ class Nametag extends FlxSprite var midpointY(default, set):Float = 100; var mosaicShader:MosaicEffect; - public function new(?x:Float = 0, ?y:Float = 0) + public function new(?x:Float = 0, ?y:Float = 0, character:String) { super(x, y); mosaicShader = new MosaicEffect(); shader = mosaicShader; - switchChar("bf"); + // So that's why there was that cursed sight (originally defaulted to bf) + if (character != null) switchChar(character); + else + switchChar(Constants.DEFAULT_CHARACTER); } public function updatePosition():Void diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 894867188e7..1cd8b86fed1 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -191,21 +191,26 @@ class FreeplayState extends MusicBeatSubState public function new(?params:FreeplayStateParams, ?stickers:StickerSubState) { - currentCharacterId = params?.character ?? rememberedCharacterId; - styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacterId); - var fetchPlayableCharacter = function():PlayableCharacter { var targetCharId = params?.character ?? rememberedCharacterId; var result = PlayerRegistry.instance.fetchEntry(targetCharId); - if (result == null) throw 'No valid playable character with id ${targetCharId}'; + if (result == null) + { + trace('No valid playable character with id ${targetCharId}'); + result = PlayerRegistry.instance.fetchEntry(Constants.DEFAULT_CHARACTER); + if (result == null) throw 'WTH your default character is null?????'; + } return result; }; currentCharacter = fetchPlayableCharacter(); + currentCharacterId = currentCharacter.getFreeplayStyleID(); + currentVariation = rememberedVariation; currentDifficulty = rememberedDifficulty; - styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacter.getFreeplayStyleID()); + styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacterId); rememberedCharacterId = currentCharacter?.id ?? Constants.DEFAULT_CHARACTER; + fromCharSelect = params?.fromCharSelect ?? false; fromResultsParams = params?.fromResults; prepForNewRank = fromResultsParams?.playRankAnim ?? false; @@ -1179,7 +1184,8 @@ class FreeplayState extends MusicBeatSubState fadeShader.fade(1.0, 0.0, 0.8, {ease: FlxEase.quadIn}); FlxG.sound.music?.fadeOut(0.9, 0); new FlxTimer().start(0.9, _ -> { - FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState()); + FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState({character: currentCharacterId} // Passing the currrent Freeplay character to the CharSelect so we can start it with that character selected + )); }); for (grpSpr in exitMoversCharSel.keys()) { diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index ae8260ee99e..84c5c854fc3 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -104,11 +104,12 @@ class MainMenuState extends MusicBeatState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; + var rememberedFreeplayCharacter = FreeplayState.rememberedCharacterId; #if FEATURE_DEBUG_FUNCTIONS // Debug function: Hold SHIFT when selecting Freeplay to swap character without the char select menu - var targetCharacter:Null = (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : null; + var targetCharacter:Null = (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : rememberedFreeplayCharacter; #else - var targetCharacter:Null = null; + var targetCharacter:Null = rememberedFreeplayCharacter; #end openSubState(new FreeplayState( diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx index 240eade8b9b..156e0d3d0b8 100644 --- a/source/funkin/ui/options/PreferencesMenu.hx +++ b/source/funkin/ui/options/PreferencesMenu.hx @@ -116,7 +116,7 @@ class PreferencesMenu extends Page createPrefItemCheckbox('Pause on Unfocus', 'If enabled, game automatically pauses when it loses focus.', function(value:Bool):Void { Preferences.autoPause = value; }, Preferences.autoPause); - createPrefItemCheckbox('Launch in Fullscreen', 'Automatically launch the game in fullscreen on startup', function(value:Bool):Void { + createPrefItemCheckbox('Launch in Fullscreen', 'Automatically launch the game in fullscreen on startup.', function(value:Bool):Void { Preferences.autoFullscreen = value; }, Preferences.autoFullscreen); From caf56d496cd802e5c628f653abf4a562ef20da6f Mon Sep 17 00:00:00 2001 From: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:25:24 -0300 Subject: [PATCH 02/66] fix: Clear waveform data when destroying audio (#5231) This fixes an issue where recycled sounds would use the previous sound's waveform data. --- source/funkin/audio/FunkinSound.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index fb1f8aa1171..9644a6d20f2 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -551,6 +551,7 @@ class FunkinSound extends FlxSound implements ICloneable } FlxTween.cancelTweensOf(this); this._label = 'unknown'; + this._waveformData = null; } @:access(openfl.media.Sound) From 62b2815d044a0a2e339a2d55c128c579564649bd Mon Sep 17 00:00:00 2001 From: KutikiPlayz <73810550+KutikiPlayz@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:25:39 -0600 Subject: [PATCH 03/66] notes move freaking normally (#3544) --- source/funkin/Conductor.hx | 18 ++++++++++++++++++ source/funkin/play/notes/Strumline.hx | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 803b9e1b362..6080a93306b 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -92,6 +92,12 @@ class Conductor */ public var songPosition(default, null):Float = 0; + /** + * The offset between frame time and music time. + * Used in `getTimeWithDelta()` to get a more accurate music time when on higher framerates. + */ + var songPositionDelta(default, null):Float = 0; + var prevTimestamp:Float = 0; var prevTime:Float = 0; @@ -423,6 +429,7 @@ class Conductor if (FlxG.sound.music != null && FlxG.sound.music.playing) { this.songPosition = Math.min(currentLength, Math.max(0, songPos)); + this.songPositionDelta += FlxG.elapsed * 1000 * FlxG.sound.music.pitch; } else { @@ -488,12 +495,23 @@ class Conductor // which it doesn't do every frame! if (prevTime != this.songPosition) { + this.songPositionDelta = 0; + // Update the timestamp for use in-between frames prevTime = this.songPosition; prevTimestamp = Std.int(Timer.stamp() * 1000); } } + /** + * Returns a more accurate music time for higher framerates. + * @return Float + */ + public function getTimeWithDelta():Float + { + return this.songPosition + this.songPositionDelta; + } + /** * Can be called in-between frames, usually for input related things * that can potentially get processed on exact milliseconds/timestmaps. diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 50c85b73377..fe4f4a4d3a9 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -448,7 +448,6 @@ class Strumline extends FlxSpriteGroup } } - /** * For a note's strumTime, calculate its Y position relative to the strumline. * NOTE: Assumes Conductor and PlayState are both initialized. @@ -458,7 +457,7 @@ class Strumline extends FlxSpriteGroup public function calculateNoteYPos(strumTime:Float):Float { return - Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1); + Constants.PIXELS_PER_MS * (conductorInUse.getTimeWithDelta() - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1); } public function updateNotes():Void From 7be65fc2bd5e84e7d3ab739a7c839e760180f9d0 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+KoloInDaCrib@users.noreply.github.com> Date: Tue, 17 Jun 2025 02:26:06 +0200 Subject: [PATCH 04/66] [BUGFIX/ENHANCEMENT] Miscellaneous Stage Editor Bugfixes and Missing Features (#3974) * stage editor bugfixes + features :D * Merge branch 'develop' of https://github.com/FunkinCrew/Funkin into stage-editor-bugfix-n-stuff * even more fixes and missing features * delete logic fix + 2 new feats feat 1: new objects now have the zIndex 1 higher than the last one (thanks hundrec) feat 2: chars to test as are now saved (thanks imverybad) * compilation fix --------- Co-authored-by: Kolo <67389779+JustKolosaki@users.noreply.github.com> --- assets | 2 +- source/funkin/InitState.hx | 8 + source/funkin/play/stage/Stage.hx | 4 +- source/funkin/save/Save.hx | 78 ++- .../ui/debug/stageeditor/StageEditorState.hx | 260 +++++--- .../components/BackupAvailableDialog.hx | 2 +- .../stageeditor/components/NewObjDialog.hx | 4 +- .../stageeditor/components/WelcomeDialog.hx | 7 +- .../stageeditor/handlers/AssetDataHandler.hx | 55 +- .../stageeditor/handlers/StageDataHandler.hx | 41 +- .../stageeditor/handlers/UndoRedoHandler.hx | 12 +- .../toolboxes/StageEditorCharacterToolbox.hx | 170 +++--- .../StageEditorObjectAnimsToolbox.hx | 209 +++++++ .../StageEditorObjectGraphicToolbox.hx | 199 ++++++ .../StageEditorObjectPropertiesToolbox.hx | 286 +++++++++ .../toolboxes/StageEditorObjectToolbox.hx | 577 ------------------ .../toolboxes/StageEditorStageToolbox.hx | 2 +- source/funkin/util/CLIUtil.hx | 30 +- source/funkin/util/Constants.hx | 5 + 19 files changed, 1171 insertions(+), 780 deletions(-) create mode 100644 source/funkin/ui/debug/stageeditor/toolboxes/StageEditorObjectAnimsToolbox.hx create mode 100644 source/funkin/ui/debug/stageeditor/toolboxes/StageEditorObjectGraphicToolbox.hx create mode 100644 source/funkin/ui/debug/stageeditor/toolboxes/StageEditorObjectPropertiesToolbox.hx delete mode 100644 source/funkin/ui/debug/stageeditor/toolboxes/StageEditorObjectToolbox.hx diff --git a/assets b/assets index c108a7ff0d1..c2794fa02a1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c108a7ff0d11bf328e7b232160b8f68c71e21bca +Subproject commit c2794fa02a1968c71bfecfac828c12005bcef0f3 diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index d374e4c9ca3..1d6fff5d2f4 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -27,6 +27,7 @@ import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.notes.notekind.NoteKindManager; import funkin.play.PlayStatePlaylist; import funkin.ui.debug.charting.ChartEditorState; +import funkin.ui.debug.stageeditor.StageEditorState; import funkin.ui.title.TitleState; import funkin.ui.transition.LoadingState; import funkin.util.CLIUtil; @@ -303,6 +304,13 @@ class InitState extends FlxState fnfcTargetPath: params.chart.chartPath, })); } + else if (params.stage.shouldLoadStage) + { + FlxG.switchState(() -> new StageEditorState( + { + fnfsTargetPath: params.stage.stagePath, + })); + } else { FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu')); diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index cf432a52abe..22fa4580dd3 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -248,8 +248,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements propSprite.scrollFactor.y = dataProp.scroll[1]; propSprite.angle = dataProp.angle; - propSprite.color = FlxColor.fromString(dataProp.color); - @:privateAccess if (!isSolidColor) propSprite.blend = BlendMode.fromString(dataProp.blend); + if (!isSolidColor) propSprite.color = FlxColor.fromString(dataProp.color); + @:privateAccess propSprite.blend = BlendMode.fromString(dataProp.blend); propSprite.zIndex = dataProp.zIndex; diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1ef2810df53..57f21755a43 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -2,6 +2,7 @@ package funkin.save; import flixel.util.FlxSave; import funkin.input.Controls.Device; +import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring.ScoringRank; import funkin.save.migrator.RawSaveData_v1_0_0; @@ -181,7 +182,10 @@ class Save previousFiles: [], moveStep: "1px", angleStep: 5, - theme: StageEditorTheme.Light + theme: StageEditorTheme.Light, + bfChar: "bf", + gfChar: "gf", + dadChar: "dad" } }; } @@ -550,6 +554,60 @@ class Save return data.optionsStageEditor.theme; } + public var stageBoyfriendChar(get, set):String; + + function get_stageBoyfriendChar():String + { + if (data.optionsStageEditor.bfChar == null + || CharacterDataParser.fetchCharacterData(data.optionsStageEditor.bfChar) == null) data.optionsStageEditor.bfChar = "bf"; + + return data.optionsStageEditor.bfChar; + } + + function set_stageBoyfriendChar(value:String):String + { + // Set and apply. + data.optionsStageEditor.bfChar = value; + flush(); + return data.optionsStageEditor.bfChar; + } + + public var stageGirlfriendChar(get, set):String; + + function get_stageGirlfriendChar():String + { + if (data.optionsStageEditor.gfChar == null + || CharacterDataParser.fetchCharacterData(data.optionsStageEditor.gfChar ?? "") == null) data.optionsStageEditor.gfChar = "gf"; + + return data.optionsStageEditor.gfChar; + } + + function set_stageGirlfriendChar(value:String):String + { + // Set and apply. + data.optionsStageEditor.gfChar = value; + flush(); + return data.optionsStageEditor.gfChar; + } + + public var stageDadChar(get, set):String; + + function get_stageDadChar():String + { + if (data.optionsStageEditor.dadChar == null + || CharacterDataParser.fetchCharacterData(data.optionsStageEditor.dadChar ?? "") == null) data.optionsStageEditor.dadChar = "dad"; + + return data.optionsStageEditor.dadChar; + } + + function set_stageDadChar(value:String):String + { + // Set and apply. + data.optionsStageEditor.dadChar = value; + flush(); + return data.optionsStageEditor.dadChar; + } + /** * When we've seen a character unlock, add it to the list of characters seen. * @param character @@ -1699,4 +1757,22 @@ typedef SaveDataStageEditorOptions = * @default `StageEditorTheme.Light` */ var ?theme:StageEditorTheme; + + /** + * The BF character ID used in testing stages. + * @default bf + */ + var ?bfChar:String; + + /** + * The GF character ID used in testing stages. + * @default gf + */ + var ?gfChar:String; + + /** + * The Dad character ID used in testing stages. + * @default dad + */ + var ?dadChar:String; }; diff --git a/source/funkin/ui/debug/stageeditor/StageEditorState.hx b/source/funkin/ui/debug/stageeditor/StageEditorState.hx index a1c56bb8561..cf4794ee279 100644 --- a/source/funkin/ui/debug/stageeditor/StageEditorState.hx +++ b/source/funkin/ui/debug/stageeditor/StageEditorState.hx @@ -45,6 +45,8 @@ import funkin.audio.FunkinSound; import haxe.ui.notifications.NotificationType; import haxe.ui.notifications.NotificationManager; import funkin.util.logging.CrashHandler; +import funkin.graphics.shaders.Grayscale; +import funkin.data.stage.StageRegistry; /** * Da Stage Editor woo!! @@ -110,7 +112,9 @@ class StageEditorState extends UIState var menubarItemViewCamBounds:MenuCheckBox; // view cam bounds check var menubarMenuWindow:Menu; - var menubarItemWindowObject:MenuCheckBox; + var menubarItemWindowObjectGraphic:MenuCheckBox; + var menubarItemWindowObjectAnims:MenuCheckBox; + var menubarItemWindowObjectProps:MenuCheckBox; var menubarItemWindowCharacter:MenuCheckBox; var menubarItemWindowStage:MenuCheckBox; @@ -133,8 +137,11 @@ class StageEditorState extends UIState function set_selectedSprite(value:StageEditorObject) { + selectedSprite?.selectedShader.setAmount(0); this.selectedSprite = value; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_GRAPHIC); + updateDialog(StageEditorDialogType.OBJECT_ANIMS); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); if (selectedSprite != null) { @@ -251,6 +258,7 @@ class StageEditorState extends UIState public var bitmaps:Map = []; // used for optimizing the file size!!! + var charDeselectShader:Grayscale = new Grayscale(); var floorLines:Array = []; var posCircles:Array = []; var camFields:FlxTypedGroup; @@ -285,6 +293,17 @@ class StageEditorState extends UIState return value; } + /** + * The params which were passed in when the Stage Editor was initialized. + */ + var params:Null; + + public function new(?params:StageEditorParams) + { + super(); + this.params = params; + } + override public function create():Void { WindowManager.instance.reset(); @@ -304,8 +323,6 @@ class StageEditorState extends UIState persistentUpdate = false; - // FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height); - bg = FlxGridOverlay.create(10, 10); bg.scrollFactor.set(); add(bg); @@ -322,12 +339,12 @@ class StageEditorState extends UIState WindowManager.instance.container = root; Screen.instance.addComponent(root); - // group shit + assets - var gf = CharacterDataParser.fetchCharacter("gf", true); + // Characters setup. + var gf = CharacterDataParser.fetchCharacter(Save.instance.stageGirlfriendChar, true); gf.characterType = CharacterType.GF; - var dad = CharacterDataParser.fetchCharacter("dad", true); + var dad = CharacterDataParser.fetchCharacter(Save.instance.stageDadChar, true); dad.characterType = CharacterType.DAD; - var bf = CharacterDataParser.fetchCharacter("bf", true); + var bf = CharacterDataParser.fetchCharacter(Save.instance.stageBoyfriendChar, true); bf.characterType = CharacterType.BF; bf.flipX = !bf.getDataFlipX(); @@ -338,17 +355,13 @@ class StageEditorState extends UIState dad.updateHitbox(); bf.updateHitbox(); - // only one char !!! + // Only one character per group allowed. charGroups = [ CharacterType.BF => new FlxTypedGroup(1), CharacterType.GF => new FlxTypedGroup(1), CharacterType.DAD => new FlxTypedGroup(1) ]; - // this is the part where the stage generate function comes up - // apparently no, said the future me - // back to the regular program - gf.x = charPos[CharacterType.GF][0] - gf.characterOrigin.x + gf.globalOffsets[0]; gf.y = charPos[CharacterType.GF][1] - gf.characterOrigin.y + gf.globalOffsets[1]; dad.x = charPos[CharacterType.DAD][0] - dad.characterOrigin.x + dad.globalOffsets[0]; @@ -366,12 +379,7 @@ class StageEditorState extends UIState add(charGroups[CharacterType.DAD]); add(charGroups[CharacterType.BF]); - // ui - // spriteMarker = new FlxSprite().makeGraphic(1, 1, FlxColor.CYAN); - // spriteMarker.alpha = 0.3; - // spriteMarker.zIndex = MAX_Z_INDEX + CHARACTER_COLORS.length + 3; // PLEASE - // add(spriteMarker); - + // UI Sprites setup. camFields = new FlxTypedGroup(); camFields.visible = false; camFields.zIndex = MAX_Z_INDEX + CHARACTER_COLORS.length + 1; @@ -422,6 +430,7 @@ class StageEditorState extends UIState addUI(); + // Some callbacks. findObjDialog = new FindObjDialog(this, selectedSprite == null ? "" : selectedSprite.name); FlxG.stage.window.onDropFile.add(function(path:String):Void { @@ -442,29 +451,66 @@ class StageEditorState extends UIState } }); - onMenuItemClick("new stage"); - welcomeDialog.closable = false; + if (params?.targetStageId != null && StageRegistry.instance.hasEntry(params?.targetStageId)) + { + var stageData = StageRegistry.instance.parseEntryDataWithMigration(params.targetStageId, StageRegistry.instance.fetchEntryVersion(params.targetStageId)); - #if sys - if (Save.instance.stageEditorHasBackup) + if (stageData != null) + { + // Load the stage data. + currentFile = ""; + this.loadFromDataRaw(stageData); + } + else + { + // Notify the error and create a new stage. + notifyChange("Problem Loading the Stage", "The Stage File could not be loaded.", true); + onMenuItemClick("new stage"); + } + } + else if (params?.fnfsTargetPath != null) { - FileUtil.createDirIfNotExists(BACKUPS_PATH); + var bytes = FileUtil.readBytesFromPath(params.fnfsTargetPath); - var files = sys.FileSystem.readDirectory(BACKUPS_PATH); + if (bytes != null) + { + // Open the stage file. + currentFile = params.fnfsTargetPath; + this.unpackShitFromZip(bytes); + } + else + { + // Notify the error and create a new stage. + notifyChange("Problem Loading the Stage", "The Stage File could not be loaded.", true); + onMenuItemClick("new stage"); + } + } + else + { + onMenuItemClick("new stage"); + welcomeDialog.closable = false; - if (files.length > 0) + #if sys + if (Save.instance.stageEditorHasBackup) { - // ensures that the top most file is a backup - files.sort(funkin.util.SortUtil.alphabetically); + FileUtil.createDirIfNotExists(BACKUPS_PATH); - while (!files[files.length - 1].endsWith(FileUtil.FILE_EXTENSION_INFO_FNFS.extension) - || !files[files.length - 1].startsWith("stage-editor-")) - files.pop(); - } + var files = sys.FileSystem.readDirectory(BACKUPS_PATH); - if (files.length != 0) new BackupAvailableDialog(this, haxe.io.Path.join([BACKUPS_PATH, files[files.length - 1]])).showDialog(true); + if (files.length > 0) + { + // ensures that the top most file is a backup + files.sort(funkin.util.SortUtil.alphabetically); + + while (!files[files.length - 1].endsWith(FileUtil.FILE_EXTENSION_INFO_FNFS.extension) + || !files[files.length - 1].startsWith("stage-editor-")) + files.pop(); + } + + if (files.length != 0) new BackupAvailableDialog(this, haxe.io.Path.join([BACKUPS_PATH, files[files.length - 1]])).showDialog(true); + } + #end } - #end WindowUtil.windowExit.add(windowClose); CrashHandler.errorSignal.add(autosavePerCrash); @@ -502,6 +548,22 @@ class StageEditorState extends UIState override public function update(elapsed:Float):Void { + // Save the stage if exiting through the F4 keybind, as it moves you to the Main Menu. + if (FlxG.keys.justPressed.F4) + { + @:privateAccess + if (!autoSaveTimer.finished) autoSaveTimer.onLoopFinished(); + resetWindowTitle(); + + WindowUtil.windowExit.remove(windowClose); + CrashHandler.errorSignal.remove(autosavePerCrash); + CrashHandler.criticalErrorSignal.remove(autosavePerCrash); + + Cursor.hide(); + FlxG.sound.music.stop(); + return; + } + updateBGSize(); conductorInUse.update(); @@ -517,7 +579,7 @@ class StageEditorState extends UIState if (testingMode) { for (char in getCharacters()) - char.alpha = 1; + char.shader = null; // spriteMarker.visible = camMarker.visible = false; findObjDialog.hideDialog(DialogButton.CANCEL); @@ -612,19 +674,20 @@ class StageEditorState extends UIState if (moveMode == "assets") { - for (spr in spriteArray) + if (selectedSprite != null && !FlxG.mouse.overlaps(selectedSprite) && FlxG.mouse.justPressed && !isCursorOverHaxeUI) { - spr.active = spr.isOnScreen(); + selectedSprite = null; + } - if (spr.pixelsOverlapPoint(FlxG.mouse.getWorldPosition())) + for (spr in spriteArray) + { + if (FlxG.mouse.overlaps(spr)) { if (spr.visible && !FlxG.keys.pressed.SHIFT) nameTxt.text = spr.name; if (FlxG.mouse.justPressed && allowInput && spr.visible && !FlxG.keys.pressed.SHIFT && !isCursorOverHaxeUI) { - selectedSprite.selectedShader.setAmount(0); selectedSprite = spr; - updateDialog(StageEditorDialogType.OBJECT); } } @@ -639,7 +702,7 @@ class StageEditorState extends UIState if (FlxG.mouse.pressed && allowInput && selectedSprite != null && FlxG.mouse.overlaps(selectedSprite) && FlxG.mouse.justMoved && !isCursorOverHaxeUI) { saved = false; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); if (moveOffset.length == 0) { @@ -671,15 +734,15 @@ class StageEditorState extends UIState arrowMovement(selectedSprite); for (char in getCharacters()) - char.alpha = 1; + char.shader = null; } else { - selectedChar.alpha = 1; + selectedChar.shader = null; for (char in getCharacters()) { - if (char != selectedChar) char.alpha = 0.3; + if (char != selectedChar) char.shader = charDeselectShader; if (char != null && checkCharOverlaps(char)) // flxg.mouse.overlaps crashes the game { @@ -732,12 +795,10 @@ class StageEditorState extends UIState nameTxt.x = FlxG.mouse.getScreenPosition(camHUD).x; nameTxt.y = FlxG.mouse.getScreenPosition(camHUD).y - nameTxt.height; - // spriteMarker.visible = (moveMode == "assets" && selectedSprite != null); camMarker.visible = moveMode == "chars"; - // if (selectedSprite != null) spriteMarker.setPosition(selectedSprite.x, selectedSprite.y); - // for (item in sprDependant) - // item.disabled = !spriteMarker.visible; + for (item in sprDependant) + item.disabled = (moveMode != "assets" || selectedSprite == null); menubarItemPaste.disabled = copiedSprite == null; menubarItemFindObj.disabled = !(moveMode == "assets"); @@ -755,22 +816,22 @@ class StageEditorState extends UIState function autosavePerCrash(message:String) { - trace("fuuuucckkkkk we crashed, reason: " + message); + trace("Crashed the game for the reason: " + message); if (!saved) { - trace("dw we're making a backup!!!"); + trace("You haven't saved recently, so a backup will be made."); autoSaveTimer.onComplete(autoSaveTimer); } } function windowClose(exitCode:Int) { - trace("closing da window ye"); + trace("Closing the game window."); if (!saved) { - trace("dum dum why no save >:["); + trace("You haven't saved recently, so a backup will be made."); autoSaveTimer.onComplete(autoSaveTimer); } } @@ -873,6 +934,7 @@ class StageEditorState extends UIState public function updateArray() { + sortAssets(); spriteArray = []; for (thing in members) @@ -881,7 +943,6 @@ class StageEditorState extends UIState } findObjDialog.updateIndicator(); - updateDialog(StageEditorDialogType.OBJECT); } public function sortAssets() @@ -967,7 +1028,7 @@ class StageEditorState extends UIState menubarItemRedo.onClick = function(_) onMenuItemClick("redo"); menubarItemCopy.onClick = function(_) onMenuItemClick("copy object"); menubarItemCut.onClick = function(_) onMenuItemClick("cut object"); - menubarItemPaste.onClick = function(_) onMenuItemClick("paste stage"); + menubarItemPaste.onClick = function(_) onMenuItemClick("paste object"); menubarItemDelete.onClick = function(_) onMenuItemClick("delete object"); menubarItemNewObj.onClick = function(_) onMenuItemClick("new object"); menubarItemFindObj.onClick = function(_) onMenuItemClick("find object"); @@ -993,7 +1054,7 @@ class StageEditorState extends UIState var shit = Std.parseInt(StringTools.replace(bottomBarMoveStepText.text, "px", "")); moveStep = shit; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); updateDialog(StageEditorDialogType.CHARACTER); updateDialog(StageEditorDialogType.STAGE); } @@ -1016,7 +1077,7 @@ class StageEditorState extends UIState Save.instance.stageEditorAngleStep = angleOptions[id]; bottomBarAngleStepText.text = (angleOptions.contains(Save.instance.stageEditorAngleStep) ? Save.instance.stageEditorAngleStep : 5) + "°"; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); } bottomBarAngleStepText.onClick = function(_) changeAngle(1); @@ -1024,11 +1085,15 @@ class StageEditorState extends UIState changeAngle(); // update - dialogs.set(StageEditorDialogType.OBJECT, new StageEditorObjectToolbox(this)); + dialogs.set(StageEditorDialogType.OBJECT_GRAPHIC, new StageEditorObjectGraphicToolbox(this)); + dialogs.set(StageEditorDialogType.OBJECT_ANIMS, new StageEditorObjectAnimsToolbox(this)); + dialogs.set(StageEditorDialogType.OBJECT_PROPERTIES, new StageEditorObjectPropertiesToolbox(this)); dialogs.set(StageEditorDialogType.CHARACTER, new StageEditorCharacterToolbox(this)); dialogs.set(StageEditorDialogType.STAGE, new StageEditorStageToolbox(this)); - menubarItemWindowObject.onChange = function(_) toggleDialog(StageEditorDialogType.OBJECT, menubarItemWindowObject.selected); + menubarItemWindowObjectGraphic.onChange = function(_) toggleDialog(StageEditorDialogType.OBJECT_GRAPHIC, menubarItemWindowObjectGraphic.selected); + menubarItemWindowObjectAnims.onChange = function(_) toggleDialog(StageEditorDialogType.OBJECT_ANIMS, menubarItemWindowObjectAnims.selected); + menubarItemWindowObjectProps.onChange = function(_) toggleDialog(StageEditorDialogType.OBJECT_PROPERTIES, menubarItemWindowObjectProps.selected); menubarItemWindowCharacter.onChange = function(_) toggleDialog(StageEditorDialogType.CHARACTER, menubarItemWindowCharacter.selected); menubarItemWindowStage.onChange = function(_) toggleDialog(StageEditorDialogType.STAGE, menubarItemWindowStage.selected); @@ -1133,8 +1198,6 @@ class StageEditorState extends UIState currentFile = path; }, null, stageName + "." + FileUtil.FILE_EXTENSION_INFO_FNFS.extension); - bitmaps.clear(); - case "save stage": if (currentFile == "") { @@ -1154,8 +1217,7 @@ class StageEditorState extends UIState saved = true; - updateRecentFiles(); - bitmaps.clear(); + reloadRecentFiles(); case "open stage": if (!saved) @@ -1172,14 +1234,18 @@ class StageEditorState extends UIState return; } - FileUtil.browseForSaveFile([FileUtil.FILE_FILTER_FNFS], function(path:String) { + FileUtil.browseForBinaryFile("Open Stage Data", [FileUtil.FILE_EXTENSION_INFO_FNFS], function(_) { + if (_?.fullPath == null) return; + clearAssets(); - currentFile = path; - this.unpackShitFromZip(FileUtil.readBytesFromPath(path)); + currentFile = _.fullPath; + this.unpackShitFromZip(FileUtil.readBytesFromPath(currentFile)); reloadRecentFiles(); - }, null, null, "Open Stage Data"); + }, function() { + // This function does nothing, it's there for crash prevention. + }); case "exit": if (!saved) @@ -1207,7 +1273,10 @@ class StageEditorState extends UIState FlxG.sound.music.stop(); case "switch mode": - if (!testingMode) moveMode = (moveMode == "assets" ? "chars" : "assets"); + if (testingMode) return; + moveMode = (moveMode == "assets" ? "chars" : "assets"); + + selectedSprite?.selectedShader.setAmount((moveMode == "assets" ? 1 : 0)); case "switch focus": if (testingMode) @@ -1281,19 +1350,25 @@ class StageEditorState extends UIState a.isDebugged = testingMode; } - if (!testingMode) menubarItemWindowObject.selected = menubarItemWindowCharacter.selected = menubarItemWindowStage.selected = false; + if (!testingMode) + { + menubarItemWindowObjectGraphic.selected = menubarItemWindowObjectAnims.selected = menubarItemWindowObjectProps.selected = menubarItemWindowCharacter.selected = menubarItemWindowStage.selected = false; + } + selectedSprite?.selectedShader.setAmount((testingMode ? (moveMode == "assets" ? 1 : 0) : 0)); testingMode = !testingMode; case "clear assets": - Dialogs.messageBox("This will destroy all the Objects in this Stage.\n\nAre you sure? This cannot be undone.", "Clear Assets", - MessageBoxType.TYPE_YESNO, true, function(btn:DialogButton) { + Dialogs.messageBox("This will destroy all Objects in this Stage.\n\nAre you sure? This cannot be undone.", "Clear Assets", MessageBoxType.TYPE_YESNO, + true, function(btn:DialogButton) { if (btn == DialogButton.YES) { clearAssets(); saved = false; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_GRAPHIC); + updateDialog(StageEditorDialogType.OBJECT_ANIMS); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); } }); @@ -1301,7 +1376,7 @@ class StageEditorState extends UIState if (selectedSprite != null && moveMode == "assets") { selectedSprite.screenCenter(); - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); saved = false; } @@ -1313,6 +1388,8 @@ class StageEditorState extends UIState } case "delete object": + if (selectedSprite == null) return; + this.createAndPushAction(OBJECT_DELETED); spriteArray.remove(selectedSprite); @@ -1323,7 +1400,6 @@ class StageEditorState extends UIState selectedSprite = null; updateArray(); - sortAssets(); case "copy object": if (selectedSprite == null) return; @@ -1347,6 +1423,7 @@ class StageEditorState extends UIState spr.name += " (" + i + ")"; } + add(spr); selectedSprite = spr; updateArray(); @@ -1355,7 +1432,9 @@ class StageEditorState extends UIState onMenuItemClick("delete object"); // already changes the saved var case "new stage": - if (menubarItemWindowObject.selected) menubarItemWindowObject.selected = false; + if (menubarItemWindowObjectGraphic.selected) menubarItemWindowObjectGraphic.selected = false; + if (menubarItemWindowObjectAnims.selected) menubarItemWindowObjectAnims.selected = false; + if (menubarItemWindowObjectProps.selected) menubarItemWindowObjectProps.selected = false; if (menubarItemWindowCharacter.selected) menubarItemWindowCharacter.selected = false; if (menubarItemWindowStage.selected) menubarItemWindowStage.selected = false; @@ -1366,7 +1445,9 @@ class StageEditorState extends UIState updateWindowTitle(); welcomeDialog = null; - updateDialog(StageEditorDialogType.OBJECT); + updateDialog(StageEditorDialogType.OBJECT_GRAPHIC); + updateDialog(StageEditorDialogType.OBJECT_ANIMS); + updateDialog(StageEditorDialogType.OBJECT_PROPERTIES); updateDialog(StageEditorDialogType.CHARACTER); updateDialog(StageEditorDialogType.STAGE); } @@ -1388,9 +1469,7 @@ class StageEditorState extends UIState undoArray = []; redoArray = []; - updateArray(); - sortAssets(); removeUnusedBitmaps(); } @@ -1479,7 +1558,30 @@ enum StageEditorDialogType CHARACTER; /** - * The Object Options Dialog. + * The Object Graphic Options Dialog. */ - OBJECT; + OBJECT_GRAPHIC; + + /** + * The Object Animations Options Dialog. + */ + OBJECT_ANIMS; + + /** + * The Object Properties Options Dialog. + */ + OBJECT_PROPERTIES; } + +typedef StageEditorParams = +{ + /** + * If non-null, load this stage immediately instead of the welcome screen. + */ + var ?fnfsTargetPath:String; + + /** + * If non-null, load this stage immediately instead of the welcome screen. + */ + var ?targetStageId:String; +}; diff --git a/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx b/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx index e7b4e31e1d6..7ccc792dc2f 100644 --- a/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx +++ b/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx @@ -11,7 +11,7 @@ using StringTools; @:xml(' -