• 🏆 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] Wander system

Status
Not open for further replies.
Level 17
Joined
Mar 21, 2011
Messages
1,597
Hi, i made a system for my map that basically lets units move a certain path. i used a hashtable to save the x/y values of the points you add to the path. now, i want to remake the system using a struct, i have nearly no knowledge using structs and got stuck very quickly. this is what i came up with for now

JASS:
library Wander requires TimerUtils

    struct Wander
        real array x[5]
        real array y[5]
        unit u
        integer counter = 0
        integer lastpos = 0
       
        static method create takes unit U returns Wander
            local Wander this = Wander.allocate()
            set this.u = U
            call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
            return this
        endmethod
       
        method addWaypoint takes real X, real Y returns nothing
            set this.x[this.counter] = X
            set this.y[this.counter] = Y
            set this.counter = this.counter + 1
        endmethod
       
        method move takes nothing returns nothing
            if SquareRoot((GetUnitX(this.u) - this.x[this.lastpos]) * (GetUnitX(this.u) - this.x[this.lastpos]) + (GetUnitY(this.u) - this.y[this.lastpos]) * (GetUnitY(this.u) - this.y[this.lastpos])) < 10 then
                set this.lastpos = this.lastpos + 1
                call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
            endif
        endmethod
       
        method destroy takes nothing returns nothing
            set this.u = null
            call this.deallocate()
        endmethod
       
        static method onInit takes nothing returns nothing
            call TimerStart(NewTimer(), 0.1, true, method move)
        endmethod
       
    endstruct
   
    function test takes nothing returns nothing
        local Wander wanderer
        set wanderer = Wander.create(gg_unit_n000_0003)
        call wanderer.addWaypoint(-21550.0, -21222.0)
        call wanderer.addWaypoint(-20000.0, -20000.0)
        call wanderer.addWaypoint(-19000.0, -19000.0)
    endfunction
   
endlibrary

the timer part does not work at all, i dont know how i can make my move method periodically. i guess there are more mistakes.
 
Hello. A timer must call a function which takes nothing and returns nothing, so it must be a static method, not a normal method. (a normal method is actually a function which takes an integer, the object/instance, as parameter)
And you reference the static method with "function thistype.move", instead of "method move".

I've worked on such a patrol system which uses struct syntax, you're very welcome to check it out: Patrol System v1.6.
(you can always ask everything you are interested in)
 
Level 15
Joined
Mar 25, 2016
Messages
1,327
Only static methods can be used as timer functions. You can use a static method for your timer, that calls a normal method for your specific struct instance. The struct instance can be attached with a hashtable to the timer.
I don't know if this a good solution though, since I rarely used structs so far.
 
Level 15
Joined
Mar 25, 2016
Messages
1,327
Thanks for your replies. I forgot to mention, i tried making it static already, which worked (or at least it didnt throw an error), but then the "this" keyword did not work anymore inside the method. Is this a problem?
Static means, that there is no this. Static methods can be called without an instance. A normal method is called like this: call instance.method and a static method is called like this: call staticmethod
As you see in the static method there is no instance for which the method is called, it is called for the struct instead. Each instance is identified by an integer, so if you actually want a static method that only does something for one instance, you need to give this integer to the static method:
JASS:
method A takes nothing returns nothing
    ....
endmethod

static method B takes nothing returns nothing
    call whichInstance.A()
endmethod
Before using method B (which can be used as timer function), you have to set whichInstance, so B knows for which instance it should call A. A hashable works very well with a timer, because you can attach whichInstance to the timer with a hashtable, when the timer is created.

This code is just from the top of my head, so syntax might not be 100% correct.
 
JASS:
library Wander requires TimerUtils

    struct Wander
        real array x[5]
        real array y[5]
        unit u
        integer counter = 0
        integer lastpos = 0
       
        static method create takes unit U returns Wander
            local Wander this = Wander.allocate()
            set this.u = U
            call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
            return this
        endmethod
       
        method addWaypoint takes real X, real Y returns nothing
            set this.x[this.counter] = X
            set this.y[this.counter] = Y
            set this.counter = this.counter + 1
        endmethod
       
        method move takes nothing returns nothing
            if SquareRoot((GetUnitX(this.u) - this.x[this.lastpos]) * (GetUnitX(this.u) - this.x[this.lastpos]) + (GetUnitY(this.u) - this.y[this.lastpos]) * (GetUnitY(this.u) - this.y[this.lastpos])) < 10 then
                set this.lastpos = this.lastpos + 1
                call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
            endif
        endmethod
       
        method destroy takes nothing returns nothing
            set this.u = null
            call this.deallocate()
        endmethod
       
        static method onInit takes nothing returns nothing
            call TimerStart(NewTimer(), 0.1, true, method move)
        endmethod
       
    endstruct
   
    function test takes nothing returns nothing
        local Wander wanderer
        set wanderer = Wander.create(gg_unit_n000_0003)
        call wanderer.addWaypoint(-21550.0, -21222.0)
        call wanderer.addWaypoint(-20000.0, -20000.0)
        call wanderer.addWaypoint(-19000.0, -19000.0)
    endfunction
   
endlibrary

Looking at this piece-by-piece;
JASS:
    struct Wander

You declare a struct called Wander, which has the following members,

JASS:
    real array x[5]
    real array y[5]
    unit u
    integer counter = 0
    integer lastpos = 0

Note that you can also declare the following:

JASS:
real array x[5]
real array y[5]

as this

JASS:
static real array x[5]
static real array y[5]

The static method create is declared as this

JASS:
static method create takes unit U returns Wander
    local Wander this = Wander.allocate()
    set this.u = U
    call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
    return this
endmethod

You can declare it like these:

JASS:
    local Wander this = Wander.allocate() // What you used...
    local thistype this = thistype.allocate() // Keyword based....
    local thistype this = .allocate() // implicit thistype...
    local thistype this = allocate() // My favorite...

When you have a variable "this" in a static method (as a local) or method (as a parameter), you can simplify the following

JASS:
set this.u = U

to

JASS:
set .u = U
set u = U

This method is okay, ..
JASS:
method addWaypoint takes real X, real Y returns nothing
    set this.x[this.counter] = X
    set this.y[this.counter] = Y
    set this.counter = this.counter + 1
endmethod
.. but the x and y members of the instance can only use up 5 spaces, as indicated above. You can add a check for that, or set the first value to the second, second to third, third to fourth, fourth to fifth and fifth to a new value (which requires a linked list data structure).

Now, let's look at the following methods,

JASS:
    method move takes nothing returns nothing
            if SquareRoot((GetUnitX(this.u) - this.x[this.lastpos]) * (GetUnitX(this.u) - this.x[this.lastpos]) + (GetUnitY(this.u) - this.y[this.lastpos]) * (GetUnitY(this.u) - this.y[this.lastpos])) < 10 then
                set this.lastpos = this.lastpos + 1
                call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
            endif
        endmethod
       
        method destroy takes nothing returns nothing
            set this.u = null
            call this.deallocate()
        endmethod
       
        static method onInit takes nothing returns nothing
            call TimerStart(NewTimer(), 0.1, true, method move)
        endmethod

At this point, the onInit method tries to call code (a function that takes nothing and returns nothing or something) but the move method is not code (that is, it requires integer this even though nothing is taken). That is because of the parsing of vJASS, which is another topic by itself. To fix it, you restate the move method to be a static method, add a local Wander this to the new static method and set that this to the value of the wanderer.

JASS:
    endstruct
   
    function test takes nothing returns nothing
        local Wander wanderer
        set wanderer = Wander.create(gg_unit_n000_0003)
        call wanderer.addWaypoint(-21550.0, -21222.0)
        call wanderer.addWaypoint(-20000.0, -20000.0)
        call wanderer.addWaypoint(-19000.0, -19000.0)
    endfunction
   
endlibrary

To do that, we'll need a static member of the Wander struct. Let's call it current.

JASS:
    static thistype current = 0
    static Wander current = 0 // Works as well.

We improve the test function to set current to the value of wanderer.
JASS:
    function test takes nothing returns nothing
        local Wander wanderer
        set wanderer = Wander.create(gg_unit_n000_0003)
       
        // Since the test function is outside the struct, and the member that we're accessing is static,
        // we have to declare its' StructName first, followed by a "." and the member.

        set Wander.current = wanderer

        call wanderer.addWaypoint(-21550.0, -21222.0)
        call wanderer.addWaypoint(-20000.0, -20000.0)
        call wanderer.addWaypoint(-19000.0, -19000.0)
    endfunction

Now to improve the move method
JASS:
    static method move takes nothing returns nothing
        local Wander this = Wander.current
        if SquareRoot((GetUnitX(this.u) - this.x[this.lastpos]) * (GetUnitX(this.u) - this.x[this.lastpos]) + (GetUnitY(this.u) - this.y[this.lastpos]) * (GetUnitY(this.u) - this.y[this.lastpos])) < 10 then
            set this.lastpos = this.lastpos + 1
            call IssuePointOrder(this.u, "move", this.x[this.lastpos], this.y[this.lastpos])
        endif
    endmethod

The final result:

JASS:
library Wander requires TimerUtils

    struct Wander
        static Wander current = 0
       
        static real array x[5]
        static real array y[5]
        unit u
       
        // Oh, I forgot to mention that setting an initial value other than 0 for members is not allowed.
        integer counter = 0
        integer lastpos = 0
       
        static method create takes unit U returns Wander
            local Wander this = Wander.allocate()
            set .u = U
            call IssuePointOrder(.u, "move", .x[this.lastpos], .y[this.lastpos])
            return this
        endmethod
       
        method addWaypoint takes real X, real Y returns nothing
            set .x[.counter] = X
            set .y[.counter] = Y
            set .counter = .counter + 1
        endmethod
       
        // Most this keywords have been removed for convenience.
        method move takes nothing returns nothing
            local thistype this = current
            if SquareRoot((GetUnitX(.u) - .x[.lastpos]) * (GetUnitX(.u) - .x[.lastpos]) + (GetUnitY(.u) - .y[.lastpos]) * (GetUnitY(.u) - .y[.lastpos])) < 10 then
                set .lastpos = .lastpos + 1
                call IssuePointOrder(.u, "move", .x[.lastpos], .y[.lastpos])
            endif
        endmethod
       
        method destroy takes nothing returns nothing
            set .u = null
            call .deallocate()
        endmethod
       
        static method onInit takes nothing returns nothing
        // call TimerStart(NewTimer(), 0.1, true, static method move)
            // call TimerStart(NewTimer(), 0.1, true, function Wander.move)  // If the first doesn't work, try the second.
        endmethod
       
    endstruct
   
    function test takes nothing returns nothing
        local Wander wanderer
        set wanderer = Wander.create(gg_unit_n000_0003)
        call wanderer.addWaypoint(-21550.0, -21222.0)
        call wanderer.addWaypoint(-20000.0, -20000.0)
        call wanderer.addWaypoint(-19000.0, -19000.0)
    endfunction
   
endlibrary
 
^^


Static real array is usually the harder way to do about the assigned task, and I believe it involves multiplication. To each to their own preference.

By experience, members of a struct ...
JASS:
struct Foo
    real instance
endstruct

become arrays after parsing from vJASS to JASS.

However, static members parse into ordinary global variables.

From
JASS:
struct tre
    static real lol = 0.
endstruct

to

JASS:
// JassHelper globals
...
    real s__tre__lol = 0.

If you declare something like the following code...
JASS:
struct foo
    static real array instance
endstruct

the static array member will be similar to the ordinary member, ...
JASS:
// JassHelper globals

    real array s__foo__instance

but the member will no longer be associated with an instance. So, the following would not ideally work, ...
JASS:
    set this.instance = 5.

but this will..
JASS:
    set instance[this] = 5

As to how each instance is done, the association is this; Whatever integer parameter you give to the static array member will be used to divide the maximum number of instances in the struct.

So if you did the above like this,

JASS:
struct foo
    real array instance[5]
endstruct

The end result is that you'll only have 8190/5 = 1638 instances.

If I didn't address the question above, look at the topmost answer.
 
Status
Not open for further replies.
Top