Moderator
M
Moderator
30th Jan 2012
Bribe: Thanks for making the updates. Rating upgraded to 4.25/5 (Recommended).
Bribe: Thanks for making the updates. Rating upgraded to 4.25/5 (Recommended).
/*
* Charge of Darkness by jim7777
* v1.5
*
* Spell based from the game, Defense of the Ancients
*
* The hero charges towards the target, knockbacking every enemy on its path, then stunning the target if the caster reaches it.
*
* This spell is mostly configurable to suit your needs
* Hope you enjoy it!
*
* Requires:
* GetClosestWidget - Spinnaker
* DummyCaster - Nestharus
* RegisterPlayerUnitEvent - Magtheridon96
* SpellEffectEvent - Bribe
* TimerUtils - Vexorian
* Timer32 - Jesus4Lyf
*
********************************************************************/
scope ChargeOfDarkness
globals
private constant integer ABILITY_ID = 'A000' //ability id of Charge of Darkness ability.
private constant integer FAERIE_ID = 'A001' //ability id of CoD_BuffPlacer ability
private constant integer STUN_ID = 'A002' //ability id of CoD_StunPlacer ability, modify the ability to suit your needs
private constant integer BUFF_ID = 'B000' //buff id of Charge of Darkness buff
private constant integer ANIM_INDEX = 2 //The animation index from the model you'll be using on this spell, applicable if ANIM_TYPE is set to true
//2 is the animation index for Walk of the model Spirit Walker
//Read the Documentation on checking the animation index of a unit.
private constant real DETECTOR_RANGE = 1500 //This value will tell if we are in range of the target and if we are, add the buff to the target
private constant real STUN_RANGE = 100 //how far will we cast the stun? This is already a recommended value >:D
private constant real RADIUS = 400 //range to detect enemies when last target is dead, only applicable if CHANGE_ONDEAD is set to true
private constant real KNOCK_RADIUS = 200 //the radius within the caster that will be affected by knock backs, applicable if KNOCKBACK_PATH is set to true
private constant real KNOCK_DISTANCE = 80 //the distance of the knockback
private constant real KNOCK_DUR = 1.5 //duration of knockback
private constant boolean CHANGE_ONDEAD = true //charge to the nearest target if target is dead? This will detect enemies within RADIUS range, setting this to false will just stop the caster from charging
private constant boolean DISABLE_STOP = true //Should orders stop the spell?
private constant boolean LOOPING_SFX = false //The LOOP_SFX will be created for every instance,if false, LOOP SFX will be created on spell start
private constant boolean ANIM_TYPE = true //true: uses index, false: uses string animations
private constant boolean KNOCKBACK_PATH = true //knockback enemies in its path
private constant string ANIM_STR = "walk" //what animation to be applied on the caster, applicable if ANIM_TYPE is set to false.
//NOTE: I recommend using the INDEX because the native we are using here is quite buggy to some models like the Spirit Walker model
//I advise using the ANIM_INDEX variable instead of this variable if you'd like to see your spell animations smoother.
private constant string TARGET = "Abilities\\Spells\\Other\\HowlOfTerror\\HowlTarget.mdl" //the effect that will appear on the target
private constant string LOOP_SFX = "Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl" //the effect that will appear on the caster while charging
private constant string KNOCK_SFX = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl" //the effect that will appear if a unit is knock backed
private constant string ATTACH_POINT = "origin" //attchment point of LOOP_SFX
private hashtable ht = InitHashtable()
private group tmpGroup = null
endglobals
//! textmacro ChargeOfDarkness_OnChargeEnemyPath
//the variable f is our target
//this.caster is the unit who casted ChargeOfDarkness
//this.owner is the player who owns our caster
//modify this thing to suit your needs
//This will only work if KNOCKBACK_PATH is set to true
call DummyCaster[STUN_ID].castTarget(this.owner,1,OrderId("firebolt"),f)
//! endtextmacro
//! textmacro ChargeOfDarkness_IsOnKnockback
//variable u is our target
//it should match with the ChargeOfDarkness_OnChargeEnemyPath macro
//in this example, we check if the unit is stunned.
//modify this thing to suit your needs
//This will only work if KNOCKBACK_PATH is set to true
local boolean b = /*
only modify the following parts to your condition
*/ GetUnitAbilityLevel(u,'BSTN') > 0
//! endtextmacro
private function CoD_Speed takes unit u, real lvl returns real
//total speed is to be multiplied by the number of instances per second.
//there are 33 instances per second
return 15+(2*lvl)
endfunction
private struct SChaser
unit caster
unit target
integer lvl
effect eff
player owner
real speed
real px
real py
group stacks
static if not LOOPING_SFX then
effect lpsfx
endif
private static thistype tmp
private method destroy takes nothing returns nothing
static if not LOOPING_SFX then
call DestroyEffect(this.lpsfx)
endif
static if KNOCKBACK_PATH then
call DestroyGroup(this.stacks)
endif
call RemoveSavedInteger(ht,GetHandleId(this.caster),0)
call SetUnitPathing(this.caster,true)
call SetUnitTimeScale(this.caster,1)
call SetUnitAnimation(this.caster,"stand")
call UnitShareVision(this.target,this.owner,false)
call DestroyEffect(this.eff)
call UnitRemoveAbility(this.target,BUFF_ID)
call this.stopPeriodic()
call this.deallocate()
endmethod
static method Filt takes nothing returns boolean
return GetFilterUnit() != tmp.target and IsUnitEnemy(GetFilterUnit(),tmp.owner) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) and GetUnitAbilityLevel(GetFilterUnit(),'Aloc') <= 0
endmethod
static if KNOCKBACK_PATH then
static method CheckStack takes nothing returns nothing
local unit u = GetEnumUnit()
//! runtextmacro ChargeOfDarkness_IsOnKnockback()
if not b then
call GroupRemoveUnit(tmp.stacks,u)
endif
set u = null
endmethod
endif
private method periodic takes nothing returns nothing
local real ux = GetUnitX(this.caster)
local real uy = GetUnitY(this.caster)
local real tx = GetUnitX(this.target)
local real ty = GetUnitY(this.target)
local real dx = tx - ux
local real dy = ty - uy
local real dist = SquareRoot(dx * dx + dy * dy)
local real angle = Atan2(dy, dx)
local real x = ux + this.speed * Cos(angle)
local real y = uy + this.speed * Sin(angle)
local string str
local boolean dest = false
local unit f
static if LOOPING_SFX then
call DestroyEffect(AddSpecialEffectTarget(LOOP_SFX,this.caster,ATTACH_POINT))
endif
static if ANIM_TYPE then
call SetUnitAnimationByIndex(this.caster,ANIM_INDEX)
else
call SetUnitAnimation(this.caster,ANIM_STR)
endif
if not IsUnitVisible(this.target,this.owner) and not IsUnitType(this.target,UNIT_TYPE_DEAD) then
call UnitShareVision(this.target,this.owner,true)
endif
static if DISABLE_STOP then
call SaveBoolean(ht,GetHandleId(this.caster),1,true) //we need to do this D:
if not IssueImmediateOrder(this.caster,"holdposition") then //detect if the unit can do any orders, if not, 99% it is stunned or disabled.
call this.destroy()
set dest = true
endif
call RemoveSavedBoolean(ht,GetHandleId(this.caster),1)
else
call IssueImmediateOrder(this.caster,"holdposition")
endif
if IsUnitType(this.target,UNIT_TYPE_DEAD) then
static if CHANGE_ONDEAD then
call UnitRemoveAbility(this.target,BUFF_ID)
call DestroyEffect(this.eff)
set tmp = this
set this.target = GetClosestUnitInRange(tx,ty,RADIUS,Filter(function thistype.Filt))
if this.target == null and not dest then
call IssueImmediateOrder(this.caster,"stop")
call this.destroy()
set dest = true
else
set str = TARGET
if not IsPlayerAlly(GetLocalPlayer(),this.owner) then
set str = ""
endif
set this.eff = AddSpecialEffectTarget(str,this.target,"head")
endif
elseif not dest then
call this.destroy()
set dest = true
endif
elseif (IsUnitType(this.caster,UNIT_TYPE_DEAD) or IsUnitPaused(this.caster)) and not dest then
call this.destroy()
set dest = true
endif
if not IsUnitType(this.target,UNIT_TYPE_DEAD) then
call SetUnitX(this.caster,x)
call SetUnitY(this.caster,y)
call SetUnitFacing(this.caster,angle*bj_RADTODEG)
static if KNOCKBACK_PATH then
set tmp = this
call ForGroup(this.stacks,function thistype.CheckStack)
call GroupEnumUnitsInRange(tmpGroup,x,y,KNOCK_RADIUS,null)
loop
set f = FirstOfGroup(tmpGroup)
exitwhen f == null
call GroupRemoveUnit(tmpGroup,f)
if not HaveSavedInteger(ht,GetHandleId(this.caster),0) and not IsUnitInGroup(f,this.stacks) and f != this.target and IsUnitEnemy(f,this.owner) and not IsUnitType(f,UNIT_TYPE_DEAD) and not IsUnitType(f,UNIT_TYPE_MAGIC_IMMUNE) and GetUnitAbilityLevel(f,'Aloc') <= 0 then
set angle = Atan2(GetUnitY(f) - GetUnitY(this.caster), GetUnitX(f) - GetUnitX(this.caster))
//call KBS_BeginEx(f,KNOCK_DISTANCE,KNOCK_DUR,angle*bj_RADTODEG,KNOCK_SFX,100,false,true)
//! runtextmacro ChargeOfDarkness_OnChargeEnemyPath()
call GroupAddUnit(this.stacks,f)
endif
endloop
endif
if dist <= DETECTOR_RANGE and GetUnitAbilityLevel(this.target,BUFF_ID) == 0 then
call DummyCaster[FAERIE_ID].castTarget(this.owner,1,OrderId("faeriefire"),this.target)
endif
if dist <= STUN_RANGE then
call DummyCaster[STUN_ID].castTarget(this.owner,this.lvl,OrderId("firebolt"),this.target)
call IssueTargetOrder(this.caster,"attack",this.target)
if not dest then
call this.destroy()
endif
endif
endif
set f = null
endmethod
implement T32x
private static method start takes nothing returns boolean
local thistype this = thistype.allocate()
local string str = TARGET
set this.caster = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set this.speed = CoD_Speed(this.caster,GetUnitAbilityLevel(this.caster,ABILITY_ID))
set this.owner = GetOwningPlayer(this.caster)
set this.px = GetUnitX(this.caster)
set this.py = GetUnitY(this.caster)
set this.lvl = GetUnitAbilityLevel(this.caster,ABILITY_ID)
static if KNOCKBACK_PATH then
set this.stacks = CreateGroup() //seriously
endif
if not IsPlayerAlly(GetLocalPlayer(),this.owner) then
set str = ""
endif
set this.eff = AddSpecialEffectTarget(str,this.target,"head")
static if not LOOPING_SFX then
set this.lpsfx = AddSpecialEffectTarget(LOOP_SFX,this.caster,ATTACH_POINT)
endif
call SetUnitTimeScale(this.caster,1.2)
call UnitShareVision(this.target,this.owner,true)
call SetUnitPathing(this.caster,false)
call SaveInteger(ht,GetHandleId(this.caster),0,this)
call this.startPeriodic()
return false
endmethod
static if DISABLE_STOP then
static method OnOrder takes nothing returns boolean
local unit u = GetTriggerUnit()
local thistype dat = LoadInteger(ht,GetHandleId(u),0)
if not HaveSavedBoolean(ht,GetHandleId(u),1) and dat != 0 then
call dat.destroy()
endif
set u = null
return false
endmethod
endif
static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABILITY_ID,function thistype.start)
static if DISABLE_STOP then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER,function thistype.OnOrder)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER,function thistype.OnOrder)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER,function thistype.OnOrder)
endif
call Preload(TARGET)
call Preload(LOOP_SFX)
call Preload(KNOCK_SFX)
static if KNOCKBACK_PATH then //let us save memory!
set tmpGroup = CreateGroup()
endif
endmethod
endstruct
endscope