Skip to content

Commit b8abc8c

Browse files
committed
Add Bonus Items and Custom Reward Hooks
1 parent 83ec5f0 commit b8abc8c

4 files changed

Lines changed: 454 additions & 0 deletions

File tree

pages/lootbox.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ A CS:GO-style lootbox/case opening system for FiveM with a polished React UI, we
1818
- 📦 **Multiple inventory support** - Works with ox_inventory, qb-inventory, and ESX inventory
1919
- 🎁 **Metadata support** - Items can include custom metadata
2020
- 📝 **Config + Runtime API** - Define lootboxes in config or register them dynamically via exports
21+
- 🎀 **Bonus items** - Award hidden bonus items alongside the main reward (e.g., ammo with weapons)
22+
- 🔌 **Custom reward hooks** - Extensible hook system for custom reward types (vehicles, bank money, etc.)
2123

2224
## Dependencies
2325

pages/lootbox/configuration.mdx

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ Each item in the `items` array follows this format:
121121
| `amount` | `number` | Yes | Quantity of item to give |
122122
| `metadata` | `table` | No | Custom metadata to attach to item |
123123
| `rarity` | `string` | No | Override auto-calculated rarity |
124+
| `label` | `string` | No | Custom display label (auto-fetched from inventory if not provided) |
125+
| `image` | `string` | No | Custom image URL (auto-fetched from inventory if not provided) |
126+
| `bonusItems` | `table` | No | Additional items to award alongside the main reward (not shown in UI) |
127+
| `rewardType` | `string` | No | Custom reward type for non-item rewards (e.g., `'vehicle'`, `'bank'`) |
128+
| `rewardData` | `table` | No | Custom data passed to reward hooks (e.g., vehicle model, garage) |
124129

125130
### Complete Example
126131

@@ -197,6 +202,186 @@ Include custom metadata that will be attached to the item when given:
197202
When using metadata, ensure your inventory system supports the metadata fields you're using.
198203
</Callout>
199204

205+
### Bonus Items
206+
207+
Award additional items alongside the main reward. Bonus items are **not displayed in the UI** - players only see the main item during the roll animation, but receive all bonus items when the reward is claimed.
208+
209+
```lua
210+
{ 40, {
211+
name = 'WEAPON_PISTOL',
212+
amount = 1,
213+
bonusItems = {
214+
{ name = 'ammo-9', amount = 50 },
215+
{ name = 'armour', amount = 1 },
216+
}
217+
} }
218+
```
219+
220+
Each bonus item has the following properties:
221+
222+
| Property | Type | Required | Description |
223+
|----------|------|----------|-------------|
224+
| `name` | `string` | Yes | Item spawn name |
225+
| `amount` | `number` | Yes | Quantity to give |
226+
| `metadata` | `table` | No | Optional item metadata |
227+
228+
<Callout type="info">
229+
Bonus items are great for giving ammo with weapons, or adding extra consumables as a surprise bonus without cluttering the UI.
230+
</Callout>
231+
232+
### Custom Reward Types
233+
234+
For rewards that aren't standard inventory items (vehicles, bank deposits, etc.), use the `rewardType` and `rewardData` fields along with the reward hook system.
235+
236+
<Callout type="info">
237+
For custom reward types, the `name` field is just a **unique identifier** used for internal tracking and logging - it doesn't need to be an actual item in your inventory. Since custom rewards aren't real items, you must also provide `label` and `image` explicitly.
238+
</Callout>
239+
240+
```lua
241+
{ 0.26, {
242+
name = 'vehicle_adder', -- Unique identifier, not an actual item
243+
label = 'Adder', -- Required: display label
244+
amount = 1,
245+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp', -- Required: custom image
246+
rarity = 'legendary',
247+
rewardType = 'vehicle',
248+
rewardData = { model = 'adder', garage = 'legion' },
249+
} }
250+
```
251+
252+
<Callout type="warning">
253+
Custom reward types require a registered reward hook to handle them. If no hook is registered, the system will fall back to default item behavior and log a warning.
254+
</Callout>
255+
256+
#### How Custom Rewards Work
257+
258+
1. When a player wins an item with a custom `rewardType`, the system looks for a registered hook for that type
259+
2. If a hook exists and returns `true`, the hook handled the reward
260+
3. If a hook returns `false`/`nil`, the system falls back to default item behavior
261+
4. If no hook exists, a warning is logged and the system falls back to default item behavior
262+
263+
This ensures rewards are never silently lost - if something isn't configured correctly, players still receive items.
264+
265+
#### Registering Reward Hooks
266+
267+
Register hooks from your own resource to handle custom reward types:
268+
269+
```lua
270+
-- In your server script (e.g., a vehicle management resource)
271+
exports.sleepless_lootbox:registerRewardHook('vehicle', function(source, reward, caseName)
272+
local data = reward.rewardData
273+
-- Your vehicle spawning logic here
274+
-- e.g., exports['qbx_vehicles']:CreatePlayerVehicle(source, data.model, data.garage)
275+
276+
-- Notify the player
277+
TriggerClientEvent('ox_lib:notify', source, {
278+
title = 'Vehicle Won!',
279+
description = 'Your ' .. reward.label .. ' has been added to your garage.',
280+
type = 'success'
281+
})
282+
283+
return true -- Return true to indicate we handled this reward
284+
end)
285+
286+
-- You can also register a 'bank' hook for bank money rewards
287+
exports.sleepless_lootbox:registerRewardHook('bank', function(source, reward, caseName)
288+
local data = reward.rewardData
289+
-- Your bank deposit logic here
290+
-- e.g., exports['qbx_core']:AddMoney(source, 'bank', data.amount)
291+
return true
292+
end)
293+
```
294+
295+
See the [Server Exports](/lootbox/exports/Server) page for full API documentation.
296+
297+
### Complete Vehicle Crate Example
298+
299+
Here's a complete example of a lootbox that awards vehicles using custom reward types:
300+
301+
```lua
302+
['vehicle_crate'] = {
303+
label = 'Vehicle Crate',
304+
description = 'Win a brand new vehicle!',
305+
items = {
306+
-- Common vehicles (~80% total)
307+
{ 40, {
308+
name = 'vehicle_blista', -- Unique identifier (not an actual item, just for internal tracking/logging)
309+
label = 'Blista', -- Required: display label since this isn't a real item
310+
amount = 1,
311+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp', -- Required: custom image
312+
rewardType = 'vehicle',
313+
rewardData = { model = 'blista', garage = 'legion' },
314+
} },
315+
{ 40, {
316+
name = 'vehicle_prairie', -- Unique identifier
317+
label = 'Prairie',
318+
amount = 1,
319+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
320+
rewardType = 'vehicle',
321+
rewardData = { model = 'prairie', garage = 'legion' },
322+
} },
323+
324+
-- Uncommon vehicles (~16% total)
325+
{ 8, {
326+
name = 'vehicle_buffalo',
327+
label = 'Buffalo',
328+
amount = 1,
329+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
330+
rewardType = 'vehicle',
331+
rewardData = { model = 'buffalo', garage = 'legion' },
332+
} },
333+
{ 8, {
334+
name = 'vehicle_sultan',
335+
label = 'Sultan',
336+
amount = 1,
337+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
338+
rewardType = 'vehicle',
339+
rewardData = { model = 'sultan', garage = 'legion' },
340+
} },
341+
342+
-- Rare vehicles (~3.1% total)
343+
{ 1.6, {
344+
name = 'vehicle_elegy2',
345+
label = 'Elegy RH8',
346+
amount = 1,
347+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
348+
rewardType = 'vehicle',
349+
rewardData = { model = 'elegy2', garage = 'legion' },
350+
} },
351+
{ 1.5, {
352+
name = 'vehicle_comet2',
353+
label = 'Comet',
354+
amount = 1,
355+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
356+
rewardType = 'vehicle',
357+
rewardData = { model = 'comet2', garage = 'legion' },
358+
} },
359+
360+
-- Epic vehicles (~0.64% total)
361+
{ 0.64, {
362+
name = 'vehicle_zentorno',
363+
label = 'Zentorno',
364+
amount = 1,
365+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
366+
rarity = 'epic',
367+
rewardType = 'vehicle',
368+
rewardData = { model = 'zentorno', garage = 'legion' },
369+
} },
370+
371+
-- Legendary vehicles (~0.26% total)
372+
{ 0.26, {
373+
name = 'vehicle_adder',
374+
label = 'Adder',
375+
amount = 1,
376+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
377+
rarity = 'legendary',
378+
rewardType = 'vehicle',
379+
rewardData = { model = 'adder', garage = 'legion' },
380+
} },
381+
},
382+
},
383+
```
384+
200385
## Server Settings
201386

202387
```lua

pages/lootbox/examples.mdx

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,156 @@ local function tryOpenCase(caseName)
106106
TriggerServerEvent('myresource:openCase', caseName)
107107
end
108108
```
109+
110+
## Bonus Items (Hidden Rewards)
111+
112+
Award additional items alongside the main reward without showing them in the UI. Useful for giving ammo with weapons.
113+
114+
```lua
115+
-- In config.lua
116+
config.lootboxes = {
117+
['gun_case'] = {
118+
label = 'Gun Case',
119+
description = 'Contains various firearms with ammo',
120+
items = {
121+
{ 40, {
122+
name = 'WEAPON_PISTOL',
123+
amount = 1,
124+
-- Bonus items are awarded but NOT shown in the UI
125+
bonusItems = {
126+
{ name = 'ammo-9', amount = 50 },
127+
}
128+
} },
129+
{ 0.1, {
130+
name = 'WEAPON_CARBINERIFLE',
131+
amount = 1,
132+
bonusItems = {
133+
{ name = 'ammo-rifle', amount = 250 },
134+
{ name = 'armour', amount = 2 },
135+
{ name = 'money', amount = 5000 },
136+
}
137+
} },
138+
},
139+
},
140+
}
141+
```
142+
143+
<Callout type="info">
144+
Bonus items are given server-side when the reward is claimed. Players will see them appear in their inventory but won't see them during the roll animation.
145+
</Callout>
146+
147+
## Custom Reward Types with Hooks
148+
149+
Use custom reward types to handle non-item rewards like vehicles or bank deposits. Register hooks in your own resource to handle these reward types.
150+
151+
### Vehicle Reward Hook
152+
153+
```lua
154+
-- Server side
155+
exports.sleepless_lootbox:registerRewardHook('vehicle', function(source, reward, caseName)
156+
local data = reward.rewardData
157+
158+
if not data or not data.model then
159+
print('Vehicle reward missing model data')
160+
return false -- Return false to fall back to default item behavior
161+
end
162+
163+
lib.notify(source, {
164+
title = 'Vehicle Won!',
165+
description = ('You won a %s! Check your garage.'):format(reward.label),
166+
type = 'success',
167+
})
168+
169+
return true -- Fall back to default if something went wrong
170+
end)
171+
```
172+
173+
### Bank Money Reward Hook
174+
175+
```lua
176+
-- Server side
177+
exports.sleepless_lootbox:registerRewardHook('bank', function(source, reward, caseName)
178+
local data = reward.rewardData
179+
180+
if not data or not data.amount then
181+
return false
182+
end
183+
184+
lib.notify(source, {
185+
title = 'Bank Deposit',
186+
description = ('$%s has been deposited to your bank!'):format(data.amount),
187+
type = 'success',
188+
})
189+
190+
return true
191+
end)
192+
```
193+
194+
### Lootbox Config with Custom Rewards
195+
196+
```lua
197+
-- In config.lua
198+
config.lootboxes = {
199+
['vehicle_crate'] = {
200+
label = 'Vehicle Crate',
201+
description = 'Win a brand new vehicle!',
202+
items = {
203+
{ 40, {
204+
name = 'vehicle_blista', -- Unique identifier (not an actual item, just for internal tracking/logging)
205+
label = 'Blista', -- Display label in UI (required for custom reward types)
206+
amount = 1,
207+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
208+
rewardType = 'vehicle', -- Custom reward type
209+
rewardData = { model = 'blista', garage = 'legion' }, -- Data passed to hook
210+
} },
211+
{ 0.26, {
212+
name = 'vehicle_adder',
213+
label = 'Adder',
214+
amount = 1,
215+
image = 'nui://ox_inventory/web/images/vehicle_crate.webp',
216+
rarity = 'legendary', -- Can still specify rarity
217+
rewardType = 'vehicle',
218+
rewardData = { model = 'adder', garage = 'legion' },
219+
} },
220+
},
221+
},
222+
223+
['vip_rewards'] = {
224+
label = 'VIP Rewards',
225+
description = 'Premium rewards including bank deposits',
226+
items = {
227+
{ 50, { name = 'armour', amount = 5 } }, -- Regular item reward
228+
{ 30, {
229+
name = 'bank_deposit_10k', -- Unique identifier (used for logging, not an actual item)
230+
label = '$10,000 Bank Deposit', -- Required: display label since this isn't a real item
231+
amount = 1,
232+
image = 'nui://ox_inventory/web/images/money.webp',
233+
rewardType = 'bank',
234+
rewardData = { amount = 10000 },
235+
} },
236+
{ 1, {
237+
name = 'bank_deposit_100k', -- Unique identifier
238+
label = '$100,000 Bank Deposit', -- Display label
239+
amount = 1,
240+
image = 'nui://ox_inventory/web/images/money.webp',
241+
rarity = 'legendary',
242+
rewardType = 'bank',
243+
rewardData = { amount = 100000 },
244+
} },
245+
},
246+
},
247+
}
248+
```
249+
250+
<Callout type="warning">
251+
If a custom `rewardType` is set but no hook is registered, the system will log a warning and fall back to treating it as a regular item. This prevents rewards from being lost but may not give the intended reward.
252+
</Callout>
253+
254+
### Removing a Hook
255+
256+
You can remove a previously registered hook if needed:
257+
258+
```lua
259+
-- Server side
260+
exports.sleepless_lootbox:removeRewardHook('vehicle')
261+
```

0 commit comments

Comments
 (0)