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

[JASS] The Next Spawn System

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
Yes... I'm working on a new Spawn : O.


For those of you who don't know what Spawn was, it was a system that allowed one to periodically spawn units (the original system was incredibly complicated, was 2000+ lines of code, and had a 15 page manual to go with it).


The new Spawn is much simpler. In fact, it is split into two different systems, these being SimpleSpawns and Spawns.


The normal method for spawning is enumerating every x seconds and creating a unit if a unit type id is right (like create a footmen at all barracks on the map ('hfoo' at 'hbar')).


The next method that people use, which is more optimal, is using timer loops and unit indexing to iterate through all units on a list (iterate through list rather than enumerate).


The problem with the above is that spawns start at different periods. Furthermore, it disallows tech upgrades for players and specific units.


SimpleSpawn creates a timer for each specific spawn so that the timers can be paused and modified. It also uses elapsed times when a time is changed so that the timer isn't simply reset. This means that if a timer went for 15 seconds out of 30 and had 15 seconds left, changing it to 18 seconds will make it have 3 seconds left instead. A normal system would make it have 18 seconds remaining (reset).

SimpleSpawn uses a combination of unit type id properties, player unit type id properties, and specific spawn properties to manipulate spawns. Properties that reals/integers (like count and time) are stacked upon each other (unit type id time + player unit type id time + spawn time = total time).

Updating spawn time on a global level updates the spawn time of all spawns matching its criteria. For example, modifying the spawn time of barracks owned by player 0 will iterate through all spawners owned by player 0 of type barracks and modify their spawn time.

For example
JASS:
set Spawn['hbar'].time = 10
set Spawn['hbar'].spawns['hfoo'].count = 3 //add footman x3
set Spawn['hbar'].spawns['ewsp'].count = 1 //add wisp x1
set Spawn['hbar'].spawns['hfoo'].count = 0 //remove footman
set Spawn['hbar'].player[0].spawns['hfoo'].count = 2 //add two footmen to player 0 only
set Player[0].spawns.time = 5 //add 5 seconds to all spawns of player 0

//actual spawn time
time = Spawn['hbar']+Player['hbar']+SpecificSpawn['hbar']

//time is updated on initial set
local real t = SpecificSpawn['hbar']+Spawn['hbar'].time+Player['hbar'].time
set t = t-TimerGetElapsed(timer)
if (t < 0) then
    set t = 0
endif
call TimerStart(timer, t, false, function DoSpawn)
if (not enabled)
    call PauseTimer(timer)
endif
Updating a value like a boolean on a global level will only apply itself to spawns that derive itself from the same level. For example, if the player enabled value for barracks is not set (meaning barracks don't spawn), enabled for barracks will apply itself to all barracks owned by that player that don't have their specific values set.

For example
JASS:
set Spawn['hbar'].enabled = true

//background if statements in enumeration
if (player['hbar'].enabled.used == false) then
   if (specificSpawn.enabled.used == false) then
       set specificSpawn.enabled = true
   endif
endif

Custom code works the same way. If code is null, it uses default spawning, which just places x spawns at a spawner.



Advanced Spawning works in a very different fashion. With Advanced Spawning, each spawn is specifically created and given a position (a position being any agent with coordinates). Advanced Spawning also has stacked fields, meaning that changes to its properties like time can be undone. This is useful when transferring a spawn from one position to another and then wanting to undo the change (like a stealing spawn ability).

The stacked fields are grouped into two different stacks-
Position Stack (x, y, z, etc)
Stat Stack (time, count, spawn type, etc)

Spawns can also have multiple positions and positions can have multiple spawns.

Advanced Spawning also has special macros for the intense spawning loops involved.




Both Advanced Spawning and Simple Spawning keep track of all spawns via a set of groups.

1. Global spawn group, split by types
2. Global spawn group 2, split by types and players
3. Specific spawn group, split by spawners/positions
4. Instantaneous spawn groups, descending by time and organized by specific spawners/positions, types and players, and types

In this way, spawned units can be manipulated at any point in the game (for example, tracking a group of unit's ages for possible life/death or giving units of a base bonuses when that base is upgraded, or etc, who knows)


Archiving and Spawn Tracking are extra features and can be disabled.



I am simply posting these designs up to see what people think (what they want me to add, etc) : ). The code for the two systems should be done soon.

Woo, now I have 60000 projects going + end of the semester exams/projects : D.

/muahahahahas


edit
Can actually merge advanced and simple spawning ; O.

edit
further ideas for API (while background code may be coded to a degree, the API hasn't been set ^^)
JASS:
//Global Spawning Manipulation that spawns may or may not use
origin['hbar'].spawn['hfoo'].count = 3
origin['hbar'].player[0].spawn['hfoo'].count = 3
player[0].spawn['hfoo'].count = 3
spawn['hfoo'].count = 3
origin['hbar'].player[0].spawn['hfoo'].count = 3
origin.spawn[0].count = 3 //set spawn via origin

//Specific Spawn Manipulation
spawn.position[position] = 3
spawn.type['hfoo'].count = 2
spawn.type['hfoo'].share(spawn2)

//soft/hard is when a spawn shares its stats with another spawn (may be specific stats)
//this is like spawn1 giving a spawntype to another spawn, or increasing another spawn's spawn count

//soft relies on spawn1 position integrity
//hard does not rely on spawn1 position integrity
spawn.soft[spawn2] = 1 //soft, x1
spawn.soft.position[position] = 1 //soft, x1
spawn.soft[spawn2] = 0
spawn.hard[spawn3] = 2 //hard, x2

//Lending
spawn.lend.position = pos //stack position
spawn.lend.position.revert()
spawn.lend.position.base()

//Mimicing
spawn.mimic = spawnToMimic
spawn.mimic.count = spawnToMimic.count
spawn.mimic.revert() //undo mimic
spawn.mimic.base() //stop mimicing
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
->This is like what... your 3rd time creating a spawn system? :p

Fourth actually : )

If you notice by API, I have learned much ^_^.

Spawn System is about the hardest type of system I've ever worked on, and each revision is just as hard as the previous ones to code (if not harder) : P.

The new Spawn system uses Position script and Stacked Fields script, so all of the older ideas were much easier to code. However, this new and glorious/wonder API is going to be a serious pain O-o.
 
Actually instead doing this

JASS:
set Spawn['hbar'].time = 10
set Spawn['hbar'].spawns['hfoo'].count = 3 //add footman x3
set Spawn['hbar'].spawns['ewsp'].count = 1 //add wisp x1
set Spawn['hbar'].spawns['hfoo'].count = 0 //remove footman
set Spawn['hbar'].player[0].spawns['hfoo'].count = 2 //add two footmen to player 0 only
set Player[0].spawns.time = 5 //add 5 seconds to all spawns of player 0
You should do this

JASS:
local unit structure = /* get unit */ null
set Spawn[structure].time = 10
set Spawn[structure].spawns['hfoo'].count = 3 //add footman x3
set Spawn[structure].spawns['ewsp'].count = 1 //add wisp x1
set Spawn[structure].spawns['hfoo'].count = 0 //remove footman
set Spawn[structure].player[1].spawns['hfoo'].count = 2 //add two footmen to player 1 only
set Spawn[structure].time = 5 //add 5 seconds to all spawns of player 0
set Spawn[structure].player = Player(0) //normal owner
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Er... you don't get it...

Spawns are manipulated 6 different ways

Global (all)
Global 2 (unit type)
Global 3 (player)
Global 4 (player unit type)
Local (specific position)
Local 2 (specific spawn)


.... like I said before, what you mentioned is already part of the system... unless you are saying that all of the global things should be removed, which isn't going to happen : P.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Look at code so far, lol

and yes, I am achieving the syntax I set out to do.

If you look through, you're going to see a load of hashtables. The overall system might end up using 20 hashtables. The first Spawn was sloppily written and generated like 5 hashtables per spawn, but this one will use 20 for entire system hopefully ; ).

And yes, these hashtables are condensed as much as possible. I do hashing and tons of merging. The numbers start getting up to around (2^32)/4 in size. The many hashtables prevent collisions as well.

The count/time in the global stuff go through groups so as to updated all of the units that the player's own. Keep in mind that I will update this to run through a hashtable loop of positions so that they will effect all spawns.

The global stuff will work with all positions, but it will only be automatically enabled for units for obvious reasons (no viable way for me to make it auto run as items/destructables are placed and locations are created ^_^).

Now, I'm still trying to figure out a good way to work the singles API in with the globals, singles being like-
Origin[positionStruct].spawns['hfoo'].count = 4

thus adding 4 footmen spawns to the origin

Spawns can also be created from scratch and sent to the origin
Origin[positionStruct].add(spawn)

These will run on separate timers and will not sync up with other spawns, but it allows you to easily add bonuses or special techs or w/e.

I'm also getting rid of the idea of lending and mimicing as that can easily be achieved outside of the system.

I am adding a bonus property. This bonus property will be a multiplier for spawns. It is useful if spawns are shared, but you don't want the spawns spawning at the actual owner (set bonus to 0 for owner, meaning it won't effect the other origins sharing in the spawn).

I'm also changing it from a single position to a set of positions.

Woo hoo for the hardest system to make ever ; D. This really is the hardest type of system I've ever worked on ^_^. Lucky for me this is my fourth time working on a Spawn system, so I'm getting experience ; P.

The spawn system will probably just include 2 loop text macros that you can put inside of your function. With these, you can easily set up the spawns in whatever positions you want to set them up in. Alternatively, you can just not pass in custom code and it'll spawn with default code.

In the below code, you'll see groups. These groups are for updating via global settings.

JASS:
library Spawn uses UnitIndexer, Position
    globals
        private hashtable groups = InitHashtable()
        private hashtable unitTypeTable = InitHashtable()
        private integer unitTypeCount = 0
        private integer array runit
        private hashtable rtable = InitHashtable()
        
        private constant integer TIME = 0
        private constant integer COUNT = 1
        private constant integer INDEX = 2
        private constant integer PLAYER = 3
        private constant integer TYPE = 4
        private constant integer TYPE_2 = 5
    endglobals
    
    private keyword OnIndex
    private keyword OnDeindex
    private module init
        private static method onInit takes nothing returns nothing
            call OnUnitIndex(Condition(function OnIndex))
            call OnUnitDeindex(Condition(function OnDeindex))
        endmethod
    endmodule
    
    globals
        private real enumTime = 0
        private integer enumCount = 0
    endglobals
    
    private function EnumTime takes nothing returns nothing
    endfunction
    
    private function EnumCount takes nothing returns nothing
    endfunction
    
    private struct GlobalSpawns extends array
        /*
        public static hashtable table = InitHashtable()
        
        public method operator count takes nothing returns integer
            if (this > 0) then
                return counts
            endif
            return LoadInteger(addSpawnTable, this, SPAWN_COUNT)
        endmethod
        
        public method operator count= takes integer i returns nothing
            set counts = i
        endmethod
        
        public method operator [] takes integer id returns thistype
            local integer spawnType = LoadInteger(addSpawnTable, id, 0)
            if (spawnType == 0) then
                set unitTypeCount = unitTypeCount + 1
                call SaveInteger(addSpawnTable, id, 0, unitTypeCount)
                call SaveGroupHandle(groups, unitTypeCount, 0, CreateGroup())
                set spawnType = unitTypeCount
                if (this < 0) then
                    set spawnType = -spawnType
                endif
            endif
            return this*8193+spawnType
        endmethod
        */
        
        public method operator group takes nothing returns group
            return LoadGroupHandle(groups, this, 2)
        endmethod
    endstruct
    
    private struct GlobalPlayerSpawns extends array
        private static hashtable table = InitHashtable()
        
        public method operator count takes nothing returns integer
            return LoadInteger(table, this, COUNT)
        endmethod
        
        public method operator count= takes integer c returns nothing
            local integer c2 = LoadInteger(table, this, COUNT)
            if (c2 != c) then
                call SaveInteger(table, this, COUNT, c)
                set c2 = enumCount
                set enumCount = c
                call ForGroup(LoadGroupHandle(groups, this, 2), function EnumCount)
                set enumCount = c2
            endif
        endmethod
        
        public method operator time takes nothing returns real
            return LoadReal(table, this, TIME)
        endmethod
        
        public method operator time= takes real r returns nothing
            local real r2 = LoadReal(table, this, TIME)
            if (r2 != r) then
                call SaveReal(table, this, TIME, r)
                set r2 = enumTime
                set enumTime = r
                call ForGroup(LoadGroupHandle(groups, this, 2), function EnumTime)
                set enumTime = r2
            endif
        endmethod
        
        public method operator [] takes integer unitTypeId returns thistype
            local integer k = LoadInteger(unitTypeTable, unitTypeId, 0)
            local integer i
            if (k == 0) then
                set unitTypeCount = unitTypeCount + 1
                call SaveInteger(unitTypeTable, unitTypeId, 0, unitTypeCount)
                set runit[unitTypeCount] = unitTypeId
                call SaveGroupHandle(groups, unitTypeCount, 0, CreateGroup())
                set k = unitTypeCount
            endif
            set i = this*8192+k
            
            if (not HaveSavedHandle(groups, i, 2)) then
                call SaveGroupHandle(groups, i, 2, CreateGroup())
                call SaveInteger(rtable, i, TYPE_2, k)
                call SaveInteger(rtable, i, TYPE, LoadInteger(rtable, this, TYPE))
                call SaveInteger(rtable, i, PLAYER, LoadInteger(rtable, this, PLAYER))
            endif
            
            return i
        endmethod
    endstruct
    
    private struct GlobalPlayers extends array
        public method operator [] takes integer playerId returns thistype
            local integer i = this*16+playerId
            if (not HaveSavedHandle(groups, i, 1)) then
                call SaveInteger(rtable, i, TYPE, this) 
                call SaveInteger(rtable, i, PLAYER, playerId) 
                call SaveGroupHandle(groups, i, 1, CreateGroup())
            endif
            return i
        endmethod
        
        public method operator group takes nothing returns group
            return LoadGroupHandle(groups, this, 1)
        endmethod
        
        public method operator spawns takes nothing returns GlobalPlayerSpawns
            return this
        endmethod
        
        /*
        public method operator count takes nothing returns integer
            
        endmethod
        
        public method operator count= takes integer i returns nothing
            call SaveInteger(addSpawnTable, this, SPAWN_COUNT, i)
        endmethod
        
        public method operator time takes nothing returns real
            return LoadReal(addSpawnTable, this, SPAWN_TIME)
        endmethod
        
        public method operator time= takes real r returns nothing
            call SaveReal(addSpawnTable, this, SPAWN_TIME, r)
        endmethod
        */
    endstruct
    
    private struct Origins extends array
        public static hashtable table = InitHashtable()
        
        public method operator group takes nothing returns group
            return LoadGroupHandle(groups, this, 0)
        endmethod
        
        public static method operator [] takes integer typeId returns thistype
            local thistype this = LoadInteger(unitTypeTable, typeId, 0)
            if (this == 0) then
                set unitTypeCount = unitTypeCount + 1
                call SaveInteger(unitTypeTable, typeId, 0, unitTypeCount)
                set runit[unitTypeCount] = typeId
                call SaveGroupHandle(groups, unitTypeCount, 0, CreateGroup())
                return unitTypeCount
            endif
            return this
        endmethod
        
        public method operator spawns takes nothing returns GlobalSpawns
            return this
        endmethod
        
        public method operator player takes nothing returns GlobalPlayers
            return this
        endmethod
    endstruct
    
    private struct Spawns extends array
        public static hashtable table = InitHashtable()
    endstruct
    
    struct Spawn extends array
        
    endstruct
    
    private function OnDeindex takes nothing returns boolean
        /*
        local Origin o = Origin[GetUnitTypeId(GetIndexedUnit())]
        call GroupRemoveUnit(LoadGroupHandle(groups, o, 0), GetIndexedUnit())
        set o = o.player[GetPlayerId(GetOwningPlayer(GetIndexedUnit()))]
        call GroupRemoveUnit(LoadGroupHandle(groups, o, 0), GetIndexedUnit())
        */
        return false
    endfunction
    
    private function OnIndex takes nothing returns boolean
        /*
        local Origin o = Origin[GetUnitTypeId(GetIndexedUnit())]
        call GroupAddUnit(LoadGroupHandle(groups, o, 0), GetIndexedUnit())
        set o = o.player[GetPlayerId(GetOwningPlayer(GetIndexedUnit()))]
        call GroupAddUnit(LoadGroupHandle(groups, o, 0), GetIndexedUnit())
        */
        return false
    endfunction
    
    private struct inits extends array
        implement init
    endstruct
endlibrary

function test takes nothing returns nothing
    //set Origin['hbar'].spawns['hfoo'].count = 5
    //set Origin['hbar'].player[0].
endfunction
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
So, lately I've been thinking about spawn limits: how many spawns a specific spawner can have on the map at any given time. Furthermore, I've been thinking about using spawn limits of other objects.

With the map I may or may not be working on (which may or may not be using Spawn), I'd probably need that ; ).

It would be something built on top of this but anyways.. my idea was that spawn maximums would be based on commanders on the map. Each player would have x amount of troops off the map. The rate they place troops on the map are based on their spawn points. The amount of troops they can have on the map at one time are based on how many troops their top commanders can lead as well as their supplies. No supplies = starving = no morale. A poor commander = hard cap.

Now, with this layout, I also think it would be possible to successfully create an RTSRPG (RTSMMORPG as well).

Thoughts?

And yes, I'm working on Spawn + Lua crap + Morale + Troop Command + keeping up with anime + etc, so dev on everything is going slow ; P.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Eventually, but I got a lot on my plate. At the moment I'm writing tutorials and accompanying systems for save/load codes ;P. I also have to update a slew of systems. I'm also working with T2D =). I have some of the code for this written up in my System Library map thingie, but don't know when I'll finish it.

If you really need it, and possibly others, I could prioritize it : ).
 
Status
Not open for further replies.
Top