Name | Type | is_array | initial_value |
Damage | real | No | |
DamageBlockingAbility | abilcode | No | |
DamageEvent | real | No | |
DamageEventAmount | real | No | |
DamageEventExplodesUnit | boolean | No | |
DamageEventOverride | boolean | No | |
DamageEventPhysical | boolean | No | |
DamageEventPrevAmt | real | No | |
DamageEventSource | unit | No | |
DamageEventsWasted | integer | No | |
DamageEventTarget | unit | No | |
DamageEventTrigger | trigger | No | |
DamageEventType | integer | No | |
DamageModifierEvent | real | No | |
DamageTypeDOT | integer | No | |
DamageTypeRanged | integer | No | |
DamageTypeSpell | integer | No | |
DmgEvLife | real | No | |
DmgEvN | integer | No | |
DmgEvStack | unit | Yes | |
DmgEvTimer | timer | No | |
DmgTypPrev | integer | No | |
DummyOwners | unit | Yes | |
TempGroup | group | No | |
TempInt | integer | No | |
TempPoint | location | No | |
TempUnit | unit | No | |
UDex | integer | No | |
UDexGen | integer | No | |
UDexNext | integer | Yes | |
UDexPrev | integer | Yes | |
UDexRecycle | integer | No | |
UDexUnits | unit | Yes | |
UDexWasted | integer | No | |
UnitDamageRegistered | boolean | Yes | |
UnitIndexerEnabled | boolean | No | |
UnitIndexEvent | real | No | |
UnitIndexLock | integer | Yes |
//TESH.scrollpos=3
//TESH.alwaysfold=0
//###################################################################
//#Geometry library, which provides some useful geometry functions #
//#Created by Erkki2 #
//###################################################################
library Geometry
//Creator: Maker
//Returns a boolean that tells whether the point is inside a rectangle.
//(px , py) = Point to check
//(cx , cy) = lower left corner of the rect
//(ax , ay) = upper left corner
//(bx , by) = lower right corner
function isPointInRect takes real px , real py , real cx , real cy , real ax , real ay , real bx , real by returns boolean
local real dot1 = (px-cx)*(ax-cx) + (py-cy)*(ay-cy)
local real dot2 = (ax-cx)*(ax-cx) + (ay-cy)*(ay-cy)
local real dot3 = (px-cx)*(bx-cx) + (py-cy)*(by-cy)
local real dot4 = (bx-cx)*(bx-cx) + (by-cy)*(by-cy)
return dot1 >= 0 and dot1 <= dot2 and dot3 >= 0 and dot3 <= dot4
endfunction
//Returns the parabola height (y) at selected point (x).
//Height is the max height of the parabola, and dist is the distance between roots
function getParabolaHeight takes real dist, real height, real x returns real
local real a = 4*height/dist
return a*x*(-x/(dist)+1)
endfunction
//Returns the angle factor of a parabola at selected point.
function angleFactor takes real dist, real height, real x returns real
local real a = 4*height/dist
return a*(-2/dist + 1)
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
//#################################
//#Blizzard triggered version #
//#Created by Erkki2 #
//#################################
scope Blizzard
native UnitAlive takes unit id returns boolean
// #################
// # CONFIGURABLES #
// #################
globals
//Dummy unit used for moving effects and casting spell
private constant integer DUMMY_ID = 'h000'
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A000'
//Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
private constant integer DUMMY_ABILITY = 'A001'
//Order string for dummy spell
private constant string DUMMY_ORDER = "slow"
//If NO_MISSILES == false, this effect is used as a falling down missile.
//If NO_MISILES == true, this effect is created as a stationary effect (works well for standard blizzard effect).
private constant string MISSILE_ART = "Abilities\\Spells\\Human\\Blizzard\\BlizzardTarget.mdl"
//Iteration interval
private constant real TIMEOUT = 0.03125
//Collision size of missiles. This has no effect if INDIVIDUAL_DAMAGE == false.
private constant real COLLISION = 100
//If NO_MISSILES == false, this is the time it takes for a missile to land.
//If NO_MISSILES == true, this serves as a delay between effect and damage. For standard blizzard effect, 0.8 is recommended.
private constant real FALLING_TIME = 0.8
//Heigh that missiles fall from. Has no effect if NO_MISSILES == true.
private constant real HEIGHT = 900
//Effect which is created when missiles hit the ground.
private constant string LANDING_EFFECT = ""
//Sound which is played on every wave
private constant string LOOP_SOUND = "BlizzardWave"
//If true, all missiles will damage small area around them.
//If false, each wave will damage the whole spell area, regardless where the missiles fall.
private constant boolean INDIVIDUAL_DAMAGE = false
//If true, the effect is treated as an effect without falling down.
//If false, the effect is treated as a falling down missile.
private constant boolean NO_MISSILES = true
//If true, spell will be interrupted if caster stops channelling.
private constant boolean REQUIRES_CHANNELLING = true
//True if ability is cast at a target point, false if it's centered on caster (instant, no target).
private constant boolean TARGET_ABILITY = true
//Attack type of the spell damage (hero, siege, chaos, jne...)
private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
//Damage type of the spell (normal, universal, magic etc...)
private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
endglobals
//Returns the interval between waves (in seconds)
private function setWaveInterval takes unit caster returns real
return 0.8 - 0.1 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the number of shards in each wave
private function setShardsPerWave takes unit caster returns integer
return 10 + 2 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the spell area
private function setArea takes unit caster returns real
return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the spell duration.
private function setDuration takes unit caster returns real
return 7.0 + 3.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//If INDIVIDUAL_DAMAGE == true, returns damage dealt by shard.
//If INDIVIDUAL_DAMAGE == false, returns damage dealt by wave.
private function setDamage takes unit caster returns real
return 20.0 + 10.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Dummy
private thistype next
private thistype prev
private static integer count = 0
private static group enumGroup = CreateGroup()
private integer shardsPerWave
private real area
private unit dummy
private integer executions
private real heightDecrement
private unit caster
private player ownerOfCaster
private real damage
private effect missileEffect
private real x
private real y
private boolean onlyVisual
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.missileEffect)
set this.missileEffect = null
set this.caster = null
set this.dummy = null
set this.ownerOfCaster = null
endmethod
static method move takes nothing returns boolean
local thistype this = thistype(0).next
local real x
local real y
local unit u
local real damageArea
local unit dummyCaster
loop
exitwhen this == 0
//Move dummy units if they exist
static if not NO_MISSILES then
call SetUnitFlyHeight(this.dummy, GetUnitFlyHeight(this.dummy) - this.heightDecrement, 0)
endif
//If dummy unit has reached its destination or damage delay has passed, damage area
if executions == 0 then
//Create landing effect
call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, this.x, this.y))
static if INDIVIDUAL_DAMAGE then
//Only damage small area around the missile
set damageArea = COLLISION
else
//Damage whole spell area
set damageArea = this.area
endif
//If INDIVIDUAL_DAMAGE == false, only one of the missiles will deal damage. For that missile,
//onlyVisual == false
if not this.onlyVisual then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, damageArea, null)
set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, this.x, this.y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage and cast dummy spell on damaged units
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
endif
static if not NO_MISSILES then
call KillUnit(this.dummy)
endif
call this.destroy()
endif
set this.executions = this.executions - 1
set this = this.next
endloop
set dummyCaster = null
//Return true if number of dummy units is 0. This value will be used to determine, if it's safe
//to turn off the timer.
return count == 0
endmethod
static method create takes unit caster, player owner, real x, real y, real damage, boolean onlyVisual returns thistype
local thistype this = thistype.allocate()
local real angle
local real offset
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set this.shardsPerWave = setShardsPerWave(caster)
set this.area = setArea(caster)
set this.heightDecrement = HEIGHT/FALLING_TIME*TIMEOUT
set angle = GetRandomReal(0, 2*bj_PI)
set offset = GetRandomReal(0, this.area)
set this.x = x + offset*Cos(angle)
set this.y = y + offset*Sin(angle)
static if NO_MISSILES then
//Create stationary special effect
set this.dummy = null
set this.missileEffect = AddSpecialEffect(MISSILE_ART, this.x, this.y)
else
//Create falling missile
set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x , this.y, 0)
set this.missileEffect = AddSpecialEffectTarget(MISSILE_ART, this.dummy, "origin")
call UnitAddAbility(this.dummy, 'Arav')
call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
endif
//If INDIVIDUAL_DAMAGE == false, the casting point will be used as a damage center.
static if not INDIVIDUAL_DAMAGE then
set this.x = x
set this.y = y
endif
set this.executions = R2I(HEIGHT/this.heightDecrement)
set this.damage = damage
set this.caster = caster
set this.ownerOfCaster = owner
//This is true for all missiles except one per wave, if INDIVIDUAL_DAMAGE == false.
set this.onlyVisual = onlyVisual
return this
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private real waveInterval
private integer shardsPerWave
private real duration
private unit caster
private player ownerOfCaster
private real damage
private boolean channelling = true
private real dummyInterval = 0
private real x
private real y
private integer executions
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
set this.caster = null
set this.ownerOfCaster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local boolean spellEnd = false
local integer i
local sound loopSound
loop
exitwhen this == 0
//Create a wave of missiles once per waveInterval (seconds)
if this.dummyInterval >= this.waveInterval then
set i = 0
loop
exitwhen i == this.shardsPerWave
//If INDIVIDUAL_DAMAGE == false, only one missile per wave will deal damage, others are "only visual"
call Dummy.create(this.caster, this.ownerOfCaster, this.x, this.y, this.damage, not ((i == 0) or INDIVIDUAL_DAMAGE))
set i = i + 1
endloop
set this.dummyInterval = 0
else
set this.dummyInterval = this.dummyInterval + TIMEOUT
endif
set loopSound = CreateSoundFromLabel(LOOP_SOUND, false, true, true, 2000, 2000)
call SetSoundPosition(loopSound, this.x, this.y, 0)
call SetSoundVolume(loopSound, 127)
call StartSound(loopSound)
call KillSoundWhenDone(loopSound)
set this.executions = this.executions - 1
//Destroy this spell instance
static if REQUIRES_CHANNELLING then
if (not this.channelling) or executions <= 0 then
call this.destroy()
endif
else
if executions <= 0 then
call this.destroy()
endif
endif
set this = this.next
endloop
//move all dummy units
set spellEnd = Dummy.move()
//if there are no dummy units and no units casting the spell, stop timer
if spellEnd and count == 0 then
call PauseTimer(iterator)
endif
set loopSound = null
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.ownerOfCaster = GetOwningPlayer(this.caster)
static if TARGET_ABILITY then
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
else
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
endif
set this.waveInterval = setWaveInterval(this.caster)
set this.shardsPerWave = setShardsPerWave(this.caster)
set this.duration = setDuration(this.caster)
set this.damage = setDamage(this.caster)
set this.executions = R2I(this.duration/TIMEOUT)
set this.dummyInterval = this.waveInterval
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen GetTriggerUnit() == this.caster or this == 0
set this = this.next
endloop
set this.channelling = false
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//#################################
//#Flame Strike triggered version #
//#Created by Erkki2 #
//#################################
scope FlameStrike
native UnitAlive takes unit id returns boolean
// #################
// # CONFIGURABLES #
// #################
globals
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A004'
//Iteration interval
private constant real TIMEOUT = 0.03125
//Effect shown while channelling
private constant string CHANNEL_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl"
//The primary effect of the spell
private constant string SPELL_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrike1.mdl"
//Burning ground effect
private constant string BURN_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeEmbers.mdl"
//Delay between ahowing SPELL_EFFECT and starting to deal primary damage
private constant real DAMAGE_DELAY = 0.5
//duration of primary damage and effect
private constant real SPELL_TIME = 3
//duration of secondary damage and burning effect
private constant real BURN_TIME = 5
//Attack type of the spell damage (hero, siege, chaos, jne...)
private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
//Damage type of the spell (normal, universal, magic etc...)
private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
endglobals
//The spell area
private function setArea takes unit caster returns real
return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Damage that is dealt periodically by spell
private function setPrimaryDamage takes unit caster returns real
return 10.0 + 2.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Damage that is dealt periodically by burning ground
private function setSecondaryDamage takes unit caster returns real
return 3.0 + 1.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Time it takes to cast the spell.
//The spell must have equally long or longer casting time in object editor.
private function setChannelTime takes unit caster returns real
return 1.5
endfunction
//How often primary damage is dealt
private function setDamageInterval takes unit caster returns real
return 0.5 - 0.1 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//how often secondary damage is dealt
private function setBurnInterval takes unit caster returns real
return 2.0 - 0.2 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//How many burning particles there are on the ground.
//Even number is recommended for more symmetric effect.
private function setParticleCount takes unit caster returns integer
return 10 + 4 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct BurningGround
private thistype next
private thistype prev
private static integer count = 0
private thistype prevParticle
private effect eff
//Destroy particle and all other particles, that are linked to it
method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.eff)
set this.eff = null
if not (this.prevParticle == 0) then
call this.prevParticle.destroy()
endif
endmethod
static method create takes unit caster, real x, real y, real area, integer particleCount returns thistype
local thistype this = 0
local real angle
local real offset
local integer i = 0
local thistype previous = 0
local boolean outerRound = true
//Create a chain of particles, and return the last particle
loop
exitwhen i == particleCount
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set this.prevParticle = previous
set previous = this
set angle = i*2*bj_PI/particleCount
if outerRound then
set offset = 0.8*area
set outerRound = false
else
set offset = 0.3*area
set outerRound = true
endif
set eff = AddSpecialEffect(BURN_EFFECT, x + offset*Cos(angle), y + offset*Sin(angle))
set i = i + 1
endloop
return this
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private static group enumGroup = CreateGroup()
private unit caster
private boolean channelling
private real x
private real y
//The time, how long the current phase has been going
private real castTime
private real damageCounter = 0
private effect eff
private BurningGround burnEffect
private real area
private real channelTime
private real primaryDamage
private real secondaryDamage
private real damageInterval
private real burnInterval
//phase == 0: channelling
//phase == 1: delay between effect and damage
//phase == 2: spell primary damage
//phase == 3: burning, secondary damage
private integer phase
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.eff)
set this.eff = null
set this.caster = null
//if there are no spell instances running, pause the timer
if count == 0 then
call PauseTimer(iterator)
endif
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
loop
exitwhen this == 0
//The spell is being channelled
if this.phase == 0 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= this.channelTime then
set this.phase = 1
call DestroyEffect(this.eff)
set this.eff = AddSpecialEffect(SPELL_EFFECT, this.x, this.y)
set this.castTime = 0
elseif not this.channelling then
call this.destroy()
endif
//Delay between the primary spell effect and primary damage
elseif this.phase == 1 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= DAMAGE_DELAY then
set this.phase = 2
set this.castTime = 0
endif
//Deal primary damage while showing SPELL_EFFECT
elseif this.phase == 2 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= SPELL_TIME then
set this.phase = 3
call DestroyEffect(this.eff)
set this.burnEffect = BurningGround.create(this.caster, this.x, this.y, this.area, setParticleCount(this.caster))
set this.castTime = 0
set this.damageCounter = this.burnInterval
else
if this.damageCounter >= this.damageInterval then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage
call UnitDamageTarget(this.caster, u, this.primaryDamage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
set this.damageCounter = this.damageCounter - this.damageInterval
endif
set this.damageCounter = this.damageCounter + TIMEOUT
endif
//Deal secondary damage while showing BURN_EFFECT
else //this.phase == 3
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= BURN_TIME then
call this.burnEffect.destroy()
call this.destroy()
else
if this.damageCounter >= this.burnInterval then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage
call UnitDamageTarget(this.caster, u, this.secondaryDamage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
set this.damageCounter = this.damageCounter - this.damageInterval
endif
set this.damageCounter = this.damageCounter + TIMEOUT
endif
endif
set this = this.next
endloop
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
set this.phase = 0
set this.channelling = true
set this.castTime = 0
set this.area = setArea(this.caster)
set this.channelTime = setChannelTime(this.caster)
set this.primaryDamage = setPrimaryDamage(this.caster)
set this.secondaryDamage = setSecondaryDamage(this.caster)
set this.damageInterval = setDamageInterval(this.caster)
set this.burnInterval = setBurnInterval(this.caster)
set this.damageCounter = this.damageInterval
set this.eff = AddSpecialEffect(CHANNEL_EFFECT, this.x, this.y)
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen this == 0
if this.caster == GetTriggerUnit() then
set this.channelling = false
endif
set this = this.next
endloop
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=114
//TESH.alwaysfold=0
//#################################
//#Shockwave triggered version #
//#Created by Erkki2 #
//#################################
scope Shockwave
native UnitAlive takes unit id returns boolean
// #################
// # CONFIGURABLES #
// #################
globals
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A006'
private constant integer DUMMY_ID = 'h000'
//Iteration interval
private constant real TIMEOUT = 0.03125
//The missile effect
private constant string MISSILE_EFFECT = "Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl"
//Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
private constant integer DUMMY_ABILITY = 0
//Order string for dummy spell
private constant string DUMMY_ORDER = ""
//Speed of the projectile
private constant real SPEED = 700.0
//Set this to true, if you want a unit to be damaged only once by a single spell
private constant boolean DAMAGE_ONLY_ONCE = true
//Set this to true to enable terrain deformation
private constant boolean TERRAIN_DEFORMATION = true
//This determines how wide area is damaged. If < 1, the whole are isn't covered. If 2, every point in the path is damaged twice.
private constant real DAMAGE_OVERLAP = 2.0
//If true, the spell area is filled by selected special effects
private constant boolean FILL_AREA = false
//Distance between fill particles
private constant real FILL_DISTANCE = 30.0
//Special effect which is used for filling area
private constant string FILL_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
//If true, fill effects are attached on dummy units for a more realistic effect. If that is not necessary,
//it is advisable to have this option set to false for better performance.
private constant boolean FILL_CORRECT_FACING = false
//If true, secondary missiles are sent following the borders of the spell area
private constant boolean BORDER_MISSILES = false
//Art of the secondary missiles
private constant string BORDER_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
//Attack type of the spell damage (hero, siege, chaos, jne...)
private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
//Damage type of the spell (normal, universal, magic etc...)
private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
endglobals
//The spell distance
private function setDistance takes unit caster returns real
return 800.0
endfunction
//Cone width at caster
private function setStartArea takes unit caster returns real
return 100.0
endfunction
//Cone width at target
private function setEndArea takes unit caster returns real
return 100.0
endfunction
//Damage that is dealt by spell
private function setDamage takes unit caster returns real
return 100.0 + 50*GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private static group enumGroup = CreateGroup()
private unit caster
private player ownerOfCaster
private unit dummy
private integer executions
private effect eff
private real dx
private real dy
private real tipX
private real tipY
private real sliceWidth
private real maxArea
private real damage
private real rightAngle
private real leftAngle
private group damagedUnits
private real x
private real y
private real currWidth
private real widthInc
private real angle
private real border1X
private real border1Y
private real border2X
private real border2Y
private unit border1Unit
private unit border2Unit
private effect border1Eff
private effect border2Eff
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call KillUnit(this.dummy)
call KillUnit(this.border1Unit)
call KillUnit(this.border2Unit)
call DestroyEffect(this.eff)
call DestroyEffect(this.border1Eff)
call DestroyEffect(this.border2Eff)
call DestroyGroup(this.damagedUnits)
set this.damagedUnits = null
set this.border1Unit = null
set this.border2Unit = null
set this.border1Eff = null
set this.border2Eff = null
set this.eff = null
set this.caster = null
set this.dummy = null
set this.ownerOfCaster = null
//if there are no spell instances running, pause the timer
if count == 0 then
call PauseTimer(iterator)
endif
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
local real dummyDist
local real targetDist
local real diffX
local real diffY
local real angleToTip
local real targetX
local real targetY
local unit dummyCaster
local real width
local real reverseAngle
local real cosOfReverseAngle
local real sinOfReverseAngle
loop
exitwhen this == 0
if this.executions == 0 then
call this.destroy()
else
//Move the projectile
set this.x = GetUnitX(this.dummy) + this.dx
set this.y = GetUnitY(this.dummy) + this.dy
call SetUnitX(this.dummy, this.x)
call SetUnitY(this.dummy, this.y)
//if border missiles are enabled, move them too
static if BORDER_MISSILES then
call SetUnitX(this.border1Unit, GetUnitX(this.border1Unit) + this.border1X)
call SetUnitY(this.border1Unit, GetUnitY(this.border1Unit) + this.border1Y)
call SetUnitX(this.border2Unit, GetUnitX(this.border2Unit) + this.border2X)
call SetUnitY(this.border2Unit, GetUnitY(this.border2Unit) + this.border2Y)
endif
static if FILL_AREA then
set width = 0
set reverseAngle = this.angle + bj_PI/2
set cosOfReverseAngle = Cos(reverseAngle)
set sinOfReverseAngle = Sin(reverseAngle)
//Create one effect at the middle line
static if FILL_CORRECT_FACING then
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, this.angle*bj_RADTODEG)
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
else
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x, this.y))
endif
//Create N more effects vertical to the cast angle, with FILL_DISTANCE between each effect
loop
//Set offset from middle line, and create effect on each side
set width = width + FILL_DISTANCE
exitwhen width > this.currWidth
static if FILL_CORRECT_FACING then
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + width*cosOfReverseAngle, this.y + width*sinOfReverseAngle, this.angle*bj_RADTODEG)
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - width*cosOfReverseAngle, this.y - width*sinOfReverseAngle, this.angle*bj_RADTODEG)
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
else
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x + width*cosOfReverseAngle, this.y + width*sinOfReverseAngle))
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x - width*cosOfReverseAngle, this.y - width*sinOfReverseAngle))
endif
endloop
set this.currWidth = this.currWidth + this.widthInc
endif
//Create caster for optional spell effects
set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, this.x, this.y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.maxArea, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
set targetX = GetUnitX(u)
set targetY = GetUnitY(u)
//Calculate target unit's distance from the tip of the cone
set diffX = targetX - tipX
set diffY = targetY - tipY
set targetDist = SquareRoot(diffX*diffX + diffY*diffY)
//Calculate the distance between the projectile and tip
set diffX = this.x - tipX
set diffY = this.y - tipY
set dummyDist = SquareRoot(diffX*diffX + diffY*diffY)
//Calculate target unit's direction from the tip
set angleToTip = Atan2(this.tipY - targetY, this.tipX - targetX)
//The unit is in the right direction...
if ((angleToTip > this.leftAngle and angleToTip < this.rightAngle) /*
*/ or (this.leftAngle > this.rightAngle and (angleToTip < this.leftAngle or angleToTip > this.rightAngle))) /*
//...and at the right distance.
*/ and targetDist - dummyDist < this.sliceWidth and dummyDist - targetDist < this.sliceWidth /*
//Check allowed targets
*/ and isTargetAllowed(u, this.caster) then
//If DAMAGE_ONLY_ONCE is enabled, only damage units which aren't in damagedUnits group, and add damaged units to group...
static if DAMAGE_ONLY_ONCE then
if not IsUnitInGroup(u, this.damagedUnits) then
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
call GroupAddUnit(this.damagedUnits, u)
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
//...else damage just damage units
else
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
set this.executions = this.executions - 1
endif
set this = this.next
endloop
set dummyCaster = null
endmethod
private static method run takes nothing returns boolean
local thistype this
local real distPerIter
local real distance
local real startArea
local real endArea
local real distToTip
local real targetX
local real targetY
local real areaOffsetX
local real areaOffsetY
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.ownerOfCaster = GetOwningPlayer(this.caster)
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
set targetX = GetSpellTargetX()
set targetY = GetSpellTargetY()
set distance = setDistance(this.caster)
set this.executions = R2I(distance/SPEED/TIMEOUT)
//Angle from cast point to target point
set this.angle = Atan2(targetY - this.y, targetX - this.x)
//set target point to a "correct" value
set targetX = this.x + distance*Cos(this.angle)
set targetY = this.y + distance*Sin(this.angle)
//Calculate travelled distance per iteration
set distPerIter = distance/this.executions
set this.dx = distPerIter*Cos(this.angle)
set this.dy = distPerIter*Sin(this.angle)
//Create projectile
set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, this.angle*bj_RADTODEG)
set this.eff = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
//Calculate the width of an area, which is damaged per iteration
set this.sliceWidth = DAMAGE_OVERLAP*distPerIter/2
set startArea = setStartArea(this.caster)
set endArea = setEndArea(this.caster)
//Cone is \/ shaped (cast upwards)
if startArea < endArea then
//Tip of the cone is at cast point
if startArea == 0 then
set distToTip = 0
else
set distToTip = distance*startArea/(endArea - startArea)
endif
set this.tipX = this.x - distToTip*Cos(this.angle)
set this.tipY = this.y - distToTip*Sin(this.angle)
set this.maxArea = endArea
//Calculate borderlines of the cone
set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
set this.rightAngle = Atan2(this.tipY - (targetY + areaOffsetY), this.tipX - (targetX + areaOffsetX))
set this.leftAngle = Atan2(this.tipY - (targetY - areaOffsetY), this.tipX - (targetX - areaOffsetX))
//The cone is /\ shaped (cast upwards)
elseif startArea > endArea then
//Tip of the cone is at target point
if endArea == 0 then
set distToTip = 0
else
set distToTip = distance*endArea/(startArea - endArea)
endif
set this.tipX = targetX + distToTip*Cos(this.angle)
set this.tipY = targetY + distToTip*Sin(this.angle)
set this.maxArea = startArea
//Calculate borderlines of the cone
set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
set this.leftAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
set this.rightAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
//The Cone is || shaped, in other words a line
else // startArea == endArea
//Set tip of the cone very far behind the caster, which makes the cone to spread very little
set distToTip = 100000
set this.tipX = this.x - distToTip*Cos(this.angle)
set this.tipY = this.y - distToTip*Sin(this.angle)
set this.maxArea = startArea
//Calculate borderlines of the cone
set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
set this.rightAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
set this.leftAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
endif
set this.damage = setDamage(this.caster)
set this.damagedUnits = CreateGroup()
static if TERRAIN_DEFORMATION then
call TerrainDeformWave(this.x, this.y, targetX, targetY, distance, SPEED, startArea, 150, 2, 1)
endif
static if FILL_AREA then
set this.currWidth = startArea
set this.widthInc = (endArea - startArea)/this.executions
endif
static if BORDER_MISSILES then
set border1X = -distPerIter*Cos(this.rightAngle)
set border1Y = -distPerIter*Sin(this.rightAngle)
set border2X = -distPerIter*Cos(this.leftAngle)
set border2Y = -distPerIter*Sin(this.leftAngle)
set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
set this.border1Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + areaOffsetX, this.y + areaOffsetY, this.rightAngle*bj_RADTODEG)
set this.border2Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - areaOffsetX, this.y - areaOffsetY, this.leftAngle*bj_RADTODEG)
set this.border1Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border1Unit, "origin")
set this.border2Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border2Unit, "origin")
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.run))
endmethod
endstruct
endscope
//TESH.scrollpos=189
//TESH.alwaysfold=0
//###################################
//#Volcano triggered version #
//#Created by Erkki2 #
//#Required libraries: #
//# Geometry by Erkki2 #
//###################################
scope Volcano
native UnitAlive takes unit id returns boolean
globals
// #################
// # CONFIGURABLES #
// #################
//Dummy unit used for moving effects and casting spell
private constant integer DUMMY_ID = 'h000'
//Spell ability
private constant integer ABIL_CODE = 'A008'
//Iteration interval
private constant real TIMEOUT = 0.03125
//Speed of the missiles
private constant real SPEED = 350
//Max height of the missiles
private constant real HEIGHT = 600
//Effect when missiles land
private constant string LANDING_EFFECT = "Abilities\\Spells\\Orc\\LiquidFire\\Liquidfire.mdl"
//Missile art
private constant string MISSILE_EFFECT = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl"
//Effect that stays at the target point while the spell is cast
private constant string CAST_EFFECT = "Abilities\\Spells\\Other\\Volcano\\Volcano.mdl"
//Ability that is cast on all hit units. Set this to 0 if there's no ability.
private constant integer DUMMY_ABILITY = 'A009'
//Order string for DUMMY_ABILITY
private constant string DUMMY_ORDER = "creepthunderbolt"
//Time between missiles in the same wave
private constant real MISSILE_INTERVAL = 0.2
//Time between waves
private constant real WAVE_INTERVAL = 3
//Time before first wave
private constant real TIME_BEFORE_FIRST_WAVE = 3
//If true, the spell end when caster stops channelling
private constant boolean REQUIRES_CHANNELLING = true
//Area which is damaged when missiles land
private constant real MISSILE_COLLISION = 100
//Set this to true if the spell has a target point, and to false if the spell is instant.
private constant boolean TARGET_ABILITY = true
//Attack type of the spell damage (hero, siege, chaos, jne...)
private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
//Damage type of the spell (normal, universal, magic etc...)
private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
endglobals
//Returns the number of missiles in one wave
private function missilesPerWave takes unit caster returns integer
return 7
endfunction
//Returns the spell area
private function setArea takes unit caster returns real
return 300.0 + 100.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the spell duration.
private function setDuration takes unit caster returns real
return 12.0 + 4.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns damage dealt when a missile lands
private function setDamage takes unit caster returns real
return 40.0 + 20.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and UnitAlive(target)
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Dummy
private thistype next
private thistype prev
private static integer count = 0
private static group enumGroup = CreateGroup()
private unit dummy
//The distance the dummy unit is about to travel
private real dist
//Already travelled distance
private real travelled
private real dx
private real dy
private unit caster
private player ownerOfCaster
private real damage
private effect attachment
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
call KillUnit(this.dummy)
call DestroyEffect(this.attachment)
set this.attachment = null
set this.caster = null
set this.dummy = null
set this.ownerOfCaster = null
set this.count = this.count - 1
endmethod
static method move takes nothing returns boolean
local thistype this = thistype(0).next
local real x
local real y
local group enumGroup
local unit u
local unit dummyCaster
loop
exitwhen this == 0
//Move dummy units
set x = GetUnitX(this.dummy) + this.dx
set y = GetUnitY(this.dummy) + this.dy
call SetUnitX(this.dummy, x)
call SetUnitY(this.dummy, y)
call SetUnitFlyHeight(this.dummy, getParabolaHeight(this.dist, HEIGHT, this.travelled), 0)
set this.travelled = this.travelled + SquareRoot(this.dx*this.dx + this.dy*this.dy)
//If dummy unit has reached its destination, destroy it and damage area
if this.travelled >= this.dist then
call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, x, y))
set enumGroup = CreateGroup()
//Create caster for optional spell effects
set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, x, y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
call GroupEnumUnitsInRange(enumGroup, x, y, MISSILE_COLLISION, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
if isTargetAllowed(u, this.caster) then
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
//Cast DUMMY_ABILITY on all hit units
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
call this.destroy()
endif
set this = this.next
endloop
set dummyCaster = null
//return true if number of dummy units is 0
return count == 0
endmethod
static method create takes unit caster, player owner, real x, real y, real damage, real area returns thistype
local thistype this = thistype.allocate()
local real angle
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set angle = GetRandomReal(0, 2*bj_PI)
set this.dist = GetRandomReal(10, area)
set this.dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, x, y, angle*bj_RADTODEG)
set this.attachment = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
set this.travelled = 0
set this.damage = damage
set this.caster = caster
set this.ownerOfCaster = owner
set this.dx = Cos(angle)*SPEED*TIMEOUT*dist/area
set this.dy = Sin(angle)*SPEED*TIMEOUT*dist/area
return this
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private unit caster
private player ownerOfCaster
private real damage
private boolean channelling = true
//Time before next dummy unit is created
private real dummyDelay
private real x
private real y
private integer executions
private effect eff
//Number of dummy units in a wave
private integer dummyCountTotal
private real area
//How many dummies of this wave have been already created
private integer dummyCountCurrent
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
call DestroyEffect(this.eff)
set this.eff = null
set this.count = this.count - 1
set this.caster = null
set this.ownerOfCaster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local boolean spellEnd = false
local integer i
loop
exitwhen this == 0
set this.dummyDelay = this.dummyDelay - TIMEOUT
set i = 0
//Create dummy units. If MISSILE_INTERVAL < TIMEOUT, more than one will be created.
loop
exitwhen this.dummyDelay >= 0
call Dummy.create(this.caster, this.ownerOfCaster, this.x, this.y, this.damage, this.area)
set i = i + 1
//This dummy is the last of this wave
if dummyCountCurrent == dummyCountTotal then
set this.dummyDelay = this.dummyDelay + WAVE_INTERVAL
set this.dummyCountCurrent = 0
else
set this.dummyDelay = this.dummyDelay + MISSILE_INTERVAL
set this.dummyCountCurrent = this.dummyCountCurrent + 1
endif
endloop
//Caster stops channelling
static if REQUIRES_CHANNELLING then
if not this.channelling then
call this.destroy()
endif
endif
//Spell duration is reached
set this.executions = this.executions - 1
if this.executions == 0 then
call this.destroy()
endif
set this = this.next
endloop
//Move all dummy units
set spellEnd = Dummy.move()
//If there are no dummy units and no units casting the spell, stop timer
if spellEnd and count == 0 then
call PauseTimer(iterator)
endif
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.ownerOfCaster = GetOwningPlayer(this.caster)
set this.damage = setDamage(this.caster)
static if TARGET_ABILITY then
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
else
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
endif
set this.area = setArea(this.caster)
set this.executions = R2I(setDuration(this.caster)/TIMEOUT)
set this.eff = AddSpecialEffect(CAST_EFFECT, this.x, this.y)
set this.dummyCountTotal = missilesPerWave(this.caster)
set this.dummyCountCurrent = 0
set this.dummyDelay = TIME_BEFORE_FIRST_WAVE
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen GetTriggerUnit() == this.caster or this == 0
set this = this.next
endloop
set this.channelling = false
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=287
//TESH.alwaysfold=0
//#################################
//#Blizzard triggered version #
//#Created by Erkki2 #
//#################################
scope Blizzard2
// #################
// # CONFIGURABLES #
// #################
globals
//Dummy unit used for moving effects and casting spell
private constant integer DUMMY_ID = 'h002'
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A002'
//Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
private constant integer DUMMY_ABILITY = 0
//Order string for dummy spell
private constant string DUMMY_ORDER = ""
//If NO_MISSILES == false, this effect is used as a falling down missile.
//If NO_MISILES == true, this effect is created as a stationary effect (works well for standard blizzard effect).
private constant string MISSILE_ART = "Abilities\\Weapons\\GuardTowerMissile\\GuardTowerMissile.mdl"
//Iteration interval
private constant real TIMEOUT = 0.04
//Collision size of missiles. This has no effect if INDIVIDUAL_DAMAGE == false.
private constant real COLLISION = 50.0
//If NO_MISSILES == false, this is the time it takes for a missile to land.
//If NO_MISSILES == true, this serves as a delay between effect and damage. For standard blizzard effect, 0.8 is recommended.
private constant real FALLING_TIME = 1
//Heigh that missiles fall from. Has no effect if NO_MISSILES == true.
private constant real HEIGHT = 1200.0
//Effect which is created when missiles hit the ground.
private constant string LANDING_EFFECT = ""
//Sound which is played on every wave
private constant string LOOP_SOUND = ""
//If true, all missiles will damage small area around them.
//If false, each wave will damage the whole spell area, regardless where the missiles fall.
private constant boolean INDIVIDUAL_DAMAGE = true
//If true, the effect is treated as an effect without falling down.
//If false, the effect is treated as a falling down missile.
private constant boolean NO_MISSILES = false
//If true, spell will be interrupted if caster stops channelling.
private constant boolean REQUIRES_CHANNELLING = true
//True if ability is cast at a target point, false if it's centered on caster (instant, no target).
private constant boolean TARGET_ABILITY = true
endglobals
//Returns the interval between waves (in seconds)
private function setWaveInterval takes unit caster returns real
return 0.04
endfunction
//Returns the number of shards in each wave
private function setShardsPerWave takes unit caster returns integer
return 1
endfunction
//Returns the spell area
private function setArea takes unit caster returns real
return 200.0
endfunction
//Returns the spell duration.
private function setDuration takes unit caster returns real
return 5.0
endfunction
//If INDIVIDUAL_DAMAGE == true, returns damage dealt by shard.
//If INDIVIDUAL_DAMAGE == false, returns damage dealt by wave.
private function setDamage takes unit caster returns real
return 10.0 + 5.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and GetUnitState(target, UNIT_STATE_LIFE) > 0
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Dummy
private thistype next
private thistype prev
private static integer count = 0
private static group enumGroup = CreateGroup()
private integer shardsPerWave
private real area
private unit dummy
private integer executions
private real heightDecrement
private unit caster
private real damage
private effect missileEffect
private real x
private real y
private boolean onlyVisual
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.missileEffect)
set this.missileEffect = null
set this.caster = null
set this.dummy = null
endmethod
static method move takes nothing returns boolean
local thistype this = thistype(0).next
local real x
local real y
local unit u
local real damageArea
local unit dummyCaster
loop
exitwhen this == 0
//Move dummy units if they exist
static if not NO_MISSILES then
call SetUnitFlyHeight(this.dummy, GetUnitFlyHeight(this.dummy) - this.heightDecrement, 0)
endif
//If dummy unit has reached its destination or damage delay has passed, damage area
if executions == 0 then
//Create landing effect
call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, this.x, this.y))
static if INDIVIDUAL_DAMAGE then
//Only damage small area around the missile
set damageArea = COLLISION
else
//Damage whole spell area
set damageArea = this.area
endif
//If INDIVIDUAL_DAMAGE == false, only one of the missiles will deal damage. For that missile,
//onlyVisual == false
if not this.onlyVisual then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, damageArea, null)
set dummyCaster = CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, this.x, this.y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage and cast dummy spell on damaged units
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
endif
static if not NO_MISSILES then
call KillUnit(this.dummy)
endif
call this.destroy()
endif
set this.executions = this.executions - 1
set this = this.next
endloop
set dummyCaster = null
//Return true if number of dummy units is 0. This value will be used to determine, if it's safe
//to turn off the timer.
return count == 0
endmethod
static method new takes unit caster, real x, real y, real damage, boolean onlyVisual returns nothing
local thistype this = thistype.allocate()
local real angle
local real offset
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set this.shardsPerWave = setShardsPerWave(caster)
set this.area = setArea(caster)
set this.heightDecrement = HEIGHT/FALLING_TIME*TIMEOUT
set angle = GetRandomReal(0, 2*bj_PI)
set offset = GetRandomReal(0, this.area)
set this.x = x + offset*Cos(angle)
set this.y = y + offset*Sin(angle)
static if NO_MISSILES then
//Create stationary special effect
set this.dummy = null
set this.missileEffect = AddSpecialEffect(MISSILE_ART, this.x, this.y)
else
//Create falling missile
set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x , this.y, 0)
set this.missileEffect = AddSpecialEffectTarget(MISSILE_ART, this.dummy, "origin")
call UnitAddAbility(this.dummy, 'Arav')
call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
endif
//If INDIVIDUAL_DAMAGE == false, the casting point will be used as a damage center.
static if not INDIVIDUAL_DAMAGE then
set this.x = x
set this.y = y
endif
set this.executions = R2I(HEIGHT/this.heightDecrement)
set this.damage = damage
set this.caster = caster
//This is true for all missiles except one per wave, if INDIVIDUAL_DAMAGE == false.
set this.onlyVisual = onlyVisual
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private real waveInterval
private integer shardsPerWave
private real duration
private unit caster
private real damage
private boolean channelling = true
private real dummyInterval = 0
private real x
private real y
private integer executions
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
set this.caster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local boolean spellEnd = false
local integer i
local sound loopSound
loop
exitwhen this == 0
//Create a wave of missiles once per waveInterval (seconds)
if this.dummyInterval >= this.waveInterval then
set i = 0
loop
exitwhen i == this.shardsPerWave
//If INDIVIDUAL_DAMAGE == false, only one missile per wave will deal damage, others are "only visual"
call Dummy.new(this.caster, this.x, this.y, this.damage, not ((i == 0) or INDIVIDUAL_DAMAGE))
set i = i + 1
endloop
set this.dummyInterval = 0
else
set this.dummyInterval = this.dummyInterval + TIMEOUT
endif
set loopSound = CreateSoundFromLabel(LOOP_SOUND, false, true, true, 2000, 2000)
call SetSoundPosition(loopSound, this.x, this.y, 0)
call SetSoundVolume(loopSound, 127)
call StartSound(loopSound)
call KillSoundWhenDone(loopSound)
set this.executions = this.executions - 1
//Destroy this spell instance
static if REQUIRES_CHANNELLING then
if (not this.channelling) or executions <= 0 then
call this.destroy()
endif
else
if executions <= 0 then
call this.destroy()
endif
endif
set this = this.next
endloop
//move all dummy units
set spellEnd = Dummy.move()
//if there are no dummy units and no units casting the spell, stop timer
if spellEnd and count == 0 then
call PauseTimer(iterator)
endif
set loopSound = null
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
static if TARGET_ABILITY then
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
else
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
endif
set this.waveInterval = setWaveInterval(this.caster)
set this.shardsPerWave = setShardsPerWave(this.caster)
set this.duration = setDuration(this.caster)
set this.damage = setDamage(this.caster)
set this.executions = R2I(this.duration/TIMEOUT)
set this.dummyInterval = this.waveInterval
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen GetTriggerUnit() == this.caster or this == 0
set this = this.next
endloop
set this.channelling = false
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=297
//TESH.alwaysfold=0
//#################################
//#Blizzard triggered version #
//#Created by Erkki2 #
//#################################
scope Blizzard3
// #################
// # CONFIGURABLES #
// #################
globals
//Dummy unit used for moving effects and casting spell
private constant integer DUMMY_ID = 'h000'
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A003'
//Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
private constant integer DUMMY_ABILITY = 0
//Order string for dummy spell
private constant string DUMMY_ORDER = ""
//If NO_MISSILES == false, this effect is used as a falling down missile.
//If NO_MISILES == true, this effect is created as a stationary effect (works well for standard blizzard effect).
private constant string MISSILE_ART = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
//Iteration interval
private constant real TIMEOUT = 0.04
//Collision size of missiles. This has no effect if INDIVIDUAL_DAMAGE == false.
private constant real COLLISION = 100
//If NO_MISSILES == false, this is the time it takes for a missile to land.
//If NO_MISSILES == true, this serves as a delay between effect and damage. For standard blizzard effect, 0.8 is recommended.
private constant real FALLING_TIME = 3.0
//Heigh that missiles fall from. Has no effect if NO_MISSILES == true.
private constant real HEIGHT = 900.0
//Effect which is created when missiles hit the ground.
private constant string LANDING_EFFECT = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
//Sound which is played on every wave
private constant string LOOP_SOUND = ""
//If true, all missiles will damage small area around them.
//If false, each wave will damage the whole spell area, regardless where the missiles fall.
private constant boolean INDIVIDUAL_DAMAGE = true
//If true, the effect is treated as an effect without falling down.
//If false, the effect is treated as a falling down missile.
private constant boolean NO_MISSILES = false
//If true, spell will be interrupted if caster stops channelling.
private constant boolean REQUIRES_CHANNELLING = false
//True if ability is cast at a target point, false if it's centered on caster (instant, no target).
private constant boolean TARGET_ABILITY = true
endglobals
//Returns the interval between waves (in seconds)
private function setWaveInterval takes unit caster returns real
return 2 - 0.3 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the number of shards in each wave
private function setShardsPerWave takes unit caster returns integer
return 10 + 2 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the spell area
private function setArea takes unit caster returns real
return 700.0
endfunction
//Returns the spell duration.
private function setDuration takes unit caster returns real
return 10.0
endfunction
//If INDIVIDUAL_DAMAGE == true, returns damage dealt by shard.
//If INDIVIDUAL_DAMAGE == false, returns damage dealt by wave.
private function setDamage takes unit caster returns real
return 40 + 15.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and GetUnitState(target, UNIT_STATE_LIFE) > 0
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Dummy
private thistype next
private thistype prev
private static integer count = 0
private static group enumGroup = CreateGroup()
private integer shardsPerWave
private real area
private unit dummy
private integer executions
private real heightDecrement
private unit caster
private real damage
private effect missileEffect
private real x
private real y
private boolean onlyVisual
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.missileEffect)
set this.missileEffect = null
set this.caster = null
set this.dummy = null
endmethod
static method move takes nothing returns boolean
local thistype this = thistype(0).next
local real x
local real y
local unit u
local real damageArea
local unit dummyCaster
loop
exitwhen this == 0
//Move dummy units if they exist
static if not NO_MISSILES then
call SetUnitFlyHeight(this.dummy, GetUnitFlyHeight(this.dummy) - this.heightDecrement, 0)
endif
//If dummy unit has reached its destination or damage delay has passed, damage area
if executions == 0 then
//Create landing effect
call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, this.x, this.y))
static if INDIVIDUAL_DAMAGE then
//Only damage small area around the missile
set damageArea = COLLISION
else
//Damage whole spell area
set damageArea = this.area
endif
//If INDIVIDUAL_DAMAGE == false, only one of the missiles will deal damage. For that missile,
//onlyVisual == false
if not this.onlyVisual then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, damageArea, null)
set dummyCaster = CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, this.x, this.y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage and cast dummy spell on damaged units
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
endif
static if not NO_MISSILES then
call KillUnit(this.dummy)
endif
call this.destroy()
endif
set this.executions = this.executions - 1
set this = this.next
endloop
set dummyCaster = null
//Return true if number of dummy units is 0. This value will be used to determine, if it's safe
//to turn off the timer.
return count == 0
endmethod
static method new takes unit caster, real x, real y, real damage, boolean onlyVisual returns nothing
local thistype this = thistype.allocate()
local real angle
local real offset
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set this.shardsPerWave = setShardsPerWave(caster)
set this.area = setArea(caster)
set this.heightDecrement = HEIGHT/FALLING_TIME*TIMEOUT
set angle = GetRandomReal(0, 2*bj_PI)
set offset = GetRandomReal(0, this.area)
set this.x = x + offset*Cos(angle)
set this.y = y + offset*Sin(angle)
static if NO_MISSILES then
//Create stationary special effect
set this.dummy = null
set this.missileEffect = AddSpecialEffect(MISSILE_ART, this.x, this.y)
else
//Create falling missile
set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x , this.y, 0)
set this.missileEffect = AddSpecialEffectTarget(MISSILE_ART, this.dummy, "origin")
call UnitAddAbility(this.dummy, 'Arav')
call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
endif
//If INDIVIDUAL_DAMAGE == false, the casting point will be used as a damage center.
static if not INDIVIDUAL_DAMAGE then
set this.x = x
set this.y = y
endif
set this.executions = R2I(HEIGHT/this.heightDecrement)
set this.damage = damage
set this.caster = caster
//This is true for all missiles except one per wave, if INDIVIDUAL_DAMAGE == false.
set this.onlyVisual = onlyVisual
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private real waveInterval
private integer shardsPerWave
private real duration
private unit caster
private real damage
private boolean channelling = true
private real dummyInterval = 0
private real x
private real y
private integer executions
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
set this.caster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local boolean spellEnd = false
local integer i
local sound loopSound
loop
exitwhen this == 0
//Create a wave of missiles once per waveInterval (seconds)
if this.dummyInterval >= this.waveInterval then
set i = 0
loop
exitwhen i == this.shardsPerWave
//If INDIVIDUAL_DAMAGE == false, only one missile per wave will deal damage, others are "only visual"
call Dummy.new(this.caster, this.x, this.y, this.damage, not ((i == 0) or INDIVIDUAL_DAMAGE))
set i = i + 1
endloop
set this.dummyInterval = 0
else
set this.dummyInterval = this.dummyInterval + TIMEOUT
endif
set loopSound = CreateSoundFromLabel(LOOP_SOUND, false, true, true, 2000, 2000)
call SetSoundPosition(loopSound, this.x, this.y, 0)
call SetSoundVolume(loopSound, 127)
call StartSound(loopSound)
call KillSoundWhenDone(loopSound)
set this.executions = this.executions - 1
//Destroy this spell instance
static if REQUIRES_CHANNELLING then
if (not this.channelling) or this.executions <= 0 then
call this.destroy()
endif
else
if executions <= 0 then
call this.destroy()
endif
endif
set this = this.next
endloop
//move all dummy units
set spellEnd = Dummy.move()
//if there are no dummy units and no units casting the spell, stop timer
if spellEnd and count == 0 then
call PauseTimer(iterator)
endif
set loopSound = null
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
static if TARGET_ABILITY then
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
else
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
endif
set this.waveInterval = setWaveInterval(this.caster)
set this.shardsPerWave = setShardsPerWave(this.caster)
set this.duration = setDuration(this.caster)
set this.damage = setDamage(this.caster)
set this.executions = R2I(this.duration/TIMEOUT)
set this.dummyInterval = this.waveInterval
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen GetTriggerUnit() == this.caster or this == 0
set this = this.next
endloop
set this.channelling = false
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=38
//TESH.alwaysfold=0
//#################################
//#Blizzard triggered version #
//#Created by Erkki2 #
//#################################
scope LightningStorm
// #################
// # CONFIGURABLES #
// #################
globals
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A005'
//Iteration interval
private constant real TIMEOUT = 0.04
//Effect shown while channelling
private constant string CHANNEL_EFFECT = ""
//The primary effect of the spell
private constant string SPELL_EFFECT = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
//Burning ground effect
private constant string BURN_EFFECT = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
//Delay between ahowing SPELL_EFFECT and starting to deal primary damage
private constant real DAMAGE_DELAY = 0.1
//duration of primary damage and effect
private constant real SPELL_TIME = 0.5
//duration of secondary damage and burning effect
private constant real BURN_TIME = 2
endglobals
//The spell area
private function setArea takes unit caster returns real
return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Damage that is dealt periodically by spell
private function setPrimaryDamage takes unit caster returns real
return 50.0 + 25.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Damage that is dealt periodically by burning ground
private function setSecondaryDamage takes unit caster returns real
return 25.0 + 15.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Time it takes to cast the spell.
//The spell must have equally long or longer casting time in object editor.
private function setChannelTime takes unit caster returns real
return 0.0
endfunction
//How often primary damage is dealt
private function setDamageInterval takes unit caster returns real
return 1.0
endfunction
//how often secondary damage is dealt
private function setBurnInterval takes unit caster returns real
return 1.0
endfunction
//How many burning particles there are on the ground.
//Even number is recommended for more symmetric effect.
private function setParticleCount takes unit caster returns integer
return 14 + 6 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and GetUnitState(target, UNIT_STATE_LIFE) > 0
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct BurningGround
private thistype next
private thistype prev
private static integer count = 0
private thistype prevParticle
private effect eff
//Destroy particle and all other particles, that are linked to it
method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.eff)
set this.eff = null
if not (this.prevParticle == 0) then
call this.prevParticle.destroy()
endif
endmethod
static method new takes unit caster, real x, real y, real area, integer particleCount returns thistype
local thistype this = 0
local real angle
local real offset
local integer i = 0
local thistype previous = 0
local boolean outerRound = true
//Create a chain of particles, and return the last particle
loop
exitwhen i == particleCount
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set this.prevParticle = previous
set previous = this
set angle = i*2*bj_PI/particleCount
if outerRound then
set offset = 0.8*area
set outerRound = false
else
set offset = 0.3*area
set outerRound = true
endif
set eff = AddSpecialEffect(BURN_EFFECT, x + offset*Cos(angle), y + offset*Sin(angle))
set i = i + 1
endloop
return this
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private static group enumGroup = CreateGroup()
private unit caster
private boolean channelling
private real x
private real y
//The time, how long the current phase has been going
private real castTime
private real damageCounter = 0
private effect eff
private BurningGround burnEffect
private real area
private real channelTime
private real primaryDamage
private real secondaryDamage
private real damageInterval
private real burnInterval
//phase == 0: channelling
//phase == 1: delay between effect and damage
//phase == 2: spell primary damage
//phase == 3: burning, secondary damage
private integer phase
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call DestroyEffect(this.eff)
set this.eff = null
set this.caster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
loop
exitwhen this == 0
//The spell is being channelled
if this.phase == 0 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= this.channelTime then
set this.phase = 1
call DestroyEffect(this.eff)
set this.eff = AddSpecialEffect(SPELL_EFFECT, this.x, this.y)
set this.castTime = 0
elseif not this.channelling then
call this.destroy()
endif
//Delay between the primary spell effect and primary damage
elseif this.phase == 1 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= DAMAGE_DELAY then
set this.phase = 2
set this.castTime = 0
endif
//Deal primary damage while showing SPELL_EFFECT
elseif this.phase == 2 then
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= SPELL_TIME then
set this.phase = 3
call DestroyEffect(this.eff)
set this.burnEffect = BurningGround.new(this.caster, this.x, this.y, this.area, setParticleCount(this.caster))
set this.castTime = 0
set this.damageCounter = this.burnInterval
else
if this.damageCounter >= this.damageInterval then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage
call UnitDamageTarget(this.caster, u, this.primaryDamage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
set this.damageCounter = this.damageCounter - this.damageInterval
endif
set this.damageCounter = this.damageCounter + TIMEOUT
endif
//Deal secondary damage while showing BURN_EFFECT
else //this.phase == 3
set this.castTime = this.castTime + TIMEOUT
if this.castTime >= BURN_TIME then
call this.burnEffect.destroy()
call this.destroy()
else
if this.damageCounter >= this.burnInterval then
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
//Check allowed targets
if isTargetAllowed(u, this.caster) then
//Deal damage
call UnitDamageTarget(this.caster, u, this.secondaryDamage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
endif
call GroupRemoveUnit(enumGroup, u)
endloop
set this.damageCounter = this.damageCounter - this.damageInterval
endif
set this.damageCounter = this.damageCounter + TIMEOUT
endif
endif
set this = this.next
endloop
//if there are no spell instances running, pause the timer
if count == 0 then
call PauseTimer(iterator)
endif
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
set this.phase = 0
set this.channelling = true
set this.castTime = 0
set this.area = setArea(this.caster)
set this.channelTime = setChannelTime(this.caster)
set this.primaryDamage = setPrimaryDamage(this.caster)
set this.secondaryDamage = setSecondaryDamage(this.caster)
set this.damageInterval = setDamageInterval(this.caster)
set this.burnInterval = setBurnInterval(this.caster)
set this.damageCounter = this.damageInterval
set this.eff = AddSpecialEffect(CHANNEL_EFFECT, this.x, this.y)
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen this == 0
if this.caster == GetTriggerUnit() then
set this.channelling = false
endif
set this = this.next
endloop
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope
//TESH.scrollpos=186
//TESH.alwaysfold=0
//#################################
//#Blizzard triggered version #
//#Created by Erkki2 #
//#################################
scope WaveOfFrost
// #################
// # CONFIGURABLES #
// #################
globals
//Raw code of the spell ability
private constant integer ABIL_CODE = 'A007'
private constant integer DUMMY_ID = 'h000'
//Iteration interval
private constant real TIMEOUT = 0.04
//The missile effect
private constant string MISSILE_EFFECT = ""
//Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
private constant integer DUMMY_ABILITY = 'A001'
//Order string for dummy spell
private constant string DUMMY_ORDER = "slow"
//Speed of the projectile
private constant real SPEED = 700.0
//Set this to true, if you want a unit to be damaged only once by a single spell
private constant boolean DAMAGE_ONLY_ONCE = true
//Set this to true to enable terrain deformation
private constant boolean TERRAIN_DEFORMATION = false
//This determines how wide area is damaged. If < 1, the whole are isn't covered. If 2, every point in the path is damaged twice.
private constant real DAMAGE_OVERLAP = 2.0
//If true, the spell area is filled by selected special effects
private constant boolean FILL_AREA = true
//Distance between fill particles
private constant real FILL_DISTANCE = 40.0
//Special effect which is used for filling area
private constant string FILL_EFFECT = "Abilities\\Spells\\Undead\\FrostArmor\\FrostArmorDamage.mdl"
//If true, fill effects are attached on dummy units for a more realistic effect. If that is not necessary,
//it is advisable to have this option set to false for better performance.
private constant boolean FILL_CORRECT_FACING = true
//If true, secondary missiles are sent following the borders of the spell area
private constant boolean BORDER_MISSILES = true
//Art of the secondary missiles
private constant string BORDER_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
endglobals
//The spell distance
private function setDistance takes unit caster returns real
return 800.0
endfunction
//Cone width at caster
private function setStartArea takes unit caster returns real
return 10.0
endfunction
//Cone width at target
private function setEndArea takes unit caster returns real
return 100.0 + 100*GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Damage that is dealt by spell
private function setDamage takes unit caster returns real
return 100.0 + 50*GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and GetUnitState(target, UNIT_STATE_LIFE) > 0
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private static group enumGroup = CreateGroup()
private unit caster
private unit dummy
private integer executions
private effect eff
private real dx
private real dy
private real tipX
private real tipY
private real sliceWidth
private real maxArea
private real damage
private real rightAngle
private real leftAngle
private group damagedUnits
private real x
private real y
private real currWidth
private real widthInc
private real angle
private real border1X
private real border1Y
private real border2X
private real border2Y
private unit border1Unit
private unit border2Unit
private effect border1Eff
private effect border2Eff
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
set count = count - 1
call KillUnit(this.dummy)
call KillUnit(this.border1Unit)
call KillUnit(this.border2Unit)
call DestroyEffect(this.eff)
call DestroyEffect(this.border1Eff)
call DestroyEffect(this.border2Eff)
call DestroyGroup(this.damagedUnits)
set this.damagedUnits = null
set this.border1Unit = null
set this.border2Unit = null
set this.border1Eff = null
set this.border2Eff = null
set this.eff = null
set this.caster = null
set this.dummy = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
local real dummyDist
local real targetDist
local real diffX
local real diffY
local real angleToTip
local real targetX
local real targetY
local unit dummyCaster
local real width
local real reverseAngle
loop
exitwhen this == 0
if this.executions == 0 then
call this.destroy()
else
//Move the projectile
set this.x = GetUnitX(this.dummy) + this.dx
set this.y = GetUnitY(this.dummy) + this.dy
call SetUnitX(this.dummy, this.x)
call SetUnitY(this.dummy, this.y)
//if border missiles are enabled, move them too
static if BORDER_MISSILES then
call SetUnitX(this.border1Unit, GetUnitX(this.border1Unit) + this.border1X)
call SetUnitY(this.border1Unit, GetUnitY(this.border1Unit) + this.border1Y)
call SetUnitX(this.border2Unit, GetUnitX(this.border2Unit) + this.border2X)
call SetUnitY(this.border2Unit, GetUnitY(this.border2Unit) + this.border2Y)
endif
static if FILL_AREA then
set width = 0
set reverseAngle = this.angle + bj_PI/2
//Create one effect at the middle line
static if FILL_CORRECT_FACING then
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, Rad2Deg(this.angle))
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
else
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x, this.y))
endif
//Create N more effects vertical to the cast angle, with FILL_DISTANCE between each effect
loop
//Set offset from middle line, and create effect on each side
set width = width + FILL_DISTANCE
exitwhen width > this.currWidth
static if FILL_CORRECT_FACING then
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + width*Cos(reverseAngle), this.y + width*Sin(reverseAngle), Rad2Deg(this.angle))
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - width*Cos(reverseAngle), this.y - width*Sin(reverseAngle), Rad2Deg(this.angle))
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
else
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x + width*Cos(reverseAngle), this.y + width*Sin(reverseAngle)))
call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x - width*Cos(reverseAngle), this.y - width*Sin(reverseAngle)))
endif
endloop
set this.currWidth = this.currWidth + this.widthInc
endif
//Create caster for optional spell effects
set dummyCaster = CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, this.x, this.y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.maxArea, null)
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
set targetX = GetUnitX(u)
set targetY = GetUnitY(u)
//Calculate target unit's distance from the tip of the cone
set diffX = targetX - tipX
set diffY = targetY - tipY
set targetDist = SquareRoot(diffX*diffX + diffY*diffY)
//Calculate the distance between the projectile and tip
set diffX = this.x - tipX
set diffY = this.y - tipY
set dummyDist = SquareRoot(diffX*diffX + diffY*diffY)
//Calculate target unit's direction from the tip
set angleToTip = Atan2(this.tipY - targetY, this.tipX - targetX)
//The unit is in the right direction...
if ((angleToTip > this.leftAngle and angleToTip < this.rightAngle) /*
*/ or (this.leftAngle > this.rightAngle and (angleToTip < this.leftAngle or angleToTip > this.rightAngle))) /*
//...and at the right distance.
*/ and targetDist - dummyDist < this.sliceWidth and dummyDist - targetDist < this.sliceWidth /*
//Check allowed targets
*/ and isTargetAllowed(u, this.caster) then
//If DAMAGE_ONLY_ONCE is enabled, only damage units which aren't in damagedUnits group, and add damaged units to group...
static if DAMAGE_ONLY_ONCE then
if not IsUnitInGroup(u, this.damagedUnits) then
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
call GroupAddUnit(this.damagedUnits, u)
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
//...else damage just damage units
else
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
endif
call GroupRemoveUnit(enumGroup, u)
endloop
call RemoveUnit(dummyCaster)
set this.executions = this.executions - 1
endif
set this = this.next
endloop
//if there are no spell instances running, pause the timer
if count == 0 then
call PauseTimer(iterator)
endif
set dummyCaster = null
endmethod
private static method run takes nothing returns boolean
local thistype this
local real distPerIter
local real distance
local real startArea
local real endArea
local real distToTip
local real targetX
local real targetY
local real areaOffsetX
local real areaOffsetY
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
set targetX = GetSpellTargetX()
set targetY = GetSpellTargetY()
set distance = setDistance(this.caster)
set this.executions = R2I(distance/SPEED/TIMEOUT)
//Angle from cast point to target point
set this.angle = Atan2(targetY - this.y, targetX - this.x)
//set target point to a "correct" value
set targetX = this.x + distance*Cos(this.angle)
set targetY = this.y + distance*Sin(this.angle)
//Calculate travelled distance per iteration
set distPerIter = distance/this.executions
set this.dx = distPerIter*Cos(this.angle)
set this.dy = distPerIter*Sin(this.angle)
//Create projectile
set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, Rad2Deg(this.angle))
set this.eff = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
//Calculate the width of an area, which is damaged per iteration
set this.sliceWidth = DAMAGE_OVERLAP*distPerIter/2
set startArea = setStartArea(this.caster)
set endArea = setEndArea(this.caster)
//Cone is \/ shaped (cast upwards)
if startArea < endArea then
//Tip of the cone is at cast point
if startArea == 0 then
set distToTip = 0
else
set distToTip = distance*startArea/(endArea - startArea)
endif
set this.tipX = this.x - distToTip*Cos(this.angle)
set this.tipY = this.y - distToTip*Sin(this.angle)
set this.maxArea = endArea
//Calculate borderlines of the cone
set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
set this.rightAngle = Atan2(this.tipY - (targetY + areaOffsetY), this.tipX - (targetX + areaOffsetX))
set this.leftAngle = Atan2(this.tipY - (targetY - areaOffsetY), this.tipX - (targetX - areaOffsetX))
//The cone is /\ shaped (cast upwards)
elseif startArea > endArea then
//Tip of the cone is at target point
if endArea == 0 then
set distToTip = 0
else
set distToTip = distance*endArea/(startArea - endArea)
endif
set this.tipX = targetX + distToTip*Cos(this.angle)
set this.tipY = targetY + distToTip*Sin(this.angle)
set this.maxArea = startArea
//Calculate borderlines of the cone
set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
set this.leftAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
set this.rightAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
//The Cone is || shaped, in other words a line
else // startArea == endArea
//Set tip of the cone very far behind the caster, which makes the cone to spread very little
set distToTip = 100000
set this.tipX = this.x - distToTip*Cos(this.angle)
set this.tipY = this.y - distToTip*Sin(this.angle)
set this.maxArea = startArea
//Calculate borderlines of the cone
set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
set this.rightAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
set this.leftAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
endif
set this.damage = setDamage(this.caster)
set this.damagedUnits = CreateGroup()
static if TERRAIN_DEFORMATION then
call TerrainDeformWave(this.x, this.y, targetX, targetY, distance, SPEED, startArea, 150, 2, 1)
endif
static if FILL_AREA then
set this.currWidth = startArea
set this.widthInc = (endArea - startArea)/this.executions
endif
static if BORDER_MISSILES then
set border1X = -distPerIter*Cos(this.rightAngle)
set border1Y = -distPerIter*Sin(this.rightAngle)
set border2X = -distPerIter*Cos(this.leftAngle)
set border2Y = -distPerIter*Sin(this.leftAngle)
set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
set this.border1Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + areaOffsetX, this.y + areaOffsetY, Rad2Deg(this.rightAngle))
set this.border2Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - areaOffsetX, this.y - areaOffsetY, Rad2Deg(this.leftAngle))
set this.border1Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border1Unit, "origin")
set this.border2Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border2Unit, "origin")
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.run))
endmethod
endstruct
endscope
//TESH.scrollpos=51
//TESH.alwaysfold=0
//###################################
//#Volcano triggered version #
//#Created by Erkki2 #
//#Required libraries: #
//# Geometry by Erkki2 #
//###################################
scope LightningVolley
globals
// #################
// # CONFIGURABLES #
// #################
//Dummy unit used for moving effects and casting spell
private constant integer DUMMY_ID = 'h000'
//Spell ability
private constant integer ABIL_CODE = 'A00A'
//Iteration interval
private constant real TIMEOUT = 0.03125
//Speed of the missiles
private constant real SPEED = 350
//Max height of the missiles
private constant real HEIGHT = 600
//Effect when missiles land
private constant string LANDING_EFFECT = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
//Missile art
private constant string MISSILE_EFFECT = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl"
//Effect that stays at the target point while the spell is cast
private constant string CAST_EFFECT = ""
//Ability that is cast on all hit units. Set this to 0 if there's no ability.
private constant integer DUMMY_ABILITY = 0
//Order string for DUMMY_ABILITY
private constant string DUMMY_ORDER = ""
//Time between missiles in the same wave
private constant real MISSILE_INTERVAL = 0
//Time between waves
private constant real WAVE_INTERVAL = 0.1
//Time before first wave
private constant real TIME_BEFORE_FIRST_WAVE = 0
//If true, the spell end when caster stops channelling
private constant boolean REQUIRES_CHANNELLING = true
//Area which is damaged when missiles land
private constant real MISSILE_COLLISION = 100
//Set this to true if the spell has a target point, and to false if the spell is instant.
private constant boolean TARGET_ABILITY = false
endglobals
//Returns the number of missiles in one wave
private function missilesPerWave takes unit caster returns integer
return 1
endfunction
//Returns the spell area
private function setArea takes unit caster returns real
return 400.0 + 150.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns the spell duration.
private function setDuration takes unit caster returns real
return 2.0
endfunction
//Returns damage dealt when a missile lands
private function setDamage takes unit caster returns real
return 20.0 + 10.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
endfunction
//Returns true, if target unit can be hit by the spell
private function isTargetAllowed takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and GetUnitState(target, UNIT_STATE_LIFE) > 0
endfunction
// ############################################################
// # CONFIGURABLES END, Don't touch anything after this line. #
// ############################################################
private struct Dummy
private thistype next
private thistype prev
private static integer count = 0
private unit dummy
//The distance the dummy unit is about to travel
private real dist
//Already travelled distance
private real travelled
private real dx
private real dy
private unit caster
private real damage
private effect attachment
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
call KillUnit(this.dummy)
call DestroyEffect(this.attachment)
set this.attachment = null
set this.caster = null
set this.dummy = null
set this.count = this.count - 1
endmethod
static method move takes nothing returns boolean
local thistype this = thistype(0).next
local real x
local real y
local group g
local unit u
local unit dummyCaster
loop
exitwhen this == 0
//Move dummy units
set x = GetUnitX(this.dummy) + this.dx
set y = GetUnitY(this.dummy) + this.dy
call SetUnitX(this.dummy, x)
call SetUnitY(this.dummy, y)
call SetUnitFlyHeight(this.dummy, getParabolaHeight(this.dist, HEIGHT, this.travelled), 0)
set this.travelled = this.travelled + SquareRoot(this.dx*this.dx + this.dy*this.dy)
//If dummy unit has reached its destination, destroy it and damage area
if this.travelled >= this.dist then
call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, x, y))
set g = CreateGroup()
//Create caster for optional spell effects
set dummyCaster = CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, x, y, 0)
call UnitRemoveAbility(dummyCaster, 'Amov')
call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
call ShowUnit(dummyCaster, false)
call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
call GroupEnumUnitsInRange(g, x, y, MISSILE_COLLISION, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
if isTargetAllowed(u, this.caster) then
call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
//Cast DUMMY_ABILITY on all hit units
if not (DUMMY_ABILITY == 0) then
call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
endif
endif
call GroupRemoveUnit(g, u)
endloop
call DestroyGroup(g)
call RemoveUnit(dummyCaster)
call this.destroy()
endif
set this = this.next
endloop
set g = null
set dummyCaster = null
//return true if number of dummy units is 0
return count == 0
endmethod
static method new takes unit caster, real x, real y, real damage, real area returns nothing
local thistype this = thistype.allocate()
local real angle
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
set angle = GetRandomReal(0, 2*bj_PI)
set this.dist = GetRandomReal(10, area)
set this.dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, x, y, angle)
set this.attachment = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
set this.travelled = 0
set this.damage = damage
set this.caster = caster
set this.dx = Cos(angle)*SPEED*TIMEOUT*dist/area
set this.dy = Sin(angle)*SPEED*TIMEOUT*dist/area
endmethod
endstruct
private struct Spell
private thistype next
private thistype prev
private static timer iterator = CreateTimer()
private static integer count = 0
private unit caster
private real damage
private boolean channelling = true
//Time before next dummy unit is created
private real dummyDelay
private real x
private real y
private integer executions
private effect eff
//Number of dummy units in a wave
private integer dummyCount
private real area
//How many dummies of this wave have been already created
private integer currentDummy
private method destroy takes nothing returns nothing
call this.deallocate()
set this.next.prev = this.prev
set this.prev.next = this.next
call DestroyEffect(this.eff)
set this.eff = null
set this.count = this.count - 1
set this.caster = null
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local boolean spellEnd = false
local integer i
loop
exitwhen this == 0
set this.dummyDelay = this.dummyDelay - TIMEOUT
set i = 0
//Create dummy units. If MISSILE_INTERVAL < TIMEOUT, more than one will be created.
loop
exitwhen this.dummyDelay >= 0
call Dummy.new(this.caster, this.x, this.y, this.damage, this.area)
set i = i + 1
//This dummy is the last of this wave
if currentDummy == dummyCount then
set this.dummyDelay = this.dummyDelay + WAVE_INTERVAL
set this.currentDummy = 0
else
set this.dummyDelay = this.dummyDelay + MISSILE_INTERVAL
set this.currentDummy = this.currentDummy + 1
endif
endloop
//Caster stops channelling
static if REQUIRES_CHANNELLING then
if not this.channelling then
call this.destroy()
endif
endif
//Spell duration is reached
set this.executions = this.executions - 1
if this.executions == 0 then
call this.destroy()
endif
set this = this.next
endloop
//Move all dummy units
set spellEnd = Dummy.move()
//If there are no dummy units and no units casting the spell, stop timer
if spellEnd and count == 0 then
call PauseTimer(iterator)
endif
endmethod
private static method run takes nothing returns boolean
local thistype this
if GetSpellAbilityId() == ABIL_CODE then
set this = thistype.allocate()
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set count = count + 1
if count == 1 then
call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
endif
set this.caster = GetTriggerUnit()
set this.damage = setDamage(this.caster)
static if TARGET_ABILITY then
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
else
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
endif
set this.area = setArea(this.caster)
set this.executions = R2I(setDuration(this.caster)/TIMEOUT)
set this.eff = AddSpecialEffect(CAST_EFFECT, this.x, this.y)
set this.dummyCount = missilesPerWave(this.caster)
set this.currentDummy = 0
set this.dummyDelay = TIME_BEFORE_FIRST_WAVE
endif
return false
endmethod
private static method stop takes nothing returns boolean
local thistype this = thistype(0).next
if GetSpellAbilityId() == ABIL_CODE then
loop
exitwhen GetTriggerUnit() == this.caster or this == 0
set this = this.next
endloop
set this.channelling = false
endif
return false
endmethod
private static method onInit 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 thistype.run))
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t2, Condition(function thistype.stop))
endmethod
endstruct
endscope