| 11-06-2007, 08:52 AM | #1 | |
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:
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: 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: 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): 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: 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: 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: 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: 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: 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 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 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 | |
Quote:
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 |
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 |
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 |
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 | |
[OFF TOPIC] Quote:
[/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 | ||
Quote:
Quote:
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 |
Updated to include Itempools. If someone could change the thread title... |
| 11-08-2007, 01:13 PM | #9 | |
Quote:
|
| 11-08-2007, 11:10 PM | #10 |
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 |
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 |
When will the next versions be released ? |
| 08-13-2008, 05:03 PM | #13 |
Dunno? |
| 08-13-2008, 05:37 PM | #14 | ||
Quote:
Quote:
.... 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. |
