- Joined
- Apr 24, 2012
- Messages
- 5,111
JASS:
scope Devourer initializer Init /*
*************************************************************************************
*
* Zephyr Contest # 14 : Unique Summoning
*
*************************************************************************************
*
* Summon Devourer
*
* ---------------------------------------------------------------------------
*
* Target point, if there are 5/4/3 trees within 256 range from the target
* point, it conjures a portal. After 5/4/3 seconds, the Devourer is summoned.
*
* Whenever a unit loses health within 1000 radius, the Devourer consumes the
* health, healing by 30/40/50% of the lost health. Whenever the Devourer has
* consumed, 400 Health it increases the Devourer's timed life by 1/2/3 seconds.
*
* Whenever a unit loses mana within 1000 radius, the Devourer stores them inside
* his vessel. If the amount of mana stored reaches 400/350/300 mana, it releases a
* shockwave that expands for 1 seconds 400 units outwards from the Devourer,
* dealing 100/200/300 damage to enemy units.
*
* When the devourer attacks a unit, it releases a swarm that travels 768
* range and deals 75/150/225 damage to units within 128 radius.
* Has a cooldown of 10/8/6 seconds.
*
* The Devourer has 750/1000/1250 max health but doesn't regenerate HP, has
* a movement speed of 300/315/330, attack cooldown of 1.5/1.4/1.3 seconds,
* attack damage of 25/35/45 and 1200/1200 sight range.
*
* Lasts 20/30/40 seconds.
*
*************************************************************************************
*
* Requires:
*
* Table
* - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
* DummyRecycler
* - hiveworkshop.com/forums/spells-569/dummy-recycler-v1-13-a-277659/?prev=r%3D20%26page%3D2
* GetUnitCollision
* - github.com/nestharus/JASS/blob/master/jass/Systems/GetUnitCollision/script.j
* MapBounds
* - hiveworkshop.com/forums/jass-resources-412/snippet-mapbounds-222870/
* SpellEffectEvent
* - hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
* RegisterPlayerUnitEvent
* - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
* IsDestructableTree
* - hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
* Projection
* - hiveworkshop.com/forums/2038279-post655.html
* (optional) ZLibrary
* - hiveworkshop.com/forums/jass-resources-412/snippet-zlibrary-237821/
*
*************************************************************************************
*
* How to Import:
*
* 1) Import first the dummy.mdx into your map(if you don't have it)
* 2) Copy the Missile Dummy unit from the Object Editor
* 3) Copy the 3 Devourer units, then configure the ids at the end of
* the constants.
* 4) Copy the libraries (ZLibrary is optional)
* 5) Copy the spell code
*
*************************************************************************************
*
* Credits:
*
* Bribe
* Adiktuz
* Nestharus
* Magtheridon96
* D.O.G.
* BPower
* Deaod
* Maker
* Flux
* D4RK_G4ND4LF
*
*************************************************************************************/
globals
/*
* Timeout for all timers running in this spell
*/
private constant real TIMEOUT = 0.03125
/*
* The ability id of the spell
*/
private constant integer ABIL_ID = 'Asdv'
/*
* rawcode id for the creeps (sfx units)
*/
private constant integer CREEP_ID = 'nspg'
/*
* rawcode id of dummies
*/
private constant integer DUMMY_ID = 'dumi'
//------------------------------------------------------
/*
* Spell on cast
*/
//------------------------------------------------------
/*
* Required trees for summoning
*/
private constant integer REQUIRED_TREES = 6
private constant integer REQUIRED_TREES_PER_LVL = -1
/*
* Radius for checking the required trees
*/
private constant real TREE_RADIUS_CHECK = 256
private constant real TREE_RADIUS_CHECK_PER_LVL = 0
/*
* Spawn delay of the devourer
*/
private constant real SPAWN_DELAY = 6
private constant real SPAWN_DELAY_PER_LVL = -1
/*
* How long the the devourer last
*/
private constant real TIMED_LIFE = 10
private constant real TIMED_LIFE_PER_LVL = 10
//------------------------------------------------------
/*
* Souls configurables
*/
//------------------------------------------------------
/*
* Consumes all health lost within the given range
*/
private constant real HEALTH_FEED_RADIUS = 1000
private constant real HEALTH_FEED_RADIUS_PER_LVL = 0
/*
* How much of the consume health are healed to the
* devourer
*/
private constant real HEALTH_RATE = 0.2
private constant real HEALTH_RATE_PER_LVL = 0.1
/*
* How much health are required to increase the timed
* life
*/
private constant real TIMED_LIFE_REQ = 400
private constant real TIMED_LIFE_REQ_PER_LVL = 0
/*
* How much timed life is added?
*/
private constant real TIMED_LIFE_BONUS = 0
private constant real TIMED_LIFE_BONUS_PER_LVL = 1
//------------------------------------------------------
/*
* Mana Release/Shockwave Configurables
*/
//------------------------------------------------------
/*
* Consumes all mana lost within the given range
*/
private constant real MANA_FEED_RADIUS = 1000
private constant real MANA_FEED_RADIUS_PER_LVL = 0
/*
* Required mana to release the shockwave
*/
private constant real MANA_RELEASE_REQ = 450
private constant real MANA_RELEASE_REQ_PER_LVL = -50
/*
* How large the shockwave is
*/
private constant real MANA_RELEASE_RADIUS = 450
private constant real MANA_RELEASE_RADIUS_PER_LVL = 0
/*
* How long does the shockwave grow
* from it's release point to it's max radius
*/
private constant real MANA_RELEASE_GROW_TIME = 1
/*
* Damage dealt to the affected units
*/
private constant real MANA_RELEASE_DMG = 0
private constant real MANA_RELEASE_DMG_PER_LVL = 100
private constant attacktype MANA_RELEASE_ATKTYPE = ATTACK_TYPE_CHAOS
private constant damagetype MANA_RELEASE_DMGTYPE = DAMAGE_TYPE_UNIVERSAL
/*
* Is the shockwave locked to the devourer?
* locked = follows the devourer's position instead
* of the position of release.
*/
private constant boolean MANA_RELEASED_LOCKED = true
/*
* Cooldown of the shockwave
*/
private constant real MANA_RELEASE_CD = 0
private constant real MANA_RELEASE_CD_PER_LVL = 0
/*
* Does the devourer collect mana even
* if the shockwave is on cooldown?
*/
private constant boolean COLLECT_MANA_ON_CD = false
//------------------------------------------------------
/*
* Swarm configurables
*/
//------------------------------------------------------
/*
* Starting polar offset of the swarms
*/
private constant real SWARM_SPAWN_OFFSET = 128
private constant real SWARM_SPAWN_OFFSET_PER_LVL = 0
/*
* How far doest the swarm travel
* (from the offset)
*/
private constant real SWARM_TRAVEL_DIST = 768
private constant real SWARM_TRAVEL_DIST_PER_LVL = 0
/*
* Radius of the swarm
*/
private constant real SWARM_RADIUS = 128
private constant real SWARM_RADIUS_PER_LVL = 0
/*
* Damage dealt by the swarm
*/
private constant real SWARM_DAMAGE = 0
private constant real SWARM_DAMAGE_PER_LVL = 75
private constant attacktype SWARM_DAMAGE_ATKTYPE = ATTACK_TYPE_CHAOS
private constant damagetype SWARM_DAMAGE_DMGTYPE = DAMAGE_TYPE_UNIVERSAL
/*
* Swarm cooldown
*/
private constant real SWARM_CD = 12
private constant real SWARM_CD_PER_LVL = -2
/*
* Swarm speed
*/
private constant real SWARM_SPEED = 512
//------------------------------------------------------
/*
* On Cast Effects
*/
//------------------------------------------------------
private constant real DUMMY_COLLISION_SIZE = 16
/*
* Portal Fields
*/
/*
* Model of the portal
*/
private constant string PORTAL_MODEL = "Abilities\\Spells\\Other\\Parasite\\ParasiteMissile.mdl"
/*
* Height of the portal
*/
private constant real PORTAL_Z = 128
/*
* Starting appearance of the portal
*/
private constant real PORTAL_START_SCALE = 1
private constant integer PORTAL_START_ALPHA = 255
private constant integer PORTAL_START_RED = 255
private constant integer PORTAL_START_GREEN = 255
private constant integer PORTAL_START_BLUE = 255
/*
* End appearance of the portal
*/
private constant real PORTAL_END_SCALE = 5
private constant integer PORTAL_END_ALPHA = 255
private constant integer PORTAL_END_RED = 255
private constant integer PORTAL_END_GREEN = 255
private constant integer PORTAL_END_BLUE = 255
/*
* Portal shard fields
*/
/*
* Show the portal shards?
*/
private constant boolean SHOW_SHARDS = true
/*
* Model of the shards
*/
private constant string SHARD_MODEL = "Abilities\\Spells\\Other\\Parasite\\ParasiteMissile.mdl"
/*
* Start appearance of the shards
*/
private constant real SHARD_START_SCALE = 1
private constant integer SHARD_START_ALPHA = 255
private constant integer SHARD_START_RED = 255
private constant integer SHARD_START_GREEN = 255
private constant integer SHARD_START_BLUE = 255
/*
* End appearance of the shards
*/
private constant real SHARD_END_SCALE = 0
private constant integer SHARD_END_ALPHA = 255
private constant integer SHARD_END_RED = 255
private constant integer SHARD_END_GREEN = 255
private constant integer SHARD_END_BLUE = 255
/*
* Spawn distance of the shards
*/
private constant real SHARD_DISTANCE = 300
private constant real SHARD_DISTANCE_VAR = 100
/*
* Spawn angular variation of the shards
*/
private constant real SHARD_ANGLE = 0
private constant real SHARD_ANGLE_VAR = bj_PI
private constant real SHARD_Z_ANGLE = bj_PI/2
private constant real SHARD_Z_ANGLE_VAR = SHARD_Z_ANGLE*2
/*
* How often shards spawn
*/
private constant real SHARD_PERIOD = 0.0625
/*
* How many shards spawn per period
*/
private constant integer SHARD_COUNT = 2
/*
* Speed of the shards
*/
private constant real SHARD_SPEED = 200
private constant real SHARD_SPEED_VAR = 50
//------------------------------------------------------
/*
* On Spawn effects
*/
private constant string ON_SPAWN_SFX = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
/*
* Show creep nova
*/
private constant boolean SHOW_CREEP_NOVA = true
/*
* Start appearance of the nova creeps
*/
private constant real NCREEP_START_SCALE = 0.3
private constant integer NCREEP_START_ALPHA = 200
private constant integer NCREEP_START_RED = 64
private constant integer NCREEP_START_GREEN = 64
private constant integer NCREEP_START_BLUE = 64
/*
* End appearance of the nova creeps
*/
private constant real NCREEP_END_SCALE = 0.3
private constant integer NCREEP_END_ALPHA = 0
private constant integer NCREEP_END_RED = 64
private constant integer NCREEP_END_GREEN = 64
private constant integer NCREEP_END_BLUE = 64
/*
* Number of creeps released
*/
private constant integer NCREEP_COUNT = 32
/*
* Speed of the creeps
*/
private constant real NCREEP_SPEED = 100
private constant real NCREEP_SPEED_VAR = 50
/*
* Creep life time
*/
private constant real NCREEP_TIME = 3.0
/*
* Animation played by the creep
*/
private constant integer NCREEP_ANIM = 1
/*
* Nova creep height
*/
private constant real NCREEP_Z = 0
//------------------------------------------------------
/*
* Devourer appearances
*/
private constant real DEVOURER_SCALE = 0.75
private constant real DEVOURER_SCALE_PER_LVL = 0.25
private constant integer DEVOURER_ALPHA = 200
private constant integer DEVOURER_RED = 100
private constant integer DEVOURER_GREEN = 100
private constant integer DEVOURER_BLUE = 100
private constant real DEVOURER_Z = 0
private constant string DEVOURER_DEATH_SFX = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
//------------------------------------------------------
/*
* Shadow Trail fields
*/
private constant boolean SHOW_TRAIL = true
/*
* Trail period
*/
private constant real TRAIL_PERIOD = 0.15625
/*
* Trail duration
*/
private constant real TRAIL_DURATION = 1.5
/*
* Trail animation
*/
private constant integer TRAIL_ANIM = 1
//------------------------------------------------------
/*
* Aura fields
*/
private constant boolean SHOW_AURA = true
/*
* Aura creep spawn period
*/
private constant real AURA_SPAWN_PERIOD = 0.125
/*
* Number of creep spawn
*/
private constant integer ACREEP_COUNT = 2
/*
* Start appearance of the aura creeps
*/
private constant real ACREEP_START_SCALE = 0.3
private constant integer ACREEP_START_ALPHA = 200
private constant integer ACREEP_START_RED = 64
private constant integer ACREEP_START_GREEN = 64
private constant integer ACREEP_START_BLUE = 64
/*
* End appearance of the aura creeps
*/
private constant real ACREEP_END_SCALE = 0.3
private constant integer ACREEP_END_ALPHA = 0
private constant integer ACREEP_END_RED = 64
private constant integer ACREEP_END_GREEN = 64
private constant integer ACREEP_END_BLUE = 64
/*
* Aura creep speed
*/
private constant real ACREEP_SPEED = 200
private constant real ACREEP_SPEED_VAR = 50
/*
* Aura creep duration
*/
private constant real ACREEP_TIME = 1.25
/*
* Aura creep animation
*/
private constant integer ACREEP_ANIM = 1
/*
* Aura creep height
*/
private constant real ACREEP_Z = 0
//------------------------------------------------------
/*
* Shockwave fields
*/
private constant string MANA_RELEASE_SFX = "Abilities\\Spells\\Other\\HowlOfTerror\\HowlCaster.mdl"
/*
* Number of shockwave segments
*/
private constant integer WAVE_COUNT = 18
/*
* Shockwave appearance
*/
private constant string WAVE_MODEL = "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl"
private constant real WAVE_SCALE = 1.25
private constant integer WAVE_ALPHA = 255
private constant integer WAVE_RED = 255
private constant integer WAVE_GREEN = 255
private constant integer WAVE_BLUE = 255
private constant real WAVE_Z = 32
//------------------------------------------------------
/*
* Swarm fields
*/
private constant string SWARM_RELEASE_SFX = ""
private constant string SWARM_MODEL = "Abilities\\Weapons\\CryptFiendMissile\\CryptFiendMissile.mdl"
/*
* Swarm appearance
*/
private constant real SWARM_SCALE = 1
private constant integer SWARM_RED = 255
private constant integer SWARM_GREEN = 255
private constant integer SWARM_BLUE = 255
private constant integer SWARM_ALPHA = 255
/*
* Number of swarm segment
*/
private constant integer SWARM_COUNT = 5
/*
* How curved the Swarm is
* (the arc is 2D and is perpendicular to the point)
*/
private constant real SWARM_ARC = 1
/*
* Swarm height
*/
private constant real SWARM_Z = 32
endglobals
/*
* Setup Devourer IDs
*/
globals
private integer array DEVOURER_ID
endglobals
private function SetupDevourerId takes nothing returns nothing
set DEVOURER_ID[1] = 'dev1'
set DEVOURER_ID[2] = 'dev2'
set DEVOURER_ID[3] = 'dev3'
endfunction
/*
* For checking alive units
*/
native UnitAlive takes unit u returns boolean
private function ManaFeedValid takes unit u, unit t, player p returns boolean
return GetUnitState(t, UNIT_STATE_MAX_MANA) > 0 and UnitAlive(t) and t != u // and IsUnitEnemy(t, p)
endfunction
private function HealthFeedValid takes unit u, unit t, player p returns boolean
return UnitAlive(t) and t != u // and IsUnitEnemy(t, p)
endfunction
private function ManaReleaseValid takes unit u, unit t, player p returns boolean
return u != t and /*
*/ IsUnitEnemy(t, p) and /*
*/ UnitAlive(t) and /*
*/ not IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ not IsUnitType(t, UNIT_TYPE_STRUCTURE)
endfunction
private function SwarmValid takes unit u, unit t, player p returns boolean
return u != t and /*
*/ IsUnitEnemy(t, p) and /*
*/ UnitAlive(t) and /*
*/ not IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ not IsUnitType(t, UNIT_TYPE_STRUCTURE)
endfunction
/*
* For calculating Values with respect to levels
*/
private function GetLevelValueR takes real base, real increment, integer level returns real
return base + increment*level
endfunction
private function GetLevelValueI takes integer base, integer increment, integer level returns integer
return base + increment*level
endfunction
/*
* For calculating value variance
*/
private function GetVarianceR takes real base, real variance returns real
if variance == 0 then
return base
endif
return GetRandomReal(base - variance, base + variance)
endfunction
private function GetVarianceI takes integer base, integer variance returns integer
if variance == 0 then
return base
endif
return GetRandomInt(base - variance, base + variance)
endfunction
/*
* For calculating value over time
*/
private function LinearR takes real a, real b, real t returns real
return a + (b - a)*t
endfunction
private function LinearI takes integer a, integer b, real t returns integer
return R2I(LinearR(I2R(a), I2R(b), t))
endfunction
/*
* Parabola function
*/
function Parabola takes real x, real d, real h returns real
return 4*h*x*(d - x)/(d*d)
endfunction
/**************************************
*
* For getting the location surface z
*
**************************************/
static if (not LIBRARY_ZLibrary) then
globals
private constant location p = Location(0.0, 0.0)
endglobals
function GetSurfaceZ takes real x, real y returns real
call MoveLocation(p, x, y)
return GetLocationZ(p)
endfunction
endif
/*
* Making the unit fly
*/
private function UnitFly takes unit u returns boolean
return UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf')
endfunction
/*
* Create a unit without shadows
*/
private function CreateUnitWithoutShadow takes player owner, integer uid, real x, real y, real facing returns unit
local image i = CreateImage("Textures\\white.blp", 1, 1, 0, 0, 0, 0, 1, 1, 0, 3)
call DestroyImage(i)
set bj_lastCreatedUnit = CreateUnit(owner, uid, x, y, facing)
call DestroyImage(i)
call CreateImage("Textures\\white.blp", 1, 1, 0, 0, 0, 0, 1, 1, 0, 3)
call SetImageRenderAlways(i, false)
call SetImageColor(i, 0, 0, 0, 0)
return bj_lastCreatedUnit
endfunction
/*
* For handling lists and periods
*/
//! textmacro DEVOURER_LIST_TIMER
/*
* list variables
*/
private static thistype array next
private static thistype array prev
/*
* the timer
*/
private static constant timer t = CreateTimer()
/*
* method that allocates and inserts the allocated
* instance to the linked list
*/
private static method insert takes code c returns thistype
// allocate new instance
local thistype this = allocate()
// insert it to the list
set next[this] = 0
set prev[this] = prev[0]
set next[prev[0]] = this
set prev[0] = this
// check if list contains only a single instance
if prev[this] == 0 then
// if true, start timer
call TimerStart(t, TIMEOUT, true, c)
endif
// return instance
return this
endmethod
/*
* method that deallocates and removes the allocated
* instance from the linked list
*/
private method remove takes nothing returns nothing
// dealloc instance
call deallocate()
// remove isntance from list
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
endmethod
//! endtextmacro
/*
* Structs for holding appearance values and position values
*/
private struct Appearance
integer red
integer green
integer blue
integer alpha
real scale
endstruct
/*
* Struct for timed appearances (fade, color change, etc.)
*/
private struct TimedAppearance
//! runtextmacro DEVOURER_LIST_TIMER()
private unit u
// start and end appearances
private Appearance start
private Appearance end
// timer
private real current
private real max
method destroy takes nothing returns nothing
call remove()
set u = null
call start.destroy()
call end.destroy()
set current = 0
set max = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
local real pct
local integer a
local integer r
local integer g
local integer b
local real s
loop
exitwhen 0 == this
/*
* Check if the timer has reached the max time.
*/
if current < max then
/*
* if not, increment by TIMEOUT
*/
set current = current + TIMEOUT
/*
* Calculate the progress of the timer
*/
set pct = current/max
/*
* apply linear calculation from start values to end values
*/
set a = LinearI(start.alpha, end.alpha, pct)
set r = LinearI(start.red, end.red, pct)
set g = LinearI(start.green, end.green, pct)
set b = LinearI(start.blue, end.blue, pct)
set s = LinearR(start.scale, end.scale, pct)
/*
* apply the calculated appearance
*/
call SetUnitVertexColor(u, r, g, b, a)
call SetUnitScale(u, s, 0, 0)
else
call destroy()
endif
set this = next[this]
endloop
endmethod
static method add takes unit temp, Appearance startColor, Appearance endColor, real maxTime returns nothing
/*
* Get the new node
*/
local thistype this = insert(function thistype.period)
/*
* setup the variables values
*/
set start = startColor
set end = endColor
set current = 0
set max = maxTime
set u = temp
/*
* Modify the unit's appearance using the start values
*/
call SetUnitVertexColor(u, start.red, start.green, start.blue, start.alpha)
call SetUnitScale(u, start.scale, 0, 0)
endmethod
endstruct
/*
* Struct for effect dummies
*/
private struct Particle
readonly unit u
readonly real x
readonly real y
readonly real z
private effect mdl
static method create takes string sfx, real tx, real ty, real tz, real face returns thistype
local thistype this = allocate()
/*
* Get the dummy
*/
set u = GetRecycledUnit(tx, ty, false, face)
/*
* create the vector
*/
set x = tx
set y = ty
set z = GetSurfaceZ(tx, ty) + tz // calculate the true z
call SetUnitFlyHeight(u, tz, 0)
/*
* attach model
*/
set mdl = AddSpecialEffectTarget(sfx, u, "origin")
return this
endmethod
method destroy takes nothing returns nothing
/*
* recycle the dummy
*/
call AddRecycleTimer(u, 2.0)
/*
* destroy the model
*/
call DestroyEffect(mdl)
set u = null
set mdl = null
set x = 0
set y = 0
set z = 0
endmethod
endstruct
/*
* Projectile motion
*/
//! textmacro DEV_PROJECTILE
/*
* Polar offsets
*/
private real ox
private real oy
private real oz
/*
* the target data
*/
private unit target
private real size
/*
* Speed of the missile (used for homing)
*/
private real speed
/*
* How long does the missile last (used for missiles that are not homing)
*/
private real missileTime
/*
* For checking
*/
private boolean started
/*
* Clear instance data then execute onImpact method
*/
private method destroyMissile takes nothing returns nothing
if started then
call onImpact(target)
set target = null
set speed = 0
set missileTime = 0
set started = false
endif
endmethod
/*
* Update the movement of the projectile
*/
private method move takes nothing returns nothing
local real ux
local real uy
local real uz
local real tx
local real ty
local real tz
local real dx
local real dy
local real dz
local real a2
local real a3
/*
* Instance is valid?
*/
if started then
/*
* Get coordinates of the projectile
*/
set ux = GetUnitX(u)
set uy = GetUnitY(u)
set uz = GetSurfaceZ(ux, uy) + GetUnitFlyHeight(u)
/*
* Check if target is alive (also for checking if it is homing
*/
if UnitAlive(target) then
/*
* Get the target's coordinates
*/
set tx = GetUnitX(target)
set ty = GetUnitY(target)
set tz = GetSurfaceZ(tx, ty) + GetUnitFlyHeight(target)
/*
* Get coordinate differences
*/
set dx = tx - ux
set dy = ty - uy
set dz = tz - uz
/*
* Check if missile has already hit the target
*/
if GetMagnitude3D(dx, dy, dz) <= size then
/*
* Destroy if true
*/
call destroyMissile()
endif
/*
* Calculate 2D angle and 3D pitch
*/
set a2 = Atan2(dy, dx)
set a3 = Atan2(dz, GetMagnitude2D(dx, dy))
/*
* Update coordinates
*/
set ux = ux + speed*Cos(a2)*Cos(a3)
set uy = uy + speed*Sin(a2)*Cos(a3)
set uz = uz + speed*Sin(a3)
/*
* For safe coordinates
*/
set ux = GetBoundedX(ux)
set uy = GetBoundedY(uy)
set uz = uz - GetSurfaceZ(ux, uy)
/*
* Coordinates is safe, move the unit
*/
call SetUnitX(u, ux)
call SetUnitY(u, uy)
call SetUnitFlyHeight(u, uz, 0)
/*
* Set the facing towards the target
*/
call SetUnitFacing(u, a2*bj_RADTODEG)
/*
* If the unit is a dummy, update pitch
*/
if GetUnitTypeId(u) == DUMMY_ID then
call SetUnitAnimationByIndex(u, R2I(a3*bj_RADTODEG + 90.5))
endif
elseif missileTime > 0 then
/*
* Update missile time
*/
set missileTime = missileTime - TIMEOUT
/*
* Update coordinates
*/
set ux = GetBoundedX(ux + ox)
set uy = GetBoundedY(uy + oy)
call SetUnitX(u, ux)
call SetUnitY(u, uy)
call SetUnitFlyHeight(u, (uz + oz) - GetSurfaceZ(ux, uy), 0)
else
/*
* Unit has no target and missileTime, destroy
*/
call destroyMissile()
endif
endif
endmethod
/*
* To set the target
*/
private method setTarget takes unit temp returns nothing
set target = temp
set size = GetUnitCollision(temp)
if GetUnitTypeId(temp) == DUMMY_ID then
/*
* If the unit is a dummy, give it a larger collision size
*/
set size = size + DUMMY_COLLISION_SIZE
endif
endmethod
/*
* To set the time
*/
private method setTime takes real time returns nothing
set missileTime = time
endmethod
/*
* To launch the missile
*/
private method start takes real vel, real angle2d, real angle3d returns nothing
set speed = vel*TIMEOUT
set ox = speed*Cos(angle2d)*Cos(angle3d)
set oy = speed*Sin(angle2d)*Cos(angle3d)
set oz = speed*Sin(angle3d)
set started = true
endmethod
//! endtextmacro
/*
* Struct for creeps
*/
struct Creep
/*
* Implement list
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The creep
*/
private unit u
/*
* Missile required method
*/
method onImpact takes unit temp returns nothing
call remove()
call RemoveUnit(u)
set u = null
endmethod
/*
* Implement missile
*/
//! runtextmacro DEV_PROJECTILE()
private static method period takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen 0 == this
call move()
set this = next[this]
endloop
endmethod
static method spawn takes player p, real x, real y, boolean aura returns nothing
/*
* Get the new instance
*/
local thistype this = insert(function thistype.period)
local Appearance start = Appearance.create()
local Appearance end = Appearance.create()
/*
* Generate random angle
*/
local real angle2d = GetRandomReal(-bj_PI, bj_PI)
local real velocity
local real time
local real z
/*
* There are two types of creeps(see constants) : Aura and Nova
*/
if aura then
set start.red = ACREEP_START_RED
set start.green = ACREEP_START_GREEN
set start.blue = ACREEP_START_BLUE
set start.alpha = ACREEP_START_ALPHA
set start.scale = ACREEP_START_SCALE
set end.red = ACREEP_END_RED
set end.green = ACREEP_END_GREEN
set end.blue = ACREEP_END_BLUE
set end.alpha = ACREEP_END_ALPHA
set end.scale = ACREEP_END_SCALE
set velocity = GetVarianceR(ACREEP_SPEED, ACREEP_SPEED_VAR)
set time = ACREEP_TIME
set z = ACREEP_Z
else
set start.red = NCREEP_START_RED
set start.green = NCREEP_START_GREEN
set start.blue = NCREEP_START_BLUE
set start.alpha = NCREEP_START_ALPHA
set start.scale = NCREEP_START_SCALE
set end.red = NCREEP_END_RED
set end.green = NCREEP_END_GREEN
set end.blue = NCREEP_END_BLUE
set end.alpha = NCREEP_END_ALPHA
set end.scale = NCREEP_END_SCALE
set velocity = GetVarianceR(NCREEP_SPEED, NCREEP_SPEED_VAR)
set time = NCREEP_TIME
set z = NCREEP_Z
endif
/*
* Create the unit without shadow
*/
set u = CreateUnitWithoutShadow(p, CREEP_ID, x, y, angle2d*bj_RADTODEG)
/*
* To prevent the unit from displacing
*/
call SetUnitPathing(u, false)
call SetUnitX(u, x)
call SetUnitY(u, y)
/*
* Make the unit a "dummy"
*/
call PauseUnit(u, true)
call UnitAddAbility(u, 'Aloc')
call UnitFly(u)
call SetUnitFlyHeight(u, z, 0)
call SetUnitUseFood(u, false)
call SetUnitTurnSpeed(u, 1000)
/*
* Play the animation
*/
if aura then
call SetUnitAnimationByIndex(u, ACREEP_ANIM)
else
call SetUnitAnimationByIndex(u, NCREEP_ANIM)
endif
/*
* Launch the unit as a missile
*/
call setTime(time)
call start(velocity, angle2d, 0)
/*
* Apply timed appearance
*/
call TimedAppearance.add(u, start, end, time)
endmethod
endstruct
/*
* Struct for portal shards
*/
private struct Shard
/*
* Implement the list timer
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The shard.
* Note: "u" is required by the Missile textmacro
*/
private Particle pt
private unit u
/*
* Required missile method
*/
method onImpact takes unit temp returns nothing
call remove()
call pt.destroy()
set u = null
endmethod
/*
* Implement missile
*/
//! runtextmacro DEV_PROJECTILE()
private static method period takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen 0 == this
call move()
set this = next[this]
endloop
endmethod
static method spawn takes Particle p returns nothing
/*
* Get new instance
*/
local thistype this = insert(function thistype.period)
/*
* Get the spawn variance
*/
local real a2 = GetVarianceR(SHARD_ANGLE, SHARD_ANGLE_VAR)
local real a3 = GetVarianceR(SHARD_Z_ANGLE, SHARD_Z_ANGLE_VAR)
local real vel = GetVarianceR(SHARD_SPEED, SHARD_SPEED_VAR)
local real dist = GetVarianceR(SHARD_DISTANCE, SHARD_DISTANCE_VAR)
/*
* Calculate the spawn coordinates
*/
local real x = p.x + dist*Cos(a2)*Cos(a3)
local real y = p.y + dist*Sin(a2)*Cos(a3)
local real z = p.z + dist*Sin(a3)
/*
* Calculate the time it takes to travel towards the target
*/
local real time = dist/vel
local Appearance start
local Appearance end
/*
* Create shard
*/
set pt = Particle.create(SHARD_MODEL, x, y, z, a2*bj_RADTODEG + 180)
set u = pt.u
/*
* Launch missile
*/
call setTarget(p.u)
call start(vel, 0, 0)
/*
* Create timed appearance
*/
set start = Appearance.create()
set end = Appearance.create()
set start.red = SHARD_START_RED
set start.green = SHARD_START_GREEN
set start.blue = SHARD_START_BLUE
set start.alpha = SHARD_START_ALPHA
set start.scale = SHARD_START_SCALE
set end.red = SHARD_END_RED
set end.green = SHARD_END_GREEN
set end.blue = SHARD_END_BLUE
set end.alpha = SHARD_END_ALPHA
set end.scale = SHARD_END_SCALE
call TimedAppearance.add(u, start, end, time)
endmethod
endstruct
/*
* Struct for swarm
*/
private struct Swarm
/*
* Implement list
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The particles that travel
*/
private Table particles
/*
* the source of damage
*/
private unit own
private player owner
private real damage
private real radius
/*
* Position of swarm
*/
private real x
private real y
/*
* Polar offset
*/
private real ox
private real oy
/*
* Duration of swarm
*/
private real time
/*
* For saving units who are already damaged
*/
private Table damagedUnits
private static group g = CreateGroup()
method destroy takes nothing returns nothing
local integer i = 0
/*
* Remove instance
*/
call remove()
/*
* If the swarm has particles, destroy them
*/
if SWARM_COUNT > 0 then
loop
set i = i + 1
call Particle(particles.integer[i]).destroy()
exitwhen i >= SWARM_COUNT
endloop
call particles.destroy()
endif
/*
* Clear data
*/
call damagedUnits.destroy()
set own = null
set owner = null
set x = 0
set y = 0
set ox = 0
set oy = 0
set radius = 0
set damage = 0
set time = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
local integer i
local unit u
loop
exitwhen 0 == this
/*
* Check if swarm can still travel
*/
if time > 0 then
/*
* Update time
*/
set time = time - TIMEOUT
/*
* Move the swarm safely
*/
set x = GetBoundedX(x + ox)
set y = GetBoundedY(y + oy)
/*
* Enum units
*/
call GroupEnumUnitsInRange(g, x, y, radius, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
set i = GetHandleId(u)
/*
* Check if unit is valid through filter and if it has not yet damaged
*/
if SwarmValid(own, u, owner) and not damagedUnits.boolean[i] then
/*
* Add the unit to damagedUnits then deal damage to it
*/
set damagedUnits.boolean[i] = true
call UnitDamageTarget(own, u, damage, true, false, SWARM_DAMAGE_ATKTYPE, SWARM_DAMAGE_DMGTYPE, null)
endif
/*
* Remove unit from group
*/
call GroupRemoveUnit(g, u)
endloop
/*
* If swarm has particles, update their positions parallel to the swarm
*/
if SWARM_COUNT > 0 then
set i = 0
loop
set i = i + 1
set u = particles.unit[SWARM_COUNT + i]
call SetUnitX(u, GetBoundedX(GetUnitX(u) + ox))
call SetUnitY(u, GetBoundedY(GetUnitY(u) + oy))
exitwhen i >= SWARM_COUNT
endloop
endif
else
call destroy()
endif
set this = next[this]
endloop
endmethod
static method spawn takes unit source, unit target, integer level returns nothing
local thistype this = insert(function thistype.period)
local real tx = GetUnitX(target)
local real ty = GetUnitY(target)
local real a2
local real facing
local real offset = GetLevelValueR(SWARM_SPAWN_OFFSET, SWARM_SPAWN_OFFSET_PER_LVL, level)
local integer i
local real sx
local real sy
local real ex
local real ey
local real cx
local real cy
local real arc
local real ri
local real max
local Particle p
/*
* Get the source coordinates
*/
set x = GetUnitX(source)
set y = GetUnitY(source)
/*
* Get the angle towards the target
*/
set a2 = Atan2(ty - y, tx - x)
/*
* Move the swarm position by offset
*/
set ox = SWARM_SPEED*TIMEOUT*Cos(a2)
set oy = SWARM_SPEED*TIMEOUT*Sin(a2)
set x = x + offset*Cos(a2)
set y = y + offset*Sin(a2)
/*
* Setup data
*/
set damagedUnits = Table.create()
set damage = GetLevelValueR(SWARM_DAMAGE, SWARM_DAMAGE_PER_LVL, level)
set radius = GetLevelValueR(SWARM_RADIUS, SWARM_RADIUS_PER_LVL, level)
set own = source
set owner = GetOwningPlayer(own)
/*
* If the constant has a swarm count, create the particles
*/
set i = 0
if SWARM_COUNT > 0 then
set particles = Table.create()
set facing = a2*bj_RADTODEG
/*
* Calculate the perpendicular position
*/
set ex = x + radius*Cos(a2 + bj_PI/2)
set ey = y + radius*Sin(a2 + bj_PI/2)
set sx = x + radius*Cos(a2 - bj_PI/2)
set sy = y + radius*Sin(a2 - bj_PI/2)
set max = I2R(SWARM_COUNT) + 1
loop
set i = i + 1
set ri = I2R(i)
/*
* Get the position
*/
set cx = LinearR(sx, ex, ri/max)
set cy = LinearR(sy, ey, ri/max)
/*
* Calculate parabola offset
*/
set arc = Parabola(ri, max, SWARM_ARC*radius)
/*
* Update position
*/
set cx = cx + arc*Cos(a2)
set cy = cy + arc*Sin(a2)
/*
* Create particle
*/
set p = Particle.create(SWARM_MODEL, cx, cy, SWARM_Z, facing)
set particles.integer[i] = p
set particles.unit[SWARM_COUNT + i] = p.u
/*
* Apply appearance changes
*/
call SetUnitScale(p.u, SWARM_SCALE, 0, 0)
call SetUnitVertexColor(p.u, SWARM_RED, SWARM_GREEN, SWARM_BLUE, SWARM_ALPHA)
exitwhen i >= SWARM_COUNT
endloop
endif
/*
* Calculate the time needed
*/
set time = GetLevelValueR(SWARM_TRAVEL_DIST, SWARM_TRAVEL_DIST_PER_LVL, level)/SWARM_SPEED
/*
* Create release sfx
*/
if SWARM_RELEASE_SFX != "" then
call DestroyEffect(AddSpecialEffect(SWARM_RELEASE_SFX, x, y))
endif
endmethod
endstruct
/*
* Struct for the shockwave (mana release)
*/
private struct Shockwave
/*
* Implement list
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The particles
*/
private Table particles
/*
* The damage source
*/
private player pOwner
private unit owner
/*
* The center of shockwave
* This is needed if the shockwave is not locked
* to it's owner
*/
private real x
private real y
private real damage
/*
* The time it takes for the shockwave to expand
*/
private real expandTime
/*
* The max shockwave radius
*/
private real maxRadius
/*
* Same concept as the swarm
*/
private Table damagedUnits
private static group g = CreateGroup()
method destroy takes nothing returns nothing
local integer i = 0
/*
* Remove instance
*/
call remove()
/*
* If the shockwave as particles, destroy each
*/
if WAVE_COUNT > 0 then
loop
set i = i + 1
call Particle(particles[i]).destroy()
exitwhen i >= WAVE_COUNT
endloop
call particles.destroy()
endif
call damagedUnits.destroy()
/*
* Clear data
*/
set pOwner = null
set owner = null
set x = 0
set y = 0
set damage = 0
set expandTime = 0
set maxRadius = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
local integer i
local unit t
local integer id
local real radius
local real pct
local real angle
loop
exitwhen 0 == this
/*
* If the shockwave is locked and the source
* is alive, move the shockwave
*/
if MANA_RELEASED_LOCKED and UnitAlive(owner) then
set x = GetUnitX(owner)
set y = GetUnitY(owner)
endif
/*
* Check if the shockwave is still growing
*/
if expandTime < MANA_RELEASE_GROW_TIME then
/*
* update time
*/
set expandTime = expandTime + TIMEOUT
/*
* Calculate the progress of the growth
*/
set pct = expandTime/MANA_RELEASE_GROW_TIME
/*
* Calculate the radius of the shockwave
*/
set radius = maxRadius*pct
/*
* Enum units
*/
call GroupEnumUnitsInRange(g, x, y, radius, null)
loop
set t = FirstOfGroup(g)
exitwhen t == null
set id = GetHandleId(t)
/*
* Same mechanics as to the swarm
*/
if ManaReleaseValid(owner, t, pOwner) and not damagedUnits.boolean[id] then
call UnitDamageTarget(owner, t, damage, true, false, MANA_RELEASE_ATKTYPE, MANA_RELEASE_DMGTYPE, null)
set damagedUnits.boolean[id] = true
endif
call GroupRemoveUnit(g, t)
endloop
/*
* If the shockwave has particles, update their position
*/
if WAVE_COUNT > 0 then
set i = 0
set angle = (bj_PI*2)/I2R(WAVE_COUNT)
loop
set i = i + 1
call SetUnitX(particles.unit[WAVE_COUNT + i], GetBoundedX(x + radius*Cos(angle*I2R(i))))
call SetUnitY(particles.unit[WAVE_COUNT + i], GetBoundedY(y + radius*Sin(angle*I2R(i))))
exitwhen i >= WAVE_COUNT
endloop
endif
else
call destroy()
endif
set this = next[this]
endloop
endmethod
static method start takes unit o, player own, integer level returns nothing
local thistype this = insert(function thistype.period)
local integer i
local real angle
local Particle p
set owner = o
set pOwner = own
set x = GetUnitX(o)
set y = GetUnitY(o)
set expandTime = 0
set damagedUnits = Table.create()
set maxRadius = GetLevelValueR(MANA_RELEASE_RADIUS, MANA_RELEASE_RADIUS_PER_LVL, level)
set damage = GetLevelValueR(MANA_RELEASE_DMG, MANA_RELEASE_DMG_PER_LVL, level)
set i = 0
/*
* If the shockwave can create particles, create them
*/
if WAVE_COUNT > 0 then
set particles = Table.create()
/*
* Calculate angle distribution
*/
set angle = 360/I2R(WAVE_COUNT)
loop
set i = i + 1
/*
* Create particles
*/
set p = Particle.create(WAVE_MODEL, x, y, WAVE_Z, angle*I2R(i))
set particles[i] = p
set particles.unit[WAVE_COUNT + i] = p.u
/*
* Update their appearances
*/
call SetUnitVertexColor(p.u, WAVE_RED, WAVE_GREEN, WAVE_BLUE, WAVE_ALPHA)
call SetUnitScale(p.u, WAVE_SCALE, 0, 0)
exitwhen i >= WAVE_COUNT
endloop
endif
/*
* If the shockwave has a release effect, create it
*/
if MANA_RELEASE_SFX != "" then
call DestroyEffect(AddSpecialEffect(MANA_RELEASE_SFX, x, y))
endif
endmethod
endstruct
/*
* Shadow trail
*/
private struct DevourerTrail
/*
* Implement list
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The trail
*/
private unit u
/*
* Trail duration
*/
private real time
method destroy takes nothing returns nothing
call remove()
call RemoveUnit(u)
set u = null
set time = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen 0 == this
if time > 0 then
set time = time - TIMEOUT
else
call destroy()
endif
set this = next[this]
endloop
endmethod
static method create takes unit own, player p, integer lvl returns thistype
local thistype this = insert(function thistype.period)
local Appearance start = Appearance.create()
local Appearance end = Appearance.create()
local real x = GetUnitX(own)
local real y = GetUnitY(own)
/*
* Create the unit without shadow (same concept as to Creep struct)
*/
set u = CreateUnitWithoutShadow(p, DEVOURER_ID[lvl], x, y, GetUnitFacing(own))
/*
* In case the unit is displaced
*/
call SetUnitPathing(u, false)
call SetUnitX(u, x)
call SetUnitY(u, y)
/*
* Make the unit a "dummy"
*/
call PauseUnit(u, true)
call UnitAddAbility(u, 'Aloc')
call UnitFly(u)
call SetUnitFlyHeight(u, DEVOURER_Z, 0)
call SetUnitUseFood(u, false)
call SetUnitTurnSpeed(u, 1000)
/*
* Play the animation
*/
call SetUnitAnimationByIndex(u, TRAIL_ANIM)
set time = TRAIL_DURATION
/*
* Apply timed appearance
*/
set start.red = DEVOURER_RED
set start.green = DEVOURER_GREEN
set start.blue = DEVOURER_BLUE
set start.alpha = DEVOURER_ALPHA
set start.scale = GetLevelValueR(DEVOURER_SCALE, DEVOURER_SCALE_PER_LVL, lvl)
set end.red = start.red
set end.green = start.green
set end.blue = start.blue
set end.alpha = 0
set end.scale = start.scale
call TimedAppearance.add(u, start, end, time)
return this
endmethod
endstruct
private struct Devourer
//! runtextmacro DEVOURER_LIST_TIMER()
private static Table id
private unit u
private integer level
private real time
private real timeBonus
private Table healthTable
private real healthMax
private real healthStorage
private real healthRadius
private real healthRate
private Table manaTable
private real manaMax
private real manaStorage
private real manaRadius
private real manaCD
private real manaCDMax
private real swarmMax
private real swarmCD
private player owner
private real trailPeriod
private real prevX
private real prevY
private real auraPeriod
private static group g = CreateGroup()
method destroy takes nothing returns nothing
call remove()
/*
* If the devourer as death sfx
*/
if DEVOURER_DEATH_SFX != "" then
call DestroyEffect(AddSpecialEffect(DEVOURER_DEATH_SFX, GetUnitX(u), GetUnitY(u)))
endif
/*
* Destroy health and mana table
*/
call healthTable.destroy()
call manaTable.destroy()
call id.remove(GetHandleId(u))
call RemoveUnit(u)
/*
* Clear data
*/
set u = null
set level = 0
set healthMax = 0
set healthStorage = 0
set healthRadius = 0
set healthRate = 0
set manaMax = 0
set manaStorage = 0
set manaRadius = 0
set manaCD = 0
set manaCDMax = 0
set swarmMax = 0
set swarmCD = 0
set owner = null
set trailPeriod = 0
set prevX = 0
set prevY = 0
set auraPeriod = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
local unit t
local integer id
local real prevLife
local real newLife
local real lifeDif
local real prevMana
local real newMana
local real manaDif
local integer i
local real x
local real y
loop
exitwhen 0 == this
/*
* Check if the devourer's timed life has ended or if it still alive
*/
if time > 0 and UnitAlive(u) then
/*
* update time
*/
set time = time - TIMEOUT
/*
* Get devourer position
*/
set x = GetUnitX(u)
set y = GetUnitY(u)
/*
* If the spell is allowed to create trails
*/
if SHOW_TRAIL then
/*
* Check if trailPeriod has ended
*/
if trailPeriod > 0 then
set trailPeriod = trailPeriod - TIMEOUT
/*
* Check if the devourer has changed position
*/
elseif prevX != x and prevY != y then
/*
* If true, turn the trail cooldown on
*/
set trailPeriod = TRAIL_PERIOD
/*
* Create the trail
*/
call DevourerTrail.create(u, owner, level)
/*
* Save the devourer's position
*/
set prevX = x
set prevY = y
endif
endif
/*
* If the spell is allowed to show the aura
*/
if SHOW_AURA then
if auraPeriod > 0 then
set auraPeriod = auraPeriod - TIMEOUT
else
set auraPeriod = AURA_SPAWN_PERIOD
set i = ACREEP_COUNT
loop
exitwhen i == 0
call Creep.spawn(owner, x, y, true)
set i = i - 1
endloop
endif
endif
/*
* Check if the devourer can monitor the health
*/
if 0 < healthRadius then
/*
* Enum units
*/
call GroupEnumUnitsInRange(g, x, y, healthRadius, null)
loop
set t = FirstOfGroup(g)
exitwhen t == null
/*
* Check for unit validity
*/
if HealthFeedValid(u, t, owner) then
/*
* Get the handle id of the unit
*/
set id = GetHandleId(t)
/*
* Get the recorded life of the unit
*/
set prevLife = healthTable.real[id]
/*
* Get the current life of the unit
*/
set newLife = GetWidgetLife(t)
/*
* Save the current life
*/
set healthTable.real[id] = newLife
/*
* Check if the unit has lost some health
*/
set lifeDif = prevLife - newLife
/*
* If true, store the lost health
*/
if lifeDif > 0 then
set healthStorage = healthStorage + lifeDif
/*
* Heal the devourer
*/
call SetWidgetLife(u, GetWidgetLife(u) + lifeDif*healthRate)
/*
* If the health storage has reached it's max value
* increment the timed life
*/
if healthStorage >= healthMax then
set healthStorage = 0
set time = time + timeBonus
endif
endif
endif
call GroupRemoveUnit(g, t)
endloop
endif
/*
* Check if the devourer can monitor the mana
*/
if 0 < manaRadius then
/*
* Update shockwave cooldown
*/
if manaCD > 0 then
set manaCD = manaCD - TIMEOUT
endif
/*
* Enum units
*/
call GroupEnumUnitsInRange(g, x, y, manaRadius, null)
loop
set t = FirstOfGroup(g)
exitwhen t == null
/*
* Validate unit
*/
if ManaFeedValid(u, t, owner) then
/*
* Get the handle id of the unit
*/
set id = GetHandleId(t)
/*
* Get the previous mana of the unit
*/
set prevMana = manaTable.real[id]
/*
* Get the current mana
*/
set newMana = GetUnitState(t, UNIT_STATE_MANA)
/*
* Save the new mana
*/
set manaTable.real[id] = newMana
/*
* Check if the unit can collect mana while on cooldown
*/
if COLLECT_MANA_ON_CD or manaCD <= 0 then
/*
* get mana difference
*/
set manaDif = prevMana - newMana
/*
* if the unit has lost mana, add it to the storage
*/
if manaDif > 0 then
set manaStorage = manaStorage + manaDif
/*
* Check if the mana storage has reached it's max value
*/
if manaStorage >= manaMax and manaCD <= 0 then
/*
* Empty the storage, start the cooldown then release the shockwave
*/
set manaStorage = 0
set manaCD = manaCDMax
call Shockwave.start(u, owner, level)
endif
endif
endif
endif
call GroupRemoveUnit(g, t)
endloop
endif
/*
* Update the swarm release cooldown
*/
if swarmCD > 0 then
set swarmCD = swarmCD - TIMEOUT
endif
else
call destroy()
endif
set this = next[this]
endloop
endmethod
static method summon takes player p, real x, real y, integer lvl returns nothing
local thistype this = insert(function thistype.period)
local integer i
/*
* Create the devourer
*/
set u = CreateUnit(p, DEVOURER_ID[lvl], x, y, 270)
/*
* Apply appearance changes
*/
call SetUnitVertexColor(u, DEVOURER_RED, DEVOURER_GREEN, DEVOURER_BLUE, DEVOURER_ALPHA)
call SetUnitScale(u, GetLevelValueR(DEVOURER_SCALE, DEVOURER_SCALE_PER_LVL, lvl), 0, 0)
call UnitFly(u)
call SetUnitFlyHeight(u, DEVOURER_Z, 0)
/*
* Setup all data
*/
set owner = p
set level = lvl
set time = GetLevelValueR(TIMED_LIFE, TIMED_LIFE_PER_LVL, lvl)
set timeBonus = GetLevelValueR(TIMED_LIFE_BONUS, TIMED_LIFE_BONUS_PER_LVL, lvl)
set manaTable = Table.create()
set manaMax = GetLevelValueR(MANA_RELEASE_REQ, MANA_RELEASE_REQ_PER_LVL, lvl)
set manaStorage = 0
set manaCD = 0
set manaCDMax = GetLevelValueR(MANA_RELEASE_CD, MANA_RELEASE_CD_PER_LVL, lvl)
set manaRadius = GetLevelValueR(MANA_FEED_RADIUS, MANA_FEED_RADIUS_PER_LVL, lvl)
set healthTable = Table.create()
set healthMax = GetLevelValueR(TIMED_LIFE_REQ, TIMED_LIFE_REQ_PER_LVL, lvl)
set healthStorage = 0
set healthRate = GetLevelValueR(HEALTH_RATE, HEALTH_RATE_PER_LVL, lvl)
set healthRadius = GetLevelValueR(HEALTH_FEED_RADIUS, HEALTH_FEED_RADIUS_PER_LVL, lvl)
set swarmMax = GetLevelValueR(SWARM_CD, SWARM_CD_PER_LVL, lvl)
/*
* If the devourer is displaced
*/
set x = GetUnitX(u)
set y = GetUnitY(u)
/*
* If the spell is allowed to create trail, save the position
*/
if SHOW_TRAIL then
set prevX = x
set prevY = y
endif
/*
* Create the creep nova if it is allowed
*/
if SHOW_CREEP_NOVA then
set i = NCREEP_COUNT
loop
exitwhen i == 0
call Creep.spawn(p, x, y, false)
set i = i - 1
endloop
endif
/*
* Create spawn sfx
*/
if ON_SPAWN_SFX != "" then
call DestroyEffect(AddSpecialEffect(ON_SPAWN_SFX, x, y))
endif
/*
* Save the instance (For swarm proc)
*/
set id[GetHandleId(u)] = this
endmethod
static method onAttack takes nothing returns boolean
local unit source = GetAttacker()
local unit victim = GetTriggerUnit()
local thistype this = id[GetHandleId(source)]
/*
* If unit is a valid devourer
*/
if this != 0 then
/*
* Check if target is valid and swarm is not in cooldown
*/
if swarmCD <= 0 and SwarmValid(source, victim, owner) then
set swarmCD = swarmMax
call Swarm.spawn(source, victim, level)
endif
endif
set source = null
set victim = null
return false
endmethod
static method init takes nothing returns nothing
set id = Table.create()
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.onAttack)
endmethod
endstruct
private struct SummonDevourer
/*
* Implement list
*/
//! runtextmacro DEVOURER_LIST_TIMER()
/*
* The owner of the portal
*/
private player owner
private Particle portal
private real time
/*
* For spawning shards
*/
private real spawnTime
private integer level
method destroy takes nothing returns nothing
call remove()
call portal.destroy()
set owner = null
set time = 0
set spawnTime = 0
set level = 0
endmethod
private static method period takes nothing returns nothing
local thistype this = next[0]
local integer i
loop
exitwhen 0 == this
/*
* Check if portal is not dying
*/
if time > 0 then
/*
* Update time
*/
set time = time - TIMEOUT
/*
* Create shards if allowed
*/
if SHOW_SHARDS then
if spawnTime > 0 then
set spawnTime = spawnTime - TIMEOUT
else
set spawnTime = SHARD_PERIOD
set i = SHARD_COUNT
loop
exitwhen i == 0
call Shard.spawn(portal)
set i = i - 1
endloop
endif
endif
else
/*
* Create the devourer at current position
*/
call Devourer.summon(owner, portal.x, portal.y, level)
call destroy()
endif
set this = next[this]
endloop
endmethod
private static real checkY
private static real checkX
private static real checkRadius
private static integer treeCount
private static Table trees
private static method treeFilter takes nothing returns boolean
local real dx
local real dy
local destructable dest = GetFilterDestructable()
/*
* Get the coordinate differences
*/
set dx = GetDestructableX(dest) - checkX
set dy = GetDestructableY(dest) - checkY
/*
* Check if
* - The destructable is within range
* - The destructable is a tree and alive
*/
if dx*dx + dy*dy <= checkRadius and IsDestructableTree(dest) and IsDestructableAlive(dest) then
/*
* Increment tree count
*/
set treeCount = treeCount + 1
set trees.destructable[treeCount] = dest
endif
set dest = null
return false
endmethod
static method onCast takes nothing returns boolean
local thistype this
local unit caster = GetTriggerUnit()
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local Appearance start
local Appearance end
local integer lvl = GetUnitAbilityLevel(caster, ABIL_ID)
local real radius = GetLevelValueR(TREE_RADIUS_CHECK, TREE_RADIUS_CHECK_PER_LVL, lvl)
/*
* Create the rect for tree check
*/
local rect r = Rect(x - radius, y - radius, x + radius, y + radius)
/*
* Prepare tree checking
*/
call trees.flush()
set treeCount = 0
set checkX = x
set checkY = y
set checkRadius = radius*radius
call EnumDestructablesInRect(r, Filter(function thistype.treeFilter), null)
/*
* If the number of trees pass the requirement
*/
if treeCount >= GetLevelValueI(REQUIRED_TREES, REQUIRED_TREES_PER_LVL, lvl) then
/*
* Destroy all trees
*/
loop
exitwhen treeCount == 0
call KillTree(trees.destructable[treeCount])
set treeCount = treeCount - 1
endloop
/*
* Create portal
*/
set this = insert(function thistype.period)
set level = lvl
set owner = GetTriggerPlayer()
set portal = Particle.create(PORTAL_MODEL, x, y, PORTAL_Z, 270)
set time = GetLevelValueR(SPAWN_DELAY, SPAWN_DELAY_PER_LVL, level)
/*
* Apply timed appearance
*/
set start = Appearance.create()
set end = Appearance.create()
set start.red = PORTAL_START_RED
set start.green = PORTAL_START_GREEN
set start.blue = PORTAL_START_BLUE
set start.alpha = PORTAL_START_ALPHA
set start.scale = PORTAL_START_SCALE
set end.red = PORTAL_END_RED
set end.green = PORTAL_END_GREEN
set end.blue = PORTAL_END_BLUE
set end.alpha = PORTAL_END_ALPHA
set end.scale = PORTAL_END_SCALE
call TimedAppearance.add(portal.u, start, end, time)
if SHOW_SHARDS then
set spawnTime = SHARD_PERIOD
endif
else
/*
* The number is invalid, stop casting
*/
call IssueImmediateOrderById(caster, 851972)
endif
set treeCount = 0
call trees.flush()
call RemoveRect(r)
set r = null
set caster = null
return false
endmethod
static method init takes nothing returns nothing
set trees = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
endmethod
endstruct
private function Init takes nothing returns nothing
call SetupDevourerId()
call Devourer.init()
call SummonDevourer.init()
endfunction
endscope