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

463 lines
20 KiB
Lua

local ____lualib = require("lualib_bundle")
local Set = ____lualib.Set
local __TS__New = ____lualib.__TS__New
local __TS__Class = ____lualib.__TS__Class
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith
local __TS__ArraySome = ____lualib.__TS__ArraySome
local __TS__Decorate = ____lualib.__TS__Decorate
local ____exports = {}
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
require("utils.utils")
local WORLD_DESTROYER_BLOCKED_UNITS = __TS__New(Set, {"npc_wisps", "npc_fish_1", "npc_fish_2", "npc_bomb"})
local WORLD_DESTROYER_BLOCKED_PREFIXES = {"npc_wave_boss"}
local WORLD_DESTROYER_PREP_DURATION = 3
local function getWorldDestroyerSoulEaterMaxHealth(self, ability, caster)
local base = ability:GetSpecialValueFor("health_soul_eater")
return base + caster:GetMaxHealth() * 0.5
end
--- Неуязвимость кастера при шарде на world destroyer / leader call.
function ____exports.applyNagashSecondSkillShardInvulnerable(self, caster, ability, duration)
if not IsServer() then
return
end
if not HasShard(nil, caster) then
return
end
if duration <= 0 then
return
end
caster:AddNewModifier(caster, ability, ____exports.modifier_world_destroyer_shard_invulnerable.name, {duration = duration})
end
____exports.world_destroyer = __TS__Class()
local world_destroyer = ____exports.world_destroyer
world_destroyer.name = "world_destroyer"
world_destroyer.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua"
__TS__ClassExtends(world_destroyer, BaseAbility)
function world_destroyer.prototype.Precache(self, context)
PrecacheResource("sound", "soundevents/game_sounds_heroes/game_sounds_arc_warden.vsndevts", context)
end
function world_destroyer.prototype.OnSpellStart(self)
if not IsServer() then
return
end
local caster = self:GetCaster()
local playerId = caster:GetPlayerOwnerID()
local point = self:GetCursorPosition()
local casterLevel = caster:GetLevel()
local shardInvulnerableDuration = self:GetSpecialValueFor("shard_invulnerable_duration")
____exports.applyNagashSecondSkillShardInvulnerable(nil, caster, self, shardInvulnerableDuration > 0 and shardInvulnerableDuration or WORLD_DESTROYER_PREP_DURATION)
local radius = self:GetSpecialValueFor("radius")
local duration = self:GetSpecialValueFor("dominate_duration")
local soulEater = CreateUnitByName(
"npc_dota_nagash_soul_eater",
point,
true,
caster,
caster,
caster:GetTeamNumber()
)
if soulEater and IsValidEntity(soulEater) then
local countParticle
FindClearSpaceForUnit(soulEater, point, true)
local soulEaterMaxHealth = getWorldDestroyerSoulEaterMaxHealth(nil, self, caster)
soulEater:AddNewModifier(caster, self, ____exports.modifier_world_destroyer_soul_eater.name, {duration = WORLD_DESTROYER_PREP_DURATION + 0.15, max_health = soulEaterMaxHealth})
local prepParticle = ParticleManager:CreateParticle("particles/nagash_world_full.vpcf", PATTACH_ABSORIGIN_FOLLOW, soulEater)
ParticleManager:SetParticleControl(
prepParticle,
0,
soulEater:GetAbsOrigin()
)
local cancelled = false
local soulEaterIndex = soulEater:GetEntityIndex()
local killListener
killListener = ListenToGameEvent(
"entity_killed",
function(event)
local killed = EntIndexToHScript(event.entindex_killed)
if killed and IsValidEntity(killed) and killed:GetEntityIndex() == soulEaterIndex then
cancelled = true
do
pcall(function()
ParticleManager:DestroyParticle(prepParticle, false)
ParticleManager:ReleaseParticleIndex(prepParticle)
end)
end
do
pcall(function()
if countParticle ~= nil then
ParticleManager:DestroyParticle(countParticle, true)
ParticleManager:ReleaseParticleIndex(countParticle)
end
end)
end
do
pcall(function()
StopListeningToGameEvent(killListener)
end)
end
end
end,
nil
)
local function showCount(____, n)
do
pcall(function()
if countParticle ~= nil then
ParticleManager:DestroyParticle(countParticle, true)
ParticleManager:ReleaseParticleIndex(countParticle)
end
countParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_centaur/centaur_shard_buff_strength_counter_stack.vpcf", PATTACH_OVERHEAD_FOLLOW, soulEater)
ParticleManager:SetParticleControl(
countParticle,
2,
Vector(n, 0, 0)
)
ParticleManager:SetParticleControlEnt(
countParticle,
3,
soulEater,
PATTACH_OVERHEAD_FOLLOW,
nil,
soulEater:GetAbsOrigin(),
true
)
EmitSoundOn("General.ButtonClick", soulEater)
EmitSoundOn("Hero_ArcWarden.MagneticField", soulEater)
end)
end
end
showCount(nil, 3)
Timers:CreateTimer(
1,
function()
if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then
return
end
showCount(nil, 2)
end
)
Timers:CreateTimer(
2,
function()
if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then
return
end
showCount(nil, 1)
end
)
Timers:CreateTimer(
3,
function()
if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then
return
end
local origin = soulEater:GetAbsOrigin()
local burst = ParticleManager:CreateParticle("particles/units/heroes/hero_warlock/warlock_rain_of_chaos_start.vpcf", PATTACH_WORLDORIGIN, nil)
ParticleManager:SetParticleControl(burst, 0, origin)
ParticleManager:ReleaseParticleIndex(burst)
EmitSoundOnLocationWithCaster(origin, "Hero_Enchantress.EnchantCreep", caster)
local enemies = FindUnitsInRadius(
caster:GetTeamNumber(),
origin,
nil,
radius,
DOTA_UNIT_TARGET_TEAM_ENEMY,
DOTA_UNIT_TARGET_BASIC,
DOTA_UNIT_TARGET_FLAG_NOT_ANCIENTS + DOTA_UNIT_TARGET_FLAG_NOT_SUMMONED + DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO,
FIND_ANY_ORDER,
false
)
local processed = 0
for ____, unit in ipairs(enemies) do
do
if not unit:IsAlive() then
goto __continue26
end
if unit:IsConsideredHero() then
goto __continue26
end
if unit:IsAncient() then
goto __continue26
end
if __TS__StringStartsWith(
unit:GetUnitName(),
"npc_wave_boss"
) then
goto __continue26
end
if unit.GetLevel and unit:GetLevel() > casterLevel then
goto __continue26
end
local spawnPos = unit:GetAbsOrigin()
local spawnForward = unit:GetForwardVector()
local originalName = unit:GetUnitName()
local isBlockedByName = WORLD_DESTROYER_BLOCKED_UNITS:has(originalName)
local isBlockedByPrefix = __TS__ArraySome(
WORLD_DESTROYER_BLOCKED_PREFIXES,
function(____, prefix) return __TS__StringStartsWith(originalName, prefix) end
)
if isBlockedByName or isBlockedByPrefix then
goto __continue26
end
local copy = CreateUnitByName(
originalName,
spawnPos,
true,
caster,
caster,
caster:GetTeamNumber()
)
if copy and IsValidEntity(copy) then
copy:SetForwardVector(spawnForward)
FindClearSpaceForUnit(copy, spawnPos, true)
copy:SetOwner(caster)
copy:SetControllableByPlayer(playerId, true)
EmitSoundOn("Hero_Warlock.RainOfChaos", copy)
do
pcall(function()
local copyMaxHealth = unit:GetMaxHealth()
local copyHealth = unit:GetHealth()
copy:SetBaseDamageMin(unit:GetBaseDamageMin())
copy:SetBaseDamageMax(unit:GetBaseDamageMax())
copy:SetBaseMoveSpeed(unit:GetBaseMoveSpeed())
copy:SetBaseMaxHealth(copyMaxHealth)
copy:SetMaxHealth(copyMaxHealth)
copy:SetHealth(math.min(copyHealth, copyMaxHealth))
end)
end
if duration > 0 then
do
pcall(function()
copy:AddNewModifier(caster, self, "modifier_dominated", {duration = duration})
copy:AddNewModifier(caster, self, "modifier_dominated_bonus", {duration = duration})
end)
end
end
end
if unit:IsAlive() then
unit:Kill(self, unit)
end
processed = processed + 1
end
::__continue26::
end
do
pcall(function()
ParticleManager:DestroyParticle(prepParticle, false)
ParticleManager:ReleaseParticleIndex(prepParticle)
end)
end
do
pcall(function()
if countParticle ~= nil then
ParticleManager:DestroyParticle(countParticle, true)
ParticleManager:ReleaseParticleIndex(countParticle)
end
end)
end
do
pcall(function()
UTIL_Remove(soulEater)
end)
end
do
pcall(function()
StopListeningToGameEvent(killListener)
end)
end
end
)
end
end
function world_destroyer.prototype.GetAOERadius(self)
return self:GetSpecialValueFor("radius")
end
world_destroyer = __TS__Decorate(
world_destroyer,
world_destroyer,
{registerAbility(nil)},
{kind = "class", name = "world_destroyer"}
)
____exports.world_destroyer = world_destroyer
--- Высасыватель душ на фазе 3-2-1: фиксирует целевой пул HP (без отката к ~150 при маг. уроне).
____exports.modifier_world_destroyer_soul_eater = __TS__Class()
local modifier_world_destroyer_soul_eater = ____exports.modifier_world_destroyer_soul_eater
modifier_world_destroyer_soul_eater.name = "modifier_world_destroyer_soul_eater"
modifier_world_destroyer_soul_eater.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua"
__TS__ClassExtends(modifier_world_destroyer_soul_eater, BaseModifier)
function modifier_world_destroyer_soul_eater.prototype.____constructor(self, ...)
BaseModifier.prototype.____constructor(self, ...)
self.desiredMaxHealth = 0
end
function modifier_world_destroyer_soul_eater.prototype.IsHidden(self)
return true
end
function modifier_world_destroyer_soul_eater.prototype.IsPurgable(self)
return false
end
function modifier_world_destroyer_soul_eater.prototype.OnCreated(self, kv)
if not IsServer() then
return
end
self.desiredMaxHealth = tonumber(tostring(kv.max_health or 0)) or 0
if self.desiredMaxHealth <= 0 then
return
end
self:applySoulEaterHealth(true)
Timers:CreateTimer(
0,
function()
if not IsValidEntity(self:GetParent()) or not self:GetParent():IsAlive() then
return nil
end
self:applySoulEaterHealth(false)
return nil
end
)
end
function modifier_world_destroyer_soul_eater.prototype.applySoulEaterHealth(self, healToFull)
local parent = self:GetParent()
if not parent or not parent:IsAlive() or self.desiredMaxHealth <= 0 then
return
end
parent:SetBaseMaxHealth(self.desiredMaxHealth)
parent:SetMaxHealth(self.desiredMaxHealth)
if healToFull then
parent:SetHealth(self.desiredMaxHealth)
return
end
local current = parent:GetHealth()
if current > self.desiredMaxHealth then
parent:SetHealth(self.desiredMaxHealth)
end
end
function modifier_world_destroyer_soul_eater.prototype.DeclareFunctions(self)
return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS}
end
function modifier_world_destroyer_soul_eater.prototype.GetModifierExtraHealthBonus(self)
if self.desiredMaxHealth <= 0 then
return 0
end
local base = self:GetParent():GetBaseMaxHealth()
return math.max(0, self.desiredMaxHealth - base)
end
modifier_world_destroyer_soul_eater = __TS__Decorate(
modifier_world_destroyer_soul_eater,
modifier_world_destroyer_soul_eater,
{registerModifier(nil)},
{kind = "class", name = "modifier_world_destroyer_soul_eater"}
)
____exports.modifier_world_destroyer_soul_eater = modifier_world_destroyer_soul_eater
____exports.modifier_world_destroyer_shard_invulnerable = __TS__Class()
local modifier_world_destroyer_shard_invulnerable = ____exports.modifier_world_destroyer_shard_invulnerable
modifier_world_destroyer_shard_invulnerable.name = "modifier_world_destroyer_shard_invulnerable"
modifier_world_destroyer_shard_invulnerable.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua"
__TS__ClassExtends(modifier_world_destroyer_shard_invulnerable, BaseModifier)
function modifier_world_destroyer_shard_invulnerable.prototype.IsHidden(self)
return true
end
function modifier_world_destroyer_shard_invulnerable.prototype.IsPurgable(self)
return false
end
function modifier_world_destroyer_shard_invulnerable.prototype.IsDebuff(self)
return false
end
function modifier_world_destroyer_shard_invulnerable.prototype.RemoveOnDeath(self)
return true
end
function modifier_world_destroyer_shard_invulnerable.prototype.CheckState(self)
return {[MODIFIER_STATE_INVULNERABLE] = true}
end
modifier_world_destroyer_shard_invulnerable = __TS__Decorate(
modifier_world_destroyer_shard_invulnerable,
modifier_world_destroyer_shard_invulnerable,
{registerModifier(nil)},
{kind = "class", name = "modifier_world_destroyer_shard_invulnerable"}
)
____exports.modifier_world_destroyer_shard_invulnerable = modifier_world_destroyer_shard_invulnerable
____exports.modifier_dominated_bonus = __TS__Class()
local modifier_dominated_bonus = ____exports.modifier_dominated_bonus
modifier_dominated_bonus.name = "modifier_dominated_bonus"
modifier_dominated_bonus.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua"
__TS__ClassExtends(modifier_dominated_bonus, BaseModifier)
function modifier_dominated_bonus.prototype.IsHidden(self)
return true
end
function modifier_dominated_bonus.prototype.IsPurgable(self)
return false
end
function modifier_dominated_bonus.prototype.RemoveOnDeath(self)
return false
end
function modifier_dominated_bonus.prototype.GetStatusEffectName(self)
return "particles/events/crownfall/survivors/status/status_effect_burn.vpcf"
end
function modifier_dominated_bonus.prototype.GetEffectName(self)
return "particles/econ/items/omniknight/omni_crimson_witness_2021/omniknight_crimson_witness_2021_degen_aura_debuff.vpcf"
end
function modifier_dominated_bonus.prototype.StatusEffectPriority(self)
return 100
end
function modifier_dominated_bonus.prototype.DeclareFunctions(self)
return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS}
end
function modifier_dominated_bonus.prototype.CheckState(self)
return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true}
end
function modifier_dominated_bonus.prototype.OnDestroy(self)
if IsClient() then
return
end
local caster = self:GetCaster()
local tokenModifier = caster:AddNewModifier(
caster,
self:GetAbility(),
"modifier_leader_token",
{}
)
if tokenModifier ~= nil then
tokenModifier:SetStackCount(tokenModifier:GetStackCount() + 1)
end
self:GetParent():Kill(
self:GetAbility(),
self:GetParent()
)
end
function modifier_dominated_bonus.prototype.GetModifierPreAttack_BonusDamage(self)
local ability = self:GetAbility()
local level = ability and math.max(
1,
ability:GetLevel()
) or 1
return self:GetAbility():GetSpecialValueFor("bonus_damage")
end
function modifier_dominated_bonus.prototype.GetModifierAttackSpeedBonus_Constant(self)
local ability = self:GetAbility()
local level = ability and math.max(
1,
ability:GetLevel()
) or 1
return self:GetAbility():GetSpecialValueFor("bonus_attack_speed")
end
function modifier_dominated_bonus.prototype.GetModifierPhysicalArmorBonus(self, event)
local ability = self:GetAbility()
local level = ability and math.max(
1,
ability:GetLevel()
) or 1
return self:GetAbility():GetSpecialValueFor("bonus_armor")
end
modifier_dominated_bonus = __TS__Decorate(
modifier_dominated_bonus,
modifier_dominated_bonus,
{registerModifier(nil)},
{kind = "class", name = "modifier_dominated_bonus"}
)
____exports.modifier_dominated_bonus = modifier_dominated_bonus
return ____exports