[General] Creating a spawn table and drop table system

Level 3
Joined
Jun 6, 2024
Messages
13
Upfront disclaimer, I know virtually nothing about Jass. All of my experience in programming is in other languages or using the editor itself and my map so far has been built using gui entirely until i stumbled across this drop table and it was easy enough. So these may be easy questions, sorry if answers are easy to find, I apparently am struggling to find them. I just cant seem to find out how to write the commands or find where to even find that information and rather than keep hunting for ages or continuing trial and error, maybe this is more practical? I keep making commands i think are what i want in gui, then converting them to jass to see how they work and im just making no progress.

Below is currently one of the generic tables I've worked on, pulled from Itempools Guide with very minimal modifications.

JASS:
function Trig_Neutral_Hostile_Drop_Table1_Actions takes nothing returns nothing
 local unit u       = GetDyingUnit()
 local real ux      = GetUnitX(u)
 local real uy      = GetUnitY(u)
 local itempool ip  = CreateItemPool()

//===========================================================================
// Generic Table 1
//===========================================================================
 
    call ItemPoolAddItemType(ip, 'I007', 1000) // 5gp    Chance:
    call ItemPoolAddItemType(ip, 'I008', 1000) // 7gp    Chance:
    call ItemPoolAddItemType(ip, 'I009', 750)  // 10gp   Chance:
    call ItemPoolAddItemType(ip, 'I00A', 500)  // 12gp   Chance:
    call ItemPoolAddItemType(ip, 'I00B', 250)  // 15gp   Chance:
    call ItemPoolAddItemType(ip, 'I00C', 150)  // 20gp   Chance:
    call ItemPoolAddItemType(ip, 'I00D', 100)  // 25gp   Chance:
    call ItemPoolAddItemType(ip, 'I00E', 75)   // 50gp   Chance:
    call ItemPoolAddItemType(ip, 'I00F', 50)   // 75gp   Chance:
    call ItemPoolAddItemType(ip, 'I00G', 25)   // 100gp  Chance:
    call ItemPoolAddItemType(ip, 'I00H', 5)    // 150gp  Chance:
    call ItemPoolAddItemType(ip, 'I00I', 2)    // 250gp  Chance:
    call ItemPoolAddItemType(ip, 'I00J', 1)    // 500gp  Chance:
    call ItemPoolAddItemType(ip, 'I00K', 0)    // 1000gp Chance:
    call ItemPoolAddItemType(ip, 'I00L', 0)    // 2000gp Chance:
    call ItemPoolAddItemType(ip, 'I00M', 0)    // 5000gp Chance:
    call ItemPoolAddItemType(ip, 'pghe', 50)   // Chance:
    call ItemPoolAddItemType(ip, 'whwd', 75)   // Chance:
    call ItemPoolAddItemType(ip, 'hlst', 50)   // Chance:
    call ItemPoolAddItemType(ip, 'phea', 150)  // Chance:
    call ItemPoolAddItemType(ip, 'pman', 200)  // Chance:
    call PlaceRandomItem(ip,ux,uy)
    call DestroyItemPool(ip)

 set ip = null
 set u  = null
endfunction

//===========================================================================
function InitTrig_Neutral_Hostile_Drop_Table1 takes nothing returns nothing
    set gg_trg_Neutral_Hostile_Drop_Table1 = CreateTrigger(  )
    call TriggerRegisterPlayerUnitEventSimple( gg_trg_Neutral_Hostile_Drop_Table1, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddAction( gg_trg_Neutral_Hostile_Drop_Table1, function Trig_Neutral_Hostile_Drop_Table1_Actions )
endfunction

Im working on taking a similar template to use for a creep pool since they seem to use most the same commands.

Now for what I'm getting at, my target idea or current iteration I'm trying to implement that i need some help with finding information on. What my end goal looks like in steps:

1. Creep dies
2. Check region
3. Generate number between 1 and X.
4. If number is less than/= Y, continue, if above end.
5. If X is some function involving level of creep. perhaps X = A - (lvl * lvl) so higher level creeps are weighted much heavier. if max level is 100 and A is 12,000. Y can be 2,000 so a level 100 creep always procs but a level 1 has a large chance not to. values are just dummy for this and id balance them later.
6. If successful then roll another value and this determines which table to roll.
7. Roll that table.

Another system is the respawn. Inside these larger regions, ill create regions for creep spawn. Perhaps triggered off the time of day where it checks if any units owned by neutral hostile are in that region, and if not creates some amount based on the creep spawn region, but it rolls which creep to spawn on a table similar to the drops so i can weight some rarer or higher level spawns. These would be a lot more straightforward and i can probably figure these out.

Any advice or resources you can point me to that will assist with information involving these is greatly appreciated. If something I'm asking isnt quite clear, ask for clarification and ill provide it asap. Or if you think there's a glaring issue with a system like this that my ignorance is missing please let me know and I can alter the plan. Also this doesn't need to be done in Jass if gui is capable of it. Thanks for taking the time to read.
 
Last edited:
Level 45
Joined
Feb 27, 2007
Messages
5,578
Another system is the respawn. Inside these larger regions, ill create regions for creep spawn. Perhaps triggered off the time of day where it checks if any units owned by neutral hostile are in that region, and if not creates some amount based on the creep spawn region, but it rolls which creep to spawn on a table similar to the drops so i can weight some rarer or higher level spawns.
This is a fine solution but has one caveat that might make a tweak necessary. What should happen if a creep leaves the zone it's supposed to be in by being kited or otherwise moved? If it should be counted toward the new zone's total then you're fine. If it should be counted toward the original zone it spawned in you should use a group array to keep track of the units spawned for each region (which could also be an array, or you could use a struct to hold both). Then check how many dead units are in each group and remove them before spawning replacements (or remove them on unit death and just count each group).

If the regions are large, how do you intended to determine where to spawn units?
Also this doesn't need to be done in Jass if gui is capable of it.
Item/unitpools are inaccessible in GUI so you could do some of it with GUI. I think it behooves you to learn more JASS, since it sounds like you have some experience with other languages (JASS is very simple and vastly more flexible/convenient than GUI).

You are using call DestroyItemPool(ip) immediately after setting up the itempool, which is counterproductive. The idea is you create and set up the pool object, then tell it to place a random item when/where desired. Other than that your setup is reasonable. Adding items with 0 weight is meaningless so while it doesn't hurt anything it also doesn't do anything. You could define some custom function that adds the same 'basic' items to all pools so you wouldn't have to copy/paste it if you ever make changes to the different pools:
JASS:
function AddBasicItems takes itempool ip returns nothing
    call ItemPoolAddItemType(ip, 'I007', 1000) // 5gp    Chance:
    call ItemPoolAddItemType(ip, 'I008', 1000) // 7gp    Chance:
    call ItemPoolAddItemType(ip, 'I009', 750)  // 10gp   Chance:
    call ItemPoolAddItemType(ip, 'I00A', 500)  // 12gp   Chance:
    call ItemPoolAddItemType(ip, 'I00B', 250)  // 15gp   Chance:
    call ItemPoolAddItemType(ip, 'I00C', 150)  // 20gp   Chance:
    call ItemPoolAddItemType(ip, 'I00D', 100)  // 25gp   Chance:
    call ItemPoolAddItemType(ip, 'I00E', 75)   // 50gp   Chance:
    call ItemPoolAddItemType(ip, 'I00F', 50)   // 75gp   Chance:
    call ItemPoolAddItemType(ip, 'I00G', 25)   // 100gp  Chance:
    call ItemPoolAddItemType(ip, 'I00H', 5)    // 150gp  Chance:
    call ItemPoolAddItemType(ip, 'I00I', 2)    // 250gp  Chance:
    call ItemPoolAddItemType(ip, 'I00J', 1)    // 500gp  Chance:
    call ItemPoolAddItemType(ip, 'I00K', 0)    // 1000gp Chance:
    call ItemPoolAddItemType(ip, 'I00L', 0)    // 2000gp Chance:
    call ItemPoolAddItemType(ip, 'I00M', 0)    // 5000gp Chance:
endfunction

//elsewhere...
  local itempool ip  = CreateItemPool()
  call AddBasicItems(ip)
  call ItemPoolAddItemType(ip, 'pghe', 50)   // Chance:
  call ItemPoolAddItemType(ip, 'whwd', 75)   // Chance:
  call ItemPoolAddItemType(ip, 'hlst', 50)   // Chance:
  call ItemPoolAddItemType(ip, 'phea', 150)  // Chance:
  call ItemPoolAddItemType(ip, 'pman', 200)  // Chance:
1. Creep dies
2. Check region
3. Generate number between 1 and X.
4. If number is less than/= Y, continue, if above end.
5. If X is some function involving level of creep. perhaps X = A - (lvl * lvl) so higher level creeps are weighted much heavier. if max level is 100 and A is 12,000. Y can be 2,000 so a level 100 creep always procs but a level 1 has a large chance not to. values are just dummy for this and id balance them later.
6. If successful then roll another value and this determines which table to roll.
7. Roll that table.
These are all relatively easy things to do, though the region check is unintuitive if you're not using groups. There is no function like GetTriggeringRegion() so you must check to see if every one of the regions that could cause the trigger to fire contains the triggering unit (or use a different trigger for each region, which sucks). This is why an array is helpful: you can walk through its used indices and check all the regions in order. But there's another catch: in JASS you have direct access to rects and regions, where GUI has a blur of both to obfuscate that rects exist. So what's the difference?

Rects are rectangular areas of the ground. They are a single rectangle, as the GUI regions appear to be. Regions are collections of rects that can all work together for the region events; by grouping them you can make multiple areas have the same behavior, as well as create more complex shapes than just rectangles. A particular rect, then could be part of multiple regions if you wanted it to be. This actually gives you a lot of flexibility in designing the creep areas; regions created in the wc3 editor's region palette are really rects under-the-hood that it converts to regions before adding to enter/leave events and checks and stuff. Finally there is this current bug with RegionAddRect which is necessary to turn rects into regions (and even GUI uses this) which you may or may not care about (it grabs an extra 32 units past the top and right edges of the 'area' added to the region):
JASS:
@bug An extra row and column of cells in positive x and y direction is added. E.g., if the rect is
[minX=0, minY=0, maxX=96, maxY=96], the region will gain cells spanning the area [minX=0, minY=0, maxX=128, maxY=128].
This is reflected in `IsPointInRegion`, `IsUnitInRegion`, `TriggerRegisterEnterRegion` and
`TriggerRegisterLeaveRegion`. It still respects the map bounds.
JASS:
globals
  region array CREEP_REGIONS
  rect array CREEP_RECTS
  group array CREEP_GROUPS //if you are using the groups method
  integer R_COUNT = 0
endglobals

function CallThisFunctionOnMapInit takes nothing returns nothing
  local region rgn
  local rect rct

  set rct = gg_rct_YourRectNameGoesHere //you'll have to type these all in manually to match your region names (and if they ever change you'll have to update them)
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1 //this starts indexing from 1, but you could start at 0 too if you're comfortable with that
  set CREEP_RECTS[R_COUNT] = rct //because enum units in rect requires a rect not a unit
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null) //I think it's okay to pass null boolexpr here now? if not you can use a generic boolean function that returns true

  set rct = gg_rct_AnotherRect
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1
  set CREEP_RECTS[R_COUNT] = rct
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null)

  set rct = gg_rct_RectopolisCity
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1
  set CREEP_RECTS[R_COUNT] = rct
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null)

  set rgn = null
  set rct = null
  //...
  //After all of this R_COUNT will always be the index of the last rect!
endfunction

function WhichRegionDidThisCreepDieIn takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > R_COUNT
    if IsUnitInRegion(u, CREEP_REGIONS[i]) then
      return i
    endif
  endloop

  return 0
endfunction
By keeping the units in groups you avoid having to deal with a lot of that bologna, so to do it with only groups:
JASS:
globals
  group array CREEP_GROUPS //if you are using the groups method
  integer R_COUNT = 0
endglobals

function CallThisFunctionOnMapInit takes nothing returns nothing
  local rect rct

  set rct = gg_rct_YourRectNameGoesHere
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = gg_rct_AnotherRect
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = gg_rct_RectopolisCity
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = null
  //...
  //After all of this R_COUNT will always be the index of the last rect!
endfunction

function WhichRegionDidThisCreepDieIn takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > R_COUNT
    if IsUnitInGroup(u, CREEP_GROUPS[i]) then
      return i
    endif
  endloop

  return 0
endfunction
Now the rest is quite simple. Use GetRandomReal/Int to randomize numbers and check creep level with GetUnitLevel, then decide if something is to be rolled. If so, pick from one of the itempools and place an item:
JASS:
globals
  itempool array DROP_POOLS //you make and populate these yourself elsewhere
  integer DP_COUNT //the index of the last itempool you make in the DROP_POOLS array
endglobals

function CreepItemSpawnMaybe takes unit u, boolean giveItem returns item
  local integer lvl = GetUnitLevel(u)
  local integer A = 12000
  local integer X = A - lvl*lvl
  local integer Y = 2000
  local item it = null

  if GetRandomInt(1, X) > Y then //this doesn't work as you described above (it's actually less chance at higher level), but thats for you to experiment with
    set it = PlaceRandomItem(DROP_POOLS[GetRandomInt(1, DP_COUNT)], GetUnitX(u), GetUnitY(u))
    if giveItem then
      call UnitAddItem(u, it)
    endif
  endif

  return it
endfunction
 
Last edited:
Level 3
Joined
Jun 6, 2024
Messages
13
This is a fine solution but has one caveat that might make a tweak necessary. What should happen if a creep leaves the zone it's supposed to be in by being kited or otherwise moved? If it should be counted toward the new zone's total then you're fine. If it should be counted toward the original zone it spawned in you should use a group array to keep track of the units spawned for each region (which could also be an array, or you could use a struct to hold both). Then check how many dead units are in each group and remove them before spawning replacements (or remove them on unit death and just count each group).

If the regions are large, how do you intended to determine where to spawn units?

Item/unitpools are inaccessible in GUI so you could do some of it with GUI. I think it behooves you to learn more JASS, since it sounds like you have some experience with other languages (JASS is very simple and vastly more flexible/convenient than GUI).

You are using call DestroyItemPool(ip) immediately after setting up the itempool, which is counterproductive. The idea is you create and set up the pool object, then tell it to place a random item when/where desired. Other than that your setup is reasonable. Adding items with 0 weight is meaningless so while it doesn't hurt anything it also doesn't do anything. You could define some custom function that adds the same 'basic' items to all pools so you wouldn't have to copy/paste it if you ever make changes to the different pools:
JASS:
function AddBasicItems takes itempool IP returns nothing
    call ItemPoolAddItemType(ip, 'I007', 1000) // 5gp    Chance:
    call ItemPoolAddItemType(ip, 'I008', 1000) // 7gp    Chance:
    call ItemPoolAddItemType(ip, 'I009', 750)  // 10gp   Chance:
    call ItemPoolAddItemType(ip, 'I00A', 500)  // 12gp   Chance:
    call ItemPoolAddItemType(ip, 'I00B', 250)  // 15gp   Chance:
    call ItemPoolAddItemType(ip, 'I00C', 150)  // 20gp   Chance:
    call ItemPoolAddItemType(ip, 'I00D', 100)  // 25gp   Chance:
    call ItemPoolAddItemType(ip, 'I00E', 75)   // 50gp   Chance:
    call ItemPoolAddItemType(ip, 'I00F', 50)   // 75gp   Chance:
    call ItemPoolAddItemType(ip, 'I00G', 25)   // 100gp  Chance:
    call ItemPoolAddItemType(ip, 'I00H', 5)    // 150gp  Chance:
    call ItemPoolAddItemType(ip, 'I00I', 2)    // 250gp  Chance:
    call ItemPoolAddItemType(ip, 'I00J', 1)    // 500gp  Chance:
    call ItemPoolAddItemType(ip, 'I00K', 0)    // 1000gp Chance:
    call ItemPoolAddItemType(ip, 'I00L', 0)    // 2000gp Chance:
    call ItemPoolAddItemType(ip, 'I00M', 0)    // 5000gp Chance:
endfunction

//elsewhere...
  local itempool ip  = CreateItemPool()
    call AddBasicItems(ip)
    call ItemPoolAddItemType(ip, 'pghe', 50)   // Chance:
    call ItemPoolAddItemType(ip, 'whwd', 75)   // Chance:
    call ItemPoolAddItemType(ip, 'hlst', 50)   // Chance:
    call ItemPoolAddItemType(ip, 'phea', 150)  // Chance:
    call ItemPoolAddItemType(ip, 'pman', 200)  // Chance:

These are all relatively easy things to do, though the region check is unintuitive if you're not using groups. There is no function like GetTriggeringRegion() so you must check to see if every one of the regions that could cause the trigger to fire contains the triggering unit (or use a different trigger for each region, which sucks). This is why an array is helpful: you can walk through its used indices and check all the regions in order. But there's another catch: in JASS you have direct access to rects and regions, where GUI has a blur of both to obfuscate that rects exist. So what's the difference?

Rects are rectangular areas of the ground. They are a single rectangle, as the GUI regions appear to be. Regions are collections of rects that can all work together for the region events; by grouping them you can make multiple areas have the same behavior, as well as create more complex shapes than just rectangles. A particular rect, then could be part of multiple regions if you wanted it to be. This actually gives you a lot of flexibility in designing the creep areas; regions created in the wc3 editor's region palette are really rects under-the-hood that it converts to regions before adding to enter/leave events and checks and stuff. Finally there is this current bug with RegionAddRect which is necessary to turn rects into regions (and even GUI uses this) which you may or may not care about (it grabs an extra 32 units past the top and right edges of the 'area' added to the region):
JASS:
@bug An extra row and column of cells in positive x and y direction is added. E.g., if the rect is
[minX=0, minY=0, maxX=96, maxY=96], the region will gain cells spanning the area [minX=0, minY=0, maxX=128, maxY=128].
This is reflected in `IsPointInRegion`, `IsUnitInRegion`, `TriggerRegisterEnterRegion` and
`TriggerRegisterLeaveRegion`. It still respects the map bounds.
JASS:
globals
  region array CREEP_REGIONS
  rect array CREEP_RECTS
  group array CREEP_GROUPS //if you are using the groups method
  integer R_COUNT = 0
endglobals

function CallThisFunctionOnMapInit takes nothing returns nothing
  local region rgn
  local rect rct

  set rct = gg_rct_YourRectNameGoesHere //you'll have to type these all in manually to match your region names (and if they ever change you'll have to update them)
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1 //this starts indexing from 1, but you could start at 0 too if you're comfortable with that
  set CREEP_RECTS[R_COUNT] = rct //because enum units in rect requires a rect not a unit
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null) //I think it's okay to pass null boolexpr here now? if not you can use a generic boolean function that returns true

  set rct = gg_rct_AnotherRect
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1
  set CREEP_RECTS[R_COUNT] = rct
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null)

  set rct = gg_rct_RectopolisCity
  set rgn = CreateRegion()
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) //fixing the rect for the bugged region add
  call RegionAddRect(rgn, rct)
  set R_COUNT = R_COUNT + 1
  set CREEP_RECTS[R_COUNT] = rct
  set CREEP_REGIONS[R_COUNT] = rgn
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], CREEP_RECTS[R_COUNT], null)

  set rgn = null
  set rct = null
  //...
  //After all of this R_COUNT will always be the index of the last rect!
endfunction

function WhichRegionDidThisCreepDieIn takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > R_COUNT
    if IsUnitInRegion(u, CREEP_REGIONS[i]) then
      return i
    endif
  endloop

  return 0
endfunction
By keeping the units in groups you avoid having to deal with a lot of that bologna, so to do it with only groups:
JASS:
globals
  group array CREEP_GROUPS //if you are using the groups method
  integer R_COUNT = 0
endglobals

function CallThisFunctionOnMapInit takes nothing returns nothing
  local rect rct

  set rct = gg_rct_YourRectNameGoesHere
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = gg_rct_AnotherRect
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = gg_rct_RectopolisCity
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null)

  set rct = null
  //...
  //After all of this R_COUNT will always be the index of the last rect!
endfunction

function WhichRegionDidThisCreepDieIn takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > R_COUNT
    if IsUnitInGroup(u, CREEP_GROUPS[i]) then
      return i
    endif
  endloop

  return 0
endfunction
Now the rest is quite simple. Use GetRandomReal/Int to randomize numbers and check creep level with GetUnitLevel, then decide if something is to be rolled. If so, pick from one of the itempools and place an item:
JASS:
globals
  itempool array DROP_POOLS //you make and populate these yourself elsewhere
  integer DP_COUNT //the index of the last itempool you make in the DROP_POOLS array
endglobals

function CreepItemSpawnMaybe takes unit u, boolean giveItem returns item
  local integer lvl = GetUnitLevel(u)
  local integer A = 12000
  local integer X = A - lvl*lvl
  local integer Y = 2000
  local item it = null

  if GetRandomInt(1, X) > Y then //this doesn't work as you described above (it's actually less chance at higher level), but thats for you to experiment with
    set it = PlaceRandomItem(DROP_POOLS[GetRandomInt(1, DP_COUNT)], GetUnitX(u), GetUnitY(u))
    if giveItem then
      call UnitAddItem(u, it)
    endif
  endif

  return it
endfunction
First off thanks, this is all very helpful.

On the first note about regions, the idea is that ill have some large region that denotes an area and this will likely be what I use to setup the itempools, then ill have smaller boxes the size of camps. Although I'm toying with the idea of having larger spawn areas with random placement so there's variance there as well. like a pseudo maze but quadrants of it are a zone that spawn units. Maybe you walk around and they are fairly evenly spread 1 by 1, or maybe some of them happen to be in a cluster. That seems appealing to me, but I need to try both and see them in practice. My idea was to have it check at a time of day if the smaller box contains any hostile units, if not then proc the spawn table for the zone. If it happens to be that time of day when I'm luring the spawn or something and they're outside of the box, that's not the worst thing to happen. I can think of a few ways to handle it, but I think its a fairly minimal issue if a camp manages to get a lucky/unluck extra boost. It'll self correct the next clear. If it becomes an issue ill find a resolution.

The next section about items was all very useful. The 0 weight was to have a placeholder for later itempools so i didn't need to add or remove items, but it sounds like i can make a table to populate the item pools and that's a lot more practical for tweaks later on. The code there is almost directly copied from the link i sent, so things like the destroying of the pool after use i assumed had a purpose like avoiding leaks and if it isn't broke...

As far as the use of rects and regions thats something ill have to take into consideration so thanks for all that.

if GetRandomInt(1, X) > Y then //this doesn't work as you described above
Yeah, I meant < Y. My bad.

But seriously, I cant thank you enough. This was above and beyond what I expected and solves a lot of my issues. Plus seeing different code written out allows me to begin to understand how it all works and ill pick it up fairly quickly. Ill get a lot of this implemented tomorrow, I hope, or over the next few days.
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
I agree with your commentary about regions and camps. Try a bit and see what works; you can tweak when you have the structure in place. A larger rect for the overall items and smaller rects for each camp area is fine; the overlap should cause no issues as long as you don't use the big one to check to respawn units.
The next section about items was all very useful. The 0 weight was to have a placeholder for later itempools so i didn't need to add or remove items, but it sounds like i can make a table to populate the item pools and that's a lot more practical for tweaks later on.
The relative chance of everything in a pool is determined by (the object's weight / total weight of everything in the pool). So adding blank placeholders doesn't affect the chance of anything directly, but it does mean they won't work like placeholders unless they're given a nonzero weight.
the destroying of the pool after use i assumed had a purpose like avoiding leaks
An object is only a memory leak if you do not intend to reuse it later. In the example the pool didn't need to persist because it was temporary and for creating only one object, whereas you are going to create static pools that will place many items/creeps.
As far as the use of rects and regions thats something ill have to take into consideration so thanks for all that.
...
But seriously, I cant thank you enough. This was above and beyond what I expected and solves a lot of my issues. Plus seeing different code written out allows me to begin to understand how it all works and ill pick it up fairly quickly. Ill get a lot of this implemented tomorrow, I hope, or over the next few days.
You'll start to see functions that use rects and some that use regions; eventually it will all make sense to you. I'm happy to help, and happy you were helped. Post questions and someone will filter through to give an explanation eventually! :)
 
Level 3
Joined
Jun 6, 2024
Messages
13
I'm struggling with implementation of all this. If you get a moment, well... several moments as this got quite long... I'd love some clarification on some things? If you don't have time or this is a hassle then no worries, I'm sure ill get it sorted out with time. you've already been very generous with your time and i don't want to impose. Or if you happen to know of a good introduction guide to the syntax used im all for picking it up? Ive read the one on this site, but its not getting into what I need here. This was why i was trying to avoid getting into Jass because I can figure out whats going on in GUI with some common sense and exploration.

I went with the groups since it had a lot less fluff. Here is what I have:
JASS:
function Trig_CreepGroup_Actions takes nothing returns nothing
endfunction

globals
  group array CREEP_GROUPS //if you are using the groups method
  integer R_COUNT = 0
endglobals

function CallThisFunctionOnMapInit takes nothing returns nothing
  local rect rct

  set rct = gg_rct_CeldarinWoodsCreepCamp1
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null) // Index 1

  set rct = gg_rct_CeldarinWoodsCreepCamp2
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null) // Index 2

  //... Removed the 3 here since its just the woods 3-5

  set rct = gg_rct_CeldarinWoodsCreepCamp6
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null) // Index 6

  set rct = gg_rct_NextRegionIHaventMadeYet // Just making this up so you know that i know that the index number keeps going, but the above are actual regions
  call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32)
  set R_COUNT = R_COUNT + 1
  set CREEP_GROUPS[R_COUNT] = CreateGroup()
  call GroupEnumUnitsInRect(CREEP_GROUPS[R_COUNT], rct, null) // Index 7

  set rct = null
  //...
  //After all of this R_COUNT will always be the index of the last rect!
endfunction

function WhichRegionDidThisCreepDieInCG takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > R_COUNT
    if IsUnitInGroup(u, CREEP_GROUPS[i]) then
      return i
    endif
  endloop

  return 0
endfunction
//===========================================================================
function InitTrig_CreepGroup takes nothing returns nothing
    set gg_trg_CreepGroup = CreateTrigger(  )
    call TriggerAddAction( gg_trg_CreepGroup, function Trig_CreepGroup_Actions )
endfunction

What this does is take all the regions I name and gives them an index number which the function "WhichRegionDidThisCreepDieIn" returns when a unit dies. Which leads to the first question, if a unit is in multiple regions, i assume this returns both and if i happened to have a drop trigger for both this would go off for each? I don't plan to overlap any of the regions used to determine drops, but for future debugs and such it would be good to know? Also this creates an index number for a creep that just died, which I can use to spawn a new unit after some amount of time, but this seems to require the creep to die inside the smaller region? My initial idea was to have a function trigger off the time of day to check if regions had creeps and if not, create them from a unit pool. If I need to set a timer and fines the regions to operate this way off a leash range i can, it just wasn't what i was aiming for and perhaps I'm misunderstanding the purpose of this?

Also something that could be absurdly obvious that I'm overlooking is perhaps I'm meant to assign unitpools to an array called CREEP_GROUPS? I feel like a dumbass asking these questions but my minimal coding experience is in C++ and a majority of that is over a decade old.

Then I've been trying to make the DROP_POOLS but I either suck at research or its difficult to find the info for a rookie. I've attempted to copy the group system used for the rects, but I'm unsure if that's even the right approach and if it is i cant find the right commands to fill the blanks to format it. Here is where I'm at with this:
JASS:
function Trig_DropPools_Actions takes nothing returns nothing
endfunction

globals
  group array DROP_POOLS //if you are using the groups method
  integer DP_COUNT = 0
endglobals

function CallThisFunctionOnMapInit2 takes nothing returns nothing
  local rect rct

  set ???(rct) = ???(gg)_???(rct)_BasicDropTable1 (or use the region for drops and link the itempool in another line?)
  //call SetRect(rct, GetRectMinX(rct), GetRectMinY(rct), GetRectMaxX(rct)-32, GetRectMaxY(rct)-32) <- Use this if using a region to fix the bug
  set DP_COUNT = DP_COUNT + 1
  set DROP_POOLS[I_COUNT] = CreateGroup() // Index 1

  set ???(rct) = null
  //...
  //After all of this DP_COUNT will always be the index of the last pool
endfunction

function WhichRegionDidThisCreepDieInDP takes unit u returns integer //not rect return, we want to return the integer so we know which indicies to use.
  local integer i = 0

  loop
    set i = i+1 //indexing starting at 1 again
    exitwhen i > DP_COUNT
    if IsUnitInGroup(u, DROP_POOLS[i]) then
      return i
    endif
  endloop

  return 0
endfunction

//===========================================================================
function InitTrig_DropPools takes nothing returns nothing
    set gg_trg_DropPools = CreateTrigger(  )
    call TriggerAddAction( gg_trg_DropPools, function Trig_DropPools_Actions )
endfunction

the line "set ??? = ???_???_BasicDropTable1" I'm just lost on. I cant find what I should be filling in here or the line below it or the field to null. I have all them denoted by "???" in the code. Or should I be using a region, but then utilize a new line to also link an itempool to these index numbers? If so, which would make sense, how would i go about this?

This set is called on here:
JASS:
function Trig_DropWeighting_Actions takes nothing returns nothing
endfunction
globals
  itempool array DROP_POOLS //you make and populate these yourself elsewhere
  integer DP_COUNT //the index of the last itempool you make in the DROP_POOLS array
endglobals

function CreepItemSpawnMaybe takes unit u, boolean giveItem returns item
  local integer lvl = GetUnitLevel(u)
  local integer A = 12000
  local integer X = A - lvl*lvl
  local integer Y = 2000
  local integer B = 100
  local item it = null

  if GetRandomInt(1, X) < Y then
    set it = PlaceRandomItem(DROP_POOLS[GetRandomInt(1, DP_COUNT)], GetUnitX(u), GetUnitY(u))
    if giveItem then
      call UnitAddItem(u, it)
    endif
  endif

  return it
endfunction
//===========================================================================
function InitTrig_DropWeighting takes nothing returns nothing
    set gg_trg_DropWeighting = CreateTrigger(  )
    call TriggerAddAction( gg_trg_DropWeighting, function Trig_DropWeighting_Actions )
endfunction

I'm also wanting to add a tertiary table that can be rolled for unique items for zones. So "if GetRandomInt(1, x) < Y then" returns true, then I want to roll a 2nd int between 1 and B to determine which table to roll. I keep trying to code this in, but its giving me fits. This is what im trying, but i cant figure out what im doing wrong and the compiler error message isnt any help.
JASS:
function Trig_DropWeighting_Actions takes nothing returns nothing
endfunction

function CreepItemSpawnMaybe takes unit u, boolean giveItem returns item
  local integer lvl = GetUnitLevel(u)
  local integer A = 12000
  local integer X = A - lvl*lvl
  local integer Y = 2000
  local item it = null
  local variable_name = C

  if GetRandomInt(1, X) < Y then
    GetRandomInt(1, B) = C
      if C < 10 then
        set it = PlaceRandomItem(DROP_POOLS[GetRandomInt(1, DP_COUNT)], GetUnitX(u), GetUnitY(u))
        if giveItem then
          call UnitAddItem(u, it)
        endif
      if C >= 10 then
        set it = PlaceRandomItem(DROP_POOLS[GetRandomInt(1, DP_COUNT)], GetUnitX(u), GetUnitY(u))
        if giveItem then
          call UnitAddItem(u, it)
        endif
      endif
    endif
  endif

  return it
endfunction
This is just the latest in a series of trial and error and is likely laughably wrong, but I spent a good deal of time looking for the right forms and its a mess to learn. I was messing with IF and ELSEIF some which i feel is the right way to do it, but I wasnt having any success and kinda went elsewhere to shoot in the dark. I can also just toss these tertiary items onto the pool its already rolling, but that just means making more pools. Not a huge deal, but what I'm trying here seems easier in the long run with more ability to tune.

Im also struggling to grapple with all this new info on a macro level. So I'm going to try to layout what I believe is the idea, and if I'm wrong give me nudge?

On game initilization:
1. It clumps regions into rects and indexes them.
2. It creates indexes from itempools ive made? or maybe indexes more rects and links itempools to them?
When a unit dies:
1. It checks the regions the creep died in and returns those index numbers for the CREEP_POOLS. Not sure why its doing this?
2. It checks the regions the creep died in and returns those index numbers for the DROP_POOLS (which means my grouping funciton for indexing itempools needs to link a rect with a DROP_POOL? This is potentially the solution to my quesion there as it would be a close copy of the initial grouping for rects for creeps and it would index them that way. still lacks a way to link the itempool to the index however.)
3. This triggers my "DropWeighting" function which pulls from the DROP_POOL index number generated as a creep died.

I think this all checks out bar some minor details i mentioned, but one thing im somewhat lost on is how to link a unitpool to the CREEP_POOLS index so i can make a function to trigger off the time of day to respawn units in that rect? I touched on this earlier. If i need to trigger a delayed respawn here that uses a unitpool i can. It just leaves the possibility of losing spawns over time if they die outside the respawn check and im trying to avoid that. maybe set a hard location for a respawn using a single rect? It takes the variance away I mentioned in my former replies, but if it removes a shitload of work I'd settle.

I hope all this makes sense if you got this far. If you did thanks for the time again.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
If I understand correctly, you don't want to check which Region the unit died in, you only care about which Region it initially belonged to.

Here's my take on it, excluding the Item Pool stuff which could be merged and handled in the CreepDies function:
vJASS:
library CreepManager initializer Init

    globals
        private integer Region_Index
        private trigger Creep_Dies_Trigger
        private group array Creep_Groups
        private rect array Creep_Regions
        private integer array Creep_Assigned_Region
    endglobals

    private function DoOtherStuffBasedOnDyingCreepsRegion takes unit u, integer region_index returns nothing
        // do some other stuff like respawn it in it's original region
        // or use it's region index to determine it's loot table, etc
    endfunction

    private function CreepDies takes nothing returns nothing
        // this relies on unit indexing to get the region index
        local unit u = GetTriggerUnit()
        local integer cv = GetUnitUserData(u)
        local integer region_index = Creep_Assigned_Region[cv]
 
        if region_index > 0 then
            // we now have the dead creep's region index and can do whatever we want with it
            // for now I assume we want to remove it from it's unit group
            // then run some other logic that varies based on the region it belonged to
            call GroupRemoveUnit(Creep_Groups[region_index], u)
            call DoOtherStuffBasedOnDyingCreepsRegion(u, region_index)
        endif

        set u = null
    endfunction

    private function RegisterCreep takes nothing returns nothing
        // this relies on unit indexing to pair the region index to the unit
        local unit u = GetEnumUnit()
        local integer cv = GetUnitUserData(u)

        // add the unit to it's region and make it track the region's index
        call GroupAddUnit(Creep_Groups[Region_Index], u)
        set Creep_Assigned_Region[cv] = Region_Index

        set u = null
    endfunction

    function CreepManagerRegisterCreeps takes nothing returns nothing
        // initialize all previously set regions
        set Region_Index = 1
        loop
            set Creep_Groups[Region_Index] = CreateGroup()
            call GroupEnumUnitsInRect(Creep_Groups[Region_Index], Creep_Regions[Region_Index], function RegisterCreep)
            set Region_Index = Region_Index + 1
            exitwhen Creep_Regions[Region_Index] == null
        endloop

        // create a trigger for detecting when creeps die
        set Creep_Dies_Trigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(Creep_Dies_Trigger, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddAction(Creep_Dies_Trigger, function CreepDies)
    endfunction

    private function Init takes nothing returns nothing
        // set all regions
        set Creep_Regions[1] = gg_rct_CeldarinWoodsCreepCamp1
        set Creep_Regions[2] = gg_rct_CeldarinWoodsCreepCamp2
        set Creep_Regions[3] = gg_rct_CeldarinWoodsCreepCamp3
    endfunction

endlibrary
So this code simplifies the Region setup process and pairs your Creeps to their Regions by taking advantage of Unit Indexing. You should be using Unit Indexing in your map if you're not already, it's very lightweight and enables many possibilities. It's basically a simplified Hashtable, easier to work with and better for simple data pairings (linking a unit to another variable). However, because we need to wait for the Unit Indexer to initialize, it means we have to delay my above code slighty:
  • Events
    • Time - Elapsed game time is 0.01 seconds
  • Conditions
  • Actions
    • Custom script: call CreepManagerRegisterCreeps()
I suggest this change because it's highly inefficient to loop over X Unit Groups checking if any of them contain your dead Unit. There's no need to do that when the Unit itself could already know which Unit Group it belongs to and get that data immediately. But again, this only makes sense if you care about the unit's initial "spawn" region and not the region that it physically died in.


2. It checks the regions the creep died in and returns those index numbers for the DROP_POOLS (which means my grouping funciton for indexing itempools needs to link a rect with a DROP_POOL?
For your troubles with the Item Pool stuff, I imagine you want to be able to assign ANY of the Item Pools to a Region, not just 1 to X. For example, let's say you have 8 Item Pools in total, all different from one another, and you want CeldarinWoodsCreepCamp3 to drop a random item from pool #2, 4, and 7. This means it excludes pools 1, 3, 5, 6, and 8.

To achieve this you could use a Hashtable where the Parent Key represents your [indexes] for each Region, the Child Keys represent a basic Array setup where you start at 1 and work your way up, and the values saved at these Keys will represent the Item Pools (or rather their [index] within DROP_POOLS):
vJASS:
// in this example I'm saving the DROP_POOLS for CeldarinWoodsCreepCamp3
local integer region_index = 3

// first save how many item pools this region uses. In this example I will save three item pools
call SaveInteger( Drop_Pool_Hashtable, region_index, 0, 3 )

// next save the DROP_POOLS index of each item pool it uses. In this example I will save item pools two, four, and seven
call SaveInteger( Drop_Pool_Hashtable, region_index, 1, 2 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 2, 4 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 3, 7 )
So now CeldarinWoodsCreepCamp3 (aka region_index 3) is linked to the item pools (or rather their respective DROP_POOLS index) and as long as you have a region's index you will also have the various item pools that it uses.

Now we can easily Load this data to create a random item from one these pools:
vJASS:
// get info about the unit that died
local unit u = GetDyingUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)

// get it's region index using unit indexing
local integer cv = GetUnitUserData(u)
local integer region_index = Creep_Assigned_Region[cv]

// get how many item pools we saved to this region and then get a random child key from within this range
local integer pool_count = LoadInteger(Drop_Pool_Hashtable, region_index, 0)
local integer pool_random_key = GetRandomInt(1, pool_count)

// get the actual DROP_POOLS index by loading data saved at the random child key
local integer drop_pool_index = LoadInteger(Drop_Pool_Hashtable, region_index, pool_random_key)

// create a random item using this index
local item it = PlaceRandomItem(DROP_POOLS[drop_pool_index], x, y)
Hopefully this makes sense. We're basically creating a 2d Array where the first index represents the Region Number and the second index represents a value starting at 1 and working it's way up. The values being stored at these indices are not the actual Item Pools themselves but rather it's index within DROP_POOLS, but that's one step away from being the same thing.
 

Attachments

  • CreepManager.w3m
    23.7 KB · Views: 4
Last edited:
Level 3
Joined
Jun 6, 2024
Messages
13
If I understand correctly, you don't want to check which Region the unit died in, you only care about which Region it initially belonged to.

Here's my take on it, excluding the Item Pool stuff which could be merged and handled in the CreepDies function:
vJASS:
library CreepManager initializer Init

    globals
        private integer Region_Index
        private trigger Creep_Dies_Trigger
        private group array Creep_Groups
        private rect array Creep_Regions
        private integer array Creep_Assigned_Region
    endglobals

    private function DoOtherStuffBasedOnDyingCreepsRegion takes unit u, integer region_index returns nothing
        // do some other stuff like respawn it in it's original region
        // or use it's region index to determine it's loot table, etc
    endfunction

    private function CreepDies takes nothing returns nothing
        // this relies on unit indexing to get the region index
        local unit u = GetTriggerUnit()
        local integer cv = GetUnitUserData(u)
        local integer region_index = Creep_Assigned_Region[cv]
 
        if region_index > 0 then
            // we now have the dead creep's region index and can do whatever we want with it
            // for now I assume we want to remove it from it's unit group
            // then run some other logic that varies based on the region it belonged to
            call GroupRemoveUnit(Creep_Groups[region_index], u)
            call DoOtherStuffBasedOnDyingCreepsRegion(u, region_index)
        endif

        set u = null
    endfunction

    private function RegisterCreep takes nothing returns nothing
        // this relies on unit indexing to pair the region index to the unit
        local unit u = GetEnumUnit()
        local integer cv = GetUnitUserData(u)

        // add the unit to it's region and make it track the region's index
        call GroupAddUnit(Creep_Groups[Region_Index], u)
        set Creep_Assigned_Region[cv] = Region_Index

        set u = null
    endfunction

    function CreepManagerRegisterCreeps takes nothing returns nothing
        // initialize all previously set regions
        set Region_Index = 1
        loop
            set Creep_Groups[Region_Index] = CreateGroup()
            call GroupEnumUnitsInRect(Creep_Groups[Region_Index], Creep_Regions[Region_Index], function RegisterCreep)
            set Region_Index = Region_Index + 1
            exitwhen Creep_Regions[Region_Index] == null
        endloop

        // create a trigger for detecting when creeps die
        set Creep_Dies_Trigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(Creep_Dies_Trigger, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddAction(Creep_Dies_Trigger, function CreepDies)
    endfunction

    private function Init takes nothing returns nothing
        // set all regions
        set Creep_Regions[1] = gg_rct_CeldarinWoodsCreepCamp1
        set Creep_Regions[2] = gg_rct_CeldarinWoodsCreepCamp2
        set Creep_Regions[3] = gg_rct_CeldarinWoodsCreepCamp3
    endfunction

endlibrary
So this code simplifies the Region setup process and pairs your Creeps to their Regions by taking advantage of Unit Indexing. You should be using Unit Indexing in your map if you're not already, it's very lightweight and enables many possibilities. It's basically a simplified Hashtable, easier to work with and better for simple data pairings (linking a unit to another variable). However, because we need to wait for the Unit Indexer to initialize, it means we have to delay my above code slighty:
  • Events
    • Time - Elapsed game time is 0.01 seconds
  • Conditions
  • Actions
    • Custom script: call CreepManagerRegisterCreeps()
I suggest this change because it's highly inefficient to loop over X Unit Groups checking if any of them contain your dead Unit. There's no need to do that when the Unit itself could already know which Unit Group it belongs to and get that data immediately. But again, this only makes sense if you care about the unit's initial "spawn" region and not the region that it physically died in.



For your troubles with the Item Pool stuff, I imagine you want to be able to assign ANY of the Item Pools to a Region, not just 1 to X. For example, let's say you have 8 Item Pools in total, all different from one another, and you want CeldarinWoodsCreepCamp3 to drop a random item from pool #2, 4, and 7. This means it excludes pools 1, 3, 5, 6, and 8.

To achieve this you could use a Hashtable where the Parent Key represents your [indexes] for each Region, the Child Keys represent a basic Array setup where you start at 1 and work your way up, and the values saved at these Keys will represent the Item Pools (or rather their [index] within DROP_POOLS):
vJASS:
// in this example I'm saving the DROP_POOLS for CeldarinWoodsCreepCamp3
local integer region_index = 3

// first save how many item pools this region uses. In this example I will save three item pools
call SaveInteger( Drop_Pool_Hashtable, region_index, 0, 3 )

// next save the DROP_POOLS index of each item pool it uses. In this example I will save item pools two, four, and seven
call SaveInteger( Drop_Pool_Hashtable, region_index, 1, 2 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 2, 4 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 3, 7 )
So now CeldarinWoodsCreepCamp3 (aka region_index 3) is linked to the item pools (or rather their respective DROP_POOLS index) and as long as you have a region's index you will also have the various item pools that it uses.

Now we can easily Load this data to create a random item from one these pools:
vJASS:
// get info about the unit that died
local unit u = GetDyingUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)

// get it's region index using unit indexing
local integer cv = GetUnitUserData(u)
local integer region_index = Creep_Assigned_Region[cv]

// get how many item pools we saved to this region and then get a random child key from within this range
local integer pool_count = LoadInteger(Drop_Pool_Hashtable, region_index, 0)
local integer pool_random_key = GetRandomInt(1, pool_count)

// get the actual DROP_POOLS index by loading data saved at the random child key
local integer drop_pool_index = LoadInteger(Drop_Pool_Hashtable, region_index, pool_random_key)

// create a random item using this index
local item it = PlaceRandomItem(DROP_POOLS[drop_pool_index], x, y)
Hopefully this makes sense. We're basically creating a 2d Array where the first index represents the Region Number and the second index represents a value starting at 1 and working it's way up. The values being stored at these indices are not the actual Item Pools themselves but rather it's index within DROP_POOLS, but that's one step away from being the same thing.

My inital idea was to have a region thats large, in the examples case "CeldarinWoodsDrops" is it. This would be large and the creeps wont have the ability to leave this region. Or if geometry demands id just mesh several of these together as "CeWoDr1, 2, 3, etc." but they would cover the whole "dungeon" region of the map. Then inside the Celdarin Woods I'd have nooks that have smaller regions like the "CeldarinWoodsCreepCamp1, 2, 3, etc." Initially I was going to use the time of day to check if those smaller regions contained a unit owned by hostile and if not, trigger some number of respawns within this region from a unitpool, but a different trigger is also fine. I just know that sometimes "on death" triggers are shotty since it seems there are different kinds of deaths? That seemed like a solid safety net that cant fail. Maybe "CeldarinWoodsCreepCamp3" spawns from a unitpool of the same name. This allows me to weight the spawns, which gives some variance to leveling as the camps wont always be the same. I also wanted it to place these units within the smaller regions randomly, but if I need to designate specific spots that's also fine. It was just adding to the variance and allowed me to play with things like placing the region on both sides of a wall. Maybe, unless it might try to spawn creeps inside of trees or in unpassable terrain and cause problems. This last part isnt vital at all, just a bonus.

Then as a unit dies, it checks those big regions. To those i would have 2 tables assigned, but the system would do something like"
1. Unit dies
2. Check drop region
3. Roll random int weighted based on the units level so higher levels drop more often.
4. If this returns false, do nothing and end.
5. If this returns true, roll a 2nd random int.
6. If value is >= X, drop from a generic itempool.
7. If value is < X, drop from tertiary itempool
//---- Later....
8. Time of day hits X
9. Checks smaller regions to see if a unit owned by hostile is in them
10. If true, do nothing.
11. If false create some number of units from an assigned unitpool.

This is essentially the bread and butter system of the map and once i have this figured out, I think I can cruise to end and setup the areas and systems. I've already messed with quests, some boss encounters, and all that using GUI and it seems like I'm able to figure it all out. Its just this JASS section thats killing me since I had 0 experience. 7 and 5 could be chopped if I just add the tertiary items to the generic pool and weight them appropriately, it just means I'd need to make 2 or more generic pools for each time I'd use a new tertiary table. Might be about the same work at the end of the day, I'm honestly not sure, but it seemed ideal when i was thinking about it before. I also was going to make a generic pool with all the items i would ever place on it, so I can copy and paste it each time then weight it as needed. Might not be the best approach, idk.

Now for what your suggesting, ill try to explain what i believe is going on.

If i understand what the first chunk is doing, its taking all the creeps on the map and giving them an index number based on the region they are in. This data is then stored, and when that unit dies, this specific index is called which is then used later to tell it directly which table to go to rather than "search for this table." Its always a bit over my head, but i think i get the premise? My concern with this is on a units creation, does it still contain this index? If so this works how i was wanting it to, just a different route and that's fine with me.

I also can use some property within the editor to manually index them? Perhaps the "suffix" field? creep camp 1 is suffix 1, etc., which when the unit dies pulls that data field and sends it to the appropriate drop table? Probably makes more work, but I could do it that way? Makes me copy units of the same kind if they are using different tables, but i don't think that's often.

The next chunk in GUI is just triggering the first to run right after the game starts.

The 3rd chunk is setting specific item pools onto a 2d array/grid to determine drops. Where in simple terms 1 axis is the the region and the other is various itempools assigned to it. I think id need 2 at most for what I'm doing, but this approach could work just fine. I think its something I could use more directly for what i intended above? If i make a table like this that contains 1 axis/parent key that tells it directly which set to roll, then I use rng to determine which of the child keys to use, this accomplishes the rolls I was talking about? I think? I'm hesitant to say I've got it until I've actually got it working because the other response I got made sense until I was putting it together. Its also like someone explaining how to say something in a foreign language and it makes sense till you're speaking it to the natives and they haven't got a clue what you're saying.

And I want to end by thanking you for your time as well. Its good to see a different approach to the same thing and the more code i read over the more I begin to grasp what's happening.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
If i understand what the first chunk is doing, its taking all the creeps on the map and giving them an index number based on the region they are in. This data is then stored, and when that unit dies, this specific index is called which is then used later to tell it directly which table to go to rather than "search for this table." Its always a bit over my head, but i think i get the premise? My concern with this is on a units creation, does it still contain this index? If so this works how i was wanting it to, just a different route and that's fine with me.
Yes, you're correct about the index number based on the region. But you would need to assign the Index again upon creating a new unit since it has no relationship with the previous unit or it's data. However, you could begin this "respawning" process in response to the unit dying, meaning you would still have access to the correct Index which you could pass along to the new unit. Maybe something like: A unit dies -> Create new replacement unit and assign it the region data -> Hide it -> Unhide at a later time when it officially "respawns".

I also can use some property within the editor to manually index them? Perhaps the "suffix" field? creep camp 1 is suffix 1, etc., which when the unit dies pulls that data field and sends it to the appropriate drop table? Probably makes more work, but I could do it that way? Makes me copy units of the same kind if they are using different tables, but i don't think that's often.
Edit: Yes, you could do that. People often use the Point Value field as an Object Editor "custom value" that they can preset and reference at runtime (I doubt you can get a unit's Suffix at runtime). Anyway, I was under the assumption that you would have preplaced all of your Units in specific locations where they're meant to "exist". My solution was that when one of them dies you can simply recreate them in that same Region again. You could even recreate them in the exact spot that they originated from if you wanted to, just track the unit's starting position in a Point variable for example.

The 3rd chunk is setting specific item pools onto a 2d array/grid to determine drops. Where in simple terms 1 axis is the the region and the other is various itempools assigned to it. I think id need 2 at most for what I'm doing, but this approach could work just fine. I think its something I could use more directly for what i intended above? If i make a table like this that contains 1 axis/parent key that tells it directly which set to roll, then I use rng to determine which of the child keys to use, this accomplishes the rolls I was talking about? I think? I'm hesitant to say I've got it until I've actually got it working because the other response I got made sense until I was putting it together. Its also like someone explaining how to say something in a foreign language and it makes sense till you're speaking it to the natives and they haven't got a clue what you're saying.
Yes, this code here:
vJASS:
local integer region_index = 3
call SaveInteger( Drop_Pool_Hashtable, region_index, 0, 3 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 1, 2 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 2, 4 )
call SaveInteger( Drop_Pool_Hashtable, region_index, 3, 7 )
Is essentially doing this:
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][0] = 3
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][1] = DROP_POOLS[2]
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][2] = DROP_POOLS[4]
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][3] = DROP_POOLS[7]
So our first [index] represents the Region. Then our second [index] represents two separate things. At [0] it stores the total number of Item Pools this Region uses. Then starting at [1] and continuing upwards from there it represents the actual Item Pools that you wish to use with said Region. So you would customize this to your liking for each Region, in this example I made CeldarinWoodsCreepCamp3 use 3 Item Pools -> #2, #4, and #7. If I wanted to add a 4th Item Pool to it I would simply update it like so:
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][0] = 4
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][1] = DROP_POOLS[2]
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][2] = DROP_POOLS[4]
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][3] = DROP_POOLS[7]
  • Set Variable RegionItemPools[CeldarinWoodsCreepCamp3][4] = DROP_POOLS[5]
It now uses Item Pool #5 as well.

It's the fact that we're using Indexes to represent everything instead of the actual objects themselves that makes it more confusing, but it sounds like you understand that. This is a nice way of doing things once you get used to the pattern.
 
Last edited:
Top