AceHart
Your Friendly Neighborhood Admin
- Reaction score
- 1,494
Dropping items from Creeps.
Sounds easy.
Event:
- A unit owned by Neutral Hostile dies
Conditions:
- Random integer between 1 and 100 less than or equal to 34
Actions:
- Item - Create (Random level (Level of (Triggering unit)), random class) item at (Position of (Triggering unit))
There's a point leak, but that's of no importance here.
So, yes, it works.
Up to a point. Or two.
Those points being:
- I can only restrict it to either one particular class or all classes
- all classes includes useless classes like campaign and misc
- my map has a Marketplace... and that one doesn't work with this!
I.e. something better is needed.
And, of course, something that is not "put a table on the Creeps"...
The map I used this for has 88 of them (total level: 308).
Waaaaaayyy too much work.
Not to mention it wouldn't be truly random.
Given I wanted it to be plug&pray (or copy&paste), this was going to be a JASS library.
The start, as usual, is rather simple:
It also needs a trigger, an event and the random condition:
Until there, no big mysteries.
About 1 chance in 3 for a Creep to drop something.
The trigger simply waits for a death on Neutral Hostile.
The actual work is the "Drop" function.
What's it going to drop?
The plan is to drop a random item based on the Creep's level, of a random class from a predefined list of classes.
A quick look at a Blizzard melee map... yep, Creeps have personal item drop tables, that drop stuff like "random level 2 charged".
So, the plan holds.
It's slightly more random though.
The only "con" would be that, if you play a given melee map long enough, you'll know what Creep drops what type.
With this "system", you can never be sure what you'll get.
Also, it could happen that a group of 5 Creeps drops either nothing, or up to five items!
Unlikely.
We assume random is random enough for this to work nicely.
List of classes?
There is one in common.j:
Unknown, any and misc are not recommended.
Same for Campaign as, more often than not, campaign items don't do anything and make no sense in a melee map.
Which leads to the following list:
Why put it in an array?
Because I wanted to choose one class at random and didn't feel like adding a huge "if random equal to 1 then ... elseif ... elseif ... elseif ... else ... endif".
Well, "huge"... it's five classes only.
Still, types[GetRandomInt(1, 5)] is cleaner, easy to read, faster, and all in one line.
As for the level, something simple like GetUnitLevel(GetTriggerUnit()) can already work.
It might be better to use 1 + level / 2 though.
That would make the items slightly less uber just because you killed a level 6 Creep.
Then again, we're going with "simple".
And, yes, we do like good items
Item levels for classes though are limited in range.
I had a look through the default items, and it seems the max class level is 8.
Didn't find any official constant for that... but 8 it is.
Additionally, I didn't want any level 0 items either. Minimum is 1.
Now, it could happen that you're really unlucky.
We're asking for a random item of a random class of a given level.
What if there is no such item?
In that case, we try this one more time.
With a new class. (Or the same as random may be)
Obviously, we're paranoid and put a limit to the number of tries for the times when Blizzard hates us.
Don't want to hit the op limit on an item drop...
That can't possibly happen, except, maybe, once in a million.
According to Murphy's Law, things that happen once in a million in theory happen about 9 times out of 10 in practice.
So we prepare for that.
With a simple loop that tries to get an item.
If it didn't get one, we try again, but only up to some maximum number of times.
The script uses 12 as limit.
Works just fine.
A more interesting part is getting the Marketplace to work.
By default, it works from Creeps that have a personal Item Drop Table.
Given my map has no such things, the Marketplace doesn't work. At all
Help comes from a read through Blizzard.j and a quick test map with a random table.
Item drop tables actually create a trigger.
That waits for that particular Creep to die.
Then it creates a random item, according to the item set specified.
And, here's the interesting part, at the end of that trigger, it calls a function that updates the Marketplaces!
Victory is near!
Said function has a simple system.
It uses a couple of boolean arrays.
If one particular index is true (they are all set to false at loading time),
then the Marketplaces will start to generate items of that class and level.
The available arrays are:
- bj_stockAllowedPermanent
- bj_stockAllowedCharged
- bj_stockAllowedArtifact
I.e. if we dropped a level 2 charged item, all we have to do is
set bj_stockAllowedCharged[2] = true
and all Marketplaces will happily generate level 2 charged items, in some random order.
Which is a Good Thing(tm) because I actually happen to like them.
Other than that, there's not much to the script.
Usage, as can be expected from a JASS library, is also extremely simple:
Put it in your map, done.
Fully commented for people that like long reads, or bigger maps
Yours,
AceHart
Sounds easy.
Event:
- A unit owned by Neutral Hostile dies
Conditions:
- Random integer between 1 and 100 less than or equal to 34
Actions:
- Item - Create (Random level (Level of (Triggering unit)), random class) item at (Position of (Triggering unit))
There's a point leak, but that's of no importance here.
So, yes, it works.
Up to a point. Or two.
Those points being:
- I can only restrict it to either one particular class or all classes
- all classes includes useless classes like campaign and misc
- my map has a Marketplace... and that one doesn't work with this!
I.e. something better is needed.
And, of course, something that is not "put a table on the Creeps"...
The map I used this for has 88 of them (total level: 308).
Waaaaaayyy too much work.
Not to mention it wouldn't be truly random.
Given I wanted it to be plug&pray (or copy&paste), this was going to be a JASS library.
The start, as usual, is rather simple:
JASS:
library ITEMDROPS initializer Init
endlibrary
It also needs a trigger, an event and the random condition:
JASS:
private function Conditions takes nothing returns boolean
return GetRandomInt(1, 100) <= 34 // about 1 Creep in 3 drops something
endfunction
...
local trigger t = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(t, Condition(function Conditions))
call TriggerAddAction(t, function Drop)
...
Until there, no big mysteries.
About 1 chance in 3 for a Creep to drop something.
The trigger simply waits for a death on Neutral Hostile.
The actual work is the "Drop" function.
What's it going to drop?
The plan is to drop a random item based on the Creep's level, of a random class from a predefined list of classes.
A quick look at a Blizzard melee map... yep, Creeps have personal item drop tables, that drop stuff like "random level 2 charged".
So, the plan holds.
It's slightly more random though.
The only "con" would be that, if you play a given melee map long enough, you'll know what Creep drops what type.
With this "system", you can never be sure what you'll get.
Also, it could happen that a group of 5 Creeps drops either nothing, or up to five items!
Unlikely.
We assume random is random enough for this to work nicely.
List of classes?
There is one in common.j:
JASS:
constant itemtype ITEM_TYPE_PERMANENT = ConvertItemType(0)
constant itemtype ITEM_TYPE_CHARGED = ConvertItemType(1)
constant itemtype ITEM_TYPE_POWERUP = ConvertItemType(2)
constant itemtype ITEM_TYPE_ARTIFACT = ConvertItemType(3)
constant itemtype ITEM_TYPE_PURCHASABLE = ConvertItemType(4)
constant itemtype ITEM_TYPE_CAMPAIGN = ConvertItemType(5)
constant itemtype ITEM_TYPE_MISCELLANEOUS = ConvertItemType(6)
constant itemtype ITEM_TYPE_UNKNOWN = ConvertItemType(7)
constant itemtype ITEM_TYPE_ANY = ConvertItemType(8)
Unknown, any and misc are not recommended.
Same for Campaign as, more often than not, campaign items don't do anything and make no sense in a melee map.
Which leads to the following list:
JASS:
globals
private itemtype array types
endglobals
...
set types[1] = ITEM_TYPE_PERMANENT
set types[2] = ITEM_TYPE_CHARGED
set types[3] = ITEM_TYPE_ARTIFACT
set types[4] = ITEM_TYPE_POWERUP
set types[5] = ITEM_TYPE_PURCHASABLE
...
Why put it in an array?
Because I wanted to choose one class at random and didn't feel like adding a huge "if random equal to 1 then ... elseif ... elseif ... elseif ... else ... endif".
Well, "huge"... it's five classes only.
Still, types[GetRandomInt(1, 5)] is cleaner, easy to read, faster, and all in one line.
As for the level, something simple like GetUnitLevel(GetTriggerUnit()) can already work.
It might be better to use 1 + level / 2 though.
That would make the items slightly less uber just because you killed a level 6 Creep.
Then again, we're going with "simple".
And, yes, we do like good items
Item levels for classes though are limited in range.
I had a look through the default items, and it seems the max class level is 8.
Didn't find any official constant for that... but 8 it is.
Additionally, I didn't want any level 0 items either. Minimum is 1.
Now, it could happen that you're really unlucky.
We're asking for a random item of a random class of a given level.
What if there is no such item?
In that case, we try this one more time.
With a new class. (Or the same as random may be)
Obviously, we're paranoid and put a limit to the number of tries for the times when Blizzard hates us.
Don't want to hit the op limit on an item drop...
That can't possibly happen, except, maybe, once in a million.
According to Murphy's Law, things that happen once in a million in theory happen about 9 times out of 10 in practice.
So we prepare for that.
With a simple loop that tries to get an item.
If it didn't get one, we try again, but only up to some maximum number of times.
The script uses 12 as limit.
Works just fine.
A more interesting part is getting the Marketplace to work.
By default, it works from Creeps that have a personal Item Drop Table.
Given my map has no such things, the Marketplace doesn't work. At all
Help comes from a read through Blizzard.j and a quick test map with a random table.
Item drop tables actually create a trigger.
That waits for that particular Creep to die.
Then it creates a random item, according to the item set specified.
And, here's the interesting part, at the end of that trigger, it calls a function that updates the Marketplaces!
Victory is near!
Said function has a simple system.
It uses a couple of boolean arrays.
If one particular index is true (they are all set to false at loading time),
then the Marketplaces will start to generate items of that class and level.
The available arrays are:
- bj_stockAllowedPermanent
- bj_stockAllowedCharged
- bj_stockAllowedArtifact
I.e. if we dropped a level 2 charged item, all we have to do is
set bj_stockAllowedCharged[2] = true
and all Marketplaces will happily generate level 2 charged items, in some random order.
Which is a Good Thing(tm) because I actually happen to like them.
Other than that, there's not much to the script.
Usage, as can be expected from a JASS library, is also extremely simple:
Put it in your map, done.
JASS:
library ITEMDROPS initializer Init
globals
// How many classes the types array from "Init" holds
private constant integer MAX_CLASSES = 5
// Defines item level limits
private constant integer MIN_LEVEL = 1
private constant integer MAX_LEVEL = 8
// 12 should be enough for anyone (famous last words)
private constant integer MAX_RETRIES = 12
// Type indexes as set in "Init" for classes that work with Marketplaces
private constant integer INDEX_PERMANENT = 1
private constant integer INDEX_CHARGED = 2
private constant integer INDEX_ARTIFACT = 3
// Leave as is
private itemtype array types // See "Init" for details
endglobals
// Not every Creep necessarily drops something, adjust as needed
private function Conditions takes nothing returns boolean
return GetRandomInt(1, 100) <= 34 // About 1 Creep in 3 drops something
endfunction
// Item level depends on the level of the Creep that died
private function Level2Item takes integer level returns integer
local integer i = GetRandomInt(1, 2) + (2 * level) / 3 // Weird but impressive formula
if i < MIN_LEVEL then // No less than min level
return MIN_LEVEL
elseif i > MAX_LEVEL then // And at most max level
return MAX_LEVEL
endif
return i
endfunction
// The actual work happens here
private function Drop takes nothing returns nothing
local unit u = GetTriggerUnit() // A unit owned by Neutral Hostile dies
local integer l = Level2Item(GetUnitLevel(u)) // The item level according to the Creep level
local integer i // Random item
local integer t // Random type
local integer c = MAX_RETRIES // If we're really unlucky, no item of that level and class exists. Repeatedly.
// Try to find a nice item
loop
set t = GetRandomInt(1, MAX_CLASSES) // Randomize item class according to the table in "Init"
set i = ChooseRandomItemEx(types[t], l) // Try to find one
exitwhen i != 0 // Found it
set c = c - 1 // Didn't work, try some more
if c <= 0 then // But not too often
set u = null
return
endif
endloop
// The line this entire thing is about
call CreateItem(i, GetUnitX(u), GetUnitY(u)) // "drop" it
// Care for Marketplaces
if IsItemIdSellable(i) then
if t == INDEX_PERMANENT then // Adjust Marketplaces where applicable
set bj_stockAllowedPermanent[l] = true
elseif t == INDEX_CHARGED then
set bj_stockAllowedCharged[l] = true
elseif t == INDEX_ARTIFACT then
set bj_stockAllowedArtifact[l] = true
else
// No luck <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite3" alt=":(" title="Frown :(" loading="lazy" data-shortname=":(" />
endif
endif
// Done. Clean up and leave
set u = null
endfunction
// Preparations
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
set types[1] = ITEM_TYPE_PERMANENT // Adjust constants for Marketplaces accordingly
set types[2] = ITEM_TYPE_CHARGED
set types[3] = ITEM_TYPE_ARTIFACT
set types[4] = ITEM_TYPE_POWERUP
set types[5] = ITEM_TYPE_PURCHASABLE
// A unit owned by Neutral Hostile dies
call TriggerRegisterPlayerUnitEvent(t, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_DEATH, null)
// It might drop an item
call TriggerAddCondition(t, Condition(function Conditions))
// If yes, here's where it happens
call TriggerAddAction(t, function Drop)
endfunction
endlibrary
Fully commented for people that like long reads, or bigger maps
Yours,
AceHart