├── .gitattributes ├── README.md ├── client ├── client.lua └── framework.lua ├── config.lua ├── fxmanifest.lua ├── locales.lua └── server └── server.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ESX] Hunting System 2 | 3 | 🚀 Easily configurable with config.lua 4 | 5 | # 🔗 Installation 6 | 1. Download the repository 7 | 2. Copy the `nx_hunting` folder to your server resources folder 8 | 3. Add `ensure nx_hunting` to your server.cfg or put the resource folder into a `[collection]` of resources 9 | 4. Restart the server 10 | 11 | # ⭐ Features 12 | 13 | ### Optimization 14 | * 0.00ms | Idle and In Usage 15 | ### Customization 16 | * Add new hunting zones 17 | * Change Rarity Spawn of animals 18 | * Change Animal Loots 19 | * Add new animal models to zones 20 | 21 | # 💥 Dependency 22 | - [ox_inventory](https://github.com/overextended/ox_inventory) 23 | - [ox_target](https://github.com/overextended/ox_target) 24 | - [rprogress](https://github.com/Mobius1/rprogress) 25 | 26 | # 🎬 Video 27 | [Youtube](https://youtu.be/y6uEAt_Nytk) 28 | -------------------------------------------------------------------------------- /client/client.lua: -------------------------------------------------------------------------------- 1 | local loaded = false 2 | 3 | AddEventHandler('onClientResourceStart', function (resource) 4 | if GetCurrentResourceName() == resource then 5 | if Framework == "ESX" then 6 | if not ESX.IsPlayerLoaded() then return end 7 | elseif Framework == "QB" then 8 | if not LocalPlayer.state['isLoggedIn'] then return end 9 | end 10 | ConfigureZones(Config.Zones) 11 | ConfigureBlips(Config.Debug, Config.Zones) 12 | loaded = true 13 | end 14 | end) 15 | 16 | PlayerLoaded = function() 17 | 18 | if loaded then return end 19 | ConfigureZones(Config.Zones) 20 | ConfigureBlips(Config.Debug, Config.Zones) 21 | loaded = true 22 | end 23 | 24 | if Config.Framework == "esx" then 25 | RegisterNetEvent("esx:playerLoaded", PlayerLoaded) 26 | elseif Config.Framework == "qb" then 27 | RegisterNetEvent("QBCore:Client:OnPlayerLoaded", PlayerLoaded) 28 | else 29 | print("Unsopported Framework") 30 | return 31 | end 32 | 33 | 34 | local isInArea = false 35 | -- it contains zone animals 36 | local Animals = {} 37 | -- it contains animal npc's spawned 38 | local AnimalsNPC = {} 39 | local ox_options = {} 40 | local animalID = '' 41 | local animalZone = nil 42 | local displayRadar = not IsRadarHidden() 43 | local charmedAnimals = {} 44 | 45 | 46 | ---Get a random coord in a specific area 47 | ---@param _radius number the radius of the area 48 | ---@param _center vector3 the center of the area 49 | ---@param debug boolean shall we need the debug print now? 50 | ---@return vector3 safePosition the positiion in the world 51 | local getRandomCoord = function (_radius, _center, debug) 52 | 53 | local reducedRadius = _radius - (_radius * 0.25) 54 | reducedRadius = math.min(80, reducedRadius) 55 | local radius = math.random(reducedRadius * 0.35, reducedRadius) 56 | local coords = _center 57 | 58 | local x = coords.x + math.random(-radius, radius) 59 | local y = coords.y + math.random(-radius, radius) 60 | local _, safeZ, safePosition 61 | 62 | _, safeZ = GetGroundZFor_3dCoord(x, y, coords.z, true) 63 | 64 | safePosition = vector3(x, y, safeZ) 65 | 66 | if Config.Debug and debug then 67 | print(Lang["spawning"]:format(safePosition, radius)) 68 | end 69 | 70 | return safePosition 71 | end 72 | 73 | ---Remove one element from table by key 74 | ---@param Table table table 75 | ---@param Key string the index in the table 76 | local removeFromTable = function (Table, Key) 77 | for k, v in pairs(Table) do 78 | if v.npcEntity == Key then 79 | if Config.Debug then 80 | print(Lang["delete"]:format(v.npcEntity)) 81 | end 82 | table.remove(Table, k) 83 | end 84 | end 85 | end 86 | 87 | ---Handle the hunting 88 | ---@param data table 89 | Hunt = function (data) 90 | 91 | for k, v in pairs(AnimalsNPC) do 92 | if v.npcEntity == data.entity then 93 | animalID = v.name 94 | RemoveBlip(v.blip) 95 | end 96 | end 97 | -- remove the ox_option 98 | exports.ox_target:removeLocalEntity(data.entity, ox_options.name) 99 | -- remove from the spawned animal npc's table 100 | removeFromTable(AnimalsNPC, data.entity) 101 | 102 | if Config.Progress == "ox_lib" then 103 | LibProgressConfiguration(data.entity) 104 | elseif Config.Progress == "rprogress" then 105 | RProgressConfiguration(data.entity) 106 | end 107 | 108 | 109 | end 110 | 111 | ---Create the map blip for the entity 112 | ---@param entity number model entity 113 | ---@param scale number the scale of the blip 114 | ---@return number blip the created blip 115 | local createBlipForEntity = function (entity, scale) 116 | local blip = AddBlipForEntity(entity) 117 | SetBlipScale(blip, scale) 118 | return blip 119 | end 120 | 121 | ---Handle rarity 122 | ---@param rarity number 123 | ---@return boolean true if can spawn, else false 124 | local handleRarity = function (rarity) 125 | local random = math.random(100) 126 | 127 | if random < rarity then 128 | return false 129 | end 130 | 131 | return true 132 | end 133 | 134 | ---Handle the animal spawn 135 | ---@param huntZones table the table containing the hunt Zones 136 | ---@param zone string the index (key) in the Config.Zones table 137 | local spawnAnimals = function (huntZones, zone) 138 | 139 | Animals = huntZones[zone].Animals 140 | 141 | Citizen.CreateThread(function () 142 | 143 | local sleep = Config.SECONDS * 1 144 | while isInArea do 145 | 146 | if not (#AnimalsNPC > Config.MaxEntities) then 147 | 148 | for k, v in pairs(Animals) do 149 | 150 | if handleRarity(v.rarity) then 151 | 152 | if Config.Debug then 153 | print(Lang["not-spawning"]:format(v.model)) 154 | end 155 | 156 | sleep = Config.SECONDS * 5 157 | Wait(sleep) 158 | goto continue 159 | end 160 | 161 | local hashKey = GetHashKey(v.model) 162 | RequestModel(hashKey) 163 | while not HasModelLoaded(hashKey) do 164 | Wait(1) 165 | end 166 | 167 | if not isInArea then return end 168 | 169 | local animalNPC = CreatePed(4, hashKey, getRandomCoord(huntZones[zone].radius, huntZones[zone].position, true), 0.0, true, true) 170 | 171 | local blip = createBlipForEntity(animalNPC, .65) 172 | 173 | local coords = getRandomCoord(huntZones[zone].radius, huntZones[zone].position, false) 174 | 175 | if Config.Debug then 176 | print(Lang["moving-to"]:format(coords)) 177 | end 178 | 179 | -- si muovono verso coords nell'area 180 | TaskWanderInArea(animalNPC, coords.x, coords.y, coords.z , 100, 10, 10) 181 | -- si muovono a casa 182 | --TaskWanderStandard(animalNPC, 6, 6) 183 | SetBlockingOfNonTemporaryEvents(animalNPC, true) 184 | 185 | table.insert(AnimalsNPC, { 186 | npcEntity = animalNPC, 187 | npcCoords = coords, 188 | blip = blip, 189 | name = k 190 | }) 191 | 192 | -- animalID = k 193 | animalZone = zone 194 | 195 | exports.ox_target:addLocalEntity(animalNPC, { 196 | label = 'Hunt', 197 | name = 'hunt', 198 | icon = 'fa-solid fa-eye', 199 | distance = 1.7, 200 | item = Config.Item, 201 | onSelect = Hunt, 202 | canInteract = function (entity, distance, coords, name, bone) 203 | -- 1 - melee, 2 - explosive, 3 - any other 204 | return IsPedArmed(PlayerPedId(), 1) and IsEntityDead(entity) 205 | end, 206 | }) 207 | 208 | Wait(Config.SECONDS * 15) 209 | ::continue:: 210 | end 211 | 212 | else 213 | 214 | sleep = Config.SECONDS * 5 215 | 216 | if Config.Debug then 217 | print(Lang["max-entity"]:format(Config.MaxEntities)) 218 | end 219 | end 220 | 221 | Wait(sleep) 222 | end 223 | end) 224 | end 225 | 226 | --- Remove options from the remaining animals 227 | ---@param Table table the table with npc Animals 228 | ---@param Name string the name of the ox option 229 | local removeOptions = function (Table, Name) 230 | for k, v in pairs(Table) do 231 | exports.ox_target:removeLocalEntity(v.npcEntity, Name) 232 | end 233 | end 234 | 235 | ---Remove animal map blips 236 | ---@param Table table animals npc table 237 | local removeBlips = function (Table) 238 | for k, v in pairs(Table) do 239 | RemoveBlip(v.blip) 240 | end 241 | end 242 | 243 | ---Handle the distance between hunting zones 244 | ---@param huntZones table table containing hunting zones 245 | ConfigureZones = function (huntZones) 246 | 247 | for zone, postalCode in pairs(huntZones) do 248 | lib.zones.sphere({ 249 | coords = postalCode.position, 250 | radius = postalCode.radius, 251 | onEnter = function(self, data) 252 | 253 | isInArea = true 254 | spawnAnimals(huntZones, zone) 255 | if Config.Debug then 256 | print(Lang["we-are"]:format(zone)) 257 | end 258 | 259 | end, 260 | onExit = function(self, data) 261 | 262 | if Config.Debug then 263 | print(Lang["we-are-not"]) 264 | end 265 | 266 | isInArea = false 267 | removeBlips(AnimalsNPC) 268 | removeOptions(AnimalsNPC, ox_options.name) 269 | AnimalsNPC = {} 270 | 271 | end, 272 | debug = false 273 | }) 274 | end 275 | end 276 | 277 | ---Blip configuraton 278 | ---@param configureBlip boolean debug mode 279 | ---@param huntZones table 280 | ConfigureBlips = function (configureBlip, huntZones) 281 | 282 | if configureBlip then 283 | for k, zone in pairs(huntZones) do 284 | 285 | local blip = AddBlipForRadius(zone.position, zone.radius) 286 | 287 | SetBlipColour(blip, zone.Blip.color) 288 | SetBlipAlpha (blip, 128) 289 | 290 | local blip = AddBlipForCoord(zone.position) 291 | 292 | SetBlipSprite(blip, zone.Blip.sprite) 293 | SetBlipDisplay(blip, 4) 294 | SetBlipScale (blip, 0.9) 295 | SetBlipColour (blip, zone.Blip.color) 296 | SetBlipAsShortRange(blip, true) 297 | 298 | BeginTextCommandSetBlipName("STRING") 299 | AddTextComponentString('Hunting Zone') 300 | EndTextCommandSetBlipName(blip) 301 | end 302 | end 303 | end 304 | 305 | ---Pick random elements from a table 306 | ---@param Table table the table to iterate 307 | ---@param Count number number of elements 308 | ---@return table 309 | local pickRandomElements = function (Table, Count) 310 | 311 | local count = 0 312 | local _table = {} 313 | 314 | if Config.Debug then 315 | print(Lang["charming"]:format(Count)) 316 | end 317 | 318 | while count < Count do 319 | 320 | local animal = Table[math.random(#Table)].npcEntity 321 | 322 | table.insert(_table, animal) 323 | 324 | count = count + 1 325 | Wait(Config.SECONDS) 326 | end 327 | 328 | return _table 329 | end 330 | 331 | ---ox_lib configuration 332 | ---@param entity number entity number 333 | LibProgressConfiguration = function (entity) 334 | if OnStart(entity) then 335 | if lib.progressCircle({ 336 | duration = 4000, 337 | label = "Hunting..", 338 | canCancel = true, 339 | useWhileDead = false, 340 | allowCuffed = false, 341 | }) then 342 | Complete(entity) 343 | end 344 | end 345 | end 346 | 347 | ---rprogress configuration 348 | ---@param entity number entity number 349 | RProgressConfiguration = function (entity) 350 | exports.rprogress:Custom({ 351 | canCancel = true, 352 | cancelKey = 178, 353 | Duration = 4000, 354 | y = 0.7, 355 | Label = 'Hunting..', 356 | Color = "rgba(255, 255, 255, 1.0)", 357 | onStart = function () 358 | OnStart(entity) 359 | end, 360 | 361 | onComplete = function () 362 | Complete(entity) 363 | end 364 | }) 365 | end 366 | 367 | OnStart = function (entity) 368 | -- animal 369 | if IsEntityDead(entity) then 370 | -- ClearPedTasksImmediately(entity) 371 | -- FreezeEntityPosition(entity, true) 372 | else 373 | return false 374 | end 375 | 376 | -- hunter 377 | FreezeEntityPosition(PlayerPedId(), true) 378 | DisableControlAction(2, 32, true ) 379 | DisableControlAction(2, 33, true ) 380 | DisableControlAction(2, 34, true ) 381 | DisableControlAction(2, 35, true ) 382 | 383 | local dict = 'mini@repair' 384 | local flag = 'fixing_a_ped' 385 | 386 | RequestAnimDict(dict) 387 | while not HasAnimDictLoaded(dict) do 388 | Wait(0) 389 | end 390 | 391 | RequestAnimSet( "move_ped_crouched" ) 392 | while ( not HasAnimSetLoaded( "move_ped_crouched" ) ) do 393 | Wait(0) 394 | end 395 | 396 | SetPedMovementClipset(PlayerPedId(), "move_ped_crouched", 1) 397 | TaskPlayAnim(PlayerPedId(), dict, flag, 8.0, 8.0 , 8000, 16, 1, false, false, false) 398 | 399 | return true 400 | end 401 | 402 | ---On Complete Function 403 | ---@param entity number entity number 404 | Complete = function (entity) 405 | -- animal 406 | SetEntityHealth(entity, 0) 407 | -- FreezeEntityPosition(entity, false) 408 | 409 | --hunter 410 | ClearPedTasks(PlayerPedId()) 411 | DisableControlAction(2, 32, false ) -- W 412 | DisableControlAction(2, 33, false ) -- A 413 | DisableControlAction(2, 34, false ) -- S 414 | DisableControlAction(2, 35, false ) -- D 415 | FreezeEntityPosition(PlayerPedId(), false) 416 | ResetPedMovementClipset(PlayerPedId(), 0) 417 | 418 | if Config.Debug then 419 | print(Lang["hunted"]:format(entity)) 420 | end 421 | 422 | -- get the loots for that specific animal 423 | local loots = Config.Zones[animalZone].Animals[animalID].loots 424 | -- we choose a random loot 425 | local loot = loots[math.random(1, #loots)] 426 | -- get a random quantity 427 | local count = math.random(0, Config.MaximumItemsPerKill) 428 | TriggerServerEvent('giveInventoryItem', animalZone, loot, count) 429 | end 430 | 431 | --- Commands 432 | 433 | RegisterCommand('charm', function () 434 | if isInArea then 435 | charmedAnimals = pickRandomElements(AnimalsNPC, math.random(0, #AnimalsNPC)) 436 | for k, v in pairs(charmedAnimals) do 437 | 438 | ClearPedTasksImmediately(v) 439 | TaskGoToEntity(v, PlayerPedId(), -1, 1.0, 1.49, 0, 0) 440 | 441 | if Config.Debug then 442 | print(Lang["charmed"]:format(v)) 443 | end 444 | end 445 | charmedAnimals = {} 446 | end 447 | end, false) 448 | RegisterKeyMapping('charm', 'Charm animals', 'keyboard', 'b') 449 | 450 | 451 | -- Handle the timer 452 | Citizen.CreateThread(function () 453 | while true do 454 | Wait(1000) 455 | while displayRadar do 456 | Wait(1000 * Config.RadarTime) 457 | displayRadar = false 458 | DisplayRadar(displayRadar) 459 | end 460 | end 461 | end) 462 | 463 | RegisterCommand('radar', function () 464 | if isInArea then 465 | displayRadar = not displayRadar 466 | DisplayRadar(displayRadar) 467 | end 468 | end, false) 469 | RegisterKeyMapping('radar', 'Enable radar', 'keyboard', 'u') -------------------------------------------------------------------------------- /client/framework.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextscripts-5m/nx_hunting/b5ecc7cf5b261e304775e279637028c4f6b72c4c/client/framework.lua -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | -- You can choose between "esx" or "qb" 4 | Config.Framework = "esx" 5 | 6 | Config.Debug = true 7 | 8 | --[[ 9 | you can choose between "ox_lib" and "rprogress" 10 | ]] 11 | Config.Progress = "rprogress" 12 | 13 | Config.MINUTE = 60 * 1000 14 | Config.SECONDS = 1000 15 | 16 | Config.MaxEntities = 10 17 | 18 | -- in seconds 19 | Config.RadarTime = 5 20 | 21 | Config.Item = 'WEAPON_KNIFE' 22 | 23 | Config.MaximumItemsPerKill = 4 24 | 25 | Config.BanFunction = function () 26 | -- you can decide what to do if a cheater try to give items 27 | end 28 | 29 | Config.Zones = { 30 | 31 | Zone1 = { 32 | position = vector3(3097.5112, 3515.7256, 123.4812), 33 | radius = 200.0, 34 | Blip = { 35 | color = 33, 36 | sprite = 273, 37 | }, 38 | Animals = { 39 | ['puma'] = { 40 | model = 'a_c_boar', 41 | loots = { 42 | 'burger', 43 | 'water', 44 | }, 45 | rarity = 90 46 | }, 47 | ['lupo'] = { 48 | model = 'a_c_coyote', 49 | loots = { 50 | 'burger', 51 | 'water', 52 | }, 53 | rarity = 20 54 | }, 55 | 56 | }, 57 | }, 58 | Zone2 = { 59 | position = vector3(3697.2112, 3989.4673, 64.6255), 60 | radius = 200.0, 61 | Blip = { 62 | color = 33, 63 | sprite = 273, 64 | }, 65 | Animals = { 66 | ['puma'] = { 67 | model = 'a_c_boar', 68 | loots = { 69 | 'burger', 70 | 'water', 71 | }, 72 | rarity = 90 73 | }, 74 | ['lupo'] = { 75 | model = 'a_c_coyote', 76 | loots = { 77 | 'burger', 78 | 'water', 79 | }, 80 | rarity = 20 81 | }, 82 | 83 | }, 84 | }, 85 | } -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | lua54 'yes' 4 | 5 | shared_scripts { 6 | '@es_extended/imports.lua', 7 | 'config.lua', 8 | '@ox_lib/init.lua', 9 | 'locales.lua' 10 | } 11 | 12 | client_scripts { 13 | 'client/client.lua' 14 | } 15 | 16 | server_scripts { 17 | '@oxmysql/lib/MySQL.lua', 18 | 'server/server.lua' 19 | } 20 | -------------------------------------------------------------------------------- /locales.lua: -------------------------------------------------------------------------------- 1 | Lang = {} 2 | 3 | Lang = { 4 | ["hunted"] = "You have hunted %s", 5 | ["not-spawning"] = "%s will not spawn", 6 | ["moving-to"] = "The animal is moving to %s", 7 | ["max-entity"] = "You have reached the %s entity limit!", 8 | ["we-are"] = "We are in the '%s' zone!", 9 | ["we-are-not"] = "We are not in any area!", 10 | ["charming"] = "We are charming %s animals", 11 | ["charmed"] = "%s has been charmed!", 12 | ["spawning"] = "Animal spawn: %s at %s metres", 13 | ["delete"] = "Deleting %s", 14 | } -------------------------------------------------------------------------------- /server/server.lua: -------------------------------------------------------------------------------- 1 | local ox_inventory = exports.ox_inventory 2 | 3 | RegisterNetEvent('giveInventoryItem', function (animalZone, item, count) 4 | local source = source 5 | local success = false 6 | local animals = Config.Zones[animalZone].Animals 7 | 8 | for _, animal in pairs(animals) do 9 | if CheckLoot(animal.loots, item) then 10 | success = true 11 | if count > 0 then 12 | ox_inventory:AddItem(source, item, count) 13 | break 14 | end 15 | end 16 | end 17 | 18 | 19 | if not success then 20 | Config.BanFunction() 21 | print(("%s it's probably a cheater"):format(GetPlayerName(source))) 22 | end 23 | end) 24 | 25 | CheckLoot = function (t, e) 26 | for k, v in pairs(t) do 27 | if v == e then 28 | return true 29 | end 30 | end 31 | return false 32 | end --------------------------------------------------------------------------------