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);
}