scope ChillingAura // v1.0.0 // requires PUI, TimerUtils, Table, StructuredDD, StillDummy, UnitTextTag
//===========================================================================
// GLOBALS DECLARATION
//===========================================================================
globals
private constant integer MAX_TILE = 3
private constant boolean USE_TEXTTAG = true
private constant string TT_COLOR = "|c0000ffff"
private constant string TT_COLOR_RAGE = "|c00ff3300"
private constant integer ABILITY_ID = 'AHab'
private constant real INTERVAL = 0.05 // alt. 0.03125
private constant real AURA_INTERVAL = 0.5
private constant real TICK = 2.
private constant real FULL_TICK = 6.
private constant player EFFECT_OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
private constant string EFFECT_HIT = "Abilities\\Spells\\Undead\\CarrionSwarm\\CarrionSwarmDamage.mdl"
private constant string EFFECT_BUFF = "Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl"
private constant string EFFECT_READY_ORIGIN = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
private constant string EFFECT_READY_OVERHEAD = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareTarget.mdl"
private constant string EFFECT_MAX_1 = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
private constant string EFFECT_MAX_2 = "Abilities\\Spells\\Other\\Charm\\CharmTarget.mdl"
private constant string EFFECT_MAX_3 = "Abilities\\Spells\\Human\\Feedback\\SpellBreakerAttack.mdl"
private effect array EFFECT
private group SWITCH_GROUP
private group TEMP_GROUP = CreateGroup()
endglobals
//===========================================================================
// USEFUL FUNCTIONS
//===========================================================================
private constant function GetSmaller takes real r1, real r2 returns real
if r1 < r2 then
return r1
endif
return r2
endfunction
private constant function GetBigger takes real r1, real r2 returns real
if r1 > r2 then
return r1
endif
return r2
endfunction
//===========================================================================
// USEFUL STRUCTS
//===========================================================================
private struct UnitScale extends array
private real goal
private real current
private real speed
private unit u
private boolean active
boolean flushOnFinish
method flush takes nothing returns nothing
set active = false
set current = 1
set goal = 1
set u = null
endmethod
private static method onLoop takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype s = GetTimerData(t)
if s.current < s.goal then
set s.current = GetSmaller(s.current + s.speed, s.goal)
call SetUnitScale(s.u, s.current, s.current, s.current)
elseif s.current > s.goal then
set s.current = GetBigger(s.current - s.speed, s.goal)
call SetUnitScale(s.u, s.current, s.current, s.current)
endif
if s.current == s.goal then
call ReleaseTimer(t)
set s.active = false
if s.flushOnFinish then
call s.flush()
endif
endif
set t = null
endmethod
method setScale takes real r, real rate returns nothing
local timer t
set goal = r
set speed = rate
set flushOnFinish = false
if u != GetIndexUnit(this) then
set u = GetIndexUnit(this)
set current = 1
endif
if not active then
set active = true
set t = NewTimer()
call SetTimerData(t, this)
call TimerStart(t, INTERVAL, true, function thistype.onLoop)
set t = null
endif
endmethod
endstruct
private struct currentPool extends array
readonly integer size
readonly Table table
method add takes integer which returns nothing
if size == 0 then
set table = Table.create()
set size = 0
endif
set size = size + 1
set table [size] = which
endmethod
method flush takes nothing returns nothing
if size != 0 then
set size = 0
call table.destroy()
endif
endmethod
private method flushIndex takes integer index returns nothing
local integer i = size
loop
set i = i - 1
set table [i] = table [i+1]
exitwhen i == 1 or i == index
endloop
set size = size - 1
endmethod
method remove takes integer which returns nothing
local integer i = 0
loop
set i = i + 1
if table [i] == which then
if size > 1 then
call flushIndex(i)
else
set size = size - 1
endif
exitwhen true
endif
exitwhen i >= size
endloop
if size == 0 then
call table.destroy()
endif
endmethod
endstruct
//===========================================================================
// EFFECT STRUCT
//===========================================================================
private struct EffectReady
private unit target
private effect ef1
private effect ef2
boolean max
method destroy takes nothing returns nothing
call UnitScale[GetUnitIndex(target)].setScale(1,INTERVAL*2)
set UnitScale[GetUnitIndex(target)].flushOnFinish = true
call DestroyEffect(ef1)
call DestroyEffect(ef2)
if max then
call Tile_RemoveProducer(target)
endif
set max = false
set ef1 = null
set ef2 = null
set target = null
endmethod
static method create takes unit u returns thistype
local thistype s = thistype.allocate()
set s.target = u
set s.max = false
set s.ef1 = AddSpecialEffectTarget(EFFECT_READY_ORIGIN, u, "origin")
set s.ef2 = AddSpecialEffectTarget(EFFECT_READY_OVERHEAD, u, "overhead")
call UnitScale[GetUnitIndex(u)].setScale(1.4,INTERVAL)
return s
endmethod
method stateMax takes Table tiles returns nothing
local unit u = StillDummy_Create(EFFECT_OWNER, GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target), .8)
set max = true
call BJDebugMsg("asd")
call SetUnitScale(u, 1.7, 1.7, 1.7)
call DestroyEffect(AddSpecialEffectTarget(EFFECT_MAX_1, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(EFFECT_MAX_2, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(EFFECT_MAX_3, u, "origin"))
call Tile_Spawn(GetUnitX(target),GetUnitY(target),tiles,MAX_TILE,400,5)
call Tile_AddProducer(target, tiles, MAX_TILE, 250)
call UnitScale[GetUnitIndex(target)].setScale(1.7,INTERVAL)
set u = null
endmethod
endstruct
//===========================================================================
// AURA STRUCT
//===========================================================================
private struct Struct
// member declaration
private static Table tTable // tiles table
private static TableArray vTable // value table
private static thistype s
private static thistype array current
private static trigger array deathTrigger
private integer level
private EffectReady efR
private trigger t
private timer tick
private timer aura
static if USE_TEXTTAG then
private real textCd
endif
private real step
private real outPut
private real rage
private real rageMultiplier
private real rageCap
private real damageMin
private real damage
private real damagePerSecond
private real damageMax
private real damageAoE
private real auraAoE
private unit caster
private group g
private static method ungroupUnit takes unit u, thistype from returns nothing
local integer i = GetUnitIndex(u)
call currentPool[i].remove(from)
if currentPool[i].size == 0 then
call DestroyEffect(EFFECT[i])
set EFFECT[i] = null
call DestroyTrigger(deathTrigger[i])
set deathTrigger[i] = null
endif
endmethod
private static method clearGroupEnum takes nothing returns nothing
call ungroupUnit(GetEnumUnit(), s)
endmethod
private method destroy takes nothing returns nothing
// destroy method
set current[GetUnitIndex(caster)] = 0
set caster = null
set s = this
call ForGroup(g, function thistype.clearGroupEnum)
call DestroyGroup(g)
set g = null
call ReleaseTimer(tick)
set tick = null
call ReleaseTimer(aura)
set aura = null
if efR != 0 then
call efR.destroy()
set efR = 0
endif
call DestroyTrigger(t)
set t = null
endmethod
static if USE_TEXTTAG then
private method displayRageText takes nothing returns nothing
// display text plus orange text
set textCd = 1.
call CreateTextTagUnit(caster, TT_COLOR+I2S(R2I(damage))+TT_COLOR_RAGE+" +"+I2S(R2I(rage)), TICK+0.5, 1.2, 0.8)
endmethod
endif
private static method onAttack takes nothing returns nothing
// attack event
local integer i = 0
local unit u = GetTriggerUnit()
local unit u2
local currentPool cp = currentPool[GetUnitUserData(u)]
if cp.size > 0 and IsUnitEnemy(GetEventDamageSource(),GetOwningPlayer(u)) then
loop
set i = i + 1
set s = cp.table [i]
set s.rage = GetSmaller(s.rage + GetEventDamage()*s.rageMultiplier, s.rageCap)
static if USE_TEXTTAG then
if s.textCd == 0 then
call s.displayRageText()
endif
endif
exitwhen i == cp.size
endloop
set u2 = StillDummy_Create(EFFECT_OWNER, GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u), .8)
call SetUnitScale(u2, 2, 2, 2)
call DestroyEffect(AddSpecialEffectTarget(EFFECT_HIT, u2, "origin"))
set u2 = null
endif
set u = null
endmethod
private static method auraFilter takes nothing returns boolean
// valid aura targets
return UnitAlive(GetFilterUnit()) and IsUnitAlly(GetFilterUnit(), GetOwningPlayer(s.caster)) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)
endmethod
private static method onDeath takes nothing returns boolean
// aura member death
local unit u = GetTriggerUnit()
call currentPool[GetUnitIndex(u)].flush()
call DestroyEffect(EFFECT[GetUnitIndex(u)])
set EFFECT[GetUnitIndex(u)] = null
call DestroyTrigger(deathTrigger[GetUnitIndex(u)])
set deathTrigger[GetUnitIndex(u)] = null
set u = null
return false
endmethod
private static method auraLoop takes nothing returns nothing
// register units
local group g = CreateGroup()
local unit FoG
set s = GetTimerData(GetExpiredTimer())
call GroupEnumUnitsInRange(g, GetUnitX(s.caster), GetUnitY(s.caster), s.auraAoE, Filter(function thistype.auraFilter))
loop
set FoG = FirstOfGroup(s.g)
exitwhen FoG == null
if IsUnitInGroup(FoG, g) then
call GroupAddUnit(TEMP_GROUP, FoG)
elseif UnitAlive(FoG) then
call ungroupUnit(FoG,s)
endif
call GroupRemoveUnit(s.g, FoG)
endloop
set SWITCH_GROUP = s.g
set s.g = TEMP_GROUP
set TEMP_GROUP = SWITCH_GROUP
loop
set FoG = FirstOfGroup(g)
exitwhen FoG == null
if not IsUnitInGroup(FoG, s.g) then
call GroupAddUnit(s.g, FoG)
call currentPool[GetUnitIndex(FoG)].add(s)
if EFFECT[GetUnitIndex(FoG)] == null and FoG != s.caster then
set EFFECT[GetUnitIndex(FoG)] = AddSpecialEffectTarget(EFFECT_BUFF, FoG, "origin")
set deathTrigger[GetUnitIndex(FoG)] = CreateTrigger()
call TriggerRegisterUnitEvent(deathTrigger[GetUnitIndex(FoG)], FoG, EVENT_UNIT_DEATH)
call TriggerRegisterUnitEvent(deathTrigger[GetUnitIndex(FoG)], FoG, EVENT_UNIT_DECAY)
call TriggerRegisterUnitEvent(deathTrigger[GetUnitIndex(FoG)], FoG, EVENT_UNIT_CHANGE_OWNER)
call TriggerAddCondition(deathTrigger[GetUnitIndex(FoG)], Condition(function thistype.onDeath))
endif
endif
call GroupRemoveUnit(g, FoG)
endloop
call DestroyGroup(g)
set g = null
endmethod
private static method onLoop takes nothing returns nothing
// spell loop
set s = GetTimerData(GetExpiredTimer())
static if USE_TEXTTAG then
set s.textCd = GetBigger(s.textCd-INTERVAL, 0.)
endif
set s.step = s.step - INTERVAL
if s.step <= 0. then
set s.step = TICK
if s.damage < s.damageMax then
set s.damage = GetSmaller(s.damage + s.damagePerSecond, s.damageMax)
if s.rage > 0 then
static if USE_TEXTTAG then
if s.textCd == 0 then
call s.displayRageText()
endif
endif
else
static if USE_TEXTTAG then
if s.textCd == 0 then
call CreateTextTagUnit(s.caster, TT_COLOR+I2S(R2I(s.damage)), TICK+0.5, 1.2, 0.8)
set s.textCd = 1
endif
endif
endif
else
set s.step = FULL_TICK
static if USE_TEXTTAG then
if s.textCd == 0 then
call s.displayRageText()
endif
endif
endif
if Lich_EXTRA_DAMAGE[GetUnitIndex(s.caster)] != 0 then
// there is damage applied onto caster
// refresh the output value
set s.outPut = s.rage + s.damage
if s.outPut < s.damageMin then
set Lich_EXTRA_DAMAGE[GetUnitIndex(s.caster)] = 0
if s.efR != 0 then
call s.efR.destroy()
set s.efR = 0
endif
else
set Lich_EXTRA_DAMAGE[GetUnitIndex(s.caster)] = s.outPut
set Lich_EXTRA_DAMAGE_AOE[GetUnitIndex(s.caster)] = s.damageAoE
if s.damage == s.damageMax and not s.efR.max then
call s.efR.stateMax(tTable)
endif
endif
endif
endif
if Lich_EXTRA_DAMAGE[GetUnitIndex(s.caster)] == 0 then
// there's no damage applied onto caster
if s.outPut == 0 then
// if there hasn't been any damage
if s.rage + s.damage >= s.damageMin then
set s.outPut = s.rage + s.damage
set Lich_EXTRA_DAMAGE[GetUnitIndex(s.caster)] = s.outPut
set Lich_EXTRA_DAMAGE_AOE[GetUnitIndex(s.caster)] = s.damageAoE
if s.efR == 0 then
set s.efR = EffectReady.create(s.caster)
endif
endif
else
// if there has been damage
// reduce the output amount from damage values
set s.rage = 0
set s.damage = 0
set s.outPut = 0
// remove possible effect applied
if s.efR != 0 then
call s.efR.destroy()
set s.efR = 0
endif
endif
endif
endmethod
private method updateStats takes nothing returns nothing
// update values
set damagePerSecond = vTable [1].real [level]
set damageMax = vTable [2].real [level]
set auraAoE = vTable [3].real [level]
set damageAoE = vTable [4].real [level]
set rageMultiplier = vTable [5].real [level]
set rageCap = vTable [6].real [level]
set damageMin = vTable [7].real [level]
endmethod
private static method casterDeath takes nothing returns boolean
call current[GetUnitIndex(GetTriggerUnit())].destroy()
return false
endmethod
private static method registerCaster takes unit u, integer i returns boolean
if current[GetUnitIndex(u)] == 0 then
set s = thistype.create()
set Lich_EXTRA_DAMAGE[GetUnitIndex(u)] = 0
set s.caster = u
set s.level = i
set s.outPut = 0
set s.t = CreateTrigger()
call TriggerRegisterUnitEvent(s.t, u, EVENT_UNIT_DEATH)
call TriggerRegisterUnitEvent(s.t, u, EVENT_UNIT_CHANGE_OWNER)
call TriggerRegisterUnitEvent(s.t, u, EVENT_UNIT_DECAY)
call TriggerAddCondition(s.t, Condition(function thistype.casterDeath))
set s.efR = 0
set s.rage = 0
static if USE_TEXTTAG then
set s.textCd = 0
endif
set s.g = CreateGroup()
set current[GetUnitIndex(u)] = s
set s.damage = vTable [0].real [i]
call s.updateStats()
set s.step = TICK
set s.tick = NewTimer()
set s.aura = NewTimer()
call SetTimerData(s.tick, s)
call SetTimerData(s.aura, s)
call TimerStart(s.tick, INTERVAL, true, function thistype.onLoop)
call TimerStart(s.aura, AURA_INTERVAL, true, function thistype.auraLoop)
return true
endif
return false
endmethod
private static method onCast takes nothing returns boolean
// spell learned / unit entered field
local unit u = GetTriggerUnit()
local integer i = GetUnitAbilityLevel(u, ABILITY_ID)
call BJDebugMsg("asd")
if i > 0 then
if not registerCaster(u,i) and current[GetUnitIndex(u)].level != i then
set current[GetUnitIndex(u)].level = i
call current[GetUnitIndex(u)].updateStats()
endif
elseif current[GetUnitIndex(u)] != 0 then
call current[GetUnitIndex(u)].destroy()
endif
set u = null
return false
endmethod
private static method onInit takes nothing returns nothing
// spell initialization
local trigger t = CreateTrigger()
set tTable = Table.create()
// tiles
set tTable [0] = 'Wsnw'
set tTable [1] = 'Nsnw'
set tTable [2] = 'Nsnr'
set tTable [3] = 'Isnw'
set vTable = TableArray [8]
// index 0 = starting damage
set vTable [0].real[1] = 20.
set vTable [0].real[2] = 30.
set vTable [0].real[3] = 40.
// index 1 = damage per tick
set vTable [1].real[1] = 4.
set vTable [1].real[2] = 8.
set vTable [1].real[3] = 12.
// index 2 = max damage
set vTable [2].real[1] = 200.
set vTable [2].real[2] = 250.
set vTable [2].real[3] = 300.
// index 3 = aura AoE
set vTable [3].real[1] = 800.
set vTable [3].real[2] = 800.
set vTable [3].real[3] = 800.
// index 4 = damage AoE
set vTable [4].real[1] = 250.
set vTable [4].real[2] = 250.
set vTable [4].real[3] = 250.
// index 5 -rage mutliplier
set vTable [5].real[1] = 0.2
set vTable [5].real[2] = 0.4
set vTable [5].real[3] = 0.6
// index 6 - rage cap
set vTable [6].real[1] = 100
set vTable [6].real[2] = 200
set vTable [6].real[3] = 300
// index 7 - min damage
set vTable [7].real[1] = 100
set vTable [7].real[2] = 100
set vTable [7].real[3] = 100
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
call TriggerRegisterEnterRectSimple(t, bj_mapInitialPlayableArea)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_REVIVE_FINISH)
call TriggerAddCondition(t, Condition(function thistype.onCast))
set t = null
call StructuredDD.addHandler(function thistype.onAttack)
endmethod
endstruct
endscope
//===========================================================================
// OUTPUT
//===========================================================================
library Lich
globals
public real array EXTRA_DAMAGE
public real array EXTRA_DAMAGE_AOE
endglobals
endlibrary
//===========================================================================
// USED BY SUB-ABILITIES (such as Frost nova) AND AURA
//===========================================================================
library Tile initializer init requires Table, TimerUtils
globals
private constant real INTERVAL = .08334
private constant real DISTANCE = 128.
private group PRODUCERS = CreateGroup()
private real CHECK_DIST = 0.
private constant trigger TRIGGER = CreateTrigger()
private Table array TILES
private integer array TILE_MAX
private real array RADIUS
private group SWITCH_GROUP
private group TEMP_GROUP = CreateGroup()
endglobals
private constant function GetBigger takes real r1, real r2 returns real
if r1 > r2 then
return r1
endif
return r2
endfunction
private function Pythagoras takes real x, real y returns real
return SquareRoot(x*x + y*y)
endfunction
private function round takes real r1, real r2 returns real
if r1 > 0 then
return I2R(R2I(r1/r2 + 0.5))*r2
endif
return I2R(R2I(r1/r2 - 0.5))*r2
endfunction
private function ProducerFilter takes nothing returns boolean
return IsUnitInGroup(GetFilterUnit(), PRODUCERS)
endfunction
private struct Struct
private integer original
private integer tile
private integer var
private real time
private real x
private real y
private static method onLoop takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype s = GetTimerData(t)
local group g = CreateGroup()
call GroupEnumUnitsInRange(g, s.x, s.y, CHECK_DIST, Filter(function ProducerFilter))
if FirstOfGroup(g) == null then
set s.time = s.time - INTERVAL
endif
if s.time <= 0 then
if GetTerrainType(s.x, s.y) == s.tile and GetTerrainVariance(s.x, s.y) == s.var then
call SetTerrainType(s.x, s.y, s.original, -1, 1, 0)
endif
call ReleaseTimer(t)
endif
call DestroyGroup(g)
set t = null
set g = null
endmethod
static method create takes real x, real y, Table table, integer max, real duration returns thistype
local thistype s
local timer t
local boolean b = true
local integer i = GetTerrainType(x, y)
local integer n = -1
loop
set n = n + 1
if i == table[n] then
set b = false
exitwhen true
endif
exitwhen n == max
endloop
if b then
set s = thistype.allocate()
set t = NewTimer()
set s.original = i
set s.time = duration
call SetTerrainType(x, y, table[GetRandomInt(0, max)], -1, 1, 0)
set s.tile = GetTerrainType(x,y)
set s.var = GetTerrainVariance(x,y)
call SetTimerData(t, s)
set s.x = x
set s.y = y
call TimerStart(t, INTERVAL, true, function thistype.onLoop)
set t = null
endif
return s
endmethod
endstruct
public function Spawn takes real x, real y, Table tiles, integer maxTile, real radius, real time returns nothing
// current coord
local real cX = round(x - radius/2, DISTANCE)
local real cY
local real r
loop
set cX = cX + DISTANCE
set cY = round(y - radius/2, DISTANCE)
loop
set cY = cY + DISTANCE
set r = Pythagoras(x-cX,y-cY)
if r <= radius then
call Struct.create(cX, cY, tiles, maxTile, GetBigger(0.5,time - r/DISTANCE))
endif
exitwhen cY >= y + radius/2
endloop
exitwhen cX >= x + radius/2
endloop
endfunction
public function AddProducer takes unit u, Table tiles, integer maxTile, real radius returns nothing
if FirstOfGroup(PRODUCERS) == null then
set CHECK_DIST = 0
call EnableTrigger(TRIGGER)
endif
if not IsUnitInGroup(u, PRODUCERS) then
set CHECK_DIST = GetBigger(radius, CHECK_DIST)
call GroupAddUnit(PRODUCERS, u)
set TILES[GetUnitIndex(u)] = tiles
set TILE_MAX[GetUnitIndex(u)] = maxTile
set RADIUS[GetUnitIndex(u)] = radius
endif
endfunction
public function RemoveProducer takes unit u returns nothing
if IsUnitInGroup(u, PRODUCERS) then
call GroupRemoveUnit(PRODUCERS, u)
endif
endfunction
private function Cond takes nothing returns boolean
local unit FoG
local integer i
if FirstOfGroup(PRODUCERS) == null then
call DisableTrigger(TRIGGER)
else
loop
set FoG = FirstOfGroup(PRODUCERS)
exitwhen FoG == null
set i = GetUnitIndex(FoG)
call Spawn(GetUnitX(FoG),GetUnitY(FoG),TILES[i],TILE_MAX[i],RADIUS[i],1)
call GroupRemoveUnit(PRODUCERS, FoG)
call GroupAddUnit(TEMP_GROUP, FoG)
endloop
set SWITCH_GROUP = PRODUCERS
set PRODUCERS = TEMP_GROUP
set TEMP_GROUP = SWITCH_GROUP
endif
return false
endfunction
private function init takes nothing returns nothing
call TriggerRegisterTimerEvent(TRIGGER, INTERVAL, true)
call TriggerAddCondition(TRIGGER, Condition(function Cond))
call DisableTrigger(TRIGGER)
endfunction
endlibrary