• 🏆 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!

The Dark Elf - SpellPack v1.2

  • Like
Reactions: Imadori

Foreword

Spell's Description

Spell Codes

System Used/Credits

Bugs

Changelogs


This is my first ever SpellPack submitted in the spell's section. This is originally from [post=1958167]Hero Contest #4 - Animalistic Nature[/post] contest but some improvements.

Locust Raid

Throws a magical bottle that has locusts inside. When the bottle breaks, angry locusts follows and attacks nearby enemy unit. If there is no enemy around, the locusts follows the caster.
The locusts are invulnerable and cannot be controlled until the target dies.

|cffffcc00Level 1|r - Maximum of 2 locust, lasts 10 seconds
|cffffcc00Level 2|r - Maximum of 4 locust, lasts 20 seconds
|cffffcc00Level 3|r - Maximum of 6 locust, lasts 30 seconds
|cffffcc00Level 4|r - Maximum of 8 locust, lasts 40 seconds
|cffffcc00Level 5|r - Maximum of 10 locust, lasts 50 seconds

|cffffcc00Locust damage 2-6|r
Twin Wolves

Summons two uncontrollable escort wolves which cannot attack but each one has an individual spell. Each one can damage and heal nearby enemy and allied units respectively.
Wolves lasts for 60 seconds or when the caster dies. You may only summon 2 wolves at a time.

|cffffcc00Level 1|r - Wolves damages 50 or heals 100.
|cffffcc00Level 2|r - Wolves damages 100 or heals 150.
|cffffcc00Level 3|r - Wolves damages 150 or heals 200.
|cffffcc00Level 4|r - Wolves damages 200 or heals 250.
|cffffcc00Level 5|r - Wolves damages 250 or heals 300.

Damage dealt by |cffffcc00chainlightning|r. Cooldown 5 seconds.
Heal is done by |cffffcc00rejuvenation|r for 3 seconds. Cooldown 3 seconds.
Enchanted Bottle

This bottle is enchanted by the locusts inside it so that when it breaks, it gives a chance to poison all nearby enemy organic units.

|cffffcc00Level 1|r - 10% chance, deals 90 damage
|cffffcc00Level 2|r - 15% chance, deals 120 damage
|cffffcc00Level 3|r - 20% chance, deals 150 damage
|cffffcc00Level 4|r - 25% chance, deals 180 damage
|cffffcc00Level 5|r - 30% chance, deals 210 damage
Forbidden Wrath

Outburst the anger of the caster and summons 3 powerful beasts. A Red Dragon, Salamander Lord and a Dragon Turtle. During the entire duration of the spell, the caster dissapears and cannot gain level. The caster will be back to normal when the spell expires or if all 3 beast dies. Beasts life lasts 60 seconds.

Locust Raid

Twin Wolves

Enchanted Bottle

Forbidden Wrath


JASS:
/*
Spell Name: Locust Raid
Made by: mckill2009

REQUIRES:
- Jass New Gen Pack (JNGP) by Vexorian
- CTL by Nesthaurus
- SpellEffectEvent by Bribe
- SummonedEscort by mckill2009
*/

scope LocustRaid

globals
    private constant integer               SPELL_ID = 'A000' //The Main spell
    private constant integer              BOTTLE_ID = 'h001'
    private constant integer       SUMMONED_UNIT_ID = 'h003'
    private constant real                       AOE = 300 //enemy filter for passive and active
    private constant real                    HEIGHT = 300 //bottle height
    private constant real                     SPEED = 10 //bottle speed
    //===USED ONLY BY PASSIVE SPELL
    private constant integer       PASSIVE_SPELL_ID = 'A001' //Passive spell, optional
    private constant boolean   ENABLE_PASSIVE_SPELL = true
    private constant attacktype                 ATK = ATTACK_TYPE_MAGIC //used only by passive
    private constant damagetype                 DMG = DAMAGE_TYPE_POISON //used only by passive
    private constant string                     SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl" //used only by passive
    //===NON-CONFIGURABLE
    private unit TempUnit
    private group G = CreateGroup()
endglobals

private function GetChance takes integer level returns integer
    return 5 * level + 5 //used by passive only
endfunction

private function GetDamage takes integer level returns real
    return 30. * level + 60 //used by passive only
endfunction

private function SummonedCount takes integer level returns integer
    return level * 2
endfunction

private function SummonedTimer takes integer level returns real
    return level * 10.
endfunction

private function UnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_DEAD)
endfunction

private function UnitInRange takes nothing returns boolean
    return UnitAlive(GetFilterUnit()) and IsUnitEnemy(TempUnit, GetOwningPlayer(GetFilterUnit()))
endfunction

private function EnemyFilter takes unit u returns boolean
    return UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) /*
        */ and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

private struct Engage extends array
    unit u
    unit target
    real interval
    
    implement CTLExpire
        if UnitAlive(.target) and UnitAlive(.u) then
            set .interval = .interval + 0.03125
            if .interval > 1.5 then
                call IssueTargetOrder(.u, "attack", .target)          
            endif          
        else
            call MakeUnitSelectable(.u)
            set .u = null
            set .target = null           
            call .destroy()
        endif
    implement CTLEnd
    
    static method register takes unit caster, unit u returns nothing
        local thistype this
        local unit first 
        set TempUnit = u
        set first = GetClosestUnitInRange(GetUnitX(u), GetUnitY(u), AOE, Filter(function UnitInRange))         
        if first==null then
            call SE.summoned(caster, u)
        else
            set this = thistype.create()
            set .u = u
            set .target = first
            set .interval = 0
            set first = null
        endif
    endmethod
endstruct

private struct Cast extends array
    unit caster
    unit bottle
    real xUnit
    real yUnit
    real xLoc
    real yLoc
    real damage
    real distance
    real distX
    real angle
    real summonedLife
    real cos
    real sin
    integer summonedCount
    integer level
    player pl
    
    implement CTL
        local unit tempUnit
        local integer count
        local real ht
    implement CTLExpire
        if .distance > .distX then
            set .distX = .distX + SPEED
            call SetUnitX(.bottle, .xUnit+.distX * .cos)
            call SetUnitY(.bottle, .yUnit+.distX * .sin)
            set ht = (4 * HEIGHT / .distance) * (.distance - .distX) * (.distX / .distance)
            call SetUnitFlyHeight(.bottle, ht, 0)
        else
            static if ENABLE_PASSIVE_SPELL then
                if UnitAlive(.caster) then
                    set level = GetUnitAbilityLevel(.caster, PASSIVE_SPELL_ID)
                    if level > 0 and GetRandomInt(0, 100) < GetChance(.level) then
                        call GroupEnumUnitsInRange(G, .xLoc, .yLoc, AOE, null)
                        loop
                            set tempUnit = FirstOfGroup(G)
                            exitwhen tempUnit==null
                            if EnemyFilter(tempUnit) and IsUnitEnemy(tempUnit, .pl) then
                                call DestroyEffect(AddSpecialEffectTarget(SFX, tempUnit, "origin"))
                                call UnitDamageTarget(.bottle, tempUnit, GetDamage(level), false, false, ATK, DMG, null)                        
                            endif
                            call GroupRemoveUnit(G, tempUnit)
                        endloop
                    endif
                endif    
            endif
            call KillUnit(.bottle)
            set count = 0
            loop
                exitwhen count==.summonedCount
                set count = count + 1
                set tempUnit = CreateUnit(.pl, SUMMONED_UNIT_ID, .xLoc, .yLoc, 0)
                call Engage.register(.caster, tempUnit)
                call UnitApplyTimedLife(tempUnit, 'BTLF', .summonedLife)
                call MakeUnitUnSelectable(tempUnit, 0)
                call SetUnitInvulnerable(tempUnit, true)
                set tempUnit = null
            endloop
            set .caster = null
            set .bottle = null
            set .pl = null
            call .destroy()
        endif
    implement CTLEnd
    
    private static method cast takes nothing returns nothing
        local thistype this = create()
        local real dx
        local real dy
        set .caster = GetTriggerUnit()
        set .level = GetUnitAbilityLevel(.caster, SPELL_ID)
        set .xUnit = GetUnitX(.caster)
        set .yUnit = GetUnitY(.caster)
        set .xLoc = GetSpellTargetX()
        set .yLoc = GetSpellTargetY()
        set dx = .xLoc - .xUnit
        set dy = .yLoc - .yUnit
        set .distance = SquareRoot(dx * dx + dy * dy)
        set .distX = 0
        set .summonedCount = SummonedCount(.level)      
        set .summonedLife = SummonedTimer(.level)
        set .angle = Atan2(.yLoc-.yUnit, .xLoc-.xUnit)
        set .pl = GetTriggerPlayer()
        set .bottle = CreateUnit(.pl, BOTTLE_ID , .xUnit, .yUnit, 0)
        set .cos = Cos(.angle)
        set .sin = Sin(.angle)
        call SetUnitFlyHeight(.bottle, GetUnitFlyHeight(.caster)+100, 0)
        if UnitAddAbility(.bottle, 'Arav') and UnitRemoveAbility(.bottle, 'Arav') then
        endif
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(SPELL_ID, function thistype.cast)
    endmethod    
endstruct

endscope

JASS:
/*
Spell Name: Twin Wolves
Made by: mckill2009

REQUIRES:
- Jass New Gen Pack (JNGP) by Vexorian
- RegisterPlayerUnitEvent by Magtheridon96
- Table by Bribe
- SimError by Vexorian
- SummonedEscort by mckill2009
*/

scope TwinWolves

globals
    private constant integer              SPELL_ID = 'A002'
    private constant integer               WOLF_ID = 'n000'
    private constant integer     FOR_ALLY_SPELL_ID = 'A004' //rejuvenation
    private constant integer    FOR_ENEMY_SPELL_ID = 'A005' //chainlightning
    private constant integer          FOR_ALLY_OID = 852160 //this should match the FOR_ALLY_SPELL_ID
    private constant integer         FOR_ENEMY_OID = 852119 //this should match the FOR_ENEMY_SPELL_ID    
    private constant real                      AOE = 600.
    private constant string             SFX_APPEAR = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdl"
    private constant real                   PERIOD = 1.0 //how fast the wolves will cast spells, affected by cooldowns
    private constant real                WOLF_LIFE = 60.
    //===NON-CONFIGURABLE
    private Table c
    private unit FilterUnit
    private unit TempUnit
endglobals

private function UnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_DEAD)
endfunction

private function UnitAllyInRange takes nothing returns boolean
    set FilterUnit = GetFilterUnit()
    return UnitAlive(FilterUnit) and not IsUnitEnemy(TempUnit, GetOwningPlayer(FilterUnit)) /*
        */ and not IsUnitType(FilterUnit, UNIT_TYPE_STRUCTURE) and not IsUnitType(FilterUnit, UNIT_TYPE_MECHANICAL) /*
        */ and not IsUnitType(FilterUnit, UNIT_TYPE_MAGIC_IMMUNE) and GetWidgetLife(FilterUnit) < GetUnitState(FilterUnit, UNIT_STATE_MAX_LIFE)
endfunction

private function UnitEnemyInRange takes nothing returns boolean
    set FilterUnit = GetFilterUnit()
    return UnitAlive(FilterUnit) and IsUnitEnemy(TempUnit, GetOwningPlayer(FilterUnit)) /*
        */ and not IsUnitType(FilterUnit, UNIT_TYPE_STRUCTURE) and not IsUnitType(FilterUnit, UNIT_TYPE_MECHANICAL) /*
        */ and not IsUnitType(FilterUnit, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

struct TW
    unit caster
    unit healer
    unit damager
    real duration
    
    private static integer index = 0
    private static integer array indexAR
    private static timer t = CreateTimer()
    
    private static method periodic takes nothing returns nothing
        local thistype this
        local integer i = 0
        local unit first
        loop
            set i = i+1
            set this = indexAR[i]            
            if .duration > 0 and UnitAlive(.caster) then
                set .duration = .duration - PERIOD
                set TempUnit = .caster
                if UnitAlive(.damager) then                    
                    set first = GetClosestUnitInRange(GetUnitX(.damager), GetUnitY(.damager), AOE, Filter(function UnitEnemyInRange))  
                    if first!=null then
                        call IssueTargetOrderById(.damager, FOR_ENEMY_OID, first)
                    endif
                endif
                if UnitAlive(.healer) then
                    set first = GetClosestUnitInRange(GetUnitX(.healer), GetUnitY(.healer), AOE, Filter(function UnitAllyInRange))  
                    if first!=null then
                        call IssueTargetOrderById(.healer, FOR_ALLY_OID, first)
                    endif                
                endif
            else
                call c.remove(GetHandleId(.caster))
                call KillUnit(.damager)
                call KillUnit(.healer)
                set .caster = null
                set .damager = null
                set .healer = null
                call .destroy()
                set indexAR[i] = indexAR[index]               
                set indexAR[index] = this
                set index = index - 1
                set i = i-1
                if index==0 then
                    call PauseTimer(t)
                endif                 
            endif
            exitwhen i==index
        endloop
        set first = null
    endmethod  
    
    private static method cast takes nothing returns nothing
        local unit u
        local integer level
        local real facing
        local real x
        local real y
        local thistype this
        if GetSpellAbilityId()==SPELL_ID then
            set u = GetTriggerUnit()
            if c.has(GetHandleId(u)) then
                call IssueImmediateOrder(u, "stop")
                call SimError(GetTriggerPlayer(), "you cant use this spell yet")
            else
                set this = allocate() 
                set x = GetUnitX(u)
                set y = GetUnitY(u)
                set facing = GetUnitFacing(u)*0.017453333
                set .caster = u
                set level = GetUnitAbilityLevel(u, SPELL_ID)
                //===Healer
                set .healer = CreateUnit(GetTriggerPlayer(), WOLF_ID, x+150*Cos(facing+1.5708), y+150*Sin(facing+1.5708), GetUnitFacing(u))
                call DestroyEffect(AddSpecialEffectTarget(SFX_APPEAR, .healer, "origin"))
                call UnitAddAbility(.healer, FOR_ALLY_SPELL_ID)
                call SetUnitAbilityLevel(.healer, FOR_ALLY_SPELL_ID, level)
                call MakeUnitUnSelectable(.healer, 0)
                call SE.summoned(.caster, .healer)
                //===Damager
                set .damager = CreateUnit(GetTriggerPlayer(), WOLF_ID, x+150*Cos(facing-1.5708), y+150*Sin(facing-1.5708), GetUnitFacing(u))            
                call DestroyEffect(AddSpecialEffectTarget(SFX_APPEAR, .damager, "origin"))
                call UnitAddAbility(.damager, FOR_ENEMY_SPELL_ID)
                call SetUnitAbilityLevel(.damager, FOR_ENEMY_SPELL_ID, level)
                call MakeUnitUnSelectable(.damager, 0)
                call SE.summoned(.caster, .damager)
                set .duration = WOLF_LIFE
                set c[GetHandleId(u)] = 0
                if index==0 then
                    call TimerStart(t, PERIOD, true, function thistype.periodic) 
                endif
                set index = index + 1
                set indexAR[index] = this        
            endif        
            set u = null
        endif
    endmethod  

    private static method onInit takes nothing returns nothing
        local unit u = CreateUnit(Player(15), WOLF_ID, 0,0,0)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.cast)
        set c = Table.create()
        //Preloading
        call UnitAddAbility(u, FOR_ENEMY_SPELL_ID)
        call UnitAddAbility(u, FOR_ALLY_SPELL_ID)
        call ShowUnit(u, false)
        call RemoveUnit(u)
        set u = null      
    endmethod
endstruct

endscope

JASS:
/*
Spell inside Locust Raid Spell
*/

JASS:
/*
Spell Name: ForbiddenWrath
Made by: mckill2009

REQUIRES:
- Jass New Gen Pack (JNGP) by Vexorian
- SpellEffectEvent by Bribe
- Table by Bribe
*/

scope ForbiddenWrath

globals
    private constant integer        SPELL_ID = 'A003'
    private constant integer      MAX_AVATAR = 3
    private constant real           DURATION = 60. 
    private constant string       SFX_RETAIN = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
    private constant string       SFX_APPEAR = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdl"
    //===NON-CONFIGURABLES:
    private integer array avatarID
    private TableArray c
endglobals

private struct FW
    unit u
    real duration
    integer check
    
    private static integer array indexAR
    private static integer index = 0
    
    static method periodic takes nothing returns nothing
        local thistype this
        local integer i = 0
        local integer count
        local integer id
        local unit avatar
        loop
            set i = i+1
            set this = indexAR[i]
            set .duration = .duration - 0.03125
            set id = GetHandleId(.u)
            set count = 0
            loop
                set avatar = c[count].unit[id]
                if IsUnitType(avatar, UNIT_TYPE_DEAD) and c[1][GetHandleId(avatar)]==0 then
                    set .check = .check - 1
                    set c[1][GetHandleId(avatar)] = 1
                endif
                set count = count + 1
                exitwhen count==MAX_AVATAR
            endloop     
            if 0 > .duration or .check==0 then
                set count = 0
                loop
                    set avatar = c[count].unit[id]
                    if not IsUnitType(avatar, UNIT_TYPE_DEAD) then
                        call KillUnit(avatar)
                    endif
                    set count = count + 1
                    exitwhen count==MAX_AVATAR
                endloop
                call SetUnitPropWindow(.u, 1)
                call UnitRemoveAbility(.u, 'Abun')
                call UnitRemoveAbility(.u, 'Avul')
                call SuspendHeroXP(.u, false)
                call ShowUnit(.u, true)  
                call DestroyEffect(AddSpecialEffectTarget(SFX_RETAIN, .u, "origin"))
                set .u = null
                call .destroy()
                set indexAR[i] = indexAR[index]               
                set indexAR[index] = this
                set index = index - 1
                set i = i-1
                if index==0 then
                    call PauseTimer(GetExpiredTimer())
                    call DestroyTimer(GetExpiredTimer())
                endif 
            endif         
            exitwhen i==index
        endloop
        set avatar = null
    endmethod
    
    static method cast takes nothing returns nothing
        local thistype this = allocate()
        local integer count = 0
        local real angle = 0
        local integer id
        local real x
        local real y
        local unit avatar
        set .u = GetTriggerUnit()
        set .duration = DURATION
        set x = GetUnitX(.u)
        set y = GetUnitY(.u)
        set id = GetHandleId(.u)
        loop
            set avatar = CreateUnit(GetTriggerPlayer(), avatarID[count], x+150*Cos(angle*0.01745333), y+150*Sin(angle*0.01745333), GetUnitFacing(.u))
            set c[count].unit[id] = avatar
            call DestroyEffect(AddSpecialEffectTarget(SFX_APPEAR, avatar, "origin"))
            set c[1][GetHandleId(avatar)] = 0     
            set angle = angle + 40
            set count = count + 1
            set avatar = null
            exitwhen count==MAX_AVATAR
        endloop
        set .check = MAX_AVATAR
        call SetUnitPropWindow(.u, 0)
        call UnitAddAbility(.u, 'Abun')
        call UnitAddAbility(.u, 'Avul')
        call SuspendHeroXP(.u, true)
        call ShowUnit(.u, false)      
        if index==0 then
            call TimerStart(CreateTimer(), 0.04, true, function thistype.periodic) 
        endif
        set index = index + 1
        set indexAR[index] = this
    endmethod
    
    static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(SPELL_ID, function thistype.cast)
        set c = TableArray[0x200]
        set avatarID[0] = 'nrwm' //red dragon
        set avatarID[1] = 'nsll' //salamander lord
        set avatarID[2] = 'ntrd' //dragon turtle
    endmethod    
endstruct

endscope


CTL by Nesthaurus
Table by Bribe
RegisterPlayerunitEvent by Magtheridon96
GetClosestWidget by Spinnaker
SummonedEscort by mckill2009
UnSelectableUnit by mckill2009
SimError by Vexorian

Skin and Warden Icon by 67chrome
DISPASGreenBottle by Static

- None ATM

v1.2
- Fixed bug that Twin Wolf dont cast chainlightning
- Changes applied

v1.1
- Passive ability and Locust Raid level fixed
- Most suggestion by Maker applied


Keywords:
pack, mckill2009, vexorian, elf, blood, summon, ai, dota, night, attack, fire, cold
Contents

The Dark Elf v1.2 (Map)

Reviews
23:56, 22nd Jun 2013 PurgeandFire: Review: http://www.hiveworkshop.com/forums/2365597-post9.html Changes made. Approved.

Moderator

M

Moderator

23:56, 22nd Jun 2013
PurgeandFire: Review:
http://www.hiveworkshop.com/forums/2365597-post9.html

Changes made. Approved.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
-implement CTL and implement CTLNull are optional. As you do not declare locals or null them, you can remove those lines
-I prefer using ...and not (b1 or b2 or b3 or b4) over ...and not b1 and not b2 and not b3 and not b4. Just a personal preference
-Add GetUnitTypeId(unit) != 0 to the IsAlive check
-The angle in Cast struct doesn't change, so you could precalculate Cos(angle) and Sin(angle)
 
Last edited:
Locust Raid

  • In "register", you should inline first. Just put local unit first = GetClosestUnitInRange(...).
  • In "register", make sure you null the variable first.
  • In "cast", GetOwningPlayer(caster) -> GetTriggerPlayer()
  • You shouldn't use bj_lastCreatedGroup, it can be problematic for GUI users since it will clear the group. Use your own global group.

Twin Wolves
  • In the onInit function, you should just have local unit u = CreateUnit(...) rather than having it over two lines.
  • For "set .healer = ...Cos(facing-90)" etc.. [all the facing+90 or facing-90], you should have facing-bj_PI/2 instead. Otherwise it will return the wrong angles.
  • Null first in the periodic function.

Forbidden Wrath
  • In the static method "cast", Cos(...) or Sin(...) you are inputting degrees when you should input radians.
  • You should set "avatar" to null at the end of the function, not inside the loop. Variables can only point to one object (excluding arrays), so when you assign it to something else, you are effectively clearing its reference to the last object.
  • ^Same for periodic.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Locust Raid

  • In "register", you should inline first. Just put local unit first = GetClosestUnitInRange(...).
  • In "register", make sure you null the variable first.
  • In "cast", GetOwningPlayer(caster) -> GetTriggerPlayer()
  • You shouldn't use bj_lastCreatedGroup, it can be problematic for GUI users since it will clear the group. Use your own global group.

Twin Wolves
  • In the onInit function, you should just have local unit u = CreateUnit(...) rather than having it over two lines.
  • For "set .healer = ...Cos(facing-90)" etc.. [all the facing+90 or facing-90], you should have facing-bj_PI/2 instead. Otherwise it will return the wrong angles.
  • Null first in the periodic function.

Forbidden Wrath
  • In the static method "cast", Cos(...) or Sin(...) you are inputting degrees when you should input radians.
  • You should set "avatar" to null at the end of the function, not inside the loop. Variables can only point to one object (excluding arrays), so when you assign it to something else, you are effectively clearing its reference to the last object.
  • ^Same for periodic.

Changes applied except the first one in Locust Raid, I can't do that coz Im saving the TempUnit global first, as you know I cant put it above locals...
SOmeone didnt noticed the bug that the wolf cant cast chainlightning and the orderID configurable cannot be used :D, but it's already been fixed...
 
Top