ItemDrop

Status
Not open for further replies.
Level 19
Joined
Mar 18, 2012
Messages
1,716
I quickly wrote it this evening for own usage.
I think it turned out quite nice. maybeit's useful for others.

The first is a wrapper around the itempool handle.

The second is a item drop snippet. You create a drop list for an id ( unit id or any integer you wish )
Then you add ItemPools to it with a drop chance and a max drop.

Drop items whenever you want or automatically on unit death event.
See yourself.

JASS:
library ItemPool uses Table 

//===================================================================================
// Wrapper around the itempool handle. Use in combination with library ItemDrop.
//===================================================================================
      
    struct ItemPool 
        // implement Alloc; Place your allow module here.

        // For removing an id from all pools.
        private static thistype array next
        private static thistype array prev
        
        // Read and use PlaceRandomItem()
        readonly itempool pool
        //
        private Table   items// Tracks added items. I don't know how safe itempool handles are.
        private integer itemCounter
        private integer references
        
        // Returns the weight for an item id in this pool.
        // For setting a new weight simply use this.addItemId(id, newWeight).
        method getItemIdWeight takes integer itemId returns real
            return items.real[itemId]
        endmethod
        
        method operator empty takes nothing returns boolean
            return (itemCounter == 0)
        endmethod
        
        method removeItemId takes integer itemId returns nothing
            debug call ThrowWarning((pool == null), "ItemPool", "removeItemId", "pool", this, "Invalid itempool ( null )!")
            //
            if items.has(itemId) then
                call ItemPoolRemoveItemType(pool, itemId)
                call items.remove(itemId)
                set itemCounter = itemCounter - 1
            endif
        endmethod
        
        // Runs for all pools.
        static method removeItemIdFromAll takes integer itemId returns nothing
            local thistype this = next[0]
            loop
                exitwhen (0 == this)  
                call removeItemId(itemId)
                set this = next[this]
            endloop
        endmethod
        
        // Please don't add invalid item ids.
        method addItemId takes integer itemId, real weight returns nothing
            debug call ThrowError((pool == null),  "ItemPool", "addItemId", "pool",   this, "Instance not allocated!")
            debug call ThrowWarning((weight <= 0), "ItemPool", "addItemId", "weight", this, "Invalid weight ( <= 0 )!")
            
            if items.has(itemId) then
                call removeItemId(itemId)
            endif
            
            call ItemPoolAddItemType(pool, itemId, weight)
            set items.real[itemId] = weight
            set itemCounter = itemCounter + 1
        endmethod
        
        method destroy takes nothing returns nothing
            debug local string msg = "Can't destroy pool. It's still locked to " + I2S(references) + " object ids"
            //
            if (0 == references) then
                call deallocate()
                call items.destroy()
                call DestroyItemPool(pool)
                set pool = null
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
            endif
            //
            debug call ThrowWarning((references != 0), "ItemPool", "destroy", "references", this, msg)
        endmethod
        
        method lock takes nothing returns thistype
            set references = references + 1
            return this
        endmethod
        
        method unlock takes nothing returns nothing
            set references = references - 1
        endmethod

        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            // Add to list.
            set next[this] = 0
            set prev[this] = prev[0]
            set next[prev[0]] = this
            set prev[0] = this
            // Set members.
            set itemCounter = 0
            set references  = 0
            set items       = Table.create()
            set pool        = CreateItemPool()
            return this
        endmethod
        
    endstruct
    
    function RemoveItemIdFromAllPools takes integer itemid returns nothing
        call ItemPool.removeItemIdFromAll(itemid)
    endfunction
    
endlibrary

JASS:
library ItemDrop initializer Init uses RegisterPlayerUnitEvent, ItemPool, List
//**
//*  Settings:
//*  =========
    // Runs always when an item drop takes place.
    private function DropItem takes item justDropped, widget fromWidget returns nothing
     
    endfunction

// API:
//
//      static method allocate takes integer id returns ItemPool
//          - New pool for this id. ( unit id, destructable id, 1, 2, ... )
//
//      method deallocate takes integer nothing returns nothing
//          - Destroy the instance.
//
//      static method operator [] takes integer id returns thistype
//          - Get the instance reference on the id
//
//      method addItemPool takes ItemPool pool, integer maxDrop, real dropChance returns thistype
//          - add an ItemPool to your id. Set drop parameters for this pool. 
//          - returns thistype for addItemPool().addItemPool syntax.
//
//      method dropItems takes real x, real y, widget from returns nothing
//          - Run this when you wish to drop items from this ItemDrop instance.
//          - Runs for all added itempools.
//          - You can define a widget which drops the items. "null" is also ok.
    
//===================================================================================
// ItemPool code. Make changes carefully.
//===================================================================================
    
    globals
        private Table table
    endglobals
    
    // For the table.
    private module inits 
        private static method onInit takes nothing returns nothing
            call thistype.init()
        endmethod
    endmodule
    //
    struct ItemDrop extends array
        implement List
        
        private ItemPool pool
        private integer  maxItems
        private real     chance// 1. - 100.
        
        static method operator [] takes integer id returns thistype
            return table[id]
        endmethod
        
        // Loop through all ItemPool instances queued to this list.
        method dropItems takes real x, real y, widget from returns nothing
            local thistype node = this.first
            local integer amount 
            
            loop
                exitwhen (0 == node)
                set amount = node.maxItems
                loop
                    exitwhen (0 == amount) or (node.pool.empty)
                    //
                    if (node.chance >= GetRandomReal(1., 100.)) then  
                        call DropItem(PlaceRandomItem(node.pool.pool, x, y), from)
                    endif
                    
                    set amount = amount - 1
                endloop
                set node = node.next
            endloop
        endmethod

        // static deallocate ....
        static method deallocate takes integer id returns nothing
            local thistype this = table[id]
            local thistype node = first
            loop
                exitwhen (0 == node)
                call node.pool.unlock()
                set node = node.next
            endloop
            call table.remove(id)
            call destroy()
        endmethod
        
        // Add a itempools to this instance
        method addItemPool takes ItemPool pool, integer maxDrop, real dropChance returns thistype
            local thistype node = enqueue()
            //
            set node.pool     = pool.lock()
            set node.chance   = dropChance
            set node.maxItems = maxDrop
            //
            debug call ThrowError((maxDrop <= 0),      "ItemDrop", "addItemPool", "node.maxItems", this, "Invalid max item drop ( <= 0 )!")
            debug call ThrowError((pool.pool == null), "ItemDrop", "addItemPool", "index.pool",    this, "Invalid itempool ( null )!")
            return this
        endmethod
       
        // Can be any reference key. Type ids, handle ids fictional values.
        static method allocate takes integer id returns thistype
            local thistype this = thistype.create()
            //
            debug call ThrowError(table.has(id), "ItemDrop", "allocate", "id", id, "Table already has saved integer " + GetObjectName(id))
            //
            set table[id] = this
            return this
        endmethod

        private static method init takes nothing returns nothing
            set table = Table.create()
        endmethod
        implement inits
    endstruct
    
//========================================================================
// Event handler.
//======================================================================== 
    
    private function OnUnitDeath takes nothing returns nothing
        local unit source = GetTriggerUnit()
        //
        // Run item drop for any unit of this type id.
        local ItemDrop this = table[GetUnitTypeId(source)]
        if (0 != this) then
            call this.dropItems(GetUnitX(source), GetUnitY(source), source)
        endif
        //
        // Runs specific for this handle id.
        set this = table[GetHandleId(source)]
        if (0 != this) then
            call this.dropItems(GetUnitX(source), GetUnitY(source), source)
            call ItemDrop.deallocate(GetHandleId(source))
        endif
    endfunction
    
    private function Init takes nothing returns nothing
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnUnitDeath)
    endfunction
    
endlibrary
 
Last edited:
Would you mind giving an example of how to use this?

Say I want to drop 2 set of items:

JASS:
set 1:
    'phea' with chance 34%
    'pman' with cance 56%
    // 10% chance to drop nothing from set 1

set 2:
    'afac' with chance 60%
    'ajen' with chance 40%

The sets are independent, i.e getting or not getting an item from the 1st set does not influence the outcome of the 2nd.
 
Of course. Here is a simple example:

JASS:
private function Init takes nothing returns nothing
    local ItemPool myPool  = ItemPool.create()
    local ItemPool myPool2 = ItemPool.create()
    local ItemDrop myDrop  
    //
    // Add items of your choice. They can have a weight.
    call myPool.addItemId('I000',  5.)
    call myPool.addItemId('I001'  10.)
    call myPool.addItemId('I002', 20.)
    //
    call myPool2.addItemId('I003', 1.)
    //
    // Register the pool to a unit id.  
    // It's not restricted to unit ids but it's a good example to use them.
    set myDrop = ItemDrop.allocate('hfoo')
    call myDrop.addItemPool(myPool, 2, 15.)// Footman can drop 0 - 2 items from myPool with a chance per drop of 15%.
    call myDrop.addItemPool(myPool2, 1, 5.)// Also 0 - 1 item from my pool2 with a chance of 5%
                                           // Ergo he can drop all together 0 - 3 items.
                                           
    set myDrop = ItemDrop.allocate('Hpal')
    call myDrop.addItemPool(myPool, 3, 80.)// Paladin also uses "myPool", but for him the max item count is 3
                                           // and the chance for dropping is 80% per item.
                                           
    call myDrop.addItemPool(myPool, 1, 5.)// Also valid to add a pool multiple times.
                                          // Runs this pool again, but just 1 item and 5% chance.
                                          
    set myDrop = ItemDrop['hfoo']// Returns the allocated instance for the Footman.
    call myDrop.dropItems(x, y, widget)// Runs the item drop. The widget is just an extra parameter,
                                       // with no influence on the mechanics of the drop system.
                                       
    // ItemDrops run automatically on UNIT_DEATH_EVENT.
endfunction

here is the code of dropItems. Function DropItem is just an event catcher for you needs.

JASS:
//**
//*  Settings:
//*  =========
    // Runs always when an item drop takes place.
    private function DropItem takes item justDropped, widget fromWidget returns nothing
     
    endfunction

        // Loop through all ItemPool instances queued to this list.
        method dropItems takes real x, real y, widget from returns nothing
            local thistype node = this.first
            local integer amount 
            
            loop
                exitwhen (0 == node)
                set amount = node.maxItems
                loop
                    exitwhen (0 == amount) or (node.pool.empty)
                    //
                    if (node.chance >= GetRandomReal(1., 100.)) then  
                        call DropItem(PlaceRandomItem(node.pool.pool, x, y), from)
                    endif
                    
                    set amount = amount - 1
                endloop
                set node = node.next
            endloop
        endmethod
 
Hm, okay... but what would the example I gave look like, I mean the exact weight values:

JASS:
myPool.addItemId('hpea',  ???) // what is a weight of 34%?

It seems very unintuitive that an ItemPool has a drop chance (ItemDrop.addItemPool(..., <chance>) and then if it that happens then the native itempool through some weight to chance conversion has a chance to drop the actual item.

So what's the actual chance that an item (say 'phea') can drop?? That chance value seems implicit, hard to calculate and again unintuitive.
 
So I guess if I would have to answer my question it would look like something like this?:

JASS:
local ItemPool item_set_a  = ItemPool.create()
local ItemPool item_set_b = ItemPool.create()
local DropItem item_sets = ItemDrop.allocate('foo!')

call item_set_a.addItemId('phea', 34)
call item_set_a.addItemId('pman', 56)
call item_set_a.addItemId(-1, 10)
//  34 + 56 + 10 = 100

call item_set_b.addItemId('afac', 60)
call item_set_b.addItemId('ajen', 40)

call item_sets.addItemPool(item_set_a, 1, 100) // this should be the default?
call item_sets.addItemPool(item_set_b, 1, 100)

call item_sets.dropItems(0, 0, null)

call item_set_a.destroy()
call item_set_b.destroy()
call item_sets.destroy()
 
Status
Not open for further replies.
Back
Top