• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] [Needs work] SimpleSpawn

3 kilobytes per spawn; avg footmen wars map of 17 dif spawn types would have 50kb or so worth of spawns

JASS:
library SimpleSpawn /* v1.0.0.4
*************************************************************************************
*
*   Handles simple spawning
*
*       -   one type of unit gets one type of spawn per SimpleSpawn
*       -   constant spawn time
*       -   constant spawn count
*       -   self managing
*           -   adds/removes spawns as units are created/destroyed
*           -   adds/removes spawns as units are upgraded
*
*************************************************************************************
*
*   */uses/*
*   
*       */ TQ /*                    hiveworkshop.com/forums/jass-functions-413/system-timerqueue-187502/
*       */ optional Table /*        hiveworkshop.com/forums/jass-functions-413/snippet-new-table-188084/
*       */ UnitIndexer /*           hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
*       */ UnitEvent /*             hiveworkshop.com/forums/jass-functions-413/extension-unit-event-172365/
*
************************************************************************************
*
*   SETTINGS
*/
globals
    /*************************************************************************************
    *
    *                                   DYNAMIC
    *
    *   If this is true, changing a unit's type via abilities will properly update the spawn.
    *
    *                                           -   Table is required if this is enabled
    *
    *************************************************************************************/
    private constant boolean DYNAMIC = true
endglobals
/*
************************************************************************************
*
*   Example
*
*       struct Barracks extends array
*           private static constant real INTERVAL = 6           //6 interval spawn time
*           private static constant integer ORIGIN = 'hbar'     //barracks
*           private static constant integer SPAWN = 'hfoo'      //footman
*           private static constant integer COUNT = 2           //count
*
*           implement LightSpawn
*       endstruct
*
************************************************************************************/
    globals
        private trigger tt = CreateTrigger()
        private trigger tt2 = CreateTrigger()
        private trigger tt3 = CreateTrigger()
    endglobals
    static if DYNAMIC then
        //dynamic will evaluate a trigger that generates a spawn given an origin
        //this way spawns can be updated on the fly
        //only useful if working with changing abilities like chaos, avatar, hex that effect
        //spawners
        private module Init
            public static timer c           //clean up timer
            public static Table t           //trigger table
            public static Table h           //timer table
            public static integer i = 0     //unit pointer
            public static integer o = 0     //unit type id
            public static real r = 0        //timeout
            public static real array l      //largest remaining time
            private static method onInit takes nothing returns nothing
                set t = Table.create()
                set h = Table.create()
                set c = CreateTimer()
            endmethod
        endmodule
    endif
    private module Init2
        private static method onInit takes nothing returns nothing
            //upgrade trigger registration
            local integer i = 15
            loop
                call TriggerRegisterPlayerUnitEvent(tt, Player(i), EVENT_PLAYER_UNIT_UPGRADE_START, null)
                call TriggerRegisterPlayerUnitEvent(tt2, Player(i), EVENT_PLAYER_UNIT_UPGRADE_CANCEL, null)
                call TriggerRegisterPlayerUnitEvent(tt3, Player(i), EVENT_PLAYER_UNIT_UPGRADE_FINISH, null)
                exitwhen i == 0
                set i = i - 1
            endloop
        endmethod
    endmodule
    private struct D extends array
        implement optional Init
        implement Init2
    endstruct
    static if DYNAMIC then
        private function C takes nothing returns nothing
            //simply cleans up the spawn unit type id (stops multiple trigger evaluations)
            //has to be cleaned on a timer
            set D.o = 0
            set D.i = 0
            set D.r = 0
        endfunction
    endif
    module LightSpawn
        private static unit array u         //unit
        private static integer array p      //timer by unit
        static if DYNAMIC then
            //spawn trigger registration
            private static method r2 takes nothing returns nothing
                local integer t = GetHandleId(GetExpiredTimer())
                local integer i = GetUnitUserData(D.h.unit[t])
                local integer c
                set D.h.unit[t] = null
                call DestroyTimer(GetExpiredTimer())
                if (GetUnitTypeId(GetUnitById(i)) == ORIGIN) then
                    set t = allocate()
                    set c = COUNT
                    set p[i] = t
                    set u[t] = GetUnitById(i)
                    loop
                        exitwhen c == 0
                        call CreateUnit(GetOwningPlayer(u[t]), SPAWN, GetUnitX(u[t]), GetUnitY(u[t]), GetUnitFacing(u[t]))
                        set c = c - 1
                    endloop
                endif
            endmethod
            private static method rs takes nothing returns boolean
                local timer t
                local integer i = D.i
                local real r = INTERVAL-D.r      //remaining time on new spawn
                local integer c = COUNT
                
                //the reason remaining time is so important is that the origin could have changed at any
                //point during the interval
                //origin changes are only checked at the end of each interval
                
                //loop until the remaining time is > 0
                loop
                    exitwhen r > 0
                    loop
                        exitwhen c == 0
                        call CreateUnit(GetOwningPlayer(GetUnitById(i)), SPAWN, GetUnitX(GetUnitById(i)), GetUnitY(GetUnitById(i)), GetUnitFacing(GetUnitById(i)))
                        set c = c - 1
                    endloop
                    set r = r + INTERVAL
                endloop
                //start up a timer using remainnig time and then send to the timer queue
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
                set t = CreateTimer()
                call TimerStart(t, r, false, function thistype.r2)
                set D.h.unit[GetHandleId(t)] = GetUnitById(i)
                set t = null
                return false
            endmethod
        endif
        method expire takes nothing returns nothing
            local integer c = COUNT
            static if DYNAMIC then
                local integer i
                local integer id = GetUnitTypeId(u[this])
                //if the unit type id isn't the origin, then it was changed via an ability
                //and needs to be updated
                if (id == ORIGIN) then
                    loop
                        exitwhen c == 0
                        call CreateUnit(GetOwningPlayer(u[this]), SPAWN, GetUnitX(u[this]), GetUnitY(u[this]), GetUnitFacing(u[this]))
                        set c = c - 1
                    endloop
                else
                    //deallocate it
                    set i = GetUnitUserData(u[this])
                    call TQ_D(this)
                    set u[this] = null
                    set p[i] = 0
                    //if the trigger wasn't already evaluated for this unit, evaluate it
                    //use unit type id, not pointer id***. A unit may be changed multiple times
                    //before the timer runs
                    if (D.o != id and D.i != i) then
                        set D.i = i
                        set D.o = id
                        set D.r = D.l[i]
                        set D.l[i] = 0
                        call TriggerEvaluate(D.t.trigger[id])
                        call TimerStart(D.c, 0, false, function C)
                    endif
                endif
            else
                //without dynamics, just create the unit like normal
                loop
                    exitwhen c == 0
                    call CreateUnit(GetOwningPlayer(u[this]), SPAWN, GetUnitX(u[this]), GetUnitY(u[this]), GetUnitFacing(u[this]))
                    set c = c - 1
                endloop
            endif
        endmethod
        //this uses a timer queue as otherwise an origin will more than likely spawn too soon on the
        //first spawn
        implement TQ
        private static method index takes nothing returns boolean
            //on index, allocate a timer for the unit
            local integer t                             //timer
            local integer i = GetIndexedUnitId()        //indexed unit id
            if (GetUnitTypeId(GetUnitById(i)) == ORIGIN) then
                set t = allocate()                      //allocate timer
                set u[t] = GetUnitById(i)               //store indexed unit in timer
                set p[i] = t                            //store timer in unit
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            endif
            return false
        endmethod
        private static method deindex takes nothing returns boolean
            //on deindex, deallocate timer for unit
            local integer i = GetIndexedUnitId()
            local integer t = p[i]     //timer
            if (t != 0) then
                set p[i] = 0
                set u[t] = null
                call TQ_D(t)
                static if DYNAMIC then
                    set D.l[i] = 0
                endif
            endif
            return false
        endmethod
        private static method start takes nothing returns nothing
            //starting an upgrade will destroy the timer
            //it would be better to pause, but it's impossible to pause
            //a timer in a timer queue
            local integer i = GetUnitUserData(GetTriggerUnit())
            local integer t = p[i]
            if (t != 0) then
                call TQ_D(t)
                static if DYNAMIC then
                    set D.l[i] = 0
                endif
            endif
        endmethod
        private static method cancel takes nothing returns nothing
            //on cancellation, recreate the timer (add it back in)
            local integer i = GetUnitUserData(GetTriggerUnit())
            local integer t = p[i]
            if (t != 0) then
                set t = allocate()
                set p[i] = t
                set u[t] = GetTriggerUnit()
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            endif
        endmethod
        private static method finish takes nothing returns nothing
            //when finished, either finish destroying the timer (if going out), or
            //create a new timer (if going in)
            local integer i = GetUnitUserData(GetTriggerUnit())
            local integer t = p[i]
            if (GetUnitTypeId(GetTriggerUnit()) == ORIGIN) then
                set t = allocate()
                set u[t] = GetTriggerUnit()
                set p[i] = t
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            elseif (t != 0) then
                set p[i] = 0
                set u[t] = null
            endif
        endmethod
        private static method stop takes nothing returns boolean
            local integer i = GetEventUnitId()
            local integer t = p[i]
            if (t != 0) then
                set p[i] = 0
                set u[t] = null
                call TQ_D(t)
                static if DYNAMIC then
                    set D.l[i] = 0
                endif
            endif
            return false
        endmethod
        private static method restart takes nothing returns boolean
            local integer t
            local integer i = GetEventUnitId()
            if (GetUnitTypeId(GetUnitById(i)) == ORIGIN) then
                set t = allocate()                      //allocate timer
                set u[t] = GetUnitById(i)               //store indexed unit in timer
                set p[i] = t                            //store timer in unit
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            endif
            return false
        endmethod
        private static method onInit takes nothing returns nothing
            //upgrade trigger registration
            call TriggerAddAction(tt, function thistype.start)
            call TriggerAddAction(tt2, function thistype.cancel)
            call TriggerAddAction(tt3, function thistype.finish)
            call RegisterUnitIndexEvent(Condition(function thistype.index), UnitIndexer.INDEX)
            call RegisterUnitIndexEvent(Condition(function thistype.deindex), UnitIndexer.DEINDEX)
            call UnitEvent.DEATH.register(Condition(function thistype.stop))
            call UnitEvent.START_REINCARNATE.register(Condition(function thistype.stop))
            call UnitEvent.RESURRECT.register(Condition(function thistype.restart))
            call UnitEvent.ANIMATE.register(Condition(function thistype.restart))
            call UnitEvent.REINCARNATE.register(Condition(function thistype.restart))
            static if DYNAMIC then
                //if the trigger doesn't already exist, create it
                if (not D.t.trigger.has(ORIGIN)) then
                    set D.t.trigger[ORIGIN] = CreateTrigger()
                endif
                //register the registration function
                call TriggerAddCondition(D.t.trigger[ORIGIN], Condition(function thistype.rs))
            endif
        endmethod
    endmodule
endlibrary

Cool demo
JASS:
struct BarracksFoot extends array
    private static constant real INTERVAL = 6           //6 interval spawn time
    private static constant integer ORIGIN = 'hbar'     //barracks
    private static constant integer SPAWN = 'hfoo'      //footman
    private static constant integer COUNT = 1           //count
    implement LightSpawn
endstruct
struct BarracksRif extends array
    private static constant real INTERVAL = 9           //9 interval spawn time
    private static constant integer ORIGIN = 'hbar'     //barracks
    private static constant integer SPAWN = 'hrif'      //footman
    private static constant integer COUNT = 1           //count
    implement LightSpawn
endstruct
 
Last edited:
I thought it said "Simple" :D
Nice system ^^
Just one thing.. You don't need to create a "Dynamic" boolean (Everybody wants it to be dynamic =P)

JASS:
 private static method finish takes nothing returns nothing
            //when finished, either finish destroying the timer (if going out), or
            //create a new timer (if going in)
            local integer i = GetUnitUserData(GetTriggerUnit())
            local integer t = p[i]
            if (GetUnitTypeId(GetTriggerUnit()) == ORIGIN) then
                set t = allocate()
                set u[t] = GetTriggerUnit()
                set p[i] = t
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            elseif (t != 0) then
                set p[i] = 0
                set u[t] = null
            endif
        endmethod

You should be ASHAMED of yourself!
You repeated "GetTriggerUnit()" like 3 times =P

JASS:
private static method cancel takes nothing returns nothing
            //on cancellation, recreate the timer (add it back in)
            local integer i = GetUnitUserData(GetTriggerUnit())
            local integer t = p[i]
            if (t != 0) then
                set t = allocate()
                set p[i] = t
                set u[t] = GetTriggerUnit()
                static if DYNAMIC then
                    if (INTERVAL > D.l[i]) then
                        set D.l[i] = INTERVAL
                    endif
                endif
            endif
        endmethod

Here, you repeated it twice!

I also found other repeating functions.. Did someone hack you? :D
 
Looks like we need some benchmarks ;)

EDIT: It may be better sometimes, but 3 repeating functions is crossing the line lol :p

EDIT:

The function call process:
1- Give command to processor
2- Make space for parameter storage
3- Store parameters
4- Do as the function says
5- Load parameters
6- Do more actions
7- bla bla bla
8- Remove parameter data


VS

The variable storage / nulling process
1- Allocate space
2- Store Data
4- Load Data somewhere :p
3- null data


I think using locals just WINS here :p
 
Top