Skip to content

Ability Lua Tutorial 7: Slardar's Slithereen Crush

Elfansoer edited this page Aug 1, 2018 · 1 revision

Search around.

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_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 LinkLuaModifiers. 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:

  1. 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?
  2. Vector. Identifies the center point to find units.
  3. Table. Just leave this as nil.
  4. Integer. The name says it all. Though, you can use FIND_UNITS_EVERYWHERE to FIND UNITS EVERYWHERE.
  5. Integer-enum. Pick one from here.
  6. Integer-enum. Pretty much like AbilityUnitTargetType from Ability form. Pick from here, and for combinations, use + instead of |.
  7. Integer-enum. Pretty much like AbilityUnitTargetFlags from Ability form. Pick from here, and for combinations, use + instead of |.
  8. Integer-enum. Determines the order of units in the table. Pick from here.
  9. 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

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 _,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--)

Modifer Property: Movespeed Bonus and Attack Bonus

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

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.