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