Files
Dota-Zombie-Invasion/scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua
T
2026-05-29 15:11:31 +07:00

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