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

354 lines
14 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_boss_nevermore_coil_wave_lock
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 ____modifier_boss_nevermore_coil_debuff = require("abilities.creep.modifier_boss_nevermore_coil_debuff")
local modifier_boss_nevermore_coil_debuff = ____modifier_boss_nevermore_coil_debuff.modifier_boss_nevermore_coil_debuff
____exports.boss_nevermore_coil_wave = __TS__Class()
local boss_nevermore_coil_wave = ____exports.boss_nevermore_coil_wave
boss_nevermore_coil_wave.name = "boss_nevermore_coil_wave"
boss_nevermore_coil_wave.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua"
__TS__ClassExtends(boss_nevermore_coil_wave, BaseAbility)
function boss_nevermore_coil_wave.prototype.Precache(self, context)
PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context)
PrecacheResource("particle", "particles/darkmoon_creep_warning.vpcf", context)
PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context)
PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context)
end
function boss_nevermore_coil_wave.prototype.GetAOERadius(self)
local r = self:GetSpecialValueFor("radius")
local forward = self:getSpecialOrDefault("lane_forward_dist", ____exports.boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST)
return forward + r * 2
end
function boss_nevermore_coil_wave.prototype.getSpecialOrDefault(self, name, fallback)
local value = self:GetSpecialValueFor(name)
if not value or value <= 0 then
return fallback
end
return value
end
function boss_nevermore_coil_wave.prototype.getBossPhase(self)
local caster = self:GetCaster()
if not caster or caster:IsNull() then
return 1
end
local hp = caster:GetHealthPercent()
if hp <= 25 then
return 4
end
if hp <= 50 then
return 3
end
if hp <= 75 then
return 2
end
return 1
end
function boss_nevermore_coil_wave.prototype.buildGapLaneSlots(self, origin, direction, phase)
local radius = self:GetSpecialValueFor("radius")
local baseSlots = math.floor(self:getSpecialOrDefault("lane_slot_count", ____exports.boss_nevermore_coil_wave.DEFAULT_SLOT_COUNT))
local slotCount = baseSlots + (phase - 1)
local forwardDist = self:getSpecialOrDefault("lane_forward_dist", ____exports.boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST) + (phase - 1) * 35
local spacing = math.max(
radius * self:getSpecialOrDefault("lane_slot_spacing", ____exports.boss_nevermore_coil_wave.DEFAULT_SLOT_SPACING_FACTOR),
185
)
local dir2d = direction:Normalized()
dir2d.z = 0
local right = Vector(-dir2d.y, dir2d.x, 0):Normalized()
local centerRow = GetGroundPosition(origin + dir2d * forwardDist, nil)
local firstWaveHitsEvenIndex = RandomInt(0, 1) == 0
local slots = {}
local mid = (slotCount - 1) / 2
do
local i = 0
while i < slotCount do
local lateral = (i - mid) * spacing
local pos = GetGroundPosition(centerRow + right * lateral, nil)
local isEven = i % 2 == 0
local ____firstWaveHitsEvenIndex_0
if firstWaveHitsEvenIndex then
____firstWaveHitsEvenIndex_0 = isEven
else
____firstWaveHitsEvenIndex_0 = not isEven
end
local hitsOnFirstWave = ____firstWaveHitsEvenIndex_0
slots[#slots + 1] = {position = pos, hitsOnFirstWave = hitsOnFirstWave}
i = i + 1
end
end
return slots
end
function boss_nevermore_coil_wave.prototype.OnSpellStart(self)
if not IsServer() then
return
end
local caster = self:GetCaster()
local origin = caster:GetAbsOrigin()
local point = self:GetCursorPosition()
local toPoint = point - origin
local direction = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized()
local phase = self:getBossPhase()
self:spawnGapLanePattern(origin, direction, phase)
end
function boss_nevermore_coil_wave.prototype.spawnGapLanePattern(self, origin, direction, phase)
local caster = self:GetCaster()
local slots = self:buildGapLaneSlots(origin, direction, phase)
if #slots == 0 then
return
end
local radius = self:GetSpecialValueFor("radius")
local startDelay = self:getSpecialOrDefault("start_delay", ____exports.boss_nevermore_coil_wave.DEFAULT_START_DELAY)
local warningTime = self:getSpecialOrDefault("precast_warning_time", ____exports.boss_nevermore_coil_wave.DEFAULT_WARNING_TIME)
local secondDelayRaw = self:GetSpecialValueFor("second_wave_delay")
local secondWaveDelay = secondDelayRaw > 0 and secondDelayRaw or ____exports.boss_nevermore_coil_wave.DEFAULT_SECOND_WAVE_DELAY
local firstHitTime = startDelay
local secondHitTime = firstHitTime + secondWaveDelay
local firstWarningStart = math.max(0, firstHitTime - warningTime)
local secondWarningStart = math.max(firstHitTime + 0.1, secondHitTime - warningTime)
caster:AddNewModifier(caster, self, modifier_boss_nevermore_coil_wave_lock.name, {duration = secondHitTime + 0.35})
local warnings = {}
local function destroyWarningPair(____, pair)
ParticleManager:DestroyParticle(pair[1], true)
ParticleManager:ReleaseParticleIndex(pair[1])
ParticleManager:DestroyParticle(pair[2], true)
ParticleManager:ReleaseParticleIndex(pair[2])
end
local function clearWarning(____, idx)
local pair = warnings[idx + 1]
if not pair then
return
end
destroyWarningPair(nil, pair)
warnings[idx + 1] = nil
end
Timers:CreateTimer(
firstWarningStart,
function()
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
do
local i = 0
while i < #slots do
do
if not slots[i + 1].hitsOnFirstWave then
goto __continue22
end
warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, true)
end
::__continue22::
i = i + 1
end
end
return nil
end
)
Timers:CreateTimer(
firstHitTime,
function()
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
local impactPhase = self:getBossPhase()
local baseDamage = caster:GetAttackDamage()
local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage")
local damageMultiplier = 1 + (impactPhase - 1) * 0.3
do
local i = 0
while i < #slots do
do
if not slots[i + 1].hitsOnFirstWave then
goto __continue26
end
clearWarning(nil, i)
self:applyPulseDamage(
slots[i + 1].position,
radius,
baseDamage,
bonusDamagePerStack,
damageMultiplier
)
end
::__continue26::
i = i + 1
end
end
return nil
end
)
Timers:CreateTimer(
secondWarningStart,
function()
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
do
local i = 0
while i < #slots do
do
if slots[i + 1].hitsOnFirstWave then
goto __continue30
end
if warnings[i + 1] then
goto __continue30
end
warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, false)
end
::__continue30::
i = i + 1
end
end
return nil
end
)
Timers:CreateTimer(
secondHitTime,
function()
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
local impactPhase = self:getBossPhase()
local baseDamage = caster:GetAttackDamage()
local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage")
local damageMultiplier = 1 + (impactPhase - 1) * 0.3
do
local i = 0
while i < #slots do
do
if slots[i + 1].hitsOnFirstWave then
goto __continue35
end
clearWarning(nil, i)
self:applyPulseDamage(
slots[i + 1].position,
radius,
baseDamage,
bonusDamagePerStack,
damageMultiplier
)
end
::__continue35::
i = i + 1
end
end
return nil
end
)
end
function boss_nevermore_coil_wave.prototype.createPulseWarningColored(self, pulsePoint, radius, firstWaveStrike)
local caster = self:GetCaster()
local warningParticle = ParticleManager:CreateParticle("particles/darkmoon_creep_warning.vpcf", PATTACH_CUSTOMORIGIN, caster)
local aoeParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, caster)
ParticleManager:SetParticleControl(warningParticle, 0, pulsePoint)
ParticleManager:SetParticleControl(
warningParticle,
1,
Vector(radius, 100, 100)
)
ParticleManager:SetParticleControl(aoeParticle, 0, pulsePoint)
ParticleManager:SetParticleControl(
aoeParticle,
1,
Vector(radius, 0, 0)
)
ParticleManager:SetParticleControl(
aoeParticle,
2,
Vector(6, 0, 1)
)
local rgb = firstWaveStrike and Vector(240, 40, 40) or Vector(70, 170, 255)
ParticleManager:SetParticleControl(aoeParticle, 3, rgb)
ParticleManager:SetParticleControl(aoeParticle, 4, pulsePoint)
return {warningParticle, aoeParticle}
end
function boss_nevermore_coil_wave.prototype.applyPulseDamage(self, pulsePoint, radius, baseDamage, bonusDamagePerStack, damageMultiplier)
if not IsServer() then
return
end
local caster = self:GetCaster()
if not caster or caster:IsNull() or not caster:IsAlive() then
return
end
local enemies = FindUnitsInRadius(
caster:GetTeamNumber(),
pulsePoint,
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
local coilDebuff = enemy:FindModifierByName(modifier_boss_nevermore_coil_debuff.name)
local stacks = coilDebuff and coilDebuff:GetStackCount() or 0
local damage = (baseDamage + stacks * bonusDamagePerStack) * damageMultiplier
ApplyDamage({
victim = enemy,
attacker = caster,
damage = damage,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self
})
local updatedDebuff = enemy:AddNewModifier(caster, self, modifier_boss_nevermore_coil_debuff.name, {})
if updatedDebuff ~= nil then
if stacks > 0 then
updatedDebuff:SetStackCount(stacks + 1)
else
updatedDebuff:SetStackCount(1)
end
end
end
EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", caster)
local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil)
ParticleManager:SetParticleControl(hitParticle, 0, pulsePoint)
ParticleManager:SetParticleControl(
hitParticle,
1,
Vector(radius, 1, 1)
)
ParticleManager:ReleaseParticleIndex(hitParticle)
end
boss_nevermore_coil_wave.DEFAULT_START_DELAY = 0.9
boss_nevermore_coil_wave.DEFAULT_SECOND_WAVE_DELAY = 1
boss_nevermore_coil_wave.DEFAULT_WARNING_TIME = 0.85
boss_nevermore_coil_wave.DEFAULT_SLOT_COUNT = 5
boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST = 360
boss_nevermore_coil_wave.DEFAULT_SLOT_SPACING_FACTOR = 1.32
boss_nevermore_coil_wave = __TS__Decorate(
boss_nevermore_coil_wave,
boss_nevermore_coil_wave,
{registerAbility(nil)},
{kind = "class", name = "boss_nevermore_coil_wave"}
)
____exports.boss_nevermore_coil_wave = boss_nevermore_coil_wave
modifier_boss_nevermore_coil_wave_lock = __TS__Class()
modifier_boss_nevermore_coil_wave_lock.name = "modifier_boss_nevermore_coil_wave_lock"
modifier_boss_nevermore_coil_wave_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua"
__TS__ClassExtends(modifier_boss_nevermore_coil_wave_lock, BaseModifier)
function modifier_boss_nevermore_coil_wave_lock.prototype.IsHidden(self)
return true
end
function modifier_boss_nevermore_coil_wave_lock.prototype.IsPurgable(self)
return false
end
function modifier_boss_nevermore_coil_wave_lock.prototype.CheckState(self)
return {[MODIFIER_STATE_DISARMED] = true}
end
modifier_boss_nevermore_coil_wave_lock = __TS__Decorate(
modifier_boss_nevermore_coil_wave_lock,
modifier_boss_nevermore_coil_wave_lock,
{registerModifier(nil)},
{kind = "class", name = "modifier_boss_nevermore_coil_wave_lock"}
)
return ____exports