HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Unitpools Tutorial

11-06-2007, 08:52 AM#1
Pyrogasm
Unitpools, Itempools and Their Usage
by Pyrogasm


What are Unitpools good for?
A fair amount of maps nowadays try to achieve a randomized selection of heroes for each player, but they all usually have a common misconception, as is characterized by the following quote:
Quote:
Originally Posted by Fulla
Why not just add each Hero to a unit array.

Then when creating unit, create Hero[RandomNumber(1-Max Heroes)]
The error here is that it is very possible for the same hero to be picked twice. (Now, if this is something you want in your map then that method should work fine for you and you probably needn't continue reading this tutorial.)

Using unitpools, we can solve this problem quite simply with a single line of code, and we can also add a few more interesting aspects to the "randomness" of each hero. A fair amount of this, actually can be accomplished with regular unit groups and optionally some binary searching, but I find that unitgroups just make things so much easier.

Finally, unitpools (and itempools, which are nearly the same thing except with items) can be extended to be used in random rolls of dice in games that may use a D 'n D style attack, or by simply giving the randomness of other types of "rolls" new aspects.



What are the unitpool functions?
There are 5, and here they are:
Collapse JASS:
native CreateUnitPool           takes nothing returns unitpool
native DestroyUnitPool          takes unitpool whichPool returns nothing
native UnitPoolAddUnitType      takes unitpool whichPool, integer unitId, real weight returns nothing
native UnitPoolRemoveUnitType   takes unitpool whichPool, integer unitId returns nothing
native PlaceRandomUnit          takes unitpool whichPool, player forWhichPlayer, real x, real y, real facing returns unit



Unitpools in application
To use unitpools in a game, you'll first need to create the unitpool and add the appropriate types you need, this can be done in a function like so:
Collapse JASS:
globals
    unitpool RandomPool = CreateUnitPool() //Global so we can access it later
endglobals

InitPool takes nothing returns nothing
    call UnitPoolAddUnitType(RandomPool, 'H001', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'H0AC', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'E095', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'E001', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
    call UnitPoolAddUnitType(RandomPool, 'H22F', 1.00)
endfunction

So now that we've got it set up, let's set up a simple trigger that detects when a player types "Random" such that it will give that player one of the random heroes (let's assume that there are 6 players in the game):
Collapse JASS:
library RandomPools initializer InitPool
     globals
        unitpool RandomPool = CreateUnitPool() //Global so we can access it later
        trigger RandomDetect = CreateTrigger() //Global so we can destroy or disable it later
     endglobals

     private function RandomDetect takes nothing returns nothing
     endfunction

     private function InitPool takes nothing returns nothing
        local integer I = -1

        call UnitPoolAddUnitType(RandomPool, 'H001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H0AC', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E095', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H22F', 1.00)

        loop
            set I = I+1
            exitwhen I > 5
            call TriggerRegisterPlayerChatEvent(RandomDetect, Player(I), "Random", true)
        endloop
        call TriggerAddAction(RandomDetect, function RandomDetect)
     endfunction
endlibrary

Up until this point, all of this could have been accomplished by using the method of the misinformed or a regular unit group, but now things get interesting. To remove a chosen type, we don't have to do any sort of moving of indexes, reducing counters, etc.; we simply have to call two functions when the trigger fires:

Collapse JASS:
library RandomPools initializer InitPool
     globals
        unitpool RandomPool = CreateUnitPool() //Global so we can access it later
        trigger RandomDetect = CreateTrigger() //Global so we can destroy or disable it later
     endglobals

     private function RandomDetect takes nothing returns nothing
         local player P = GetTriggerPlayer()
         local integer StartInt = GetPlayerStartLocation(P)
         local unit U = PlaceRandomUnit(RandomPool, P, GetStartLocationX(StartInt), GetStartLocationY(StartInt), 0.00)

         call UnitPoolRemoveUnitType(RandomPool, GetUnitTypeId(U))
         call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Orc\\HealingWave\\HealingWaveTarget.mdl", U, "chest"))

         set P = null
         set U = null
     endfunction

     private function InitPool takes nothing returns nothing
        local integer I = -1

        call UnitPoolAddUnitType(RandomPool, 'H001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H0AC', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E095', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H22F', 1.00)

        loop
            set I = I+1
            exitwhen I > 5
            call TriggerRegisterPlayerChatEvent(RandomDetect, Player(I), "Random", true)
        endloop
        call TriggerAddAction(RandomDetect, function RandomDetect)
     endfunction
endlibrary


What other things can I do with unitpools?
Until now I haven't touched on the final argument in these functions: the "weight" given to each new unit in the pool. As far as I know, there is no limit on what number you can put as the weight, but keep in mind that the bigger the number the more often the unit will be picked.

For general convention, anything you want to be of "base" or "normal" weight should probably be given a weight of 1.00 or 100.00 for easy recognition. Then, a unit that you want to make more rare would be given a smaller weight value.

As far as I know, weight should scale linearly. Thus, 15.00:100.00 is the same as 0.15:1.00. So just keep in mind what sort of "base" you want to use when you set up the weighting.

That being said, here's an example using the above code. Say, for instance, you decided that you wanted to make an extra-rare 8th hero; you might change the above function to something like this:
Collapse JASS:
//...
        call UnitPoolAddUnitType(RandomPool, 'H001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H0AC', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E095', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E001', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'HA50', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'H22F', 1.00)
        call UnitPoolAddUnitType(RandomPool, 'E000', 0.15)
//...

One could also use this sort of thing for somewhat randomized sort of spawning in an arena, AoS, or Hero Defense map. For example having the small chance that a wave will spawn an elite champion, a priest, or maybe a few extra spellcasters.

I want to see someone attempt that with arrays of units instead of unitpools. Apparently this can also be done with binary searching and sorting arrays in order of most to least weight, though I don't really know how this would be done.




What are Itempools good for?
In general, itempools are very good for unit item-dropping in RPGs. They can easily be used to modify unit drop-tables on-the-fly and are useful for incorporating quest items into quests. With item pools you can turn a boring quest like "kill 12 lizards" into "bring me 10 shiny lizard scales".


What are the itempool functions?
They're nearly identical to the unitpool API:
Collapse JASS:
native CreateItemPool           takes nothing returns itempool
native DestroyItemPool          takes itempool whichItemPool returns nothing
native ItemPoolAddItemType      takes itempool whichItemPool, integer itemId, real weight returns nothing
native ItemPoolRemoveItemType   takes itempool whichItemPool, integer itemId returns nothing
native PlaceRandomItem          takes itempool whichItemPool, real x, real y returns item


Things to do with itempools
One could setup a simple on-death item-drop library like so:
Collapse JASS:
library ItemDrop initializer Init
    globals
        public itempool DropPool = CreateItemPool()
        public trigger DropPoolTrig = CreateTrigger()
    endglobals    

    private function Conditions takes nothing returns boolean
        return GetPlayerId(GetOwningPlayer(GetTriggerUnit())) > 11 //For neutral players only
    endfunction

    private function OnDeath takes nothing returns nothing
        local unit U = GetTriggerUnit()
        if not(IsUnitAlly(U, GetOwningPlayer(GetKillingUnit()))) then
            call PlaceRandomItem(DropPool, GetUnitX(U), GetUnitY(U))
        endif
        set U = null
    endfunction

    private function Init takes nothing returns nothing
        call TriggerRegisterAnyUnitEventBJ(DropPoolTrig, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(DropPoolTrig, Condition(function Conditions)
        call TriggerAddAction(DropPoolTrig, function OnDeath)

        call ItemPoolAddItemType(DropPool, 'I004', 1.00) //Just some random item
        call ItemPoolAddItemType(DropPool, 'I000', 1.25) //Possibly a potion
        call ItemPoolAddItemType(DropPool, 'I0AF', 0.45) //Maybe a rarer sword item
        call ItemPoolAddItemType(DropPool, 'IA7G', 1.00) //Armor or whatever
    endfunction
endlibrary

This is, of course very simplistic... but it can be expanded to be dynamic depending on the level of the hero, say. An example of this would be to detect when the hero levels up and then modify the itempool's items/weights to change what monsters can drop.

To do this, I would suggest the use of a function like so:
Collapse JASS:
function ModifyWeight takes integer ItemId, real NewWeight returns nothing
    call ItemPoolRemoveItemType(DropPool, ItemId)
    if Weight > 0.00 then //If <= 0.00, assume that the player wants the item removed from the pool
        call ItemPoolAddItemType(DropPool, ItemId, NewWeight)
    endif
endfunction
Put together, here's an example that would make "lower-level" items become obsolete as the hero leveled up (say to a max level of 5):

Collapse JASS:
library ItemDrop initializer Init
    globals
        public itempool DropPool = CreateItemPool()
        public trigger DropPoolTrig = CreateTrigger()
        public trigger LevelTrig = CreateTrigger()
    endglobals    

    private function ModifyWeight takes integer ItemId, real NewWeight returns nothing
        call ItemPoolRemoveItemType(DropPool, ItemId)
        if Weight > 0.00 then
            call ItemPoolAddItemType(DropPool, ItemId, NewWeight)
        endif
    endfunction

    private function Conditions takes nothing returns boolean
        return GetPlayerId(GetOwningPlayer(GetTriggerUnit())) > 11 //For neutral players only
    endfunction

    private function OnDeath takes nothing returns nothing
        local unit U = GetTriggerUnit()
        if not(IsUnitAlly(U, GetOwningPlayer(GetKillingUnit()))) then
            call PlaceRandomItem(DropPool, GetUnitX(U), GetUnitY(U))
        endif
        set U = null
    endfunction

    private function OnLevel takes nothing returns nothing
        local unit U = GetTriggerUnit()
        local integer Level = GetHeroLevel()

        call ModifyWeight('I004', 1.00-(Level*0.60))
        call ModifyWeight('I000', 1.25-(Level*0.25))
        call ModifyWeight('IA7G', 1.00-(Level*0.20))
        if Level == 2 then
            call ItemPoolAddItemType(DropPool, 'I20H', 0.90) //A shield of some sort
        elseif Level == 3 then
            call ModifyWeight('I20H', 0.70)
            call ItemPoolAddItemType(DropPool, 'I44F', 1.10) //A hammer weapon
        elseif Level == 4 then
            call ModifyWeight('I20H', 0.50)
            call ModifyWeight('I44F', 0.90)
            call ItemPoolAddItemType(DropPool, 'I0CP', 1.10) //A hammer weapon
            call ItemPoolAddItemType((DropPool, 'IV4L', 0.50) //An invulnerability potion
        elseif Level == 5 then
            call ModifyWeight('I20H', 0.30)
            call ModifyWeight('I44F', 0.60)
            call ItemPoolAddItemType((DropPool, 'ID3G', 0.10) //A superweapon!
        endif

        set U = null
    endfunction

    private function Init takes nothing returns nothing
        call TriggerRegisterAnyUnitEventBJ(DropPoolTrig, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(DropPoolTrig, Condition(function Conditions)
        call TriggerAddAction(DropPoolTrig, function OnDeath)

        call TriggerRegisterAnyUnitEventBJ(LevelTrig, EVENT_PLAYER_HERO_LEVEL)
        call TriggerAddAction(DropPoolTrig, function OnLevel)

        call ItemPoolAddItemType(DropPool, 'I004', 1.00) //Just some random item
        call ItemPoolAddItemType(DropPool, 'I000', 1.25) //Possibly a potion
        call ItemPoolAddItemType(DropPool, 'I0AF', 0.45) //Maybe a rarer sword item
        call ItemPoolAddItemType(DropPool, 'IA7G', 1.00) //Armor or whatever
    endfunction
endlibrary
This is, of course, somewhat simplistic and definitely not the most efficient... but you get the idea.



Itempools and Quests
Coming later when I don't need a lot of sleep.




How can I use unit—/item—pools in relationship to rolling "dice" to determine things?
This will get here eventually, but I'm a bit tired to do it now.


Closing Notes
I'm really surprised at how few people know about unit—/item—pools because I always found them to be extremely useful in randomness situations.
11-06-2007, 02:02 PM#2
Rising_Dusk
Quote:
I want to see someone attempt that with arrays of units instead of unitpools
It can be done with groups just as easily, unitpools are just a step removed in the "using the most basic datatype" thing. That's why unit pools really don't see a lot of use in most code... You can do the same thing with a group. (The weight stuff is useful and saves work, though for applications like selecting a random hero is completely unnecessary) That's probably why it doesn't see much use.

On a side note, itempools are entirely more useful than unitpools... This is especially true in RPGs where you want random and weighted drops from monsters. It's how I did it awhile back for an RPG map that's still quasi-in development.
11-06-2007, 07:23 PM#3
Salbrismind
I thought the same thing when I read the first part. All you do is set all the units in an array, add them all to a group, then randomly pick one at a time removing that unit from the group...
11-07-2007, 01:14 AM#4
Pyrogasm
Bah; I didn't even think about using groups...

Planning on updating this here in a second; I'll change the tutorial's focus to both unit and item pools.
11-07-2007, 01:44 PM#5
Vexorian
Hmm, keep the array sorted by accumulated weight and then do binary searches to get the item?

Edit: Of all the things I saw in my life I think that using groups for integers is the biggest abomination of all.
11-08-2007, 05:30 AM#6
The Elite
[OFF TOPIC]
Quote:
Originally Posted by Vexorian's Sig
Random fact REMIX!: Prefixing "no offense" to what you say does not make it less offensive.
Best fact ever
[/OFF TOPIC]
This is pretty good, i never knew about unit/item pools and this opens my eyes
Also i want to make an ORPG now because of how easy it is to make weighted drops with this. Just thought i'd announce that.
Also2 +REP
EDIT damit, gota spread rep
11-08-2007, 05:43 AM#7
Pyrogasm
Quote:
Originally Posted by Vexorian
Hmm, keep the array sorted by accumulated weight and then do binary searches to get the item?
And that's easier...? I'm sure that doing a binary search is beyond the average person.

Quote:
Originally Posted by Vexorian
Edit: Of all the things I saw in my life I think that using groups for integers is the biggest abomination of all.
Well, to use them directly as integers, no that would not be smart, but assume you wanted to get a number between 30 and 40 that should usually be about 35, though the possibility of getting a very high or very low number should be much lower. You could use a weighted unit-/item- pool for this (assuming you didn't want to do any binary searching through sorted arrays) with units/items representing the numbers weighted in a bell curve. So the unit that was 30 might have a weight of 0.10, the one for 31 have a weight of 0.25, 33 have a weight of 0.40, etc., and then decreasing after 35 or 36 or so.

I guess the only time you'd use something like that would be if you wanted to entirely rescript Warcraft's attacking/damage system, at which point you probably would know how to do a binary search anyway.


Augh... I'm lazy. Getting to editing this right now.
11-08-2007, 07:54 AM#8
Pyrogasm
Updated to include Itempools.

If someone could change the thread title...
11-08-2007, 01:13 PM#9
Vexorian
Quote:
Originally Posted by Vexorian
Hmm, keep the array sorted by accumulated weight and then do binary searches to get the item?

Edit: Of all the things I saw in my life I think that using unit groups for integers is the biggest abomination of all.
I think binary search is easier than sorting and sorting is as basic as it gets, anyways what's in bold in my quote is a correction. I think unitpools or item pools are useful but not that much, they are good for what they are made for, but for example if you wanted your own weigthed random selection system it is easy to code in comparison to the simplest physics system yet it could do a lot of fun things that item/unit pools are not able to do. You could use interfaces for conditional selection and control removal and stuff like that...
11-08-2007, 11:10 PM#10
Pyrogasm
Ok, I see your point. The fact of the matter is that I simply don't know how to do that... so I'll just leave that section of the tutorial out and not even touch on it.

Quest-related stuff is coming this evening.


Oh, and what else would you like me to add before it might become an acceptable tutorial?
11-12-2007, 09:25 PM#11
PitzerMike
I think it's quite acceptable.
Too bad you have to place units/items and can't just get a rawcode out of the pool without placing a dummy item and removing it.
They should have made a generic IntegerPool instead of those.
08-13-2008, 01:36 PM#12
Flame_Phoenix
When will the next versions be released ?
08-13-2008, 05:03 PM#13
Pyrogasm
Dunno?
08-13-2008, 05:37 PM#14
Flame_Phoenix
Quote:
Itempools and Quests
Coming later when I don't need a lot of sleep.
Quote:
Dunno?

....

Great job you did, I already used this knowledge in my Golem Invocation spell. Thx for creating it, can't wait to see next versions.