diff --git a/README.md b/README.md index 6bfc6bc..f46b137 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ set sky_phone_fliptok_password_pepper "replace-with-a-long-random-secret" Passwords are stored as salted hashes. The pepper is read server-side from the convar and is never included in the NUI bundle. -Standalone FiveM phone built with Vue 3, TypeScript, Pinia, Vue Router, the Sky-owned FiveM UI system, and Tailwind CSS 4. Konsta UI remains only for screens that have not yet completed their migration. The phone opens through the usable item; `/phone` is disabled unless `Config.Phone.DevelopmentCommand` is enabled explicitly. Phone identity and SIM-card behavior are selected independently through `Config.Phone.Unique` and `Config.Sim.Enabled`. +Standalone FiveM phone built with Vue 3, TypeScript, Pinia, Vue Router, the Sky-owned FiveM UI system, and Tailwind CSS 4. Konsta UI remains only for screens that have not yet completed their migration. The phone opens through the usable item; `/phone` is disabled unless `Config.Phone.DevelopmentCommand` is enabled explicitly. `Config.Phone.AllowMovement` controls whether game input remains active while the mobile phone is open. Phone identity and SIM-card behavior are selected independently through `Config.Phone.Unique` and `Config.Sim.Enabled`. ### Ingame test data diff --git a/sky_phone/config/config.lua b/sky_phone/config/config.lua index 845fce6..02f1780 100644 --- a/sky_phone/config/config.lua +++ b/sky_phone/config/config.lua @@ -16,6 +16,7 @@ Config.Command = "phone" Config.Phone = { Item = "phone", Unique = true, -- true: data follows each phone item; false: one persistent phone per character + AllowMovement = true, -- true: game input stays active while the mobile phone is open DevelopmentCommand = true, DeviceName = "iFruit Phone", } diff --git a/sky_phone/source/client/focus.lua b/sky_phone/source/client/focus.lua index 8252694..e5c3103 100644 --- a/sky_phone/source/client/focus.lua +++ b/sky_phone/source/client/focus.lua @@ -16,6 +16,6 @@ function SkyPhoneFocus.Resolve(state) or state.payphone_focus or state.sim_picker_open or (state.camera_active and state.camera_nui_focused), - keep_input = false, + keep_input = state.is_open and state.allow_movement and not state.camera_active, } end diff --git a/sky_phone/source/client/main.lua b/sky_phone/source/client/main.lua index a3f5777..9faa30f 100644 --- a/sky_phone/source/client/main.lua +++ b/sky_phone/source/client/main.lua @@ -292,6 +292,7 @@ end local function update_nui_focus() local focus = SkyPhoneFocus.Resolve({ activity_suspended = activity_suspended, + allow_movement = Config.Phone.AllowMovement, call_focus = call_focus, camera_active = camera_active, camera_nui_focused = camera_nui_focused, diff --git a/tests/client_focus.lua b/tests/client_focus.lua index f70ae6c..5cad84b 100644 --- a/tests/client_focus.lua +++ b/tests/client_focus.lua @@ -3,6 +3,7 @@ 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, @@ -26,6 +27,24 @@ assert(not minimized_call.focused, "a replayed call without an attention claim m local incoming_call = resolve({ call_focus = true }) assert(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.focused 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.focused and movable_phone.keep_input, + "an open phone must keep game input when movement is enabled" +) + +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, @@ -36,6 +55,17 @@ assert( "unfocused camera must own game input over the open phone" ) +local focused_camera = resolve({ + allow_movement = true, + camera_active = true, + camera_nui_focused = true, + is_open = true, +}) +assert( + focused_camera.focused and not focused_camera.keep_input, + "focused camera must override movement configuration until Space releases NUI focus" +) + local camera_interrupted_by_call = resolve({ call_focus = true, camera_active = true,