• 🏆 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!

Resource Spawn System

By: Imperial Good (Dr Super Good) / me

A simple item spawn system for resources as requested by Mr.Goblin. The system can be configured to spawn items randomly within a rect at a certain rate up to a certain number. Items that are used on pickup will automatically be removed. Items that are picked up and moved outside the rect do not count to the spawn limit.

The system supports up to 8191 item spawn rects with up to 8191 spawned items in total. Although each spawn polls all spawned items, the process is staggered for scalability. Due to the nature of the algorithms used spawns with a large number of items (>>100) will be prone to causing performance issues.

The staggering can cause multiple spawns to occur at once if the period of the spawn is less than the period between updating the same spawn. On average the rate items spawn at will remain correct to the period defined for the spawn.

One possible use for this system is for spawning resource items in a survival map that are used for crafting (eg Island Troll Tribes). It can also be used to periodically respawn powerups such as gold coins, tomes or runes in an arena style map.

Although the core of the system is in JASS, it is fed by data initialized in GUI. This makes it easy to configure with limited triggering experience. In theory the data the system uses can be modified dynamically during a session (adding new spawns, changing spawn period, etc) however it may result in unexpected behaviour if done so in an illogical way.


JASS:
function Trig_RS_Spawn_FuncItemListAdd takes item litem returns nothing
    local integer node
  
    // Get free node
    if udg_RS_ItemList_Free == udg_RS_ItemList_N then
        // New node from top of node heap
        set node = udg_RS_ItemList_Free
        set udg_RS_ItemList_N = udg_RS_ItemList_N + 1
        set udg_RS_ItemList_Free = udg_RS_ItemList_N
    else
        // Recycle node from free list
        set node = udg_RS_ItemList_Free
        set udg_RS_ItemList_Free = udg_RS_ItemList_Link[node]
    endif
  
    // Prepare node and add to item list
    set udg_RS_ItemList_Item[node] = litem
    set udg_RS_ItemList_Link[node] = udg_RS_Table_ItemList[udg_RS_Index]
    set udg_RS_Table_ItemList[udg_RS_Index] = node
endfunction

function Trig_RS_Spawn_FuncItemListRemove takes integer prev returns nothing
    local integer free
  
    // Extract node
    if prev == -1 then
        // Header reference needs updating
        set free = udg_RS_Table_ItemList[udg_RS_Index]
        set udg_RS_Table_ItemList[udg_RS_Index] = udg_RS_ItemList_Link[free]
    else
        // Previous node reference needs updating
        set free = udg_RS_ItemList_Link[prev]
        set udg_RS_ItemList_Link[prev] = udg_RS_ItemList_Link[free]
    endif
  
    // Prepare node and add to free list
    set udg_RS_ItemList_Item[free] = null
    set udg_RS_ItemList_Link[free] = udg_RS_ItemList_Free
    set udg_RS_ItemList_Free = free
endfunction

function Trig_RS_Spawn_FuncCountProcessItems takes nothing returns integer
    local integer index = udg_RS_Table_ItemList[udg_RS_Index]
    local integer prev = -1
    local integer next
    local integer count = 0
    local item litem
  
    // Process and count list
    loop
        // Evaluate node
        exitwhen index == -1
        set next = udg_RS_ItemList_Link[index]
      
        // Process item
        set litem = udg_RS_ItemList_Item[index]
        if litem == null then
            // Item removed so remove from list
            call Trig_RS_Spawn_FuncItemListRemove(prev)
        elseif GetWidgetLife(litem) < 0.405 then
            // Item is dead so clean and remove from list
            call RemoveItem(litem)
            call Trig_RS_Spawn_FuncItemListRemove(prev)
        elseif IsItemOwned(litem) or not RectContainsCoords(udg_RS_Table_Region[udg_RS_Index], GetItemX(litem), GetItemY(litem)) then
            // Item moved away from rect so remove from list
            call Trig_RS_Spawn_FuncItemListRemove(prev)
        else
            // Item is counted
            set count = count + 1
        endif
      
        // Advance list position
        set prev = index
        set index = next
    endloop
  
    // LDLHVRCLR fix
    set litem = null
  
    return count
endfunction

function Trig_RS_Spawn_FuncSpawnItem takes nothing returns nothing
    // Spawn item
    local rect whichRect = udg_RS_Table_Region[udg_RS_Index]
    local item litem = CreateItem(udg_RS_Table_Item_Type[udg_RS_Index], GetRandomReal(GetRectMinX(whichRect), GetRectMaxX(whichRect)), GetRandomReal(GetRectMinY(whichRect), GetRectMaxY(whichRect)))
  
    // Evaluate spawned location
    if not RectContainsCoords(udg_RS_Table_Region[udg_RS_Index], GetItemX(litem), GetItemY(litem)) then
        // Fallback to safe spawn location
        call SetItemPositionLoc(litem, udg_RS_Table_Fallback[udg_RS_Index])
    endif
  
    // Add to list
    call Trig_RS_Spawn_FuncItemListAdd(litem)
      
    // LDLHVRCLR fix
    set whichRect = null
    set litem = null
endfunction

function Trig_RS_Spawn_FuncSpawnLogic takes integer periods returns nothing
    // Get item count
    local integer count = Trig_RS_Spawn_FuncCountProcessItems()
  
    // For each period
    loop
        // Break when all periods done
        exitwhen periods <= 0
      
        // Spawn logic
        if count < udg_RS_Table_Item_Count[udg_RS_Index] then
            // Spawn an item and count it
            call Trig_RS_Spawn_FuncSpawnItem()
            set count = count + 1
        else
            // No more spawning needed so break
            exitwhen true
        endif
      
        // Advance period
        set periods = periods - 1
    endloop
endfunction

function Trig_RS_Spawn_Actions takes nothing returns nothing
    local integer periods

    // Advance timer
    set udg_RS_Table_Timer[udg_RS_Index] = udg_RS_Table_Timer[udg_RS_Index] + udg_RS_UPDATE_PERIOD * udg_RS_Table_N
  
    // Compute number of periods to schedule
    set periods = R2I(udg_RS_Table_Timer[udg_RS_Index] / udg_RS_Table_Period[udg_RS_Index])
  
    // Scheduler logic
    if periods > 0 then
        // Only schedule if at least 1 period has lapsed
        call Trig_RS_Spawn_FuncSpawnLogic(periods)
      
        // Compute unscheduled time (emulated modulus)
        set udg_RS_Table_Timer[udg_RS_Index] = udg_RS_Table_Timer[udg_RS_Index] - udg_RS_Table_Period[udg_RS_Index] * periods
    endif
  
    // Cycle scheduler index
    set udg_RS_Index = ModuloInteger(udg_RS_Index + 1, udg_RS_Table_N)
endfunction

//===========================================================================
function InitTrig_RS_Spawn takes nothing returns nothing
    set gg_trg_RS_Spawn = CreateTrigger(  )
    call TriggerAddAction( gg_trg_RS_Spawn, function Trig_RS_Spawn_Actions )
endfunction



  • RS Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- Spawn 1 --------
      • Set RS_Table_Region[RS_Table_N] = Item Region 1 <gen>
      • Set RS_Table_Fallback[RS_Table_N] = (Center of RS_Table_Region[RS_Table_N])
      • Set RS_Table_Period[RS_Table_N] = 1.00
      • Set RS_Table_Item_Type[RS_Table_N] = Scroll of Regeneration
      • Set RS_Table_Item_Count[RS_Table_N] = 32
      • Set RS_Table_Timer[RS_Table_N] = 0.00
      • Set RS_Table_N = (RS_Table_N + 1)
      • -------- Spawn 2 --------
      • Set RS_Table_Region[RS_Table_N] = Item Region 2 <gen>
      • Set RS_Table_Fallback[RS_Table_N] = (Center of RS_Table_Region[RS_Table_N])
      • Set RS_Table_Period[RS_Table_N] = 2.00
      • Set RS_Table_Item_Type[RS_Table_N] = Tome of Agility +2
      • Set RS_Table_Item_Count[RS_Table_N] = 16
      • Set RS_Table_Timer[RS_Table_N] = 0.00
      • Set RS_Table_N = (RS_Table_N + 1)
      • -------- Spawn 3 --------
      • Set RS_Table_Region[RS_Table_N] = Item Region 3 <gen>
      • Set RS_Table_Fallback[RS_Table_N] = (Center of RS_Table_Region[RS_Table_N])
      • Set RS_Table_Period[RS_Table_N] = 4.00
      • Set RS_Table_Item_Type[RS_Table_N] = Tome of Intelligence +2
      • Set RS_Table_Item_Count[RS_Table_N] = 8
      • Set RS_Table_Timer[RS_Table_N] = 0.00
      • Set RS_Table_N = (RS_Table_N + 1)
      • -------- Spawn 4 --------
      • Set RS_Table_Region[RS_Table_N] = Item Region 4 <gen>
      • Set RS_Table_Fallback[RS_Table_N] = (Center of RS_Table_Region[RS_Table_N])
      • Set RS_Table_Period[RS_Table_N] = 8.00
      • Set RS_Table_Item_Type[RS_Table_N] = Tome of Strength +2
      • Set RS_Table_Item_Count[RS_Table_N] = 4
      • Set RS_Table_Timer[RS_Table_N] = 0.00
      • Set RS_Table_N = (RS_Table_N + 1)
      • -------- More entries go here (copy and paste a previous entry) --------
      • -------- Other Init --------
      • Set RS_UPDATE_PERIOD = 1.00
      • Trigger - Add to RS Spawn <gen> the event (Time - Every RS_UPDATE_PERIOD seconds of game time)
      • -------- Auto Init --------
      • For each (Integer A) from 0 to (RS_Table_N - 1), do (Actions)
        • Loop - Actions
          • Set RS_Table_ItemList[(Integer A)] = -1


Keywords:
resource, item, spawn, list, rect, data, easy, staggered, schedule, time, pickup, area, limit
Contents

Resource Spawn System for Mr.Goblin (Map)

Reviews
05:40, 25th May 2016 Tank-Commander: A neat system which could prove very useful to mapmakers. Only have one or two criticisms and only one for the code: - Could use a proper name (The "For Mr.Globlin" is weird and unnecessary for the resource name...

Moderator

M

Moderator

05:40, 25th May 2016
Tank-Commander: A neat system which could prove very useful to mapmakers. Only have one or two criticisms and only one for the code:
- Could use a proper name (The "For Mr.Globlin" is weird and unnecessary for the resource name - sticks out a lot)
- I'd suggest replacing the BJ functions which don't inline well with their native equivelents (SetItemPositionLoc with SetItemPosition, and suchlike) otherwise the code is very well done
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
the only thing I could find is that you prefer TriggerAddAction over TriggerAddCondition.
Which makes practically no difference. Yes it may be slightly faster, but there will almost always be bigger places to gain speed. It is used purely for neatness/readability as TriggerAddCondition forces one to make the function return a constant boolean and also conditions were likely not intended to be used in that way.

And I'm not sure why you use those names for functions.
The system was originally meant to be standard GUI but unfortunately WC3 GUI is just too annoying to use so it was extended to standard JASS. Since it is standard JASS there are no vJASS scope/library and access modifiers so the functions were named as such to try and keep them unique and avoid possible name conflicts with other systems.

The long function names should not matter as they will get shortened during optimization. Before publishing maps one should run them through Vexorian's optimizer to both optimize the JASS code for speed (shorter variable names) and file size (spaces and comments removed) as well as provide some basic "protection".
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
SetItemPositionLoc no reason to use this.
Procedural coupling reduction. One could inline expand it but outside of optimization there would be no point.

JASS:
function SetItemPositionLoc takes item whichItem,location loc returns nothing
    call SetItemPosition(whichItem, GetLocationX(loc), GetLocationY(loc))
endfunction

A location is used by the system because it makes it easier for GUI users to configure.

// LDLHVRCLR fix what? lol just wondering.
"local" declared local handle variable reference counter leak on return

Basically the reason why we have to null local declared handles. If the bug is ever fixed (which it hopefully will be) then those pieces of code can be deleted.
 
Procedural coupling reduction. One could inline expand it but outside of optimization there would be no point.

Why not optimize it then? It's not like it's any harder to read.

I also think it only gets inlined when running through Vex's map optimizer while also providing a Blizzard.j file, as opposed to the inlining feature of JassHelper which runs when saving the map.

This could be approved regardless but just my opinion.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
SetItemPositionLoc doesn't match the inlining criterias of the JassHelper,
because every argument must be evaluated only once.

Also I'm not sure if a BJ function inlines at all.

On the other side I don't see the need for a micro optimization.
SetUnitPositionLoc is totally fine.
 
I read the description and config trigger.

-----

What is the Fallback?
What is the Timer variable?

It is possible at the moment to register multiple item types to an instance, so it will create always a random one from a list?
From config it seems not possible, but it would be useful:
The downside from registrating multple instances, if the user wants to spawn multiple item types in a rect, is that they won't share the timeout.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
What is the Fallback?
The location where items will be placed if the random location in the rect results in the item being displaced outside the rect. This can happen if the item is spawned at the edge of the rect and is then displaced out by WC3's collision algorithm. This location should be in an open space which is almost always guaranteed to displace inside the rect.

What is the Timer variable?
Can be used to offset the phase with respect to time of the spawn with respect to other spawns. It can also be used to force some initial spawn volume.

It is possible at the moment to register multiple item types to an instance, so it will create always a random one from a list?
Was not part of the requirements. Also would be hard to configure for GUI users as it would need a linked list structure for spawn item types.
 
Ah, okay that's clever and makes good sense. (I think comments would be nice for config.)

Was not part of the requirements.
I guess requirements from Mr.Goblin.
Probably. But it's goaling to be a public submission, and not a private one.

It would be just nice to have a more complete submission I mean, so it covers more useabiliy, also for other users, next to Mr.Goblin.

But yeh, for achieving the list/pool from items being spawned you probably would need to change the "registration" style. (" ", as atm it's not a real registration)
Item types could be registered one by one with running a register-item trigger after each ItemType variable assigning.
So all item types would be registered to same instance.
Though there would be a loss from easily manipulating ItemTypes in the future, as it's provided now.
So an other possibility would be to use an IetmType array and to run a Register trigger after each instance config.
So the users can always start from 0 again if they config item types for a new instance.
Thoughts?

An other point that might be added, is an feature to turn an instance temporary off. (maybe even to "deindex")
I also used similar systems for my own projects in past, but the situation I probably always had was
that I never needed such spawning behaviour to be permanent. I only used them temporary for certain events.
Maybe a bool[] variable variable would already do the disabling part already.
 
Top