2025-04-07 01:41:12 +00:00

529 lines
14 KiB
Lua

---@class OxTargetOption
---@field resource? string
local utils = require 'client.utils'
local api = setmetatable({}, {
__newindex = function(self, index, value)
rawset(self, index, value)
exports(index, value)
end
})
---Throws a formatted type error
---@param variable string
---@param expected string
---@param received string
local function typeError(variable, expected, received)
error(("expected %s to have type '%s' (received %s)"):format(variable, expected, received))
end
---Checks options and throws an error on type mismatch
---@param options OxTargetOption | OxTargetOption[]
---@return OxTargetOption[]
local function checkOptions(options)
local optionsType = type(options)
if optionsType ~= 'table' then
typeError('options', 'table', optionsType)
end
local tableType = table.type(options)
if tableType == 'hash' and options.label then
options = { options }
elseif tableType ~= 'array' then
typeError('options', 'array', ('%s table'):format(tableType))
end
return options
end
---@param data OxTargetPolyZone | table
---@return number
function api.addPolyZone(data)
if data.debug then utils.warn('Creating new PolyZone with debug enabled.') end
data.resource = GetInvokingResource()
data.options = checkOptions(data.options)
return lib.zones.poly(data).id
end
---@param data OxTargetBoxZone | table
---@return number
function api.addBoxZone(data)
if data.debug then utils.warn('Creating new BoxZone with debug enabled.') end
data.resource = GetInvokingResource()
data.options = checkOptions(data.options)
return lib.zones.box(data).id
end
---@param data OxTargetSphereZone | table
---@return number
function api.addSphereZone(data)
if data.debug then utils.warn('Creating new SphereZone with debug enabled.') end
data.resource = GetInvokingResource()
data.options = checkOptions(data.options)
return lib.zones.sphere(data).id
end
---@param id number | string The ID of the zone to check. It can be either a number or a string representing the zone's index or name, respectively.
---@return boolean returns true if the zone with the specified ID exists, otherwise false.
function api.zoneExists(id)
if not Zones or (type(id) ~= 'number' and type(id) ~= 'string') then return false end
if type(id) == 'number' and Zones[id] then return true end
for _, zone in pairs(lib.zones.getAllZones()) do
if type(id) == 'string' and zone.name == id then return true end
end
return false
end
---@param id number | string
---@param suppressWarning boolean?
function api.removeZone(id, suppressWarning)
if Zones then
if type(id) == 'string' then
local foundZone
for _, v in pairs(lib.zones.getAllZones()) do
if v.name == id then
foundZone = true
v:remove()
end
end
if foundZone then return end
elseif Zones[id] then
return Zones[id]:remove()
end
end
if suppressWarning then return end
warn(('attempted to remove a zone that does not exist (id: %s)'):format(id))
end
---@param target table
---@param remove string | string[]
---@param resource string
---@param showWarning? boolean
local function removeTarget(target, remove, resource, showWarning)
if type(remove) ~= 'table' then remove = { remove } end
for i = #target, 1, -1 do
local option = target[i]
if option.resource == resource then
for j = #remove, 1, -1 do
if option.name == remove[j] then
table.remove(target, i)
if showWarning then
utils.warn(("Replacing existing target option '%s'."):format(option.name))
end
end
end
end
end
end
---@param target table
---@param options OxTargetOption | OxTargetOption[]
---@param resource string
local function addTarget(target, options, resource)
options = checkOptions(options)
local checkNames = {}
resource = resource or 'ox_target'
for i = 1, #options do
local option = options[i]
option.resource = resource
if option.name then
checkNames[#checkNames + 1] = option.name
end
end
if checkNames[1] then
removeTarget(target, checkNames, resource, true)
end
local num = #target
for i = 1, #options do
local option = options[i]
if resource == 'ox_target' then
if option.canInteract then
option.canInteract = msgpack.unpack(msgpack.pack(option.canInteract))
end
if option.onSelect then
option.onSelect = msgpack.unpack(msgpack.pack(option.onSelect))
end
end
num += 1
target[num] = options[i]
end
end
---@type table<number, OxTargetOption[]>
local peds = {}
---@param options OxTargetOption | OxTargetOption[]
function api.addGlobalPed(options)
addTarget(peds, options, GetInvokingResource())
end
---@param options string | string[]
function api.removeGlobalPed(options)
removeTarget(peds, options, GetInvokingResource())
end
---@type table<number, OxTargetOption[]>
local vehicles = {}
---@param options OxTargetOption | OxTargetOption[]
function api.addGlobalVehicle(options)
addTarget(vehicles, options, GetInvokingResource())
end
---@param options string | string[]
function api.removeGlobalVehicle(options)
removeTarget(vehicles, options, GetInvokingResource())
end
---@type table<number, OxTargetOption[]>
local objects = {}
---@param options OxTargetOption | OxTargetOption[]
function api.addGlobalObject(options)
addTarget(objects, options, GetInvokingResource())
end
---@param options string | string[]
function api.removeGlobalObject(options)
removeTarget(objects, options, GetInvokingResource())
end
---@type table<number, OxTargetOption[]>
local players = {}
---@param options OxTargetOption | OxTargetOption[]
function api.addGlobalPlayer(options)
addTarget(players, options, GetInvokingResource())
end
---@param options string | string[]
function api.removeGlobalPlayer(options)
removeTarget(players, options, GetInvokingResource())
end
---@type table<number, OxTargetOption[]>
local models = {}
---@param arr (number | string) | (number | string)[]
---@param options OxTargetOption | OxTargetOption[]
function api.addModel(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local model = arr[i]
model = tonumber(model) or joaat(model)
if not models[model] then
models[model] = {}
end
addTarget(models[model], options, resource)
end
end
---@param arr (number | string) | (number | string)[]
---@param options? string | string[]
function api.removeModel(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local model = arr[i]
model = tonumber(model) or joaat(model)
if models[model] then
if options then
removeTarget(models[model], options, resource)
end
if not options or #models[model] == 0 then
models[model] = nil
end
end
end
end
---@type table<number, OxTargetOption[]>
local entities = {}
---@param arr number | number[]
---@param options OxTargetOption | OxTargetOption[]
function api.addEntity(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local netId = arr[i]
if NetworkDoesNetworkIdExist(netId) then
if not entities[netId] then
entities[netId] = {}
if not Entity(NetworkGetEntityFromNetworkId(netId)).state.hasTargetOptions then
TriggerServerEvent('ox_target:setEntityHasOptions', netId)
end
end
addTarget(entities[netId], options, resource)
end
end
end
---@param arr number | number[]
---@param options? string | string[]
function api.removeEntity(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local netId = arr[i]
if entities[netId] then
if options then
removeTarget(entities[netId], options, resource)
end
if not options or #entities[netId] == 0 then
entities[netId] = nil
end
end
end
end
RegisterNetEvent('ox_target:removeEntity', api.removeEntity)
---@type table<number, OxTargetOption[]>
local localEntities = {}
---@param arr number | number[]
---@param options OxTargetOption | OxTargetOption[]
function api.addLocalEntity(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local entityId = arr[i]
if DoesEntityExist(entityId) then
if not localEntities[entityId] then
localEntities[entityId] = {}
end
addTarget(localEntities[entityId], options, resource)
else
print(("No entity with id '%s' exists."):format(entityId))
end
end
end
---@param arr number | number[]
---@param options? table
function api.removeLocalEntity(arr, options)
if type(arr) ~= 'table' then arr = { arr } end
local resource = GetInvokingResource()
for i = 1, #arr do
local entity = arr[i]
if localEntities[entity] then
if options then
removeTarget(localEntities[entity], options, resource)
end
if not options or #localEntities[entity] == 0 then
localEntities[entity] = nil
end
end
end
end
CreateThread(function()
while true do
Wait(60000)
for entityId in pairs(localEntities) do
if not DoesEntityExist(entityId) then
localEntities[entityId] = nil
end
end
end
end)
---@param resource string
---@param target table
local function removeResourceGlobals(resource, target)
for i = 1, #target do
local options = target[i]
for j = #options, 1, -1 do
if options[j].resource == resource then
table.remove(options, j)
end
end
end
end
---@param resource string
---@param target table
local function removeResourceTargets(resource, target)
for i = 1, #target do
local tbl = target[i]
for key, options in pairs(tbl) do
for j = #options, 1, -1 do
if options[j].resource == resource then
table.remove(options, j)
end
end
if #options == 0 then
tbl[key] = nil
end
end
end
end
---@param resource string
AddEventHandler('onClientResourceStop', function(resource)
removeResourceGlobals(resource, { peds, vehicles, objects, players })
removeResourceTargets(resource, { models, entities, localEntities })
if Zones then
for _, v in pairs(Zones) do
if v.resource == resource then
v:remove()
end
end
end
end)
local NetworkGetEntityIsNetworked = NetworkGetEntityIsNetworked
local NetworkGetNetworkIdFromEntity = NetworkGetNetworkIdFromEntity
---@class OxTargetOptions
local options_mt = {}
options_mt.__index = options_mt
options_mt.size = 1
function options_mt:wipe()
options_mt.size = 1
self.globalTarget = nil
self.model = nil
self.entity = nil
self.localEntity = nil
if self.__global[1]?.name == 'builtin:goback' then
table.remove(self.__global, 1)
end
end
---@param entity? number
---@param _type? number
---@param model? number
function options_mt:set(entity, _type, model)
if not entity then return end
if _type == 1 and IsPedAPlayer(entity) then
self:wipe()
self.globalTarget = players
options_mt.size += 1
return
end
local netId = NetworkGetEntityIsNetworked(entity) and NetworkGetNetworkIdFromEntity(entity)
self.globalTarget = _type == 1 and peds or _type == 2 and vehicles or objects
self.model = models[model]
self.entity = netId and entities[netId] or nil
self.localEntity = localEntities[entity]
options_mt.size += 1
if self.model then options_mt.size += 1 end
if self.entity then options_mt.size += 1 end
if self.localEntity then options_mt.size += 1 end
end
---@type OxTargetOption[]
local global = {}
---@param options OxTargetOption | OxTargetOption[]
function api.addGlobalOption(options)
addTarget(global, options, GetInvokingResource())
end
---@param options string | string[]
function api.removeGlobalOption(options)
removeTarget(global, options, GetInvokingResource())
end
---@class OxTargetOptions
local options = setmetatable({
__global = global
}, options_mt)
---@param entity? number
---@param _type? number
---@param model? number
function api.getTargetOptions(entity, _type, model)
if not entity then return options end
if IsPedAPlayer(entity) then
return {
global = players,
}
end
local netId = NetworkGetEntityIsNetworked(entity) and NetworkGetNetworkIdFromEntity(entity)
return {
global = _type == 1 and peds or _type == 2 and vehicles or objects,
model = models[model],
entity = netId and entities[netId] or nil,
localEntity = localEntities[entity],
}
end
local state = require 'client.state'
function api.disableTargeting(value)
if value then
state.setActive(false)
end
state.setDisabled(value)
end
function api.isActive()
return state.isActive()
end
return api