mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:01:31 +00:00
97db163cd0
* FIX - repair inventory metadata and ESX contracts Use the stable core_inventory metadata setter and validate the complete inventory adapter contract after provider bridges load. Preserve the real ESX configuration error instead of cascading into a missing RegisterUsableItem failure. * FIX - clarify phone opening without SIM * FIX - harden inventory and device contracts Validate SIM number configuration before item handling, preserve QB metadata mutation contracts, and propagate phone-open failures to callers. Extend regression coverage for inventory bridges and device bootstrap errors. * FIX - resolve reported phone interaction issues Stop game-input passthrough while NUI text fields are focused and add an independent hold-to-look option. Propagate SIM number formatting to the frontend, migrate configured currency symbols to utf8mb4, and clarify SkyRide, VaultX, and DarkChat behavior with focused contract coverage. * FIX - detect missing packaged phone UI Validate the generated NUI entrypoint, its referenced assets, and required static media when the resource starts. Print an actionable server-console warning for incomplete source archives, package phone sounds through the manifest, and cover the detection contract with Lua regressions.
260 lines
8.2 KiB
Lua
260 lines
8.2 KiB
Lua
SkyPhoneFocus = {}
|
|
|
|
local blocked_phone_controls = { 24, 140, 141, 142, 257, 263, 264 }
|
|
local blocked_phone_look_controls = { 1, 2, 3, 4, 5, 6 }
|
|
local focused_control_groups = { 0, 1, 2 }
|
|
local hold_to_look_config = Config.Phone.HoldToLook
|
|
local hold_to_look_enabled = type(hold_to_look_config) == "table" and hold_to_look_config.Enabled == true
|
|
local hold_to_look_control = hold_to_look_enabled and tonumber(hold_to_look_config.Control) or nil
|
|
if hold_to_look_enabled and (
|
|
not hold_to_look_control
|
|
or hold_to_look_control ~= math.floor(hold_to_look_control)
|
|
or hold_to_look_control < 0
|
|
) then
|
|
error("[sky_phone] Config.Phone.HoldToLook.Control must be a non-negative whole control index.")
|
|
end
|
|
local state = {
|
|
activity_suspended = false,
|
|
allow_movement = Config.Phone.AllowMovement,
|
|
call_focus = false,
|
|
camera_active = false,
|
|
camera_nui_focused = true,
|
|
cursor_disabled = false,
|
|
external_game_input = nil,
|
|
external_game_input_owner = nil,
|
|
is_open = false,
|
|
look_passthrough = false,
|
|
notification_focus = false,
|
|
payphone_focus = false,
|
|
sim_picker_open = false,
|
|
text_input_focused = false,
|
|
}
|
|
local block_game = false
|
|
local block_look = false
|
|
local game_input = false
|
|
|
|
local function allows_game_input(value)
|
|
local allowed = value.external_game_input
|
|
if allowed == nil then
|
|
allowed = value.allow_movement
|
|
end
|
|
return allowed == true
|
|
end
|
|
|
|
function SkyPhoneFocus.ApplyFocusedControls()
|
|
for _, group in ipairs(focused_control_groups) do
|
|
DisableAllControlActions(group)
|
|
end
|
|
DisablePlayerFiring(PlayerId(), true)
|
|
end
|
|
|
|
function SkyPhoneFocus.ApplyGameInputControls(block_look)
|
|
for _, control in ipairs(blocked_phone_controls) do
|
|
DisableControlAction(0, control, true)
|
|
end
|
|
if block_look then
|
|
for _, control in ipairs(blocked_phone_look_controls) do
|
|
DisableControlAction(0, control, true)
|
|
end
|
|
end
|
|
DisablePlayerFiring(PlayerId(), true)
|
|
end
|
|
|
|
function SkyPhoneFocus.Resolve(state)
|
|
if state.activity_suspended then
|
|
return { block_game = false, cursor = false, focused = false, game_input = false, keep_input = false }
|
|
end
|
|
if state.call_focus then
|
|
return { block_game = true, cursor = true, focused = true, game_input = false, keep_input = false }
|
|
end
|
|
if state.camera_active and not state.camera_nui_focused then
|
|
return { block_game = false, block_look = false, cursor = false, focused = true, game_input = true, keep_input = true }
|
|
end
|
|
local game_input = state.is_open
|
|
and allows_game_input(state)
|
|
and not state.camera_active
|
|
and not state.text_input_focused
|
|
local focused = state.is_open
|
|
or state.notification_focus
|
|
or state.payphone_focus
|
|
or state.sim_picker_open
|
|
or (state.camera_active and state.camera_nui_focused)
|
|
local cursor = focused and not (state.is_open and (state.cursor_disabled or state.look_passthrough))
|
|
return {
|
|
block_game = cursor and not game_input,
|
|
block_look = game_input and cursor,
|
|
cursor = cursor,
|
|
focused = focused,
|
|
game_input = game_input,
|
|
keep_input = game_input,
|
|
}
|
|
end
|
|
|
|
function SkyPhoneFocus.Reapply()
|
|
if state.look_passthrough and (
|
|
not hold_to_look_enabled
|
|
or not state.is_open
|
|
or state.cursor_disabled
|
|
or state.text_input_focused
|
|
or state.camera_active
|
|
or not allows_game_input(state)
|
|
) then
|
|
state.look_passthrough = false
|
|
end
|
|
local focus = SkyPhoneFocus.Resolve(state)
|
|
SetNuiFocus(focus.focused, focus.cursor)
|
|
SetNuiFocusKeepInput(focus.keep_input)
|
|
block_game = focus.block_game == true
|
|
block_look = focus.block_look == true
|
|
game_input = focus.game_input == true
|
|
TriggerEvent("sky_phone:client:cameraFocusApplied", {
|
|
active = state.camera_active,
|
|
cursor = focus.cursor,
|
|
focused = focus.focused,
|
|
gameInput = focus.keep_input,
|
|
})
|
|
end
|
|
|
|
function SkyPhoneFocus.BeginNuiHydration()
|
|
-- Browser-owned focus claims cannot survive a CEF reload.
|
|
state.notification_focus = false
|
|
state.look_passthrough = false
|
|
state.text_input_focused = false
|
|
end
|
|
|
|
function SkyPhoneFocus.SetPhone(open, cursor_disabled)
|
|
state.is_open = open == true
|
|
if cursor_disabled ~= nil then
|
|
state.cursor_disabled = cursor_disabled == true
|
|
end
|
|
if state.is_open then
|
|
state.notification_focus = false
|
|
state.call_focus = false
|
|
else
|
|
state.activity_suspended = false
|
|
state.call_focus = false
|
|
state.cursor_disabled = false
|
|
state.external_game_input = nil
|
|
state.external_game_input_owner = nil
|
|
state.look_passthrough = false
|
|
state.text_input_focused = false
|
|
end
|
|
SkyPhoneFocus.Reapply()
|
|
end
|
|
|
|
function SkyPhoneFocus.SetCall(active)
|
|
state.call_focus = active == true
|
|
SkyPhoneFocus.Reapply()
|
|
end
|
|
|
|
function SkyPhoneFocus.SetSimPicker(active)
|
|
state.sim_picker_open = active == true
|
|
SkyPhoneFocus.Reapply()
|
|
end
|
|
|
|
function SkyPhoneFocus.SetTextInputFocused(active)
|
|
state.text_input_focused = active == true
|
|
if state.text_input_focused then
|
|
state.look_passthrough = false
|
|
end
|
|
SkyPhoneFocus.Reapply()
|
|
end
|
|
|
|
function SkyPhoneFocus.SetExternalGameInput(owner_resource, allow_game_input)
|
|
if type(owner_resource) ~= "string" or owner_resource == "" or type(allow_game_input) ~= "boolean" then
|
|
return false, "invalid_focus_claim"
|
|
end
|
|
if not state.is_open then
|
|
return false, "phone_closed"
|
|
end
|
|
|
|
state.external_game_input = allow_game_input
|
|
state.external_game_input_owner = owner_resource
|
|
SkyPhoneFocus.Reapply()
|
|
return true
|
|
end
|
|
|
|
function SkyPhoneFocus.Reset()
|
|
state.activity_suspended = false
|
|
state.call_focus = false
|
|
state.camera_active = false
|
|
state.camera_nui_focused = true
|
|
state.cursor_disabled = false
|
|
state.external_game_input = nil
|
|
state.external_game_input_owner = nil
|
|
state.is_open = false
|
|
state.look_passthrough = false
|
|
state.notification_focus = false
|
|
state.payphone_focus = false
|
|
state.sim_picker_open = false
|
|
state.text_input_focused = false
|
|
block_game = false
|
|
block_look = false
|
|
game_input = false
|
|
SetNuiFocusKeepInput(false)
|
|
SetNuiFocus(false, false)
|
|
end
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
if game_input or block_game then
|
|
local look_passthrough = hold_to_look_enabled
|
|
and game_input
|
|
and not state.cursor_disabled
|
|
and IsControlPressed(0, hold_to_look_control)
|
|
if look_passthrough ~= state.look_passthrough then
|
|
state.look_passthrough = look_passthrough
|
|
SkyPhoneFocus.Reapply()
|
|
end
|
|
if block_game then
|
|
SkyPhoneFocus.ApplyFocusedControls()
|
|
else
|
|
SkyPhoneFocus.ApplyGameInputControls(block_look)
|
|
end
|
|
Wait(0)
|
|
else
|
|
Wait(250)
|
|
end
|
|
end
|
|
end)
|
|
|
|
AddEventHandler("sky_phone:client:setSuspended", function(suspended)
|
|
state.activity_suspended = suspended == true
|
|
SkyPhoneFocus.Reapply()
|
|
end)
|
|
|
|
AddEventHandler("sky_phone:client:setPayphoneFocus", function(focused)
|
|
state.payphone_focus = focused == true
|
|
SkyPhoneFocus.Reapply()
|
|
end)
|
|
|
|
AddEventHandler("sky_phone:client:setCameraFocus", function(data)
|
|
if type(data) ~= "table" or type(data.active) ~= "boolean" or type(data.nuiFocused) ~= "boolean" then
|
|
Bridge.Debug("error", "[sky_phone] Rejected invalid camera focus claim.")
|
|
return
|
|
end
|
|
state.camera_active = data.active
|
|
state.camera_nui_focused = data.nuiFocused
|
|
SkyPhoneFocus.Reapply()
|
|
end)
|
|
|
|
AddEventHandler("onClientResourceStop", function(resource_name)
|
|
if resource_name ~= state.external_game_input_owner then
|
|
return
|
|
end
|
|
|
|
state.external_game_input = nil
|
|
state.external_game_input_owner = nil
|
|
SkyPhoneFocus.Reapply()
|
|
end)
|
|
|
|
RegisterNUICallback("notification:focus", function(data, cb)
|
|
if type(data) ~= "table" or type(data.active) ~= "boolean" then
|
|
cb({ success = false, error = "invalid_request" })
|
|
return
|
|
end
|
|
state.notification_focus = data.active == true and not state.is_open
|
|
SkyPhoneFocus.Reapply()
|
|
cb({ success = true })
|
|
end)
|