├── fxmanifest.lua ├── locales ├── en.lua ├── fr.lua └── es.lua ├── config.lua ├── client.lua └── server.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | lua54 'yes' 5 | 6 | author 'Bryan' 7 | version '1.1.0' 8 | description 'Script that allows Airdrops to fall and players fight over it' 9 | 10 | shared_scripts { 11 | 'config.lua', 12 | '@es_extended/imports.lua', 13 | '@es_extended/locale.lua', 14 | 'locales/*.lua' 15 | } 16 | client_script 'client.lua' 17 | server_script 'server.lua' 18 | 19 | escrow_ignore { 20 | 'config.lua', 21 | 'locales/*.lua' 22 | } -------------------------------------------------------------------------------- /locales/en.lua: -------------------------------------------------------------------------------- 1 | Locales['en'] = { 2 | -- Command 3 | ['command_help'] = 'Spawn An Airdrop', 4 | ['command_args_lootTable'] = 'Loot Table ID | Not Necessary', 5 | ['command_error_loottable_not_found'] = 'Loot Table (ID: %s) Not Found', 6 | 7 | -- 3D Text 8 | ['3d_press_to_pickup'] = 'Press [E] To Pickup Airdrop', 9 | 10 | -- Progress Bar 11 | ['progress_bar_picking_up'] = 'Picking Up The Airdrop', 12 | 13 | -- Notification 14 | ['notification_airdrop_spawned'] = 'Airdrop has been Called In!', 15 | 16 | -- Blip 17 | ['blip_name'] = 'Airdrop', 18 | } -------------------------------------------------------------------------------- /locales/fr.lua: -------------------------------------------------------------------------------- 1 | Locales['fr'] = { 2 | -- Command 3 | ['command_help'] = 'Générer un Airdrop', 4 | ['command_args_lootTable'] = 'L\'ID de la LootTable | (dans le fichier config)', 5 | ['command_error_loottable_not_found'] = 'La Loot Table (ID: %s) n\'a pas été trouvé', 6 | 7 | -- 3D Text 8 | ['3d_press_to_pickup'] = 'Appuyez sur [E] pour ramasser l\'airdrop', 9 | 10 | -- Progress Bar 11 | ['progress_bar_picking_up'] = 'Récupération de l\'airdrop', 12 | 13 | -- Notification 14 | ['notification_airdrop_spawned'] = 'Un airdrop a été appelé !', 15 | 16 | -- Blip 17 | ['blip_name'] = 'Airdrop', 18 | } -------------------------------------------------------------------------------- /locales/es.lua: -------------------------------------------------------------------------------- 1 | Locales['es'] = { 2 | -- Command 3 | ['command_help'] = 'Desplegar un Suministro Aéreo', 4 | ['command_args_lootTable'] = 'ID de Botín en la Tabla| No Necesario', 5 | ['command_error_loottable_not_found'] = '(ID: %s) de Botín No Encontrado en la Tabla', 6 | 7 | -- 3D Text 8 | ['3d_press_to_pickup'] = 'Presiona [E] para coger el Suministro Aéreo', 9 | 10 | -- Progress Bar 11 | ['progress_bar_picking_up'] = 'Cogiendo el Suministro Aéreo', 12 | 13 | -- Notification 14 | ['notification_airdrop_spawned'] = '¡Un Suministro Aéreo ha sido Desplegado!', 15 | 16 | -- Blip 17 | ['blip_name'] = 'Suministro Aéreo', 18 | } -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.FrameworkObj = 'esx:getSharedObject' 4 | Config.PlayerLoaded = 'esx:playerLoaded' 5 | 6 | Config.Locale = 'en' --en/fr/es 7 | 8 | -- Default: false | This just prints things in console 9 | Config.Debug = false 10 | 11 | Config.Airdrops = { 12 | Command = { -- Command which spawns Airdrop | Command can be disabled: enable = false | You can configure the name of the command and also who can execute it 13 | enabled = true, 14 | name = 'airdrop', 15 | groups = {'admin', 'superadmin'} 16 | }, 17 | OnlyCommand = false, -- true = only admins with commands can spawn in airdrops | false = time interval between automatic spawn (command will also be active) 18 | CoordsOfCommand = true, -- Spawn Airdrop on command executor's location 19 | Cooldown = { min = 5, max = 15 }, -- Minutes | Cooldown time before next airdrop spawns in | Default: { min = 5, max = 15 } 20 | CommandRestart = false, -- Does Command (if enabled) restart Cooldown time of next airdrop | Default: false 21 | FallSpeed = 1, -- Try modifying this to see how fast You want the Airdrop to go down | Lowering number, means slowing down airdrop fall speed | Default: 1 22 | SpawnOffline = false, -- Will Airdrops spawn when there're no players online | Default: false 23 | CollectTime = 10, -- Time it takes to pickup airdrop | Seconds 24 | DeletePrevious = false, -- Deletes Previous Airdrop if new is spawned 25 | UseFlareParticles = true, 26 | } 27 | 28 | -- Locations of where the Airdrops can spawn 29 | Config.Locations = { 30 | vector3(-55.02, -1764.84, 200.0), 31 | vector3(-93.91, -1749.73, 200.0), 32 | vector3(-139.08, -1712.92, 200.0) 33 | } 34 | 35 | -- Loot Table is chosen randomly, unless admin spawns it with specific ID. If randomly chosen Loot Table doesn't meet chance %, the script will automatically try to select other Loot Table 36 | Config.LootTables = { 37 | [1] = { 38 | Chance = 100, -- Chance that this Airdrop (If Selected) will spawn | If called by command, this Chance doesn't matter | Value in % 39 | Items = { -- Item you get list | Types: 'item', 'weapon', 'account' 40 | { name = 'water', count = 3, type = 'item' }, 41 | { name = 'bread', count = 2, type = 'item' }, 42 | } 43 | }, 44 | [2] = { 45 | Chance = 10, 46 | Items = { 47 | { name = 'WEAPON_PISTOL', count = 2, type = 'weapon' }, 48 | { name = 'WEAPON_APPISTOL', count = 1, type = 'weapon' }, 49 | { name = 'money', count = 5000, type = 'account' }, 50 | } 51 | }, 52 | } 53 | 54 | Config.Object = 'prop_drop_armscrate_01' 55 | 56 | Config.Blip = { 57 | Enabled = true, 58 | Sprite = 306, 59 | Colour = 28, 60 | Scale = 0.8, 61 | ShortRange = true, 62 | } 63 | 64 | 65 | -- Functions 66 | Config.Notification = function(msg) -- Client Side | Custom Notification if You want | By Default: ESX Notification 67 | TriggerEvent('esx:showNotification', msg) 68 | end 69 | 70 | Config.ProgressBar = function(msg, time) -- Client Side | Custom Progress Bar | This function can return true (Airdrop will be looted) or false (Airdrop won't be looted) 71 | Citizen.Wait(1000) 72 | 73 | return true 74 | end 75 | 76 | Config.NotifyPlayers = function(msg, coords) -- Server Side | Should be perhaps some kind of email to the player's phone | For testing it is made as Default ESX Notification 77 | TriggerClientEvent('esx:showNotification', -1, msg) 78 | end 79 | 80 | Config.NotifyExecute = function(playerId, xPlayer) -- Server Side | This function is triggered when someone spawn in airdrop through command | You can add logs for example here and get identifier, name from playerId or xPlayer 81 | 82 | end -------------------------------------------------------------------------------- /client.lua: -------------------------------------------------------------------------------- 1 | ESX = nil 2 | local airdrops = {} 3 | 4 | Citizen.CreateThread(function() 5 | while ESX == nil do 6 | TriggerEvent(Config.FrameworkObj, function(obj) ESX = obj end) 7 | Citizen.Wait(100) 8 | end 9 | 10 | while ESX.GetPlayerData().job == nil do Citizen.Wait(100) end 11 | 12 | Citizen.CreateThread(function() StartScript(); end) 13 | Citizen.CreateThread(function() ShowLocations(); end) 14 | if Config.Debug then print(string.format('%s Started Successfully | Client Side', GetCurrentResourceName())) end 15 | end) 16 | 17 | RegisterNetEvent('bryan_airdrops:syncAirdrops', function(airdropsSv) 18 | airdrops = airdropsSv 19 | end) 20 | 21 | RegisterNetEvent('bryan_airdrops:removeObject', function(id) 22 | RemoveObject(id) 23 | end) 24 | 25 | RegisterNetEvent('bryan_airdrops:removeAllObjects', function() 26 | for k, v in pairs(airdrops) do 27 | RemoveObject(v.id) 28 | end 29 | 30 | airdrops = {} 31 | end) 32 | 33 | AddEventHandler('onResourceStop', function(resourceName) 34 | if resourceName == GetCurrentResourceName() then 35 | for k, v in pairs(airdrops) do 36 | if v.object then 37 | RemoveObject(v.id) 38 | end 39 | end 40 | end 41 | end) 42 | 43 | StartScript = function() 44 | while true do 45 | local wait = 1000 46 | 47 | if #airdrops > 0 then 48 | wait = 1 49 | 50 | for k, v in pairs(airdrops) do 51 | if not v.landed then 52 | airdrops[k].coords = airdrops[k].coords - vector3(0.0, 0.0, 0.01 * Config.Airdrops.FallSpeed) 53 | end 54 | 55 | if v.object and DoesEntityExist(v.object) then 56 | if GetEntityHeightAboveGround(v.object) > 0.2 then 57 | SetEntityCoords(v.object, v.coords.x, v.coords.y, v.coords.z, 0.0, 0.0, 0.0, false) 58 | elseif not v.landed then 59 | airdrops[k].landed = true 60 | TriggerServerEvent('bryan_airdrops:airdropLanded', v.id) 61 | end 62 | elseif not v.spawning and GetDistanceBetweenCoords(GetEntityCoords(PlayerPedId()), v.coords, true) <= 50.0 then 63 | SpawnObject(k, v.coords) 64 | end 65 | 66 | if not v.blip then 67 | AddBlip(k, v.coords) 68 | end 69 | end 70 | end 71 | 72 | Citizen.Wait(wait) 73 | end 74 | end 75 | 76 | ShowLocations = function() 77 | while true do 78 | local wait = 500 79 | local ped = PlayerPedId() 80 | local coords = GetEntityCoords(ped) 81 | 82 | for k, v in pairs(airdrops) do 83 | if v.landed then 84 | local distance = GetDistanceBetweenCoords(coords, v.coords, true) 85 | 86 | if distance <= 3.0 then 87 | wait = 2 88 | ESX.Game.Utils.DrawText3D(v.coords, _U('3d_press_to_pickup'), 0.8, 4) 89 | 90 | if IsControlJustPressed(1, 51) then 91 | Config.ProgressBar(_U('progress_bar_picking_up'), Config.Airdrops.CollectTime * 1000) 92 | 93 | TriggerServerEvent('bryan_airdrops:pickupAirdrop', v.id) 94 | end 95 | end 96 | end 97 | end 98 | 99 | Citizen.Wait(wait) 100 | end 101 | end 102 | 103 | SpawnObject = function(id, coords) 104 | airdrops[id].spawning = true 105 | 106 | local model = GetHashKey(Config.Object) 107 | 108 | RequestModel(model) 109 | while not HasModelLoaded(model) do Citizen.Wait(10) end 110 | 111 | local obj = CreateObject(model, coords.x, coords.y, coords.z, false, false, true) 112 | 113 | if Config.Debug then print('Object Spawned') end 114 | FreezeEntityPosition(obj, true) 115 | airdrops[id].object = obj 116 | 117 | if Config.Airdrops.UseFlareParticles then 118 | RequestNamedPtfxAsset('core') 119 | while not HasNamedPtfxAssetLoaded('core') do Citizen.Wait(10) end 120 | 121 | if Config.Debug then print('Particles Loaded') end 122 | 123 | UseParticleFxAssetNextCall("core") 124 | SetParticleFxNonLoopedColour(1.0, 0.0, 0.0) 125 | StartParticleFxLoopedOnEntity('weap_heist_flare_trail', obj, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0) 126 | end 127 | end 128 | 129 | AddBlip = function(id, coords) 130 | if Config.Blip.Enabled then 131 | local blip = AddBlipForCoord(coords) 132 | SetBlipSprite(blip, Config.Blip.Sprite) 133 | SetBlipColour(blip, Config.Blip.Colour) 134 | SetBlipScale(blip, Config.Blip.Scale) 135 | SetBlipDisplay(blip, 4) 136 | SetBlipAsShortRange(blip, Config.Blip.ShortRange) 137 | 138 | BeginTextCommandSetBlipName('STRING') 139 | AddTextComponentString(_U('blip_name')) 140 | EndTextCommandSetBlipName(blip) 141 | 142 | airdrops[id].blip = blip 143 | end 144 | end 145 | 146 | RemoveObject = function(id) 147 | for k, v in pairs(airdrops) do 148 | if v.id == id then 149 | ESX.Game.DeleteObject(v.object) 150 | if Config.Blip.Enabled then RemoveBlip(v.blip) end 151 | end 152 | end 153 | end -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | ESX = nil 2 | local airdrops = {} 3 | local tryCount = 0 4 | local cooldown = math.random(Config.Airdrops.Cooldown.min, Config.Airdrops.Cooldown.max) 5 | 6 | TriggerEvent(Config.FrameworkObj, function(obj) ESX = obj end) 7 | 8 | AddEventHandler('onResourceStart', function(resourceName) 9 | if resourceName == GetCurrentResourceName() then 10 | if Config.Debug then print(string.format('%s Started Successfully | Server Side', GetCurrentResourceName())) end 11 | 12 | if not Config.Airdrops.OnlyCommand then 13 | Citizen.CreateThread(function() StartAirdropLoop(); end) 14 | end 15 | end 16 | end) 17 | 18 | RegisterNetEvent('esx:playerLoaded', function(playerId, xPlayer) 19 | TriggerClientEvent('bryan_airdrops:syncAirdrops', playerId, airdrops) 20 | end) 21 | 22 | RegisterNetEvent('bryan_airdrops:pickupAirdrop', function(id) 23 | local airdrop = GetAirdrop(id) 24 | 25 | if airdrop then 26 | RewardPlayer(source, airdrop.items) 27 | RemoveAirdrop(id) 28 | TriggerClientEvent('bryan_airdrops:removeObject', -1, id) 29 | TriggerClientEvent('bryan_airdrops:syncAirdrops', -1, airdrops) 30 | else 31 | if Config.Debug then 32 | print('Could not find airdrop by it\'s ID') 33 | end 34 | end 35 | end) 36 | 37 | RegisterNetEvent('bryan_airdrops:airdropLanded', function(id) 38 | local airdropIndex = GetAirdropIndex(id) 39 | 40 | airdrops[airdropIndex].landed = true 41 | end) 42 | 43 | StartAirdropLoop = function() 44 | while true do 45 | while cooldown > 0 do 46 | if Config.Debug then print(string.format('%s Minutes Till Next Airdrop Spawn', cooldown)) end 47 | Citizen.Wait(60 * 1000) 48 | cooldown = cooldown - 1 49 | end 50 | 51 | RestartCooldown() 52 | if Config.Airdrops.SpawnOffline or (not Config.Airdrops.SpawnOffline and #ESX.GetPlayers() > 0) then 53 | SpawnAirdrop() 54 | end 55 | 56 | Citizen.Wait(10) 57 | end 58 | end 59 | 60 | if Config.Airdrops.Command.enabled then 61 | ESX.RegisterCommand(Config.Airdrops.Command.name, Config.Airdrops.Command.groups, function(xPlayer, args, showError) 62 | if Config.Debug then print(args.lootTable == nil, Config.LootTables[args.lootTable] ~= nil) end 63 | if args.lootTable == nil or Config.LootTables[args.lootTable] ~= nil then 64 | if Config.Airdrops.Command.CommandRestart then RestartCooldown() end 65 | 66 | if Config.Airdrops.CoordsOfCommand and (xPlayer and xPlayer.source) then 67 | SpawnAirdrop(args.lootTable, GetEntityCoords(GetPlayerPed(xPlayer.source)) + vector3(0.0, 0.0, 150.0)) 68 | else 69 | SpawnAirdrop(args.lootTable) 70 | end 71 | 72 | if xPlayer and xPlayer.source then 73 | Config.NotifyExecute(xPlayer.source, xPlayer) 74 | end 75 | else 76 | showError(_U('command_error_loottable_not_found', args.lootTable)) 77 | end 78 | end, true, {help = _U('command_help'), validate = false, arguments = { 79 | { name = 'lootTable', help = _U('command_args_lootTable'), type = 'number' }, 80 | }}) 81 | end 82 | 83 | SpawnAirdrop = function(lootTable, customCoords) 84 | local randomLocation = Config.Locations[math.random(1, #Config.Locations)] 85 | local isLocationTaken = IsLocationTaken(randomLocation) 86 | 87 | if Config.Debug then print(tryCount > 10, not isLocationTaken) end 88 | if tryCount > 10 or not isLocationTaken or customCoords then 89 | tryCount = 0 90 | 91 | local airdropId = math.random(10000, 99999) 92 | local airdropItems = SelectLoottable(lootTable) 93 | 94 | local coords = randomLocation 95 | 96 | if customCoords then coords = customCoords 97 | elseif coords.z < 150.0 then coords = vector3(randomLocation.x, randomLocation.y, 200.0) end 98 | 99 | if Config.Airdrops.DeletePrevious then 100 | TriggerClientEvent('bryan_airdrops:removeAllObjects', -1) 101 | airdrops = {} 102 | end 103 | 104 | table.insert(airdrops, { 105 | id = airdropId, 106 | items = airdropItems, 107 | coords = coords 108 | }) 109 | 110 | if Config.Debug then print('Airdrop Spawned') end 111 | TriggerClientEvent('bryan_airdrops:syncAirdrops', -1, airdrops) 112 | Config.NotifyPlayers(_U('notification_airdrop_spawned')) 113 | 114 | Citizen.CreateThread(function() 115 | local airdropIndex = GetAirdropIndex(airdropId) 116 | 117 | while not airdrops[airdropIndex].landed do 118 | airdrops[airdropIndex].coords = airdrops[airdropIndex].coords - vector3(0.0, 0.0, 0.01 * Config.Airdrops.FallSpeed) 119 | Citizen.Wait(1) 120 | end 121 | 122 | if Config.Debug then print(string.format('Airdrop (ID: %s) Landed', airdrops[airdropIndex].id)) end 123 | end) 124 | elseif isLocationTaken then 125 | tryCount = tryCount + 1 126 | SpawnAirdrop(lootTable) 127 | end 128 | end 129 | 130 | IsLocationTaken = function(location) 131 | for k, v in pairs(airdrops) do 132 | if v.coords == location then 133 | return true 134 | end 135 | end 136 | 137 | return false 138 | end 139 | 140 | SelectLoottable = function(lootTable) 141 | if lootTable ~= nil then 142 | return Config.LootTables[lootTable].Items 143 | else 144 | local randomLootTable = Config.LootTables[math.random(1, #Config.LootTables)] 145 | local chance = math.random(1, 100) 146 | 147 | while randomLootTable.Chance < chance do 148 | Citizen.Wait(10) 149 | 150 | randomLootTable = Config.LootTables[math.random(1, #Config.LootTables)] 151 | chance = math.random(1, 100) 152 | 153 | if Config.Debug then print('searching ' .. randomLootTable.Chance < chance) end 154 | end 155 | 156 | return randomLootTable.Items 157 | end 158 | end 159 | 160 | RestartCooldown = function() 161 | cooldown = math.random(Config.Airdrops.Cooldown.min, Config.Airdrops.Cooldown.max) 162 | end 163 | 164 | RemoveAirdrop = function(id) 165 | for k, v in pairs(airdrops) do 166 | if v.id == id then 167 | airdrops[k] = nil 168 | break 169 | end 170 | end 171 | end 172 | 173 | RewardPlayer = function(source, items) 174 | local xPlayer = ESX.GetPlayerFromId(source) 175 | 176 | for k, v in pairs(items) do 177 | if v.type == 'item' then 178 | xPlayer.addInventoryItem(v.name, v.count) 179 | elseif v.type == 'weapon' then 180 | xPlayer.addWeapon(v.name, v.count) 181 | elseif v.type == 'account' then 182 | xPlayer.addAccountMoney(v.name, v.count) 183 | end 184 | end 185 | end 186 | 187 | GetAirdrop = function(id) 188 | for k, v in pairs(airdrops) do 189 | if v.id == id then 190 | return v 191 | end 192 | end 193 | 194 | return nil 195 | end 196 | 197 | GetAirdropIndex = function(id) 198 | for k, v in pairs(airdrops) do 199 | if v.id == id then 200 | return k 201 | end 202 | end 203 | 204 | return nil 205 | end --------------------------------------------------------------------------------