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

[Trigger] Leak Check, alternatives, needs to be faster

Status
Not open for further replies.
Level 12
Joined
May 20, 2009
Messages
822
  • Spawner Clock
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • For each (Integer Int[0]) from 1 to SpawnerMaxIndex, do (Actions)
        • Loop - Actions
          • Custom script: set bj_wantDestroyGroup = true
          • Unit Group - Pick every unit in (Units of type SpawnerUnit[Int[0]]) and do (Actions)
            • Loop - Actions
              • Set Int[1] = (Int[1] + 1)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Unit-type of (Picked unit)) Equal to SpawnerUnit[Int[0]]
                • Then - Actions
                  • Set _SpawnerUnits[Int[1]] = (Picked unit)
                  • Set _SpawnerPositions[Int[1]] = (Position of _SpawnerUnits[Int[1]])
                • Else - Actions
          • For each (Integer Int[2]) from 1 to Int[1], do (Actions)
            • Loop - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _SpawnTimer[Int[2]] Less than SpawnTimer[Int[0]]
                • Then - Actions
                  • Set _SpawnTimer[Int[2]] = (_SpawnTimer[Int[2]] + 0.05)
                • Else - Actions
              • Set temp_point[Int[2]] = (_SpawnerPositions[Int[2]] offset by SpawnerRadius[Int[0]] towards 0.00 degrees)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Number of units in (Units within SpawnerZone[Int[0]] of _SpawnerPositions[Int[2]] matching ((Unit-type of (Matching unit)) Equal to SpawnType[Int[0]]))) Less than or equal to SpawnMax[Int[0]]
                • Then - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • (Number of units in (Units within SpawnerZone[Int[0]] of _SpawnerPositions[Int[2]] matching ((Unit-type of (Matching unit)) Equal to SpawnType[Int[0]]))) Equal to 0
                    • Then - Actions
                      • Set _angle[Int[2]] = (Random angle)
                      • Custom script: call RemoveLocation(udg_temp_point[udg_Int[2]])
                      • Set temp_point[Int[2]] = (_SpawnerPositions[Int[2]] offset by ((SpawnerRadius[Int[0]] x 2.00) x (Real(_offsetmult[Int[2]]))) towards _angle[Int[2]] degrees)
                    • Else - Actions
                      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                        • If - Conditions
                          • (Number of units in (Units within SpawnRadius[Int[0]] of temp_point[Int[2]] matching ((Unit-type of (Matching unit)) Equal to SpawnType[Int[0]]))) Greater than 0
                        • Then - Actions
                          • Set _angle[Int[2]] = (_angle[Int[2]] + (SpawnRadius[Int[0]] / (Real(_offsetmult[Int[2]]))))
                          • Game - Display to (All players) the text: (String(_angle[Int[2]]))
                          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                            • If - Conditions
                              • _angle[Int[2]] Greater than or equal to 360.00
                            • Then - Actions
                              • Game - Display to (All players) the text: FIRED!
                              • Set _angle[Int[2]] = 0.00
                              • Set _offsetmult[Int[2]] = (_offsetmult[Int[2]] + 1)
                            • Else - Actions
                          • Custom script: call RemoveLocation(udg_temp_point[udg_Int[2]])
                          • Set temp_point[Int[2]] = (_SpawnerPositions[Int[2]] offset by ((SpawnRadius[Int[0]] x 2.00) x (Real(_offsetmult[Int[2]]))) towards _angle[Int[2]] degrees)
                        • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • (Number of units in (Units within SpawnRadius[Int[0]] of temp_point[Int[2]] matching ((Unit-type of (Matching unit)) Equal to SpawnType[Int[0]]))) Equal to 0
                    • Then - Actions
                      • Game - Display to (All players) the text: FIRED!
                      • Unit - Create 1 SpawnType[Int[0]] for Player 1 (Red) at temp_point[Int[2]] facing (Random angle) degrees
                      • Set OreCreated = 1.00
                      • Set LastCreatedOre = (Last created unit)
                      • Set OreCreated = 0.00
                      • Set _SpawnTimer[Int[2]] = 0.00
                      • Custom script: call RemoveLocation(udg__SpawnerPositions[udg_Int[2]])
                      • Set _angle[Int[2]] = 0.00
                      • Set _offsetmult[Int[2]] = 1
                    • Else - Actions
                • Else - Actions
          • Set Int[1] = 0

Basically what this is trying to do is make something spawn in an order on rings coming off of a main spawner and make it as adjustable as possible. However, this seems to run rather slowly. Video to show what I mean:


It's running slowly because any one that gets destroyed needs to be replaced, so it's always starting from a certain position and working it's way back to where it current is to make sure all holes are filled. Is there a better/faster way to do this? The only other thing I can think of is to register all points from the get-go and check the points to make sure they're all filled, but I'm not sure how to make it do this sequence that way, as it needs to start from the spawner and work outword while also having a timer to delay when the spawns occur.

EDIT: It also seems to break after ~16 spawners are on the map at a time. (Though surprisingly runs kind of smooth, TBH, despite the fact it's running so many freaking loops at one time)

UPDATE: I did one hell of a stress-test with this and it still had passable performance. It was hit, but it wasn't really hit hard. This was with something like 16 spawners that were all spawning 50 spawns in total.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
As you said registering all points is a good idea. But instead I would save the relative coordinates of all the points on map init in two arrays (x,y).
This way if you want to create a new block you do not need to calculate the position, only which position it should be created.

You use a hashtable to save the blocks for each spawner.

If you create a new block, you save its spawner and its index (a block with index i is at position x,y) in a hashtable.
If a block gets killed you know which spawner it belongs to and change an entry in the hashtable of its spawner.

You always create the block with the lowest index for each spawner.

This should reduce the number of iterations and avoid calculations during the game.


Also in general you should use JASS, if you want triggers to be faster. This allows you to use reals(x,y) instead of locations which is faster.

If you are interested in this kind of system I can make an example map.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Here it is.
In the Init trigger you configure which coordinates are used.
You can either do it manually setting SP_X[] and SP_Y[] or you can use the AddCircle function, which adds to the coordinates a circle with ParamNumber coordinates and radius ParamRadius. ParamOffset is used, if you don't want the degrees to start with 0, but a different value.
In the map I use 3 circles. The manual method is not because of the if(true == false).

To add an element to a spawner set ParamSpawner to the spawner you want to add an element to and run the trigger SpawnElement. You can see it in the Test trigger (which leaks) how to do it. To test the map just hit the esc button and every spawner spawns a barrel.



Efficiency:
I tested it with 16 spawners with 48 elements each. I had no lag spikes when using the SpawnElement trigger. Calling the SpawnElement is the only time when this system could cause lag, as it's the only trigger that runs a loop during the game.
If I found a better solution for the loop in SpawnElement I will message you.
At the moment it checks in loop of booleans for the first empty spot. This means, that if your spawners are filled, calling SpawnElement will have a lot of iterations.

Edit: Version 2 uploaded

I now use an integer to store at which index a spawner should look for a free space. This means the amount iterations in the loop per call of SpawnElement is greatly reduced (most of the time only one iteration).

I can run the system without performance issues from this system with 25 spawners with 48 elements per spawner.
The lag comes from the massive amount of units.

Edit: Version 3 uploaded

Fixed a real inaccuracy in the calculation of circles by putting the /360 after all other calculations.
 

Attachments

  • SpawnerV3.w3x
    28.3 KB · Views: 32
Last edited:
Level 12
Joined
May 20, 2009
Messages
822
So, there is one thing that's missing here: When one of the spawnee's dies it shouldn't just respawn. It should be replaced by the next one to spawn. So the one that's going to spawn next in the line, instead of it spawning next in the line it spawns to replace the now-empty location. This way the one that was next to spawn takes that place so all holes are filled by the progress of continuing to move outward is stopped. This is important because I intend to have an adjustable delay between each spawnee. That's about all that seems to be missing. I'm looking at your code right now and it seems readable so I'm gonna see what can be applied to what I got (or if I should just use yours as a base and build off of it, Lol)

What am I using this for? This is going to be a resource. It's going to work a lot like C&C's Tiberium/Ore. There is a main source that spawns the resource. It spawns starting at the source and going outward in a circle. Any resource that is removed from the inside of the circle will be replaced. There is a maximum size of the field. There is a delay between the resource spawning. Etc.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Seems like I forgot to remove a line from the Spawn Element trigger. There is still an expiration timer for the elements I used to test the system.

So, there is one thing that's missing here: When one of the spawnee's dies it shouldn't just respawn. It should be replaced by the next one to spawn. So the one that's going to spawn next in the line, instead of it spawning next in the line it spawns to replace the now-empty location. This way the one that was next to spawn takes that place so all holes are filled by the progress of continuing to move outward is stopped.

I don't understand what you mean. They do not respawn. When the trigger Spawn Element is called the element with the smallest number, which is missing is created.
Increase the periodic time to 0.5 seoconds in the test trigger, so you can better see what's going on.
 
Level 12
Joined
May 20, 2009
Messages
822
For different types of spawners with different types of spawns they should also have different timers. For example one could take 5 seconds to spawn the next element while the other could take 30 seconds. I've tried applying a clock to your Test trigger but doesn't seem to work.

  • Test
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • Custom script: set bj_wantDestroyGroup = true
      • For each (Integer Int[39]) from 1 to SpawnerMaxIndex, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • _SpawnTimer[Int[39]] Less than SpawnTimer[Int[39]]
            • Then - Actions
              • Set _SpawnTimer[Int[39]] = (SpawnTimer[Int[39]] + 0.05)
            • Else - Actions
          • Unit Group - Pick every unit in (Units in (Playable map area) matching ((Unit-type of (Matching unit)) Equal to SpawnerUnit[Int[39]])) and do (Actions)
            • Loop - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _SpawnTimer[Int[39]] Greater than or equal to SpawnTimer[Int[39]]
                • Then - Actions
                  • Set ParamSpawner = (Picked unit)
                  • Set SpawnUnit = SpawnType[Int[39]]
                  • Set _SpawnTimer[Int[39]] = 0.00
                  • Trigger - Run SpawnElement <gen> (checking conditions)
                • Else - Actions
I've already replaced h001 in your trigger to use SpawnUnit instead and that works perfectly fine. But for some reason I get 1 spawner not spawning anything and all others spawn rapidly as usual, instead of all being delayed by their respective SpawnTimer.

EDIT: Oops. That's what I get for having very similarly-named variables. Fixed it +0.05ing SpawnTimer instead of _SpawnTimer and it's kind of working now, although still a little funky. Only 1 of the same type of spawner is working.

EDIT2: Okay, everything's fixed. I just need to be able to control the circles and how many spawns there are per spawner-type.

  • Test
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • Custom script: set bj_wantDestroyGroup = true
      • For each (Integer Int[39]) from 1 to SpawnerMaxIndex, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • _SpawnTimer[Int[39]] Less than SpawnTimer[Int[39]]
            • Then - Actions
              • Set _SpawnTimer[Int[39]] = (_SpawnTimer[Int[39]] + 0.05)
            • Else - Actions
          • Unit Group - Pick every unit in (Units in (Playable map area) matching ((Unit-type of (Matching unit)) Equal to SpawnerUnit[Int[39]])) and do (Actions)
            • Loop - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • _SpawnTimer[Int[39]] Greater than or equal to SpawnTimer[Int[39]]
                • Then - Actions
                  • Set ParamSpawner = (Picked unit)
                  • Set SpawnUnit = SpawnType[Int[39]]
                  • Trigger - Run SpawnElement <gen> (checking conditions)
                • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • _SpawnTimer[Int[39]] Greater than or equal to SpawnTimer[Int[39]]
            • Then - Actions
              • Set _SpawnTimer[Int[39]] = 0.00
            • Else - Actions

The Radius of the circles should also be controllable for different spawners.

Here is a bit of my design doc showing off what I was trying to accomplish so maybe it's easier to understand that way:

Each Spawn can have the following values:

SpawnSize = the amount of spawns this particular spawn is worth.
SpawnMaxSize = The maximum amount of spawns this spawn is worth.
SpawnValue = the amount of credits the spawn is worth.
SpawnChance = The chance that the spawner will spawn this spawn.
SpawnCount = The amount of this spawn that will be spawned. Positions are random.
SpawnTimer = A cooldown timer for this spawn to spawn. If it is not up, will not spawn regardless of other variables.
SpawnType = The unit-type that is this spawn.
SpawnAmount = The amount of physical spawns that are spawned.

Each Spawner can have the following values:

SpawnMax = the maximum size of this field in spawns.
SpawnerRadius = The radius of the Spawner. See SpawnRadius.
SpawnerZone = The maximum radius that the spawner can spawn a spawn.
SpawnerRate = The time, in seconds, that this Spawner will spawn.
SpawnerChance = The chance that this spawner will decide to spawn its spawn.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
If you are using an array-based system to store the indices of the spawner you do not need to use the group anymore.
In the else block of your first if you can put:

Set ParamSpawner = SpawnerUnit[Int[39]] <- you should have a variable that stores the spawner
Set SpawnUnit = SpawnType[Int[39]]
Set _SpawnTimer[Int[39]] = 0.00
Trigger - Run SpawnElement



Each Spawner can have the following values:

SpawnMax = the maximum size of this field in spawns.
SpawnerRadius = The radius of the Spawner. See SpawnRadius.
SpawnerZone = The maximum radius that the spawner can spawn a spawn.
SpawnerRate = The time, in seconds, that this Spawner will spawn.
SpawnerChance = The chance that this spawner will decide to spawn its spawn.

If you individual cricles for each spawn, you could save them in a hashtable for each spawner.
If you create a spawner you fill the hashtable with the necessary data.

I am not sure, if I understood what all variables are supposed to do, so I am asking these questions:

SpawnSize = the amount of spawns this particular spawn is worth.
SpawnMaxSize = The maximum amount of spawns this spawn is worth.
What do you mean? A spawn is worth a certain amount of spawns?

SpawnValue = the amount of credits the spawn is worth.
Credits is the resource you use?

SpawnChance = The chance that the spawner will spawn this spawn.
Shouldn't the spawn chance be specified in the spawner?
SpawnCount = The amount of this spawn that will be spawned. Positions are random.
Same here.
SpawnTimer = A cooldown timer for this spawn to spawn. If it is not up, will not spawn regardless of other variables.
If a certain spawn was spawned it cannot be spawned again from the same spawner as long as it's on cooldown, right?
SpawnAmount = The amount of physical spawns that are spawned.
You mean how many spawns of this type are spawned when calling SpawnElement?

SpawnMax = the maximum size of this field in spawns.
So the maximum amount of spawns spawned by this spawner?


SpawnerRadius = The radius of the Spawner. See SpawnRadius.
SpawnerZone = The maximum radius that the spawner can spawn a spawn.
You want the spawning to happen within these two radiuses and not on fixed circles?

SpawnerChance = The chance that this spawner will decide to spawn its spawn.
A spawner has to decide first if it wants to spawn, when the SpawnerRate causes it to spawn?
 
Level 12
Joined
May 20, 2009
Messages
822
Same here.

What do you mean? A spawn is worth a certain amount of spawns?

Basically, this is a resource. Each "spawn" is going to be harvestable. The SpawnSize determines how many actual spawns each spawn is worth, or basically how many times they can be harvested before they are destroyed. The SpawnValue determines how much resources you harvest.

Credits is the resource you use?

I just use the term credits because it's generic. The actual resource is gems. Each spawn is a physical gem and each gem can be harvested SpawnSize times, or for a maximum of SpawnMaxSize times. This value determiens how much each SpawnSize is worth. So Say it's a SpawnValue of 5, each Spawn has a SpawnSize of 5, and the worker can harvest at maximum 3. So it will take 2 trips to harvest the individual spawn, resulting in 25 resources dropped off in total, or 15 for the first trip and 10 for the second.

Finally, the reason SpawnMaxSize exists is because if the field is full (By reaching SpawnMax or SpawnerZone limits) then the Spawner would continue to "spawn" by adding to the SpawnSize of each Spawn until they reach SpawnMaxSize, distributing SpawnAmount across random spawns in the field instead of actually spawning spawns.

Shouldn't the spawn chance be specified in the spawner?

If a certain spawn was spawned it cannot be spawned again from the same spawner as long as it's on cooldown, right?

Yes, this is also independent from SpawnRate. Basically what determines if a spawn will finally spawn if this chain of checks all return successful:
SpawnerChance->SpawnerRate->SpawnChance->SpawnTimer->SpawnerRadius->SpawnRadius->SpawnerZone.

SpawnerChance and SpawnChance both exist because I originally intended for a single spawner to be able to spawn multiple different spawns, and SpawnChance would determine which spawn would be spawned.

You mean how many spawns of this type are spawned when calling SpawnElement?

Yep.

So the maximum amount of spawns spawned by this spawner?

Yes. If there are already this many spawns of any SpawnType then it cannot spawn any more spawns.

You want the spawning to happen within these two radiuses and not on fixed circles?

The fixed circles are cleaner. But basically, the goal is to spawn them as close to the spawner as possible, filling up all available spaces near the spawner before finally moving outward to start filling up spaces one SpawnRadius further outward until SpawnerZone or SpawnMax is met.

A spawner has to decide first if it wants to spawn, when the SpawnerRate causes it to spawn?

Yeah. As long as SpawnerRate is up, it's up to SpawnerChance to determine if anything ever actually spawns.

If you individual cricles for each spawn, you could save them in a hashtable for each spawner.
If you create a spawner you fill the hashtable with the necessary data.

I have a very poor understanding of Hash Tables and no matter how much I look up tutorials on how to use them it still just goes right over my head. So what I'm saying is, I'm not really sure how to use them and how to get them to make individual circles for each SpawnerType.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The resource part should be no problem as it's not directly conneteced to the spawning process.
However spawning will be more difficult.
The advantage of a predefined spawnorder is that it is way faster than a random spawn position.
You could make a pseudo randomized spawn order. This means every spawner has its defined spawn order, but the spawn order itself is randomized whe nthe respective spawner is created. You will still have a spawn order from the center, but the spawn order will always be the same.
Maybe you could even shuffle the indexes, so it gets even more random, but still has the advantage of the a defined spawn order.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Hashtable is the way to go. It basically allows you to store an array to a unit.
This way you can store different SpawnCircles to different spawners.

So you will use predefined non-random spawn circles then?
 
Level 12
Joined
May 20, 2009
Messages
822
For now they will be pre-defined. I don't think I want the process of spawning to be random anymore. That stuff from my doc is about 2 days out-dated from what I currently am visioning. Can you explain how I would go about using a Hashtable to do that? Again, it's not really territory I've been into and all the tutorials I've looked at are pretty confusing.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Yeah hashtable tutorials are often times confusing.
A hashtable is basically a 2d-array with some advantages:
- you can store data of different data types in it.
- you can store data to a unit(in general to a handle but in most cases you store it to a unit)

There are two ways to use a hashtable.
The first is to just use it to save data in a table (if a 1d array is not enough for you).
-> 2d array

JASS:
call SaveInteger(udg_SP_Hashtable ,3 ,7 ,15)
This saves the value 15 at poistion 3,7 in the hashtable. If 2d arrays would exist this would be Integer[3][7]=15
In this case Parent key is 3 and Child key is 7, value is 15.
With this method it does not matter whether you use 3,7 or 7,3 as long as you always use the same.


The second way is to store data for a unit.

This means that you use the parent key to indicate for which unit you want to store the data.
The child key is used as a index like in an array
-> 1d array for each unit

To use a unit as a key, you use GetHandleId(unit).

JASS:
call SaveInteger(udg_SP_Hashtable ,GetHandleId(udg_UNIT) ,7 ,15)
This saves the value 15 at position 7 for the unit UNIT.

Important: in GUI it is childkey, parentkey and in Jass it is parentkey,childkey

The reason why you use parent key for the unit is because of the function FlushChildHashtable.
JASS:
call FlushParentHashtable(udg_SP_Hashtable)
This deletes all data in a hashtable.
JASS:
call FlushChildHashtable(udg_SP_Hashtable,p)
This deletes all data with the parentkey p.

If you store data to a unit you want nearly always use the unit as the parent key, so you can use FlushChildHashtable to delete all data for this unit.
Deleting data is important to reduce memory usage.


To load data use the function LoadReal(...). I think it's pretty self-explanatory.


We are using the second way. We store data for each spawner.

You must first think which key is used for what. Parent key is used for unit.
The childkeys are now indices to store data.
For example:
0 = SpawnMax
1 = SpawnerChance = The chance that this spawner will decide to spawn its spawn.
2 = x0
3 = y0
4 = x1
5 = y1
6 = x2
7 = y2
...

It is important to start with simple variables and put the array at the end, as you don't know how long the array will be.

Code to save data to a spawner.
SpawnMax,SpawnChance,x,y are the values you want to save.

JASS:
    local integer h
    local unit u
    set h = GetHandleId(u) // this will be our parent key
    call SaveInteger(udg_SpawnerData,h,0,SpawnMax)
    call SaveInteger(udg_SpawnerData,h,1,SpawnChance)
 
    loop
    // save all coordinates
    call SaveReal(udg_SpawnerData,h,i*2,x[i])
    call SaveReal(udg_SpawnerData,h,i*2+1,y[i])
    endloop
 
Level 12
Joined
May 20, 2009
Messages
822
Okay, so I'm editing AddCircle. Does this seem to be right?

JASS:
function AddCircle takes unit u returns nothing
   
    local integer i
    local real angle
    local unit u
    local integer h = GetHandleId(u)
    set i = 0
    loop
        exitwhen i>=udg_ParamNumber
        set angle = bj_DEGTORAD*(udg_ParamOffset+(i*360)/udg_ParamNumber)
        // set udg_SP_X[udg_SP_Points+i] = udg_ParamRadius*Cos(angle)
        // set udg_SP_Y[udg_SP_Points+i] = udg_ParamRadius*Sin(angle)
        call SaveReal(udg_SpawnerXYTable,h,i*2,udg_ParamRadius*Cos(angle))
        call SaveReal(udg_SpawnerXYTable,h,i*2+1,udg_ParamRadius*Sin(angle))
        set i = i + 1 
    endloop
    set udg_SP_Points = udg_SP_Points+udg_ParamNumber
endfunction

//===========================================================================
function InitTrig_AddCircle takes nothing returns nothing
    set gg_trg_AddCircle = CreateTrigger(  )
    call TriggerAddAction( gg_trg_AddCircle, function Trig_AddCircle_Actions )
endfunction

I also decided to put AddCircle in the mapscript instead of in a trigger. Then with this I just do a custom script that is call AddCircle(ParamSpawner) or something like that, I think. Right?

Okay, so now I'm working on implementing that. I'm sticking this in the Init:

  • For each (Integer Int[99]) from 1 to SpawnerMaxIndex, do (Actions)
    • Loop - Actions
      • Unit Group - Pick every unit in (Units in (Playable map area) matching ((Unit-type of (Matching unit)) Equal to SpawnerUnit[Int[99]])) and do (Actions)
        • Loop - Actions
          • Set ParamSpawner = (Picked unit)
          • Set _offsetmult[Int[99]] = 1
          • For each (Integer Int[56]) from 1 to SpawnMax[Int[99]], do (Actions)
            • Loop - Actions
              • Set Int[101] = (360 / ((Integer(SpawnRadius[Int[99]])) / _offsetmult[99]))
              • Set ParamNumber = Int[101]
              • Set ParamOffset = 0.00
              • Set ParamRadius = (SpawnerRadius[Int[99]] + (SpawnRadius[Int[99]] x (Real(_offsetmult[Int[99]]))))
              • Custom script: call AddCircle(udg_ParamSpawner)
              • Set _offsetmult[Int[99]] = (_offsetmult[Int[99]] + 1)
              • Set Int[56] = (Int[56] + (Int[101] - 1))
I'm messing with the the loop's current position because I think I need to keep it lined up with how many units are actually getting set in ParamNumber, so the loop ends when there are no more units available. I do the -1 because at the end of the loop it's gonna +1 anyway. It needs to stay in line with how many slots in SpawnMax are available, I think, so that way when the units are all used up through ParamNum the loop ends.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The first trigger (jass) looks mostly fine.
Keep in mind that udg_SP_Points is no longer used to store the amount of points (you need to store this in the hashtable for each spawner).
You should add an offset to i*2 so you can store other values at postion 0 for example. I started my coordinates at 10, so I can store other data in the indices(0-9).
But seeing your hashtable name is SpawnerXYTable, you probably only want to store the coordinates in this hashtable. You can use a different hashtable to store SP_Points, if you prefer it.
You can however use udg_SP_Points to store the points to save them later in the hashtable.

I also decided to put AddCircle in the mapscript instead of in a trigger.
Good idea. This will reduce the amount of globals needed as parameters and should be faster as well.

Second trigger (GUI).
I assume you want to setup every spawner depennding on its unit type.

I have taken a different apporach in initializing the spawners. I have written a trigger CreateSpawner, that creates a spawner at a position and initilizes its spawning points by the current varaibles SP_X[],SP_Y[],SP_POINTS and save these variables into its hashtable.
My process of creating a new spawner looks like this:

AddCricle
CreateSpawner

I first use AddCricle to setup the positions. With this method I can have multiple circles per spawner. When I have all points set up I run CreateSpawner. I think this method is very easy to implenent as it's only about 15 lines of code. You could easily change CreateSpawner into a trigger that initializes an existing unit depending on its unit type.

I will upload my current version. It supports individual points for each spawner.
 

Attachments

  • SpawnerV5.w3x
    30 KB · Views: 34
Level 12
Joined
May 20, 2009
Messages
822
Messing around with that, that seems to be pretty much perfect TBH. I think I like having it this way over the way I thought of. More control over it that way I think.

EDIT: This is far from a necessity, but they can't actually be built by a player in-game this way. Not a big deal as I don't intend for them to be buildable, though. It's just something that should be kept in mind.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
EDIT: This is far from a necessity, but they can't actually be built by a player in-game this way. Not a big deal as I don't intend for them to be buildable, though. It's just something that should be kept in mind.

JASS:
set u = CreateUnit(udg_Param_CS_Player,udg_Param_CS_UnitType,GetLocationX(udg_Param_CS_Location),GetLocationY(udg_Param_CS_Location),0)
This line creates the unit in the CreateSpawner trigger. If you remove it and set u = ConstructedUnit() or something like that you can apply the data to any unit in the game.
You still need to set the parameters before, but you can do it in the same trigger just before and set the parameters depending on the unit-type.

A good way to do so is by using a function that assigns a unique index to every unit type.
For example footman -> 1, rifleman -> 2, ...
This is another situation where the mighty hashtable can help you. ;)

Instead of using unit as the parent key you can use unit-type. This way you can store data to a unit-type.
You can either save the data directly in the hashtable or only the index and use an array to save the data.

Please take a look at the map. I only added the two last trigger which show you how you can attach data to a unit type.
It's an amazing method, because it both saves you time in making the trigger and is more efficient than using a lot of ifs.
In the map I attached to the unit-type of riflemen the value 205 and to footmen 135. If you select a unit this value is displayer (0 for other units).



However I have not yet implentend a mehod to check whether a unit is a spawner. But you could just check the hashtable to do so.
If a spawner dies you need to make sure that it's hashtable data is deleted (FlushChildHashtable).
 

Attachments

  • SpawnerV5.w3x
    30.6 KB · Views: 35

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
I would imagine the most efficient approach would be to track the state of the spawn location generator (algorithm to find next spawn position) and then track all spawned units in a separate data structure. When a spawned unit moves/is removed then it is added to a replenishment list, possibly ordered based on some kind of priority. Spawning units first checks if anything is inside the replenishment list, if so it chooses the highest priority element to respawn. If the replenishment list is empty then it asks the spawn location generator for a new location and creates a new unit which is then added to the data structure to be tracked.

If speed is critical, a lot of this could be done inside arrays, aka vJASS struct like structures. However a hashtable solution should be perfectly viable as well since all the data structures and algorithms involved should scale very well.
 
Status
Not open for further replies.
Top