RoyaleWindRoyaleWind

Server

Server hooks in hooks/server.lua.

Register these in hooks/server.lua. See Hooks for how gates, filters and notifies work.

Opening a store

canOpenStore

Gate. May this player open this store? Runs after the built-in store, job lock and distance checks.

  • source: number
  • storeId: string — store id
  • store: table — the store's config block
Hooks.on('canOpenStore', function(source, storeId, store)
    if exports.qbx_policejob:isPlayerCuffed(source) then
        return false, 'no_access'
    end
end)

storePayload

Filter. The data sent to the store UI when it opens. Return a changed copy to hide outfits, rename them, and so on.

  • payload: table — includes outfits, the list shown in the store
  • source: number
  • storeId: string
Hooks.on('storePayload', function(payload, source, storeId)
    local kept = {}

    for index = 1, #payload.outfits do
        local outfit = payload.outfits[index]

        if outfit.collection ~= 'staff' or IsPlayerAceAllowed(source, 'fashion.staff') then
            kept[#kept + 1] = outfit
        end
    end

    payload.outfits = kept

    return payload
end)

The contents of outfits the player doesn't own are already stripped out. Don't put them back — that's what keeps unbought outfits sealed.

Money

discount

Filter. The final discount rate, from 0 to 1. What you return replaces the discount worked out from the editor's Discounts settings, including the Largest total discount cap — this is the only way past it. The result is clamped to 0–1.

  • rate: number — 0–1
  • source: number
  • storeId: string
  • sources: table[] — { label, rate } for each discount that applied
Hooks.on('discount', function(rate, source, storeId, sources)
    if exports.my_loyalty:tier(source) == 'gold' then
        return math.max(rate, 0.25)
    end
end)

canBuy

Gate. The last check before the player is charged. Refusing refuses the whole cart.

  • source: number
  • order: table
FieldTypeDescription
identifierstringthe character the outfits would go to
storestringstore id
accountstring'bank' or 'cash'
totalintegerwhat they're about to be charged
discountnumber0–1
outfitsstring[]catalog ids in the cart
Hooks.on('canBuy', function(source, order)
    if order.total > 50000 and not IsPlayerAceAllowed(source, 'fashion.bigspender') then
        return false, 'no_access'
    end
end)

bought

Notify. A purchase went through: paid for, owned and logged.

  • source: number
  • receipt: table — same fields as order in canBuy, with outfits being what was granted
Hooks.on('bought', function(source, receipt)
    exports.my_stats:add(source, 'clothes_spent', receipt.total)
end)

Player listings

canList

Gate. May this player list this outfit? Runs after validation, before the fee is taken.

  • source: number
  • draft: table
FieldTypeDescription
labelstringoutfit name
descriptionstringoutfit description
collectionstringcollection id
gender'male' | 'female'ped the outfit was captured on
priceintegerthe seller's asking price, before store multipliers
piecesintegerhow many components and props it has
Hooks.on('canList', function(source, draft)
    if draft.pieces < 4 then
        return false, 'empty_outfit'
    end
end)

listed

Notify. The listing is live and the fee has been taken.

  • source: number
  • outfit: table — the draft from canList plus id and author (the lister's identifier)
Hooks.on('listed', function(source, outfit)
    exports.my_discord:post(('%s listed %s for %d'):format(GetPlayerName(source), outfit.label, outfit.price))
end)

earningsCollected

Notify. A seller pressed Collect in a store and their banked sales income was paid out.

  • source: number
  • amount: number — what was paid into Listing.feeAccount
Hooks.on('earningsCollected', function(source, amount)
    exports.my_stats:add(source, 'fashion_income', amount)
end)

On this page