203 lines
6.6 KiB
Lua
203 lines
6.6 KiB
Lua
print("^2[environment]^7 Cash drop system loaded")
|
|
|
|
-- =====================
|
|
-- CONFIG
|
|
-- =====================
|
|
local MAX_CASH = 200 -- Maximum possible drop
|
|
local WEIGHT_POWER = 2.2 -- Higher = lower values more common
|
|
local CHECK_INTERVAL = 1000 -- How often we scan peds (ms)
|
|
|
|
local PICKUP_RADIUS = 1.25
|
|
local PICKUP_CHECK_MS = 200
|
|
local PICKUP_COOLDOWN = 750 -- ms between pickup attempts per drop
|
|
|
|
-- Prevent duplicate drops per ped handle
|
|
local processedPeds = {}
|
|
|
|
-- dropId -> { obj=entity, coords=vector3, amount=number }
|
|
local spawnedDrops = {}
|
|
local pickupCooldown = {} -- dropId -> lastAttempt GetGameTimer()
|
|
|
|
-- =====================
|
|
-- WEIGHTED RANDOM CASH
|
|
-- =====================
|
|
local function getWeightedCash()
|
|
local roll = math.random()
|
|
local weighted = roll ^ WEIGHT_POWER
|
|
return math.floor(weighted * MAX_CASH)
|
|
end
|
|
|
|
-- =====================
|
|
-- PED DEATH MONITOR
|
|
-- =====================
|
|
CreateThread(function()
|
|
math.randomseed(GetGameTimer())
|
|
|
|
while true do
|
|
Wait(CHECK_INTERVAL)
|
|
|
|
local peds = GetGamePool("CPed")
|
|
for _, ped in ipairs(peds) do
|
|
if DoesEntityExist(ped)
|
|
and not IsPedAPlayer(ped)
|
|
and IsEntityDead(ped)
|
|
and not processedPeds[ped] then
|
|
|
|
processedPeds[ped] = true
|
|
|
|
local cash = getWeightedCash()
|
|
if cash > 0 then
|
|
local c = GetEntityCoords(ped)
|
|
TriggerServerEvent("environment:pedCashDrop", cash, { x = c.x, y = c.y, z = c.z })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- =====================
|
|
-- ARG PARSER (server compatibility)
|
|
-- =====================
|
|
local function parseSpawnArgs(p1, p2, p3, p4, p5)
|
|
-- Preferred (your server currently uses):
|
|
-- (dropId, amount, coordsTable)
|
|
-- Also supports:
|
|
-- (dropId, amount, x, y, z)
|
|
-- (amount, coordsTable) [fallback id]
|
|
-- (amount, x, y, z) [fallback id]
|
|
|
|
local dropId, amount, coords
|
|
|
|
-- (dropId, amount, coordsTable) OR (dropId, amount, x, y, z)
|
|
if type(p1) == "number" and (type(p2) == "number" or type(p2) == "string") then
|
|
local maybeDropId = tonumber(p1)
|
|
local maybeAmount = tonumber(p2) or 0
|
|
|
|
if type(p3) == "table" and p3.x and p3.y and p3.z then
|
|
dropId = maybeDropId
|
|
amount = maybeAmount
|
|
coords = vector3(p3.x, p3.y, p3.z)
|
|
return dropId, amount, coords
|
|
elseif type(p3) == "number" and type(p4) == "number" and type(p5) == "number" then
|
|
dropId = maybeDropId
|
|
amount = maybeAmount
|
|
coords = vector3(p3, p4, p5)
|
|
return dropId, amount, coords
|
|
end
|
|
end
|
|
|
|
-- (amount, coordsTable) OR (amount, x, y, z) -> local fallback id
|
|
local maybeAmount = tonumber(p1) or 0
|
|
if type(p2) == "table" and p2.x and p2.y and p2.z then
|
|
dropId = GetGameTimer() + math.random(1, 999999)
|
|
amount = maybeAmount
|
|
coords = vector3(p2.x, p2.y, p2.z)
|
|
return dropId, amount, coords
|
|
elseif type(p2) == "number" and type(p3) == "number" and type(p4) == "number" then
|
|
dropId = GetGameTimer() + math.random(1, 999999)
|
|
amount = maybeAmount
|
|
coords = vector3(p2, p3, p4)
|
|
return dropId, amount, coords
|
|
end
|
|
|
|
return nil, nil, nil
|
|
end
|
|
|
|
-- =====================
|
|
-- CASH PICKUP SPAWN
|
|
-- =====================
|
|
RegisterNetEvent("environment:spawnCashPickup", function(p1, p2, p3, p4, p5)
|
|
local dropId, amount, coords = parseSpawnArgs(p1, p2, p3, p4, p5)
|
|
|
|
if not dropId or not coords then
|
|
print(("^1[environment]^7 spawnCashPickup: bad args types: %s %s %s %s %s")
|
|
:format(type(p1), type(p2), type(p3), type(p4), type(p5)))
|
|
return
|
|
end
|
|
|
|
amount = tonumber(amount) or 0
|
|
if amount <= 0 then return end
|
|
|
|
-- If already exists, delete old one first
|
|
local existing = spawnedDrops[dropId]
|
|
if existing and existing.obj and DoesEntityExist(existing.obj) then
|
|
DeleteEntity(existing.obj)
|
|
end
|
|
|
|
local model = GetHashKey("prop_cash_pile_01")
|
|
RequestModel(model)
|
|
while not HasModelLoaded(model) do Wait(0) end
|
|
|
|
local obj = CreateObject(model, coords.x, coords.y, coords.z - 0.9, false, false, false)
|
|
PlaceObjectOnGroundProperly(obj)
|
|
FreezeEntityPosition(obj, true)
|
|
SetEntityAsMissionEntity(obj, true, true)
|
|
|
|
spawnedDrops[dropId] = { obj = obj, coords = coords, amount = amount }
|
|
-- print(("[environment] Cash drop #%s: $%s at %.2f %.2f %.2f"):format(dropId, amount, coords.x, coords.y, coords.z))
|
|
end)
|
|
|
|
-- =====================
|
|
-- WALK-OVER COLLECT LOOP
|
|
-- =====================
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(PICKUP_CHECK_MS)
|
|
|
|
local ped = PlayerPedId()
|
|
if not ped or ped == 0 then goto continue end
|
|
if IsEntityDead(ped) then goto continue end
|
|
|
|
local pcoords = GetEntityCoords(ped)
|
|
local now = GetGameTimer()
|
|
|
|
for dropId, data in pairs(spawnedDrops) do
|
|
if data and data.coords and data.obj and DoesEntityExist(data.obj) then
|
|
local dist = #(pcoords - data.coords)
|
|
if dist <= PICKUP_RADIUS then
|
|
local last = pickupCooldown[dropId] or 0
|
|
if (now - last) > PICKUP_COOLDOWN then
|
|
pickupCooldown[dropId] = now
|
|
TriggerServerEvent("environment:pickupCash", dropId)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
end)
|
|
|
|
-- =====================
|
|
-- FEEDBACK
|
|
-- =====================
|
|
RegisterNetEvent("environment:pickupFeedback", function(amount)
|
|
amount = tonumber(amount) or 0
|
|
if amount <= 0 then return end
|
|
|
|
PlaySoundFrontend(-1, "PICK_UP", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
|
|
|
|
BeginTextCommandThefeedPost("STRING")
|
|
AddTextComponentSubstringPlayerName(("+~g~$%d~s~"):format(amount))
|
|
EndTextCommandThefeedPostTicker(false, true)
|
|
end)
|
|
|
|
-- =====================
|
|
-- REMOVE / DESPAWN
|
|
-- =====================
|
|
RegisterNetEvent("environment:removeCashPickup", function(dropId)
|
|
dropId = tonumber(dropId)
|
|
if not dropId then return end
|
|
|
|
local data = spawnedDrops[dropId]
|
|
if data and data.obj and DoesEntityExist(data.obj) then
|
|
-- Helpful in some cases where entity control is finicky
|
|
NetworkRequestControlOfEntity(data.obj)
|
|
SetEntityAsMissionEntity(data.obj, true, true)
|
|
DeleteEntity(data.obj)
|
|
end
|
|
|
|
spawnedDrops[dropId] = nil
|
|
pickupCooldown[dropId] = nil
|
|
end)
|