//////////////////////////////////////Globals with important values////////////////////////////////////////
scope DuskySoul initializer Init
globals
private constant integer Spell = 'A005'
//Dark Soul Spell
private constant integer Summon = 'n001'
//Summoned unit (Dark Soul)
private constant integer AttackDummy = 'h002'
//An Attack dummy to fake an attack at the target
private constant integer AttackProjectile = 'h003'
//Projectile of the Dark Soud
private constant integer DummyVertex = 170
//Vertex the Summon has
private constant integer DummyDarkness = 255
//How dark the summon is
private constant real TimerPeriod = 0.0225
//Timer Interval of the custom projectiles
private constant real TimerPeriodDsys = 0.1
//Timer Interval for the Attack dummys locaation adjust (moves the dummy to the target to faake an attack)
private constant real CrueltyDamage = 0.14
//Percentage damage of hp missing
private constant real HitRange = 30
//Range in which the projectiles hit an enemy
private constant real HitHeight = 45
//Height in which the projectiles will reach the target (flying heigh wwill be additional)
private constant real ProjectileSpeed = 18
//Speed the Projectiles got, also affaectey by the timer period
private constant real ProjectileStartX = -12
// X Offset of the Projectile Creation (look at the model you used)
private constant real ProjectileStartY = 100
// Y Offset of the Projectile Creation (look at the model you used)
private constant real ProjectileStartZ = 130
// Z Offset of the Projectile Creation (look at the model you used)
private constant real ProjectileSway = 0.15
//Defines the amplitude of the projectiles movement (their distance to each other increaes while flying like a parabula
// 0.15 means the amplitube will be 15% of the flying distance
private constant real GrowSpeed = 7
//Rate the summon will grow and shring (played when creating it)
private constant real DummySize = 1.15
//The normal size of the Summon (needed for the grow/shrink)
private constant string AttackEffect = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl"
//The effects displayed the the darksould hands when releasing a missile
private constant string AOEeffect = "Abilities\\Weapons\\GreenDragonMissile\\GreenDragonMissile.mdl"
// AOE effect when hitting a target with Inner Blaze
private constant string StartAnimation = "Spell"
//The animation the dummy plays when being created
private constant boolean KillDS = false //Sets the action if the hero casts the ability while he already got one Dark soul summoned
//True = Kiling the current Dark Sould when casting it again
//False = Odering the caster to stop
///////////////////////////////////Globals that should not be changed////////////////////////////
private trigger array tr //Trigger for each hero casting and having the summoned unit to protect any damage
private trigger array tr2 //Trigger to get the damage of each attack dummy and create missiles then
private integer trin = 0 //Trigger index , how many Summoned units are ingame which use this custom system (equals number of dummies and current triggers Tr1 and Tr2)
private unit array ds //Dark soul (with trin index)
private unit array ad //Attack Dummy (with trin index)
private unit array targ //Target of each Dark sould (with trin index)
private unit array hero //Corrensponding hero (with trin index)
private timer t2 = CreateTimer()
private boolean cb = false // boolean if any callbacls should run
private boolexpr filter
private timer t = CreateTimer()
endglobals
/////////////////////////////////////////Function modifications////////////////////////////////////////////////////////
private function RunCondition takes nothing returns boolean
return GetSpellAbilityId() == Spell
//The condition if the spell is casted
endfunction
private constant function Duration takes integer level returns real
return 75.00
endfunction
private function DamageTarget takes real damage, unit target, unit dealer returns nothing
//Sets which function is used to deal the damage, now it is dependent on the IFDamage function in Inner Blaze which will cause its damage to splash if the unit got the buff
//If you dont want it to splash, replace it, e.g. with the one below or whichever you want
call IFDamage(damage,target,dealer,AOEeffect,"chest")
//call UnitDamageTarget(dealer,target,damage,true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL , WEAPON_TYPE_WHOKNOWS)
endfunction
function DisplayDamage takes real damage, unit u returns nothing
//Displays the bonus damage of the Dark Soul
local texttag tt = null
if R2I(damage) != 0 then // does not display "+0" damage
set tt = CreateTextTag()
call SetTextTagText(tt, "|c00ff5656+"+I2S(R2I(damage))+"|r", 0.023)
//The color is set here
//Any other things you may want to modify or not
call SetTextTagPos(tt, GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u))
call SetTextTagColor(tt,255,255,255,255)
call SetTextTagVisibility(tt, true)
call SetTextTagFadepoint(tt,3.00)
call SetTextTagPermanent(tt,false)
call SetTextTagVelocity(tt, 0, 0.0355)
call SetTextTagLifespan(tt, 4.00)
set tt = null
endif
endfunction
////////////////////////////////////////Main Script /////////////////////////////////////////////////////////////
private constant function NullFilter takes nothing returns boolean
return true
endfunction
private function GetHeroIndex takes unit a returns integer
// Gets the trin of the hero to find its correnspoding summon ect
local integer i = 0
loop
exitwhen i == trin
if a == hero[i] then
return i
endif
set i = i + 1
endloop
return -1
endfunction
private function GetTargIndex takes unit a returns integer
// Gets the trin of a targeted unit to check if a unit s attacking it (cuz the attack dummy stay and else it would be still "attacked")
local integer i = 0
loop
exitwhen i == trin
if a == targ[i] then
return i
endif
set i = i + 1
endloop
return -1
endfunction
private function GetDSIndex takes unit a returns integer
/// Gets the trin of a Dark sould to let the hero deal the damage (needs to be for Inner Blaze cuz he has the ability)
local integer i = 0
loop
exitwhen i == trin
if a == ds[i] then
return i
endif
set i = i + 1
endloop
return -1
endfunction
private function GetDummyIndex takes unit a returns integer
// Gets the trin of a Dummy unit
local integer i = 0
loop
exitwhen i == trin
if a == ad[i] then
return i
endif
set i = i + 1
endloop
return -1
endfunction
private function GetUnitsAngle takes unit a, unit b returns real
//angle between units, from a to b
return Atan2(GetUnitY(b) - GetUnitY(a), GetUnitX(b) - GetUnitX(a))
endfunction
function GetUnitDistance takes unit a, unit b returns real
//Distance between 2 units
return SquareRoot((GetUnitX(a) - GetUnitX(b)) * (GetUnitX(a) - GetUnitX(b)) + (GetUnitY(a) - GetUnitY(b)) * (GetUnitY(a) - GetUnitY(b)))
endfunction
///////////////////////////////////////CallBack//////////////////////////////////////////////
private function Callback takes nothing returns nothing
local integer i = 0
local real db
//looping through each dark Sould (creation grow)
loop
exitwhen i >= DS.Index
if DS.Data[i].inc == -1 and DS.Data[i].size == DummySize then
set DS.Data[i].c = null
set DS.Data[i].Ds = null
call DS.destroy(DS.Data[i])
set DS.Index = DS.Index - 1
set DS.Data[i] = DS.Data[DS.Index]
else
call DS.Data[i].grow()
set i = i + 1
endif
endloop
set i = 0
//looping through each Projectile to move it
loop
exitwhen i >= Pr.Index
if GetUnitDistance(Pr.Data[i].pr[0],Pr.Data[i].target) <= HitRange then
//Here is defined if aa Projectile hits its target
set db = (GetUnitState(Pr.Data[i].target,UNIT_STATE_MAX_LIFE)-GetWidgetLife(Pr.Data[i].target)) * CrueltyDamage
call UnitDamageTarget(Pr.Data[i].ca,Pr.Data[i].target,0.001,true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL , WEAPON_TYPE_WHOKNOWS)
call DamageTarget(Pr.Data[i].dmg+db,Pr.Data[i].target,Pr.Data[i].ma)
call DisplayDamage(db,Pr.Data[i].target)
call KillUnit(Pr.Data[i].pr[0])
call KillUnit(Pr.Data[i].pr[1])
call KillUnit(Pr.Data[i].pr[2])
set Pr.Data[i].pr[0] = null
set Pr.Data[i].pr[1] = null
set Pr.Data[i].pr[2] = null
set Pr.Data[i].ca = null
set Pr.Data[i].target = null
call Pr.destroy(Pr.Data[i])
set Pr.Index = Pr.Index - 1
set Pr.Data[i] = Pr.Data[Pr.Index]
else
call Pr.Data[i].move()
set i = i + 1
endif
endloop
if DS.Index > 0 or Pr.Index > 0 then
call TimerStart(t, TimerPeriod, false, function Callback)
else
set cb = false
endif
endfunction
////////////////////////////////////////Summon Projectile////////////////////////////////////////////
struct Pr //Projectile
static integer Index = 0
static Pr array Data
unit target //Missile dummy
unit ca //"Caster" , means its the attacking summon
unit ma //"Master" of the summon, the corrensponding hero
real maxd //Distance when creating the missile
real dmg //dmage stored
unit array pr [3] // the 3 Proectiles
static method create takes unit ca, unit ta, real damage returns Pr
local Pr data = Pr.allocate()
local real a = GetUnitsAngle(ca,ta)
local integer i = 0
local real x = GetUnitX(ca) + (Cos(a) * ProjectileStartY) + (Sin(a) * ProjectileStartX)
local real y = GetUnitY(ca) + (Cos(a) * ProjectileStartX) + (Sin(a) * ProjectileStartY)
local integer index = GetDSIndex(ca)
set Pr.Data[Pr.Index] = data
set data.ma = hero[index]
//Creating the 3 projectiles
loop
exitwhen i == 3
set data.pr[i] = CreateUnit(GetOwningPlayer(ca),AttackProjectile,x,y,a)
call SetUnitFlyHeight(data.pr[i],ProjectileStartZ,0)
set i = i + 1
endloop
set data.maxd = GetUnitDistance(ta,data.pr[1]) * 1.05 // lil bit bigger cuz else its amplitude will not be 0 at the end
set data.ca = ca
set data.target = ta
set data.dmg = damage
if cb == false then
call TimerStart(t, TimerPeriod,false, function Callback)
set cb = true
endif
set Pr.Index = Pr.Index + 1
return data
endmethod
method move takes nothing returns nothing
local integer i = 0
local real a = GetUnitsAngle(this.pr[1],this.target)
local real d = (ProjectileSpeed+GetUnitDistance(this.target,this.pr[1]))/this.maxd
local real offset = Sin(3.14159*d) * ProjectileSway * this.maxd
//Moving all 3 Missiles (refers to pr[1]´s x and y because its not affected by the amplitide
call SetUnitX(this.pr[1],GetUnitX(this.pr[1])+ProjectileSpeed*Cos(a))
call SetUnitY(this.pr[1],GetUnitY(this.pr[1])+ProjectileSpeed*Sin(a))
call SetUnitFlyHeight(this.pr[1],ProjectileStartZ + offset + (GetUnitFlyHeight(this.target) - ProjectileStartZ + HitHeight) * (1-d) ,0)
call SetUnitX(this.pr[0],GetUnitX(this.pr[1])+offset*Cos(a + (3.14159/2)) )
call SetUnitY(this.pr[0],GetUnitY(this.pr[1])+offset*Sin(a + (3.14159/2)) )
call SetUnitFlyHeight(this.pr[0],ProjectileStartZ + (GetUnitFlyHeight(this.target) - ProjectileStartZ + HitHeight) * (1-d) ,0)
call SetUnitX(this.pr[2],GetUnitX(this.pr[1])+offset*Cos(a - (3.14159/2)) )
call SetUnitY(this.pr[2],GetUnitY(this.pr[1])+offset*Sin(a - (3.14159/2)) )
call SetUnitFlyHeight(this.pr[2],ProjectileStartZ + (GetUnitFlyHeight(this.target) - ProjectileStartZ + HitHeight) * (1-d) ,0)
endmethod
endstruct
////////////////////////////////////////Damage System////////////////////////////////////////////////
function AdjustTarget takes nothing returns nothing
//If a summon gets aa new target its set here any the dummy is moved
local unit b = GetAttacker()
local integer index = GetDSIndex(b)
local unit a = GetTriggerUnit()
local real angle = GetUnitsAngle(b,ad[index])
if GetUnitTypeId(a) != AttackDummy and ad[index] != a then
set targ[index] = a
//Need to do this senseless crap here, else the Summons attack animation is not played properly (but i wonder why, it just gets the order to attack another unit)
// I <3 Blizzard logic
call SetUnitPosition(ad[index],GetUnitX(a),GetUnitY(a))
call IssueImmediateOrder(b, "stop" )
call PauseUnit(b,true)
call PauseUnit(b,false)
call IssueTargetOrder( b, "attack", ad[index])
endif
set b = null
set a = null
endfunction
function UnitDies takes nothing returns nothing
//If a unit dies which is "ttacked" by a summon it gets the stor order 8stopt attacking the neutral dummy
//Then it searched a new target
local unit u = GetTriggerUnit()
local integer i = GetTargIndex(u)
if i != -1 then
call IssueImmediateOrder(ds[i], "stop" )
endif
set u = null
endfunction
function InitDeath takes nothing returns nothing
local trigger d = CreateTrigger( )
local integer index = 0
// Creates a trigger to check if a target dies
loop
call TriggerRegisterPlayerUnitEvent(d, Player(index), EVENT_PLAYER_UNIT_DEATH , filter)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction( d, function UnitDies )
endfunction
function MoveDummy takes nothing returns nothing
local integer i = 0
//periodicly movinge the dummy to the real targets location
loop
exitwhen i > trin
call SetUnitX(ad[i],GetUnitX(targ[i]))
call SetUnitY(ad[i],GetUnitY(targ[i]))
set i = i + 1
endloop
if trin > 0 then
call TimerStart(t2, TimerPeriodDsys, false, function MoveDummy)
endif
endfunction
function StartMissile takes nothing returns nothing
//Event is when a dummy gets damage, then (due the attck of the sumon is instant) its damage is stored and projectiles are creted
local integer index = GetDummyIndex(GetTriggerUnit())
call DestroyEffect(AddSpecialEffectTarget(AttackEffect,GetEventDamageSource(),"hand, left"))
call DestroyEffect(AddSpecialEffectTarget(AttackEffect,GetEventDamageSource(),"hand, right"))
call Pr.create(GetEventDamageSource(),targ[index],GetEventDamage())
endfunction
function RightDamager takes nothing returns boolean
//Condition that a summon damaged thee dummy, could also happen with AOE spells
return GetUnitTypeId(GetEventDamageSource()) == Summon
endfunction
function InitGetDamage takes unit dummy returns nothing
//Initializing the Trigger when a Dummy takes damage
set tr2[trin] = CreateTrigger()
if trin == 0 then
call TimerStart(t2, TimerPeriodDsys, true, function MoveDummy)
endif
call TriggerRegisterUnitEvent( tr2[trin], dummy, EVENT_UNIT_DAMAGED )
call TriggerAddAction( tr2[trin], function StartMissile )
call TriggerAddCondition( tr2[trin], Condition( function RightDamager ) )
endfunction
function RightAttacker takes nothing returns boolean
//Condition that its a Summon which targets the dummy
return GetUnitTypeId(GetAttacker()) == Summon
endfunction
function InitDsys takes nothing returns nothing
local integer index = 0
local trigger tr = CreateTrigger( )
//Generally initializing the system with creating the trigger to get the point a dummy is attacked
loop
call TriggerRegisterPlayerUnitEvent(tr, Player(index), EVENT_PLAYER_UNIT_ATTACKED, filter)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction( tr, function AdjustTarget )
call TriggerAddCondition( tr, Condition( function RightAttacker ) )
endfunction
/////////////////////////////////////////////Damage Protection//////////////////////////////////
function ProtectDamage takes nothing returns nothing
local unit c = GetTriggerUnit()
local integer index = GetHeroIndex(c)
//Protects damage from the casting hero , event is that it takes damage,
call SetWidgetLife(c,GetWidgetLife(c) + GetEventDamage())
//Damages the summon instead
call UnitDamageTarget(GetEventDamageSource(),ds[index],GetEventDamage(),true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL , WEAPON_TYPE_WHOKNOWS)
set c = null
endfunction
function InitDamageProtect takes unit c, unit s returns nothing
//Initializing the damage protection for the hero
set tr[trin] = CreateTrigger()
set ds[trin] = s
set ad[trin] = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),AttackDummy,0,0,0)
set hero[trin] = c
call TriggerRegisterUnitEvent( tr[trin], c, EVENT_UNIT_DAMAGED )
call TriggerAddAction( tr[trin], function ProtectDamage )
call InitGetDamage(ad[trin])
set trin = trin + 1
endfunction
/////////////////////////////////////////////////////////////////////////////////////////////////
function DestroyTr takes nothing returns nothing
local integer index = GetDSIndex(GetTriggerUnit())
//Destroying all triggers and the dummy which are not needed anymore when a Summon dies
call RemoveUnit(ad[index])
set ds[index] = null
set hero[index] = null
set ad[index] = null
set targ[index] = null
call DestroyTrigger(tr[index])
set tr[index] = null
call DestroyTrigger(tr2[index])
set tr2[index] = null
set trin = trin - 1
set tr[index] = tr[trin]
set ds[index] = ds[trin]
set hero[index] = hero[trin]
set ad[index] = ad[trin]
set targ[index] = targ[trin]
endfunction
private function UnitDeath takes nothing returns boolean
//Condition that it should be a summon which dies to destroy its correspoding dummy and trigger
return GetUnitTypeId(GetTriggerUnit()) == Summon
endfunction
function DestroyTrInit takes nothing returns nothing
local integer index = 0
local trigger tr = CreateTrigger( )
//Initializes the destruction of the dummy and the trigger if a summons dies
loop
call TriggerRegisterPlayerUnitEvent(tr, Player(index), EVENT_PLAYER_UNIT_DEATH, filter)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction( tr, function DestroyTr )
call TriggerAddCondition( tr, Condition( function UnitDeath ) )
endfunction
struct DS //Dark Sould struct
static integer Index = 0
static DS array Data
unit c //Hero
unit Ds //Dark Soul
integer level //level of the spell
real size = DummySize //Current size
integer inc = 1 //Defines if the unit is groing or shrinking
unit ad //Attackdummy
static method create takes unit a, integer level returns DS
//Creates the Dark soul
local DS data = DS.allocate()
set data.c = a
set data.Ds = CreateUnit(GetOwningPlayer(a),Summon,GetUnitX(a),GetUnitY(a),GetUnitFacing(a))
call InitDamageProtect(a,data.Ds)
call SetUnitPathing(data.Ds,false)
call PauseUnit(data.Ds,true)
call SetUnitX(data.Ds,GetUnitX(a))
call SetUnitY(data.Ds,GetUnitY(a))
call SetUnitAnimation(data.Ds, StartAnimation)
call SetUnitVertexColor(data.Ds,255-DummyDarkness,255-DummyDarkness,255-DummyDarkness,DummyVertex)
set DS.Data[DS.Index] = data
if cb == false then
call TimerStart(t, TimerPeriod, true, function Callback)
set cb = true
endif
set data.Index = data.Index + 1
return data
endmethod
method grow takes nothing returns nothing
local real vertex = DummyVertex - (75*(this.size + (GrowSpeed * this.inc * 0.01) - DummySize) )
set this.size = this.size + (GrowSpeed * this.inc * 0.01)
if vertex <= 0.0 then
//Vertex reached 0, setting it to increase again
set this.inc = -1
call SetUnitPathing(this.Ds,true)
call SetUnitPosition(this.Ds,GetUnitX(this.c),GetUnitY(this.c))
call SetUnitAnimation(this.Ds, StartAnimation)
endif
if this.inc == -1 and this.size <= DummySize then
//Unit has its normal size again and animation is finished
set this.size = DummySize
set vertex = DummyVertex
call PauseUnit(this.Ds,false)
call UnitApplyTimedLife(this.Ds,0,Duration(this.level))
endif
call SetUnitScale(this.Ds,this.size,this.size,this.size)
call SetUnitVertexColor(this.Ds,255-DummyDarkness,255-DummyDarkness,255-DummyDarkness,R2I(vertex))
endmethod
endstruct
function Actions takes nothing returns nothing
local integer check = GetHeroIndex(GetTriggerUnit())
if check == -1 then
call DS.create(GetTriggerUnit(),GetUnitAbilityLevel(GetTriggerUnit(),Spell))
else
if KillDS == true then
call KillUnit(ds[check])
call DS.create(GetTriggerUnit(),GetUnitAbilityLevel(GetTriggerUnit(),Spell))
else
call IssueImmediateOrder(GetTriggerUnit(), "stop" )
endif
//A hero cannot have more than one Dark Soul a the same time
endif
endfunction
private function Init takes nothing returns nothing
local trigger tr = CreateTrigger()
local integer index = 0
//Preloading the Units
local unit preload = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),AttackProjectile,0,0,0)
local unit preload2 = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),AttackDummy,0,0,0)
call KillUnit(preload)
call KillUnit(preload2)
//Preloading Effects
call Preload(AttackEffect)
call Preload(AOEeffect)
set filter = Filter(function NullFilter)
//Initializing the 3 systems
call DestroyTrInit() //General thing if a Summon dies / removing dummy + destroying other triggers
call InitDeath() //General thing if a target dies
call InitDsys() //General Damage System with custom projectiles
loop
call TriggerRegisterPlayerUnitEvent(tr, Player(index), EVENT_PLAYER_UNIT_SPELL_CAST, filter)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition( tr, Condition( function RunCondition ) )
call TriggerAddAction( tr, function Actions )
set preload = null
endfunction
endscope