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

495 lines
20 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ArrayPush = ____lualib.__TS__ArrayPush
local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew
local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush
local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread
local __TS__Decorate = ____lualib.__TS__Decorate
local ____exports = {}
local modifier_boss_nevermore_requiem_barrage_casting
local ____nevermore_boss_requiem_bridge = require("ai.nevermore_boss_requiem_bridge")
local nevermoreIncrementRequiemCastCount = ____nevermore_boss_requiem_bridge.nevermoreIncrementRequiemCastCount
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_requiem_magic_resist_debuff = require("abilities.creep.modifier_boss_nevermore_requiem_magic_resist_debuff")
local modifier_boss_nevermore_requiem_magic_resist_debuff = ____modifier_boss_nevermore_requiem_magic_resist_debuff.modifier_boss_nevermore_requiem_magic_resist_debuff
--- Глобальный множитель урона залпа (подогнан под KV-бафф ~+60%).
local REQUIEM_BARRAGE_DAMAGE_MULT = 1.6
--- Доп. дальность волны за стадию HP босса (согласовано с KV wave_distance ×1.6).
local REQUIEM_PHASE_DISTANCE_BONUS = 192
____exports.boss_nevermore_requiem_barrage = __TS__Class()
local boss_nevermore_requiem_barrage = ____exports.boss_nevermore_requiem_barrage
boss_nevermore_requiem_barrage.name = "boss_nevermore_requiem_barrage"
boss_nevermore_requiem_barrage.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua"
__TS__ClassExtends(boss_nevermore_requiem_barrage, BaseAbility)
function boss_nevermore_requiem_barrage.prototype.____constructor(self, ...)
BaseAbility.prototype.____constructor(self, ...)
self.castSerial = 0
self.phasePreviewParticles = {}
self.phasePreviewRotationOffset = 0
self.activePreviewParticles = {}
self.gestureLoopSerial = 0
end
function boss_nevermore_requiem_barrage.prototype.Precache(self, context)
PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", context)
PrecacheResource("particle", "particles/boss_tinker_laser_preview_vector.vpcf", context)
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_nevermore.vsndevts", context)
end
function boss_nevermore_requiem_barrage.prototype.GetChannelTime(self)
local channel = self:GetSpecialValueFor("channel_time")
return channel > 0 and channel or 3
end
function boss_nevermore_requiem_barrage.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_requiem_barrage.prototype.OnAbilityPhaseStart(self)
if not IsServer() then
return true
end
local caster = self:GetCaster()
local point = self:GetCursorPosition()
local toPoint = point - caster:GetAbsOrigin()
local baseDirection = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized()
self:clearPhasePreview()
self.phasePreviewRotationOffset = self:getWaveRotationOffset(1)
local previewDirections = self:buildBurstDirections(
baseDirection,
self.phasePreviewRotationOffset,
self:getBossPhase()
)
local previewDuration = self:GetCastPoint() > 0 and self:GetCastPoint() or 0.3
self.phasePreviewParticles = self:createWavePreview(previewDirections, previewDuration)
return true
end
function boss_nevermore_requiem_barrage.prototype.OnAbilityPhaseInterrupted(self)
if not IsServer() then
return
end
self:clearPhasePreview()
end
function boss_nevermore_requiem_barrage.prototype.OnSpellStart(self)
if not IsServer() then
return
end
local caster = self:GetCaster()
nevermoreIncrementRequiemCastCount(nil, caster)
local point = self:GetCursorPosition()
local toPoint = point - caster:GetAbsOrigin()
local baseDirection = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized()
self.castSerial = self.castSerial + 1
local currentCast = self.castSerial
local channelTime = self:GetChannelTime()
local phase = self:getBossPhase()
local baseWaveInterval = self:GetSpecialValueFor("wave_interval") > 0 and self:GetSpecialValueFor("wave_interval") or 1
local waveInterval = math.max(0.35, baseWaveInterval - (phase - 1) * 0.12)
local wavesCount = math.max(
1,
math.floor(channelTime / waveInterval)
)
local previewTime = self:GetSpecialValueFor("wave_preview_time") > 0 and self:GetSpecialValueFor("wave_preview_time") or 0.35
local waveDistanceKv = self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850
local travelDist = waveDistanceKv + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS
local waveTravelTimeMax = self:getMaxWaveTravelTime(travelDist)
self:clearPhasePreview()
caster:AddNewModifier(caster, self, modifier_boss_nevermore_requiem_barrage_casting.name, {duration = channelTime + 0.1})
EmitSoundOn("Hero_Nevermore.RequiemOfSoulsCast", caster)
self:startGestureLoop(caster)
local firstRotationOffset = self.phasePreviewRotationOffset
self:fireSoulWaveBurst(baseDirection, firstRotationOffset)
Timers:CreateTimer(
waveTravelTimeMax,
function()
self:destroyPreviewParticles(self.phasePreviewParticles)
self.phasePreviewParticles = {}
return nil
end
)
do
local i = 1
while i <= wavesCount do
local fireTime = i * waveInterval
local waveRotationOffset = self:getWaveRotationOffset(i + 1)
local wavePreviewParticles = {}
local wasPreviewShown = false
local speedsForWave
Timers:CreateTimer(
math.max(0, fireTime - previewTime),
function()
if self.castSerial ~= currentCast then
return nil
end
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
local previewDirections = self:buildBurstDirections(
baseDirection,
waveRotationOffset,
self:getBossPhase()
)
speedsForWave = __TS__ArrayMap(
previewDirections,
function() return self:sampleWaveSpeed() end
)
wavePreviewParticles = self:createWavePreview(previewDirections, previewTime, speedsForWave)
wasPreviewShown = true
return nil
end
)
Timers:CreateTimer(
fireTime,
function()
if not wasPreviewShown then
return nil
end
if not caster or caster:IsNull() or not caster:IsAlive() then
return nil
end
self:fireSoulWaveBurst(baseDirection, waveRotationOffset, speedsForWave)
Timers:CreateTimer(
waveTravelTimeMax,
function()
self:destroyPreviewParticles(wavePreviewParticles)
return nil
end
)
return nil
end
)
i = i + 1
end
end
end
function boss_nevermore_requiem_barrage.prototype.OnChannelFinish(self, _bInterrupted)
if not IsServer() then
return
end
self.castSerial = self.castSerial + 1
local caster = self:GetCaster()
self:clearPhasePreview()
self:clearAllActivePreviews()
self:stopGestureLoop(caster)
caster:RemoveModifierByName(modifier_boss_nevermore_requiem_barrage_casting.name)
StopSoundOn("Hero_Nevermore.RequiemOfSoulsCast", caster)
end
function boss_nevermore_requiem_barrage.prototype.startGestureLoop(self, caster)
self.gestureLoopSerial = self.gestureLoopSerial + 1
local serial = self.gestureLoopSerial
local interval = 0.45
local tick
tick = function()
if self.gestureLoopSerial ~= serial then
return
end
if not caster or caster:IsNull() or not caster:IsAlive() then
return
end
if not caster:HasModifier(modifier_boss_nevermore_requiem_barrage_casting.name) then
return
end
caster:StartGestureWithPlaybackRate(ACT_DOTA_RAZE_1, 1)
Timers:CreateTimer(
interval,
function()
tick(nil)
return nil
end
)
end
tick(nil)
end
function boss_nevermore_requiem_barrage.prototype.stopGestureLoop(self, caster)
self.gestureLoopSerial = self.gestureLoopSerial + 1
if not caster or caster:IsNull() then
return
end
caster:FadeGesture(ACT_DOTA_RAZE_1)
end
function boss_nevermore_requiem_barrage.prototype.getWaveSpeedBounds(self)
local base = self:GetSpecialValueFor("wave_speed") > 0 and self:GetSpecialValueFor("wave_speed") or 800
local minKv = self:GetSpecialValueFor("wave_speed_min")
local maxKv = self:GetSpecialValueFor("wave_speed_max")
if minKv > 0 and maxKv > 0 and maxKv >= minKv then
return {minKv, maxKv}
end
local lo = math.max(120, base * 0.72)
local hi = base * 1.32
return {lo, hi}
end
function boss_nevermore_requiem_barrage.prototype.sampleWaveSpeed(self)
local mn, mx = unpack(self:getWaveSpeedBounds())
return RandomFloat(mn, mx)
end
function boss_nevermore_requiem_barrage.prototype.getMaxWaveTravelTime(self, distance)
local mn, _ = unpack(self:getWaveSpeedBounds())
return distance / mn
end
function boss_nevermore_requiem_barrage.prototype.fireSoulWaveBurst(self, baseDirection, rotationOffset, precomputedSpeeds)
local phase = self:getBossPhase()
local directions = self:buildBurstDirections(baseDirection, rotationOffset, phase)
local waveDistanceBase = self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850
local startRadius = self:GetSpecialValueFor("wave_width_start") > 0 and self:GetSpecialValueFor("wave_width_start") or 45
local endRadius = self:GetSpecialValueFor("wave_width_end") > 0 and self:GetSpecialValueFor("wave_width_end") or 45
local distance = waveDistanceBase + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS
do
local i = 0
while i < #directions do
local direction = directions[i + 1]
local spd = precomputedSpeeds ~= nil and precomputedSpeeds[i + 1] ~= nil and precomputedSpeeds[i + 1] or self:sampleWaveSpeed()
self:fireSoulWave(
direction,
distance,
spd,
startRadius,
endRadius
)
i = i + 1
end
end
EmitSoundOn(
"Hero_Nevermore.RequiemOfSouls",
self:GetCaster()
)
end
function boss_nevermore_requiem_barrage.prototype.getWaveRotationOffset(self, waveIndex)
local randomOffset = RandomInt(0, 359)
local waveOffset = waveIndex * 17
return (randomOffset + waveOffset) % 360
end
function boss_nevermore_requiem_barrage.prototype.buildBurstDirections(self, baseDirection, rotationOffset, phase)
local dirs = {}
local baseAngles = {
0,
90,
180,
270,
45,
135,
225,
315
}
local phaseAngles = {}
if phase >= 1 then
__TS__ArrayPush(
phaseAngles,
22.5,
112.5,
202.5,
292.5
)
end
if phase >= 2 then
__TS__ArrayPush(
phaseAngles,
30,
120,
210,
300
)
end
if phase >= 3 then
__TS__ArrayPush(
phaseAngles,
67.5,
157.5,
247.5,
337.5
)
end
if phase >= 4 then
__TS__ArrayPush(
phaseAngles,
15,
105,
195,
285
)
end
local ____array_0 = __TS__SparseArrayNew(unpack(baseAngles))
__TS__SparseArrayPush(
____array_0,
unpack(phaseAngles)
)
local angles = {__TS__SparseArraySpread(____array_0)}
for ____, angle in ipairs(angles) do
local q = QAngle(0, angle + rotationOffset, 0)
dirs[#dirs + 1] = RotatePosition(
Vector(0, 0, 0),
q,
baseDirection
)
end
return dirs
end
function boss_nevermore_requiem_barrage.prototype.createWavePreview(self, directions, previewDuration, speeds)
local caster = self:GetCaster()
local phase = self:getBossPhase()
local distance = (self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850) + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS
local defaultSpeed = self:GetSpecialValueFor("wave_speed") > 0 and self:GetSpecialValueFor("wave_speed") or 800
local casterPos = caster:GetAbsOrigin()
local casterZ = casterPos.z
local particles = {}
do
local i = 0
while i < #directions do
local direction = directions[i + 1]
local spd = speeds ~= nil and speeds[i + 1] ~= nil and speeds[i + 1] or defaultSpeed
local travelTime = distance / spd
local startPos = casterPos + direction * 110
startPos.z = casterZ
local endPos = startPos + direction * distance
endPos.z = casterZ
local previewFx = ParticleManager:CreateParticle("particles/boss_tinker_laser_preview_vector.vpcf", PATTACH_WORLDORIGIN, nil)
ParticleManager:SetParticleControl(previewFx, 0, startPos)
ParticleManager:SetParticleControl(previewFx, 1, endPos)
ParticleManager:SetParticleControl(
previewFx,
2,
Vector(
math.max(previewDuration, travelTime),
0,
0
)
)
particles[#particles + 1] = previewFx
local ____self_activePreviewParticles_1 = self.activePreviewParticles
____self_activePreviewParticles_1[#____self_activePreviewParticles_1 + 1] = previewFx
i = i + 1
end
end
return particles
end
function boss_nevermore_requiem_barrage.prototype.destroyPreviewParticles(self, particles)
if #particles == 0 then
return
end
local toRemove = {}
for ____, p in ipairs(particles) do
toRemove[p] = true
end
for ____, particle in ipairs(particles) do
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
local nextActive = {}
for ____, p in ipairs(self.activePreviewParticles) do
if not (toRemove[p] ~= nil) then
nextActive[#nextActive + 1] = p
end
end
self.activePreviewParticles = nextActive
end
function boss_nevermore_requiem_barrage.prototype.clearPhasePreview(self)
self:destroyPreviewParticles(self.phasePreviewParticles)
self.phasePreviewParticles = {}
end
function boss_nevermore_requiem_barrage.prototype.clearAllActivePreviews(self)
self:destroyPreviewParticles(self.activePreviewParticles)
self.activePreviewParticles = {}
end
function boss_nevermore_requiem_barrage.prototype.fireSoulWave(self, direction, distance, speed, startRadius, endRadius)
local caster = self:GetCaster()
local casterPos = caster:GetAbsOrigin()
local startPos = casterPos + direction * 110
startPos.z = casterPos.z
local velocity = direction * speed
velocity.z = 0
ProjectileManager:CreateLinearProjectile({
Ability = self,
EffectName = "",
vSpawnOrigin = startPos,
fDistance = distance,
fStartRadius = startRadius,
fEndRadius = endRadius,
Source = caster,
bHasFrontalCone = false,
iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY,
iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE,
iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
vVelocity = velocity,
bProvidesVision = false
})
local travelTime = distance / speed
local lineParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", PATTACH_WORLDORIGIN, nil)
ParticleManager:SetParticleControl(lineParticle, 0, startPos)
ParticleManager:SetParticleControl(lineParticle, 1, velocity)
ParticleManager:SetParticleControl(
lineParticle,
2,
Vector(0, travelTime, 0)
)
ParticleManager:ReleaseParticleIndex(lineParticle)
end
function boss_nevermore_requiem_barrage.prototype.OnProjectileHit(self, target, _location)
if not target then
return false
end
local caster = self:GetCaster()
local phase = self:getBossPhase()
local baseDamage = caster:GetAverageTrueAttackDamage(target)
local damage = baseDamage * (1 + (phase - 1) * 0.28) * REQUIEM_BARRAGE_DAMAGE_MULT
ApplyDamage({
victim = target,
attacker = caster,
damage = damage,
damage_type = DAMAGE_TYPE_MAGICAL,
ability = self
})
local debuff = target:AddNewModifier(caster, self, modifier_boss_nevermore_requiem_magic_resist_debuff.name, {duration = -1})
if debuff ~= nil then
debuff:SetStackCount(debuff:GetStackCount() + 1)
end
return false
end
boss_nevermore_requiem_barrage = __TS__Decorate(
boss_nevermore_requiem_barrage,
boss_nevermore_requiem_barrage,
{registerAbility(nil)},
{kind = "class", name = "boss_nevermore_requiem_barrage"}
)
____exports.boss_nevermore_requiem_barrage = boss_nevermore_requiem_barrage
modifier_boss_nevermore_requiem_barrage_casting = __TS__Class()
modifier_boss_nevermore_requiem_barrage_casting.name = "modifier_boss_nevermore_requiem_barrage_casting"
modifier_boss_nevermore_requiem_barrage_casting.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua"
__TS__ClassExtends(modifier_boss_nevermore_requiem_barrage_casting, BaseModifier)
function modifier_boss_nevermore_requiem_barrage_casting.prototype.IsHidden(self)
return true
end
function modifier_boss_nevermore_requiem_barrage_casting.prototype.IsPurgable(self)
return false
end
function modifier_boss_nevermore_requiem_barrage_casting.prototype.CheckState(self)
return {
[MODIFIER_STATE_STUNNED] = false,
[MODIFIER_STATE_DISARMED] = true,
[MODIFIER_STATE_SILENCED] = false,
[MODIFIER_STATE_ROOTED] = true,
[MODIFIER_STATE_COMMAND_RESTRICTED] = true
}
end
modifier_boss_nevermore_requiem_barrage_casting = __TS__Decorate(
modifier_boss_nevermore_requiem_barrage_casting,
modifier_boss_nevermore_requiem_barrage_casting,
{registerModifier(nil)},
{kind = "class", name = "modifier_boss_nevermore_requiem_barrage_casting"}
)
return ____exports