Skip to content

Ability Lua Tutorial 6: Chaos Knight's Chaos Bolt

Elfansoer edited this page Aug 1, 2018 · 1 revision

Playing with projectile.

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:

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:

  • 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 (true)
  • bReplaceExisting: Destroys similar projectile if true (false)
  • flExpireTime: Projectile will expire after given time (0, won't expire until hit or dodged)
  • 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.lua
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.lua
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.

RandomFloat()

First, we'll throw a random number. A RandomFloat() function provided by the engine 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.lua
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 = RandomFloat(0,1)
	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.lua
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.lua
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.lua
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

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 = RandomFloat(0,1)
	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.