Spellpack The Necro Walker

Dinowc

don't expect anything, prepare for everything
Reaction score
223
Morbent Splinterspine
it's been a long time since I've posted my last spell
now I've decided to post a whole spellpack
screenshots are useless, but since everybody wants them, they are here :rolleyes:

Bone Spear
Releases a bone spear towards target location that pierces trough enemy units dealing physical damage and reduces their armor. Nearby Skeleton Warriors are also transformed into bone spears, dealing bonus damage. Armor reduction stacks up to 5 times.

Level 1 - Deals 60 damage (10% of skeleton's HP in bonus damage), -1 armor.
Level 2 - Deals 100 damage (15% of skeleton's HP in bonus damage), -2 armor.
Level 3 - Deals 140 damage (20% of skeleton's HP in bonus damage), -3 armor.
Level 4 - Deals 180 damage (25% of skeleton's HP in bonus damage), -4 armor.

bonespear.jpg

JASS:
scope bonespear initializer init

//CONFIGURABLE:
globals
    public constant integer SPELL_ID = 'A002'
    private constant integer SPELL_UPGRADE_ID = 'A00B'
    private constant integer SPEAR_ID = 'h001'
    private constant integer PIERCE_DUMMY = 'h003'
    private constant integer PIERCE_ID = 'A007'
    private constant integer PIERCE_BUFF = 'B001' // btw pierce == armor reduction spell
    private constant integer SKELETON1_ID = 'u001'
    private constant integer SKELETON2_ID = 'u002'
    private constant integer SKELETON3_ID = 'u003'
    private constant integer SKELETON4_ID = 'u004' // if you want more levels, you'll have to edit the code a bit
    
    private constant real INTERVAL = 0.03125
    
    private constant real DAMAGE_BASE = 60.
    private constant real DAMAGE_INCREMENT = 40.
    private constant real DAMAGE_AOE = 75.
    private constant real RANGE_BASE = 800.
    private constant real RANGE_INCREMENT = 50.
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
    
    private constant real SPEED = 40.
    private constant real OFFSET = 50. // offset from unit where the spear is initialy created
    private constant boolean DESTROY_TREES = true
    private constant string DAMAGE_EFFECT = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
    
    private constant boolean TRANSFORM_SKELETONS = true
    private constant real DAMAGE_BONUS = 10. // percent of skeleton's HP added in damage
    private constant real PERCENT_INCREMENT = 5.
    private constant real SEARCH_AOE = 300. // AOE at which nearby skellies are detected
    
    private constant boolean REDUCE_ARMOR = true // armor reductions are set in object editor
endglobals

//NOT SO MUCH CONFIGURABLE:
globals
    private timer T = CreateTimer()
    private group G = CreateGroup()
    private group SKELETONS = CreateGroup()
    private integer COUNT = 0
    private real MaxX
    private real MaxY
    private real MinX
    private real MinY
    private player OWNER
    private unit CASTER
    private real ANGLE
    private real DAMAGE
    private group DAMAGED = CreateGroup()
    private rect R
    private unit DUMMY
    private integer LVL
    private integer array BUFF_LEVEL
    public boolean array DIRECTION
    private real X
    private real Y
endglobals

private function Range takes integer lvl returns real
    return RANGE_BASE + (lvl - 1) * RANGE_INCREMENT
endfunction

private function Damage takes integer lvl returns real
    return DAMAGE_BASE + (lvl - 1) * DAMAGE_INCREMENT
endfunction

private function DamageBonus takes integer lvl returns real
    return DAMAGE_BONUS + (lvl - 1) * PERCENT_INCREMENT
endfunction

private function SafeX takes real x returns real
    if x > MaxX then
        return MaxX
    elseif x < MinX then
        return MinX
    endif
    return x
endfunction

private function SafeY takes real y returns real
    if y > MaxY then
        return MaxY
    elseif y < MinY then
        return MinY
    endif
    return y
endfunction

private struct spear extends array
//! runtextmacro AIDS()
    unit owner
    real sin
    real cos
    real dist
    real range
    real dmgBonus
    group g
    integer spell
    
    method AIDS_onCreate takes nothing returns nothing
        set this.dist = OFFSET
        set this.g = NewGroup()
        set this.dmgBonus = 0.
    endmethod
    
    static method AIDS_filter takes unit u returns boolean
        return GetUnitTypeId(u) == SPEAR_ID
    endmethod
    
    method AIDS_onDestroy takes nothing returns nothing
        call ReleaseGroup(this.g)
        set COUNT = COUNT - 1
        if COUNT == 0 then
            call PauseTimer(T)
        endif
    endmethod
endstruct

private function DamageFilter takes nothing returns boolean
    local unit picked = GetFilterUnit()
    local integer i
    if IsUnitEnemy(picked, OWNER) and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and GetWidgetLife(picked) > 0. and IsUnitInGroup(picked, DAMAGED) == false then
        call UnitDamageTarget(CASTER, picked, DAMAGE, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
        call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, picked, "chest"))
        call GroupAddUnit(DAMAGED, picked)
        static if REDUCE_ARMOR then
            set i = GetUnitId(picked)
            call SetUnitAbilityLevel(DUMMY, PIERCE_ID, LVL + (BUFF_LEVEL<i> * 4))
            call SetUnitX(DUMMY, GetUnitX(picked))
            call SetUnitY(DUMMY, GetUnitY(picked))
            call IssueTargetOrder(DUMMY, &quot;innerfire&quot;, picked)
            if GetUnitAbilityLevel(picked, PIERCE_BUFF) &gt; 0 then
                if BUFF_LEVEL<i> &lt; 4 then
                    set BUFF_LEVEL<i> = BUFF_LEVEL<i> + 1
                endif
            else
                set BUFF_LEVEL<i> = 0
            endif
        endif
    endif
    set picked = null
    return false
endfunction

private function filterTrees takes nothing returns boolean
    local destructable d = GetFilterDestructable()
    if GetDestructableLife(d) &gt; 0. then
        call KillDestructable(d)
    endif
    set d = null
    return false
endfunction

private function MoveSkellies takes nothing returns nothing
    local unit picked = GetEnumUnit()
    local spear d = spear[picked]
    local real x = SafeX(GetUnitX(picked) + d.cos)
    local real y = SafeY(GetUnitY(picked) + d.sin)
    call SetUnitX(picked, x)
    call SetUnitY(picked, y)
    if d.dist &lt;= spear[picked].range then
        set CASTER = d.owner
        set OWNER = GetOwningPlayer(CASTER)
        set LVL = GetUnitAbilityLevel(CASTER, d.spell)
        set DAMAGE = Damage(LVL) + d.dmgBonus
        set DAMAGED = d.g

        static if REDUCE_ARMOR then
            call SetUnitOwner(DUMMY, OWNER, false)
        endif
        
        call GroupEnumUnitsInRange(G, x, y, DAMAGE_AOE, Filter(function DamageFilter))
        if DESTROY_TREES then
            call MoveRectTo(R, x, y)
            call EnumDestructablesInRect(R, Filter(function filterTrees), null)
        endif
    else
        call KillUnit(picked)
        call GroupRemoveUnit(SKELETONS, picked)
    endif
    set d.dist = d.dist + SPEED
    set picked = null
endfunction

private function callback takes nothing returns nothing
    call ForGroup(SKELETONS, function MoveSkellies)
endfunction

private function filterSkellies takes nothing returns boolean
    local unit picked = GetFilterUnit()
    local unit skeleton
    local integer i = GetUnitTypeId(picked)
    local spear d
    if (i == SKELETON1_ID or i == SKELETON2_ID or i == SKELETON3_ID or i == SKELETON4_ID) and GetOwningPlayer(picked) == OWNER and GetWidgetLife(picked) &gt; 0. then
        set skeleton = CreateUnit(OWNER, SPEAR_ID, GetUnitX(picked), GetUnitY(picked), ANGLE)
        call GroupAddUnit(SKELETONS, skeleton)
        
        set d = spear[skeleton]
        if DIRECTION[LVL] == false then
            set d.cos = PolarX(SPEED, ANGLE)
            set d.sin = PolarY(SPEED, ANGLE)
        else
            set ANGLE = GetAngle(GetUnitX(picked), GetUnitY(picked), X, Y)
            set d.cos = PolarX(SPEED, ANGLE)
            set d.sin = PolarY(SPEED, ANGLE)
        endif
        
        set d.owner = CASTER
        set i = GetUnitAbilityLevel(CASTER, d.spell)
        set d.range = Range(i)
        set d.dmgBonus = GetUnitState(picked, UNIT_STATE_LIFE) * 0.01 * DamageBonus(i)
        call KillUnit(picked)
        set COUNT = COUNT + 1
    endif
    set skeleton = null
    set picked = null
    return false
endfunction

private function actions takes nothing returns boolean
    local unit caster
    local real angle
    local real x
    local real y
    local unit skeleton
    local integer i = GetSpellAbilityId()
    local spear d
    if i == SPELL_ID or i == SPELL_UPGRADE_ID then
        set caster = GetTriggerUnit()
        set x = GetUnitX(caster)
        set y = GetUnitY(caster)
        set X = GetSpellTargetX()
        set Y = GetSpellTargetY()
        set angle = GetAngle(x, y, X, Y)
        set OWNER = GetOwningPlayer(caster)

        set skeleton = CreateUnit(OWNER, SPEAR_ID, x + PolarX(OFFSET, angle), y + PolarY(OFFSET, angle), angle)
        call GroupAddUnit(SKELETONS, skeleton)
        set d = spear[skeleton]
        set d.cos = PolarX(SPEED, angle)
        set d.sin = PolarY(SPEED, angle)
        set d.owner = caster
        set d.range = Range(GetUnitAbilityLevel(caster, i))
        set d.spell = i
        
        if COUNT == 0 then
            call TimerStart(T, INTERVAL, true, function callback)
        endif
        set COUNT = COUNT + 1
        set ANGLE = angle
        set CASTER = caster
        set LVL = GetUnitId(caster)
        
        static if TRANSFORM_SKELETONS then
            call GroupEnumUnitsInRange(G, x, y, SEARCH_AOE, Filter(function filterSkellies))
        endif
    endif
    set caster = null
    set skeleton = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    
    set MaxX = GetRectMaxX(bj_mapInitialPlayableArea) - 50.
    set MaxY = GetRectMaxY(bj_mapInitialPlayableArea) - 50.
    set MinX = GetRectMinX(bj_mapInitialPlayableArea) + 50.
    set MinY = GetRectMinY(bj_mapInitialPlayableArea) + 50.
    set R = Rect(-DAMAGE_AOE, -DAMAGE_AOE, DAMAGE_AOE, DAMAGE_AOE)
    set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), PIERCE_DUMMY, 0., 0., 0.)
endfunction
endscope
</i></i></i></i></i>


Necromancy
Steals souls of nearby dying non-hero units, healing Necro Walker and storing the soul. He can release stored souls upon group of corpses, raising Skeleton Warriors from them. Souls deal damage as they fly trough enemy units. Necro Walker loses a soul every 30 seconds.

Level 1 - Heals by 5% of dying unit's max HP, souls deal 60 damage, can store up to 4 souls, summons lesser skeletons.
Level 2 - Heals by 7% of dying unit's max HP, souls deal 90 damage, can store up to 6 souls, summons skeletons.
Level 3 - Heals by 9% of dying unit's max HP, souls deal 120 damage, can store up to 8 souls, summons greater skeletons.
Level 4 - Heals by 11% of dying unit's max HP, souls deal 150 damage, can store up to 10 souls, summons elite skeletons with critical strike.

necromancy.jpg

JASS:
scope necromancy initializer init

//CONFIGURABLE:
globals
    private constant integer SPELL_ID = &#039;A000&#039;
    private constant integer SOUL_ID = &#039;h000&#039;
    private constant integer RAISE_DEAD = &#039;A001&#039;
    
    private constant real INTERVAL = 0.03125
    private constant real MOVESPEED = 15.
    private constant real SEARCH_AOE = 1000. // AOE at which is a nearby hero detected; closest hero with this ability learned will steal the soul
    private constant integer MAX_SOULS_BASE = 4
    private constant integer MAX_SOULS_INCREMENT = 2
    private constant string SFX = &quot;war3mapImported\\InvisibilityTarget.mdx&quot;
    private constant string SFX_ATTACH = &quot;hand right&quot;
    private constant string SOUL_INDICATION = &quot;war3mapImported\\Fire.mdx&quot; // indicates when you have stored max amount of souls
    private constant string INDICATION_ATTACH = &quot;hand right&quot;
    
    private constant real HEAL_PERCENT_BASE = 3.
    private constant real HEAL_PERCENT_INCREMENT = 1.
    
    private constant boolean RAW_HEAL = false // setting this to true will heal the unit by a specified raw value instead
    private constant real RAW_HEAL_BASE = 60.
    private constant real RAW_HEAL_INCREMENT = 30.
    
    private constant real DAMAGE_BASE = 60.
    private constant real DAMAGE_INCREMENT = 30.
    private constant real DAMAGE_AOE = 100.
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
    
    private constant real AOE_BASE = 400.
    private constant real AOE_INCREMENT = 50. // these values should obviously be the same as they are in object editor
endglobals

//NOT SO MUCH CONFIGURABLE:
globals
    private unit HERO
    private unit SOUL
    private integer COUNT
    private boolean IS_DEAD
    private real DISTANCE = SEARCH_AOE + 100.
    private real X1
    private real Y1
    private real X2
    private real Y2
    private group G = CreateGroup()
    private group G2 = CreateGroup()
endglobals

private function HealPercent takes integer lvl returns real
    return HEAL_PERCENT_BASE + (lvl - 1) * HEAL_PERCENT_INCREMENT
endfunction

private function HealRaw takes integer lvl returns real
    return RAW_HEAL_BASE + (lvl - 1) * RAW_HEAL_INCREMENT
endfunction

private function MaxSouls takes integer lvl returns integer
    return MAX_SOULS_BASE + (lvl - 1) * MAX_SOULS_INCREMENT
endfunction

private function AoE takes integer lvl returns real
    return AOE_BASE + (lvl - 1) * AOE_INCREMENT
endfunction

//===========================================================

private struct dead extends array
//! runtextmacro AIDS()
    boolean IsDead
    
    method AIDS_onCreate takes nothing returns nothing
        set this.IsDead = false
    endmethod
    
    static method AIDS_filter takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_HERO) == false and IsUnitType(u, UNIT_TYPE_SUMMONED) == false and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) == false
    endmethod
endstruct

private struct data extends array
//! runtextmacro AIDS()
    group g
    timer t
    integer count
    effect sfx
    
    method AIDS_onCreate takes nothing returns nothing
        set this.g = NewGroup()
        set this.t = NewTimer()
        call SetTimerData(this.t, this)
        set this.count = 0
    endmethod
    
    method AIDS_onDestroy takes nothing returns nothing
        call ReleaseTimer(this.t)
        call ReleaseGroup(this.g)
    endmethod
    
    static method AIDS_filter takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_HERO)
    endmethod
endstruct

private struct soul extends array
//! runtextmacro AIDS()
    unit target
    group g
    boolean b
    real heal
    
    method AIDS_onCreate takes nothing returns nothing
        set this.g = NewGroup()
        set this.b = true
        set this.heal = 0.
    endmethod
    
    method AIDS_onDestroy takes nothing returns nothing
        call ReleaseGroup(this.g)
    endmethod
    
    static method AIDS_filter takes unit u returns boolean
        return GetUnitTypeId(u) == SOUL_ID
    endmethod
endstruct

//===========================================================

private function DamageAmount takes integer lvl returns real
    return DAMAGE_BASE + (lvl - 1) * DAMAGE_INCREMENT
endfunction

private function filterDamage takes nothing returns boolean
    local unit picked = GetFilterUnit()
    if IsUnitEnemy(picked, GetOwningPlayer(HERO)) and IsUnitInGroup(picked, soul[SOUL].g) == false and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and GetWidgetLife(picked) &gt; 0. then
        call UnitDamageTarget(SOUL, picked, DamageAmount(GetUnitAbilityLevel(HERO, SPELL_ID)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
        call GroupAddUnit(soul[SOUL].g, picked)
    endif
    set picked = null
    return false
endfunction

private function MoveSouls takes nothing returns nothing
    local unit picked = GetEnumUnit()
    local real x = GetUnitX(picked)
    local real y = GetUnitY(picked)
    local real angle = GetAngle(x, y, X1, Y1)
    local real heal = GetUnitState(HERO, UNIT_STATE_LIFE)
    local integer lvl = GetUnitAbilityLevel(HERO, SPELL_ID)
    local data d
    local soul s = soul[picked]
    if s.b == true then
        call SetUnitX(picked, x + PolarX(MOVESPEED, angle))
        call SetUnitY(picked, y + PolarY(MOVESPEED, angle))
        call SetUnitFacingTimed(picked, angle, INTERVAL)
        set d = data[HERO]
        set DISTANCE = GetDistance(x, y, X1, Y1)
        
        if d.count &lt; MaxSouls(lvl) then
        
        if DISTANCE &lt; MOVESPEED + 5. and IsUnitHidden(picked) == false then
            static if RAW_HEAL then
                set heal = heal + HealRaw(lvl)
            else
                set heal = heal + s.heal
            endif
            
            call SetUnitState(HERO, UNIT_STATE_LIFE, heal)
            call GroupRemoveUnit(data[HERO].g, picked)
            call KillUnit(picked)
            set d.count = d.count + 1
            if d.count == MaxSouls(lvl) and d.sfx == null then
                set d.sfx = AddSpecialEffectTarget(SOUL_INDICATION, HERO, INDICATION_ATTACH)
            endif
            call DestroyEffect(AddSpecialEffectTarget(SFX, HERO, SFX_ATTACH))
        endif
        
        else
            call GroupRemoveUnit(d.g, picked)
            call KillUnit(picked)
        endif
    endif
    
    set SOUL = picked
    call GroupEnumUnitsInRange(G, x, y, DAMAGE_AOE, Filter(function filterDamage))
    
    set picked = null
endfunction

//=================================================

private function callback1 takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local data d = GetTimerData(t)
    set X1 = GetUnitX(d.unit)
    set Y1 = GetUnitY(d.unit)
    set HERO = d.unit
    call ForGroup(d.g, function MoveSouls)
    set HERO = null
    set t = null
endfunction

private function filterFunc takes nothing returns boolean
    local unit picked = GetFilterUnit()
    local real dist

    if IsUnitType(picked, UNIT_TYPE_HERO) and GetUnitAbilityLevel(picked, SPELL_ID) &gt; 0 then
        set X2 = GetUnitX(picked)
        set Y2 = GetUnitY(picked)
        set dist = GetDistance(X1, Y1, X2, Y2)
        if dist &lt; DISTANCE then
            set DISTANCE = dist
            set HERO = picked
        endif
    endif
    set picked = null
    return false
endfunction

private function actions2 takes nothing returns boolean
    local unit dying = GetTriggerUnit()
    local unit SOUL
    local soul s
    local data d

    if IsUnitType(dying, UNIT_TYPE_SUMMONED) == false and IsUnitType(dying, UNIT_TYPE_HERO) == false and IsUnitType(dying, UNIT_TYPE_STRUCTURE) == false then
        set X1 = GetUnitX(dying)
        set Y1 = GetUnitY(dying)
        set dead[dying].IsDead = true
        call GroupEnumUnitsInRange(G, X1, Y1, SEARCH_AOE, Filter(function filterFunc))
        set d = data[HERO]
        if HERO != null and d.count &lt; MaxSouls(GetUnitAbilityLevel(HERO, SPELL_ID)) then
            set SOUL = CreateUnit(GetOwningPlayer(HERO), SOUL_ID, X1, Y1, GetAngle(X1, Y1, X2, Y2))
            set s = soul[SOUL]
            set s.target = HERO
            
            if RAW_HEAL == false then
                set s.heal = GetUnitState(dying, UNIT_STATE_MAX_LIFE) * 0.01 * HealPercent(GetUnitAbilityLevel(HERO, SPELL_ID))
            endif
            
            call GroupAddUnit(d.g, SOUL)

            if d.count == 0 then
                call TimerStart(d.t, INTERVAL, true, function callback1)
            endif
        endif
        set DISTANCE = SEARCH_AOE + 100.
    endif
    
    set SOUL = null
    set dying = null
    return false
endfunction

//=================================================

private function RaiseDead takes nothing returns boolean
    local unit picked = GetFilterUnit()
    local unit SOUL
    local soul s
    local data d = data[HERO]
    if GetWidgetLife(picked) &lt;= 0. and d.count &gt; 0 and IsUnitInGroup(picked, G2) == false and IsUnitType(picked, UNIT_TYPE_SUMMONED) == false and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and dead[picked].IsDead == true then
        set SOUL = CreateUnit(GetOwningPlayer(HERO), SOUL_ID, GetUnitX(HERO), GetUnitY(HERO), GetUnitFacing(HERO))
        call SetUnitAbilityLevel(SOUL, RAISE_DEAD, GetUnitAbilityLevel(HERO, SPELL_ID))
        call GroupAddUnit(d.g, SOUL)
        set s = soul[SOUL]
        set s.b = false
        set s.target = HERO
        call IssueTargetOrder(SOUL, &quot;raisedead&quot;, picked)
        call GroupAddUnit(G2, picked)
        set d.count = data[HERO].count - 1
        if d.count &lt;= 0 and d.sfx != null then
            call DestroyEffect(d.sfx)
            set d.sfx = null
        endif
    endif
    set picked = null
    set SOUL = null
    return false
endfunction

private function actions1 takes nothing returns boolean
    if GetSpellAbilityId() == SPELL_ID then
        set HERO = GetTriggerUnit()
        call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE(GetUnitAbilityLevel(HERO, SPELL_ID)), Filter(function RaiseDead))
        set HERO = null
    endif
    return false
endfunction

//=================================================

private function actions3 takes nothing returns boolean
    local data d
    local unit caster = GetTriggerUnit()
    if GetUnitTypeId(caster) == SOUL_ID then
        set d = data[soul[caster].target]
        call GroupRemoveUnit(d.g, caster)
        call GroupRemoveUnit(G2, GetSpellTargetUnit())
        call KillUnit(caster)
        if d.count == 0 and CountUnitsInGroup(d.g) == 0 then
            call PauseTimer(d.t)
        endif
    endif
    set caster = null
    return false
endfunction

//=================================================

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions1))
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DECAY)
    call TriggerAddCondition(t, Condition(function actions2))
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_FINISH)
    call TriggerAddCondition(t, Condition(function actions3))
endfunction
endscope

Confusion
Curses group of enemy units causing them to attack random targets.

Level 1 - Duration 2 seconds, 300 AOE, 550 Cast Range.
Level 2 - Duration 2.75 seconds, 350 AOE, 600 Cast Range.
Level 3 - Duration 3.5 seconds, 400 AOE, 650 Cast Range.
Level 4 - Duration 4.25 seconds, 450 AOE, 700 Cast Range.

confusionw.jpg

JASS:
scope confusion initializer init

//CONFIGURABLE:
globals
    private constant integer SPELL_ID = &#039;A003&#039;
    private constant integer CURSE_DUMMY = &#039;h002&#039;
    private constant integer CURSE_SPELL = &#039;A004&#039;
    private constant integer CURSE_BUFF = &#039;B000&#039;
    
    private constant real INTERVAL = 0.1
    private constant real AOE_BASE = 300.
    private constant real AOE_INCREMENT = 50.
    private constant real SEARCH_AOE = 500.
    
    //Note: Duration depends on CURSE_SPELL duration; change it in object editor
endglobals

//NOT SO MUCH CONFIGURABLE:
globals
    private group G1 = CreateGroup()
    private group G2 = CreateGroup()
    private group CURSED = CreateGroup()
    private timer T = CreateTimer()
    private unit DUMMY
    private integer COUNT = 0
    private unit array TARGET
    private integer ID
    private player OWNER
    private unit U
endglobals

private function AoE takes integer lvl returns real
    return AOE_BASE + (lvl - 1) * AOE_INCREMENT
endfunction

private function SearchTargets takes nothing returns boolean
    local unit picked = GetFilterUnit()
    if picked != U and GetWidgetLife(picked) &gt; 0. then
        call GroupAddUnit(G2, picked)
    endif
    set picked = null
    return false
endfunction

private function ForceAttack takes nothing returns nothing
    local unit picked = GetEnumUnit()
    set ID = GetUnitId(picked)
    if TARGET[ID] == null or GetWidgetLife(TARGET[ID]) == 0 then
        set U = picked
        call GroupEnumUnitsInRange(G1, GetUnitX(picked), GetUnitY(picked), SEARCH_AOE, Filter(function SearchTargets))
        set TARGET[ID] = GroupPickRandomUnit(G2)
        call GroupClear(G2)
    else
        call ClearSelectionForPlayer(GetOwningPlayer(picked))
        call IssueTargetOrder(picked, &quot;attack&quot;, TARGET[ID])
    endif
    if GetUnitAbilityLevel(picked, CURSE_BUFF) == 0 then
        call IssueImmediateOrder(picked, &quot;stop&quot;)
        call GroupRemoveUnit(CURSED, picked)
        set COUNT = COUNT - 1
    endif
    set picked = null
endfunction

private function callback takes nothing returns nothing
    call ForGroup(CURSED, function ForceAttack)
    if COUNT == 0 then
        call PauseTimer(T)
    endif
endfunction

private function filterFunc takes nothing returns boolean
    local unit picked = GetFilterUnit()
    if IsUnitEnemy(picked, OWNER) and GetWidgetLife(picked) &gt; 0. and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false then
        call SetUnitX(DUMMY, GetUnitX(picked))
        call SetUnitY(DUMMY, GetUnitY(picked))
        call IssueTargetOrder(DUMMY, &quot;curse&quot;, picked)
        call GroupAddUnit(CURSED, picked)
        set COUNT = COUNT + 1
    endif
    set picked = null
    return false
endfunction

private function actions takes nothing returns boolean
    local unit caster
    local integer lvl
    if GetSpellAbilityId() == SPELL_ID then
        set caster = GetTriggerUnit()
        set lvl = GetUnitAbilityLevel(caster, SPELL_ID)
        set OWNER = GetOwningPlayer(caster)
        call SetUnitOwner(DUMMY, OWNER, false)
        call SetUnitAbilityLevel(DUMMY, CURSE_SPELL, lvl)
        if COUNT == 0 then
            call TimerStart(T, INTERVAL, true, function callback)
        endif
        call GroupEnumUnitsInRange(G1, GetSpellTargetX(), GetSpellTargetY(), AoE(lvl), Filter(function filterFunc))
    endif
    set caster = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    
    set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CURSE_DUMMY, 0., 0., 0.)
endfunction
endscope

Living Wall
Raises a living wall of zombies that block the path and attack enemy units that stray too close.

Level 1 - Zombies have 500 HP, deal 30 damage and last for 6 seconds.
Level 2 - Zombies have 700 HP, deal 40 damage and last for 7 seconds.
Level 3 - Zombies have 900 HP, deal 50 damage and last for 8 seconds.
Level 4 - Zombies have 1100 HP, deal 60 damage and last for 9 seconds.

livingwall.jpg

JASS:
scope wall initializer init

//CONFIGURABLE:
globals
    private constant integer SPELL_ID = &#039;A006&#039;
    private constant integer ZOMBIE1_ID = &#039;n000&#039;
    private constant integer ZOMBIE2_ID = &#039;n001&#039;
    private constant integer ZOMBIE3_ID = &#039;n002&#039;
    private constant integer ZOMBIE4_ID = &#039;n003&#039;
    
    private constant real DURATION_BASE = 6.
    private constant real DURATION_INCREMENT = 1.
    private constant integer EXPIRATION_TYPE = &#039;BTLF&#039;
    private constant real ZOMBIE_COLLISION_SIZE = 15.
    private constant integer ZOMBIE_COUNT = 8
    private constant string RAISE_EFFECT = &quot;Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl&quot;
endglobals

//NOT SO MUCH CONFIGURABLE:
globals
    private real SIZE = ZOMBIE_COLLISION_SIZE * I2R(ZOMBIE_COUNT) * 2
    private real DIST = ZOMBIE_COLLISION_SIZE * 2
    private integer array ZOMBIE
    private group G = CreateGroup()
    private timer T = CreateTimer()
endglobals

private function duration takes integer lvl returns real
    return DURATION_BASE + lvl * DURATION_INCREMENT
endfunction

private struct data
    group g
    timer t
    
    method onDestroy takes nothing returns nothing
        call ReleaseGroup(this.g)
        call ReleaseTimer(this.t)
    endmethod
endstruct

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

private function callback takes nothing returns nothing
    local data d = GetTimerData(GetExpiredTimer())
    call ForGroup(d.g, function KillZombies)
    call d.destroy()
endfunction

private function actions takes nothing returns boolean
    local real dist = 0.
    local real angle
    local real x1
    local real y1
    local real x2
    local real y2
    local real cos
    local real sin
    local unit caster
    local integer lvl
    local data d
    local unit zombie
    local player p
    if GetSpellAbilityId() == SPELL_ID then
        set d = data.create()
        set caster = GetTriggerUnit()
        set p = GetOwningPlayer(caster)
        set lvl = GetUnitAbilityLevel(caster, SPELL_ID) - 1
        set x1 = GetSpellTargetX()
        set y1 = GetSpellTargetY()
        set angle = GetAngle(GetUnitX(caster), GetUnitY(caster), x1, y1)
        set x2 = x1 + PolarX(SIZE, angle + 90.)
        set y2 = y1 + PolarY(SIZE, angle + 90.)
        set cos = PolarX(DIST * 2, angle - 90.)
        set sin = PolarY(DIST * 2, angle - 90.)
        
        set d.t = NewTimer()
        set d.g = NewGroup()
        
        loop
            call DestroyEffect(AddSpecialEffect(RAISE_EFFECT, x2, y2))
            set zombie = CreateUnit(p, ZOMBIE[lvl], x2, y2, angle)
            call SetUnitAnimation(zombie, &quot;birth&quot;)
            call QueueUnitAnimation(zombie, &quot;stand&quot;)
            call IssueImmediateOrder(zombie, &quot;holdposition&quot;)
            call GroupAddUnit(d.g, zombie)
            set x2 = x2 + cos
            set y2 = y2 + sin
            set dist = dist + DIST
            exitwhen dist &gt; SIZE
        endloop
        
        call SetTimerData(d.t, d)
        call TimerStart(d.t, duration(lvl), false, function callback)
    endif
    set zombie = null
    set caster = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    set ZOMBIE[0] = ZOMBIE1_ID
    set ZOMBIE[1] = ZOMBIE2_ID
    set ZOMBIE[2] = ZOMBIE3_ID
    set ZOMBIE[3] = ZOMBIE4_ID
endfunction
endscope

Unholy Frenzy
Enrages every nearby allied unit increasing movement and attack speed for 15 seconds. While Unholy Frenzy is active, Bone Spear cooldown is removed and any skeleton transformed spear will shoot at the target point instead.

Level 1 - Increases MS by 20% and AS by 50%.
Level 2 - Increases MS by 25% and AS by 75%.
Level 3 - Increases MS by 30% and AS by 100%.

unholyfrenzy.jpg

JASS:
scope frenzy initializer init

//CONFIGURABLES
globals
    private constant integer SPELL_ID = &#039;A008&#039;
    private constant integer SPEAR_UPGRADE_ID = &#039;A00A&#039;
    private constant integer BLOODLUST_DUMMY = &#039;h004&#039;
    private constant integer BLOODLUST_ID = &#039;A009&#039;
    private constant integer BLOODLUST_BUFF = &#039;B002&#039;
    
    private constant real INTERVAL = 0.1
    private constant real AOE_BASE = 800.
    private constant real AOE_INCREMENT = 100.
endglobals

//NOT SO MUCH CONFIGURABLE:
globals
    private unit DUMMY
    private timer T = CreateTimer()
    private group G = CreateGroup()
    private group G2 = CreateGroup()
    private player P
    private integer COUNT = 0
endglobals

private function AoE takes integer lvl returns real
    return AOE_BASE + (lvl - 1) * AOE_INCREMENT
endfunction

private function CheckUnits takes nothing returns nothing
    local unit picked = GetEnumUnit()
    if GetUnitAbilityLevel(picked, BLOODLUST_BUFF) == 0 then
        call UnitRemoveAbility(picked, SPEAR_UPGRADE_ID)
        set bonespear_DIRECTION[GetUnitId(picked)] = false
        call GroupRemoveUnit(G2, picked)
        set COUNT = COUNT - 1
    endif
    set picked = null
endfunction

private function callback takes nothing returns nothing
    call ForGroup(G2, function CheckUnits)
    if COUNT == 0 then
        call PauseTimer(GetExpiredTimer())
    endif
endfunction

private function filterFunc takes nothing returns boolean
    local unit picked = GetFilterUnit()
    if IsUnitAlly(picked, P) and GetWidgetLife(picked) &gt; 0. and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and picked != DUMMY then
        call SetUnitX(DUMMY, GetUnitX(picked))
        call SetUnitY(DUMMY, GetUnitY(picked))
        call IssueTargetOrder(DUMMY, &quot;bloodlust&quot;, picked)
    endif
    set picked = null
    return false
endfunction

private function actions takes nothing returns boolean
    local unit caster
    local integer lvl
    if GetSpellAbilityId() == SPELL_ID then
        set caster = GetTriggerUnit()
        set P = GetOwningPlayer(caster)
        set lvl = GetUnitAbilityLevel(caster, SPELL_ID)
        call SetUnitOwner(DUMMY, P, false)
        call SetUnitAbilityLevel(DUMMY, BLOODLUST_ID, lvl)
        call UnitAddAbility(caster, SPEAR_UPGRADE_ID)
        call GroupEnumUnitsInRange(G, GetUnitX(caster), GetUnitY(caster), AoE(lvl), Filter(function filterFunc))
        set bonespear_DIRECTION[GetUnitId(caster)] = true
        
        call GroupAddUnit(G2, caster)
        if COUNT == 0 then
            call TimerStart(T, INTERVAL, true, function callback)
        endif
        set COUNT = COUNT + 1
    endif
    set caster = null
    return false
endfunction

private function actions2 takes nothing returns boolean
    local unit caster
    if GetLearnedSkill() == bonespear_SPELL_ID then
        set caster = GetTriggerUnit()
        if GetUnitAbilityLevel(caster, bonespear_SPELL_ID) == 1 then
            set bonespear_DIRECTION[GetUnitId(caster)] = false
        endif
    endif
    set caster = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition(t, Condition(function actions2))
    
    set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), BLOODLUST_DUMMY, 0., 0., 0.)
endfunction
endscope

Changelog:
v1.0
-initial release
v1.1
code and tooltip fixes
Necromancy
-souls heal depending on dying unit's max HP; added a visual indication whenever max souls are stored
v1.2
code and tooltip fixes
Necromancy:
-instead of killing and creating the souls all over again, consumed souls are hidden instead; souls deal damage only when released; hero loses a soul every 30 seconds (this is optional and can be changed in CONIFGURABLE globals block)
Non-code related fixes/changes:
-fixed a bug where you would receive +2 dmg bonus when casting Unholy Frenzy
-buffed Unholy Frenzy MS bonus
-changed soul release sound
-reduced Necro Walker's cast backswing (for faster casting)
Requirements:
AIDS, TimerUtils, GroupUtils, Trigonometric functions (all included in map)
Credits:
J4L (AIDS), Vexorian (TU and GU?), Afronight_76 (model), tonic (icon), PeeKay (Bone Spear model and icon), Diablo 2 (Bone Spear :p) and Diablo 3 (Living Wall :p)
 

Attachments

  • Necro Walker v1.2.w3x
    407 KB · Views: 457

Laiev

Hey Listen!!
Reaction score
188
I think you should use PUI instead of AIDS for dynamic things.
If you're using TU, why you're using a normal timer?
If you're using GU, why you're using a normal group?
You don't need two trigger variable, just re-set the current one.

JASS:
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger t2 = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    
    call TriggerRegisterAnyUnitEventBJ( t2, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition(t2, Condition(function actions2))
    
    set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), BLOODLUST_DUMMY, 0., 0., 0.)
endfunction



>>


JASS:
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function actions))
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition(t, Condition(function actions2))
    
    set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), BLOODLUST_DUMMY, 0., 0., 0.)
endfunction
 

Weep

Godspeed to the sound of the pounding
Reaction score
401
Very cool spell pack. I haven't seen much like it, although the wall spell is probably the least unique in it. Perhaps it could be made to interact with the other spells, eg. converting the zombies to skeletons if they have Necromancy cast upon them, or allowing them to slowly move if they are affected by Unholy Frenzy?

I'd say the description of the interaction of Bone Spear and Unholy Frenzy could be improved, saying "shoot at the target point", since "in the targeted direction" is already how it works normally. Also, the practical mechanic of soul storage and release with Necromancy isn't clear.
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
thanks for feedback
the wall spell is probably the least unique in it. Perhaps it could be made to interact with the other spells, eg. converting the zombies to skeletons if they have Necromancy cast upon them
you mean when they are still alive? or make them decay as well?
it will be too imba, creating armies of skeletons would be much faster/easier
also it will kinda be forced synergy...

or allowing them to slowly move if they are affected by Unholy Frenzy?
I like this idea much better, but they are intended to just stand there and block the path
maybe make them have Return Damage? that way there will be more synergy with Confusion, though that ain't much xd

I'd say the description of the interaction of Bone Spear and Unholy Frenzy could be improved, saying "shoot at the target point", since "in the targeted direction" is already how it works normally.
I don't really care about typos or visuals at this time, but thanks anyway

Also, the practical mechanic of soul storage and release with Necromancy isn't clear.
not sure what you meant by this
you mean more souls are consumed/released than released/consumed?
you will drain a soul when it decays, not dies
you release a soul for every corpse within targeted location, if there are no corpses, you will still waste the spell

I might create an effect that indicates number of souls stored though

I think you should use PUI instead of AIDS for dynamic things.
I'm not very familiar to any indexing system other than AIDS
If you're using TU, why you're using a normal timer?
If you're using GU, why you're using a normal group?
I thought as long as they are not created dynamically, it's fine
You don't need two trigger variable, just re-set the current one.
knew, but forgot that xd
 

Romek

Super Moderator
Reaction score
963
> I'm not very familiar to any indexing system other than AIDS
AIDS > PUI anyway.

> I thought as long as they are not created dynamically, it's fine
It is.

_________________

JASS:
private function SafeX takes real x returns real
    if x &lt; MaxX then
        return x
    else
        return MaxX
    endif
    
    if x &gt; MinX then
        return x
    else
        return MinX
    endif
endfunction

Could be:
JASS:
private function SafeX takes real x returns real
    if x &gt; MaxX then
        return MaxX
    elseif x &lt; MinX
        return MinX
    endif
    return x
endfunction

Same for the 'y' equivalent.

_________________

With any sort of [ljass]if <CONSTANT>[/ljass] expression, you could use a [ljass]static if[/ljass], so the code is removed if the constant is false.

_________________

Save [ljass]spear[skeleton][/ljass] to a variable. There's a function call every time you reference that.

_________________

In the second spell, why are you using a local in this function?
JASS:
private function actions1 takes nothing returns boolean
    local data d
// ....


_________________

I'll look over this spell closely later. :p
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
some1 actually took a look at the codes :D
@Romek

1. I've changed it back
_________________

2. done.
_________________

3. did that too, but I really don't have much constant booleans anyway :/
_________________

4. done.
_________________

5. I have no idea :D
_________________

6. please do

I'm not in the mood editing this thread again xd
will upload a new version though
 

Romek

Super Moderator
Reaction score
963
> did that too, but I really don't have much constant booleans anyway :/
Oh, the standard naming convention is to have constants in ALL_CAPS and all other globals in PascalCase. I assumed all the globals in all-caps were constant, though it seems some of them are just regular globals. :p
 

Weep

Godspeed to the sound of the pounding
Reaction score
401
you mean when they are still alive? or make them decay as well? it will be too imba, creating armies of skeletons would be much faster/easier also it will kinda be forced synergy...
Well, whatever you imagine. I was just suggesting a possible type of interaction.

I like this idea much better, but they are intended to just stand there and block the pathmaybe make them have Return Damage? that way there will be more synergy with Confusion, though that ain't much xd
Damage return might work, though having them slowly move is more strategic (sacrificing blocking for more DPS).

not sure what you meant by this
you mean more souls are consumed/released than released/consumed?
you will drain a soul when it decays, not dies
you release a soul for every corpse within targeted location, if there are no corpses, you will still waste the spell
I mean that from the spell description, and even from your description here, it's not clear to me how the spell works. It's a combo passive and active? I had no idea - I thought you had to cast to collect souls.
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
I mean that from the spell description, and even from your description here, it's not clear to me how the spell works. It's a combo passive and active? I had no idea - I thought you had to cast to collect souls.
well full description of the spell's mechanic would make a hell of a tooltip :p
 

Weep

Godspeed to the sound of the pounding
Reaction score
401
well full description of the spell's mechanic would make a hell of a tooltip :p

I suggest:
"Passively steals souls of nearby decaying units, healing Necro Walker and storing the souls. He can release stored souls upon a targeted group of corpses, raising Skeleton Warriors from them. Souls passing through enemies deal damage as they fly toward the targeted corpses."
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
Souls passing through enemies deal damage as they fly toward the targeted corpses."
actually they deal damage when they are being consumed as well :rolleyes:
but that's actually better
I'll change it in the next version to only deal damage when released
that will make the spell some kind of a nuke and more balanced
 

BlackRose

Forum User
Reaction score
239
Bonespear:

- The [ljass]SPEED[/ljass] variable should be input as Warcraft III speed units, as it's easier for the user. You can calculate the movespeed in your actions function instead as [ljass]SPEED * INTERVAL[/ljass]. Although users can input [ljass]1280 * INTERVAL[/ljass] themselves.

- Lack of documentation? You should let users know that AIDS and GroupUtils is a requirement.

Okay, that's all I'm going to say. I lack motivation checking over the rest.
 

HeX.16

Isn't Trollin You Right Now
Reaction score
131
Epic, bone spear is awesome. I love the way the ultimate and the summons all affect it and how it targets.

Not going to go into code as i suck at it =/

One thing i dislike is how the units will ignore the living wall units. I think its just a priority thing but it should be changed.
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
One thing i dislike is how the units will ignore the living wall units. I think its just a priority thing but it should be changed.
actually I was thinking to replace Living Wall with a completelly different spell (like Weep suggested)
it just seems unfitting with other spells somehow (although there's a synergy between the wall, confusion and the ultimate)
also there are already bunch of skellies to do the dmg/blocking part

and it looks kinda ugly tbh (I love being self-critical)

taking ideas for remaking the wall!

oh wait... should I make a new thread now? xd
 

Laiev

Hey Listen!!
Reaction score
188
i don't think so, you're discussing about your spellpack, which the link is this :p
 

HeX.16

Isn't Trollin You Right Now
Reaction score
131
Maybe a Morph spell.

Skeleton Monster

Merges 5*Level of ability into one single big skeleton

Skeleton gets 5 damage extra, 25 health and 1 armor for each skeleton hes made up from.

He can also be used by the bone spear spell, he deals 5*Number of skeletons EXTRA damage
 

Dinowc

don't expect anything, prepare for everything
Reaction score
223
Maybe a Morph spell.

Skeleton Monster

Merges 5*Level of ability into one single big skeleton

Skeleton gets 5 damage extra, 25 health and 1 armor for each skeleton hes made up from.

He can also be used by the bone spear spell, he deals 5*Number of skeletons EXTRA damage

meh
it would be hard to code since modifying dmg, hp and armor like that would be complicated
and I don't like it tbh, it's boring, has almost no synergy with other skills (except for this forced one) and it's dependant on Necromancy spell (if you don't lvl Necromancy, you don't lvl this skill either)
skeletons aren't supposed to deal damage anyway, but to block enemies and keep them busy when they are confused while you finish them off with Bone Spear

all of the spells are offensive (if you exclude the wall)
a defensive ability would be good
 

HeX.16

Isn't Trollin You Right Now
Reaction score
131
Maybe, make the wall units(zombies) attached to the hero. Then allow attackers to only damage them. They can also attack nearby enemies. Last 15 secs or until destroyed(50 hp * level of ability). They all share this health so once it reaches its cap all the units die.

Numbers can be changed.
 
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