Denver Studio - Docs

Sistema de Baú

Configuração do baú das casas com suporte a inventários externos

Sistema de Baú

O baú de cada casa pode operar em dois modos, selecionados via CFG['preferences'].chestProvider:

  • "builtin" (padrão) — UI própria do DS-Homes + persistência em ds_homes.chest.
  • "external" — delega a abertura para hooks em shared/chest_hooks.lua. Útil para integrar com o inventário próprio do servidor (ox_inventory, qb-inventory, vRP nativo, sistemas customizados).

Em ambos os modos, o DS-Homes continua validando:

  • Se o jogador está dentro da casa
  • Se é dono OU morador com canAccessChest
  • Lock exclusivo (apenas um jogador abre o baú por vez)

Config (preferences.lua)

CFG['preferences'] = {
  chestProvider = "builtin",      -- "builtin" | "external"
  defaultChestSlots = 50,         -- passado em ctx.maxSlots para hooks externos
}

Modo External

Quando chestProvider = "external", o DS-Homes chama o hook onOpen em shared/chest_hooks.lua. Você é dono de tudo a partir daí: UI, persistência, limites de peso, eventos de close.

Arquivo shared/chest_hooks.lua

CFG = CFG or {}

CFG['chestHooks'] = {
    onRegisterStash = function(ctx)
        -- Opcional. Chamado lazy antes da primeira onOpen por stashId.
    end,

    onOpen = function(source, ctx)
        -- OBRIGATÓRIO. Abre o inventário do seu sistema.
        -- Return { success = bool, message? = string }.
        return { success = true }
    end,

    onDump = function(ctx)
        -- Opcional. Usado pelo comando dshomes:chest:export.
        -- Return { items = { {item, quantity, metadata?}, ... } } ou nil.
        return nil
    end,

    onClear = function(ctx)
        -- Opcional mas RECOMENDADO. Chamado toda vez que a casa é removida
        -- (IPTU vencido, venda pro governo, apreensão por inadimplência,
        -- deleção via admin). Limpe a stash externa associada ao stashId
        -- para evitar dados órfãos no seu inventário.
    end,
}

Campos do ctx

CampoTipoDescrição
stashIdstringdshomes:<ownerId>:<homeName> — determinístico, idempotente
ownerIdnumberuser_id do dono da casa
homeNamestringNome da casa (ou apartamento)
buildingNamestringNome do prédio (igual a homeName em casas não-apartamento)
maxWeightnumberPeso máximo (baseStorage + chest_storage_upgrade)
maxSlotsnumberSlots — vem de CFG['preferences'].defaultChestSlots

Exemplos de integração

CFG = CFG or {}

CFG['chestHooks'] = {
    onRegisterStash = function(ctx)
        exports.ox_inventory:RegisterStash(ctx.stashId, ctx.homeName, ctx.maxSlots, ctx.maxWeight, nil)
    end,
    onOpen = function(source, ctx)
        exports.ox_inventory:forceOpenInventory(source, 'stash', ctx.stashId)
        return { success = true }
    end,
    onClear = function(ctx)
        exports.ox_inventory:ClearInventory(ctx.stashId)
    end,
}
CFG = CFG or {}

CFG['chestHooks'] = {
    onOpen = function(source, ctx)
        TriggerClientEvent('inventory:client:SetCurrentStash', source, ctx.stashId)
        TriggerEvent('qb-inventory:server:OpenInventory', 'stash', ctx.stashId, {
            maxweight = ctx.maxWeight,
            slots = ctx.maxSlots,
        })
        return { success = true }
    end,
}
CFG = CFG or {}

CFG['chestHooks'] = {
    onOpen = function(source, ctx)
        vRP.openChest(source, ctx.stashId, ctx.maxWeight)
        return { success = true }
    end,
}

Dispare um evento cliente que o seu inventário já escuta, passando o payload que ele espera:

CFG = CFG or {}

CFG['chestHooks'] = {
    onOpen = function(source, ctx)
        TriggerClientEvent("meu_inventario:openStash", source, {
            id = ctx.stashId,
            maxWeight = ctx.maxWeight,
            name = ctx.homeName,
        })
        return { success = true }
    end,
    onDump = function(ctx)
        local raw = vRP.getSData("STASH:" .. ctx.stashId)
        if not raw or raw == "" then
            return { items = {}, source = "custom" }
        end
        local decoded = json.decode(raw) or {}
        local items = {}
        for item, data in pairs(decoded) do
            items[#items + 1] = { item = item, quantity = data.amount }
        end
        return { items = items, source = "custom" }
    end,
    onClear = function(ctx)
        vRP.setSData("STASH:" .. ctx.stashId, "")
    end,
}

Se o inventário externo valida internamente se o jogador está dentro da casa (chamando, por exemplo, exports.brasil_homes:isUserInsideHome(source, houseId)), aponte essa chamada para o DS-Homes:

if not exports['ds-homes']:isUserInsideHome(source, houseId) then
    return false
end

O DS-Homes expõe isUserInsideHome(source, houseId) retornando true se o jogador está dentro da casa informada.

Comando de dump

Admins podem exportar o conteúdo do baú (qualquer modo) via console do servidor:

dshomes:chest:export <ownerId> <homeName>

Retorna um JSON com ctx e dump.items. Útil para debug, migração manual ou auditoria.

  • No modo "builtin": dump lido direto de ds_homes.chest.
  • No modo "external": dump vem do hook onDump(ctx). Se o hook não estiver implementado, o comando reporta "Dump indisponivel".

Troca de provider é feita via config + restart. Não existe migration automática entre modos — dados em ds_homes.chest ficam intactos se você voltar para builtin.

Upgrade de peso

Funciona em ambos os modos. O jogador pode expandir o baú in-game; o valor expandido é somado em ctx.maxWeight e é visível pelos hooks externos.

CFG['preferences'] = {
  chestUpgrade = {
    pricePerKg = 1500,
    maxUpgradeKg = 500,
    minUpgradeKg = 10,
  },
}

Limpeza ao remover a casa

Toda vez que uma casa é deletada — IPTU vencido, venda pro governo, apreensão por inadimplência no financiamento, deleção via admin — o DS-Homes chama CFG['chestHooks'].onClear(ctx) antes de apagar a row em ds_homes.

No modo "builtin", isso é desnecessário: a coluna chest mora dentro da própria row e some junto. No modo "external", sem esse hook os itens da stash externa ficam órfãos no seu inventário — a casa some mas o conteúdo do baú continua acessível pelo stashId, ocupando espaço.

CFG['chestHooks'] = {
    onClear = function(ctx)
        -- ctx é o mesmo passado em onOpen/onDump
        -- Implemente a chamada que zera a stash no seu inventário
    end,
}
InventárioImplementação típica
ox_inventoryexports.ox_inventory:ClearInventory(ctx.stashId)
qb-inventoryexports['qb-inventory']:ClearStash(ctx.stashId)
vRP/sdata customvRP.setSData("STASH:" .. ctx.stashId, "")

Se você não implementar onClear, o DS-Homes apenas loga um warning e segue com a deleção da casa. Itens permanecem no seu inventário externo até você limpar manualmente.

Onde a remoção acontece

Internamente, o DS-Homes centraliza a remoção em Homes.deleteHome (server/modules/lifecycle.lua), invocado por:

  • Loop de IPTU vencido (server/_index.lua)
  • Venda pro governo (server/modules/sell.lua)
  • Apreensão por inadimplência (server/modules/financing.lua)
  • Deleção via admin (server/modules/admin.luadeletePlayerHome, purgar config, /delhome)

Em todos esses caminhos, a sequência é: chama onClear → limpa ds_homes_favorites e ds_homes_outfitsDELETE FROM ds_homes → invalida cache.

Lifecycle do lock exclusivo

No modo "external", o lock (chestsInUse) libera quando:

  1. O jogador sai da casa.
  2. O jogador desconecta.

Se você quiser que o lock libere assim que o jogador fecha a UI do seu inventário, dispare o evento server:

TriggerEvent('homes:closeChest')

Geralmente de dentro do evento de close do seu próprio inventário.