scope SpinningShards initializer Init
//User constants, feel free to experiment
globals
private constant integer SID = 'spsw'
//Place the spell Rawcode here
private constant integer DID = 'spsd'
//Place the Dummy´s Rawcode here
private constant integer LID ='lnch'
//Rawcode for the launcher ability
private constant integer TAG_RED = 255
//How Red should the textag be ? NOTE: The value must be between 0 and 255
private constant integer TAG_BLUE = 255
//How Blue should the textag be ? NOTE: The value must be between 0 and 255
private constant integer TAG_GREEN = 255
//How Green should the textag be ? NOTE: The value must be between 0 and 255
private constant integer TAG_OPA = 0
//How transparent should the textag be ? NOTE: The value must be between 0 and 255
private constant boolean TIME_STACK = true
//true = The caster will keep his shards when the spell is recasted and the duration increases
//false = The caster shoots his shards away and creates new ones
private constant boolean DAMAGE_STACK = false
//true = The spells deals more AoE damage during the spinning phase when recasted
//false = The spell won´t deal more AoE damage when recasting
private constant boolean REMOVE_BY_HIT = false
//true = The shards will disappear when they hit an enemy
//false = The shards can travel trough enemies (deals more damage)
private constant boolean SPREAD_OUT = false
//true = Shoots every missile in its current facing direction
//false = Shoots all missiles int the facing directon of the caster
private constant boolean WANT_TEXT_TAG = true
//true = Creates a time indicating floating text above the caster
//false = No floating text is created
private constant string IMPACT_SFX = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
//The special effect being displayed when an enemy gets hit during the spinning phase
private constant string SPAWN_SFX = "Abilities\\Spells\\Undead\\OrbOfDeath\\OrbOfDeathMissile.mdl"
//The special effect being displayed when the shards are created
private constant string DEATH_SFX = SPAWN_SFX
//The special effect being displayed when the shards disappear
private constant string IMPACT_ATTATCH = "head"
//Where should the IMPACT_SFX be attatched on a target ?
private constant real DIAMETER = 200
//The diameter of the circle of the shards
private constant real SPIN_INTER = 0.02
//How fast should the shards spin ?
private constant real ANGLE_ALTER = 13
//How many degrees should the shards move per interval ?
private constant real SHOOT_INTER = 0.03
//How fast should the shards move when they shoot away ?
private constant real SHOOT_DIST = 45
//Which distance should a shard being moved per interval ?
private constant real SHOOT_IMPACT_RANGE = 120
//The range in which a shard can damae an enemy when it´s moving
private constant real DAMAGE_INTER = 0.1
//How long is the interval for damageing enemys when spinning around the caster ?
private constant real SPIN_IMPACT_RANGE = 300
//The AoE radius for dealing damage while the shards are spinning
private constant real TAG_SIZE = 0.03
//How large should a textag be (the values are some kinda weird)
private constant real TAG_HEIGHT = 13
//How far should the textag float above the caster ?
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//The spell privates. PLEASE DO NOT TOUCH !
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
private constant group TEMP_GROUP = CreateGroup()
private constant timer SPIN_TIM = CreateTimer()
private constant timer SHOOT_TIM = CreateTimer()
private integer TEMP_STRUCT
private filterfunc SPIN_DAMAGE_FILTER
private filterfunc SHOOT_DAMAGE_FILTER
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
endglobals
//Howmany shards should spawn related to the level ?
private constant function GetSwordAmount takes integer level returns integer
return level * 2 + 1
endfunction
//Howmuch damage shall the enemies recieve per second when the sworeds are spinning
private constant function GetSpinDamage takes integer level returns integer
return level * 40 + 10
endfunction
//How much damage should a singe shard deal upon impact when they are launched ?
private constant function GetImpactDamage takes integer level returns integer
return level * 20 + 15
endfunction
//How long should the shards spin around the caster ?
private constant function GetDuration takes integer level returns integer
return level * 5 + 4
endfunction
//How far should the shards travel when they are launched ?
private constant function GetShootDistance takes integer level returns integer
return level * 250 + 650
endfunction
//The struct that hodls the data when the shards are spinning
private struct SpinData
static thistype array Index
static integer Total = 0
//Static fields needed for struct indexing method
unit caster
group swords
texttag tag
real spin_damage
real damage_inter
real dur
method onDestroy takes nothing returns nothing
//Clean up some leaks
call UnitRemoveAbility(.caster,LID)
call DestroyGroup(.swords)
call DestroyTextTag(.tag)
set .caster = null
endmethod
//The creator mehod
static method create takes unit u returns thistype
local thistype this = thistype.allocate()
local integer lv = GetUnitAbilityLevel(u,SID)
local integer i = GetSwordAmount(lv)
local real a
local real x
local real y
//General setup
set .caster = u
set .spin_damage = GetSpinDamage(lv) * SPIN_INTER
set .dur = GetDuration(lv)
set .swords = CreateGroup()
set .damage_inter = 0
call UnitAddAbility(u,LID)
//TextTag Setup
if WANT_TEXT_TAG then
set .tag = CreateTextTag()
call SetTextTagText(.tag,I2S(R2I(.dur)),TAG_SIZE)
call SetTextTagColor(.tag,TAG_RED,TAG_GREEN,TAG_BLUE,255 - TAG_OPA)
call SetTextTagPermanent(.tag,false)
call SetTextTagVisibility(.tag,true)
call SetTextTagLifespan(.tag,.dur)
call SetTextTagFadepoint(.tag,.dur - 1)
call SetTextTagPosUnit(.tag,.caster,TAG_HEIGHT)
endif
//Preparing the shards and placing them in a circle
loop
exitwhen i == 0
set a = (360/GetSwordAmount(lv)) * i * bj_DEGTORAD
set x = GetUnitX(.caster) + DIAMETER * Cos(a)
set y = GetUnitY(.caster) + DIAMETER * Sin(a)
set u = CreateUnit(GetOwningPlayer(.caster),DID,x,y,a * bj_RADTODEG)
call PauseUnit(u,true)
call SetUnitPathing(u,false)
call SetUnitUseFood(u,false)
call GroupAddUnit(.swords,u)
call UnitAddAbility(u,'Aloc')
call DestroyEffect(AddSpecialEffect(SPAWN_SFX,x,y))
set i = i - 1
endloop
//Update the index
set thistype.Index[thistype.Total] = this
set thistype.Total = thistype.Total + 1
return this
endmethod
endstruct
//This struct hodls the data for shooting the shards
private struct ShootData
static thistype array Index
static integer Total = 0
//Static fields needed for struct indexing method
static real MAX_X
static real MAX_Y
static real MIN_X
static real MIN_Y
//since this struct behaves like a knockback the units could escape the map but we don´t want that
unit missile
real dist
real cos
real sin
real dmg
real dmg_inter
static method Move takes nothing returns nothing
local thistype this
local integer inst = 0
//Loop for all structs
loop
exitwhen inst == thistype.Total
set this = thistype.Index[inst]
//Moving a shard
set .dist = .dist - SHOOT_DIST
set .dmg_inter = .dmg_inter + SHOOT_INTER
call SetUnitPosition(.missile,GetUnitX(.missile) + .cos,GetUnitY(.missile) + .sin)
//Ready for dealing damage ?
if .dmg_inter > DAMAGE_INTER then
call GroupEnumUnitsInRange(TEMP_GROUP,GetUnitX(.missile),GetUnitY(.missile),SHOOT_IMPACT_RANGE,SHOOT_DAMAGE_FILTER)
set .dmg_inter = 0
endif
//Destrying structs and recycle the index
if .dist <= 0 or GetUnitX(.missile) >= ShootData.MAX_X or GetUnitX(.missile) <= ShootData.MIN_X or GetUnitY(.missile) >= ShootData.MAX_Y or GetUnitY(.missile) <= ShootData.MIN_Y or (FirstOfGroup(TEMP_GROUP) != null and REMOVE_BY_HIT) then
call DestroyEffect(AddSpecialEffect(DEATH_SFX,GetUnitX(.missile),GetUnitY(.missile)))
call RemoveUnit(.missile)
call this.destroy()
set thistype.Total = thistype.Total - 1
set thistype.Index[inst] = thistype.Index[thistype.Total]
set inst = inst - 1
endif
call GroupClear(TEMP_GROUP)
set inst = inst + 1
endloop
//No struct running, no timer needed
if thistype.Total == 0 then
call PauseTimer(SHOOT_TIM)
endif
endmethod
static method create takes unit m, unit c returns thistype
local thistype this = thistype.allocate()
local integer lv = GetUnitAbilityLevel(c,SID)
local real a = GetUnitFacing(c) * bj_DEGTORAD
//General Setup
set .missile = m
set .dist = GetShootDistance(lv)
set .dmg = GetImpactDamage(lv)
if SPREAD_OUT then
set a = GetUnitFacing(m) * bj_DEGTORAD
set .cos = SHOOT_DIST * Cos(a)
set .sin = SHOOT_DIST * Sin(a)
else
call SetUnitFacing(m,a * bj_RADTODEG)
set .cos = SHOOT_DIST * Cos(a)
set .sin = SHOOT_DIST * Sin(a)
endif
//Is the timer running ?
if thistype.Total == 0 then
call TimerStart(SHOOT_TIM,SHOOT_INTER,true,function thistype.Move)
endif
//Update the index
set thistype.Index[thistype.Total] = this
set thistype.Total = thistype.Total + 1
return this
endmethod
endstruct
//Makes the shards circulate
private function Shift takes nothing returns nothing
local SpinData this = TEMP_STRUCT
local unit u = GetEnumUnit()
local real a = (GetUnitFacing(u) + ANGLE_ALTER) * bj_DEGTORAD
call SetUnitPosition(u,GetUnitX(this.caster) + DIAMETER * Cos(a),GetUnitY(this.caster) + DIAMETER * Sin(a))
call SetUnitFacing(u,a * bj_RADTODEG)
set u = null
endfunction
//Launching the shards....
private function Launch takes nothing returns nothing
local SpinData this = TEMP_STRUCT
call ShootData.create(GetEnumUnit(),this.caster)
endfunction
//Deals damage during the spin phase, this "Filter" adds no units to any group
private function SpinDamage takes nothing returns boolean
local SpinData this = TEMP_STRUCT
local unit u = GetFilterUnit()
if IsUnitEnemy(u,GetOwningPlayer(this.caster)) and GetWidgetLife(u) > 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE) !=true and IsUnitType(u, UNIT_TYPE_MECHANICAL) != true and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) != true and IsUnitType(u, UNIT_TYPE_ANCIENT) != true then
call UnitDamageTarget(this.caster,u,this.spin_damage,false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNKNOWN,WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffectTarget(IMPACT_SFX,u,IMPACT_ATTATCH))
set u = null
return true
endif
set u = null
return false
endfunction
//Deals damage during the shoot phase, this is a real Filter which adds units under certain circumstances
private function ShootDamage takes nothing returns boolean
local ShootData this = TEMP_STRUCT
local unit u = GetFilterUnit()
if IsUnitEnemy(u,GetOwningPlayer(this.missile)) and GetWidgetLife(u) > 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE) !=true and IsUnitType(u, UNIT_TYPE_MECHANICAL) != true and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) != true and IsUnitType(u, UNIT_TYPE_ANCIENT) != true then
call UnitDamageTarget(this.missile,u,this.dmg,false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNKNOWN,WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffectTarget(IMPACT_SFX,u,IMPACT_ATTATCH))
set u = null
return true
endif
set u = null
return false
endfunction
//Main function that moves the shards in a circle
private function Spin takes nothing returns nothing
local SpinData this
local integer inst = 0
//Looping trough all structs
loop
exitwhen inst == SpinData.Total
set this = SpinData.Index[inst]
if this.dur > 0 then
set this.dur = this.dur - SPIN_INTER
set this.damage_inter = this.damage_inter + SPIN_INTER
//Moving the Shards
set TEMP_STRUCT = this
call ForGroup(this.swords,function Shift)
//Moving the texttag (if you want)
if WANT_TEXT_TAG then
call SetTextTagText(this.tag,I2S(R2I(this.dur)),TAG_SIZE)
call SetTextTagPosUnit(this.tag,this.caster,TAG_HEIGHT)
endif
//Deals the damage...Not every sword deals damage, only the caster
if this.damage_inter > DAMAGE_INTER then
set TEMP_STRUCT = this
call GroupEnumUnitsInRange(TEMP_GROUP,GetUnitX(this.caster),GetUnitY(this.caster),SPIN_IMPACT_RANGE,SPIN_DAMAGE_FILTER)
set this.damage_inter = 0
endif
else
//Launching the shards
set TEMP_STRUCT = this
call ForGroup(this.swords,function Launch)
//Destroying the struct and recycle the index
call this.destroy()
set SpinData.Total = SpinData.Total - 1
set SpinData.Index[inst] = SpinData.Index[SpinData.Total]
set inst = inst - 1
endif
set inst = inst + 1
endloop
//No struct running no timer needed
if SpinData.Total == 0 then
call PauseTimer(SPIN_TIM)
endif
endfunction
//The remains of GUI
private function InitSpin takes nothing returns nothing
local SpinData this
local unit u = GetTriggerUnit()
local integer i = 0
loop
exitwhen i == SpinData.Total
set this = SpinData.Index[i]
if u == this.caster then
if TIME_STACK then
set this.dur = this.dur + GetDuration(GetUnitAbilityLevel(u,SID))
call SetTextTagAge(this.tag,0)
call SetTextTagLifespan(this.tag,this.dur)
call SetTextTagFadepoint(this.tag,this.dur-1)
set u = null
return
endif
if DAMAGE_STACK then
set this.spin_damage = this.spin_damage + GetSpinDamage(GetUnitAbilityLevel(u,SID))
set u = null
return
endif
return
endif
set i = i + 1
endloop
//Starting the spell and exchanging the abilities
call SpinData.create(u)
set u = null
if SpinData.Total - 1 == 0 then
call TimerStart(SPIN_TIM,SPIN_INTER,true,function Spin)
endif
endfunction
//When the launcher ability is being cast the shards move instantly away
private function InstantLaunch takes nothing returns nothing
local SpinData this
local integer inst = 0
local unit u = GetTriggerUnit()
//Looping thourgh all active instances to find out the caster
loop
exitwhen inst == SpinData.Total
set this = SpinData.Index[inst]
//When we found our caster we end the spell
if u == this.caster then
set this.dur = -1
set u = null
return
endif
set inst = inst + 1
endloop
set u = null
endfunction
//Checking the spell rawcode
private function CheckSID takes nothing returns boolean
return GetSpellAbilityId() == SID
endfunction
//Checking the rawcode for the instant launch ability
private function CheckLID takes nothing returns boolean
return GetSpellAbilityId() == LID
endfunction
//I don´t want to leak upon trigger creation
private constant function AntiLeak takes nothing returns boolean
return true
endfunction
//The initializer....
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local trigger t2 = CreateTrigger()
local filterfunc f = Filter(function AntiLeak)
local integer i = 0
//Initializing some globals
set SPIN_DAMAGE_FILTER = Filter(function SpinDamage)
set SHOOT_DAMAGE_FILTER = Filter(function ShootDamage)
set ShootData.MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 100
set ShootData.MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 100
set ShootData.MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 100
set ShootData.MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 100
//Registrating the Trigger
loop
call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,f)
call TriggerRegisterPlayerUnitEvent(t2,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,f)
exitwhen i == 15
set i = i + 1
endloop
//Preloading the SFX´s
call Preload(SPAWN_SFX)
call Preload(IMPACT_SFX)
call Preload(DEATH_SFX)
call PreloadStart()
//Triger Registraion Part 2
call TriggerAddCondition(t,Condition(function CheckSID))
call TriggerAddCondition(t2,Condition(function CheckLID))
call TriggerAddAction(t,function InitSpin)
call TriggerAddAction(t2,function InstantLaunch)
call DestroyFilter(f)
set t = null
set t2 = null
set f = null
endfunction
endscope