A CS:GO-style lootbox/case opening system for FiveM with weight-based loot pools.
- π° CS:GO-style roller animation - Smooth spinning animation
- βοΈ Weight-based loot system - Uses
ox_libselector for flexible drop rates - π Preview system - Players can view case contents and drop chances before opening
- π¨ Rarity system - Visual rarity tiers (Common, Uncommon, Rare, Epic, Legendary)
- π§ Framework agnostic - Supports ESX, QBCore, Qbox, and ox_core out of the box
- π¦ Multiple inventory support - Works with ox_inventory, qb-inventory
- π Metadata support - Items can include custom metadata
- π Config + Runtime API - Define lootboxes in config or register them dynamically via exports
- ox_lib (required)
- A supported framework (ESX, QBCore, Qbox, or ox_core)
- A supported inventory system
- Download and extract to your resources folder
- Add
ensure sleepless_lootboxto your server.cfg (after ox_lib and your framework) - Configure lootboxes in
config.lua - Build the UI (if not pre-built):
cd web npm install npm run build
config.lootboxes = {
['gun_case'] = {
label = 'Gun Case',
description = 'Contains various firearms',
items = {
-- Format: { weight, { name, amount, metadata?, rarity? } }
{ 80, { name = 'WEAPON_PISTOL', amount = 1 } },
{ 15, { name = 'WEAPON_SMG', amount = 1 } },
{ 4, { name = 'WEAPON_RIFLE', amount = 1 } },
{ 1, { name = 'WEAPON_RPG', amount = 1 } },
},
},
}Weights determine the relative drop chance of each item:
- Higher weight = more common
- Example: Items with weights 80, 15, 4, 1 (total 100) have 80%, 15%, 4%, and 1% chances respectively
- Weights don't need to add up to 100 - they're calculated relative to the total weight of all items
If you don't specify a rarity on an item, it's automatically calculated based on the weight:
| Rarity | Weight Threshold |
|---|---|
| Common | weight >= 17 |
| Uncommon | weight >= 4 |
| Rare | weight >= 1 |
| Epic | weight >= 0.3 |
| Legendary | weight < 0.3 |
You can customize these thresholds in config.lua.
-- Register a new lootbox at runtime
exports.sleepless_lootbox:registerLootbox(name, data)
-- Unregister a lootbox
exports.sleepless_lootbox:unregisterLootbox(name)
-- Get lootbox data
exports.sleepless_lootbox:getLootbox(name)
-- Get all registered lootboxes
exports.sleepless_lootbox:getAllLootboxes()
-- Open a lootbox for a player
-- Set skipItemRemoval to true if you handle item removal yourself
exports.sleepless_lootbox:open(source, caseName, skipItemRemoval)
-- Get preview data for a lootbox (items with chances)
exports.sleepless_lootbox:getPreview(caseName)-- Request to show the preview modal for a case
exports.sleepless_lootbox:preview(caseName)
-- Check if a roll is currently in progress
exports.sleepless_lootbox:isRolling()
-- Close the lootbox UI
exports.sleepless_lootbox:close()-- server side
exports.sleepless_lootbox:registerLootbox('mystery_box', {
label = 'Mystery Box',
description = 'Who knows what\'s inside?',
registerItem = true, -- Auto-register as usable item
items = {
{ 50, { name = 'bread', amount = 5 } },
{ 30, { name = 'water', amount = 3 } },
{ 15, { name = 'bandage', amount = 2 } },
{ 5, { name = 'medkit', amount = 1, rarity = 'rare' } },
},
})-- server side
RegisterNetEvent('myresource:openCase', function(caseName)
local src = source
-- skipItemRemoval = true because we handle it ourselves
exports.sleepless_lootbox:open(src, caseName, true)
end){ 1, {
name = 'weapon_pistol',
amount = 1,
rarity = 'legendary',
metadata = {
serial = 'ABC123',
durability = 100,
}
} }The UI is built with React and Vite. For development:
cd web
npm install
npm run devFor production build:
npm run buildWhen config.debug = true:
| Command | Description |
|---|---|
/lootbox_test [caseName] |
Test open a case without removing the item |
/lootbox_preview [caseName] |
Preview a case's contents (client) |
/lootbox_list |
List all registered lootboxes |
/lootbox_test_ui |
Test the UI with dummy data (client) |
/lootbox_test_preview |
Test the preview UI with dummy data (client) |
When using ox_core, usable items are handled through ox_inventory item definitions rather than the framework itself. For each lootbox item you want players to be able to use, you need to add a server.export entry in your ox_inventory item definitions pointing to this resource:
-- ox_inventory/data/items.lua
["gun_case"] = {
label = 'Gun Case',
weight = 500,
server = {
export = "sleepless_lootbox.gun_case"
}
},The export name must match the format sleepless_lootbox.<item_name>, where <item_name> is the lootbox name defined in your config or registered via exports. This is handled automatically by the bridge when config.registerUsableItems is enabled β you just need to make sure the item definitions include the server.export.
π Reference: See
_items.luafor a complete set of example ox_inventory item definitions covering all default lootbox cases and their contents. This file is not loaded at runtime β it's purely for reference. Copy the relevant entries into yourox_inventory/data/items.lua.
When using QBCore lootbox case items must be defined in your shared items (qb-core/shared/items.lua) with useable = true so that the framework can register them as usable items:
-- qb-core/shared/items.lua
["gun_case"] = {
name = "gun_case",
label = "Gun Case",
weight = 500,
type = "item",
image = "gun_case.png",
unique = false,
useable = true,
shouldClose = true,
combinable = nil,
description = "Contains various firearms",
},The useable = true flag is what allows QBCore.Functions.CreateUseableItem to register the callback. When config.registerUsableItems is enabled, the resource handles this automatically β you just need to make sure the item definitions exist with useable = true.
π Reference: See
_items.luafor a complete set of example qb-core shared item definitions covering all default lootbox cases and their contents. This file is not loaded at runtime β it's purely for reference. Copy the relevant entries into yourqb-core/shared/items.lua.
For full documentation, visit: https://sleeplessdevelopment.dev/lootbox
MIT License - See LICENSE for details.