Item Drop System

edited March 2016 in Tutorials

Here I'll go over the implementation of a flexible item drop system for any sort of gamemode, mostly useful for RPGs.

There are multiple ways to do this, for example Warchasers uses a pure datadriven system that goes over 2 thousand lines of abilities, each one for a different drop type... yeah you don't want to do that :sweat_smile:

The best way for this is to have a text file to configure what items can drop from each unit, how many, its chances, etc, then whenever a unit dies, if it has an entry for item drops, handle the chances and drops accordingly, with a couple of choices that can be further extended if necessary.


1. Key Values Table

I recommend having a kv folder under scripts to store this and other similar table files. The file can have any extension, but using .kv is a good convention.

"Drops" 
{ 
    "creature_name1"
    { 
        "item_name1" "10"
        "item_name2" "50"
        "item_name3" "100"
    }
}

This table will set a creature to drop the first item with 10% chance, 50% on the second, and the third item will be dropped every time.

After saving and naming the file, this table has to be loaded in Lua, ideally in the initialization of the game mode, using the LoadKeyValues("relative/path/to/file") this way:

GameRules.DropTable = LoadKeyValues("scripts/kv/item_drops.kv")

In this initial version, each item drop chance is independent from the others. From the same creature there might be 1 drop, all of them, or none (if the chances are all less than 100). This behavior will be expanded later to provide some of the classic drop options.

2. OnEntityKilled Lua Event

Simply listen to entity_killed and call a custom RollDrops function with the killed unit as a parameter.

ListenToGameEvent('entity_killed', Dynamic_Wrap(GameMode, 'OnEntityKilled'), self)
function GameMode:OnEntityKilled( keys )
    local killedUnit = EntIndexToHScript( keys.entindex_killed )
    if killedUnit:IsCreature() then
        RollDrops(killedUnit)
    end
end

3. RollDrops Lua Script

Now given the subtable of the unit name contained in the main Drop Table, if it exists, iterate over the elements rolling each chance value.

If the Roll succeeds, proceed to create an item handle with the name, and LaunchLoot it with some fancy parameters (could also just use a CreateItemOnPositionSync to drop the item instantly at the death position)

function RollDrops(unit)
    local DropInfo = GameRules.DropTable[unit:GetUnitName()]
    if DropInfo then
        for item_name,chance in pairs(DropInfo) do
            if RollPercentage(chance) then
                -- Create the item
                local item = CreateItem(item_name, nil, nil)
                local pos = unit:GetAbsOrigin()
                local drop = CreateItemOnPositionSync( pos, item )
                local pos_launch = pos+RandomVector(RandomFloat(150,200))
                item:LaunchLoot(false, 200, 0.75, pos_launch)
            end
        end
    end
end

4. Extending the solution to allow multiple drops of the same item

The way Lua KV tables work, it's not possible to have more than 1 of the same index, so if we were to add 2 "item_name1" entries both with some chance value, LoadKeyValues would fail.

To get around this, the table has to use another level and have each possible item drop of the unit be a table by itself:

"Drops" 
{ 
    "creature_name1"
    { 
        "1"
        {
            "Item"     "item_name1"
            "Chance"   "10"
            "Multiple" "3"
        }
        "2"
        {
            "Item"     "item_name2"
            "Chance"   "50"
            "Multiple" "1"
        }
    }
}

This structure along with the Multiple value will allow an item to be dropped more than once from the same creature. "Multiple" "1" will just be 1 drop max.

The RollDrops function needs to be adjusted to read the subtables and the Item/Chance in a slightly different way:

function RollDrops(unit)
    local DropInfo = GameRules.DropTable[unit:GetUnitName()]
    if DropInfo then
        for k,ItemTable in pairs(DropInfo) do
            local chance = ItemTable.Chance or 100
            local max_drops = ItemTable.Multiple or 1
            local item_name = ItemTable.Item
            for i=1,max_drops do
                if RollPercentage(chance) then
                    print("Creating "..item_name)
                    local item = CreateItem(item_name, nil, nil)
                    item:SetPurchaseTime(0)
                    local pos = unit:GetAbsOrigin()
                    local drop = CreateItemOnPositionSync( pos, item )
                    local pos_launch = pos+RandomVector(RandomFloat(150,200))
                    item:LaunchLoot(false, 200, 0.75, pos_launch)
                end
            end
        end
    end
end

The 'or 100' and 'or 1' are just to make sure that if the "Chance" or "Multiple" lines are missing, a default value ('drop always' and 'drop 1') will be used.

5. Extending to "100% drop one of these"

Sometimes doing "50% of item 1 and 50% of item 2" is too random, because it will mean sometimes a mob will drop nothing, and sometimes it might drop 2. In order to reduce the randomness and ensure a certain combination of items will drop, the most common approach is to have a set list of possible drops, and make it so that the unit will drop only one of that set at random.

To do this, instead of tying a single item to each item table, there will be yet another table of the { possible Set of items } that we want this creature to drop:

"Drops" 
{ 
    "creature_name1"
    { 
        "1"
        {
            "ItemSets"
            {
                "1" "item_name_set1"
                "2" "item_name_set2"
                "3" "item_name_set3"
            }
            "Chance"   "100" //of dropping 1 of the set
        }
        "2"
        {
            "Item"     "item_name2"
            "Chance"   "50"
            "Multiple" "3"
        }
    }
}

The ItemSets entry could also have a "Multiple" kv if we wanted an scenario like "2 of these 3", but this can't guarantee that the 2nd roll won't drop the same item than the first, if it did.

And the RollDrops now looks like this:

function RollDrops(unit)
    local DropInfo = GameRules.DropTable[unit:GetUnitName()]
    if DropInfo then
        print("Rolling Drops for "..unit:GetUnitName())
        for k,ItemTable in pairs(DropInfo) do
            -- If its an ItemSet entry, decide which item to drop
            local item_name
            if ItemTable.ItemSets then
                -- Count how many there are to choose from
                local count = 0
                for i,v in pairs(ItemTable.ItemSets) do
                    count = count+1
                end
                local random_i = RandomInt(1,count)
                item_name = ItemTable.ItemSets[tostring(random_i)]
            else
                item_name = ItemTable.Item
            end
            local chance = ItemTable.Chance or 100
            local max_drops = ItemTable.Multiple or 1
            for i=1,max_drops do
                print("Rolling chance "..chance)
                if RollPercentage(chance) then
                    print("Creating "..item_name)
                    local item = CreateItem(item_name, nil, nil)
                    item:SetPurchaseTime(0)
                    local pos = unit:GetAbsOrigin()
                    local drop = CreateItemOnPositionSync( pos, item )
                    local pos_launch = pos+RandomVector(RandomFloat(150,200))
                    item:LaunchLoot(false, 200, 0.75, pos_launch)
                end
            end
        end
    end
end

Example

item_drops.kv file at TBR

Leave any questions or suggestions below

The concept of Modding Community doesn't go well together with Competitive Business
My Project Page || My GitHub Profile ||

Comments

  • Posts: 74

    Dat call out (will look at it tomorrow)

    Working on The Black Road 3 - Persistent RPG

  • Posts: 59

    I tried doing this with the RollDrops at number 3, but it didn't work, so I changed it to this.

    function RollDrops(unit)  
        local DropInfo = GameRules.DropTable[unit:GetUnitName()]
        if DropInfo then
            for item_name,chance in pairs(DropInfo) do
                if RollPercentage(chance) then
                    -- Create the item
                    local item = CreateItem(item_name, nil, nil)
                    item:SetPurchaseTime(0)
                    local pos = unit:GetAbsOrigin()
                    local drop = CreateItemOnPositionSync( pos, item )
                    local pos_launch = pos+RandomVector(RandomFloat(150,200))
                    item:LaunchLoot(false, 200, 0.75, pos_launch)
                end
            end
        end
    end
    
  • edited June 2015 Posts: 1,670

    Correct it was missing the CreateItemOnPositionSync, edited it and the item drops KV link example from TBR3.

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • Posts: 184

    Dat LaunchLoot usage :happy:

    Treat everyday as if you are a student, not a master. The student learns, grows and sees beauty. The master becomes bitter, resentful, and stagnates.

  • edited June 2015 Posts: 1

    so i started looking at scripting and lua etc for the very first time yesterday. So for me it is very hard to understand, but im getting some of it. Now, the problem i have is basicly more spesificly how to order the coding in the lua files, and how to make the file interact with the game, if you understand me. Now, this my be supereasy for some of you that have done this for a long time, but for me the tiniest little tweek can be hard to figure out. Now i watched some tutorial, and i kinda know how to make customized spells and put them onto heroes, so i know some already. But ye, for a beginner i need some very specific pointers on how to structure things to make them function in game. any help would be very much appreciated :) Bottom line i do this cuz i want to make custom made RPG maps with custom heroes, custom spells, quest, custom item drops, different bosses, game events etc. So i just need to learn and understand the basics of scripting, KV files and lua i guess. good stuff here anyways, and if no help, im sure ill get a grip on it after some time :)

  • Posts: 3

    Vigre for structure tryhttps://github.com/bmddota/barebones. It will give you a good starting point as well as examples.