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)