A lot of you who have got an esx server are familiar with how the weapon buying system works, you basically buy a weapon and it stores it into your gang storage. This script changes how usual gangs operate and adds more of a challenge in getting weapons.
Here is a video Demo of it (Remember it is still alpha)
Before I get started on the actual script, here is a full download of the faction I used in the video (I edited the boss menu back to its original place), just in case you are confused where to actually place the code or in case I missed something out lol
esx_families.zip (46.1 KB)
Now onto the script part of it, the main stuff to edit is the client.lua but some stuff need to be done in the server.lua too.
Client
Add this somewhere at the top in the client file, pretty common sense where to add it
local pilot, aircraft, parachute, crate, pickup, blip, soundID
local requiredModels = {"p_cargo_chute_s", "ex_prop_adv_case_sm", "cuban800", "s_m_m_pilot_02", "prop_box_wood02a_pu"} -- parachute, pickup case, plane, pilot, crate
This is for the main event that allows you to buy weapons. Now remember to change the function names to the name of the faction you’re adding this too. Eg Change esx:grove to esx:mafia for example. Also don’t forget to change the location of the crate drop
function OpenBuyWeaponsMenu(station)
ESX.TriggerServerCallback('esx_grovejob:getArmoryWeapons', function(weapons)
local elements = {}
for i=1, #Config.GroveStations[station].AuthorizedWeapons, 1 do
local weapon = Config.GroveStations[station].AuthorizedWeapons[i]
local count = 0
for i=1, #weapons, 1 do
if weapons[i].name == weapon.name then
count = weapons[i].count
break
end
end
table.insert(elements, {label = 'x' .. count .. ' ' .. ESX.GetWeaponLabel(weapon.name) .. ' $' .. weapon.price, value = weapon.name, price = weapon.price})
end
ESX.UI.Menu.Open(
'default', GetCurrentResourceName(), 'armory_buy_weapons',
{
title = _U('buy_weapon_menu'),
align = 'bottom-right',
elements = elements,
},
function(data, menu)
ESX.TriggerServerCallback('esx_grovejob:buy', function(hasEnoughMoney)
if hasEnoughMoney then
ESX.TriggerServerCallback('esx_grovejob:removeArmoryWeapon', function()
TriggerEvent("crateDrop", data.current.value, 250, true, 400.0, {["x"] = 3348.9521484375, ["y"] = 5497.583984375, ["z"] = 18.58197593689})
end)
else
ESX.ShowNotification(_U('not_enough_money'))
end
end, data.current.price)
end,
function(data, menu)
menu.close()
end
)
end)
end
Finally, somewhere at the bottom of your client file, add this events.
RegisterNetEvent("crateDrop")
AddEventHandler("crateDrop", function(weapon, ammo, roofCheck, planeSpawnDistance, dropCoords) -- all of the error checking is done here before passing the parameters to the function itself
Citizen.CreateThread(function()
if IsWeaponValid(GetHashKey(weapon)) then -- only supports weapon pickups for now, use the function directly to bypass this
weapon = "pickup_" .. weapon
-- print("WEAPON VALIDITY: true, after concatenating 'pickup_'")
elseif IsWeaponValid(GetHashKey("weapon_" .. weapon)) then
weapon = "pickup_weapon_" .. weapon
-- print("WEAPON VALIDITY: true, after concatenating 'pickup_weapon_'")
elseif IsWeaponValid(GetHashKey(weapon:sub(8))) then
-- print("WEAPON VALIDITY: true")
else
-- print("WEAPON VALIDITY: false")
return
end
-- print("WEAPON: " .. string.lower(weapon))
local ammo = (ammo and tonumber(ammo)) or 250
if ammo > 9999 then
ammo = 9999
elseif ammo < -1 then
ammo = -1
end
-- print("AMMO: " .. ammo)
if dropCoords.x and dropCoords.y and dropCoords.z and tonumber(dropCoords.x) and tonumber(dropCoords.y) and tonumber(dropCoords.z) then
-- print(("DROP COORDS: success, X = %.4f; Y = %.4f; Z = %.4f"):format(dropCoords.x, dropCoords.y, dropCoords.z))
else
dropCoords = {0.0, 0.0, 72.0}
-- print("DROP COORDS: fail, defaulting to X = 0; Y = 0")
end
if roofCheck and roofCheck ~= "false" then -- if roofCheck is not false then a check will be performed if a plane can drop a crate to the specified location before actually spawning a plane, if it can't, function won't be called
-- print("ROOFCHECK: true")
local ray = StartShapeTestRay(vector3(dropCoords.x, dropCoords.y, dropCoords.z) + vector3(0.0, 0.0, 500.0), vector3(dropCoords.x, dropCoords.y, dropCoords.z), -1, -1, 0)
local _, hit, impactCoords = GetShapeTestResult(ray)
-- print("HIT: " .. hit)
-- print(("IMPACT COORDS: X = %.4f; Y = %.4f; Z = %.4f"):format(impactCoords.x, impactCoords.y, impactCoords.z))
-- print("DISTANCE BETWEEN DROP AND IMPACT COORDS: " .. #(vector3(dropCoords.x, dropCoords.y, dropCoords.z) - vector3(impactCoords)))
if hit == 0 or (hit == 1 and #(vector3(dropCoords.x, dropCoords.y, dropCoords.z) - vector3(impactCoords)) < 10.0) then -- ± 10 units
-- print("ROOFCHECK: success")
CrateDrop(weapon, ammo, planeSpawnDistance, dropCoords)
else
-- print("ROOFCHECK: fail")
return
end
else
-- print("ROOFCHECK: false")
CrateDrop(weapon, ammo, planeSpawnDistance, dropCoords)
end
end)
end)
function CrateDrop(weapon, ammo, planeSpawnDistance, dropCoords)
Citizen.CreateThread(function()
for i = 1, #requiredModels do
RequestModel(GetHashKey(requiredModels[i]))
while not HasModelLoaded(GetHashKey(requiredModels[i])) do
Wait(0)
end
end
--[[
RequestAnimDict("P_cargo_chute_S")
while not HasAnimDictLoaded("P_cargo_chute_S") do -- wasn't able to get animations working
Wait(0)
end
]]
RequestWeaponAsset(GetHashKey("weapon_flare")) -- flare won't spawn later in the script if we don't request it right now
while not HasWeaponAssetLoaded(GetHashKey("weapon_flare")) do
Wait(0)
end
local rHeading = math.random(0, 360) + 0.0
local planeSpawnDistance = (planeSpawnDistance and tonumber(planeSpawnDistance) + 0.0) or 400.0 -- this defines how far away the plane is spawned
local theta = (rHeading / 180.0) * 3.14
local rPlaneSpawn = vector3(dropCoords.x, dropCoords.y, dropCoords.z) - vector3(math.cos(theta) * planeSpawnDistance, math.sin(theta) * planeSpawnDistance, -500.0) -- the plane is spawned at
-- print(("PLANE COORDS: X = %.4f; Y = %.4f; Z = %.4f"):format(rPlaneSpawn.x, rPlaneSpawn.y, rPlaneSpawn.z))
-- print("PLANE SPAWN DISTANCE: " .. #(vector2(rPlaneSpawn.x, rPlaneSpawn.y) - vector2(dropCoords.x, dropCoords.y)))
local dx = dropCoords.x - rPlaneSpawn.x
local dy = dropCoords.y - rPlaneSpawn.y
local heading = GetHeadingFromVector_2d(dx, dy) -- determine plane heading from coordinates
aircraft = CreateVehicle(GetHashKey("cuban800"), rPlaneSpawn, heading, true, true)
SetEntityHeading(aircraft, heading)
SetVehicleDoorsLocked(aircraft, 2) -- lock the doors so pirates don't get in
SetEntityDynamic(aircraft, true)
ActivatePhysics(aircraft)
SetVehicleForwardSpeed(aircraft, 60.0)
SetHeliBladesFullSpeed(aircraft) -- works for planes I guess
SetVehicleEngineOn(aircraft, true, true, false)
ControlLandingGear(aircraft, 3) -- retract the landing gear
OpenBombBayDoors(aircraft) -- opens the hatch below the plane for added realism
SetEntityProofs(aircraft, true, false, true, false, false, false, false, false)
pilot = CreatePedInsideVehicle(aircraft, 1, GetHashKey("s_m_m_pilot_02"), -1, true, true)
SetBlockingOfNonTemporaryEvents(pilot, true) -- ignore explosions and other shocking events
SetPedRandomComponentVariation(pilot, false)
SetPedKeepTask(pilot, true)
SetPlaneMinHeightAboveTerrain(aircraft, 50) -- the plane shouldn't dip below the defined altitude
TaskVehicleDriveToCoord(pilot, aircraft, vector3(dropCoords.x, dropCoords.y, dropCoords.z) + vector3(0.0, 0.0, 500.0), 60.0, 0, GetHashKey("cuban800"), 262144, 15.0, -1.0) -- to the dropsite, could be replaced with a task sequence
local droparea = vector2(dropCoords.x, dropCoords.y)
local planeLocation = vector2(GetEntityCoords(aircraft).x, GetEntityCoords(aircraft).y)
while not IsEntityDead(pilot) and #(planeLocation - droparea) > 5.0 do -- wait for when the plane reaches the dropCoords ± 5 units
Wait(100)
planeLocation = vector2(GetEntityCoords(aircraft).x, GetEntityCoords(aircraft).y) -- update plane coords for the loop
end
if IsEntityDead(pilot) then -- I think this will end the script if the pilot dies, no idea how to return works
print("PILOT: dead")
do return end
end
TaskVehicleDriveToCoord(pilot, aircraft, 0.0, 0.0, 500.0, 60.0, 0, GetHashKey("cuban800"), 262144, -1.0, -1.0) -- disposing of the plane like Rockstar does, send it to 0; 0 coords with -1.0 stop range, so the plane won't be able to achieve its task
SetEntityAsNoLongerNeeded(pilot)
SetEntityAsNoLongerNeeded(aircraft)
local crateSpawn = vector3(dropCoords.x, dropCoords.y, GetEntityCoords(aircraft).z - 5.0) -- crate will drop to the exact position as planned, not at the plane's current position
crate = CreateObject(GetHashKey("prop_box_wood02a_pu"), crateSpawn, true, true, true) -- a breakable crate to be spawned directly under the plane, probably could be spawned closer to the plane
SetEntityLodDist(crate, 1000) -- so we can see it from the distance
ActivatePhysics(crate)
SetDamping(crate, 2, 0.1) -- no idea but Rockstar uses it
SetEntityVelocity(crate, 0.0, 0.0, -0.2) -- I think this makes the crate drop down, not sure if it's needed as many times in the script as I'm using
parachute = CreateObject(GetHashKey("p_cargo_chute_s"), crateSpawn, true, true, true) -- create the parachute for the crate, location isn't too important as it'll be later attached properly
SetEntityLodDist(parachute, 1000)
SetEntityVelocity(parachute, 0.0, 0.0, -0.2)
-- PlayEntityAnim(parachute, "P_cargo_chute_S_deploy", "P_cargo_chute_S", 1000.0, false, false, false, 0, 0)
-- ForceEntityAiAndAnimationUpdate(parachute)
pickup = CreateAmbientPickup(GetHashKey(weapon), crateSpawn, 0, ammo, GetHashKey("ex_prop_adv_case_sm"), true, true) -- create the pickup itself, location isn't too important as it'll be later attached properly
ActivatePhysics(pickup)
SetDamping(pickup, 2, 0.0245)
SetEntityVelocity(pickup, 0.0, 0.0, -0.2)
soundID = GetSoundId() -- we need a sound ID for calling the native below, otherwise we won't be able to stop the sound later
PlaySoundFromEntity(soundID, "Crate_Beeps", pickup, "MP_CRATE_DROP_SOUNDS", true, 0) -- crate beep sound emitted from the pickup
blip = AddBlipForEntity(pickup)
SetBlipSprite(blip, 408) -- 351 or 408 are both fine, 408 is just bigger
SetBlipNameFromTextFile(blip, "AMD_BLIPN")
SetBlipScale(blip, 0.7)
SetBlipColour(blip, 2)
SetBlipAlpha(blip, 120) -- blip will be semi-transparent
-- local crateBeacon = StartParticleFxLoopedOnEntity_2("scr_crate_drop_beacon", pickup, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 1065353216, 0, 0, 0, 1065353216, 1065353216, 1065353216, 0)--1.0, false, false, false)
-- SetParticleFxLoopedColour(crateBeacon, 0.8, 0.18, 0.19, false)
AttachEntityToEntity(parachute, pickup, 0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, false, false, false, false, 2, true) -- attach the crate to the pickup
AttachEntityToEntity(pickup, crate, 0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.0, false, false, true, false, 2, true) -- attach the pickup to the crate, doing it in any other order makes the crate drop spazz out
while HasObjectBeenBroken(crate) == false do -- wait till the crate gets broken (probably on impact), then continue with the script
Wait(0)
end
local parachuteCoords = vector3(GetEntityCoords(parachute)) -- we get the parachute dropCoords so we know where to drop the flare
ShootSingleBulletBetweenCoords(parachuteCoords, parachuteCoords - vector3(0.0001, 0.0001, 0.0001), 0, false, GetHashKey("weapon_flare"), 0, true, false, -1.0) -- flare needs to be dropped with dropCoords like that, otherwise it remains static and won't remove itself later
DetachEntity(parachute, true, true)
-- SetEntityCollision(parachute, false, true) -- pointless right now but would be cool if animations would work and you'll be able to walk through the parachute while it's disappearing
-- PlayEntityAnim(parachute, "P_cargo_chute_S_crumple", "P_cargo_chute_S", 1000.0, false, false, false, 0, 0)
DeleteEntity(parachute)
DetachEntity(pickup) -- will despawn on its own
SetBlipAlpha(blip, 255) -- make the blip fully visible
while DoesEntityExist(pickup) do -- wait till the pickup gets picked up, then the script can continue
Wait(0)
end
while DoesObjectOfTypeExistAtCoords(parachuteCoords, 10.0, GetHashKey("w_am_flare"), true) do
Wait(0)
local prop = GetClosestObjectOfType(parachuteCoords, 10.0, GetHashKey("w_am_flare"), false, false, false)
RemoveParticleFxFromEntity(prop)
SetEntityAsMissionEntity(prop, true, true)
DeleteObject(prop)
end
if DoesBlipExist(blip) then -- remove the blip, should get removed when the pickup gets picked up anyway, but isn't a bad idea to make sure of it
RemoveBlip(blip)
end
StopSound(soundID) -- stop the crate beeping sound
ReleaseSoundId(soundID) -- won't need this sound ID any longer
for i = 1, #requiredModels do
Wait(0)
SetModelAsNoLongerNeeded(GetHashKey(requiredModels[i]))
end
RemoveWeaponAsset(GetHashKey("weapon_flare"))
end)
end
AddEventHandler("onResourceStop", function(resource)
if resource == GetCurrentResourceName() then
-- print("RESOURCE: stopped")
SetEntityAsMissionEntity(pilot, false, true)
DeleteEntity(pilot)
SetEntityAsMissionEntity(aircraft, false, true)
DeleteEntity(aircraft)
DeleteEntity(parachute)
DeleteEntity(crate)
RemovePickup(pickup)
RemoveBlip(blip)
StopSound(soundID)
ReleaseSoundId(soundID)
for i = 1, #requiredModels do
Wait(0)
SetModelAsNoLongerNeeded(GetHashKey(requiredModels[i]))
end
end
end)
Server
The server one is pretty simple, it is just configuring the removeArmoryWeapon and once again don’t forget to change the gang names.
ESX.RegisterServerCallback('esx_grovejob:removeArmoryWeapon', function(source, cb, weaponName)
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addWeapon(weaponName, 1000)
TriggerEvent('esx_datastore:getSharedDataStore', 'society_grove', function(store)
local weapons = store.get('weapons')
if weapons == nil then
weapons = {}
end
local foundWeapon = false
for i=1, #weapons, 1 do
if weapons[i].name == weaponName then
weapons[i].count = (weapons[i].count > 0 and weapons[i].count - 1 or 0)
foundWeapon = true
end
end
if not foundWeapon then
table.insert(weapons, {
name = weaponName,
count = 0
})
end
store.set('weapons', weapons)
cb()
end)
end)
Props to @Vechro for releasing the a crate drop script in the first place, as this script is adapted off his original one.
Remember, this is still a work in progress but feedback is much appreciated as I am pretty sure there will be some bugs
Enjoy!