forked from Simnation/Main
319 lines
9.6 KiB
Lua
319 lines
9.6 KiB
Lua
local PLAYER_INTERACTIONS = {}
|
|
local INTERACTION_THREAD_RUNNING = false
|
|
local LAST_OUTLINE_ENTITY = nil
|
|
|
|
-- HELPER FUNCTIONS
|
|
local function GetClosestPlayerInteraction(maxDistance)
|
|
return UseCache('GetClosestPlayerInteraction', function()
|
|
local playerPed = PlayerPedId()
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
|
|
local nearest = nil
|
|
local nearestDistance = maxDistance
|
|
|
|
for k, playerInteraction in pairs(PLAYER_INTERACTIONS) do
|
|
if playerInteraction.canInteract() then
|
|
local coords = playerInteraction.GetCoords()
|
|
|
|
local distance = #(playerCoords - coords)
|
|
if distance < nearestDistance then
|
|
nearest = playerInteraction
|
|
nearestDistance = distance
|
|
end
|
|
end
|
|
end
|
|
|
|
return nearest, nearestDistance
|
|
end, 1000)
|
|
end
|
|
|
|
local function CleanUpDeadInteractions()
|
|
for k, interaction in pairs(PLAYER_INTERACTIONS) do
|
|
if interaction.entity and not DoesEntityExist(interaction.entity) then
|
|
interaction.Delete()
|
|
elseif GetResourceState(interaction.GetInvoker()) ~= 'started' then
|
|
interaction.Delete()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function DeadInteractionRemovalThread()
|
|
Citizen.CreateThread(function()
|
|
while true do
|
|
CleanUpDeadInteractions()
|
|
Citizen.Wait(5000)
|
|
end
|
|
end)
|
|
end
|
|
|
|
--- MAIN
|
|
local function TriggerInteractionThread()
|
|
Citizen.CreateThread(function()
|
|
Citizen.Wait(500
|
|
)
|
|
Debug('triggering interaction thread')
|
|
-- Only needs to run once for non-target systems
|
|
if INTERACTION_THREAD_RUNNING or Link.input.target.enabled then
|
|
Debug('aborted', Count(PLAYER_INTERACTIONS))
|
|
return
|
|
end
|
|
|
|
INTERACTION_THREAD_RUNNING = true
|
|
|
|
while Count(PLAYER_INTERACTIONS) > 0 do
|
|
local sleep = 2500
|
|
|
|
local interaction, distance = GetClosestPlayerInteraction(5)
|
|
if interaction then
|
|
sleep = 500
|
|
end
|
|
|
|
if interaction ~= nil and distance < interaction.interactDist then
|
|
sleep = 1
|
|
|
|
if interaction.Handle() and interaction.entity then
|
|
local entity = interaction.entity
|
|
if Link.input.other.outline.enabled and LAST_OUTLINE_ENTITY ~= interaction.entity then
|
|
if LAST_OUTLINE_ENTITY ~= nil then
|
|
SetEntityDrawOutline(LAST_OUTLINE_ENTITY, false)
|
|
end
|
|
|
|
LAST_OUTLINE_ENTITY = entity
|
|
|
|
SetEntityDrawOutline(entity, true)
|
|
SetEntityDrawOutlineShader(1)
|
|
local outlineColor = Link.input.other.outline.color
|
|
SetEntityDrawOutlineColor(outlineColor.r, outlineColor.g, outlineColor.b, outlineColor.a)
|
|
end
|
|
end
|
|
else
|
|
if LAST_OUTLINE_ENTITY ~= nil then
|
|
SetEntityDrawOutline(LAST_OUTLINE_ENTITY, false)
|
|
LAST_OUTLINE_ENTITY = nil
|
|
end
|
|
end
|
|
|
|
Citizen.Wait(sleep)
|
|
end
|
|
INTERACTION_THREAD_RUNNING = false
|
|
|
|
if Link.input.other.outline.enabled then
|
|
SetEntityDrawOutline(LAST_OUTLINE_ENTITY, false)
|
|
LAST_OUTLINE_ENTITY = nil
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function RegisterInteraction(data)
|
|
local self = {
|
|
invoker = GetInvokingResource(),
|
|
|
|
key = GetGameTimer() .. '-' .. math.random(0, 9999999),
|
|
|
|
entity = data.entity,
|
|
entityOffset = data.offset,
|
|
|
|
coords = data.coords,
|
|
rotation = data.rotation or vector3(0, 0, 0),
|
|
scale = data.scale,
|
|
|
|
message = data.message,
|
|
targetMessage = data.targetMessage,
|
|
input = data.input,
|
|
interactDist = data.interactDist or 2,
|
|
|
|
callback = data.callback,
|
|
canInteract = data.canInteract,
|
|
|
|
meta = data.meta,
|
|
|
|
-- Filled later
|
|
targetEntity = nil,
|
|
targetZone = nil,
|
|
|
|
eventHandler = nil,
|
|
}
|
|
|
|
-- Booting / Setup
|
|
self.Boot = function()
|
|
if Link.input.target.enabled then
|
|
self.SetupTargetSystem()
|
|
|
|
return
|
|
end
|
|
|
|
TriggerInteractionThread()
|
|
end
|
|
|
|
self.SetupTargetSystem = function()
|
|
local eventKey = GetCurrentResourceName() .. ':target:' .. self.key
|
|
|
|
RegisterNetEvent(eventKey)
|
|
self.eventHandler = AddEventHandler(eventKey, function()
|
|
self.PerformSafeCallback()
|
|
end)
|
|
|
|
if self.entity then
|
|
self.targetEntity = InputUtils.AddEntityToTargeting(self.entity, self.targetMessage, eventKey, function()
|
|
if not self then return end
|
|
return self.canInteract(self.clientReturnData)
|
|
end, self.meta, self.interactDist, self.icon)
|
|
else
|
|
self.targetZone = InputUtils.AddZoneToTargeting(self.coords, self.rotation, self.scale, self.targetMessage, eventKey, function()
|
|
if not self then return end
|
|
return self.canInteract(self.clientReturnData)
|
|
end, self.meta, self.interactDist, self.icon)
|
|
end
|
|
end
|
|
|
|
-- Displaying and handling of the input options
|
|
self.Handle = function()
|
|
if not self then
|
|
return
|
|
end
|
|
|
|
local cachedCanInteract = UseCache('canInteract' .. self.key, function()
|
|
return self.canInteract(self.clientReturnData)
|
|
end, 500)
|
|
|
|
if not cachedCanInteract then
|
|
return
|
|
end
|
|
|
|
local coords = self.GetCoords()
|
|
|
|
local inputType = Link.input.other.inputType
|
|
if inputType == 'top-left' then
|
|
InputUtils.KeybindTip(self.message)
|
|
elseif inputType == 'help-text' then
|
|
InputUtils.DrawFloatingText(coords, self.message)
|
|
elseif inputType == '3d-text' then
|
|
InputUtils.Draw3DText(coords, self.message, 0.042)
|
|
end
|
|
|
|
if IsControlJustReleased(0, self.input) then
|
|
if self.entity then
|
|
LAST_OUTLINE_ENTITY = nil
|
|
SetEntityDrawOutline(self.entity, false)
|
|
end
|
|
|
|
self.PerformSafeCallback()
|
|
Citizen.Wait(500) -- Interaction debounce
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Perform a safe callback with error logging
|
|
self.PerformSafeCallback = function()
|
|
local success, err = pcall(self.callback, self.clientReturnData)
|
|
if not success then
|
|
Debug(
|
|
('^1Interactable callback from {resource} has failed.'):gsub('{resource}', self.invoker),
|
|
err
|
|
)
|
|
end
|
|
end
|
|
|
|
-- Getters
|
|
self.GetMeta = function()
|
|
return self.meta
|
|
end
|
|
|
|
self.GetInvoker = function()
|
|
return self.invoker
|
|
end
|
|
|
|
self.GetCoords = function()
|
|
return self.coords or GetOffsetFromEntityInWorldCoords(self.entity, self.entityOffset)
|
|
end
|
|
|
|
self.GetEntity = function()
|
|
return self.entity
|
|
end
|
|
|
|
-- Deleting
|
|
|
|
self.Delete = function()
|
|
if self.eventHandler then
|
|
RemoveEventHandler(self.eventHandler)
|
|
end
|
|
|
|
-- Targeting cleanup
|
|
if self.targetEntity then
|
|
InputUtils.RemoveTargetEntity(self.targetEntity)
|
|
elseif self.targetZone then
|
|
InputUtils.RemoveTargetZone(self.targetZone)
|
|
end
|
|
|
|
Debug('Delete interaction', self.key)
|
|
|
|
PLAYER_INTERACTIONS[self.key] = nil
|
|
self = nil
|
|
end
|
|
|
|
--
|
|
|
|
self.Boot()
|
|
|
|
-- Add the interaction to the list
|
|
PLAYER_INTERACTIONS[self.key] = self
|
|
|
|
Debug('Registered new interactable', json.encode(self.meta), self.GetCoords(), DoesEntityExist(self.entity))
|
|
|
|
self.clientReturnData = {
|
|
GetMeta = self.GetMeta,
|
|
GetCoords = self.GetCoords,
|
|
GetEntity = self.GetEntity,
|
|
GetInvoker = self.GetInvoker,
|
|
Delete = self.Delete,
|
|
}
|
|
|
|
return self.clientReturnData
|
|
end
|
|
|
|
function AddInteractionEntity(entity, offset, message, targetMessage, input, callback, canInteract, meta, interactDist, icon)
|
|
return RegisterInteraction({
|
|
entity = entity,
|
|
offset = offset,
|
|
|
|
message = message,
|
|
targetMessage = targetMessage,
|
|
input = input,
|
|
callback = callback,
|
|
canInteract = canInteract,
|
|
interactDist = interactDist,
|
|
icon = icon,
|
|
|
|
meta = meta,
|
|
})
|
|
end
|
|
|
|
function AddInteractionZone(coords, rotation, scale, message, targetMessage, input, callback, canInteract, meta, interactDist, icon)
|
|
return RegisterInteraction({
|
|
coords = coords,
|
|
rotation = rotation,
|
|
scale = scale,
|
|
|
|
message = message,
|
|
targetMessage = targetMessage,
|
|
input = input,
|
|
callback = callback,
|
|
canInteract = canInteract,
|
|
interactDist = interactDist,
|
|
icon = icon,
|
|
|
|
meta = meta,
|
|
})
|
|
end
|
|
|
|
-- CLEANUP
|
|
DeadInteractionRemovalThread()
|
|
|
|
AddEventHandler('onResourceStop', function(stoppedResource)
|
|
for k, interaction in pairs(PLAYER_INTERACTIONS) do
|
|
if interaction.GetInvoker() == stoppedResource then
|
|
interaction.Delete()
|
|
end
|
|
end
|
|
end)
|