• 🏆 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] Still new, am I doing it right, and some questions

Status
Not open for further replies.
Level 15
Joined
Dec 21, 2013
Messages
910
So I already spent 2 weeks reading vjass tutorials daily. And now I tried to make some spell. Its kinda confusing because each tutorials is different and not really mention the different method..My current target is just to make it works properly, so there are no configuration thingy. And I'm pretty sure its still got bugged if casted multiple times by same unit. Am I doing it right?

Here is some general question:
1. I'm still not sure when to use a global timer or timer each spell....

2.
JASS:
private tempdat array data
is actually same like when I use
JASS:
local tempdat this = i
but can be changed inside (so I can use the loop i), am I right?

3. do i need to add private for struct's child?
JASS:
private struct tempdat
        private unit caster
I saw some tutorial use it but I tried it and compiler report about private thingy..

4. How to use method OnInit properly? I see its different with normal init function. I tried but it doesnt work so i decided to make the init with function, and use call structname.methodname

5. How to revert back
JASS:
call AddUnitAnimationProperties( unit, "defend", true )
?

6. if I use
JASS:
globals
        private integer dindex = -1
inside scope, is that globals only works inside scope? so thats mean its fined to have same globals name on another scope, right?

7. how about a globals name is same with local inside scope? err... to make it easier, how to check to compiled vjass? I wanted to see it myself

8. whats the different between method and function? I see it same, only method is inside struct, so it can use thistype this. Or maybe I'm doing it wrong

9. If i use an unit indexer system, thats mean i cant setunitcustomvalue right? I think it will cause other system that use that bugged..




And here is my second spell. the first one, the map got corrupted somehow.

JASS:
scope MercilesPursuit initializer init
    //use ability, the next attack stun target
     globals
        private integer dindex = -1
        //timer period = CreateTimer()
        private integer spellID = 'AHds'
        private group g
    endglobals
 
    private struct tempdat
        unit caster
        real duration
    endstruct
 
    globals
        private tempdat array data
    endglobals
 
    private function stuns takes nothing returns nothing
        local unit u = udg_DamageEventSource
        local unit target = udg_DamageEventTarget
        local real timestun = 5
        call BJDebugMsg("attack")
        if IsUnitInGroup(u, g) == true then
            call stunplz(target, timestun)
            //set udg_GDS_Duration = timestun
            //set udg_GDS_Target = target
            //call TriggerExecute( gg_trg_GDS_Main )
            call AddUnitAnimationProperties( u, "", false )
            call UnitRemoveAbility(u, 'BHds')
            call GroupRemoveUnit(g, u)
            call BJDebugMsg("STUNshouldbe")
        endif
    endfunction
 
    private function periodic takes nothing returns nothing
        local integer i = 0
        local timer tmr = GetExpiredTimer()
        local tempdat this
        loop
            exitwhen i>dindex
            call BJDebugMsg("i= " + I2S(i))
            set this=data[i]
            set this.duration= this.duration - 0.03125
       
            if IsUnitInGroup(this.caster,g)==false then
           
                set data[i] = data[dindex]
                set i = i - 1
                set dindex = dindex - 1
                call this.destroy()
                call BJDebugMsg("CASTED")
           
            endif
       
            if this.duration <= 0 then
                call GroupRemoveUnit(g, this.caster)
                call AddUnitAnimationProperties( this.caster, "", false )
                set data[i] = data[dindex]
                set i = i - 1
                set dindex = dindex - 1
                set this.caster = null
                call this.destroy()
                call BJDebugMsg("TimeOUT")
            endif
       
            if dindex == -1 then
                call ReleaseTimer(tmr)
            endif
            set i = i+1
        endloop
    endfunction
 
    private function action takes nothing returns nothing
        local tempdat this= tempdat.create()
        set this.caster=GetTriggerUnit()
        set this.duration=5
        call ResetUnitAnimation(this.caster)
        call GroupAddUnit(g,this.caster)
        call AddUnitAnimationProperties( this.caster, "defend", true )
        set dindex=dindex + 1
        set data[dindex]=this
        call BJDebugMsg("dindex= " + I2S(dindex))
        if dindex==0 then
            call TimerStart(NewTimer(), 0.03125, true, function periodic)
            call BJDebugMsg("TimerStart")
        endif
   
    endfunction
 
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger(  )
   
        call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
        call TriggerAddAction( t, function stuns )
        call RegisterSpellEffectEvent(spellID, function action)
        set g = CreateGroup()
    endfunction
endscope
Currently it still use detect if unit in group, gonna change it unit has buff since the ability add buff. I use that stun system because i might be want to add stun duration modifier based on stats


and second spell.

JASS:
scope Shield initializer OnInit
    //deflect damage to nearby enemy
     globals
        private integer spellID = 'AHtc'
        private integer array index
        private group g
    endglobals
 
    private struct tempdat
        unit caster
        real duration
        real shield
   
        static integer dindex = -1
   
        static thistype array data
 
   
        static method damage takes unit caster, real damage returns nothing
            local group gro = CreateGroup()
            local real x = GetUnitX(caster)
            local real y = GetUnitY(caster)
            local unit f
            call GroupEnumUnitsInRange(gro, x, y, 900, null)
            call GroupRemoveUnit(gro,caster)
            loop
                set f = FirstOfGroup(gro)
                exitwhen f==null
                call UnitDamageTarget(caster,f,damage,true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                call GroupRemoveUnit(gro,f)
           
            endloop
            call BJDebugMsg("Damage")
        endmethod
       
 
        static method periodic takes nothing returns nothing
            local timer tmr = GetExpiredTimer()
            local thistype this = GetTimerData(tmr)
            //call BJDebugMsg("this= " + I2S(GetTimerData(tmr)))
            set this.duration= this.duration - 0.03125
            if IsUnitInGroup(this.caster,g)==false then
                call this.destroy()
                call BJDebugMsg("ShieldOFF")
                call ReleaseTimer(tmr)
            endif
            if this.duration <= 0 then
                call GroupRemoveUnit(g, this.caster)
                call this.destroy()
                call BJDebugMsg("TimeOUT")
                call ReleaseTimer(tmr)
            endif
       
        endmethod
 
   
    endstruct
 
        private function barriereflect takes nothing returns nothing
       
            local real damage = udg_DamageEventAmount
            local unit attacker = udg_DamageEventSource
            local unit attacked = udg_DamageEventTarget
            local integer i = GetUnitUserData(attacked)
            local tempdat this =  index[i]
   
            if IsUnitInGroup(attacked, g) == true then
                //call UnitRemoveAbility(attacked, 'BHds')
                call BJDebugMsg("attacked")
                if this.shield <= 0 then
                    call GroupRemoveUnit(g, attacked)
                else
                    set udg_DamageEventAmount=0
                    call tempdat.damage(attacked,damage)
                    set this.shield=this.shield-damage
                    set this.duration=this.duration - 1
                endif
            endif
        endfunction
 
 
        private function action takes nothing returns nothing
            local tempdat this= tempdat.create()
            local integer i = GetUnitUserData(GetTriggerUnit())
            set this.caster=GetTriggerUnit()
            set this.duration=10
            set this.shield=300
            call GroupAddUnit(g,this.caster)
            set index[i]=this
            call TimerStart(NewTimerEx(this), 0.03125, true, function tempdat.periodic)
            call BJDebugMsg("TimerStart")
        endfunction
 
        private function OnInit takes nothing returns nothing
            local trigger t = CreateTrigger(  )
            call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
            call TriggerAddAction( t, function barriereflect )
            call RegisterSpellEffectEvent(spellID, function action)
            set g = CreateGroup()
        endfunction
 
endscope

Now this one is confusing me. Err.. there are some unuesed variable because I actually copy paste previous ability and want to use method instead. But i dont know how to make method OnInit, so still use some function. So I make a trigger to detect cast that shield ability. the other trigger is dettect if that guy attacked.

function action is kinda straight forward, just register thingy sand start countdown. The periodic just detect if shield still on and remove unit from group when done.

function barriereflect (i changed this name because i thought it cause error, it was "shield" but same name with scope is not allowed). It registed if the shielded guy attacked. I dont know how to set up the "temp this" other than saving a global indeger storing each unit id to "this" value. well, it will bugged if caster multiple times by same unit... actually i can fix this by adding more thing in function action, by checking if unit is already shielded if will call another function to load the "this". Is there another way to make this spell?

I cant remember every question i have, so i will post here again if i remember them
 
Last edited:
1. I'm still not sure when to use a global timer or timer each spell....
It's hard to give a unversal answer, because both can be perfectly fine, depending on what you want and need.
In your case I would bing each instance a timer, using hashtable instead of using loop and extra data structure (array).

2. private tempdat array data and local tempdat this = i isn't the same.
One is an array, the other one a local scalar variable.

3, 6, 7 (public/private keyword):

If you declare a scope, or library, everything within it could be normaly used, just like in normal jass, if you don't use a private or public keyword. It's in no kind "protected".
When you declare something with using the private keyword, then this variable, function, struct, or method will be accessible only in the respective scope:
  • using private inside scope -> only, but the whole scope can access it
  • usingprivate inside struct -> only, but the whole struct can access it
  • usingprivate inside library ->only, but the whole library can access it
When you directly declare a struct as private, but not the methods/variables inside the struc, so like:
JASS:
library A

private struct A
    static integer a_i
endstruct

struct B
endstruct
Then it means nothing from outside the library can access struct A, or it's content. However, everything inside the library, so struct B can still access struct A and integer a_i, because the integer itself is not declared as private.

When declaring something in a library, then you need to use the library name before you can access it:
JASS:
library LibName
    globals
        public integer i = 0
    endglobals
endlibrary

function test takes nothing returns nothing
    set LibName_i = 3 // library prefix
endfunction
I recommend to use vJASS manual as reference.

4. How to use method OnInit properly?
Inside a struct you can declare a static onInit method, which will be executed automatically. For scopes, and library you need to call it with the initializer Init (or an other func name):

JASS:
struct S
    private static method onInit takes nothing returns nothing
        // will run
    endmethod
endstruct

library L initializer Init
    private function Init takes nothing returns nothing
        // will run
  endfunction
endlibrary

whats the different between method and function? I see it same, only method is inside struct, so it can use thistype this. Or maybe I'm doing it wrong
Functions, binded to a struct, so to a new type of object, are called methods. Methods are functions that exist exclusively for your objects of the struct.
A static method is not function that does exclusively for struct objects, but for the whole struct once. So a static method is actually almost the very same like a normal function that is wrapped in a struct.
  • You need create struct instance to use methods, and call it with using it.
  • You need nothing created to call static methods. They can be called always, like a function.
Inside the struct you can use thistype instead of the struct's name, it's just an alternative. thistype means the type/name of the struct, while this is the actual instance/object with which a method was called, for example:
JASS:
struct S

static method create takes nothing returns thistype
    local thistype this = allocate()
    return this
endmethod

//now will come the very same, but without "thistype"
static method create takes nothing returns S
    local S this = allocate()
endmethod

endstruct
How to use this:
JASS:
struct S
// method can be only used by struct instance
method run takes nothing returns nothing
    // "this" has used run method
    call this.destroy()
endmethod

static method create takes nothing returns nothing
    local thistype this = allocate() // create new instance
    call this.run()
endmethod
endstruct

function foo takes nothing returns nothing
    call S.create() // can access the static method
endmethod

9. If i use an unit indexer system, thats mean i cant setunitcustomvalue right? I think it will cause other system that use that bugged..
Basically yes, and especially in JASS you probably don't need this anymore. UnitUserData can be read, but never be manipulated but a system when UnitIndexer is used.

===

Could we focus on first code, first, and could you elaborate what exactly is needed to have clarified furthermore?
 
Level 39
Joined
Feb 27, 2007
Messages
5,011
err... to make it easier, how to check to compiled vjass? I wanted to see it myself
To see it in a compiled map you have to open the .w3x file with an MPQ editor and extract war3map.j. However I wouldn't really suggest that because of how messy and time consuming it can be to sort through literally hundreds of functions named things like __s__mytype__meth_153 (all vJASS variables and functions become crazy convoluted messes when converted to regular JASS).

Instead what I would recommend you do is intentionally write a compiler error (not a parser error, like writing "acll" instead of "call", because that will throw the error before the conversion to regular JASS) into your code just after the section you want to see, so the compiler finds it and tells you. In the alert/compiler error window that pops up you can read through the converted JASS that the JNGP has made out of your vJASS code, ostensibly to solve your error. But you can use it to look through the converted code. For example:
JASS:
struct mystuff
    private integer N
    private unit myU

    method doSomething takes nothing returns unit
        return myU
        call NotARealFunction() //throws error
    endmethod

    //Or put it out here
    method error takes nothing returns nothing
        set notavariable = 5
    endmethod
endstruct
 
Last edited:
Level 15
Joined
Dec 21, 2013
Messages
910
@Pyrogasm Thanks! Never really thought of it 0.0 I always got that error because I always type "return" instead of "returns"

@IcemanBo Thanks! Now I felt kinda guilty asking those basic question :p
errr... does "onInit" and "OnInit" different? I guess I typed it "OnInit" previously so the trigger not work..

I guess we should take a look at the second one. Its practically do the same, its just i tried to use method xp
Just take a look on the lower part of this code
JASS:
scope Shield initializer OnInit
    //create a shiled, and deflect incoming damage to nearby units
     globals
        private integer spellID = 'AHtc'
        private integer array index
        private group g
    endglobals
   
    private struct tempdat
        unit caster
        real duration
        real shield   
       
        static method damage takes unit caster, real damage returns nothing
            local group gro = CreateGroup()
            //is it find to use local group g again? i already used it for a global..
            local real x = GetUnitX(caster)
            local real y = GetUnitY(caster)
            local unit f
            call GroupEnumUnitsInRange(gro, x, y, 900, null)
            //idk how to set up the booleanexpression for now, so I just remove the caster for now so 
            //it  wont have infinite loop self damage that make shield depleted instantly
            call GroupRemoveUnit(gro,caster)
            loop
                set f = FirstOfGroup(gro)
                exitwhen f==null
                call UnitDamageTarget(caster,f,damage,true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                call GroupRemoveUnit(gro,f)
            endloop
            call BJDebugMsg("Damage")
        endmethod
           
   
        static method periodic takes nothing returns nothing
            local timer tmr = GetExpiredTimer()
            local thistype this = GetTimerData(tmr)
            //call BJDebugMsg("this= " + I2S(GetTimerData(tmr)))
            set this.duration= this.duration - 0.03125
            call BJDebugMsg("Shield = " + I2S(this,duration))
            if IsUnitInGroup(this.caster,g)==false then
                call this.destroy()
                call BJDebugMsg("ShieldOFF")
                call ReleaseTimer(tmr)
            endif
            if this.duration <= 0 then
                call GroupRemoveUnit(g, this.caster)
                call this.destroy()
                call BJDebugMsg("TimeOUT")
                call ReleaseTimer(tmr)
            endif
           
        endmethod     
    endstruct
   
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //this function used when a unit attacked, detecting if unit already cast shield
        private function barriereflect takes nothing returns nothing
           
            local real damage = udg_DamageEventAmount
            local unit attacker = udg_DamageEventSource
            local unit attacked = udg_DamageEventTarget
            local integer i = GetUnitUserData(attacked)
            //load the "this"
            local tempdat this =  index[i]
       
            if IsUnitInGroup(attacked, g) == true then
                //call UnitRemoveAbility(attacked, 'BHds')
                call BJDebugMsg("attacked")
                if this.shield <= 0 then
                    call GroupRemoveUnit(g, attacked)
                else
                    set udg_DamageEventAmount=0
                    call tempdat.damage(attacked,damage)
                    set this.shield=this.shield-damage
                    set this.duration=this.duration - 1
                endif
            endif
        endfunction
   
   
        private function action takes nothing returns nothing
            local tempdat this= tempdat.create()
            local integer i = GetUnitUserData(GetTriggerUnit())
            set this.caster=GetTriggerUnit()
            set this.duration=10
            set this.shield=300
            call GroupAddUnit(g,this.caster)
            //index[i] used for load "this" value, so i can recall "this" on other trigger
            //my other option is using hashtable, not really familiar with it yet
            set index[i]=this
            call TimerStart(NewTimerEx(this), 0.03125, true, function tempdat.periodic)
            call BJDebugMsg("TimerStart")
        endfunction
   
        private function OnInit takes nothing returns nothing
            local trigger t = CreateTrigger(  )
            call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 1.00 )
            call TriggerAddAction( t, function barriereflect )
            call RegisterSpellEffectEvent(spellID, function action)
            set g = CreateGroup()
        endfunction
endscope

I am really bad at making question 0.0 So i wanted to make a ability that create shield, reflect incoming damage. I make 2 trigger, one for register the unit, and countdown the duration. Second trigger is when the unit is attacked, nulling the damage and reduce the shield capacity. I'm not sure how to recall the struct on the second trigger. Currently i use "set index=this" then "local tempdat this = index" so i can get recall it. My other option is to make hashtable, which i'm still not familiar with it. Am I doing that right?
 
JASS is case sensitive, means there's a difference with "onInit" and "OnInit". Only "onInit" will run automatically inside a struct.
i use "set index=this" then "local tempdat this = index"
What you do is:
  • index[UnitIndex] = this
  • this = index[UnitIndex]
If you use a unit indexer of somd kine it should work, as it's a normal array being used.
 
Level 39
Joined
Feb 27, 2007
Messages
5,011
Hashtable integer store/retrieval on the unit's handle id (or equivalent) is a pretty fast operation, actually. Indexer is generally easier, but you can definitely do this without resorting to an indexer if want.
 
Status
Not open for further replies.
Top