//#################################
//#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