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

363 lines
15 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_beam_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
--- Как 1-я способность (две волны по «шахматке»), но слоты в линию от босса к точке каста — «к врагу».
____exports.boss_nevermore_coil_beam = __TS__Class()
local boss_nevermore_coil_beam = ____exports.boss_nevermore_coil_beam
boss_nevermore_coil_beam.name = "boss_nevermore_coil_beam"
boss_nevermore_coil_beam.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua"
__TS__ClassExtends(boss_nevermore_coil_beam, BaseAbility)
function boss_nevermore_coil_beam.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_beam.prototype.GetAOERadius(self)
local r = self:GetSpecialValueFor("radius")
local start = self:getSpecialOrDefault("beam_start_dist", ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_START)
local step = self:getBeamStepDistance()
local slots = self:getSlotCountForPhase(self:getBossPhase())
return start + step * math.max(0, slots - 1) + r * 2
end
function boss_nevermore_coil_beam.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_beam.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_beam.prototype.getSlotCountForPhase(self, phase)
local baseSlots = math.floor(self:getSpecialOrDefault("lane_slot_count", ____exports.boss_nevermore_coil_beam.DEFAULT_SLOT_COUNT))
local perPhase = math.floor(self:getSpecialOrDefault("lane_slot_phase_bonus", ____exports.boss_nevermore_coil_beam.DEFAULT_SLOT_PHASE_BONUS))
return baseSlots + (phase - 1) * perPhase
end
function boss_nevermore_coil_beam.prototype.getBeamStepDistance(self)
local radius = self:GetSpecialValueFor("radius")
local stepKv = self:GetSpecialValueFor("beam_step")
if stepKv > 0 then
return stepKv
end
return math.max(radius * 1.22, ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_STEP)
end
function boss_nevermore_coil_beam.prototype.buildBeamSlots(self, origin, direction, phase)
local dir2d = direction:Normalized()
dir2d.z = 0
local slotCount = self:getSlotCountForPhase(phase)
local startDist = self:getSpecialOrDefault("beam_start_dist", ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_START)
local step = self:getBeamStepDistance()
local firstWaveHitsEvenIndex = RandomInt(0, 1) == 0
local slots = {}
do
local i = 0
while i < slotCount do
local along = startDist + i * step
local pos = GetGroundPosition(origin + dir2d * along, 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_beam.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:spawnBeamPattern(origin, direction, phase)
end
function boss_nevermore_coil_beam.prototype.spawnBeamPattern(self, origin, direction, phase)
local caster = self:GetCaster()
local slots = self:buildBeamSlots(origin, direction, phase)
if #slots == 0 then
return
end
local radius = self:GetSpecialValueFor("radius")
local startDelay = self:getSpecialOrDefault("start_delay", ____exports.boss_nevermore_coil_beam.DEFAULT_START_DELAY)
local warningTime = self:getSpecialOrDefault("precast_warning_time", ____exports.boss_nevermore_coil_beam.DEFAULT_WARNING_TIME)
local secondDelayRaw = self:GetSpecialValueFor("second_wave_delay")
local secondWaveDelay = secondDelayRaw > 0 and secondDelayRaw or ____exports.boss_nevermore_coil_beam.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_beam_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 __continue25
end
warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, true)
end
::__continue25::
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 __continue29
end
clearWarning(nil, i)
self:applyPulseDamage(
slots[i + 1].position,
radius,
baseDamage,
bonusDamagePerStack,
damageMultiplier
)
end
::__continue29::
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 __continue33
end
if warnings[i + 1] then
goto __continue33
end
warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, false)
end
::__continue33::
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 __continue38
end
clearWarning(nil, i)
self:applyPulseDamage(
slots[i + 1].position,
radius,
baseDamage,
bonusDamagePerStack,
damageMultiplier
)
end
::__continue38::
i = i + 1
end
end
return nil
end
)
end
function boss_nevermore_coil_beam.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_beam.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_beam.DEFAULT_START_DELAY = 0.9
boss_nevermore_coil_beam.DEFAULT_SECOND_WAVE_DELAY = 1
boss_nevermore_coil_beam.DEFAULT_WARNING_TIME = 0.85
boss_nevermore_coil_beam.DEFAULT_SLOT_COUNT = 12
boss_nevermore_coil_beam.DEFAULT_SLOT_PHASE_BONUS = 2
boss_nevermore_coil_beam.DEFAULT_BEAM_START = 140
boss_nevermore_coil_beam.DEFAULT_BEAM_STEP = 195
boss_nevermore_coil_beam = __TS__Decorate(
boss_nevermore_coil_beam,
boss_nevermore_coil_beam,
{registerAbility(nil)},
{kind = "class", name = "boss_nevermore_coil_beam"}
)
____exports.boss_nevermore_coil_beam = boss_nevermore_coil_beam
modifier_boss_nevermore_coil_beam_lock = __TS__Class()
modifier_boss_nevermore_coil_beam_lock.name = "modifier_boss_nevermore_coil_beam_lock"
modifier_boss_nevermore_coil_beam_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua"
__TS__ClassExtends(modifier_boss_nevermore_coil_beam_lock, BaseModifier)
function modifier_boss_nevermore_coil_beam_lock.prototype.IsHidden(self)
return true
end
function modifier_boss_nevermore_coil_beam_lock.prototype.IsPurgable(self)
return false
end
function modifier_boss_nevermore_coil_beam_lock.prototype.CheckState(self)
return {[MODIFIER_STATE_DISARMED] = true}
end
modifier_boss_nevermore_coil_beam_lock = __TS__Decorate(
modifier_boss_nevermore_coil_beam_lock,
modifier_boss_nevermore_coil_beam_lock,
{registerModifier(nil)},
{kind = "class", name = "modifier_boss_nevermore_coil_beam_lock"}
)
return ____exports