Skip to content
This repository was archived by the owner on Sep 28, 2025. It is now read-only.

Commit f8829c6

Browse files
Mod options support (#58)
* mod options wip * add more types * yeah * add 2nd arg
1 parent 1b87369 commit f8829c6

File tree

10 files changed

+181
-88
lines changed

10 files changed

+181
-88
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [1.0.3] - Unreleased
9+
10+
### Added
11+
- Added the ability for mods to create their own options through an `options.json` file
912

1013
### Fixed
1114
- Note type textures not loading sometimes
15+
- Switch Mod Menu not having a scrollfactor of 0, making mod options scroll with the camera.
1216

1317
## [1.0.2] - 7/20/25
1418

source/modding/ModOptions.hx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package modding;
2+
3+
typedef ModOptions = {
4+
var options:Array<ModOption>;
5+
}
6+
7+
typedef ModOption = {
8+
var name:String;
9+
var description:String;
10+
var type:ModOptionType;
11+
12+
@:optional
13+
var save:String;
14+
@:optional
15+
var defaultValue:Dynamic;
16+
@:optional
17+
var values:Array<String>;
18+
@:optional
19+
var script:String;
20+
}
21+
22+
enum abstract ModOptionType(String) to String from String {
23+
var BOOL:String = "bool";
24+
var STRING:String = "string";
25+
var STATE:String = "state";
26+
var SUBSTATE:String = "substate";
27+
}

source/modding/SwitchModSubstate.hx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package modding;
22

3+
import flixel.group.FlxSpriteContainer.FlxTypedSpriteContainer;
34
#if MODDING_ALLOWED
45
import substates.MusicBeatSubstate;
56
import utilities.Options;
@@ -16,7 +17,7 @@ class SwitchModSubstate extends MusicBeatSubstate {
1617
var curSelected:Int = 0;
1718
var ui_Skin:Null<String>;
1819

19-
public var page:FlxTypedGroup<ChangeModOption> = new FlxTypedGroup<ChangeModOption>();
20+
public var page:FlxTypedSpriteContainer<ChangeModOption> = new FlxTypedSpriteContainer<ChangeModOption>();
2021

2122
public static var instance:SwitchModSubstate;
2223

@@ -39,6 +40,7 @@ class SwitchModSubstate extends MusicBeatSubstate {
3940
add(menuBG);
4041

4142
super.create();
43+
page.scrollFactor.set();
4244
add(page);
4345

4446
PolymodHandler.loadModMetadata();
Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package modding.custom;
22

3+
#if HSCRIPT_ALLOWED
34
import modding.scripts.ExecuteOn;
45
import openfl.utils.Assets;
56
import flixel.FlxG;
@@ -9,46 +10,50 @@ import flixel.FlxObject;
910
import states.TitleState;
1011
import flixel.FlxObject;
1112

13+
class CustomState extends MusicBeatState {
14+
public var script:HScript;
15+
public var scriptPath:String;
1216

13-
class CustomState extends MusicBeatState{
14-
public var script:HScript;
15-
public static var instance:CustomState = null;
16-
override function new(script:String){
17-
if(Assets.exists(Paths.hx("classes/states/" + script))){
18-
instance = this;
19-
this.script = new HScript(Paths.hx("classes/states/" + script));
20-
this.script.interp.variables.set("add", function(obj:FlxObject)
21-
{
22-
add(obj);
23-
});
24-
}
25-
else{
26-
trace('Could not find script at path ${script}', ERROR);
27-
FlxG.switchState(() -> new TitleState());
28-
}
29-
super();
30-
}
31-
override function create(){
32-
super.create();
33-
call("createPost");
34-
}
35-
override function update(elapsed:Float){
36-
call("update", [elapsed]);
17+
public static var instance:CustomState = null;
18+
19+
override public function new(scriptPath:String) {
20+
super();
21+
this.scriptPath = scriptPath;
22+
}
23+
24+
override function create() {
25+
if (Assets.exists(Paths.hx("classes/states/" + scriptPath))) {
26+
instance = this;
27+
this.script = new HScript(Paths.hx("classes/states/" + scriptPath));
28+
} else {
29+
trace('Could not find script at path ${scriptPath}', ERROR);
30+
FlxG.switchState(() -> new TitleState());
31+
}
32+
super.create();
33+
call("createPost");
34+
}
35+
36+
override function update(elapsed:Float) {
37+
call("update", [elapsed]);
3738
super.update(elapsed);
3839
call("updatePost", [elapsed]);
39-
}
40-
override function beatHit(){
41-
call("beatHit");
40+
}
41+
42+
override function beatHit() {
43+
call("beatHit");
4244
super.beatHit();
4345
call("beatHitPost");
44-
}
45-
override function stepHit(){
46-
call("stepHit");
46+
}
47+
48+
override function stepHit() {
49+
call("stepHit");
4750
super.stepHit();
4851
call("stepHitPost");
49-
}
50-
override public function call(func:String, ?args:Array<Any>, executeOn:ExecuteOn = BOTH) {
51-
super.call(func, args);
52+
}
53+
54+
override public function call(func:String, ?args:Array<Any>, executeOn:ExecuteOn = BOTH) {
55+
super.call(func, args);
5256
script?.call(func, args);
5357
}
54-
}
58+
}
59+
#end
Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,53 @@
11
package modding.custom;
22

3+
#if HSCRIPT_ALLOWED
34
import openfl.utils.Assets;
45
import modding.scripts.languages.HScript;
56
import substates.MusicBeatSubstate;
67
import flixel.FlxObject;
78

8-
class CustomSubstate extends MusicBeatSubstate{
9-
public var script:HScript;
10-
public static var instance:CustomSubstate = null;
11-
override function new(script:String){
12-
if(Assets.exists(Paths.hx("classes/substates/" + script))){
13-
instance = this;
14-
this.script = new HScript(Paths.hx("classes/substates/" + script));
15-
this.script.interp.variables.set("add", function(obj:FlxObject)
16-
{
17-
add(obj);
18-
});
19-
}
20-
else
21-
trace('Could not find script at path ${script}', ERROR);
22-
super();
23-
}
24-
override function create(){
25-
super.create();
26-
call("createPost");
27-
}
28-
override function update(elapsed:Float){
29-
call("update", [elapsed]);
9+
class CustomSubstate extends MusicBeatSubstate {
10+
public var script:HScript;
11+
12+
public static var instance:CustomSubstate = null;
13+
public var scriptPath:String;
14+
15+
override public function new(scriptPath:String) {
16+
super();
17+
this.scriptPath = scriptPath;
18+
}
19+
20+
override public function create() {
21+
if (Assets.exists(Paths.hx("classes/substates/" + scriptPath))) {
22+
instance = this;
23+
this.script = new HScript(Paths.hx("classes/substates/" + scriptPath));
24+
} else {
25+
trace('Could not find script at path ${scriptPath}', ERROR);
26+
}
27+
super.create();
28+
call("createPost");
29+
}
30+
31+
override public function update(elapsed:Float) {
32+
call("update", [elapsed]);
3033
super.update(elapsed);
3134
call("updatePost", [elapsed]);
32-
}
33-
override function beatHit(){
34-
call("beatHit");
35+
}
36+
37+
override public function beatHit() {
38+
call("beatHit");
3539
super.beatHit();
3640
call("beatHitPost");
37-
}
38-
override function stepHit(){
39-
call("stepHit");
41+
}
42+
43+
override function stepHit() {
44+
call("stepHit");
4045
super.stepHit();
4146
call("stepHitPost");
42-
}
43-
public inline function call(func:String, ?args:Array<Dynamic>) {
44-
script.call(func, args);
4547
}
46-
}
48+
49+
public inline function call(func:String, ?args:Array<Dynamic>) {
50+
script?.call(func, args);
51+
}
52+
}
53+
#end

source/modding/scripts/languages/LuaScript.hx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,8 +3255,8 @@ class LuaScript extends Script {
32553255
return '${FlxG.random.int(100, 999)}.${FlxG.random.int(1, 99)}.${FlxG.random.int(1, 99)}.${FlxG.random.int(1, 99)}';
32563256
});
32573257

3258-
setFunction("getOption", function(saveStr:String) {
3259-
return Options.getData(saveStr);
3258+
setFunction("getOption", function(saveStr:String, saveKey:String = "main") {
3259+
return Options.getData(saveStr, saveKey);
32603260
});
32613261

32623262
setFunction("getCurrentMod", function(saveStr:String) {

source/states/OptionsMenu.hx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package states;
22

3+
import haxe.Json;
34
import flixel.text.FlxText;
45
import flixel.util.FlxColor;
56
import utilities.MusicUtilities;
@@ -21,6 +22,9 @@ class OptionsMenu extends MusicBeatState {
2122
new PageOption("Gameplay", "Gameplay", "Change gameplay-related options,\nsuch as downscroll and ghost tapping."),
2223
new PageOption("Graphics", "Graphics", "Change graphical-related options,\nsuch as max FPS."),
2324
new PageOption("Misc", "Misc", "Change miscellaneous options that\ndon't fit in the other categories."),
25+
#if MODDING_ALLOWED
26+
new PageOption("Mod Options", "Mod Options", "Change options for specific mods."),
27+
#end
2428
new PageOption("Developer Options", "Developer Options", "Change options for developing mods.")
2529
],
2630
"Gameplay" => [
@@ -40,7 +44,7 @@ class OptionsMenu extends MusicBeatState {
4044
new StringSaveOption("Hitsound", CoolUtil.coolTextFile(Paths.txt("hitsoundList")), "hitsound", "Change the hitsound used when hitting a note.")
4145
],
4246
"Graphics" => [
43-
new PageOption("Back", "Categories", "Go back to the main menu."),
47+
new PageOption("Back", "Categories", "Go back to the main menu."),
4448
new PageOption("Note Options", "Note Options", "Change note-related options."),
4549
new PageOption("Info Display", "Info Display", "Change optiosn related to the info display.\n(FPS counter, memory display, etc)."),
4650
new PageOption("Optimizations", "Optimizations", "Change optimization options, such as anitaliasing."),
@@ -155,12 +159,16 @@ class OptionsMenu extends MusicBeatState {
155159
"Developer Options" => [
156160
new PageOption("Back", "Categories", "Go back to the main menu."),
157161
new BoolOption("Developer Mode", "developer", "When toggled, enables developer tools.\n(traced lines display, toolbox, etc)"),
158-
new DeveloperOption("Throw Exception On Error", "throwExceptionOnError", "When toggled, the game will throw an\nexception when an error is thrown."),
162+
new DeveloperOption("Throw Exception On Error", "throwExceptionOnError",
163+
"When toggled, the game will throw an\nexception when an error is thrown."),
159164
new DeveloperOption("Auto Open Charter", "autoOpenCharter",
160165
"When toggled, the game will automatically\nopen the chart editor when no chart is found."),
161166
new StepperSaveDeveloperOption("Chart Backup Interval", 1, 10, "backupDuration", 1,
162167
"Change how long the game will wait\nbefore creating a chart backup.\n(in minutes.)"),
163-
]
168+
],
169+
#if MODDING_ALLOWED
170+
"Mod Options" => [new PageOption("Back", "Categories", "Go back to the main menu."),]
171+
#end
164172
];
165173

166174
public var page:FlxTypedGroup<Option> = new FlxTypedGroup<Option>();
@@ -185,6 +193,31 @@ class OptionsMenu extends MusicBeatState {
185193
}
186194

187195
public override function create():Void {
196+
#if MODDING_ALLOWED
197+
for (mod in modding.ModList.getActiveMods(modding.PolymodHandler.metadataArrays)) {
198+
pages.get("Mod Options").push(new PageOption(mod, mod, modding.ModList.modMetadatas.get(mod).description));
199+
pages.set(mod, [new PageOption("Back", "Mod Options", "Go back to mod options.")]);
200+
if (sys.FileSystem.exists('mods/$mod/data/options.json')) {
201+
var modOptions:modding.ModOptions = cast Json.parse(sys.io.File.getContent('mods/$mod/data/options.json'));
202+
for (option in modOptions.options) {
203+
switch (StringTools.trim(option.type).toLowerCase()) { // thank you haxe for not wanting to cast it to a string.
204+
case "bool":
205+
pages.get(mod).push(new BoolOption(option.name, option.save, option.description, mod));
206+
case "string":
207+
pages.get(mod).push(new StringSaveOption(option.name, option.values, option.save, option.description, mod));
208+
#if HSCRIPT_ALLOWED
209+
case "state":
210+
pages.get(mod).push(new GameStateOption(option.name, new modding.custom.CustomState(option.script), option.description));
211+
case "substate":
212+
pages.get(mod).push(new GameSubStateOption(option.name, new modding.custom.CustomSubstate(option.script), option.description));
213+
#end
214+
default:
215+
throw 'Option type \'${option.type}\' is not a valid option type!';
216+
}
217+
}
218+
}
219+
}
220+
#end
188221
MusicBeatState.windowNameSuffix = "";
189222
instance = this;
190223

source/states/TitleState.hx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ class TitleState extends MusicBeatState {
7171
NoteVariables.init();
7272

7373
Options.init();
74-
Options.fixBinds();
7574

7675
LogStyle.ERROR.throwException = Options.getData("throwExceptionOnError");
7776

@@ -89,6 +88,7 @@ class TitleState extends MusicBeatState {
8988
PolymodHandler.loadMods();
9089
MusicBeatState.windowNamePrefix = Options.getData("curMod");
9190
CoolUtil.setWindowIcon("mods/" + Options.getData("curMod") + "/_polymod_icon.png");
91+
Options.initModOptions();
9292
#end
9393
NoteVariables.init();
9494
Options.fixBinds();

0 commit comments

Comments
 (0)