• 🏆 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] What should i use for variable storage?

Status
Not open for further replies.
Level 4
Joined
May 25, 2009
Messages
100
Hi, i'm quite new to (v)jass and i want to save different informations (variables) for units. (Like a boolean and effect to create a attachment and other stuff).
My question is now what i should use to do it.
Currently im using hashtables, but i througt that there is maybe a way to create a struct, but how can i access the informations later?
Can i create structs with "custom" names (like the unitid) to acces them later?

Like:
JASS:
local unitstruct I2S(GetHandleId(GetTriggerUnit()))=unitstruct.create()


set hasattachmentbool= I2S(GetHandleID(GetEnteringUnit())).attachmentbool
(I know that this won't work, but i think you get the point)



PS: I don't just want to create a attachmentsystem, there are other informations to store for units, so plz don't post some attachmentsystems...thats not what i want

Thx for your answers!
 
Structs are great for storing multiple values to one thing. When you "create" a struct, you are creating a new instance. Each instance can have its own members, and those members vary depending on which instance you have (that's what I meant by storing multiple values to one thing). So let's take this example:
JASS:
struct A
    real x
    real y
endstruct

function Example takes nothing returns nothing
    local A myStruct = A.create()
    set myStruct.x = 5
    set myStruct.y = 10
endfunction

The A.create() will return a specific instance, and then you can assign the member values to that instance. In this case, x = 5 and y = 10. If you have another instance, those x and y values won't be the same unless you make it so.

Now, you want to assign some variables to a unit, correct? Well, that just means we have to associate a unit with the struct instance. After all, if each instance has its own variables with values, and we have an instance for each unit, then you will have a bunch of variables that you can assign for one unit! (which is what you seem to be trying to do)

The problem is the association. How do we get the struct instance from the unit? If you just create an instance like so:
JASS:
struct A
    unit u
endstruct
/* .. some code later .. */
local A temp = A.create()
set A.u = GetTriggerUnit()
How will you get the instance later on? You have to have some way to point back to the instance. Some people will use unit indexing systems for this, in that you can have a struct instance that is equal to the unit's index. Whenever you need to get the struct instance, you would just do A(GetUnitIndex(<unit>)), or something similar.

I assume you don't want to use a custom system. Therefore, your best bet is to use hashtables. This is actually pretty efficient, so don't worry about it being slow or anything. You basically would assign it under the unit's handle ID:
JASS:
call SaveInteger(hash, 0, GetHandleId(<unit>), myInstance)
So let me give you an example. I want to store a unit's last point order in a struct. I want to store the real x and y of it.
JASS:
scope Example initializer onInit
/* scopes are just wrappers */
/* I use it to declare an initialization function */
/* initializers are called on map initialization */
    globals
        private hashtable hash /* for associating unit with struct */
    endglobals    

    struct LastOrder
        real x
        real y
    endstruct 

    private function onPointOrder takes nothing returns boolean
        /* this function is called when a unit is issued a point order */
        /* see the function onInit for the setup */
        /* first we declare the instance and create it */
        local LastOrder node = LastOrder.create()

        /* next we set the member values */
        set node.x = GetOrderPointX()
        set node.y = GetOrderPointY()

        /* 
             now if we want to associate it with a unit
             we can simply save it under a hashtable 
        */
         call SaveInteger(hash, 0, GetHandleId(GetTriggerUnit()), node)
         /* you can save struct instances as integers */
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        set hash = InitHashtable() /* initialize our hashtable */
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        /* the function above registers when a unit is issued a point order */
        call TriggerAddCondition(t, Condition(function onPointOrder))
    endfunction

endscope

Now that we have it all saved, how do we retrieve it? That part is pretty simple:
JASS:
local LastOrder temp = LoadInteger(hash, 0, GetHandleId(<unit>))

That is all you need to do! Then you can refer to temp.x and temp.y freely. When you are done using the instance, you can call the .destroy() method if you are not going to use that specific instance at all anymore.

You may be wondering, if we can consider struct instances as integers, can't we just save under the handle ID and use that as the struct instance? Well, sadly the struct instance value must be a number between 0-8191. Of course, you can use subtraction or hash the value into that number, but it gets complex, messy, and it can be dangerous if you have too many handles and aren't careful. That's why a hashtable or unit indexer is the best route.

As an advanced tip -- if you think using LoadInteger(hash, 0, GetHandleId(<unit>) is ugly for each time you want to use it, you can take advantage of operators:
JASS:
globals
    hashtable hash = InitHashtable()
endglobals

struct LastOrder
    real x
    real y
    
    static method operator [] takes unit u returns thistype
        return LoadInteger(hash, 0, GetHandleId(u))
    endmethod
endstruct
If you used the same code above, then instead of:
JASS:
local LastOrder temp = LoadInteger(hash, 0, GetHandleId(GetTriggerUnit()))
You could simply do:
JASS:
local LastOrder temp = LastOrder[GetTriggerUnit()]
Pretty cool, huh? I won't go into too much detail about that, because it requires a bit more practice. It is just to show you what cool things you can do with vJASS.

If you want to know more, you should check out Vexorian's jasshelper manual, look at some tutorials around here, or just go out and start practicing with some code. :) It is daunting at first but very powerful.

----
Note: I did not compile or test any of this, so I apologize if there are some syntax errors.
 
Level 4
Joined
May 25, 2009
Messages
100
Thank you! Thats exactly what i wanted to know and makes my life so much easier ;D
That you can store structs as integers is so useful^^

PS:
Is call TriggerAddCondition() basicly the same than TriggerAddAction() just faster and what should i do when i want actions and conditions?
 
PS:
Is call TriggerAddCondition() basicly the same than TriggerAddAction() just faster and what should i do when i want actions and conditions?

Ah yes. In JASS we usually merge conditions and actions since conditions are faster. To simulate a condition, this is usually what you do:
JASS:
scope Test initializer Init
    private function onSpellEffect takes nothing returns boolean
        // we have to return boolean because we pass it under Condition(func..)
        if GetSpellAbilityId() == 'A000' then // this is your condition
            // actions here 
        endif 
        return false 
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function onSpellEffect))
    endfunction
endscope

Sometimes that becomes a hassle when you have so many locals, because you don't want to declare them outside the "if" block in case the condition isn't true. So you can alternatively do:
JASS:
scope Test initializer Init
    private function onSpellEffectCond takes nothing returns boolean
        return GetSpellAbilityId() == 'A000'
    endfunction

    private function onSpellEffect takes nothing returns nothing
        // actions
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function onSpellEffectCond))
        call TriggerAddAction(t, function onSpellEffect)
    endfunction
endscope

The difference in speed is very minimal. It just comes down to which one you prefer. :)
 
or maybe even this:

JASS:
scope Test initializer Init

    private function onSpellEffect takes nothing returns nothing
        // actions
    endfunction

    private function onSpellEffectCond takes nothing returns boolean
        if GetSpellAbilityId() == 'A000' then
            call onSpellEffect() 
        endif
        return false
    endfunction

   
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function onSpellEffectCond))
    endfunction
endscope

that way, you don't register an action and it might also work a bit faster I guess... since the actions are called from within the conditions part...
 
Status
Not open for further replies.
Top