432 lines
16 KiB
Lua
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
|