Drake Whisperer v2
3500
ID:
426
Family ID:
Author:
Ely
Rarity:
unique
Element:
astral
Attack Type:
Elemental
Attack Range:
1000
Attack CD:
1.9
Damage:
3109-3138
Status:
Approved

Description:

Unleashes mighty drakes against his enemies.
Specials:
+15% dmg to air (+0.4%/lvl)
Versatile
Every time this tower deals spell damage through its abilities, it increases its dps by 1.5% of the spell damage dealt. Lasts 2.5 seconds and stacks. Maximum bonus of [200 x (current wave)].

Level Bonus:
+0.04% damage
Unleash
On attack, the Drake Whisperer has a 12.5% chance to unleash a bronze drake towards its target, dealing 1250 spell damage to a random creep in front of itself in 600 range every 0.2 seconds. Lasts 2 seconds.

Level Bonus:
+40 spell damage
+0.3% chance
Feed the Drakes
Every 1.5 seconds, the Drake Whisperer feeds a nearby corpse to one of his drakes and unleashes it to a random target in 1000 range. If there is no target, the drake will attack on the next feeding, with a maximum of 5 fed drakes. Each corpse has a 15% chance to feed 2 drakes.

The Blue Drake deals 6000 spell damage in 125 AoE and slows by 25% for 3 seconds.
The Red Drake deals 200% of the tower's attack damage and stuns for 3 seconds.
The Green Drake deals 5000 spell damage and spreads Versatile's current dps bonus to towers in 175 range for 2.5 seconds.

Level Bonus:
+0.4% double feed chance
Blue Drake : +150 spell damage
Red Drake : +8% damage
Green Drake : +0.04 seconds duration
Download

Toggle Triggers

Header

            globals
        BuffType versatileBuff
        BuffType blueDrakeBuff

        ProjectileType blueDrake
        ProjectileType redDrake
        ProjectileType greenDrake
        ProjectileType bronzeDrake
        ProjectileType bronzeDrakeAttack

        constant integer BLUE = 0
        constant integer GREEN = 1
        constant integer RED = 2
        
        // Drakeling status
        constant integer IDLE = 0
        constant integer ATTACKING = 1
        constant integer COMING_BACK = 2
    endglobals
    
    struct Vec3
        real x
        real y
        real z
    endstruct
    
    struct Drakelings
        Projectile array d[3]
        Vec3 array startPos[3]
    endstruct
    
    function allDrakesBusy takes Drakelings d returns boolean
        return d.d[0].userInt != IDLE and d.d[1].userInt != IDLE and d.d[2].userInt != IDLE
    endfunction
    
    function launchDrakeling takes Tower t, integer which, Unit u returns nothing
        local Drakelings d = t.userInt2

        set d.d[which].speed = 600
        call d.d[which].startBezierInterpolationToUnit(u, 0.15, 0.15, 0.17, true)
        set d.d[which].userInt = ATTACKING
        set t.userInt = t.userInt - 1
    endfunction

    function launchRandomDrakeling takes Tower t, Unit u returns nothing
        local integer i = GetRandomInt(0, 2)
        local Drakelings d = t.userInt2

        loop
            if d.d[i].userInt == IDLE then
                call launchDrakeling(t, i, u)
                return
            endif
            set i = ModuloInteger(i + 1, 3)
        endloop
    endfunction
    
    function feeding takes Tower tower returns nothing     
        local Iterate it = Iterate.overUnitsInRangeOfCaster(tower, TARGET_TYPE_CREEPS, 1000)
        local Unit u
        local integer random
        local Projectile p
        local Drakelings d = tower.userInt2
        local Effect e
        local Iterate itCorpse
        local unit corpse
        local integer maxFedDrakes static constant = 5

        // userInt = number of fed drakes, 5 max
        if tower.userInt < maxFedDrakes then
            set itCorpse = Iterate.overCorpsesInRange(tower, tower.getX(), tower.getY(), 1000)
            set corpse = itCorpse.nextCorpse()
            
            if corpse != null then
                call ShowUnit(corpse, false)

                set e = Effect.createScaled("Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodFootman.mdl", tower.getX()+10, tower.getY(), tower.getZ() - 120, 0, 0.15)
                call e.setLifetime(0.8)
        
                if tower.calcChance(15/100.0 + (0.4/100.0)*tower.getLevel()) then
                    set tower.userInt = tower.userInt + 2
                else
                    set tower.userInt = tower.userInt + 1
                endif
                    
                if tower.userInt > maxFedDrakes then
                    set tower.userInt = maxFedDrakes
                endif
                
                set corpse = null
                call itCorpse.destroy()
            endif
        endif
        
        loop
            if tower.userInt <= 0 or allDrakesBusy(d) then
                call it.destroy()
                return
            endif
            set u = it.nextRandom()
            exitwhen u == 0
            
            // Only red drakes deal physical, so unleash only them vs immune
            if u.isImmune() then
                if d.d[RED].userInt == IDLE then
                    call launchDrakeling(tower, RED, u)
                endif
            else
                call launchRandomDrakeling(tower, u)
            endif
        endloop
    endfunction
    
    function refreshBuff takes Tower tower, real damage returns nothing
        local Buff b = tower.getBuffOfType(versatileBuff)
        local real powerup
        local real maxDamage = 200*tower.getOwner().getTeam().getLevel()

        if damage <= 0 then
            return
        endif
        
        set powerup = damage*(1.5/100.0 + (0.04/100.0)*tower.getLevel())
        
        if b != 0 then
            set powerup = b.getPower() + powerup

            if powerup > maxDamage then
                set powerup = maxDamage
            endif

            call b.setPower(R2I(powerup))
            call b.refreshDuration()
        else
            if powerup > maxDamage then
                set powerup = maxDamage
            endif
        
            call versatileBuff.applyCustomPower(tower, tower, 1, R2I(powerup))
        endif
    endfunction
    
    function spreadBuff takes Tower tower returns nothing
        local Iterate it
        local Buff b = tower.getBuffOfType(versatileBuff)
        local Tower target

        if b == 0 then
            return
        endif
        
        set it = Iterate.overUnitsInRangeOfCaster(tower, TARGET_TYPE_TOWERS, 175)
        
        loop
            set target = it.next()
            exitwhen target == 0

            if target.getUnitType() != tower.getUnitType() then
                call versatileBuff.applyAdvanced(tower, target, 1, b.getPower(), 2.5 + 0.04*tower.getLevel())
            endif
        endloop
    endfunction
    
    function blueDrakeHit takes Projectile p, Unit target returns nothing
        local Tower tower = p.getCaster()
        local real damage = tower.getOverallDamage()
        local Iterate it
        local Unit u
        local Effect e

        set it = Iterate.overUnitsInRange(tower, TARGET_TYPE_CREEPS, p.x, p.y, 125)
        
        if it.count() == 0 then
            call it.destroy()
            return
        endif
        
        set e = Effect.createScaled("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl", p.x, p.y, p.z, 0, 0.2)
        call e.setLifetime(2.0)

        loop
            set u = it.next()
            exitwhen u == 0

            call blueDrakeBuff.apply(tower, u, 1)
            call tower.doSpellDamage(u, 6000 + 150*tower.getLevel(), tower.calcSpellCritNoBonus())
        endloop

        set damage = tower.getOverallDamage() - damage
        call refreshBuff(tower, damage)
    endfunction
    
    function greenDrakeHit takes Projectile p, Unit target returns nothing
        local Tower tower = p.getCaster()
        local real damage = tower.getOverallDamage()
        
        if target == 0 then
            call spreadBuff(tower)
            return
        endif
        
        call tower.doSpellDamage(target, 5000, tower.calcSpellCritNoBonus())
        set damage = tower.getOverallDamage() - damage

        call refreshBuff(tower, damage)
        call spreadBuff(tower)
    endfunction
    
    function redDrakeHit takes Projectile p, Unit target returns nothing
        local Tower tower = p.getCaster()
        
        if target == 0 then
            return
        endif
        
        call cb_stun.applyOnlyTimed(tower, target, 3) 
        call tower.doAttackDamage(target, tower.getCurrentAttackDamageWithBonus()*(200/100.0 + (8/100.0)*tower.getLevel()), tower.calcAttackCritNoBonus())
    endfunction
    
    function sendDrakelingHome takes Projectile p returns nothing
        local Drakelings d = p.getCaster().userInt2
        local integer which = p.userInt2
        
        set d.d[which].speed = 600
        call d.d[which].startBezierInterpolationToPoint(d.startPos[which].x, d.startPos[which].y, d.startPos[which].z, 0.15, 0.15, 0.17)
        set d.d[which].userInt = COMING_BACK
    endfunction
    
    function onDrakelingEndInterpol takes Projectile p, Unit target returns nothing
        local integer which = p.userInt2

        call p.avertDestruction() 
        
        if p.userInt == COMING_BACK then
            // Will be used to reset the drake, otherwise the code in Projectile messes with our position right after this event handler
            call p.enablePeriodic(1)
            set p.remainingLifetime = 999999
            return
        endif
        
        if which == RED then
            call redDrakeHit(p, target)
        elseif which == BLUE then
            call blueDrakeHit(p, target)
        else
            call greenDrakeHit(p, target)
        endif
        
        call sendDrakelingHome(p)
    endfunction

    function bronzeDrakeTick takes Projectile p returns nothing 
        local Tower tower = p.getCaster() 
        local Iterate it
        local Unit u
        local Projectile atkProj
        local real startX
        local real startY
        local real angleDiff

        if p.getAge() > 2 then
            call p.color(255, 255, 255, 255 - R2I(((p.getAge() - 2) / (3 - 2))*255))
            return
        endif
        
        set it = Iterate.overUnitsInRange(tower, TARGET_TYPE_CREEPS, p.x, p.y, 600) 

        loop
            set u = it.nextRandom()
            exitwhen u == 0
            if not u.isImmune() then
                // Test if the target is in a 90° cone in front of the drake
                set angleDiff = Atan2(u.getY() - p.y, u.getX() - p.x)*bj_RADTODEG - p.direction
                
                if angleDiff <= -310 or angleDiff >= 310 or (angleDiff >= -50 and angleDiff <= 50) then
                    set startX = p.x + Cos(p.direction*bj_DEGTORAD) * 100
                    set startY = p.y + Sin(p.direction*bj_DEGTORAD) * 100

                    set atkProj = Projectile.createLinearInterpolationFromPointToUnit(bronzeDrakeAttack, tower, 0, 0, startX, startY, p.z + 20, u, 0.30, true) 
                    call atkProj.setScale(0.55)
                    call it.destroy() 
                    return
                endif
            endif
        endloop
    endfunction
    
    function onBronzeDrakeHit takes Projectile p, Unit target returns nothing
        local Tower tower = p.getCaster()
        local real damage = tower.getOverallDamage()

        if target == 0 then
            return
        endif
        
        call tower.doSpellDamage(target, 1250 + 40*tower.getLevel(), tower.calcSpellCritNoBonus())
        set damage = tower.getOverallDamage() - damage
        call refreshBuff(tower, damage)
    endfunction
   
    // Hackish way of resetting the projectile and making it still, facing the tower
    function resetPosition takes Projectile p returns nothing
        local Tower t = p.getCaster()
        local Drakelings d = t.userInt2
        local integer which = p.userInt2
        local real finalX
        local real finalY

        call p.disablePeriodic()
        
        set p.x = d.startPos[which].x
        set p.y = d.startPos[which].y
        set p.z = d.startPos[which].z
        set p.speed = 0
        set p.userInt = IDLE

        set finalX = p.x + (t.getX() - p.x)*10
        set finalY = p.y + (t.getY() - p.y)*10
        call p.aimAtPoint(finalX, finalY, p.z, false, false)
    endfunction
    
    //Do not remove or rename this function!
    //Put your initialization tasks here, this function will be called on map init
    private function init takes nothing returns nothing
        local Modifier versatileModifier = Modifier.create()
        local Modifier blueDrakeModifier = Modifier.create()

        call versatileModifier.addModification(MOD_DPS_ADD, 0.00, 1.00)
        set versatileBuff = BuffType.create(2.5, 0.00, true)
        call versatileBuff.setBuffModifier(versatileModifier)
        call versatileBuff.setBuffIcon('@@0@@')
        
        call blueDrakeModifier.addModification(MOD_MOVESPEED, -1*(25/100.0), 0)
        set blueDrakeBuff = BuffType.create(3, 0.00, true)
        call blueDrakeBuff.setBuffModifier(blueDrakeModifier)
        call blueDrakeBuff.setBuffIcon('@@1@@')
        
        set blueDrake = ProjectileType.create("Units\\Creeps\\AzureDragon\\AzureDragon.mdl", 999999, 0)
        call blueDrake.disableExplodeOnHit()
        call blueDrake.disableExplodeOnExpiration()
        call blueDrake.setEventOnInterpolationFinished(onDrakelingEndInterpol)
        call blueDrake.enablePeriodic(resetPosition, 0.1)
        
        set redDrake = ProjectileType.create("Units\\Creeps\\RedDragon\\RedDragon.mdl", 999999, 0)
        call redDrake.disableExplodeOnHit()
        call redDrake.disableExplodeOnExpiration()
        call redDrake.setEventOnInterpolationFinished(onDrakelingEndInterpol)
        call redDrake.enablePeriodic(resetPosition, 0.1)

        set greenDrake = ProjectileType.create("Units\\Creeps\\GreenDragon\\GreenDragon.mdl", 999999, 0)
        call greenDrake.disableExplodeOnHit()
        call greenDrake.disableExplodeOnExpiration()
        call greenDrake.setEventOnInterpolationFinished(onDrakelingEndInterpol)
        call greenDrake.enablePeriodic(resetPosition, 0.1)
        
        set bronzeDrake = ProjectileType.create("Units\\creeps\\BronzeDragon\\BronzeDragon.mdl", 3, 350.0)
        call bronzeDrake.disableExplodeOnHit()
        call bronzeDrake.disableExplodeOnExpiration()
        call bronzeDrake.enablePeriodic(bronzeDrakeTick, 0.2)
        
        set bronzeDrakeAttack = ProjectileType.create("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", 2.5, 900.0)
        call bronzeDrakeAttack.setEventOnInterpolationFinished(onBronzeDrakeHit)
    endfunction
        

On Attack

ONATTACK_chance: 0.125 ONATTACK_chanceLevelAdd: 0.003
function onAttack takes Tower tower returns nothing
            
local integer i = GetRandomInt(0, 1)
    local Projectile p
    local Unit u = Event.getTarget()
    local real finalX = tower.getX() + (u.getX() - tower.getX())*6
    local real finalY = tower.getY() + (u.getY() - tower.getY())*6

    set p = Projectile.createFromUnitToPoint(bronzeDrake, tower, 0, 0, tower, finalX, finalY, tower.getZ(), true, false)
    call p.setScale(0.60)
        
endfunction

On Tower Creation

function onCreate takes Tower tower returns nothing
            
local Drakelings d = Drakelings.create()

    set d.d[0] = Projectile.create(blueDrake, tower, 0, 0, tower.getX() + 36, tower.getY() - 30, tower.getZ() - 30, 120)
    set d.d[1] = Projectile.create(greenDrake, tower, 0, 0, tower.getX() - 50, tower.getY() - 13, tower.getZ() - 30, 10)
    set d.d[2] = Projectile.create(redDrake, tower, 0, 0, tower.getX() + 27, tower.getY() + 59, tower.getZ() - 30, 250)
    
    call d.d[0].disablePeriodic()
    call d.d[1].disablePeriodic()
    call d.d[2].disablePeriodic()
    
    set d.d[0].userInt = IDLE
    set d.d[1].userInt = IDLE
    set d.d[2].userInt = IDLE
    
    set d.d[0].userInt2 = 0
    set d.d[1].userInt2 = 1
    set d.d[2].userInt2 = 2

    set d.startPos[0] = Vec3.create()
    set d.startPos[0].x = tower.getX() + 36
    set d.startPos[0].y = tower.getY() - 30
    set d.startPos[0].z = tower.getZ() - 30
    
    set d.startPos[1] = Vec3.create()
    set d.startPos[1].x = tower.getX() - 50
    set d.startPos[1].y = tower.getY() - 13
    set d.startPos[1].z = tower.getZ() - 30
    
    set d.startPos[2] = Vec3.create()
    set d.startPos[2].x = tower.getX() + 27
    set d.startPos[2].y = tower.getY() + 59
    set d.startPos[2].z = tower.getZ() - 30
    
    call d.d[0].setScale(0.25)
    call d.d[1].setScale(0.25)
    call d.d[2].setScale(0.25)
    
    set tower.userInt = 0
    set tower.userInt2 = d
        
endfunction

On Tower Destruction

function onDestruct takes Tower tower returns nothing
            
local Drakelings d = tower.userInt2
    local integer i = 0
    
    loop
        call d.d[i].destroy()
        call d.startPos[i].destroy()
        set i = i + 1
        exitwhen i == 3
    endloop
    
    call d.destroy()
        
endfunction

Periodic

PERIODIC_period: 1.5
function periodic takes Tower tower returns nothing
            
call feeding(tower)
        
endfunction