From cad9aee68e3eb9a30dfbd505aaa002c72b597e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:09:35 +0200 Subject: [PATCH 01/18] Files have been restructured --- client.lua | 488 ----------------------------------------------------- config.lua | 69 -------- 2 files changed, 557 deletions(-) delete mode 100644 client.lua delete mode 100644 config.lua 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/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 -} From cd1b3a50c4a5f0b1a29fae91400f9faf117aded9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:09:59 +0200 Subject: [PATCH 02/18] Replaced standard FiveM natives with optimized ox_lib alternatives --- client/main.lua | 471 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 client/main.lua diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 0000000..a755f2a --- /dev/null +++ b/client/main.lua @@ -0,0 +1,471 @@ +local config = require 'config.client' + +-- Variables -- +local inTelescope = 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 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 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 + }) +end + +local function HideText() + lib.hideTextUI() +end + +local function SetupInstructions() + ShowText({text = locale('localization.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 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 + Notification({description = locale('localization.telescope_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('localization.telescope_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 + + 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 + Notification({description = locale('localization.to_far_away'), duration = 7500, type = 'error'}) + ClearPedTasks(playerPed) + 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(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) + + lib.playAnim(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) + + -- 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(playerPed, "mini@telescope", animation.exit, 2.0, 1.0, -1, 0, 0, false, false, false) + Wait(1500) + else + ClearPedTasks(playerPed) + 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('targeting.label'), + distance = config.maxInteractionDist, + onSelect = function(data) + UseTelescope(data.entity) + end + } + }) + else + exports[config.target]:AddTargetModel(models, { + options = { + { + icon = config.targeting.icon, + label = locale('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 + 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('localization.help_text'), position = 'right-center'}) + end + if IsControlJustPressed(0, 38) then + UseTelescope(closest) + end + Wait(0) + else + HideText() + Wait(distance*20) + end + else + Wait(500) + end + end + end) +end + + +-- Commands -- +RegisterNetEvent('telescopes:client:UseTelescope', function() + local telescope, distance = GetClosestTelescope() + if telescope ~= 0 and distance < config.maxInteractionDist then + UseTelescope(telescope) + else + Notification({description = locale('localization.non_found'), duration = 7500, type = 'error'}) + end +end) \ No newline at end of file From d2da4d5813c6f423f003dd51acf6c490e122c668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:10:21 +0200 Subject: [PATCH 03/18] Migrated the telescope command from the client side to the server side using lib.addCommand for better security and control --- config/server.lua | 7 +++++++ server/main.lua | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 config/server.lua create mode 100644 server/main.lua 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/server/main.lua b/server/main.lua new file mode 100644 index 0000000..adc28b5 --- /dev/null +++ b/server/main.lua @@ -0,0 +1,10 @@ +local config = require 'config.server' + +if config.command.enabled then + lib.addCommand(config.command.name, { + help = locale('command.help'), + restricted = config.command.restricted + }, function(source) + TriggerClientEvent('telescopes:client:UseTelescope', source) + end) +end \ No newline at end of file From e4888e7f0c812988e02786cbb1f41209fd47595f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:10:33 +0200 Subject: [PATCH 04/18] Added and configured 3D markers to provide better visual feedback for available telescopes --- config/client.lua | 132 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 config/client.lua diff --git a/config/client.lua b/config/client.lua new file mode 100644 index 0000000..0c3baef --- /dev/null +++ b/config/client.lua @@ -0,0 +1,132 @@ +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 + }, + + -- 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 = 1.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 From 9beedbb3f40f6623bb8f72617bd89fe6b4396aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:11:02 +0200 Subject: [PATCH 05/18] Bump version to 1.4.2 --- fxmanifest.lua | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 945ffaf..cac503f 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -3,9 +3,31 @@ 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 { + '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 From c2ec53eb42da7c39996d6755e2d761920be23866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Wed, 13 May 2026 17:11:12 +0200 Subject: [PATCH 06/18] Implemented ox_lib localization system with new English (en.json) and Spanish (es.json) translations. --- locales/en.json | 19 +++++++++++++++++++ locales/es.json | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 locales/en.json create mode 100644 locales/es.json diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..1479c3c --- /dev/null +++ b/locales/en.json @@ -0,0 +1,19 @@ +{ + "command": { + "help": "Use the nearest telescope" + }, + "notification": { + "title": "Telescope" + }, + "targeting": { + "label": "Use Telescope" + }, + "localization": { + "help_text": "Press E to look through the telescope", + "non_found": "No telescope was found!", + "telescope_in_use": "Someone else is already using the telescope!", + "telescope_too_tilted": "This telescope is too tilted to be used!", + "to_far_away": "You went to far away!", + "Exit": "Press E to exit the telescope" + } +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json new file mode 100644 index 0000000..6da38e6 --- /dev/null +++ b/locales/es.json @@ -0,0 +1,19 @@ +{ + "command": { + "help": "Usar el telescopio más cercano" + }, + "notification": { + "title": "Telescopio" + }, + "targeting": { + "label": "Usar telescopio" + }, + "localization": { + "help_text": "Pulsa E para mirar por el telescopio", + "non_found": "¡No se encontró ningún telescopio!", + "telescope_in_use": "¡Alguien más ya está usando el telescopio!", + "telescope_too_tilted": "¡Este telescopio está demasiado inclinado para usarse!", + "to_far_away": "¡Te alejaste demasiado!", + "exit": "Pulsa E para salir del telescopio" + } +} \ No newline at end of file From 3f9b370d99b1328c1364e1b4de443ccb1457a1d8 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 13 May 2026 19:41:21 +0200 Subject: [PATCH 07/18] Fixed an issue with localization.exit. --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 1479c3c..d2a1802 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,6 +14,6 @@ "telescope_in_use": "Someone else is already using the telescope!", "telescope_too_tilted": "This telescope is too tilted to be used!", "to_far_away": "You went to far away!", - "Exit": "Press E to exit the telescope" + "exit": "Press E to exit the telescope" } -} \ No newline at end of file +} From e5c7c441c7f2730efbd08171c8771a3d6adf1142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Thu, 14 May 2026 23:37:17 +0200 Subject: [PATCH 08/18] PlayerPedId() replaced with cache.ped --- client/main.lua | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/client/main.lua b/client/main.lua index a755f2a..04b9ff6 100644 --- a/client/main.lua +++ b/client/main.lua @@ -105,10 +105,9 @@ local function IsPedPlayingAnyTelescopeAnim(ped) 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 #(GetEntityCoords(ped) - coords) < 1.0 and ped ~= cache.ped then if IsPedPlayingAnyTelescopeAnim(ped) then return false end @@ -168,8 +167,7 @@ local function GetClosestTelescope() end end - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) + local playerCoords = GetEntityCoords(cache.ped) local closest = 0 local distance = 1000 @@ -239,29 +237,28 @@ local function UseTelescope(entity) 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) + TaskGoStraightToCoord(cache.ped, 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") + local taskStatus = GetScriptTaskStatus(cache.ped, "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)) + ClearPedTasks(cache.ped) + local difference = math.abs(heading - GetEntityHeading(cache.ped)) if difference > 10.0 then - SetEntityHeading(playerPed, heading) + SetEntityHeading(cache.ped, heading) end - local dist = #(GetEntityCoords(playerPed)-offsetCoords) + local dist = #(GetEntityCoords(cache.ped)-offsetCoords) if dist > 0.425 and dist < 2.0 then - SetEntityCoords(playerPed, offsetCoords.x, offsetCoords.y, offsetCoords.z-1.0) + SetEntityCoords(cache.ped, offsetCoords.x, offsetCoords.y, offsetCoords.z-1.0) elseif dist > 2.0 then Notification({description = locale('localization.to_far_away'), duration = 7500, type = 'error'}) - ClearPedTasks(playerPed) + ClearPedTasks(cache.ped) inTelescope = true return end @@ -275,7 +272,7 @@ local function UseTelescope(entity) return end - lib.playAnim(playerPed, "mini@telescope", animation.enter, 2.0, 2.0, -1, 2, 0, false, false, false) + lib.playAnim(cache.ped, "mini@telescope", animation.enter, 2.0, 2.0, -1, 2, 0, false, false, false) gameplayCamera.heading = GetGameplayCamRelativeHeading() gameplayCamera.pitch = GetGameplayCamRelativePitch() @@ -284,7 +281,7 @@ local function UseTelescope(entity) DoScreenFadeOut(500) Wait(600) - lib.playAnim(playerPed, "mini@telescope", animation.idle, 2.0, 2.0, -1, 1, 0, false, false, false) + lib.playAnim(cache.ped, "mini@telescope", animation.idle, 2.0, 2.0, -1, 1, 0, false, false, false) CreateTelescopeCamera(entity, data) SetupInstructions() @@ -312,7 +309,7 @@ local function UseTelescope(entity) -- Only handle "less important" stuff every 100 frames if tick >= 100 then - if #(GetEntityCoords(playerPed)-offsetCoords) > 1.5 or IsEntityDead(playerPed) then + if #(GetEntityCoords(cache.ped)-offsetCoords) > 1.5 or IsEntityDead(cache.ped) then doAnim = false break end @@ -349,10 +346,10 @@ local function UseTelescope(entity) Wait(500) if doAnim then - lib.playAnim(playerPed, "mini@telescope", animation.exit, 2.0, 1.0, -1, 0, 0, false, false, false) + lib.playAnim(cache.ped, "mini@telescope", animation.exit, 2.0, 1.0, -1, 0, 0, false, false, false) Wait(1500) else - ClearPedTasks(playerPed) + ClearPedTasks(cache.ped) HideText() end @@ -421,8 +418,7 @@ if config.useDistanceThread then CreateThread(function() while true do if not inTelescope then - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) + local playerCoords = GetEntityCoords(cache.ped) local closest = 0 local distance = 250 From 31e849e79705e049bd525c7212c40afbe0882910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 00:03:01 +0200 Subject: [PATCH 09/18] Add icon support to the ShowText --- client/main.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/main.lua b/client/main.lua index 04b9ff6..20e1ebb 100644 --- a/client/main.lua +++ b/client/main.lua @@ -39,7 +39,8 @@ end local function ShowText(data) lib.showTextUI(data.text, { - position = data.position + position = data.position, + icon = data.icon or 'fa-solid fa-arrow-right-from-bracket' }) end @@ -438,7 +439,7 @@ if config.useDistanceThread then 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('localization.help_text'), position = 'right-center'}) + ShowText({text = locale('localization.help_text'), position = 'right-center', icon = config.targeting.icon}) end if IsControlJustPressed(0, 38) then UseTelescope(closest) From 7baed436f74c41a71afa745c390dbe44a49b7f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 01:20:58 +0200 Subject: [PATCH 10/18] Require QBX opt-in before using telescope --- server/main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/server/main.lua b/server/main.lua index adc28b5..fd771f5 100644 --- a/server/main.lua +++ b/server/main.lua @@ -5,6 +5,7 @@ if config.command.enabled then help = locale('command.help'), restricted = config.command.restricted }, function(source) + if not exports.qbx_core:IsOptin(source) then TriggerClientEvent('ox_lib:notify', source, locale('command.not_optin'), 'error') return end TriggerClientEvent('telescopes:client:UseTelescope', source) end) end \ No newline at end of file From 9b6d5cfccb9d8c50255634a7df6126815626c41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 01:21:38 +0200 Subject: [PATCH 11/18] Add not_optin message to en and es locales --- locales/en.json | 3 ++- locales/es.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index d2a1802..3f44849 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,6 +1,7 @@ { "command": { - "help": "Use the nearest telescope" + "help": "Use the nearest telescope", + "not_optin": "You are not opted in for admin duty. (/optin to toggle)" }, "notification": { "title": "Telescope" diff --git a/locales/es.json b/locales/es.json index 6da38e6..f3c279e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,6 +1,7 @@ { "command": { - "help": "Usar el telescopio más cercano" + "help": "Usar el telescopio más cercano", + "not_optin": "No has optado por realizar tareas administrativas. (/optin para alternar)" }, "notification": { "title": "Telescopio" @@ -16,4 +17,4 @@ "to_far_away": "¡Te alejaste demasiado!", "exit": "Pulsa E para salir del telescopio" } -} \ No newline at end of file +} From 1522c260d36fbff8a96904342766530b363217bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 19:18:59 +0200 Subject: [PATCH 12/18] Add telescope placement, spawn/despawn & refactor --- client/main.lua | 227 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 211 insertions(+), 16 deletions(-) diff --git a/client/main.lua b/client/main.lua index 20e1ebb..1b884bb 100644 --- a/client/main.lua +++ b/client/main.lua @@ -2,6 +2,7 @@ local config = require 'config.client' -- Variables -- local inTelescope = false +local isPlacingObject = false local gameplayCamera = {} local telescopeHeading = 0.0 local frozen = false @@ -14,6 +15,9 @@ 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 @@ -28,6 +32,21 @@ local hudComponentsToHide = { } -- 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'), @@ -49,7 +68,7 @@ local function HideText() end local function SetupInstructions() - ShowText({text = locale('localization.exit'), position = 'right-center'}) + ShowText({text = locale('interaction.prompt_exit'), position = 'right-center'}) end local function CreateTelescopeCamera(entity, data) @@ -217,16 +236,16 @@ local function GetEntityTilt(entity) return xRot + yRot end -local function UseTelescope(entity) +local function InteractTelescope(entity) if GetEntityTilt(entity) > config.maxTilt then - Notification({description = locale('localization.telescope_too_tilted'), duration = 7500, type = 'error'}) + 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('localization.telescope_in_use'), duration = 7500, type = 'error'}) + Notification({description = locale('notification.in_use'), duration = 7500, type = 'error'}) return end @@ -258,7 +277,7 @@ local function UseTelescope(entity) 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('localization.to_far_away'), duration = 7500, type = 'error'}) + Notification({description = locale('notification.too_far'), duration = 7500, type = 'error'}) ClearPedTasks(cache.ped) inTelescope = true return @@ -374,10 +393,12 @@ if config.target then exports.ox_target:addModel(models, { { icon = config.targeting.icon, - label = locale('targeting.label'), + label = locale('interaction.target_use'), distance = config.maxInteractionDist, onSelect = function(data) - UseTelescope(data.entity) + if not isPlacingObject then + InteractTelescope(data.entity) + end end } }) @@ -386,9 +407,11 @@ if config.target then options = { { icon = config.targeting.icon, - label = locale('targeting.label'), + label = locale('interaction.target_use'), action = function(entity) - UseTelescope(entity) + if not isPlacingObject then + InteractTelescope(entity) + end end } }, @@ -418,7 +441,7 @@ if config.useDistanceThread then CreateThread(function() while true do - if not inTelescope then + if not inTelescope and not isPlacingObject then local playerCoords = GetEntityCoords(cache.ped) local closest = 0 local distance = 250 @@ -439,15 +462,15 @@ if config.useDistanceThread then 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('localization.help_text'), position = 'right-center', icon = config.targeting.icon}) + ShowText({text = locale('interaction.prompt_use'), position = 'right-center', icon = config.targeting.icon}) end if IsControlJustPressed(0, 38) then - UseTelescope(closest) + InteractTelescope(closest) end Wait(0) else HideText() - Wait(distance*20) + Wait(distance*5) end else Wait(500) @@ -456,13 +479,185 @@ if config.useDistanceThread then 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:UseTelescope', function() +RegisterNetEvent('telescopes:client:InteractTelescope', function() + if isPlacingObject then return end local telescope, distance = GetClosestTelescope() if telescope ~= 0 and distance < config.maxInteractionDist then - UseTelescope(telescope) + InteractTelescope(telescope) else - Notification({description = locale('localization.non_found'), duration = 7500, type = 'error'}) + Notification({description = locale('notification.not_found'), duration = 7500, type = 'error'}) end end) \ No newline at end of file From be2a6eb0c22b700e160bbf40a003dda61408d1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 19:19:14 +0200 Subject: [PATCH 13/18] Add telescopes and increase marker height --- config/client.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/config/client.lua b/config/client.lua index 0c3baef..69d2bab 100644 --- a/config/client.lua +++ b/config/client.lua @@ -35,6 +35,17 @@ return { 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, @@ -102,7 +113,7 @@ return { animation = "upright", cameraOffset = vector3(0.0, 0.2, 1.7), scaleform = "BINOCULARS", - markerHeight = 1.0 + markerHeight = 2.0 }, --[[ Domestic/private telescope ]] From 061b58c8df91729177d9fe7e8390b5b87c8d555a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 19:19:31 +0200 Subject: [PATCH 14/18] Refactor telescope locale keys and add messages --- locales/en.json | 31 ++++++++++++++++++------------- locales/es.json | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3f44849..91f8522 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,20 +1,25 @@ { "command": { - "help": "Use the nearest telescope", - "not_optin": "You are not opted in for admin duty. (/optin to toggle)" + "telescope_help": "Use the nearest telescope", + "admin_not_optin": "You are not opted in for admin duty. Use /optin to toggle." }, "notification": { - "title": "Telescope" + "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." }, - "targeting": { - "label": "Use 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" }, - "localization": { - "help_text": "Press E to look through the telescope", - "non_found": "No telescope was found!", - "telescope_in_use": "Someone else is already using the telescope!", - "telescope_too_tilted": "This telescope is too tilted to be used!", - "to_far_away": "You went to far away!", - "exit": "Press E to exit the telescope" + "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 index f3c279e..405185e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,20 +1,25 @@ { "command": { - "help": "Usar el telescopio más cercano", - "not_optin": "No has optado por realizar tareas administrativas. (/optin para alternar)" + "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" + "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!" }, - "targeting": { - "label": "Usar 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" }, - "localization": { - "help_text": "Pulsa E para mirar por el telescopio", - "non_found": "¡No se encontró ningún telescopio!", - "telescope_in_use": "¡Alguien más ya está usando el telescopio!", - "telescope_too_tilted": "¡Este telescopio está demasiado inclinado para usarse!", - "to_far_away": "¡Te alejaste demasiado!", - "exit": "Pulsa E para salir del telescopio" + "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 From 3ad1b3c42e7647d56f8fbea2607095f22b57bed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 19:19:53 +0200 Subject: [PATCH 15/18] Persist telescopes and add server callbacks --- server/main.lua | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/server/main.lua b/server/main.lua index fd771f5..721fb62 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,11 +1,65 @@ local config = require 'config.server' +local telescopes = {} if config.command.enabled then lib.addCommand(config.command.name, { - help = locale('command.help'), + 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.not_optin'), 'error') return end - TriggerClientEvent('telescopes:client:UseTelescope', 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 \ No newline at end of file +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 From 999d4dd3334eec396b3f145b1e1779cad29e9f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 19:42:22 +0200 Subject: [PATCH 16/18] Update README with setup and DB instructions --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 13 deletions(-) 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 From 6da2fe9a01be32cf73ed61709207da9ce8f06f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 20:06:59 +0200 Subject: [PATCH 17/18] Add oxmysql dependency to server scripts --- fxmanifest.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/fxmanifest.lua b/fxmanifest.lua index cac503f..e33885c 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -16,6 +16,7 @@ client_script { } server_script { + '@oxmysql/lib/MySQL.lua', 'server/main.lua', } From b808d6a52c06fee63b0f67ba7b5e69cc7be17eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= Date: Fri, 15 May 2026 20:12:08 +0200 Subject: [PATCH 18/18] Add telescope items and SQL table --- install/add_items.md | 38 ++++++++++++++++++++++++++++++++++++++ install/telescopes.sql | 6 ++++++ 2 files changed, 44 insertions(+) create mode 100644 install/add_items.md create mode 100644 install/telescopes.sql 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