588 lines
19 KiB
Lua
588 lines
19 KiB
Lua
local log = require 'shared.log'
|
|
local utils = require 'client.utils'
|
|
local settings = require 'shared.settings'
|
|
local interactions, filteredInteractions = {}, {}
|
|
local table_sort = table.sort
|
|
|
|
-- CACHE.
|
|
local MODELS = {}
|
|
local ENTITIES = {}
|
|
local ENTITY_BONES = {}
|
|
local NETWORKED_ENTITIES = {}
|
|
|
|
local api = {}
|
|
|
|
AddEventHandler('interactions:groupsChanged', function(newgroups)
|
|
-- Use this event handler to loop through all current interactions and remove any that are not in the new groups that way we limit the amount of iterations needed
|
|
end)
|
|
|
|
local function checkParams(entity, options)
|
|
if not entity then
|
|
log:error('Entity is required to add an interaction')
|
|
return
|
|
else
|
|
if type(entity) ~= 'number' then
|
|
log:error('Entity must be a number')
|
|
return
|
|
end
|
|
|
|
if not DoesEntityExist(entity) then
|
|
log:error('Entity %s does not exist', entity)
|
|
return
|
|
end
|
|
end
|
|
|
|
if not options then
|
|
log:error('Options are required to add an interaction')
|
|
return
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--#TODO: Add a way to filter interactions based on the players group
|
|
local function filterInteractions()
|
|
local myGroups = 'police'
|
|
|
|
local newInteractions = {}
|
|
local amount = 0
|
|
for k ,v in pairs(interactions) do
|
|
amount += 1
|
|
newInteractions[amount] = v
|
|
end
|
|
|
|
filteredInteractions = newInteractions
|
|
end
|
|
|
|
---@param model number|string : The model to add the interaction to
|
|
---@param options table : { label, canInteract, action, event, serverEvent, args }
|
|
---@param data table : { distance, interactDst, resource }
|
|
local function addModel(model, options, data)
|
|
if type(model) ~= "number" then
|
|
model = joaat(model)
|
|
end
|
|
|
|
if not IsModelValid(model) then
|
|
log:error('Model %s is not valid', model)
|
|
return
|
|
end
|
|
|
|
if not MODELS[model] then
|
|
log:debug('Adding model %s to interactions', model)
|
|
MODELS[model] = {
|
|
model = model,
|
|
offset = data.offset,
|
|
options = options,
|
|
distance = data.distance,
|
|
interactDst = data.interactDst,
|
|
resource = data.resource,
|
|
}
|
|
else
|
|
log:debug('Updating model %s in interactions', model)
|
|
for _, option in pairs(options) do
|
|
MODELS[model].options[#MODELS[model].options + 1] = option
|
|
end
|
|
|
|
-- Update the distance and interactDst if the new data is greater
|
|
if data.distance > MODELS[model].distance then
|
|
MODELS[model].distance = data.distance
|
|
end
|
|
|
|
if data.interactDst > MODELS[model].interactDst then
|
|
MODELS[model].interactDst = data.interactDst
|
|
end
|
|
|
|
-- Update the offset if there is new data
|
|
if data.offset then
|
|
MODELS[model].offset = data.offset
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param data table : { name, coords, options, distance, interactDst, groups }
|
|
---@return number : The id of the interaction
|
|
-- Add an interaction point at a set of coords
|
|
function api.addInteraction(data)
|
|
if not data.coords then
|
|
log:error('Coords are required to add an interaction')
|
|
return
|
|
end
|
|
|
|
if not data.options then
|
|
log:error('Options are required to add an interaction')
|
|
return
|
|
end
|
|
|
|
local id = #interactions + 1
|
|
interactions[id] = {
|
|
id = id,
|
|
name = data.name or 'interaction:'..id,
|
|
coords = data.coords,
|
|
options = data.options or {},
|
|
distance = data.distance or 8.0,
|
|
interactDst = data.interactDst or 1.0,
|
|
groups = data.groups or nil,
|
|
resource = GetInvokingResource()
|
|
}
|
|
|
|
filterInteractions()
|
|
|
|
return id
|
|
end exports('AddInteraction', api.addInteraction)
|
|
|
|
---@param data table : { name, entity, options, distance, interactDst, groups }
|
|
---@return number : The id of the interaction
|
|
-- Add an interaction point on a local (client side) entity
|
|
function api.addLocalEntityInteraction(data)
|
|
local entity = data.entity
|
|
if not checkParams(entity, data.options) then
|
|
if ENTITIES[entity] then
|
|
ENTITIES[entity] = nil
|
|
end
|
|
return
|
|
end
|
|
local id = #interactions + 1
|
|
-- If then entity not registered yet, add it
|
|
if not ENTITIES[entity] then
|
|
log:debug('Adding entity %s to interactions', entity)
|
|
|
|
interactions[id] = {
|
|
id = id,
|
|
name = data.name or 'interaction:'..id,
|
|
entity = entity,
|
|
options = data.options or {},
|
|
distance = data.distance or 8.0,
|
|
interactDst = data.interactDst or 1.0,
|
|
offset = data.offset or vec(0.0, 0.0, 0.0),
|
|
groups = data.groups or nil,
|
|
resource = GetInvokingResource()
|
|
}
|
|
|
|
ENTITIES[entity] = interactions[id]
|
|
|
|
filterInteractions()
|
|
return id
|
|
end
|
|
|
|
-- If the entity is already registered, update it
|
|
log:debug('Updating entity %s in interactions', entity)
|
|
for index, option in pairs(data.options) do
|
|
if option.name and ENTITIES[entity].options[index]?.name == option.name then
|
|
log:debug('Option with name: ( %s ) already exists, updating', option.name)
|
|
ENTITIES[entity].options[index] = option
|
|
else
|
|
ENTITIES[entity].options[#ENTITIES[entity].options + 1] = option
|
|
end
|
|
end
|
|
|
|
-- Update the distance and interactDst if the new data is greater
|
|
if data.distance > ENTITIES[entity].distance then
|
|
ENTITIES[entity].distance = data.distance
|
|
end
|
|
|
|
if data.interactDst > ENTITIES[entity].interactDst then
|
|
ENTITIES[entity].interactDst = data.interactDst
|
|
end
|
|
|
|
-- Update the offset if there is new data
|
|
if data.offset then
|
|
ENTITIES[entity].offset = data.offset
|
|
end
|
|
|
|
interactions[id] = {
|
|
id = id,
|
|
entity = entity,
|
|
options = ENTITIES[entity].options,
|
|
distance = ENTITIES[entity].distance,
|
|
interactDst = ENTITIES[entity].interactDst,
|
|
offset = ENTITIES[entity].offset,
|
|
resource = GetInvokingResource()
|
|
}
|
|
|
|
filterInteractions()
|
|
|
|
return id
|
|
end exports('AddLocalEntityInteraction', api.addLocalEntityInteraction)
|
|
|
|
---@param data table : { name, netId, options, distance, interactDst, groups }
|
|
---@return number : The id of the interaction
|
|
-- Add an interaction point on a networked entity
|
|
function api.addEntityInteraction(data)
|
|
local netId = data.netId
|
|
-- If the netId does not exist, we assume it is an entity
|
|
local entity
|
|
if type(netId) == 'number' and NetworkDoesNetworkIdExist(netId) then
|
|
entity = utils.getEntity(netId)
|
|
elseif type(netId) == 'number' then
|
|
entity = netId
|
|
end
|
|
|
|
if not checkParams(entity, data.options) then
|
|
if NETWORKED_ENTITIES[netId] then
|
|
NETWORKED_ENTITIES[netId] = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
-- If the entity is not networked, add it as a local entity
|
|
if not NetworkGetEntityIsNetworked(entity) then
|
|
log:debug('Entity %s is not networked, adding as a local entity', entity)
|
|
data.entity = entity
|
|
return api.addLocalEntityInteraction(data)
|
|
end
|
|
netId = utils.getNet(entity)
|
|
local id = #interactions + 1
|
|
-- If then netId not registered yet, add it
|
|
if not NETWORKED_ENTITIES[netId] then
|
|
log:debug('Adding networkID %s to interactions', netId)
|
|
|
|
|
|
interactions[id] = {
|
|
id = id,
|
|
name = data.name or 'interaction:'..id,
|
|
entity = entity,
|
|
options = data.options or {},
|
|
distance = data.distance or 10.0,
|
|
interactDst = data.interactDst or 1.0,
|
|
offset = data.offset or vec(0.0, 0.0, 0.0),
|
|
groups = data.groups or nil,
|
|
resource = GetInvokingResource()
|
|
}
|
|
|
|
NETWORKED_ENTITIES[netId] = interactions[id]
|
|
|
|
filterInteractions()
|
|
return id
|
|
end
|
|
|
|
-- If the networkID is already registered, update it
|
|
log:debug('Updating networkID %s in interactions', netId)
|
|
for index, option in pairs(data.options) do
|
|
if option.name and NETWORKED_ENTITIES[netId].options[index]?.name == option.name then
|
|
log:debug('Option with name: ( %s ) already exists, updating', option.name)
|
|
NETWORKED_ENTITIES[netId].options[index] = option
|
|
else
|
|
NETWORKED_ENTITIES[netId].options[#NETWORKED_ENTITIES[netId].options + 1] = option
|
|
end
|
|
end
|
|
|
|
-- Update the distance and interactDst if the new data is greater
|
|
if data.distance > NETWORKED_ENTITIES[netId].distance then
|
|
NETWORKED_ENTITIES[netId].distance = data.distance
|
|
end
|
|
|
|
if data.interactDst > NETWORKED_ENTITIES[netId].interactDst then
|
|
NETWORKED_ENTITIES[netId].interactDst = data.interactDst
|
|
end
|
|
|
|
-- Update the offset if there is new data
|
|
if data.offset then
|
|
NETWORKED_ENTITIES[netId].offset = data.offset
|
|
end
|
|
|
|
interactions[id] = {
|
|
id = id,
|
|
entity = entity,
|
|
options = NETWORKED_ENTITIES[netId].options,
|
|
distance = NETWORKED_ENTITIES[netId].distance,
|
|
interactDst = NETWORKED_ENTITIES[netId].interactDst,
|
|
offset = NETWORKED_ENTITIES[netId].offset,
|
|
resource = GetInvokingResource()
|
|
}
|
|
|
|
filterInteractions()
|
|
|
|
return id
|
|
end exports('AddEntityInteraction', api.addEntityInteraction)
|
|
|
|
---@param data table : { name, entity[number|string], bone[string], options, distance, interactDst, groups }
|
|
---@return number : The id of the interaction
|
|
-- Add an interaction point on a networked entity's bone
|
|
function api.addEntityBoneInteraction(data)
|
|
|
|
if not data.entity then
|
|
log:error('Entity is required to add an interaction')
|
|
return
|
|
end
|
|
|
|
if not data.bone then
|
|
log:error('Bone is required to add an interaction')
|
|
return
|
|
end
|
|
|
|
if not data.options then
|
|
log:error('Options are required to add an interaction')
|
|
return
|
|
end
|
|
|
|
-- temp workaround until table refactoring.
|
|
local key = string.format('%s:%s', data.entity, data.bone)
|
|
if not ENTITY_BONES[key] then
|
|
log:debug('Added new entity bone interaction: %s', key)
|
|
ENTITY_BONES[key] = {
|
|
entity = data.entity,
|
|
bone = data.bone,
|
|
distance = data.distance or 10.0,
|
|
interactDst = data.interactDst or 1.0,
|
|
offset = data.offset or vec(0.0, 0.0, 0.0),
|
|
options = data.options,
|
|
groups = data.groups or nil,
|
|
}
|
|
else
|
|
log:debug('Updating %s in bone interactions', key)
|
|
|
|
for index, option in pairs(data.options) do
|
|
if option.name and ENTITY_BONES[key].options[index]?.name == option.name then
|
|
log:debug('Option with name: ( %s ) already exists, updating', option.name)
|
|
ENTITY_BONES[key].options[index] = option
|
|
else
|
|
ENTITY_BONES[key].options[#ENTITY_BONES[key].options + 1] = option
|
|
end
|
|
end
|
|
|
|
-- Update the distance and interactDst if the new data is greater
|
|
if data.distance > ENTITY_BONES[key].distance then
|
|
ENTITY_BONES[key].distance = data.distance
|
|
end
|
|
|
|
if data.interactDst > ENTITY_BONES[key].interactDst then
|
|
ENTITY_BONES[key].interactDst = data.interactDst
|
|
end
|
|
|
|
-- Update the offset if there is new data
|
|
if data.offset then
|
|
ENTITY_BONES[key].offset = data.offset
|
|
end
|
|
|
|
log:debug('Updated entity bone interaction: %s', key)
|
|
ENTITY_BONES[key] = {
|
|
entity = data.entity,
|
|
bone = data.bone,
|
|
options = ENTITY_BONES[key].options,
|
|
distance = ENTITY_BONES[key].distance,
|
|
interactDst = ENTITY_BONES[key].interactDst,
|
|
offset = ENTITY_BONES[key].offset,
|
|
resource = GetInvokingResource()
|
|
}
|
|
end
|
|
|
|
filterInteractions()
|
|
|
|
return id
|
|
end exports('AddEntityBoneInteraction', api.addEntityBoneInteraction)
|
|
|
|
---@param data table : { name, modelData table : { model[string], offset[vec3] }, options, distance, interactDst, groups }
|
|
-- Add interaction(s) to a list of models
|
|
function api.addModelInteraction(data)
|
|
data.distance = data.distance or 8.0
|
|
data.interactDst = data.interactDst or 1.0
|
|
for i = 1, #data.modelData do
|
|
local modelData = data.modelData[i]
|
|
if IsModelValid(modelData.model) then
|
|
local min, max = GetModelDimensions(modelData.model)
|
|
local size = (max - min)
|
|
data.interactDst += (size.x / 8)
|
|
data.distance += (size.x / 4)
|
|
data.resource = GetInvokingResource()
|
|
addModel(modelData.model, data.options, { offset = modelData.offset, distance = data.distance, interactDst = data.interactDst, resource = data.resource })
|
|
end
|
|
end
|
|
end exports('AddModelInteraction', api.addModelInteraction)
|
|
|
|
---@param id number : The id of the interaction to remove
|
|
-- Remove an interaction point by id.
|
|
function api.removeInteraction(id)
|
|
interactions[id] = nil
|
|
log:debug('Removed interaction %s', id)
|
|
filterInteractions()
|
|
end
|
|
exports('RemoveInteraction', api.removeInteraction)
|
|
|
|
---@param entity number : The entity to remove the interaction from
|
|
-- Remove an interaction point by entity.
|
|
function api.removeInteractionByEntity(entity)
|
|
for i = #interactions, 1, -1 do
|
|
local interaction = interactions[i]
|
|
|
|
if interaction.entity == entity then
|
|
api.removeInteraction(i)
|
|
end
|
|
end
|
|
end exports('RemoveInteractionByEntity', api.removeInteractionByEntity)
|
|
|
|
---@param id number : The id of the interaction to remove the option from
|
|
---@param name? string : The name of the option to remove
|
|
-- Remove an option from an interaction point by id.
|
|
function api.removeInteractionOption(id, name)
|
|
if not interactions[id] then
|
|
log:error('Interaction with id: ( %s ) does not exist', id)
|
|
return
|
|
end
|
|
|
|
if not name then
|
|
api.removeInteraction(id)
|
|
return
|
|
end
|
|
|
|
local options = interactions[id].options
|
|
|
|
if not options then
|
|
log:error('Interaction with id: ( %s ) does not have any options', id)
|
|
api.removeInteraction(id)
|
|
return
|
|
end
|
|
|
|
for i = #options, 1, -1 do
|
|
local option = options[i]
|
|
|
|
if option.name == name then
|
|
options[i] = nil
|
|
log:debug('Removed option %s from interaction %s', name, id)
|
|
end
|
|
end
|
|
end exports('RemoveInteractionOption', api.removeInteractionOption)
|
|
|
|
---@param id number : The id of the interaction to update
|
|
---@param options table : The new options to update the interaction with
|
|
-- Update an interaction point by id.
|
|
function api.updateInteraction(id, options)
|
|
if not options then
|
|
log:error('Options are required to update an interaction')
|
|
return
|
|
end
|
|
|
|
if not interactions[id] then
|
|
log:error('Interaction with id: ( %s ) does not exist', id)
|
|
return
|
|
end
|
|
|
|
if interactions[id] then
|
|
interactions[id].options = options
|
|
filterInteractions()
|
|
end
|
|
end exports('UpdateInteraction', api.updateInteraction)
|
|
|
|
function api.getNearbyInteractions()
|
|
local options = {}
|
|
local amount = 0
|
|
|
|
local playercoords = GetEntityCoords(cache.ped)
|
|
|
|
-- Temp loop : these checks need to be broken out into their own threads.
|
|
local nearbyVehicles = lib.getNearbyVehicles(playercoords, settings.nearbyVehicleDistance, false)
|
|
for i = 1, #nearbyVehicles do
|
|
local vehicle = nearbyVehicles[i].vehicle
|
|
local vehicleCoords = nearbyVehicles[i].coords
|
|
|
|
if settings.vehicleBoneDefaults.enabled then
|
|
for bone, data in pairs(settings.vehicleBoneDefaults.bones) do
|
|
local key = string.format('%s:%s', vehicle, bone)
|
|
if not ENTITY_BONES[key] then
|
|
api.addEntityBoneInteraction({
|
|
entity = vehicle,
|
|
bone = bone,
|
|
options = data.options,
|
|
distance = data.distance,
|
|
interactDst = data.interactDst,
|
|
offset = data.offset,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, interaction in pairs(ENTITY_BONES) do
|
|
local distance = #(utils.getCoordsFromInteract(interaction) - playercoords)
|
|
if distance <= interaction.distance then
|
|
amount += 1
|
|
interaction.curDist = distance
|
|
options[amount] = interaction
|
|
end
|
|
end
|
|
|
|
local nearbyObjects = lib.getNearbyObjects(playercoords, settings.nearbyObjectDistance)
|
|
for i = 1, #nearbyObjects do
|
|
local nearby = nearbyObjects[i]
|
|
local hash = GetEntityModel(nearby.object)
|
|
|
|
if MODELS[hash] then
|
|
local interaction = lib.table.deepclone(MODELS[hash])
|
|
-- put the functions to original references
|
|
for k, v in pairs(interaction) do
|
|
if type(v) == "table" then
|
|
for id, item in pairs(v) do
|
|
if item.action then
|
|
item.action = MODELS[hash].options[id].action
|
|
end
|
|
if MODELS[hash].options[id].canInteract then
|
|
item.canInteract = MODELS[hash].options[id].canInteract
|
|
end
|
|
if item.canInteract then
|
|
local canInteract = false
|
|
pcall(function()
|
|
canInteract = item.canInteract(nearby.object, nearby.coords, item.args)
|
|
end)
|
|
v[id] = canInteract and item or nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local distance = #(nearby.coords - playercoords)
|
|
interaction.entity = nearby.object
|
|
|
|
if distance <= interaction.distance then
|
|
amount += 1
|
|
interaction.curDist = distance
|
|
options[amount] = interaction
|
|
end
|
|
end
|
|
end
|
|
|
|
local amountOfInteractions = #filteredInteractions
|
|
if amountOfInteractions > 0 then
|
|
for i = 1, amountOfInteractions do
|
|
local interaction = filteredInteractions[i]
|
|
|
|
local coords = interaction.coords or utils.getCoordsFromInteract(interaction)
|
|
|
|
local distance = #(coords - playercoords)
|
|
|
|
if distance <= interaction.distance then
|
|
amount += 1
|
|
interaction.curDist = distance
|
|
options[amount] = interaction
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, v in pairs(options) do
|
|
for i = 1, #v.options, 1 do
|
|
if not v.options[i] then
|
|
table.remove(v.options, i)
|
|
end
|
|
end
|
|
end
|
|
if amount > 1 then
|
|
table_sort(options, function(a, b)
|
|
return a.curDist < b.curDist
|
|
end)
|
|
end
|
|
|
|
return options
|
|
end
|
|
|
|
function api.disable(state)
|
|
LocalPlayer.state:set('interactionsDisabled', state, true)
|
|
end exports('Disable', api.disable)
|
|
|
|
AddEventHandler('onClientResourceStop', function(resource)
|
|
for i = #interactions, 1, -1 do
|
|
local interaction = interactions[i]
|
|
|
|
if interaction.resource == resource then
|
|
api.removeInteraction(interaction.id)
|
|
end
|
|
end
|
|
end)
|
|
|
|
return api |