├── README.md ├── config.lua ├── fxmanifest.lua ├── images └── detector.png ├── interface.lua ├── lang.json ├── scripts ├── cl_prospecting.lua └── sv_prospecting.lua └── stream ├── gen_w_am_metaldetector.ytyp ├── w_am_metaldetector.ydr └── w_am_metaldetector.ytd /README.md: -------------------------------------------------------------------------------- 1 | # Prospecting QBCore/ESX 2 | 3 | ## Original Github Repository 4 | https://github.com/glitchdetector/fivem-prospecting 5 | 6 | ## Resource Installation 7 | * Download the repository 8 | * Put the `prospecting` folder in your resources directory. Make sure the resource is named **prospecting** 9 | * Add `ensure prospecting` to your server config 10 | * Add metaldetector item in your items.lua 11 | 12 | ### QBCore 13 | ```lua 14 | ['detector'] = {['name'] = 'detector', ['label'] = 'Detector', ['weight'] = 100, ['type'] = 'item', ['image'] = 'detector.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Go find some things'}, 15 | ``` 16 | 17 | ### ESX 18 | ```sql 19 | INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES ('detector', 'Metal Detector', 1, 0, 1) 20 | ``` 21 | 22 | ## Support 23 | 24 | - Discord: https://discord.gg/VGYkkAYVv2 25 | - Website : https://snipe.tebex.io 26 | 27 | ## Goals 28 | - Remove dependency with instructional buttons - **Done** 29 | - Make it item based - **Done** 30 | - Remove Multilingual dependency (create a basic locale instead) - **Done** 31 | - Add multiple locations and configure return items - **Done** 32 | 33 | If you are interested, you can create pr's for this and make sure they are tested. 34 | 35 | # This is the original README below this part 36 | 37 | #### A prospecting mini-game handler for FiveM. Allows resources to control via an API. 38 | 39 | ![](https://cdn.tycoon.community/rrerr/pwq1q.png) 40 | 41 | ## Synopsis 42 | 43 | Ever wanted to look for treasure under the ground? With this prospecting mini-game you can do just that! 44 | 45 | Get your metal detectors ready and go on an adventure, alone or with all of your friends! 46 | 47 | You can have a stroll on the beach by yourself looking for lost items, or fight against your friends in a competitive environment! 48 | 49 | ## Note 50 | 51 | This resource is just a handler, it will not work without a controlling resource. 52 | 53 | An example controller can be found here: https://github.com/glitchdetector/fivem-prospecting-example 54 | 55 | ## Features 56 | * Scan for stuff under the ground 57 | * Audio and visual indicators 58 | * Built-in support for multiple languages 59 | * Instructional Buttons integration 60 | * Allows other resources to interact with it 61 | * Handles all of the complicated things, leaving it simple to integrate 62 | * A custom metal detector prop 63 | * Multiplayer support 64 | * Automatically synced locations 65 | 66 | ## Resource Installation 67 | * Download the repository 68 | * Put the `prospecting` folder in your resources directory 69 | * Add `ensure prospecting` to your server config 70 | 71 | ## Dependencies 72 | | Resource | Description | Download | 73 | |----------|-------------|----------| 74 | | Multilingual | Allows the system to use the players configured game language. | https://github.com/glitchdetector/fivem-multilingual | 75 | | Instructional Buttons | Shows what buttons do what action at any given time. | https://github.com/glitchdetector/fivem-instructional-buttons | 76 | | Any controller | One or more resources that controls this mini-game | Example provided above | 77 | 78 | ## Developers 79 | 80 | Since this resource does nothing by itself, it's up to you developers to implement it within your own systems and frameworks. 81 | 82 | To control the Prospecting mini-game, you need to use exports or the server script `interface.lua` 83 | 84 | Preferred method is the `interface.lua` script and will be covered here. 85 | 86 | ### Setup 87 | 88 | In your resource manifest, add the following entries: 89 | 90 | ```lua 91 | dependencies {'prospecting'} 92 | server_script '@prospecting/interface.lua' 93 | ``` 94 | 95 | Your server scripts can now access the `Prospecting` object 96 | 97 | ### Prospecting Interface 98 | 99 | #### AddTarget `(x, y, z, data)` 100 | Adds a location that a player can prospect for. 101 | 102 | | Parameter | Description | 103 | |-----------|-------------| 104 | | x, y, z | The coordinates | 105 | | data | Data that is sent back in the Pickup handler when the target is picked up by a player. Can be anything. | 106 | 107 | #### AddTargets `(list)` 108 | Adds a list of locations at once. 109 | 110 | | Parameter | Description | 111 | |-----------|-------------| 112 | | list | An array/list/table with tables containing an `x`, `y`, `z` and `data` property | 113 | 114 | #### StartProspecting `(player)` 115 | Make the player to start prospecting. 116 | 117 | | Parameter | Description | 118 | |-----------|-------------| 119 | | player | The server id of the player | 120 | 121 | #### StopProspecting `(player)` 122 | Make the player to stop prospecting. 123 | 124 | | Parameter | Description | 125 | |-----------|-------------| 126 | | player | The server id of the player | 127 | 128 | #### IsProspecting `(player)` 129 | Returns wether the player is currently prospecting. 130 | 131 | | Parameter | Description | 132 | |-----------|-------------| 133 | | player | The server id of the player | 134 | 135 | #### SetDifficulty `(modifier)` 136 | Sets the scanning difficulty modifier. 137 | 138 | 139 | | Parameter | Default | Description | 140 | |-----------|---------|-------------| 141 | | modifier | 1.0 | The difficulty modifier, higher means you must be closer for the scanner to pick it up. Distance to target is multiplied by this number, so 2.0 means you need to be half the distance away to begin picking the targets up. | 142 | 143 | **Note:** This is set per controller, meaning each controller can have different difficulties. Only targets made by this controller will have this difficulty. 144 | 145 | #### OnStart `(handler(player))` 146 | Adds a function to be called when a player starts prospecting. 147 | 148 | | Parameter | Description | 149 | |-----------|-------------| 150 | | handler | A function that is called with a `player` parameter | 151 | | player | The player that started prospecting | 152 | 153 | **Note:** This will trigger even if another controller caused the player to start prospecting. 154 | 155 | #### OnStop `(handler(player, time))` 156 | Adds a function to be called when a player stops prospecting. 157 | 158 | | Parameter | Description | 159 | |-----------|-------------| 160 | | handler | A function that is called with a `player` and `time` parameter | 161 | | player | The player that stopped prospecting | 162 | | time | How long the player was prospecting for in milliseconds (game time) | 163 | 164 | **Note:** This will trigger even if another controller caused the player to stop prospecting. 165 | 166 | #### SetHandler `(handler(player, data, x, y, z))` 167 | Adds a function to be called when a player collects a target (by digging). 168 | 169 | | Parameter | Description | 170 | |-----------|-------------| 171 | | handler | A function that is called with `player`, `data`, `x`, `y`, `z` parameters | 172 | | player | The player that found the target | 173 | | data | The data that was supplied when creating the target | 174 | | x, y, z | The coordinates where they found the target | 175 | 176 | **Note:** This will only trigger for targets added by this controller. 177 | 178 | ### Example 179 | ```lua 180 | -- Default difficulty 181 | Prospecting.SetDifficulty(1.0) 182 | 183 | -- Add a few things for the player to find 184 | Prospecting.AddTarget(1548.082, 6633.096, 2.377085, "Nuts and Bolts") 185 | Prospecting.AddTargets({ 186 | {x = 1600.185, y = 6622.714, z = 15.85106, data = "Bones"}, 187 | {x = 1580.016, y = 6547.394, z = 15.96557, data = "Dragon Scales"}, 188 | }) 189 | 190 | Prospecting.SetHandler(function(player, data, x, y, z) 191 | -- The player collected something 192 | TriggerClientEvent("chatMessage", player, "You found " .. data .. "!") 193 | end) 194 | 195 | Prospecting.OnStart(function(player) 196 | -- The player started prospecting 197 | TriggerClientEvent("chatMessage", player, "Started prospecting") 198 | end) 199 | 200 | -- time in milliseconds 201 | Prospecting.OnStop(function(player, time) 202 | -- The player stopped prospecting 203 | TriggerClientEvent("chatMessage", player, "Stopped prospecting") 204 | end) 205 | 206 | RegisterCommand("prospect", function(player, _, _) 207 | -- Toggle prospecting 208 | if Prospecting.IsProspecting(player) then 209 | Prospecting.StopProspecting(player) 210 | else 211 | Prospecting.StartProspecting(player) 212 | end 213 | end) 214 | ``` 215 | 216 | A complete resource example can be found at https://github.com/glitchdetector/fivem-prospecting-example 217 | 218 | ## Credits 219 | Script development by **[glitchdetector](https://github.com/glitchdetector)** 220 | 221 | Metal Detector Model by **[Vartanyan](https://www.turbosquid.com/3d-models/3d-metal-detector/1138741)** (converted to RAGE format by glitchdetector) 222 | 223 | Play testing by the **[Tycoon Gaming](https://discord.gg/tycoon)** community 224 | 225 | ## Disclaimer 226 | Please refrain from remixing this resource. 227 | 228 | PRs for improvements are very welcome! 229 | 230 | You may include this as part of pack downloads, as long as it is not modified and a link to an up-to-date version is provided (such as this repository). 231 | 232 | A copy of this README must also be present with re-distributions. 233 | 234 | ## License 235 | [Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)](https://creativecommons.org/licenses/by-nd/3.0/) 236 | 237 | 238 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | -----------------For support, scripts, and more---------------- 2 | --------------- https://discord.gg/VGYkkAYVv2 ------------- 3 | --------------------------------------------------------------- 4 | 5 | Config = {} 6 | 7 | Config.Core = "QBCore" -- ESX or QBCore 8 | Config.PlayerLoadedEvent = "QBCore:Client:OnPlayerLoaded" -- esx:playerLoaded || QBCore:Client:OnPlayerLoaded 9 | 10 | Config.ShowBlip = true -- show blip on map 11 | 12 | Config.Chances = { 13 | ["common"] = 100, -- 100% 14 | ["rare"] = 15, -- 15% 15 | ["epic"] = 5, -- 5% 16 | } 17 | Config.ShowDrawMaker = true -- show draw marker on in game while prospecting 18 | Config.DetectorItem = "detector" 19 | 20 | Config.Zones = { 21 | [1] = {coords = vector3(1429.933, 1222.926, 110.88), data = "loc1", zoneSize = 100, zoneLocations = 200}, 22 | [2] = {coords = vector3(1615.378, 2095.902, 85.007), data = "loc2", zoneSize = 100, zoneLocations = 100}, 23 | } 24 | 25 | Config.DefaultItems = { 26 | [1] = {name = "steel", min = 1, max = 2} 27 | } -- will be selected if you dont put the common, rare and epic items in the config 28 | 29 | Config.Items = { 30 | ["loc1"] = { 31 | ["common"] = { 32 | [1] = {name = "steel", min = 5, max = 10}, 33 | [2] = {name = "metalscrap", min = 5, max = 10}, 34 | }, 35 | ["rare"] = { 36 | [1] = {name = "phone", min = 1, max = 1}, 37 | }, 38 | ["epic"] = { 39 | [1] = {name = "handcuffs", min = 1, max = 1}, 40 | } 41 | }, 42 | ["loc2"] = { 43 | ["common"] = { 44 | [1] = {name = "steel", min = 5, max = 10}, 45 | [2] = {name = "metalscrap", min = 5, max = 10}, 46 | }, 47 | ["rare"] = { 48 | [1] = {name = "phone", min = 1, max = 1}, 49 | }, 50 | ["epic"] = { 51 | [1] = {name = "handcuffs", min = 1, max = 1}, 52 | } 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | name 'Prospecting' 2 | author 'glitchdetector' 3 | contact 'glitchdetector@gmail.com' 4 | 5 | fx_version 'adamant' 6 | game 'gta5' 7 | 8 | lua54 'yes' 9 | 10 | description 'Prospect for treasure in key locations' 11 | details [[ 12 | Inspired by the Prospect minigame in MySims 13 | Metal detector model by Vartanyan 14 | https://www.turbosquid.com/3d-models/3d-metal-detector/1138741 15 | Converted by glitchdetector 16 | Yes, I did actually buy it for $11 17 | ]] 18 | 19 | shared_scripts{ 20 | '@ox_lib/init.lua', 21 | "config.lua", 22 | } 23 | 24 | client_script 'scripts/cl_*.lua' 25 | server_scripts { 26 | 'scripts/sv_*.lua', 27 | 'interface.lua', 28 | } 29 | 30 | file 'stream/gen_w_am_metaldetector.ytyp' 31 | 32 | data_file 'DLC_ITYP_REQUEST' 'stream/gen_w_am_metaldetector.ytyp' 33 | 34 | server_exports { 35 | 'AddProspectingTarget', -- x, y, z, data 36 | 'AddProspectingTargets', -- list 37 | 'StartProspecting', -- player 38 | 'StopProspecting', -- player 39 | 'IsProspecting', -- player 40 | 'SetDifficulty', -- modifier 41 | } 42 | -------------------------------------------------------------------------------- /images/detector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipe-scripts/prospecting/d89f2c05156839230046207b2e31dbc33138aacf/images/detector.png -------------------------------------------------------------------------------- /interface.lua: -------------------------------------------------------------------------------- 1 | Prospecting = {} 2 | local prospecting = exports['prospecting'] 3 | 4 | -- Add a node to collect 5 | -- Data can be whatever and is returned with the node in the handler 6 | function Prospecting.AddTarget(x, y, z, data) 7 | prospecting:AddProspectingTarget(x, y, z, data) 8 | end 9 | 10 | function Prospecting.AddTargets(list) 11 | prospecting:AddProspectingTargets(list) 12 | end 13 | 14 | function Prospecting.StartProspecting(player) 15 | prospecting:StartProspecting(player) 16 | end 17 | 18 | function Prospecting.StopProspecting(player) 19 | prospecting:StopProspecting(player) 20 | end 21 | 22 | function Prospecting.IsProspecting(player) 23 | return prospecting:IsProspecting(player) 24 | end 25 | 26 | -- Set this resources difficulty modifier 27 | -- Does not affect other resources settings 28 | function Prospecting.SetDifficulty(modifier) 29 | return prospecting:SetDifficulty(modifier) 30 | end 31 | 32 | function Prospecting.OnStart(handler) 33 | AddEventHandler("prospecting:onStart", function(player) 34 | handler(player) 35 | end) 36 | end 37 | 38 | function Prospecting.OnStop(handler) 39 | AddEventHandler("prospecting:onStop", function(player, time) 40 | handler(player, time) 41 | end) 42 | end 43 | 44 | -- Sets a handler function for collected nodes 45 | -- Parameters are: player, data, x, y, z 46 | function Prospecting.SetHandler(handler) 47 | AddEventHandler("prospecting:onCollected", function(player, resource, data, x, y, z) 48 | if resource == GetCurrentResourceName() then 49 | handler(player, data, x, y, z) 50 | end 51 | end) 52 | end 53 | -------------------------------------------------------------------------------- /lang.json: -------------------------------------------------------------------------------- 1 | { 2 | "american": [ 3 | { 4 | "LABEL": "PROSP_DIG", 5 | "ORIGINAL": "Dig", 6 | "TRANSLATION": "Dig" 7 | }, 8 | { 9 | "LABEL": "PROSP_DIG_HINT", 10 | "ORIGINAL": "You found something!", 11 | "TRANSLATION": "You found something!" 12 | }, 13 | { 14 | "LABEL": "PROSP_STOP", 15 | "ORIGINAL": "Stop prospecting", 16 | "TRANSLATION": "Stop prospecting" 17 | }, 18 | { 19 | "LABEL": "PROSP_AUDIOON", 20 | "ORIGINAL": "Enable scanner audio", 21 | "TRANSLATION": "Enable scanner audio" 22 | }, 23 | { 24 | "LABEL": "PROSP_AUDIOOFF", 25 | "ORIGINAL": "Disable scanner audio", 26 | "TRANSLATION": "Disable scanner audio" 27 | }, 28 | { 29 | "LABEL": "PROSP_BLIP", 30 | "ORIGINAL": "Prospecting", 31 | "TRANSLATION": "Prospecting" 32 | } 33 | ], 34 | "french": [ 35 | { 36 | "LABEL": "PROSP_DIG", 37 | "ORIGINAL": "Dig", 38 | "TRANSLATION": "Creuser" 39 | }, 40 | { 41 | "LABEL": "PROSP_DIG_HINT", 42 | "ORIGINAL": "You found something!", 43 | "TRANSLATION": "Tu l'as trouvé!" 44 | }, 45 | { 46 | "LABEL": "PROSP_STOP", 47 | "ORIGINAL": "Stop prospecting", 48 | "TRANSLATION": "Arrêtez la prospection" 49 | }, 50 | { 51 | "LABEL": "PROSP_AUDIOON", 52 | "ORIGINAL": "Enable scanner audio", 53 | "TRANSLATION": "Activer l'audio du scanner" 54 | }, 55 | { 56 | "LABEL": "PROSP_AUDIOOFF", 57 | "ORIGINAL": "Disable scanner audio", 58 | "TRANSLATION": "Désactiver l'audio du scanner" 59 | }, 60 | { 61 | "LABEL": "PROSP_BLIP", 62 | "ORIGINAL": "Prospecting", 63 | "TRANSLATION": "Prospection" 64 | } 65 | ], 66 | "german": [ 67 | { 68 | "LABEL": "PROSP_DIG", 69 | "ORIGINAL": "Dig", 70 | "TRANSLATION": "Graben" 71 | }, 72 | { 73 | "LABEL": "PROSP_DIG_HINT", 74 | "ORIGINAL": "You found something!", 75 | "TRANSLATION": "Du hast es gefunden!" 76 | }, 77 | { 78 | "LABEL": "PROSP_STOP", 79 | "ORIGINAL": "Stop prospecting", 80 | "TRANSLATION": "Hör auf zu schürfen" 81 | }, 82 | { 83 | "LABEL": "PROSP_AUDIOON", 84 | "ORIGINAL": "Enable scanner audio", 85 | "TRANSLATION": "Aktivieren Detektorton" 86 | }, 87 | { 88 | "LABEL": "PROSP_AUDIOOFF", 89 | "ORIGINAL": "Disable scanner audio", 90 | "TRANSLATION": "Deaktivieren Detektorton" 91 | }, 92 | { 93 | "LABEL": "PROSP_BLIP", 94 | "ORIGINAL": "Prospecting", 95 | "TRANSLATION": "Prospektion" 96 | } 97 | ], 98 | "italian": [ 99 | { 100 | "LABEL": "PROSP_DIG", 101 | "ORIGINAL": "Dig", 102 | "TRANSLATION": "Scavare" 103 | }, 104 | { 105 | "LABEL": "PROSP_DIG_HINT", 106 | "ORIGINAL": "You found something!", 107 | "TRANSLATION": "L'hai trovato!" 108 | }, 109 | { 110 | "LABEL": "PROSP_STOP", 111 | "ORIGINAL": "Stop prospecting", 112 | "TRANSLATION": "Smettere di prospezione" 113 | }, 114 | { 115 | "LABEL": "PROSP_AUDIOON", 116 | "ORIGINAL": "Enable scanner audio", 117 | "TRANSLATION": "Attiva il suono di scansione" 118 | }, 119 | { 120 | "LABEL": "PROSP_AUDIOOFF", 121 | "ORIGINAL": "Disable scanner audio", 122 | "TRANSLATION": "Disattiva il suono di scansione" 123 | }, 124 | { 125 | "LABEL": "PROSP_BLIP", 126 | "ORIGINAL": "Prospecting", 127 | "TRANSLATION": "Prospezione" 128 | } 129 | ], 130 | "spanish": [ 131 | { 132 | "LABEL": "PROSP_DIG", 133 | "ORIGINAL": "Dig", 134 | "TRANSLATION": "Cavar" 135 | }, 136 | { 137 | "LABEL": "PROSP_DIG_HINT", 138 | "ORIGINAL": "You found something!", 139 | "TRANSLATION": "¡Lo encontraste!" 140 | }, 141 | { 142 | "LABEL": "PROSP_STOP", 143 | "ORIGINAL": "Stop prospecting", 144 | "TRANSLATION": "Deja de prospectar" 145 | }, 146 | { 147 | "LABEL": "PROSP_AUDIOON", 148 | "ORIGINAL": "Enable scanner audio", 149 | "TRANSLATION": "Activar sonido de escaneo" 150 | }, 151 | { 152 | "LABEL": "PROSP_AUDIOOFF", 153 | "ORIGINAL": "Disable scanner audio", 154 | "TRANSLATION": "Apague el sonido de escaneo" 155 | }, 156 | { 157 | "LABEL": "PROSP_BLIP", 158 | "ORIGINAL": "Prospecting", 159 | "TRANSLATION": "Prospección" 160 | } 161 | ], 162 | "polish": [ 163 | { 164 | "LABEL": "PROSP_DIG", 165 | "ORIGINAL": "Dig", 166 | "TRANSLATION": "Kopać" 167 | }, 168 | { 169 | "LABEL": "PROSP_DIG_HINT", 170 | "ORIGINAL": "You found something!", 171 | "TRANSLATION": "Znalazłeś to!" 172 | }, 173 | { 174 | "LABEL": "PROSP_STOP", 175 | "ORIGINAL": "Stop prospecting", 176 | "TRANSLATION": "Przestań poszukiwać" 177 | }, 178 | { 179 | "LABEL": "PROSP_AUDIOON", 180 | "ORIGINAL": "Enable scanner audio", 181 | "TRANSLATION": "Włącz dźwięk skanowania" 182 | }, 183 | { 184 | "LABEL": "PROSP_AUDIOOFF", 185 | "ORIGINAL": "Disable scanner audio", 186 | "TRANSLATION": "Wyłącz dźwięk skanowania" 187 | }, 188 | { 189 | "LABEL": "PROSP_BLIP", 190 | "ORIGINAL": "Prospecting", 191 | "TRANSLATION": "Poszukiwanie" 192 | } 193 | ], 194 | "portuguese": [ 195 | { 196 | "LABEL": "PROSP_DIG", 197 | "ORIGINAL": "Dig", 198 | "TRANSLATION": "Escavação" 199 | }, 200 | { 201 | "LABEL": "PROSP_DIG_HINT", 202 | "ORIGINAL": "You found something!", 203 | "TRANSLATION": "Você achou!" 204 | }, 205 | { 206 | "LABEL": "PROSP_STOP", 207 | "ORIGINAL": "Stop prospecting", 208 | "TRANSLATION": "Interromper a prospecção" 209 | }, 210 | { 211 | "LABEL": "PROSP_AUDIOON", 212 | "ORIGINAL": "Enable scanner audio", 213 | "TRANSLATION": "Ativar som de digitalização" 214 | }, 215 | { 216 | "LABEL": "PROSP_AUDIOOFF", 217 | "ORIGINAL": "Disable scanner audio", 218 | "TRANSLATION": "Desativar som de digitalização" 219 | }, 220 | { 221 | "LABEL": "PROSP_BLIP", 222 | "ORIGINAL": "Prospecting", 223 | "TRANSLATION": "Prospecção" 224 | } 225 | ], 226 | "russian": [ 227 | { 228 | "LABEL": "PROSP_DIG", 229 | "ORIGINAL": "Dig", 230 | "TRANSLATION": "Копать землю" 231 | }, 232 | { 233 | "LABEL": "PROSP_DIG_HINT", 234 | "ORIGINAL": "You found something!", 235 | "TRANSLATION": "Ты нашел это!" 236 | }, 237 | { 238 | "LABEL": "PROSP_STOP", 239 | "ORIGINAL": "Stop prospecting", 240 | "TRANSLATION": "Прекратить поиски" 241 | }, 242 | { 243 | "LABEL": "PROSP_AUDIOON", 244 | "ORIGINAL": "Enable scanner audio", 245 | "TRANSLATION": "Включить сканирование звука" 246 | }, 247 | { 248 | "LABEL": "PROSP_AUDIOOFF", 249 | "ORIGINAL": "Disable scanner audio", 250 | "TRANSLATION": "Отключить звук сканирования" 251 | }, 252 | { 253 | "LABEL": "PROSP_BLIP", 254 | "ORIGINAL": "Prospecting", 255 | "TRANSLATION": "Геологоразведочные" 256 | } 257 | ] 258 | } 259 | -------------------------------------------------------------------------------- /scripts/cl_prospecting.lua: -------------------------------------------------------------------------------- 1 | -----------------For support, scripts, and more---------------- 2 | --------------- https://discord.gg/VGYkkAYVv2 ------------- 3 | --------------------------------------------------------------- 4 | 5 | QBCore, ESX = nil, nil 6 | if Config.Core == "QBCore" then 7 | QBCore = exports['qb-core']:GetCoreObject() 8 | elseif Config.Core == "ESX" then 9 | ESX = exports['es_extended']:getSharedObject() 10 | end 11 | 12 | 13 | local ShowInteraction = false 14 | 15 | local targetPool = {} 16 | 17 | local maxTargetRange = 200.0 18 | local targets = {} 19 | 20 | RegisterNetEvent(Config.PlayerLoadedEvent) 21 | AddEventHandler(Config.PlayerLoadedEvent, function() 22 | TriggerServerEvent("prospecting:userRequestsLocations") 23 | end) 24 | 25 | local function RemoveTargetIndex(coords) 26 | for index, target in next, targetPool do 27 | local targetCoords = target[1] 28 | if vec3(targetCoords.x, targetCoords.y, targetCoords.z) == coords then 29 | table.remove(targetPool, index) 30 | break 31 | end 32 | end 33 | end 34 | 35 | RegisterNetEvent("prospecting:client:removeTarget", function (coords) 36 | RemoveTargetIndex(coords) 37 | end) 38 | 39 | 40 | function EnsureAnimDict(dict) 41 | RequestAnimDict(dict) 42 | while not HasAnimDictLoaded(dict) do 43 | Wait(0) 44 | end 45 | end 46 | function EnsureModel(model) 47 | if not IsModelInCdimage(model) then 48 | print("model", model, "not in cd image") 49 | else 50 | if not HasModelLoaded(model) then 51 | RequestModel(model) 52 | while not HasModelLoaded(model) do 53 | Wait(0) 54 | end 55 | end 56 | end 57 | end 58 | 59 | local previousAnim = nil 60 | function StopAnim(ped) 61 | if previousAnim then 62 | StopEntityAnim(ped, previousAnim[2], previousAnim[1], true) 63 | previousAnim = nil 64 | end 65 | end 66 | function PlayAnimFlags(ped, dict, anim, flags) 67 | StopAnim(ped) 68 | EnsureAnimDict(dict) 69 | local len = GetAnimDuration(dict, anim) 70 | TaskPlayAnim(ped, dict, anim, 1.0, -1.0, len, flags, 1, 0, 0, 0) 71 | previousAnim = {dict, anim} 72 | end 73 | 74 | function PlayAnimUpper(ped, dict, anim) 75 | PlayAnimFlags(ped, dict, anim, 49) 76 | end 77 | function PlayAnim(ped, dict, anim) 78 | PlayAnimFlags(ped, dict, anim, 0) 79 | end 80 | 81 | 82 | 83 | RegisterNetEvent("prospecting:setTargetPool") 84 | AddEventHandler("prospecting:setTargetPool", function(pool) 85 | targetPool = {} 86 | for n, pos in next, pool do 87 | targetPool[n] = {vector3(pos[1], pos[2], pos[3]), pos[4], n} 88 | end 89 | end) 90 | 91 | local isProspecting = false 92 | local pauseProspecting = false 93 | local didCancelProspecting = false 94 | local scannerState = "none" 95 | local scannerFrametime = 0.0 96 | local scannerScale = 0.0 97 | local scannerAudio = true 98 | 99 | local CONTROLS = { 100 | ["dig"] = { 101 | label = "PROSP_DIG", 102 | control = 24, 103 | input = "INPUT_ATTACK" 104 | }, 105 | ["dig_hint"] = { 106 | label = "PROSP_DIG_HINT", 107 | control = 24, 108 | input = "INPUT_ATTACK" 109 | }, 110 | ["stop"] = { 111 | label = "PROSP_STOP", 112 | control = 75, 113 | input = "INPUT_VEH_EXIT" 114 | }, 115 | ["audio_on"] = { 116 | label = "PROSP_AUDIOON", 117 | control = 140, 118 | input = "INPUT_MELEE_ATTACK_LIGHT" 119 | }, 120 | ["audio_off"] = { 121 | label = "PROSP_AUDIOOFF", 122 | control = 140, 123 | input = "INPUT_MELEE_ATTACK_LIGHT" 124 | }, 125 | } 126 | 127 | local entityOffsets = { 128 | ["w_am_metaldetector"] = { 129 | bone = 18905, 130 | offset = vector3(0.15, 0.1, 0.0), 131 | rotation = vector3(270.0, 90.0, 80.0), 132 | }, 133 | } 134 | 135 | local attachedEntities = {} 136 | local scannerEntity = nil 137 | function AttachEntity(ped, model) 138 | if entityOffsets[model] then 139 | EnsureModel(model) 140 | local pos = GetEntityCoords(PlayerPedId()) 141 | local ent = CreateObjectNoOffset(model, pos, 1, 1, 0) 142 | AttachEntityToEntity(ent, ped, GetPedBoneIndex(ped, entityOffsets[model].bone), entityOffsets[model].offset, entityOffsets[model].rotation, 1, 1, 0, 0, 2, 1) 143 | scannerEntity = ent 144 | table.insert(attachedEntities, ent) 145 | end 146 | end 147 | 148 | function CleanupModels() 149 | for _, ent in next, attachedEntities do 150 | DetachEntity(ent, 0, 0) 151 | DeleteEntity(ent) 152 | end 153 | attachedEntities = {} 154 | scannerEntity = nil 155 | ClearPedTasksImmediately(PlayerPedId()) 156 | end 157 | 158 | function DisableAllInstructions() 159 | -- for _, inst in next, CONTROLS do 160 | -- SetInstructionalButton(inst["label"], inst["control"], false) 161 | -- end 162 | end 163 | 164 | function DigSequence(cb) 165 | DisableAllInstructions() 166 | CleanupModels() 167 | local ped = PlayerPedId() 168 | StopEntityAnim(ped, "wood_idle_a", "mini@golfai", true) 169 | TaskStartScenarioInPlace(PlayerPedId(), "WORLD_HUMAN_GARDENER_PLANT", 0, true) 170 | Wait(5000) 171 | if cb then cb() end 172 | Wait(3000) 173 | ClearPedTasks(PlayerPedId()) 174 | AttachEntity(PlayerPedId(), "w_am_metaldetector") 175 | end 176 | 177 | function ShowHelp(text, n) 178 | BeginTextCommandDisplayHelp(text) 179 | EndTextCommandDisplayHelp(n or 0, false, true, -1) 180 | end 181 | function ShowFloatingHelp(text, pos) 182 | SetFloatingHelpTextWorldPosition(1, pos) 183 | SetFloatingHelpTextStyle(1, 1, 2, -1, 3, 0) 184 | ShowHelp(text, 2) 185 | end 186 | 187 | function getClosestTarget(pos) 188 | local closest, index, closestdist, difficulty 189 | for n, target in next, targets do 190 | local dist = #(pos.xy - target[1].xy) 191 | if (not closest) or closestdist > dist then 192 | closestdist = dist 193 | index = n 194 | closest = target 195 | difficulty = target[2] 196 | end 197 | end 198 | -- Return 0,0,0 if no targets 199 | return closest or vector3(0.0, 0.0, 0.0), closestdist, index, difficulty 200 | end 201 | 202 | function DigTarget(index) 203 | pauseProspecting = true 204 | local target = table.remove(targets, index) 205 | local pos = target[1] 206 | 207 | DigSequence(function() 208 | -- TriggerServerEvent("prospecting:userCollectedNode", index, pos.x, pos.y, pos.z) 209 | lib.callback.await("prospecting:userCollectedNode", false, index, pos.x, pos.y, pos.z) 210 | end) 211 | Wait(5000) 212 | StopProspecting() 213 | scannerState = "none" 214 | pauseProspecting = false 215 | end 216 | 217 | function StopProspecting() 218 | if not didCancelProspecting then 219 | lib.hideTextUI() 220 | ShowInteraction = false 221 | didCancelProspecting = true 222 | CleanupModels() 223 | local ped = PlayerPedId() 224 | -- StopEntityAnim(ped, "wood_idle_a", "mini@golfai", true) 225 | Wait(1000) 226 | -- ClearPedTasksImmediately(PlayerPedId()) 227 | circleScale = 0.0 228 | scannerScale = 0.0 229 | scannerState = "none" 230 | isProspecting = false 231 | TriggerServerEvent("prospecting:userStoppedProspecting") 232 | end 233 | end 234 | 235 | AddEventHandler("onResourceStop", function(resource) 236 | if resource == GetCurrentResourceName() then 237 | CleanupModels() 238 | StopProspecting() 239 | end 240 | end) 241 | 242 | function StartProspecting() 243 | if not isProspecting then 244 | ProspectingThreads() 245 | end 246 | end 247 | 248 | RegisterNetEvent("prospecting:forceStart") 249 | AddEventHandler("prospecting:forceStart", function() 250 | StartProspecting() 251 | end) 252 | 253 | RegisterNetEvent("prospecting:forceStop") 254 | AddEventHandler("prospecting:forceStop", function() 255 | StopProspecting() 256 | end) 257 | 258 | AddEventHandler("onResourceStart", function(name) 259 | if GetCurrentResourceName() == name then 260 | -- init 261 | Wait(1000) -- waits 10 secs on restart to populate the data 262 | TriggerServerEvent("prospecting:userRequestsLocations") 263 | end 264 | end) 265 | 266 | function ProspectingThreads() 267 | if IsProspecting then return false end 268 | TriggerServerEvent("prospecting:userStartedProspecting") 269 | isProspecting = true 270 | didCancelProspecting = false 271 | pauseProspecting = false 272 | if not ShowInteraction then 273 | ShowInteraction = true 274 | lib.showTextUI("Left Click to Digging | [F] Stop") 275 | end 276 | -- Prospecting handler 277 | CreateThread(function() 278 | AttachEntity(PlayerPedId(), "w_am_metaldetector") 279 | while isProspecting do 280 | Wait(0) 281 | local ped = PlayerPedId() 282 | local ply = PlayerId() 283 | local canProspect = true 284 | for _, control in next, CONTROLS do 285 | DisableControlAction(0, control["control"], true) 286 | end 287 | if not IsEntityPlayingAnim(ped, "mini@golfai", "wood_idle_a", 3) then 288 | PlayAnimUpper(PlayerPedId(), "mini@golfai", "wood_idle_a") 289 | end 290 | 291 | -- Actions that halt prospecting animations and scanning 292 | local restrictedMovement = false 293 | restrictedMovement = restrictedMovement or IsPedFalling(ped) 294 | restrictedMovement = restrictedMovement or IsPedJumping(ped) 295 | restrictedMovement = restrictedMovement or IsPedSprinting(ped) 296 | restrictedMovement = restrictedMovement or IsPedRunning(ped) 297 | restrictedMovement = restrictedMovement or IsPlayerFreeAiming(ply) 298 | restrictedMovement = restrictedMovement or IsPedRagdoll(ped) 299 | restrictedMovement = restrictedMovement or IsPedInAnyVehicle(ped) 300 | restrictedMovement = restrictedMovement or IsPedInCover(ped) 301 | restrictedMovement = restrictedMovement or IsPedInMeleeCombat(ped) 302 | 303 | if restrictedMovement then canProspect = false end 304 | if canProspect then 305 | local pos = GetEntityCoords(ped) + vector3(GetEntityForwardX(ped) * 0.75, GetEntityForwardY(ped) * 0.75, -0.75) 306 | 307 | -- local pos = GetWorldPositionOfEntityBone(scannerEntity, 0) 308 | local target, dist, index, difficulyModifier = getClosestTarget(pos) 309 | if index then 310 | local dist = dist * difficulyModifier 311 | if dist < 3.0 then 312 | -- SetInstructionalButton(CONTROLS["dig"]["label"], CONTROLS["dig"]["control"], true) 313 | -- ShowFloatingHelp(CONTROLS["dig_hint"]["label"], pos) 314 | if IsDisabledControlJustPressed(0, CONTROLS["dig"]["control"]) then 315 | DigTarget(index) 316 | end 317 | else 318 | 319 | -- SetInstructionalButton(CONTROLS["dig"]["label"], CONTROLS["dig"]["control"], false) 320 | end 321 | if dist < 3.0 then 322 | circleScale = 0.0 323 | scannerScale = 0.0 324 | scannerState = "ultra" 325 | elseif dist < 4.0 then 326 | scannerFrametime = 0.35 327 | scannerScale = 4.50 328 | scannerState = "fast" 329 | elseif dist < 5.0 then 330 | scannerFrametime = 0.4 331 | scannerScale = 3.75 332 | scannerState = "fast" 333 | elseif dist < 6.5 then 334 | scannerFrametime = 0.425 335 | scannerScale = 3.00 336 | scannerState = "fast" 337 | elseif dist < 7.5 then 338 | scannerFrametime = 0.45 339 | scannerScale = 2.50 340 | scannerState = "fast" 341 | elseif dist < 10.0 then 342 | scannerFrametime = 0.5 343 | scannerScale = 1.75 344 | scannerState = "fast" 345 | elseif dist < 12.5 then 346 | scannerFrametime = 0.75 347 | scannerScale = 1.25 348 | scannerState = "medium" 349 | elseif dist < 15.0 then 350 | scannerFrametime = 1.0 351 | scannerScale = 1.00 352 | scannerState = "medium" 353 | elseif dist < 20.0 then 354 | scannerFrametime = 1.25 355 | scannerScale = 0.875 356 | scannerState = "medium" 357 | elseif dist < 25.0 then 358 | scannerFrametime = 1.5 359 | scannerScale = 0.75 360 | scannerState = "slow" 361 | elseif dist < 30.0 then 362 | scannerFrametime = 2.0 363 | scannerScale = 0.5 364 | scannerState = "slow" 365 | else 366 | circleScale = 0.0 367 | scannerScale = 0.0 368 | scannerState = "none" 369 | end 370 | scannerDistance = dist 371 | else 372 | circleScale = 0.0 373 | scannerScale = 0.0 374 | scannerState = "none" 375 | end 376 | -- SetInstructionalButton(CONTROLS["stop"]["label"], CONTROLS["stop"]["control"], true) 377 | if IsDisabledControlJustPressed(0, CONTROLS["stop"]["control"]) then 378 | isProspecting = false 379 | end 380 | if IsDisabledControlJustPressed(0, CONTROLS["audio_on"]["control"]) then 381 | PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0) 382 | scannerAudio = not scannerAudio 383 | end 384 | end 385 | if not canProspect then 386 | -- Ped is busy and can't prospect at this time (like falling or w/e) 387 | StopEntityAnim(ped, "wood_idle_a", "mini@golfai", true) 388 | circleScale = 0.0 389 | scannerScale = 0.0 390 | scannerState = "none" 391 | end 392 | if not isProspecting then 393 | -- We stopped prospecting mid-frame 394 | CleanupModels() 395 | StopEntityAnim(ped, "wood_idle_a", "mini@golfai", true) 396 | circleScale = 0.0 397 | scannerScale = 0.0 398 | scannerState = "none" 399 | end 400 | end 401 | DisableAllInstructions() 402 | StopProspecting() 403 | end) 404 | 405 | -- Marker rendering 406 | -- Audio 407 | CreateThread(function() 408 | local framecount = 0 409 | local frametime = 0 410 | local circleScale = 0.0 411 | local circleR, circleG, circleB, circleA = 255, 255, 255, 255 412 | local _circleR, _circleG, _circleB = 255, 255, 255 413 | local circleScaleMultiplier = 1.5 414 | local renderCircle = false 415 | while isProspecting do 416 | Wait(0) 417 | if not pauseProspecting then 418 | local ped = PlayerPedId() 419 | local pos = GetEntityCoords(ped) + vector3(GetEntityForwardX(ped) * 0.75, GetEntityForwardY(ped) * 0.75, -0.75) 420 | -- local pos = GetWorldPositionOfEntityBone(scannerEntity, 0) 421 | if scannerState == "none" then 422 | renderCircle = false 423 | circleR, circleG, circleB = 150, 255, 150 424 | _circleR, _circleG, _circleB = 150, 255, 150 425 | if Config.ShowDrawMaker then 426 | circleSize = (circleScale % 100) / 100 427 | circleA = math.floor(255 - ((circleScale % 100) / 100) * 255) 428 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.1, circleR, circleG, circleB, circleA) 429 | end 430 | elseif scannerState == "slow" then 431 | renderCircle = true 432 | circleScale = circleScale + scannerScale 433 | circleR, circleG, circleB = 150, 255, 150 434 | if frametime > scannerFrametime then 435 | frametime = 0.0 436 | end 437 | if Config.ShowDrawMaker then 438 | circleSize = (circleScale % 100) / 100 439 | circleA = math.floor(255 - ((circleScale % 100) / 100) * 255) 440 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.1, circleR, circleG, circleB, circleA) 441 | end 442 | elseif scannerState == "medium" then 443 | renderCircle = true 444 | circleScale = circleScale + scannerScale 445 | circleR, circleG, circleB = 255, 255, 150 446 | if frametime > scannerFrametime then 447 | frametime = 0.0 448 | end 449 | elseif scannerState == "fast" then 450 | renderCircle = true 451 | circleScale = circleScale + scannerScale 452 | circleR, circleG, circleB = 255, 150, 150 453 | if frametime > scannerFrametime then 454 | frametime = 0.0 455 | end 456 | elseif scannerState == "ultra" then 457 | renderCircle = false 458 | circleScale = circleScale + scannerScale 459 | circleR, circleG, circleB = 255, 100, 100 460 | if frametime > 0.125 then 461 | frametime = 0.0 462 | if scannerAudio then PlaySoundFrontend(-1, "ATM_WINDOW", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0) end 463 | -- PlaySoundFrontend(-1, "TIMER_STOP", "HUD_MINI_GAME_SOUNDSET", 0) 464 | if scannerAudio then PlaySoundFrontend(-1, "BOATS_PLANES_HELIS_BOOM", "MP_LOBBY_SOUNDS", 0) end 465 | end 466 | if Config.ShowDrawMaker then 467 | circleA = 150 468 | circleSize = 1.20 * circleScaleMultiplier 469 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.2, circleR, circleG, circleB, circleA) 470 | DrawMarker(6, pos, 0.0, 0.0, 0.0, 270.0, 0.0, 0.0, circleSize, 0.1, circleSize, circleR, circleG, circleB, circleA) 471 | circleA = 200 472 | circleSize = 0.70 * circleScaleMultiplier 473 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.2, circleR, circleG, circleB, circleA) 474 | DrawMarker(6, pos, 0.0, 0.0, 0.0, 270.0, 0.0, 0.0, circleSize, 0.1, circleSize, circleR, circleG, circleB, circleA) 475 | circleA = 255 476 | circleSize = 0.20 * circleScaleMultiplier 477 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.2, circleR, circleG, circleB, circleA) 478 | DrawMarker(6, pos, 0.0, 0.0, 0.0, 270.0, 0.0, 0.0, circleSize, 0.1, circleSize, circleR, circleG, circleB, circleA) 479 | end 480 | end 481 | if renderCircle then 482 | if circleScale > 100 then 483 | while circleScale > 100 do 484 | circleScale = circleScale - 100 485 | end 486 | _circleR, _circleG, _circleB = circleR, circleG, circleB 487 | -- PlaySoundFrontend(-1, "BOATS_PLANES_HELIS_BOOM", "MP_LOBBY_SOUNDS", 0) 488 | if scannerAudio then PlaySoundFrontend(-1, "ATM_WINDOW", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0) end 489 | 490 | end 491 | if Config.ShowDrawMaker then 492 | circleSize = ((circleScale % 100) / 100) * circleScaleMultiplier 493 | circleA = math.floor(255 - ((circleScale % 100) / 100) * 155) 494 | DrawMarker(1, pos, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, circleSize, circleSize, 0.2, _circleR, _circleG, _circleB, circleA) 495 | DrawMarker(6, pos, 0.0, 0.0, 0.0, 270.0, 0.0, 0.0, circleSize, 0.1, circleSize, _circleR, _circleG, _circleB, circleA) 496 | end 497 | end 498 | 499 | framecount = (framecount + 1) % 120 500 | frametime = frametime + Timestep() 501 | end 502 | end 503 | end) 504 | 505 | -- Location updater 506 | -- Adds nearby targets to the target pool 507 | -- Prevents client from doing frame-checks on targets across the map 508 | CreateThread(function() 509 | while isProspecting do 510 | local pos = GetEntityCoords(PlayerPedId()) 511 | local newTargets = {} 512 | for n, target in next, targetPool do 513 | if #(pos.xy - target[1].xy) < maxTargetRange then 514 | newTargets[#newTargets + 1] = {target[1], target[2], n} 515 | end 516 | end 517 | targets = newTargets 518 | Wait(10000) 519 | end 520 | end) 521 | return true 522 | end 523 | 524 | 525 | CreateThread(function() 526 | if Config.ShowBlip then 527 | for _, zone in next, Config.Zones do 528 | local blip = AddBlipForRadius(zone.coords.x, zone.coords.y, zone.coords.z, zone.zoneSize * 1.0) 529 | SetBlipColour(blip, 1) 530 | SetBlipAlpha(blip, 55) 531 | 532 | local blip2 = AddBlipForCoord(zone.coords.x, zone.coords.y, zone.coords.z) 533 | SetBlipSprite(blip2, 485) 534 | SetBlipColour(blip2, 0) 535 | SetBlipAlpha(blip2, 128) 536 | SetBlipAsShortRange(blip2, true) 537 | BeginTextCommandSetBlipName("STRING") 538 | AddTextComponentString("Prospecting") 539 | EndTextCommandSetBlipName(blip2) 540 | end 541 | end 542 | end) -------------------------------------------------------------------------------- /scripts/sv_prospecting.lua: -------------------------------------------------------------------------------- 1 | -----------------For support, scripts, and more---------------- 2 | --------------- https://discord.gg/VGYkkAYVv2 ------------- 3 | --------------------------------------------------------------- 4 | 5 | QBCore, ESX = nil, nil 6 | 7 | if Config.Core == "QBCore" then 8 | QBCore = exports['qb-core']:GetCoreObject() 9 | elseif Config.Core == "ESX" then 10 | ESX = exports['es_extended']:getSharedObject() 11 | end 12 | 13 | local PROSPECTING_STATUS = {} 14 | local PROSPECTING_TARGETS = {} 15 | 16 | local PROSPECTING_DIFFICULTIES = {} 17 | 18 | --[[ Common ]] 19 | function UpdateProspectingTargets(player) 20 | local targets = {} 21 | for _, target in next, PROSPECTING_TARGETS do 22 | local difficulty = PROSPECTING_DIFFICULTIES[target.resource] or 1.0 23 | targets[#targets + 1] = {target.x, target.y, target.z, difficulty} 24 | end 25 | TriggerClientEvent("prospecting:setTargetPool", player, targets) 26 | end 27 | 28 | function InsertProspectingTarget(resource, x, y, z, data) 29 | PROSPECTING_TARGETS[#PROSPECTING_TARGETS + 1] = {resource = resource, data = data, x = x, y = y, z = z} 30 | end 31 | 32 | function InsertProspectingTargets(resource, targets) 33 | for _, target in next, targets do 34 | InsertProspectingTarget(resource, target.x, target.y, target.z, target.data) 35 | end 36 | end 37 | 38 | local function RemoveTargetIndex(coords) 39 | for index, target in next, PROSPECTING_TARGETS do 40 | local dx, dy, dz = target.x, target.y, target.z 41 | if math.floor(dx) == math.floor(coords.x) and math.floor(dy) == math.floor(coords.y) and math.floor(dz) == math.floor(coords.z) then 42 | table.remove(PROSPECTING_TARGETS, index) 43 | break 44 | end 45 | end 46 | end 47 | 48 | function RemoveProspectingTarget(coords) 49 | RemoveTargetIndex(coords) 50 | TriggerClientEvent("prospecting:client:removeTarget", -1, coords) 51 | end 52 | 53 | function FindMatchingPickup(x, y, z) 54 | for index, target in next, PROSPECTING_TARGETS do 55 | local dx, dy, dz = target.x, target.y, target.z 56 | if math.floor(dx) == math.floor(x) and math.floor(dy) == math.floor(y) and math.floor(dz) == math.floor(z) then 57 | return index 58 | end 59 | end 60 | return nil 61 | end 62 | 63 | function HandleProspectingPickup(player, index, x, y, z) 64 | local target = PROSPECTING_TARGETS[index] 65 | if target then 66 | local dx, dy, dz = target.x, target.y, target.z 67 | local resource, data = target.resource, target.data 68 | if math.floor(dx) == math.floor(x) and math.floor(dy) == math.floor(y) and math.floor(dz) == math.floor(z) then 69 | RemoveProspectingTarget(vec3(x, y, z)) 70 | OnCollected(player, resource, data, x, y, z) 71 | else 72 | local newMatch = FindMatchingPickup(x, y, z) 73 | if newMatch then 74 | HandleProspectingPickup(player, newMatch, x, y, z) 75 | end 76 | end 77 | else 78 | end 79 | end 80 | 81 | local function AddItem(id, name, amount) 82 | if Config.Core == "QBCore" then 83 | local Player = QBCore.Functions.GetPlayer(id) 84 | Player.Functions.AddItem(name, amount) 85 | TriggerClientEvent("inventory:client:ItemBox", id, QBCore.Shared.Items[name], "add") 86 | elseif Config.Core == "ESX" then 87 | local xPlayer = ESX.GetPlayerFromId(id) 88 | xPlayer.addInventoryItem(name, amount) 89 | end 90 | end 91 | 92 | function OnCollected(player, resource, data, x, y, z) 93 | 94 | local items = {} 95 | math.randomseed(os.time()) 96 | local randomizer = math.random(1, 100) 97 | if randomizer < Config.Chances.epic then 98 | items = Config.Items[data]["epic"] or Config.DefaultItems 99 | elseif randomizer < Config.Chances.rare and randomizer > Config.Chances.epic then 100 | items = Config.Items[data]["rare"] or Config.DefaultItems 101 | else 102 | items = Config.Items[data]["common"] or Config.DefaultItems 103 | end 104 | local item = items[math.random(1, #items)] 105 | local amount = math.random(item.min, item.max) 106 | AddItem(player, item.name, amount) 107 | end 108 | 109 | --[[ Export handling ]] 110 | 111 | function AddProspectingTarget(x, y, z, data) 112 | local resource = GetInvokingResource() 113 | InsertProspectingTarget(resource, x, y, z, data) 114 | end 115 | 116 | function AddProspectingTargets(list) 117 | local resource = GetInvokingResource() 118 | InsertProspectingTargets(resource, list) 119 | 120 | end 121 | 122 | function StartProspecting(player) 123 | if not PROSPECTING_STATUS[player] then 124 | TriggerClientEvent("prospecting:forceStart", player) 125 | end 126 | end 127 | AddEventHandler("prospecting:StartProspecting", function(player) 128 | StartProspecting(player) 129 | end) 130 | 131 | function StopProspecting(player) 132 | if PROSPECTING_STATUS[player] then 133 | TriggerClientEvent("prospecting:forceStop", player) 134 | end 135 | end 136 | AddEventHandler("prospecting:StopProspecting", function(player) 137 | StopProspecting(player) 138 | end) 139 | 140 | function IsProspecting(player) 141 | return PROSPECTING_STATUS[player] ~= nil 142 | end 143 | 144 | function SetDifficulty(modifier) 145 | local resource = GetInvokingResource() 146 | PROSPECTING_DIFFICULTIES[resource] = modifier 147 | end 148 | 149 | --[[ Client triggered events ]] 150 | 151 | -- When the client stops prospecting 152 | RegisterServerEvent("prospecting:userStoppedProspecting") 153 | AddEventHandler("prospecting:userStoppedProspecting", function() 154 | local player = source 155 | if PROSPECTING_STATUS[player] then 156 | local time = GetGameTimer() - PROSPECTING_STATUS[player] 157 | PROSPECTING_STATUS[player] = nil 158 | TriggerEvent("prospecting:onStop", player, time) 159 | end 160 | end) 161 | 162 | -- When the client starts prospecting 163 | RegisterServerEvent("prospecting:userStartedProspecting") 164 | AddEventHandler("prospecting:userStartedProspecting", function() 165 | local player = source 166 | if not PROSPECTING_STATUS[player] then 167 | PROSPECTING_STATUS[player] = GetGameTimer() 168 | TriggerEvent("prospecting:onStart", player) 169 | end 170 | end) 171 | 172 | -- When the client collects a node 173 | -- RegisterServerEvent("prospecting:userCollectedNode") 174 | -- AddEventHandler("prospecting:userCollectedNode", function(index, x, y, z) 175 | lib.callback.register("prospecting:userCollectedNode", function(source, index, x, y, z) 176 | local player = source 177 | if PROSPECTING_STATUS[player] then 178 | HandleProspectingPickup(player, index, x, y, z) 179 | end 180 | end) 181 | 182 | RegisterServerEvent("prospecting:userRequestsLocations") 183 | AddEventHandler("prospecting:userRequestsLocations", function() 184 | local player = source 185 | UpdateProspectingTargets(player) 186 | end) 187 | 188 | -- thread to setup prospecting target at server start 189 | 190 | --command to start and stop prospecting 191 | 192 | CreateThread(function() 193 | if Config.Core == "QBCore" then 194 | QBCore.Functions.CreateUseableItem(Config.DetectorItem, function(source, item) 195 | if Prospecting.IsProspecting(source) then 196 | Prospecting.StopProspecting(source) 197 | else 198 | Prospecting.StartProspecting(source) 199 | end 200 | 201 | end) 202 | elseif Config.Core == "ESX" then 203 | ESX.RegisterUsableItem(Config.DetectorItem, function(source) 204 | if Prospecting.IsProspecting(source) then 205 | Prospecting.StopProspecting(source) 206 | else 207 | Prospecting.StartProspecting(source) 208 | end 209 | end) 210 | end 211 | end) 212 | 213 | 214 | CreateThread(function() 215 | for k, v in pairs(Config.Zones) do 216 | GenerateCoords(v.coords, v.data, v.zoneSize, v.zoneLocations) 217 | end 218 | end) 219 | 220 | 221 | function GenerateCoords(coords, data, zoneSize, zoneLocations) 222 | local coordslist = {} 223 | local totalLocationsForOneCoord = zoneLocations 224 | while totalLocationsForOneCoord > 0 do 225 | totalLocationsForOneCoord = totalLocationsForOneCoord - 1 226 | 227 | local coordX, coordY 228 | 229 | local modX = math.random(-zoneSize, zoneSize) 230 | 231 | 232 | local modY = math.random(-zoneSize, zoneSize) 233 | 234 | coordX = coords.x + modX 235 | coordY = coords.y + modY 236 | coordslist[#coordslist + 1] = {x = coordX, y = coordY, z = coords.z, data = data} 237 | end 238 | AddProspectingTargets(coordslist) 239 | end -------------------------------------------------------------------------------- /stream/gen_w_am_metaldetector.ytyp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipe-scripts/prospecting/d89f2c05156839230046207b2e31dbc33138aacf/stream/gen_w_am_metaldetector.ytyp -------------------------------------------------------------------------------- /stream/w_am_metaldetector.ydr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipe-scripts/prospecting/d89f2c05156839230046207b2e31dbc33138aacf/stream/w_am_metaldetector.ydr -------------------------------------------------------------------------------- /stream/w_am_metaldetector.ytd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipe-scripts/prospecting/d89f2c05156839230046207b2e31dbc33138aacf/stream/w_am_metaldetector.ytd --------------------------------------------------------------------------------