449 lines
16 KiB
Lua
449 lines
16 KiB
Lua
local ____lualib = require("lualib_bundle")
|
|
local __TS__Class = ____lualib.__TS__Class
|
|
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
|
|
local __TS__Decorate = ____lualib.__TS__Decorate
|
|
local ____exports = {}
|
|
local modifier_sandking_epicenter_active, modifier_sandking_epicenter_slow
|
|
local ____dota_ts_adapter = require("lib.dota_ts_adapter")
|
|
local BaseAbility = ____dota_ts_adapter.BaseAbility
|
|
local BaseModifier = ____dota_ts_adapter.BaseModifier
|
|
local registerAbility = ____dota_ts_adapter.registerAbility
|
|
local registerModifier = ____dota_ts_adapter.registerModifier
|
|
local ____luck = require("utils.luck")
|
|
local getLuck = ____luck.getLuck
|
|
local ____sandking_scorpion_strike_custom = require("abilities.heroes.sand_king.sandking_scorpion_strike_custom")
|
|
local performSandKingScorpionStrikeAtPoint = ____sandking_scorpion_strike_custom.performSandKingScorpionStrikeAtPoint
|
|
____exports.sandking_epicenter_custom = __TS__Class()
|
|
local sandking_epicenter_custom = ____exports.sandking_epicenter_custom
|
|
sandking_epicenter_custom.name = "sandking_epicenter_custom"
|
|
sandking_epicenter_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua"
|
|
__TS__ClassExtends(sandking_epicenter_custom, BaseAbility)
|
|
function sandking_epicenter_custom.prototype.Precache(self, context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_epicenter_tell.vpcf", context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_epicenter.vpcf", context)
|
|
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context)
|
|
end
|
|
function sandking_epicenter_custom.prototype.GetBehavior(self)
|
|
return bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING)
|
|
end
|
|
function sandking_epicenter_custom.prototype.GetCastPoint(self)
|
|
local cp = BaseAbility.prototype.GetCastPoint(self)
|
|
local caster = self:GetCaster()
|
|
if caster and not caster:IsNull() and HasShard(nil, caster) then
|
|
cp = math.max(
|
|
0.05,
|
|
cp - self:GetSpecialValueFor("shard_cast_reduction")
|
|
)
|
|
end
|
|
return cp
|
|
end
|
|
function sandking_epicenter_custom.prototype.GetPulsePhaseDuration(self)
|
|
return math.max(
|
|
0.01,
|
|
self:GetSpecialValueFor("pulse_phase_duration")
|
|
)
|
|
end
|
|
function sandking_epicenter_custom.prototype.OnAbilityPhaseStart(self)
|
|
if not IsServer() then
|
|
return true
|
|
end
|
|
local caster = self:GetCaster()
|
|
EmitSoundOn("Ability.SandKing_Epicenter.spell", caster)
|
|
local cp = math.max(
|
|
0.05,
|
|
self:GetCastPoint()
|
|
)
|
|
local playbackRate = 2 / cp
|
|
caster:StartGestureWithPlaybackRate(ACT_DOTA_CAST_ABILITY_4, playbackRate)
|
|
self.epicenterTellFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_epicenter_tell.vpcf", PATTACH_CUSTOMORIGIN_FOLLOW, caster)
|
|
local tailFollow = PATTACH_POINT_FOLLOW
|
|
ParticleManager:SetParticleControlEnt(
|
|
self.epicenterTellFx,
|
|
0,
|
|
caster,
|
|
tailFollow,
|
|
"attach_tail",
|
|
caster:GetAbsOrigin(),
|
|
true
|
|
)
|
|
ParticleManager:SetParticleControlEnt(
|
|
self.epicenterTellFx,
|
|
1,
|
|
caster,
|
|
tailFollow,
|
|
"attach_tail",
|
|
caster:GetAbsOrigin(),
|
|
true
|
|
)
|
|
ParticleManager:SetParticleControlEnt(
|
|
self.epicenterTellFx,
|
|
2,
|
|
caster,
|
|
tailFollow,
|
|
"attach_tail",
|
|
caster:GetAbsOrigin(),
|
|
true
|
|
)
|
|
return true
|
|
end
|
|
function sandking_epicenter_custom.prototype.OnAbilityPhaseInterrupted(self)
|
|
if not IsServer() then
|
|
return true
|
|
end
|
|
local caster = self:GetCaster()
|
|
StopSoundOn("Ability.SandKing_Epicenter.spell", caster)
|
|
caster:FadeGesture(ACT_DOTA_CAST_ABILITY_4)
|
|
self:clearEpicenterTellFx()
|
|
return true
|
|
end
|
|
function sandking_epicenter_custom.prototype.clearEpicenterTellFx(self)
|
|
if self.epicenterTellFx == nil then
|
|
return
|
|
end
|
|
ParticleManager:DestroyParticle(self.epicenterTellFx, false)
|
|
ParticleManager:ReleaseParticleIndex(self.epicenterTellFx)
|
|
self.epicenterTellFx = nil
|
|
end
|
|
function sandking_epicenter_custom.prototype.OnSpellStart(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local caster = self:GetCaster()
|
|
if HasShard(nil, caster) then
|
|
StopSoundOn("Ability.SandKing_Epicenter.spell", caster)
|
|
end
|
|
caster:FadeGesture(ACT_DOTA_CAST_ABILITY_4)
|
|
self:clearEpicenterTellFx()
|
|
local phaseDur = self:GetPulsePhaseDuration()
|
|
local pulses = math.max(
|
|
1,
|
|
self:GetSpecialValueFor("epicenter_pulses")
|
|
)
|
|
local pulseGap = phaseDur / pulses
|
|
caster:AddNewModifier(caster, self, modifier_sandking_epicenter_active.name, {duration = phaseDur + pulseGap + 0.45})
|
|
end
|
|
sandking_epicenter_custom = __TS__Decorate(
|
|
sandking_epicenter_custom,
|
|
sandking_epicenter_custom,
|
|
{registerAbility(nil)},
|
|
{kind = "class", name = "sandking_epicenter_custom"}
|
|
)
|
|
____exports.sandking_epicenter_custom = sandking_epicenter_custom
|
|
modifier_sandking_epicenter_active = __TS__Class()
|
|
modifier_sandking_epicenter_active.name = "modifier_sandking_epicenter_active"
|
|
modifier_sandking_epicenter_active.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua"
|
|
__TS__ClassExtends(modifier_sandking_epicenter_active, BaseModifier)
|
|
function modifier_sandking_epicenter_active.prototype.____constructor(self, ...)
|
|
BaseModifier.prototype.____constructor(self, ...)
|
|
self.nextPulseIndex = 1
|
|
self.pulseInterval = 0.5
|
|
self.thinkStep = 0.1
|
|
self.pulseTimeBank = 0
|
|
self.radiusBase = 0
|
|
self.radiusInc = 0
|
|
self.pulseDamage = 0
|
|
self.slowPct = 0
|
|
self.slowAs = 0
|
|
self.slowDur = 0
|
|
self.totalPulses = 12
|
|
self.shardBreakEvery = 4
|
|
self.shardBreakDur = 3
|
|
self.shardBreakRadius = 300
|
|
self.scepterRollTimeBank = 0
|
|
self.scepterTimeSlotCounter = 0
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.IsHidden(self)
|
|
return true
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.IsDebuff(self)
|
|
return false
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.IsPurgable(self)
|
|
return false
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.OnCreated(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local ability = self:GetAbility()
|
|
if not ability then
|
|
self:Destroy()
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
local phaseDur = ability:GetPulsePhaseDuration()
|
|
self.totalPulses = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("epicenter_pulses")
|
|
)
|
|
self.pulseInterval = math.max(0.03, phaseDur / self.totalPulses)
|
|
self.radiusBase = ability:GetSpecialValueFor("epicenter_radius_base")
|
|
self.radiusInc = ability:GetSpecialValueFor("epicenter_radius_increment")
|
|
self.pulseDamage = ability:GetSpecialValueFor("epicenter_damage")
|
|
self.slowPct = ability:GetSpecialValueFor("epicenter_slow")
|
|
self.slowAs = ability:GetSpecialValueFor("epicenter_slow_as")
|
|
self.slowDur = ability:GetSpecialValueFor("slow_duration")
|
|
self.shardBreakEvery = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("shard_break_count")
|
|
)
|
|
self.shardBreakDur = ability:GetSpecialValueFor("shard_break_duration")
|
|
self.shardBreakRadius = ability:GetSpecialValueFor("shard_break_radius")
|
|
local rollsPerSec = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("scepter_rolls_per_second")
|
|
)
|
|
local rollPeriod = 1 / rollsPerSec
|
|
self.thinkStep = math.max(
|
|
0.03,
|
|
math.min(self.pulseInterval, rollPeriod)
|
|
)
|
|
EmitSoundOn("Ability.SandKing_Epicenter", parent)
|
|
parent:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_4)
|
|
self.pulseTimeBank = 0
|
|
self.scepterRollTimeBank = 0
|
|
self.scepterTimeSlotCounter = 0
|
|
self:DoPulse(parent, ability, 0)
|
|
self.nextPulseIndex = 1
|
|
self:StartIntervalThink(self.thinkStep)
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.OnDestroy(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
parent:FadeGesture(ACT_DOTA_OVERRIDE_ABILITY_4)
|
|
StopSoundOn("Ability.SandKing_Epicenter", parent)
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.OnIntervalThink(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
local ability = self:GetAbility()
|
|
if not parent or not parent:IsAlive() or not ability then
|
|
self:Destroy()
|
|
return
|
|
end
|
|
self.pulseTimeBank = self.pulseTimeBank + self.thinkStep
|
|
while self.pulseTimeBank >= self.pulseInterval and self.nextPulseIndex < self.totalPulses do
|
|
local pulseIdx = self.nextPulseIndex
|
|
self:DoPulse(parent, ability, pulseIdx)
|
|
if HasShard(nil, parent) and self.shardBreakEvery > 0 and (pulseIdx + 1) % self.shardBreakEvery == 0 then
|
|
local enemies = FindUnitsInRadius(
|
|
parent:GetTeamNumber(),
|
|
GetGroundPosition(
|
|
parent:GetAbsOrigin(),
|
|
parent
|
|
),
|
|
nil,
|
|
self.shardBreakRadius,
|
|
DOTA_UNIT_TARGET_TEAM_ENEMY,
|
|
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
|
|
DOTA_UNIT_TARGET_FLAG_NONE,
|
|
FIND_ANY_ORDER,
|
|
false
|
|
)
|
|
for ____, enemy in ipairs(enemies) do
|
|
enemy:AddNewModifier(
|
|
parent,
|
|
ability,
|
|
"modifier_stunned",
|
|
{duration = self.shardBreakDur * (1 - enemy:GetStatusResistance())}
|
|
)
|
|
end
|
|
end
|
|
self.pulseTimeBank = self.pulseTimeBank - self.pulseInterval
|
|
self.nextPulseIndex = self.nextPulseIndex + 1
|
|
end
|
|
if parent:HasScepter() then
|
|
local hero = parent
|
|
local strike = parent:FindAbilityByName("sandking_scorpion_strike_custom")
|
|
if hero:IsHero() and strike and not strike:IsNull() and strike:GetLevel() > 0 then
|
|
self.scepterRollTimeBank = self.scepterRollTimeBank + self.thinkStep
|
|
local ringForTail = math.max(0, self.nextPulseIndex - 1)
|
|
self:tryScepterScorpionStrikesFromBank(
|
|
parent,
|
|
ability,
|
|
strike,
|
|
hero,
|
|
ringForTail
|
|
)
|
|
end
|
|
end
|
|
if self.nextPulseIndex >= self.totalPulses then
|
|
self:Destroy()
|
|
end
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.tryScepterScorpionStrikesFromBank(self, parent, ability, strike, hero, pulseIdx)
|
|
local rollsPerSec = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("scepter_rolls_per_second")
|
|
)
|
|
local period = 1 / rollsPerSec
|
|
local everyNTicks = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("scepter_proc_every_n_time_checks")
|
|
)
|
|
while self.scepterRollTimeBank >= period do
|
|
do
|
|
self.scepterRollTimeBank = self.scepterRollTimeBank - period
|
|
self.scepterTimeSlotCounter = self.scepterTimeSlotCounter + 1
|
|
if self.scepterTimeSlotCounter % everyNTicks ~= 0 then
|
|
goto __continue35
|
|
end
|
|
self:performScepterTailVolleyOnEnemies(
|
|
parent,
|
|
ability,
|
|
strike,
|
|
hero,
|
|
pulseIdx
|
|
)
|
|
end
|
|
::__continue35::
|
|
end
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.findEnemiesInPulseRing(self, caster, pulseIdx)
|
|
local radius = self.radiusBase + pulseIdx * self.radiusInc
|
|
local ground = GetGroundPosition(
|
|
caster:GetAbsOrigin(),
|
|
caster
|
|
)
|
|
return FindUnitsInRadius(
|
|
caster:GetTeamNumber(),
|
|
ground,
|
|
nil,
|
|
radius,
|
|
DOTA_UNIT_TARGET_TEAM_ENEMY,
|
|
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
|
|
DOTA_UNIT_TARGET_FLAG_NONE,
|
|
FIND_ANY_ORDER,
|
|
false
|
|
)
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.performScepterTailVolleyOnEnemies(self, parent, ability, strike, hero, pulseIdx)
|
|
local enemies = self:findEnemiesInPulseRing(parent, pulseIdx)
|
|
if #enemies == 0 then
|
|
return
|
|
end
|
|
local luckPerExtra = math.max(
|
|
1,
|
|
ability:GetSpecialValueFor("scepter_luck_per_extra_tail")
|
|
)
|
|
local strikeCount = 1 + math.floor(getLuck(nil, hero) / luckPerExtra)
|
|
local start = RandomInt(0, #enemies - 1)
|
|
do
|
|
local i = 0
|
|
while i < strikeCount do
|
|
do
|
|
local enemy = enemies[(start + i) % #enemies + 1]
|
|
if not enemy or enemy:IsNull() or not enemy:IsAlive() then
|
|
goto __continue40
|
|
end
|
|
local pt = GetGroundPosition(
|
|
enemy:GetAbsOrigin(),
|
|
parent
|
|
)
|
|
performSandKingScorpionStrikeAtPoint(nil, parent, strike, pt)
|
|
end
|
|
::__continue40::
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
function modifier_sandking_epicenter_active.prototype.DoPulse(self, caster, ability, index)
|
|
local radius = self.radiusBase + index * self.radiusInc
|
|
local ground = GetGroundPosition(
|
|
caster:GetAbsOrigin(),
|
|
caster
|
|
)
|
|
local pulseFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_epicenter.vpcf", PATTACH_WORLDORIGIN, nil)
|
|
ParticleManager:SetParticleControl(pulseFx, 0, ground)
|
|
ParticleManager:SetParticleControl(
|
|
pulseFx,
|
|
1,
|
|
Vector(radius, radius, 1)
|
|
)
|
|
ParticleManager:ReleaseParticleIndex(pulseFx)
|
|
EmitSoundOnLocationWithCaster(ground, "SandKing.Pulse", caster)
|
|
local enemies = FindUnitsInRadius(
|
|
caster:GetTeamNumber(),
|
|
ground,
|
|
nil,
|
|
radius,
|
|
DOTA_UNIT_TARGET_TEAM_ENEMY,
|
|
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
|
|
DOTA_UNIT_TARGET_FLAG_NONE,
|
|
FIND_ANY_ORDER,
|
|
false
|
|
)
|
|
for ____, enemy in ipairs(enemies) do
|
|
ApplyDamage({
|
|
victim = enemy,
|
|
attacker = caster,
|
|
damage = self.pulseDamage,
|
|
damage_type = DAMAGE_TYPE_MAGICAL,
|
|
ability = ability
|
|
})
|
|
enemy:AddNewModifier(caster, ability, modifier_sandking_epicenter_slow.name, {duration = self.slowDur, slow_pct = self.slowPct, slow_as = self.slowAs})
|
|
end
|
|
end
|
|
modifier_sandking_epicenter_active = __TS__Decorate(
|
|
modifier_sandking_epicenter_active,
|
|
modifier_sandking_epicenter_active,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_sandking_epicenter_active"}
|
|
)
|
|
modifier_sandking_epicenter_slow = __TS__Class()
|
|
modifier_sandking_epicenter_slow.name = "modifier_sandking_epicenter_slow"
|
|
modifier_sandking_epicenter_slow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua"
|
|
__TS__ClassExtends(modifier_sandking_epicenter_slow, BaseModifier)
|
|
function modifier_sandking_epicenter_slow.prototype.____constructor(self, ...)
|
|
BaseModifier.prototype.____constructor(self, ...)
|
|
self.slowPct = 0
|
|
self.slowAs = 0
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.IsHidden(self)
|
|
return false
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.IsDebuff(self)
|
|
return true
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.IsPurgable(self)
|
|
return true
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.OnCreated(self, params)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self.slowPct = params.slow_pct or -30
|
|
self.slowAs = params.slow_as or -50
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.OnRefresh(self, params)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
if params.slow_pct ~= nil then
|
|
self.slowPct = params.slow_pct
|
|
end
|
|
if params.slow_as ~= nil then
|
|
self.slowAs = params.slow_as
|
|
end
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.DeclareFunctions(self)
|
|
return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT}
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self)
|
|
return self.slowPct
|
|
end
|
|
function modifier_sandking_epicenter_slow.prototype.GetModifierAttackSpeedBonus_Constant(self)
|
|
return self.slowAs
|
|
end
|
|
modifier_sandking_epicenter_slow = __TS__Decorate(
|
|
modifier_sandking_epicenter_slow,
|
|
modifier_sandking_epicenter_slow,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_sandking_epicenter_slow"}
|
|
)
|
|
return ____exports
|