Thread: Dark Lightning
View Single Post
Old 01-25-2009, 01:27 PM   #1
retired coder | real ilfe
Flame_Phoenix's Avatar
Join Date: Mar 2007
Posts: 2,208

Submissions (10)

Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)

Send a message via MSN to Flame_Phoenix
Default Dark Lightning

Ok guys, this is another one of my spell for the spell Olympics. Although I am totally sure I can't win against Anitarf nor Griffen (my freaking Nemesis) I still think this is a spell people should see and I believe some of you may even like it.
The code is well commented, and I feel a system can be explored from the code. Unlike many other spells, I prepared this code for the user, this means the user is not limited only to the SETUP part, I also want the user to enter the code's core so he can also learn from my code and mainly make even better lightning spells. This is mainly why my code has many methods and why it is so divided, so people can actually use an easy and efficient "Divide and Conquer" strategy. This makes sections of the code easier to change.
I would really like to see this spell approved, it is my first working lightning spell, and it passed nearly through hell to make it work for Olympics.
I just feel bad I actually delivered a not perfect version of this spell... The Math formula is not what I exactly pretended, but it was what I could do with the time I had.
Please enjoy and be nice on comments, I put a lot of effort into making this spell for you, the user.

This time I feel forced to tell explicitly the people who helped me making this spell. Note that without them this spell would have not been possible to make. Thx to Anitarf and Pyrogasm, both of them wasted many hours seeing my code, I can never thank them enough for what they did. I would also like to thank Daelin for his outdated, but useful math formulas and to Deaod as well.

A JESP spell that allows the user to create lightning spells with any model he desires. In this sample, the caster sends a dark projectile which will damage enemy units and heal the caster by the 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 or a hero.

Click image for larger version

Name:	DarkLightning2.3.jpg
Views:	671
Size:	248.6 KB
ID:	39976

- Jass NewGen Pack (uses vJASS)
- TimerUtils

Hidden information:

Versions 1.0, 1.2, 1.3, 1.4:
- This versions all had different approaches to try solving the same problem "How to make a lightning spell ?" but they all proved either to be inefficient or to fail
- Yes, I actually remade from zero the spell 3 times (without counting with version 1.0)

Version 1.5:
- First release to the spell Olympics
- Math formula improved
- Removed a useless timer
- Cleaned and commented the code

Version 1.6:
- The Math formula for damage reduction was still, wrong, so now it is corrected and now most things work as they should

Version 1.7:
- Code optimizations
- Fixed a glitch with the dummy unit, now it actually dies

Version 1.8:
- Made the function Targets easier to use
- Now the code has its own algorithm and re uses groups
- Added new function SetProjectile
- Many other minor code fixes and updates were done
- Added Anitarf to the credits

Version 1.9
- Changed the model of the missile to make it look better and now it works with flying units by changing its flying height properly
- Now the missile always faces the direction of the victim
- Improved the map and the terrain
- Changed the algorithm for picking units, now he picks close units to the target
- Now the effects appear on the units
- Now I also preload the units missile and the dummy

Version 2.0
- Now the abilities for the dummy unit are also preloaded
- Replaced SetUnitPosition with SetUnitX and SetUnitY, now the spell is faster!

Version 2.1
- Replaced some variables in order to make the preloading of the dummy unit faster.
- Cleaned the code and deleted old comments and code fragments as well as eliminated the "data = this" laziness

Version 2.2
- Now the code really works well with 1 single timer. Thx Pyrogasm and Anitarf!
- This spell would have not been possible without the people on the credits, Anitarf, Daelin, Deaod and Pyrogasm, thx to you all!

Version 2.3:
- Fixed a leak in method NextTarget
- Moved the timer code to the struct
- Transformed some of the SETUP functions into constants
- Fixed a spelling mistake , replaced TIMER_CICLE, by TIMER_CYCLE
- Added JESP document

Version 2.31:
- Fixed the tooltips of the spell

Version 2.32:
- Updated for patch 1.24

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 
//- 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.32
scope DarkLightning initializer Init
//=============================SETUP START===================================
        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
    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)   
    private constant function Damage takes integer level returns real
    //Damage each Target will take
        return 100. * level
    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
    private constant function Reduction takes integer level returns real
    //Damage reduction per Target
        return 0.15 + (level * 0)
    private constant function TargetsNumber takes integer level returns integer
    //The number of targets
        return 4 + (level * 1)
    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)
//=============================SETUP END=====================================

        private group g
        private boolexpr b
        private unit tmpCaster = null
        private timer t
        private integer instancesCount
    private function AceptedTargets takes nothing returns boolean
        return Targets(tmpCaster, GetFilterUnit())
    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()
            return data
        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)
        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)
                set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level))
            //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.)
            set dum = null
        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)
                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
            set .vic = ret
            set ret = null
        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()
                    set .done = true
                set .done = true
        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()
        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)
        private SpellData array datas
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
            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
                    call ReleaseTimer(t)
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
    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)
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit())
            call datas[instancesCount].SetProjectile()
            set instancesCount = instancesCount + 1

        return false
    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))
Attached Files
File Type: w3x Dark Lightning 2.32.w3x (39.5 KB, 116 views)
Check out my tutorials at:
1-Creating a Hero Tavern
2-Complete Icon Tutorial - ALL about Icons
3-Making a spell in vJass - Practice Session 1
Check out all my current spells at here
Finally, check my project:
Castle vs Castle Flame Edition
Flame_Phoenix is offline   Reply With Quote
Sponsored Links - Login to hide this ad!