11# ruff: noqa: D103
22from typing import Any
33
4- from unrealsdk .hooks import Block , Type
5- from unrealsdk .unreal import BoundFunction , UObject , WrappedStruct
4+ from unrealsdk .hooks import Type
5+ from unrealsdk .unreal import BoundFunction , UObject , WeakPointer , WrappedStruct
66
77from mods_base import hook
88
99from .options import create_mod_list_options_menu
10+ from .util import find_focused_item
1011
11- MODS_MENU_TAG = "willow1-mod-menu:mods-pause"
12+ current_menu = WeakPointer ()
1213
1314
1415# In contrast to the frontend menu, the pause menu uses a generic menu screen, so the best place to
@@ -23,14 +24,18 @@ def inject_mods_into_pause_screen(
2324 if args .menuCaption != "$WillowMenu.Pause.Exit" :
2425 return
2526
26- # We also don't seem to have a great way of detecting generic item activate events, so instead
27- # we hook this to an unused debug function
28- func (0 , "Mods" , MODS_MENU_TAG , "extMainDebug" )
27+ func (0 , "Mods" )
2928
3029
3130@hook ("WillowGame.WillowGFxMenuPause:extInitMain" , immediately_enable = True )
32- def open_pause_pre (* _ : Any ) -> None :
31+ def open_pause_pre (obj : UObject , _args : WrappedStruct , _ret : Any , _func : BoundFunction ) -> None :
32+ global current_menu
33+ current_menu = WeakPointer (obj )
34+
3335 inject_mods_into_pause_screen .enable ()
36+ pause_play_sound .enable ()
37+ reenable_pause_after_nested .enable ()
38+ reenable_pause_after_achievements .enable ()
3439
3540
3641@hook (
@@ -42,15 +47,67 @@ def open_pause_post(*_: Any) -> None:
4247 inject_mods_into_pause_screen .disable ()
4348
4449
45- @hook ("WillowGame.WillowGFxMenuPause:extMainDebug" , immediately_enable = True )
46- def pause_activate (
50+ # Since we can't safely pass callback names into ActionScript, have to detect when you click mods by
51+ # the menu sound. This has quite some complications trying not to trigger while in other menus...
52+ @hook ("GearboxFramework.GearboxGFxMovie:PlaySpecialUISound" )
53+ def pause_play_sound (
4754 obj : UObject ,
48- _args : WrappedStruct ,
55+ args : WrappedStruct ,
4956 _ret : Any ,
5057 _func : BoundFunction ,
51- ) -> type [Block ]:
58+ ) -> None :
59+ if args .SoundString != "Confirm" :
60+ return
61+
62+ # Disable this hook now, regardless of what screen we actually went into, to try avoid firing
63+ # spuriously. If we back out of the next screen, another hook should re-enable us.
64+ pause_play_sound .disable ()
65+
66+ if (menu := current_menu ()) is None :
67+ return
68+
69+ if len (menu .ScreenStack ) > 1 :
70+ # Sanity check, should only be able to fire on the top screen
71+ return
72+
73+ if menu .GetVariableString (find_focused_item (obj ) + ".mLabel.text" ) != "Mods" :
74+ return
75+
5276 # We don't seem to be able to open the multiplayer lobby menu from in game - even if we force
5377 # load it's definition - so instead just open a standard options list showing each mod
5478 create_mod_list_options_menu (obj )
5579
56- return Block
80+
81+ # If we open a nested movie (e.g. lobby, quit), we won't re-init the pause menu when we leave it
82+ # This hook will detect when we return to the pause movie, so we can re-enable the sound hook
83+ @hook ("WillowGame.WillowGFxUIManager:UpdateFocus" , hook_type = Type .POST )
84+ def reenable_pause_after_nested (
85+ obj : UObject ,
86+ _args : WrappedStruct ,
87+ _ret : Any ,
88+ _func : BoundFunction ,
89+ ) -> None :
90+ if (menu := current_menu ()) is None :
91+ return
92+
93+ # If we've re-enabled the current pause movie
94+ if menu == obj .GetPlayingMovie ():
95+ # Re-enable the sound hook
96+ pause_play_sound .enable ()
97+
98+
99+ @hook ("WillowGame.WillowGFxMenuPause:extViewAchievements" )
100+ def reenable_pause_after_achievements (* _ : Any ) -> None :
101+ # Since the achievements menu just opens the steam overlay, with no in game menus, immediately
102+ # re-enable the sound hook
103+ pause_play_sound .enable ()
104+
105+
106+ @hook ("WillowGame.WillowGFxMenuPause:OnClose" , immediately_enable = True )
107+ def pause_close (* _ : Any ) -> None :
108+ global current_menu
109+ current_menu = WeakPointer ()
110+
111+ pause_play_sound .disable ()
112+ reenable_pause_after_nested .disable ()
113+ reenable_pause_after_achievements .disable ()
0 commit comments