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

[vJASS] Simple AoS Spawn and Movement system

Level 11
Joined
Apr 29, 2007
Messages
826
pew pew. A very neat update in my opinion. I again rewrote the whole system to archieve even more configurability and ease the working with it.

It now features 2 struct types, one is called lane and the other is called spawn.
The lane is obviously the way a spawn moves, which still uses rects. (Since you can easily move them within the editor)
A spawn contains the data to spawn units. Each spawn requires one lane, one owner (a player), one unit type, an amount and an interval.

This features in-game changes of everything, means you can replace (or change) the lane, the player, the unit type, the amount and even the interval while the game is running.

You can also get the revertion of any lane (pretty useful for AoS maps, you just have to create half the lanes and get the reversed ones)

The functions featured are:

  • Lane:
    • JASS:
          method registerWaypoint takes rect returns nothing
          method getWaypoint takes integer returns rect
          method getWaypointX takes integer returns real
          method getWaypointY takes integer returns real
          method getWaypointCount takes nothing returns integer
          method getReversed takes nothing returns Lane
          method list takes nothing returns nothing
  • Spawn:
    • JASS:
          static method create takes Lane, player, integer id, integer amount, real time returns thistype
          method startSpawn takes nothing returns nothing
          method pauseSpawn takes nothing returns nothing
          method setSpawningUnit takes integer id returns nothing
          method setSpawnCount takes integer amount returns nothing
          method setOwner takes player returns nothing
          method setLane takes Lane returns nothing
          method setInterval takes real returns nothing


Enough talked, let's come to the code:
JASS:
library SpawnAndMove

    globals
        //Which order get's issued to spawned units
        public string           ISSUED_ORDER    = "attack"
        //Distance to check if the unit is near his next waypoint (Set this higher if your rects are big)
        private constant real   CHECK_DISTANCE  = 500.
        
        //Do NOT change
        private hashtable hash
    endglobals
  
    struct Lane
        private integer waypointCount = 0
    
        method registerWaypoint takes rect r returns nothing
            call SaveRectHandle(hash, this, this.waypointCount, r)
            set this.waypointCount = this.waypointCount + 1
        endmethod
        
        method getWaypoint takes integer index returns rect
            return LoadRectHandle(hash, this, index)
        endmethod
        
        method getWaypointX takes integer index returns real
            return GetRectCenterX(LoadRectHandle(hash, this, index))
        endmethod
        
        method getWaypointY takes integer index returns real
            return GetRectCenterY(LoadRectHandle(hash, this, index))
        endmethod
        
        method getWaypointCount takes nothing returns integer
            return this.waypointCount
        endmethod
    
        method getReversed takes nothing returns thistype
            local thistype reversed = thistype.allocate()
            local integer i = this.waypointCount-1
            
            loop
                call reversed.registerWaypoint(this.getWaypoint(i))
                
                set i = i-1
                exitwhen i < 0
            endloop
            
            return reversed
        endmethod

        method list takes nothing returns nothing
            local integer i = 0
            
            loop
                call BJDebugMsg("Rect in Pos: " + I2S(i))
                
                call BJDebugMsg("---")
                
                call BJDebugMsg("X: " + I2S(R2I(this.getWaypointX(i))))
                call BJDebugMsg("X: " + I2S(R2I(this.getWaypointY(i))))
                
                set i = i + 1
                exitwhen i >= this.waypointCount
            endloop
        endmethod
        
        private method onDestroy takes nothing returns nothing
            call FlushChildHashtable(hash, this)
        endmethod
    endstruct
    
  
    struct Spawn
        private player owner
        private integer spawned
        private integer spawnCount
        private real interval
    
        private Lane lane
        
        private timer t

        
        private static trigger onEnter
        private region waypoint
        
        private static method onInit takes nothing returns nothing
            set hash = InitHashtable()
            set thistype.onEnter = CreateTrigger()
        endmethod
        
        private static method enter takes nothing returns boolean
            local thistype this
            local integer cur
            
            local unit u = GetFilterUnit()
            
            if HaveSavedInteger(hash, GetHandleId(u), 0) then
                set this = LoadInteger(hash, GetHandleId(u), 1)
                set cur = LoadInteger(hash, GetHandleId(u), 0)
                
                if IsUnitInRangeXY(u, LoadReal(hash, GetHandleId(u), 2), LoadReal(hash, GetHandleId(u), 3), CHECK_DISTANCE) then
                    set cur = cur + 1
                    
                    if cur >= this.lane.getWaypointCount() then
                        call FlushChildHashtable(hash, GetHandleId(u))
                        return false
                    endif
                    
                    call SaveInteger(hash, GetHandleId(u), 0, cur)
                    
                    call SaveReal(hash, GetHandleId(u), 2, this.lane.getWaypointX(cur))
                    call SaveReal(hash, GetHandleId(u), 3, this.lane.getWaypointY(cur))
                    
                    call IssuePointOrder(u, ISSUED_ORDER, this.lane.getWaypointX(cur), this.lane.getWaypointY(cur))
                endif
            endif
            
            set u = null
            
            return false
        endmethod
  
        static method create takes Lane l, player p, integer toSpawn, integer count, real time returns thistype
            local thistype this = thistype.allocate()
            local trigger t = CreateTrigger()
            local integer i = 0
            
            set this.lane = l
            
            set this.owner = p
            set this.spawned = toSpawn
            set this.spawnCount = count
            set this.interval = time
            
            set this.t = CreateTimer()
            call SaveInteger(hash, GetHandleId(this.t), 0, this)
            
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(t, Filter(function thistype.flush))
            
            
            set this.waypoint = CreateRegion()
            loop
                call RegionAddRect(this.waypoint, l.getWaypoint(i))
                
                set i = i + 1
                exitwhen i >= l.getWaypointCount()
            endloop
            
            call TriggerRegisterEnterRegion(this.onEnter, this.waypoint, Filter(function thistype.enter))
            
            return this
        endmethod
      
      
        method startSpawn takes nothing returns nothing
            call TimerStart(this.t, this.interval, true, function thistype.spawn)
        endmethod
        
        method pauseSpawn takes nothing returns nothing
            call PauseTimer(this.t)
        endmethod
        
        
        method setSpawningUnit takes integer new returns nothing
            set this.spawned = new
        endmethod
        
        method setSpawnCount takes integer count returns nothing
            set this.spawnCount = count
        endmethod
        
        method setOwner takes player new returns nothing
            set this.owner = new
        endmethod
        
        method setLane takes Lane new returns nothing
            local integer i = 0
        
            set this.lane = new
            
            call RemoveRegion(this.waypoint)
            
            set this.waypoint = CreateRegion()
            
            loop
                call RegionAddRect(this.waypoint, this.lane.getWaypoint(i))
                
                set i = i + 1
                exitwhen i >= this.lane.getWaypointCount()
            endloop
            
            call TriggerRegisterEnterRegion(this.onEnter, this.waypoint, Filter(function thistype.enter))
        endmethod
        
        method setInterval takes real time returns nothing
            call PauseTimer(this.t)
            
            set this.interval = time
            
            call TimerStart(this.t, time, true, function thistype.spawn)
        endmethod
    
        private static method spawn takes nothing returns nothing
            local thistype this = LoadInteger(hash, GetHandleId(GetExpiredTimer()), 0)
            
            local integer i = 0
            local unit u
            
            loop
                set u = CreateUnit(this.owner, this.spawned, this.lane.getWaypointX(0), this.lane.getWaypointY(0), 0)
                call IssuePointOrder(u, ISSUED_ORDER, this.lane.getWaypointX(1), this.lane.getWaypointY(1))
                
                call SaveInteger(hash, GetHandleId(u), 0, 1)
                call SaveInteger(hash, GetHandleId(u), 1, this)
                call SaveReal(hash, GetHandleId(u), 2, this.lane.getWaypointX(1))
                call SaveReal(hash, GetHandleId(u), 3, this.lane.getWaypointY(1))
                
                set i = i + 1
                exitwhen i >= this.spawnCount
            endloop
            
            set u = null
        endmethod
         
        private static method flush takes nothing returns boolean
            if HaveSavedInteger(hash, GetHandleId(GetDyingUnit()), 0) then
                call FlushChildHashtable(hash, GetHandleId(GetDyingUnit()))
            endif
            return false
        endmethod
    endstruct

endlibrary
 
Last edited:
  • Inline $NAME$TimerStart.
    JASS:
    call TimerStart(CreateTimer(), $HOWOFTEN$, true, function $NAME$Create )
  • TriggerRegisterTimerEventSingle - Useless BJ.
  • Who taught you how to use conditions ?
    JASS:
    function $NAME$Cond takes nothing returns boolean
        if GetOwningPlayer( GetEnteringUnit() ) == $PLAYER$ then
            return true
        endif
        return false
    endfunction
    
    // should become
    
    function $NAME$Cond takes nothing returns boolean
        return GetOwningPlayer( GetEnteringUnit() ) == $PLAYER$
    endfunction
  • IMO the textmacro is useless, you should have just used global functions with some timer attachment system.
  • You never null your local unit in $NAME$Create.
 
Level 11
Joined
Apr 29, 2007
Messages
826
Ok I reworked your points.

Who taught you how to use conditions ?

meh, I don't know, was quite late when I did it :p

IMO the textmacro is useless, you should have just used global functions with some timer attachment system.
Yeah, that's your opinion, but IMO, it's alot easier for some people like this.
 
Level 11
Joined
Apr 29, 2007
Messages
826
Fair enough.

thx :) I think alot of people who aren't so good with JASS might find this easier. Also, this doesn't need any other system to function.

Remove set Create = null from the loop, it only needs to be nulled once your done using the variable. (Place it at the bottom of the function, out of the loop.)
meh, didn't knew. I never null anything basicly. But thanks for your help.
 
Level 8
Joined
Aug 4, 2006
Messages
357
I'm pretty sure you could do this stuff in structs, and have public function which make instances of the structs (i.e. create a Spawn or a WayPoint object). If you do it right, the user could still setup a Spawn or Waypoint with one line of code, but it will reduce the size of the map dramatically; you wouldn't be copy/pasting the whole system code each time you used it.

Let me know if you would like help doing this.
 
Level 8
Joined
Aug 4, 2006
Messages
357
Whenever you update, you should delete outdated code/instructions in the original post. You can keep the old code in your own files if you want.

This version is looking better, but I'm not really sure what you're doing with "MoveInteger" and "CondMoveInt". I have an idea that will make this system both easier to use, and easier to understand the code. My idea is to make a SpawnPath struct and a WayPoint struct. Each SpawnPath object will contain a list of WayPoint objects that the spawns will walk to, in the order of the Waypoints in the list. Here's the basic layout (missing a bunch of stuff):

JASS:
library APN requires List //a library that allows you to easily make lists
    struct WayPoint
        WayPoint nextWayPoint = 0
        private rect wpRect

        method orderToNextWayPoint takes nothing returns nothing
            //self explanatory
        endmethod

        static method create takes rect whichRect returns thistype
            //set wpRect
            //setup trigger, make action orderToNextWayPoint.
        endmethod
    endstruct

    struct SpawnPath
        IntegerList waypoints //IntegerList is a list of integers.
                             //struct instances are basically integers.
        //declare your variables like theTimer, unitType, howMany, howOften, etc.

        static method create takes integer unitType, integer howMany, ... returns thistype
            local thistype sp = thistype.allocate()
            set sp.waypoints = IntegerList.create()
            //initialize variables and start timer
            return sp
        endmethod

        method addWayPoint takes rect whichRect returns nothing
            local WayPoint newWP = WayPoint.create(whichRect)
            set WayPoint(.waypoints[.waypoints.size-1]).nextWayPoint = newWP
            //the last waypoint's next waypoint is now the waypoint we just created
            call .waypoints.addLast(newWP)
            //now the new waypoint has been added to the end of the list
        endmethod
    endstruct
Well, I hope you get the general idea. Send me a PM if you need more explanation. I am happy to help. Btw, here's the List library that you'll need.
 
Level 8
Joined
Aug 4, 2006
Messages
357
Hey I see you used my suggestions :D You are learning quickly. You should really update the original post with that code. The only thing I don't get is what the units do once they reach the second waypoint. I don't see you making any events for when the spawned units enter the rects, to order them to move to the next ones. Did you test this code to see if it works?

P.S. I like the new system name.
 
I also made my system!

This is an example how to call:

JASS:
scope example initializer init

    private function init takes nothing returns nothing
        local WayPoint wp = 0
        local Way wy = 0
        local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 270.)

        //: Instaciate a new way
        set wy = Way.create()
        //: Add a new waypoint
        call wy.addWayPoint(WayPoint.create(0., 0., 192., 192., TARGET_TOLERANCE))
        //: Add a unit to the way, starting at waypoint 0.
        call wy.addUnit(u, 0)
    endfunction

endscope

Mine does the following: You can determine whether spellcasts, stop order and hold position orders will move the unit again to the target upon finishing order.

Also its pretty easy to create waypoints and its customizeable.
You also don't need a system to move units, its doing everything on its own.

I might post mine when its finished.


edit: so back to yours, its pretty basic, but cool.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
While the interface is pretty nice, this would be better suited as a module. The person creates a lane struct and implements your module thus adding all of the necessary stuff. This would also help you get away from hashtables and improve the overall performance. From here, as units are created for the lanes, they can have their waypoint counter incremented to determine which waypoint they are supposed to go to, so your only hashtable read is for associative purposes on the waypoint counts. Everything else will turn into array reads.

Furthermore, the struct wouldn't necessarily be created or destroyed as a lane would be a lane ; ), so you could move to static methods and what not. It'd be one region per lane (so that the correct struct is done), so you'd have a static var for that etc.

I think if you move towards that design your entire thing will be much better ^_^. Interface will still be clean and tight and the performance will improve.

I mean, honestly I don't see why you did a lot of the stuff you did (poor design).

Furthermore, BJDebugMsg should be changed to DisplayTextToPlayer(GetLocalPlayer(), ...)

That is all ^_^
 
Level 11
Joined
Apr 29, 2007
Messages
826
While the interface is pretty nice, this would be better suited as a module. The person creates a lane struct and implements your module thus adding all of the necessary stuff. This would also help you get away from hashtables and improve the overall performance. From here, as units are created for the lanes, they can have their waypoint counter incremented to determine which waypoint they are supposed to go to, so your only hashtable read is for associative purposes on the waypoint counts. Everything else will turn into array reads.
If I didn't misunderstood what you said, implementing the spawn as a module would be a pretty bad idea. Mainly because you can't use one lane for multiple spawns then. And who cares if you use hashtables for this? They're fast enough and you're not creating spawns every 0.01 seconds.

Furthermore, the struct wouldn't necessarily be created or destroyed as a lane would be a lane ; ), so you could move to static methods and what not. It'd be one region per lane (so that the correct struct is done), so you'd have a static var for that etc.

I think if you move towards that design your entire thing will be much better ^_^. Interface will still be clean and tight and the performance will improve.
^ Same as above.

I mean, honestly I don't see why you did a lot of the stuff you did (poor design).
What do you mean?

Furthermore, BJDebugMsg should be changed to DisplayTextToPlayer(GetLocalPlayer(), ...)
Since for debugging reasons only, who cares if you use BJDebugMsg or anything else? :/

That is all ^_^
Well, thanks for the feedback! :)
 
Level 11
Joined
Apr 29, 2007
Messages
826
Agreed. Anyway, is there any chance this system is getting more advanced? Well yeah, its not simple anymore then, but adjustable systems rock.
Explain further. :)

edit:
Btw, WIP idea of redoing a small thing of this:
Make it use coords and check every x seconds, if a unit is near the waypoint and order it to the next. This would save you from using rects and I'd also check if a unit got stunned or stopped running etc and reorder it to the next waypoint.
What do you guys think?
 
Btw, WIP idea of redoing a small thing of this:
Make it use coords and check every x seconds, if a unit is near the waypoint and order it to the next. This would save you from using rects and I'd also check if a unit got stunned or stopped running etc and reorder it to the next waypoint.
What do you guys think?
This is exactly how I did it. You should check this.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
private static method flush takes nothing returns boolean

Please re-evaluate the placement of this method in the struct. It's at the bottom of the struct, but you're calling it from... (you are aware how something like this is compiled, right?)

Also, module initializers are required because of JassHelper's initialization priorities. Whenever something might need to be accessed outside of the library, it should initialize as fast as possible to support users who might need to access these things from a module.

A Unit Indexer would help sort out the hashtable mess. You could also easily use the new Table I've developed (link in sig) to reduce total hashtables on the map.
 
Top