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

[vJASS] Basic Struct Help

Status
Not open for further replies.
Level 17
Joined
Feb 11, 2011
Messages
1,860
[Solved] Basic Struct Help

Hello,

I am trying to learn how structs work. I am planning to use them in a spell called Death Coil, which deals damage to the target equal to 20% of their current hit points.

The timer in the script is because I want the damage to be done only when the missile hits, i.e. I am calculating how long it takes to hit and waiting that amount of time.

Please note that I am new to vJASS and simple explanations will be appreciated :thumbs_up:

JASS:
scope DeathCoil

    struct Spell
        unit Unit
    endstruct
    
    function exp_act takes nothing returns nothing
        local real dmg = (GetUnitState(target.Unit, UNIT_STATE_LIFE) * 0.2)
        local texttag t = CreateTextTag()
        call UnitDamageTarget(caster.Unit, target.Unit, dmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
        call SetTextTagText(t, "|cff8000FF-" + I2S(R2I(dmg)) + "|r", 0.023)
        call SetTextTagPosUnit(t, target.Unit, 50)
        call SetTextTagVelocity(t, 0, 64 * 0.071 / 128)
        call SetTextTagPermanent(t, false)
        call SetTextTagLifespan(t, 2)
        call SetTextTagFadepoint(t, 1)
        call SetTextTagVisibility(t, true)
        set t = null       
    endfunction

    function act takes nothing returns nothing
        local Spell caster = Spell.create()
        local Spell target = Spell.create()
        set caster.Unit = GetTriggerUnit()
        set target.Unit = GetSpellTargetUnit()
        local timer t = CreateTimer()
        local real dx = (GetUnitX(caster.Unit) - GetUnitX(target.Unit))
        local real dy = (GetUnitY(caster.Unit) - GetUnitY(target.Unit))
        local real dist
        set dist = SquareRoot((dx * dx) + (dy * dy))
        call TimerStart(t, dist / 1000, false, function exp_act)
        set t = null
    endfunction
    
    function cond takes nothing returns boolean
        if (GetSpellAbilityId() == 'A03W') then
            call act()
        endif
        return false
    endfunction
    
    function InitTrig_Lvl_10_Death_Coil takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function cond))
    endfunction
    
endscope

Basically, I want to store the caster and target units instead of using global variables.

When saving, I get the following message:

"target is not of a type that allows . syntax"

Thanks for your help!
 
Last edited:
1. Why make it a scope if the functions are not set as private?

2. Since a struct is basically used as a bundle of variables and the ability to apply behaviours to them, i don't really see why you would want to make a struct containing a single variable.. you could equally well put both target and caster in the same struct. The reason why you would want to put them in a struct in the first place is just to make it easier to attach the data to the timer.

3. And that leads us to your problem. You get syntax error because "target" and "caster" were local variables in the function "act", and do not exist in the function "exp_act". To be able to use them in "exp_act", you need to somehow attach the struct to the timer before you fire it. This can be easily done with hashtables, especially since structs are integer values. Just make a hashtable, and use "SaveInteger(TableName, 0, GetHandleId(timer), target)" to attach the struct to the timer. Then, in the next function, set timer = GetExpiredTimer, and set "local Spell target = LoadInteger(TableName, 0, GetHandleId(timer))" to retrieve the struct again!

Just remember that you have to create a global hashtable first and initialize it. Also remember to use "RemoveSavedInteger" to remove the struct from the hashtable when you are done with it, otherwise the code will leak. You should also destroy the timer "t" that you created by adding "call DestroyTimer(GetExpiredTimer())" to the callback function, otherwise it will also leak.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
So wouldn't it be simpler to use two global variables? udg_caster and udg_target?

Question: Why does this spell not work? It is RemoveUnit that is causing it. If I remove RemoveUnit, it works. However, I need to remove the existing portals when the spell is cast.
Basically, the spell creates 2 portals that spawn creeps. When the unit casts the spell again, the existing portals are removed and new ones are created.
JASS:
scope CreateDarkRifts initializer InitTrigger

    globals
        unit udg_LeftPortal
        unit udg_RightPortal
    endglobals

    private function act takes unit u returns nothing
        local real x1 = GetRectCenterX(gg_rct_LeftPortal)
        local real y1 = GetRectCenterY(gg_rct_LeftPortal)
        local real x2 = GetRectCenterX(gg_rct_RightPortal)
        local real y2 = GetRectCenterY(gg_rct_RightPortal)
        call SetPlayerAbilityAvailable(GetOwningPlayer(u), 'A011', false)
        set udg_LeftPortal = CreateUnit(GetOwningPlayer(u), 'h00H', x1, y1, 180)
        set udg_RightPortal = CreateUnit(GetOwningPlayer(u), 'h00H', x2, y2, 0)
    endfunction

    private function cond takes nothing returns boolean
        if (GetSpellAbilityId() == 'A011') then
            call RemoveUnit(udg_LeftPortal)
            call RemoveUnit(udg_RightPortal)
            call act(GetTriggerUnit())
        endif
        return false
    endfunction
    
    private function InitTrigger takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function cond))
        set t = null
    endfunction

endscope
 
Last edited:
Level 4
Joined
Mar 27, 2008
Messages
112
I think it's because the globals have no initial value (not sure though) try this:
JASS:
globals
        unit udg_LeftPortal = null //also you do not need to name your globals udg_..... as udg stands for user difined global and that's automaticly added to globals created in gui it's not needed to add it here just more typing:P
        unit udg_RightPortal = null
endglobals
 
Well, first of all, globals are not multi-instanceable. If for instance you have two players casting the spell at the same time, the second will overwrite the global data in the interval and break the spell for the first player. For the same reason, your above script will only work for one unit - if another unit tries to cast the spell, it will delete the portals for all other players.

A hashtable is simply a database where you can store data in different slots. Think of it as a bank vault, where you have lockers ranging from number 0 to the highest integer value supported by warcraft. You can choose to put data inside one of theese lockers and later retrieve it using it's number. Since all handles in wc3 - such as units, timers, destructables, locations etc - have their own unique ID, you can use this ID as the key for your data, since you can retrieve the timer you used in it's callback function and use it to find your data again. After you have done this, you simply clear the locker (which cannot be done with global variables since, well, they're global).

A struct is a class, and you can kinda compare it to a unit or a doodad in wc3. A unit holds different types of values, such as health, mana, move speed, etc, and there are different behaviours that units use to change theese values. In a similar way, a struct is a bunch of variables, and it can also hold so called "methods", which are basically functions private to the struct itself. For example, a struct can have a "create" method, that will fire everytime a struct instance is created, and you can use it to do stuff like initializing variables inside the struct and such. If you have a struct called "bullet", it could look like this:

JASS:
struct bullet
    unit u            = null
    real direction    = 0
    real speed        = 0
    boolean doesDrop  = true

    static method create takes real x, real y, player owner, real direction, real speed, boolean drops returns bullet //"static" implies that the function is not instanced for each struct, but rather more like a normal function private to the struct class.
    local bullet B = bullet.allocate() //allocate does exactly the same as "create" does when you use it, except here we have overridden the function "create" with our own, so we have to use allocate to create the struct instance inside it
    set B.direction = direction
    set B.u = CreateUnit(x, y, owner, direction) //dunno if the parameter order is correct, just writing from the top of my head
    set B.speed = speed
    set B.doesDrop = drops
   
    return B

    endmethod
endstruct

To move the bullet, we can then add instanced methods like this:

JASS:
method move takes nothing returns nothing
    set .x = .x+.speed*Cos(.direction*bj_DEGTORAD) //as you see, we can now simply use "." to reference the struct, since because of how we will be calling the method later, the system will already know what struct it is. 
    set .y = y+.speed*Sin(.direction*bj_DEGTORAD)
    if .doesDrop = true then 
      call SetUnitFlyHeight(.u, GetUnitFlyHeight(.u)-10, 10)
    endif
endmethod

and finally, to call the function:

JASS:
local unit u = GetTriggerUnit()
local bullet MyBullet = bullet.create(GetUnitX(u), GetUnitY(u), GetOwningPlayer(u), GetUntiFacing(u), 300, true)
call MyBullet.move()

I am just using GetTriggerUnit here for no apparent reason, you would ofcourse want the .move() function to be called periodically every 0.03 seconds or whatsoever, but this is just for show.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Wow, thanks man. This really helps a lot! I will experiment around and try to understand it better.

EDIT: Would you mind explaining the difference between static and non-static things? I read about it in a tutorial, but I didn't really understand it.
 
Level 6
Joined
Jun 16, 2007
Messages
235
Static is same as normal function.
Non-static is a method that is -different- for every struct you create.
(read the jasshelper manual)
 
This is how it works in practice:

A normal method can be called from an instance of the struct, like this:

JASS:
call b.move()

A static method however, cannot be called through a separate instance, it has to call through the struct name itself, like this:

JASS:
local bullet b = bullet.create()

Notice how in the first case we call the function through the variable, and in the second through the name of the struct?

The static methods are really just like normal private functions, that are private to the struct. You can also create static variables inside the struct, like this:

JASS:
struct bullet
    unit u = null
    static real DefaultMoveSpeed = 100
endstruct

In the above case, the variable "DefaultMoveSpeed" practically works like a private global variable inside the struct. It can be referenced like this:

JASS:
set bullet.DefaultMoveSpeed = 200

If the variable was not static, like the variable "u", it would be unique for each instance of the struct.

The reason the "create" method has to be static is, well, because the instance has not yet been created before you call it, so it is kinda hard to call through it!
 
Status
Not open for further replies.
Top