Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Dynamic Indexing Tutorial

Discussion in 'JASS/AI Scripts Tutorials' started by Malhorne, Dec 25, 2013.

  1. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Hi everybody !
    Today, I’ll explain you how to use properly dynamic indexing with structs (so in vJASS :D)!
    I’ll not explain how struct works (it is not the goal of this tutorial) but how to properly index them to render spells MUI.



    I – Introduction
    Okay I hope you know how works struct because as I just said before I’ll not explain how they work.
    I’ll show two manners to do this indexing :
    - The first way is C-like which means that we will use structs just as data.
    - The second way is Java-like which means that we will use methods and static member to do so.
    I hope you know a bit how work indexing otherwise I’ll explain this just now (and again later in the code commentaries).
    Dynamic indexing’s goal is to do MUI spells!
    If you ever did so in GUI you remembered that it needs one array for each thing you want to store about the spell. Now with structs we will just have to store the struct inside a struct array and there we go, we will be able to access every member!
    To do dynamic indexing we also need an index that will increase when an instance will fire and that will decrease when an instance will be finished and recycled.



    II – What spell will we do?
    We will do a timed damage spell.
    It will deal 75 damage per second over 3 seconds.
    So we will have to store:
    - The caster of the spell (to know which unit will deal damages) => caster
    - The target of the spell (to know which unit will get damaged) => target
    - The amount of damage => damage
    - The time there is before the damaging => temp
    - How many times will we have to hit the target before the end of the spell => steps
    So we got our struct members just above!
    Now, how the spell will work: the caster cast the spell, then 1s later the target got the damage. So we will need a periodic function that decrease the temp value and when the temp value is below or equal to 0 we will damage the target and recycle the index.



    III – The spell
    So we will start with global variables we need:
    Code (vJASS):
     globals
        private integer dindex = -1
        private constant real FPS = 0.0312500
        private timer period = CreateTimer()
    endglobals

    So there is our index that will start to -1 and each time an instance fire we will increase it by one so we will start to the index 0. We got our FPS of 0.0312500 which is surely one of the best period interval you can have.
    Why it is a good period ? Because 1/0.0312500=32 so we have an integer number for the number of period per second :)
    And at last we create a timer.
    Now we will create our struct:
    Code (vJASS):
    private struct tempDat
        unit caster
        unit target
        real temp
        real damage
        integer steps
    endstruct


    Now we need a struct array!
    So we will just add it to our global block:
    Code (vJASS):
     globals
        private integer dindex = -1
        private constant real FPS = 0.0312500
        private tempDat array data
        private timer period = CreateTimer()
    endglobals

    Okay now we will use a scope to implement our spell so the code looks like this :
    Code (vJASS):
    scope MySuperCoolSpell initializer init
        globals
            private integer dindex = -1
            private constant real FPS = 0.0312500
            private tempDat array data
            private timer period = CreateTimer()
        endglobals

        private struct tempDat
            unit caster
            unit target
            real temp
            real damage
            integer steps
        endstruct

        private function cond takes nothing returns boolean
            if GetSpellAbilityId() == OurSpellId then
         
            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 cond))
            set t = null
        endfunction
    endscope

    Okay I implemented the init function and the condition function (with nothing inside ^-^)!
    Now we will implement the beginning of the condition function!
    Code (vJASS):
    private function cond takes nothing returns boolean
        local tempDat this
        if GetSpellAbilityId() == OurSpellId then
            //We create an instance of tempDat
            set this = tempDat.create()
            //We store the caster of the spell
            set this.caster = GetTriggerUnit()
            //We store the target of the spell
            set this.target = GetSpellTargetUnit()
            //We store the damage of the spell
            set this.damage = 75 * GetUnitAbilityLevel(this.caster, OurSpellId)
            //We store in how many times the spell will deal damage.
            set this.temp = 1
            //We will do the damage three times.
            set this.steps = 3
        endif
        return false
    endfunction

    Ok so I chose 75*level and 1s for fun you can freely change them or better store them into globals block for the second) or into constant function before the core spell but I’ll just interest myself to the indexing.

    So I’ll implement the indexing :
    Code (vJASS):
     private function cond takes nothing returns boolean
        local tempDat this
        if GetSpellAbilityId() == OurSpellId then
            //We create an instance of tempDat
            set this = tempDat.create()
            //We store the caster of the spell
            set this.caster = GetTriggerUnit()
            //We store the target of the spell
            set this.target = GetSpellTargetUnit()
            //We store the damage of the spell
            set this.damage = 75 * GetUnitAbilityLevel(this.caster, OurSpellId)
            //We store in how many times the spell will deal damage.
            set this.temp = 1
            // We will do the damage three times.
            set this.steps = 3
            //We increase our index by one.
            set dindex = dindex + 1
            //We store our struct instance into the array.
            set data[dindex] = this
            //if the array was empty before
            if dindex == 0 then
                //We start the timer (I didn’t create the periodic function for the moment wait !)
                call TimerStart(period, FPS, true, function periodic)
            endif
        endif
        return false
    endfunction

    So to rephrase this we increase our index, we store the struct instance and if the array was empty so we start our timer.
    Sweet clear isn’t it :) ?
    Now let’s do our periodic function:
    Code (vJASS):
    private function periodic takes nothing returns nothing
        local tempDat this
        local integer i = 0
        loop
            exitwhen i>dindex
            set this = data[i]
            …
            set i = i + 1
        endloop
    endfunction

    With this we’ll loop through every struct instance we stored before.
    Now we will do the core of the spell (nothing complicated xD) : we have to decrease the temp value of FPS each time we run the periodic function and if the temp value is expired we have to damage thetargeted unit :
    Code (vJASS):
    private function periodic takes nothing returns nothing
        local integer i = 0
        local tempDat this
        loop
            exitwhen i>dindex
            set this = data[i]
            set this.temp = this.temp-FPS
            if this.temp <= 0 then
                call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                //We decrement the number of times we damaged the unit.
                set this.steps = this.steps - 1
                //If it the third time the unit is damaged so we will stop the spell
                if this.steps == 0 then
                    //Wash leaks :)
                    set this.caster = null
                    //Wash leaks :)
                    set this.target = null
                    //The spell is finish we don’t need to have this struct anymore.
                    //We will see the recycle after
                    call this.destroy()
                //Otherwise we continue the spell by setting the temp value to 1 again.
                else
                    set this.temp = 1
                endif
            endif
            set i = i + 1
        endloop
    endfunction

    Now we have to recycle our index:
    Code (vJASS):
    set data[i] = data[dindex]
    set i = i – 1
    set dindex = dindex – 1

    To explain this piece of code we put the last stored instance to the position where an instance has been destroyed and we decrement of one both index.
    Now we implement it:
    Code (vJASS):
    private function periodic takes nothing returns nothing
        local integer i = 0
        local tempDat this
        loop
            exitwhen i>dindex
            set this = data[i]
            set this.temp = this.temp-FPS
            if this.temp <= 0 then
                call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                //We decrement the number of times we damaged the unit.
                set this.steps = this.steps - 1
                //If it the third time the unit is damaged so we will stop the spell
                if this.steps == 0 then
                    //Wash leaks :)
                    set this.caster = null
                    //Wash leaks :)
                    set this.target = null
                    set data[i] = data[dindex]
                    set i = i – 1
                    set dindex = dindex – 1
                    //The spell is finish we don’t need to have this struct anymore.
                    call this.destroy()
                //Otherwise we continue the spell by setting the temp value to 1 again.
                else
                    set this.temp = 1
                endif
            endif
            set i = i + 1
        endloop
    endfunction

    Okay now there is still one problem !
    The timer will run forever even if we got no instance :/
    So we’ll have to check if your index is equal to -1 (meaning the array is empty):
    Code (vJASS):
    if dindex == -1 then
        call PauseTimer(period)
    endif


    So here is the result :
    Code (vJASS):
    scope MySuperCoolSpell initializer init
        globals
            private integer dindex = -1
            private constant real FPS = 0.0312500
            private timer period = CreateTimer()
        endglobals
       
        private struct tempDat
            unit caster
            unit target
            real temp
            real damage
            integer steps
                   
            method destroy takes nothing returns nothing
                //Wash leaks
                this.caster = null
                this.target = null
                if dindex == -1 then
                    call PauseTimer(period)
                endif
                call this.deallocate()
            endmethod
        endstruct
       
        //Sometimes it bugs if you declare this before the struct so in the final result I put it after for safety.
        globals
            private tempDat array data
        endglobals
       
        private function periodic takes nothing returns nothing
            local integer i = 0
            local tempDat this
            loop
                exitwhen i>dindex
                set this = data[i]
                set this.temp = this.temp-FPS
                if this.temp <= 0 then
                    call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                    set this.steps = this.steps - 1
                    if this.steps == 0 then
                        set data[i] = data[dindex]
                        set i = i - 1
                        set dindex = dindex - 1
                        call this.destroy()
                    else
                        set this.temp = 1
                    endif
                endif
                set i = i + 1
            endloop
        endfunction
       
        private function cond takes nothing returns boolean
            local tempDat this
            if GetSpellAbilityId() == 'A000' then
                set this = tempDat.create()
                set this.caster = GetTriggerUnit()
                set this.target = GetSpellTargetUnit()
                set this.temp = 1
                set this.damage = 75*GetUnitAbilityLevel(this.caster, 'A000')
                set this.steps = 3
                set dindex = dindex + 1
                set data[dindex] = this
                if dindex == 0 then
                    call TimerStart(period, FPS, true, function periodic)
                endif
            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 cond))
            set t = null
        endfunction
    endscope





    IV - Now within the struct
    I'll put the code with commentary to show you how to do it only in the struct :
    Code (vJASS):
    scope MySuperCoolSpell
        globals
            private constant real FPS = 0.0312500
        endglobals
       
        private struct tempDat
            unit caster
            unit target
            real temp
            real damage
            integer steps
            //Static members work like globals :)
            static integer dindex
            static timer period
            static thistype array data
           
            method destroy takes nothing returns nothing
                //Wash leaks
                this.caster = null
                this.target = null
                if dindex == -1 then
                    call PauseTimer(period)
                endif
                call this.deallocate()
            endmethod
           
            static method periodic takes nothing returns nothing
                local integer i = 0
                local tempDat this
                loop
                    exitwhen i>dindex
                    set this = data[i]
                    set this.temp = this.temp-FPS
                    if this.temp <= 0 then
                        call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                        set this.steps = this.steps - 1
                        if this.steps == 0 then
                            set data[i] = data[dindex]
                            set i = i - 1
                            set dindex = dindex - 1
                            call this.destroy()
                        else
                            set this.temp = 1
                        endif
                    endif
                    set i = i + 1
                endloop
            endmethod
           
            static method cond takes nothing returns boolean
                //Thistype is replaced by the name of the struct :)
                local thistype this
                if GetSpellAbilityId() == 'A000' then
                    set this = thistype.allocate() //Exactly the same as create
                    set this.caster = GetTriggerUnit()
                    set this.target = GetSpellTargetUnit()
                    set this.temp = 1
                    set this.damage = 75*GetUnitAbilityLevel(this.caster, 'A000')
                    set this.steps = 3
                    set dindex = dindex + 1
                    set data[dindex] = this
                    if dindex == 0 then
                        call TimerStart(period, FPS, true, function thistype.periodic)
                    endif          
                endif
                return false
            endmethod
           
            //This method is called at the begginning of the map.
            //The keyword onInit does it.
            static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.cond))
                //I personally prefer to instanciate static members there just a point of view
                //You can do it in their declaration too it is the same.
                set dindex = -1
                set period = CreateTimer()
                set t = null
            endmethod
        endstruct
    endscope




    Conclusion
    Here you made a MUI spell in vJASS.
    Feel free to add more configurable things like this after the globals to make this more user-friendly :
    Code (vJASS):
    private constant function Damage takes integer level returns real
        return 75.*level
    endfunction


    Thanks for reading and feel free to tell me what do you think of it !


    Malhorne :)
     
    Last edited: Mar 8, 2017
  2. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    ~Reserved

    EDIT : This was Christmas gift (only if it is good otherwise it is Christmas random thingy !)
     
  3. Geshishouhu

    Geshishouhu

    Joined:
    Oct 11, 2012
    Messages:
    709
    Resources:
    1
    Spells:
    1
    Resources:
    1
    This is a nice, easy-understandable and very useful tutorial. It is especially helpful for newbies like me. :)
    Thanks, Malhorne.
    +Rep if I can.
     
  4. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Nice tutorial overall. The content is fine. However, I think overall it would be a bit better to do a periodic spell as an example, such as a DoT spell, just because you don't normally associate a looping timer with a one-shot sort of spell. The spell in your example deals damage after "1 second", which would probably make more sense with a TimerUtils approach (just wait 1 second and then deal damage vs. 32 loops of 0.03125 seconds before doing damage).

    If you change it to a DoT (damage over time) type of spell (or anything like that), it'll be a perfect example of using dynamic indexing + a timer.

    Otherwise, it is pretty well done. nj. I might need to fix some grammar though, are you okay with that?
     
  5. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Oh yes go on to fix my grammar I'm french xD !
    I might to a second approach with DoT spells but I'd like to keep this as first approach :)

    Anywya thanks for the reply !
     
  6. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    Yes, it is not too efficient as is, but I prefer having one timer personally

    Imagine you have 150 instances running, you would have to have 150 timers
     
  7. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Yes it is better to use one timer normally if you have large amount of instances it will be better.
    Thanks for the reply edo
     
  8. GhostWolf

    GhostWolf

    Joined:
    Jul 29, 2007
    Messages:
    4,952
    Resources:
    2
    Tools:
    1
    Tutorials:
    1
    Resources:
    2
    Or just use locals and a sleep, and you end up with about 10 lines of code, because not everything has to be overcomplicated.
    So yes, this example isn't the best.
     
  9. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    if you want to be half precise about timing you have to use timer
     
  10. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Okay I'll change the example to a three times periodic damage.
     
  11. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Okay updated with a spell that deal damage thrice in a row !
     
  12. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Not bad, still it lacks read-ability (not the code, but the whole tutorial).
    While the content is good, I wasn't really caught by the presentation.
    For instance one of the best tuts we have is the dynamic indexing from Purge,
    it is short, well highlighted and easy to understand.

    What we do need are very user(beginner)-friendly tutorials for correct vJass usage. People must get into the subject matter as fast as possible. The key is to let them know that what they do in GUI is as easy in vJass.

    If I come up with a proper layout or constructive ideas I'll let you know.

    - For now don't use the whole page from left to right for one sentence.
    - Make the content short, but distinct -->
    It will deal 5 damage per second over 3 seconds.
    I see this quite often. People say something is awesome or bad, but don't eplain why.
    Something like 0.03125 is handy, because it is 1/32 of a second, speaking about such don't be over-correct (if this word even exists ^^).

    Finally:
    Code (vJASS):

    /*
    *   I like these comments more ....
    *   because with spacing you can seperate
    *   your code into blocks
    *
    *   Use spacing so eyes and head don't
    *   have to move too much.
    */

    if this != 0 then
        /*
        *   Check whatsoever
        */

        if IsThisAwesome(evidence) then
           
        endif
        /*
        *   Here we do ...
        */

    endif


    Good luck with this. :thumbs_up:
     
  13. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Thanks for your review.
    I posted only this tut among the three I did because it is the only one which is at least somewhat read-able ^^'
    I've a huge JASS tutorial but it is not that good to read (even if people manage to learn JASS through it xD) ....
    I'm pretty bad to make something read-able because I don't see what it needs to be so :/
    So thanks for pointing some idea !

    EDIT : Corrected some mistakes and changed some things you pointed.
     
  14. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    I like it. I agree with BPower's statements about compaction, etc., but it seems that you have improved it for the most part. There may be a few grammar/spelling things to fix here and there, but it is still perfectly readable, so it is approved. :)
     
  15. Jet Kong

    Jet Kong

    Joined:
    Aug 12, 2012
    Messages:
    7
    Resources:
    0
    Resources:
    0
    Hello, you're tutorial is very short but it is something, I really want to learn vJass. I did configure something in your spell code ( ehm or trigger i guess. ) I just want something to get clarify

    Code (vJASS):
    scope MySuperCoolSpell
        globals
            private constant real FPS = 0.0312500
        endglobals
       
        private struct tempDat
            unit caster
            unit target
            real x
            real y
            real temp
            real damage
           
            integer duration
            //Static members work like globals :)
            static integer dindex
            static timer period
            static thistype array data
           
            method destroy takes nothing returns nothing
                if dindex == -1 then
                    call PauseTimer(period)
                endif
                call this.deallocate()
            endmethod
           
            static method periodic takes nothing returns nothing
                local integer i = 0
                local tempDat this
                loop
                    exitwhen i>dindex
                    set this = data[i]
                    set this.temp = this.temp-FPS
                    set this.x = GetWidgetX(this.caster)
                    set this.y = GetWidgetY(this.caster)
                    call SetUnitPosition(this.target, this.x, this.y)
                    if this.temp <= 0 then
                        call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC,null)
                        call AddSpecialEffectTarget( "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl", this.caster, "head" )
                        call DestroyEffect( GetLastCreatedEffectBJ() )
                        set this.duration = this.duration - 1
                        if this.duration == 0 then
                            call PauseUnit ( this.target, false )
                            call ShowUnitShow ( this.target )
                            call SetUnitPathing ( this.target, true )
                            set this.caster = null
                            set this.target = null
                            set data[i] = data[dindex]
                            set i = i - 1
                            set dindex = dindex - 1
                            call this.destroy()
                        else
                            set this.temp = 1
                        endif
                    endif
                    set i = i + 1
                endloop
            endmethod
           
            static method cond takes nothing returns boolean
                //Thistype is replaced by the name of the struct :)
                local thistype this
                if GetSpellAbilityId() == 'AHtb' then
                    set this = thistype.allocate() //Exactly the same as create
                    set this.caster = GetTriggerUnit()
                    set this.target = GetSpellTargetUnit()
                    call PauseUnit ( this.target, true )
                    call ShowUnitHide ( this.target )
                    call SetUnitPathing ( this.target, false )
                    set this.temp = 1
                    set this.damage = 15*GetUnitAbilityLevel(this.caster, 'AHtb')
                    set this.duration = 3
                    set dindex = dindex + 1
                    set data[dindex] = this
                    if dindex == 0 then
                        call TimerStart(period, FPS, true, function thistype.periodic)
                    endif            
                endif
                return false
            endmethod
           
            //This method is called at the begginning of the map.
            //The keyword onInit does it.
            static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.cond))
                set dindex = -1
                set period = CreateTimer()
                set t = null
            endmethod
        endstruct
    endscope


    PLEASE DO CHECK IF I DID SOMETHING WRONG.

    and 1 more question, if i'm gonna make another spell which part of the code should or needed to be changed, i mean in gui if use the variable caster in the next spell you need to use caster2 something like that ... I hope i'm getting somewhere with my question :grin:
     
  16. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    ShowUnitHide(this.target) -> ShowUnit(this.target, false)


    Code (vJASS):
    call AddSpecialEffectTarget( "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl", this.caster, "head" )
                        call DestroyEffect( GetLastCreatedEffectBJ() )

    ->
    call DestroyEffect(AddSpecialEffectTarget( "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl", this.caster, "head" ))


    call PauseUnit ( this.target, true )

    PauseUnit is a bad function to use -> I would rather use the Stun System or use a custom stun.

    set this.x = GetWidgetX(this.caster)

    Better use GetUnitX/Y they are faster.

    call SetUnitPosition(this.target, this.x, this.y)

    Use SetUnitX/Y it is faster.
    Then don't forget to use BoundSentinel by Vexorian.

    Code (vJASS):
    set this.x = GetWidgetX(this.caster)
                    set this.y = GetWidgetY(this.caster)
                    call SetUnitPosition(this.target, this.x, this.y)

    This piece of code does pretty nothing since you're moving the unit to its position.

    call ShowUnitShow ( this.target )

    ->
    call ShowUnit(this.target, true)


    Then in your code you never deal the damage.
     
  17. Jet Kong

    Jet Kong

    Joined:
    Aug 12, 2012
    Messages:
    7
    Resources:
    0
    Resources:
    0
    I don't get this one. The spell was based on devour so I tried to hide the unit, it still get the damage.

    Thanks. I think I will get use to this.

    +rep
     
  18. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    When I was saying it does nothing it was on the SetUnitPosition ^^
     
  19. Malhorne

    Malhorne

    Joined:
    Sep 14, 2012
    Messages:
    2,327
    Resources:
    6
    Spells:
    4
    Tutorials:
    1
    JASS:
    1
    Resources:
    6
    Fixed readibility.
     
  20. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    This tutorial teaches bad OOP styles.

    The cleaning of leaks should always be done in the onDestroy method, not manually from within the calling method (in your case, "periodic").


    This is so that when destroying the struct from the outside, your spell will not crash or leave any uncleaned leaks.

    .destroy() should always be coded in a way to correctly terminate the struct and all of it's members in a single call, no matter what the execution state of the spell is. It should immediately stop and destroy all timers and free all handle variables.
    If the spell creates a dummy unit or special effect, it should also remove the dummy unit and effect instead of just nulling the variables.
    This not only avoids potential bugs, but it also allows for extra functionality, like having spells that immediately terminate all ongoing spell effects.

    Also, replacing the static method create by something that takes the initial struct members as parameters is a good practice to reduce the number of lines required for each struct allocation.


    Also, for indexing spells (to be honest, I always prefer having an individual timer per spell instance, as it gets rid of all the indexing overhead and shortens the code), I recommend having a base indexed spell struct and extend your spells upon it.
    This avoids repetition of code and allows easier access from the outside.

    Basicly:

    struct MySpell extends Spell


    This way, each new index-based spell reuses the base structure from Spell and only adds custom callbacks on top of it, which eliminates repeating the index structure again and again.


    If you want to get extra fancy, you can add another layer of abstraction to it, with each layer adding new members to your base struct:

    struct Flamethrower extends ChanneledDot extends Spell



    PS: Reminds me ... someone really has to write styleguides for vJass.
     
    Last edited: Jul 9, 2015