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

432 lines
16 KiB
Lua

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