mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 16:23:22 +00:00
ENH - detect payphones and spawn custom locations
Remove the static vanilla payphone coordinate list so configured prop models are detected directly in the streamed world. Add vector4-based custom locations that spawn the selected custom prop, and validate detected booth models and player proximity server-side.
This commit is contained in:
@@ -18,6 +18,7 @@ local visuals_starting = false
|
||||
local visuals_ending = false
|
||||
local hangup_requested = false
|
||||
local remote_visuals = {}
|
||||
local custom_payphone_props = {}
|
||||
|
||||
local configured_models = {}
|
||||
for _, model_name in ipairs(Config.Payphones.Props or {}) do
|
||||
@@ -97,6 +98,64 @@ local function load_model(model_hash)
|
||||
return HasModelLoaded(model_hash)
|
||||
end
|
||||
|
||||
local function spawn_custom_payphones()
|
||||
local locations = Config.Payphones.CustomLocations or {}
|
||||
if not Config.Payphones.Enabled or #locations == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local model_name = Config.Payphones.CustomProp
|
||||
local model_hash = type(model_name) == "string" and joaat(model_name) or 0
|
||||
if not configured_models[model_hash] then
|
||||
Bridge.Debug(
|
||||
"error",
|
||||
"[sky_phone] Config.Payphones.CustomProp must also be listed in Config.Payphones.Props.",
|
||||
{ always = true }
|
||||
)
|
||||
return
|
||||
end
|
||||
if not load_model(model_hash) then
|
||||
Bridge.Debug("error", "[sky_phone] The configured custom payphone prop could not be loaded.", {
|
||||
always = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
for index, coords in ipairs(locations) do
|
||||
local coords_type = type(coords)
|
||||
local x = (coords_type == "vector4" or coords_type == "table")
|
||||
and valid_visual_number(coords.x, 10000.0) or nil
|
||||
local y = x and valid_visual_number(coords.y, 10000.0) or nil
|
||||
local z = y and valid_visual_number(coords.z, 2000.0) or nil
|
||||
local heading = z and valid_visual_number(coords.w, 360000.0) or nil
|
||||
if not heading then
|
||||
Bridge.Debug(
|
||||
"error",
|
||||
"[sky_phone] Ignored invalid Config.Payphones.CustomLocations entry %s; use vector4(x, y, z, heading).",
|
||||
index,
|
||||
{ always = true }
|
||||
)
|
||||
else
|
||||
local entity = CreateObjectNoOffset(model_hash, x, y, z, false, true, false)
|
||||
if entity == 0 or not DoesEntityExist(entity) then
|
||||
Bridge.Debug(
|
||||
"error",
|
||||
"[sky_phone] Failed to spawn custom payphone %s.",
|
||||
index,
|
||||
{ always = true }
|
||||
)
|
||||
else
|
||||
SetEntityHeading(entity, heading % 360.0)
|
||||
FreezeEntityPosition(entity, true)
|
||||
custom_payphone_props[#custom_payphone_props + 1] = entity
|
||||
end
|
||||
end
|
||||
end
|
||||
SetModelAsNoLongerNeeded(model_hash)
|
||||
end
|
||||
|
||||
CreateThread(spawn_custom_payphones)
|
||||
|
||||
local function load_animation(dictionary)
|
||||
if HasAnimDictLoaded(dictionary) then
|
||||
return true
|
||||
@@ -667,4 +726,10 @@ AddEventHandler("onResourceStop", function(resource_name)
|
||||
for _, id in ipairs(visual_ids) do
|
||||
restore_remote_visual(id)
|
||||
end
|
||||
for _, entity in ipairs(custom_payphone_props) do
|
||||
if DoesEntityExist(entity) then
|
||||
SetEntityAsMissionEntity(entity, true, true)
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1074,34 +1074,23 @@ for _, model_name in ipairs(Config.Payphones.Props or {}) do
|
||||
end
|
||||
end
|
||||
|
||||
local payphone_locations, rejected_payphone_locations = SkyPhonePayphones.ValidateLocations(
|
||||
Config.Payphones.Locations,
|
||||
payphone_models
|
||||
)
|
||||
if Config.Payphones.Enabled and #payphone_locations == 0 then
|
||||
if Config.Payphones.Enabled and not next(payphone_models) then
|
||||
Bridge.Debug(
|
||||
"error",
|
||||
"[sky_phone] Payphones are enabled, but no valid server-owned locations are configured; payphone calls will be rejected.",
|
||||
{ always = true }
|
||||
)
|
||||
elseif Config.Payphones.Enabled and rejected_payphone_locations > 0 then
|
||||
Bridge.Debug(
|
||||
"warn",
|
||||
"[sky_phone] Ignored %s invalid server-owned payphone location(s).",
|
||||
rejected_payphone_locations,
|
||||
"[sky_phone] Payphones are enabled, but Config.Payphones.Props contains no valid models; payphone calls will be rejected.",
|
||||
{ always = true }
|
||||
)
|
||||
end
|
||||
|
||||
local function valid_payphone_position(source)
|
||||
local function valid_payphone_position(source, detected_booth)
|
||||
local ped = GetPlayerPed(source)
|
||||
if not ped or ped == 0 then
|
||||
return nil
|
||||
end
|
||||
local player_coords = GetEntityCoords(ped)
|
||||
local location = SkyPhonePayphones.FindNearest(
|
||||
payphone_locations,
|
||||
player_coords,
|
||||
local location = SkyPhonePayphones.ValidateDetected(
|
||||
detected_booth,
|
||||
payphone_models,
|
||||
GetEntityCoords(ped),
|
||||
Config.Payphones.ServerValidationDistance
|
||||
)
|
||||
if not location then
|
||||
@@ -1129,7 +1118,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, booth_model = valid_payphone_position(source)
|
||||
local booth_coords, booth_model = valid_payphone_position(source, data)
|
||||
if not booth_coords then
|
||||
return { success = false, error = "invalid_payphone" }
|
||||
end
|
||||
|
||||
@@ -12,7 +12,8 @@ end
|
||||
|
||||
local function normalize_coordinates(value, allow_vector)
|
||||
local value_type = type(value)
|
||||
if value_type ~= "table" and (not allow_vector or value_type ~= "vector3") then
|
||||
local supported_vector = value_type == "vector3" or value_type == "vector4"
|
||||
if value_type ~= "table" and (not allow_vector or not supported_vector) then
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -46,60 +47,24 @@ local function normalize_location(location, allowed_models)
|
||||
}
|
||||
end
|
||||
|
||||
function SkyPhonePayphones.ValidateLocations(locations, allowed_models)
|
||||
if type(locations) ~= "table" or type(allowed_models) ~= "table" then
|
||||
return {}, 0
|
||||
end
|
||||
|
||||
local validated = {}
|
||||
local rejected = 0
|
||||
for index, location in pairs(locations) do
|
||||
local valid_index = type(index) == "number" and index >= 1 and index % 1 == 0
|
||||
local normalized = valid_index and normalize_location(location, allowed_models) or nil
|
||||
if normalized then
|
||||
normalized.index = index
|
||||
validated[#validated + 1] = normalized
|
||||
else
|
||||
rejected = rejected + 1
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(validated, function(left, right)
|
||||
return left.index < right.index
|
||||
end)
|
||||
for index = 1, #validated do
|
||||
validated[index].index = nil
|
||||
end
|
||||
|
||||
return validated, rejected
|
||||
end
|
||||
|
||||
function SkyPhonePayphones.FindNearest(locations, player_coords, maximum_distance)
|
||||
if type(locations) ~= "table" then
|
||||
function SkyPhonePayphones.ValidateDetected(location, allowed_models, player_coords, maximum_distance)
|
||||
if type(allowed_models) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local normalized = normalize_location(location, allowed_models)
|
||||
local coords = normalize_coordinates(player_coords, true)
|
||||
local distance = finite_number(maximum_distance)
|
||||
if not coords or not distance or distance <= 0 then
|
||||
if not normalized or not coords or not distance or distance <= 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local nearest = nil
|
||||
local nearest_distance_squared = distance * distance
|
||||
for index = 1, #locations do
|
||||
local location = locations[index]
|
||||
if type(location) == "table" and type(location.coords) == "table" then
|
||||
local dx = coords.x - location.coords.x
|
||||
local dy = coords.y - location.coords.y
|
||||
local dz = coords.z - location.coords.z
|
||||
local distance_squared = dx * dx + dy * dy + dz * dz
|
||||
if distance_squared <= nearest_distance_squared then
|
||||
nearest = location
|
||||
nearest_distance_squared = distance_squared
|
||||
end
|
||||
end
|
||||
local dx = coords.x - normalized.coords.x
|
||||
local dy = coords.y - normalized.coords.y
|
||||
local dz = coords.z - normalized.coords.z
|
||||
if dx * dx + dy * dy + dz * dz > distance * distance then
|
||||
return nil
|
||||
end
|
||||
|
||||
return nearest
|
||||
return normalized
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user