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: 248

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
964
> 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
964
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
964
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
964
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.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top