Divine Halo v2.1

Hey guys, this is one of my first JASS spells and i hope you guys will like it, comments and suggestions both positive and negative are welcome.

Divine Halo summons orbs that will begin to expand from the caster, stop at a distance and continue to rotate. After a short amount of the time the orbs will contract, damage all enemies that are vulnerable and heal all allies nearby.

This spell has many configurable values.

Create 1 timer array with size 1 called TimerUtils_timer
Create 1 integer called TimerUtils_int
Create 1 hashtable called TimerUtils_hash

Copy ALL of the following code into your maps header (the map icon above all triggers and folders)

NOTE: Do NOT give me credits for TimerUtils, i did not make it.
JASS:
//*************************************************
//*
//*   TimerUtils (Jass Version)
//*   v1.3.2.0
//*   By Magtheridon96
//*
//*   Original version by Vexorian.
//*   All the functions have O(1) complexity.
//*
//*   API:
//*       - function NewTimer takes nothing returns timer
//*           - Returns a new timer from the stack
//*       - function ReleaseTimer takes timer t returns nothing
//*           - Throws a timer back into the stack
//*       - function SetTimerData takes timer t, integer value returns nothing
//*           - Attaches a value to a timer
//*       - function GetTimerData takes timer t returns integer
//*           - Returns the attached value
//*
//*************************************************

    function SetTimerData takes timer t, integer value returns nothing
        call SaveInteger(udg_TimerUtils_hash,GetHandleId(t),0,value)
    endfunction

    function GetTimerData takes timer t returns integer
        return LoadInteger(udg_TimerUtils_hash,GetHandleId(t),0)
    endfunction

    function NewTimer takes nothing returns timer
        if 0==udg_TimerUtils_int then
            return CreateTimer()
        endif
        set udg_TimerUtils_int = udg_TimerUtils_int - 1
        return udg_TimerUtils_timer[udg_TimerUtils_int]
    endfunction

    function ReleaseTimer takes timer t returns nothing
        call PauseTimer(t)
        set udg_TimerUtils_timer[udg_TimerUtils_int] = t
        set udg_TimerUtils_int = udg_TimerUtils_int + 1
    endfunction

    function InitTrig_TimerUtils takes nothing returns nothing
        set udg_TimerUtils_hash = InitHashtable()
    endfunction
JASS:
//Divine Halo by Zaio
// ************************************* IMPORTING INSTRUCTIONS **********************************
// To import this spell, open up the test map and copy over the dummy, the halo ability, the holy bolt ability and buff
// Go to file then preferences and enable "automatically create unknown variables when pasting trigger data"
// Copy the code into your map
// Replace the ability and unit IDs below to your maps ability and unit ID
// Open up variables (Ctrl + B) and create 1 hashtable named DHash

function DHCDID takes nothing returns integer
    return 'h000' // Change the h000 to your dummy units ID (Hold ctrl and press D on the unit in object editor)
endfunction

function DHCAID takes nothing returns integer
    return 'A000' // Change the A000 to your spells ability ID (Hold ctrl and press D on the ability in object editor)
endfunction

// DONE!

// ****************************************** CONFIGURABLES **********************************************************

function DHDN takes nothing returns integer
    return 10 // This value is the amount of orbs in the halo.
endfunction

function DHFDUF takes nothing returns real
    return 100.00 // This value is the amount the spell will damage enemies for at the end of the spells duration.
endfunction

function DHFHUF takes nothing returns real
    return 100.00 // This value is the amount the spell will heal allies for at the end of the spells duration.
endfunction

function DHBE takes nothing returns string
    return "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" // This is the holy bolt effect path, change this to alter the effect when the spell ends.
endfunction

function DHOS takes nothing returns real
    return 6.00 // This is the speed at which the orbs spin, supports positive and negative numbers (recommended to have on a value between -9.00 to 9.00)
endfunction

function DHDD takes nothing returns real
    return 300.00 // This is the max distance the orbs will travel over 1 second
endfunction

function DHDDI takes nothing returns real
    return 50.00 // This is the increment value per level (Example: if set to 50.00 and DHDD is set to 300, level 1 will travel 350, level 2 will travel 400 and level 3 will travel 450)
endfunction

function DHFD takes nothing returns real
    return 500.00 // This is the range units will be healed or damaged in when the spell ends
endfunction

function DHFDI takes nothing returns real
    return 50.00 // This is the range increment per level for the filter (Example: if set to 50.00 and DHFD is set to 500.00, level 1 will heal/damage units in 550 range whereas level 2 will do for 600 range and level 3 will do for 650 range)
endfunction

function DHLO takes nothing returns boolean
    return true // Lightning effects on/off (true = on, false = off)
endfunction

function DHDH takes nothing returns real
    return 200.00 // The orbs flying height
endfunction

function DHLH takes nothing returns real
    return 200.00 // The lightnings flying height (If DHLO = true)
endfunction

// ******************************** DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING! *****************************

function DHFilter takes nothing returns boolean
    local unit fu = GetFilterUnit()
    local real x = GetUnitX(fu)
    local real y = GetUnitY(fu)
    if not IsUnitType(fu, UNIT_TYPE_STRUCTURE) and not IsUnitType(fu, UNIT_TYPE_DEAD) then
      if IsUnitEnemy(fu, GetOwningPlayer(udg_DHEC)) and not (GetUnitAbilityLevel(fu, 'Bams') > 0) and not (GetUnitAbilityLevel(fu, 'BHds') > 0) then
        call DestroyEffect(AddSpecialEffect(DHBE(), x, y))
        call UnitDamageTarget(udg_DHEC, fu, DHFDUF(), false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
      else
        if not IsUnitEnemy(fu, GetOwningPlayer(udg_DHEC)) then
          call DestroyEffect(AddSpecialEffect(DHBE(), x, y))
          call SetWidgetLife(fu, (GetWidgetLife(fu) + DHFHUF()))
        endif
      endif
    endif
    set fu = null
    return false   
endfunction

function DHLoop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_DHash, id, 8190)
    local integer c
    local real x
    local real y
    local real x2
    local real y2
    local integer li = 0
    local unit lu
    local unit lu2
    local real dl
    local real d
    local real rn
    local real dps
    local real dis
    local integer lvl
    local real x3
    local real y3
    local lightning ll
    if not IsUnitType(u, UNIT_TYPE_DEAD) then
      set x = GetUnitX(u)
      set y = GetUnitY(u)
      set c = LoadInteger(udg_DHash, id, 8191)
      set d = LoadReal(udg_DHash, id, 8192)
      set rn = (360. / (I2R(DHDN())))
      set dps = LoadReal(udg_DHash, id, 8188)
      set dis = LoadReal(udg_DHash, id, 8189)
      set lvl = LoadInteger(udg_DHash, id, 8187)
      set c = (c - 1)
      call SaveInteger(udg_DHash, id, 8191, c)
      if c > 330 then
        set d = (d + dps)
        call SaveReal(udg_DHash, id, 8192, d)
        loop
          set li = li + 1
          set dl = ((I2R(c) * DHOS()) + (rn * I2R(li)))
          set x2 = x + d * Cos((dl) * bj_DEGTORAD)
          set y2 = y + d * Sin((dl) * bj_DEGTORAD)
          set lu = LoadUnitHandle(udg_DHash, id, li)
          call SetUnitX(lu, x2)
          call SetUnitY(lu, y2)
          if DHLO() == true then
            set ll = LoadLightningHandle(udg_DHash, id, (DHDN() + li))
            if li == DHDN() then
              set lu2 = LoadUnitHandle(udg_DHash, id, 1)
              set x3 = GetUnitX(lu2)
              set y3 = GetUnitY(lu2)
            else
              set lu2 = LoadUnitHandle(udg_DHash, id, (li +1))
              set x3 = GetUnitX(lu2)
              set y3 = GetUnitY(lu2)
            endif
            call MoveLightningEx(ll, true, x2, y2, DHLH(), x3, y3, DHLH())
          endif
          exitwhen li == DHDN()
        endloop
      elseif c < 34 and c > 0 then
        set d = (d - dps)
        call SaveReal(udg_DHash, id, 8192, d)
        loop
          set li = li + 1
          set dl = ((I2R(c) * DHOS()) + (rn * I2R(li)))
          set x2 = x + d * Cos((dl) * bj_DEGTORAD)
          set y2 = y + d * Sin((dl) * bj_DEGTORAD)
          set lu = LoadUnitHandle(udg_DHash, id, li)
          call SetUnitX(lu, x2)
          call SetUnitY(lu, y2)
          if DHLO() == true then
            set ll = LoadLightningHandle(udg_DHash, id, (DHDN() + li))
            if li == DHDN() then
              set lu2 = LoadUnitHandle(udg_DHash, id, 1)
              set x3 = GetUnitX(lu2)
              set y3 = GetUnitY(lu2)
            else
              set lu2 = LoadUnitHandle(udg_DHash, id, (li +1))
              set x3 = GetUnitX(lu2)
              set y3 = GetUnitY(lu2)
            endif
          call MoveLightningEx(ll, true, x2, y2, DHLH(), x3, y3, DHLH())
          endif
          exitwhen li == DHDN()
        endloop
      elseif c <= 0 then
        loop
          set li = li + 1
          set lu = LoadUnitHandle(udg_DHash, id, li)
          if DHLO() == true then
            set ll = LoadLightningHandle(udg_DHash, id, (DHDN() + li))
            call DestroyLightning(ll)
          endif
          call KillUnit(lu)
          exitwhen li == DHDN()
        endloop
        call ReleaseTimer(t)
        call FlushChildHashtable(udg_DHash, id)
        set udg_DHEC = u
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, (DHFD() + (DHFDI() * (I2R(lvl)))), Filter(function DHFilter))
      else
        loop
          set li = li + 1
          set dl = ((I2R(c) * DHOS()) + (rn * I2R(li)))
          set x2 = x + dis * Cos((dl) * bj_DEGTORAD)
          set y2 = y + dis * Sin((dl) * bj_DEGTORAD)
          set lu = LoadUnitHandle(udg_DHash, id, li)
          call SetUnitX(lu, x2)
          call SetUnitY(lu, y2)
            if DHLO() == true then
              set ll = LoadLightningHandle(udg_DHash, id, (DHDN() + li))
              if li == DHDN() then
                set lu2 = LoadUnitHandle(udg_DHash, id, 1)
                set x3 = GetUnitX(lu2)
                set y3 = GetUnitY(lu2)
              else
                set lu2 = LoadUnitHandle(udg_DHash, id, (li +1))
                set x3 = GetUnitX(lu2)
                set y3 = GetUnitY(lu2)
            endif
            call MoveLightningEx(ll, true, x2, y2, DHLH(), x3, y3, DHLH())
          endif
          exitwhen li == DHDN()
        endloop  
      endif
    else
      loop
        set li = li + 1
        set lu = LoadUnitHandle(udg_DHash, id, li)
        if DHLO() == true then
          set ll = LoadLightningHandle(udg_DHash, id, (DHDN() + li))
          call DestroyLightning(ll)
        endif
        call KillUnit(lu)
        exitwhen li == DHDN()
      endloop
      call ReleaseTimer(t)
      call FlushChildHashtable(udg_DHash, id)
    endif
    set lu = null
    set lu2 = null
    set ll = null
    set u = null
    set t = null
endfunction

function DHAID takes nothing returns boolean
    local unit u
    local timer t
    local integer id
    local integer li
    local real x
    local real y
    local real x2
    local real y2
    local unit lu
    local unit lu2
    local real rn
    local integer lvl
    local real dis
    local real dps
    local lightning ll
    local real x3
    local real y3
    if GetSpellAbilityId() == DHCAID() then
      set u = GetTriggerUnit()
      set t = NewTimer()
      set id = GetHandleId(t)
      set li = 0
      set x = GetUnitX(u)
      set y = GetUnitY(u)
      set rn = (360. / (I2R(DHDN())))
      set lvl = GetUnitAbilityLevel(u, DHCAID())
      set dis = (DHDD() + (DHDDI() * I2R(lvl)))
      set dps = (dis / 33.)
      call SaveInteger(udg_DHash, id, 8187, lvl)
      call SaveReal(udg_DHash, id, 8188, dps)
      call SaveReal(udg_DHash, id, 8189, dis)
      call SaveUnitHandle(udg_DHash, id, 8190, u)
      call SaveInteger(udg_DHash, id, 8191, 363)
      loop
        set li = li + 1
        set x2 = x + Cos((rn * li) * bj_DEGTORAD)
        set y2 = y + Sin((rn * li) * bj_DEGTORAD)
        set lu = CreateUnit(GetTriggerPlayer(), DHCDID(), x2, y2, 0.)
        call SetUnitFlyHeight(lu, DHDH(), 0.)
        call SaveUnitHandle(udg_DHash, id, li, lu)
        exitwhen li == DHDN()
      endloop
      set li = 0
      loop
        set li = li + 1
        set lu = LoadUnitHandle(udg_DHash, id, li)
        set x2 = GetUnitX(lu)
        set y2 = GetUnitY(lu)
        if DHLO() == true then
          if li == DHDN() then
            set lu2 = LoadUnitHandle(udg_DHash, id, 1)
            set x3 = GetUnitX(lu)
            set y3 = GetUnitY(lu)
          else
            set lu2 = LoadUnitHandle(udg_DHash, id, (li + 1))
            set x3 = GetUnitX(lu)
            set y3 = GetUnitY(lu)
          endif
          set ll = AddLightningEx("HWPB", true, x2, y2, DHLH(), x3, y3, DHLH())
          call SaveLightningHandle(udg_DHash, id, (DHDN() + li), ll)
        endif
        exitwhen li == DHDN()
      endloop
      call TimerStart(t, 0.03, true, function DHLoop)
      set u = null
      set t = null
      set ll = null
      set lu = null
      set lu2 = null
    endif
    return false
endfunction

function InitTrig_Divine_Halo takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function DHAID ) )
    set udg_DHash = InitHashtable()
    set t = null
endfunction

Coding by me, Zaio
Basic idea from Skamigo's paladin in 'Skamigo's Hero Defense'


Changelog

2.1 - Efficiency update.
2.0 - Using TimerUtils
1.9 - Sorry for the extreme indenting fail, should be fixed and easily readable now.
1.8 - Minor code change, 2 new configurables for dummy and lightning height.
1.7 - Fixed a minor leak, correctly nulling locals (Thanks Deuterium!)
1.6 - Fixed what Pharaoh_ mentioned aswell as some other cody efficiency updates.
1.5 - Reinvented code quite a bit and added a configurable for lightning between each orb.
1.4 - Removed trigger action, added 4 more configurables, multiple level support and now the dummies can have sight radius back if wanted (fixed the previous error).
1.3 - Fixed everything Pharaoh_ mentioned as well as adding a configurable for orb speed.
1.2 - Removed one hashtable, made code more presentable.
1.1 - Updated importing instructions, changed heal to a set amount and changed checking of unit life to checking if unit is dead.

Keywords:
divine, halo, holy, heal, damage, mui, jass, orbs, pheonix, fire, bolt, lightning, orb, circle, radius, sphere, rotate.
Contents

Just another Warcraft III map (Map)

Reviews
18:32, 4th Sep 2011 Pharaoh_: Rename your variables into something readable. It's not GUI after all, placing the acronyms of your spell as a prefix on them. New review: http://www.hiveworkshop.com/forums/1999942-post9.html Approved; nice effects.

Moderator

M

Moderator

18:32, 4th Sep 2011
Pharaoh_: Rename your variables into something readable. It's not GUI after all, placing the acronyms of your spell as a prefix on them.

New review: http://www.hiveworkshop.com/forums/1999942-post9.html


Approved; nice effects.
 
Since you only use DHT once, just use GetExpiredTimer directly...

also, the code is pure jass so it won't create udg_hash and udg_hash2 automatically even if you have checked Automatically paste unknown variables...

Also, I think 1 hashtable will suffice...

and it will be better if the damage/heal formulas will initially take the spell's level as a parameter, that way the user won't need to edit anything on the main code part if he wants the damage/heal to be based on spell level...

also instead of using damage for the heal (and that bad formula which can be simplified to -DHFDUF()), use SetWidgetLife(unit, amount)
 
Level 8
Joined
Jul 3, 2011
Messages
251
Thanks for the feedback, ill fix the heal and add in importing instructions to create hash1 and 2, however i think two will be better, if for some reason they choose to use an extreme amount of orbs, if the number is large enough to over-write the other values they will just fly around the map widely xD
 
Level 17
Joined
Mar 17, 2009
Messages
1,350
Honestly, and as far as I understand hashtables, one hashtable should suffice doing almost everything in a map :p

As for the script, i tried checking it out but got completely lost because of how you name your locals :p
You don't need to go for big descriptive variable names, but here's how u can make things nice, neat & short:
JASS:
local unit m //missile
local unit tr //triggering unit
local unit ta //target unit
local integer i //loop counter
local real dam //damage
etc etc...
These are just random examples of how to have short names which would makes sense as one reads the code and are short enough (even too short) for you to easily & quickly code ;)
 
• Move your actions in the conditions: they proc faster.
• You definitely don't need the ua array local. Remove it from every function. You didn't even null its components.
• Destroy the halo if the caster dies (make a check in the timer callback).
• The + 0 is not needed in the x2 and y2 calculations, instantly set them to x * Cos (...) and y * Sin (...).
• Make the HolyBolt special effect configurable.
• WEAPON_TYPE_WHOKNOWS: You can just use "null" here.
• Check if the filtered enemy unit is invulnerable to avoid enumeration.
• You are loading the values quite bad:
JASS:
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_DHash, 8190, id)
Should be:
JASS:
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_DHash, id, 0)
0 or any other number that you have labeled it with.
 
Level 8
Joined
Jul 3, 2011
Messages
251
1. didnt really understand properly
2. done
3. done
4. afraid i cant, if i remove the + 0 the orbs fly around the map for 0.03 secs, which is enough to reveal a good portion of the map
5. done
6. done
7. done
8. done

also added a configurable for the speed at which the orbs spin
 
Level 8
Joined
Jul 3, 2011
Messages
251
1. Is this really necessary? It seems like an awefull amount of work for something that already works... What benefits does it have? Will my spell not get approved if i dont do this?

4. Done
 
Level 8
Joined
Jul 3, 2011
Messages
251
true, but it isnt exactly hard to do and i suppose its better if it is faster, so i done it :p i am interested to know if actions seriously leak though... if anyone knows 100% sure, say if it does or doesnt please :p
 
Level 17
Joined
Mar 17, 2009
Messages
1,350
JASS:
      call SaveInteger(udg_DHash, id, 8187, lvl)
      call SaveReal(udg_DHash, id, 8188, dps)
      call SaveReal(udg_DHash, id, 8189, dis)
      call SaveUnitHandle(udg_DHash, id, 8190, u)
      call SaveInteger(udg_DHash, id, 8191, c)
Why use such random values as 8187 & etc... just go for 1, 2, 3, 4... easier for the eye :)
I mean, each timer (giving different parent ids) would be having it's own children... so the children would be private to the spell instance ;)

Don't null locals in the middle of the function, since sometimes you null them in a loop and that's useless (since they would be overwritten)... at the very end of the functions null ALL locals at once (that would be neater code wise & more efficient) :)

In DHAID, you create a local c = 363 to save it into a handle... instead... just go for SaveInteger(hashtable, id, 1, 363) in order to create the value :)

That's all i noticed coding wise... there could be more that i didn't notice...
But I see ur doing fine :)
 
Level 8
Joined
Jul 3, 2011
Messages
251
Well i use those numbers for a reason (8187, 8188, 8189, 8190 and 8191) i save lightning and units based on the number of configurable orbs and save them accordingly, if i saved as 0/1/2/3/4 then the spell would be overwritten by the dummies/lightning, i have them as id of li for units and id of dummy amount + li for lightning, as for the rest i will update for efficiency when i have some time, thanks for code review Deuterium :)
 
Well i use those numbers for a reason (8187, 8188, 8189, 8190 and 8191) i save lightning and units based on the number of configurable orbs and save them accordingly, if i saved as 0/1/2/3/4 then the spell would be overwritten by the dummies/lightning, i have them as id of li for units and id of dummy amount + li for lightning, as for the rest i will update for efficiency when i have some time, thanks for code review Deuterium :)

Static values are static values; if 1 is static, so 8187 is. You save the values on a local timer; each local is generated with a different, unique id, there's no way they will collide.
 
Level 8
Joined
Jul 3, 2011
Messages
251
yeah i know, but the thing is im saving dummies and lightning as the low numbers, so i use high numbers so that they will never ever have problems, i know that 8000 or so numbers are extremely high and the game will crash before dummies reach 4000+ but it just assures it will never error... If for example dummies made is set to 20 and lightning is enabled, 1-20 will be the dummies and 21-40 will be the lightning, its how i made this spell.
 
If you want to really avoid collision of keys, use the HandleId of each dummy/orb as the parent key... that way, it won't ever cause key collisions with the other dummies or anything else...

I believe that its better to just use 1 global timer for this (rather than 1 timer per cast) then just loop through each orb (which will need to be added to a group for example or just plain indexed)... and then like my suggestion above, save the other things to the hashtable using the Orb's HandleId as the parent...
 
Nice spell, the only thing I dont like is the indenting...
this for example...
JASS:
if c > 330 then
set d = (d + dps)
call SaveReal(udg_DHash, id, 8192, d

can be...
JASS:
if c > 330 then
   set d = (d + dps)
   call SaveReal(udg_DHash, id, 8192, d

I also recommend to null your timer and unit at the bottom, not when the spellId is achieved only and add >>> TriggerAddAction...
 
Level 8
Joined
Jul 3, 2011
Messages
251
Well, i only have condition because Pharaoh_ told me to remove the action, apparently they leak and conditions are faster... And yeah i agree my indenting fails, as i started editting it to be very configurable i had to add a lot of if/then/elseifs so its kinda all over the place now, i will fix the indenting when i have some time :) thanks for the comment

EDIT: New properly-indented code is uploaded.
 
EVERY spell casted calls...
JASS:
local unit u
    local timer t
    local integer id
    local integer li
    local real x
    local real y
    local real x2
    local real y2
    local unit lu
    local unit lu2
    local real rn
    local integer lvl
    local real dis
    local real dps
    local lightning ll
    local real x3
    local real y3

so I suggest, putting condition first then actions...
actions leak?, not proven, else there are so many GUI triggers that leaks coz
ALL GUI spells uses actions, so why did they got approved...
I do admit that conditions are faster than actions...
 
Level 8
Joined
Jul 3, 2011
Messages
251
well... since those are undeclared variables it doesnt matter since it wont cause a very small leak if the condition is false, and i suppose GUI spells get approved because they still work, the action leaks are extremely small but still existant i think, according to Pharaoh_ there is a function that removes trigger actions, and since call RemoveLocation() clears a leak, remove action probably does the same thing. But i still think i am going to use condition, if it is faster that alone is worth not using actions, i suppose.
 
Level 8
Joined
Jul 3, 2011
Messages
251
Which nulling actions? The only thing i can see that you may be talking about is the variables in the start trigger, however there is no point putting the null outside the if, since it would be nulling undeclared variables if the ability id isnt true, can you quote the lines you are talking about please?
 
Level 7
Joined
Sep 2, 2011
Messages
349
Tested. The Idea is great, but visually, the rotating orbs is really weird.
I really dislike the way it spins. I don't think it does spin actually.
 
Level 8
Joined
Jul 3, 2011
Messages
251
You dont think it spins? Change the boolean for lightning to false and you will see it does. Also where the configurable has 6.00, if you change that to -6.00 it will spin the other way.
 
Level 7
Joined
Sep 2, 2011
Messages
349
I see :) Thanks for the info. It was just the lightning that confused me.
Is it also possible that allied units inside the circle will be healed? I think that idea
gives the spell name a role.
 
Top