FIX - synchronize payphone animations

This commit is contained in:
Dominik
2026-08-10 20:48:12 +02:00
parent 8a4207b2fd
commit 7542ffea65
2 changed files with 213 additions and 30 deletions
+160 -28
View File
@@ -10,17 +10,78 @@ local call_channel = 0
local replacement_prop = nil
local hidden_prop = nil
local animation_scene = nil
local network_animation_scene = nil
local animation_ped = nil
local animation_floor_z = nil
local visuals_starting = false
local visuals_ending = false
local hangup_requested = false
local remote_visuals = {}
local configured_models = {}
for _, model_name in ipairs(Config.Payphones.Props or {}) do
configured_models[joaat(model_name)] = model_name
end
local function valid_visual_number(value, maximum)
local number = tonumber(value)
if not number or number ~= number or math.abs(number) > maximum then
return nil
end
return number
end
local function restore_remote_visual(id)
local visual = remote_visuals[id]
if not visual then
return
end
remote_visuals[id] = nil
if not visual.hidden_entity or not DoesEntityExist(visual.hidden_entity) then
return
end
for _, other in pairs(remote_visuals) do
if other.model_hash == visual.model_hash and #(other.coords - visual.coords) < 0.5 then
other.hidden_entity = other.hidden_entity or visual.hidden_entity
return
end
end
SetEntityVisible(visual.hidden_entity, true, false)
end
RegisterNetEvent("sky_phone:payphone:visual:start", function(data)
if type(data) ~= "table" or type(data.id) ~= "string" or type(data.model) ~= "string"
or tonumber(data.callerSource) == GetPlayerServerId(PlayerId())
then
return
end
local model_hash = joaat(data.model)
if not configured_models[model_hash] or type(data.coords) ~= "table" then
return
end
local x = valid_visual_number(data.coords.x, 10000.0)
local y = valid_visual_number(data.coords.y, 10000.0)
local z = valid_visual_number(data.coords.z, 2000.0)
if not x or not y or not z then
return
end
restore_remote_visual(data.id)
remote_visuals[data.id] = {
coords = vector3(x, y, z),
model_hash = model_hash,
hidden_entity = nil,
}
end)
RegisterNetEvent("sky_phone:payphone:visual:stop", function(data)
if type(data) ~= "table" or type(data.id) ~= "string" then
return
end
restore_remote_visual(data.id)
end)
local function get_locale()
return Locales[Config.Bridge.Locale] or Locales["en"]
end
@@ -81,7 +142,11 @@ local function stop_call_visuals()
ClearPedTasksImmediately(ped)
end
if animation_scene then
if network_animation_scene then
NetworkStopSynchronisedScene(network_animation_scene)
network_animation_scene = nil
animation_scene = nil
elseif animation_scene then
SetSynchronizedSceneHoldLastFrame(animation_scene, false)
DisposeSynchronizedScene(animation_scene)
animation_scene = nil
@@ -133,28 +198,12 @@ local function play_hangup_visuals()
local duration_ms = math.max(250, math.floor(tonumber(Config.Payphones.Animation.HangupDurationMs) or 1200))
local started_at = GetGameTimer()
SetSynchronizedSceneRate(scene, 0.0)
if replacement_prop and DoesEntityExist(replacement_prop) then
SetEntityAnimSpeed(
replacement_prop,
Config.Payphones.Animation.Dictionary,
Config.Payphones.Animation.PropClip,
0.0
)
end
CreateThread(function()
while animation_scene == scene do
local progress = math.min(1.0, (GetGameTimer() - started_at) / duration_ms)
local phase = starting_phase * (1.0 - progress)
SetSynchronizedScenePhase(scene, phase)
if replacement_prop and DoesEntityExist(replacement_prop) then
SetEntityAnimCurrentTime(
replacement_prop,
Config.Payphones.Animation.Dictionary,
Config.Payphones.Animation.PropClip,
phase
)
end
keep_animation_ped_grounded()
if progress >= 1.0 then
break
@@ -202,7 +251,7 @@ local function start_call_visuals()
coords.x,
coords.y,
coords.z,
false,
true,
true,
false
)
@@ -217,27 +266,42 @@ local function start_call_visuals()
SetEntityRotation(replacement, rotation.x, rotation.y, rotation.z, 2, false)
FreezeEntityPosition(replacement, true)
SetEntityCollision(replacement, false, false)
local replacement_network_id = NetworkGetNetworkIdFromEntity(replacement)
if replacement_network_id == 0 then
Bridge.Debug("error", "[sky_phone] The animated payphone replacement prop is not networked.")
SetEntityAsMissionEntity(replacement, true, true)
DeleteEntity(replacement)
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
RemoveAnimDict(animation.Dictionary)
return
end
SetNetworkIdCanMigrate(replacement_network_id, false)
SetEntityVisible(original, false, false)
hidden_prop = original
replacement_prop = replacement
local ped = PlayerPedId()
animation_scene = CreateSynchronizedScene(
network_animation_scene = NetworkCreateSynchronisedScene(
coords.x,
coords.y,
coords.z,
rotation.x,
rotation.y,
rotation.z,
2
2,
true,
false,
1.0,
0.0,
1.0
)
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
if not network_animation_scene or network_animation_scene == -1 then
Bridge.Debug("error", "[sky_phone] The payphone network synchronized scene could not be created.")
network_animation_scene = nil
stop_call_visuals()
return
end
SetSynchronizedSceneHoldLastFrame(animation_scene, true)
animation_ped = ped
local ped_coords = GetEntityCoords(animation_ped)
local found_ground, ground_z = GetGroundZFor_3dCoord(
@@ -247,19 +311,43 @@ local function start_call_visuals()
false
)
animation_floor_z = found_ground and ground_z or ped_coords.z
TaskSynchronizedScene(
NetworkAddPedToSynchronisedScene(
ped,
animation_scene,
network_animation_scene,
animation.Dictionary,
animation.PedClip,
8.0,
-8.0,
2,
0,
1.0,
1000.0,
0
)
PlayEntityAnim(replacement, animation.PropClip, animation.Dictionary, 8.0, false, true, false, 0.0, 0)
NetworkAddEntityToSynchronisedScene(
replacement,
network_animation_scene,
animation.Dictionary,
animation.PropClip,
8.0,
-8.0,
0
)
NetworkStartSynchronisedScene(network_animation_scene)
local scene_deadline = GetGameTimer() + 1000
repeat
animation_scene = NetworkGetLocalSceneFromNetworkId(network_animation_scene)
if animation_scene and animation_scene ~= -1 then
break
end
Wait(0)
until GetGameTimer() >= scene_deadline
if not animation_scene or animation_scene == -1 then
Bridge.Debug("error", "[sky_phone] The local handle for the payphone network scene was not created.")
animation_scene = nil
stop_call_visuals()
return
end
visuals_starting = false
SetModelAsNoLongerNeeded(replacement_hash)
end
@@ -467,6 +555,43 @@ CreateThread(function()
end
end)
CreateThread(function()
while true do
local has_visuals = next(remote_visuals) ~= nil
if has_visuals then
local player_coords = GetEntityCoords(PlayerPedId())
for _, visual in pairs(remote_visuals) do
if visual.hidden_entity and not DoesEntityExist(visual.hidden_entity) then
visual.hidden_entity = nil
end
if #(player_coords - visual.coords) <= Config.Payphones.ScanDistance then
if not visual.hidden_entity then
local entity = GetClosestObjectOfType(
visual.coords.x,
visual.coords.y,
visual.coords.z,
1.0,
visual.model_hash,
false,
false,
false
)
if entity ~= 0 and DoesEntityExist(entity) then
visual.hidden_entity = entity
end
end
if visual.hidden_entity then
SetEntityVisible(visual.hidden_entity, false, false)
end
end
end
Wait(250)
else
Wait(1000)
end
end
end)
CreateThread(function()
while true do
local call_id = active_call_id
@@ -506,4 +631,11 @@ AddEventHandler("onResourceStop", function(resource_name)
leave_call_voice()
stop_call_visuals()
clear_active_call_state()
local visual_ids = {}
for id in pairs(remote_visuals) do
visual_ids[#visual_ids + 1] = id
end
for _, id in ipairs(visual_ids) do
restore_remote_visual(id)
end
end)
+53 -2
View File
@@ -169,6 +169,47 @@ local function settle_payphone_call(call, duration)
return charged and billable_seconds == elapsed_seconds
end
local function send_payphone_visual(call, action, delay_ms)
if not call.payphone then
return
end
local coords = call.payphone.coords
local payload = {
id = call.id,
callerSource = call.caller_source,
model = call.payphone.model,
coords = { x = coords.x, y = coords.y, z = coords.z },
}
local event_name = ("sky_phone:payphone:visual:%s"):format(action)
local routing_bucket = call.payphone.routing_bucket
local targets = {}
if action == "stop" and call.payphone.visual_targets then
for _, target in ipairs(call.payphone.visual_targets) do
targets[#targets + 1] = target
end
else
for _, player_source in ipairs(Bridge.Framework.GetPlayers()) do
local target = tonumber(player_source) or player_source
if GetPlayerRoutingBucket(target) == routing_bucket then
targets[#targets + 1] = target
end
end
if action == "start" then
call.payphone.visual_targets = targets
end
end
local function dispatch()
for _, target in ipairs(targets) do
TriggerClientEvent(event_name, target, payload)
end
end
if delay_ms and delay_ms > 0 then
SetTimeout(delay_ms, dispatch)
else
dispatch()
end
end
local function finish_call(call, status)
if not call or call.ended then
return
@@ -228,6 +269,13 @@ local function finish_call(call, status)
if call.callee_device and not call.payphone then
notify_recents(call.callee_device, call.callee_source)
end
if call.payphone then
local hangup_duration = math.max(
250,
math.floor(tonumber(Config.Payphones.Animation.HangupDurationMs) or 1200)
)
send_payphone_visual(call, "stop", hangup_duration)
end
calls[call.id] = nil
end
@@ -540,7 +588,7 @@ local function valid_payphone_position(source, data)
if #(player_coords - booth_coords) > Config.Payphones.ServerValidationDistance then
return nil
end
return booth_coords
return booth_coords, data.model
end
local function payphone_terminal(number, state)
@@ -559,7 +607,7 @@ Bridge.Callbacks.Register("sky_phone:payphone:dial", function(source, data)
if not Config.Payphones.Enabled or not SkyPhone.AllowOperation(source, "payphone_dial", 15, 60) then
return { success = false, error = "rate_limited" }
end
local booth_coords = valid_payphone_position(source, data)
local booth_coords, booth_model = valid_payphone_position(source, data)
if not booth_coords then
return { success = false, error = "invalid_payphone" }
end
@@ -613,7 +661,9 @@ Bridge.Callbacks.Register("sky_phone:payphone:dial", function(source, data)
elapsed_seconds = 0,
total_cost = 0,
coords = booth_coords,
model = booth_model,
price_per_second = price_per_second,
routing_bucket = GetPlayerRoutingBucket(source),
},
}
calls[id] = call
@@ -624,6 +674,7 @@ Bridge.Callbacks.Register("sky_phone:payphone:dial", function(source, data)
dial_locks[source] = nil
send_state(call, source, "ringing")
send_payphone_visual(call, "start")
SkyPhone.OpenDeviceForCall(callee_source, target.imei)
TriggerClientEvent("sky_phone:call:incoming", callee_source, {
id = id,