ktoxktox docs
ktx_cases

Cases

A case, its reward pool, and the five kinds of reward.

Config.Cases = {
    {
        key = "starter_case",
        name = "Starter Case",
        description = "A small case with everyday rewards.",
        item = "starter_case",
        icon = "case_1.svg",
        disabled = false,
        rewards = { ... },
    },
}

key is used internally and in the log, item is the inventory item that opens it. They are the same word in the download and there is no reason to make them differ. icon is a file in icons/cases/.

A reward

{
    name = "Small Cash",
    icon = "",
    chance = 32,
    rarity = "common",
    type = "money",
    amount = 500,
},

chance is a percentage and the chances in a case are meant to add up to 100. They do not have to: the draw runs over whatever they add up to, and a case that misses 100 prints a warning on start naming itself.

The five types

typeNeeds
moneyamount
itemitem, amount
weaponweapon, ammo
vehiclemodel
coinsamount

ammo is read by ESX, QBox and ox. QB-Core puts the weapon into the inventory with a count of one and does nothing with it.

A vehicle reward is written into your vehicle table, not spawned into the street. Config.Framework.vehicleGarage is the garage it is parked in, spelled the way your framework spells its garages. ESX ignores it, it has no garage column.

Rarity

Config.Rarities = {
    common = { color = "#B0BEC5", name = "Common" },
    rare   = { color = "#2196F3", name = "Rare" },
}

Rarity is the colour and the word on the card. It does not touch the odds, those stand on the reward itself. A legendary reward at 40 percent will drop four times in ten.

Coins

Coins are not a framework concept, so they need a resource of yours. Fill in whichever of the two ways reaches it:

Config.RewardTypes = {
    coins = {
        enabled = false,
        export = { resource = "myCoins", name = "addCoins" },
        event = "myCoins:add",
    },
}

Both are called with the player id and the amount. Fill in both and the event is the one that is called, the export is left alone.

There is a third way, and only on Config.Framework.name = "custom": a giveCoins() in public/framework.lua is called before the event and the export. That file is read on the "custom" setting and on no other, so on any other name a giveCoins() written there never runs.