• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

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:
Level 13
Joined
Nov 7, 2014
Messages
571
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.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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
 
Level 13
Joined
Nov 7, 2014
Messages
571
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.
 
Level 13
Joined
Nov 7, 2014
Messages
571
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.
Top