Spell Static Link

Reaction score
91
Static Link

GUI/JASS/vJASS? vJASS
MUI? Yes
Lagless? Yes
Requires?
Jass NewGen Pack
TimerUtils (any flavour)
TimedEffects
ChanceToProc
Warcraft 1.23b+
Actually, if you want this for 1.23, just use the TimerUtils 1.23 version since it depends on that.

Description:
Links the Hero and a targeted unit. Using the element of lightning, the Hero will create a storm that will periodically blast the foe with static electricity, slowing it briefly and dealing minor damage. If the linked units go too far away from each other or one of them dies the chain breaks.
Level 1 - 30 damage per hit, lasts 5 seconds.
Level 2 - 40 damage per hit, lasts 6 seconds.
Level 3 - 50 damage per hit, lasts 7 seconds.
Level 4 - 60 damage per hit, lasts 8 seconds.

Code:
JASS:

scope StaticLink initializer Init // uses TimerUtils, TimedEffects, ChanceToProc

//==================================================================================
// C O N F I G U R A T I O N     M E N U
// ==================================================================================

globals
// Raw code of the ability Static Link
    private constant integer AID_RAW = 'A000'
// Raw code of the ability Static Link Slow (the one that slows enemies).
    private constant integer AID_SLOW = 'A001'
// Order string for casting the ability Static Link Slow (AID_SLOW).
    private constant string OID_SLOW = "slow"
// Raw code of a generic dummy unit. 
    private constant integer UID_DUMMY = 'u001'
// Raw code of the unit attached to the caster (currently the unit with Unholy Frenzy model).
    private constant integer UID_RAW = 'e000'
// Raw code of the unit attached to the target (currently the unit with the lightning ball model).
    private constant integer UID_TARG_RAW = 'e001'

// Special effect that is going to randomly be created on the target when it gets hit by a lightning bolt
    private constant string SFX_ONE = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
// Attachment point of the above effect (SFX_ONE).
    private constant string AP_ONE = "origin"
// Duration of the special effect. This is used because some effects die instantly without showing
// if I use DestroyEffect(). If your effect has a death animation then you can set this to 0. 
    private constant real EFFECT_ONE_DURATION = 1.15
// Special effect that is going to be created on the target when it gets hit by a lightning bolt.
// Unlike SFX_ONE, this effect is created ALWAYS when the lightning hits, not randomly.
    private constant string SFX_TWO = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
// Attachment point of the above effect (SFX_TWO).
    private constant string AP_TWO = "origin"
// Duration of the special effect. This is used because some effects die instantly without showing
// if I use DestroyEffect(). If your effect has a death animation then you can set this to 0. 
    private constant real EFFECT_TWO_DURATION = 0.65
    
// Duration of the secondary lightning (the one that is periodically created to hit the target).
    private constant real SECONDARY_LIGHTNING_DURATION = 0.45

// Sound string which is used for creating a sound from label (CreateSoundFromLabel).
    private constant string SOUND_FROM_LABEL = "LightningBolt"
    
// Lightning type of the first ("permanent") lightning between the caster and target.
    private constant string LIGHTNING_ONE = "CLSB"
// Lightning type of the secondary lightning that periodically hits the target.
    private constant string LIGHTNING_TWO = "FORK"
    
// Attack type of the damaging.
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
    private constant weapontype WEAPON_TYPE = null
endglobals

globals
// Period for moving the lightnings, units and for damaging. Keep it in range 0.02 - 0.05!
    private constant real PERIOD = 0.03125
    
// Values used for ChanceToProc struct.
// Percentage chance to create a secondary lightning.
// Note that the chance is calculated each PERIOD tick!
    private constant real CHANCE_TO_PROC = 0.03 // currently 3%
// Weight of the chance (see ChanceToProc's documentation if you don't understand).
    private constant real WEIGHT = 0.21
endglobals

// Duration of the link.
private function DURATION takes unit cast, integer lvl returns real
    return 5. + (lvl * 1)
endfunction

// Maximum distance between the two linked units. Passing it will break the link
private function MAX_DISTANCE takes unit cast, integer lvl returns real
    return 600.
endfunction

// Damage dealt every time a secondary lightning strikes the target.
private function DAMAGE takes unit cast, integer lvl returns real
    return 20. + (lvl * 10)
endfunction

//==================================================================================
// E N D     O F     C O N F I G U R A T I O N     M E N U
//==================================================================================

globals
    private location loc = Location(0, 0)
    private unit dum = null
    private sound zap = null
endglobals

private function GetUnitZ takes unit u returns real
    call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
    return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction

private struct Lightning
    unit cast
    unit targ
    lightning light
    timer tim
    integer ticks
    
    method onDestroy takes nothing returns nothing
        call DestroyLightning(this.light)
        call ReleaseTimer(this.tim)
    endmethod
    
    private static method Callback takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        call MoveLightningEx(this.light, true, GetUnitX(this.cast), GetUnitY(this.cast), GetUnitZ(this.cast), GetUnitX(this.targ), GetUnitY(this.targ), GetUnitZ(this.targ))
        set this.ticks = this.ticks - 1 
        if this.ticks <= 0 then
            call this.destroy()
        endif
    endmethod
    
    static method create takes unit cast, unit targ returns thistype
        local thistype this = thistype.allocate()
        set this.cast = cast
        set this.targ = targ
        set this.light = AddLightningEx(LIGHTNING_TWO, true, GetUnitX(this.cast), GetUnitY(this.cast), GetUnitZ(this.cast), GetUnitX(this.targ), GetUnitY(this.targ), GetUnitZ(this.targ))
        set this.ticks = R2I(SECONDARY_LIGHTNING_DURATION / PERIOD)
        set this.tim = NewTimer()
        call SetTimerData(this.tim, this)
        call TimerStart(this.tim, PERIOD, true, function thistype.Callback)
        return this
    endmethod
endstruct

private struct Data
    unit cast
    unit targ
    unit sfx
    unit targSfx
    lightning light
    integer ticks
    integer lvl
    timer tim
    Chance ch
    
    method onDestroy takes nothing returns nothing
        call ReleaseTimer(this.tim)
        call KillUnit(this.sfx)
        call KillUnit(this.targSfx)
        call DestroyLightning(this.light)
        call this.ch.destroy()
    endmethod
    
    private static method Callback takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        local real x = GetUnitX(this.cast)
        local real y = GetUnitY(this.cast)
        local real tx = GetUnitX(this.targ)
        local real ty = GetUnitY(this.targ)
        local real dx = tx - x
        local real dy = ty - y
        local real distance = SquareRoot(dx * dx + dy * dy)
        local integer id  
        call SetUnitX(this.sfx, x)
        call SetUnitY(this.sfx, y)
        call SetUnitX(this.targSfx, tx)
        call SetUnitY(this.targSfx, ty)
        call MoveLightningEx(this.light, true, x, y, GetUnitZ(this.cast), tx, ty, GetUnitZ(this.targSfx))
        if this.ch.isProc() then
            call Lightning.create(this.sfx, this.targSfx)
            set zap = CreateSoundFromLabel(SOUND_FROM_LABEL, false, true, true, 0, 0)
            call AttachSoundToUnit(zap, this.targ)
            call SetSoundVolume(zap, 100)
            call StartSound(zap)
            call KillSoundWhenDone(zap)
            
            if GetRandomInt(0, 1) == 0 then // Some randomization, in case it becomes too much...
                call StartTimedEffect(AddSpecialEffectTarget(SFX_ONE, this.targ, AP_ONE), EFFECT_ONE_DURATION)
            endif
            call StartTimedEffect(AddSpecialEffectTarget(SFX_TWO, this.targ, AP_TWO), EFFECT_TWO_DURATION)
            call SetUnitX(dum, tx)
            call SetUnitY(dum, ty)
            call SetUnitOwner(dum, GetOwningPlayer(this.cast), true)
            call SetUnitAbilityLevel(dum, AID_SLOW, this.lvl)
            call IssueTargetOrder(dum, OID_SLOW, this.targ)
            call UnitDamageTarget(this.cast, this.targ, DAMAGE(this.cast, this.lvl), true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
        endif
        set this.ticks = this.ticks - 1
        if distance >= MAX_DISTANCE(this.cast, this.lvl) or this.ticks <= 0 or IsUnitType(this.targ, UNIT_TYPE_DEAD) == true or IsUnitType(this.cast, UNIT_TYPE_DEAD) == true or IsUnitInvisible(this.targ, GetOwningPlayer(this.cast)) then
            call this.destroy()
        endif
    endmethod
    
    static method create takes unit cast, unit targ returns thistype
        local thistype this = thistype.allocate()
        local real x = GetUnitX(cast)
        local real y = GetUnitY(cast)
        local real tx = GetUnitX(targ)
        local real ty = GetUnitY(targ)
        set this.cast = cast
        set this.targ = targ
        set this.lvl = GetUnitAbilityLevel(this.cast, AID_RAW)
        set this.ch = Chance.create(CHANCE_TO_PROC, WEIGHT)
        set this.sfx = CreateUnit(GetOwningPlayer(this.cast), UID_RAW, x, y, GetUnitFacing(this.cast))
        set this.targSfx = CreateUnit(GetOwningPlayer(this.cast), UID_TARG_RAW, tx, ty, GetUnitFacing(this.targ))
        set this.light = AddLightningEx(LIGHTNING_ONE, true, x, y, GetUnitZ(this.cast), tx, ty, GetUnitZ(this.targSfx))
        set this.ticks = R2I(DURATION(this.cast, this.lvl) / PERIOD)
        set this.tim = NewTimer()
        call SetTimerData(this.tim, this)
        call TimerStart(this.tim, PERIOD, true, function thistype.Callback)
        return this
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == AID_RAW
endfunction

private function Actions takes nothing returns nothing
    call Data.create(GetTriggerUnit(), GetSpellTargetUnit())
endfunction

private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trig, Condition(function Conditions))
    call TriggerAddAction(trig, function Actions)
    
    set dum = CreateUnit(Player(13), UID_DUMMY, 0., 0., 0.)
    call UnitAddAbility(dum, 'Aloc')
    call UnitAddAbility(dum, AID_SLOW)
endfunction

endscope


http://i39.tinypic.com/1043ols.jpg

ReadMe included in the test map. The reason I used ChanceToProc is that I get really annoyed when multiple lightnings stack over each other when using the generic "is RandomNumBetween1and100 < X ?" and this is the way to limit that, besides I haven't really found any spell/script that uses it so far.
Special thanks to Tinki3 for the testmap.
 

Attachments

  • Static Link v1[1].6.w3x
    61.8 KB · Views: 245

RaiJin

New Member
Reaction score
40
JASS:
private function Actions takes nothing returns nothing
    local unit cast = GetTriggerUnit()
    local unit targ = GetSpellTargetUnit()
    local Data d = Data.create(cast, targ)
    call TimerStart(d.tim, PERIOD, true, function Callback)
    set cast = null
    set targ = null
endfunction


can be this

JASS:
private function Actions takes nothing returns nothing
    local Data d = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction


i dont see the point of making 2 variables then discarding them
 
Reaction score
91
> Picture please
Testmap.

> i dont see the point of making 2 variables then discarding them
I left them if someone wants to edit the spell and add additional stuff. Less stuff to type ...
 
Reaction score
91
"Picture" represents nothing of what the spell does and does not show the effect of a spell no matter what it is... Still, uploaded one. Looks better in-game, I think.

EDIT: I just noticed I forgot to add the special effect creation... Updated.
 
Reaction score
91
Update, uses a global for the dummy unit and the sound and now uses TimedEffects for the effect.

> (Totally needs particle emitters.)
What?
 

Romek

Super Moderator
Reaction score
963
> I left them if someone wants to edit the spell and add additional stuff. Less stuff to type ...
It's not in a configurable function, so it's "not to be touched unless you know what you're doing". And it's unlikely to be edited.
Whoever would edit that would put in their own locals. They don't need some useless ones there... "Just in case."

JASS:
    private static method Callback takes nothing returns nothing
        local timer tim = GetExpiredTimer()
        local Effect e = Effect(GetTimerData(tim))
        call e.destroy()
        set tim = null
    endmethod

Could be:
JASS:
    private static method Callback takes nothing returns nothing
        call Effect(GetTimerData(GetExpiredTimer())).destroy()
    endmethod


JASS:
        local timer tim = GetExpiredTimer()
        local Lightning l = Lightning(GetTimerData(tim))

// ...
        set tim = null

Could be:
JASS:
local Lightning l = Lightning(GetTimerData(GetExpiredTimer()))


Typecasting isn't needed at all with SetTimerData and GetTimerData.

Really, a new variable doesn't have to be made for everything you ever use. :rolleyes:
You do that a lot. It's pointless.
 
Reaction score
91
I made an update while you were writing this stuff so the Effect struct is no longer in there. :)

> You do that a lot. It's pointless.
I guess it is but I like it. Okay, gonna clean it a little...

EDIT: Updated, and I got a better screenshot.
 

Romek

Super Moderator
Reaction score
963
JASS:
private function Actions takes nothing returns nothing
    local Data d = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction

Could be:
JASS:
private function Actions takes nothing returns nothing
    call TimerStart(Data.create(GetTriggerUnit(), GetSpellTargetUnit()).tim, PERIOD, true, function Callback)
endfunction

Or how about making create take nothing, and using all the event responses within the create method. Then, change the TriggerAddAction line to:
JASS:
call TriggerAddAction(t, function Data.create)


In Callback, why are you even making 'local Lightning l'?
How about a simple "call Lightning.create(..)"? :confused:
I don't see you using that variable other than setting it once.

The configurable functions would be better off taking levels.
If you want to stick to the unit argument too, then do so.
Functions can always take 2 arguments.
Then make 'level' a struct member, and call GetUnitAbilityLevel once, when the struct is created.
 

_whelp

New Member
Reaction score
54
This may not matter but
JASS:
private function DAMAGE takes unit cast, integer lvl returns real
    return 20. + (lvl * 10)
endfunction


The parentheses aren't needed, because

Parentheses
Exponents
Multiplication
Division
Addition
Subtraction
.

Multiplication always goes before addition...
 

Romek

Super Moderator
Reaction score
963
Do parenthesis offend you? :rolleyes:
 

_whelp

New Member
Reaction score
54
Well, no, anyways my post can be deleted and people can give me negative rep if they want...


Anyways this spell is uber cool...
 

_whelp

New Member
Reaction score
54
At school they said it was P, unless this is a different variant of those from other countries...
 

Romek

Super Moderator
Reaction score
963
My school has a different one too.
Not that it matters.

And you wouldn't get -rep for something like that. :p

As for the spell, I'll wait for some more replies. :)
 
Reaction score
91
> As for the spell, I'll wait for some more replies.
Okay. ^^

> Anyways this spell is uber cool...
Ty, I thought it sucked a lot as a concept but I guess some people like it as well.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      • Ghan
        Administrator - Servers are fun

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top