local disabled_controls = {} local all_controls_disabled = false local firing_disabled = false function DisableControlAction(group, control, disabled) assert(group == 0 and disabled, "phone controls must be disabled in the primary input group") disabled_controls[control] = true end function DisableAllControlActions(group) assert(group == 0, "focused phone input must block the primary input group") all_controls_disabled = true end function PlayerId() return 7 end function DisablePlayerFiring(player, disabled) assert(player == 7, "movement filtering must target the local player") firing_disabled = disabled end dofile("sky_phone/source/client/focus.lua") local function resolve(overrides) local state = { activity_suspended = false, allow_movement = false, call_focus = false, camera_active = false, camera_nui_focused = true, cursor_disabled = false, is_open = false, notification_focus = false, payphone_focus = false, sim_picker_open = false, } for key, value in pairs(overrides or {}) do state[key] = value end return SkyPhoneFocus.Resolve(state) end local idle = resolve() assert( not idle.cursor and not idle.focused and not idle.keep_input, "idle NUI must release focus and game input override" ) local minimized_call = resolve() assert(not minimized_call.focused, "a replayed call without an attention claim must stay unfocused") local incoming_call = resolve({ call_focus = true }) assert( incoming_call.cursor and incoming_call.focused and not incoming_call.keep_input, "incoming call attention must focus the NUI" ) local stationary_phone = resolve({ is_open = true }) assert( stationary_phone.cursor and stationary_phone.focused and stationary_phone.block_game and not stationary_phone.keep_input, "an open phone must block game input when movement is disabled" ) local movable_phone = resolve({ allow_movement = true, is_open = true }) assert( movable_phone.cursor and movable_phone.focused and movable_phone.keep_input and movable_phone.game_input and movable_phone.block_game, "an open phone with the cursor active must block GTA hotkeys while keeping NUI input" ) local cursor_disabled_phone = resolve({ allow_movement = true, cursor_disabled = true, is_open = true, }) assert( not cursor_disabled_phone.cursor and cursor_disabled_phone.focused and cursor_disabled_phone.keep_input and cursor_disabled_phone.game_input and not cursor_disabled_phone.block_game, "toggling Alt must release the NUI cursor while preserving phone and GTA input" ) SkyPhoneFocus.ApplyFocusedControls() assert(all_controls_disabled, "focused phone cursor must block every GTA control while typing") assert(firing_disabled, "focused phone cursor must block attacks while typing") all_controls_disabled = false firing_disabled = false SkyPhoneFocus.ApplyGameInputControls(true) for _, control in ipairs({ 19, 24, 140, 141, 142, 257, 263, 264 }) do assert(disabled_controls[control], ("phone control %d must remain disabled"):format(control)) end for _, control in ipairs({ 1, 2, 3, 4, 5, 6 }) do assert(disabled_controls[control], ("look control %d must be disabled while the phone cursor is active"):format(control)) end assert(not disabled_controls[21] and not disabled_controls[22], "sprint and jump must stay enabled") assert(not disabled_controls[30] and not disabled_controls[31], "movement axes must stay enabled") assert(firing_disabled, "player attacks must remain disabled while the phone is open") disabled_controls = {} firing_disabled = false SkyPhoneFocus.ApplyGameInputControls(false) assert(not disabled_controls[1] and not disabled_controls[2], "Alt cursor toggle must restore camera look") assert(firing_disabled, "player attacks must remain disabled after the cursor is toggled off") local movable_notification = resolve({ allow_movement = true, notification_focus = true }) assert( movable_notification.focused and not movable_notification.keep_input, "movement configuration must not affect a notification without an open phone" ) local camera_game_input = resolve({ camera_active = true, camera_nui_focused = false, is_open = true, }) assert( not camera_game_input.cursor and camera_game_input.focused and camera_game_input.keep_input and camera_game_input.game_input and not camera_game_input.block_look, "camera movement must keep keyboard focus and GTA movement without retaining the NUI cursor" ) local focused_camera = resolve({ allow_movement = true, camera_active = true, camera_nui_focused = true, is_open = true, }) assert( focused_camera.cursor and focused_camera.focused and not focused_camera.keep_input and not focused_camera.game_input, "focused camera must override movement configuration until Space enables passthrough" ) local camera_interrupted_by_call = resolve({ call_focus = true, camera_active = true, camera_nui_focused = false, is_open = true, }) assert( camera_interrupted_by_call.cursor and camera_interrupted_by_call.focused and not camera_interrupted_by_call.keep_input, "incoming call attention must override camera passthrough input" ) local camera_after_connected_call = resolve({ call_focus = false, camera_active = true, camera_nui_focused = false, is_open = true, }) assert( not camera_after_connected_call.cursor and camera_after_connected_call.focused and camera_after_connected_call.keep_input, "connected call without an attention claim must restore camera movement input" ) local payphone_closed_behind_phone = resolve({ is_open = true, payphone_focus = false }) assert(payphone_closed_behind_phone.focused, "releasing payphone focus must not clear mobile phone focus") local suspended = resolve({ activity_suspended = true, call_focus = true, is_open = true }) assert(not suspended.focused and not suspended.keep_input, "suspended activities must mask every focus claim") print("Client focus tests passed")