[
  {
    "path": ".github/actions/bump-manifest-version.js",
    "content": "const fxManifest = await Bun.file('./fxmanifest.lua').text();\n\nlet newVersion = process.env.TGT_RELEASE_VERSION;\nnewVersion = newVersion.replace('v', '')\n\nconst newFileContent = fxManifest.replace(/\\bversion\\s+(.*)$/gm, `version '${newVersion}'`);\n\nawait Bun.write('./fxmanifest.lua', newFileContent);\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Create release\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  create-release:\n    if: github.actor_id != 278903378\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install zip\n        run: sudo apt install zip\n\n      - name: Install Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Generate GitHub App token\n        id: app_token\n        uses: tibdex/github-app-token@v2\n        with:\n          app_id: ${{ secrets.APP_ID }}\n          private_key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Get latest code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.repository.default_branch }}\n          token: ${{ steps.app_token.outputs.token }}\n\n      - name: Bump manifest version\n        run: bun run .github/actions/bump-manifest-version.js\n        env:\n          TGT_RELEASE_VERSION: ${{ github.ref_name }}\n\n      - name: Push version bump change\n        uses: EndBug/add-and-commit@v9\n        with:\n          add: fxmanifest.lua\n          push: true\n          default_author: github_actions\n          message: \"chore: bump version to ${{ github.ref_name }}\"\n\n      - name: Bundle files\n        run: |\n          mkdir -p ./temp/ox_target\n          cp ./{LICENSE,README.md,fxmanifest.lua} ./temp/ox_target\n          cp -r ./{client,server,web,locales} ./temp/ox_target\n          cd ./temp && zip -r ../ox_target.zip ./ox_target\n\n      - name: Create release\n        uses: \"marvinpinto/action-automatic-releases@v1.2.1\"\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          prerelease: false\n          files: ox_target.zip\n\n      - name: Update tag\n        uses: EndBug/latest-tag@v1\n        with:\n          ref: ${{ github.ref_name }}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Overextended\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ox_target\n\n![](https://img.shields.io/github/downloads/overextended/ox_target/total?logo=github)\n![](https://img.shields.io/github/downloads/overextended/ox_target/latest/total?logo=github)\n![](https://img.shields.io/github/contributors/overextended/ox_target?logo=github)\n![](https://img.shields.io/github/v/release/overextended/ox_target?logo=github) \n\n\nA performant and flexible standalone \"third-eye\" targeting resource, with additional functionality for supported frameworks.\n\nox_target is the successor to qtarget, which was a mostly-compatible fork of bt-target.\nTo improve many design flaws, ox_target has been written from scratch and drops support for bt-target/qtarget standards, though partial compatibility is being implemented where possible.\n\n\n## 📚 Documentation\n\nhttps://overextended.dev/ox_target\n\n## 💾 Download\n\nhttps://github.com/overextended/ox_target/releases/latest/download/ox_target.zip\n\n## ✨ Features\n\n- Improved entity and world collision than its predecessor.\n- Improved error handling when running external code.\n- Menus for nested target options.\n- Partial compatibility for qtarget (the thing qb-target is based on, I made the original idiots).\n- Registering options no longer overrides existing options.\n- Groups and items checking for supported frameworks.\n"
  },
  {
    "path": "client/api.lua",
    "content": "---@class OxTargetOption\n---@field resource? string\n\nlocal utils = require 'client.utils'\n\nlocal api = setmetatable({}, {\n    __newindex = function(self, index, value)\n        rawset(self, index, value)\n        exports(index, value)\n    end\n})\n\n---Throws a formatted type error\n---@param variable string\n---@param expected string\n---@param received string\nlocal function typeError(variable, expected, received)\n    error((\"expected %s to have type '%s' (received %s)\"):format(variable, expected, received))\nend\n\n---Checks options and throws an error on type mismatch\n---@param options OxTargetOption | OxTargetOption[]\n---@return OxTargetOption[]\nlocal function checkOptions(options)\n    local optionsType = type(options)\n\n    if optionsType ~= 'table' then\n        typeError('options', 'table', optionsType)\n    end\n\n    local tableType = table.type(options)\n\n    if tableType == 'hash' and options.label then\n        options = { options }\n    elseif tableType ~= 'array' then\n        typeError('options', 'array', ('%s table'):format(tableType))\n    end\n\n    return options\nend\n\n---@param data OxTargetPolyZone | table\n---@return number\nfunction api.addPolyZone(data)\n    if data.debug then utils.warn('Creating new PolyZone with debug enabled.') end\n\n    data.resource = GetInvokingResource()\n    data.options = checkOptions(data.options)\n    return lib.zones.poly(data).id\nend\n\n---@param data OxTargetBoxZone | table\n---@return number\nfunction api.addBoxZone(data)\n    if data.debug then utils.warn('Creating new BoxZone with debug enabled.') end\n\n    data.resource = GetInvokingResource()\n    data.options = checkOptions(data.options)\n    return lib.zones.box(data).id\nend\n\n---@param data OxTargetSphereZone | table\n---@return number\nfunction api.addSphereZone(data)\n    if data.debug then utils.warn('Creating new SphereZone with debug enabled.') end\n\n    data.resource = GetInvokingResource()\n    data.options = checkOptions(data.options)\n    return lib.zones.sphere(data).id\nend\n\n---@param id number | string The ID of the zone to check. It can be either a number or a string representing the zone's index or name, respectively.\n---@return boolean returns true if the zone with the specified ID exists, otherwise false.\nfunction api.zoneExists(id)\n    if not Zones or (type(id) ~= 'number' and type(id) ~= 'string') then return false end\n\n    if type(id) == 'number' and Zones[id] then return true end\n\n    for _, zone in pairs(lib.zones.getAllZones()) do\n        if type(id) == 'string' and zone.name == id then return true end\n    end\n\n    return false\nend\n\n---@param id number | string\n---@param suppressWarning boolean?\nfunction api.removeZone(id, suppressWarning)\n    if Zones then\n        if type(id) == 'string' then\n            local foundZone\n\n            for _, v in pairs(lib.zones.getAllZones()) do\n                if v.name == id then\n                    foundZone = true\n                    v:remove()\n                end\n            end\n\n            if foundZone then return end\n        elseif Zones[id] then\n            return Zones[id]:remove()\n        end\n    end\n\n    if suppressWarning then return end\n\n    warn(('attempted to remove a zone that does not exist (id: %s)'):format(id))\nend\n\n---@param target table\n---@param remove string | string[]\n---@param resource string\n---@param showWarning? boolean\nlocal function removeTarget(target, remove, resource, showWarning)\n    if type(remove) ~= 'table' then remove = { remove } end\n\n    for i = #target, 1, -1 do\n        local option = target[i]\n\n        if option.resource == resource then\n            for j = #remove, 1, -1 do\n                if option.name == remove[j] then\n                    table.remove(target, i)\n\n                    if showWarning then\n                        utils.warn((\"Replacing existing target option '%s'.\"):format(option.name))\n                    end\n                end\n            end\n        end\n    end\nend\n\n---@param target table\n---@param options OxTargetOption | OxTargetOption[]\n---@param resource string\nlocal function addTarget(target, options, resource)\n    options = checkOptions(options)\n\n    local checkNames = {}\n\n    resource = resource or 'ox_target'\n\n    for i = 1, #options do\n        local option = options[i]\n        option.resource = resource\n\n        if option.name then\n            checkNames[#checkNames + 1] = option.name\n        end\n    end\n\n    if checkNames[1] then\n        removeTarget(target, checkNames, resource, true)\n    end\n\n    local num = #target\n\n    for i = 1, #options do\n        local option = options[i]\n\n        if resource == 'ox_target' then\n            if option.canInteract then\n                option.canInteract = msgpack.unpack(msgpack.pack(option.canInteract))\n            end\n\n            if option.onSelect then\n                option.onSelect = msgpack.unpack(msgpack.pack(option.onSelect))\n            end\n        end\n\n        num += 1\n        target[num] = options[i]\n    end\nend\n\n---@type table<number, OxTargetOption[]>\nlocal peds = {}\n\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addGlobalPed(options)\n    addTarget(peds, options, GetInvokingResource())\nend\n\n---@param options string | string[]\nfunction api.removeGlobalPed(options)\n    removeTarget(peds, options, GetInvokingResource())\nend\n\n---@type table<number, OxTargetOption[]>\nlocal vehicles = {}\n\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addGlobalVehicle(options)\n    addTarget(vehicles, options, GetInvokingResource())\nend\n\n---@param options string | string[]\nfunction api.removeGlobalVehicle(options)\n    removeTarget(vehicles, options, GetInvokingResource())\nend\n\n---@type table<number, OxTargetOption[]>\nlocal objects = {}\n\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addGlobalObject(options)\n    addTarget(objects, options, GetInvokingResource())\nend\n\n---@param options string | string[]\nfunction api.removeGlobalObject(options)\n    removeTarget(objects, options, GetInvokingResource())\nend\n\n---@type table<number, OxTargetOption[]>\nlocal players = {}\n\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addGlobalPlayer(options)\n    addTarget(players, options, GetInvokingResource())\nend\n\n---@param options string | string[]\nfunction api.removeGlobalPlayer(options)\n    removeTarget(players, options, GetInvokingResource())\nend\n\n---@type table<number, OxTargetOption[]>\nlocal models = {}\n\n---@param arr (number | string) | (number | string)[]\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addModel(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local model = arr[i]\n        model = tonumber(model) or joaat(model)\n\n        if not models[model] then\n            models[model] = {}\n        end\n\n        addTarget(models[model], options, resource)\n    end\nend\n\n---@param arr (number | string) | (number | string)[]\n---@param options? string | string[]\nfunction api.removeModel(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local model = arr[i]\n        model = tonumber(model) or joaat(model)\n\n        if models[model] then\n            if options then\n                removeTarget(models[model], options, resource)\n            end\n\n            if not options or #models[model] == 0 then\n                models[model] = nil\n            end\n        end\n    end\nend\n\n---@type table<number, OxTargetOption[]>\nlocal entities = {}\n\n---@param arr number | number[]\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addEntity(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local netId = arr[i]\n\n        if NetworkDoesNetworkIdExist(netId) then\n            if not entities[netId] then\n                entities[netId] = {}\n\n                if not Entity(NetworkGetEntityFromNetworkId(netId)).state.hasTargetOptions then\n                    TriggerServerEvent('ox_target:setEntityHasOptions', netId)\n                end\n            end\n\n            addTarget(entities[netId], options, resource)\n        end\n    end\nend\n\n---@param arr number | number[]\n---@param options? string | string[]\nfunction api.removeEntity(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local netId = arr[i]\n\n        if entities[netId] then\n            if options then\n                removeTarget(entities[netId], options, resource)\n            end\n\n            if not options or #entities[netId] == 0 then\n                entities[netId] = nil\n            end\n        end\n    end\nend\n\nRegisterNetEvent('ox_target:removeEntity', api.removeEntity)\n\n---@type table<number, OxTargetOption[]>\nlocal localEntities = {}\n\n---@param arr number | number[]\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addLocalEntity(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local entityId = arr[i]\n\n        if DoesEntityExist(entityId) then\n            if not localEntities[entityId] then\n                localEntities[entityId] = {}\n            end\n\n            addTarget(localEntities[entityId], options, resource)\n        else\n             lib.print.warn((\"No entity with id '%s' exists in %s.\"):format(entityId, resource))\n        end\n    end\nend\n\n---@param arr number | number[]\n---@param options? table\nfunction api.removeLocalEntity(arr, options)\n    if type(arr) ~= 'table' then arr = { arr } end\n    local resource = GetInvokingResource()\n\n    for i = 1, #arr do\n        local entity = arr[i]\n\n        if localEntities[entity] then\n            if options then\n                removeTarget(localEntities[entity], options, resource)\n            end\n\n            if not options or #localEntities[entity] == 0 then\n                localEntities[entity] = nil\n            end\n        end\n    end\nend\n\nCreateThread(function()\n    while true do\n        Wait(60000)\n\n        for entityId in pairs(localEntities) do\n            if not DoesEntityExist(entityId) then\n                localEntities[entityId] = nil\n            end\n        end\n    end\nend)\n\n---@param resource string\n---@param target table\nlocal function removeResourceGlobals(resource, target)\n    for i = 1, #target do\n        local options = target[i]\n\n        for j = #options, 1, -1 do\n            if options[j].resource == resource then\n                table.remove(options, j)\n            end\n        end\n    end\nend\n\n---@param resource string\n---@param target table\nlocal function removeResourceTargets(resource, target)\n    for i = 1, #target do\n        local tbl = target[i]\n\n        for key, options in pairs(tbl) do\n            for j = #options, 1, -1 do\n                if options[j].resource == resource then\n                    table.remove(options, j)\n                end\n            end\n\n            if #options == 0 then\n                tbl[key] = nil\n            end\n        end\n    end\nend\n\n---@param resource string\nAddEventHandler('onClientResourceStop', function(resource)\n    removeResourceGlobals(resource, { peds, vehicles, objects, players })\n    removeResourceTargets(resource, { models, entities, localEntities })\n\n    if Zones then\n        for _, v in pairs(Zones) do\n            if v.resource == resource then\n                v:remove()\n            end\n        end\n    end\nend)\n\nlocal NetworkGetEntityIsNetworked = NetworkGetEntityIsNetworked\nlocal NetworkGetNetworkIdFromEntity = NetworkGetNetworkIdFromEntity\n\n---@class OxTargetOptions\nlocal options_mt = {}\noptions_mt.__index = options_mt\noptions_mt.size = 1\n\nfunction options_mt:wipe()\n    options_mt.size = 1\n    self.globalTarget = nil\n    self.model = nil\n    self.entity = nil\n    self.localEntity = nil\n\n    if self.__global[1]?.name == 'builtin:goback' then\n        table.remove(self.__global, 1)\n    end\nend\n\n---@param entity? number\n---@param _type? number\n---@param model? number\nfunction options_mt:set(entity, _type, model)\n    if not entity then return end\n\n    if _type == 1 and IsPedAPlayer(entity) then\n        self:wipe()\n        self.globalTarget = players\n        options_mt.size += 1\n\n        return\n    end\n\n    local netId = NetworkGetEntityIsNetworked(entity) and NetworkGetNetworkIdFromEntity(entity)\n\n    self.globalTarget = _type == 1 and peds or _type == 2 and vehicles or objects\n    self.model = models[model]\n    self.entity = netId and entities[netId] or nil\n    self.localEntity = localEntities[entity]\n    options_mt.size += 1\n\n    if self.model then options_mt.size += 1 end\n    if self.entity then options_mt.size += 1 end\n    if self.localEntity then options_mt.size += 1 end\nend\n\n---@type OxTargetOption[]\nlocal global = {}\n\n---@param options OxTargetOption | OxTargetOption[]\nfunction api.addGlobalOption(options)\n    addTarget(global, options, GetInvokingResource())\nend\n\n---@param options string | string[]\nfunction api.removeGlobalOption(options)\n    removeTarget(global, options, GetInvokingResource())\nend\n\n---@class OxTargetOptions\nlocal options = setmetatable({\n    __global = global\n}, options_mt)\n\n---@param entity? number\n---@param _type? number\n---@param model? number\nfunction api.getTargetOptions(entity, _type, model)\n    if not entity then return options end\n\n    if IsPedAPlayer(entity) then\n        return {\n            global = players,\n        }\n    end\n\n    local netId = NetworkGetEntityIsNetworked(entity) and NetworkGetNetworkIdFromEntity(entity)\n\n    return {\n        global = _type == 1 and peds or _type == 2 and vehicles or objects,\n        model = models[model],\n        entity = netId and entities[netId] or nil,\n        localEntity = localEntities[entity],\n    }\nend\n\nlocal state = require 'client.state'\n\nfunction api.disableTargeting(value)\n    if value then\n        state.setActive(false)\n    end\n\n    state.setDisabled(value)\nend\n\nfunction api.isActive()\n    return state.isActive()\nend\n\nreturn api\n"
  },
  {
    "path": "client/compat/qtarget.lua",
    "content": "local function exportHandler(exportName, func)\n    AddEventHandler(('__cfx_export_qtarget_%s'):format(exportName), function(setCB)\n        setCB(func)\n    end)\nend\n\n---@param options table\n---@return table\nlocal function convert(options)\n    local distance = options.distance\n    options = options.options\n\n    -- People may pass options as a hashmap (or mixed, even)\n    for k, v in pairs(options) do\n        if type(k) ~= 'number' then\n            table.insert(options, v)\n        end\n    end\n\n    for id, v in pairs(options) do\n        if type(id) ~= 'number' then\n            options[id] = nil\n            goto continue\n        end\n\n        v.onSelect = v.action\n        v.distance = v.distance or distance\n        v.name = v.name or v.label\n        v.groups = v.job\n        v.items = v.item or v.required_item\n\n        if v.event and v.type and v.type ~= 'client' then\n            if v.type == 'server' then\n                v.serverEvent = v.event\n            elseif v.type == 'command' then\n                v.command = v.event\n            end\n\n            v.event = nil\n            v.type = nil\n        end\n\n        v.action = nil\n        v.job = nil\n        v.item = nil\n        v.required_item = nil\n        v.qtarget = true\n\n        ::continue::\n    end\n\n    return options\nend\n\nlocal api = require 'client.api'\n\nexportHandler('AddBoxZone', function(name, center, length, width, options, targetoptions)\n    local z = center.z\n\n    if not options.minZ then\n        options.minZ = -100\n    end\n\n    if not options.maxZ then\n        options.maxZ = 800\n    end\n\n    if not options.useZ then\n        z = z + math.abs(options.maxZ - options.minZ) / 2\n        center = vec3(center.x, center.y, z)\n    end\n\n    return api.addBoxZone({\n        name = name,\n        coords = center,\n        size = vec3(width, length, (options.useZ or not options.maxZ) and center.z or math.abs(options.maxZ - options.minZ)),\n        debug = options.debugPoly,\n        rotation = options.heading,\n        options = convert(targetoptions),\n    })\nend)\n\nexportHandler('AddPolyZone', function(name, points, options, targetoptions)\n    local newPoints = table.create(#points, 0)\n    local thickness = math.abs(options.maxZ - options.minZ)\n\n    for i = 1, #points do\n        local point = points[i]\n        newPoints[i] = vec3(point.x, point.y, options.maxZ - (thickness / 2))\n    end\n\n    return api.addPolyZone({\n        name = name,\n        points = newPoints,\n        thickness = thickness,\n        debug = options.debugPoly,\n        options = convert(targetoptions),\n    })\nend)\n\nexportHandler('AddCircleZone', function(name, center, radius, options, targetoptions)\n    return api.addSphereZone({\n        name = name,\n        coords = center,\n        radius = radius,\n        debug = options.debugPoly,\n        options = convert(targetoptions),\n    })\nend)\n\nexportHandler('RemoveZone', function(id)\n    api.removeZone(id, true)\nend)\n\nexportHandler('AddTargetBone', function(bones, options)\n    if type(bones) ~= 'table' then bones = { bones } end\n    options = convert(options)\n\n    for _, v in pairs(options) do\n        v.bones = bones\n    end\n\n    exports.ox_target:addGlobalVehicle(options)\nend)\n\nexportHandler('AddTargetEntity', function(entities, options)\n    if type(entities) ~= 'table' then entities = { entities } end\n    options = convert(options)\n\n    for i = 1, #entities do\n        local entity = entities[i]\n\n        if NetworkGetEntityIsNetworked(entity) then\n            api.addEntity(NetworkGetNetworkIdFromEntity(entity), options)\n        else\n            api.addLocalEntity(entity, options)\n        end\n    end\nend)\n\nexportHandler('RemoveTargetEntity', function(entities, labels)\n    if type(entities) ~= 'table' then entities = { entities } end\n\n    for i = 1, #entities do\n        local entity = entities[i]\n\n        if NetworkGetEntityIsNetworked(entity) then\n            api.removeEntity(NetworkGetNetworkIdFromEntity(entity), labels)\n        else\n            api.removeLocalEntity(entity, labels)\n        end\n    end\nend)\n\nexportHandler('AddTargetModel', function(models, options)\n    api.addModel(models, convert(options))\nend)\n\nexportHandler('RemoveTargetModel', function(models, labels)\n    api.removeModel(models, labels)\nend)\n\nexportHandler('Ped', function(options)\n    api.addGlobalPed(convert(options))\nend)\n\nexportHandler('RemovePed', function(labels)\n    api.removeGlobalPed(labels)\nend)\n\nexportHandler('Vehicle', function(options)\n    api.addGlobalVehicle(convert(options))\nend)\n\nexportHandler('RemoveVehicle', function(labels)\n    api.removeGlobalVehicle(labels)\nend)\n\nexportHandler('Object', function(options)\n    api.addGlobalObject(convert(options))\nend)\n\nexportHandler('RemoveObject', function(labels)\n    api.removeGlobalObject(labels)\nend)\n\nexportHandler('Player', function(options)\n    api.addGlobalPlayer(convert(options))\nend)\n\nexportHandler('RemovePlayer', function(labels)\n    api.removeGlobalPlayer(labels)\nend)"
  },
  {
    "path": "client/debug.lua",
    "content": "AddEventHandler('ox_target:debug', function(data)\n    if data.entity and GetEntityType(data.entity) > 0 then\n        data.archetype = GetEntityArchetypeName(data.entity)\n        data.model = GetEntityModel(data.entity)\n    end\n\n\tprint(json.encode(data, {indent=true}))\nend)\n\nif GetConvarInt('ox_target:debug', 0) ~= 1 then return end\n\nlocal ox_target = exports.ox_target\nlocal drawZones = true\n\nox_target:addBoxZone({\n    coords = vec3(442.5363, -1017.666, 28.85637),\n    size = vec3(3, 3, 3),\n    rotation = 45,\n    debug = drawZones,\n    drawSprite = true,\n    options = {\n        {\n            name = 'debug_box',\n            event = 'ox_target:debug',\n            icon = 'fa-solid fa-cube',\n            label = locale('debug_box'),\n        }\n    }\n})\n\nox_target:addSphereZone({\n    coords = vec3(440.5363, -1015.666, 28.85637),\n    radius = 3,\n    debug = drawZones,\n    drawSprite = true,\n    options = {\n        {\n            name = 'debug_sphere',\n            event = 'ox_target:debug',\n            icon = 'fa-solid fa-circle',\n            label = locale('debug_sphere'),\n        }\n    }\n})\n\nox_target:addModel(`police`, {\n    {\n        name = 'debug_model',\n        event = 'ox_target:debug',\n        icon = 'fa-solid fa-handcuffs',\n        label = locale('debug_police_car'),\n    }\n})\n\nox_target:addGlobalPed({\n    {\n        name = 'debug_ped',\n        event = 'ox_target:debug',\n        icon = 'fa-solid fa-male',\n        label = locale('debug_ped'),\n    }\n})\n\nox_target:addGlobalVehicle({\n    {\n        name = 'debug_vehicle',\n        event = 'ox_target:debug',\n        icon = 'fa-solid fa-car',\n        label = locale('debug_vehicle'),\n    }\n})\n\nox_target:addGlobalObject({\n    {\n        name = 'debug_object',\n        event = 'ox_target:debug',\n        icon = 'fa-solid fa-bong',\n        label = locale('debug_object'),\n    }\n})\n\nox_target:addGlobalOption({\n    {\n        name = 'debug_global',\n        icon = 'fa-solid fa-globe',\n        label = locale('debug_global'),\n        openMenu = 'debug_global'\n    }\n})\n\nox_target:addGlobalOption({\n    {\n        name = 'debug_global2',\n        event = 'ox_target:debug',\n        icon = 'fa-solid fa-globe',\n        label = locale('debug_global') .. ' 2',\n        menuName = 'debug_global'\n    }\n})"
  },
  {
    "path": "client/defaults.lua",
    "content": "if GetConvarInt('ox_target:defaults', 1) ~= 1 then return end\n\nlocal api = require 'client.api'\nlocal GetEntityBoneIndexByName = GetEntityBoneIndexByName\nlocal GetEntityBonePosition_2 = GetEntityBonePosition_2\nlocal GetVehicleDoorLockStatus = GetVehicleDoorLockStatus\n\nlocal bones = {\n    [0] = 'dside_f',\n    [1] = 'pside_f',\n    [2] = 'dside_r',\n    [3] = 'pside_r'\n}\n\n---@param vehicle number\n---@param door number\nlocal function toggleDoor(vehicle, door)\n    if GetVehicleDoorLockStatus(vehicle) ~= 2 then\n        if GetVehicleDoorAngleRatio(vehicle, door) > 0.0 then\n            SetVehicleDoorShut(vehicle, door, false)\n        else\n            SetVehicleDoorOpen(vehicle, door, false, false)\n        end\n    end\nend\n\n---@param entity number\n---@param coords vector3\n---@param door number\n---@param useOffset boolean?\n---@return boolean?\nlocal function canInteractWithDoor(entity, coords, door, useOffset)\n    if not GetIsDoorValid(entity, door) or GetVehicleDoorLockStatus(entity) > 1 or IsVehicleDoorDamaged(entity, door) or cache.vehicle then return end\n\n    if useOffset then return true end\n\n    local boneName = bones[door]\n\n    if not boneName then return false end\n\n    local boneId = GetEntityBoneIndexByName(entity, 'door_' .. boneName)\n\n    if boneId ~= -1 then\n        return #(coords - GetEntityBonePosition_2(entity, boneId)) < 0.5 or\n            #(coords - GetEntityBonePosition_2(entity, GetEntityBoneIndexByName(entity, 'seat_' .. boneName))) < 0.72\n    end\nend\n\nlocal function onSelectDoor(data, door)\n    local entity = data.entity\n\n    if NetworkGetEntityOwner(entity) == cache.playerId then\n        return toggleDoor(entity, door)\n    end\n\n    TriggerServerEvent('ox_target:toggleEntityDoor', VehToNet(entity), door)\nend\n\nRegisterNetEvent('ox_target:toggleEntityDoor', function(netId, door)\n    local entity = NetToVeh(netId)\n    toggleDoor(entity, door)\nend)\n\napi.addGlobalVehicle({\n    {\n        name = 'ox_target:driverF',\n        icon = 'fa-solid fa-car-side',\n        label = locale('toggle_front_driver_door'),\n        bones = { 'door_dside_f', 'seat_dside_f' },\n        distance = 2,\n        canInteract = function(entity, distance, coords, name)\n            return canInteractWithDoor(entity, coords, 0)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 0)\n        end\n    },\n    {\n        name = 'ox_target:passengerF',\n        icon = 'fa-solid fa-car-side',\n        label = locale('toggle_front_passenger_door'),\n        bones = { 'door_pside_f', 'seat_pside_f' },\n        distance = 2,\n        canInteract = function(entity, distance, coords, name)\n            return canInteractWithDoor(entity, coords, 1)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 1)\n        end\n    },\n    {\n        name = 'ox_target:driverR',\n        icon = 'fa-solid fa-car-side',\n        label = locale('toggle_rear_driver_door'),\n        bones = { 'door_dside_r', 'seat_dside_r' },\n        distance = 2,\n        canInteract = function(entity, distance, coords)\n            return canInteractWithDoor(entity, coords, 2)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 2)\n        end\n    },\n    {\n        name = 'ox_target:passengerR',\n        icon = 'fa-solid fa-car-side',\n        label = locale('toggle_rear_passenger_door'),\n        bones = { 'door_pside_r', 'seat_pside_r' },\n        distance = 2,\n        canInteract = function(entity, distance, coords)\n            return canInteractWithDoor(entity, coords, 3)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 3)\n        end\n    },\n    {\n        name = 'ox_target:bonnet',\n        icon = 'fa-solid fa-car',\n        label = locale('toggle_hood'),\n        offset = vec3(0.5, 1, 0.5),\n        distance = 2,\n        canInteract = function(entity, distance, coords)\n            return canInteractWithDoor(entity, coords, 4, true)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 4)\n        end\n    },\n    {\n        name = 'ox_target:trunk',\n        icon = 'fa-solid fa-car-rear',\n        label = locale('toggle_trunk'),\n        offset = vec3(0.5, 0, 0.5),\n        distance = 2,\n        canInteract = function(entity, distance, coords, name)\n            return canInteractWithDoor(entity, coords, 5, true)\n        end,\n        onSelect = function(data)\n            onSelectDoor(data, 5)\n        end\n    }\n})\n"
  },
  {
    "path": "client/framework/esx.lua",
    "content": "local ESX = exports.es_extended:getSharedObject()\nlocal utils = require 'client.utils'\nlocal groups = { 'job', 'job2' }\nlocal playerGroups = {}\nlocal playerItems = utils.getItems()\nlocal usingOxInventory = utils.hasExport('ox_inventory.Items')\n\nlocal function setPlayerData(playerData)\n    table.wipe(playerGroups)\n    table.wipe(playerItems)\n\n    for i = 1, #groups do\n        local group = groups[i]\n        local data = playerData[group]\n\n        if data then\n            playerGroups[group] = data\n        end\n    end\n\n    if usingOxInventory or not playerData.inventory then return end\n\n    for _, v in pairs(playerData.inventory) do\n        if v.count > 0 then\n            playerItems[v.name] = v.count\n        end\n    end\nend\n\nif ESX.PlayerLoaded then\n    setPlayerData(ESX.PlayerData)\nend\n\nRegisterNetEvent('esx:playerLoaded', function(data)\n    if source == '' then return end\n    setPlayerData(data)\nend)\n\nRegisterNetEvent('esx:setJob', function(job)\n    if source == '' then return end\n    playerGroups.job = job\nend)\n\nRegisterNetEvent('esx:setJob2', function(job)\n    if source == '' then return end\n    playerGroups.job2 = job\nend)\n\nRegisterNetEvent('esx:addInventoryItem', function(name, count)\n    playerItems[name] = count\nend)\n\nRegisterNetEvent('esx:removeInventoryItem', function(name, count)\n    playerItems[name] = count\nend)\n\n---@diagnostic disable-next-line: duplicate-set-field\nfunction utils.hasPlayerGotGroup(filter)\n    local _type = type(filter)\n    for i = 1, #groups do\n        local group = groups[i]\n\n        if _type == 'string' then\n            local data = playerGroups[group]\n\n            if filter == data?.name then\n                return true\n            end\n        elseif _type == 'table' then\n            local tabletype = table.type(filter)\n\n            if tabletype == 'hash' then\n                for name, grade in pairs(filter) do\n                    local data = playerGroups[group]\n\n                    if data?.name == name and grade <= data.grade then\n                        return true\n                    end\n                end\n            elseif tabletype == 'array' then\n                for j = 1, #filter do\n                    local name = filter[j]\n                    local data = playerGroups[group]\n\n                    if data?.name == name then\n                        return true\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "client/framework/nd.lua",
    "content": "local NDCore = exports[\"ND_Core\"]\n\nlocal playerGroups = NDCore:getPlayer()?.groups or {}\n\nRegisterNetEvent(\"ND:characterLoaded\", function(data)\n    playerGroups = data.groups\nend)\n\nRegisterNetEvent(\"ND:updateCharacter\", function(data)\n    if source == '' then return end\n    playerGroups = data.groups or {}\nend)\n\nlocal utils = require 'client.utils'\n\n---@diagnostic disable-next-line: duplicate-set-field\nfunction utils.hasPlayerGotGroup(filter)\n    local _type = type(filter)\n\n    if _type == 'string' then\n        local group = playerGroups[filter]\n\n        if group then\n            return true\n        end\n    elseif _type == 'table' then\n        local tabletype = table.type(filter)\n\n        if tabletype == 'hash' then\n            for name, grade in pairs(filter) do\n                local playerGrade = playerGroups[name]?.rank\n\n                if playerGrade and grade <= playerGrade then\n                    return true\n                end\n            end\n        elseif tabletype == 'array' then\n            for i = 1, #filter do\n                local name = filter[i]\n                local group = playerGroups[name]\n\n                if group then\n                    return true\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "client/framework/ox.lua",
    "content": "if not lib.checkDependency('ox_core', '0.21.3', true) then return end\n\nlocal Ox = require '@ox_core.lib.init' --[[@as OxClient]]\nlocal utils = require 'client.utils'\nlocal player = Ox.GetPlayer()\n\n---@diagnostic disable-next-line: duplicate-set-field\nfunction utils.hasPlayerGotGroup(filter)\n    return player.getGroup(filter)\nend\n"
  },
  {
    "path": "client/framework/qbx.lua",
    "content": "if not lib.checkDependency('qbx_core', '1.18.0', true) then return end\n\nlocal QBX = exports.qbx_core\nlocal utils = require 'client.utils'\n\n---@diagnostic disable-next-line: duplicate-set-field\nfunction utils.hasPlayerGotGroup(filter)\n    return QBX:HasGroup(filter)\nend\n"
  },
  {
    "path": "client/main.lua",
    "content": "if not lib.checkDependency('ox_lib', '3.30.0', true) then return end\n\nlib.locale()\n\nlocal utils = require 'client.utils'\nlocal state = require 'client.state'\nlocal options = require 'client.api'.getTargetOptions()\n\nrequire 'client.debug'\nrequire 'client.defaults'\nrequire 'client.compat.qtarget'\n\nlocal SendNuiMessage = SendNuiMessage\nlocal GetEntityCoords = GetEntityCoords\nlocal GetEntityType = GetEntityType\nlocal HasEntityClearLosToEntity = HasEntityClearLosToEntity\nlocal GetEntityBoneIndexByName = GetEntityBoneIndexByName\nlocal GetEntityBonePosition_2 = GetEntityBonePosition_2\nlocal GetEntityModel = GetEntityModel\nlocal IsDisabledControlJustPressed = IsDisabledControlJustPressed\nlocal DisableControlAction = DisableControlAction\nlocal DisablePlayerFiring = DisablePlayerFiring\nlocal GetModelDimensions = GetModelDimensions\nlocal GetOffsetFromEntityInWorldCoords = GetOffsetFromEntityInWorldCoords\nlocal currentTarget = {}\nlocal currentMenu\nlocal menuChanged\nlocal menuHistory = {}\nlocal nearbyZones\n\n-- Toggle ox_target, instead of holding the hotkey\nlocal toggleHotkey = GetConvarInt('ox_target:toggleHotkey', 0) == 1\nlocal mouseButton = GetConvarInt('ox_target:leftClick', 1) == 1 and 24 or 25\nlocal debug = GetConvarInt('ox_target:debug', 0) == 1\nlocal vec0 = vec3(0, 0, 0)\n\n---@param option OxTargetOption\n---@param distance number\n---@param endCoords vector3\n---@param entityHit? number\n---@param entityType? number\n---@param entityModel? number | false\nlocal function shouldHide(option, distance, endCoords, entityHit, entityType, entityModel)\n    if option.menuName ~= currentMenu then\n        return true\n    end\n\n    if distance > (option.distance or 7) then\n        return true\n    end\n\n    if option.groups and not utils.hasPlayerGotGroup(option.groups) then\n        return true\n    end\n\n    if option.items and not utils.hasPlayerGotItems(option.items, option.anyItem) then\n        return true\n    end\n\n    local bone = entityModel and option.bones or nil\n\n    if bone then\n        ---@cast entityHit number\n        ---@cast entityType number\n        ---@cast entityModel number\n\n        local _type = type(bone)\n\n        if _type == 'string' then\n            local boneId = GetEntityBoneIndexByName(entityHit, bone)\n\n            if boneId ~= -1 and #(endCoords - GetEntityBonePosition_2(entityHit, boneId)) <= 2 then\n                bone = boneId\n            else\n                return true\n            end\n        elseif _type == 'table' then\n            local closestBone, boneDistance\n\n            for j = 1, #bone do\n                local boneId = GetEntityBoneIndexByName(entityHit, bone[j])\n\n                if boneId ~= -1 then\n                    local dist = #(endCoords - GetEntityBonePosition_2(entityHit, boneId))\n\n                    if dist <= (boneDistance or 1) then\n                        closestBone = boneId\n                        boneDistance = dist\n                    end\n                end\n            end\n\n            if closestBone then\n                bone = closestBone\n            else\n                return true\n            end\n        end\n    end\n\n    local offset = entityModel and option.offset or nil\n\n    if offset then\n        ---@cast entityHit number\n        ---@cast entityType number\n        ---@cast entityModel number\n\n        if not option.absoluteOffset then\n            local min, max = GetModelDimensions(entityModel)\n            offset = (max - min) * offset + min\n        end\n\n        offset = GetOffsetFromEntityInWorldCoords(entityHit, offset.x, offset.y, offset.z)\n\n        if #(endCoords - offset) > (option.offsetSize or 1) then\n            return true\n        end\n    end\n\n    if option.canInteract then\n        local success, resp = pcall(option.canInteract, entityHit, distance, endCoords, option.name, bone)\n        return not success or not resp\n    end\nend\n\nlocal function startTargeting()\n    if state.isDisabled() or state.isActive() or IsNuiFocused() or IsPauseMenuActive() then return end\n\n    state.setActive(true)\n\n    local flag = 511\n    local hit, entityHit, endCoords, distance, lastEntity, entityType, entityModel, hasTarget, zonesChanged\n    local zones = {}\n\n    CreateThread(function()\n        local dict, texture = utils.getTexture()\n        local lastCoords\n\n        while state.isActive() do\n            lastCoords = endCoords == vec0 and lastCoords or endCoords or vec0\n\n            if debug then\n                DrawMarker(28, lastCoords.x, lastCoords.y, lastCoords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.2,\n                    0.2,\n                    ---@diagnostic disable-next-line: param-type-mismatch\n                    255, 42, 24, 100, false, false, 0, true, false, false, false)\n            end\n\n            utils.drawZoneSprites(dict, texture)\n            DisablePlayerFiring(cache.playerId, true)\n            DisableControlAction(0, 25, true)\n            DisableControlAction(0, 140, true)\n            DisableControlAction(0, 141, true)\n            DisableControlAction(0, 142, true)\n\n            if state.isNuiFocused() then\n                DisableControlAction(0, 1, true)\n                DisableControlAction(0, 2, true)\n\n                if not hasTarget or options and IsDisabledControlJustPressed(0, 25) then\n                    state.setNuiFocus(false, false)\n                end\n            elseif hasTarget and IsDisabledControlJustPressed(0, mouseButton) then\n                state.setNuiFocus(true, true)\n            end\n\n            Wait(0)\n        end\n\n        SetStreamedTextureDictAsNoLongerNeeded(dict)\n    end)\n\n    while state.isActive() do\n        if not state.isNuiFocused() and lib.progressActive() then\n            state.setActive(false)\n            break\n        end\n\n        local playerCoords = GetEntityCoords(cache.ped)\n        hit, entityHit, endCoords = lib.raycast.fromCamera(flag, 4, 20)\n        distance = #(playerCoords - endCoords)\n\n        if entityHit ~= 0 and entityHit ~= lastEntity then\n            local success, result = pcall(GetEntityType, entityHit)\n            entityType = success and result or 0\n        end\n\n        if entityType == 0 then\n            local _flag = flag == 511 and 26 or 511\n            local _hit, _entityHit, _endCoords = lib.raycast.fromCamera(_flag, 4, 20)\n            local _distance = #(playerCoords - _endCoords)\n\n            if _distance < distance then\n                flag, hit, entityHit, endCoords, distance = _flag, _hit, _entityHit, _endCoords, _distance\n\n                if entityHit ~= 0 then\n                    local success, result = pcall(GetEntityType, entityHit)\n                    entityType = success and result or 0\n                end\n            end\n        end\n\n        nearbyZones, zonesChanged = utils.getNearbyZones(endCoords)\n\n        local entityChanged = entityHit ~= lastEntity\n        local newOptions = (zonesChanged or entityChanged or menuChanged) and true\n\n        if entityHit > 0 and entityChanged then\n            currentMenu = nil\n\n            if flag ~= 511 then\n                entityHit = HasEntityClearLosToEntity(entityHit, cache.ped, 7) and entityHit or 0\n            end\n\n            if lastEntity ~= entityHit and debug then\n                if lastEntity then\n                    SetEntityDrawOutline(lastEntity, false)\n                end\n\n                if entityType ~= 1 then\n                    SetEntityDrawOutline(entityHit, true)\n                end\n            end\n\n            if entityHit > 0 then\n                local success, result = pcall(GetEntityModel, entityHit)\n                entityModel = success and result\n            end\n        end\n\n        if hasTarget and (zonesChanged or entityChanged and hasTarget > 1) then\n            SendNuiMessage('{\"event\": \"leftTarget\"}')\n\n            if entityChanged then options:wipe() end\n\n            if debug and lastEntity > 0 then SetEntityDrawOutline(lastEntity, false) end\n\n            hasTarget = false\n        end\n\n        if newOptions and entityModel and entityHit > 0 then\n            options:set(entityHit, entityType, entityModel)\n        end\n\n        lastEntity = entityHit\n        currentTarget.entity = entityHit\n        currentTarget.coords = endCoords\n        currentTarget.distance = distance\n        local hidden = 0\n        local totalOptions = 0\n\n        for k, v in pairs(options) do\n            local optionCount = #v\n            local dist = k == '__global' and 0 or distance\n            totalOptions += optionCount\n\n            for i = 1, optionCount do\n                local option = v[i]\n                local hide = shouldHide(option, dist, endCoords, entityHit, entityType, entityModel)\n\n                if option.hide ~= hide then\n                    option.hide = hide\n                    newOptions = true\n                end\n\n                if hide then hidden += 1 end\n            end\n        end\n\n        if zonesChanged then table.wipe(zones) end\n\n        for i = 1, #nearbyZones do\n            local zoneOptions = nearbyZones[i].options\n            local optionCount = #zoneOptions\n            totalOptions += optionCount\n            zones[i] = zoneOptions\n\n            for j = 1, optionCount do\n                local option = zoneOptions[j]\n                local hide = shouldHide(option, distance, endCoords, entityHit)\n\n                if option.hide ~= hide then\n                    option.hide = hide\n                    newOptions = true\n                end\n\n                if hide then hidden += 1 end\n            end\n        end\n\n        if newOptions then\n            if hasTarget == 1 and (totalOptions - hidden) > 1 then\n                hasTarget = true\n            end\n\n            if hasTarget and hidden == totalOptions then\n                if hasTarget and hasTarget ~= 1 then\n                    hasTarget = false\n                    SendNuiMessage('{\"event\": \"leftTarget\"}')\n                end\n            elseif menuChanged or hasTarget ~= 1 and hidden ~= totalOptions then\n                hasTarget = options.size\n\n                if currentMenu and options.__global[1]?.name ~= 'builtin:goback' then\n                    table.insert(options.__global, 1,\n                        {\n                            icon = 'fa-solid fa-circle-chevron-left',\n                            label = locale('go_back'),\n                            name = 'builtin:goback',\n                            menuName = currentMenu,\n                            openMenu = 'home'\n                        })\n                end\n\n                SendNuiMessage(json.encode({\n                    event = 'setTarget',\n                    options = options,\n                    zones = zones,\n                }, { sort_keys = true }))\n            end\n\n            menuChanged = false\n        end\n\n        if toggleHotkey and IsPauseMenuActive() then\n            state.setActive(false)\n        end\n\n        if not hasTarget or hasTarget == 1 then\n            flag = flag == 511 and 26 or 511\n        end\n\n        Wait(hit and 50 or 100)\n    end\n\n    if lastEntity and debug then\n        SetEntityDrawOutline(lastEntity, false)\n    end\n\n    state.setNuiFocus(false)\n    SendNuiMessage('{\"event\": \"visible\", \"state\": false}')\n    table.wipe(currentTarget)\n    options:wipe()\n\n    if nearbyZones then table.wipe(nearbyZones) end\nend\n\ndo\n    ---@type KeybindProps\n    local keybind = {\n        name = 'ox_target',\n        defaultKey = GetConvar('ox_target:defaultHotkey', 'LMENU'),\n        defaultMapper = 'keyboard',\n        description = locale('toggle_targeting'),\n    }\n\n    if toggleHotkey then\n        function keybind:onPressed()\n            if state.isActive() then\n                return state.setActive(false)\n            end\n\n            return startTargeting()\n        end\n    else\n        keybind.onPressed = startTargeting\n\n        function keybind:onReleased()\n            state.setActive(false)\n        end\n    end\n\n    lib.addKeybind(keybind)\nend\n\n---@generic T\n---@param option T\n---@param server? boolean\n---@return T\nlocal function getResponse(option, server)\n    local response = table.clone(option)\n    response.entity = currentTarget.entity\n    response.zone = currentTarget.zone\n    response.coords = currentTarget.coords\n    response.distance = currentTarget.distance\n\n    if server then\n        response.entity = response.entity ~= 0 and NetworkGetEntityIsNetworked(response.entity) and\n            NetworkGetNetworkIdFromEntity(response.entity) or 0\n    end\n\n    response.icon = nil\n    response.groups = nil\n    response.items = nil\n    response.canInteract = nil\n    response.onSelect = nil\n    response.export = nil\n    response.event = nil\n    response.serverEvent = nil\n    response.command = nil\n\n    return response\nend\n\nRegisterNUICallback('select', function(data, cb)\n    cb(1)\n\n    local zone = data[3] and nearbyZones[data[3]]\n\n    ---@type OxTargetOption?\n    local option = zone and zone.options[data[2]] or options[data[1]][data[2]]\n\n    if option then\n        if option.openMenu then\n            local menuDepth = #menuHistory\n\n            if option.name == 'builtin:goback' then\n                option.menuName = option.openMenu\n                option.openMenu = menuHistory[menuDepth]\n\n                if menuDepth > 0 then\n                    menuHistory[menuDepth] = nil\n                end\n            else\n                menuHistory[menuDepth + 1] = currentMenu\n            end\n\n            menuChanged = true\n            currentMenu = option.openMenu ~= 'home' and option.openMenu or nil\n\n            options:wipe()\n        else\n            state.setNuiFocus(false)\n        end\n\n        currentTarget.zone = zone?.id\n\n        if option.onSelect then\n            option.onSelect(option.qtarget and currentTarget.entity or getResponse(option))\n        elseif option.export then\n            exports[option.resource or zone.resource][option.export](nil, getResponse(option))\n        elseif option.event then\n            TriggerEvent(option.event, getResponse(option))\n        elseif option.serverEvent then\n            TriggerServerEvent(option.serverEvent, getResponse(option, true))\n        elseif option.command then\n            ExecuteCommand(option.command)\n        end\n\n        if option.menuName == 'home' then return end\n    end\n\n    if not option?.openMenu and IsNuiFocused() then\n        state.setActive(false)\n    end\nend)\n"
  },
  {
    "path": "client/state.lua",
    "content": "local state = {}\n\nlocal isActive = false\n\n---@return boolean\nfunction state.isActive()\n    return isActive\nend\n\n---@param value boolean\nfunction state.setActive(value)\n    isActive = value\n\n    if value then\n        SendNuiMessage('{\"event\": \"visible\", \"state\": true}')\n    end\nend\n\nlocal nuiFocus = false\n\n---@return boolean\nfunction state.isNuiFocused()\n    return nuiFocus\nend\n\n---@param value boolean\nfunction state.setNuiFocus(value, cursor)\n    if value then SetCursorLocation(0.5, 0.5) end\n\n    nuiFocus = value\n    SetNuiFocus(value, cursor or false)\n    SetNuiFocusKeepInput(value)\nend\n\nlocal isDisabled = false\n\n---@return boolean\nfunction state.isDisabled()\n    return isDisabled\nend\n\n---@param value boolean\nfunction state.setDisabled(value)\n    isDisabled = value\nend\n\nreturn state\n"
  },
  {
    "path": "client/utils.lua",
    "content": "local utils = {}\n\nlocal GetWorldCoordFromScreenCoord = GetWorldCoordFromScreenCoord\nlocal StartShapeTestLosProbe = StartShapeTestLosProbe\nlocal GetShapeTestResultIncludingMaterial = GetShapeTestResultIncludingMaterial\n\n---@param flag number\n---@return boolean hit\n---@return number entityHit\n---@return vector3 endCoords\n---@return vector3 surfaceNormal\n---@return number materialHash\nfunction utils.raycastFromCamera(flag)\n    local coords, normal = GetWorldCoordFromScreenCoord(0.5, 0.5)\n    local destination = coords + normal * 10\n    local handle = StartShapeTestLosProbe(coords.x, coords.y, coords.z, destination.x, destination.y, destination.z,\n        flag, cache.ped, 4)\n\n    while true do\n        Wait(0)\n        local retval, hit, endCoords, surfaceNormal, materialHash, entityHit = GetShapeTestResultIncludingMaterial(\n        handle)\n\n        if retval ~= 1 then\n            ---@diagnostic disable-next-line: return-type-mismatch\n            return hit, entityHit, endCoords, surfaceNormal, materialHash\n        end\n    end\nend\n\nfunction utils.getTexture()\n    return lib.requestStreamedTextureDict('shared'), 'emptydot_32'\nend\n\n-- SetDrawOrigin is limited to 32 calls per frame. Set as 0 to disable.\nlocal drawZoneSprites = GetConvarInt('ox_target:drawSprite', 24)\nlocal SetDrawOrigin = SetDrawOrigin\nlocal DrawSprite = DrawSprite\nlocal ClearDrawOrigin = ClearDrawOrigin\nlocal colour = vector(155, 155, 155, 175)\nlocal hover = vector(98, 135, 236, 255)\nlocal currentZones = {}\nlocal previousZones = {}\nlocal drawZones = {}\nlocal drawN = 0\nlocal width = 0.02\nlocal height = width * GetAspectRatio(false)\n\nif drawZoneSprites == 0 then drawZoneSprites = -1 end\n\n---@param coords vector3\n---@return CZone[], boolean\nfunction utils.getNearbyZones(coords)\n    if not Zones then return currentZones, false end\n\n    local n = 0\n    local nearbyZones = lib.zones.getNearbyZones()\n    drawN = 0\n    previousZones, currentZones = currentZones, table.wipe(previousZones)\n\n    for i = 1, #nearbyZones do\n        local zone = nearbyZones[i]\n        local contains = zone:contains(coords)\n\n        if contains then\n            n += 1\n            currentZones[n] = zone\n        end\n\n        if drawN <= drawZoneSprites and zone.drawSprite ~= false and (contains or (zone.distance or 7) < 7) then\n            drawN += 1\n            drawZones[drawN] = zone\n            zone.colour = contains and hover or nil\n        end\n    end\n\n    local previousN = #previousZones\n\n    if n ~= previousN then\n        return currentZones, true\n    end\n\n    if n > 0 then\n        for i = 1, n do\n            local zoneA = currentZones[i]\n            local found = false\n\n            for j = 1, previousN do\n                local zoneB = previousZones[j]\n\n                if zoneA == zoneB then\n                    found = true\n                    break\n                end\n            end\n\n            if not found then\n                return currentZones, true\n            end\n        end\n    end\n\n    return currentZones, false\nend\n\nfunction utils.drawZoneSprites(dict, texture)\n    if drawN == 0 then return end\n\n    for i = 1, drawN do\n        local zone = drawZones[i]\n        local spriteColour = zone.colour or colour\n\n        if zone.drawSprite ~= false then\n            SetDrawOrigin(zone.coords.x, zone.coords.y, zone.coords.z)\n            DrawSprite(dict, texture, 0, 0, width, height, 0, spriteColour.r, spriteColour.g, spriteColour.b,\n                spriteColour.a)\n        end\n    end\n\n    ClearDrawOrigin()\nend\n\nfunction utils.hasExport(export)\n    local resource, exportName = string.strsplit('.', export)\n\n    return pcall(function()\n        return exports[resource][exportName]\n    end)\nend\n\nlocal playerItems = {}\n\nfunction utils.getItems()\n    return playerItems\nend\n\n---@param filter string | string[] | table<string, number>\n---@param hasAny boolean?\n---@return boolean\nfunction utils.hasPlayerGotItems(filter, hasAny)\n    if not playerItems then return true end\n\n    local _type = type(filter)\n\n    if _type == 'string' then\n        return (playerItems[filter] or 0) > 0\n    elseif _type == 'table' then\n        local tabletype = table.type(filter)\n\n        if tabletype == 'hash' then\n            for name, amount in pairs(filter) do\n                local hasItem = (playerItems[name] or 0) >= amount\n\n                if hasAny then\n                    if hasItem then return true end\n                elseif not hasItem then\n                    return false\n                end\n            end\n        elseif tabletype == 'array' then\n            for i = 1, #filter do\n                local hasItem = (playerItems[filter[i]] or 0) > 0\n\n                if hasAny then\n                    if hasItem then return true end\n                elseif not hasItem then\n                    return false\n                end\n            end\n        end\n    end\n\n    return not hasAny\nend\n\n---stub\n---@param filter string | string[] | table<string, number>\n---@return boolean\nfunction utils.hasPlayerGotGroup(filter)\n    return true\nend\n\nSetTimeout(0, function()\n    if utils.hasExport('ox_inventory.Items') then\n        setmetatable(playerItems, {\n            __index = function(self, index)\n                self[index] = exports.ox_inventory:Search('count', index) or 0\n                return self[index]\n            end\n        })\n\n        AddEventHandler('ox_inventory:itemCount', function(name, count)\n            playerItems[name] = count\n        end)\n    end\n\n    if utils.hasExport('ox_core.GetPlayer') then\n        require 'client.framework.ox'\n    elseif utils.hasExport('es_extended.getSharedObject') then\n        require 'client.framework.esx'\n    elseif utils.hasExport('qbx_core.HasGroup') then\n        require 'client.framework.qbx'\n    elseif utils.hasExport('ND_Core.getPlayer') then\n        require 'client.framework.nd'\n    end\nend)\n\nfunction utils.warn(msg)\n    local trace = Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString())\n    local _, _, src = string.strsplit('\\n', trace, 4)\n\n    warn(('%s ^0%s\\n'):format(msg, src:gsub(\".-%(\", '(')))\nend\n\nreturn utils\n"
  },
  {
    "path": "fxmanifest.lua",
    "content": "-- FX Information\nfx_version 'cerulean'\nuse_experimental_fxv2_oal 'yes'\nnui_callback_strict_mode 'true'\nlua54 'yes'\ngame 'gta5'\n\n-- Resource Information\nname 'ox_target'\nauthor 'Overextended'\nversion '1.18.1'\nrepository 'https://github.com/overextended/ox_target'\ndescription ''\n\n-- Manifest\nui_page 'web/index.html'\n\nshared_scripts {\n    '@ox_lib/init.lua',\n}\n\nclient_scripts {\n    'client/main.lua',\n}\n\nserver_scripts {\n    'server/main.lua'\n}\n\nfiles {\n    'web/**',\n    'locales/*.json',\n    'client/api.lua',\n    'client/utils.lua',\n    'client/state.lua',\n    'client/debug.lua',\n    'client/defaults.lua',\n    'client/framework/nd.lua',\n    'client/framework/ox.lua',\n    'client/framework/esx.lua',\n    'client/framework/qbx.lua',\n    'client/compat/qtarget.lua',\n}\n\nprovide 'qtarget'\n\ndependency 'ox_lib'\n"
  },
  {
    "path": "locales/cs.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Přední dveře řidiče\",\n  \"toggle_front_passenger_door\": \"Přední dveře spolujezdce\",\n  \"toggle_rear_driver_door\": \"Zadní dveře řidiče\",\n  \"toggle_rear_passenger_door\": \"Zadní dveře spolujezdce\",\n  \"toggle_hood\": \"Kapota\",\n  \"toggle_trunk\": \"Kufr\",\n  \"debug_box\": \"(Debug) Box\",\n  \"debug_sphere\": \"(Debug) Koule\",\n  \"debug_police_car\": \"Policejní auto\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Vozidlo\",\n  \"debug_object\": \"(Debug) Objekt\",\n  \"toggle_targeting\": \"Výběr zaměření\",\n  \"go_back\": \"Zpět\"\n}\n"
  },
  {
    "path": "locales/da.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Skift forreste førerdør\",\n  \"toggle_front_passenger_door\": \"Skift forreste passagerdør\",\n  \"toggle_rear_driver_door\": \"Skift bageste førerdør\",\n  \"toggle_rear_passenger_door\": \"Skift bageste passagerdør\",\n  \"toggle_hood\": \"Skift motorhjelm\",\n  \"toggle_trunk\": \"Skift bagagerum\",\n  \"debug_box\": \"(Debug) Boks\",\n  \"debug_sphere\": \"(Debug) Kugle\",\n  \"debug_police_car\": \"Politibil\",\n  \"debug_ped\": \"(Debug) Person\",\n  \"debug_vehicle\": \"(Debug) Køretøj\",\n  \"debug_object\": \"(Debug) Objekt\",\n  \"debug_global\": \"(Debug) Global\",\n  \"toggle_targeting\": \"Skift målretning\",\n  \"go_back\": \"Gå tilbage\"\n}\n"
  },
  {
    "path": "locales/de.json",
    "content": "{\r\n  \"toggle_front_driver_door\": \"Vordere Fahrertür umschalten\",\r\n  \"toggle_front_passenger_door\": \"Vordere Beifahrertür umschalten\",\r\n  \"toggle_rear_driver_door\": \"Hintere Fahrertür umschalten\",\r\n  \"toggle_rear_passenger_door\": \"Hintere Beifahrertür umschalten\",\r\n  \"toggle_hood\": \"Motorhaube umschalten\",\r\n  \"toggle_trunk\": \"Kofferraum umschalten\",\r\n  \"debug_box\": \"(Debug) Box\",\r\n  \"debug_sphere\": \"(Debug) Sphäre\",\r\n  \"debug_police_car\": \"Polizeiauto\",\r\n  \"debug_ped\": \"(Debug) Ped\",\r\n  \"debug_vehicle\": \"(Debug) Fahrzeug\",\r\n  \"debug_object\": \"(Debug) Objekt\",\r\n  \"toggle_targeting\": \"Zielen umschalten\"\r\n}\r\n"
  },
  {
    "path": "locales/en.json",
    "content": "{\r\n  \"toggle_front_driver_door\": \"Toggle front driver door\",\r\n  \"toggle_front_passenger_door\": \"Toggle front passenger door\",\r\n  \"toggle_rear_driver_door\": \"Toggle rear driver door\",\r\n  \"toggle_rear_passenger_door\": \"Toggle rear passenger door\",\r\n  \"toggle_hood\": \"Toggle hood\",\r\n  \"toggle_trunk\": \"Toggle trunk\",\r\n  \"debug_box\": \"(Debug) Box\",\r\n  \"debug_sphere\": \"(Debug) Sphere\",\r\n  \"debug_police_car\": \"Police car\",\r\n  \"debug_ped\": \"(Debug) Ped\",\r\n  \"debug_vehicle\": \"(Debug) Vehicle\",\r\n  \"debug_object\": \"(Debug) Object\",\r\n  \"debug_global\": \"(Debug) Global\",\r\n  \"toggle_targeting\": \"Toggle targeting\",\r\n  \"go_back\": \"Go back\"\r\n}\r\n"
  },
  {
    "path": "locales/es.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Abrir/Cerrar puerta delantera del conductor\",\n  \"toggle_front_passenger_door\": \"Abrir/Cerrar puerta delantera del pasajero\",\n  \"toggle_rear_driver_door\": \"Abrir/Cerrar puerta trasera del conductor\",\n  \"toggle_rear_passenger_door\": \"Abrir/Cerrar puerta trasera del pasajero\",\n  \"toggle_hood\": \"Abrir/Cerrar capó\",\n  \"toggle_trunk\": \"Abrir/Cerrar maletero\",\n  \"debug_box\": \"(Debug) Caja\",\n  \"debug_sphere\": \"(Debug) Esfera\",\n  \"debug_police_car\": \"Coche de policía\",\n  \"debug_ped\": \"(Debug) Peatón\",\n  \"debug_vehicle\": \"(Debug) Vehículo\",\n  \"debug_object\": \"(Debug) Objeto\",\n  \"toggle_targeting\": \"Activar/Desactivar apuntado\"\n}\n"
  },
  {
    "path": "locales/et.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Ava/sulge eesuks\",\n  \"toggle_front_passenger_door\": \"Ava/sulge eesuks\",\n  \"toggle_rear_driver_door\": \"Ava/sulge tagauks\",\n  \"toggle_rear_passenger_door\": \"Ava/sulge tagauks\",\n  \"toggle_hood\": \"Ava/sulge kapott\",\n  \"toggle_trunk\": \"Ava/sulge pagasiruum\",\n  \"debug_box\": \"(Debug) Kast\",\n  \"debug_sphere\": \"(Debug) Kera\",\n  \"debug_police_car\": \"Politseiauto\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Sõiduk\",\n  \"debug_object\": \"(Debug) Objekt\",\n  \"toggle_targeting\": \"Näita kolmandat silma\",\n  \"go_back\": \"Mine tagasi\"\n}"
  },
  {
    "path": "locales/fi.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Avaa/Sulje kuljettajan etuovi\",\n  \"toggle_front_passenger_door\": \"Avaa/Sulje repsikan etuovi\",\n  \"toggle_rear_driver_door\": \"Avaa/Sulje kuljettajan takaovi\",\n  \"toggle_rear_passenger_door\": \"Avaa/Sulje repsikan takaovi\",\n  \"toggle_hood\": \"Avaa/Sulje konepelti\",\n  \"toggle_trunk\": \"Avaa/Sulje takakontti\",\n  \"debug_box\": \"(Debug) Laatikko\",\n  \"debug_sphere\": \"(Debug) Ympyrä\",\n  \"debug_police_car\": \"Poliisiauto\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Ajoneuvo\",\n  \"debug_object\": \"(Debug) Objekti\",\n  \"toggle_targeting\": \"Päällä/Pois tähtäys\"\n}\n"
  },
  {
    "path": "locales/fr.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Porte conducteur avant\",\n  \"toggle_front_passenger_door\": \"Porte passager avant\",\n  \"toggle_rear_driver_door\": \"Porte conducteur arrière\",\n  \"toggle_rear_passenger_door\": \"Porte passager arrière\",\n  \"toggle_hood\": \"Capot\",\n  \"toggle_trunk\": \"Coffre\",\n  \"debug_box\": \"(Debug) Box\",\n  \"debug_sphere\": \"(Debug) Sphère\",\n  \"debug_police_car\": \"Véhicule de police\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Véhicule\",\n  \"debug_object\": \"(Debug) Objet\",\n  \"toggle_targeting\": \"Afficher le système d'interaction\",\n  \"go_back\": \"Retour\"\n}\n"
  },
  {
    "path": "locales/hr.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Prednja lijeva vrata\",\n  \"toggle_front_passenger_door\": \"Prednja desna vrata\",\n  \"toggle_rear_driver_door\": \"Zadnja lijeva vrata\",\n  \"toggle_rear_passenger_door\": \"Zadnja desna vrata\",\n  \"toggle_hood\": \"Hauba\",\n  \"toggle_trunk\": \"Gepek\",\n  \"debug_box\": \"(Debug) Kocka (Box)\",\n  \"debug_sphere\": \"(Debug) Sfera (Sphere)\",\n  \"debug_police_car\": \"Policijski auto\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Auto\",\n  \"debug_object\": \"(Debug) Objekt\",\n  \"toggle_targeting\": \"Upali/Ugasi Target sistem\"\n}\n"
  },
  {
    "path": "locales/hu.json",
    "content": "{\r\n\"toggle_front_driver_door\": \"Sofőroldali ajtó nyitása/zárása\",\r\n\"toggle_front_passenger_door\": \"Anyósülés oldali ajtó nyitása/zárása\",\r\n\"toggle_rear_driver_door\": \"Sofőroldali hátsó ajtó nyitása/zárása\",\r\n\"toggle_rear_passenger_door\": \"Anyósülés oldali hátsó ajtó nyitása/zárása\",\r\n\"toggle_hood\": \"Motorháztető nyitása/zárása\",\r\n\"toggle_trunk\": \"Csomagtartó nyitása/zárása\",\r\n\"debug_box\": \"(Hibakeresés) Doboz\",\r\n\"debug_sphere\": \"(Hibakeresés) Gömb\",\r\n\"debug_police_car\": \"(Hibakeresés) Rendőrautó\",\r\n\"debug_ped\": \"(Hibakeresés) Entitás\",\r\n\"debug_vehicle\": \"(Hibakeresés) Jármű\",\r\n\"debug_object\": \"(Hibakeresés) Objekt\",\r\n\"toggle_targeting\": \"Célzó be- és kikapcsolása\",\r\n\"go_back\": \"Vissza\"\r\n}\r\n"
  },
  {
    "path": "locales/id.json",
    "content": "{\r\n  \"toggle_front_driver_door\": \"Tombol pintu pengemudi depan\",\r\n  \"toggle_front_passenger_door\": \"Tombol pintu penumpang depan\",\r\n  \"toggle_rear_driver_door\": \"Tombol pintu pengemudi belakang\",\r\n  \"toggle_rear_passenger_door\": \"Tombol pintu penumpang belakang\",\r\n  \"toggle_hood\": \"Tombol kap\",\r\n  \"toggle_trunk\": \"Tombol bagasi\",\r\n  \"debug_box\": \"(Debug) Kotak\",\r\n  \"debug_sphere\": \"(Debug) Bola\",\r\n  \"debug_police_car\": \"Mobil polisi\",\r\n  \"debug_ped\": \"(Debug) Ped\",\r\n  \"debug_vehicle\": \"(Debug) Kendaraan\",\r\n  \"debug_object\": \"(Debug) Objek\",\r\n  \"toggle_targeting\": \"Tombol penargetan\"\r\n}\r\n"
  },
  {
    "path": "locales/it.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Apri/Chiudi portiera anteriore sinistra\",\n  \"toggle_front_passenger_door\": \"Apri/Chiudi portiera anteriore destra\",\n  \"toggle_rear_driver_door\": \"Apri/Chiudi portiera posteriore sinistra\",\n  \"toggle_rear_passenger_door\": \"Apri/Chiudi portiera posteriore destra\",\n  \"toggle_hood\": \"Apri/Chiudi Cofano\",\n  \"toggle_trunk\": \"Apri/Chiudi Bagagliaio\",\n  \"debug_box\": \"(Debug) Box\",\n  \"debug_sphere\": \"(Debug) Sphere\",\n  \"debug_police_car\": \"Auto Polizia\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Vehicle\",\n  \"debug_object\": \"(Debug) Object\",\n  \"toggle_targeting\": \"Apri/Chiudi targeting\",\n  \"go_back\": \"Indietro\"\n}\n"
  },
  {
    "path": "locales/nl.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Toggle bestuurdersdeur\",\n  \"toggle_front_passenger_door\": \"Toggle bijrijdersdeur\",\n  \"toggle_rear_driver_door\": \"Toggle achterdeur links\",\n  \"toggle_rear_passenger_door\": \"Toggle achterdeur rechts\",\n  \"toggle_hood\": \"Toggle motorkap\",\n  \"toggle_trunk\": \"Toggle kofferbak\",\n  \"debug_box\": \"(Debug) Doos\",\n  \"debug_sphere\": \"(Debug) Bol\",\n  \"debug_police_car\": \"Politie voertuig\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Voertuig\",\n  \"debug_object\": \"(Debug) Object\",\n  \"toggle_targeting\": \"Toggle richten\"\n}\n"
  },
  {
    "path": "locales/no.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Åpne/lukke førerdør\",\n  \"toggle_front_passenger_door\": \"Åpne/lukke passasjerdør\",\n  \"toggle_rear_driver_door\": \"Åpne/lukke venstre bakdør\",\n  \"toggle_rear_passenger_door\": \"Åpne/lukke høyre bakdør\",\n  \"toggle_hood\": \"Åpne/lukke panser\",\n  \"toggle_trunk\": \"Åpne/lukke bagasjerom\",\n  \"toggle_targeting\": \"Sikt\",\n  \"go_back\": \"Tilbake\"\n}\n"
  },
  {
    "path": "locales/pl.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Przednie drzwi kierowcy\",\n  \"toggle_front_passenger_door\": \"Przednie drzwi pasażera\",\n  \"toggle_rear_driver_door\": \"Tylne drzwi kierowcy\",\n  \"toggle_rear_passenger_door\": \"Tylne drzwi pasażera\",\n  \"toggle_hood\": \"Maska\",\n  \"toggle_trunk\": \"Bagażnik\",\n  \"debug_box\": \"(Debug) Blok\",\n  \"debug_sphere\": \"(Debug) Kula\",\n  \"debug_police_car\": \"Radiowóz\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Pojazd\",\n  \"debug_object\": \"(Debug) Obiekt\",\n  \"toggle_targeting\": \"Przełącz celowanie\",\n  \"go_back\": \"Wróć\"\n}\n"
  },
  {
    "path": "locales/pt-br.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Abrir a porta do motorista dianteira\",\n  \"toggle_front_passenger_door\": \"Abrir a porta dianteira do passageiro\",\n  \"toggle_rear_driver_door\": \"Abrir a porta traseira do motorista\",\n  \"toggle_rear_passenger_door\": \"Abrir a porta traseira do passageiro\",\n  \"toggle_hood\": \"Abrir o capô\",\n  \"toggle_trunk\": \"Abrir o porta-malas\",\n  \"debug_box\": \"(Debug) Caixa\",\n  \"debug_sphere\": \"(Debug) Esfera\",\n  \"debug_police_car\": \"Carro de polícia\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Veículo\",\n  \"debug_object\": \"(Debug) Objeto\",\n  \"toggle_targeting\": \"Alternar mira\",\n  \"go_back\": \"Voltar\"\n}\n"
  },
  {
    "path": "locales/pt.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Abrir a porta do motorista dianteira\",\n  \"toggle_front_passenger_door\": \"Abrir a porta dianteira do passageiro\",\n  \"toggle_rear_driver_door\": \"Abrir a porta traseira do motorista\",\n  \"toggle_rear_passenger_door\": \"Abrir a porta traseira do passageiro\",\n  \"toggle_hood\": \"Abrir o capô\",\n  \"toggle_trunk\": \"Abrir o porta-malas\",\n  \"debug_box\": \"(Debug) Caixa\",\n  \"debug_sphere\": \"(Debug) Esfera\",\n  \"debug_police_car\": \"Carro de polícia\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Veículo\",\n  \"debug_object\": \"(Debug) Objeto\",\n  \"toggle_targeting\": \"Alternar mira\",\n  \"go_back\": \"Voltar\"\n}\n"
  },
  {
    "path": "locales/ro.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Comută ușa șoferului\",\n  \"toggle_front_passenger_door\": \"Comută ușa pasagerului\",\n  \"toggle_rear_driver_door\": \"Comută ușa din spatele șoferului\",\n  \"toggle_rear_passenger_door\": \"Comută ușa pasagerului din spate\",\n  \"toggle_hood\": \"Comută capota\",\n  \"toggle_trunk\": \"Comută portbagajul\",\n  \"debug_box\": \"(Debug) Cutie\",\n  \"debug_sphere\": \"(Debug) Sferă\",\n  \"debug_police_car\": \"Mașină de poliție\",\n  \"debug_ped\": \"(Debug) Pedestrian\",\n  \"debug_vehicle\": \"(Debug) Vehicul\",\n  \"debug_object\": \"(Debug) Obiect\",\n  \"debug_global\": \"(Debug) Global\",\n  \"toggle_targeting\": \"Foloseste ochiul\",\n  \"go_back\": \"Întoarce-te\"\n}\n"
  },
  {
    "path": "locales/sl.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Odpri/Zapri leva sprednja vrata\",\n  \"toggle_front_passenger_door\": \"Odpri/Zapri desna sprednja vrata\",\n  \"toggle_rear_driver_door\": \"Odpri/Zapri leva zadnja vrata\",\n  \"toggle_rear_passenger_door\": \"Odpri/Zapri desna zadnja vrata\",\n  \"toggle_hood\": \"Odpri/Zapri pokrov motorja\",\n  \"toggle_trunk\": \"Odpri/Zapri prtljažnik\",\n  \"debug_box\": \"(Debug) Kvadrat\",\n  \"debug_sphere\": \"(Debug) Krog\",\n  \"debug_police_car\": \"Policijsko vozilo\",\n  \"debug_ped\": \"(Debug) Pešec\",\n  \"debug_vehicle\": \"(Debug) Vozilo\",\n  \"debug_object\": \"(Debug) Predmet\",\n  \"toggle_targeting\": \"Vklopi/Izklopi tretje oko\"\n  }\n"
  },
  {
    "path": "locales/sv.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Öppna/stäng främre förardörr\",\n  \"toggle_front_passenger_door\": \"Öppna/stäng främre passagerardörr\",\n  \"toggle_rear_driver_door\": \"Öppna/stäng bakre förardörr\",\n  \"toggle_rear_passenger_door\": \"Öppna/stäng bakre passagerardörr\",\n  \"toggle_hood\": \"Öppna/stäng motorhuv\",\n  \"toggle_trunk\": \"Öppna/stäng bagagelucka\",\n  \"debug_box\": \"(Debug) Låda\",\n  \"debug_sphere\": \"(Debug) Sfär\",\n  \"debug_police_car\": \"Polisbil\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Fordon\",\n  \"debug_object\": \"(Debug) Objekt\",\n  \"debug_global\": \"(Debug) Global\",\n  \"toggle_targeting\": \"Växla Target\",\n  \"go_back\": \"Gå tillbaka\"\n}\n"
  },
  {
    "path": "locales/tr.json",
    "content": "{\n  \"toggle_front_driver_door\": \"Ön sürücü tarafı kapısını aç/kapa\",\n  \"toggle_front_passenger_door\": \"Ön yolcu tarafı kapısını aç/kapa\",\n  \"toggle_rear_driver_door\": \"Arka sürücü tarafı kapısını aç/kapa\",\n  \"toggle_rear_passenger_door\": \"Arka yolcu tarafı kapısını aç/kapa\",\n  \"toggle_hood\": \"Kaputu aç/kapa\",\n  \"toggle_trunk\": \"Bagajı aç/kapa\",\n  \"debug_box\": \"(Debug) Kutu\",\n  \"debug_sphere\": \"(Debug) Küre\",\n  \"debug_police_car\": \"Polis arabası\",\n  \"debug_ped\": \"(Debug) Ped\",\n  \"debug_vehicle\": \"(Debug) Araç\",\n  \"debug_object\": \"(Debug) Nesne\",\n  \"debug_global\": \"(Debug) Genel\",\n  \"toggle_targeting\": \"Hedeflemeyi aç/kapa\",\n  \"go_back\": \"Geri dön\"\n}\n"
  },
  {
    "path": "locales/zh-cn.json",
    "content": "{\n  \"toggle_front_driver_door\": \"开关左前车门\",\n  \"toggle_front_passenger_door\": \"开关左后车门\",\n  \"toggle_rear_driver_door\": \"开关右前车门\",\n  \"toggle_rear_passenger_door\": \"开关右后车门\",\n  \"toggle_hood\": \"打开引擎盖\",\n  \"toggle_trunk\": \"打开后备箱\",\n  \"debug_box\": \"(Debug) 矩形区域\",\n  \"debug_sphere\": \"(Debug) 球形区域\",\n  \"debug_police_car\": \"警车\",\n  \"debug_ped\": \"(Debug) 角色实体\",\n  \"debug_vehicle\": \"(Debug) 车辆\",\n  \"debug_object\": \"(Debug) 物体\",\n  \"debug_global\": \"(Debug) 全局对象\",\n  \"toggle_targeting\": \"交互菜单\",\n  \"go_back\": \"返回\"\n}\n"
  },
  {
    "path": "locales/zh-tw.json",
    "content": "{\r\n  \"toggle_front_driver_door\": \"開關左前車門\",\r\n  \"toggle_front_passenger_door\": \"開關左後車門\",\r\n  \"toggle_rear_driver_door\": \"開關右前車門\",\r\n  \"toggle_rear_passenger_door\": \"開關右後車門\",\r\n  \"toggle_hood\": \"打開引擎蓋\",\r\n  \"toggle_trunk\": \"打開後備箱\",\r\n  \"debug_box\": \"(Debug) 矩形區域\",\r\n  \"debug_sphere\": \"(Debug) 球形區域\",\r\n  \"debug_police_car\": \"警車\",\r\n  \"debug_ped\": \"(Debug) 角色實體\",\r\n  \"debug_vehicle\": \"(Debug) 車輛\",\r\n  \"debug_object\": \"(Debug) 物體\",\r\n  \"debug_global\": \"(Debug) 全局對象\",\r\n  \"toggle_targeting\": \"交互菜單\",\r\n  \"go_back\": \"返回\"\r\n}\r\n"
  },
  {
    "path": "server/main.lua",
    "content": "lib.versionCheck('overextended/ox_target')\n\nif not lib.checkDependency('ox_lib', '3.30.0', true) then return end\n\n---@type table<number, EntityInterface>\nlocal entityStates = {}\n\n---@param netId number\nRegisterNetEvent('ox_target:setEntityHasOptions', function(netId)\n    local entity = Entity(NetworkGetEntityFromNetworkId(netId))\n    entity.state.hasTargetOptions = true\n    entityStates[netId] = entity\nend)\n\n---@param netId number\n---@param door number\nRegisterNetEvent('ox_target:toggleEntityDoor', function(netId, door)\n    local entity = NetworkGetEntityFromNetworkId(netId)\n    if not DoesEntityExist(entity) then return end\n\n    local owner = NetworkGetEntityOwner(entity)\n    TriggerClientEvent('ox_target:toggleEntityDoor', owner, netId, door)\nend)\n\nCreateThread(function()\n    local arr = {}\n    local num = 0\n\n    while true do\n        Wait(10000)\n\n        for netId, entity in pairs(entityStates) do\n            if not DoesEntityExist(entity.__data) or not entity.state.hasTargetOptions then\n                entityStates[netId] = nil\n                num += 1\n\n                arr[num] = netId\n            end\n        end\n\n        if num > 0 then\n            TriggerClientEvent('ox_target:removeEntity', -1, arr)\n            table.wipe(arr)\n\n            num = 0\n        end\n    end\nend)\n"
  },
  {
    "path": "web/index.html",
    "content": "<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css\" />\n    <link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\" />\n  </head>\n  <body>\n    <div id=\"eye\">\n      <svg id=\"eyeSvg\" xmlns=\"http://www.w3.org/2000/svg\" height=\"36px\" viewBox=\"0 0 24 24\" width=\"36px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><path d=\"M12 4C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/></svg>\n    </div>\n    <div id=\"options-wrapper\"></div>\n    <script defer type=\"module\" src=\"js/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "web/js/createOptions.js",
    "content": "import { fetchNui } from \"./fetchNui.js\";\n\nconst optionsWrapper = document.getElementById(\"options-wrapper\");\n\nfunction onClick() {\n  // when nuifocus is disabled after a click, the hover event is never released\n  this.style.pointerEvents = \"none\";\n\n  fetchNui(\"select\", [this.targetType, this.targetId, this.zoneId]);\n  // is there a better way to handle this? probably\n  setTimeout(() => (this.style.pointerEvents = \"auto\"), 100);\n}\n\nexport function createOptions(type, data, id, zoneId) {\n  if (data.hide) return;\n\n  const option = document.createElement(\"div\");\n  const iconElement = `<i class=\"fa-fw ${data.icon} option-icon\" ${\n    data.iconColor ? `style = color:${data.iconColor} !important` : null\n  }\"></i>`;\n\n  option.innerHTML = `${iconElement}<p class=\"option-label\">${data.label}</p>`;\n  option.className = \"option-container\";\n  option.targetType = type;\n  option.targetId = id;\n  option.zoneId = zoneId;\n\n  option.addEventListener(\"click\", onClick);\n  optionsWrapper.appendChild(option);\n}\n"
  },
  {
    "path": "web/js/fetchNui.js",
    "content": "const resource = GetParentResourceName();\n\nexport async function fetchNui(eventName, data) {\n  const resp = await fetch(`https://${resource}/${eventName}`, {\n    method: 'post',\n    headers: {\n      'Content-Type': 'application/json; charset=UTF-8',\n    },\n    body: JSON.stringify(data),\n  });\n\n  return await resp.json();\n}\n"
  },
  {
    "path": "web/js/main.js",
    "content": "import { createOptions } from \"./createOptions.js\";\n\nconst optionsWrapper = document.getElementById(\"options-wrapper\");\nconst body = document.body;\nconst eye = document.getElementById(\"eyeSvg\");\n\nwindow.addEventListener(\"message\", (event) => {\n  switch (event.data.event) {\n    case \"visible\": {\n      optionsWrapper.innerHTML = \"\";\n      body.style.visibility = event.data.state ? \"visible\" : \"hidden\";\n      return eye.classList.remove(\"eye-hover\");\n    }\n\n    case \"leftTarget\": {\n      optionsWrapper.innerHTML = \"\";\n      return eye.classList.remove(\"eye-hover\");\n    }\n\n    case \"setTarget\": {\n      optionsWrapper.innerHTML = \"\";\n      eye.classList.add(\"eye-hover\");\n\n      if (event.data.options) {\n        for (const type in event.data.options) {\n          event.data.options[type].forEach((data, id) => {\n            createOptions(type, data, id + 1);\n          });\n        }\n      }\n\n      if (event.data.zones) {\n        for (let i = 0; i < event.data.zones.length; i++) {\n          event.data.zones[i].forEach((data, id) => {\n            createOptions(\"zones\", data, id + 1, i + 1);\n          });\n        }\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "web/style.css",
    "content": "@import url(\"https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap\");\n\n:root {\n  --color-default: #cfd2da;\n  --color-hover: white;\n}\n\nbody {\n  visibility: hidden;\n  user-select: none;\n  white-space: nowrap;\n  margin: 0;\n  user-select: none;\n  overflow: hidden;\n}\n\np {\n  margin: 0;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 300, \"GRAD\" 0, \"opsz\" 40;\n}\n\n#eye {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  font-size: 22pt;\n  fill: black;\n}\n\n.eye-hover {\n  fill: var(--color-default);\n}\n\n#options-wrapper {\n  position: absolute;\n  top: calc(48.4%);\n  left: calc(50% + 18pt);\n}\n\n.option-container {\n  color: var(--color-default);\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  align-items: center;\n  font-family: \"Nunito\";\n  background: linear-gradient(\n    90deg,\n    rgba(20, 20, 20, 0.7) 0%,\n    rgba(20, 20, 20, 0.6) 66%,\n    rgba(47, 48, 53, 0) 100%\n  );\n  font-size: 11pt;\n  line-height: 22pt;\n  vertical-align: middle;\n  margin: 2pt;\n  transition: 300ms;\n  transform-origin: left top;\n  scale: 1;\n  height: 22pt;\n  width: 150pt;\n  top: 0;\n}\n\n.option-container:hover {\n  background: linear-gradient(\n    90deg,\n    rgba(30, 30, 30, 0.7) 0%,\n    rgba(30, 30, 30, 0.6) 66%,\n    rgba(57, 58, 63, 0) 100%\n  );\n  transform-origin: left top;\n  color: var(--color-hover);\n  margin-left: 4pt;\n}\n\n.option-icon {\n  font-size: 12pt;\n  line-height: 22pt;\n  width: 14pt;\n  margin: 5pt;\n  color: var(--color-default);\n}\n\n.option-label {\n  font-weight: 500;\n}\n"
  }
]