diff --git a/README.md b/README.md index b23aa16..f81e937 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,59 @@ -# Telescopes -Telescopes is a free standalone script that allows players to use telescopes all around the map. Currently, it allows the player to use /telescope, press E when close to one or to use either qTarget, qb-target or ox_target as a 3rd eye solution. -I decided to release this to the public since I haven’t seen any other telescope scripts around. The script was made for fun, so the code might not be the greatest, feel free to improve upon it. +# Dependencies +* **oxmysql** +* **ox_lib** +* **ox_target** +* **ox_inventory** +# Inventory Items Setup +Add the following lines into your ox_inventory/data/items.lua -# Optional Dependencies -As previously stated the script allows for qTarget, qb-target pr ox_target to be used. They are disabled by default but can be toggled in the config file. -You can add your own notification exports/events inside the DisplayNotification function in client.lua (at the top). +```lua + ['telescope'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope2'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope3'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope4'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, +``` +# Database Setup +Execute the following SQL query in your database manager (MariaDB is recommended) -# Optimization -The script runs at 0.1ms when idling if you have the help text thread (gets the distance to the telescope and displays some help text if close enough). It idles at 0.0 if you only use the command/3rd eye. -It runs at 0.4 - 0.5ms when using a telescope. - - -# Known Issues -None :D +```sql +CREATE TABLE IF NOT EXISTS `telescopes` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `coords` LONGTEXT NOT NULL, + `telescope` VARCHAR(50) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +); +``` \ No newline at end of file diff --git a/client.lua b/client.lua deleted file mode 100644 index 995fa92..0000000 --- a/client.lua +++ /dev/null @@ -1,488 +0,0 @@ --- Variables -- -local inTelescope = false -local gameplayCamera = {} -local telescopeHeading = 0.0 -local frozen = false - -local camera = 0 -local scaleform = 0 -local instScaleform = 0 - -local fov = Config.Zoom.Max -local relativeOffset = 0.0 -local maxVertical = 20.0 -local maxHorizontal = 55.0 - -local hudComponentsToHide = { - [1] = true, -- Wanted Stars - [2] = true, -- Weapon icon - [3] = true, -- Cash - [4] = true, -- MP CASH - [13] = true, -- Cash Change - [11] = true, -- Floating Help Text - [12] = true, -- More floating help text - [15] = true, -- Subtitle Text - [18] = true, -- Game Stream - [19] = true -- Weapon Wheel -} - --- Functions -- -local function DisplayNotification(msg) - -- Remove the functions below and add your own notifications here - BeginTextCommandThefeedPost("STRING") - AddTextComponentSubstringPlayerName(msg) - EndTextCommandThefeedPostTicker(false, false) -end - -local function DisplayHelpText(msg) - BeginTextCommandDisplayHelp("STRING") - AddTextComponentSubstringPlayerName(msg) - EndTextCommandDisplayHelp(0, false, true, -1) -end - -local function SetupInstructions() - instScaleform = RequestScaleformMovie("instructional_buttons") - while not HasScaleformMovieLoaded(instScaleform) do - Wait(0) - end - - DrawScaleformMovieFullscreen(instScaleform, 255, 255, 255, 0, 0) - - BeginScaleformMovieMethod(instScaleform, "CLEAR_ALL") - EndScaleformMovieMethod() - - BeginScaleformMovieMethod(instScaleform, "SET_CLEAR_SPACE") - ScaleformMovieMethodAddParamInt(200) - EndScaleformMovieMethod() - - BeginScaleformMovieMethod(instScaleform, "SET_DATA_SLOT") - ScaleformMovieMethodAddParamInt(0) - ScaleformMovieMethodAddParamPlayerNameString("~INPUT_PICKUP~") - BeginTextCommandScaleformString("STRING") - AddTextComponentSubstringPlayerName(Config.Localization.Exit) - EndTextCommandScaleformString() - EndScaleformMovieMethod() - - BeginScaleformMovieMethod(instScaleform, "DRAW_INSTRUCTIONAL_BUTTONS") - EndScaleformMovieMethod() - - BeginScaleformMovieMethod(instScaleform, "SET_BACKGROUND_COLOUR") - ScaleformMovieMethodAddParamInt(0) - ScaleformMovieMethodAddParamInt(0) - ScaleformMovieMethodAddParamInt(0) - ScaleformMovieMethodAddParamInt(80) - EndScaleformMovieMethod() -end - -local function LoadAnimDict(dict) - RequestAnimDict(dict) - while not HasAnimDictLoaded(dict) do - Wait(0) - end -end - -local function CreateTelescopeCamera(entity, data) - camera = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) - local coords = GetOffsetFromEntityInWorldCoords(entity, data.cameraOffset.x, data.cameraOffset.y, data.cameraOffset.z) - local rotation = GetEntityRotation(entity, 5).z - if data.headingOffset then - rotation = rotation + data.headingOffset - if rotation > 360.0 then rotation = rotation - 360.0 end - end - - SetCamCoord(camera, coords.x, coords.y, coords.z) - SetCamRot(camera, 0.0, 0.0, rotation, 2) - - SetExtraTimecycleModifier("telescope") - - scaleform = RequestScaleformMovie(data.scaleform) - while not HasScaleformMovieLoaded(scaleform) do - Wait(0) - end - - local xRes, yRes = GetActiveScreenResolution() - BeginScaleformMovieMethod(scaleform, "SET_DISPLAY_CONFIG") - ScaleformMovieMethodAddParamInt(xRes) - ScaleformMovieMethodAddParamInt(yRes) - ScaleformMovieMethodAddParamInt(5) --_safeTopPercent - ScaleformMovieMethodAddParamInt(5) --_safeBottomPercent - ScaleformMovieMethodAddParamInt(5) --_safeLeftPercent - ScaleformMovieMethodAddParamInt(5) --_safeRightPercent - ScaleformMovieMethodAddParamBool(GetIsWidescreen()) - ScaleformMovieMethodAddParamBool(GetIsHidef()) - ScaleformMovieMethodAddParamBool(false) --isAsian - EndScaleformMovieMethod() - - RenderScriptCams(true, false, 0, false, false) -end - -local function HideHudThisFrame() - HideHudAndRadarThisFrame() - for id, _state in pairs(hudComponentsToHide) do - HideHudComponentThisFrame(id) - end -end - -local function IsPedPlayingAnyTelescopeAnim(ped) - for _animType, animations in pairs(Config.Animations) do - for _key, animation in pairs(animations) do - if type(animation) == "string" and IsEntityPlayingAnim(ped, "mini@telescope", animation, 3) then - return true - end - end - end - return false -end - -local function IsTelescopeAvailable(coords) - local playerPed = PlayerPedId() - local pedPool = GetGamePool('CPed') - for _index, ped in pairs(pedPool) do - if #(GetEntityCoords(ped) - coords) < 1.0 and ped ~= playerPed then - if IsPedPlayingAnyTelescopeAnim(ped) then - return false - end - end - end - - return true -end - -local function HandleZoom() - if GetDisabledControlNormal(0, 32) ~= 0.0 or GetDisabledControlNormal(0, 335) ~= 0.0 then -- Zoom in - fov = math.max(fov - Config.Zoom.Speed, Config.Zoom.Min) - end - - if GetDisabledControlNormal(0, 33) ~= 0.0 or GetDisabledControlNormal(0, 336) ~= 0.0 then -- Zoom out - fov = math.min(fov + Config.Zoom.Speed, Config.Zoom.Max) - end - - local current_fov = GetCamFov(camera) - if math.abs(fov-current_fov) < 0.1 then - fov = current_fov - end - - SetCamFov(camera, current_fov + (fov - current_fov)*0.05) -end - -local function HandleMovementInput() - local axisX = GetDisabledControlNormal(0, 220) - local axisY = GetDisabledControlNormal(0, 221) - - if axisX ~= 0.0 or axisY ~= 0.0 then - local zoomValue = (1.0/(Config.Zoom.Max-Config.Zoom.Min))*(fov-Config.Zoom.Min) - local rotation = GetCamRot(camera, 2) - - local movementSpeed = (IsUsingKeyboard(1) and Config.MovementSpeed.Keyboard) or Config.MovementSpeed.Controller - relativeOffset = relativeOffset + axisX*-1.0*(movementSpeed)*(zoomValue+0.1) - if relativeOffset > maxHorizontal then - relativeOffset = maxHorizontal - elseif relativeOffset < maxHorizontal*-1 then - relativeOffset = maxHorizontal*-1 - end - - local newX = math.max(math.min(maxVertical, rotation.x + axisY*-1.0*(movementSpeed)*(zoomValue+0.1)), maxVertical*-1) - local newZ = telescopeHeading + relativeOffset - - SetCamRot(camera, newX, 0.0, newZ, 2) - end -end - -local function GetClosestTelescope() - local objectPool = GetGamePool('CObject') - local telescopes = {} - for _index, entity in pairs(objectPool) do - local model = GetEntityModel(entity) - if Config.Models[model] then - telescopes[entity] = true - end - end - - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) - local closest = 0 - local distance = 1000 - - for entity, _boolean in pairs(telescopes) do - local coords = GetEntityCoords(entity) - local dist = #(playerCoords - coords) - if dist < distance then - closest = entity - distance = dist - end - end - - return closest, distance -end - -local function RequestControlIfNetworked(entity) - if NetworkGetEntityIsNetworked(entity) then - NetworkRequestControlOfEntity(entity) - end -end - -local function FreezeTelescope(entity) - if not IsEntityPositionFrozen(entity) then - RequestControlIfNetworked(entity) - FreezeEntityPosition(entity, true) - frozen = true - end -end - -local function UnfreezeTelescope(entity) - if frozen then - RequestControlIfNetworked(entity) - FreezeEntityPosition(entity, false) - frozen = false - end -end - -local function GetEntityTilt(entity) - local rot = GetEntityRotation(entity) - local xRot = rot.x - local yRot = rot.y - - if xRot < 0.0 then xRot = xRot*-1 end - if yRot < 0.0 then yRot = yRot*-1 end - - return xRot + yRot -end - -local function UseTelescope(entity) - if GetEntityTilt(entity) > Config.MaxTilt then - DisplayNotification(Config.Localization.TelescopeTooTilted) - return - end - - local data = Config.Models[GetEntityModel(entity)] - local offsetCoords = GetOffsetFromEntityInWorldCoords(entity, data.offset.x, data.offset.y, data.offset.z) - if not IsTelescopeAvailable(offsetCoords) then - DisplayNotification(Config.Localization.TelescopeInUse) - return - end - - inTelescope = true - - local heading = GetEntityHeading(entity) - if data.headingOffset then - heading = heading + data.headingOffset - if heading > 360.0 then heading = heading - 360.0 end - end - - local playerPed = PlayerPedId() - TaskGoStraightToCoord(playerPed, offsetCoords.x, offsetCoords.y, offsetCoords.z, 1, 8000, heading, 0.05) - - while true do - Wait(250) - local taskStatus = GetScriptTaskStatus(playerPed, "SCRIPT_TASK_GO_STRAIGHT_TO_COORD") - if taskStatus == 0 or taskStatus == 7 then - break - end - end - - ClearPedTasks(playerPed) - local difference = math.abs(heading - GetEntityHeading(playerPed)) - if difference > 10.0 then - SetEntityHeading(playerPed, heading) - end - - local dist = #(GetEntityCoords(playerPed)-offsetCoords) - if dist > 0.425 and dist < 2.0 then - SetEntityCoords(playerPed, offsetCoords.x, offsetCoords.y, offsetCoords.z-1.0) - elseif dist > 2.0 then - DisplayNotification(Config.Localization.ToFarAway) - ClearPedTasks(playerPed) - inTelescope = true - return - end - - FreezeTelescope(entity) - LoadAnimDict("mini@telescope") - - local animation = Config.Animations[data.animation] - TaskPlayAnim(playerPed, "mini@telescope", animation.enter, 2.0, 2.0, -1, 2, 0, false, false, false) - - gameplayCamera.heading = GetGameplayCamRelativeHeading() - gameplayCamera.pitch = GetGameplayCamRelativePitch() - - Wait(animation.enterTime) - DoScreenFadeOut(500) - Wait(600) - - TaskPlayAnim(playerPed, "mini@telescope", animation.idle, 2.0, 2.0, -1, 1, 0, false, false, false) - CreateTelescopeCamera(entity, data) - SetupInstructions() - - CreateThread(function() - DoScreenFadeIn(500) - end) - - local tick = 0 - local doAnim = true - - fov = Config.Zoom.Max - maxVertical = data.MaxVertical - maxHorizontal = data.MaxHorizontal - telescopeHeading = heading - relativeOffset = 0.0 - - while true do - -- Handle the movement and button inputs every frame - HandleZoom() - HandleMovementInput() - - if IsControlJustPressed(0, 38) then - break - end - - -- Only handle "less important" stuff every 100 frames - if tick >= 100 then - if #(GetEntityCoords(playerPed)-offsetCoords) > 1.5 or IsEntityDead(playerPed) then - doAnim = false - break - end - tick = 0 - end - - -- Draw the scaleform - DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0) - - -- Draw instructions - DrawScaleformMovieFullscreen(instScaleform, 255, 255, 255, 255, 0) - - -- Hide hud - HideHudThisFrame() - - tick = tick + 1 - Wait(0) - end - - DoScreenFadeOut(500) - while not IsScreenFadedOut() do - DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0) - Wait(0) - end - Wait(150) - - RenderScriptCams(false, false, 0, false, false) - DestroyCam(camera, false) - - ClearExtraTimecycleModifier() - SetScaleformMovieAsNoLongerNeeded(scaleform) - SetScaleformMovieAsNoLongerNeeded(instScaleform) - - SetGameplayCamRelativeHeading(gameplayCamera.heading) - SetGameplayCamRelativePitch(gameplayCamera.pitch, 1.0) - - DoScreenFadeIn(500) - Wait(500) - - if doAnim then - TaskPlayAnim(playerPed, "mini@telescope", animation.exit, 2.0, 1.0, -1, 0, 0, false, false, false) - Wait(1500) - else - ClearPedTasks(playerPed) - end - - inTelescope = false - UnfreezeTelescope(entity) - RemoveAnimDict("mini@telescope") -end - - --- Targeting -- -if Config.Target then - local models = {} - for model, _data in pairs(Config.Models) do - models[#models+1] = model - end - - if Config.Target == "ox_target" then - exports.ox_target:addModel(models, { - { - icon = Config.Targeting.Icon, - label = Config.Targeting.Label, - distance = Config.MaxInteractionDist, - onSelect = function(data) - UseTelescope(data.entity) - end - } - }) - else - exports[Config.Target]:AddTargetModel(models, { - options = { - { - icon = Config.Targeting.Icon, - label = Config.Targeting.Label, - action = function(entity) - UseTelescope(entity) - end - } - }, - distance = Config.MaxInteractionDist - }) - end -end - - --- Help Text Thread -- -if Config.UseDistanceThread then - local telescopes = {} - - CreateThread(function() - while true do - local objectPool = GetGamePool('CObject') - for _index, entity in pairs(objectPool) do - local model = GetEntityModel(entity) - if Config.Models[model] then - telescopes[entity] = true - end - end - - Wait(1000) - end - end) - - CreateThread(function() - while true do - if not inTelescope then - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) - local closest = 0 - local distance = 250 - - for entity, _boolean in pairs(telescopes) do - local coords = GetEntityCoords(entity) - local dist = #(playerCoords - coords) - if dist < distance then - closest = entity - distance = dist - end - end - - if closest ~= 0 and distance < Config.MaxInteractionDist then - DisplayHelpText(Config.Localization.HelpText) - if IsControlJustPressed(0, 38) then - UseTelescope(closest) - end - Wait(0) - else - Wait(distance*20) - end - else - Wait(500) - end - end - end) -end - - --- Commands -- -RegisterCommand("telescope", function(source, args, rawCommand) - local telescope, distance = GetClosestTelescope() - if telescope ~= 0 and distance < Config.MaxInteractionDist then - UseTelescope(telescope) - else - DisplayNotification(Config.Localization.NonFound) - end -end, false) diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 0000000..1b884bb --- /dev/null +++ b/client/main.lua @@ -0,0 +1,663 @@ +local config = require 'config.client' + +-- Variables -- +local inTelescope = false +local isPlacingObject = false +local gameplayCamera = {} +local telescopeHeading = 0.0 +local frozen = false + +local camera = 0 +local scaleform = 0 + +local fov = config.zoom.max +local relativeOffset = 0.0 +local maxVertical = 20.0 +local maxHorizontal = 55.0 + +local telescopes = {} +local prop = nil + +local hudComponentsToHide = { + [1] = true, -- Wanted Stars + [2] = true, -- Weapon icon + [3] = true, -- Cash + [4] = true, -- MP CASH + [13] = true, -- Cash Change + [11] = true, -- Floating Help Text + [12] = true, -- More floating help text + [15] = true, -- Subtitle Text + [18] = true, -- Game Stream + [19] = true -- Weapon Wheel +} + +-- Functions -- +local function rotationToDirection(rotation) + local adjustedRotation = { x = (math.pi / 180) * rotation.x, y = (math.pi / 180) * rotation.y, z = (math.pi / 180) * rotation.z } + local direction = { x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), z = math.sin(adjustedRotation.x) } + return direction +end + +local function rayCastGamePlayCamera(distance) + local cameraRotation = GetGameplayCamRot() + local cameraCoord = GetGameplayCamCoord() + local direction = rotationToDirection(cameraRotation) + local destination = { x = cameraCoord.x + direction.x * distance, y = cameraCoord.y + direction.y * distance, z = cameraCoord.z + direction.z * distance } + local a, b, c, d, e = GetShapeTestResult(StartShapeTestRay(cameraCoord.x, cameraCoord.y, cameraCoord.z, destination.x, destination.y, destination.z, -1, cache.ped, 0)) + return destination +end + +local function Notification(data) + lib.notify({ + title = locale('notification.title'), + description = data.description, + type = data.type, + duration = data.duration + }) +end + +local function ShowText(data) + lib.showTextUI(data.text, { + position = data.position, + icon = data.icon or 'fa-solid fa-arrow-right-from-bracket' + }) +end + +local function HideText() + lib.hideTextUI() +end + +local function SetupInstructions() + ShowText({text = locale('interaction.prompt_exit'), position = 'right-center'}) +end + +local function CreateTelescopeCamera(entity, data) + camera = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) + local coords = GetOffsetFromEntityInWorldCoords(entity, data.cameraOffset.x, data.cameraOffset.y, data.cameraOffset.z) + local rotation = GetEntityRotation(entity, 5).z + if data.headingOffset then + rotation = rotation + data.headingOffset + if rotation > 360.0 then rotation = rotation - 360.0 end + end + + SetCamCoord(camera, coords.x, coords.y, coords.z) + SetCamRot(camera, 0.0, 0.0, rotation, 2) + + SetExtraTimecycleModifier("telescope") + + scaleform = RequestScaleformMovie(data.scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Wait(0) + end + + local xRes, yRes = GetActiveScreenResolution() + BeginScaleformMovieMethod(scaleform, "SET_DISPLAY_CONFIG") + ScaleformMovieMethodAddParamInt(xRes) + ScaleformMovieMethodAddParamInt(yRes) + ScaleformMovieMethodAddParamInt(5) --_safeTopPercent + ScaleformMovieMethodAddParamInt(5) --_safeBottomPercent + ScaleformMovieMethodAddParamInt(5) --_safeLeftPercent + ScaleformMovieMethodAddParamInt(5) --_safeRightPercent + ScaleformMovieMethodAddParamBool(GetIsWidescreen()) + ScaleformMovieMethodAddParamBool(GetIsHidef()) + ScaleformMovieMethodAddParamBool(false) --isAsian + EndScaleformMovieMethod() + + RenderScriptCams(true, false, 0, false, false) +end + +local function HideHudThisFrame() + HideHudAndRadarThisFrame() + for id, _state in pairs(hudComponentsToHide) do + HideHudComponentThisFrame(id) + end +end + +local function IsPedPlayingAnyTelescopeAnim(ped) + for _animType, animations in pairs(config.animations) do + for _key, animation in pairs(animations) do + if type(animation) == "string" and IsEntityPlayingAnim(ped, "mini@telescope", animation, 3) then + return true + end + end + end + return false +end + +local function IsTelescopeAvailable(coords) + local pedPool = GetGamePool('CPed') + for _index, ped in pairs(pedPool) do + if #(GetEntityCoords(ped) - coords) < 1.0 and ped ~= cache.ped then + if IsPedPlayingAnyTelescopeAnim(ped) then + return false + end + end + end + + return true +end + +local function HandleZoom() + if GetDisabledControlNormal(0, 32) ~= 0.0 or GetDisabledControlNormal(0, 335) ~= 0.0 then -- Zoom in + fov = math.max(fov - config.zoom.speed, config.zoom.min) + end + + if GetDisabledControlNormal(0, 33) ~= 0.0 or GetDisabledControlNormal(0, 336) ~= 0.0 then -- Zoom out + fov = math.min(fov + config.zoom.speed, config.zoom.max) + end + + local current_fov = GetCamFov(camera) + if math.abs(fov-current_fov) < 0.1 then + fov = current_fov + end + + SetCamFov(camera, current_fov + (fov - current_fov)*0.05) +end + +local function HandleMovementInput() + local axisX = GetDisabledControlNormal(0, 220) + local axisY = GetDisabledControlNormal(0, 221) + + if axisX ~= 0.0 or axisY ~= 0.0 then + local zoomValue = (1.0/(config.zoom.max-config.zoom.min))*(fov-config.zoom.min) + local rotation = GetCamRot(camera, 2) + + local movementSpeed = (IsUsingKeyboard(1) and config.movementSpeed.keyboard) or config.movementSpeed.controller + relativeOffset = relativeOffset + axisX*-1.0*(movementSpeed)*(zoomValue+0.1) + if relativeOffset > maxHorizontal then + relativeOffset = maxHorizontal + elseif relativeOffset < maxHorizontal*-1 then + relativeOffset = maxHorizontal*-1 + end + + local newX = math.max(math.min(maxVertical, rotation.x + axisY*-1.0*(movementSpeed)*(zoomValue+0.1)), maxVertical*-1) + local newZ = telescopeHeading + relativeOffset + + SetCamRot(camera, newX, 0.0, newZ, 2) + end +end + +local function GetClosestTelescope() + local objectPool = GetGamePool('CObject') + local telescopes = {} + for _index, entity in pairs(objectPool) do + local model = GetEntityModel(entity) + if config.models[model] then + telescopes[entity] = true + end + end + + local playerCoords = GetEntityCoords(cache.ped) + local closest = 0 + local distance = 1000 + + for entity, _boolean in pairs(telescopes) do + local coords = GetEntityCoords(entity) + local dist = #(playerCoords - coords) + if dist < distance then + closest = entity + distance = dist + end + end + + return closest, distance +end + +local function RequestControlIfNetworked(entity) + if NetworkGetEntityIsNetworked(entity) then + NetworkRequestControlOfEntity(entity) + end +end + +local function FreezeTelescope(entity) + if not IsEntityPositionFrozen(entity) then + RequestControlIfNetworked(entity) + FreezeEntityPosition(entity, true) + frozen = true + end +end + +local function UnfreezeTelescope(entity) + if frozen then + RequestControlIfNetworked(entity) + FreezeEntityPosition(entity, false) + frozen = false + end +end + +local function GetEntityTilt(entity) + local rot = GetEntityRotation(entity) + local xRot = rot.x + local yRot = rot.y + + if xRot < 0.0 then xRot = xRot*-1 end + if yRot < 0.0 then yRot = yRot*-1 end + + return xRot + yRot +end + +local function InteractTelescope(entity) + if GetEntityTilt(entity) > config.maxTilt then + Notification({description = locale('notification.too_tilted'), duration = 7500, type = 'error'}) + return + end + + local data = config.models[GetEntityModel(entity)] + local offsetCoords = GetOffsetFromEntityInWorldCoords(entity, data.offset.x, data.offset.y, data.offset.z) + if not IsTelescopeAvailable(offsetCoords) then + Notification({description = locale('notification.in_use'), duration = 7500, type = 'error'}) + return + end + + inTelescope = true + + local heading = GetEntityHeading(entity) + if data.headingOffset then + heading = heading + data.headingOffset + if heading > 360.0 then heading = heading - 360.0 end + end + + TaskGoStraightToCoord(cache.ped, offsetCoords.x, offsetCoords.y, offsetCoords.z, 1, 8000, heading, 0.05) + + while true do + Wait(250) + local taskStatus = GetScriptTaskStatus(cache.ped, "SCRIPT_TASK_GO_STRAIGHT_TO_COORD") + if taskStatus == 0 or taskStatus == 7 then + break + end + end + + ClearPedTasks(cache.ped) + local difference = math.abs(heading - GetEntityHeading(cache.ped)) + if difference > 10.0 then + SetEntityHeading(cache.ped, heading) + end + + local dist = #(GetEntityCoords(cache.ped)-offsetCoords) + if dist > 0.425 and dist < 2.0 then + SetEntityCoords(cache.ped, offsetCoords.x, offsetCoords.y, offsetCoords.z-1.0) + elseif dist > 2.0 then + Notification({description = locale('notification.too_far'), duration = 7500, type = 'error'}) + ClearPedTasks(cache.ped) + inTelescope = true + return + end + + FreezeTelescope(entity) + + local animation = config.animations[data.animation] + + if not animation then + print('[telescope] animation missing in config') + return + end + + lib.playAnim(cache.ped, "mini@telescope", animation.enter, 2.0, 2.0, -1, 2, 0, false, false, false) + + gameplayCamera.heading = GetGameplayCamRelativeHeading() + gameplayCamera.pitch = GetGameplayCamRelativePitch() + + Wait(animation.enterTime) + DoScreenFadeOut(500) + Wait(600) + + lib.playAnim(cache.ped, "mini@telescope", animation.idle, 2.0, 2.0, -1, 1, 0, false, false, false) + CreateTelescopeCamera(entity, data) + SetupInstructions() + + CreateThread(function() + DoScreenFadeIn(500) + end) + + local tick = 0 + local doAnim = true + + fov = config.zoom.max + maxVertical = data.MaxVertical + maxHorizontal = data.MaxHorizontal + telescopeHeading = heading + relativeOffset = 0.0 + + while true do + -- Handle the movement and button inputs every frame + HandleZoom() + HandleMovementInput() + + if IsControlJustPressed(0, 38) then + break + end + + -- Only handle "less important" stuff every 100 frames + if tick >= 100 then + if #(GetEntityCoords(cache.ped)-offsetCoords) > 1.5 or IsEntityDead(cache.ped) then + doAnim = false + break + end + tick = 0 + end + + -- Draw the scaleform + DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0) + + -- Hide hud + HideHudThisFrame() + + tick = tick + 1 + Wait(0) + end + + DoScreenFadeOut(500) + while not IsScreenFadedOut() do + DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0) + Wait(0) + end + Wait(150) + + RenderScriptCams(false, false, 0, false, false) + DestroyCam(camera, false) + + ClearExtraTimecycleModifier() + SetScaleformMovieAsNoLongerNeeded(scaleform) + + SetGameplayCamRelativeHeading(gameplayCamera.heading) + SetGameplayCamRelativePitch(gameplayCamera.pitch, 1.0) + + DoScreenFadeIn(500) + Wait(500) + + if doAnim then + lib.playAnim(cache.ped, "mini@telescope", animation.exit, 2.0, 1.0, -1, 0, 0, false, false, false) + Wait(1500) + else + ClearPedTasks(cache.ped) + HideText() + end + + if not config.target == false then + HideText() + end + + inTelescope = false + UnfreezeTelescope(entity) +end + + +-- Targeting -- +if config.target then + local models = {} + for model, _data in pairs(config.models) do + models[#models+1] = model + end + + if config.target == "ox_target" then + exports.ox_target:addModel(models, { + { + icon = config.targeting.icon, + label = locale('interaction.target_use'), + distance = config.maxInteractionDist, + onSelect = function(data) + if not isPlacingObject then + InteractTelescope(data.entity) + end + end + } + }) + else + exports[config.target]:AddTargetModel(models, { + options = { + { + icon = config.targeting.icon, + label = locale('interaction.target_use'), + action = function(entity) + if not isPlacingObject then + InteractTelescope(entity) + end + end + } + }, + distance = config.maxInteractionDist + }) + end +end + + +-- Help Text Thread -- +if config.useDistanceThread then + local telescopes = {} + + CreateThread(function() + while true do + local objectPool = GetGamePool('CObject') + for _index, entity in pairs(objectPool) do + local model = GetEntityModel(entity) + if config.models[model] then + telescopes[entity] = true + end + end + + Wait(1000) + end + end) + + CreateThread(function() + while true do + if not inTelescope and not isPlacingObject then + local playerCoords = GetEntityCoords(cache.ped) + local closest = 0 + local distance = 250 + + for entity, _boolean in pairs(telescopes) do + local coords = GetEntityCoords(entity) + local dist = #(playerCoords - coords) + if dist < distance then + closest = entity + distance = dist + end + end + + if closest ~= 0 and distance < config.maxInteractionDist then + if config.marker.enabled then + local coords = GetEntityCoords(closest) + local model = GetEntityModel(closest) + DrawMarker(config.marker.type, coords.x, coords.y, coords.z + config.models[model].markerHeight, 0.0, 0.0, 0.0, config.marker.rotX, config.marker.rotY, config.marker.rotZ, config.marker.scale.x, config.marker.scale.y, config.marker.scale.z, config.marker.color.r, config.marker.color.g, config.marker.color.b, config.marker.color.a, config.marker.bobUpAndDown, config.marker.faceCamera, config.marker.rotationOrder, config.marker.rotate, config.marker.textureDict, config.marker.textureName, config.marker.drawOnEnts) + end + if config.target == false then + ShowText({text = locale('interaction.prompt_use'), position = 'right-center', icon = config.targeting.icon}) + end + if IsControlJustPressed(0, 38) then + InteractTelescope(closest) + end + Wait(0) + else + HideText() + Wait(distance*5) + end + else + Wait(500) + end + end + end) +end + +local function UseTelescope(data, item) + isPlacingObject = true + ShowText({text = locale('placement.controls'), position = 'right-center'}) + lib.callback.await('telescopes:server:itemActions', false, item.name, 'remove') + local prop = CreateObject(GetHashKey(config.useItem[item.name]), 0, 0, 0, false, false, false) + local heading = GetEntityHeading(prop) + SetEntityAlpha(prop, 150, false) + SetEntityCollision(prop, false, false) + + CreateThread(function() + while isPlacingObject do + Wait(0) + local coords = rayCastGamePlayCamera(4.0) + SetEntityCoords(prop, coords.x, coords.y, coords.z, heading, false, false, false) + PlaceObjectOnGroundProperly(prop) + + DisableControlAction(0, 24, true) + DisableControlAction(0, 25, true) + + if IsControlPressed(0, 15) then + heading += 1.0 + SetEntityHeading(prop, heading) + elseif IsControlPressed(0, 14) then + heading -= 1.0 + SetEntityHeading(prop, heading) + end + + if IsControlPressed(0, 176) then + if lib.progressCircle({ + duration = 1000, + position = 'bottom', + label = locale('placement.progress_label'), + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + }, + anim = { + dict = 'pickup_object', + clip = 'pickup_low', + flag = 16, + }, + prop = { + model = config.useItem[item.name], + bone = 28422, + pos = vec3(0.05, 0.05, 0.0), + rot = vec3(0.0, 0.0, 180.0) + }, + }) then + DeleteObject(prop) + DeleteEntity(prop) + HideText() + lib.callback.await('telescopes:server:place', false, item.name, coords, heading) + break + else + DeleteObject(prop) + DeleteEntity(prop) + HideText() + lib.callback.await('telescopes:server:itemActions', false, item.name, 'add') + break + end + elseif IsControlPressed(0, 177) then + DeleteObject(prop) + DeleteEntity(prop) + HideText() + lib.callback.await('telescopes:server:itemActions', false, item.name, 'add') + break + end + end + isPlacingObject = false + end) +end + +exports('UseTelescope', UseTelescope) + +local function deleteTelescope(id, item) + if lib.progressCircle({ + duration = 1500, + position = 'bottom', + label = locale('placement.collect_label'), + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + }, + anim = { + dict = 'pickup_object', + clip = 'pickup_low', + flag = 16, + }, + }) then + lib.callback.await('telescopes:server:delete', false, id) + lib.callback.await('telescopes:server:itemActions', false, item, 'add') + else + Notification({description = locale('notification.cancelled'), duration = 7500, type = 'error'}) + end +end + +local function spawnAllTelescopes() + local svTelescopes = lib.callback.await('telescopes:server:getTelescopes') + for k, v in pairs(svTelescopes) do + if telescopes[v.id] then goto continue end + + local prop = CreateObject(GetHashKey(config.useItem[v.telescope]), v.coords.x, v.coords.y, v.coords.z, false, false, false) + SetEntityHeading(prop, v.coords.w) + PlaceObjectOnGroundProperly(prop) + Wait(200) + FreezeEntityPosition(prop, true) + + telescopes[k] = v + telescopes[k].prop = prop + + exports.ox_target:addLocalEntity(prop, { + { + label = locale('interaction.target_collect'), + icon = 'fas fa-hand-paper', + onSelect = function() + if not inTelescope and not isPlacingObject then + deleteTelescope(k, v.telescope) + end + end + } + }) + + :: continue :: + end +end + +local function despawnAllTelescopes() + for _, v in pairs(telescopes) do + DeleteObject(v.prop) + DeleteEntity(v.prop) + end + telescopes = {} +end + +lib.callback.register('telescopes:client:updateTelescopes', function() + spawnAllTelescopes() +end) + +lib.callback.register('telescopes:client:delete', function(id) + if telescopes[id] then + DeleteObject(telescopes[id].prop) + DeleteEntity(telescopes[id].prop) + telescopes[id] = nil + end +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + Wait(500) + spawnAllTelescopes() +end) + +RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() + despawnAllTelescopes() +end) + +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + Wait(500) + spawnAllTelescopes() +end) + +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + despawnAllTelescopes() +end) + +-- Commands -- +RegisterNetEvent('telescopes:client:InteractTelescope', function() + if isPlacingObject then return end + local telescope, distance = GetClosestTelescope() + if telescope ~= 0 and distance < config.maxInteractionDist then + InteractTelescope(telescope) + else + Notification({description = locale('notification.not_found'), duration = 7500, type = 'error'}) + end +end) \ No newline at end of file diff --git a/config.lua b/config.lua deleted file mode 100644 index 5972916..0000000 --- a/config.lua +++ /dev/null @@ -1,69 +0,0 @@ -Config = {} - --- The targeting solution (3rd eye) to use. --- false = don't use any targeting solution. --- 'qb-target' = qb-target by BerkieBb and its many other contributors. (https://github.com/BerkieBb/qb-target) --- 'qtarget' = qTarget by Linden (thelindat), Noms (OfficialNoms) and its many other contributors. (https://github.com/overextended/qtarget) --- 'ox_target' = ox_target by Linden (thelindat) and its many other contributors. (https://github.com/overextended/ox_target) -Config.Target = false - --- Targeting -Config.Targeting = { - Icon = "fas fa-binoculars", - Label = "Use Telescope" -} - --- Localization -Config.Localization = { - HelpText = "Press ~INPUT_PICKUP~ to look through the telescope", - NonFound = "No telescope was found!", - TelescopeInUse = "Someone else is already using the telescope!", - TelescopeTooTilted = "This telescope is too tilted to be used!", - ToFarAway = "You went to far away!", - Exit = "Exit Telescope" -} - --- Other -Config.UseDistanceThread = true -Config.MaxInteractionDist = 1.75 - -Config.MaxTilt = 20.0 - -Config.MovementSpeed = { - Keyboard = 2.75, - Controller = 1.0 -} - -Config.Zoom = { - Max = 50.0, - Min = 5.0, - Speed = 5.0 -} - -Config.Animations = { - ["default"] = { - enter = "enter_front", - enterTime = 1500, - exit = "exit_front", - idle = "idle" - }, - ["public"] = { - enter = "public_enter_front", - enterTime = 1500, - exit = "public_exit_front", - idle = "public_idle" - }, - ["upright"] = { - enter = "upright_enter_front", - enterTime = 2500, - exit = "upright_exit_front", - idle = "upright_idle" - } -} - -Config.Models = { - [`prop_telescope_01`] = { MaxHorizontal = 55.0, MaxVertical = 20.0, offset = vector3(-0.03, 0.96, 0.0), headingOffset = 180.0, animation = "public", cameraOffset = vector3(0.0, -0.5, 0.7), scaleform = "OBSERVATORY_SCOPE" }, -- Public - [`prop_telescope`] = { MaxHorizontal = 55.0, MaxVertical = 20.0, offset = vector3(0.02, -0.78, 1.0), animation = "upright", cameraOffset = vector3(0.0, 0.2, 1.7), scaleform = "BINOCULARS" }, -- Mount Chilliad - [`prop_t_telescope_01b`] = { MaxHorizontal = 55.0, MaxVertical = 35.0, offset = vector3(1.14, 0.0, 0.0), headingOffset = 90.0, animation = "default", cameraOffset = vector3(-0.25, 0.0, 1.3), scaleform = "OBSERVATORY_SCOPE" }, -- Domestic - [`xs_prop_arena_telescope_01`] = { MaxHorizontal = 55.0, MaxVertical = 20.0, offset = vector3(-0.03, 0.96, 0.0), headingOffset = 180.0, animation = "public", cameraOffset = vector3(0.0, -0.5, 0.7), scaleform = "BINOCULARS" }, -- Arena -} diff --git a/config/client.lua b/config/client.lua new file mode 100644 index 0000000..69d2bab --- /dev/null +++ b/config/client.lua @@ -0,0 +1,143 @@ +return { + --[[ + Interaction system used to trigger telescope usage. + + Options: + - 'ox_target' : ox_target interaction system + - 'qTarget' : qTarget system + - 'qb-target' : qb-target system + - false : disables target system and uses distance-based detection + + NOTE: If set to false, you MUST enable useDistanceThread. + ]] + target = 'ox_target', + + -- Marker: https://docs.fivem.net/docs/game-references/markers/ + -- Color: https://docs.fivem.net/docs/game-references/hud-colors/ + marker = { + enabled = true, -- Enable or disable the marker above the telescope + type = 21, -- Marker type + scale = {x = 0.1, y = 0.1, z = 0.1}, -- Scale of the marker (X, Y, Z) + color = {r = 0, g = 255, b = 80, a = 200}, -- RGBA color + bobUpAndDown = true, -- Native GTA bounce animation + faceCamera = true, -- Marker always faces the player camera + rotationOrder = 2, -- Rotation order + rotX = 0.0, -- X Rotation + rotY = 180.0, -- Y Rotation + rotZ = 0.0, -- Z Rotation + rotate = false, -- Enable continuous rotation + textureDict = nil, -- Custom texture dictionary + textureName = nil, -- Custom texture name + drawOnEnts = false -- Whether the marker should draw on intersecting entities + }, + + targeting = { + icon = 'fas fa-binoculars', -- Icon displayed in target / interaction UI + }, + + useItem = { + --[[ Public street telescope ]] + ["telescope"] = 'prop_telescope_01', + --[[ Mount Chiliad telescope ]] + ["telescope2"] = 'prop_telescope', + --[[ Domestic/private telescope ]] + ["telescope3"] = 'prop_t_telescope_01b', + --[[ Arena telescope ]] + ["telescope4"] = 'xs_prop_arena_telescope_01', + }, + + -- Enables proximity-based interaction detection (required when target = false) + useDistanceThread = false, + + -- Maximum distance (in meters) allowed to interact with a telescope + maxInteractionDist = 1.75, + + -- Maximum tilt angle allowed before interaction is blocked (prevents unrealistic usage) + maxTilt = 20.0, + + movementSpeed = { + -- Camera rotation sensitivity when using keyboard/mouse + keyboard = 2.75, + + -- Camera rotation sensitivity when using a controller + controller = 1.0 + }, + + zoom = { + max = 50.0, -- Maximum FOV (fully zoomed out / wide view) + min = 5.0, -- Minimum FOV (fully zoomed in / narrow view) + speed = 5.0 -- Speed at which zoom transitions change + }, + + animations = { + default = { + enter = "enter_front", -- Played when starting telescope interaction + enterTime = 1500, -- Time before switching to idle animation + exit = "exit_front", -- Played when exiting telescope + idle = "idle" -- Loop animation while using telescope + }, + + public = { + enter = "public_enter_front", + enterTime = 1500, + exit = "public_exit_front", + idle = "public_idle" + }, + + upright = { + enter = "upright_enter_front", + enterTime = 2500, + exit = "upright_exit_front", + idle = "upright_idle" + } + }, + + models = { + --[[ Public street telescope ]] + [`prop_telescope_01`] = { + MaxHorizontal = 55.0, -- Max horizontal camera rotation + MaxVertical = 20.0, -- Max vertical camera rotation + offset = vector3(-0.03, 0.96, 0.0), -- Player position offset relative to object + headingOffset = 180.0, -- Adjusts telescope orientation alignment + animation = "public", -- Animation set used for this model + cameraOffset = vector3(0.0, -0.5, 0.7), -- Camera position relative to telescope + scaleform = "OBSERVATORY_SCOPE", -- UI overlay style + markerHeight = 1.0 -- Vertical offset above the telescope (adjust according to prop size) + }, + + --[[ Mount Chiliad telescope ]] + [`prop_telescope`] = { + MaxHorizontal = 55.0, + MaxVertical = 20.0, + offset = vector3(0.02, -0.78, 1.0), + animation = "upright", + cameraOffset = vector3(0.0, 0.2, 1.7), + scaleform = "BINOCULARS", + markerHeight = 2.0 + }, + + --[[ Domestic/private telescope ]] + [`prop_t_telescope_01b`] = { + MaxHorizontal = 55.0, + MaxVertical = 35.0, + offset = vector3(1.14, 0.0, 0.0), + headingOffset = 90.0, + animation = "default", + cameraOffset = vector3(-0.25, 0.0, 1.3), + scaleform = "OBSERVATORY_SCOPE", + markerHeight = 1.2 + }, + + --[[ Arena telescope ]] + [`xs_prop_arena_telescope_01`] = { + MaxHorizontal = 55.0, + MaxVertical = 20.0, + offset = vector3(-0.03, 0.96, 0.0), + headingOffset = 180.0, + animation = "public", + cameraOffset = vector3(0.0, -0.5, 0.7), + scaleform = "BINOCULARS", + markerHeight = 1.0 + } + } +} \ No newline at end of file diff --git a/config/server.lua b/config/server.lua new file mode 100644 index 0000000..c710caf --- /dev/null +++ b/config/server.lua @@ -0,0 +1,7 @@ +return { + command = { + enabled = true, + name = 'telescope', + restricted = 'group.admin', + } +} \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 945ffaf..e33885c 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -3,9 +3,32 @@ game 'gta5' author 'Mads' description 'Telescopes' -version '1.4.1' +version '1.4.2' -client_scripts { - 'config.lua', - 'client.lua' +ox_lib 'locale' + +shared_scripts { + '@ox_lib/init.lua', +} + +client_script { + 'client/main.lua', +} + +server_script { + '@oxmysql/lib/MySQL.lua', + 'server/main.lua', } + +files { + 'locales/*.json', + 'config/client.lua', + 'config/server.lua', +} + +dependencies { + 'ox_lib' +} + +lua54 'yes' +use_experimental_fxv2_oal 'yes' \ No newline at end of file diff --git a/install/add_items.md b/install/add_items.md new file mode 100644 index 0000000..c8d7227 --- /dev/null +++ b/install/add_items.md @@ -0,0 +1,38 @@ +```lua + ['telescope'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope2'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope3'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, + ['telescope4'] = { + label = 'Telescope', + weight = 1500, + stack = false, + close = true, + client = { + export = 'telescopes.UseTelescope' + } + }, +``` \ No newline at end of file diff --git a/install/telescopes.sql b/install/telescopes.sql new file mode 100644 index 0000000..b7326d4 --- /dev/null +++ b/install/telescopes.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `telescopes` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `coords` LONGTEXT NOT NULL, + `telescope` VARCHAR(50) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +); \ No newline at end of file diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..91f8522 --- /dev/null +++ b/locales/en.json @@ -0,0 +1,25 @@ +{ + "command": { + "telescope_help": "Use the nearest telescope", + "admin_not_optin": "You are not opted in for admin duty. Use /optin to toggle." + }, + "notification": { + "title": "Telescope", + "cancelled": "Action canceled.", + "not_found": "No telescope was found nearby.", + "too_tilted": "This telescope is too tilted to be used properly.", + "too_far": "You moved too far away from the telescope.", + "in_use": "Someone else is already using this telescope." + }, + "interaction": { + "target_use": "Use Telescope", + "target_collect": "Collect Telescope", + "prompt_use": "Press E to look through the telescope", + "prompt_exit": "Press E to exit the view" + }, + "placement": { + "progress_label": "Placing telescope...", + "collect_label": "Picking up telescope...", + "controls": "[SCROLL] - Rotation \n[ENTER] - Place \n[BACKSPACE] - Cancel" + } +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json new file mode 100644 index 0000000..405185e --- /dev/null +++ b/locales/es.json @@ -0,0 +1,25 @@ +{ + "command": { + "telescope_help": "Usar el telescopio más cercano.", + "admin_not_optin": "No estás en servicio de administración. Usa /optin para activarlo." + }, + "notification": { + "title": "Telescopio", + "cancelled": "Acción cancelada.", + "not_found": "No se encontró ningún telescopio cerca.", + "too_tilted": "Este telescopio está demasiado inclinado para usarse correctamente.", + "too_far": "¡Te has alejado demasiado del telescopio!", + "in_use": "¡Alguien más ya está usando este telescopio!" + }, + "interaction": { + "target_use": "Usar telescopio", + "target_collect": "Recoger telescopio", + "prompt_use": "Presiona E para mirar por el telescopio", + "prompt_exit": "Presiona E para salir de la vista" + }, + "placement": { + "progress_label": "Colocando telescopio...", + "collect_label": "Recogiendo telescopio...", + "controls": "[SCROLL] - Rotación \n[ENTER] - Colocar \n[BACKSPACE] - Cancelar" + } +} \ No newline at end of file diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 0000000..721fb62 --- /dev/null +++ b/server/main.lua @@ -0,0 +1,65 @@ +local config = require 'config.server' +local telescopes = {} + +if config.command.enabled then + lib.addCommand(config.command.name, { + help = locale('command.telescope_help'), + restricted = config.command.restricted + }, function(source) + if not exports.qbx_core:IsOptin(source) then TriggerClientEvent('ox_lib:notify', source, locale('command.admin_not_optin'), 'error') return end + TriggerClientEvent('telescopes:client:InteractTelescope', source) + end) +end + +MySQL.ready(function() + MySQL.Async.fetchAll('SELECT * FROM `telescopes`', {}, function(result) + for _, v in pairs(result) do + telescopes[v.id] = { coords = json.decode(v.coords), telescope = v.telescope } + end + end) +end) + +local function updateTelescopes() + for _, v in pairs(GetPlayers()) do + lib.callback.await('telescopes:client:updateTelescopes', v) + end +end + +local function deleteTelescope(id) + for _, v in pairs(GetPlayers()) do + lib.callback.await('telescopes:client:delete', v, id) + end +end + +lib.callback.register('telescopes:server:getTelescopes', function(source) + return telescopes +end) + +lib.callback.register('telescopes:server:itemActions', function(source, telescope, action) + local src = source + if action == 'remove' then + exports.ox_inventory:RemoveItem(src, telescope, 1) + else + exports.ox_inventory:AddItem(src, telescope, 1) + end +end) + +lib.callback.register('telescopes:server:place', function(source, telescope, coords, heading) + local src = source + coords = vec4(coords.x, coords.y, coords.z, heading) + MySQL.insert('INSERT INTO `telescopes` (telescope, coords) VALUES (?, ?)', { + telescope, json.encode(coords) + }, function(id) + telescopes[id] = { id = id, coords = coords, telescope = telescope } + updateTelescopes() + end) + return true +end) + +lib.callback.register('telescopes:server:delete', function(source, id) + MySQL.Async.execute('DELETE FROM `telescopes` WHERE `id` = ?', { id }, function() + telescopes[id] = nil + deleteTelescope(id) + end) + return true +end) \ No newline at end of file