diff --git a/Options.py b/Options.py
index e11e078a1d45..7f480cbaae8d 100644
--- a/Options.py
+++ b/Options.py
@@ -1130,6 +1130,8 @@ class OptionGroup(typing.NamedTuple):
"""Name of the group to categorize these options in for display on the WebHost and in generated YAMLS."""
options: typing.List[typing.Type[Option[typing.Any]]]
"""Options to be in the defined group."""
+ start_collapsed: bool = False
+ """Whether the group will start collapsed on the WebHost options pages."""
item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints,
diff --git a/WebHostLib/options.py b/WebHostLib/options.py
index bc63ec93318a..4a791135d7c6 100644
--- a/WebHostLib/options.py
+++ b/WebHostLib/options.py
@@ -32,11 +32,16 @@ def render_options_page(template: str, world_name: str, is_complex: bool = False
return redirect("games")
visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui
+ start_collapsed = {"Game Options": False}
+ for group in world.web.option_groups:
+ start_collapsed[group.name] = group.start_collapsed
+
return render_template(
template,
world_name=world_name,
world=world,
option_groups=Options.get_option_groups(world, visibility_level=visibility_flag),
+ start_collapsed=start_collapsed,
issubclass=issubclass,
Options=Options,
theme=get_world_theme(world_name),
diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html
index 56576109149f..2506cf9619e6 100644
--- a/WebHostLib/templates/playerOptions/playerOptions.html
+++ b/WebHostLib/templates/playerOptions/playerOptions.html
@@ -69,7 +69,7 @@
Player Options
{% for group_name, group_options in option_groups.items() %}
-
+
{{ group_name }}
diff --git a/WebHostLib/templates/weightedOptions/weightedOptions.html b/WebHostLib/templates/weightedOptions/weightedOptions.html
index c21671a86385..b3aefd483535 100644
--- a/WebHostLib/templates/weightedOptions/weightedOptions.html
+++ b/WebHostLib/templates/weightedOptions/weightedOptions.html
@@ -51,7 +51,7 @@
Weighted Options
{% for group_name, group_options in option_groups.items() %}
-
+
{{ group_name }}
{% for option_name, option in group_options.items() %}
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index f8bc525ea57b..5d674c0c22fd 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -116,12 +116,19 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Web
# don't allow an option to appear in multiple groups, allow "Item & Location Options" to appear anywhere by the
# dev, putting it at the end if they don't define options in it
option_groups: List[OptionGroup] = dct.get("option_groups", [])
+ prebuilt_options = ["Game Options", "Item & Location Options"]
seen_options = []
item_group_in_list = False
for group in option_groups:
- assert group.name != "Game Options", "Game Options is a pre-determined group and can not be defined."
assert group.options, "A custom defined Option Group must contain at least one Option."
+ # catch incorrectly titled versions of the prebuilt groups so they don't create extra groups
+ title_name = group.name.title()
+ if title_name in prebuilt_options:
+ group.name = title_name
+
if group.name == "Item & Location Options":
+ assert not any(option in item_and_loc_options for option in group.options), \
+ f"Item and Location Options cannot be specified multiple times"
group.options.extend(item_and_loc_options)
item_group_in_list = True
else:
@@ -133,7 +140,7 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Web
assert option not in seen_options, f"{option} found in two option groups"
seen_options.append(option)
if not item_group_in_list:
- option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options))
+ option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options, True))
return super().__new__(mcs, name, bases, dct)