Configuration
Every metal detecting option in config.lua and logs.lua with its default and what it changes, the text prompt setting in client/functions.lua, and the strings file.
metal detecting is set up in three files that ship open: config.lua (the Config table), logs.lua (Logs) and strings.lua (Strings). One setting lives elsewhere, the text prompt system, in client/functions.lua. Changes apply after restart lation_detecting.
The sections below follow config.lua from top to bottom. The defaults are the values it ships with.
Config.Setup
| Option | Default | What it does |
|---|---|---|
Config.Setup.debug | false | Prints debug lines in the server console and F8, prints the hash of the ground in F8 every second while detecting, and adds the /simulate command. It costs a lot of resource time, so keep it off in production. |
Config.Setup.interact | 'textui' | How players open the shops. 'textui' shows a key prompt within 2.25 m of the shopkeeper. 'ox_target', 'qb-target' and 'interact' (the interact resource) add an Open Shop option to them instead, within 2 m. 'custom' does nothing until you add your code to AddTargetEntity and RemoveTargetEntity in client/functions.lua. lation interact answers to 'ox_target' and 'qb-target'. |
Config.Setup.notify | 'ox_lib' | 'lation_ui', 'ox_lib', 'esx', 'qb', 'okok', 'sd-notify', 'wasabi_notify' or 'custom'. 'qb' works on QBCore only; on Qbox use 'ox_lib' or 'lation_ui'. |
Config.Setup.menu | 'ox_lib' | The detector shop, sell shop and stats menus: 'lation_ui', 'ox_lib' or 'custom'. |
Config.Setup.dialogs | 'ox_lib' | The purchase and Sell All confirms and the quantity prompt: 'lation_ui', 'ox_lib' or 'custom'. |
Config.Setup.version | true | Checks for a newer version on start and prints a notice in the server console when one is out. |
Config.Setup.control | 38 | The key for the shop prompt and for digging, whatever interact is set to. 38 is E; see FiveM's controls. The prompts in strings.lua say E, so change them with it. |
Config.Setup.command | 'metaldetecting' | The command that opens a player's stats. |
The custom options do nothing until you add your code in client/functions.lua, which ships open.
Config.Detecting
Who can detect
| Option | Default | What it does |
|---|---|---|
Config.Detecting.job.require | false | Keeps detecting to one job. Players without it can't use a detector or open the shops, and don't see the shopkeepers or their blips. |
Config.Detecting.job.job | 'metal_detector' | That job's name. |
Config.Detecting.verify_level | true | Each detector needs its level to buy and to use: level 2 for the second, and so on. false lets anyone buy and use any detector. |
Config.Detecting.bans | ['identifier'] = true (an example) | Players who can't detect at all: they get You are not authorized to do this. Key them by their ESX identifier (char1:...) or their citizenid on Qbox and QBCore. |
The shopkeepers and blips for a job are set up as the player loads in, so a player given the job later sees them after they rejoin.
Where
| Option | Default | What it does |
|---|---|---|
Config.Detecting.soil_check | true | Detecting only counts on the ground types in soil_type. false allows any ground. |
Config.Detecting.soil_type | [-1595148316] = true (beach sand) | The allowed ground types, by material hash. |
Config.Detecting.restricted_areas | One at vec3(0, 0, 0), radius 20 (a placeholder) | Circles where nobody can detect, whatever the ground. Each is { coords = vec3(x, y, z), radius = meters }. |
Config.Detecting.designated_areas.enabled | false | Keeps detecting to the circles in areas. |
Config.Detecting.designated_areas.areas | One at vec3(0, 0, 0), radius 50 (a placeholder) | Those circles, in the same shape. |
As it ships:
soil_check = true,
soil_type = {
[-1595148316] = true, -- Sand/beach
},
restricted_areas = {
[1] = { coords = vec3(0, 0, 0), radius = 20 },
},
designated_areas = {
enabled = false,
areas = {
[1] = { coords = vec3(0, 0, 0), radius = 50 },
}
},The ground is read just ahead of the player's feet, once a second. To find a ground's hash, turn on Config.Setup.debug, take a detector out on it and read Currently detecting on soil type: in F8, then add that number to soil_type. Only the player's ground is checked: a find can be placed anywhere within the detector's radius, off the sand too.
A blocked area refuses the detector when it is used and puts it away when a player walks in. Designated areas are checked once the detector is out: outside them it is put away with You cannot metal detect in this area. The server checks both again before it hands over a find.
While detecting
| Option | Default | What it does |
|---|---|---|
Config.Detecting.running | false | Lets the search count while running and sprinting. Off, a running player sees Error. |
Config.Detecting.disable | 200, 22, 24, 140, 36, 44 | Controls turned off while the detector is out: ESC, Space, left click, R, left Ctrl and Q. |
Config.Detecting.sound.enable | true | The beeps. |
Config.Detecting.sound.audio | Beep_Red from DLC_HEIST_HACKING_SNAKE_SOUNDS | The sound, as { bank, name }. |
The detector beeps only within 35 m of a find. How often is set by GetBeepTime in client/functions.lua: every 3.5 seconds from 30 to 35 m, then every 3, 2.5 and 2 seconds for each 5 m closer, then the meters times 150 ms under 15 m.
Levels
| Option | Default | What it does |
|---|---|---|
Config.Detecting.levels | [1] = 0, [2] = 12500, [3] = 25000, [4] = 50000, [5] = 100000 | The total XP each level needs. XP stops at the last one. |
The script is built for five levels, one per detector: the shop has a row for each and the stats menu counts to 5. Change the numbers, not how many there are.
Config.Shops
Two shops with the same options, Config.Shops.detectors and Config.Shops.sellShop. Each shopkeeper appears when a player comes within 65 m.
| Option | detectors | sellShop | What it does |
|---|---|---|---|
enabled | true | true | Off, the shopkeeper and their prompt are gone. The blip stays unless you also turn off blip.enabled. |
ped | 'a_m_y_beach_02' | 'a_m_o_genstreet_01' | The shopkeeper's model. |
coords | vec4(-1234.3484, -1476.9872, 3.3116, 79.8191) | vec4(412.6790, 314.3495, 102.0208, 207.4230) | Where they stand and which way they face: Vespucci Beach, and outside a pawn shop in Downtown Vinewood. |
account | 'cash' | 'cash' | 'cash' or 'bank'. The detector shop takes payment from it; the sell shop pays into it and sells the shovel from it. |
blip.enabled | true | true | Shows the shop on the map. |
blip.sprite | 103 | 207 | The blip's icon, from FiveM's blips. |
blip.color | 0 | 2 | The blip's color. |
blip.scale | 0.8 | 0.8 | The blip's size. |
blip.label | 'Metal Detectors' | 'Detectors Sell Shop' | The blip's name on the map. |
The sell shop has two more:
| Option | Default | What it does |
|---|---|---|
Config.Shops.sellShop.sellAll | true | Shows Sell All. |
Config.Shops.sellShop.items | The 46 finds, $5 to $5,000 | What the shop buys, and the price of one. An item that isn't listed can't be sold there. The default prices are listed under finds. |
items = {
['md_bottlecap'] = 5,
-- ...
['md_presidentialwatch'] = 5000
},Config.Detectors
One entry per detector, in level order: Config.Detectors[1] is the Beginner's Beacon, Config.Detectors[5] the Archaeo Ace.
| Option | Defaults, detectors 1 to 5 | What it does |
|---|---|---|
Config.Detectors[n].price | 2,750, 5,250, 8,825, 13,250, 19,575 | What the detector shop charges. |
Config.Detectors[n].item | blue_metaldetector, green_metaldetector, red_metaldetector, orange_metaldetector, black_metaldetector | The item that takes this detector out. |
Config.Detectors[n].prop | the same names | The model held while detecting. The five ship in stream/. Another prop works too, though Config.Animations.detecting.prop places it for these. |
Config.Detectors[n].radius | 15, 25, 35, 45, 60 | In meters: how far away a find can be placed, and how far the player can wander from it before they lose it. |
Config.Detectors[n].cooldown | math.random(10000, 20000), (15000, 30000), (20000, 40000), (25000, 50000), (30000, 60000) | Milliseconds of walking before a find is placed. |
Config.Detectors[n].requiredXP | 0, 12500, 25000, 50000, 100000 | Not read by the script. A detector's level is its place in the list, and the XP per level is Config.Detecting.levels. |
Config.Detectors[n].loot | See loot tables | What this detector finds. |
math.random in cooldown runs once, as the script loads on each player's game, so a player keeps one search time per detector until they rejoin. A fixed number works the same way.
The detector names, descriptions, pictures and the Radius, Depth and Accuracy bars in the shop are in strings.lua under Strings.ContextMenu.detector_shop, as level1 to level5. The bars are descriptions only and don't change anything.
Loot tables
Each detector's loot lists the items it can find:
['md_goldring'] = { chance = 2, quantity = { min = 1, max = 1 }, addXP = 750 },| Field | What it does |
|---|---|
chance | The item's weight. Each find picks one item, each as likely as its weight against the total of the detector's weights, so 2 out of a total of 1,348 is about 1 find in 674. Use whole numbers: a weight below 1 never comes up. |
quantity.min, quantity.max | How many of it one find gives. |
addXP | The XP a find of it pays, once, whatever the quantity. |
Every dig turns up one item from the table, in its quantity; there are no empty digs. Take an item out of a detector's table and that detector never finds it. To add a find of your own, add the item to your inventory and its picture, named after the item, to install/images/, list it in each detector's loot, and give it a price in Config.Shops.sellShop.items so it can be sold.
To check the odds before players do, turn on Config.Setup.debug and run /simulate <detector> <count> in game, such as /simulate 3 1000. It rolls that many finds on that detector's table and prints how many of each item came up in F8. Nothing is given.
The default tables, as weight and XP for each detector. The weights add up to 1,348, 1,589, 1,555, 1,440 and 1,505.
| Item | Beginner's Beacon | Lustrous Locator | Treasure Trove Tracker | Goldseeker's Grail | Archaeo Ace |
|---|---|---|---|---|---|
md_bottlecap | 60 (40 XP) | 45 (20 XP) | 30 (10 XP) | 20 (5 XP) | 10 (2 XP) |
md_brokenjunk | 60 (40 XP) | 45 (20 XP) | 30 (10 XP) | 20 (5 XP) | 10 (2 XP) |
md_crushedcan | 60 (40 XP) | 45 (20 XP) | 30 (10 XP) | 20 (5 XP) | 10 (2 XP) |
md_lighter | 55 (45 XP) | 45 (20 XP) | 35 (25 XP) | 25 (5 XP) | 15 (3 XP) |
md_metalcan | 55 (45 XP) | 45 (25 XP) | 35 (25 XP) | 25 (5 XP) | 15 (3 XP) |
md_nails | 55 (45 XP) | 45 (25 XP) | 35 (25 XP) | 25 (5 XP) | 15 (3 XP) |
md_needle | 55 (45 XP) | 45 (25 XP) | 35 (25 XP) | 25 (5 XP) | 20 (4 XP) |
md_nut | 55 (45 XP) | 45 (25 XP) | 35 (25 XP) | 25 (5 XP) | 20 (4 XP) |
md_oldshotgunshell | 55 (45 XP) | 45 (25 XP) | 35 (25 XP) | 25 (5 XP) | 20 (4 XP) |
md_paperclip | 50 (50 XP) | 50 (40 XP) | 40 (30 XP) | 30 (10 XP) | 25 (5 XP) |
md_pulltab | 50 (50 XP) | 50 (40 XP) | 40 (30 XP) | 30 (10 XP) | 25 (5 XP) |
md_rustyball | 50 (50 XP) | 50 (40 XP) | 40 (30 XP) | 30 (10 XP) | 25 (5 XP) |
md_rustyironball | 50 (50 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 30 (6 XP) |
md_rustyjunk | 50 (50 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 30 (6 XP) |
md_rustynails | 50 (50 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 30 (6 XP) |
md_rustypliers | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 35 (7 XP) |
md_rustyring | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 35 (7 XP) |
md_rustyscissors | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 35 (7 XP) |
md_rustyscrewdriver | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 40 (8 XP) |
md_rustyspring | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 40 (8 XP) |
md_screw | 45 (75 XP) | 50 (40 XP) | 45 (35 XP) | 35 (15 XP) | 40 (8 XP) |
md_wheatpenny | 40 (100 XP) | 50 (40 XP) | 45 (35 XP) | 40 (20 XP) | 45 (9 XP) |
md_nickle | 40 (100 XP) | 50 (40 XP) | 45 (35 XP) | 40 (20 XP) | 45 (9 XP) |
md_silverdime | 35 (125 XP) | 45 (45 XP) | 40 (45 XP) | 40 (20 XP) | 45 (9 XP) |
md_quarter | 30 (150 XP) | 45 (45 XP) | 40 (45 XP) | 40 (20 XP) | 50 (10 XP) |
md_halfdollar | 25 (200 XP) | 40 (50 XP) | 40 (45 XP) | 40 (20 XP) | 50 (10 XP) |
md_blackwatch | 20 (250 XP) | 40 (50 XP) | 40 (45 XP) | 40 (20 XP) | 50 (10 XP) |
md_coppernugget | 15 (300 XP) | 35 (55 XP) | 40 (45 XP) | 45 (30 XP) | 55 (15 XP) |
md_ironnugget | 15 (300 XP) | 35 (55 XP) | 40 (45 XP) | 45 (30 XP) | 55 (15 XP) |
md_earpod | 10 (400 XP) | 35 (55 XP) | 40 (45 XP) | 45 (30 XP) | 55 (15 XP) |
md_relicrevolver | 5 (450 XP) | 30 (60 XP) | 35 (50 XP) | 45 (30 XP) | 50 (20 XP) |
md_silverearings | 4 (500 XP) | 25 (75 XP) | 35 (50 XP) | 40 (40 XP) | 45 (25 XP) |
md_silverring | 4 (500 XP) | 25 (75 XP) | 35 (50 XP) | 40 (40 XP) | 45 (25 XP) |
md_silverounce | 4 (500 XP) | 20 (100 XP) | 30 (55 XP) | 40 (40 XP) | 45 (25 XP) |
md_ancientcoin | 3 (600 XP) | 20 (100 XP) | 30 (55 XP) | 35 (50 XP) | 40 (30 XP) |
md_golddollar | 3 (600 XP) | 15 (200 XP) | 25 (65 XP) | 30 (55 XP) | 35 (35 XP) |
md_goldearings | 2 (750 XP) | 15 (200 XP) | 25 (65 XP) | 30 (55 XP) | 35 (35 XP) |
md_goldnecklace | 2 (750 XP) | 10 (300 XP) | 25 (65 XP) | 30 (55 XP) | 35 (35 XP) |
md_goldnugget | 2 (750 XP) | 10 (300 XP) | 20 (75 XP) | 25 (65 XP) | 30 (40 XP) |
md_goldounce | 2 (750 XP) | 10 (300 XP) | 20 (75 XP) | 25 (65 XP) | 30 (40 XP) |
md_goldring | 2 (750 XP) | 10 (300 XP) | 20 (75 XP) | 25 (65 XP) | 30 (40 XP) |
md_diamondearings | 1 (1,200 XP) | 5 (400 XP) | 15 (100 XP) | 20 (75 XP) | 25 (50 XP) |
md_diamondnecklace | 1 (1,200 XP) | 5 (400 XP) | 15 (100 XP) | 20 (75 XP) | 25 (50 XP) |
md_diamondring | 1 (1,200 XP) | 5 (400 XP) | 15 (100 XP) | 20 (75 XP) | 25 (50 XP) |
md_platinumnugget | 1 (1,200 XP) | 3 (480 XP) | 10 (200 XP) | 15 (100 XP) | 20 (60 XP) |
md_presidentialwatch | 1 (1,200 XP) | 1 (600 XP) | 5 (300 XP) | 10 (150 XP) | 15 (75 XP) |
Config.Shovel
| Option | Default | What it does |
|---|---|---|
Config.Shovel.item | 'md_shovel' | The shovel's item name. |
Config.Shovel.addToShop | 'sellShop' | Where Buy Shovel shows: 'sellShop', 'detectors', or false for nowhere. |
Config.Shovel.price | 250 | What it costs, taken from that shop's account. |
Config.Shovel.required | true | Players need a shovel to dig. false lets them dig without one. |
If you move the shovel to the detector shop, keep both shops on the same account: the player's balance is checked against the sell shop's.
Config.Animations
| Option | Default | What it does |
|---|---|---|
Config.Animations.detecting.anim | { dict = 'mini@golfai', clip = 'wood_idle_a' } | The sweeping animation. The player can walk while it plays. |
Config.Animations.detecting.prop | pos = vec3(0.849, 0.050, 0.059), rot = vec3(-176.460, 86.093, 5.054) | Where the detector sits in the left hand. |
Config.Animations.shovel.duration | 7250 | How long digging takes, in milliseconds. |
Config.Animations.shovel.anim | { dict = 'random@burial', clip = 'a_burial' } | The digging animation. |
Config.Animations.shovel.prop | model = 'prop_tool_shovel', pos = vec3(0.0, 0.0, 0.240), rot = vec3(0.0, 0.0, 0.0) | The shovel model and where it sits in the right hand. |
Text prompts
The prompts (Searching.., Distance, E - Start Digging, Error and the shop's Shop) use the system named at the top of client/functions.lua, not one in config.lua:
-- Options: 'lation_ui', 'ox_lib', 'jg-textui', 'okokTextUI', 'qbcore' & 'custom'
local textui = 'ox_lib'| Value | Shows the prompts with |
|---|---|
'ox_lib' | ox_lib, at the left of the screen |
'lation_ui' | lation ui, at the right of the screen |
'jg-textui' | jg-textui |
'okokTextUI' | okokTextUI |
'qbcore' | qb-core's DrawText |
'custom' | Your own code in ShowTextUI and HideTextUI, in the same file |
Logs
logs.lua sets what is logged and where. Every log names the player and their identifier.
| Option | Default | What it does |
|---|---|---|
Logs.Service | 'none' | 'fivemanage' (needs fmsdk), 'fivemerr' (needs fm-logs), 'discord' or 'none'. |
Logs.Screenshots | false | Adds a screenshot of the player's screen to each log, on Fivemanage and Fivemerr. |
Logs.Events.found_item | false | Each find: how many of what. |
Logs.Events.level_up | false | Each level reached. |
Logs.Events.buy_detector | false | Each detector bought. |
Logs.Events.item_sold | false | Each sale, single items and Sell All. |
Logs.Discord.name | 'Metal Detecting Logs' | The webhook's name. |
Logs.Discord.link | '' | The webhook URL. |
Logs.Discord.image | an imgur picture | The webhook's avatar. |
Logs.Discord.footer | an imgur picture | The footer's icon. |
With Config.Setup.debug on and Fivemanage or Fivemerr picked, the debug lines are sent there too.
Language
Every message, prompt, menu row, dialog and log line is in strings.lua. Edit it to reword or translate.
| Section | Holds |
|---|---|
Strings.Notify | Notifications |
Strings.TextUI | The prompts and their icons |
Strings.Target | The Open Shop target option, its icon and reach |
Strings.ContextMenu | The detector shop (each detector's name, description, picture and bars), the sell shop and the stats menu |
Strings.InputDialog | The quantity prompt |
Strings.AlertDialog | The purchase and Sell All confirms |
Strings.Logs | Log titles and messages |
Lines with %s get a value put in their place, for example 'You have x%s and they sell for $%s each'. The notification title, Metal Detecting, is set in ShowNotification in client/functions.lua, and item names come from your inventory's labels.
Still stuck? Ask in our Discord: the people who write the scripts answer.
Last updated
Detecting
How players buy a detector, search the beach, dig up finds, level up and sell in metal detecting, with the default detectors, finds and prices.
Developers
The metal detecting client and server exports, its notify event, the hooks in its open files, and the bridge you edit for another framework, inventory or interface.