local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local __TS__ClassExtends = ____lualib.__TS__ClassExtends local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed local __TS__Decorate = ____lualib.__TS__Decorate local ____exports = {} local modifier_boss_nevermore_time_walk local ____dota_ts_adapter = require("lib.dota_ts_adapter") local BaseAbility = ____dota_ts_adapter.BaseAbility local BaseModifierMotionHorizontal = ____dota_ts_adapter.BaseModifierMotionHorizontal local registerAbility = ____dota_ts_adapter.registerAbility local registerModifier = ____dota_ts_adapter.registerModifier local DEBUG_TIME_WALK = false local twDebugNextAt = {} local function timeWalkDebug(self, tag, message, throttle) if throttle == nil then throttle = 0.35 end if not DEBUG_TIME_WALK then return end local now = GameRules:GetGameTime() local nextAt = twDebugNextAt[tag] or 0 if now < nextAt then return end twDebugNextAt[tag] = now + throttle print((("[NevermoreTimeWalk][" .. tag) .. "] ") .. message) end ____exports.boss_nevermore_time_walk = __TS__Class() local boss_nevermore_time_walk = ____exports.boss_nevermore_time_walk boss_nevermore_time_walk.name = "boss_nevermore_time_walk" boss_nevermore_time_walk.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua" __TS__ClassExtends(boss_nevermore_time_walk, BaseAbility) function boss_nevermore_time_walk.prototype.Precache(self, context) PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk_slow.vpcf", context) PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk_preimage.vpcf", context) PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk.vpcf", context) end function boss_nevermore_time_walk.prototype.GetCastRange(self, _location, _target) return self:GetSpecialValueFor("range") end function boss_nevermore_time_walk.prototype.OnSpellStart(self) if not IsServer() then return end local caster = self:GetCaster() if caster:HasModifier(modifier_boss_nevermore_time_walk.name) then timeWalkDebug(nil, "cast_skip", "already moving, skip recast", 0.2) return end local range = self:GetSpecialValueFor("range") local speed = math.max( 1, self:GetSpecialValueFor("speed") ) local radius = self:GetSpecialValueFor("radius") local point = self:GetCursorPosition() local origin = caster:GetAbsOrigin() local direction = point - origin local distance = direction:Length2D() if distance < 1 then direction = caster:GetForwardVector() distance = range else direction = direction:Normalized() end local clampedDistance = math.min(distance, range) local targetPosition = GetGroundPosition(origin + direction * clampedDistance, nil) local duration = clampedDistance / speed timeWalkDebug( nil, "cast", (((("distance=" .. __TS__NumberToFixed(clampedDistance, 0)) .. " duration=") .. __TS__NumberToFixed(duration, 2)) .. " speed=") .. tostring(speed), 0.1 ) EmitSoundOn("Hero_FacelessVoid.TimeWalk", caster) local startFx = ParticleManager:CreateParticle("particles/units/heroes/hero_faceless_void/faceless_void_time_walk_slow.vpcf", PATTACH_WORLDORIGIN, caster) ParticleManager:SetParticleControl(startFx, 0, origin) ParticleManager:SetParticleControl( startFx, 1, Vector(radius, 0, 0) ) ParticleManager:ReleaseParticleIndex(startFx) local preimageFx = ParticleManager:CreateParticle("particles/units/heroes/hero_faceless_void/faceless_void_time_walk_preimage.vpcf", PATTACH_WORLDORIGIN, caster) ParticleManager:SetParticleControl(preimageFx, 0, origin) ParticleManager:SetParticleControl(preimageFx, 1, targetPosition) ParticleManager:SetParticleControl(preimageFx, 2, targetPosition) ParticleManager:ReleaseParticleIndex(preimageFx) caster:AddNewModifier( caster, self, modifier_boss_nevermore_time_walk.name, { duration = duration, target_x = targetPosition.x, target_y = targetPosition.y, target_z = targetPosition.z, speed = speed, radius = radius, damage = self:GetSpecialValueFor("damage") } ) ProjectileManager:ProjectileDodge(caster) end function boss_nevermore_time_walk.prototype.OnProjectileHit(self, target, _location) if not IsServer() or not target then return false end local caster = self:GetCaster() if not caster or caster:IsNull() or not caster:IsAlive() then return false end local base = self:GetSpecialValueFor("damage") local damage = math.max(1, base * 0.35) ApplyDamage({ victim = target, attacker = caster, damage = damage, damage_type = DAMAGE_TYPE_MAGICAL, ability = self }) return false end boss_nevermore_time_walk = __TS__Decorate( boss_nevermore_time_walk, boss_nevermore_time_walk, {registerAbility(nil)}, {kind = "class", name = "boss_nevermore_time_walk"} ) ____exports.boss_nevermore_time_walk = boss_nevermore_time_walk modifier_boss_nevermore_time_walk = __TS__Class() modifier_boss_nevermore_time_walk.name = "modifier_boss_nevermore_time_walk" modifier_boss_nevermore_time_walk.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua" __TS__ClassExtends(modifier_boss_nevermore_time_walk, BaseModifierMotionHorizontal) function modifier_boss_nevermore_time_walk.prototype.____constructor(self, ...) BaseModifierMotionHorizontal.prototype.____constructor(self, ...) self.direction = Vector(0, 0, 0) self.remainingDistance = 0 self.speed = 0 self.damage = 0 self.radius = 0 self.coilInterval = 0.25 self.sideWaveInterval = 0.7 self.nextSideWaveAt = 0 self.sideWaveToggle = false self.maxTargetsPerTick = 8 self.nextCoilSoundAllowedAt = 0 self.coilPulseIndex = 0 end function modifier_boss_nevermore_time_walk.prototype.IsHidden(self) return true end function modifier_boss_nevermore_time_walk.prototype.IsPurgable(self) return false end function modifier_boss_nevermore_time_walk.prototype.OnCreated(self, params) if not IsServer() then return end local parent = self:GetParent() local target = Vector( params.target_x or parent:GetAbsOrigin().x, params.target_y or parent:GetAbsOrigin().y, params.target_z or parent:GetAbsOrigin().z ) local toTarget = target - parent:GetAbsOrigin() self.remainingDistance = toTarget:Length2D() self.direction = self.remainingDistance < 1 and parent:GetForwardVector() or toTarget:Normalized() local ____math_max_3 = math.max local ____params_speed_2 = params.speed if ____params_speed_2 == nil then local ____opt_0 = self:GetAbility() ____params_speed_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("speed") end self.speed = ____math_max_3(1, ____params_speed_2 or 1) local ____math_max_7 = math.max local ____params_radius_6 = params.radius if ____params_radius_6 == nil then local ____opt_4 = self:GetAbility() ____params_radius_6 = ____opt_4 and ____opt_4:GetSpecialValueFor("radius") end self.radius = ____math_max_7(1, ____params_radius_6 or 180) local ____params_damage_10 = params.damage if ____params_damage_10 == nil then local ____opt_8 = self:GetAbility() ____params_damage_10 = ____opt_8 and ____opt_8:GetSpecialValueFor("damage") end self.damage = ____params_damage_10 or 0 local ____math_max_13 = math.max local ____opt_11 = self:GetAbility() self.coilInterval = ____math_max_13( 0.22, ____opt_11 and ____opt_11:GetSpecialValueFor("coil_interval") or 0.25 ) local ____math_max_16 = math.max local ____opt_14 = self:GetAbility() self.sideWaveInterval = ____math_max_16( 0.45, ____opt_14 and ____opt_14:GetSpecialValueFor("side_wave_interval") or 0.7 ) local ____math_max_19 = math.max local ____opt_17 = self:GetAbility() self.maxTargetsPerTick = ____math_max_19( 1, ____opt_17 and ____opt_17:GetSpecialValueFor("max_targets_per_tick") or 8 ) local now = GameRules:GetGameTime() self.nextSideWaveAt = now + self.sideWaveInterval self.nextCoilSoundAllowedAt = now timeWalkDebug( nil, "start", (((((("dist=" .. __TS__NumberToFixed(self.remainingDistance, 0)) .. " speed=") .. tostring(self.speed)) .. " coilInt=") .. __TS__NumberToFixed(self.coilInterval, 2)) .. " sideInt=") .. __TS__NumberToFixed(self.sideWaveInterval, 2), 0.1 ) if not self:ApplyHorizontalMotionController() then self:Destroy() return end self.cachedRequiemAbility = parent:FindAbilityByName("boss_nevermore_requiem_barrage") or nil self:StartIntervalThink(self.coilInterval) self:runCoilPulse(now) end function modifier_boss_nevermore_time_walk.prototype.OnIntervalThink(self) if not IsServer() then return end local parent = self:GetParent() local ability = self:GetAbility() if not parent or parent:IsNull() or not ability then return end local now = GameRules:GetGameTime() if now >= self.nextSideWaveAt then self.nextSideWaveAt = now + self.sideWaveInterval self.sideWaveToggle = not self.sideWaveToggle self:fireSideWaves( parent, ability, GetGroundPosition( parent:GetAbsOrigin(), nil ), self.sideWaveToggle ) timeWalkDebug(nil, "side_waves", "spawn side=" .. (self.sideWaveToggle and "L" or "R"), 0.25) end self:runCoilPulse(now) end function modifier_boss_nevermore_time_walk.prototype.runCoilPulse(self, now) local parent = self:GetParent() local ability = self:GetAbility() if not parent or parent:IsNull() or not ability then return end local pulsePoint = GetGroundPosition( parent:GetAbsOrigin(), nil ) local enemies = FindUnitsInRadius( parent:GetTeamNumber(), pulsePoint, nil, self.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 ) local targetCount = math.min(#enemies, self.maxTargetsPerTick) do local i = 0 while i < targetCount do local enemy = enemies[i + 1] ApplyDamage({ victim = enemy, attacker = parent, damage = self.damage, damage_type = DAMAGE_TYPE_MAGICAL, ability = ability }) i = i + 1 end end self.coilPulseIndex = self.coilPulseIndex + 1 timeWalkDebug( nil, "coil_tick", (("enemies=" .. tostring(#enemies)) .. " applied=") .. tostring(targetCount), 0.25 ) if now >= self.nextCoilSoundAllowedAt then self.nextCoilSoundAllowedAt = now + 0.45 EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", parent) end if self.coilPulseIndex % 2 == 0 then 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(self.radius, 1, 1) ) ParticleManager:ReleaseParticleIndex(hitParticle) else local aoeFx = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, parent) ParticleManager:SetParticleControl(aoeFx, 0, pulsePoint) ParticleManager:SetParticleControl( aoeFx, 1, Vector(self.radius, 0, 0) ) ParticleManager:SetParticleControl( aoeFx, 2, Vector(5, 0, 1) ) ParticleManager:SetParticleControl( aoeFx, 3, Vector(200, 50, 0) ) ParticleManager:SetParticleControl(aoeFx, 4, pulsePoint) ParticleManager:ReleaseParticleIndex(aoeFx) end end function modifier_boss_nevermore_time_walk.prototype.fireSideWaves(self, caster, sourceAbility, origin, fireLeft) local requiem = self.cachedRequiemAbility if not requiem or requiem:IsNull() then requiem = caster:FindAbilityByName("boss_nevermore_requiem_barrage") or nil self.cachedRequiemAbility = requiem end local distance = requiem and not requiem:IsNull() and math.max( 200, requiem:GetSpecialValueFor("wave_distance") or 900 ) or 900 local waveSpeed = requiem and not requiem:IsNull() and math.max( 200, requiem:GetSpecialValueFor("wave_speed") or 700 ) or 700 local startRadius = requiem and not requiem:IsNull() and math.max( 10, requiem:GetSpecialValueFor("wave_width_start") or 45 ) or 45 local endRadius = requiem and not requiem:IsNull() and math.max( 10, requiem:GetSpecialValueFor("wave_width_end") or 45 ) or 45 local dir = fireLeft and Vector(-self.direction.y, self.direction.x, 0):Normalized() or Vector(self.direction.y, -self.direction.x, 0):Normalized() local startPos = origin + dir * 80 startPos.z = origin.z local velocity = dir * waveSpeed velocity.z = 0 ProjectileManager:CreateLinearProjectile({ Ability = sourceAbility, 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 }) end function modifier_boss_nevermore_time_walk.prototype.OnDestroy(self) if not IsServer() then return end local parent = self:GetParent() parent:RemoveHorizontalMotionController(self) FindClearSpaceForUnit( parent, parent:GetAbsOrigin(), true ) parent:StartGesture(ACT_DOTA_CAST_ABILITY_1_END) timeWalkDebug(nil, "end", "modifier destroyed", 0.1) end function modifier_boss_nevermore_time_walk.prototype.UpdateHorizontalMotion(self, me, dt) if not IsServer() then return end local step = self.speed * dt if self.remainingDistance > 0 then local oldPos = me:GetAbsOrigin() local nextPos = GetGroundPosition( oldPos + self.direction * math.min(step, self.remainingDistance), nil ) me:SetAbsOrigin(nextPos) self.remainingDistance = self.remainingDistance - step return end self:Destroy() end function modifier_boss_nevermore_time_walk.prototype.OnHorizontalMotionInterrupted(self) if not IsServer() then return end self:Destroy() end function modifier_boss_nevermore_time_walk.prototype.CheckState(self) return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true, [MODIFIER_STATE_FLYING_FOR_PATHING_PURPOSES_ONLY] = true} end function modifier_boss_nevermore_time_walk.prototype.GetEffectName(self) return "particles/units/heroes/hero_faceless_void/faceless_void_time_walk.vpcf" end function modifier_boss_nevermore_time_walk.prototype.GetStatusEffectName(self) return "particles/status_fx/status_effect_abaddon_borrowed_time.vpcf" end function modifier_boss_nevermore_time_walk.prototype.StatusEffectPriority(self) return 10 end modifier_boss_nevermore_time_walk = __TS__Decorate( modifier_boss_nevermore_time_walk, modifier_boss_nevermore_time_walk, {registerModifier(nil)}, {kind = "class", name = "modifier_boss_nevermore_time_walk"} ) return ____exports