463 lines
20 KiB
Lua
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
|