Ability Lua Tutorial 7: Slardar's Slithereen Crush - Elfansoer/dota-2-lua-abilities GitHub Wiki
Search around.
I think I should go straight to the action, since it would become repetitive if I explain the ability form again, which barely have no new content. So let's start with the main file:
-- slardar_slithereen_crush_lua.lua
slardar_slithereen_crush_lua = class({})
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua", LUA_MODIFIER_MOTION_NONE )
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua_slow", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua_slow", LUA_MODIFIER_MOTION_NONE )
function slardar_slithereen_crush_lua:OnSpellStart()
-- get references
local radius = self:GetSpecialValueFor("crush_radius")
local damage = self:GetAbilityDamage()
local stun_duration = self:GetSpecialValueFor("stun_duration")
local slow_duration = self:GetSpecialValueFor("crush_extra_slow_duration")
end
There are 2 LinkLuaModifier
s. One is for the stun, and one is for the slow. To make life easier, let's just split the ability debuff effects into those two modifier.
Slithereen Crush would stun and slow enemies around Slardar. Dota 2 engine provides an API called FindUnitsInRadius
, which do what it says. Here's the snippet:
local units = FindUnitsInRadius(
<team-number>,
<center-point>,
nil,
<radius>,
<team-filter>,
<type-filter>,
<flag-filter>,
<order-filter>,
false
)
This function returns a table of units which fits the specification provided, but may be nil
if not found. Note that this snippet is a bit different than ApplyDamage
or CreateTrackingProjectile
; they take table as 1 parameter, while FindUnitsInRadius
takes a lot of parameters, but no table. Which means, order matters.
Here's the explanation, based on parameter order:
-
Integer-enum. Team number will affect the filter parameters below. Like, team-filter may use
DOTA_UNIT_TARGET_TEAM_ENEMY
, but whose enemy? Radiant's enemy, Enemy's enemy or our enemy? Or creep's enemy? Or enemy of the enemy's enemy? - Vector. Identifies the center point to find units.
-
Table. Just leave this as
nil
. -
Integer. The name says it all. Though, you can use
FIND_UNITS_EVERYWHERE
to FIND UNITS EVERYWHERE. - Integer-enum. Pick one from here.
-
Integer-enum. Pretty much like
AbilityUnitTargetType
from Ability form. Pick from here, and for combinations, use+
instead of|
. -
Integer-enum. Pretty much like
AbilityUnitTargetFlags
from Ability form. Pick from here, and for combinations, use+
instead of|
. - Integer-enum. Determines the order of units in the table. Pick from here.
-
Bool. Just leave this as
false
.
In this case, we just need to find enemies of the caster's team within a radius from caster's location (origin) which may be heroes or creeps. No special flags, order doesn't matter.
-- slardar_slithereen_crush_lua.lua
slardar_slithereen_crush_lua = class({})
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua", LUA_MODIFIER_MOTION_NONE )
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua_slow", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua_slow", LUA_MODIFIER_MOTION_NONE )
function slardar_slithereen_crush_lua:OnSpellStart()
-- get references
local radius = self:GetSpecialValueFor("crush_radius")
local damage = self:GetAbilityDamage()
local stun_duration = self:GetSpecialValueFor("stun_duration")
local slow_duration = self:GetSpecialValueFor("crush_extra_slow_duration")
-- find affected units
local enemies = FindUnitsInRadius(
self:GetCaster():GetTeamNumber(),
self:GetCaster():GetOrigin(),
nil,
radius,
DOTA_UNIT_TARGET_TEAM_ENEMY,
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_ANY_ORDER,
false
)
end
Any programmer should familiar with this loop. Since we have collected all caught enemies within variable enemies
, we'll iterate it through and apply effects for each of them.
Usually, I use this:
for _,enemy in pairs(enemies) do
-- your code goes here
end
There are 3 things to do for each enemy: apply the damage, stun, and slow enemies. We'll do just that:
-- slardar_slithereen_crush_lua.lua
slardar_slithereen_crush_lua = class({})
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua", LUA_MODIFIER_MOTION_NONE )
LinkLuaModifier( "modifier_slardar_slithereen_crush_lua_slow", "lua_abilities/slardar_slithereen_crush_lua/modifier_slardar_slithereen_crush_lua_slow", LUA_MODIFIER_MOTION_NONE )
function slardar_slithereen_crush_lua:OnSpellStart()
-- get references
local radius = self:GetSpecialValueFor("crush_radius")
local damage = self:GetAbilityDamage()
local stun_duration = self:GetSpecialValueFor("stun_duration")
local slow_duration = self:GetSpecialValueFor("crush_extra_slow_duration")
-- find affected units
local enemies = FindUnitsInRadius(
self:GetCaster():GetTeamNumber(),
self:GetCaster():GetOrigin(),
nil,
radius,
DOTA_UNIT_TARGET_TEAM_ENEMY,
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_ANY_ORDER,
false
)
-- for each caught enemies
for _,enemy in pairs(enemies) do
-- Apply Damage
local damage = {
victim = enemy,
attacker = self:GetCaster(),
damage = damage,
damage_type = DAMAGE_TYPE_PHYSICAL,
}
ApplyDamage( damage )
-- Apply stun debuff
enemy:AddNewModifier(
self:GetCaster(),
self,
"modifier_slardar_slithereen_crush_lua",
{ duration = stun_duration }
)
-- Apply slow debuff
enemy:AddNewModifier(
self:GetCaster(),
self,
"modifier_slardar_slithereen_crush_lua_slow",
{ duration = stun_duration + slow_duration }
)
end
end
ApplyDamage
and AddNewModifier
have been discussed before. The stun modifier is just a copy-paste of the previous stun modifier. What's new is the slow modifier; what's with the duration?
Since both slow and stun are applied at the same time but the slow duration starts after the stun, I simply add those numbers (You can't be slowed if you're stunned --insert roll-safe meme--)
Let's implement the slow modifier, starts like this:
-- modifier_slardar_slithereen_crush_lua.lua
modifier_slardar_slithereen_crush_lua_slow = class({})
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:IsDebuff()
return true
end
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:OnCreated( kv )
self.ms_slow = self:GetAbility():GetSpecialValueFor("crush_extra_slow")
self.as_slow = self:GetAbility():GetSpecialValueFor("crush_attack_slow_tooltip")
end
function modifier_slardar_slithereen_crush_lua_slow:OnRefresh( kv )
self.ms_slow = self:GetAbility():GetSpecialValueFor("crush_extra_slow")
self.as_slow = self:GetAbility():GetSpecialValueFor("crush_attack_slow_tooltip")
end
I know, the special value names are a bit weird. This lua only follows what's written on the .txt file, so if you want to change them, change them there.
The Crush slows both attack speed and movespeed. The ms slow is a percentage value, while as slow is a constant value. Let's declare this modifier's function:
-- modifier_slardar_slithereen_crush_lua.lua
function modifier_slardar_slithereen_crush_lua_slow:DeclareFunctions()
local funcs = {
MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE,
MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT,
}
return funcs
end
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:GetModifierMoveSpeedBonus_Percentage( params )
end
function modifier_slardar_slithereen_crush_lua_slow:GetModifierAttackSpeedBonus_Constant( params )
end
Wait, why bonus when we want to reduce? As per reference, they can have negative number as return value, so it's fine (no other function for reducing values anyway).
Return the function values using their respective variables, and we're done.
-- modifier_slardar_slithereen_crush_lua.lua
modifier_slardar_slithereen_crush_lua_slow = class({})
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:IsDebuff()
return true
end
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:OnCreated( kv )
self.ms_slow = self:GetAbility():GetSpecialValueFor("crush_extra_slow")
self.as_slow = self:GetAbility():GetSpecialValueFor("crush_attack_slow_tooltip")
end
function modifier_slardar_slithereen_crush_lua_slow:OnRefresh( kv )
self.ms_slow = self:GetAbility():GetSpecialValueFor("crush_extra_slow")
self.as_slow = self:GetAbility():GetSpecialValueFor("crush_attack_slow_tooltip")
end
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:DeclareFunctions()
local funcs = {
MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE,
MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT,
}
return funcs
end
--------------------------------------------------------------------------------
function modifier_slardar_slithereen_crush_lua_slow:GetModifierMoveSpeedBonus_Percentage( params )
return self.ms_slow
end
function modifier_slardar_slithereen_crush_lua_slow:GetModifierAttackSpeedBonus_Constant( params )
return self.as_slow
end
I hope this may give you insight about how to do an AOE abilities. Next time, we'll discuss Slark's Dark Pact, about intervals.