• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Lightning Cloud v2.0

Summons a thick cloud that follows the caster and discharges bolts of lightning to nearby enemy units. Cloud lasts for 20 seconds.
Level 1 - Lightning strike deals 20 damage.
Level 2 - Lightning strike deals 40 damage.
Level 3 - Lightning strike deals 60 damage.
Level 4 - Lightning strike deals 80 damage.
Level 5 - Lightning strike deals 100 damage.

Credits for helping me improve the spell:
- baassee
- Maker
- Adiktuz
- Bribe


JASS:
//Spell Name: Lightning Cloud v2.0
//Made by: Mckill2009

//NOTE:
//- To all of you who are thinking that this is a rip-off dota's Razor's ultimate spell
// well, you are wrong, I even dont know that spell coz I really dont play DotA

//===HOW TO USE:
//- Make a new trigger and convert to custom text via EDIT >>> CONVERT CUSTOM TEXT
//- The trigger name MUST be >>> InitTrig_LightningCloud (see the name below)
//- Copy ALL that is written here and overwrite the existing texts in the custom text
//- Copy the Dummy/custom abilities/buffs etc... to your object editor
//- Make sure you inputed the correct raw codes of the base spell/buffs/dummy etc...
//- If your raw code is different, you MUST CHANGE IT...
//- You can view the raw codes by pressing CTRL+B in the object editor
//- Examples of raw codes are 'A000', 'h000' etc... 

//===REQUIRED VARIABLES:
//- HASH = Hashtable
//- LC_Cloud = Unit
//- LC_TimerN - Integer
//- LC_Timers - Timer Array

//===CONFIGURABLES:
constant function LC_SpellId takes nothing returns integer
    return 'A000' //Raw code for the Hero ability
endfunction 

constant function LC_DummySpellId takes nothing returns integer
    return 'A001' //Raw code for the Lightning Strike ability
endfunction                          

constant function LC_CloudUnit takes nothing returns integer
    return 'h001' //Raw code for the Cloud Unit
endfunction 

constant function LC_Damage takes integer i returns real
    return i * 20. //This is the ability level multiply by base damage
endfunction

constant function LC_Range takes nothing returns real
    return 500. //Maximum range is 3000 or you can adjust it via object editor 
endfunction

constant function LC_Duration takes nothing returns real
    return 20. //This is the duration of the cloud and spell
endfunction

constant function LC_TimerSpeed takes nothing returns real
    return 0.05
endfunction

constant function LC_ATT takes nothing returns attacktype
    return ATTACK_TYPE_MAGIC
endfunction

constant function LC_DAM takes nothing returns damagetype
    return DAMAGE_TYPE_LIGHTNING 
endfunction
//===END OF CONFIGURABLES===

function LC_RTimer takes timer t returns nothing
    if t != null then
        call PauseTimer(t)
        set udg_LC_Timers[udg_LC_TimerN] = t
        set udg_LC_TimerN = udg_LC_TimerN + 1
    endif
endfunction

//and get a timer
function LC_NTimer takes nothing returns timer
    if udg_LC_TimerN == 0 then
        return CreateTimer()
    endif
    set udg_LC_TimerN = udg_LC_TimerN - 1
    return udg_LC_Timers[udg_LC_TimerN]
endfunction
                                                                         
function LC_FilterThem takes nothing returns boolean
    local unit u = GetFilterUnit()
    local boolean b = GetWidgetLife(u) > 0.405 and IsUnitEnemy(u, GetOwningPlayer(udg_LC_Cloud)) and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false
    set u = null
    return b
endfunction 
          
function LC_Loop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer parent = GetHandleId(t)
    local integer cloudkey
    local unit caster = LoadUnitHandle(udg_HASH, parent, 1)
    local unit cloud = LoadUnitHandle(udg_HASH, parent, 2)
    local real dur = LoadReal(udg_HASH, parent, 3)
    local real range = LoadReal(udg_HASH, parent, 4)
    set cloudkey = GetHandleId(cloud)
    set udg_LC_Cloud = cloud //should be transfered to a global variable
    if dur > 0 and GetWidgetLife(caster) > 0.405 then 
        call SaveReal(udg_HASH, parent, 3 , dur - LC_TimerSpeed())
        call SetUnitX(cloud, GetUnitX(caster))
        call SetUnitY(cloud, GetUnitY(caster))
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(cloud), GetUnitY(cloud), range, Filter(function LC_FilterThem))
        call IssueTargetOrder(cloud, "chainlightning" , FirstOfGroup(bj_lastCreatedGroup)) //852119       
    else
        call UnitApplyTimedLife(cloud, 'BTLF', 0.01)
        call LC_RTimer(t)
        call FlushChildHashtable(udg_HASH, parent)
        call FlushChildHashtable(udg_HASH, cloudkey)    
    endif
    set t = null
    set cloud = null
    set caster = null  
endfunction 

function LC_Cast takes nothing returns boolean
    local timer t
    local unit caster
    local unit cloud
    local integer cloudkey
    local integer parent
    local integer abilitylevel
    if GetSpellAbilityId()==LC_SpellId() then
        set caster = GetTriggerUnit()
        set abilitylevel = GetUnitAbilityLevel(caster, LC_SpellId())  
        set t = LC_NTimer()
        set parent = GetHandleId(t)
        set cloud = CreateUnit(GetTriggerPlayer(), LC_CloudUnit() , GetUnitX(caster), GetUnitY(caster), 0)
        set cloudkey = GetHandleId(cloud)
        //===This portion saves the caster so that he will deal damage, not the cloud
        call SaveUnitHandle(udg_HASH, cloudkey, 1, caster)
        call SaveReal(udg_HASH, cloudkey, 2 , LC_Damage(abilitylevel))
        //===This portion saves the caster/cloud/duration and range
        call SaveUnitHandle(udg_HASH, parent, 1, caster) 
        call SaveUnitHandle(udg_HASH, parent, 2, cloud)
        call SaveReal(udg_HASH, parent, 3, LC_Duration()) //See Function    
        call SaveReal(udg_HASH, parent, 4, LC_Range()) //See Function 
        call TimerStart(t, LC_TimerSpeed(), true, function LC_Loop) 
    elseif GetSpellAbilityId()==LC_DummySpellId() then
        set cloudkey = GetHandleId(GetTriggerUnit())
        call UnitDamageTarget(LoadUnitHandle(udg_HASH, cloudkey, 1), GetSpellTargetUnit(), LoadReal(udg_HASH, cloudkey, 2), false, false, LC_ATT(), LC_DAM(), null)
    endif 
    set t = null
    set caster = null
    set cloud = null
    return false
endfunction 
       
function InitTrig_LightningCloud takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function LC_Cast))
    set udg_HASH = InitHashtable()
    set t = null
endfunction



167625-albums4356-picture47040.jpg

167625-albums4356-picture47041.jpg




02 July 2011
- Function and Variables of Timers are prefixed with LC to avoid conflicts
- Hashtable is initialized in the code

10 June 2011
- Conditions merged
- Use release timer instead of destroy

09 June 2011
- Triggers reduced from 2 to 1
- Indenting fixed
- Codes reduced

28 May 2011
- Code is greatly optimized and reduced
- Stringhashes removed
- Constant functions applied
- Merged with condition
- Global variables reduced from 4 to 2

30 April 2011
- Most global variables replaced by fuctions
- More configurables
- Hashtables flushed

27 April 2011
- Made in JASS
- Attack & Damage type is now configurable
- Damage has been reduced but configurable
- Range/AoE has been reduced but configurable
- Speed when lightning strike is configurable
- Caster is now who deals damage
- Conditions for UnitDamageTarget is set
- Conditions for ability being cast is now separate
- Triggers reduced
- Special thanks to Maker and Adiktuz

09 April 2011
- New improved movement using SetUnitX and SetUnitY
- Triggers greatly reduced
- Dummy lightning caster removed
- AoE reduced from 3000 to 700

11 Feb 2011
- Added configuration timer and range of the cloud strike
- Cleaned group leaks
- Trigger optimized

04 Jan 2011
- Lightning hit is now random
- Lightning damage increased
- Lightning level damage optimized
- Icon changed from passive to active


Keywords:
lightning, cloud, strike, gust, light, rain, AoS, melee, rpg, JASS, vJASS
Contents

Lightning Cloud v2.0 (Map)

Reviews
19 October 2011 Bribe: Approved. But I still think if you make the origin of each lightning a random point in the cloud it will make it look more realistic.

Moderator

M

Moderator

19 October 2011
Bribe: Approved. But I still think if you make the origin of each lightning a random point in the cloud it will make it look more realistic.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
The spell is quite good, no leaks, it is MUI, tooltip is okay, object editor works seems to be in order.

How to improve:

Notice where the turn off block is. Like this, you'll do far less function calls. Do the same thing with the other trigger.

  • Lightning Cloud Movement
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in LC_EffectGrp and do (Actions)
        • Loop - Actions
          • Set LC_CloudEffect1 = (Picked unit)
          • Set LC_Caster1 = (Load (Key lccaster1) of (Key (Picked unit)) in Hash)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (LC_Caster1 is alive) Equal to True
              • (LC_CloudEffect1 is alive) Equal to True
            • Then - Actions
              • Set LC_Loc[2] = (Position of LC_Caster1)
              • Unit - Move LC_CloudEffect1 instantly to LC_Loc[2], facing LC_Loc[2]
              • Custom script: call RemoveLocation(udg_LC_Loc[2])
            • Else - Actions
              • Unit - Kill LC_CloudEffect1
              • Unit Group - Remove (Picked unit) from LC_EffectGrp
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in Hash
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (LC_EffectGrp is empty) Equal to True
                • Then - Actions
                  • Trigger - Turn off (This trigger)
                • Else - Actions


You could remove the loc array and use two loc variables for less memory usage, since you don't need an array. But that is a very minor issue.

LC_Caster2 and LC_CloudEffect2 variables are not needed. Remove them.

You don't need to save the caster twice into the hashtable.

You don't need these in Lightning Cloud Caster. Do the turn off check and unit group remove in the other looping trigger.
  • Unit - Kill LC_CloudEffect2
  • Unit Group - Remove (Picked unit) from LC_DummyGrp
  • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in Hash
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (LC_DummyGrp is empty) Equal to True
    • Then - Actions
      • Trigger - Turn off (This trigger)
    • Else - Actions
The icon is passive, but the spell is active.

Make the lightnings look more random. Now all clouds damage all units at the same time. Doesn't look good. Make the loop time shorter and randomize whether it causes a lightning or not. Only make it create one lightning per cloud per loop. That way it looks more random. Or come up with your own way to make it look more random.

You should store the level when the spell is cast and load the level for this:
  • Unit - Set level of Lightning Cloud (dummy) for (Last created unit) to (Level of Lightning Cloud for LC_Caster2)
 
Level 10
Joined
Aug 21, 2010
Messages
316
Really interesting.I found out here numerous comments such as I generally do not play dota or I played a few times or I do not know what it is dota,and eighty percent of the spell are made on the model of spells from dota.Not a big deal if someone's spell or system from dota served as a model.

You love it, you can not love, that you play or not but one thing is certain DOTA is the best and most played map that has ever made and just mentally disturbed man can deny that.

For my own part I play dota from the very beginning of its creation and it will always be my favorite.

As for your spell 5/5
 
Level 17
Joined
Jul 15, 2009
Messages
632
Why make it so hard?
- Init : Make trigger dummy and set variables
- Loop : Make it 'jump' to the position of the caster until durationvariable is 0.

Now all you need is a dummy unit that looks like a cloud and has Phoenix Fire (Or even let it attack with Lightning Attack).
Only downside here is you either have to have a upgrade for the dummy's damage or have X different dummy units.

Also, the idea is kinda like Razor's ultimate (DotA).

3.5/5
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
I prefer to use chain lightning coz I want the lightning 'visible' when firing at the
target, apparently the only normal attack I see to emulate chain lightning is the
chimaera/hawk projectiles which is very easy to configure, the downside of this is that it will
show ‘miss’ signs (which is I don’t like)…so that’s why I stick to chain lightning…
 
Level 30
Joined
Jul 31, 2010
Messages
5,246
Well, i am guessing that you didn't get idea from DotA Razor's ultimate spell. So i'll rate this with 5/5
their both the same dude, its like apple pie and....uhm pie! :alol:
thanks for the rating, but I dissagree that DOTA is the best, It maybe the most played game, but the best?, No, see the game Diablo 3 warcraft which is better than DOTA...
how can you disagree from DOTA since that game is totally good? yeah for me its the best made AOS in the world, but not the best? man you should play it a lot, we don't know, you might end up as the best pro gamer of the game :ap:

nice spell by the way, keep up the good work!
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
It seems you haven't done almost any of the suggestions I made. Unneeded functions calls, useless data stored, overlapping actions.

Additionally, you don't need to create the dummy casters at all.

Instead of Move unit instantly, use these:
  • Custom script: call SetUnitX( udg_LC_CloudEffect1 , GetUnitX(udg_LC_Caster1 ) )
  • Custom script: call SetUnitY( udg_LC_CloudEffect1 , GetUnitY(udg_LC_Caster1 ) )
Give the cloud the lightning ability in object editor and order the cloud to cast the lightning.

You leak:
  • Unit Group - Pick every unit in (Random 1 units from LC_HitGrp) and do (Actions)
    • Loop - Actions
And the clouds still cast the lightning at the same time. It would be cooler if they didn't.

Make the radius configurable.

It is a good spell, but it could be better :)

3/5 for now.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
@Maker

Tnx dude but I preffer to use functions which I understand, meaning Im not using "call SetUnitX( udg_LC_CloudEffect1 , GetUnitX(udg_LC_Caster1 ) )" coz I really dont understand that...

Anyway I've tried your suggestion to do random hit instead of all and I did...

I did try to reduce the triggers to two and order the cloud dummy effect to move to the position of the caster and put the chain lightning ability from the object editor, but the problem is that the cloud doesnt cast the lightning, perhaps this is the caused by the 'movement' of the cloud...


  • Lightning Cloud Movement
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in LC_EffectGrp and do (Actions)
        • Loop - Actions
          • Set LC_CloudEffect = (Picked unit)
          • Set LC_Caster = (Load (Key lccaster1) of (Key (Picked unit)) in Hash)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (LC_Caster is alive) Equal to True
              • (LC_CloudEffect is alive) Equal to True
            • Then - Actions
              • Set LC_Loc[2] = (Position of LC_Caster)
              • Set LC_HitGrp = (Units within 500.00 of LC_Loc[2] matching ((((Matching unit) belongs to an enemy of (Owner of LC_Caster)) Equal to True) and ((((Matching unit) is alive) Equal to True) and ((((Matching unit) is A structure) Equal to False) and (((Matching unit) is Magic Imm
              • Unit - Order LC_CloudEffect to Orc Far Seer - Chain Lightning (Random unit from LC_HitGrp)
              • Unit - Order LC_CloudEffect to Move To LC_Loc[2]
              • Custom script: call RemoveLocation(udg_LC_Loc[2])
            • Else - Actions
              • Unit Group - Remove (Picked unit) from LC_EffectGrp
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in Hash
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (LC_EffectGrp is empty) Equal to True
                • Then - Actions
                  • Trigger - Turn off (This trigger)
                • Else - Actions


that's why I used another dummy unit to cast the lightning...

EDIT:

Updated: Lightning Cloud v1.2
 
Last edited:
Level 37
Joined
Mar 6, 2006
Messages
9,240
I did try to reduce the triggers to two and order the cloud dummy effect to move to the position of the caster and put the chain lightning ability from the object editor, but the problem is that the cloud doesnt cast the lightning, perhaps this is the caused by the 'movement' of the cloud...

Moving a unit instantly will interrupt the current order. That's why SetUnitX() and SetUnitY() would be better. They won't interrupt the order, and you don't have to create an additional dummy.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Set LC_HitGrp = (Random 1 units from (Units within LC_Radius of LC_Loc[3]

That leaks... you create two unit groups here but you only destroy the one generated by (Random 1 units from (Group))

To fix the leak, you need to store (Units within LC_Radius of LC_Loc[3]) into a group first, then set LC_HitGrp, then destroy the other group as you're done with it.
 
Suggestions based on preliminary look-out

  • You could also just add a dummy trigger which has all the vars so that they can copy paste it, and the editor will make it for them (assuming they have the auto create missing vars option to true...)
  • on the dummy cast action, you don't actually need the local integer var as you only use the value of GetHandleId() once...
  • you could save into reals the GetUnitX/Y on the main cast action
  • you should just use a global group rather than using RandomSubGroup
  • you should change the IsUnitAliveBJ into the IsUnitDead native...
  • and three triggers is too much I guess... example, you don't need the trigger for set-up since you can just do
    call Setup()
  • you should still do a filter for the ability for the dummy cast so as not to waste performance for running those actions when the spell casted is not the dummy spell..
Verdict


-I think this needs a lot of improvements

 
Level 37
Joined
Mar 6, 2006
Messages
9,240
It's better than v1.1 that I tested.

However you should make the caster deal the damage. Save the caster's handle for the cloud, and load it at the damage function.

Currently you can damage units without them attacking you.

And in my opinion, you should reverse the parent- and child keys. Now you have it like this:
call SaveUnitHandle(udg_HASHTABLE, StringHash("CASTER"), child, caster)
It should be
call SaveUnitHandle(udg_HASHTABLE, child, StringHash("CASTER"), caster)

That way everything is keyed for the unit's handle, not for random strings.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
as you can see, there is the thing called function setup there so they can auto create variables but as for call Setup(), gonna update that tonight...

as for now, Im finding a way to know what to use as replacement of RandomSubGroup, seems there isnt, I've tried ForGroup() but its not working properly...

Im using Jasscraft when doing all these but I cant find UnitIsDead, that's why Im using IsUnitAliveBJ...

This...I dont understand...
you should still do a filter for the ability for the dummy cast so as not to waste performance for running those actions when the spell casted is not the dummy spell..

EDIT:
@Maker

Im updating it next time...
 
I only looked up at the code you posted...

You would still use the GroupEnumUnits blahblah (of course ForGroup wont work if you use it to feed a group into the Enum as ForGroup also takes a group as a parameter)... but instead of feeding it with RandomSubGroup, just make a global group and initialize (create) it on the setup part...

call GroupEnumUnitsInRange(Your udg group here, blahblah)

IsUnitDead is what you would use...

On the dummy cast action, you don't filter out what the casted ability is, meaning that action would run whenever you use an ability...

also instead of Actions, use Conditions as they are faster...
 
and yeah, didn't notice that hashtable problem...

actually just change that dummy cast action to something like this:

JASS:
/*

Note: I set-up this with the mind set that you're already using Conditions rather than actions

Also, those locals are totally unneeded and just reduces performance as you only use them ONCE
*/
function LCdummyCast takes nothing returns boolean
    if GetSpellAbilityId() == blahblah then
        call UnitDamageTarget(GetTriggerUnit(), GetSpellTargetUnit(), LoadReal(udg_HASHTABLE, StringHash("DAMAGE"), GetHandleId(GetTriggerUnit()))  , false, false, udg_LC_ATT, udg_LC_DAM, null)
    endif
    return false
endfunction

/*
 but yeah, you should make the ORIGINAL caster cause the damage rather than the DUMMY caster
*/
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Good to see you jassing, a few points though.

Let's start from the init and forward. And btw, you're wrong, it's not just import the code and you're done, you have to change the ability id. And dummy.

-you should use conditions instead of actions as they're faster than actions

-trigger ttt is a bit useless, read below

-Useless variables.

set udg_LC_BaseDamage = 20
set udg_LC_Duration = 20
set udg_LC_Speed = 0.5 // Sets the speed of the lightning strike
set udg_LC_Range = 400 // Sets the range of the lightning maximum 3000
set udg_LC_ATT = ATTACK_TYPE_MAGIC
set udg_LC_DAM = DAMAGE_TYPE_LIGHTNING

Make them all different functions which you call to recieve the different amount of certain type.

like

JASS:
function LC_Damage takes integer lvl returns real
         return 20. * lvl
endfunction

-in the cast functions, things such as the ability id, dummy id, formulas should all be made into seperate functions at top for best configuration possible in JASS, like my example above

-The setup function could call "call DestroyTrigger(GetTriggeringTrigger())", I think it's save enough for setups but if you change to my suggestion above, ignore this

-you will need a simple timer recycler (you can copy one straight from my pounce spell, just made it the simpliest) as creating/destroying timers arent enough these days, recycle them is the new deal (pretty old actually)

-GetOwningPlayer(caster) -> GetTriggerPlayer()

-You made it pretty wrong with the hashtable saves. You made the parent key a stringhashed string instead of the id of the timer which you used as the child key. Switch these.

-Preferably you can make different functions for each child key to make them integers instead of string hash.

-set udg_LC_Group = CreateGroup() should be a local group

-you should use a group recycler too :p

-
JASS:
    set caster = LoadUnitHandle(udg_HASHTABLE, StringHash("CASTER"), child)
    set cloudduration = LoadReal(udg_HASHTABLE, StringHash("DURATION"), child)

these above can be loaded when you declare the locals

-use GetWidgetLife(u) > 0.405 and IsUnitType(u, UNIT_TYPE_DEAD) == false instead of IsUnitAliveBJ(caster)

-the variable you use to pass over the dummy to the "filterthem" function, if you change it into a local as I said above, pass it on via the hash but you can keep the global in this case

-GetTriggerUnit() can be stored in the dummycast call to save one function call (lol)

love the idea, like a small remake of thrall dota spell :p
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
@baassee

Im doing the conditions already, not all though...as you can see the trigger ttt is no more...

I thought that global variables for setup purposes is easier and faster to make that's why I did that...

About hashtable switches, like maker also told me, Im gonna do it next update...but I preffer StringHash instead of different childkeys(I also use stringhash in GUI's)...

lol >>> udg_LC_Group = CreateGroup() this is useless, I just forgot to delete it, too tired...

In all, thanks for the review, the next update I will apply this...+rep
 
Top