Snippet Random item drops - And Marketplaces

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:
JASS:
library ITEMDROPS initializer Init
endlibrary


:p


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:


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 :p

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&#039;s where it happens
    call TriggerAddAction(t, function Drop)
endfunction

endlibrary


Fully commented for people that like long reads, or bigger maps :p


Yours,
AceHart
 

emjlr3

Change can be a good thing
Reaction score
395
i dont think I get it

yea lets drop items, but, whats the deal with the karketplace updating, rather, whats the purpose, or what am I missing?

also I think return ""nothing here" desyncs mac users, remove the triggeraction and just use the conditino for the whole thing, therefore removing the issue
 

saw792

Is known to say things. That is all.
Reaction score
280
Returning nothing cannot desync mac users without some serious retardation on blizzard's part. Have a look at what the GUI "Skip Remaining Actions" function does when you convert it.
 

phyrex1an

Staff Member and irregular helper
Reaction score
447
Returning nothing cannot desync mac users without some serious retardation on blizzard's part.
Agrees. And it would be even more retarded if you actually could return nothing from functions used as boolexprs. And even more retarded if that would work perfectly for windows users but not for mac users.
Well... it's the true story...

The return nothing in ace code is fine anyway.

I guess this is the end all low effort random item drops system? Sounds like world domination to me. But why did you decide to make one of the least useful values a constant but left the others as magic? Funny read to but I guess you knew that.

(after 12 post btw)
 

AceHart

Your Friendly Neighborhood Admin
Reaction score
1,494
> what's the deal with the Marketplace updating

Personally, I think a Marketplace is the coolest neutral building your map can have for selling items.


> but left the others as magic?

Updated.
 

emjlr3

Change can be a good thing
Reaction score
395
i dont get it....
 

AceHart

Your Friendly Neighborhood Admin
Reaction score
1,494
Hm... just a random question, ever seen a Marketplace in a map?
And, yes, an actual Marketplace, not a Goblin Merchant or other shop.
 

emjlr3

Change can be a good thing
Reaction score
395
what do they do special?
 

AceHart

Your Friendly Neighborhood Admin
Reaction score
1,494
My dear Hulk3...

They do not have any preset items (object editor).
Instead, the items they sell come and go, rather randomly, over time, as the map is played.
 

emjlr3

Change can be a good thing
Reaction score
395
so your manipulating what items it will sell, and such
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top