diff --git a/assets/data/config/options.xml b/assets/data/config/options.xml new file mode 100644 index 00000000..15555af0 --- /dev/null +++ b/assets/data/config/options.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildnumber.txt b/buildnumber.txt index a7318fc0..0b77351e 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -2474 \ No newline at end of file +2481 \ No newline at end of file diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx index f314d97b..6735f621 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -1,15 +1,15 @@ package funkin.options; +import funkin.options.type.Checkbox; +import haxe.xml.Access; import flixel.tweens.FlxTween; -import funkin.options.OptionsScreen; import funkin.menus.MainMenuState; -import funkin.options.type.TextOption; import flixel.util.typeLimit.OneOfTwo; -import funkin.options.type.OptionType; +import funkin.options.type.*; import funkin.options.categories.*; +import funkin.options.TreeMenu; -class OptionsMenu extends MusicBeatState { - +class OptionsMenu extends TreeMenu { public static var mainOptions:Array = [ { name: 'Controls', @@ -38,39 +38,10 @@ class OptionsMenu extends MusicBeatState { state: DebugOptions } ]; - - public var options:Array; - public var optionsTree:OptionsTree; - public function new(?options:Array) { - super(); - } public override function create() { super.create(); - var optionSprites:Array = []; - if (options == null) { - optionSprites = [for(o in mainOptions) new TextOption(o.name, o.desc, function() { - if (o.substate != null) { - persistentUpdate = false; - persistentDraw = true; - if (o.substate is MusicBeatSubstate) { - openSubState(o.substate); - } else { - openSubState(Type.createInstance(o.substate, [])); - } - } else { - if (o.state is OptionsScreen) { - optionsTree.add(o.state); - } else { - optionsTree.add(Type.createInstance(o.state, [])); - } - } - })]; - } else { - optionSprites = [for(o in options) Type.createInstance(o.type, o.args)]; - } - var bg:FlxSprite = new FlxSprite(-80).loadAnimatedGraphic(Paths.image('menus/menuBGBlue')); // bg.scrollFactor.set(); bg.scale.set(1.15, 1.15); @@ -80,61 +51,76 @@ class OptionsMenu extends MusicBeatState { bg.antialiasing = true; add(bg); - optionsTree = new OptionsTree(); - optionsTree.onMenuChange = onMenuChange; - optionsTree.onMenuClose = onMenuClose; - var mainOptionScreen:OptionsScreen = new OptionsScreen(); - for(o in optionSprites) - mainOptionScreen.add(o); - optionsTree.add(mainOptionScreen); - add(optionsTree); - - FlxG.camera.scroll.set(-FlxG.width, 0); - } - - public function onMenuChange() { - if (optionsTree.members.length <= 0) { - exit(); - } else { - if (menuChangeTween != null) { - menuChangeTween.cancel(); + main = new OptionsScreen("Options", "Select a category to continue.", [for(o in mainOptions) new TextOption(o.name, o.desc, function() { + if (o.substate != null) { + persistentUpdate = false; + persistentDraw = true; + if (o.substate is MusicBeatSubstate) { + openSubState(o.substate); + } else { + openSubState(Type.createInstance(o.substate, [])); + } + } else { + if (o.state is OptionsScreen) { + optionsTree.add(o.state); + } else { + optionsTree.add(Type.createInstance(o.state, [])); + } + } + })]); + + var xmlPath = Paths.xml("config/options"); + for(source in [funkin.system.AssetsLibraryList.AssetSource.SOURCE, funkin.system.AssetsLibraryList.AssetSource.MODS]) { + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { + var access:Access = null; + try { + access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source))); + } catch(e) { + Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR); + } + + if (access != null) + for(o in parseOptionsFromXML(access)) + main.add(o); } - - menuChangeTween = FlxTween.tween(FlxG.camera.scroll, {x: FlxG.width * Math.max(0, (optionsTree.members.length-1))}, 1.5, {ease: menuTransitionEase, onComplete: function(t) { - optionsTree.clearLastMenu(); - menuChangeTween = null; - }}); - // TODO: update top info } + } - public function exit() { + public override function exit() { Options.save(); Options.applySettings(); - FlxG.switchState(new MainMenuState()); + super.exit(); } - public function onMenuClose(m:OptionsScreen) { - CoolUtil.playMenuSFX(CANCEL); + /** + * XML STUFF + */ + public function parseOptionsFromXML(xml:Access):Array { + var options:Array = []; + + for(node in xml.elements) { + if (!node.has.name) { + Logs.trace("An option node requires a name attribute.", WARNING); + continue; + } + var name = node.getAtt("name"); + var desc = node.getAtt("desc").getDefault("No Description"); + + switch(node.name) { + case "checkbox": + if (!node.has.id) { + Logs.trace("A checkbox option requires an \"id\" for option saving.", WARNING); + continue; + } + options.push(new Checkbox(name, desc, node.att.id, FlxG.save.data)); + case "menu": + options.push(new TextOption(name, desc, function() { + optionsTree.add(new OptionsScreen(name, desc, parseOptionsFromXML(node))); + })); + } + } + + return options; } - - var menuChangeTween:FlxTween; - public override function update(elapsed:Float) { - super.update(elapsed); - } - - public static inline function menuTransitionEase(e:Float) - return FlxEase.quintInOut(FlxEase.cubeOut(e)); -} - -typedef OptionCategory = { - var name:String; - var desc:String; - var state:OneOfTwo>; - var ?substate:OneOfTwo>; -} - -typedef OptionTypeDef = { - var type:Class; - var args:Array; } \ No newline at end of file diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx index 735112bd..bf3314ae 100644 --- a/source/funkin/options/OptionsScreen.hx +++ b/source/funkin/options/OptionsScreen.hx @@ -12,9 +12,14 @@ class OptionsScreen extends FlxTypedSpriteGroup { private var __firstFrame:Bool = true; - public function new() { + public var name:String; + public var desc:String; + + public function new(name:String, desc:String, ?options:Array) { super(); - // EXTEND then add options + this.name = name; + this.desc = desc; + if (options != null) for(o in options) add(o); } public override function update(elapsed:Float) { diff --git a/source/funkin/options/OptionsTree.hx b/source/funkin/options/OptionsTree.hx index 2960e0ab..44970f16 100644 --- a/source/funkin/options/OptionsTree.hx +++ b/source/funkin/options/OptionsTree.hx @@ -1,5 +1,6 @@ package funkin.options; +import flixel.util.FlxDestroyUtil; class OptionsTree extends FlxTypedGroup { public var lastMenu:OptionsScreen; public override function new() { @@ -59,6 +60,11 @@ class OptionsTree extends FlxTypedGroup { } } + public override function destroy() { + super.destroy(); + lastMenu = FlxDestroyUtil.destroy(lastMenu); + } + public dynamic function onMenuChange() { } diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx new file mode 100644 index 00000000..0d8a60b7 --- /dev/null +++ b/source/funkin/options/TreeMenu.hx @@ -0,0 +1,108 @@ +package funkin.options; + +import funkin.ui.FunkinText; +import flixel.tweens.FlxTween; +import funkin.menus.MainMenuState; +import funkin.options.type.TextOption; +import flixel.util.typeLimit.OneOfTwo; +import funkin.options.type.OptionType; +import funkin.options.categories.*; + +class TreeMenu extends MusicBeatState { + + public var main:OptionsScreen; + public var optionsTree:OptionsTree; + public var pathLabel:FunkinText; + public var pathDesc:FunkinText; + public var pathBG:FlxSprite; + + public function new() { + super(); + } + + public override function createPost() { + if (main == null) throw "\"main\" variable has not been set in extended class!"; + + FlxG.camera.scroll.set(-FlxG.width, 0); + + pathLabel = new FunkinText(4, 4, FlxG.width - 8, "> Tree Menu", 32, false); + pathLabel.scrollFactor.set(); + + pathDesc = new FunkinText(4, pathLabel.y + pathLabel.height + 2, FlxG.width - 8, "Current Tree Menu Description", 16, false); + pathDesc.scrollFactor.set(); + + pathBG = new FlxSprite().makeGraphic(1, 1, 0xFF000000); + pathBG.scale.set(FlxG.width, pathDesc.y + pathDesc.height + 2); + pathBG.updateHitbox(); + pathBG.alpha = 0.25; + pathBG.scrollFactor.set(); + + optionsTree = new OptionsTree(); + optionsTree.onMenuChange = onMenuChange; + optionsTree.onMenuClose = onMenuClose; + optionsTree.add(main); + + + add(optionsTree); + add(pathBG); + add(pathLabel); + add(pathDesc); + + + super.createPost(); + } + + public function onMenuChange() { + if (optionsTree.members.length <= 0) { + exit(); + } else { + if (menuChangeTween != null) + menuChangeTween.cancel(); + + menuChangeTween = FlxTween.tween(FlxG.camera.scroll, {x: FlxG.width * Math.max(0, (optionsTree.members.length-1))}, 1.5, {ease: menuTransitionEase, onComplete: function(t) { + optionsTree.clearLastMenu(); + menuChangeTween = null; + }}); + + var t = ""; + for(o in optionsTree.members) + t += '${o.name} > '; + pathLabel.text = t; + + pathDesc.text = optionsTree.members.last().desc; + pathBG.scale.set(FlxG.width, pathDesc.y + pathDesc.height + 2); + pathBG.updateHitbox(); + } + } + + public function exit() { + FlxG.switchState(new MainMenuState()); + } + + public function onMenuClose(m:OptionsScreen) { + CoolUtil.playMenuSFX(CANCEL); + } + + var menuChangeTween:FlxTween; + public override function update(elapsed:Float) { + super.update(elapsed); + + // in case path gets so long it goes offscreen + pathLabel.x = lerp(pathLabel.x, Math.max(0, FlxG.width - 4 - pathLabel.width), 0.125); + } + + public static inline function menuTransitionEase(e:Float) + return FlxEase.quintInOut(FlxEase.cubeOut(e)); +} + +typedef OptionCategory = { + var name:String; + var desc:String; + var state:OneOfTwo>; + var ?substate:OneOfTwo>; +} + +typedef OptionTypeDef = { + var type:Class; + var args:Array; +} \ No newline at end of file diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx index 3fd7ee88..6fc48e5d 100644 --- a/source/funkin/options/categories/AppearanceOptions.hx +++ b/source/funkin/options/categories/AppearanceOptions.hx @@ -2,7 +2,7 @@ package funkin.options.categories; class AppearanceOptions extends OptionsScreen { public override function new() { - super(); + super("Appearance", "Change Appearance options such as Flashing menus..."); add(new Checkbox( "Antialiasing", "If unchecked, will disable antialiasing on every sprite. Can boost performances at the cost of sharper, more pixely sprites", diff --git a/source/funkin/options/categories/DebugOptions.hx b/source/funkin/options/categories/DebugOptions.hx index 0a355730..7f98bddc 100644 --- a/source/funkin/options/categories/DebugOptions.hx +++ b/source/funkin/options/categories/DebugOptions.hx @@ -4,7 +4,7 @@ import funkin.windows.WindowsAPI; class DebugOptions extends OptionsScreen { public override function new() { - super(); + super("Debug Options", "Use this menu to change debug options."); #if windows add(new TextOption( "Show Console", diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 9d04023c..42cd3df9 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -2,7 +2,7 @@ package funkin.options.categories; class GameplayOptions extends OptionsScreen { public override function new() { - super(); + super("Gameplay", 'Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...'); add(new Checkbox( "Downscroll", "If checked, notes will go from up to down instead of down to up, like if they were falling", diff --git a/source/funkin/options/categories/MiscOptions.hx b/source/funkin/options/categories/MiscOptions.hx index 48a4492b..dc4a2172 100644 --- a/source/funkin/options/categories/MiscOptions.hx +++ b/source/funkin/options/categories/MiscOptions.hx @@ -3,7 +3,7 @@ package funkin.options.categories; class MiscOptions extends OptionsScreen { public override function new() { - super(); + super("Miscellaneous", "Use this menu to reset save data or engine settings."); #if UPDATE_CHECKING add(new Checkbox( "Enable Beta Updates", diff --git a/source/funkin/options/type/Checkbox.hx b/source/funkin/options/type/Checkbox.hx index 2b767429..a06076c6 100644 --- a/source/funkin/options/type/Checkbox.hx +++ b/source/funkin/options/type/Checkbox.hx @@ -8,6 +8,8 @@ class Checkbox extends TextOption { public var checkbox:FlxSprite; public var checked:Bool; + public var parent:Dynamic; + private function get_selected() { } @@ -20,9 +22,14 @@ class Checkbox extends TextOption { ]; private var baseCheckboxOffset:FlxPoint = FlxPoint.get(); - public function new(text:String, desc:String, optionName:String) { + public function new(text:String, desc:String, optionName:String, ?parent:Dynamic) { super(text, desc, null); + if (parent == null) + parent = Options; + + this.parent = parent; + checkbox = new FlxSprite(10, -40); checkbox.frames = Paths.getFrames('menus/options/checkboxThingie'); checkbox.animation.addByPrefix("checked", "Check Box Selected Static", 24); @@ -36,7 +43,7 @@ class Checkbox extends TextOption { baseCheckboxOffset.set(checkbox.offset.x, checkbox.offset.y); this.optionName = optionName; - checked = Reflect.field(Options, optionName); + checked = Reflect.field(parent, optionName); } public override function update(elapsed:Float) { @@ -52,7 +59,7 @@ class Checkbox extends TextOption { } public override function onSelect() { - Reflect.setField(Options, optionName, checked = !checked); + Reflect.setField(parent, optionName, checked = !checked); checkbox.animation.play("checking", true, !checked); }