April 15, 2018
Ability Lua Tutorial 7: Slardar's Slithereen Crush
Views: 1646
Elfansoer
Slardar: Slithereen Crush
Search around
Preface
It's been a while, eh? Don't worry, I'm still pretty much alive (at least physically, but inside...).
So, I've been tweaking around github and decided to split between real version of Dota 2 abilities and tutorial version. Meaning, there are 2 implementations for each ability created in different github branch.
Why the split? I thought that for beginners, it would be better if they just understand basic functions explained in this tutorial, and the ability scripts should only show what's essential for them. Meanwhile, the real version of the ability tends to have a lot of fuss and fancy APIs which may confuse the learners.
I've moved tutorial version of the abilities from "master" branch to "tutorial" branch. Previous links have been updated to the new links, so make sure you get the correct version.
In short, to download ability scripts, go to branch
tutorial in the github to get the 'easy' version, and go to branch
master to get the real version.
Logic: FindUnitsInRadius
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
Code:
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. 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:
FindUnitsInRadius
Code:
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:
Spoiler: Click to view
- 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" (without quote) 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.
- 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
Code:
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
Logic: For loop
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 loop
Code:
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
Code:
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*)
Modifer Property: Movespeed Bonus and Attack Bonus
Let's implement the slow modifier, starts like this:
Slardar: Modifier Slithereen Crush Slow
Code:
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:
Slardar: Modifier Slithereen Crush Slow
Code:
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 )
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.
Slardar: Modifier Slithereen Crush Slow
Code:
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
Conclusion
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.
Sorry for the long void-period, I just too busy building the real version (not the tutorial version). Hope we meet again next time!
References
Quick Comment
You need to log in before commenting.
[-] Collapse All Comments