FIX - improve payphone call flow

This commit is contained in:
Dominik
2026-08-10 20:03:29 +02:00
parent 6f06708c71
commit 1e426fba80
4 changed files with 145 additions and 29 deletions
+4 -2
View File
@@ -648,8 +648,8 @@ onBeforeUnmount(() => {
width: 50.9%;
height: 5.9%;
display: grid;
grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
gap: 3%;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 3.2%;
}
.payphone-action {
@@ -676,10 +676,12 @@ onBeforeUnmount(() => {
}
.payphone-action--call {
grid-column: span 2;
background: linear-gradient(#3dbd74, #187c43);
}
.payphone-action--hangup {
grid-column: span 2;
background: linear-gradient(#e95c55, #a12420);
}
+3 -1
View File
@@ -30,7 +30,9 @@ Locales["en"] = {
default = "The phone could not be opened.",
},
Payphone = {
Interact = "Press ~INPUT_CONTEXT~ to use the payphone.",
Interact = "Use the payphone.",
RingingHelp = "Calling {number}... | Hang up",
ConnectedHelp = "Connected to {number} | {duration} | {currency}{cost} | Hang up",
},
Nui = {
Payphone = {
@@ -50,3 +50,10 @@ function Bridge.Framework.Notify(title, message, notification_type, duration)
Bridge.Debug("error", "[sky_phone] Notification requested for unsupported framework '%s'.", tostring(framework_name))
end
function Bridge.Framework.ShowHelpNotification(message, key)
local control = key == "E" and "~INPUT_CONTEXT~" or ("[%s]"):format(tostring(key or "E"))
BeginTextCommandDisplayHelp("STRING")
AddTextComponentSubstringPlayerName(("%s %s"):format(control, tostring(message or "")))
EndTextCommandDisplayHelp(0, false, true, -1)
end
+131 -26
View File
@@ -2,11 +2,16 @@ local payphone_open = false
local nearest_payphone = nil
local active_booth = nil
local active_call_id = nil
local active_call_state = nil
local active_call_number = nil
local active_call_elapsed_seconds = 0
local active_call_elapsed_updated_at = 0
local call_channel = 0
local replacement_prop = nil
local hidden_prop = nil
local animation_scene = nil
local distance_hangup_requested = false
local visuals_starting = false
local hangup_requested = false
local configured_models = {}
for _, model_name in ipairs(Config.Payphones.Props or {}) do
@@ -64,16 +69,24 @@ end
local function stop_call_visuals()
local animation = Config.Payphones.Animation
local ped = PlayerPedId()
local visuals_active = animation_scene ~= nil or replacement_prop ~= nil
visuals_starting = false
if visuals_active and DoesEntityExist(ped) then
StopAnimTask(ped, animation.Dictionary, animation.PedClip, 0.0)
ClearPedTasksImmediately(ped)
end
if animation_scene then
ClearPedTasks(ped)
SetSynchronizedSceneHoldLastFrame(animation_scene, false)
DisposeSynchronizedScene(animation_scene)
animation_scene = nil
else
StopAnimTask(ped, animation.Dictionary, animation.PedClip, 2.0)
end
if replacement_prop and DoesEntityExist(replacement_prop) then
StopEntityAnim(replacement_prop, animation.PropClip, animation.Dictionary, -2.0)
StopEntityAnim(replacement_prop, animation.PropClip, animation.Dictionary, 0.0)
SetEntityVisible(replacement_prop, false, false)
SetEntityAsMissionEntity(replacement_prop, true, true)
DeleteEntity(replacement_prop)
end
replacement_prop = nil
@@ -86,19 +99,32 @@ local function stop_call_visuals()
end
local function start_call_visuals()
if replacement_prop or not active_booth or not DoesEntityExist(active_booth.entity) then
if visuals_starting or replacement_prop or not active_call_id
or not active_booth or not DoesEntityExist(active_booth.entity)
then
return
end
visuals_starting = true
local expected_call_id = active_call_id
local booth = active_booth
local replacement_hash = joaat(Config.Payphones.ReplacementProp)
local animation = Config.Payphones.Animation
if not load_model(replacement_hash) or not load_animation(animation.Dictionary) then
Bridge.Debug("error", "[sky_phone] The payphone animation assets could not be loaded.")
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
RemoveAnimDict(animation.Dictionary)
return
end
if active_call_id ~= expected_call_id or active_booth ~= booth or not DoesEntityExist(booth.entity) then
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
RemoveAnimDict(animation.Dictionary)
return
end
local original = active_booth.entity
local original = booth.entity
local coords = GetEntityCoords(original)
local rotation = GetEntityRotation(original, 2)
local replacement = CreateObjectNoOffset(
@@ -112,7 +138,9 @@ local function start_call_visuals()
)
if replacement == 0 or not DoesEntityExist(replacement) then
Bridge.Debug("error", "[sky_phone] The animated payphone replacement prop could not be created.")
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
RemoveAnimDict(animation.Dictionary)
return
end
@@ -133,6 +161,12 @@ local function start_call_visuals()
rotation.z,
2
)
if not animation_scene or animation_scene == -1 then
Bridge.Debug("error", "[sky_phone] The payphone synchronized scene could not be created.")
animation_scene = nil
stop_call_visuals()
return
end
SetSynchronizedSceneHoldLastFrame(animation_scene, true)
TaskSynchronizedScene(
ped,
@@ -147,6 +181,7 @@ local function start_call_visuals()
0
)
PlayEntityAnim(replacement, animation.PropClip, animation.Dictionary, 8.0, false, true, false, 0.0, 0)
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
end
@@ -159,14 +194,74 @@ local function booth_payload(booth)
end
local function close_payphone()
local was_open = payphone_open
payphone_open = false
SetNuiFocus(false, false)
SendNUIMessage({ type = "payphone:close" })
if was_open then
SetNuiFocus(false, false)
SendNUIMessage({ type = "payphone:close" })
end
if not active_call_id then
active_booth = nil
end
end
local function format_duration(seconds)
local duration = math.max(0, math.floor(tonumber(seconds) or 0))
return ("%02d:%02d"):format(math.floor(duration / 60), duration % 60)
end
local function replace_placeholder(value, placeholder, replacement)
return value:gsub("{" .. placeholder .. "}", tostring(replacement))
end
local function current_call_elapsed_seconds()
if active_call_state ~= "connected" then
return 0
end
return active_call_elapsed_seconds
+ math.max(0, math.floor((GetGameTimer() - active_call_elapsed_updated_at) / 1000))
end
local function call_help_message()
local locale = get_locale().Payphone
local message
if active_call_state == "connected" then
local elapsed_seconds = current_call_elapsed_seconds()
message = locale.ConnectedHelp
message = replace_placeholder(message, "duration", format_duration(elapsed_seconds))
message = replace_placeholder(message, "currency", Config.Payphones.Currency)
message = replace_placeholder(message, "cost", elapsed_seconds * (tonumber(Config.Payphones.PricePerSecond) or 0))
else
message = locale.RingingHelp
end
return replace_placeholder(message, "number", active_call_number or "")
end
local function apply_active_call_state(data)
if active_call_id ~= data.id then
hangup_requested = false
end
active_call_id = data.id
active_call_state = data.state
active_call_number = data.otherNumber or active_call_number
if data.state == "connected" then
active_call_elapsed_seconds = math.max(0, math.floor(tonumber(data.elapsedSeconds) or 0))
active_call_elapsed_updated_at = GetGameTimer()
else
active_call_elapsed_seconds = 0
active_call_elapsed_updated_at = 0
end
end
local function clear_active_call_state()
active_call_id = nil
active_call_state = nil
active_call_number = nil
active_call_elapsed_seconds = 0
active_call_elapsed_updated_at = 0
hangup_requested = false
end
local function open_payphone(booth)
if payphone_open or active_call_id or IsNuiFocused() then
return
@@ -193,12 +288,16 @@ RegisterNUICallback("payphone:dial", function(data, cb)
local payload = booth_payload(active_booth)
payload.phoneNumber = type(data) == "table" and data.phoneNumber or nil
local result = Bridge.Callbacks.Trigger("sky_phone:payphone:dial", payload)
if result and result.success and result.data and (result.data.state == "ringing" or result.data.state == "connected") then
active_call_id = result.data.id
distance_hangup_requested = false
start_call_visuals()
local call_started = result and result.success and result.data
and (result.data.state == "ringing" or result.data.state == "connected")
if call_started then
apply_active_call_state(result.data)
end
cb(result or { success = false, error = "request_failed" })
if call_started then
close_payphone()
start_call_visuals()
end
end)
RegisterNUICallback("payphone:hangup", function(_, cb)
@@ -223,19 +322,20 @@ RegisterNetEvent("sky_phone:payphone:state", function(data)
return
end
if data.state == "ringing" or data.state == "connected" then
active_call_id = data.id
distance_hangup_requested = false
apply_active_call_state(data)
close_payphone()
start_call_visuals()
if data.state == "connected" and data.channel and call_channel ~= tonumber(data.channel)
and not join_call_voice(data.channel)
then
hangup_requested = true
Bridge.Callbacks.Trigger("sky_phone:payphone:hangup", { id = active_call_id })
end
else
active_call_id = nil
distance_hangup_requested = false
clear_active_call_state()
leave_call_voice()
stop_call_visuals()
active_booth = nil
end
SendNUIMessage({ type = "payphone:state", data = data })
end)
@@ -278,9 +378,7 @@ end)
CreateThread(function()
while true do
if nearest_payphone and nearest_payphone.distance <= Config.Payphones.InteractionDistance and not IsNuiFocused() then
BeginTextCommandDisplayHelp("STRING")
AddTextComponentSubstringPlayerName(get_locale().Payphone.Interact)
EndTextCommandDisplayHelp(0, false, true, -1)
Bridge.Framework.ShowHelpNotification(get_locale().Payphone.Interact, "E")
if IsControlJustReleased(0, 38) then
open_payphone(nearest_payphone)
end
@@ -293,15 +391,21 @@ end)
CreateThread(function()
while true do
if active_call_id and active_booth and not distance_hangup_requested then
local distance = #(GetEntityCoords(PlayerPedId()) - active_booth.coords)
if distance > Config.Payphones.MaximumCallDistance then
distance_hangup_requested = true
if active_call_id and active_booth then
Bridge.Framework.ShowHelpNotification(call_help_message(), "E")
if IsControlJustReleased(0, 38) and not hangup_requested then
hangup_requested = true
Bridge.Callbacks.Trigger("sky_phone:payphone:hangup", { id = active_call_id })
end
Wait(500)
local distance = #(GetEntityCoords(PlayerPedId()) - active_booth.coords)
if distance > Config.Payphones.MaximumCallDistance and not hangup_requested then
hangup_requested = true
Bridge.Callbacks.Trigger("sky_phone:payphone:hangup", { id = active_call_id })
end
Wait(0)
else
Wait(1000)
Wait(250)
end
end
end)
@@ -315,4 +419,5 @@ AddEventHandler("onResourceStop", function(resource_name)
end
leave_call_voice()
stop_call_visuals()
clear_active_call_state()
end)