Ability Lua Tutorial 8: Slark's Dark Pact - Elfansoer/dota-2-lua-abilities GitHub Wiki
Waiting in interval.
I think we'll go straight to the action. And by action I mean to the modifier file, since there's nothing happened on the main Lua than creating a modifier for the caster. For clarity, here's the ability file.
-- slark_dark_pact_lua.lua
slark_dark_pact_lua = class({})
LinkLuaModifier( "modifier_slark_dark_pact_lua", "lua_abilities/slark_dark_pact_lua/modifier_slark_dark_pact_lua", LUA_MODIFIER_MOTION_NONE )
--------------------------------------------------------------------------------
-- Ability Start
function slark_dark_pact_lua:OnSpellStart()
-- Add timer
self:GetCaster():AddNewModifier(
self:GetCaster(),
self,
"modifier_slark_dark_pact_lua",
{}
)
end
Well, on this case, the modifier's duration isn't specified by the creator (the ability). The modifier we create will self-destruct, once the timer expires.
We start off by declaring the modifier's properties:
-- modifier_slark_dark_pact_lua.lua
modifier_slark_dark_pact_lua = class({})
--------------------------------------------------------------------------------
-- Classifications
function modifier_slark_dark_pact_lua:IsHidden()
return true
end
function modifier_slark_dark_pact_lua:GetAttributes()
return MODIFIER_ATTRIBUTE_MULTIPLE
end
function modifier_slark_dark_pact_lua:IsPurgable()
return false
end
The modifier is hidden in gui, since it is not a buff, gameplay-wise. IsPurgable
is self-explanatory.
GetAttributes
declare more specific attribute of the modifier. You can see here for references, but here's some explanation of modifiers I often use:
-
MODIFIER_ATTRIBUTE_PERMANENT
: Basically states that the modifier won't get removed by in-game effects. Removed only whenDestroy()
is called. -
MODIFIER_ATTRIBUTE_MULTIPLE
: By default,AddNewModifier
creates new modifier to a unit if it doesn't have it, and refreshes modifier if it already exist. This attribute declares thatAddNewModifier
always creates new modifier, allowing multiple instances over a unit.
Let's initialize the modifier by collecting information:
-- modifier_slark_dark_pact_lua.lua
--------------------------------------------------------------------------------
-- Initializations
function modifier_slark_dark_pact_lua:OnCreated( kv )
-- references
self.delay = self:GetAbility():GetSpecialValueFor( "delay" )
self.pulse_duration = self:GetAbility():GetSpecialValueFor( "pulse_duration" )
self.radius = self:GetAbility():GetSpecialValueFor( "radius" )
self.total_damage = self:GetAbility():GetSpecialValueFor( "total_damage" )
self.total_pulses = self:GetAbility():GetSpecialValueFor( "total_pulses" )
self.pulse_interval = self:GetAbility():GetSpecialValueFor( "pulse_interval" )
end
function modifier_slark_dark_pact_lua:OnDestroy( kv )
end
Now, the great question came,
"What does this ability do?"
When cast, Dark Pact purges and damages nearby units, after a delay. We can create delays by using modifier:StartIntervalThink( float )
function. When it is called, the modifier will periodically calls OnIntervalThink()
for the specified interval until it is stopped or the modifier is destroyed.
Since we're having a delay right from the start, we'll do this:
-- modifier_slark_dark_pact_lua.lua
--------------------------------------------------------------------------------
-- Initializations
function modifier_slark_dark_pact_lua:OnCreated( kv )
-- references
self.delay = self:GetAbility():GetSpecialValueFor( "delay" )
--/--/--
if IsServer() then
-- Start interval
self:StartIntervalThink( self.delay )
end
end
--/--/--
function modifier_slark_dark_pact_lua:OnIntervalThink()
-- delayed logic here
end
StartIntervalThink
is called within IsServer
block, since this is about logic which will be computed only on Server, and Client will only follow it. StartIntervalThink
will only be called on server, so OnIntervalThink
method doesn't have to call IsServer
again.
Now, what happened after the delay? It purges and deals damage. But there's a twist that the purge and damage are happened continuously! We'll have intervals again then.
But wait. At the moment, the current engine can only have 1 interval instances. That is, when you set another interval, the previous one will got replaced.
For example, if I were to do this:
function modifier_slark_dark_pact_lua:OnCreated( kv )
if IsServer() then
-- Start interval
self:StartIntervalThink( 1 )
end
end
--/--/--
function modifier_slark_dark_pact_lua:OnIntervalThink()
self:StartIntervalThink( 0.1 )
end
OnIntervalThink
will be called after 1 second, and then every 0.1 seconds until stopped or destroyed.
You know what it leads to?
The implication of the twist is that any delayed or interval effects and logic must be within one single OnIntervalThink
method. Therefore, the method should be able to tell on which interval it is invoked upon, to be able to give appropriate logic.
Confused? Well, look at this:
function modifier_slark_dark_pact_lua:OnCreated( kv )
-- additional variaable
self.cast = false
if IsServer() then
-- Start interval
self:StartIntervalThink( 1 )
end
end
--/--/--
function modifier_slark_dark_pact_lua:OnIntervalThink()
if self.cast then
-- first logic here
self:StartIntervalThink( 0.1 )
self.cast = true
else
-- second logic here
end
end
This way, first logic
will get invoked after 1 second delay, because the variable self.cast
is false
.
At the same time, the variable is set to true
and a new interval is set every 0.1 second.
After 0.1 seconds (and goes on periodically), second logic
will be invoked due to self.cast
is true
.
At each tick after the delay, Dark Pact purges the caster and deals damage around the caster, including the caster themselves.
To make life easier, let's do the tick logic in separate function, named Splash()
-- modifier_slark_dark_pact_lua.lua
function modifier_slark_dark_pact_lua:Splash()
-- tick logic here
end
First, we have to purge. Coincidentally, a function named Purge
exist, so we'll have that:
-- modifier_slark_dark_pact_lua.lua
function modifier_slark_dark_pact_lua:Splash()
-- tick logic here
self:GetParent():Purge(false, true, false, true, false)
end
Explanation time:
Purge method is owned by unit, so the one above means that this modifier's (self
) parent unit (GetParent()
) will be purged, with parameters (all boolean
):
-
RemovePositiveBuffs
: Remove buffs if true -
RemoveDebuffs
: Remove debuffs if true -
BuffsCreatedThisFrameOnly
: Remove debuffs that happened at this exact time only. Probably, I don't know. I always set it to false anyway. -
RemoveStuns
: Can remove stuns if true -
RemoveExceptions
: Considered as strong dispel if true
Things about searching for enemies and deals damage have been covered, so I'll show the full version instead:
function modifier_slark_dark_pact_lua:Splash()
-- purge
self:GetParent():Purge(false, true, false, true, false)
-- get damage per interval
local damage_interval = self.total_damage/self.total_pulses
-- find units in radius
local enemies = FindUnitsInRadius(
self:GetParent():GetTeamNumber(), -- int, your team number
self:GetParent():GetOrigin(), -- point, center point
nil, -- handle, cacheUnit. (not known)
self.radius, -- float, radius. or use FIND_UNITS_EVERYWHERE
DOTA_UNIT_TARGET_TEAM_ENEMY, -- int, team filter
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, -- int, type filter
0, -- int, flag filter
0, -- int, order filter
false -- bool, can grow cache
)
-- do for each enemies caught
for _,enemy in pairs(enemies) do
-- Apply damage
local damageTable = {
victim = enemy,
attacker = self:GetParent(),
damage = damage_interval,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self:GetAbility()
}
ApplyDamage(damageTable)
end
-- Apply damage to self
local damageTable = {
victim = self:GetParent(),
attacker = self:GetParent(),
damage = damage_interval/2,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self:GetAbility()
}
ApplyDamage(damageTable)
end
Now, time to put the Splash()
appropriately. The first tick happened right after the delay, and then happened at every self.pulse_interval
seconds. Therefore, we have:
-- modifier_slark_dark_pact_lua.lua
--------------------------------------------------------------------------------
-- Interval Effects
function modifier_slark_dark_pact_lua:OnIntervalThink()
if not self.cast then
self:Splash()
self.cast = true
self:StartIntervalThink( self.pulse_interval )
else
self:Splash()
end
end
Something's missing, no? How is it going to stop???
Well, we know that there will be self.total_pulses
ticks, so after the last tick, the modifier self-destruct. First, we'll have to track the number of ticks happened, in self.current_pulse
:
--------------------------------------------------------------------------------
-- Initializations
function modifier_slark_dark_pact_lua:OnCreated( kv )
-- references
self.delay = self:GetAbility():GetSpecialValueFor( "delay" )
self.total_pulses = self:GetAbility():GetSpecialValueFor( "total_pulses" )
self.pulse_interval = self:GetAbility():GetSpecialValueFor( "pulse_interval" )
--/--/--
-- Additional variables
self.cast = false
self.current_pulse = 0
if IsServer() then
-- Start interval
self:StartIntervalThink( self.delay )
end
end
Then, we'll increment the numbers each time splash happened. If the number of ticks already equal to total pulses, the modifier self-destructs:
--------------------------------------------------------------------------------
-- Interval Effects
function modifier_slark_dark_pact_lua:OnIntervalThink()
if not self.cast then
self:Splash()
-- add counter
self.current_pulse = self.current_pulse + 1
self.cast = true
self:StartIntervalThink( self.pulse_interval )
else
self:Splash()
-- add counter
self.current_pulse = self.current_pulse + 1
-- self destruct
if self.current_pulse >=10 then
self:StartIntervalThink( -1 )
self:Destroy()
end
end
end
Okay, a lot happened, it's time to stop. Next time, we'll having a Linear Projectiles and How To Use Them, in Mirana's Sacred Arrow. Stay Tuned.