Explorer
KNOW-PAT-104

FiveM - Motel IPL + Stash : ox_inventory RegisterStash, achat, society percentage, raid

Domaine
fivem
Type
pattern
Priorité
P2

Parent : [[INDEX-FIVEM-GAMEPLAY]]

FiveM - Motel IPL + Stash : ox_inventory RegisterStash, achat, society percentage, raid

Problème

Un système de motel nécessite : intérieurs IPL, stockage d'items par chambre, achat avec 2 modes de paiement, commission société, et possibilité de raid par le staff/police.

Solution

Insertion DB pour persistence, ox_inventory:RegisterStash dynamique par chambre, 2 modes de paiement (cash/bank), commission automatique vers la société, et webhook logging.

-- Achat chambre
RegisterNetEvent('pw-motel:buyRoom')
AddEventHandler('pw-motel:buyRoom', function(motelId, roomNumber, days, paymentMethod)
    local src = source
    local xPlayer = ESX.GetPlayerFromId(src)
    local identifier = xPlayer.getIdentifier()
    
    local motel = Config.Motels[motelId]
    local pricePerDay = motel.price
    local totalPrice = pricePerDay * days
    
    -- Vérification paiement
    if paymentMethod == 'cash' then
        if xPlayer.getMoney() < totalPrice then
            TriggerClientEvent('ox_lib:notify', src, { type = 'error', description = 'Fonds insuffisants' })
            return
        end
        xPlayer.removeMoney(totalPrice)
    elseif paymentMethod == 'bank' then
        if xPlayer.getAccount('bank').money < totalPrice then
            TriggerClientEvent('ox_lib:notify', src, { type = 'error', description = 'Fonds bancaires insuffisants' })
            return
        end
        xPlayer.removeAccountMoney('bank', totalPrice)
    end
    
    -- Insertion BDD
    MySQL.insert('INSERT INTO motel (owner, labelMotel, numberChambre, day, expire) VALUES (?, ?, ?, ?, ?)', {
        identifier,
        motel.label,
        roomNumber,
        days,
        os.time() + (days * 86400)
    }, function(insertId)
        if insertId then
            -- Registration du stash ox_inventory
            local stashName = ('motel_%s_%s'):format(motelId, roomNumber)
            exports.ox_inventory:RegisterStash(
                stashName,
                ('Chambre %s - %s'):format(roomNumber, motel.label),
                motel.slots or 50,
                motel.maxWeight or 100000,
                false,
                false
            )
            
            -- Commission société (optionnel)
            if Config.pourcentageSociety then
                TriggerEvent('esx_addonaccount:getSharedAccount', Config.societyname, function(account)
                    local commission = math.floor(totalPrice * (Config.pourcentage / 100))
                    account.addMoney(commission)
                end)
            end
            
            -- Logging webhook
            SendWebhook('Motel Purchase', ('%s a acheté chambre %s à %s pour $%s (%s jours)'):format(
                xPlayer.getName(), roomNumber, motel.label, totalPrice, days
            ))
            
            TriggerClientEvent('pw-motel:purchaseSuccess', src, motelId, roomNumber)
        end
    end)
end)

-- Raid staff/police
RegisterNetEvent('pw-motel:raidRoom')
AddEventHandler('pw-motel:raidRoom', function(motelId, roomNumber)
    local src = source
    local xPlayer = ESX.GetPlayerFromId(src)
    
    -- Vérification permission
    local hasPermission = false
    if Config.RaidPermission.job then
        if Config.RaidPermission.jobs[xPlayer.job.name] and
           xPlayer.job.grade >= Config.RaidPermission.jobs[xPlayer.job.name] then
            hasPermission = true
        end
    end
    if Config.RaidPermission.ace and IsPlayerAceAllowed(src, 'motel.raid') then
        hasPermission = true
    end
    
    if not hasPermission then
        TriggerClientEvent('ox_lib:notify', src, { type = 'error', description = 'Permission insuffisante' })
        return
    end
    
    -- Ouverture du stash en raid
    local stashName = ('motel_%s_%s'):format(motelId, roomNumber)
    exports.ox_inventory:forceOpenInventory(src, 'stash', stashName)
    
    SendWebhook('Motel Raid', ('%s (%s) a forcé la chambre %s de %s'):format(
        xPlayer.getName(), xPlayer.job.name, roomNumber, Config.Motels[motelId].label
    ))
end)

Pourquoi

  • ox_inventory:RegisterStash crée un stockage unique par chambre sans thread custom
  • Commission société crée une économie de service
  • Expiration timestamp permet le renouvellement automatique

Quand l'utiliser

  • Motels, appartements, garages, stockages de gang
  • Tout système de location avec stockage

Quand NE PAS l'utiliser

  • Stockages globaux (pas de propriété) → ox_inventory stashes classiques suffisent

Exemples

Correct

-- Cleanup des stashes expirés (cron ou au démarrage)
CreateThread(function()
    while true do
        Wait(3600000) -- Toutes les heures
        
        local expired = MySQL.query.await('SELECT * FROM motel WHERE expire < ?', { os.time() })
        for _, room in ipairs(expired) do
            local stashName = ('motel_%s_%s'):format(room.motelId, room.numberChambre)
            
            -- Vider le stash avant suppression
            local stashItems = exports.ox_inventory:GetInventoryItems(stashName)
            for _, item in ipairs(stashItems or {}) do
                exports.ox_inventory:RemoveItem(stashName, item.name, item.count)
            end
            
            MySQL.update('DELETE FROM motel WHERE id = ?', { room.id })
            SendWebhook('Motel Expired', ('Chambre %s expirée et nettoyée'):format(room.numberChambre))
        end
    end
end)

Incorrect

-- Pas de vérification du propriétaire → n'importe qui ouvre le stash
RegisterNetEvent('motel:openStash')
AddEventHandler('motel:openStash', function(stashName)
    exports.ox_inventory:forceOpenInventory(source, 'stash', stashName)
end)

Références

  • pw-motel/server/server.lua L26-91
  • pw-motel/config/config.lua L70+
  • REF-003 (ox_inventory documentation)