354 lines
14 KiB
Lua
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
|