February 05, 2018
Ability Lua Tutorial 6: Chaos Knight's Chaos Bolt
Views: 2023
Elfansoer
Chaos Knight's Chaos Bolt
Playing with projectile
EDIT: Some links have been updated due to github repository change.
Ability Form: Precache
I've been avoiding aesthetics up until now, but finally it's unavoidable anymore. If you look at the
base .txt file, everything seems normal excluding that "precache" key-value.
Okay, here's the thing about precache: the engine is just pure lazy. It won't load any resources (such as particles and sounds) unless it is explicitly required. This 'precache' is a way to ensure the particle is required 'explicitly'. Spawning a hero is another one, but even the engine won't automatically load his/her voice until they're triggered to speak.
Particle not loaded means it won't show up. Got it?
Knowing a particle's path would require a
Workshop Tools or a
GCFScape, so if you don't want to be bothered by those, simply use the path I set there. The most essential particle is the "chaos_knight_chaos_bolt.vpcf" one; the rest is optional.
Logic: Tracking Projectile
That fuss about precache things is none other than creating a tracking projectile. After the spell is cast, Chaos Bolt releases a "bolt" which follows the target. The target may disjoint it, though most of the time it will hit. To spawn a tracking projectile, use this snippet:
Tracking Projectile
Code:
local info = {
Target = <unit>,
Source = <unit>,
Ability = <ability>,
EffectName = <string>,
iMoveSpeed = <integer>,
vSourceLoc= <position>, -- Optional
bDrawsOnMinimap = <bool>, -- Optional
bDodgeable = <bool>, -- Optional
bIsAttack = <bool>, -- Optional
bVisibleToEnemies = <bool>, -- Optional
bReplaceExisting = <bool>, -- Optional
flExpireTime = <float>, -- Optional but recommended
bProvidesVision = <bool>, -- Optional
iVisionRadius = <integer>, -- Optional
iVisionTeamNumber = <integer> -- Optional
}
projectile = ProjectileManager:CreateTrackingProjectile(info)
Like ApplyDamage(), the real function is at the bottom.
ProjectileManager manages all projectiles (duh!), and it has a function named CreateTrackingProjectile() which takes a table as parameter. Here's the explanation, and default value is on bracket:
parameters
- Target: Unit who is the target
- Source: Unit who throws
- Ability: Ability which invokes
- EffectName: A path to the particle file
- iMoveSpeed: Projectile speed
- vSourceLoc: Position it is spawned at (source's position)
- bDrawsOnMinimap: Creates a dot in minimap if true (false)
- bDodgeable: Can be disjointed (true)
- bIsAttack: Is considered an attack projectile (false)
- bVisibleToEnemies: Enemy can see if true (false)
- bReplaceExisting: Destroys similar projectile if true (true)
- flExpireTime: Projectile will expire after given time (0, no expire)
- bProvidesVision: Projectile gives vision if true (false)
- iVisionRadius: Vision radius (0)
- iVisionTeamNumber: an enum stating a team number (0)
That's a long list, but you won't use them all most of the time. In fact, Chaos Bolt only uses these:
Chaos Knight Chaos Bolt Lua
Code:
chaos_knight_chaos_bolt_lua = class({})
LinkLuaModifier(
"modifier_chaos_knight_chaos_bolt_lua",
"lua_abilities/chaos_knight_chaos_bolt_lua/modifier_chaos_knight_chaos_bolt_lua",
LUA_MODIFIER_MOTION_NONE )
function chaos_knight_chaos_bolt_lua:OnSpellStart()
-- get references
local target = self:GetCursorTarget()
local bolt_lua_speed = self:GetSpecialValueFor("chaos_bolt_speed")
local projectile = "particles/units/heroes/hero_chaos_knight/chaos_knight_chaos_bolt.vpcf"
-- Create Tracking Projectile
local info = {
Source = self:GetCaster(),
Target = target,
Ability = self,
iMoveSpeed = bolt_lua_speed,
EffectName = projectile,
bDodgeable = true,
}
ProjectileManager:CreateTrackingProjectile( info )
end
Ability: OnProjectileHit
Okay, a projectile is thrown, what next? When the projectile connects, Chaos Bolt will stun and damages the target, so we'll do that. The engine will call OnProjectileHit() if the ability throws a projectile, and it connects. If dodged? Well, just forget it, you failed.
Chaos Knight Chaos Bolt Lua
Code:
function chaos_knight_chaos_bolt_lua:OnProjectileHit( hTarget, vLocation )
end
This function will give you 2 contexts:
hTarget is the unit which got struck by the projectile, and
vLocation is the position where that unit is struck. How you use them is up to you.
Logic: Randomness
Chaos Knight, my favourite hero, is notorious for his randomness, and Chaos Bolt is one of this manifestation. Chaos Bolt will deal a random damage and stun for random duration. The note says that the damage and stun's randomness is inversely related. It means that if the bolt deals max damage, then the stun would be minimal. Let's get into math.
Math.random()
First, we'll throw a random number. A "math.random()" function would be sufficient to produce random number between 0 and 1, say it's
x. We'll use this
x for the damage.
As for the stun, we'll use
1-x instead, since it is inversely related (let's call it
y).
Chaos Knight Chaos Bolt Lua
Code:
function chaos_knight_chaos_bolt_lua:OnProjectileHit( hTarget, vLocation )
-- get references
local damage_min = self:GetSpecialValueFor("damage_min")
local damage_max = self:GetSpecialValueFor("damage_max")
local stun_min = self:GetSpecialValueFor("stun_min")
local stun_max = self:GetSpecialValueFor("stun_max")
-- throw a random number
local x = math.random()
local y = 1-x
end
Now we get
x and
y, we'll Expand it.
Expand()
It takes some algebra to 'alter' a random number between 0 and 1 into a random number between
min_value and
max_value. Believe or not, it is like this:
Chaos Knight Chaos Bolt Lua
Code:
function chaos_knight_chaos_bolt_lua:Expand( value, min, max )
return (max-min)*value + min
end
This helper function will help interpolating (incorrect term, but fair enough) number so that it produces value between
min and
max according to
value. Zero value returns minimum, one returns maximum, 0.5 returns value between them, and so on.
Chaos Knight Chaos Bolt Lua
Code:
function chaos_knight_chaos_bolt_lua:OnProjectileHit( hTarget, vLocation )
-- get references
local damage_min = self:GetSpecialValueFor("damage_min")
local damage_max = self:GetSpecialValueFor("damage_max")
local stun_min = self:GetSpecialValueFor("stun_min")
local stun_max = self:GetSpecialValueFor("stun_max")
-- throw a random number
local x = math.random()
local y = 1-x
-- calculate damage and stun values
local damage_act = self:Expand(x,damage_min,damage_max)
local stun_act = self:Expand(y,stun_min,stun_max)
end
function chaos_knight_chaos_bolt_lua:Expand( value, min, max )
return (max-min)*value + min
end
Finalize
We have determine the values, now apply them. Previous tutorial had described how to, so I'll give you the finalized version instead:
Chaos Knight Chaos Bolt Lua
Code:
chaos_knight_chaos_bolt_lua = class({})
LinkLuaModifier(
"modifier_chaos_knight_chaos_bolt_lua",
"lua_abilities/chaos_knight_chaos_bolt_lua/modifier_chaos_knight_chaos_bolt_lua",
LUA_MODIFIER_MOTION_NONE )
function chaos_knight_chaos_bolt_lua:OnSpellStart()
-- get references
local target = self:GetCursorTarget()
local bolt_lua_speed = self:GetSpecialValueFor("chaos_bolt_speed")
local projectile = "particles/units/heroes/hero_chaos_knight/chaos_knight_chaos_bolt.vpcf"
-- Create Tracking Projectile
local info = {
Source = self:GetCaster(),
Target = target,
Ability = self,
iMoveSpeed = bolt_lua_speed,
EffectName = projectile,
bDodgeable = true,
}
ProjectileManager:CreateTrackingProjectile( info )
self:PlayEffect1()
end
function chaos_knight_chaos_bolt_lua:OnProjectileHit( hTarget, vLocation )
-- get references
local damage_min = self:GetSpecialValueFor("damage_min")
local damage_max = self:GetSpecialValueFor("damage_max")
local stun_min = self:GetSpecialValueFor("stun_min")
local stun_max = self:GetSpecialValueFor("stun_max")
-- throw a random number
local x = math.random()
local y = 1-x
-- calculate damage and stun values
local damage_act = self:Expand(x,damage_min,damage_max)
local stun_act = self:Expand(y,stun_min,stun_max)
-- Apply damage
local damage = {
victim = hTarget,
attacker = self:GetCaster(),
damage = damage_act,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self
}
ApplyDamage( damage )
-- Add stun modifier
hTarget:AddNewModifier(
self:GetCaster(),
self,
"modifier_chaos_knight_chaos_bolt_lua",
{ duration = stun_act }
)
end
function chaos_knight_chaos_bolt_lua:Expand( value, min, max )
return (max-min)*value + min
end
As for the
modifier, simply copy-paste from Fireblast's stun modifier, and repace the name of all Fireblast functions into Chaos Bolt.
Conclusion
I hope this may give you insight about projectiles, specially tracking projectiles. Next time, we'll use Slardar's Slithereen Crush to simulate AOE effects. Stay tuned.
References
Take your time though, make it whenever/if you feel like it.
Edit: on second thought, how about a summoning ability? Beastmaster boar / NP treants?