diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 08e9b4d2..f7e7ef90 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,6 +25,9 @@ jobs: export/release/linux/obj/ restore-keys: | cache-build-linux + - name: Installing LibVLC + run: | + sudo apt-get install libvlc-dev libvlccore-dev - name: Installing/Updating libraries run: | haxe -cp commandline -D analyzer-optimize --run Main setup diff --git a/assets/images/editors/charter/add-strumline.png b/assets/images/editors/charter/add-strumline.png index d205c17e..f6355d05 100644 Binary files a/assets/images/editors/charter/add-strumline.png and b/assets/images/editors/charter/add-strumline.png differ diff --git a/assets/images/editors/charter/event-icons/Alt Animation Toggle.png b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png index f2180276..39e1afd1 100644 Binary files a/assets/images/editors/charter/event-icons/Alt Animation Toggle.png and b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png differ diff --git a/assets/images/editors/charter/lock-strumline.png b/assets/images/editors/charter/lock-strumline.png new file mode 100644 index 00000000..cf283f19 Binary files /dev/null and b/assets/images/editors/charter/lock-strumline.png differ diff --git a/assets/images/editors/charter/strumline-drag.png b/assets/images/editors/charter/strumline-drag.png new file mode 100644 index 00000000..9fb8fa5e Binary files /dev/null and b/assets/images/editors/charter/strumline-drag.png differ diff --git a/assets/images/editors/new.png b/assets/images/editors/new.png index d205c17e..945c49e0 100644 Binary files a/assets/images/editors/new.png and b/assets/images/editors/new.png differ diff --git a/assets/images/editors/ui/audio-time-empty.png b/assets/images/editors/ui/audio-time-empty.png index d5a36361..0f380282 100644 Binary files a/assets/images/editors/ui/audio-time-empty.png and b/assets/images/editors/ui/audio-time-empty.png differ diff --git a/assets/images/editors/ui/audio-time-full.png b/assets/images/editors/ui/audio-time-full.png index 22073536..c52c765a 100644 Binary files a/assets/images/editors/ui/audio-time-full.png and b/assets/images/editors/ui/audio-time-full.png differ diff --git a/assets/images/editors/ui/audio-volume-empty.png b/assets/images/editors/ui/audio-volume-empty.png index 7255b513..5ad6dc52 100644 Binary files a/assets/images/editors/ui/audio-volume-empty.png and b/assets/images/editors/ui/audio-volume-empty.png differ diff --git a/assets/images/editors/ui/audio-volume-full.png b/assets/images/editors/ui/audio-volume-full.png index 76ffc958..bac31729 100644 Binary files a/assets/images/editors/ui/audio-volume-full.png and b/assets/images/editors/ui/audio-volume-full.png differ diff --git a/assets/images/editors/ui/context-icons.png b/assets/images/editors/ui/context-icons.png index 73b8ff97..7bf46448 100644 Binary files a/assets/images/editors/ui/context-icons.png and b/assets/images/editors/ui/context-icons.png differ diff --git a/assets/images/editors/ui/grayscale-button.png b/assets/images/editors/ui/grayscale-button.png new file mode 100644 index 00000000..1467fece Binary files /dev/null and b/assets/images/editors/ui/grayscale-button.png differ diff --git a/assets/images/editors/ui/grayscale-button.xml b/assets/images/editors/ui/grayscale-button.xml new file mode 100644 index 00000000..0c0811f4 --- /dev/null +++ b/assets/images/editors/ui/grayscale-button.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/editors/ui/grayscale-popup.png b/assets/images/editors/ui/grayscale-popup.png new file mode 100644 index 00000000..b0113603 Binary files /dev/null and b/assets/images/editors/ui/grayscale-popup.png differ diff --git a/assets/images/editors/ui/grayscale-popup.xml b/assets/images/editors/ui/grayscale-popup.xml new file mode 100644 index 00000000..021dd81a --- /dev/null +++ b/assets/images/editors/ui/grayscale-popup.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/editors/ui/warning-popup.png b/assets/images/editors/ui/warning-popup.png deleted file mode 100644 index c190bffa..00000000 Binary files a/assets/images/editors/ui/warning-popup.png and /dev/null differ diff --git a/assets/images/editors/warnings/error.png b/assets/images/editors/warnings/error.png new file mode 100644 index 00000000..9c65f94b Binary files /dev/null and b/assets/images/editors/warnings/error.png differ diff --git a/assets/images/editors/warnings/warning.png b/assets/images/editors/warnings/warning.png new file mode 100644 index 00000000..0d4f1999 Binary files /dev/null and b/assets/images/editors/warnings/warning.png differ diff --git a/libs.xml b/libs.xml index aad494a6..70e72592 100644 --- a/libs.xml +++ b/libs.xml @@ -1,24 +1,22 @@ - + - - - + + + - + + + + + - - - - + + + - - + + - - - - - diff --git a/project.xml b/project.xml index 1a008240..5e4aaa8a 100644 --- a/project.xml +++ b/project.xml @@ -21,7 +21,6 @@ - @@ -53,12 +52,11 @@ - + - @@ -74,17 +72,11 @@ - - - - - - @@ -100,6 +92,7 @@ + @@ -113,10 +106,12 @@ - + - +
+ +
@@ -157,7 +152,9 @@ - +
+ +
@@ -172,23 +169,20 @@ + + - -
- -
+ + - - - @@ -208,5 +202,6 @@ + diff --git a/source/funkin/backend/FlxAnimate.hx b/source/funkin/backend/FlxAnimate.hx index ea1644bc..c6559dfd 100644 --- a/source/funkin/backend/FlxAnimate.hx +++ b/source/funkin/backend/FlxAnimate.hx @@ -25,7 +25,7 @@ class FlxAnimate extends flxanimate.FlxAnimate { getScreenPosition(_point, camera).subtractPoint(offset); rMatrix.translate(-origin.x, -origin.y); - if (!showPivot && limb.name != "pivot") { + if (limb != _pivot) { if (frameOffsetAngle != null && frameOffsetAngle != angle) { var angleOff = (-angle + frameOffsetAngle) * FlxAngle.TO_RAD; diff --git a/source/funkin/backend/MusicBeatState.hx b/source/funkin/backend/MusicBeatState.hx index eca0c466..1b5499de 100644 --- a/source/funkin/backend/MusicBeatState.hx +++ b/source/funkin/backend/MusicBeatState.hx @@ -9,6 +9,7 @@ import flixel.FlxState; import flixel.FlxSubState; import funkin.backend.scripting.events.*; import funkin.backend.scripting.Script; +import funkin.backend.scripting.ScriptPack; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.system.Conductor; import funkin.options.PlayerSettings; @@ -89,7 +90,7 @@ class MusicBeatState extends FlxState implements IBeatReceiver /** * Current injected script attached to the state. To add one, create a file at path "data/states/stateName" (ex: data/states/FreeplayState) */ - public var stateScript:Script; + public var stateScripts:ScriptPack; public var scriptsAllowed:Bool = true; @@ -112,15 +113,22 @@ class MusicBeatState extends FlxState implements IBeatReceiver } function loadScript() { + var className = Type.getClassName(Type.getClass(this)); + if (stateScripts == null) + (stateScripts = new ScriptPack(className)).setParent(this); if (scriptsAllowed) { - if (stateScript == null || stateScript is DummyScript) { - var className = Type.getClassName(Type.getClass(this)); + if (stateScripts.scripts.length == 0) { var scriptName = this.scriptName != null ? this.scriptName : className.substr(className.lastIndexOf(".")+1); - stateScript = Script.create(Paths.script('data/states/${scriptName}')); - stateScript.setParent(this); - stateScript.load(); - } else - stateScript.reload(); + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var path = Paths.script('data/states/${scriptName}/LIB_$i'); + var script = Script.create(path); + if (script is DummyScript) continue; + script.fileName = '$i:$scriptName'; + stateScripts.add(script); + script.load(); + } + } + else stateScripts.reload(); } } @@ -161,13 +169,14 @@ class MusicBeatState extends FlxState implements IBeatReceiver } public function call(name:String, ?args:Array, ?defaultVal:Dynamic):Dynamic { // calls the function on the assigned script - if (stateScript == null) return defaultVal; - return stateScript.call(name, args); + if(stateScripts != null) + return stateScripts.call(name, args); + return defaultVal; } public function event(name:String, event:T):T { - if (stateScript == null) return event; - stateScript.call(name, [event]); + if(stateScripts != null) + stateScripts.call(name, [event]); return event; } @@ -176,8 +185,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver // TODO: DEBUG MODE!! if (FlxG.keys.justPressed.F5) { loadScript(); - if (stateScript != null && !(stateScript is DummyScript)) - Logs.trace('State script successfully reloaded', WARNING, GREEN); } call("update", [elapsed]); @@ -234,8 +241,7 @@ class MusicBeatState extends FlxState implements IBeatReceiver super.destroy(); graphicCache.destroy(); call("onDestroy"); - if (stateScript != null) - stateScript.destroy(); + stateScripts = FlxDestroyUtil.destroy(stateScripts); } public override function draw() { diff --git a/source/funkin/backend/MusicBeatSubstate.hx b/source/funkin/backend/MusicBeatSubstate.hx index d1d96f43..4b4315ca 100644 --- a/source/funkin/backend/MusicBeatSubstate.hx +++ b/source/funkin/backend/MusicBeatSubstate.hx @@ -3,6 +3,7 @@ package funkin.backend; import flixel.FlxState; import funkin.backend.scripting.events.*; import funkin.backend.scripting.Script; +import funkin.backend.scripting.ScriptPack; import funkin.backend.scripting.DummyScript; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.system.Conductor.BPMChangeEvent; @@ -63,7 +64,7 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver /** * Current injected script attached to the state. To add one, create a file at path "data/states/stateName" (ex: "data/states/PauseMenuSubstate.hx") */ - public var stateScript:Script; + public var stateScripts:ScriptPack; public var scriptsAllowed:Bool = true; @@ -99,15 +100,22 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver } function loadScript() { + var className = Type.getClassName(Type.getClass(this)); + if (stateScripts == null) + (stateScripts = new ScriptPack(className)).setParent(this); if (scriptsAllowed) { - if (stateScript == null || stateScript is DummyScript) { - var className = Type.getClassName(Type.getClass(this)); + if (stateScripts.scripts.length == 0) { var scriptName = this.scriptName != null ? this.scriptName : className.substr(className.lastIndexOf(".")+1); - stateScript = Script.create(Paths.script('data/states/${scriptName}')); - stateScript.setParent(this); - stateScript.load(); - } else - stateScript.reload(); + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var path = Paths.script('data/states/${scriptName}/LIB_$i'); + var script = Script.create(path); + if (script is DummyScript) continue; + script.fileName = '$i:$scriptName'; + stateScripts.add(script); + script.load(); + } + } + else stateScripts.reload(); } } @@ -151,13 +159,14 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver } public function call(name:String, ?args:Array, ?defaultVal:Dynamic):Dynamic { // calls the function on the assigned script - if (stateScript == null) return defaultVal; - return stateScript.call(name, args); + if(stateScripts != null) + return stateScripts.call(name, args); + return defaultVal; } public function event(name:String, event:T):T { - if (stateScript == null) return event; - stateScript.call(name, [event]); + if(stateScripts != null) + stateScripts.call(name, [event]); return event; } @@ -166,8 +175,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver // TODO: DEBUG MODE!! if (FlxG.keys.justPressed.F5) { loadScript(); - if (stateScript != null && !(stateScript is DummyScript)) - Logs.trace('State script successfully reloaded', WARNING, GREEN); } call("update", [elapsed]); super.update(elapsed); @@ -222,8 +229,7 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver public override function destroy() { super.destroy(); call("onDestroy"); - if(stateScript != null) - stateScript.destroy(); + stateScripts = FlxDestroyUtil.destroy(stateScripts); } public override function switchTo(nextState:FlxState) { diff --git a/source/funkin/backend/MusicBeatTransition.hx b/source/funkin/backend/MusicBeatTransition.hx index c49f88a8..6313c10b 100644 --- a/source/funkin/backend/MusicBeatTransition.hx +++ b/source/funkin/backend/MusicBeatTransition.hx @@ -11,14 +11,14 @@ class MusicBeatTransition extends MusicBeatSubstate { public var transitionCamera:FlxCamera; public var newState:FlxState; + public var blackSpr:FlxSprite; + public var transitionSprite:FunkinSprite; public function new(?newState:FlxState) { super(); this.newState = newState; } public override function create() { - super.create(); - if (newState != null) add(new FunkinParentDisabler(true)); @@ -29,13 +29,13 @@ class MusicBeatTransition extends MusicBeatSubstate { cameras = [transitionCamera]; var out = newState != null; - var blackSpr = new FlxSprite(0, out ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1); + blackSpr = new FlxSprite(0, out ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1); blackSpr.scale.set(transitionCamera.width, transitionCamera.height); blackSpr.color = 0xFF000000; blackSpr.updateHitbox(); add(blackSpr); - var transitionSprite = new FunkinSprite(); + transitionSprite = new FunkinSprite(); transitionSprite.loadSprite(Paths.image('menus/transitionSpr')); if (transitionSprite.animateAtlas == null) { transitionSprite.setGraphicSize(transitionCamera.width, transitionCamera.height); @@ -53,6 +53,8 @@ class MusicBeatTransition extends MusicBeatSubstate { finish(); } }); + + super.create(); } public override function update(elapsed:Float) { diff --git a/source/funkin/backend/assets/IModsAssetLibrary.hx b/source/funkin/backend/assets/IModsAssetLibrary.hx index 5c90b185..a080189b 100644 --- a/source/funkin/backend/assets/IModsAssetLibrary.hx +++ b/source/funkin/backend/assets/IModsAssetLibrary.hx @@ -23,6 +23,7 @@ using StringTools; interface IModsAssetLibrary { public var prefix:String; + public var modName:String; public var libName:String; #if MOD_SUPPORT diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx index 33458fe0..a88377ad 100644 --- a/source/funkin/backend/assets/ModsFolder.hx +++ b/source/funkin/backend/assets/ModsFolder.hx @@ -41,6 +41,11 @@ class ModsFolder { */ public static var addonsPath:String = "./addons/"; + /** + * If accessing a file as assets/data/global/LIB_mymod.hx should redirect to mymod:assets/data/global.hx + */ + public static var useLibFile:Bool = true; + /** * Whenever its the first time mods has been reloaded. */ @@ -72,18 +77,34 @@ class ModsFolder { * @param modName Name of the mod * @param force Whenever the mod should be reloaded if it has already been loaded */ - public static function loadModLib(path:String, force:Bool = false) { + public static function loadModLib(path:String, force:Bool = false, ?modName:String) { #if MOD_SUPPORT if (FileSystem.exists('$path.zip')) - return loadLibraryFromZip('$path'.toLowerCase(), '$path.zip', force); + return loadLibraryFromZip('$path'.toLowerCase(), '$path.zip', force, modName); else - return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force); + return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force, modName); #else return null; #end } + public static function getLoadedMods():Array { + var libs = []; + for (i in Paths.assetsTree.libraries) { + var l = i; + if (l is openfl.utils.AssetLibrary) { + var al = cast(l, openfl.utils.AssetLibrary); + @:privateAccess + if (al.__proxy != null) l = al.__proxy; + } + var libString:String; + if (l is ScriptedAssetLibrary || l is IModsAssetLibrary) libString = cast(l, IModsAssetLibrary).modName; + else continue; + libs.push(libString); + } + return libs; + } public static function prepareLibrary(libName:String, force:Bool = false) { var assets:AssetManifest = new AssetManifest(); assets.name = libName; @@ -111,12 +132,12 @@ class ModsFolder { } #if MOD_SUPPORT - public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false) { - return prepareModLibrary(libName, new ModsFolderLibrary(folder, libName), force); + public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false, ?modName:String) { + return prepareModLibrary(libName, new ModsFolderLibrary(folder, libName, modName), force); } - public static function loadLibraryFromZip(libName:String, zipPath:String, force:Bool = false) { - return prepareModLibrary(libName, new ZipFolderLibrary(zipPath, libName), force); + public static function loadLibraryFromZip(libName:String, zipPath:String, force:Bool = false, ?modName:String) { + return prepareModLibrary(libName, new ZipFolderLibrary(zipPath, libName, modName), force); } #end } \ No newline at end of file diff --git a/source/funkin/backend/assets/ModsFolderLibrary.hx b/source/funkin/backend/assets/ModsFolderLibrary.hx index 69f87693..ed608bda 100644 --- a/source/funkin/backend/assets/ModsFolderLibrary.hx +++ b/source/funkin/backend/assets/ModsFolderLibrary.hx @@ -17,14 +17,19 @@ using StringTools; class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary { public var folderPath:String; + public var modName:String; public var libName:String; public var useImageCache:Bool = true; public var prefix = 'assets/'; - public function new(folderPath:String, libName:String) { + public function new(folderPath:String, libName:String, ?modName:String) { this.folderPath = folderPath; this.libName = libName; this.prefix = 'assets/$libName/'; + if(modName == null) + this.modName = libName; + else + this.modName = modName; super(); } @@ -133,6 +138,15 @@ class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary { private function __parseAsset(asset:String):Bool { if (!asset.startsWith(prefix)) return false; _parsedAsset = asset.substr(prefix.length); + if(ModsFolder.useLibFile) { + var file = new haxe.io.Path(_parsedAsset); + if(file.file.startsWith("LIB_")) { + var library = file.file.substr(4); + if(library != modName) return false; + + _parsedAsset = file.dir + "." + file.ext; + } + } return true; } #end diff --git a/source/funkin/backend/assets/MultiFramesCollection.hx b/source/funkin/backend/assets/MultiFramesCollection.hx new file mode 100644 index 00000000..a3d9c733 --- /dev/null +++ b/source/funkin/backend/assets/MultiFramesCollection.hx @@ -0,0 +1,72 @@ +package funkin.backend.assets; + +import flixel.graphics.frames.FlxFramesCollection; +import flixel.FlxG; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFrame.FlxFrameAngle; +import flixel.graphics.frames.FlxFrame.FlxFrameType; +import flixel.math.FlxMath; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; +import flixel.util.FlxDestroyUtil; +import flixel.util.FlxStringUtil; + +/** + * Base class for all frame collections. + */ +class MultiFramesCollection extends FlxFramesCollection +{ + public var parentedFrames:Array = []; + + public function new(parent:FlxGraphic, ?border:FlxPoint) + { + super(parent, USER("MULTI"), border); + } + + /** + * Returns the `FlxAtlasFrame` of the specified `FlxGraphic` object. + * + * @param graphic `FlxGraphic` object to find the `FlxAtlasFrames` collection for. + * @return `FlxAtlasFrames` collection for the specified `FlxGraphic` object + * Could be `null` if `FlxGraphic` doesn't have it yet. + */ + public static function findFrame(graphic:FlxGraphic, ?border:FlxPoint):MultiFramesCollection + { + if (border == null) + border = FlxPoint.weak(); + + var atlasFrames:Array = cast graphic.getFramesCollections(USER("MULTI")); + + for (atlas in atlasFrames) + if (atlas.border.equals(border)) + return atlas; + + return null; + } + + public function addFrames(collection:FlxFramesCollection) { + if (collection == null || collection.frames == null) return; + + collection.parent.useCount++; + parentedFrames.push(collection); + + for(f in collection.frames) { + if (f != null) { + pushFrame(f); + f.parent = collection.parent; + } + } + } + + public override function destroy():Void + { + if(parentedFrames != null) { + for(collection in parentedFrames) { + if(collection != null) + collection.parent.useCount--; + } + parentedFrames = null; + } + super.destroy(); + } +} \ No newline at end of file diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 2d7c0c75..495d5577 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -48,6 +48,9 @@ class Paths inline static public function txt(key:String, ?library:String) return getPath('data/$key.txt', library); + inline static public function pack(key:String, ?library:String) + return getPath('data/$key.pack', library); + inline static public function ini(key:String, ?library:String) return getPath('data/$key.ini', library); @@ -161,7 +164,7 @@ class Paths inline static public function getPackerAtlasAlt(key:String) return FlxAtlasFrames.fromSpriteSheetPacker('$key.png', '$key.txt'); - inline static public function getAssetsRoot():String + inline static public function getAssetsRoot():String return ModsFolder.currentModFolder != null ? '${ModsFolder.modsPath}${ModsFolder.currentModFolder}' : './assets'; /** @@ -196,25 +199,18 @@ class Paths // MULTIPLE SPRITESHEETS!! var graphic = FlxG.bitmap.add("flixel/images/logo/default.png", false, '$noExt/mult'); - var frames = FlxAtlasFrames.findFrame(graphic); + var frames = MultiFramesCollection.findFrame(graphic); if (frames != null) return frames; trace("no frames yet for multiple atlases!!"); - var spritesheets = []; var cur = 1; - var finalFrames = new FlxFramesCollection(graphic, ATLAS); + var finalFrames = new MultiFramesCollection(graphic); while(Assets.exists('$noExt/$cur.png')) { - spritesheets.push(loadFrames('$noExt/$cur.png')); + var spr = loadFrames('$noExt/$cur.png'); + finalFrames.addFrames(spr); cur++; } - for(frames in spritesheets) - if (frames != null && frames.frames != null) - for(f in frames.frames) - if (f != null) { - finalFrames.frames.push(f); - f.parent = frames.parent; - } return finalFrames; } else if (Assets.exists('$noExt.xml')) { return Paths.getSparrowAtlasAlt(noExt); diff --git a/source/funkin/backend/assets/ScriptedAssetLibrary.hx b/source/funkin/backend/assets/ScriptedAssetLibrary.hx index 972f74e2..0fd8d380 100644 --- a/source/funkin/backend/assets/ScriptedAssetLibrary.hx +++ b/source/funkin/backend/assets/ScriptedAssetLibrary.hx @@ -21,9 +21,9 @@ class ScriptedAssetLibrary extends ModsFolderLibrary { public var scriptName:String; private static var nullValue:Dynamic = {}; - public function new(scriptName:String, args:Array = null, folderPath:String="./assets/", libName:String="assets") { - super(folderPath, libName); - + public function new(scriptName:String, args:Array = null, folderPath:String="./assets/", libName:String="assets", ?modName:String) { + if(modName == null) modName = scriptName; + super(folderPath, libName, modName); this.scriptName = scriptName; script = Script.create(Paths.script("data/library/" + scriptName)); script.setParent(this); @@ -107,6 +107,14 @@ class ScriptedAssetLibrary extends ModsFolderLibrary { return super.getAssetPath(); } + public override function list(type:String) { + var result:Dynamic = script.call("list", [type]); + if(result != null) { + return result == nullValue ? null : result; + } + return super.list(type); + } + private override function __isCacheValid(cache:Map, asset:String, isLocalCache:Bool = false) { var result:Dynamic = script.call("__isCacheValid", [cache, asset, isLocalCache]); if(result != null) { diff --git a/source/funkin/backend/assets/ZipFolderLibrary.hx b/source/funkin/backend/assets/ZipFolderLibrary.hx index 7465482e..21e425d8 100644 --- a/source/funkin/backend/assets/ZipFolderLibrary.hx +++ b/source/funkin/backend/assets/ZipFolderLibrary.hx @@ -27,6 +27,7 @@ import funkin.backend.utils.SysZip.SysZipEntry; class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { public var zipPath:String; + public var modName:String; public var libName:String; public var useImageCache:Bool = false; public var prefix = 'assets/'; @@ -34,10 +35,15 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { public var zip:SysZip; public var assets:Map = []; - public function new(zipPath:String, libName:String) { + public function new(zipPath:String, libName:String, ?modName:String) { this.zipPath = zipPath; this.libName = libName; + if(modName == null) + this.modName = libName; + else + this.modName = modName; + zip = SysZip.openFromFile(zipPath); zip.read(); for(entry in zip.entries) @@ -77,7 +83,18 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { public function __parseAsset(asset:String):Bool { if (!asset.startsWith(prefix)) return false; - _parsedAsset = asset.substr(prefix.length).toLowerCase(); + _parsedAsset = asset.substr(prefix.length); + if(ModsFolder.useLibFile) { + var file = new haxe.io.Path(_parsedAsset); + if(file.file.startsWith("LIB_")) { + var library = file.file.substr(4); + if(library != modName) return false; + + _parsedAsset = file.dir + "." + file.ext; + } + } + + _parsedAsset = _parsedAsset.toLowerCase(); return true; } diff --git a/source/funkin/backend/chart/BaseGameParser.hx b/source/funkin/backend/chart/BaseGameParser.hx index b8d2d6b9..71e707cc 100644 --- a/source/funkin/backend/chart/BaseGameParser.hx +++ b/source/funkin/backend/chart/BaseGameParser.hx @@ -69,12 +69,13 @@ class BaseGameParser { var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection; if (note.length > 2) { - if (note[3] is Int) + if (note[3] is Int && data.noteTypes != null) daNoteType = Chart.addNoteType(result, data.noteTypes[Std.int(note[3])-1]); else if (note[3] is String) daNoteType = Chart.addNoteType(result, note[3]); } else { - daNoteType = Chart.addNoteType(result, data.noteTypes[daNoteType-1]); + if(data.noteTypes != null) + daNoteType = Chart.addNoteType(result, data.noteTypes[daNoteType-1]); } result.strumLines[gottaHitNote ? 1 : 0].notes.push({ diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index 0c8f0a97..2cb50557 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -42,15 +42,21 @@ class EventsData { hscriptParser.allowJSON = hscriptParser.allowMetadata = false; for (file in Paths.getFolderContent('data/events/', true, BOTH)) { - if (Path.extension(file) != "json") continue; + if (Path.extension(file) != "json" && Path.extension(file) != "pack") continue; var eventName:String = Path.withoutExtension(Path.withoutDirectory(file)); + var fileTxt:String = Assets.getText(file); + + if (Path.extension(file) == "pack") { + var arr = fileTxt.split("________PACKSEP________"); + eventName = Path.withoutExtension(arr[0]); + fileTxt = arr[2]; + } + + if (fileTxt.trim() == "") continue; eventsList.push(eventName); eventsParams.set(eventName, []); - var fileTxt:String = Assets.getText(file); - if (fileTxt.trim() == "") continue; - try { var data:Dynamic = Json.parse(fileTxt); if (data == null || data.params == null) continue; diff --git a/source/funkin/backend/scripting/GlobalScript.hx b/source/funkin/backend/scripting/GlobalScript.hx index 14803c8b..dbca645c 100644 --- a/source/funkin/backend/scripting/GlobalScript.hx +++ b/source/funkin/backend/scripting/GlobalScript.hx @@ -9,7 +9,7 @@ import funkin.backend.assets.ModsFolder; * Class for THE Global Script, aka script that runs in the background at all times. */ class GlobalScript { - public static var script:Script; + public static var scripts:ScriptPack; public static function init() { #if MOD_SUPPORT @@ -43,19 +43,17 @@ class GlobalScript { FlxG.signals.postUpdate.add(function() { call("postUpdate", [FlxG.elapsed]); if (FlxG.keys.justPressed.F5) { - if (script != null && !(script is DummyScript)) { + if (scripts.scripts.length > 0) { Logs.trace('Reloading global script...', WARNING, YELLOW); - script.reload(); + scripts.reload(); Logs.trace('Global script successfully reloaded.', WARNING, GREEN); } else { Logs.trace('Loading global script...', WARNING, YELLOW); onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end); - if (script is DummyScript) - Logs.trace('Global script not found. Are you sure "data/global.hx" exists?', ERROR, RED); - else - Logs.trace('Global script successfully loaded.', WARNING, GREEN); } } + if (FlxG.keys.justPressed.F2) + NativeAPI.allocConsole(); }); FlxG.signals.preDraw.add(function() { call("preDraw"); @@ -81,23 +79,25 @@ class GlobalScript { } public static function event(name:String, event:T):T { - if (script == null) return event; - script.call(name, [event]); + if (scripts != null) scripts.call(name, [event]); return event; } public static function call(name:String, ?args:Array) { - if (script == null) return; - script.call(name, args); + if (scripts != null) scripts.call(name, args); } public static function onModSwitch(newMod:String) { call("onDestroy"); - if (script != null) { - script.destroy(); - script = null; + scripts = FlxDestroyUtil.destroy(scripts); + scripts = new ScriptPack("GlobalScript"); + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var path = Paths.script('data/global/LIB_$i'); + var script = Script.create(path); + if (script is DummyScript) continue; + script.fileName = '$i:global.hx'; + scripts.add(script); + script.load(); } - script = Script.create(Paths.script('data/global')); - script.load(); } public static function beatHit(curBeat:Int) { diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index 158012a6..adb829a6 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -29,7 +29,7 @@ class HScript extends Script { interp = new Interp(); - code = Assets.getText(path); + code = Assets.exists(path) ? Assets.getText(path) : null; parser = initParser(); folderlessPath = Path.directory(path); __importedPaths = [path]; @@ -45,6 +45,13 @@ class HScript extends Script { this.trace(v); })); + funkin.backend.scripting.GlobalScript.call("onScriptCreated", [this, "hscript"]); + + if (code != null && code.trim() != "") + loadFromString(code); + } + + public override function loadFromString(code:String) { try { if (code != null && code.trim() != "") expr = parser.parseString(code, fileName); @@ -53,6 +60,8 @@ class HScript extends Script { } catch(e) { _errorHandler(new Error(ECustom(e.toString()), 0, 0, fileName, 0)); } + + return this; } private function importFailedCallback(cl:Array):Bool { diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx index 927e1189..bdfe59bf 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -109,6 +109,7 @@ class Script extends FlxBasic implements IFlxDestroyable { */ public static var scriptExtensions:Array = [ "hx", "hscript", "hsc", "hxs", + "pack", // combined file "lua" /** ACTUALLY NOT SUPPORTED, ONLY FOR THE MESSAGE **/ ]; @@ -138,6 +139,9 @@ class Script extends FlxBasic implements IFlxDestroyable { return switch(Path.extension(path).toLowerCase()) { case "hx" | "hscript" | "hsc" | "hxs": new HScript(path); + case "pack": + var arr = Assets.getText(path).split("________PACKSEP________"); + fromString(arr[1], arr[0]); case "lua": Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR); new DummyScript(path); @@ -148,6 +152,23 @@ class Script extends FlxBasic implements IFlxDestroyable { return new DummyScript(path); } + /** + * Creates a script from the string. The language is determined based on the path. + * @param code code + * @param path filename + */ + public static function fromString(code:String, path:String):Script { + return switch(Path.extension(path).toLowerCase()) { + case "hx" | "hscript" | "hsc" | "hxs": + new HScript(path).loadFromString(code); + case "lua": + Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR); + new DummyScript(path).loadFromString(code); + default: + new DummyScript(path).loadFromString(code); + } + } + /** * Creates a new instance of the script class. * @param path @@ -224,6 +245,14 @@ class Script extends FlxBasic implements IFlxDestroyable { return result; } + /** + * Loads the code from a string, doesnt really work after the script has been loaded + * @param code The code. + */ + public function loadFromString(code:String) { + return this; + } + /** * Sets a script's parent object so that its properties can be accessed easily. Ex: Passing `PlayState.instance` will allow `boyfriend` to be typed instead of `PlayState.instance.boyfriend`. * @param variable Parent variable. diff --git a/source/funkin/backend/system/CommandLineHandler.hx b/source/funkin/backend/system/CommandLineHandler.hx index d00af9a5..4050b049 100644 --- a/source/funkin/backend/system/CommandLineHandler.hx +++ b/source/funkin/backend/system/CommandLineHandler.hx @@ -11,13 +11,14 @@ class CommandLineHandler { break; case "-h" | "-help" | "help": Sys.println("-- Codename Engine Command Line help --"); - Sys.println("-help | Show this help"); + Sys.println("-help | Show this help"); #if MOD_SUPPORT - Sys.println("-mod [mod name] | Load a specific mod"); - Sys.println("-modfolder [path] | Sets the mod folder path"); + Sys.println("-mod [mod name] | Load a specific mod"); + Sys.println("-modfolder [path] | Sets the mod folder path"); + Sys.println("-addonsfolder [path] | Sets the addons folder path"); #end - Sys.println("-nocolor | Disables colors in the terminal"); - Sys.println("-nogpubitmap | Forces GPU only bitmaps off"); + Sys.println("-nocolor | Disables colors in the terminal"); + Sys.println("-nogpubitmap | Forces GPU only bitmaps off"); Sys.exit(0); #if MOD_SUPPORT case "-m" | "-mod" | "-currentmod": @@ -25,7 +26,7 @@ class CommandLineHandler { var arg = cmd[i]; if (arg == null) { Sys.println("[ERROR] You need to specify the mod name"); - Sys.exit(0); + Sys.exit(1); } else { Main.modToLoad = arg.trim(); } @@ -34,12 +35,24 @@ class CommandLineHandler { var arg = cmd[i]; if (arg == null) { Sys.println("[ERROR] You need to specify the mod folder path"); - Sys.exit(0); + Sys.exit(1); } else if (FileSystem.exists(arg)) { funkin.backend.assets.ModsFolder.modsPath = arg; } else { Sys.println('[ERROR] Mod folder at "${arg}" does not exist.'); - Sys.exit(0); + Sys.exit(1); + } + case "-addonsfolder": + i++; + var arg = cmd[i]; + if (arg == null) { + Sys.println("[ERROR] You need to specify the addon folder path"); + Sys.exit(1); + } else if (FileSystem.exists(arg)) { + funkin.backend.assets.ModsFolder.addonsPath = arg; + } else { + Sys.println('[ERROR] Addons folder at "${arg}" does not exist.'); + Sys.exit(1); } #end case "-nocolor": diff --git a/source/funkin/backend/system/GraphicCacheSprite.hx b/source/funkin/backend/system/GraphicCacheSprite.hx index 4f0fe98f..fd03bdc4 100644 --- a/source/funkin/backend/system/GraphicCacheSprite.hx +++ b/source/funkin/backend/system/GraphicCacheSprite.hx @@ -25,14 +25,21 @@ class GraphicCacheSprite extends FlxSprite { * @param path Path to the graphic. */ public function cache(path:String) { - var graphic = FlxG.bitmap.add(path); - if (graphic != null) { - // make their useCount one time higher to prevent them from auto being cleared from cache - graphic.useCount++; - graphic.destroyOnNoUse = false; - cachedGraphics.push(graphic); - nonRenderedCachedGraphics.push(graphic); - } + cacheGraphic(FlxG.bitmap.add(path)); + } + + /** + * Caches a graphic. + * @param graphic The FlxGraphic + */ + public function cacheGraphic(graphic:FlxGraphic) { + if (graphic == null) return; + + // make their useCount one time higher to prevent them from auto being cleared from cache + graphic.useCount++; + graphic.destroyOnNoUse = false; + cachedGraphics.push(graphic); + nonRenderedCachedGraphics.push(graphic); } public override function destroy() { diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx index fbe59ac1..79ef9adc 100644 --- a/source/funkin/backend/system/Main.hx +++ b/source/funkin/backend/system/Main.hx @@ -1,5 +1,6 @@ package funkin.backend.system; +import funkin.editors.SaveWarning; import funkin.backend.assets.AssetsLibraryList; import funkin.backend.system.framerate.SystemInfo; import openfl.utils.AssetLibrary; @@ -107,7 +108,8 @@ class Main extends Sprite } public static function loadGameSettings() { - WindowTitle.init(); + WindowUtils.init(); + SaveWarning.init(); MemoryUtil.init(); @:privateAccess FlxG.game.getTimer = getTimer; @@ -134,6 +136,7 @@ class Main extends Sprite trace("Used cne test / cne build. Switching into source assets."); #if MOD_SUPPORT ModsFolder.modsPath = './${pathBack}mods/'; + ModsFolder.addonsPath = './${pathBack}addons/'; #end Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './${pathBack}assets/', true)); #elseif USE_ADAPTED_ASSETS diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index ba67513d..f2284350 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -33,11 +33,11 @@ class MainState extends FlxState { #if MOD_SUPPORT if (ModsFolder.currentModFolder != null) - Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.modsPath}${ModsFolder.currentModFolder}')); + Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.modsPath}${ModsFolder.currentModFolder}', ModsFolder.currentModFolder)); if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)) for(addon in [for(dir in FileSystem.readDirectory(ModsFolder.addonsPath)) if (FileSystem.isDirectory('${ModsFolder.addonsPath}$dir')) dir]) - Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon')); + Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon', addon)); #end Main.refreshAssets(); diff --git a/source/funkin/backend/system/framerate/AssetTreeInfo.hx b/source/funkin/backend/system/framerate/AssetTreeInfo.hx index 3f8c0388..b886abb4 100644 --- a/source/funkin/backend/system/framerate/AssetTreeInfo.hx +++ b/source/funkin/backend/system/framerate/AssetTreeInfo.hx @@ -22,9 +22,9 @@ class AssetTreeInfo extends FramerateCategory { } if (l is ScriptedAssetLibrary) - text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, ScriptedAssetLibrary).scriptName} (${cast(l, ScriptedAssetLibrary).libName} | ${cast(l, ScriptedAssetLibrary).prefix})\n'; + text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, ScriptedAssetLibrary).scriptName} (${cast(l, ScriptedAssetLibrary).modName} | ${cast(l, ScriptedAssetLibrary).libName} | ${cast(l, ScriptedAssetLibrary).prefix})\n'; else if (l is IModsAssetLibrary) - text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, IModsAssetLibrary).libName} (${cast(l, IModsAssetLibrary).prefix})\n'; + text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, IModsAssetLibrary).modName} - ${cast(l, IModsAssetLibrary).libName} (${cast(l, IModsAssetLibrary).prefix})\n'; else text += Std.string(e) + "\n"; } diff --git a/source/funkin/backend/system/macros/ScriptsMacro.hx b/source/funkin/backend/system/macros/ScriptsMacro.hx index db0f9f55..013d58b6 100644 --- a/source/funkin/backend/system/macros/ScriptsMacro.hx +++ b/source/funkin/backend/system/macros/ScriptsMacro.hx @@ -21,7 +21,7 @@ class ScriptsMacro { "flixel.addons.util", // OTHER LIBRARIES & STUFF "away3d", "flx3d", - #if VIDEO_CUTSCENES "hxcodec.flixel", "hxcodec.openfl", "hxcodec.lime", #end + #if VIDEO_CUTSCENES "hxvlc.flixel", "hxvlc.openfl", #end // BASE HAXE "DateTools", "EReg", "Lambda", "StringBuf", "haxe.crypto", "haxe.display", "haxe.exceptions", "haxe.extern", "scripting" ]) @@ -38,10 +38,8 @@ class ScriptsMacro { Compiler.include("funkin", #if !UPDATE_CHECKING ['funkin.backend.system.updating'] #end); - // FOR ABSTRACTS - Compiler.addGlobalMetadata('haxe.xml', '@:build(hscript.UsingHandler.build())'); - Compiler.addGlobalMetadata('haxe.CallStack', '@:build(hscript.UsingHandler.build())'); #if HL HashLinkFixer.init(); #end + // Todo rewrite this to use if(Context.defined("")) } } #end \ No newline at end of file diff --git a/source/funkin/backend/utils/WindowTitle.hx b/source/funkin/backend/utils/WindowUtils.hx similarity index 58% rename from source/funkin/backend/utils/WindowTitle.hx rename to source/funkin/backend/utils/WindowUtils.hx index 239efa4f..609e3977 100644 --- a/source/funkin/backend/utils/WindowTitle.hx +++ b/source/funkin/backend/utils/WindowUtils.hx @@ -2,7 +2,7 @@ package funkin.backend.utils; import openfl.Lib; -class WindowTitle { +class WindowUtils { public static var winTitle(default, set):String; public static function set_winTitle(newWinTitle:String):String { winTitle = newWinTitle; @@ -22,10 +22,26 @@ class WindowTitle { return endPrefix; } - public static inline function init() - reset(); + public static var preventClosing:Bool = true; + public static var onClosing:Void->Void; - public static inline function reset() { + static var __triedClosing:Bool = false; + public static inline function resetClosing() __triedClosing = false; + + public static inline function init() { + resetTitle(); + resetClosing(); + + Lib.application.window.onClose.add(function () { + if (preventClosing && !__triedClosing) { + Lib.application.window.onClose.cancel(); + __triedClosing = true; + } + if (onClosing != null) onClosing(); + }); + } + + public static inline function resetTitle() { winTitle = Lib.application.meta["name"]; prefix = endfix = ""; updateTitle(); diff --git a/source/funkin/editors/SaveWarning.hx b/source/funkin/editors/SaveWarning.hx new file mode 100644 index 00000000..73b465ac --- /dev/null +++ b/source/funkin/editors/SaveWarning.hx @@ -0,0 +1,46 @@ +package funkin.editors; + +class SaveWarning { + public static var showWarning(default, set):Bool; + public static function set_showWarning(warning:Bool):Bool + return WindowUtils.preventClosing = showWarning = warning; + + public static var selectionClass:Class = null; + + public static function init() { + showWarning = false; + + WindowUtils.onClosing = function () { + if (SaveWarning.showWarning) triggerWarning(true); + } + } + + public static function triggerWarning(?closingWindow:Bool = false) { + if (FlxG.state != null && FlxG.state is UIState) { + FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes!", "Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?", + [{ + label: closingWindow ? "Exit Window" : "Exit To Menu", + color: 0x74732D, + onClick: function(_) { + if (!closingWindow) { + if (selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, [])); + if (closingWindow) WindowUtils.resetClosing(); + } else { + WindowUtils.preventClosing = false; WindowUtils.resetClosing(); + Sys.exit(0); + } + } + }, + { + label: "Cancel", + color: 0x969533, + onClick: function (_) {if (closingWindow) WindowUtils.resetClosing();} + }], false)); + } + } + + public static inline function reset() { + SaveWarning.showWarning = false; + SaveWarning.selectionClass = null; + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimButtons.hx b/source/funkin/editors/character/CharacterAnimButtons.hx index e0ebd3b9..bccc51a4 100644 --- a/source/funkin/editors/character/CharacterAnimButtons.hx +++ b/source/funkin/editors/character/CharacterAnimButtons.hx @@ -39,6 +39,7 @@ class CharacterAnimButtons extends UIButton { editButton = new UIButton(ghostButton.x+32+17, y, "", function () { CharacterEditor.instance.editAnimWithUI(this.anim); }, 32); + editButton.frames = Paths.getFrames("editors/ui/grayscale-button"); editButton.color = FlxColor.YELLOW; editButton.autoAlpha = false; members.push(editButton); diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index 970288ff..287b4a44 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -11,7 +11,7 @@ import flixel.input.keyboard.FlxKey; import funkin.game.Character; class CharacterEditor extends UIState { - var __character:String; + static var __character:String; public var character:Character; public var ghosts:CharacterGhostsHandler; @@ -45,12 +45,14 @@ class CharacterEditor extends UIState { public function new(character:String) { super(); - __character = character; + if (character != null) __character = character; } public override function create() { super.create(); + WindowUtils.endfix = " (Character Editor)"; + SaveWarning.selectionClass = CharacterSelection; topMenu = [ { label: "File", @@ -309,12 +311,16 @@ class CharacterEditor extends UIState { characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height); characterBG.scale.set(characterBG.scale.x / charCamera.zoom, characterBG.scale.y / charCamera.zoom); + + WindowUtils.prefix = undos.unsaved ? "* " : ""; + SaveWarning.showWarning = undos.unsaved; } // TOP MENU OPTIONS #if REGION function _file_exit(_) { - FlxG.switchState(new CharacterSelection()); + if (undos.unsaved) SaveWarning.triggerWarning(); + else FlxG.switchState(new CharacterSelection()); } function _file_new(_) { @@ -323,9 +329,10 @@ class CharacterEditor extends UIState { function _file_save(_) { #if sys sys.io.File.saveContent( - Assets.getPath(Paths.xml('characters/${character.curCharacter}')), + '${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml', buildCharacter() ); + undos.save(); return; #end _file_saveas(_); @@ -335,6 +342,7 @@ class CharacterEditor extends UIState { openSubState(new SaveSubstate(buildCharacter(), { defaultSaveFile: '${character.curCharacter}.xml' })); + undos.save(); } function buildCharacter():String { diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index ae5e6635..9d1a60ed 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -27,13 +27,8 @@ class CharacterSelection extends EditorTreeMenu ]; list.insert(0, new NewOption("New Character", "New Character", function() { - openSubState(new UIWarningSubstate("New Character", "This feature isnt implemented yet", [ - { - label: "OK", - onClick: function(t) { - - } - } + openSubState(new UIWarningSubstate("New Character: Feature Not Implemented!", "This feature isnt implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} ])); })); diff --git a/source/funkin/editors/charter/ChartDataScreen.hx b/source/funkin/editors/charter/ChartDataScreen.hx index 175a51c4..d05f5d0f 100644 --- a/source/funkin/editors/charter/ChartDataScreen.hx +++ b/source/funkin/editors/charter/ChartDataScreen.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import funkin.game.Stage; import funkin.backend.chart.ChartData; import flixel.math.FlxPoint; import funkin.backend.chart.ChartData.ChartMetaData; @@ -12,7 +13,7 @@ class ChartDataScreen extends UISubstateWindow { public var closeButton:UIButton; public var scrollSpeedStepper:UINumericStepper; - public var stageTextBox:UITextBox; + public var stageTextBox:UIAutoCompleteTextBox; public function new(data:ChartData) { super(); @@ -34,7 +35,11 @@ class ChartDataScreen extends UISubstateWindow { var title:UIText; add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Chart Data", 28)); - stageTextBox = new UITextBox(title.x, title.y + title.height + 38, PlayState.SONG.stage, 200); + var stageFileList = Stage.getList(true); + if (stageFileList.length == 0) stageFileList = Stage.getList(false); + + stageTextBox = new UIAutoCompleteTextBox(title.x, title.y + title.height + 38, PlayState.SONG.stage, 200); + stageTextBox.suggestItems = stageFileList; add(stageTextBox); addLabelOn(stageTextBox, "Stage"); diff --git a/source/funkin/editors/charter/ChartHoverNote.hx b/source/funkin/editors/charter/ChartHoverNote.hx deleted file mode 100644 index d3eee237..00000000 --- a/source/funkin/editors/charter/ChartHoverNote.hx +++ /dev/null @@ -1,66 +0,0 @@ -package funkin.editors.charter; - -import flixel.tweens.misc.VarTween; -import flixel.tweens.FlxTween; - -class ChartHoverNote extends UISprite { - public var angleTween:FlxTween; - - public var onGrid:Bool = false; - public var id:Int = 0; - public var nextID:Int = 0; - - public function new() { - super(); - antialiasing = true; - loadGraphic(Paths.image('editors/charter/note'), true, 157, 154); - animation.add("note", [for(i in 0...frames.frames.length) i], 0, true); - animation.play("note"); - this.setUnstretchedGraphicSize(40, 40, false); - - moves = selectable = false; - colorTransform.color = 0xFFFFFF; - } - - public override function update(elapsed:Float) { - super.update(elapsed); - - var mousePos = FlxG.mouse.getWorldPosition(__lastDrawCameras[0]); - alpha = FlxMath.lerp(alpha, onGrid ? 0.2 : 0, 1/8); - id = Math.floor(x / 40); - - if (onGrid) { - Charter.instance.gridBackdropDummy.cursor = BUTTON; - x = (nextID = Math.floor(mousePos.x / 40)) * 40; - y = (FlxG.keys.pressed.SHIFT ? (mousePos.y / 40) : Math.floor(mousePos.y / 40)) * 40; - - if (id != nextID) { - if (angleTween != null) angleTween.cancel(); - - angle = switch(id % 4) { - case 0: -90; - case 1: 180; - case 2: 0; - case 3: 90; - default: 0; // how is that even possible - }; - - var destAngle = switch((nextID % 4)) { - case 0: -90; - case 1: 180; - case 2: 0; - case 3: 90; - default: 0; // how is that even possible - }; - - angleTween = FlxTween.tween(this, {angle: destAngle}, 2/3, {ease: function(t) { - return ((Math.sin(t * Math.PI) * 0.35) * 3 * t * Math.sqrt(1 - t)) + t; - }, onComplete: function (t) {angleTween = null;}}); - } - } else if (alpha > 0) { - Charter.instance.gridBackdropDummy.cursor = ARROW; - x = mousePos.x - 20; y = mousePos.y -20; - } - mousePos.put(); - } -} \ No newline at end of file diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 57dd195c..e04119e8 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1,12 +1,15 @@ package funkin.editors.charter; +import funkin.editors.ui.UITopMenu.UITopMenuButton; +import funkin.editors.charter.CharterStrumline; +import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; import funkin.backend.system.framerate.Framerate; import haxe.Json; import flixel.input.keyboard.FlxKey; import flixel.sound.FlxSound; import flixel.util.FlxSort; import flixel.math.FlxPoint; -import funkin.editors.charter.CharterBackdrop.CharterBackdropDummy; +import funkin.editors.charter.CharterBackdropGroup.CharterBackdropDummy; import funkin.backend.system.Conductor; import funkin.backend.chart.*; import funkin.backend.chart.ChartData; @@ -18,9 +21,9 @@ import funkin.editors.ui.UIState; import openfl.net.FileReference; class Charter extends UIState { - var __song:String; - var __diff:String; - var __reload:Bool; + static var __song:String; + static var __diff:String; + static var __reload:Bool; var chart(get, null):ChartData; private function get_chart() @@ -43,22 +46,17 @@ class Charter extends UIState { public var scrollBar:UIScrollBar; public var songPosInfo:UIText; + public var playBackSlider:UISlider; public var topMenuSpr:UITopMenu; - public var gridBackdrop:CharterBackdrop; - public var eventsBackdrop:FlxBackdrop; + public var gridBackdrops:CharterBackdropGroup; + public var eventsBackdrop:EventBackdrop; public var addEventSpr:CharterEventAdd; - public var beatSeparator:FlxBackdrop; - public var sectionSeparator:FlxBackdrop; public var gridBackdropDummy:CharterBackdropDummy; - public var conductorFollowerSpr:FlxSprite; - public var topLimit:FlxSprite; - public var bottomLimit:FlxSprite; - public var bottomSeparator:FlxSprite; - public var hoverNote:ChartHoverNote; public var strumlineInfoBG:FlxSprite; - public var strumlineAddButton:CharterStrumlineExtraButton; + public var strumlineAddButton:CharterStrumlineButton; + public var strumlineLockButton:CharterStrumlineButton; public var hitsound:FlxSound; public var metronome:FlxSound; @@ -68,7 +66,7 @@ class Charter extends UIState { /** * ACTUAL CHART DATA */ - public var strumLines:FlxTypedGroup = new FlxTypedGroup(); + public var strumLines:CharterStrumLineGroup = new CharterStrumLineGroup(); public var notesGroup:CharterNoteGroup = new CharterNoteGroup(); public var eventsGroup:CharterEventGroup = new CharterEventGroup(); @@ -89,15 +87,18 @@ class Charter extends UIState { public function new(song:String, diff:String, reload:Bool = true) { super(); - __song = song; - __diff = diff; - __reload = reload; + if (song != null) { + __song = song; + __diff = diff; + __reload = reload; + } } public override function create() { super.create(); - WindowTitle.endfix = " (Chart Editor)"; + WindowUtils.endfix = " (Chart Editor)"; + SaveWarning.selectionClass = CharterSelection; topMenu = [ { label: "File", @@ -232,7 +233,7 @@ class Charter extends UIState { onSelect: _note_selectall }, { - label: "Select maeasure", + label: "Select measure", keybind: [CONTROL, SHIFT, A], onSelect: _note_selectmeasure }, @@ -268,12 +269,12 @@ class Charter extends UIState { null, { label: 'Show Sections Separator', - onSelect: _view_showsectionseparator, + onSelect: _view_showeventSecSeparator, icon: Options.charterShowSections ? 1 : 0 }, { label: 'Show Beats Separator', - onSelect: _view_showbeatseparator, + onSelect: _view_showeventBeatSeparator, icon: Options.charterShowBeats ? 1 : 0 } ] @@ -345,43 +346,21 @@ class Charter extends UIState { charterBG.scrollFactor.set(); add(charterBG); - gridBackdrop = new CharterBackdrop(); + gridBackdrops = new CharterBackdropGroup(strumLines); + gridBackdrops.notesGroup = this.notesGroup; - eventsBackdrop = new FlxBackdrop(Paths.image('editors/charter/events-grid'), Y, 0, 0); + eventsBackdrop = new EventBackdrop(); eventsBackdrop.x = -eventsBackdrop.width; - eventsBackdrop.alpha = 0.9; eventsBackdrop.cameras = [charterCamera]; - add(eventsBackdrop); - - hoverNote = new ChartHoverNote(); - hoverNote.cameras = [charterCamera]; - - add(gridBackdropDummy = new CharterBackdropDummy(gridBackdrop)); - sectionSeparator = new FlxBackdrop(null, Y, 0, 0); - sectionSeparator.x = -20; - sectionSeparator.y = -2; - sectionSeparator.visible = Options.charterShowSections; - beatSeparator = new FlxBackdrop(null, Y, 0, 0); - beatSeparator.x = -10; - beatSeparator.y = -1; - sectionSeparator.visible = Options.charterShowBeats; - for(sep in [sectionSeparator, beatSeparator]) { - sep.makeGraphic(1, 1, -1); - sep.alpha = 0.5; - sep.scrollFactor.set(1, 1); - sep.cameras = [charterCamera]; - } + eventsGroup.eventsBackdrop = eventsBackdrop; + add(gridBackdropDummy = new CharterBackdropDummy(gridBackdrops)); selectionBox = new UISliceSprite(0, 0, 2, 2, 'editors/ui/selection'); selectionBox.visible = false; selectionBox.scrollFactor.set(1, 1); selectionBox.incorporeal = true; - conductorFollowerSpr = new FlxSprite(0, 0).makeGraphic(1, 1, -1); - conductorFollowerSpr.scale.set(gridBackdrop.strumlinesAmount * 4 * 40, 4); - conductorFollowerSpr.updateHitbox(); - - conductorFollowerSpr.cameras = selectionBox.cameras = notesGroup.cameras = gridBackdrop.cameras = [charterCamera]; + selectionBox.cameras = notesGroup.cameras = gridBackdrops.cameras = [charterCamera]; topMenuSpr = new UITopMenu(topMenu); topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; @@ -390,42 +369,34 @@ class Charter extends UIState { scrollBar.cameras = [uiCamera]; scrollBar.onChange = function(v) { if (!FlxG.sound.music.playing) - Conductor.songPosition = Conductor.getTimeForStep(v); + Conductor.songPosition = Conductor.getTimeForStep(v) + Conductor.songOffset; }; uiGroup.add(scrollBar); songPosInfo = new UIText(FlxG.width - 30 - 400, scrollBar.y + 10, 400, "00:00\nBeat: 0\nStep: 0\nMeasure: 0\nBPM: 0\nTime Signature: 4/4"); - songPosInfo.alignment = RIGHT; + songPosInfo.alignment = RIGHT; songPosInfo.optimized = true; uiGroup.add(songPosInfo); - topLimit = new FlxSprite(); - topLimit.makeGraphic(1, 1, -1); - topLimit.color = 0xFF888888; - topLimit.blend = MULTIPLY; - - bottomLimit = new FlxSprite(); - bottomLimit.makeGraphic(1, 1, -1); - bottomLimit.color = 0xFF888888; - bottomLimit.blend = MULTIPLY; - - bottomSeparator = new FlxSprite(); - bottomSeparator.makeGraphic(1, 1, -1); - bottomSeparator.alpha = 0.5; - bottomSeparator.scrollFactor.set(1, 1); - bottomSeparator.cameras = [charterCamera]; - - bottomSeparator.x = -20; - bottomSeparator.y = -2; + playBackSlider = new UISlider(FlxG.width - 160 - 26 - 20, (23/2) - (12/2), 160, 1, [{start: 0.25, end: 1, size: 0.5}, {start: 1, end: 2, size: 0.5}], true); + playBackSlider.onChange = function (v) { + FlxG.sound.music.pitch = vocals.pitch = v; + }; + uiGroup.add(playBackSlider); strumlineInfoBG = new UISprite(); strumlineInfoBG.loadGraphic(Paths.image('editors/charter/strumline-info-bg')); strumlineInfoBG.y = 23; strumlineInfoBG.scrollFactor.set(); - strumlineAddButton = new CharterStrumlineExtraButton("editors/charter/add-strumline", "Add New"); + strumlineAddButton = new CharterStrumlineButton("editors/charter/add-strumline", "Create New"); strumlineAddButton.onClick = createStrumWithUI; - strumlineAddButton.cameras = [charterCamera]; + strumlineLockButton = new CharterStrumlineButton("editors/charter/lock-strumline", "Lock/Unlock"); + strumlineLockButton.onClick = function () { + if (strumLines != null) strumLines.draggable = !strumLines.draggable; + }; + + strumlineAddButton.cameras = strumlineLockButton.cameras = [charterCamera]; strumlineInfoBG.cameras = [charterCamera]; strumLines.cameras = [charterCamera]; @@ -434,19 +405,14 @@ class Charter extends UIState { addEventSpr.cameras = [charterCamera]; // adds grid and notes so that they're ALWAYS behind the UI - add(gridBackdrop); - add(hoverNote); - add(sectionSeparator); - add(beatSeparator); + add(gridBackdrops); + add(eventsBackdrop); add(addEventSpr); add(eventsGroup); add(notesGroup); - add(topLimit); - add(bottomLimit); - add(bottomSeparator); - add(conductorFollowerSpr); add(selectionBox); add(strumlineInfoBG); + add(strumlineLockButton); add(strumlineAddButton); add(strumLines); // add the top menu last OUT of the ui group so that it stays on top @@ -461,6 +427,7 @@ class Charter extends UIState { Framerate.memoryCounter.alpha = 0.4; Framerate.codenameBuildField.alpha = 0.4; } + updateDisplaySprites(); } override function destroy() { @@ -469,7 +436,6 @@ class Charter extends UIState { Framerate.memoryCounter.alpha = 1; Framerate.codenameBuildField.alpha = 1; } - WindowTitle.reset(); super.destroy(); } @@ -485,11 +451,30 @@ class Charter extends UIState { vocals = FlxG.sound.load(Paths.voices(__song, __diff)); vocals.group = FlxG.sound.defaultMusicGroup; - trace("generating notes..."); - for(strL in PlayState.SONG.strumLines) - createStrumline(strumLines.members.length, strL, false); + gridBackdrops.createGrids(PlayState.SONG.strumLines.length); - trace("generating events..."); + for(strL in PlayState.SONG.strumLines) + createStrumline(strumLines.members.length, strL, false, false); + + // create notes + notesGroup.autoSort = false; + var noteCount:Int = 0; + for (strL in PlayState.SONG.strumLines) + for (note in strL.notes) noteCount++; + notesGroup.preallocate(noteCount); + + var notesCreated:Int = 0; + for (i => strL in PlayState.SONG.strumLines) + for (note in strL.notes) { + var n = new CharterNote(); + var t = Conductor.getStepForTime(note.time); + n.updatePos(t, note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type, strumLines.members[i]); + notesGroup.members[notesCreated++] = n; + } + notesGroup.sortNotes(); + notesGroup.autoSort = true; + + // create events var __last:CharterEvent = null; var __lastTime:Float = Math.NaN; for(e in PlayState.SONG.events) { @@ -509,13 +494,14 @@ class Charter extends UIState { refreshBPMSensitive(); } + public var __endStep:Float = 0; public function refreshBPMSensitive() { // refreshes everything dependant on BPM, and BPM changes var length = FlxG.sound.music.getDefault(vocals).length; - scrollBar.length = Conductor.getStepForTime(length); + scrollBar.length = __endStep = Conductor.getStepForTime(length); - bottomLimit.y = Conductor.getStepForTime(length) * 40; - bottomSeparator.y = bottomLimit.y-2; + gridBackdrops.bottomLimitY = Conductor.getStepForTime(length) * 40; + eventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; } public override function beatHit(curBeat:Int) { @@ -532,6 +518,7 @@ class Charter extends UIState { #if REGION var gridActionType:CharterGridActionType = NONE; var dragStartPos:FlxPoint = new FlxPoint(); + var selectionDragging:Bool = false; public function updateNoteLogic(elapsed:Float) { for (group in [notesGroup, eventsGroup]) { @@ -564,7 +551,6 @@ class Charter extends UIState { if (!gridBackdropDummy.hoveredByChild && !FlxG.mouse.pressed) gridActionType = NONE; selectionBox.visible = false; - hoverNote.onGrid = false; switch(gridActionType) { case BOX: if (gridBackdropDummy.hoveredByChild) { @@ -600,12 +586,18 @@ class Charter extends UIState { if (!FlxG.mouse.pressed) gridActionType = NONE; case DRAG: - if (FlxG.mouse.pressed) { + selectionDragging = FlxG.mouse.pressed; + if (selectionDragging) { + gridBackdrops.draggingObj = null; selection.loop(function (n:CharterNote) { - n.setPosition(n.id * 40 + (mousePos.x - dragStartPos.x), n.step * 40 + (mousePos.y - dragStartPos.y)); + n.snappedToStrumline = false; + n.setPosition(n.fullID * 40 + (mousePos.x - dragStartPos.x), n.step * 40 + (mousePos.y - dragStartPos.y)); + n.y = FlxMath.bound(n.y, 0, (__endStep*40) - n.height); + n.x = FlxMath.bound(n.x, 0, ((strumLines.members.length * 4)-1) * 40); n.cursor = HAND; }, function (e:CharterEvent) { e.y = e.step * 40 + (mousePos.y - dragStartPos.y) - 17; + e.y = FlxMath.bound(e.y, -17, (__endStep*40)-17); e.cursor = HAND; }); currentCursor = HAND; @@ -623,14 +615,30 @@ class Charter extends UIState { : CoolUtil.floorInt((mousePos.y - dragStartPos.y) / 40); var horizontalChange:Int = CoolUtil.floorInt((mousePos.x - dragStartPos.x) / 40); var changePoint:FlxPoint = FlxPoint.get(verticalChange, horizontalChange); + var undoDrags:Array = []; for (s in selection) { - if (!s.draggable) continue; - s.handleDrag(changePoint); + if (s.draggable) { + var boundedChange:FlxPoint = changePoint.clone(); + + // Some maths, so cool bro -lunar (i dont know why i quopte my self here) + if (s.step + changePoint.x < 0) boundedChange.x += Math.abs(s.step + changePoint.x); + if (s.step + changePoint.x > __endStep-1) boundedChange.x -= (s.step + changePoint.x) - (__endStep-1); + + if (s is CharterNote) { + var note:CharterNote = cast (s, CharterNote); + if (note.fullID + changePoint.y < 0) boundedChange.y += Math.abs(note.fullID + changePoint.y); + if (note.fullID + changePoint.y > (strumLines.members.length*4)-1) boundedChange.y -= (note.fullID + changePoint.y) - ((strumLines.members.length*4)-1); + } + + s.handleDrag(changePoint); + undoDrags.push({selectable:s, change: boundedChange}); + } + + if (s is CharterNote) cast(s, CharterNote).snappedToStrumline = true; if (s is UISprite) cast(s, UISprite).cursor = BUTTON; } - sortNotes(); - undos.addToUndo(CSelectionDrag(selection, changePoint.clone())); + if (!(changePoint.x == 0 && changePoint.y == 0)) undos.addToUndo(CSelectionDrag(undoDrags)); changePoint.put(); hoverOffset.put(); @@ -643,34 +651,27 @@ class Charter extends UIState { if (FlxG.mouse.justPressed) FlxG.mouse.getWorldPosition(charterCamera, dragStartPos); - hoverNote.visible = hoveredSprite != null ? hoveredSprite is CharterNote : true; - if (gridBackdropDummy.hovered) { // AUTO DETECT if (FlxG.mouse.pressed && (Math.abs(mousePos.x - dragStartPos.x) > 20 || Math.abs(mousePos.y - dragStartPos.y) > 20)) gridActionType = BOX; var id = Math.floor(mousePos.x / 40); - var mouseOnGrid = id >= 0 && id < 4 * gridBackdrop.strumlinesAmount && mousePos.y >= 0; + var mouseOnGrid = id >= 0 && id < 4 * gridBackdrops.strumlinesAmount && mousePos.y >= 0; - hoverNote.onGrid = mouseOnGrid; - if (FlxG.mouse.justReleased) { - if (selection.length > 1) { - selection = []; // clear selection - } else { - if (mouseOnGrid) { + for (n in selection) n.selected = false; + selection = []; + + if (mouseOnGrid && mousePos.y > 0 && mousePos.y < (__endStep)*40) { var note = new CharterNote(); - note.updatePos(FlxG.keys.pressed.SHIFT ? (mousePos.y / 40) : Math.floor(mousePos.y / 40), id, 0, 0); + note.updatePos(FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y-20) / 40) : Math.floor(mousePos.y / 40), 0, __endStep-1), id % 4, 0, 0, strumLines.members[Std.int(id/4)]); notesGroup.add(note); selection = [note]; - sortNotes(); undos.addToUndo(CCreateSelection([note])); } - } } } else if (gridBackdropDummy.hoveredByChild) { - // TODO: NOTE DRAGGING if (FlxG.mouse.pressed && (Math.abs(mousePos.x - dragStartPos.x) > 5 || Math.abs(mousePos.y - dragStartPos.y) > 5)) { var noteHovered:Bool = false; for(n in selection) @@ -678,7 +679,6 @@ class Charter extends UIState { noteHovered = true; break; } - gridActionType = noteHovered ? DRAG : INVALID_DRAG; } } @@ -689,6 +689,7 @@ class Charter extends UIState { } } addEventSpr.selectable = !selectionBox.visible; + if (gridActionType == NONE && mousePos.x < 0 && mousePos.x > -addEventSpr.bWidth) { addEventSpr.incorporeal = false; addEventSpr.sprAlpha = lerp(addEventSpr.sprAlpha, 0.75, 0.25); @@ -728,7 +729,6 @@ class Charter extends UIState { eventsGroup.remove(event, true); event.kill(); } - sortNotes(); if (addToUndo) undos.addToUndo(CDeleteSelection([selected])); @@ -739,6 +739,7 @@ class Charter extends UIState { public function createSelection(selection:Selection, addToUndo:Bool = true) { if (selection.length <= 0) return []; + notesGroup.autoSort = false; selection.loop(function (n:CharterNote) { notesGroup.add(n); n.revive(); @@ -747,7 +748,8 @@ class Charter extends UIState { e.revive(); e.refreshEventIcons(); }, false); - sortNotes(); + notesGroup.sortNotes(); + notesGroup.autoSort = true; for (s in selection) if (s is CharterEvent) { @@ -763,6 +765,7 @@ class Charter extends UIState { public function deleteSelection(selection:Selection, addToUndo:Bool = true) { if (selection.length <= 0) return []; + notesGroup.autoSort = false; for (objects in [notesGroup, eventsGroup]) { var group = cast(objects, FlxTypedGroup); var member = 0; @@ -773,7 +776,8 @@ class Charter extends UIState { else member++; } } - sortNotes(); + notesGroup.sortNotes(); + notesGroup.autoSort = true; for (s in selection) if (s is CharterEvent) { @@ -787,70 +791,50 @@ class Charter extends UIState { } // STRUMLINE DELETION/CREATION - public function createStrumline(strumLineID:Int, strL:ChartStrumLine, addToUndo:Bool = true) { + public function createStrumline(strumLineID:Int, strL:ChartStrumLine, addToUndo:Bool = true, ?__createNotes:Bool = true) { var cStr = new CharterStrumline(strL); strumLines.insert(strumLineID, cStr); + strumLines.snapStrums(); - // Push forward notes that are infront of the strumLine - for (note in notesGroup.members) { - if (Std.int(note.id / 4) >= strumLineID) - note.updatePos(note.step, note.id + 4, note.susLength, note.type); + if (__createNotes) { + var toBeCreated:Selection = []; + for(note in strL.notes) { + var n = new CharterNote(); + var t = Conductor.getStepForTime(note.time); + n.updatePos(t, note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type, cStr); + notesGroup.add(n); + } + createSelection(toBeCreated, false); } - var noteOffset = (strumLines.members.indexOf(cStr)) * 4; - - for(note in strL.notes) { - var n = new CharterNote(); - var t = Conductor.getStepForTime(note.time); - n.updatePos(t, noteOffset + note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type); - notesGroup.add(n); - } - sortNotes(); - if (addToUndo) undos.addToUndo(CCreateStrumLine(strumLineID, strL)); } public function deleteStrumline(strumLineID:Int, addToUndo:Bool = true) { + var undoNotes:Array = []; removeStrumlineFromSelection(strumLineID); + var i = 0; + var toBeDeleted:Selection = []; + while(i < notesGroup.members.length) { + var note = notesGroup.members[i]; + if (note.strumLineID == strumLineID) { + undoNotes.push(buildNote(note)); + toBeDeleted.push(note); + } else i++; + } + deleteSelection(toBeDeleted, false); + var strL = strumLines.members[strumLineID].strumLine; strumLines.members[strumLineID].destroy(); strumLines.members.remove(strumLines.members[strumLineID]); + strumLines.snapStrums(); - var deletedstrumNotes:Array = []; - // Delete this strums notes - var i = 0; // thanks yosh!!!!! (deleteNote removes a index) - while(i < notesGroup.members.length) { - var note = notesGroup.members[i]; - if (Std.int(note.id / 4) == strumLineID) { // thanks neo lol!!!!! - deletedstrumNotes.push(note); - deleteSingleSelection(note, false); - } else - i++; - } - - // Push back strumline notes that are infront - for (note in notesGroup.members) { - if (Std.int(note.id / 4) > strumLineID) - note.updatePos(note.step, note.id - 4, note.susLength, note.type); - } - sortNotes(); - - // Undo shit if (addToUndo) { var newStrL = Reflect.copy(strL); - newStrL.notes.clear(); + newStrL.notes = undoNotes; - for (note in deletedstrumNotes) { - var time = Conductor.getTimeForStep(note.step); - newStrL.notes.push({ - type: note.type, - time: time, - sLen: Conductor.getTimeForStep(note.step + note.susLength) - time, - id: note.id % 4 - }); - } undos.addToUndo(CDeleteStrumLine(strumLineID, newStrL)); } } @@ -889,42 +873,47 @@ class Charter extends UIState { while(i < selection.length) { if (selection[i] is CharterNote) { var note = cast (selection[i], CharterNote); - if (Std.int(note.id / 4) == strumLineID) + if (note.strumLineID == strumLineID) selection.remove(note); else i++; } } } - - public inline function sortNotes() { - notesGroup.sort(function(i, n1, n2) { - if (n1.step == n2.step) - return FlxSort.byValues(FlxSort.ASCENDING, n1.id, n2.id); - return FlxSort.byValues(FlxSort.ASCENDING, n1.step, n2.step); - }); - } #end var __crochet:Float; public override function update(elapsed:Float) { updateNoteLogic(elapsed); + if (FlxG.sound.music.playing) { + gridBackdrops.conductorSprY = curStepFloat * 40; + } else { + gridBackdrops.conductorSprY = lerp(gridBackdrops.conductorSprY, curStepFloat * 40, 1/3); + } + charterCamera.scroll.set( + ((((40*4) * gridBackdrops.strumlinesAmount) - FlxG.width) / 2), + gridBackdrops.conductorSprY - (FlxG.height * 0.5) + ); + + if (topMenuSpr.members[5] != null) { + var playBackMenu:UITopMenuButton = cast topMenuSpr.members[5]; + playBackMenu.x = playBackSlider.x-playBackSlider.startText.width-10-playBackSlider.valueStepper.bWidth-playBackMenu.bWidth-10; + playBackMenu.label.offset.x = -2; + } + super.update(elapsed); scrollBar.size = (FlxG.height / 40 / charterCamera.zoom); scrollBar.start = Conductor.curStepFloat - (scrollBar.size / 2); - if (gridBackdrop.strumlinesAmount != strumLines.members.length) + if (gridBackdrops.strumlinesAmount != strumLines.members.length) updateDisplaySprites(); - sectionSeparator.spacing.y = (10 * Conductor.beatsPerMesure * Conductor.stepsPerBeat) - 1; - beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; - // TODO: canTypeText in case an ui input element is focused if (true) { __crochet = ((60 / Conductor.bpm) * 1000); - if(FlxG.keys.justPressed.ANY) + if(FlxG.keys.justPressed.ANY && !strumLines.isDragging && this.currentFocus == null) UIUtil.processShortcuts(topMenu); if (FlxG.keys.pressed.CONTROL) { @@ -934,7 +923,7 @@ class Charter extends UIState { } } else { if (!FlxG.sound.music.playing) { - Conductor.songPosition -= __crochet * FlxG.mouse.wheel; + Conductor.songPosition -= (__crochet * FlxG.mouse.wheel) - Conductor.songOffset; } } } @@ -954,58 +943,31 @@ class Charter extends UIState { + '\nBPM: ${Conductor.bpm}' + '\nTime Signature: ${Conductor.beatsPerMesure}/${Conductor.stepsPerBeat}'; - if (FlxG.sound.music.playing) { - conductorFollowerSpr.y = curStepFloat * 40; - } else { - conductorFollowerSpr.y = lerp(conductorFollowerSpr.y, curStepFloat * 40, 1/3); - } - charterCamera.scroll.set(conductorFollowerSpr.x + ((conductorFollowerSpr.scale.x - FlxG.width) / 2), conductorFollowerSpr.y - (FlxG.height * 0.5)); - if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, 0.125))) { + if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, 0.125))) updateDisplaySprites(); - } - WindowTitle.prefix = undos.unsaved ? "UNSAVED | " : ""; + WindowUtils.prefix = undos.unsaved ? "* " : ""; + SaveWarning.showWarning = undos.unsaved; } public static var startTime:Float = 0; public static var startHere:Bool = false; function updateDisplaySprites() { - gridBackdrop.strumlinesAmount = strumLines.members.length; - - conductorFollowerSpr.scale.set(gridBackdrop.strumlinesAmount * 4 * 40, 4); - conductorFollowerSpr.updateHitbox(); - - sectionSeparator.scale.set((gridBackdrop.strumlinesAmount * 4 * 40) + 20, 4); - sectionSeparator.updateHitbox(); - - beatSeparator.scale.set((gridBackdrop.strumlinesAmount * 4 * 40) + 10, 2); - beatSeparator.updateHitbox(); + gridBackdrops.strumlinesAmount = strumLines.members.length; charterBG.scale.set(1 / charterCamera.zoom, 1 / charterCamera.zoom); - topLimit.scale.set(gridBackdrop.strumlinesAmount * 4 * 40, Math.ceil(FlxG.height / charterCamera.zoom)); - topLimit.updateHitbox(); - topLimit.y = -topLimit.height; - - bottomLimit.scale.set(gridBackdrop.strumlinesAmount * 4 * 40, Math.ceil(FlxG.height / charterCamera.zoom)); - bottomLimit.updateHitbox(); - - bottomSeparator.scale.set((gridBackdrop.strumlinesAmount * 4 * 40) + 20, 4); - bottomSeparator.updateHitbox(); strumlineInfoBG.scale.set(FlxG.width / charterCamera.zoom, 1); strumlineInfoBG.updateHitbox(); strumlineInfoBG.screenCenter(X); strumlineInfoBG.y = -(((FlxG.height - (2 * topMenuSpr.bHeight)) / charterCamera.zoom) - FlxG.height) / 2; - for(id=>str in strumLines.members) { - if (str == null) continue; - str.x = id * 40 * 4; - str.y = strumlineInfoBG.y; - } + for(id=>str in strumLines.members) + if (str != null) str.y = strumlineInfoBG.y; - strumlineAddButton.x = strumLines.members.length * (40 * 4); strumlineAddButton.y = strumlineInfoBG.y; + strumlineLockButton.y = strumlineInfoBG.y; } var zoom(default, set):Float = 0; @@ -1020,24 +982,13 @@ class Charter extends UIState { // TOP MENU OPTIONS #if REGION function _file_exit(_) { - if (undos.unsaved) { - FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes", "Your changes will be lost if you don't save them.", [ - { - label: "Exit", - onClick: function(_) FlxG.switchState(new CharterSelection()) - }, - { - label: "Cancel", - onClick: function (_) {} - } - ])); - } else - FlxG.switchState(new CharterSelection()); + if (undos.unsaved) SaveWarning.triggerWarning(); + else FlxG.switchState(new CharterSelection()); } function _file_save(_) { #if sys - saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase().replace("-", " ")}'); + saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}'); undos.save(); return; #end @@ -1054,7 +1005,7 @@ class Charter extends UIState { function _file_meta_save(_) { #if sys sys.io.File.saveContent( - '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase().replace("-", " ")}/meta.json', + '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json', Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t") ); return; @@ -1086,7 +1037,7 @@ class Charter extends UIState { for (s in selection) if (s is CharterNote) { var note:CharterNote = cast(s, CharterNote); - CNote(note.step - minStep, note.id, note.susLength, note.type); + CNote(note.step - minStep, note.id, note.strumLineID, note.susLength, note.type); } else if (s is CharterEvent) { var event = cast(s,CharterEvent); CEvent(event.step - minStep, [for (event in event.events) Reflect.copy(event)]); @@ -1100,19 +1051,21 @@ class Charter extends UIState { var sObjects:Array = []; for(c in clipboard) { switch(c) { - case CNote(step, id, sLen, type): + case CNote(step, id, strumLineID, susLength, type): var note = new CharterNote(); - note.updatePos(minStep + step + 1, id, sLen, type); + note.updatePos(minStep + step, id, susLength, type, strumLines.members[Std.int(FlxMath.bound(strumLineID, 0, strumLines.length-1))]); notesGroup.add(note); sObjects.push(note); case CEvent(step, events): - var event = new CharterEvent(minStep + step + 1, events); + var event = new CharterEvent(minStep + step, events); event.refreshEventIcons(); eventsGroup.add(event); sObjects.push(event); } } selection = sObjects; + _edit_copy(_); // to fix stupid bugs + undos.addToUndo(CCreateSelection(sObjects.copy())); } @@ -1129,6 +1082,8 @@ class Charter extends UIState { } function _edit_undo(_) { + if (strumLines.isDragging || selectionDragging) return; + selection = []; var undo = undos.undo(); switch(undo) { @@ -1137,6 +1092,10 @@ class Charter extends UIState { createStrumline(strumLineID, strumLine, false); case CCreateStrumLine(strumLineID, strumLine): deleteStrumline(strumLineID, false); + case COrderStrumLine(strumLineID, oldID, newID): + var strumLine:CharterStrumline = strumLines.members[strumLineID]; + strumLines.orderStrumline(strumLine, oldID); + undos.redoList[0] = COrderStrumLine(strumLines.members.indexOf(strumLine), oldID, newID); case CEditStrumLine(strumLineID, oldStrumLine, newStrumLine): strumLines.members[strumLineID].strumLine = oldStrumLine; strumLines.members[strumLineID].updateInfo(); @@ -1144,10 +1103,11 @@ class Charter extends UIState { deleteSelection(selection, false); case CDeleteSelection(selection): createSelection(selection, false); - case CSelectionDrag(selection, change): - for (s in selection) - if (s.draggable) s.handleDrag(change * -1); - this.selection = selection; + case CSelectionDrag(selectionDrags): + for (s in selectionDrags) + if (s.selectable.draggable) s.selectable.handleDrag(s.change * -1); + + this.selection = [for (s in selectionDrags) s.selectable]; case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.before, n.note.type); @@ -1163,6 +1123,8 @@ class Charter extends UIState { } function _edit_redo(_) { + if (strumLines.isDragging || selectionDragging) return; + selection = []; var redo = undos.redo(); switch(redo) { @@ -1171,6 +1133,10 @@ class Charter extends UIState { deleteStrumline(strumLineID, false); case CCreateStrumLine(strumLineID, strumLine): createStrumline(strumLineID, strumLine, false); + case COrderStrumLine(strumLineID, oldID, newID): + var strumLine:CharterStrumline = strumLines.members[strumLineID]; + strumLines.orderStrumline(strumLine, newID); + undos.undoList[0] = COrderStrumLine(strumLines.members.indexOf(strumLine), oldID, newID); case CEditStrumLine(strumLineID, oldStrumLine, newStrumLine): strumLines.members[strumLineID].strumLine = newStrumLine; strumLines.members[strumLineID].updateInfo(); @@ -1178,10 +1144,10 @@ class Charter extends UIState { createSelection(selection, false); case CDeleteSelection(selection): deleteSelection(selection, false); - case CSelectionDrag(selection, change): - for (s in selection) - if (s.draggable) s.handleDrag(change); - this.selection = selection; + case CSelectionDrag(selectionDrags): + for (s in selectionDrags) + if (s.selectable.draggable) s.selectable.handleDrag(s.change); + //this.selection = selection; case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.after, n.note.type); @@ -1214,7 +1180,7 @@ class Charter extends UIState { FlxG.state.openSubState(new MetaDataScreen(PlayState.SONG.meta)); function _playback_play(_) { - if (Conductor.songPosition >= FlxG.sound.music.getDefault(vocals).length) return; + if (Conductor.songPosition >= FlxG.sound.music.getDefault(vocals).length - Conductor.songOffset) return; if (FlxG.sound.music.playing) { FlxG.sound.music.pause(); @@ -1222,7 +1188,7 @@ class Charter extends UIState { } else { FlxG.sound.music.play(); vocals.play(); - vocals.time = FlxG.sound.music.time = Conductor.songPosition; + vocals.time = FlxG.sound.music.time = Conductor.songPosition + Conductor.songOffset * 2; } } @@ -1239,11 +1205,11 @@ class Charter extends UIState { } function _playback_back(_) { if (FlxG.sound.music.playing) return; - Conductor.songPosition -= Conductor.beatsPerMesure * __crochet; + Conductor.songPosition -= (Conductor.beatsPerMesure * __crochet); } function _playback_forward(_) { if (FlxG.sound.music.playing) return; - Conductor.songPosition += Conductor.beatsPerMesure * __crochet; + Conductor.songPosition += (Conductor.beatsPerMesure * __crochet); } function _playback_start(_) { if (FlxG.sound.music.playing) return; @@ -1265,13 +1231,13 @@ class Charter extends UIState { zoom = 0; __camZoom = Math.pow(2, zoom); } - function _view_showsectionseparator(t) { + function _view_showeventSecSeparator(t) { t.icon = (Options.charterShowSections = !Options.charterShowSections) ? 1 : 0; - sectionSeparator.visible = Options.charterShowSections; + eventsBackdrop.eventSecSeparator.visible = gridBackdrops.sectionsVisible = Options.charterShowSections; } - function _view_showbeatseparator(t) { + function _view_showeventBeatSeparator(t) { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; - beatSeparator.visible = Options.charterShowBeats; + eventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats; } inline function _note_addsustain(t) @@ -1295,21 +1261,17 @@ class Charter extends UIState { function changeNoteSustain(change:Float) { if (selection.length <= 0 || change == 0) return; - undos.addToUndo(CEditSustains([ - for(s in selection) { - if (s is CharterNote) { - var n:CharterNote = cast(s, CharterNote); - var old:Float = n.susLength; - n.updatePos(n.step, n.id, Math.max(n.susLength + change, 0)); - - { - before: old, - after: n.susLength, - note: n - }; - } + var undoChanges:Array = []; + for(s in selection) { + if (s is CharterNote) { + var n:CharterNote = cast(s, CharterNote); + var old:Float = n.susLength; + n.updatePos(n.step, n.id, Math.max(n.susLength + change, 0)); + undoChanges.push({before: old, after: n.susLength, note: n}); } - ])); + } + + undos.addToUndo(CEditSustains(undoChanges)); } public function playtestChart(time:Float = 0, opponentMode = false, here = false) { @@ -1321,6 +1283,16 @@ class Charter extends UIState { FlxG.switchState(new PlayState()); } + public inline function buildNote(note:CharterNote):ChartNote { + var time = Conductor.getTimeForStep(note.step); + return { + type: note.type, + time: time, + sLen: Conductor.getTimeForStep(note.step + note.susLength) - time, + id: note.id + }; + } + public function buildChart() { PlayState.SONG.strumLines = []; for(s in strumLines) { @@ -1328,22 +1300,15 @@ class Charter extends UIState { PlayState.SONG.strumLines.push(s.strumLine); } for(n in notesGroup.members) { - var strLineID = Std.int(n.id / 4); - if (PlayState.SONG.strumLines[strLineID] != null) { - var time = Conductor.getTimeForStep(n.step); - PlayState.SONG.strumLines[strLineID].notes.push({ - type: n.type, - time: time, - sLen: Conductor.getTimeForStep(n.step + n.susLength) - time, - id: n.id % 4 - }); - } + if (PlayState.SONG.strumLines[n.strumLineID] != null) + PlayState.SONG.strumLines[n.strumLineID].notes.push(buildNote(n)); } buildEvents(); } public function buildEvents() { PlayState.SONG.events = []; + eventsGroup.sortEvents(); for(e in eventsGroup.members) { for(event in e.events) { event.time = Conductor.getTimeForStep(e.step); @@ -1362,23 +1327,24 @@ class Charter extends UIState { } public inline function hitsoundsEnabled(id:Int) - return strumLines.members[Std.int(id / 4)] != null && strumLines.members[Std.int(id / 4)].hitsounds; + return strumLines.members[id] != null && strumLines.members[id].hitsounds; } enum CharterChange { CCreateStrumLine(strumLineID:Int, strumLine:ChartStrumLine); + CEditStrumLine(strumLineID:Int, oldStrumLine:ChartStrumLine, newStrumLine:ChartStrumLine); + COrderStrumLine(strumLineID:Int, oldID:Int, newID:Int); CDeleteStrumLine(strumLineID:Int, strumLine:ChartStrumLine); CCreateSelection(selection:Selection); CDeleteSelection(selection:Selection); - CSelectionDrag(selection:Selection, change:FlxPoint); - CEditStrumLine(strumLineID:Int, oldStrumLine:ChartStrumLine, newStrumLine:ChartStrumLine); + CSelectionDrag(selectionDrags:Array); CEditSustains(notes:Array); CEditEvent(event:CharterEvent, oldEvents:Array, newEvents:Array); CEditChartData(oldData:{stage:String, speed:Float}, newData:{stage:String, speed:Float}); } enum CharterCopyboardObject { - CNote(step:Float, id:Int, susLength:Float, type:Int); + CNote(step:Float, id:Int, strumLineID:Int, susLength:Float, type:Int); CEvent(step:Float, events:Array); } @@ -1388,6 +1354,11 @@ typedef NoteSustainChange = { var after:Float; } +typedef SelectionDragChange = { + var selectable:ICharterSelectable; + var change:FlxPoint; +} + @:forward abstract Selection(Array) from Array to Array { public inline function new(?array:Array) this = array == null ? [] : array; diff --git a/source/funkin/editors/charter/CharterBackdrop.hx b/source/funkin/editors/charter/CharterBackdrop.hx deleted file mode 100644 index 5768e461..00000000 --- a/source/funkin/editors/charter/CharterBackdrop.hx +++ /dev/null @@ -1,58 +0,0 @@ -package funkin.editors.charter; - -import openfl.geom.Rectangle; -import flixel.addons.display.FlxBackdrop; - -class CharterBackdrop extends FlxBackdrop { - public var strumlinesAmount:Int = 1; - - public function new() { - super(null, Y, 0, 0); - - makeGraphic(160, 160, 0xFF272727, true); - pixels.lock(); - - // Checkerboard - for(y in 0...4) - for(x in 0...2) - pixels.fillRect(new Rectangle(40*((x*2)+(y%2)), 40*y, 40, 40), 0xFF545454); - - // Edges - pixels.fillRect(new Rectangle(0, 0, 1, 160), 0xFFDDDDDD); - pixels.fillRect(new Rectangle(159, 0, 1, 160), 0xFFDDDDDD); - pixels.unlock(); - // loadFrame(frame); - } - - public override function draw() { - var ogX:Float = x; - for(_ in 0...strumlinesAmount) { - if (Charter.instance.strumLines.members[_] != null) - alpha = Charter.instance.strumLines.members[_].strumLine.visible ? 1 : 0.4; - else alpha = 0.9; - super.draw(); - x += width; - } - x = ogX; - } -} - -class CharterBackdropDummy extends UISprite { - var parent:CharterBackdrop; - public function new(parent:CharterBackdrop) { - super(); - this.parent = parent; - cameras = parent.cameras; - scrollFactor.set(1, 0); - } - - public override function updateButton() { - camera.getViewRect(__rect); - UIState.state.updateRectButtonHandler(this, __rect, onHovered); - } - - public override function draw() { - @:privateAccess - __lastDrawCameras = [for(c in cameras) c]; - } -} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx new file mode 100644 index 00000000..62324276 --- /dev/null +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -0,0 +1,303 @@ +package funkin.editors.charter; + +import flixel.util.FlxPool; +import flixel.graphics.FlxGraphic; +import funkin.backend.system.Conductor; +import openfl.geom.Rectangle; +import flixel.addons.display.FlxBackdrop; + +class CharterBackdropGroup extends FlxTypedGroup { + public var strumLineGroup:CharterStrumLineGroup; + public var notesGroup:CharterNoteGroup; + var __gridGraphic:FlxGraphic; + + public var conductorSprY:Float = 0; + public var bottomLimitY:Float = 0; + public var sectionsVisible:Bool = true; + public var beatsVisible:Bool = true; + + // Just here so you can update display sprites all dat and above + public var strumlinesAmount:Int = 0; + + public function new(strumLineGroup:CharterStrumLineGroup) { + super(); + this.strumLineGroup = strumLineGroup; + + __gridGraphic = FlxG.bitmap.create(160, 160, 0xFF272727, true); + __gridGraphic.bitmap.lock(); + + // Checkerboard + for(y in 0...4) + for(x in 0...2) __gridGraphic.bitmap.fillRect(new Rectangle(40*((x*2)+(y%2)), 40*y, 40, 40), 0xFF545454); + + // Edges + __gridGraphic.bitmap.fillRect(new Rectangle(0, 0, 1, 160), 0xFFDDDDDD); + __gridGraphic.bitmap.fillRect(new Rectangle(159, 0, 1, 160), 0xFFDDDDDD); + __gridGraphic.bitmap.unlock(); + } + + public function createGrids(amount:Int = 0) { + for (i in 0...amount) { + var grid = new CharterBackdrop(__gridGraphic); + grid.active = grid.visible = false; + add(grid); + } + } + + public override function update(elapsed:Float) { + for (grid in members) + grid.active = grid.visible = false; + + super.update(elapsed); + + for (i => strumLine in strumLineGroup.members) { + if (strumLine == null) continue; + + if (members[i] == null) + members[i] = recycle(CharterBackdrop, () -> {return new CharterBackdrop(__gridGraphic);}); + + var grid = members[i]; + grid.cameras = this.cameras; + grid.strumLine = strumLine; + + grid.conductorFollowerSpr.y = conductorSprY; + grid.bottomSeparator.y = (grid.bottomLimit.y = bottomLimitY)-2; + grid.sectionSeparator.visible = sectionsVisible; + grid.beatSeparator.visible = beatsVisible; + + grid.notesGroup.clear(); + notesGroup.forEach((n) -> { + var onStr:Bool = (n.snappedToStrumline ? n.strumLineID : Std.int(FlxMath.bound((n.x+n.width)/(40*4), 0, strumLineGroup.members.length-1))) == i; + if(n.exists && n.visible && onStr) + grid.notesGroup.add(n); + }); + + grid.active = grid.visible = true; + grid.updateSprites(); + } + + } + + public var draggingObj:CharterBackdrop = null; + override function draw() @:privateAccess { + var i:Int = 0; + var basic:FlxBasic = null; + + for (grid in members) { + if (strumLineGroup.draggingObj == null) break; + if (grid.strumLine == null) continue; + + if (strumLineGroup.draggingObj.strumLine == grid.strumLine.strumLine) { + draggingObj = grid; + break; + } + } + + var oldDefaultCameras = FlxCamera._defaultCameras; + if (cameras != null) + FlxCamera._defaultCameras = cameras; + + while (i < length) + { + basic = members[i++]; + if (basic != null && basic != draggingObj && basic.exists && basic.visible) + basic.draw(); + } + if (draggingObj != null) draggingObj.draw(); + + FlxCamera._defaultCameras = oldDefaultCameras; + } +} + +class CharterBackdrop extends FlxTypedGroup { + public var gridBackDrop:FlxBackdrop; + public var topLimit:FlxSprite; + public var topSeparator:FlxSprite; + public var bottomLimit:FlxSprite; + public var bottomSeparator:FlxSprite; + + public var conductorFollowerSpr:FlxSprite; + public var beatSeparator:FlxBackdrop; + public var sectionSeparator:FlxBackdrop; + + public var notesGroup:FlxTypedGroup = new FlxTypedGroup(); + public var strumLine:CharterStrumline; + + public function new(gridGraphic:FlxGraphic) { + super(); + + gridBackDrop = new FlxBackdrop(gridGraphic, Y, 0, 0); + add(gridBackDrop); + + sectionSeparator = new FlxBackdrop(null, Y, 0, 0); + sectionSeparator.y = -2; + sectionSeparator.visible = Options.charterShowSections; + + beatSeparator = new FlxBackdrop(null, Y, 0, 0); + beatSeparator.y = -1; + beatSeparator.visible = Options.charterShowBeats; + + for(sep in [sectionSeparator, beatSeparator]) { + sep.makeSolid(1, 1, -1); + sep.alpha = 0.5; + sep.scrollFactor.set(1, 1); + sep.scale.set((4 * 40), sep == sectionSeparator ? 4 : 2); + sep.updateHitbox(); + } + add(beatSeparator); + add(sectionSeparator); + add(notesGroup); + + bottomSeparator = new FlxSprite(0,-2); + bottomSeparator.makeSolid(1, 1, -1); + bottomSeparator.alpha = 0.5; + bottomSeparator.scrollFactor.set(1, 1); + bottomSeparator.scale.set(4 * 40, 4); + bottomSeparator.updateHitbox(); + add(bottomSeparator); + + topSeparator = new FlxSprite(0, -2); + topSeparator.makeSolid(1, 1, -1); + topSeparator.alpha = 0.5; + topSeparator.scrollFactor.set(1, 1); + topSeparator.scale.set(4 * 40, 4); + topSeparator.updateHitbox(); + add(topSeparator); + + // Limits + topLimit = new FlxSprite(); + topLimit.makeSolid(1, 1, -1); + topLimit.color = 0xFF888888; + topLimit.blend = MULTIPLY; + add(topLimit); + + bottomLimit = new FlxSprite(); + bottomLimit.makeSolid(1, 1, -1); + bottomLimit.color = 0xFF888888; + bottomLimit.blend = MULTIPLY; + add(bottomLimit); + + // Follower + conductorFollowerSpr = new FlxSprite(0, 0).makeSolid(1, 1, -1); + conductorFollowerSpr.scale.set(4 * 40, 4); + conductorFollowerSpr.updateHitbox(); + add(conductorFollowerSpr); + } + + public function updateSprites() { + var x:Float = 0; // fuck you + var alpha:Float = 0.9; + + if (strumLine != null) { + x = strumLine.x; + alpha = strumLine.strumLine.visible ? 0.9 : 0.4; + } else alpha = 0.9; + + for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit, topSeparator, bottomSeparator, conductorFollowerSpr]) { + spr.x = x; spr.alpha = alpha; + spr.cameras = this.cameras; + } + + sectionSeparator.spacing.y = (10 * Conductor.beatsPerMesure * Conductor.stepsPerBeat) - 1; + beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; + + topLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); + topLimit.updateHitbox(); + topLimit.y = -topLimit.height; + + bottomLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); + bottomLimit.updateHitbox(); + } +} + +class CharterBackdropDummy extends UISprite { + var parent:CharterBackdropGroup; + public function new(parent:CharterBackdropGroup) { + super(); + this.parent = parent; + cameras = parent.cameras; + scrollFactor.set(1, 0); + } + + public override function updateButton() { + camera.getViewRect(__rect); + UIState.state.updateRectButtonHandler(this, __rect, onHovered); + } + + public override function draw() { + @:privateAccess + __lastDrawCameras = [for(c in cameras) c]; + } +} + +class EventBackdrop extends FlxBackdrop { + public var eventBeatSeparator:FlxBackdrop; + public var eventSecSeparator:FlxBackdrop; + + public var topSeparator:FlxSprite; + public var bottomSeparator:FlxSprite; + + public function new() { + super(Paths.image('editors/charter/events-grid'), Y, 0, 0); + alpha = 0.9; + + // Separators + eventSecSeparator = new FlxBackdrop(null, Y, 0, 0); + eventSecSeparator.y = -2; + eventSecSeparator.visible = Options.charterShowSections; + + eventBeatSeparator = new FlxBackdrop(null, Y, 0, 0); + eventBeatSeparator.y = -1; + eventBeatSeparator.visible = Options.charterShowBeats; + + for(sep in [eventSecSeparator, eventBeatSeparator]) { + sep.makeSolid(1, 1, -1); + sep.alpha = 0.5; + sep.scrollFactor.set(1, 1); + } + + eventSecSeparator.scale.set(20, 4); + eventSecSeparator.updateHitbox(); + + eventBeatSeparator.scale.set(10, 2); + eventBeatSeparator.updateHitbox(); + + bottomSeparator = new FlxSprite(0,-2); + bottomSeparator.makeSolid(1, 1, -1); + bottomSeparator.alpha = 0.5; + bottomSeparator.scrollFactor.set(1, 1); + bottomSeparator.scale.set(20, 4); + bottomSeparator.updateHitbox(); + + topSeparator = new FlxSprite(0, -2); + topSeparator.makeSolid(1, 1, -1); + topSeparator.alpha = 0.5; + topSeparator.scrollFactor.set(1, 1); + topSeparator.scale.set(20, 4); + topSeparator.updateHitbox(); + + } + + public override function draw() { + super.draw(); + + eventSecSeparator.spacing.y = (10 * Conductor.beatsPerMesure * Conductor.stepsPerBeat) - 1; + eventBeatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; + + eventSecSeparator.cameras = cameras; + eventSecSeparator.x = (x+width) - 20; + if (eventSecSeparator.visible) eventSecSeparator.draw(); + + eventBeatSeparator.cameras = cameras; + eventBeatSeparator.x = (x+width) - 10; + if (eventBeatSeparator.visible) eventBeatSeparator.draw(); + + topSeparator.x = (x+width) - 20; + topSeparator.cameras = this.cameras; + if (!eventSecSeparator.visible) topSeparator.draw(); + + bottomSeparator.x = (x+width) - 20; + bottomSeparator.cameras = this.cameras; + bottomSeparator.draw(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index aa593a50..ff62f465 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -3,8 +3,8 @@ package funkin.editors.charter; import funkin.editors.charter.Charter.ICharterSelectable; import flixel.math.FlxPoint; import funkin.game.Character; -import funkin.backend.system.Conductor; import funkin.game.HealthIcon; +import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; import funkin.backend.chart.ChartData.ChartEvent; class CharterEvent extends UISliceSprite implements ICharterSelectable { @@ -15,6 +15,9 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public var selected:Bool = false; public var draggable:Bool = true; + public var eventsBackdrop:EventBackdrop; + public var snappedToGrid:Bool = true; + public function new(step:Float, ?events:Array) { super(-100, (step * 40) - 17, 100, 34, 'editors/charter/event-spr'); this.step = step; @@ -25,6 +28,10 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public override function update(elapsed:Float) { super.update(elapsed); + + if (snappedToGrid && eventsBackdrop != null) + x = eventsBackdrop.x + eventsBackdrop.width - (bWidth = 37 + (icons.length * 22)); + for(k=>i in icons) { i.follow(this, (k * 22) + 30 - (i.width / 2), (bHeight - i.height) / 2); } @@ -38,10 +45,15 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } private static function generateDefaultIcon(name:String) { + var isBase64:Bool = false; var path:String = Paths.image('editors/charter/event-icons/$name'); if (!Assets.exists(path)) path = Paths.image('editors/charter/event-icons/Unknown'); - - var spr = new FlxSprite().loadGraphic(path); + if (Assets.exists(Paths.pack('events/$name'))) { + var packimg = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; + if (isBase64 = (packimg != null)) + path = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; + } + var spr = new FlxSprite().loadGraphic(isBase64 ? openfl.display.BitmapData.fromBase64(path.trim(), 'UTF8') : path); return spr; } @@ -77,8 +89,10 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return (selectionBox.x + selectionBox.bWidth > x) && (selectionBox.x < x + bWidth) && (selectionBox.y + selectionBox.bHeight > y) && (selectionBox.y < y + bHeight); } - public function handleDrag(change:FlxPoint) - y = ((step += change.x) * 40) - 17; + public function handleDrag(change:FlxPoint) { + var newStep:Float = step = FlxMath.bound(step + change.x, 0, Charter.instance.__endStep-1); + y = ((newStep) * 40) - 17; + } public function refreshEventIcons() { while(icons.length > 0) { @@ -100,6 +114,6 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { break; } - x = -(bWidth = 37 + (icons.length * 22)); + x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22)); } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventGroup.hx b/source/funkin/editors/charter/CharterEventGroup.hx index 90719fec..5022b3a4 100644 --- a/source/funkin/editors/charter/CharterEventGroup.hx +++ b/source/funkin/editors/charter/CharterEventGroup.hx @@ -1,3 +1,40 @@ package funkin.editors.charter; -class CharterEventGroup extends FlxTypedGroup {} \ No newline at end of file +import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; +import flixel.util.FlxSort; + +class CharterEventGroup extends FlxTypedGroup { + public var eventsBackdrop:EventBackdrop; + var __lastSort:Int = 0; + + public override function update(elapsed:Float) { + filterEvents(); + if (length != __lastSort) + sortEvents(); + + super.update(elapsed); + } + + public override function draw() { + for (event in members) { + event.eventsBackdrop = eventsBackdrop; + event.snappedToGrid = true; + } + super.draw(); + } + + public inline function sortEvents() { + __lastSort = length; + this.sort(function(i, e1, e2) { + return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step); + }); + } + + public inline function filterEvents() { + for (event in members) + if (event.events.length == 0) { + remove(event, true); + event.kill(); + } + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index ee8107c4..257922c2 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -62,7 +62,7 @@ class CharterEventScreen extends UISubstateWindow { paramsPanel = new FlxGroup(); add(paramsPanel); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 10, windowSpr.y + windowSpr.bHeight - 10, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { saveCurTab(); chartEvent.refreshEventIcons(); @@ -88,7 +88,6 @@ class CharterEventScreen extends UISubstateWindow { Charter.instance.updateBPMEvents(); }); saveButton.x -= saveButton.bWidth; - saveButton.y -= saveButton.bHeight; add(saveButton); closeButton = new UIButton(saveButton.x - 10, saveButton.y, "Close", ()->close()); diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx index 9a3359f4..5156ebdd 100644 --- a/source/funkin/editors/charter/CharterNote.hx +++ b/source/funkin/editors/charter/CharterNote.hx @@ -33,8 +33,6 @@ class CharterNote extends UISprite implements ICharterSelectable { cursor = BUTTON; - //canBeHovered = false; - moves = false; sustainSpr = new FlxSprite(10, 40); @@ -50,19 +48,28 @@ class CharterNote extends UISprite implements ICharterSelectable { UIState.state.updateRectButtonHandler(this, __rect, onHovered); } - public var step:Float; public var id:Int; public var susLength:Float; public var type:Int; - public function updatePos(step:Float, id:Int, susLength:Float = 0, type:Int = 0) { + public var strumLine:CharterStrumline; + public var strumLineID(get, never):Int; + public function get_strumLineID():Int + return strumLine == null ? -1 : Charter.instance.strumLines.members.indexOf(strumLine); + + public var snappedToStrumline:Bool = true; + + public var fullID(get, never):Int; // instead of %4 get fullID (for mousepos stuff) + public function get_fullID():Int + return (strumLineID * 4) + id; + + public function updatePos(step:Float, id:Int, susLength:Float = 0, ?type:Int = 0, ?strumLine:CharterStrumline = null) { this.step = step; this.id = id; this.susLength = susLength; this.type = type; - - x = id * 40; + if (strumLine != null) this.strumLine = strumLine; y = step * 40; sustainSpr.scale.set(10, (40 * susLength)); @@ -121,12 +128,12 @@ class CharterNote extends UISprite implements ICharterSelectable { sustainSpr.follow(this, 15, 20); if (__passed != (__passed = step < Conductor.curStepFloat)) { - alpha = __passed ? 0.6 : 1; - if (__passed && FlxG.sound.music.playing && Charter.instance.hitsoundsEnabled(id)) + if (__passed && FlxG.sound.music.playing && Charter.instance.hitsoundsEnabled(strumLineID)) Charter.instance.hitsound.replay(); } - if(sustainSpr.exists) - sustainSpr.alpha = alpha; + + alpha = strumLine != null && !strumLine.strumLine.visible ? (__passed ? 0.2 : 0.4) : (__passed ? 0.6 : 1); + if(sustainSpr.exists) sustainSpr.alpha = alpha; colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1; colorTransform.redOffset = colorTransform.greenOffset = selected ? 96 : 0; @@ -141,21 +148,20 @@ class CharterNote extends UISprite implements ICharterSelectable { var maxX = Std.int(Math.ceil((selectionBox.x + selectionBox.bWidth) / 40)); var maxY = ((selectionBox.y + selectionBox.bHeight) / 40); - return this.id >= minX && this.id < maxX && this.step >= minY && this.step < maxY; + return this.fullID >= minX && this.fullID < maxX && this.step >= minY && this.step < maxY; } public function handleDrag(change:FlxPoint) { - var newID:Int = id + Std.int(change.y); - if (newID > ((Charter.instance.strumLines.members.length*4)-1)) newID %= 4; - else if (newID < 0) newID = (Charter.instance.strumLines.members.length*4) + newID % 4; + var newStep = FlxMath.bound(step + change.x, 0, Charter.instance.__endStep-1); + var newID:Int = Std.int(FlxMath.bound(fullID + Std.int(change.y), 0, (Charter.instance.strumLines.members.length*4)-1)); - updatePos(step + change.x, newID, susLength, type); - - Charter.instance.notesGroup.remove(this); - Charter.instance.notesGroup.add(this); + updatePos(newStep, newID % 4, susLength, type, Charter.instance.strumLines.members[Std.int(newID/4)]); } public override function draw() { + if (snappedToStrumline) + x = (strumLine != null ? strumLine.x : 0) + (id % 4) * 40; + drawMembers(); drawSuper(); } diff --git a/source/funkin/editors/charter/CharterNoteGroup.hx b/source/funkin/editors/charter/CharterNoteGroup.hx index 775601d0..0cfebeb9 100644 --- a/source/funkin/editors/charter/CharterNoteGroup.hx +++ b/source/funkin/editors/charter/CharterNoteGroup.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import flixel.util.FlxSort; import funkin.backend.system.Conductor; class CharterNoteGroup extends FlxTypedGroup { @@ -8,6 +9,9 @@ class CharterNoteGroup extends FlxTypedGroup { var max:Float = 0; var __currentlyLooping:Bool = false; + public var autoSort:Bool = true; + var __lastSort:Int = 0; + public override function forEach(noteFunc:CharterNote->Void, recursive:Bool = false) { __loopSprite = null; @@ -17,18 +21,18 @@ class CharterNoteGroup extends FlxTypedGroup { __currentlyLooping = true; var curStep = Conductor.curStepFloat; - if (FlxG.state is Charter && !FlxG.sound.music.playing) - curStep = cast(FlxG.state, Charter).conductorFollowerSpr.y / 40; + if (!FlxG.sound.music.playing) + curStep = Charter.instance.gridBackdrops.conductorSprY / 40; var begin = SortedArrayUtil.binarySearch(members, curStep - max, getVarForEachAdd); var end = SortedArrayUtil.binarySearch(members, curStep + max, getVarForEachRemove); for(i in begin...end) { __loopSprite = members[i]; - if (!cast(FlxG.state, Charter).selection.contains(__loopSprite)) + if (!Charter.instance.selection.contains(__loopSprite)) noteFunc(__loopSprite); } - for(c in cast(FlxG.state, Charter).selection.copy()) + for(c in Charter.instance.selection.copy()) if (c is CharterNote) noteFunc(cast (c, CharterNote)); __currentlyLooping = oldCur; @@ -50,22 +54,15 @@ class CharterNoteGroup extends FlxTypedGroup { private static function getVarForEachRemove(n:CharterNote) return n.step - n.susLength; - public override function draw() { - @:privateAccess var oldDefaultCameras = FlxCamera._defaultCameras; - @:privateAccess if (cameras != null) FlxCamera._defaultCameras = cameras; - - forEach((n) -> { - if(n.exists && n.visible) - n.draw(); - }); - - @:privateAccess FlxCamera._defaultCameras = oldDefaultCameras; - } + public override function draw() {} public override function update(elapsed:Float) { @:privateAccess var oldDefaultCameras = FlxCamera._defaultCameras; @:privateAccess if (cameras != null) FlxCamera._defaultCameras = cameras; + if (length != __lastSort && autoSort) + sortNotes(); + forEach((n) -> { if(n.exists && n.active) n.update(elapsed); @@ -73,4 +70,18 @@ class CharterNoteGroup extends FlxTypedGroup { @:privateAccess FlxCamera._defaultCameras = oldDefaultCameras; } + + public function sortNotes() { + __lastSort = length; + this.sort(function(i, n1, n2) { + if (n1.step == n2.step) + return FlxSort.byValues(FlxSort.ASCENDING, n1.fullID, n2.fullID); + return FlxSort.byValues(FlxSort.ASCENDING, n1.step, n2.step); + }); + } + + public inline function preallocate(len:Int) { + members = cast new haxe.ds.Vector(len); + length = len; + } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx index e7d2277f..5e7ffff8 100644 --- a/source/funkin/editors/charter/CharterSelection.hx +++ b/source/funkin/editors/charter/CharterSelection.hx @@ -32,13 +32,8 @@ class CharterSelection extends EditorTreeMenu { }) ]; list.push(new NewOption("New Difficulty", "New Difficulty", function() { - openSubState(new UIWarningSubstate("New Difficulty", "This feature isnt implemented yet", [ - { - label: "OK", - onClick: function(t) { - - } - } + openSubState(new UIWarningSubstate("New Difficulty: Feature Not Implemented!", "This feature isnt implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} ])); })); optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list)); @@ -91,9 +86,18 @@ class CharterSelection extends EditorTreeMenu { } public function saveSong(creation:SongCreationData) { + var songAlreadlyExsits:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase()); + + if (songAlreadlyExsits) { + openSubState(new UIWarningSubstate("Creating Song: Error!", "The song you are trying to create alreadly exists, if you would like to override it delete the song first!", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + return; + } + // Paths var songsDir:String = '${Paths.getAssetsRoot()}/songs/'; - var songFolder:String = '$songsDir${creation.meta.name.replace("-", " ")}'; + var songFolder:String = '$songsDir${creation.meta.name}'; #if sys // Make Directories @@ -115,13 +119,8 @@ class CharterSelection extends EditorTreeMenu { }) ]; list.push(new NewOption("New Difficulty", "New Difficulty", function() { - openSubState(new UIWarningSubstate("New Difficulty", "This feature isnt implemented yet", [ - { - label: "OK", - onClick: function(t) { - - } - } + openSubState(new UIWarningSubstate("New Difficulty: Feature Not Implemented!", "This feature isnt implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} ])); })); optionsTree.add(new OptionsScreen(creation.meta.name, "Select a difficulty to continue.", list)); diff --git a/source/funkin/editors/charter/CharterStrumLineGroup.hx b/source/funkin/editors/charter/CharterStrumLineGroup.hx new file mode 100644 index 00000000..c4c3c17c --- /dev/null +++ b/source/funkin/editors/charter/CharterStrumLineGroup.hx @@ -0,0 +1,114 @@ +package funkin.editors.charter; + +import funkin.backend.chart.ChartData.ChartStrumLine; +import funkin.backend.chart.EventsData; +import flixel.util.FlxSort; + +class CharterStrumLineGroup extends FlxTypedGroup { + var __pastStrumlines:Array; + var draggingObj:CharterStrumline = null; + var draggingOffset:Float = 0; + + public var draggable:Bool = false; + public var isDragging(get, never):Bool; + public function get_isDragging():Bool + return draggingObj != null; + + public override function update(elapsed:Float) { + var mousePos = FlxG.mouse.getWorldPosition(cameras[0], FlxPoint.get()); + for (strumLine in members) { + if (strumLine == null) continue; + strumLine.draggable = draggable; + if (draggable && UIState.state.isOverlapping(strumLine.draggingSprite, @:privateAccess strumLine.draggingSprite.__rect) && FlxG.mouse.justPressed) { + draggingObj = strumLine; + strumLine.dragging = true; + + draggingOffset = mousePos.x - strumLine.button.x; + __pastStrumlines = members.copy(); + break; + } + } + + if (isDragging) { + draggingObj.x = mousePos.x - draggingOffset; + this.sort(function(o, a, b) return FlxSort.byValues(o, a.x, b.x), -1); + } + + for (i=>strum in members) + if (strum != null && !strum.dragging) strum.x = CoolUtil.fpsLerp(strum.x, 160 * i, 0.225); + + if (Charter.instance.eventsBackdrop != null && members[0] != null) + Charter.instance.eventsBackdrop.x = members[0].button.x - Charter.instance.eventsBackdrop.width; + if (Charter.instance.strumlineLockButton != null && members[0] != null) + Charter.instance.strumlineLockButton.x = members[0].x - (40*4); + if (Charter.instance.strumlineAddButton != null && members[Std.int(Math.max(0, members.length-1))] != null) + Charter.instance.strumlineAddButton.x = members[members.length-1].x + (40*4); + + if ((FlxG.mouse.justReleased || !draggable) && isDragging) + finishDrag(); + + mousePos.put(); + super.update(elapsed); + } + + public function snapStrums() { + for (i=>strum in members) + if (strum != null && !strum.dragging) strum.x = 160 * i; + } + + public function orderStrumline(strumLine:CharterStrumline, newID:Int) { + __pastStrumlines = members.copy(); + + members.remove(strumLine); + members.insert(newID, strumLine); + + finishDrag(false); + } + + public function finishDrag(?addToUndo:Bool = true) { + if (isDragging) + draggingObj.dragging = false; + + // Undo + if (addToUndo) { + var oldID = __pastStrumlines.indexOf(draggingObj); + var newID = members.indexOf(draggingObj); + if (newID != oldID) Charter.instance.undos.addToUndo(COrderStrumLine(newID, oldID, newID)); + } + + draggingObj = null; + fixEvents(); + } + + public inline function fixEvents() { + for (i in Charter.instance.eventsGroup.members) { + for (j in i.events) { + var paramTypes:Array = EventsData.getEventParams(j.name); + for (i => param in paramTypes) { + if (param.type != TStrumLine) continue; + j.params[i] = members.indexOf(__pastStrumlines[j.params[i]]); + } + } + } + __pastStrumlines = null; + } + + override function draw() @:privateAccess { + var i:Int = 0; + var basic:FlxBasic = null; + + var oldDefaultCameras = FlxCamera._defaultCameras; + if (cameras != null) + FlxCamera._defaultCameras = cameras; + + while (i < length) + { + basic = members[i++]; + if (basic != null && basic != draggingObj && basic.exists && basic.visible) + basic.draw(); + } + if (draggingObj != null) draggingObj.draw(); + + FlxCamera._defaultCameras = oldDefaultCameras; + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index 14f4d671..78fb0275 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import funkin.backend.chart.EventsData; import funkin.game.Character; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import funkin.editors.ui.UITopMenu.UITopMenuButton; @@ -8,11 +9,14 @@ import funkin.backend.chart.ChartData.ChartStrumLine; class CharterStrumline extends UISprite { public var strumLine:ChartStrumLine; - public var hitsounds:Bool = true; + public var draggingSprite:UISprite; public var healthIcon:HealthIcon; - public var button:CharterStrumlineButton; + public var button:CharterStrumlineOptions; + + public var draggable:Bool = false; + public var dragging:Bool = false; public var curMenu:UIContextMenu = null; @@ -26,22 +30,52 @@ class CharterStrumline extends UISprite { var icon = Character.getIconFromCharName(strumLine.characters != null ? strumLine.characters[0] : null); healthIcon = new HealthIcon(icon); - healthIcon.scale.set(80 / 150, 80 / 150); + healthIcon.scale.set((80 - (draggable ? 21 : 0)) / 150, (80 - (draggable ? 21 : 0)) / 150); healthIcon.updateHitbox(); - if(strumLine.visible == null) - strumLine.visible = true; + healthIcon.y = draggable ? 29 : 7; + + if(strumLine.visible == null) strumLine.visible = true; healthIcon.alpha = strumLine.visible ? 1 : 0.4; members.push(healthIcon); - button = new CharterStrumlineButton(this); + draggingSprite = new UISprite(); + draggingSprite.loadGraphic(Paths.image("editors/charter/strumline-drag")); + draggingSprite.alpha = 0.4; + draggingSprite.y = 9; + draggingSprite.antialiasing = true; + draggingSprite.cursor = BUTTON; + members.push(draggingSprite); + + button = new CharterStrumlineOptions(this); members.push(button); } + private var __healthYOffset:Float = 0; + private var __draggingYOffset:Float = 0; + public override function update(elapsed:Float) { - if (healthIcon != null) - healthIcon.follow(this, ((40 * 4) - healthIcon.width) / 2, 0); + if (FlxG.keys.justPressed.K) draggable = !draggable; + if (healthIcon != null) { + var healthScale:Float = FlxMath.lerp(healthIcon.scale.x, (80 - (draggable ? 21 : 0)) / 150, 1/20); + healthIcon.scale.set(healthScale, healthScale); + healthIcon.updateHitbox(); + + healthIcon.follow(this, ((40 * 4) - healthIcon.width) / 2, 7 + (__healthYOffset = FlxMath.lerp(__healthYOffset, draggable ? 22 : 0, 1/20))); + } + + draggingSprite.selectable = draggable; + UIState.state.updateSpriteRect(draggingSprite); + + var dragScale:Float = FlxMath.lerp(draggingSprite.scale.x, draggable ? 1 : 0.8, 1/16); + draggingSprite.scale.set(dragScale, dragScale); + draggingSprite.updateHitbox(); + + draggingSprite.follow(this, (160/2) - (draggingSprite.width/2), 6 + (__draggingYOffset = FlxMath.lerp(__draggingYOffset, draggable ? 3 : 0, 1/12))); + var fullAlpha:Float = UIState.state.isOverlapping(draggingSprite, @:privateAccess draggingSprite.__rect) || dragging ? 0.9 : 0.35; + draggingSprite.alpha = FlxMath.lerp(draggingSprite.alpha, draggable ? fullAlpha : 0, 1/12); button.follow(this, 0, 95); + super.update(elapsed); } @@ -52,17 +86,19 @@ class CharterStrumline extends UISprite { var icon = Character.getIconFromCharName(strumLine.characters != null ? strumLine.characters[0] : null); healthIcon = new HealthIcon(icon); - healthIcon.scale.set(80 / 150, 80 / 150); + healthIcon.scale.set((80 - (draggable ? 21 : 0)) / 150, (80 - (draggable ? 21 : 0)) / 150); healthIcon.updateHitbox(); + healthIcon.y = draggable ? 29 : 7; + healthIcon.alpha = strumLine.visible ? 1 : 0.4; members.push(healthIcon); } } -class CharterStrumlineButton extends UITopMenuButton { +class CharterStrumlineOptions extends UITopMenuButton { var strLine:CharterStrumline; public function new(parent:CharterStrumline) { - super(0, 95, null, "Options", []); + super(0, 95, null, "Options ↓", []); strLine = parent; bWidth = 40 * 4; this.label.fieldWidth = bWidth; @@ -70,7 +106,7 @@ class CharterStrumlineButton extends UITopMenuButton { public override function update(elapsed:Float) { super.update(elapsed); - alpha = FlxMath.lerp(1/3, 1, alpha); // so that instead of 0% it is 33% visible + alpha = FlxMath.lerp(1/20, 1, alpha); // so that instead of 0% it is 33% visible } public override function openContextMenu() { @@ -96,13 +132,17 @@ class CharterStrumlineButton extends UITopMenuButton { label: "Edit", onSelect: function (_) { Charter.instance.editStrumline(strLine.strumLine); - } + }, + color: 0xFFFFFF00, + icon: 2 }, { label: "Delete", onSelect: function (_) { Charter.instance.deleteStrumlineFromData(strLine.strumLine); - } + }, + color: 0xFFFF0000, + icon: 3 } ]; super.openContextMenu(); diff --git a/source/funkin/editors/charter/CharterStrumlineExtraButton.hx b/source/funkin/editors/charter/CharterStrumlineButton.hx similarity index 82% rename from source/funkin/editors/charter/CharterStrumlineExtraButton.hx rename to source/funkin/editors/charter/CharterStrumlineButton.hx index f19e4c30..2cd156cf 100644 --- a/source/funkin/editors/charter/CharterStrumlineExtraButton.hx +++ b/source/funkin/editors/charter/CharterStrumlineButton.hx @@ -2,7 +2,7 @@ package funkin.editors.charter; import flixel.math.FlxPoint; -class CharterStrumlineExtraButton extends UISprite { +class CharterStrumlineButton extends UISprite { public var button:UISprite; public var text:UIText; @@ -30,15 +30,12 @@ class CharterStrumlineExtraButton extends UISprite { var buttonScale:FlxPoint = FlxPoint.get(0,0); public override function update(elapsed:Float) { - button.follow(this, ((40 * 4) - button.width) / 2, 18); + button.follow(this, ((40 * 4) - button.width) / 2, 24); text.follow(this, 0, 84); super.update(elapsed); - button.__rect.x = button.x; - button.__rect.y = button.y; - button.__rect.width = button.width; - button.__rect.height = button.height; + UIState.state.updateSpriteRect(button); if(UIState.state.isOverlapping(button, button.__rect)) { buttonScale.set(0.85, 0.85); if (FlxG.mouse.justReleased && onClick != null) onClick(); diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index da7e369c..e6a04399 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -59,11 +59,14 @@ class CharterStrumlineScreen extends UISubstateWindow { var title:UIText; add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, creatingStrumLine ? "Create New Strumline" : "Edit Strumline Properties", 28)); + var charFileList = Character.getList(true); + if (charFileList.length == 0) charFileList = Character.getList(false); + charactersList = new UIButtonList(15, title.y + title.height + 36, 250, 259, "", FlxPoint.get(250, 54), null, 0); - charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, "New Char", charactersList)); + charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, "New Char", charFileList, charactersList)); charactersList.cameraSpacing = 0; for (i in strumLine.characters) - charactersList.add(new CharacterButton(0, 0, i, charactersList)); + charactersList.add(new CharacterButton(0, 0, i, charFileList, charactersList)); add(charactersList); addLabelOn(charactersList, "Characters"); @@ -126,13 +129,12 @@ class CharterStrumlineScreen extends UISubstateWindow { }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20, saveButton.y, creatingStrumLine ? "Cancel" : "Close", function() { + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, creatingStrumLine ? "Cancel" : "Close", function() { if (creatingStrumLine && onSave != null) onSave(null); close(); }, 125); add(closeButton); closeButton.color = 0xFFFF0000; - closeButton.x -= closeButton.bWidth; } function saveStrumline() { @@ -158,11 +160,11 @@ class CharterStrumlineScreen extends UISubstateWindow { class CharacterButton extends UIButton { public var charIcon:HealthIcon; - public var textBox:UITextBox; + public var textBox:UIAutoCompleteTextBox; public var deleteButton:UIButton; public var deleteIcon:FlxSprite; - public function new(x:Float, y:Float, char:String, parent:UIButtonList) { + public function new(x:Float, y:Float, char:String, charsList:Array, parent:UIButtonList) { super(x, y, "", null, 250, 54); autoAlpha = false; @@ -174,7 +176,8 @@ class CharacterButton extends UIButton { members.push(charIcon); - members.push(textBox = new UITextBox(charIcon.x + charIcon.width + 16, bHeight/2 - (32/2), char, 115)); + members.push(textBox = new UIAutoCompleteTextBox(charIcon.x + charIcon.width + 16, bHeight/2 - (32/2), char, 115)); + textBox.suggestItems = charsList; textBox.antialiasing = true; textBox.onChange = function(char:String) { char = Character.getIconFromCharName(char); diff --git a/source/funkin/editors/ui/UIAutoCompleteTextBox.hx b/source/funkin/editors/ui/UIAutoCompleteTextBox.hx new file mode 100644 index 00000000..6b5d31b3 --- /dev/null +++ b/source/funkin/editors/ui/UIAutoCompleteTextBox.hx @@ -0,0 +1,99 @@ +package funkin.editors.ui; + +import flixel.math.FlxPoint; +import lime.ui.KeyModifier; +import lime.ui.KeyCode; +import openfl.desktop.Clipboard; +import openfl.geom.Rectangle; + +class UIAutoCompleteTextBox extends UITextBox { + public var suggestionText:UIText; + + public var suggestItems(default, set):Array = []; + var suggestIndex = 0; + + public function new(x:Float, y:Float, text:String = "", width:Int = 320, height:Int = 32, multiline:Bool = false) { + super(x, y, text, width, height, multiline); + + suggestionText = new UIText(0, 0, width, ""); + suggestionText.color = 0xFF888888; + suggestionText.visible = false; + members.insert(members.indexOf(label), suggestionText); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + + var selected = selectable && focused; + + suggestionText.exists = selected; + + suggestionText.follow(label, 0, 0); + } + + public override function onKeyDown(e:KeyCode, modifier:KeyModifier) { + super.onKeyDown(e, modifier); + switch(e) { + //case RETURN: + // focused = false; + case TAB: + if(_suggestions.length > 0) { + label.text = suggestionText.text; + position = label.text.length; + updateSuggestion(true); + } + case BACKSPACE: + updateSuggestion(true); + case UP: + suggestIndex--; + updateSuggestion(false); + case DOWN: + suggestIndex++; + updateSuggestion(false); + default: + // nothing + } + } + + var _suggestions:Array = []; + + public function updateSuggestion(refreshSuggestions:Bool = true) { + if(refreshSuggestions) { + var text = label.text; + _suggestions = []; + if(text.length > 0) { + for(i in suggestItems) if(!_suggestions.contains(i) && i.startsWith(text)) _suggestions.push(i); + + // Clean up suggestions + if(suggestItems.contains(text)) {_suggestions = [];} + if (_suggestions.length > 0) _suggestions.sort(function(a, b) return a.length - b.length); + } + } + + if(suggestionText.visible = (_suggestions.length != 0)) { + if(suggestIndex >= _suggestions.length) + suggestIndex = 0; + suggestIndex = FlxMath.wrap(suggestIndex, 0, _suggestions.length - 1); + + suggestionText.text = _suggestions[suggestIndex]; + } + } + + public override function onTextInput(text:String):Void { + super.onTextInput(text); + + updateSuggestion(); + } + + public override function onTextEdit(text:String, start:Int, end:Int):Void { + super.onTextEdit(text, start, end); + + updateSuggestion(); + } + + function set_suggestItems(v:Array) { + suggestItems = v; + updateSuggestion(); + return v; + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIButton.hx b/source/funkin/editors/ui/UIButton.hx index c5a7fe80..f3e67821 100644 --- a/source/funkin/editors/ui/UIButton.hx +++ b/source/funkin/editors/ui/UIButton.hx @@ -12,9 +12,6 @@ class UIButton extends UISliceSprite { super(x, y, w, h, 'editors/ui/button'); this.callback = callback; members.push(field = new UIText(x, y, w, text)); - field.borderStyle = OUTLINE; - field.borderColor = 0x88000000; - field.borderSize = 1; field.alignment = CENTER; field.fieldWidth = w; diff --git a/source/funkin/editors/ui/UIContextMenu.hx b/source/funkin/editors/ui/UIContextMenu.hx index bf4d08fc..8a60a0ff 100644 --- a/source/funkin/editors/ui/UIContextMenu.hx +++ b/source/funkin/editors/ui/UIContextMenu.hx @@ -115,6 +115,7 @@ typedef UIContextMenuOption = { var ?keybind:Array; var ?keybinds:Array>; var ?keybindText:String; + var ?color:Int; var ?icon:Int; var ?onSelect:UIContextMenuOption->Void; var ?childs:Array; @@ -132,6 +133,7 @@ class UIContextMenuOptionSpr extends UISliceSprite { label = new UIText(20, 2, 0, option.label); this.option = option; this.parent = parent; + this.color = option.color; if (option.icon != null && option.icon > 0) { icon = new FlxSprite(0, 0).loadGraphic(Paths.image('editors/ui/context-icons'), true, 20, 20); diff --git a/source/funkin/editors/ui/UISlider.hx b/source/funkin/editors/ui/UISlider.hx new file mode 100644 index 00000000..fca67325 --- /dev/null +++ b/source/funkin/editors/ui/UISlider.hx @@ -0,0 +1,153 @@ +package funkin.editors.ui; + +import flixel.util.FlxColor; +using flixel.util.FlxSpriteUtil; +using StringTools; + +typedef SliderSegement = { + var start:Float; + var end:Float; + var size:Float; +} + +class UISlider extends UISprite { + public var segments:Array = []; + public var barWidth:Int = 120; + + public var progressbar:UISprite; + public var progressCentered:Bool; + + public var startText:UIText; + public var endText:UIText; + + public var selectableBar:UISprite; + public var selectableBarHighlight:UISprite; + public var selectableHitbox:UISprite; + + public var value(default, set):Float = 0; + public function set_value(newVal:Float):Float { + __barProgress = __calcProgress(newVal); onChange(newVal); + return value = newVal; + } + + public var valueStepper:UINumericStepper; + public var onChange:Float->Void; + + public function new(x:Float, y:Float, width:Int = 120, value:Float, segments:Array, centered:Bool) { + this.segments = segments; + this.barWidth = width; + this.progressCentered = centered; + + super(x, y); + + makeGraphic(barWidth, 12, 0x00000000, true); + this.drawRoundRect(0, 0, barWidth, 12, 8, 6, 0xFFC0C0C0); + this.drawRoundRect(1, 1, barWidth-2, 12-2, 8, 5, 0xFF140013); + cursor = BUTTON; + + progressbar = new UISprite(centered ? barWidth/2 : 0); + progressbar.makeGraphic(barWidth, 8, 0x00000000, true); + progressbar.drawRoundRect(2, 0, barWidth-4, 8, 8, 2, 0xFFFFFFFF); + progressbar.origin.x = 0; progressbar.colorTransform.color = 0xFF67009B; + members.push(progressbar); + + __barProgress = visualProgress = __calcProgress(value); + + members.push(startText = new UIText(x, y, 0, Std.string(segments[0].start).replace("0.", "."))); + members.push(endText = new UIText(x + (barWidth) + 8, y, 0, Std.string(segments[segments.length-1].end).replace("0.", "."))); + + for (i in 0...2) { + var selectableBar:UISprite = new UISprite(x,y); + selectableBar.loadGraphic(Paths.image("editors/ui/slider")); + selectableBar.antialiasing = true; + selectableBar.cursor = BUTTON; + members.push(selectableBar); + + switch (i) { + case 0: this.selectableBar = selectableBar; + case 1: this.selectableBarHighlight = selectableBar; selectableBarHighlight.colorTransform.color = 0xFFFFFFFF; selectableBarHighlight.alpha = 0; + } + } + + selectableHitbox = new UISprite(x,y); + selectableHitbox.makeSolid(barWidth, 18, -1); + selectableHitbox.cursor = BUTTON; + selectableHitbox.alpha = 0; + members.push(selectableHitbox); + + valueStepper = new UINumericStepper(x - 64 - 64, y, 1, 0.01, 2, segments[0].start, segments[segments.length-1].end, 0, 16); + valueStepper.antialiasing = valueStepper.label.antialiasing = true; + valueStepper.onChange = function (text:String) { + @:privateAccess valueStepper.__onChange(text); + visualProgress = __barProgress = __calcProgress(value = valueStepper.value); + if (onChange != null) onChange(value); + } + members.push(valueStepper); + } + + var __barProgress:Float = 0; + var visualProgress:Float = 0; + + var __stepperWidth = 25; + + public override function update(elapsed:Float) { + selectableHitbox.follow(this, 0, (height-selectableHitbox.height)/2); + + __stepperWidth = valueStepper.label.text.length <= 0 ? 25 : Std.int(valueStepper.label.textField.width) + 12; + valueStepper.bWidth = Std.int(FlxMath.lerp(valueStepper.bWidth, __stepperWidth, 1/2.25)); + valueStepper.follow(this, -startText.width-10 - valueStepper.bWidth - 4, (height-valueStepper.bHeight)/2); + + var lastBarProgress:Float = __barProgress; + + if (selectableHitbox.hovered && FlxG.mouse.pressed) { + var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); + __barProgress = FlxMath.bound(mousePos.x-x, 0, barWidth)/barWidth; + mousePos.put(); + } + + if (__barProgress != lastBarProgress) { + if (onChange != null) onChange(@:bypassAccessor value = __calcValue(__barProgress)); + valueStepper.value = value; + } + + visualProgress = FlxMath.lerp(visualProgress, __barProgress, 1/2.25); + progressbar.follow(this, progressCentered ? barWidth/2 : 0, (height-progressbar.height)/2); + progressbar.scale.x = FlxMath.bound(visualProgress-(progressCentered?0.5:0),-1,1); + progressbar.colorTransform.color = FlxColor.interpolate(progressbar.colorTransform.color, selectableHitbox.hovered ? 0xFF7F00BF : 0xFF67009B, 1/14); + + selectableBar.follow(this, (visualProgress * barWidth) - (selectableBar.width/2), (height-selectableBar.height)/2); selectableBarHighlight.follow(selectableBar); + selectableBarHighlight.alpha = FlxMath.lerp(selectableBarHighlight.alpha, selectableHitbox.hovered ? 0.1: 0, 1/14); + + startText.follow(this, -startText.width-10, (height/2) - (startText.height/2)); + endText.follow(this, barWidth + 10, (height/2) - (endText.height/2)); + + super.update(elapsed); + } + + private function __calcValue(progress:Float):Float { + var totalProgress:Float = 0; + for (segment in segments) { + if (progress < totalProgress + segment.size) { + var relativeProgress = (progress - totalProgress) / segment.size; + return FlxMath.remapToRange(relativeProgress, 0, 1, segment.start, segment.end); + } + totalProgress += segment.size; + } + return segments[segments.length-1].end; + } + + private function __calcProgress(value:Float):Float { + if (value >= segments[segments.length-1].end) return 1; + if (value <= segments[0].start) return 0; + + var totalProgress:Float = 0; + for (segment in segments) { + if (value >= segment.start && value <= segment.end) { + var relativeProgress = FlxMath.remapToRange(value, segment.start, segment.end, 0, 1); + return totalProgress + relativeProgress * segment.size; + } + totalProgress += segment.size; + } + return -1; + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx index 716e277d..b43e605e 100644 --- a/source/funkin/editors/ui/UIState.hx +++ b/source/funkin/editors/ui/UIState.hx @@ -33,6 +33,7 @@ class UIState extends MusicBeatState { __mousePos = FlxPoint.get(); super.create(); Framerate.offset.y = 30; + FlxG.mouse.visible = true; FlxG.stage.window.onKeyDown.add(onKeyDown); FlxG.stage.window.onKeyUp.add(onKeyUp); @@ -59,6 +60,13 @@ class UIState extends MusicBeatState { currentFocus.onTextEdit(str, start, end); } + public inline function updateSpriteRect(spr:UISprite) { + spr.__rect.x = spr.x; + spr.__rect.y = spr.y; + spr.__rect.width = spr.width; + spr.__rect.height = spr.height; + } + public function updateButtonHandler(spr:UISprite, buttonHandler:Void->Void) { spr.__rect.x = spr.x; spr.__rect.y = spr.y; @@ -121,6 +129,10 @@ class UIState extends MusicBeatState { public override function destroy() { super.destroy(); __mousePos.put(); + + WindowUtils.resetTitle(); + SaveWarning.reset(); + FlxG.stage.window.onKeyDown.remove(onKeyDown); FlxG.stage.window.onKeyUp.remove(onKeyUp); FlxG.stage.window.onTextInput.remove(onTextInput); diff --git a/source/funkin/editors/ui/UIText.hx b/source/funkin/editors/ui/UIText.hx index 413630b2..83daab60 100644 --- a/source/funkin/editors/ui/UIText.hx +++ b/source/funkin/editors/ui/UIText.hx @@ -4,11 +4,42 @@ import flixel.util.FlxColor; import funkin.backend.FunkinText; class UIText extends FunkinText { + public var optimized:Bool = false; // PUTTING THIS HERE IN CASE IMMA MAKE FUTURE CHANGES - public function new(x, y, w, text, size:Int = 15, color:FlxColor = 0xFFFFFFFF) { + public function new(x, y, w, text, size:Int = 15, color:FlxColor = 0xFFFFFFFF, ?outline:Bool = true) { super(x, y, w, text, size, false); this.color = color; + if (outline) { + this.borderStyle = OUTLINE_FAST; + this.borderColor = 0x88000000; + this.borderSize = 1; + } this.active = false; } + + public override function applyBorderStyle() { + if (borderStyle == OUTLINE_FAST && optimized) { + var iterations:Int = Std.int(borderSize * borderQuality); + if (iterations <= 0) + { + iterations = 1; + } + var delta:Float = borderSize / iterations; + + applyFormats(_formatAdjusted, true); + + var curDelta:Float = delta; + for (i in 0...iterations) + { + copyTextWithOffset(-curDelta, -curDelta); // upper-left + copyTextWithOffset(curDelta * 2, curDelta * 2); // lower-right + + _matrix.translate(-curDelta, -curDelta); // return to center + curDelta += delta; + } + return; + } + super.applyBorderStyle(); + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UITextBox.hx b/source/funkin/editors/ui/UITextBox.hx index 17f7222e..3aa97fd2 100644 --- a/source/funkin/editors/ui/UITextBox.hx +++ b/source/funkin/editors/ui/UITextBox.hx @@ -68,7 +68,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { } var off = multiline ? 4 : ((bHeight - label.height) / 2); - label.follow(this, 4, off); + label.follow(this, label.autoSize ? (bWidth-label.textField.width)/2 : 4, off); framesOffset = (selected ? 18 : (hovered ? 9 : 0)); @:privateAccess { if (selected) { @@ -104,6 +104,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { switch(e) { case RETURN: focused = false; + if (onChange != null) onChange(label.text); case LEFT: changeSelection(-1); case RIGHT: @@ -140,7 +141,6 @@ class UITextBox extends UISliceSprite implements IUIFocusable { } // untested, but this should be a fix for if the text wont type public function onTextEdit(text:String, start:Int, end:Int):Void { - trace(text, start, end); label.text = label.text.substr(0, position) + text + label.text.substr(position); position += text.length; } diff --git a/source/funkin/editors/ui/UIWarningSubstate.hx b/source/funkin/editors/ui/UIWarningSubstate.hx index b20e78f0..cd1a70de 100644 --- a/source/funkin/editors/ui/UIWarningSubstate.hx +++ b/source/funkin/editors/ui/UIWarningSubstate.hx @@ -12,6 +12,7 @@ class UIWarningSubstate extends MusicBeatSubstate { var title:String; var message:String; var buttons:Array; + var isError:Bool = true; var titleSpr:UIText; var messageSpr:UIText; @@ -55,22 +56,30 @@ class UIWarningSubstate extends MusicBeatSubstate { warnCam.zoom = 0.1; FlxG.cameras.add(warnCam, false); - var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), 280, "editors/ui/warning-popup"); + var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), 232, 'editors/ui/${isError ? "normal" : "grayscale"}-popup'); spr.x = (FlxG.width - spr.bWidth) / 2; spr.y = (FlxG.height - spr.bHeight) / 2; + spr.color = isError ? 0xFFFF0000 : 0xFFFFFF00; add(spr); add(titleSpr = new UIText(spr.x + 25, spr.y, spr.bWidth - 50, title, 15, -1)); titleSpr.y = spr.y + ((30 - titleSpr.height) / 2); - add(messageSpr = new UIText(spr.x + 10, spr.y + 40, spr.bWidth - 20, message, 15, -1)); + var sprIcon:FlxSprite = new FlxSprite(spr.x + 26, spr.y + 28 + 26).loadGraphic(Paths.image('editors/warnings/${isError ? "error" : "warning"}')); + add(sprIcon); + + add(messageSpr = new UIText(sprIcon.x + 100 + 16, sprIcon.y + 12, spr.bWidth - 100 - (26 * 2), message)); var xPos = (FlxG.width - (30 + (170 * buttons.length))) / 2; for(k=>b in buttons) { - var button = new UIButton(xPos + 20 + (170 * k), spr.y + spr.bHeight - 40, b.label, function() { + var button = new UIButton(xPos + 20 + (170 * k), spr.y + spr.bHeight - (36 + 16), b.label, function() { b.onClick(this); close(); }, 160, 30); + if (b.color != null) { + button.frames = Paths.getFrames("editors/ui/grayscale-button"); + button.color = b.color; + } add(button); } @@ -89,15 +98,17 @@ class UIWarningSubstate extends MusicBeatSubstate { FlxG.cameras.remove(warnCam); } - public function new(title:String, message:String, buttons:Array) { + public function new(title:String, message:String, buttons:Array, ?isError:Bool = true) { super(); this.title = title; this.message = message; this.buttons = buttons; + this.isError = isError; } } typedef WarningButton = { var label:String; + var ?color:Int; var onClick:UIWarningSubstate->Void; } \ No newline at end of file diff --git a/source/funkin/game/NoteGroup.hx b/source/funkin/game/NoteGroup.hx index dc066ea4..ca617eec 100644 --- a/source/funkin/game/NoteGroup.hx +++ b/source/funkin/game/NoteGroup.hx @@ -8,6 +8,11 @@ class NoteGroup extends FlxTypedGroup { var i:Int = 0; var __currentlyLooping:Bool = false; + /** + * How many ms it should show a note before it should be hit + **/ + public var limit:Float = 1500; + /** * Preallocates the members array with nulls, but if theres anything in the array already it clears it **/ @@ -36,7 +41,7 @@ class NoteGroup extends FlxTypedGroup { if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.active) { continue; } - if (__loopSprite.strumTime - Conductor.songPosition > 1500) + if (__loopSprite.strumTime - Conductor.songPosition > limit) break; __loopSprite.update(elapsed); } @@ -55,7 +60,7 @@ class NoteGroup extends FlxTypedGroup { __loopSprite = members[i--]; if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.visible) continue; - if (__loopSprite.strumTime - Conductor.songPosition > 1500) break; + if (__loopSprite.strumTime - Conductor.songPosition > limit) break; __loopSprite.draw(); } __currentlyLooping = oldCur; @@ -74,7 +79,7 @@ class NoteGroup extends FlxTypedGroup { __loopSprite = members[i--]; if (__loopSprite == null || !__loopSprite.exists) continue; - if (__loopSprite.strumTime - Conductor.songPosition > 1500) break; + if (__loopSprite.strumTime - Conductor.songPosition > limit) break; noteFunc(__loopSprite); } __currentlyLooping = oldCur; diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 46d53041..9ad808ac 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -565,8 +565,8 @@ class PlayState extends MusicBeatState for(folder in scriptsFolders) { for(file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) { if (folder == 'data/charts/') - Logs.trace('data/charts/ is deprecrated and will be removed in the future. Please move script $file.hx to songs/', WARNING, DARKYELLOW); - + Logs.trace('data/charts/ is deprecrated and will be removed in the future. Please move script $file to songs/', WARNING, DARKYELLOW); + addScript(file); } } @@ -576,7 +576,9 @@ class PlayState extends MusicBeatState for (file in Paths.getFolderContent('data/events/', true, fromMods ? MODS : BOTH)) { var fileName:String = Path.withoutExtension(Path.withoutDirectory(file)); - if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) addScript(file); + if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) { + addScript(file); + } } } } @@ -1490,7 +1492,7 @@ class PlayState extends MusicBeatState if (event.showRating || (event.showRating == null && event.player)) { displayCombo(event); - displayRating(daRating, event); + displayRating(event.rating, event); ratingNum += 1; } } @@ -1531,7 +1533,7 @@ class PlayState extends MusicBeatState rating.velocity.y -= FlxG.random.int(140, 175); rating.velocity.x -= FlxG.random.int(0, 10); if (evt != null) { - rating.scale.set(evt.ratingScale,evt.ratingScale); + rating.scale.set(evt.ratingScale, evt.ratingScale); rating.antialiasing = evt.ratingAntialiasing; } rating.updateHitbox(); @@ -1547,7 +1549,7 @@ class PlayState extends MusicBeatState public function displayCombo(?evt:NoteHitEvent = null):Void { var pre:String = evt != null ? evt.ratingPrefix : ""; var suf:String = evt != null ? evt.ratingSuffix : ""; - + var separatedScore:String = Std.string(combo).addZeros(3); if (combo == 0 || combo >= 10) { diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index 70fd260f..f8c5f0ce 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -238,6 +238,17 @@ class Stage extends FlxBasic implements IBeatReceiver { public function stepHit(curStep:Int) {} public function measureHit(curMeasure:Int) {} + + public static function getList(?mods:Bool = false):Array { + var list:Array = []; + for (path in Paths.getFolderContent('data/stages/', true, mods ? MODS : BOTH)) + if (Path.extension(path) == "xml" || Path.extension(path) == "hx") { + var file:String = Path.withoutDirectory(Path.withoutExtension(path)); + if (!list.contains(file)) list.push(file); + } + + return list; + } } class StageCharPos extends FlxObject { diff --git a/source/funkin/game/cutscenes/ScriptedCutscene.hx b/source/funkin/game/cutscenes/ScriptedCutscene.hx index 65d48958..b17639eb 100644 --- a/source/funkin/game/cutscenes/ScriptedCutscene.hx +++ b/source/funkin/game/cutscenes/ScriptedCutscene.hx @@ -72,7 +72,7 @@ class ScriptedCutscene extends Cutscene { #if REGION public function startDialogue(path:String, ?callback:Void->Void) { persistentDraw = true; - openSubState(new VideoCutscene(path, function() { + openSubState(new DialogueCutscene(path, function() { if (callback != null) callback(); })); @@ -83,4 +83,4 @@ class ScriptedCutscene extends Cutscene { private inline function get_isDialoguePlaying() return subState is DialogueCutscene; #end -} \ No newline at end of file +} diff --git a/source/funkin/game/cutscenes/VideoCutscene.hx b/source/funkin/game/cutscenes/VideoCutscene.hx index 301cd9d3..68a86b92 100644 --- a/source/funkin/game/cutscenes/VideoCutscene.hx +++ b/source/funkin/game/cutscenes/VideoCutscene.hx @@ -9,7 +9,7 @@ import sys.io.File; import funkin.backend.FunkinText; import haxe.xml.Access; #if VIDEO_CUTSCENES -import hxcodec.flixel.FlxVideo; +import hxvlc.flixel.FlxVideo; #end /** diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index 7568c27a..3464f3ce 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -22,7 +22,7 @@ class PauseSubState extends MusicBeatSubstate var grpMenuShit:FlxTypedGroup; - var menuItems:Array = ['Resume', 'Restart Song', 'Change Controls', 'Options','Exit to menu']; + var menuItems:Array = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"]; var curSelected:Int = 0; var pauseMusic:FlxSound; @@ -42,6 +42,9 @@ class PauseSubState extends MusicBeatSubstate { super.create(); + if (menuItems.contains("Exit to charter") && !PlayState.chartingMode) + menuItems.remove("Exit to charter"); + add(parentDisabler = new FunkinParentDisabler()); pauseScript = Script.create(Paths.script(script)); @@ -147,8 +150,10 @@ class PauseSubState extends MusicBeatSubstate case "Change Controls": persistentDraw = false; openSubState(new KeybindsOptions()); - case "Options": + case "Change Options": FlxG.switchState(new OptionsMenu()); + case "Exit to charter": + FlxG.switchState(new funkin.editors.charter.Charter(PlayState.SONG.meta.name, PlayState.difficulty, false)); case "Exit to menu": CoolUtil.playMenuSong(); FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState()); diff --git a/source/funkin/menus/TitleState.hx b/source/funkin/menus/TitleState.hx index d46e04b9..da7e25f8 100644 --- a/source/funkin/menus/TitleState.hx +++ b/source/funkin/menus/TitleState.hx @@ -48,9 +48,9 @@ class TitleState extends MusicBeatState MusicBeatState.skipTransIn = true; - super.create(); - startIntro(); + + super.create(); } var logoBl:FlxSprite; diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 397b8069..8adaabce 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -43,9 +43,9 @@ class GameplayOptions extends OptionsScreen { FlxG.camera.zoom += 0.03; __lastBeat = Conductor.curBeat; } - if (__lastSongBeat != Math.floor(Conductor.getStepForTime(FlxG.sound.music.time / 2) / Conductor.stepsPerBeat)) { + if (__lastSongBeat != Math.floor(Conductor.getStepForTime(FlxG.sound.music.time) / Conductor.stepsPerBeat)) { __metronome.replay(); - __lastSongBeat = Math.floor(Conductor.getStepForTime(FlxG.sound.music.time / 2) / Conductor.stepsPerBeat); + __lastSongBeat = Math.floor(Conductor.getStepForTime(FlxG.sound.music.time) / Conductor.stepsPerBeat); } } else FlxG.sound.music.volume = 1; diff --git a/source/funkin/options/type/NewOption.hx b/source/funkin/options/type/NewOption.hx index bd7d1a14..e5c0f9eb 100644 --- a/source/funkin/options/type/NewOption.hx +++ b/source/funkin/options/type/NewOption.hx @@ -13,6 +13,7 @@ class NewOption extends TextOption { iconSpr = new FlxSprite().loadGraphic(Paths.image("editors/new")); iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2); + iconSpr.offset.set(20, -15); add(iconSpr); } } \ No newline at end of file diff --git a/source/hscript/Config.hx b/source/hscript/Config.hx new file mode 100644 index 00000000..bba205c5 --- /dev/null +++ b/source/hscript/Config.hx @@ -0,0 +1,31 @@ +package hscript; + +class Config { + // Runs support for custom classes in these + public static final ALLOWED_CUSTOM_CLASSES = [ + "flixel", + + "funkin", + ]; + + // Runs support for abstract support in these + public static final ALLOWED_ABSTRACT_AND_ENUM = [ + "flixel", + "openfl.display.BlendMode", + + "haxe.xml", + "haxe.CallStack", + "funkin", + ]; + + // Incase any of your files fail + // These are the module names + public static final DISALLOW_CUSTOM_CLASSES = [ + + ]; + + public static final DISALLOW_ABSTRACT_AND_ENUM = [ + "funkin.backend.FunkinSprite", // Error: String has no field trim, Due to Func + "funkin.backend.scripting.events.PlayAnimEvent", // Error: expected member name or ';' after declaration specifiers, Due to Func + ]; +} \ No newline at end of file