• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Custom Chain Lightning System

Level 7
Joined
Jun 10, 2007
Messages
225
I've wanted to do a system like this for awhile. It allows you to easily create chain lightnings and modify every aspect of them, and even allow them to bounce twice on one unit in certain situations.
Requires vJass and CSCache in a library called CSCache

Below is the code:
JASS:
library ChainLightning requires CSCache
//Requires vJass to compile
//Requires CSCache system in a library called CSCache
//Use GetLightningTarget() to refer to the unit being hit by lightning in any handlerFuncs you use
//Use GetLightningCaster() to refer to the casting unit 
//Use GetLightningBounceNumber() to refer to how many bounces there have been so far
//Use GetLightning() to refer to the lightning connecting the last unit to the current unit
//Use 0 for bouncesbetweenbouncetwice to make it so you can never bounce twice
//Currently there is a 99 bounce limit if you want bouncesbetweenbouncetwice to work

//function ChainLightning takes unit caster, unit target,real bouncedist, string ltype,real ldur, integer bounceamount,integer bouncesbetweenbouncetwice,real bouncerate,boolexpr filter, code handlerFunc returns nothing

struct ChainData
    integer maxbounces
    integer sofar
    unit target
    unit caster
    unit lastunit
    string ltype
    real maxdist
    trigger t
    real time
    group alreadyhit = CreateGroup()
    boolexpr filter
    unit array units[100]
    integer bounces
    real ldur
    static lightning Lightning
    static unit LightningTarget
    static unit LightningCaster
    static integer LightningBounceNumber
endstruct

struct LightningAttachData
    lightning l
    unit u1
    unit u2
    real h1
    real h2
    real dur
endstruct

constant function GetLightning takes nothing returns lightning
    return ChainData.Lightning
endfunction

constant function GetLightningTarget takes nothing returns unit
    return ChainData.LightningTarget
endfunction

constant function GetLightningCaster takes nothing returns unit
    return ChainData.LightningCaster
endfunction

constant function GetLightningBounceNumber takes nothing returns integer
    return ChainData.LightningBounceNumber
endfunction

function AttachTempLightning_Timer takes nothing returns nothing
        local LightningAttachData la = LightningAttachData(GetAttachedInt(GetExpiredTimer(),"la"))
        call MoveLightningEx(la.l,true,GetUnitX(la.u1),GetUnitY(la.u1),la.h1,GetUnitX(la.u2),GetUnitY(la.u2),la.h2)
        set la.dur = la.dur - 0.04
            if la.dur <= 0.0 then
                call PauseTimer(GetExpiredTimer())
                set la.u1 = null
                set la.u2 = null
                call DestroyLightning(la.l)
                set la.l = null
                call LightningAttachData.destroy(la)
                call CleanAttachedVars(GetExpiredTimer())
                call DestroyTimer(GetExpiredTimer())
            endif
endfunction

function AttachTempLightning takes string s,unit u1, unit u2, real dur, real height1, real height2 returns lightning
    local LightningAttachData la = LightningAttachData.create()
    local timer t = CreateTimer()
    set la.u1 = u1
    set la.u2 = u2
    set la.l = AddLightningEx(s,true,GetUnitX(u1),GetUnitY(u1),height1,GetUnitX(u2),GetUnitY(u2),height2)
    set la.dur = dur
    set la.h1 = height1
    set la.h2 = height2
    call AttachInt(t,"la",la)
    call TimerStart(t,0.04,true,function AttachTempLightning_Timer)
    set t = null
    return la.l
endfunction



function DuplicateUnit takes unit u returns unit
    return u
endfunction

function ChainLightning_Timer takes nothing returns nothing
    local ChainData cd = ChainData(GetAttachedInt(GetExpiredTimer(),"cd"))
    local group g = CreateGroup()
    local group g2 = CreateGroup()
    local unit u
    local integer i
    local lightning l
    local timer t = GetExpiredTimer()
    set cd.sofar = cd.sofar + 1
    set cd.units[cd.sofar] = cd.target
    if cd.bounces > 0 then  
        set i = cd.sofar - cd.bounces
            if i > 0 and cd.units[i] != null then
                call GroupRemoveUnit(cd.alreadyhit,cd.units[i])
                set cd.units[i] = null
            endif
    endif            
    set l = AttachTempLightning(cd.ltype,cd.target,cd.lastunit,cd.ldur,GetUnitFlyHeight(cd.target) + 50,GetUnitFlyHeight(cd.lastunit) + 50)
    set ChainData.LightningTarget = cd.target
    set ChainData.LightningCaster = cd.caster
    set ChainData.LightningBounceNumber = cd.sofar
    set ChainData.Lightning = l
    call TriggerExecute(cd.t)
    call GroupAddUnit(cd.alreadyhit,cd.target)
    call GroupEnumUnitsInRange(g,GetUnitX(cd.target),GetUnitY(cd.target),cd.maxdist,cd.filter)
    call GroupAddGroup(g,g2)
        loop
            set u = FirstOfGroup(g2)
            exitwhen u == null
            call GroupRemoveUnit(g2,u)
                if IsUnitInGroup(u,cd.alreadyhit) then
                    call GroupRemoveUnit(g,u)
                endif
        endloop
    set cd.lastunit = DuplicateUnit(cd.target)
    set cd.target = GroupPickRandomUnit(g)
    call DestroyGroup(g)
    set g = null
    if cd.sofar >= cd.maxbounces or cd.target == null then
        call PauseTimer(t)
        set cd.target = null
        set cd.caster = null
        set cd.lastunit = null
        set cd.ltype = null
        set cd.t = null
        set cd.filter = null
        call DestroyGroup(cd.alreadyhit)
        set cd.alreadyhit = null
        call ChainData.destroy(cd)
        call CleanAttachedVars(t)
        call DestroyTimer(t)
    endif
    if cd.sofar == 1 then
        call TimerStart(GetExpiredTimer(),cd.time,true,function ChainLightning_Timer)    
    endif
    call DestroyGroup(g2)
    set g2 = null
    set u = null
    set l = null
endfunction

function ChainLightning takes unit caster, unit target,real bouncedist, string ltype,real ldur, integer bounceamount,integer bouncesbetweenbouncetwice,real bouncerate,boolexpr filter, code handlerFunc returns nothing
    local ChainData cd = ChainData.create()
    local timer t = CreateTimer()
    local trigger trig = CreateTrigger()
    call TriggerAddAction(trig,handlerFunc)
    set cd.ldur = ldur
    set cd.bounces = bouncesbetweenbouncetwice
    set cd.filter = filter
    set cd.t = trig
    set cd.target = target
    set cd.caster = caster
    set cd.lastunit = caster
    set cd.maxdist = bouncedist
    set cd.ltype = ltype
    set cd.maxbounces = bounceamount
    set cd.t = trig
    set cd.time = bouncerate
    set cd.sofar = 0
    call GroupAddUnit(cd.alreadyhit,target)
    call AttachInt(t,"cd",cd)
    call TimerStart(t,0.00,false,function ChainLightning_Timer)
    set trig = null
    set t = null
endfunction

endlibrary

Chain Lighting takes
unit caster - The casting unit, the first bolt will originate from this unit. Using GetLightningCaster() in the handlerFunc will return this unit.
unit target - The first unit to be hit.
real bouncedist - The maximum distance the lightning will look for a new target to bounce to.
string ltype - The model the lightning will use.
real ldur - The duration each lightning will last. 0.5 - 1.0 are the best, in my opinion.
integer bounceamount - The number of bounces. Using GetLightningBounceNumber() in the handlerFunc will return how many bounces so far.
integer bouncesbetweenbouncetwice - The number of bounces there must be before the lightning can hit a unit again. If a unit is hit, and this is 7, it must bounce 7 more times before the target will become strikeable again. Using 0 means it cna never bounce twice.
real bouncerate - The time between the lightning's bounces.
boolexpr filter - A function which must return true, or else the target cannot be hit.
code handlerFunc - This will run whenever the lightning strikes a unit. GetLightning() returns the lightning going to the unit, GetLightningCaster() returns the lightning's caster, GetLightningUnit() returns the current unit being hit, GetLightningBounceNumber() returns how many bounces there have been so far.

Edit1: Fixed some suggestions purplepoot made, attached an updated map.
Edit2: Removed the map, theres a thread about this in the S&S section.

You can also download the attached map to see some example spells:
 
Last edited:
Level 40
Joined
Dec 14, 2005
Messages
10,532
-Count down to 0, not up to n

-What's with "DuplicateUnit"? (worse than most BJs =O)

-GetLightningBlahBlah should be constant

-For better efficiency among other things, put exitwhen u == null right after set u = FirstOfGroup(g) in group-loops

-remove the if cd.target == null then' if-statement and just inline it with the other end-checker.

-shorten your parameter names! (not required, but preferrable)

-take a boolexpr for a filter, not a filterfunc

-make use of overriding the create method in structs (not required, but preferrable again)



Also, this would be more welcome in the Spells (&Systems) section than in the Jass Snippets section, I think. (this is more suited for independent stuff that don't work on your own, while your lightning system+spells definitely can be independent compared to something like the Handle Vars or random utility libraries)

Basically, if you need to attach a map for some reason or other, it should be in the spells(&systems) section ;)
 
Level 7
Joined
Jun 10, 2007
Messages
225
- I cant count down to 0, as I need to increase cd.sofar instead of decreasing it (or else GetLightningBounceNumber() wont work)

-DuplicateUnit is so I can point the unit variable to another unit variable and then change the variable its pointing to without changing the second one also. If i dont need it, ill change it (im pretty sure i do, though)

-Made them all constant

-Fixed that

-Fixed that too

-The long paramater names wont change anything for the user, but it will help them figure out what each field does easier.

-fix'd

-No idea what that means :p

-Could you move it to the spells &systems section, then?
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
-DuplicateUnit is so I can point the unit variable to another unit variable and then change the variable its pointing to without changing the second one also. If i dont need it, ill change it (im pretty sure i do, though)
Even if warcraft worked that way, I don't see how that would help.

- I cant count down to 0, as I need to increase cd.sofar instead of decreasing it (or else GetLightningBounceNumber() wont work)
I was referring to AttachTempLightning

-The long paramater names wont change anything for the user, but it will help them figure out what each field does easier.
I'm sure they could be understandable if they were a bit shorter, and currently they make you have to scroll right for ages -.-

-Could you move it to the spells &systems section, then?
the resource sections (except jass here) aren't forum sections; you'd have to submit it there yourself.

-No idea what that means :p
I just thought it'd be cleaner if you initialized all your vars when creating the struct, eg;

JASS:
struct tempStruc
    static method create takes paramArgs returns tempStruct//must return the struct type
        local tempStruct dat = tempStruc.allocate()
        //blahblah
        return dat
    endmethod
endstruct

It'd look a little neater if you initialized all the crud (lightnings, so on) within the create, and then passed it as parameters. It's really a matter of preference, though. Even so, making it independent could remove the need for a starter function in the first place.
 
Top