11# ruff: noqa: D103
22import re
3+ import traceback
34from typing import Any
45
56import unrealsdk
67from unrealsdk .hooks import Block
78from unrealsdk .unreal import BoundFunction , UObject , WeakPointer , WrappedStruct
89
9- from mods_base import CoopSupport , Game , Mod , get_ordered_mod_list , hook , html_to_plain_text
10+ from mods_base import (
11+ CoopSupport ,
12+ EInputEvent ,
13+ Game ,
14+ Mod ,
15+ get_ordered_mod_list ,
16+ hook ,
17+ html_to_plain_text ,
18+ )
1019from mods_base .mod_list import base_mod
1120
1221from .util import find_focused_item
@@ -36,6 +45,7 @@ def open_lobby_mods_menu(frontend: WillowGFxMenuFrontend) -> None:
3645 init_content .enable ()
3746 play_sound .enable ()
3847 menu_close .enable ()
48+ handle_input_key .enable ()
3949
4050 frontend .OpenMP ()
4151
@@ -56,6 +66,23 @@ def block_search_delegate(
5666 return Block
5767
5868
69+ def get_mod_title (mod : Mod ) -> str :
70+ """
71+ Combines the mod name and status into a single title.
72+
73+ Args:
74+ mod: The mod to ge tthe title of.
75+ Returns:
76+ The title to use for the mod in this menu.
77+ """
78+ # Filter out the standard enabled statuses, for more variation in the list - it's harder
79+ # to tell what's enabled or not when every single entry has a suffix
80+ # If there's a custom status, we'll still show that
81+ status = html_to_plain_text (mod .get_status ()).strip ()
82+ suffix = "" if mod .is_enabled and status in ("Enabled" , "Loaded" ) else f" ({ status } )"
83+ return html_to_plain_text (mod .name ) + suffix
84+
85+
5986# This hook is when the menu is actually initialized - we overwrite it with all our own logic
6087@hook ("WillowGame.WillowGFxLobbyMultiplayer:extInitContent" )
6188def init_content (
@@ -76,17 +103,11 @@ def init_content(
76103
77104 drawn_mods .clear ()
78105 for mod in get_ordered_mod_list ():
79- # Filter out the standard enabled statuses, for more variation in the list - it's harder
80- # to tell what's enabled or not when every single entry has a suffix
81- # If there's a custom status, we'll still show that
82- status = html_to_plain_text (mod .get_status ()).strip ()
83- suffix = "" if mod .is_enabled and status in ("Enabled" , "Loaded" ) else f" ({ status } )"
84-
85106 # This function has extra options for other commands, and a lot of base game calls pass
86107 # something like `menuAddItem(0, "title", "tag", "extHostP", "Focus:extMenuFocus")`
87108 # Unfortunately for us, it seems passing anything after the title also results in corruption
88109 # This gives us a rough time later on actually detecting select/focus
89- obj .menuAddItem (0 , html_to_plain_text (mod . name ) + suffix )
110+ obj .menuAddItem (0 , get_mod_title (mod ) )
90111 drawn_mods .append (mod )
91112
92113 obj .menuEnd ()
@@ -152,7 +173,7 @@ def update_menu_for_mod(menu: WillowGFxLobbyMultiplayer, mod: Mod) -> None:
152173
153174 tooltip = "$<StringAliasMap:GFx_Accept> DETAILS"
154175 if not mod .enabling_locked :
155- tooltip += " [Space] " + "DISABLE" if mod .is_enabled else "ENABLE"
176+ tooltip += " [Space] " + ( "DISABLE" if mod .is_enabled else "ENABLE" )
156177 tooltip += " <Strings:WillowMenu.TitleMenu.BackBar>"
157178
158179 menu .SetVariableString ("lobby.tooltips.text" , tooltip )
@@ -216,6 +237,39 @@ def select_next_tick(*_: Any) -> None:
216237 update_menu_for_mod (menu , mod )
217238
218239
240+ @hook ("WillowGame.WillowGFxLobbyMultiplayer:HandleInputKey" )
241+ def handle_input_key (
242+ obj : UObject ,
243+ args : WrappedStruct ,
244+ _ret : Any ,
245+ _func : BoundFunction ,
246+ ) -> tuple [type [Block ], bool ] | None :
247+ key : str = args .ukey
248+ event : EInputEvent = args .uevent
249+
250+ if not (key == "SpaceBar" and event == EInputEvent .IE_Released ):
251+ return None
252+
253+ mod = get_focused_mod (obj )
254+ if mod is None or mod .enabling_locked :
255+ return None
256+
257+ old_enabled = mod .is_enabled
258+ try :
259+ (mod .disable if old_enabled else mod .enable )()
260+ except Exception : # noqa: BLE001
261+ traceback .print_exc ()
262+
263+ # Extra safety layer in case the mod rejected the toggle, no need to update if we haven't
264+ # changed state
265+ if old_enabled != mod .is_enabled :
266+ update_menu_for_mod (obj , mod )
267+
268+ obj .SetVariableString (find_focused_item (obj ) + ".mLabel.text" , get_mod_title (mod ))
269+
270+ return Block , True
271+
272+
219273@hook ("WillowGame.WillowGFxLobbyMultiplayer:OnClose" )
220274def menu_close (
221275 _obj : UObject ,
@@ -229,6 +283,7 @@ def menu_close(
229283 play_sound .disable ()
230284 select_next_tick .disable ()
231285 menu_close .disable ()
286+ handle_input_key .disable ()
232287
233288 global current_menu
234289 current_menu = WeakPointer ()
0 commit comments