• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Loot System Request

Status
Not open for further replies.
Level 8
Joined
Feb 17, 2007
Messages
368
Can anyone make me a system where after the boss is killed X random items from a specified item list appear in a shop. I would like to have a percentage for the rate at which each item is dropped and have a check in place which prevents duplicate items from appearing in the shop and then immediately remove the item from the loot list after it appears in the shop.
 
Level 6
Joined
May 13, 2009
Messages
260
EDIT: Fixed!

I uploaded a test map for you to see the system in action. Hope you like it and don't be scared to ask question I'll try to answer as many as I can :)
JASS:
//Scope is used to have global variables only usable by the functions in the scope
//Initalizer is used to state which function is run on map init 
scope BossDrop initializer InitBossDrop

    globals
        //Go to object manager and under the menu "view" set "display raw data" to true it will then show all units unitId, change this variable to the boss id
        constant integer BOSSID = 'hpea'

        //This constant changes how many random drops the unit is going to create 
        constant integer NDROPS = 5

        //The id of the shop, sadly this code only works if you have just one of these shops. Otherwise it will add it's boss drops to all of those shops
        constant integer SHOPID = 'nmrk'
    endglobals

    //Our boolexpr leak fix
    function ReturnNull takes nothing returns boolexpr
        return null
    endfunction

    //Get shop
    function GroupFilter takes nothing returns boolean
        return GetUnitTypeId(GetFilterUnit()) == SHOPID
    endfunction

    function GetShop takes nothing returns unit
        //Create a group which will contain the shop (units from neatural passive)
        local group neutralPassiveGroup = CreateGroup()

        //Get entire map
        local rect worldBounds = GetWorldBounds()

        //Get all units on the map that has the unit type SHOPID
        call GroupEnumUnitsInRect(neutralPassiveGroup, worldBounds, function GroupFilter)

        //Clear memory leaks
        call RemoveRect(worldBounds)

        //Return the first unit
        return FirstOfGroup(neutralPassiveGroup)
    endfunction

    function RandomBossDrops takes nothing returns nothing
        local unit boss = GetTriggerUnit()
        local unit shop = GetShop()
        local integer i = 0
    
        //Create an "itempool", it holds items and can specify rarity of the items
        local itempool ip = CreateItemPool()

        //This will be our temporary random item
        local item itemDrop

        //We add item to the itempool with "weight" 1. The more weight it has the higher chance of drop
        //Formula for drop chance: divide the individual items wieght with the sum of all items weight.
        //'wild's chance to drop is 1/7 since 1 / (1 + 1 + 1 + 1 + 1 + 1 + 1) = 1/7 which is 14.28%
        call ItemPoolAddItemType(ip, 'wild', 1)
        call ItemPoolAddItemType(ip, 'ankh', 1)
        call ItemPoolAddItemType(ip, 'fgsk', 1)
        call ItemPoolAddItemType(ip, 'whwd', 1)
        call ItemPoolAddItemType(ip, 'hlst', 1)
        call ItemPoolAddItemType(ip, 'infs', 1)
        call ItemPoolAddItemType(ip, 'pdiv', 1)
        
        for i = 1 to NDROPS

            //Get item from itempool
            set itemDrop = PlaceRandomItem(ip, 0, 0)

            //Add item to shops stock. First one is current stock, second one is max stock
            call AddItemToStock(shop, GetItemTypeId(itemDrop), 1, 1)

            //This will prevent the item from being randomed again, but this will increase the chance of the other items
            call ItemPoolRemoveItemType(ip, GetItemTypeId(itemDrop))
        endfor

        call DestroyItemPool(ip)

        set ip = null
        set boss = null
        set shop = null
        set itemDrop = null
    endfunction

    //Checks if the dying unit is the boss
    function IsUnitBoss takes nothing returns boolean
        if GetUnitTypeId(GetTriggerUnit()) == BOSSID then
            return true
        endif
        return false
    endfunction

    function InitBossDrop takes nothing returns nothing

        //Creates a local trigger which we are going to add action functions and condition functions 
        local trigger dropTrg = CreateTrigger()

        //This connects the trigger to an event
        //Player(11) is Player 12 Brown, since computers start counting with 0
        //returnNull() is because null'ed bool expressions leak, so we use a function that can be reused to return null
        call TriggerRegisterPlayerUnitEvent(dropTrg, Player(11), EVENT_PLAYER_UNIT_DEATH, ReturnNull())

        //Every time the event happens it checks the condition function if it's true
        //Our condition is if the dying unit (or TriggerUnit ) is the boss
        call TriggerAddCondition(dropTrg, Condition(function IsUnitBoss))

        call TriggerAddAction(dropTrg, function RandomBossDrops)
    endfunction

endscope
 

Attachments

  • test.w3x
    19.4 KB · Views: 49
Last edited:
Level 8
Joined
Feb 17, 2007
Messages
368
Wow it works perfectly man, you really did a great job on it! You have no idea how much I appreciate this. :) One thing though, is there a way to add categories to this? Like, if it were to drop 4 items, it would only drop 2 items classified as "weapons", 1 as "armor, and 1 as "charms". Oh also how can I change the boss ID to another boss when a boss dies?
 
Level 6
Joined
May 13, 2009
Messages
260
Wow it works perfectly man, you really did a great job on it! You have no idea how much I appreciate this. :)
Thanks, glad you like it ^^

Oh also how can I change the boss ID to another boss when a boss dies?
Do you want to be able to kill the bosses in any order or linearly?

One thing though, is there a way to add categories to this? Like, if it were to drop 4 items, it would only drop 2 items classified as "weapons", 1 as "armor, and 1 as "charms".
Yeah, I'll write a fix :)
 
Level 8
Joined
Feb 17, 2007
Messages
368
Linearly, but it would be cool if I could just specify any boss because I may give players a choice as to which boss to fight after the first couple bosses. If that's too hard or too much work then linearly is fine :) thank you for what you've done so far.
 
Level 6
Joined
May 13, 2009
Messages
260
The system got a little bit longer, but now categories work and you can have multiple bosses in any order. Be sure to read the comments the most is explained there.
In the map I upload, the script will work with riflemen, footmen and knights because they are the "bosses".

Feel free to ask questions and I'll answer as many as I can :)
JASS:
//Scope is used to have global variables only usable by the functions in the scope
//Initalizer is used to state which function is run on map init 
scope BossDrop initializer InitBossDrop

    //Create a struct which basically are a variables with many values that can use special functions
    struct ItemCategory
    
        //Max drops of this category
        integer MaxDrops

        //Current number of drops in this category
        integer CurDrops
        
        //Number of items added to this struct
        integer numberOfItems = 0
        
        //An array of itemids and their different rarities
        //Set these to the number of items you want in a categories
        //If you have 5 rings and 15 armors set this to 15
        integer array ItemIds[10]
        integer array Rarities[10]
        
        //Method to initiate the struct which sets MaxDrops for that category
        static method New takes integer MaxDrops returns ItemCategory
            local ItemCategory this = ItemCategory.allocate()
        
            set this.MaxDrops = MaxDrops
            
            return this
        endmethod

        //Add a new itemid and rarity to this struct
        method AddItem takes integer itemId, integer rarity returns nothing
            set this.ItemIds[this.numberOfItems] = itemId
            set this.Rarities[this.numberOfItems] = rarity
            set this.numberOfItems = this.numberOfItems + 1
        endmethod
        
        //Method to check if an itemid is in this category
        method IsInCategory takes integer itemId returns boolean
            local integer i = 0
            
            //Loop through itemids and compare them to our supplied itemid
            for i = 0 to ItemIds.size-1
                if ItemIds[i] == itemId then
                    return true
                endif
            endfor
            
            return false
        endmethod
        
        //Returns the index in itemids where supplied itemid is
        method GetItemIndex takes integer itemId returns integer
            local integer i = 0
            
            for i = 0 to ItemIds.size -1
                if ItemIds[i] == itemId then
                    return i
                endif
            endfor
            
            return -1
        endmethod
    endstruct

    globals
        //Go to object manager and under the menu "view" set "display raw data" to true it will then show all units unitId
        //In the init function add all ids that will be able to trigger this drop function
        integer array BossIds[4]

        //The id of the shop, sadly this code only works if you have just one of these shops. Otherwise it will add it's boss drops to all of those shops
        constant integer SHOPID = 'nmrk'
        
        //Change these to the number of weapons that you set down in init
        ItemCategory array ItemCategories[2]
        
        //Makes indexes in ItemCategories more readable
        constant integer WEAPONINDEX = 0
        constant integer RINGINDEX = 1
        
    endglobals

    //Our boolexpr leak fix
    function ReturnNull takes nothing returns boolexpr
        return null
    endfunction

    //Get shop
    function GroupFilter takes nothing returns boolean
        return GetUnitTypeId(GetFilterUnit()) == SHOPID
    endfunction

    function GetShop takes nothing returns unit
        //Create a group which will contain the shop (units from neatural passive)
        local group neutralPassiveGroup = CreateGroup()

        //Get entire map
        local rect worldBounds = GetWorldBounds()

        //Get all units on the map that has the unit type SHOPID
        call GroupEnumUnitsInRect(neutralPassiveGroup, worldBounds, function GroupFilter)

        //Clear memory leaks
        call RemoveRect(worldBounds)

        //Return the first unit
        return FirstOfGroup(neutralPassiveGroup)
    endfunction

    function RandomBossDrops takes nothing returns nothing
        local unit boss = GetTriggerUnit()
        local unit shop = GetShop()
        local integer i = 0
        local integer j = 0
        
        //Create an "itempool", it holds items and can specify rarity of the items
        local itempool ip = CreateItemPool()

        //This will be our temporary random item
        local item itemDrop
        
        //Loop through itemcategories and add them to itempool
        for i = 0 to ItemCategories.size-1
            //Loop through each itemcategories itemids
            for j = 0 to ItemCategories[i].ItemIds.size-1
                //If itemid is initiated (0 is the standard value for empty elements on integer)
                if ItemCategories[i].ItemIds[j] != 0 then
                    //Add item to itempool with specified rarity
                    call ItemPoolAddItemType(ip, ItemCategories[i].ItemIds[j], ItemCategories[i].Rarities[j])
                endif
            endfor
        endfor
        
        loop
            //Get item from itempool
            set itemDrop = PlaceRandomItem(ip, 0, 0)
                
            //Loop through categories
            for i = 0 to ItemCategories.size-1
                
                //If Item is in category and current drops of category isn't max amount of drops
                if ItemCategories[i].IsInCategory(GetItemTypeId(itemDrop)) and ItemCategories[i].CurDrops < ItemCategories[i].MaxDrops then
                    //Increase current drops of category
                    set ItemCategories[i].CurDrops = ItemCategories[i].CurDrops + 1
                    
                    //Set j to index of itemDrop to get rarity
                    set j = ItemCategories[i].GetItemIndex(GetItemTypeId(itemDrop))
                    
                    //Add item to shops stock. First one is current stock, second one is max stock
                    call AddItemToStock(shop, GetItemTypeId(itemDrop), 1, ItemCategories[i].Rarities[j])
                            
                    //This will prevent the item from being randomed again, but this will increase the chance of the other items
                    call ItemPoolRemoveItemType(ip, GetItemTypeId(itemDrop))
                endif
            endfor
            
            //Exit when current drops equals max drops
            exitwhen ((ItemCategories[WEAPONINDEX].CurDrops + ItemCategories[RINGINDEX].CurDrops) == (ItemCategories[WEAPONINDEX].MaxDrops + ItemCategories[RINGINDEX].MaxDrops))

        endloop

        call DestroyItemPool(ip)

        set ip = null
        set boss = null
        set shop = null
        set itemDrop = null
    endfunction

    //Checks if the dying unit is the boss
    function IsUnitBoss takes nothing returns boolean
        local integer i

        for i = 0 to BossIds.size-1
            if GetUnitTypeId(GetTriggerUnit()) == BossIds[i] then
                return true
            endif
        endfor
        
        return false
    endfunction

    function InitBossDrop takes nothing returns nothing

        //Creates a local trigger which we are going to add action functions and condition functions 
        local trigger dropTrg = CreateTrigger()
        local integer i = 0
        
        //This connects the trigger to an event
        //Player(11) is Player 12 Brown, since computers start counting with 0
        //returnNull() is because null'ed bool expressions leak, so we use a function that can be reused to return null
        call TriggerRegisterPlayerUnitEvent(dropTrg, Player(11), EVENT_PLAYER_UNIT_DEATH, ReturnNull())

        //Every time the event happens it checks the condition function if it's true
        //Our condition is if the dying unit (or TriggerUnit ) is the boss
        call TriggerAddCondition(dropTrg, Condition(function IsUnitBoss))

        call TriggerAddAction(dropTrg, function RandomBossDrops)
        
        //Different bosses that will trigger the Random boss drops
        set BossIds[0] = 'hpea'
        set BossIds[1] = 'hfoo'
        set BossIds[2] = 'hkni'
        set BossIds[3] = 'hrif'
        
        //Set max number of drops for each category
        set ItemCategories[WEAPONINDEX] = ItemCategory.New(2)
        set ItemCategories[RINGINDEX] = ItemCategory.New(1)
        
        //Add items to different categories
        call ItemCategories[WEAPONINDEX].AddItem('ratc', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rat6', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rat9', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rst1', 1)

        call ItemCategories[RINGINDEX].AddItem('rde1', 1)
        call ItemCategories[RINGINDEX].AddItem('rde2', 1)
        call ItemCategories[RINGINDEX].AddItem('rde3', 1)
        call ItemCategories[RINGINDEX].AddItem('brac', 1)
    endfunction

endscope
 

Attachments

  • test.w3x
    22.9 KB · Views: 51
Level 8
Joined
Feb 17, 2007
Messages
368
Awesome! :) ok so I looked at it, several questions if you don't mind...

JASS:
//An array of itemids and their different rarities
        //Set these to the number of items you want in a categories
        //If you have 5 rings and 15 armors set this to 15
        integer array ItemIds[10]
        integer array Rarities[10]

For this part, I'm not quite sure what you mean. Why would I only specify the armor amount and not the ring? Not sure exactly what this part does in other words :p

And to add another category, are these the correct parts of the script I would have to add the categories to?

JASS:
//Change these to the number of weapons that you set down in init
        ItemCategory array ItemCategories[2]

JASS:
        //Makes indexes in ItemCategories more readable
        constant integer WEAPONINDEX = 0
        constant integer RINGINDEX = 1

JASS:
//Exit when current drops equals max drops
            exitwhen ((ItemCategories[WEAPONINDEX].CurDrops + ItemCategories[RINGINDEX].CurDrops) == (ItemCategories[WEAPONINDEX].MaxDrops + ItemCategories[RINGINDEX].MaxDrops))

JASS:
//Set max number of drops for each category
        set ItemCategories[WEAPONINDEX] = ItemCategory.New(2)
        set ItemCategories[RINGINDEX] = ItemCategory.New(1)

JASS:
 //Add items to different categories
        call ItemCategories[WEAPONINDEX].AddItem('ratc', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rat6', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rat9', 1)
        call ItemCategories[WEAPONINDEX].AddItem('rst1', 1)

This part above is the part of the script that determines the % chance of the items dropping right? With the 1 at the end? You had that in the earlier description of this part, just want to make sure it's the same. :p
 
Last edited:
Level 6
Joined
May 13, 2009
Messages
260
JASS:
//An array of itemids and their different rarities
        //Set these to the number of items you want in a categories
        //If you have 5 rings and 15 armors set this to 15
        integer array ItemIds[10]
        integer array Rarities[10]

For this part, I'm not quite sure what you mean. Why would I only specify the armor amount and not the ring? Not sure exactly what this part does in other words :p
Since the function is going to loop through the categories you need to set the index to the largest categorys number of items. Because it's specified for all categories and not a single category :)

And to add another category, are these the correct parts of the script I would have to add the categories to?
Yes that's all.


JASS:
//Change these to the number of weapons that you set down in init
         ItemCategory array ItemCategories[2]
This comment should be the "number of categories" not number of weapons :)
 
Status
Not open for further replies.
Top