Skip to content

Commit d1423fd

Browse files
committed
try address options menu crashes
1 parent 4ce6162 commit d1423fd

File tree

4 files changed

+111
-58
lines changed

4 files changed

+111
-58
lines changed

src/willow1_mod_menu/lobby.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ruff: noqa: D103
22
import re
3-
from typing import TYPE_CHECKING, Any
3+
from typing import Any
44

55
import unrealsdk
66
from unrealsdk.hooks import Block
@@ -9,34 +9,19 @@
99
from mods_base import CoopSupport, Game, Mod, get_ordered_mod_list, hook, html_to_plain_text
1010
from mods_base.mod_list import base_mod
1111

12+
from .util import find_focused_item
13+
1214
# from .options import create_mod_options_menu
1315

1416
type WillowGFxLobbyMultiplayer = UObject
1517
type WillowGFxMenuFrontend = UObject
1618

17-
if TYPE_CHECKING:
18-
from enum import auto
19-
20-
from unrealsdk.unreal._uenum import UnrealEnum # pyright: ignore[reportMissingModuleSource]
21-
22-
class ASType(UnrealEnum):
23-
AS_Undefined = auto()
24-
AS_Null = auto()
25-
AS_Number = auto()
26-
AS_String = auto()
27-
AS_Boolean = auto()
28-
AS_MAX = auto()
29-
30-
else:
31-
from unrealsdk import find_enum
32-
33-
ASType = find_enum("ASType")
3419

3520
FRIENDLY_DISPLAY_VERSION = unrealsdk.config.get("willow1_mod_menu", {}).get(
3621
"display_version",
3722
base_mod.version,
3823
)
39-
RE_SELECTED_IDX = re.compile(r"_level\d+\.mMenu\.mList\.item(\d+)$")
24+
RE_SELECTED_IDX = re.compile(r"^_level\d+\.mMenu\.mList\.item(\d+)$")
4025

4126
current_menu = WeakPointer()
4227
drawn_mods: list[Mod] = []
@@ -165,15 +150,8 @@ def get_focused_mod(menu: WillowGFxLobbyMultiplayer) -> Mod | None:
165150
Returns:
166151
The selected mod, or None if unable to find.
167152
"""
168-
# Being a little awkward so we can use .emplace_struct
169-
# This pattern isn't that important for single arg functions, but for longer ones it adds up
170-
invoke = menu.Invoke
171-
invoke_args = WrappedStruct(invoke.func)
172-
invoke_args.Method = "findFocusedItem"
173-
invoke_args.args.emplace_struct(Type=ASType.AS_Number, N=0)
174-
selected = invoke(invoke_args).S
175-
176-
match = RE_SELECTED_IDX.match(selected)
153+
focused = find_focused_item(menu)
154+
match = RE_SELECTED_IDX.match(focused)
177155
if match is None:
178156
return None
179157

src/willow1_mod_menu/options.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# ruff: noqa: D103, TD002, TD003, FIX002
1+
# ruff: noqa: D103
22
from __future__ import annotations
33

4+
import re
45
from typing import TYPE_CHECKING, Any
56

67
import unrealsdk
@@ -9,10 +10,14 @@
910

1011
from mods_base import Mod, NestedOption, hook, html_to_plain_text
1112

13+
from .util import find_focused_item
14+
1215
if TYPE_CHECKING:
13-
from .populators import Populator, WillowGFxMenu
16+
from .populators import Populator
17+
from .util import WillowGFxMenu
1418

1519
CUSTOM_OPTIONS_MENU_TAG = "willow1-mod-menu:custom-option"
20+
RE_SELECTED_IDX = re.compile(r"^_level\d+\.menu\.selections\.mMenu\.mList\.item(\d+)$")
1621

1722
populator_stack: list[Populator] = []
1823

@@ -48,7 +53,7 @@ def open_new_generic_menu(menu: WillowGFxMenu) -> None:
4853
menu.PlayUISound("Confirm")
4954

5055
tools = menu.GetLobbyTools()
51-
tools.menuStart(0, CUSTOM_OPTIONS_MENU_TAG)
56+
tools.menuStart(0)
5257

5358
populator = populator_stack[-1]
5459
populator.populate(tools)
@@ -62,18 +67,40 @@ def open_new_generic_menu(menu: WillowGFxMenu) -> None:
6267
)
6368

6469

70+
def get_selected_idx(menu: WillowGFxMenu) -> int | None:
71+
"""
72+
Gets the index of the currently selected option item.
73+
74+
Args:
75+
menu: The current menu to read the selected item of.
76+
Returns:
77+
The selected index, or None if unable to find.
78+
"""
79+
focused = find_focused_item(menu)
80+
match = RE_SELECTED_IDX.match(focused)
81+
if match is None:
82+
return None
83+
84+
try:
85+
return int(match.group(1))
86+
except ValueError:
87+
return None
88+
89+
6590
@hook("WillowGame.WillowGFxMenu:extKeybinds")
6691
def custom_menu_activate(
6792
obj: UObject,
68-
args: WrappedStruct,
93+
_args: WrappedStruct,
6994
_ret: Any,
7095
_func: BoundFunction,
7196
) -> type[Block]:
7297
try:
7398
populator = populator_stack[-1]
7499
except IndexError:
75100
return Block
76-
populator.on_activate(obj, args.MenuTag)
101+
if (idx := get_selected_idx(obj)) is None:
102+
return Block
103+
populator.on_activate(obj, idx)
77104
return Block
78105

79106

@@ -88,7 +115,9 @@ def custom_menu_spinner_change(
88115
populator = populator_stack[-1]
89116
except IndexError:
90117
return Block
91-
populator.on_spinner_change(obj, args.MenuTag, args.IValue)
118+
if (idx := get_selected_idx(obj)) is None:
119+
return Block
120+
populator.on_spinner_change(obj, idx, args.IValue)
92121
return Block
93122

94123

@@ -103,7 +132,9 @@ def custom_menu_slider_change(
103132
populator = populator_stack[-1]
104133
except IndexError:
105134
return Block
106-
populator.on_slider_change(obj, args.MenuTag, args.Value)
135+
if (idx := get_selected_idx(obj)) is None:
136+
return Block
137+
populator.on_slider_change(obj, idx, args.Value)
107138
return Block
108139

109140

src/willow1_mod_menu/populators/__init__.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from unrealsdk.unreal import UObject
88

99
from mods_base import BaseOption, BoolOption, DropdownOption, SliderOption, SpinnerOption
10+
from willow1_mod_menu.util import WillowGFxMenu
1011

11-
type WillowGFxMenu = UObject
1212
type WillowGFxLobbyTools = UObject
1313

1414

@@ -18,10 +18,10 @@
1818
@dataclass
1919
class Populator(ABC):
2020
title: str
21-
drawn_options: dict[str, BaseOption] = field(
21+
drawn_options: list[BaseOption] = field(
2222
init=False,
2323
repr=False,
24-
default_factory=dict[str, BaseOption],
24+
default_factory=list[BaseOption],
2525
)
2626

2727
@abstractmethod
@@ -60,7 +60,7 @@ def draw_text(self, tools: WillowGFxLobbyTools, text: str, option: BaseOption) -
6060
"""
6161
tag = self._get_next_tag(option)
6262
tools.menuAddItem(0, text, tag, "extKeybinds")
63-
self.drawn_options[tag] = option
63+
self.drawn_options.append(option)
6464

6565
def draw_spinner(
6666
self,
@@ -107,7 +107,7 @@ def draw_spinner(
107107
config_str,
108108
"Change:extSpinnerChanged",
109109
)
110-
self.drawn_options[tag] = option
110+
self.drawn_options.append(option)
111111

112112
def draw_slider(
113113
self,
@@ -138,62 +138,67 @@ def draw_slider(
138138
f"Min:{min_value},Max:{max_value},Step:{step},Value:{value}",
139139
"Change:extSliderChanged",
140140
)
141-
self.drawn_options[tag] = option
141+
self.drawn_options.append(option)
142142

143-
def on_activate(self, menu: WillowGFxMenu, tag: str) -> None:
143+
def on_activate(self, menu: WillowGFxMenu, idx: int) -> None:
144144
"""
145145
Handles a raw menu item activation.
146146
147147
Args:
148148
menu: The currently open menu.
149-
tag: The tag of the item which was activated.
149+
idx: The index of the item which was activated.
150150
"""
151-
if (option := self.drawn_options.get(tag)) is None:
152-
logging.error(f"Can't find option '{tag}' which was changed")
151+
try:
152+
option = self.drawn_options[idx]
153+
except IndexError:
154+
logging.error(f"Can't find option which was changed at index {idx}")
153155
return
154156

155157
self.handle_activate(menu, option)
156158

157-
def on_spinner_change(self, menu: WillowGFxMenu, tag: str, idx: int) -> None:
159+
def on_spinner_change(self, menu: WillowGFxMenu, idx: int, choice_idx: int) -> None:
158160
"""
159161
Handles a raw spinner change.
160162
161163
Args:
162164
menu: The currently open menu.
163-
tag: The tag of the item which was activated.
164-
idx: The newly selected choice's index.
165+
idx: The index of the item which was activated.
166+
choice_idx: The newly selected choice's index.
165167
"""
166168
_ = menu
167-
168-
if (option := self.drawn_options.get(tag)) is None:
169-
logging.error(f"Can't find option '{tag}' which was changed")
169+
try:
170+
option = self.drawn_options[idx]
171+
except IndexError:
172+
logging.error(f"Can't find option which was changed at index {idx}")
170173
return
171174

172175
match option:
173176
case BoolOption():
174-
option.value = bool(idx)
177+
option.value = bool(choice_idx)
175178
case DropdownOption() | SpinnerOption():
176-
option.value = option.choices[idx]
179+
option.value = option.choices[choice_idx]
177180
case _:
178181
logging.error(
179182
f"Option '{option.identifier}' got a spinner change event despite not being a"
180183
" spinner",
181184
)
182185

183-
def on_slider_change(self, menu: WillowGFxMenu, tag: str, value: float) -> None:
186+
def on_slider_change(self, menu: WillowGFxMenu, idx: int, value: float) -> None:
184187
"""
185188
Handles a raw slider change.
186189
187190
Args:
188191
menu: The currently open menu.
189-
tag: The tag of the item which was activated.
192+
idx: The index of the item which was activated.
190193
value: The new value of the slider.
191194
"""
192195
_ = menu
193-
194-
if (option := self.drawn_options.get(tag)) is None:
195-
logging.error(f"Can't find option '{tag}' which was changed")
196+
try:
197+
option = self.drawn_options[idx]
198+
except IndexError:
199+
logging.error(f"Can't find option which was changed at index {idx}")
196200
return
201+
197202
if not isinstance(option, SliderOption):
198203
logging.error(
199204
f"Option '{option.identifier}' got a spinner change event despite not being a"

src/willow1_mod_menu/util.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import TYPE_CHECKING
2+
3+
from unrealsdk.unreal import UObject, WrappedStruct
4+
5+
if TYPE_CHECKING:
6+
from enum import auto
7+
8+
from unrealsdk.unreal._uenum import UnrealEnum # pyright: ignore[reportMissingModuleSource]
9+
10+
class ASType(UnrealEnum):
11+
AS_Undefined = auto()
12+
AS_Null = auto()
13+
AS_Number = auto()
14+
AS_String = auto()
15+
AS_Boolean = auto()
16+
AS_MAX = auto()
17+
18+
else:
19+
from unrealsdk import find_enum
20+
21+
ASType = find_enum("ASType")
22+
23+
type WillowGFxMenu = UObject
24+
25+
26+
def find_focused_item(menu: WillowGFxMenu) -> str:
27+
"""
28+
Runs the 'findFocusedItem' ActionScript function on the given menu.
29+
30+
Args:
31+
menu: The menu to invoke the function under
32+
"""
33+
# Being a little awkward so we can use .emplace_struct
34+
# This pattern isn't that important for single arg functions, but for longer ones it adds up
35+
invoke = menu.Invoke
36+
invoke_args = WrappedStruct(invoke.func)
37+
invoke_args.Method = "findFocusedItem"
38+
invoke_args.args.emplace_struct(Type=ASType.AS_Number, N=0)
39+
return invoke(invoke_args).S

0 commit comments

Comments
 (0)