HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Dark Lightning - Code Critic

01-20-2009, 08:47 AM#1
Flame_Phoenix
Hi guys, since Dusk had the unpleasant decision of moving my code here for no good reason at all, and since that thread is huge and contains a lot of crap, I decided to remake the thread, this time for code criticism. I would like to hear the opinion from the user point of view of the code, since I made the code thinking on the user. If you have ideas to upgrade, please make a comment.
Please note that this should already be approved (having in mind what Moyack said), but I am just asking another opinion.

Collapse JASS:
//===========================================================================
//A JESP spell that allows the user to create lightning spells with any model
//he desires. In this sample, the caster sends a purple projectile which will 
//damage enemy units and heal the caster by an amount of damage they received.
//If an enemy unit dies due this ability, it will return as an Undead to aid 
//the caster, unless it is a summon, a hero or a flying unit.
//
//Requires TimerUtils
//
//@author Flame_Phoenix 
//
//@credits
//- Deaod, for all hi help in the code, with math formulas and advices
//- Anitarf, for math formulas and advices for efficiency
//- Pyrogasm, for giving me the algorithm for making the spell with 1 timer only
//- Daelin, for the math formulas on his outdated spells 
//- Vexorian, for the idea of preloading units and abilities and for TimerUtils
//
//@version 2.3
//===========================================================================
scope DarkLightning initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
   globals
        private constant integer AID = 'A000'   //rw of teh ability
        private constant real SPEED = 700.  //speed of the missile
        private constant integer MISSILE_ID = 'h000'    //rw of the missile
        private constant integer DUM_ID = 'h001'    //rw of the dummy unit
        private constant integer DUM_AB = 'A001'    //Ability of the dumy unit (animated dead)
        private constant string DUM_ORDER = "animatedead"   //string order of the dummy unit
        private constant real TIMER_CICLE = 0.03    //cicles of the timer
        private constant string DRAIN_EFFECT = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
        private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl"
        private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC  //the attack type of the spell
        private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL  //the damage type of the spell
    endglobals
    
    private function Range takes integer level returns real
    //If there is more than one Target, a next target will be picked in a 500
    //AOE from the first
        return 500. + (level * 0)   
    endfunction
    
    private function Damage takes integer level returns real
    //Damage each Target will take
        return 100. * level
    endfunction
    
    private function Heal takes integer level, real damage returns real
    //the heal the caster will get when damaging enemies
    // in this case in level 1 caster gains 33% of damage done, in level
    //2 he gains 66% of damage done and in level 3 he gains 99% of the 
    //damage deal to the target
        return level * 0.33 * damage
    endfunction
    
    private function Reduction takes integer level returns real
    //Damage reduction per Target
        return 0.15 + (level * 0)
    endfunction
    
    private function TargetsNumber takes integer level returns integer
    //The number of targets
        return 4 + (level * 1)
    endfunction
    
    private function Targets takes unit caster, unit target returns boolean
    //"caster" is the caster of the spell, and "target" is the target being evauated
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(target) > 0.405)
    endfunction
    
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================

    globals
        private group g
        private boolexpr b
        private unit tmpCaster = null
        private timer t
        private integer instancesCount
    endglobals
    
//===========================================================================
    private function AceptedTargets takes nothing returns boolean
        return Targets(tmpCaster, GetFilterUnit())
    endfunction
//=========================================================================== 
    private struct SpellData 
        //static SpellData array datas    //an array which will contain the instances
    
        unit caster     //our caster !
        unit vic    //the current victim
        integer level   //the level of the ability
        group picked   //saves all targeted units so far, so they don't get picked again
        unit missile    //the missile
        real wait   //tells us how much time we must wait
        integer targNum   //Current number of the target
        real lastDamage //this tells us the last amount of damage a unit received. NOTE: this is NOT the damage a unit is taking.
        boolean done
        
        static method create takes unit caster, unit vic returns SpellData
            local SpellData data = SpellData.allocate()
            
            //setting variables
            set data.caster = caster
            set data.vic = vic
            set data.level = GetUnitAbilityLevel(caster, AID)
            set data.missile = CreateUnit(GetOwningPlayer(caster),  MISSILE_ID, GetUnitX(caster), GetUnitY(caster), 0)  
            set data.wait = 0.
            set data.targNum = 0
            set data.done = false
            
            //we recycle the group 
            if data.picked == null then
                set data.picked = CreateGroup()
            endif
            
            return data
        endmethod
        
        method SetProjectile takes nothing returns nothing
            local real a = GetUnitX(.missile) - GetUnitX(.vic)
            local real b = GetUnitY(.missile) - GetUnitY(.vic)
            local real d = SquareRoot(a*a + b*b) //the distance between "a" and "b"
            
            set .wait = d / SPEED
            
            //we adapt the fly height of the missile to the height of the target!
            call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.vic), (GetUnitFlyHeight(.missile) - GetUnitFlyHeight(.vic)) / .wait)
        endmethod
        
        method TargetEffect takes nothing returns nothing 
            local unit dum
        
            //the hp the enemies will lose and that the caster will win
            if (.targNum == 0) then
                set .lastDamage = Damage(.level)
            else
                set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level))
            endif
            
            //here we damage the bad guy ! Die you bastard !!!!
            //we also created the effects for both targets and caster
            call UnitDamageTarget(.caster, .vic, .lastDamage, true, false, A_TYPE, D_TYPE, null)
            call DestroyEffect(AddSpecialEffectTarget(DRAIN_EFFECT, .vic, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT, .caster, "origin"))
            
            //Heal the caster
            call SetWidgetLife(.caster, GetWidgetLife(.caster) + Heal(.level, .lastDamage))
            
            if (GetWidgetLife(.vic) < 0.405)  and (IsUnitType(.vic, UNIT_TYPE_HERO) == false) and (IsUnitType(.vic, UNIT_TYPE_SUMMONED) == false) and (IsUnitType(.vic, UNIT_TYPE_FLYING) == false) then
                set dum = CreateUnit(GetOwningPlayer(.caster), DUM_ID, GetUnitX(.vic), GetUnitY(.vic), 0)
                call UnitAddAbility(dum, DUM_AB)
                call SetUnitAbilityLevel(dum, DUM_AB, .level)
                call IssueImmediateOrder(dum, DUM_ORDER)
                call UnitApplyTimedLife(dum, 'BTLF', 1.)
            endif
            
            set dum = null
        endmethod
        
        method NextTarget takes nothing returns nothing
            local unit f = null
            local unit ret = null
            
            //the position of the current target
            local real cX = GetUnitX(.vic)
            local real cY = GetUnitY(.vic)
            
            //the position of our new target
            local real nX
            local real nY
            
            //by saving the minimal distance, this will help us choose
            //the closest new target to our current target
            //note we start it with the biggest value possible, so
            //we can find the minimum after
            //Also note that I am calculating minDist^2 so I can avoid
            //the use of a squareroot which will save speed. Know that if
            //minDist <, >, <=, >= d then minDis^2 <, >, <=, >= d^2 and vice-versa
            local real minDist = Range(.level)*Range(.level)
            
            local real d //this will be a temporary variable for the distances we will calculate
            
            //here we pick all units near the last target
            set tmpCaster = .caster
            call GroupEnumUnitsInRange(g, GetUnitX(.vic), GetUnitY(.vic), Range(.level), b)
            
            loop
                set f = FirstOfGroup(g)
                exitwhen(f == null)
                call GroupRemoveUnit(g, f)
                if (IsUnitInGroup(f, .picked) == false) then
                    set nX = GetUnitX(f)
                    set nY = GetUnitY(f)
                    
                    //now we calculate the distance between f and our current target
                    //note that: d^2 = (x2-x1)^2 + (y2-y1)^2
                    //I avoid using a square to make this faster
                    set d = (nX - cX)*(nX - cX) + (nY - cY)*(nY - cY)
                    
                    //if this new distance is the smallest, then we update our target
                    if (d < minDist) then
                        set minDist = d
                        set ret = f
                    endif
                endif
            endloop
            
            set .vic = ret
            set ret = null
            
        endmethod
        
        method ChainEffect takes nothing returns nothing
            //we add the victim to the victims groups, so we won't pick it twice
            call GroupAddUnit(.picked, .vic)
            
            //here we call the function responsable for the bad things we do to the bad guys xD
            call .TargetEffect()
            
            //now we increase the counter to know how many units we hit           
            set .targNum = .targNum + 1

            //if the number of our current target is lower than the maximum amount
            //of targets we can hit, we continue, else we end everything
            if (.targNum < TargetsNumber(.level)) then 
                
                //pick new target !
                call .NextTarget()
                
                //if the new unit is not null, we repeat this step, else we end
                if (.vic != null) then
                   call .SetProjectile()
                else
                    set .done = true
                endif
            else
                set .done = true
            endif
        endmethod
        
        method MoveMissile takes nothing returns nothing
            local real x1 = GetUnitX(.missile)
            local real x2 = GetUnitX(.vic)
            local real y1 = GetUnitY(.missile)
            local real y2 = GetUnitY(.vic)
            
            local real dx = TIMER_CICLE * (x2 - x1) / .wait  
            local real dy = TIMER_CICLE * (y2 - y1) / .wait
         
            call SetUnitX(.missile, x1 + dx)
            call SetUnitY(.missile, y1 + dy)
            
           //here we set the facing of the missile
            call SetUnitFacing(.missile, bj_RADTODEG * Atan2(y2 - y1, x2 - x1))
            
            set .wait = .wait - TIMER_CICLE
            
            //this is when the missile gets to the unit
            if .wait < TIMER_CICLE then
                
                call SetUnitX(.missile, x2)
                call SetUnitY(.missile, y2)
            
                //This runs the ChainEffect function again!
                call .ChainEffect()
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            //we clear the gorup so we can use it later
            call GroupClear(.picked)

            //we destroy the projectile
            call ShowUnit(.missile, false)
            call KillUnit(.missile)
        endmethod
    endstruct
//=========================================================================== 
    globals
        private SpellData array datas
    endglobals
//=========================================================================== 
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
        
        
        loop
            set currentInstance = datas[currentIndex]
            call currentInstance.MoveMissile() 
            
            //if our instance is done, we decrement the number of total instances
            //and then we check if there are any more instances being run
            if (currentInstance.done) then
                set instancesCount = instancesCount - 1
                
                //if there are, then we update our instance to the next of the array
                //and we correct the index
                if (instancesCount > 0) then
                    set datas[currentIndex] = datas[instancesCount]
                    set currentIndex = currentIndex - 1
                //else we just release the timer
                else
                    call ReleaseTimer(t)
                endif
                
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            
            endif
                
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
            
        endloop
    endfunction 
//===========================================================================    
    private function Conditions takes nothing returns boolean
        if GetSpellAbilityId() == AID then
            
            //if there are no instances of the spell then it means the timer does not
            //exist, so we create it and start it!
            if instancesCount == 0 then
                set t = NewTimer()
                call TimerStart(t, TIMER_CICLE, true, function Periodic)
            endif
            
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit())
            call datas[instancesCount].SetProjectile()
            set instancesCount = instancesCount + 1
        
        endif

        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger LightningTrg = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( LightningTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( LightningTrg, Condition( function Conditions ) )
                
        //setting globals
        set b = Condition(function AceptedTargets)
        set g = CreateGroup()
        set instancesCount = 0
        
        //Preload the effects
        call Preload(DRAIN_EFFECT)
        call Preload(BLOOD_EFFECT)
        
        //preloading units and spells
        set bj_lastCreatedUnit = CreateUnit(Player(0), DUM_ID, 0, 0, 0)
        call UnitAddAbility(bj_lastCreatedUnit, DUM_AB)
        call KillUnit(bj_lastCreatedUnit)
        
        call KillUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), MISSILE_ID, 0, 0, 0))
    endfunction
endscope

This is a lightning spell that uses only 1 timer for all instances. I find the code quite easy to understand, although I admit that the movement of the projectile could be better. Hope you all like it.
01-20-2009, 10:21 AM#2
Archmage Owenalacaster
I think the functions in the setup section should be constant.

EDIT: Except Targets, which makes non-constant function calls.
01-20-2009, 10:54 AM#3
chobibo
Quote:
Originally Posted by Archmage Owenalacaster
I think the functions in the setup section should be constant.

EDIT: Except Targets, which makes non-constant function calls.

jasshelper will inline them so it's okay to keep it that way.
01-20-2009, 10:54 AM#4
Flame_Phoenix
Quote:
I think the functions in the setup section should be constant.
Well, I also used to ii that way, but Griffen and some other mods were always bugging my head saying that "there is no real difference between constant functions and the others" so I really don't know anymore ... yet I still believe constant function must be faster (because Daelin said so!) and I will change them to constant.

Thx for critic! +rep


Off-topic
PS: When will you download msn ? xD

EDIT
Sry, must spread repo =(

Quote:
jasshelper will inline them so it's okay to keep it that way.
Meh so, should I or should I not do it? There must be some sort of advantage I think ...
01-20-2009, 11:08 AM#5
chobibo
here's what happens:

Collapse your code:
function A takes integer lvl returns integer
    return 10*lvl
endfunction

function Sampler takes nothing returns nothing
    local integer damage=A(GetUnitLevel(GetTriggerUnit()))
endfunction

Collapse after inline:
function Sampler takes nothing returns nothing
    local integer damage=10*GetUnitLevel(GetTriggerUnit()) // INLINE!!
endfunction

It doesn't matter if it's a constant function or not, if both gets inlined then there's no advantage over the other since they do the same thing.
01-20-2009, 11:36 AM#6
Archmage Owenalacaster
Inlined or not, constant is a helpful convention for designating configurable functions which for all purposes ought to be constant.
01-20-2009, 11:45 AM#7
akolyt0r
Quote:
Originally Posted by Archmage Owenalacaster
Inlined or not, constant is a helpful convention for designating configurable functions which for all purposes ought to be constant.
if constant functions are not inlined and non-constant (oneline) functions are... that would decrease performance...
01-20-2009, 12:08 PM#8
Flame_Phoenix
Mmm, I think constant functions are inlined too, but I am really not sure...
Besides this fact, is there anything else to upgrade?
01-20-2009, 12:16 PM#9
Anitarf
I still say you should move the stack and timer manipulation from the Conditions and Periodic function to the create and onDestroy methods. All it should take would be to move the Periodic function and the datas global array above the SpellData struct and slap a private keyword SpellData above them; or make them a static method and a static array member of the struct, respectively, that way the periodic code would be above the create/onDestroy methods but below the MoveMissile method, making calls to it faster; they'd still be unnecessary function calls, though, easily avoided by inlining the MoveMissile code into the Periodic method/function.
01-20-2009, 12:17 PM#10
Archmage Owenalacaster
TIMER_CICLE is a typo, yes? I think you mean either "cycle" or "circle".
01-20-2009, 12:24 PM#11
Anitarf
No, he means "cicle". It's his style, man. :)
01-20-2009, 01:03 PM#12
chobibo
Then why use function at all, use global constants instead.
01-20-2009, 01:03 PM#13
Flame_Phoenix
Quote:
TIMER_CICLE is a typo, yes? I think you mean either "cycle" or "circle".
Well, somehow I always do that typo xD It kinda happens when you are not english. Anyway typo fixed thx!

Quote:
No, he means "cicle". It's his style, man. :)
I wonder why you make of my obviously superior english skills =P
PS: this is a joke

Ok, I edited some stuff:
1 - now SETUP functions are constant (assuming they are inlined, I decided to change them)
2 - Typo CICLE -> CYCLE is fixed

Now to the list of things I can't do, or wont do.

I will not change:
Quote:
unnecessary function calls, though, easily avoided by inlining the MoveMissile code into the Periodic method/function.
Arrghh no. I prefer to waste a few nanoseconds of CPU power and to have my coder easier to read and modify, than to inline everything and have a huge gigantic, not divided, nightmare. Problems are easier if we divide them, this is why MoveMissile is divided. If tomorrow I find out that moving my missile in a linear form is too stupid, I can always find the corresponding method and change it easily.

Now about the other stuff you suggest Anitarf, after re-reading your post again (and again [and again]) i finally understand your point, but I still have a problem with parameters (which I will never be able to pass to the onDestroy method). Yet, even If I do succeed in making the modification, my entire code will be inside a single method, which will define nothing ...

I really prefer to have things divided, this way my structure defines an instance of a spell, and the functions that loop through all instances are outside the structure, because they are something different, they are not instances of the spell, they loop through instances of the spell.
I am really sorry Anitarf, but now that my mind is clear, I am afraid I will have to say no once more.
Well, don't be mad at me, see the bright side, I did everything else you requested (except xe, that doesn't count). So you gave like 999 tips and I just refused 2 of them. That is a good statistic =D


Quote:
Then why use function at all, use global constants instead.
Hell no, that is a common mistake. You see chobibo, if I use globals instead of functions my spell with not compile with JESP as good as with functions, because while functions can take parameter, such as level or target (see my SETUP functions, all take arguments) a global can't. That is why globals in SETUP section should be in most cases constant, because we don't want (nor expect them) to be changed while in game, when functions can change and are far more versatile. There are obviously some exceptions ( I am sure Anitarf or some one else is already preparing a post to shoot me) and I am sure some people will sooner or later tell them.

Anyway, since I see some people are interested, I will let this code here a few more days, and then I will move it back to submissions. Than help me God, if they don't approve this I will kill them all xD because according to Moyack this should already be approved.

Anyway, thx all for the time!
01-20-2009, 01:03 PM#14
Rising_Dusk
Quote:
Originally Posted by Flame_Phoenix
Hi guys, since Dusk had the unpleasant decision of moving my code here for no good reason at all
I actually had a very substantial reason. Please stop being spiteful over something so trivial.
01-20-2009, 01:08 PM#15
Flame_Phoenix
Quote:
I actually had a very substantial reason. Please stop being spiteful over something so trivial.
I can't believe you lost two minutes to make this post. Anyway, you know I felt offended about that... I will stop being spiteful (I agree this is going too far for to long), but God help you if you do that again for no reason xD

Anyway, for those you want to see the new version of the code:

Collapse JASS:
//===========================================================================
//A JESP spell that allows the user to create lightning spells with any model
//he desires. In this sample, the caster sends a purple projectile which will 
//damage enemy units and heal the caster by an amount of damage they received.
//If an enemy unit dies due this ability, it will return as an Undead to aid 
//the caster, unless it is a summon, a hero or a flying unit.
//
//Requires TimerUtils
//
//@author Flame_Phoenix 
//
//@credits
//- Deaod, for all hi help in the code, with math formulas and advices
//- Anitarf, for math formulas and advices for efficiency
//- Pyrogasm, for giving me the algorithm for making the spell with 1 timer only
//- Daelin, for the math formulas on his outdated spells 
//- Vexorian, for the idea of preloading units and abilities and for TimerUtils
//
//@version 2.3
//===========================================================================
scope DarkLightning initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
   globals
        private constant integer AID = 'A000'   //rw of teh ability
        private constant real SPEED = 700.  //speed of the missile
        private constant integer MISSILE_ID = 'h000'    //rw of the missile
        private constant integer DUM_ID = 'h001'    //rw of the dummy unit
        private constant integer DUM_AB = 'A001'    //Ability of the dumy unit (animated dead)
        private constant string DUM_ORDER = "animatedead"   //string order of the dummy unit
        private constant real TIMER_CYCLE = 0.03    //cicles of the timer
        private constant string DRAIN_EFFECT = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
        private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl"
        private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC  //the attack type of the spell
        private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL  //the damage type of the spell
    endglobals
    
    private constant function Range takes integer level returns real
    //If there is more than one Target, a next target will be picked in a 500
    //AOE from the first
        return 500. + (level * 0)   
    endfunction
    
    private constant function Damage takes integer level returns real
    //Damage each Target will take
        return 100. * level
    endfunction
    
    private constant function Heal takes integer level, real damage returns real
    //the heal the caster will get when damaging enemies
    // in this case in level 1 caster gains 33% of damage done, in level
    //2 he gains 66% of damage done and in level 3 he gains 99% of the 
    //damage deal to the target
        return level * 0.33 * damage
    endfunction
    
    private constant function Reduction takes integer level returns real
    //Damage reduction per Target
        return 0.15 + (level * 0)
    endfunction
    
    private constant function TargetsNumber takes integer level returns integer
    //The number of targets
        return 4 + (level * 1)
    endfunction
    
    private function Targets takes unit caster, unit target returns boolean
    //"caster" is the caster of the spell, and "target" is the target being evauated
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(target) > 0.405)
    endfunction
    
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================

    globals
        private group g
        private boolexpr b
        private unit tmpCaster = null
        private timer t
        private integer instancesCount
    endglobals
    
//===========================================================================
    private function AceptedTargets takes nothing returns boolean
        return Targets(tmpCaster, GetFilterUnit())
    endfunction
//=========================================================================== 
    private struct SpellData 
        //static SpellData array datas    //an array which will contain the instances
    
        unit caster     //our caster !
        unit vic    //the current victim
        integer level   //the level of the ability
        group picked   //saves all targeted units so far, so they don't get picked again
        unit missile    //the missile
        real wait   //tells us how much time we must wait
        integer targNum   //Current number of the target
        real lastDamage //this tells us the last amount of damage a unit received. NOTE: this is NOT the damage a unit is taking.
        boolean done
        
        static method create takes unit caster, unit vic returns SpellData
            local SpellData data = SpellData.allocate()
            
            //setting variables
            set data.caster = caster
            set data.vic = vic
            set data.level = GetUnitAbilityLevel(caster, AID)
            set data.missile = CreateUnit(GetOwningPlayer(caster),  MISSILE_ID, GetUnitX(caster), GetUnitY(caster), 0)  
            set data.wait = 0.
            set data.targNum = 0
            set data.done = false
            
            //we recycle the group 
            if data.picked == null then
                set data.picked = CreateGroup()
            endif
            
            return data
        endmethod
        
        method SetProjectile takes nothing returns nothing
            local real a = GetUnitX(.missile) - GetUnitX(.vic)
            local real b = GetUnitY(.missile) - GetUnitY(.vic)
            local real d = SquareRoot(a*a + b*b) //the distance between "a" and "b"
            
            set .wait = d / SPEED
            
            //we adapt the fly height of the missile to the height of the target!
            call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.vic), (GetUnitFlyHeight(.missile) - GetUnitFlyHeight(.vic)) / .wait)
        endmethod
        
        method TargetEffect takes nothing returns nothing 
            local unit dum
        
            //the hp the enemies will lose and that the caster will win
            if (.targNum == 0) then
                set .lastDamage = Damage(.level)
            else
                set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level))
            endif
            
            //here we damage the bad guy ! Die you bastard !!!!
            //we also created the effects for both targets and caster
            call UnitDamageTarget(.caster, .vic, .lastDamage, true, false, A_TYPE, D_TYPE, null)
            call DestroyEffect(AddSpecialEffectTarget(DRAIN_EFFECT, .vic, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT, .caster, "origin"))
            
            //Heal the caster
            call SetWidgetLife(.caster, GetWidgetLife(.caster) + Heal(.level, .lastDamage))
            
            if (GetWidgetLife(.vic) < 0.405)  and (IsUnitType(.vic, UNIT_TYPE_HERO) == false) and (IsUnitType(.vic, UNIT_TYPE_SUMMONED) == false) and (IsUnitType(.vic, UNIT_TYPE_FLYING) == false) then
                set dum = CreateUnit(GetOwningPlayer(.caster), DUM_ID, GetUnitX(.vic), GetUnitY(.vic), 0)
                call UnitAddAbility(dum, DUM_AB)
                call SetUnitAbilityLevel(dum, DUM_AB, .level)
                call IssueImmediateOrder(dum, DUM_ORDER)
                call UnitApplyTimedLife(dum, 'BTLF', 1.)
            endif
            
            set dum = null
        endmethod
        
        method NextTarget takes nothing returns nothing
            local unit f = null
            local unit ret = null
            
            //the position of the current target
            local real cX = GetUnitX(.vic)
            local real cY = GetUnitY(.vic)
            
            //the position of our new target
            local real nX
            local real nY
            
            //by saving the minimal distance, this will help us choose
            //the closest new target to our current target
            //note we start it with the biggest value possible, so
            //we can find the minimum after
            //Also note that I am calculating minDist^2 so I can avoid
            //the use of a squareroot which will save speed. Know that if
            //minDist <, >, <=, >= d then minDis^2 <, >, <=, >= d^2 and vice-versa
            local real minDist = Range(.level)*Range(.level)
            
            local real d //this will be a temporary variable for the distances we will calculate
            
            //here we pick all units near the last target
            set tmpCaster = .caster
            call GroupEnumUnitsInRange(g, GetUnitX(.vic), GetUnitY(.vic), Range(.level), b)
            
            loop
                set f = FirstOfGroup(g)
                exitwhen(f == null)
                call GroupRemoveUnit(g, f)
                if (IsUnitInGroup(f, .picked) == false) then
                    set nX = GetUnitX(f)
                    set nY = GetUnitY(f)
                    
                    //now we calculate the distance between f and our current target
                    //note that: d^2 = (x2-x1)^2 + (y2-y1)^2
                    //I avoid using a square to make this faster
                    set d = (nX - cX)*(nX - cX) + (nY - cY)*(nY - cY)
                    
                    //if this new distance is the smallest, then we update our target
                    if (d < minDist) then
                        set minDist = d
                        set ret = f
                    endif
                endif
            endloop
            
            set .vic = ret
            set ret = null
            
        endmethod
        
        method ChainEffect takes nothing returns nothing
            //we add the victim to the victims groups, so we won't pick it twice
            call GroupAddUnit(.picked, .vic)
            
            //here we call the function responsable for the bad things we do to the bad guys xD
            call .TargetEffect()
            
            //now we increase the counter to know how many units we hit           
            set .targNum = .targNum + 1

            //if the number of our current target is lower than the maximum amount
            //of targets we can hit, we continue, else we end everything
            if (.targNum < TargetsNumber(.level)) then 
                
                //pick new target !
                call .NextTarget()
                
                //if the new unit is not null, we repeat this step, else we end
                if (.vic != null) then
                   call .SetProjectile()
                else
                    set .done = true
                endif
            else
                set .done = true
            endif
        endmethod
        
        method MoveMissile takes nothing returns nothing
            local real x1 = GetUnitX(.missile)
            local real x2 = GetUnitX(.vic)
            local real y1 = GetUnitY(.missile)
            local real y2 = GetUnitY(.vic)
            
            local real dx = TIMER_CYCLE * (x2 - x1) / .wait  
            local real dy = TIMER_CYCLE * (y2 - y1) / .wait
         
            call SetUnitX(.missile, x1 + dx)
            call SetUnitY(.missile, y1 + dy)
            
           //here we set the facing of the missile
            call SetUnitFacing(.missile, bj_RADTODEG * Atan2(y2 - y1, x2 - x1))
            
            set .wait = .wait - TIMER_CYCLE
            
            //this is when the missile gets to the unit
            if .wait < TIMER_CYCLE then
                
                call SetUnitX(.missile, x2)
                call SetUnitY(.missile, y2)
            
                //This runs the ChainEffect function again!
                call .ChainEffect()
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            //we clear the gorup so we can use it later
            call GroupClear(.picked)

            //we destroy the projectile
            call ShowUnit(.missile, false)
            call KillUnit(.missile)
        endmethod
    endstruct
//=========================================================================== 
    globals
        private SpellData array datas
    endglobals
//=========================================================================== 
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
        
        
        loop
            set currentInstance = datas[currentIndex]
            call currentInstance.MoveMissile() 
            
            //if our instance is done, we decrement the number of total instances
            //and then we check if there are any more instances being run
            if (currentInstance.done) then
                set instancesCount = instancesCount - 1
                
                //if there are, then we update our instance to the next of the array
                //and we correct the index
                if (instancesCount > 0) then
                    set datas[currentIndex] = datas[instancesCount]
                    set currentIndex = currentIndex - 1
                //else we just release the timer
                else
                    call ReleaseTimer(t)
                endif
                
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            
            endif
                
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
            
        endloop
    endfunction 
//===========================================================================    
    private function Conditions takes nothing returns boolean
        if GetSpellAbilityId() == AID then
            
            //if there are no instances of the spell then it means the timer does not
            //exist, so we create it and start it!
            if instancesCount == 0 then
                set t = NewTimer()
                call TimerStart(t, TIMER_CYCLE, true, function Periodic)
            endif
            
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit())
            call datas[instancesCount].SetProjectile()
            set instancesCount = instancesCount + 1
        
        endif

        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger LightningTrg = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( LightningTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( LightningTrg, Condition( function Conditions ) )
                
        //setting globals
        set b = Condition(function AceptedTargets)
        set g = CreateGroup()
        set instancesCount = 0
        
        //Preload the effects
        call Preload(DRAIN_EFFECT)
        call Preload(BLOOD_EFFECT)
        
        //preloading units and spells
        set bj_lastCreatedUnit = CreateUnit(Player(0), DUM_ID, 0, 0, 0)
        call UnitAddAbility(bj_lastCreatedUnit, DUM_AB)
        call KillUnit(bj_lastCreatedUnit)
        
        call KillUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), MISSILE_ID, 0, 0, 0))
    endfunction
endscope

I can update the first post though. I also have a test map I can post, if you people find necessary or if you people want to have fun testing an unbalanced spell xD