HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Custom Chain Lightning System

09-11-2007, 03:28 AM#1
Dil999
For the past few days I've been working on a complex chain lightning, which allows you to easily create custom chain lightning effects with many possibilities. Below is the almost finalized product of my work, but before I submit it to any sites I want to have it looked over by people. If you have any suggestions or ideas of what I can add to it, or suggestions to make my code more efficient (im going to have alot of those), feel free to post.
Requires vJass and CSCache in a library called CSCache



Collapse 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

//ChainLightning takes unit caster, unit target,real maxbouncedist, string lightningtype,real lightningduration, integer numberofbounces,integer bouncesbetweenbouncetwice,real timebetweenbounces,filterfunc 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()
    filterfunc 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
    real dursofar
endstruct

function GetLightning takes nothing returns lightning
    return ChainData.Lightning
endfunction

function GetLightningTarget takes nothing returns unit
    return ChainData.LightningTarget
endfunction

function GetLightningCaster takes nothing returns unit
    return ChainData.LightningCaster
endfunction

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.dursofar = la.dursofar + 0.04
            if la.dursofar >= la.dur 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.dursofar = 0
    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)
            call GroupRemoveUnit(g2,u)
                if IsUnitInGroup(u,cd.alreadyhit) then
                    call GroupRemoveUnit(g,u)
                endif
            exitwhen u == null
        endloop
    set cd.lastunit = DuplicateUnit(cd.target)
    set cd.target = GroupPickRandomUnit(g)
    if cd.target == null then
        set cd.sofar = cd.maxbounces
    endif
    call DestroyGroup(g)
    set g = null
    if cd.sofar >= cd.maxbounces 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 maxbouncedist, string lightningtype,real lightningdur, integer numberofbounces,integer bouncesbetweenbouncetwice,real timebetweenbounces,filterfunc 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 = lightningdur
    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 = maxbouncedist
    set cd.ltype = lightningtype
    set cd.maxbounces = numberofbounces
    set cd.t = trig
    set cd.time = timebetweenbounces
    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


You can also download the map below to see a few sample spells.
Attached Files
File type: w3xChain Lightning.w3x (40.6 KB)
09-11-2007, 06:02 AM#2
Captain Griffen
Why require CSCache when TimerAttach/GetTimerInt will do it just as fast (if not faster) and without needing CSCache to be implemented (since most people with vJASS won't have CSCache, I presume).
09-11-2007, 06:16 AM#3
Pyrogasm
You're going to run into problems when using this on flying units. Getting a unit's flyheight is just simply not accurate for placement of lightning effects.
The only way to be sure is to use a dummy unit to cast a modified Finger of Death on the target... which seems like something you're not going to want to do.
09-11-2007, 05:06 PM#4
TaintedReality
There is a much easier way to make chain effects. Just have a dummy unit cast an 0.01 damage chain lightning and detect when units are damaged. No need to make all this code when war3 already includes capabilities for chain effects.

http://www.jx3.net/TDG/forum/viewtopic.php?f=16&t=877
09-11-2007, 08:53 PM#5
botanic
humm i suppose this could be useful in certain situations because with a little modifying you could make it target all heroes first then normal units or w/e... but generally ya go with the built in wc3 stuff.
09-11-2007, 10:23 PM#6
Dil999
This is meant to be easily modifyable. You only need a few lines of code to make a good-looking new spell.
Botanic, thats a good idea. I need to decide how to do it-
1.Put booleans when calling the function (function ChaiNlighting takes boolean.. etc) or have it take something like TARGET_HEROS_FIRST, TARGET_WEAKEST_FIRST, tc. If you have any better suggestions of how to do it, or any ideas of other kingds of things mappers will want to target first, tell me.
09-11-2007, 10:43 PM#7
rain9441
Your chain lightning has an infamous GroupEnum leak.

http://wc3campaigns.net/showthread.php?t=90186

When using GroupEnum be sure to use a global group that is only ever created once and always use a filter. Just call GroupClear before you use it. Its minor and leaks once per bounce of lightning but its the leak I hate the most.

Instead of GroupPickRandomUnit(g) to pick the next target, you should make it a function interface in the struct so that the person making a spell can create a function which takes a group and returns a unit from that group according to the desires of the spell at hand.

Suppose I wanted to remake chain heal because I have too much time on my hands. I'd make a function that returned the unit with the lowest percentage health from the group. Suppose I wanted to make a chain mana burn, I'd want the chain to pick the unit with the highest mana percentage from the group.
09-11-2007, 11:05 PM#8
botanic
I didnt look through your code but it would simply be a
set Hero_T_F = Hero_First()
pick unit matching condition (matching unit is a hero = Hero_T_F)
09-12-2007, 02:01 AM#9
TaintedReality
Quote:
This is meant to be easily modifyable. You only need a few lines of code to make a good-looking new spell.

But what is the advantage over using the existing chain functionality within war3?
09-12-2007, 03:32 AM#10
Dil999
Its easier to say 1 line of code than create a long code and a few custom abilities, and a dummy to cast them.
Plus, if you had taken the time to read the top of the code, you would realize you have the ability to change several other things:
1. the duration of the lightnings
2. the time between bounces
3. the possibility to bounce multiple times on the same unit
4. the possibility to use filters to select specific units, for example a single unit or units in a group
5. the upcoming possibility to give the lightning it own target preferences.

Anyways, I posted this to have my code scrutinized and to have suggestions to what i could add; not to have people question why I made it in the first place..
09-12-2007, 04:51 AM#11
Vexorian
Hmmm:

Regarding detecting chain lightning: If there are two ways to do something, and one involves detecting something the game doesn't allow you to detect directly, pick the way that doesn't require to detect things.

regarding the code: what the hell is this?:
Collapse JASS:
function DuplicateUnit takes unit u returns unit
    return u
endfunction

Regarding timerattach: The very fact you are attaching things to timers is proof you got bad design, replacing CSCache with yet another return bug dependant thing will not help. It is better to fix the issue from the inside than to get ineffective patch solutions.
09-12-2007, 04:58 AM#12
Dil999
Vex: That piece of code is because i needed to point one unit variable to another, and I thought if i directly did that and then changed the original, the second variable would change also.
Reguarding timerattach: And how do I fix this? I'm still learning the complexities of vJass.
09-12-2007, 11:13 AM#13
Silvenon
I'm confused now, aren't these things supposed to be in Submit Resource section?
09-12-2007, 11:31 AM#14
Tide-Arc Ephemera
Quote:
Originally Posted by Dil999
For the past few days I've been working on a complex chain lightning, which allows you to easily create custom chain lightning effects with many possibilities. Below is the almost finalized product of my work, but before I submit it to any sites I want to have it looked over by people

He's trying to improve it first.
09-12-2007, 01:33 PM#15
Vexorian
Quote:
Originally Posted by Dil999
Vex: That piece of code is because i needed to point one unit variable to another, and I thought if i directly did that and then changed the original, the second variable would change also.
Reguarding timerattach: And how do I fix this? I'm still learning the complexities of vJass.
here is an idea, you take your code, find the places in which you are calling that function, replace the calls with a single reference to the unit and see how your system does exactly the same.

--
timers are fun, we've been doing attaching to them for years and it turns out attaching info to timers is not only risky but totally unnecessary.

Specially if you are using the timer for a multi instance 0.04 seconds timer:

Collapse JASS:
globals
     timer myGlobalTimer
     mycustomtype array V
     integer N=0
endglobals

function expire takes nothing returns nothing
  local integer i=0
      if(N==0) then
            call PauseTimer(myGlobalTimer)
            return
      endif
      loop
             exitwhen(i>=N)
             if(V[i].process() ) then // returning true means it should stay...
                    set i=i+1
             else
                   call V[i].destroy()
                   set V[i]=V[N-1]
                   set N=N-1
             endif
      endloop
endfunction

function addtotimer takes mycustomtype t returns nothing
      if(N==0) then
           set V[0]=t
           set N=1
           call TimerStart(myGlobalTimer, function expire)
      else
           set V[N]=t
           set N=N+1
      endif
endfunction




function init takes nothing returns nothing
    set myGlobalTimer=CreateTimer()

endfunction