• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Shadow Sting Spellpack v1.3

Made 3 spells, inspired from a few skills from Granado Espada.

Overview
  • Eagle Eye [Active]
  • Heartbreaker [Active]
  • Bolt Sequence [Active]


Details
- The spells are vJASS
- They should be leak-less and lag-less
- It is MUI, meaning can be cast many times at the same instance


Implementation
JASS:
//==============================================================================
//                       SHADOW STING SPELLPACK v1.3                       
//                              BY Ayanami                             
//==============================================================================

//==============================================================================
//                            REQUIREMENTS                                      
//==============================================================================
// - Alloc
// - DamageEvent
// - SpellEffectEvent
// - T32
// - Table
// - Unit Indexer                                                         
//==============================================================================

//==============================================================================
//                           IMPLEMENTATION                                     
//==============================================================================
// 1) Copy the whole "Required Systems" Trigger folder 
// 2) Save the map, it will take a bit longer than usual
// 3) Close the map and re-open it, then disable or delete the trigger "Objects"
// 4) Go to the Import Manager and export all resources, then import into map
// 5) Copy all 3 abilities under "Night Elf - Heroes"    
// 6) Copy the whole "Shadow Sting Spellpack" Trigger folder                                         
//==============================================================================


Spell Codes



eagleeye.png

Utilizing the shadows within the hero, the hero temporarily has sharper vision. This allows the hero to deal armor shattering blows for the duration. Any damage received from the hero during this duration will result in an armor penalty to the target for 5 seconds. The amount of armor shattered is dependant to the damage dealt. Maximum of 15 armor penalty.

Level 1 - 0.4% of damage as armor penalty. Buff lasts 8 seconds.
Level 2 - 0.6% of damage as armor penalty. Buff lasts 9 seconds.
Level 3 - 0.8% of damage as armor penalty. Buff lasts 10 seconds.
Level 4 - 1.0% of damage as armor penalty. Buff lasts 11 seconds.

Cast Range: Self
Target Type: Instant
Cooldown: 22 seconds

JASS:
library EagleEye uses Alloc, DamageEvent, SpellEffectEvent, UnitIndexer

globals
    private integer array ARMORID
endglobals

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABEE' // raw code of ability "Eagle Eye"
    private constant integer DUMABILID = 'AEE0' // raw code of ability "Eagle Eye (Buff)"
    private constant integer BUFFID = 'BEE0' // raw code of buff "Eagle Eye"
    private constant integer PRELOADID = 'prel' // raw code of unit "Preloader"
    
    private constant string FX = "Abilities\\Spells\\Items\\SpellShieldAmulet\\SpellShieldCaster.mdl" // effect on target when damaged
    private constant string FX_AT = "chest" // attachment point of FX
    
    private constant real PERIOD = 0.10 // timer update frequency for buff checking
endglobals

private function InitFunc takes nothing returns nothing
    local unit u
    local integer i = 0
    
    set ARMORID[0] = 'dEE4' // raw code of ability "Eagle Eye (Armor - -10000's)"
    set ARMORID[1] = 'dEE3' // raw code of ability "Eagle Eye (Armor - -1000's)"
    set ARMORID[2] = 'dEE2' // raw code of ability "Eagle Eye (Armor - -100's)"
    set ARMORID[3] = 'dEE1' // raw code of ability "Eagle Eye (Armor - -10's)"
    set ARMORID[4] = 'dEE0' // raw code of ability "Eagle Eye (Armor - -1's)"
    
    //preload resources
    set u = CreateUnit(Player(13), PRELOADID, 0, 0, 0)
    call UnitAddAbility(u, DUMABILID)
    loop
        exitwhen i > 4
        call UnitAddAbility(u, ARMORID[i])
        set i = i + 1
    endloop
    call RemoveUnit(u)
    set u = null
endfunction

// duration of buff on caster
private constant function GetDuration takes integer level returns real
    return 7. + level
endfunction

// duration of armor penalty
private constant function GetTargetDuration takes integer level returns real
    return 5.
endfunction

// percentage of damage taken as armor penalty, where 1.00 = 100%
private constant function GetArmorPenalty takes integer level returns real
    return 0.002 + (0.002 * level)
endfunction

// maximum armor penalty (supports up to 99999 armor reduction)
private constant function GetMaxPenalty takes integer level returns integer
    return 15
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

private struct Main extends array
    implement Alloc

    thistype next
    thistype prev
    unit u
    real dur
    static timer linkTimer = CreateTimer()
    static integer array store
    
    private method destroy takes nothing returns nothing
        set this.next.prev = this.prev
        set this.prev.next = this.next
        set store[GetUnitId(this.u)] = 0
        
        if thistype(0).next == 0 then
            call PauseTimer(linkTimer)
        endif
        
        call UnitRemoveAbility(this.u, DUMABILID)
        call UnitRemoveAbility(this.u, BUFFID)
        call this.deallocate()
    endmethod
    
    private static method iterate takes nothing returns nothing
        local thistype this = thistype(0)
        
        loop
            set this = this.next
            exitwhen this == 0
        
            if this.dur <= 0 or IsUnitType(this.u, UNIT_TYPE_DEAD) or GetUnitTypeId(this.u) == 0 then
                call this.destroy()
            else
                set this.dur = this.dur - PERIOD
            endif
        endloop
    endmethod
    
    private static method onCast takes nothing returns boolean
        local thistype this
        local integer id = GetUnitId(GetTriggerUnit())
        
        if store[id] > 0 then
            set this = store[id]
        else
            if thistype(0).next == 0 then
                call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
            endif
        
            set this = thistype.allocate()
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)
            
            set this.u = GetTriggerUnit()
            
            call UnitAddAbility(this.u, DUMABILID)
        endif
                
        set this.dur = GetDuration(GetUnitAbilityLevel(this.u, ABILID))

        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(ABILID, function thistype.onCast)
        call InitFunc()
    endmethod
endstruct

private struct Damage extends array
    implement Alloc

    thistype next
    thistype prev
    unit u
    real amount
    real dur
    static timer linkTimer = CreateTimer()
    static integer array store
    
    private method destroy takes nothing returns nothing
        local integer i
    
        set this.next.prev = this.prev
        set this.prev.next = this.next
        set store[GetUnitId(this.u)] = 0
        
        if thistype(0).next == 0 then
            call PauseTimer(linkTimer)
        endif
        
        set i = 0
        loop
            exitwhen i > 4
            call SetUnitAbilityLevel(this.u, ARMORID[i], 1)
            set i = i + 1
        endloop
        
        call this.deallocate()
    endmethod
    
    private static method iterate takes nothing returns nothing
        local thistype this = thistype(0)
        local integer i
        
        loop
            set this = this.next
            exitwhen this == 0
        
            if this.dur <= 0 or IsUnitType(this.u, UNIT_TYPE_DEAD) or GetUnitTypeId(this.u) == 0 then
                call this.destroy()
            else
                set this.dur = this.dur - PERIOD
            endif
        endloop
    endmethod

    private static method onDamage takes nothing returns boolean
        local thistype this
        local unit u = DamageEvent.source
        local integer level
        local integer id
        local integer max
        local integer value
        local integer factor
        local integer i
        
        if GetUnitAbilityLevel(u, DUMABILID) == 1 then
            set level = GetUnitAbilityLevel(u, ABILID)
            set id = GetUnitId(DamageEvent.target)
            set max = GetMaxPenalty(level)
            if store[id] > 0 then
                set this = store[id]
                set this.amount = this.amount + (DamageEvent.amount * GetArmorPenalty(level))
            else
                if thistype(0).next == 0 then
                    call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
                endif
            
                set this = thistype.allocate()
                set thistype(0).next.prev = this
                set this.next = thistype(0).next
                set thistype(0).next = this
                set this.prev = thistype(0)
                
                set this.u = DamageEvent.target
                set this.amount = DamageEvent.amount * GetArmorPenalty(level)
                set store[id] = this
            endif
            set this.dur = GetTargetDuration(level)
            if this.amount > max then
                set this.amount = max
            endif
            call DestroyEffect(AddSpecialEffectTarget(FX, this.u, FX_AT))
            
            set value = R2I(this.amount)
            set factor = 10000
            set i = 0
            loop
                exitwhen i > 4
                if GetUnitAbilityLevel(this.u, ARMORID[i]) == 0 then
                    call UnitAddAbility(this.u, ARMORID[i])
                endif
                set level = value / factor
                call SetUnitAbilityLevel(this.u, ARMORID[i], level + 1)
                set value = value - (level * factor)
                set factor = factor / 10
                set i = i + 1
            endloop
        endif

        set u = null
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterEvent(Condition(function thistype.onDamage), DamageEvent.ANY)
    endmethod
endstruct

endlibrary




heartbreaker.png

Fires a bolt that pierces through the soul of the target, dealing damage. A shadow curse is also applied to the target and nearby enemy units. The curse takes effect after 3 seconds, which then enemies are stunned based on the damage taken. Enemies are immediately stunned if the maximum damage taken is reached.

Level 1 - 50 damage. 100 curse damage limit, 1 second stun limit.
Level 2 - 100 damage. 200 curse damage limit, 2 seconds stun limit.
Level 3 - 150 damage. 300 curse damage limit, 3 seconds stun limit.
Level 4 - 200 damage. 400 curse damage limit, 4 seconds stun limit.

Cast Range: 600
Target Type: Unit
Cooldown: 16 seconds

JASS:
library Heartbreaker uses Alloc, SpellEffectEvent, T32, Table

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABHe' // raw code of ability "Heartbreaker"
    private constant integer DUM_ABILID_1 = 'AHe0' // raw code of ability "Heartbreaker (Stun)"
    private constant integer DUM_ABILID_2 = 'AHe1' // raw code of ability "Heartbreaker (Buff)"
    private constant integer BUFFID_1 = 'BHe0' // raw code of buff "Heartbreaker (Stun)"
    private constant integer BUFFID_2 = 'BHe1' // raw code of buff "Heartbreaker (Buff)"
    private constant integer DUMMYID = 'dHEA' // raw code of unit "Heartbreaker Dummy"
    private constant integer CASTERID = 'cAST' // raw code of unit "Caster Dummy"
    private constant integer PRELOADID = 'prel' // raw code of unit "Preloader"
    
    private constant string FX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" // effect used upon being stunned
    private constant string FX_AT = "origin" // attachment point of FX
    private constant string PROJ_FX = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" // effect used upon projectile impact
    
    private constant boolean SHOWTEXT = true // true if floating text (displaying stun duration) is displayed
    private constant string COLOR = "|cffff0000" // color code for floating text
    
    private constant real SPEED = 1000.0 // distance traveled by projectile per second
    private constant real TRUESPEED = SPEED * T32_PERIOD // distance per interval
    private constant real COL_SIZE = 50. // collision size of projectile
    private constant real PERIOD = 0.1 // timer update frequency for stun
    
    private constant real ENUM_RADIUS = 176. // max collision size of a unit in your map
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage
    private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type of damage
endglobals

// damage dealt
private constant function GetDamage takes integer level returns real
    return 50. * level
endfunction

// damage limit for curse
private constant function GetDamageLimit takes integer level returns real
    return 100. * level
endfunction

// stun limit for curse
private constant function GetStunLimit takes integer level returns real
    return 1. * level
endfunction

// delay time before curse takes effect
private constant function GetDelayTime takes integer level returns real
    return 3.
endfunction

// curse area of effect
private constant function GetArea takes integer level returns real
    return 400.
endfunction

// target filter
private constant function GetFilter takes unit c, unit u returns boolean
    return /*
    */ not IsUnitType(u, UNIT_TYPE_DEAD) /* // target is alive
    */ and IsUnitEnemy(u, GetOwningPlayer(c)) /* // target is an enemy
    */ and not IsUnitType(u, UNIT_TYPE_STRUCTURE) /* // target is not a structure
    */
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

globals
    private constant real TRUE_COL_SIZE = COL_SIZE * COL_SIZE
    
    private group G = bj_lastCreatedGroup
endglobals

private struct Curse extends array
    implement Alloc

    thistype next
    thistype prev
    unit u
    unit tar
    trigger trig
    integer level
    real damage
    real damagelimit
    real dur
    static unit castDummy
    static Table table
    static timer linkTimer = CreateTimer()
    
    private method destroy takes nothing returns nothing
        set this.next.prev = this.prev
        set this.prev.next = this.next
        
        if thistype(0).next == 0 then
            call PauseTimer(linkTimer)
        endif
        
        call UnitRemoveAbility(this.tar, BUFFID_1)
        call this.deallocate()
    endmethod
    
    private static method iterate takes nothing returns nothing
        local thistype this = thistype(0)
        
        loop
            set this = this.next
            exitwhen this == 0
            
            if this.dur <= 0 or IsUnitType(this.tar, UNIT_TYPE_DEAD) or GetUnitTypeId(this.tar) == 0 then
                call this.destroy()
            else
                if GetUnitAbilityLevel(this.tar, BUFFID_1) == 0 then
                    call IssueTargetOrder(castDummy, "firebolt", this.tar)
                endif
                set this.dur = this.dur - PERIOD
            endif
        endloop
    endmethod
    
    private method periodic takes nothing returns nothing
        local texttag t
        local integer digitone
        local integer digittwo
        local integer id
        
        if this.dur <= 0 or this.damage >= this.damagelimit or IsUnitType(this.tar, UNIT_TYPE_DEAD) then
            if not IsUnitType(this.tar, UNIT_TYPE_DEAD) and this.damage > 0 then
                if this.damage > this.damagelimit then
                    set this.damage = this.damagelimit
                endif
                set this.dur = (this.damage / this.damagelimit) * GetStunLimit(this.level)
                
                static if SHOWTEXT then
                    set digitone = R2I(this.dur)
                    set digittwo = R2I((this.dur - digitone) * 10)
                    set t = CreateTextTag()
                    
                    call SetTextTagText(t, COLOR + I2S(digitone) + "." + I2S(digittwo) + "!|r", 0.0253)
                    call SetTextTagPosUnit(t, this.tar, 0)
                    call SetTextTagColor(t, 255, 255, 255, 255)
                    call SetTextTagVelocity(t, 0.0355 * Cos(bj_PI / 2), 0.0355 * Sin(bj_PI / 2))
                    call SetTextTagPermanent(t, false)
                    call SetTextTagLifespan(t, 2.50)
                endif
                
                call IssueTargetOrder(castDummy, "firebolt", this.tar)
                call DestroyEffect(AddSpecialEffectTarget(FX, this.tar, FX_AT))
                
                if thistype(0).next == 0 then
                    call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
                endif
                
                set thistype(0).next.prev = this
                set this.next = thistype(0).next
                set thistype(0).next = this
                set this.prev = thistype(0)
            endif
            
            call UnitRemoveAbility(this.tar, DUM_ABILID_2)
            call UnitRemoveAbility(this.tar, BUFFID_2)
            call table.remove(GetHandleId(this.trig))
            call DestroyTrigger(this.trig)
            call this.stopPeriodic()
            
            if IsUnitType(this.tar, UNIT_TYPE_DEAD) then
                call this.deallocate()
            endif
        else
            set this.dur = this.dur - T32_PERIOD
        endif
        
        set t = null
    endmethod
    implement T32x
    
    private static method onDamage takes nothing returns boolean
        local thistype this = table[GetHandleId(GetTriggeringTrigger())]
        
        if this.tar == GetTriggerUnit() then
            set this.damage = this.damage + GetEventDamage()
        endif

        return false
    endmethod

    public static method create takes unit u, unit tar, integer level returns thistype
        local thistype this = thistype.allocate()

        set this.u = u
        set this.tar = tar
        set this.trig = CreateTrigger()
        set this.level = level
        set this.damage = 0
        set this.damagelimit = GetDamageLimit(this.level)
        set this.dur = GetDelayTime(this.level)
        
        call UnitAddAbility(this.tar, DUM_ABILID_2)
        call TriggerRegisterUnitEvent(this.trig, this.tar, EVENT_UNIT_DAMAGED)
        call TriggerAddCondition(this.trig, Condition(function thistype.onDamage))
        set table[GetHandleId(this.trig)] = this
        
        call this.startPeriodic()
        
        return this
    endmethod
    
    private static method onInit takes nothing returns nothing
        set table = Table.create()
        
        set castDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTERID, 0, 0, 0)
        call UnitAddAbility(castDummy, DUM_ABILID_1)
    endmethod
endstruct

private struct Main extends array
    implement Alloc

    unit u
    unit tar
    unit dummy
    integer level
    
    private method destroy takes nothing returns nothing
        call KillUnit(this.dummy)
        call this.deallocate()
    endmethod
    
    private method periodic takes nothing returns nothing
        local real x = GetUnitX(this.dummy)
        local real y = GetUnitY(this.dummy)
        local real dx = GetUnitX(this.tar) - x
        local real dy = GetUnitY(this.tar) - y
        local real a
        local unit u
        
        if dx * dx + dy * dy <= TRUE_COL_SIZE then
            set dx = dx + x
            set dy = dy + y
            set a = GetArea(this.level)
        
            call DestroyEffect(AddSpecialEffect(PROJ_FX, x, y))
            call UnitDamageTarget(this.u, this.tar, GetDamage(this.level), true, false, ATK, DMG, null)
            call GroupEnumUnitsInRange(G, dx, dy, a + ENUM_RADIUS, null)
            
            loop
                set u = FirstOfGroup(G)
                exitwhen u == null
                call GroupRemoveUnit(G, u)
                
                if IsUnitInRangeXY(u, dx, dy, a) and GetFilter(this.u, u) then
                    call Curse.create(this.u, u, this.level)
                endif
            endloop
            
            call this.stopPeriodic()
            call this.destroy()
        else
            set a = Atan2(dy, dx)
            call SetUnitX(this.dummy, x + TRUESPEED * Cos(a))
            call SetUnitY(this.dummy, y + TRUESPEED * Sin(a))
            call SetUnitFacing(this.dummy, a * bj_RADTODEG)
        endif
    endmethod
    implement T32x

    private static method onCast takes nothing returns boolean
        local thistype this
        local real x
        local real y
        
        set this = thistype.allocate()
        set this.u = GetTriggerUnit()
        set this.tar = GetSpellTargetUnit()
        set x = GetUnitX(this.u)
        set y = GetUnitY(this.u)
        set this.dummy = CreateUnit(GetOwningPlayer(this.u), DUMMYID, x, y, Atan2(GetUnitY(this.tar) - y, GetUnitX(this.tar) - x) * bj_RADTODEG)
        set this.level = GetUnitAbilityLevel(this.u, ABILID)
            
        call this.startPeriodic()

        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local unit u
        
        call RegisterSpellEffectEvent(ABILID, function thistype.onCast)
        
        // preload
        set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTERID, 0, 0, 0)
        call UnitAddAbility(u, DUM_ABILID_1)
        call UnitAddAbility(u, DUM_ABILID_2)
        call RemoveUnit(u)
        call RemoveUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMYID, 0, 0, 0))
        
        set u = null
    endmethod
endstruct

endlibrary




boltsequence.png

Fires a series of arrows at all enemies within the area, dealing physical damage. Every wave of arrows are released at 0.5 seconds intervals. Channels for 3 seconds.

Level 1 - 35 damage.
Level 2 - 50 damage.
Level 3 - 65 damage.
Level 4 - 80 damage.

Cast Range: 400
Target Type: Unit Area (400)
Cooldown: 20/19/18/17 seconds
Channeling

JASS:
library BoltSequence uses Alloc, SpellEffectEvent, T32

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABBS' // raw code of ability "Bolt Sequence"
    private constant string ORDERID = "blight" // order ID of "Bolt Sequence"
    private constant integer DUMMYID = 'dBOL' // raw code of unit "Bolt Sequence Dummy"
    
    private constant real ANIMTIME = 1.334 // this value is the attack animation time of the model
    private constant real COLLISION = 50.0 // collision check for arrow impact
    private constant real TRUECOL = COLLISION * COLLISION // square of COLLISION
    private constant real SPEED = 1000.0 // distance traveled by projectile per second
    private constant real TRUESPEED = SPEED * T32_PERIOD // distance per interval
    
    private constant real ENUM_RADIUS = 176. // max collision size of a unit in your map
    
    private constant attacktype ATK = ATTACK_TYPE_CHAOS // attack type of damage
    private constant damagetype DMG = DAMAGE_TYPE_NORMAL // damage type of damage
endglobals

// area of effect
private constant function GetArea takes integer level returns real
    return 400.
endfunction

// damage per wave
private constant function GetDamage takes integer level returns real
    return 15. * level + 20.
endfunction

// interval per wave
private constant function GetWaves takes integer level returns real
    return 0.5
endfunction

// target filter
private constant function GetFilter takes unit c, unit u returns boolean
    return /*
    */ not IsUnitType(u, UNIT_TYPE_DEAD) /* // target is alive
    */ and IsUnitEnemy(u, GetOwningPlayer(c)) /* // target is an enemy
    */ and not IsUnitType(u, UNIT_TYPE_STRUCTURE) /* // target is not a structure
    */
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

globals
    private group G = bj_lastCreatedGroup
endglobals

private struct Projectile extends array
    implement Alloc

    unit u
    unit tar
    unit dummy
    real damage
    
    private method destroy takes nothing returns nothing
        call KillUnit(this.dummy)
        call this.deallocate()
    endmethod
    
    private method periodic takes nothing returns nothing
        local real x = GetUnitX(this.dummy)
        local real y = GetUnitY(this.dummy)
        local real dx = GetUnitX(this.tar) - x
        local real dy = GetUnitY(this.tar) - y
        local real a
        
        if dx * dx + dy * dy <= TRUECOL then
            call UnitDamageTarget(this.u, this.tar, this.damage, true, false, ATK, DMG, null)
            
            call this.destroy()
            call this.stopPeriodic()
        else
            set a = Atan2(dy, dx)
            call SetUnitX(this.dummy, x + TRUESPEED * Cos(a))
            call SetUnitY(this.dummy, y + TRUESPEED * Sin(a))
            call SetUnitFacing(this.dummy, a * bj_RADTODEG)
        endif
    endmethod
    implement T32x
    
    static method create takes unit u, unit tar, real damage returns thistype
        local thistype this = thistype.allocate()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        
        set this.u = u
        set this.tar = tar
        set this.dummy = CreateUnit(GetOwningPlayer(this.u), DUMMYID, x, y, Atan2(GetUnitY(tar) - y, GetUnitX(tar) - x) * bj_RADTODEG)
        set this.damage = damage
        
        call this.startPeriodic()
        
        return this
    endmethod
endstruct

private struct Main extends array
    implement Alloc

    unit u
    real area
    real damage
    real interval
    real count
    real x
    real y
    
    private method destroy takes nothing returns nothing
        call SetUnitTimeScale(this.u, 1.)
        call this.deallocate()
    endmethod
    
    private method periodic takes nothing returns nothing
        local unit u
    
        if this.count <= 0 then
            call GroupEnumUnitsInRange(G, this.x, this.y, this.area + ENUM_RADIUS, null)
            
            loop
                set u = FirstOfGroup(G)
                exitwhen u == null
                call GroupRemoveUnit(G, u)
                
                if IsUnitInRangeXY(u, this.x, this.y, this.area) and GetFilter(this.u, u) then
                    call Projectile.create(this.u, u, this.damage)
                endif
            endloop
            
            set this.count = this.interval - T32_PERIOD
        else
            set this.count = this.count - T32_PERIOD
        endif
        if (GetUnitCurrentOrder(this.u)) != OrderId(ORDERID) then
            call this.stopPeriodic()
            call this.destroy()
        endif
    endmethod
    implement T32x

    private static method onCast takes nothing returns boolean
        local thistype this
        local unit u
        local integer level
        
        set this = thistype.allocate()
        set this.u = GetTriggerUnit()
        set level = GetUnitAbilityLevel(this.u, ABILID)
        set this.area = GetArea(level)
        set this.damage = GetDamage(level)
        set this.interval = GetWaves(level)
        set this.count = this.interval
        set this.x = GetSpellTargetX()
        set this.y = GetSpellTargetY()
           
        call GroupEnumUnitsInRange(G, this.x, this.y, this.area + ENUM_RADIUS, null)
        
        loop
            set u = FirstOfGroup(G)
            exitwhen u == null
            call GroupRemoveUnit(G, u)
                
            if IsUnitInRangeXY(u, this.x, this.y, this.area) and GetFilter(this.u, u) then
                call Projectile.create(this.u, u, this.damage)
            endif
        endloop
            
        call SetUnitTimeScale(this.u, ANIMTIME / this.interval)
        call this.startPeriodic()

        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(ABILID, function thistype.onCast)
        
        //preload
        call RemoveUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMYID, 0, 0, 0))
    endmethod
endstruct

endlibrary



Credits

Sevion - Alloc
Nestharus - DamageEvent
- Unit Indexer
Bribe - SpellEffectEvent
- Table
Jesus4Lyf - Timer32
JetFangInferno - Eagle Eye Model
Granado Espada - Skill Icons



Changelogs

- Initial relase


- Rewrote the triggers using Timer32 and TimerUtils instead of KeyTimers2
- Optimized code


- Removed use of TimerUtils
- Optimized code


- Removed library requirements: AIDS, GroupUtils and GTrigger
- Added library requirements: Alloc, DamageEvent, SpellEffectEvent and Unit Indexer
- Optimized code


Feedback will be appreciated.

Keywords:
shadow, string, eagle, eye, heart, breaker, bolt, sequence, granado, espada, sword, new, world
Contents

Shadow Sting Spellpack v1.3 (Map)

Reviews
30 Nov 2011 Bribe: Approved - another great resource from yourself. One thing which should be pointed out is that level-10 abilities take up a lot of map loading time because they can't be converted to SLK files. If you made it 4 levels then it...

Moderator

M

Moderator

30 Nov 2011
Bribe: Approved - another great resource from yourself.

One thing which should be pointed out is that level-10 abilities take up a lot of map loading time because they can't be converted to SLK files. If you made it 4 levels then it can be converted and it makes a big difference (4 is the highest).
 
Level 15
Joined
Jul 6, 2009
Messages
889
I noticed you are using the deallocate directly. Neat.

Is there a reason to use KeyTimers2? You can basically use Timer32 for Heartbreaker and Bolt Sequence, and TimerUtils for Eagle Eye. Infact, even Jesus4Lyf himself said (in a chat I had with him) that KeyTimers2 is useless now, it's just T32 and TU for all your timer needs. TU for low frequency, T32 for high frequency, you shouldn't really need something being called less than 32 times a second unless you want something to be smoother.

  • In Heartbreaker, if SquareRoot(dx * dx + dy * dy) <= 50 then, in this line, the collision size should be made configurable as a constant global. Also, you don't need to square root the distance, simply multiply the other end: if (dx * dx + dy * dy) <= 2500.00 then.
  • In Heartbreaker, in the projectile method, you should calculate a in the else. This saves you one call per callback.
  • You don't need to null t in the onInit methods, they're going to stay around all game. However, I guess it is good practise.
 
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
1,084
Quick Code Look

Eagle Eye

  • In the Setup function, i could be initially set to 0.
  • You have a hardcoded raw id for a unit: 'prel' in this and Heartbreaker.
Heartbreaker

  • You have a SetTextTagVelocityBJ in there; inline it.
Bolt Sequence

  • The level member is pretty useless; use a local integer variable instead.
  • You have a String2OrderIdBJ in there; inline it.
  • You can preload the dummy unit like this:
    JASS:
    call RemoveUnit(CreateUnit(Player(13), DUMMYID, 0, 0, 0))
 
Top