845 lines
28 KiB
Lua
845 lines
28 KiB
Lua
local ____lualib = require("lualib_bundle")
|
|
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
|
|
local __TS__ArraySort = ____lualib.__TS__ArraySort
|
|
local Map = ____lualib.Map
|
|
local __TS__New = ____lualib.__TS__New
|
|
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
|
|
local __TS__Class = ____lualib.__TS__Class
|
|
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
|
|
local __TS__Decorate = ____lualib.__TS__Decorate
|
|
local ____exports = {}
|
|
local clampToxinPoolRadius, toxinIsValidUnit, isToxinPoolThinker, toxinIsValidAbility, toxinPoolsOverlap, getToxinPoolRadiusForThinker, getToxinPoolMergeStackForThinker, getToxinDamagePerTickForThinker, getToxinPoolRemainingDuration, toxinDistSqHoriz, buildToxinOverlapCluster, computeMergedPoolStats, toxinSortDedupeEntIndices, toxinEnqueuePoolMergeForThinker, toxinDrainToxinMergeQueueFrame, toxinTryResolvePoolMergeForThinkerIndex, TOXIN_POOL_MODIFIER_NAME, TOXIN_MERGE_SCAN_RADIUS, TOXIN_MERGE_OVERLAP_EPSILON, TOXIN_THINKER_CLASS, TOXIN_POOL_MAX_RADIUS, TOXIN_MAX_MERGE_SCAN_THINKERS, toxinMergeQueue, toxinMergeDrainScheduled, toxinIsDrainingMerge
|
|
local ____difficulty_manager = require("difficulty_manager")
|
|
local Difficulty = ____difficulty_manager.Difficulty
|
|
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
|
|
local ____creep_render_color = require("utils.creep_render_color")
|
|
local trySetIntrinsicCreepRenderColor = ____creep_render_color.trySetIntrinsicCreepRenderColor
|
|
local ____entity_radius = require("utils.entity_radius")
|
|
local findAllByClassnameInRadius = ____entity_radius.findAllByClassnameInRadius
|
|
function clampToxinPoolRadius(self, r)
|
|
return math.max(
|
|
1,
|
|
math.min(r, TOXIN_POOL_MAX_RADIUS)
|
|
)
|
|
end
|
|
function toxinIsValidUnit(self, unit)
|
|
return unit ~= nil and unit ~= nil and not unit:IsNull() and IsValidEntity(unit)
|
|
end
|
|
function isToxinPoolThinker(self, unit)
|
|
return toxinIsValidUnit(nil, unit) and unit:GetClassname() == TOXIN_THINKER_CLASS
|
|
end
|
|
function toxinIsValidAbility(self, ab)
|
|
return ab ~= nil and ab ~= nil and not ab:IsNull() and IsValidEntity(ab)
|
|
end
|
|
function toxinPoolsOverlap(self, centerA, radiusA, centerB, radiusB)
|
|
local dx = centerA.x - centerB.x
|
|
local dy = centerA.y - centerB.y
|
|
local dist = math.sqrt(dx * dx + dy * dy)
|
|
return dist <= radiusA + radiusB + TOXIN_MERGE_OVERLAP_EPSILON
|
|
end
|
|
function getToxinPoolRadiusForThinker(self, thinker, poolMod, ability)
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return 0
|
|
end
|
|
local raw
|
|
if poolMod and poolMod.effectiveRadius > 0 then
|
|
raw = poolMod.effectiveRadius
|
|
else
|
|
raw = ability:GetSpecialValueFor("radius")
|
|
end
|
|
return clampToxinPoolRadius(nil, raw)
|
|
end
|
|
function getToxinPoolMergeStackForThinker(self, poolMod, _ability)
|
|
if poolMod ~= nil and poolMod.poolMergeStackCount > 0 then
|
|
return math.floor(poolMod.poolMergeStackCount)
|
|
end
|
|
return 1
|
|
end
|
|
function getToxinDamagePerTickForThinker(self, poolMod, ability)
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return 0
|
|
end
|
|
if poolMod and poolMod.damagePerTick > 0 then
|
|
return poolMod.damagePerTick
|
|
end
|
|
return ability:GetSpecialValueFor("damage") * 0.33
|
|
end
|
|
function getToxinPoolRemainingDuration(self, poolMod, fallbackDuration)
|
|
local getter = poolMod.GetRemainingTime
|
|
if getter ~= nil then
|
|
local v = getter(poolMod)
|
|
if type(v) == "number" and __TS__NumberIsFinite(v) then
|
|
return math.max(0, v)
|
|
end
|
|
end
|
|
return math.max(0, fallbackDuration)
|
|
end
|
|
function toxinDistSqHoriz(self, ax, bx)
|
|
local dx = ax.x - bx.x
|
|
local dy = ax.y - bx.y
|
|
return dx * dx + dy * dy
|
|
end
|
|
function buildToxinOverlapCluster(self, seed, ability)
|
|
if not toxinIsValidUnit(nil, seed) or not toxinIsValidAbility(nil, ability) then
|
|
return {}
|
|
end
|
|
local seedTeam = seed:GetTeamNumber()
|
|
local origin = seed:GetAbsOrigin()
|
|
local raw = findAllByClassnameInRadius("npc_dota_thinker", origin, TOXIN_MERGE_SCAN_RADIUS)
|
|
local toxinThinkers = {}
|
|
for ____, ent in ipairs(raw) do
|
|
do
|
|
local npc = ent
|
|
if not toxinIsValidUnit(nil, npc) then
|
|
goto __continue22
|
|
end
|
|
if npc:GetTeamNumber() ~= seedTeam then
|
|
goto __continue22
|
|
end
|
|
if not npc:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) then
|
|
goto __continue22
|
|
end
|
|
toxinThinkers[#toxinThinkers + 1] = npc
|
|
end
|
|
::__continue22::
|
|
end
|
|
if #toxinThinkers > TOXIN_MAX_MERGE_SCAN_THINKERS then
|
|
__TS__ArraySort(
|
|
toxinThinkers,
|
|
function(____, a, b) return toxinDistSqHoriz(
|
|
nil,
|
|
a:GetAbsOrigin(),
|
|
origin
|
|
) - toxinDistSqHoriz(
|
|
nil,
|
|
b:GetAbsOrigin(),
|
|
origin
|
|
) end
|
|
)
|
|
while #toxinThinkers > TOXIN_MAX_MERGE_SCAN_THINKERS do
|
|
table.remove(toxinThinkers)
|
|
end
|
|
end
|
|
local cluster = {}
|
|
local visited = __TS__New(Map)
|
|
local queue = {seed}
|
|
while #queue > 0 do
|
|
do
|
|
local cur = table.remove(queue)
|
|
if not toxinIsValidUnit(nil, cur) then
|
|
goto __continue30
|
|
end
|
|
local idx = cur:GetEntityIndex()
|
|
if visited:get(idx) then
|
|
goto __continue30
|
|
end
|
|
visited:set(idx, true)
|
|
cluster[#cluster + 1] = cur
|
|
local curMod = cur:FindModifierByName(TOXIN_POOL_MODIFIER_NAME)
|
|
local rCur = getToxinPoolRadiusForThinker(nil, cur, curMod, ability)
|
|
local pCur = cur:GetAbsOrigin()
|
|
for ____, other in ipairs(toxinThinkers) do
|
|
do
|
|
if not toxinIsValidUnit(nil, other) then
|
|
goto __continue33
|
|
end
|
|
local oIdx = other:GetEntityIndex()
|
|
if visited:get(oIdx) then
|
|
goto __continue33
|
|
end
|
|
local oMod = other:FindModifierByName(TOXIN_POOL_MODIFIER_NAME)
|
|
local rO = getToxinPoolRadiusForThinker(nil, other, oMod, ability)
|
|
if toxinPoolsOverlap(
|
|
nil,
|
|
pCur,
|
|
rCur,
|
|
other:GetAbsOrigin(),
|
|
rO
|
|
) then
|
|
queue[#queue + 1] = other
|
|
end
|
|
end
|
|
::__continue33::
|
|
end
|
|
end
|
|
::__continue30::
|
|
end
|
|
return cluster
|
|
end
|
|
function computeMergedPoolStats(self, cluster, ability)
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return {
|
|
centroid = Vector(0, 0, 0),
|
|
damagePerTick = 0,
|
|
radius = 1,
|
|
duration = 0.05,
|
|
mergeStackCount = 1
|
|
}
|
|
end
|
|
local valid = __TS__ArrayFilter(
|
|
cluster,
|
|
function(____, t) return toxinIsValidUnit(nil, t) end
|
|
)
|
|
local n = #valid
|
|
if n <= 0 then
|
|
local baseRadius = ability:GetSpecialValueFor("radius")
|
|
local baseDur = ability:GetSpecialValueFor("duration")
|
|
return {
|
|
centroid = Vector(0, 0, 0),
|
|
damagePerTick = ability:GetSpecialValueFor("damage") * 0.33,
|
|
radius = clampToxinPoolRadius(nil, baseRadius),
|
|
duration = math.max(0.05, baseDur),
|
|
mergeStackCount = 1
|
|
}
|
|
end
|
|
local baseDuration = ability:GetSpecialValueFor("duration")
|
|
local radiusBonusPerMerge = math.max(
|
|
0,
|
|
ability:GetSpecialValueFor("merge_radius_bonus")
|
|
)
|
|
local durationBonusPerMerge = math.max(
|
|
0,
|
|
ability:GetSpecialValueFor("merge_duration_bonus")
|
|
)
|
|
local sumR2 = 0
|
|
local sumDmg = 0
|
|
local sumStacks = 0
|
|
local maxRem = 0
|
|
local sx = 0
|
|
local sy = 0
|
|
local sz = 0
|
|
for ____, t in ipairs(valid) do
|
|
local mod = t:FindModifierByName(TOXIN_POOL_MODIFIER_NAME)
|
|
local r = getToxinPoolRadiusForThinker(nil, t, mod, ability)
|
|
sumR2 = sumR2 + r * r
|
|
sumDmg = sumDmg + getToxinDamagePerTickForThinker(nil, mod, ability)
|
|
sumStacks = sumStacks + getToxinPoolMergeStackForThinker(nil, mod, ability)
|
|
local p = t:GetAbsOrigin()
|
|
sx = sx + p.x
|
|
sy = sy + p.y
|
|
sz = sz + p.z
|
|
if mod then
|
|
maxRem = math.max(
|
|
maxRem,
|
|
getToxinPoolRemainingDuration(nil, mod, baseDuration)
|
|
)
|
|
else
|
|
maxRem = math.max(maxRem, baseDuration)
|
|
end
|
|
end
|
|
local mergedRadiusUncapped = math.max(
|
|
1,
|
|
math.sqrt(sumR2) + math.max(0, n - 1) * radiusBonusPerMerge
|
|
)
|
|
local mergedRadius = clampToxinPoolRadius(nil, mergedRadiusUncapped)
|
|
local mergedDuration = math.max(
|
|
0.05,
|
|
maxRem + math.max(0, n - 1) * durationBonusPerMerge
|
|
)
|
|
return {
|
|
centroid = Vector(sx / n, sy / n, sz / n),
|
|
damagePerTick = sumDmg,
|
|
radius = mergedRadius,
|
|
duration = mergedDuration,
|
|
mergeStackCount = math.max(1, sumStacks)
|
|
}
|
|
end
|
|
function toxinSortDedupeEntIndices(self, arr)
|
|
if #arr <= 1 then
|
|
return arr
|
|
end
|
|
local sorted = {unpack(arr)}
|
|
__TS__ArraySort(
|
|
sorted,
|
|
function(____, a, b) return a - b end
|
|
)
|
|
local out = {}
|
|
local prev = -2147483648
|
|
for ____, x in ipairs(sorted) do
|
|
local n = x
|
|
if n ~= prev then
|
|
out[#out + 1] = x
|
|
prev = n
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
function toxinEnqueuePoolMergeForThinker(self, entIndex)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
toxinMergeQueue[#toxinMergeQueue + 1] = entIndex
|
|
if toxinMergeDrainScheduled or toxinIsDrainingMerge then
|
|
return
|
|
end
|
|
toxinMergeDrainScheduled = true
|
|
Timers:CreateTimer(0, toxinDrainToxinMergeQueueFrame)
|
|
end
|
|
function toxinDrainToxinMergeQueueFrame(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
toxinIsDrainingMerge = true
|
|
toxinMergeDrainScheduled = false
|
|
local batch = toxinMergeQueue
|
|
toxinMergeQueue = {}
|
|
local uniq = toxinSortDedupeEntIndices(nil, batch)
|
|
for ____, idx in ipairs(uniq) do
|
|
toxinTryResolvePoolMergeForThinkerIndex(nil, idx)
|
|
end
|
|
toxinIsDrainingMerge = false
|
|
if #toxinMergeQueue > 0 then
|
|
toxinMergeDrainScheduled = true
|
|
Timers:CreateTimer(0, toxinDrainToxinMergeQueueFrame)
|
|
end
|
|
end
|
|
function toxinTryResolvePoolMergeForThinkerIndex(self, entIndex)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local npc = EntIndexToHScript(entIndex)
|
|
if not isToxinPoolThinker(nil, npc) then
|
|
return
|
|
end
|
|
local buff = npc:FindModifierByName(TOXIN_POOL_MODIFIER_NAME)
|
|
if not buff then
|
|
return
|
|
end
|
|
local poolMod = buff
|
|
local abilityRaw = buff:GetAbility()
|
|
if not toxinIsValidAbility(nil, abilityRaw) then
|
|
return
|
|
end
|
|
local ability = abilityRaw
|
|
local caster = buff:GetCaster()
|
|
if not toxinIsValidUnit(nil, caster) then
|
|
return
|
|
end
|
|
local cluster = buildToxinOverlapCluster(nil, npc, ability)
|
|
if #cluster < 2 then
|
|
poolMod:initializeSinglePoolFromAbility()
|
|
return
|
|
end
|
|
local mergeLeader
|
|
local minIdx = 2147483647
|
|
for ____, t in ipairs(cluster) do
|
|
do
|
|
if not toxinIsValidUnit(nil, t) then
|
|
goto __continue141
|
|
end
|
|
local ei = t:GetEntityIndex()
|
|
if ei < minIdx then
|
|
minIdx = ei
|
|
mergeLeader = t
|
|
end
|
|
end
|
|
::__continue141::
|
|
end
|
|
if not mergeLeader or not toxinIsValidUnit(nil, mergeLeader) then
|
|
return
|
|
end
|
|
if npc:GetEntityIndex() ~= mergeLeader:GetEntityIndex() then
|
|
toxinEnqueuePoolMergeForThinker(
|
|
nil,
|
|
mergeLeader:GetEntityIndex()
|
|
)
|
|
return
|
|
end
|
|
local merged = computeMergedPoolStats(nil, cluster, ability)
|
|
local spawnParams = {duration = merged.duration, merged_damage_per_tick = merged.damagePerTick, merged_radius = merged.radius, merged_stack_count = merged.mergeStackCount}
|
|
for ____, t in ipairs(cluster) do
|
|
if toxinIsValidUnit(nil, t) then
|
|
UTIL_Remove(t)
|
|
end
|
|
end
|
|
if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) then
|
|
return
|
|
end
|
|
CreateModifierThinker(
|
|
caster,
|
|
ability,
|
|
TOXIN_POOL_MODIFIER_NAME,
|
|
spawnParams,
|
|
merged.centroid,
|
|
caster:GetTeamNumber(),
|
|
false
|
|
)
|
|
end
|
|
TOXIN_POOL_MODIFIER_NAME = "modifier_spider_nethertoxin_lua"
|
|
TOXIN_MERGE_SCAN_RADIUS = 2400
|
|
TOXIN_MERGE_OVERLAP_EPSILON = 12
|
|
TOXIN_THINKER_CLASS = "npc_dota_thinker"
|
|
TOXIN_POOL_MAX_RADIUS = 900
|
|
TOXIN_MAX_MERGE_SCAN_THINKERS = 220
|
|
toxinMergeQueue = {}
|
|
toxinMergeDrainScheduled = false
|
|
toxinIsDrainingMerge = false
|
|
____exports.toxin = __TS__Class()
|
|
local toxin = ____exports.toxin
|
|
toxin.name = "toxin"
|
|
toxin.____file_path = "scripts/vscripts/abilities/creep/toxin.lua"
|
|
__TS__ClassExtends(toxin, BaseAbility)
|
|
function toxin.prototype.Precache(self, context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_alchemist/alchemist_acid_spray.vpcf", context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_viper/viper_nethertoxin.vpcf", context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_viper/viper_nethertoxin_debuff.vpcf", context)
|
|
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_broodmother.vsndevts", context)
|
|
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_viper.vsndevts", context)
|
|
end
|
|
function toxin.prototype.GetIntrinsicModifierName(self)
|
|
return "modifier_spider_toxin_death_listener"
|
|
end
|
|
toxin = __TS__Decorate(
|
|
toxin,
|
|
toxin,
|
|
{registerAbility(nil)},
|
|
{kind = "class", name = "toxin"}
|
|
)
|
|
____exports.toxin = toxin
|
|
____exports.modifier_spider_toxin_death_listener = __TS__Class()
|
|
local modifier_spider_toxin_death_listener = ____exports.modifier_spider_toxin_death_listener
|
|
modifier_spider_toxin_death_listener.name = "modifier_spider_toxin_death_listener"
|
|
modifier_spider_toxin_death_listener.____file_path = "scripts/vscripts/abilities/creep/toxin.lua"
|
|
__TS__ClassExtends(modifier_spider_toxin_death_listener, BaseModifier)
|
|
function modifier_spider_toxin_death_listener.prototype.IsHidden(self)
|
|
return true
|
|
end
|
|
function modifier_spider_toxin_death_listener.prototype.IsPurgable(self)
|
|
return false
|
|
end
|
|
function modifier_spider_toxin_death_listener.prototype.OnCreated(self, _params)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
if not toxinIsValidUnit(nil, parent) then
|
|
return
|
|
end
|
|
trySetIntrinsicCreepRenderColor(
|
|
nil,
|
|
parent,
|
|
51,
|
|
102,
|
|
0
|
|
)
|
|
end
|
|
function modifier_spider_toxin_death_listener.prototype.DeclareFunctions(self)
|
|
return {MODIFIER_EVENT_ON_DEATH}
|
|
end
|
|
function modifier_spider_toxin_death_listener.prototype.OnDeath(self, event)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
if event.unit ~= self:GetParent() then
|
|
return
|
|
end
|
|
local ability = self:GetAbility()
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return
|
|
end
|
|
local caster = self:GetCaster()
|
|
if not toxinIsValidUnit(nil, caster) then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
local spawnOrigin = parent:GetAbsOrigin()
|
|
local duration = ability:GetSpecialValueFor("duration")
|
|
local radius = clampToxinPoolRadius(
|
|
nil,
|
|
ability:GetSpecialValueFor("radius")
|
|
)
|
|
local splashPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_alchemist/alchemist_acid_spray.vpcf", PATTACH_WORLDORIGIN, nil)
|
|
ParticleManager:SetParticleControl(splashPfx, 0, spawnOrigin)
|
|
ParticleManager:SetParticleControl(
|
|
splashPfx,
|
|
1,
|
|
Vector(radius, 1, 1)
|
|
)
|
|
ParticleManager:SetParticleControl(
|
|
splashPfx,
|
|
15,
|
|
Vector(255, 153, 102)
|
|
)
|
|
ParticleManager:SetParticleControl(
|
|
splashPfx,
|
|
16,
|
|
Vector(1, 0, 0)
|
|
)
|
|
Timers:CreateTimer(
|
|
4,
|
|
function()
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
ParticleManager:DestroyParticle(splashPfx, false)
|
|
ParticleManager:ReleaseParticleIndex(splashPfx)
|
|
end
|
|
)
|
|
EmitSoundOn("Hero_Broodmother.SpawnSpiderlings", parent)
|
|
CreateModifierThinker(
|
|
caster,
|
|
ability,
|
|
TOXIN_POOL_MODIFIER_NAME,
|
|
{duration = duration},
|
|
spawnOrigin,
|
|
caster:GetTeamNumber(),
|
|
false
|
|
)
|
|
end
|
|
modifier_spider_toxin_death_listener = __TS__Decorate(
|
|
modifier_spider_toxin_death_listener,
|
|
modifier_spider_toxin_death_listener,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_spider_toxin_death_listener"}
|
|
)
|
|
____exports.modifier_spider_toxin_death_listener = modifier_spider_toxin_death_listener
|
|
____exports.modifier_spider_nethertoxin_lua = __TS__Class()
|
|
local modifier_spider_nethertoxin_lua = ____exports.modifier_spider_nethertoxin_lua
|
|
modifier_spider_nethertoxin_lua.name = "modifier_spider_nethertoxin_lua"
|
|
modifier_spider_nethertoxin_lua.____file_path = "scripts/vscripts/abilities/creep/toxin.lua"
|
|
__TS__ClassExtends(modifier_spider_nethertoxin_lua, BaseModifier)
|
|
function modifier_spider_nethertoxin_lua.prototype.____constructor(self, ...)
|
|
BaseModifier.prototype.____constructor(self, ...)
|
|
self.effectiveRadius = 0
|
|
self.damagePerTick = 0
|
|
self.poolMergeStackCount = 0
|
|
self.auraPenaltyStack = 1
|
|
self.damageTable = {}
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetTexture(self)
|
|
return "viper_nethertoxin"
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.IsHidden(self)
|
|
return false
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.IsDebuff(self)
|
|
return true
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.IsStunDebuff(self)
|
|
return false
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.IsPurgable(self)
|
|
return false
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.initializeAuraVictimDebuff(self)
|
|
local ability = self:GetAbility()
|
|
if toxinIsValidAbility(nil, ability) then
|
|
self:SetDuration(
|
|
math.max(
|
|
0.05,
|
|
self:GetAuraDuration()
|
|
),
|
|
true
|
|
)
|
|
end
|
|
self.effectiveRadius = 0
|
|
self.damagePerTick = 0
|
|
self.poolMergeStackCount = 0
|
|
if IsServer() then
|
|
self:syncAuraPenaltyFromCasterThinker()
|
|
self:SetStackCount(math.max(
|
|
1,
|
|
math.floor(self.auraPenaltyStack)
|
|
))
|
|
end
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.syncAuraPenaltyFromCasterThinker(self)
|
|
local parent = self:GetParent()
|
|
if isToxinPoolThinker(nil, parent) then
|
|
return
|
|
end
|
|
local source = self:GetCaster()
|
|
if not toxinIsValidUnit(nil, source) then
|
|
return
|
|
end
|
|
local srcMod = source:FindModifierByName(TOXIN_POOL_MODIFIER_NAME)
|
|
if srcMod ~= nil and srcMod.poolMergeStackCount > 0 then
|
|
self.auraPenaltyStack = math.floor(srcMod.poolMergeStackCount)
|
|
else
|
|
self.auraPenaltyStack = math.max(1, self.auraPenaltyStack)
|
|
end
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.initializeSinglePoolFromAbility(self)
|
|
local ability = self:GetAbility()
|
|
local caster = self:GetCaster()
|
|
local parent = self:GetParent()
|
|
if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) or not toxinIsValidUnit(nil, parent) then
|
|
return
|
|
end
|
|
local baseDamage = ability:GetSpecialValueFor("damage")
|
|
local baseRadius = ability:GetSpecialValueFor("radius")
|
|
local baseDuration = ability:GetSpecialValueFor("duration")
|
|
self.damagePerTick = (baseDamage + self:GetCaster():GetAttackDamage() * 0.15) * 0.33
|
|
local totaldamage = self:GetParent():IsRangedAttacker() and self.damagePerTick or self.damagePerTick * 0.25
|
|
self.effectiveRadius = clampToxinPoolRadius(nil, baseRadius)
|
|
self.poolMergeStackCount = 1
|
|
self:SetDuration(
|
|
math.max(0.05, baseDuration),
|
|
true
|
|
)
|
|
self.damageTable = {
|
|
victim = caster,
|
|
attacker = caster,
|
|
damage = totaldamage,
|
|
damage_type = ability:GetAbilityDamageType(),
|
|
ability = ability
|
|
}
|
|
self:StartIntervalThink(0.33)
|
|
self:PlayEffects()
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.initializeMergedPoolFromParams(self, params)
|
|
local ability = self:GetAbility()
|
|
local caster = self:GetCaster()
|
|
local parent = self:GetParent()
|
|
if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) or not toxinIsValidUnit(nil, parent) then
|
|
return
|
|
end
|
|
self.damagePerTick = params.merged_damage_per_tick
|
|
self.effectiveRadius = clampToxinPoolRadius(nil, params.merged_radius)
|
|
self.poolMergeStackCount = math.max(
|
|
1,
|
|
math.floor(params.merged_stack_count or 1)
|
|
)
|
|
self:SetDuration(
|
|
math.max(0.05, params.duration),
|
|
true
|
|
)
|
|
self.damageTable = {
|
|
victim = caster,
|
|
attacker = caster,
|
|
damage = self.damagePerTick,
|
|
damage_type = ability:GetAbilityDamageType(),
|
|
ability = ability
|
|
}
|
|
self:StartIntervalThink(0.33)
|
|
self:PlayEffects()
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.OnCreated(self, params)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
if not isToxinPoolThinker(nil, parent) then
|
|
self:initializeAuraVictimDebuff()
|
|
return
|
|
end
|
|
local p = params
|
|
if p.merged_damage_per_tick ~= nil and p.merged_radius ~= nil and p.duration ~= nil then
|
|
self:initializeMergedPoolFromParams(p)
|
|
return
|
|
end
|
|
toxinEnqueuePoolMergeForThinker(
|
|
nil,
|
|
parent:GetEntityIndex()
|
|
)
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.OnRefresh(self, _params)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local ability = self:GetAbility()
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return
|
|
end
|
|
if not isToxinPoolThinker(
|
|
nil,
|
|
self:GetParent()
|
|
) then
|
|
self:syncAuraPenaltyFromCasterThinker()
|
|
self:SetStackCount(math.max(
|
|
1,
|
|
math.floor(self.auraPenaltyStack)
|
|
))
|
|
return
|
|
end
|
|
self.damageTable.damage = self.damagePerTick
|
|
self.damageTable.damage_type = ability:GetAbilityDamageType()
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.DeclareFunctions(self)
|
|
return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS}
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetModifierPhysicalArmorBonus(self)
|
|
local parent = self:GetParent()
|
|
if isToxinPoolThinker(nil, parent) then
|
|
return 0
|
|
end
|
|
local ability = self:GetAbility()
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return 0
|
|
end
|
|
local per = math.max(
|
|
0,
|
|
ability:GetSpecialValueFor("armor_magic_reduce_per_stack")
|
|
)
|
|
local scale = Difficulty:getNpcStatScale()
|
|
return -per * math.max(
|
|
1,
|
|
math.floor(self.auraPenaltyStack)
|
|
) * scale
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetModifierMagicalResistanceBonus(self)
|
|
local parent = self:GetParent()
|
|
if isToxinPoolThinker(nil, parent) then
|
|
return 0
|
|
end
|
|
local ability = self:GetAbility()
|
|
if not toxinIsValidAbility(nil, ability) then
|
|
return 0
|
|
end
|
|
local per = math.max(
|
|
0,
|
|
ability:GetSpecialValueFor("armor_magic_reduce_per_stack")
|
|
)
|
|
local scale = Difficulty:getNpcStatScale()
|
|
return -per * math.max(
|
|
1,
|
|
math.floor(self.auraPenaltyStack)
|
|
) * scale
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.OnDestroy(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
if not isToxinPoolThinker(nil, parent) then
|
|
return
|
|
end
|
|
local entIndex = parent:GetEntityIndex()
|
|
Timers:CreateTimer(
|
|
0,
|
|
function()
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local npc = EntIndexToHScript(entIndex)
|
|
if not toxinIsValidUnit(nil, npc) then
|
|
return
|
|
end
|
|
if npc:GetClassname() ~= TOXIN_THINKER_CLASS then
|
|
return
|
|
end
|
|
UTIL_Remove(npc)
|
|
end
|
|
)
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.CheckState(self)
|
|
return {[MODIFIER_STATE_PASSIVES_DISABLED] = true}
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.OnIntervalThink(self)
|
|
local parent = self:GetParent()
|
|
if not isToxinPoolThinker(nil, parent) then
|
|
return
|
|
end
|
|
local caster = self:GetCaster()
|
|
local ability = self:GetAbility()
|
|
if not toxinIsValidUnit(nil, caster) or not toxinIsValidAbility(nil, ability) then
|
|
return
|
|
end
|
|
local units = FindUnitsInRadius(
|
|
caster:GetTeamNumber(),
|
|
parent:GetAbsOrigin(),
|
|
nil,
|
|
self.effectiveRadius,
|
|
self:GetAuraSearchTeam(),
|
|
self:GetAuraSearchType(),
|
|
DOTA_UNIT_TARGET_FLAG_NONE,
|
|
FIND_ANY_ORDER,
|
|
false
|
|
)
|
|
self.damageTable.attacker = caster
|
|
self.damageTable.ability = ability
|
|
self.damageTable.damage_type = ability:GetAbilityDamageType()
|
|
local damagedAny = false
|
|
for ____, unit in ipairs(units) do
|
|
do
|
|
if not toxinIsValidUnit(nil, unit) or not unit:IsAlive() then
|
|
goto __continue104
|
|
end
|
|
self.damageTable.victim = unit
|
|
ApplyDamage(self.damageTable)
|
|
damagedAny = true
|
|
end
|
|
::__continue104::
|
|
end
|
|
if damagedAny then
|
|
EmitSoundOn("Hero_Viper.NetherToxin.Damage", parent)
|
|
end
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.IsAura(self)
|
|
return isToxinPoolThinker(
|
|
nil,
|
|
self:GetParent()
|
|
)
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetModifierAura(self)
|
|
return TOXIN_POOL_MODIFIER_NAME
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetAuraRadius(self)
|
|
local ability = self:GetAbility()
|
|
if self.effectiveRadius > 0 then
|
|
return self.effectiveRadius
|
|
end
|
|
if toxinIsValidAbility(nil, ability) then
|
|
return clampToxinPoolRadius(
|
|
nil,
|
|
ability:GetSpecialValueFor("radius")
|
|
)
|
|
end
|
|
return 0
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetAuraDuration(self)
|
|
return 0.25
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetAuraSearchTeam(self)
|
|
return DOTA_UNIT_TARGET_TEAM_ENEMY
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetAuraSearchType(self)
|
|
return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetEffectName(self)
|
|
return "particles/units/heroes/hero_viper/viper_nethertoxin_debuff.vpcf"
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.GetEffectAttachType(self)
|
|
return PATTACH_ABSORIGIN_FOLLOW
|
|
end
|
|
function modifier_spider_nethertoxin_lua.prototype.PlayEffects(self)
|
|
local parent = self:GetParent()
|
|
if not toxinIsValidUnit(nil, parent) then
|
|
return
|
|
end
|
|
local particle_cast = "particles/units/heroes/hero_viper/viper_nethertoxin.vpcf"
|
|
local sound_cast = "Hero_Viper.NetherToxin"
|
|
local effect_cast = ParticleManager:CreateParticle(particle_cast, PATTACH_WORLDORIGIN, nil)
|
|
ParticleManager:SetParticleControl(
|
|
effect_cast,
|
|
0,
|
|
parent:GetOrigin()
|
|
)
|
|
ParticleManager:SetParticleControl(
|
|
effect_cast,
|
|
1,
|
|
Vector(self.effectiveRadius, 1, 1)
|
|
)
|
|
self:AddParticle(
|
|
effect_cast,
|
|
false,
|
|
false,
|
|
-1,
|
|
false,
|
|
false
|
|
)
|
|
EmitSoundOn(sound_cast, parent)
|
|
end
|
|
modifier_spider_nethertoxin_lua = __TS__Decorate(
|
|
modifier_spider_nethertoxin_lua,
|
|
modifier_spider_nethertoxin_lua,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_spider_nethertoxin_lua"}
|
|
)
|
|
____exports.modifier_spider_nethertoxin_lua = modifier_spider_nethertoxin_lua
|
|
return ____exports
|