Ability Lua Tutorial 5: Ogre Magi's Fireblast - Elfansoer/dota-2-lua-abilities GitHub Wiki
Straight to the action.
Previously we've been dealt with passive abilities, which tends to do their action on their modifiers. Now, this unit-targeted ability will have actions that is defined in the main lua instead; modifier will be a simple tag without action.
The base .txt filled with things that have been covered in the first tutorial. Fireblast is a unit-targeted ability which target enemies that may be heroes or creeps, and deals magical damage that may be dispelled by strong dispel but cannot pierce spell immunity.
A question may be raised as to why the damage section is empty even though Fireblast do damages its target. The thing is, it does not have considerable impact as long as your code follows this form.
If the form has AbilityDamage
value, the code may call Ability:GetAbilityDamage()
to retrieve it. If the form uses AbilitySpecial
instead, then the code should use ability:GetSpecialValueFor("damage_key")
to obtain damage value(s). It's a matter of choice.
Looking back at Tutorial 3, there was a point where a question is raised,
"What does this ability do?"
Back then, Spell Shield simply spawns a modifier and that's it. It's job is over. In contrast, Fireblast is an active ability; therefore it needs some action.
Looking at reference, there are several function which defines different phases of an active ability:
CastFilterResult()
OnAbilityPhaseStart()
OnAbilityPhaseInterrupted()
OnSpellStart()
OnChannelThink()
OnChannelFinish( bInterrupted )
The function itself is pretty self-explanatory; further explanations available in the reference.
In general, CastFilter
is called when a player tries to cast it. PhaseStart
defines the ability has just being cast with target has been selected (if any). PhaseInterrupted
governs what happens if ability is stopped before its cast point (imagine when Shadow Fiend stopped his dance). SpellStart
will do its actions right after ability finishes its cast point, mana is spent, and ability goes into cooldown. ChannelThink
is called continuously during channeling until the channel is stopped by any reason, which then ChannelFinish
will be called. If it is stopped by stun or other interruptions, bInterrupted
is true
, false
otherwise.
When Fireblast is fully cast, it deals damage to target and stunning them. All of these action happened after the spell started. Hence, we'll use this one:
-- ogre_magi_fireblast_lua.lua
ogre_magi_fireblast_lua = class({})
LinkLuaModifier( "modifier_ogre_magi_lua" , "lua_abilities/ogre_magi_lua/modifier_ogre_magi_lua", LUA_MODIFIER_MOTION_NONE )
function ogre_magi_fireblast_lua:OnSpellStart()
end
Find all information required. Retrieve damage and stun values, but don't forget the target. When you want to do something on a target, you must know the target. A unit reference for this ability's target can be retrieved by calling self:GetCursorTarget()
. Don't forget to store them.
-- ogre_magi_fireblast_lua.lua
ogre_magi_fireblast_lua = class({})
LinkLuaModifier( "modifier_ogre_magi_lua" , "lua_abilities/ogre_magi_lua/modifier_ogre_magi_lua", LUA_MODIFIER_MOTION_NONE )
function ogre_magi_fireblast_lua:OnSpellStart()
-- get references
local target = self:GetCursorTarget()
local damage = self:GetSpecialValueFor("fireblast_damage")
local duration = self:GetSpecialValueFor("stun_duration")
end
I don't know if the original Fireblast applies its stun first or deal its damage first. I arbitrarily pick damage first. A damage instance can be dealt by calling this piece of code:
local damageTable = {
victim = <unit>,
attacker = <unit>,
damage = <float/integer>,
damage_type = <look-at-reference>,
damage_flags = <look-at-reference>, -- optional
ability = <ability> -- optional
}
ApplyDamage( damageTable )
The main function is ApplyDamage( table )
, though it requires a table as its parameter; therefore a temporary table damageTable
is created before calling it.
See reference for damage type and flags values. Ability parameter is optional; when a modifier listen to OnTakeDamage
, this parameter will be shown in its params
.
Our code now looks like:
-- ogre_magi_fireblast_lua.lua
ogre_magi_fireblast_lua = class({})
LinkLuaModifier( "modifier_ogre_magi_lua" , "lua_abilities/ogre_magi_lua/modifier_ogre_magi_lua", LUA_MODIFIER_MOTION_NONE )
function ogre_magi_fireblast_lua:OnSpellStart()
-- get references
local target = self:GetCursorTarget()
local damage = self:GetSpecialValueFor("fireblast_damage")
local duration = self:GetSpecialValueFor("stun_duration")
local damageTable = {
victim = target,
attacker = self:GetCaster(),
damage = damage,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self
}
end
I don't have to elaborate on self:GetCaster()
, right?
This is a bridge between abilities and modifiers; abilities creates modifiers to do some of its work for a certain unit.
A modifier normally sticks to a unit, so this function should be invoked on unit references:
target_unit:AddNewModifier(
source_unit,
source_ability,
"modifier_name",
kv_table
)
A little explanation here:
-
source_unit
: A unit who is responsible in giving this modifier to target unit. Generallyself:GetCaster()
-
source_ability
: Same as above, ability version. Usuallyself
. -
"modifier_name"
: a string containing the modifier's technical name. -
kv_table
: A table which will be passed as parameter to modifier'sOnCreated(kv)
. Cannot contain tables as values. Setduration
key into an integer/float value to determine modifier's duration.
Therefore, our ability file now should be:
-- ogre_magi_fireblast_lua.lua
ogre_magi_fireblast_lua = class({})
LinkLuaModifier( "modifier_ogre_magi_lua" , "lua_abilities/ogre_magi_lua/modifier_ogre_magi_lua", LUA_MODIFIER_MOTION_NONE )
function ogre_magi_fireblast_lua:OnSpellStart()
-- get references
local target = self:GetCursorTarget()
local stun_damage = self:GetSpecialValueFor("fireblast_damage")
local stun_duration = self:GetSpecialValueFor("stun_duration")
-- Apply damage
local damageTable = {
victim = target,
attacker = self:GetCaster(),
damage = stun_damage,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self
}
ApplyDamage( damageTable )
-- Apply Stun Modifier
target:AddNewModifier(
self:GetCaster(),
self,
"modifier_ogre_magi_fireblast_lua",
{duration = stun_duration}
)
end
Onto the modifier. There are 4 main functions on a modifier. We have discussed on Modify Property and Event Listener in previous tutorial. This is the third main function of a modifier: apply a state on its parent.
All possible state can be seen here, and modifying a parent's state has different invocation than previous two:
-- modifier_ogre_magi_fireblast_lua.lua
function modifier_ogre_magi_fireblast_lua:CheckState()
local state = {
[MODIFIER_STATE_STUNNED] = true,
[MODIFIER_STATE_SILENCED] = true, -- just for example
}
return state
end
Think of this function as a switch: The parent will be stunned when its state is changed to true. As long as this modifier stays on that unit, it will be stunned.
Well, that's it. This modifier is a passive one, and no further action is needed. Here's the complete code for the modifier:
-- modifier_ogre_magi_fireblast_lua.lua
modifier_ogre_magi_fireblast = class({})
function modifier_ogre_magi_fireblast_lua:IsDebuff()
return true
end
function modifier_ogre_magi_fireblast_lua:IsStunDebuff()
return true
end
--------------------------------
function modifier_ogre_magi_fireblast_lua:CheckState()
local state = {
[MODIFIER_STATE_STUNNED] = true,
}
return state
end
Oh, if you wonder about IsStunDebuff()
, it determines whether it should be dispelled by basic or strong dispel.
I hope this gives insights on creating a simple unit-targeted ability. Next time, let's add randomness using Chaos Knight's Chaos Bolt.