Spellpack Whip Spells

DrinkSlurm

Eat Bachelor Chow!
Reaction score
50
last updated: January 18, 2008
version 1.00: original Cool Whip spell
version 2.00: original submission to the Spells forum
version 2.50: coding optimizations, additional effects for Fire Whip, added Shock Whip and Death Whip, cancel-ability added to all spells
version 2.52: more coding optimizations, preloading added


This was my Cool Whip spell originally developed for the Official Christmas Spell Contest. With help from Pyrogasm, I've been able to clean up and optimize the code quite a bit. There is also a full configuration section included. Also, I created several other similar whip spells with various shapes and effects. In addition, I also came up with an Aim Help system.

Cool Whip
Spell type: instant-cast, pseudo-channeling
Description: Summons a magical tentacle made of ice particles that whips back and forth in an enlarging sinusoid shape with an oscillating pattern. The Hero cannot move nor attack nor cast any spells while the whip is active, but the Hero CAN turn and rotate in order to aim the whip. Enemy units that are hit by the whip take damage and are briefly slowed. [100 mana cost; 15 second cooldown]
Level 1 - creates a small whip that extends up to a distance of 630 away, deals up to 20 damage per second, slows by 50%, and lasts for 6 seconds.
Level 2 - creates a medium whip that extends up to a distance of 810 away, deals up to 24 damage per second, slows by 60%, and lasts for 8 seconds.
Level 3 - creates a large whip that extends up to a distance of 990 away, deals up to 28 damage per second, slows by 70%, and lasts for 10 seconds.

Coding: JASS; requires vJASS
Leaks: Leakless
Multi Instanceability: MUI
Lag-susceptibility: moderate
Import Difficulty: easy (instructions included)

Trigger:
JASS:
//===========================================================================
//=========================== Cool Whip v2.52 ===============================
//============================ Instructions =================================
//===========================================================================
// MUI; requires vJASS
// Note: because the damage done is based on Permanent Immolation, damage will not
//   stack (i.e. a unit being hit by 2 whips at a time will NOT take double damage)
// Note: units and abilities have been preloaded to prevent 'first-cast-lag'
//===========================================================================
// 1. Go to the Object Editor, and copy + paste the following:
//    Units
//       * Dummy Caster
//       * Cool Whip Segment
//    Abilities
//       * Cool Whip: Add this ability to the Hero; make sure the buff is set to the
//         custom "Cool Whip (Active)" buff.
//       * Silence (for Cool Whip): make sure that the buff is set to the custom "Cool
//         Whip (Caster)" buff.
//       * Permanent Immolation (for Cool Whip): make sure that the buff is set
//         to the custom "Cool Whip (Target)" buff.
//       * Slow Aura (for Cool Whip): this is what provides the slow on targets.
//    Buffs
//       * Cool Whip (Active): This is the buff that comes from the "Cool Whip" ability
//         It appears on the Hero when the spell is activated and disappears when the
//         spell is cancelled.
//       * Cool Whip (Caster): This is the buff that comes from the "Silence (for Cool
//         Whip)" ability.
//       * Cool Whip (Target): This buff applies the snowflakes-looking effects on
//         targets that are hit by the whip.
// 2. Copy the following triggers:
//       * this trigger: "CoolWhip"
//       * AimHelp: this is an OPTIONAL trigger used to help with the aiming of this
//         spell.
// 3. Other options:
//       * Hero: Note that you may want to change "Stats - Can Flee" to "False" to
//         make this skill a little easier to use. Otherwise, while the Hero is
//         silenced, it will tend to run away from enemies since it can't attack. 
// 4. To adjust the balance of the skill, you can change some of the following values:
//    Abilities
//       * Cool Whip: Cooldown, Mana Cost
//       * Permanent Immolation (for Cool Whip): Damage, damage interval, AoE
//       * Slow Aura (for Cool Whip): Attack Speed Factor, Movement Speed Factor, AoE
// 5. Make sure that the rawcodes specified below reference the proper units,
//    abilities, or buffs.
scope CoolWhip
globals
    private constant integer COOLWHIP = 'A004' //Ability ID of "Cool Whip"
    private constant integer DUMMY    = 'h000' //Unit    ID of "Dummy Caster"
    private constant integer SILENCE  = 'A001' //Ability ID of "Silence (for Cool Whip)"
            constant integer CWBUFF   = 'B006' //Buff    ID of "Cool Whip (Caster)"
    private constant integer WHIPSEG  = 'h001' //Unit    ID of "Cool Whip Segment"
    private constant integer PERMIMMO = 'A002' //Ability ID of "Permanent Immolation (for Cool Whip)"
    private constant integer SLOW     = 'A003' //Ability ID of "Slow Aura (for Cool Whip)" 
    private constant integer CWBUFF2  = 'B004' //Buff    ID of "Cool Whip (Active)"
// 6. More configuration options:
    private constant string  ANIM      = "spell" //animation name during spell (adjust according to your chosen hero's model); default "spell"
    private constant real    SPACING   = 30.00   //spacing of the whip segments (adjusts 'length' of whip); default 30.00
    private constant real    INTERVAL  =  0.03   //timer interval; default 0.03
    private constant real    ANIMSCALE =  1.00   //animation scale of whip segments; default 1.00
    private constant real    ANIMSPEED =  0.20   //animation speed of whip segments; default 0.20
    private constant integer SPW       = 12      //segments per wavelength (make this a multiple of 4 for best results); default 12
    private constant real    AMP       =  1.25   //amplitude multiplier (larger value makes the whip 'wider'; default 1.25
    private constant integer WCFACTOR  =  6      //whip count factor; this and WCBASE determine total amount of whip segments; default 6
    private constant integer WCBASE    = 15      //whip count base; this and WCFACTOR determine total amount of whip segments; default 15
    private constant real    DURFACTOR =  2.00   //duration factor; this and DURBASE determine total duration of the spell; default 2.00
    private constant real    DURBASE   =  4.00   //duration base; this and DURFACTOR determine total duration of the spell; default 4.00
endglobals
//========================== End of Instructions ============================
//===========================================================================
private struct CW
    unit hero
    real herox
    real heroy
    integer c
    group g = CreateGroup()
endstruct
globals
    private CW array ALLDATA
    private timer ALLTIMER = CreateTimer()
    private integer TOTAL = 0
    private integer INSTANCE
endglobals
//===========================================================================
private function Cond takes nothing returns boolean
    return GetSpellAbilityId() == COOLWHIP
endfunction

private function GroupKillWhip takes nothing returns nothing
    call KillUnit(GetEnumUnit())
endfunction

private function GroupMoveWhip takes nothing returns nothing
    local CW data = ALLDATA[INSTANCE]
    local unit u = GetEnumUnit()
    local integer cv = GetUnitUserData(u)
    local integer osc = IAbsBJ(ModuloInteger((data.c + 10), 40) - 20) - 10
    local real face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
    local real dx = cv * SPACING
    local real dy = AMP * Sin(cv * 2 * bj_PI / SPW) * cv * osc
    local real dist = SquareRoot(dx * dx + dy * dy)
    local real ang_rad = Atan2(dy, dx) + face_rad
    local real x = data.herox + dist * Cos(ang_rad)
    local real y = data.heroy + dist * Sin(ang_rad)
    call SetUnitPosition(u, x, y)
    set u = null
endfunction

private function TimerActions takes nothing returns nothing
    local CW data
    local unit u
    local integer i = 1
    loop
        exitwhen i > TOTAL
        set data = ALLDATA<i>
        if GetUnitAbilityLevel(data.hero, CWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
        call SetUnitAnimation(data.hero, ANIM)
        set INSTANCE = i
        call ForGroup(data.g, function GroupMoveWhip)
        set data.c = data.c - 1
        if GetWidgetLife(data.hero) &lt; 0.406 or data.c &lt;= 0 or GetUnitAbilityLevel(data.hero, CWBUFF2) == 0 then
            call IssueImmediateOrder(data.hero, &quot;manashieldoff&quot;)
            if GetWidgetLife(data.hero) &lt; 0.406 then
                call SetUnitAnimation(data.hero, &quot;death&quot;)
            else
                call SetUnitAnimation(data.hero, &quot;stand&quot;)
                call UnitRemoveAbility(data.hero, CWBUFF)
                call IssueImmediateOrder(data.hero, &quot;stop&quot;)
            endif
            call ForGroup(data.g, function GroupKillWhip)
            call data.destroy()
            set ALLDATA<i> = ALLDATA[TOTAL]
            set TOTAL = TOTAL - 1
            set i = i - 1
            if TOTAL == 0 then
                call PauseTimer(ALLTIMER)
            endif
        endif
        set i = i + 1
    endloop
    set u = null
endfunction

private function Cast takes nothing returns nothing
    local CW data = CW.create()
    local real face_rad = GetUnitFacing(GetTriggerUnit()) * bj_PI/180.0
    local real facecos = Cos(face_rad)
    local real facesin = Sin(face_rad)
    local real x = GetUnitX(GetTriggerUnit())
    local real y = GetUnitY(GetTriggerUnit())
    local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), COOLWHIP)
    local integer whipcount = lvl * WCFACTOR + WCBASE
    local real dur = lvl * DURFACTOR + DURBASE
    local unit u = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY, x, y, 0)
    local integer i = 1
    set data.hero = GetTriggerUnit()
    set data.herox = x
    set data.heroy = y
    set data.c = R2I(dur / INTERVAL)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, SILENCE)
    call IssuePointOrder(u, &quot;silence&quot;, x, y)
    call SetUnitAnimation(data.hero, ANIM)
    loop
        exitwhen i &gt; whipcount
        set x = data.herox + i * SPACING * facecos
        set y = data.heroy + i * SPACING * facesin
        set u = CreateUnit(GetOwningPlayer(data.hero), WHIPSEG, x, y, 0) 
        call UnitApplyTimedLife(u, &#039;BTLF&#039;, dur)
        call SetUnitScale(u, ANIMSCALE, ANIMSCALE, ANIMSCALE)
        call SetUnitTimeScale(u, ANIMSPEED)
        call UnitAddAbility(u, PERMIMMO)
        call SetUnitAbilityLevel(u, PERMIMMO, lvl)
        call UnitAddAbility(u, SLOW)
        call SetUnitAbilityLevel(u, SLOW, lvl)
        call SetUnitUserData(u, i)
        call GroupAddUnit(data.g, u)
        set i = i + 1
    endloop
    set TOTAL = TOTAL + 1
    set ALLDATA[TOTAL] = data
    if TOTAL == 1 then
        call TimerStart(ALLTIMER, INTERVAL, true, function TimerActions)
    endif
    set u = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
    local unit u = CreateUnit(Player(0), WHIPSEG, 0, 0, 0)
    call UnitAddAbility(u, SILENCE)
    call UnitAddAbility(u, PERMIMMO)
    call UnitAddAbility(u, SLOW)
    call RemoveUnit(u)
    set gg_trg_CoolWhip = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_CoolWhip, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_CoolWhip, Condition(function Cond))
    call TriggerAddAction(gg_trg_CoolWhip, function Cast)
    set u = null
endfunction
endscope</i></i>



Fire Whip
Spell type: instant-cast, pseudo-channeling
Description: Summons a tentacle made of fire that whips back and forth in an enlarging travelling sinusoid shape. The Hero cannot move nor attack while the whip is active, but the Hero CAN rotate in order to aim the whip. The whip deals direct damage to enemy targets and lingering flames also deal residual damage and reduce armor briefly. [100 mana cost; 15 second cooldown]
Level 1 - a small whip that extends up to 525 away, deals up to 40 damage per second, and lasts for 6 seconds. Residual flames deal 5 damage per second and reduce armor by 2 for 5 seconds.
Level 2 - a medium whip that extends up 675 away, deals up to 48 damage per second, and lasts for 8 seconds. Residual flames deal 6 damage per second and reduce armor by 3 for 5 seconds.
Level 3 - a large whip that extends up to a distance of 825 away, deals up to 56 damage per second, and lasts for 10 seconds. Residual flames deal 7 damage per second and reduce armor by 3 for 5 seconds.

Coding: JASS; requires vJASS
Leaks: Leakless
Multi Instanceability: MUI
Lag-susceptibility: moderate
Import Difficulty: easy (instructions included)

Trigger:
JASS:
//===========================================================================
//=========================== Fire Whip v2.52 ===============================
//============================ Instructions =================================
//===========================================================================
// MUI; requires vJASS
// Note: because the damage done is based on Permanent Immolation, damage will not
//   stack (i.e. a unit being hit by 2 whips at a time will NOT take double damage)
// Note: units and abilities have been preloaded to prevent &#039;first-cast-lag&#039;
//===========================================================================
// 1. Go to the Object Editor, and copy + paste the following:
//    Units
//       * Dummy Caster
//       * Fire Whip Segment
//    Abilities
//       * Fire Whip: Add this ability to the Hero; make sure the buff is set to the
//         custom &quot;Fire Whip (Active)&quot; buff.
//       * Silence (for Fire Whip): make sure that the buff is set to the custom &quot;Fire
//         Whip (Caster)&quot; buff.
//       * Permanent Immolation (for Fire Whip): make sure that the buff is set
//         to the custom &quot;Fire Whip (Target)&quot; buff.
//       * Acid Bomb (for Fire Whip): make sure that the buff is set to the custom
//         &quot;Fire Whip (Target)&quot; buff.
//    Buffs
//       * Fire Whip (Active): This is the buff that comes from the &quot;Fire Whip&quot; ability.
//         It appears on the Hero when the spell is activated and disappears when the
//         spell is cancelled.
//       * Fire Whip (Caster): This is the buff that comes from the &quot;Silence (for Fire
//         Whip)&quot; ability.
//       * Fire Whip (Target): This buff applies the flames-looking effects on
//         targets that are hit by the whip.
// 2. Copy the following triggers:
//       * this trigger: &quot;FireWhip&quot;
//       * AimHelp: this is an OPTIONAL trigger used to help with the aiming of this
//         spell.
// 3. Other options:
//       * Hero: Note that you may want to change &quot;Stats - Can Flee&quot; to &quot;False&quot; to
//         make this skill a little easier to use. Otherwise, while the Hero is
//         silenced, it will tend to run away from enemies since it can&#039;t attack. 
// 4. To adjust the balance of the skill, you can change some of the following values:
//    Abilities
//       * Fire Whip: Cooldown, Mana Cost
//       * Permanent Immolation (for Fire Whip): Damage, damage interval, AoE
//       * Acid Bomb (for Fire Whip): primary damage, damage interval, duration, armor penalty
// 5. Make sure that the rawcodes specified below reference the proper units,
//    abilities, or buffs.
scope FireWhip
globals
    private constant integer FIREWHIP = &#039;A008&#039; //Ability ID of &quot;Fire Whip&quot;
    private constant integer DUMMY    = &#039;h000&#039; //Unit    ID of &quot;Dummy Caster&quot;
    private constant integer SILENCE  = &#039;A005&#039; //Ability ID of &quot;Silence (for Fire Whip)&quot;
            constant integer FWBUFF   = &#039;B002&#039; //Buff    ID of &quot;Fire Whip (Caster)&quot;
    private constant integer WHIPSEG  = &#039;h002&#039; //Unit    ID of &quot;Fire Whip Segment&quot;
    private constant integer PERMIMMO = &#039;A006&#039; //Ability ID of &quot;Permanent Immolation (for Fire Whip)&quot;
    private constant integer FWBUFF2  = &#039;B005&#039; //Buff    ID of &quot;Fire Whip (Active)&quot;
    private constant integer ACIDBOMB = &#039;A00B&#039; //Ability ID of &quot;Acid Bomb (for Fire Whip)&quot;
// 6. More configuration options:
    private constant string  ANIM      = &quot;spell&quot; //animation name during spell (adjust according to your chosen hero&#039;s model); default &quot;spell&quot;
    private constant real    SPACING   = 25.00   //spacing of the whip segments (adjusts &#039;length&#039; of whip); default 25.00
    private constant real    INTERVAL  =  0.04   //timer interval; default 0.04
    private constant real    ANIMSCALE =  1.00   //animation scale of whip segments; default 1.00
    private constant real    ANIMSPEED =  0.20   //animation speed of whip segments; default 0.20
    private constant integer SPW       = 12      //segments per wavelength (make this a multiple of 4 for best results); default 12
    private constant real    AMP       = 15.00   //amplitude multiplier (larger value makes the whip &#039;wider&#039;; default 15.00
    private constant integer WCFACTOR  =  6      //whip count factor; this and WCBASE determine total amount of whip segments; default 6
    private constant integer WCBASE    = 15      //whip count base; this and WCFACTOR determine total amount of whip segments; default 15
    private constant real    DURFACTOR =  2.00   //duration factor; this and DURBASE determine total duration of the spell; default 2.00
    private constant real    DURBASE   =  4.00   //duration base; this and DURFACTOR determine total duration of the spell; default 4.00
    private constant real    OSFACTOR  =  6.00   //offset multiplier (adjusts speed of travelling wave motion of whip); default 6.00
    private constant real    CINTERVAL =  0.50   //check interval; how often (in seconds) a check for residual DoT occurs; default 0.50
endglobals
//========================== End of Instructions ============================
//===========================================================================
private struct FW
    unit hero
    real herox
    real heroy
    integer lvl
    integer c
    integer cpi
    group g = CreateGroup()
endstruct
globals
    private FW array ALLDATA
    private timer ALLTIMER = CreateTimer()
    private integer TOTAL = 0
    private integer INSTANCE
    private boolexpr FILTER
    private group TEMPGROUP = CreateGroup()
    private unit TEMPUNIT
endglobals
//===========================================================================
private function Cond takes nothing returns boolean
    return GetSpellAbilityId() == FIREWHIP
endfunction

private function GroupFilter takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) &gt; 0.406 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TEMPUNIT)) == true
endfunction

private function GroupKillWhip takes nothing returns nothing
    call KillUnit(GetEnumUnit())
endfunction

private function GroupAcidbomb takes nothing returns nothing
    local FW data = ALLDATA[INSTANCE]
    local unit e = GetEnumUnit()
    local real x = GetUnitX(e)
    local real y = GetUnitY(e)
    local unit u = CreateUnit(GetOwningPlayer(TEMPUNIT), DUMMY, x, y, 0)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, ACIDBOMB)
    call SetUnitAbilityLevel(u, ACIDBOMB, data.lvl)
    call IssueTargetOrder(u, &quot;acidbomb&quot;, e)
    set u = null
    set e = null
endfunction

private function GroupMoveWhip takes nothing returns nothing
    local FW data = ALLDATA[INSTANCE]
    local group g = CreateGroup()
    local unit u = GetEnumUnit()
    local integer cv = GetUnitUserData(u)
    local real offset = data.c * OSFACTOR
    local real face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
    local real dx = cv * SPACING
    local real dy = AMP * Sin(cv * 2 * bj_PI / SPW - offset) * cv
    local real dist = SquareRoot(dx * dx + dy * dy)
    local real ang_rad = Atan2(dy, dx) + face_rad
    local real x = data.herox + dist * Cos(ang_rad)
    local real y = data.heroy + dist * Sin(ang_rad)
    call SetUnitPosition(u, x, y)
    if ModuloInteger(data.c, data.cpi) == 0 then
        set TEMPUNIT = u
        call GroupEnumUnitsInRange(g, x, y, 75, FILTER)
        call GroupAddGroup(g, TEMPGROUP)
    endif
    call DestroyGroup(g)
    set g = null
    set u = null
endfunction

private function TimerActions takes nothing returns nothing
    local FW data
    local unit u
    local integer i = 1
    loop
        exitwhen i &gt; TOTAL
        set data = ALLDATA<i>
        if GetUnitAbilityLevel(data.hero, FWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
        call SetUnitAnimation(data.hero, ANIM)
        set INSTANCE = i
        call ForGroup(data.g, function GroupMoveWhip)
        call ForGroup(TEMPGROUP, function GroupAcidbomb)
        call GroupClear(TEMPGROUP)
        set data.c = data.c - 1
        if GetWidgetLife(data.hero) &lt; 0.406 or data.c &lt;= 0 or GetUnitAbilityLevel(data.hero, FWBUFF2) == 0 then
            call IssueImmediateOrder(data.hero, &quot;unimmolation&quot;)
            if GetWidgetLife(data.hero) &lt; 0.406 then
                call SetUnitAnimation(data.hero, &quot;death&quot;)
            else
                call SetUnitAnimation(data.hero, &quot;stand&quot;)
                call UnitRemoveAbility(data.hero, FWBUFF)
                call IssueImmediateOrder(data.hero, &quot;stop&quot;)
            endif
            call ForGroup(data.g, function GroupKillWhip)
            call data.destroy()
            set ALLDATA<i> = ALLDATA[TOTAL]
            set TOTAL = TOTAL - 1
            set i = i - 1
            if TOTAL == 0 then
                call PauseTimer(ALLTIMER)
            endif
        endif
        set i = i + 1
    endloop
    set u = null
endfunction

private function Cast takes nothing returns nothing
    local FW data = FW.create()
    local real face_rad = GetUnitFacing(GetTriggerUnit()) * bj_PI/180.0
    local real facecos = Cos(face_rad)
    local real facesin = Sin(face_rad)
    local real x = GetUnitX(GetTriggerUnit())
    local real y = GetUnitY(GetTriggerUnit())
    local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), FIREWHIP)
    local integer whipcount = lvl * WCFACTOR + WCBASE 
    local real dur = lvl * DURFACTOR + DURBASE
    local unit u = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY, x, y, 0)
    local integer i = 1
    set data.hero = GetTriggerUnit()
    set data.herox = x
    set data.heroy = y
    set data.lvl = lvl
    set data.c = R2I(dur / INTERVAL)
    set data.cpi = R2I(CINTERVAL / INTERVAL)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, SILENCE)
    call IssuePointOrder(u, &quot;silence&quot;, x, y)
    call SetUnitAnimation(data.hero, ANIM)
    loop
        exitwhen i &gt; whipcount
        set x = data.herox + i * SPACING * facecos
        set y = data.heroy + i * SPACING * facesin
        set u = CreateUnit(GetOwningPlayer(data.hero), WHIPSEG, x, y, 0) 
        call UnitApplyTimedLife(u, &#039;BTLF&#039;, dur)
        call SetUnitScale(u, ANIMSCALE, ANIMSCALE, ANIMSCALE)
        call SetUnitTimeScale(u, ANIMSPEED)
        call UnitAddAbility(u, PERMIMMO)
        call SetUnitAbilityLevel(u, PERMIMMO, lvl)
        call SetUnitUserData(u, i)
        call GroupAddUnit(data.g, u)
        set i = i + 1
    endloop
    set TOTAL = TOTAL + 1
    set ALLDATA[TOTAL] = data
    if TOTAL == 1 then
        call TimerStart(ALLTIMER, INTERVAL, true, function TimerActions)
    endif
    set u = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
    local unit u = CreateUnit(Player(0), WHIPSEG, 0, 0, 0)
    call UnitAddAbility(u, SILENCE)
    call UnitAddAbility(u, PERMIMMO)
    call UnitAddAbility(u, ACIDBOMB)
    call RemoveUnit(u)
    set gg_trg_FireWhip = CreateTrigger()
    set FILTER = Condition(function GroupFilter)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_FireWhip, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_FireWhip, Condition(function Cond))
    call TriggerAddAction(gg_trg_FireWhip, function Cast)
    set u = null
endfunction
endscope</i></i>


Screenshot:



Shock Whip
Spell type: instant-cast, pseudo-channeling
Description: Summons a magical tentacle made of electricity that whips back and forth from side to side. The Hero cannot move nor attack nor cast any spells while the whip is active, but the Hero CAN turn and rotate in order to aim the whip. Enemy units that are hit by the whip take damage and have a chance to be purged. [100 mana cost; 15 second cooldown]
Level 1 - creates a small whip that extends up to a distance of 630 away, deals up to 30 damage per second, and lasts for 6 seconds. 20% chance to purge targets.
Level 2 - creates a medium whip that extends up to a distance of 810 away, deals up to 36 damage per second, and lasts for 8 seconds. 30% chance to purge targets.
Level 3 - creates a large whip that extends up to a distance of 990 away, deals up to 42 damage per second, and lasts for 10 seconds. 40% chance to purge targets.

Coding: JASS; requires vJASS
Leaks: Leakless
Multi Instanceability: MUI
Lag-susceptibility: moderate
Import Difficulty: easy (instructions included)

Trigger:
JASS:
//===========================================================================
//========================== Shock Whip v2.52 ===============================
//============================ Instructions =================================
//===========================================================================
// MUI; requires vJASS
// Note: because the damage done is based on Permanent Immolation, damage will not
//   stack (i.e. a unit being hit by 2 whips at a time will NOT take double damage)
// Note: units and abilities have been preloaded to prevent &#039;first-cast-lag&#039;
//===========================================================================
// 1. Go to the Object Editor, and copy + paste the following:
//    Units
//       * Dummy Caster
//       * Shcck Whip Segment
//    Abilities
//       * Shock Whip: Add this ability to the Hero; make sure the buff is set to the
//         custom &quot;Shock Whip (Active)&quot; buff.
//       * Silence (for Shock Whip): make sure that the buff is set to the custom &quot;Shock
//         Whip (Caster)&quot; buff.
//       * Permanent Immolation (for Shock Whip): make sure that the buff is set
//         to the custom &quot;Shock Whip (Target)&quot; buff.
//       * Purge (for Shock Whip): this is the purge that has a chance to be cast on targets
//    Buffs
//       * Shock Whip (Active): This is the buff that comes from the &quot;Shock Whip&quot; ability
//         It appears on the Hero when the spell is activated and disappears when the
//         spell is cancelled.
//       * Shock Whip (Caster): This is the buff that comes from the &quot;Silence (for Shock
//         Whip)&quot; ability.
//       * Shock Whip (Target): This buff applies the purge effects on targets that
//         are hit by the whip.
// 2. Copy the following triggers:
//       * this trigger: &quot;ShockWhip&quot;
//       * AimHelp: this is an OPTIONAL trigger used to help with the aiming of this
//         spell.
// 3. Other options:
//       * Hero: Note that you may want to change &quot;Stats - Can Flee&quot; to &quot;False&quot; to
//         make this skill a little easier to use. Otherwise, while the Hero is
//         silenced, it will tend to run away from enemies since it can&#039;t attack. 
// 4. To adjust the balance of the skill, you can change some of the following values:
//    Abilities
//       * Shock Whip: Cooldown, Mana Cost
//       * Permanent Immolation (for Shock Whip): Damage, damage interval, AoE
//       * Purge (for Shock Whip): durations, damage to summons
// 5. Make sure that the rawcodes specified below reference the proper units,
//    abilities, or buffs.
scope ShockWhip
globals
    private constant integer SHOCKWHIP = &#039;A000&#039; //Ability ID of &quot;Shock Whip&quot;
    private constant integer DUMMY     = &#039;h000&#039; //Unit    ID of &quot;Dummy Caster&quot;
    private constant integer SILENCE   = &#039;A007&#039; //Ability ID of &quot;Silence (for Shock Whip)&quot;
            constant integer SWBUFF    = &#039;B007&#039; //Buff    ID of &quot;Shock Whip (Caster)&quot;
    private constant integer WHIPSEG   = &#039;h003&#039; //Unit    ID of &quot;Shock Whip Segment&quot;
    private constant integer PERMIMMO  = &#039;A009&#039; //Ability ID of &quot;Permanent Immolation (for Shock Whip)&quot;
    private constant integer SWBUFF2   = &#039;B000&#039; //Buff    ID of &quot;Shock Whip (Active)&quot;
    private constant integer PURGE     = &#039;A00A&#039; //Ability ID of &quot;Purge (for Shock Whip)&quot;
// 6. More configuration options:
    private constant string  ANIM      = &quot;spell&quot; //animation name during spell (adjust according to your chosen hero&#039;s model); default &quot;spell&quot;
    private constant real    SPACING   = 30.00   //spacing of the whip segments (adjusts &#039;length&#039; of whip); default 30.00
    private constant real    INTERVAL  =  0.04   //timer interval; default 0.04
    private constant real    ANIMSCALE =  0.50   //animation scale of whip segments; default 1.00
    private constant real    ANIMSPEED =  0.50   //animation speed of whip segments; default 1.00
    private constant integer WCFACTOR  =  6      //whip count factor; this and WCBASE determine total amount of whip segments; default 6
    private constant integer WCBASE    = 15      //whip count base; this and WCFACTOR determine total amount of whip segments; default 15
    private constant real    DURFACTOR =  2.00   //duration factor; this and DURBASE determine total duration of the spell; default 2.00
    private constant real    DURBASE   =  4.00   //duration base; this and DURFACTOR determine total duration of the spell; default 4.00
    private constant real    CHBASE    = 20.00   //this and CHFACTOR determine the chance for targets to be purged; default 10.00
    private constant real    CHFACTOR  = 10.00   //this and CHBASE determine the chance for targets to be purged; default 10.00
    private constant real    WHIPCURL  =  2.00   //curling factor (higher value makes whip curl tighter); default 2.00
    private constant real    WHIPWIDTH =  1.00   //width factor (higher value makes whip swing wider); default 1.00
    private constant real    CINTERVAL =  0.25   //check interval; how often (in seconds) a check for the chance to purge occurs; default 0.25
endglobals
//========================== End of Instructions ============================
//===========================================================================
private struct SW
    unit hero
    real herox
    real heroy
    integer lvl
    integer c
    integer cpi
    group g = CreateGroup()
endstruct
globals
    private SW array ALLDATA
    private timer ALLTIMER = CreateTimer()
    private integer TOTAL = 0
    private integer INSTANCE
    private boolexpr FILTER
    private group TEMPGROUP = CreateGroup()
    private unit TEMPUNIT
endglobals
//===========================================================================
private function Cond takes nothing returns boolean
    return GetSpellAbilityId() == SHOCKWHIP
endfunction

private function GroupFilter takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) &gt; 0.406 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TEMPUNIT)) == true
endfunction

private function GroupKillWhip takes nothing returns nothing
    call KillUnit(GetEnumUnit())
endfunction

private function GroupPurge takes nothing returns nothing
    local SW data = ALLDATA[INSTANCE]
    local unit e = GetEnumUnit()
    local real x = GetUnitX(e)
    local real y = GetUnitY(e)
    local unit u
    if GetRandomReal(1, 100) &lt;= CHFACTOR * data.lvl + CHBASE then
        set u = CreateUnit(GetOwningPlayer(TEMPUNIT), DUMMY, x, y, 0)
        call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
        call UnitAddAbility(u, PURGE)
        call IssueTargetOrder(u, &quot;purge&quot;, e)
    endif
    set u = null
    set e = null
endfunction

private function GroupMoveWhip takes nothing returns nothing
    local SW data = ALLDATA[INSTANCE]
    local group g = CreateGroup()
    local unit u = GetEnumUnit()
    local integer cv = GetUnitUserData(u)
    local integer osc = IAbsBJ(ModuloInteger(data.c + 10, 40) - 20) - 10
    local real face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
    local real dist = cv * (SPACING - IAbsBJ(osc) * WHIPCURL)
    local real ang_rad = face_rad + (osc * cv * 0.01 * WHIPWIDTH)
    local real x = data.herox + dist * Cos(ang_rad)
    local real y = data.heroy + dist * Sin(ang_rad)
    call SetUnitPosition(u, x, y)
    if ModuloInteger(data.c, data.cpi) == 0 then
        set TEMPUNIT = u
        call GroupEnumUnitsInRange(g, x, y, 50, FILTER)
        call GroupAddGroup(g, TEMPGROUP)
    endif
    call DestroyGroup(g)
    set g = null
    set u = null
endfunction

private function TimerActions takes nothing returns nothing
    local SW data
    local unit u
    local integer i = 1
    loop
        exitwhen i &gt; TOTAL
        set data = ALLDATA<i>
        if GetUnitAbilityLevel(data.hero, SWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
        call SetUnitAnimation(data.hero, ANIM)
        set INSTANCE = i
        call ForGroup(data.g, function GroupMoveWhip)
        call ForGroup(TEMPGROUP, function GroupPurge)
        call GroupClear(TEMPGROUP)
        set data.c = data.c - 1
        if GetWidgetLife(data.hero) &lt; 0.406 or data.c &lt;= 0 or GetUnitAbilityLevel(data.hero, SWBUFF2) == 0 then
            call IssueImmediateOrder(data.hero, &quot;manashieldoff&quot;)
            if GetWidgetLife(data.hero) &lt; 0.406 then
                call SetUnitAnimation(data.hero, &quot;death&quot;)
            else
                call SetUnitAnimation(data.hero, &quot;stand&quot;)
                call UnitRemoveAbility(data.hero, SWBUFF)
                call IssueImmediateOrder(data.hero, &quot;stop&quot;)
            endif
            call ForGroup(data.g, function GroupKillWhip)
            call data.destroy()
            set ALLDATA<i> = ALLDATA[TOTAL]
            set TOTAL = TOTAL - 1
            set i = i - 1
            if TOTAL == 0 then
                call PauseTimer(ALLTIMER)
            endif
        endif
        set i = i + 1
    endloop
    set u = null
endfunction

private function Cast takes nothing returns nothing
    local SW data = SW.create()
    local real face_rad = GetUnitFacing(GetTriggerUnit()) * bj_PI/180.0
    local real facecos = Cos(face_rad)
    local real facesin = Sin(face_rad)
    local real x = GetUnitX(GetTriggerUnit())
    local real y = GetUnitY(GetTriggerUnit())
    local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), SHOCKWHIP)
    local integer whipcount = lvl * WCFACTOR + WCBASE
    local real dur = lvl * DURFACTOR + DURBASE
    local unit u = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY, x, y, 0)
    local integer i = 1
    set data.hero = GetTriggerUnit()
    set data.herox = x
    set data.heroy = y
    set data.lvl = lvl
    set data.c = R2I(dur / INTERVAL)
    set data.cpi = R2I(CINTERVAL / INTERVAL)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, SILENCE)
    call IssuePointOrder(u, &quot;silence&quot;, x, y)
    call SetUnitAnimation(data.hero, ANIM)
    loop
        exitwhen i &gt; whipcount
        set x = data.herox + i * (SPACING) * facecos
        set y = data.heroy + i * (SPACING) * facesin
        set u = CreateUnit(GetOwningPlayer(data.hero), WHIPSEG, x, y, 0) 
        call UnitApplyTimedLife(u, &#039;BTLF&#039;, dur)
        call SetUnitScale(u, ANIMSCALE, ANIMSCALE, ANIMSCALE)
        call SetUnitTimeScale(u, ANIMSPEED)
        call UnitAddAbility(u, PERMIMMO)
        call SetUnitAbilityLevel(u, PERMIMMO, lvl)
        call SetUnitUserData(u, i)
        call GroupAddUnit(data.g, u)
        set i = i + 1
    endloop
    set TOTAL = TOTAL + 1
    set ALLDATA[TOTAL] = data
    if TOTAL == 1 then
        call TimerStart(ALLTIMER, INTERVAL, true, function TimerActions)
    endif
    set u = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
    local unit u = CreateUnit(Player(0), WHIPSEG, 0, 0, 0)
    call UnitAddAbility(u, SILENCE)
    call UnitAddAbility(u, PERMIMMO)
    call UnitAddAbility(u, PURGE)
    call RemoveUnit(u)
    set gg_trg_ShockWhip = CreateTrigger()
    set FILTER = Condition(function GroupFilter)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_ShockWhip, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_ShockWhip, Condition(function Cond))
    call TriggerAddAction(gg_trg_ShockWhip, function Cast)
    set u = null
endfunction
endscope</i></i>


Death Whip
Spell type: instant-cast, pseudo-channeling
Description: Summons a ghastly tentacle made of dark energies that whips from side to side and extends forward and backward. The Hero cannot move nor attack nor cast any spells while the whip is active, but the Hero CAN rotate in order to aim the whip. Enemy units that are hit by the whip take damage. If targets come into contact with the Skull Head they will be Crippled. [100 mana cost; 15 second cooldown]
Level 1 - a small whip that extends up to 420 distance away, deals up to 30 damage per second, and lasts for 6 seconds. Cripple reduces target's speed and damage by 50% for 5 seconds.
Level 2 - a medium whip that extends up to 560 distance away, deals up to 36 damage per second, and lasts for 8 seconds. Cripple reduces target's speed and damage by 60% for 6 seconds.
Level 3 - a large whip that extends up to 700 distance away, deals up to 42 damage per second, and lasts for 10 seconds. Cripple reduces target's speed and damage by 70% for 7 seconds.

Coding: JASS; requires vJASS
Leaks: Leakless
Multi Instanceability: MUI
Lag-susceptibility: moderate
Import Difficulty: easy (instructions included)

Trigger:
(Trigger not shown due to post text limit.)

Screenshot:


Aim Help
Description: More than one person has made a comment about how difficult it was to aim the original Cool Whip spell, so I have included this Aim Help Trigger to address that problem. This works for all whip spells.

Trigger:
(Trigger not shown due to post text limit.)
 

Attachments

  • [Spellpack] Whip Spells v2.52.w3x
    80.9 KB · Views: 298

gref

New Member
Reaction score
33
Not bad. I wish I could cancel it...but thats just crazy anyway.

Couple of things:
I cast the flame whip at a Revenant while it was sleeping and it did no damage.
Maybe add burn damage over time to the fire whip to make it more interesting?
 

DrinkSlurm

Eat Bachelor Chow!
Reaction score
50
Thanks for comments.

Not bad. I wish I could cancel it...but thats just crazy anyway.
That comment has been made before and I wasn't sure how to address it. I'm going to think about how best to add it in. That probably means changing it so that the modified Silence does not disable spells.

I cast the flame whip at a Revenant while it was sleeping and it did no damage.
That's just how Immolation works, I suppose. If I add an additional effect as described below, this problem will solve itself, I think.

Maybe add burn damage over time to the fire whip to make it more interesting?
I thought about that but did not implement it (to be honest) because of laziness. I guess I'll add that in too.
There's a whole bunch of effects that could be added, really: poison whip does damage over time and slows; lightning whip also purges targets; acid whip reduces armor, etc.

flame whip
Hmm... that is starting to sound better than "Fire Whip."
 

Pyrogasm

There are some who would use any excuse to ban me.
Reaction score
134
Here's the code using a stack for all the instances instead of a separate timer for each one, etc..

It's on Wc3Campaigns' pastebin because I got to use highlighting there! The second post here.
 

DrinkSlurm

Eat Bachelor Chow!
Reaction score
50
Thanks, Pyro. (Need to spread rep.) After studying it some more, I think I understand it and structs in general a little more clearly.

One question: using bj_PI vs. 3.14159? It probably makes little or no difference, but I thought I remember someone (was it Cohadar?) saying that you should use bj_PI instead of 3.14159.

Anyway, major updates have been made in the first post. Lots of features and new spells added.
 

Cohadar

master of fugue
Reaction score
209
Coding: JASS; requires vJASS, uses ABC and CSSafety

If I am not mistaken you replaced ABC with arrays.
Strange how this lags even with that solution.

I wonder if it would be faster using TT.
That way you could have all 4 whips on a same timer.

But than again even single whip lags too much.

How about...
JASS:

    loop
        exitwhen i &gt; TOTAL
        set data = ALLDATA<i>
        set face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
        set offset = data.c * OSFACTOR
        if GetUnitAbilityLevel(data.hero, FWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
</i>


You optimize that by creating dummy before the loop and then hawing same dummy casting silence multiple times.
 

DrinkSlurm

Eat Bachelor Chow!
Reaction score
50
If I am not mistaken you replaced ABC with arrays.
Oops, forgot to change that in the post.

You optimize that by creating dummy before the loop and then hawing same dummy casting silence multiple times.

Hmm... well, the only reason that bit of code...
JASS:
        if GetUnitAbilityLevel(data.hero, FWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif

...is in there is just in case the Silence buff is removed for some reason then a dummy will re-silence the hero. So all-in-all, re-silencing wouldn't happen too often. So I don't think it's a major source of lag, is it?

(In case you're wondering why the Silence buff would be removed: If you cast two whip spells on the same hero simultaneously, the 2nd silence buff overwrites the 1st buff. So when the 1st spell ends, the 2nd buff gets removed for some reason. And so that bit of code re-silences the hero if the 2nd spell is still running.)

I wonder if it would be faster using TT.
That way you could have all 4 whips on a same timer.
I haven't studied TT at all, but I will if you recommend it for this type of spell.

I just assumed that the lag was due to the number of units being created per whip, no?
 

Cohadar

master of fugue
Reaction score
209
I recommend you start learning TT right away.
But that will not help you in this case.

Let us discuss fire whip for example:
JASS:

    loop // &lt;----------&lt;&lt;
        exitwhen i &gt; TOTAL
        set data = ALLDATA<i>
        set face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
        set offset = data.c * OSFACTOR
        if GetUnitAbilityLevel(data.hero, FWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
        call SetUnitAnimation(data.hero, ANIM)
        call GroupAddGroup(data.g, TEMPGROUP)
        loop // &lt;----------&lt;&lt;
            set u = FirstOfGroup(TEMPGROUP)
            exitwhen u == null
            set cv = GetUnitUserData(u)
            set dx = cv * SPACING
            set dy = AMP * Sin(cv * 2 * bj_PI / SPW - offset) * cv
            set dist = SquareRoot(dx * dx + dy * dy)
            set ang_rad = Atan2(dy, dx) + face_rad
            set x = data.herox + dist * Cos(ang_rad)
            set y = data.heroy + dist * Sin(ang_rad)
            call SetUnitPosition(u, x, y)
            call GroupRemoveUnit(TEMPGROUP, u)
            if ModuloInteger(data.c, data.cpi) == 0 then
                set TEMPUNIT = u
                set TEMPINT = data.lvl
                call GroupEnumUnitsInRange(g, x, y, 75, FILTER)
                call GroupAddGroup(g, EGROUP) // &lt;----------&lt;&lt;
</i>


loop inside a loop that calls a function witch calls ForGroup inside????
You are doing something heavily wrong there.

That fire whip spell can be made with only one loop.
I totally don't get it why you complicated it that much.
 

DrinkSlurm

Eat Bachelor Chow!
Reaction score
50
OK, I simplified it as best as I could. Is this what you had in mind?

JASS:
//===========================================================================
//=========================== Fire Whip v2.50 ===============================
//============================ Instructions =================================
//===========================================================================
// MUI; requires vJASS
// Note: because the damage done is based on Permanent Immolation, damage will not
//   stack (i.e. a unit being hit by 2 whips at a time will NOT take double damage)
//===========================================================================
// 1. Go to the Object Editor, and copy + paste the following:
//    Units
//       * Dummy Caster
//       * Fire Whip Segment
//    Abilities
//       * Fire Whip: Add this ability to the Hero; make sure the buff is set to the
//         custom &quot;Fire Whip (Active)&quot; buff. This is one of the buffs that will show
//         on the Hero.
//       * Silence (for Fire Whip): make sure that the buff is set to the custom &quot;Fire
//         Whip (Caster)&quot; buff. This is one of the buffs that will show on the Hero.
//       * Permanent Immolation (for Fire Whip): make sure that the buff is set
//         to the custom &quot;Fire Whip (Target)&quot; buff.
//       * Acid Bomb (for Fire Whip): make sure that the buff is set to the custom
//         &quot;Fire Whip (Target)&quot; buff.
//    Buffs
//       * Fire Whip (Active): This is the buff that comes from the &quot;Fire Whip&quot; ability.
//         It appears on the Hero when the spell is activated and disappears when the
//         spell is cancelled.
//       * Fire Whip (Caster): This is the buff that comes from the &quot;Silence (for Fire
//         Whip)&quot; ability. It will be removed by the trigger when the spell is deactivated
//         by the player or if the spell reaches the end of its duration on its own.
//       * Fire Whip (Target): This buff applies the flames-looking effects on
//         targets that are hit by the whip.
// 2. Copy the following triggers:
//       * this trigger: &quot;FireWhip&quot;
//       * AimHelp: this is an OPTIONAL trigger used to help with the aiming of this
//         spell.
// 3. Other options:
//       * Hero: Note that you may want to change &quot;Stats - Can Flee&quot; to &quot;False&quot; to
//         make this skill a little easier to use. Otherwise, while the Hero is
//         silenced, it will tend to run away from enemies since it can&#039;t attack. 
// 4. To adjust the balance of the skill, you can change some of the following values:
//    Abilities
//       * Fire Whip: Cooldown, Mana Cost
//       * Permanent Immolation (for Fire Whip): Damage, damage interval, AoE
//       * Acid Bomb: primary damage, damage interval, duration, armor penalty
// 5. Make sure that the rawcodes specified below reference the proper units,
//    abilities, or buffs.
scope FireWhip
globals
    private constant integer FIREWHIP = &#039;A008&#039; //Ability ID of &quot;Fire Whip&quot;
    private constant integer DUMMY    = &#039;h000&#039; //Unit    ID of &quot;Dummy Caster&quot;
    private constant integer SILENCE  = &#039;A005&#039; //Ability ID of &quot;Silence (for Fire Whip)&quot;
            constant integer FWBUFF   = &#039;B002&#039; //Buff    ID of &quot;Fire Whip (Caster)&quot;
    private constant integer WHIPSEG  = &#039;h002&#039; //Unit    ID of &quot;Fire Whip Segment&quot;
    private constant integer PERMIMMO = &#039;A006&#039; //Ability ID of &quot;Permanent Immolation (for Fire Whip)&quot;
    private constant integer FWBUFF2  = &#039;B005&#039; //Buff    ID of &quot;Fire Whip (Active)&quot;
    private constant integer ACIDBOMB = &#039;A00B&#039; //Ability ID of &quot;Acid Bomb (for Fire Whip)&quot;
// 6. More configuration options:
    private constant string  ANIM      = &quot;spell&quot; //animation name during spell (adjust according to your chosen hero&#039;s model); default &quot;spell&quot;
    private constant real    SPACING   = 25.00   //spacing of the whip segments (adjusts &#039;length&#039; of whip); default 25.00
    private constant real    INTERVAL  =  0.04   //timer interval; default 0.04
    private constant real    ANIMSCALE =  1.00   //animation scale of whip segments; default 1.00
    private constant real    ANIMSPEED =  0.20   //animation speed of whip segments; default 0.20
    private constant integer SPW       = 12      //segments per wavelength (make this a multiple of 4 for best results); default 12
    private constant real    AMP       = 15.00   //amplitude multiplier (larger value makes the whip &#039;wider&#039;; default 15.00
    private constant integer WCFACTOR  =  6      //whip count factor; this and WCBASE determine total amount of whip segments; default 6
    private constant integer WCBASE    = 15      //whip count base; this and WCFACTOR determine total amount of whip segments; default 15
    private constant real    DURFACTOR =  2.00   //duration factor; this and DURBASE determine total duration of the spell; default 2.00
    private constant real    DURBASE   =  4.00   //duration base; this and DURFACTOR determine total duration of the spell; default 4.00
    private constant real    OSFACTOR  =  6.00   //offset multiplier (adjusts speed of travelling wave motion of whip); default 6.00
    private constant real    CINTERVAL =  0.50   //check interval; how often (in seconds) a check for residual DoT occurs; default 0.50
endglobals
//========================== End of Instructions ============================
//===========================================================================
private struct FW
    unit hero
    real herox
    real heroy
    integer lvl
    integer c
    integer cpi
    group g = CreateGroup()
endstruct
globals
    private boolexpr FILTER
    private group TEMPGROUP = CreateGroup()
    private unit TEMPUNIT
    private integer INSTANCE
    private FW array ALLDATA
    private integer TOTAL = 0
    private timer ALLTIMER = CreateTimer()
endglobals
//===========================================================================
private function Cond takes nothing returns boolean
    return GetSpellAbilityId() == FIREWHIP
endfunction

private function GroupFilter takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) &gt; 0.406 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TEMPUNIT)) == true
endfunction

private function GroupActions3 takes nothing returns nothing
    call KillUnit(GetEnumUnit())
endfunction

private function GroupActions2 takes nothing returns nothing
    local FW data = ALLDATA[INSTANCE]
    local unit e = GetEnumUnit()
    local real x = GetUnitX(e)
    local real y = GetUnitY(e)
    local unit u = CreateUnit(GetOwningPlayer(TEMPUNIT), DUMMY, x, y, 0)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, ACIDBOMB)
    call SetUnitAbilityLevel(u, ACIDBOMB, data.lvl)
    call IssueTargetOrder(u, &quot;acidbomb&quot;, e)
    set u = null
    set e = null
endfunction

private function GroupActions1 takes nothing returns nothing
    local FW data = ALLDATA[INSTANCE]
    local group g = CreateGroup()
    local unit u = GetEnumUnit()
    local integer cv = GetUnitUserData(u)
    local real offset = data.c * OSFACTOR
    local real face_rad = GetUnitFacing(data.hero) * bj_PI/180.0
    local real dx = cv * SPACING
    local real dy = AMP * Sin(cv * 2 * bj_PI / SPW - offset) * cv
    local real dist = SquareRoot(dx * dx + dy * dy)
    local real ang_rad = Atan2(dy, dx) + face_rad
    local real x = data.herox + dist * Cos(ang_rad)
    local real y = data.heroy + dist * Sin(ang_rad)
    call SetUnitPosition(u, x, y)
    if ModuloInteger(data.c, data.cpi) == 0 then
        set TEMPUNIT = u
        call GroupEnumUnitsInRange(g, x, y, 75, FILTER)
        call GroupAddGroup(g, TEMPGROUP)
        call GroupClear(g)
    endif
    call DestroyGroup(g)
    set g = null
endfunction

private function TimerActions takes nothing returns nothing
    local FW data
    local real face_rad
    local unit u
    local integer i = 1
    loop
        exitwhen i &gt; TOTAL
        set data = ALLDATA<i>
        if GetUnitAbilityLevel(data.hero, FWBUFF) == 0 then
            set u = CreateUnit(GetOwningPlayer(data.hero), DUMMY, data.herox, data.heroy, 0)
            call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
            call UnitAddAbility(u, SILENCE)
            call IssuePointOrder(u, &quot;silence&quot;, data.herox, data.heroy)
        endif
        call SetUnitAnimation(data.hero, ANIM)
        set INSTANCE = i
        call ForGroup(data.g, function GroupActions1)
        call ForGroup(TEMPGROUP, function GroupActions2)
        call GroupClear(TEMPGROUP)
        set data.c = data.c - 1
        if GetWidgetLife(data.hero) &lt; 0.406 or data.c &lt;= 0 or GetUnitAbilityLevel(data.hero, FWBUFF2) == 0 then
            call IssueImmediateOrder(data.hero, &quot;unimmolation&quot;)
            if GetWidgetLife(data.hero) &lt; 0.406 then
                call SetUnitAnimation(data.hero, &quot;death&quot;)
            else
                call SetUnitAnimation(data.hero, &quot;stand&quot;)
                call UnitRemoveAbility(data.hero, FWBUFF)
                call IssueImmediateOrder(data.hero, &quot;stop&quot;)
            endif
            call ForGroup(data.g, function GroupActions3)
            call data.destroy()
            set ALLDATA<i> = ALLDATA[TOTAL]
            set TOTAL = TOTAL - 1
            set i = i - 1
            if TOTAL == 0 then
                call PauseTimer(ALLTIMER)
            endif
        endif
        set i = i + 1
    endloop
    set u = null
endfunction

private function Cast takes nothing returns nothing
    local FW data = FW.create()
    local real face_rad = GetUnitFacing(GetTriggerUnit()) * bj_PI/180.0
    local real facecos = Cos(face_rad)
    local real facesin = Sin(face_rad)
    local real x = GetUnitX(GetTriggerUnit())
    local real y = GetUnitY(GetTriggerUnit())
    local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), FIREWHIP)
    local integer whipcount = data.lvl * WCFACTOR + WCBASE 
    local real dur = lvl * DURFACTOR + DURBASE
    local unit u = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY, x, y, 0)
    local integer i = 1
    set data.hero = GetTriggerUnit()
    set data.herox = x
    set data.heroy = y
    set data.lvl = lvl
    set data.c = R2I(dur / INTERVAL)
    set data.cpi = R2I(CINTERVAL / INTERVAL)
    call UnitApplyTimedLife(u, &#039;BTLF&#039;, 1.00)
    call UnitAddAbility(u, SILENCE)
    call IssuePointOrder(u, &quot;silence&quot;, x, y)
    call SetUnitAnimation(data.hero, ANIM)
    loop
        exitwhen i &gt; whipcount
        set x = data.herox + i * SPACING * facecos
        set y = data.heroy + i * SPACING * facesin
        set u = CreateUnit(GetOwningPlayer(data.hero), WHIPSEG, x, y, 0) 
        call UnitApplyTimedLife(u, &#039;BTLF&#039;, dur)
        call SetUnitScale(u, ANIMSCALE, ANIMSCALE, ANIMSCALE)
        call SetUnitTimeScale(u, ANIMSPEED)
        call UnitAddAbility(u, PERMIMMO)
        call SetUnitAbilityLevel(u, PERMIMMO, lvl)
        call SetUnitUserData(u, i)
        call GroupAddUnit(data.g, u)
        set i = i + 1
    endloop
    set TOTAL = TOTAL + 1
    set ALLDATA[TOTAL] = data
    if TOTAL == 1 then
        call TimerStart(ALLTIMER, INTERVAL, true, function TimerActions)
    endif
    set u = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
    set gg_trg_FireWhip = CreateTrigger()
    set FILTER = Condition(function GroupFilter)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_FireWhip, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_FireWhip, Condition(function Cond))
    call TriggerAddAction(gg_trg_FireWhip, function Cast)
endfunction
endscope</i></i>
 

gref

New Member
Reaction score
33
If you want to cut down lag, put all your different types of dummy's on the map at the start to preload them, then remove them at init.
Also add the abilities that dummy will be casting later to the dummy via triggers at init, then remove them.

That should help reduce lag on the first casts.
 

Cohadar

master of fugue
Reaction score
209
OK, I simplified it as best as I could. Is this what you had in mind?

No.

The only way to make non lag spells is to make them with linear complexity.
What does that mean?

It means that inside a timer you cannot use loops inside loops.
Remember that ForGroup call is also a loop

bad:
JASS:

loop
    loop
    endloop
endloop


good:
JASS:

loop
endloop
loop
endloop


bad:
JASS:

loop
    ForGroup
endloop


good:
JASS:

loop
endloop
ForGroup


This might not be always easy to make but I know it is almost always possible, and for whip spells it is definitely possible.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      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