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

293 lines
12 KiB
Lua

local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
local __TS__Decorate = ____lualib.__TS__Decorate
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
local __TS__ObjectValues = ____lualib.__TS__ObjectValues
local __TS__Delete = ____lualib.__TS__Delete
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
local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine")
local removeStatsMultiplierIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeStatsMultiplierIncomingDamageReductionSource
local setStatsMultiplierIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setStatsMultiplierIncomingDamageReductionSource
____exports.MODIFIER_STATS_MULTIPLIER = "modifier_stats_multiplier"
--- Стабильные id источников для снятия в `modifier_arsenal_dynamic_stats.OnDestroy`.
____exports.ARSENAL_STATS_MULTIPLIER_SOURCE_ID = {
all_stats_pct = "arsenal_dyn_pct_all_stats",
strength_pct = "arsenal_dyn_pct_str",
agility_pct = "arsenal_dyn_pct_agi",
intellect_pct = "arsenal_dyn_pct_int",
outgoing_damage_pct = "arsenal_dyn_pct_outgoing",
incoming_damage_reduction_pct = "arsenal_dyn_pct_incoming",
attack_speed_pct = "arsenal_dyn_pct_as",
move_speed_pct = "arsenal_dyn_pct_ms",
base_damage_pct = "arsenal_dyn_pct_basedmg"
}
____exports.ability_stats_multiplier = __TS__Class()
local ability_stats_multiplier = ____exports.ability_stats_multiplier
ability_stats_multiplier.name = "ability_stats_multiplier"
ability_stats_multiplier.____file_path = "scripts/vscripts/modifiers/modifier_stats_multiplier.lua"
__TS__ClassExtends(ability_stats_multiplier, BaseAbility)
function ability_stats_multiplier.prototype.GetIntrinsicModifierName(self)
return "modifier_stats_multiplier"
end
function ability_stats_multiplier.prototype.IsHidden(self)
return true
end
ability_stats_multiplier = __TS__Decorate(
ability_stats_multiplier,
ability_stats_multiplier,
{registerAbility(nil)},
{kind = "class", name = "ability_stats_multiplier"}
)
____exports.ability_stats_multiplier = ability_stats_multiplier
local globalModifierFunctionSourceSeq = 1
function ____exports.createModifierFunctionSourceId(self, prefix)
local safePrefix = prefix and #prefix > 0 and prefix or "source"
local id = (safePrefix .. "_") .. tostring(globalModifierFunctionSourceSeq)
globalModifierFunctionSourceSeq = globalModifierFunctionSourceSeq + 1
return id
end
local function sourceKind(self, s)
return s.kind or "attributes"
end
local function getStorage(self, hero)
local holder = hero
if not holder.__statsMultiplierStorage then
holder.__statsMultiplierStorage = {}
end
return holder.__statsMultiplierStorage
end
local function hasSources(self, hero)
return #__TS__ObjectKeys(getStorage(nil, hero)) > 0
end
--- Только карточные % к атрибутам — для видимости баффа и тултипа «%».
local function hasAttributeSources(self, hero)
local storage = getStorage(nil, hero)
for ____, source in ipairs(__TS__ObjectValues(storage)) do
if sourceKind(nil, source) == "attributes" then
return true
end
end
return false
end
--- Сбросить кэш сумм (арсенал/колоды меняются без смены id источника).
function ____exports.invalidateStatsMultiplierSumCache(self, hero)
if not hero or not IsValidEntity(hero) then
return
end
local c = hero.__statsMultiplierSumCache
if c then
c.dirty = true
end
end
local function rebuildSumCache(self, hero, cache)
local storage = getStorage(nil, hero)
local byKind = {}
for ____, source in ipairs(__TS__ObjectValues(storage)) do
local k = sourceKind(nil, source)
byKind[k] = (byKind[k] or 0) + source:resolver()
end
cache.byKind = byKind
cache.dirty = false
end
local function sumForKind(self, hero, kind)
local holder = hero
local cache = holder.__statsMultiplierSumCache
if not cache then
cache = {dirty = true, byKind = {}}
holder.__statsMultiplierSumCache = cache
end
if cache.dirty then
rebuildSumCache(nil, hero, cache)
end
return cache.byKind[kind] or 0
end
local function ensureModifier(self, hero)
if not hero:FindModifierByName(____exports.MODIFIER_STATS_MULTIPLIER) then
local sourceAbility = hero:FindAbilityByName("ability_stats_multiplier") or hero:FindAbilityByName("ability_stacking_crit")
hero:AddNewModifier(hero, sourceAbility, ____exports.MODIFIER_STATS_MULTIPLIER, {})
end
end
local function removeModifierIfUnused(self, hero)
if hasSources(nil, hero) then
return
end
local mod = hero:FindModifierByName(____exports.MODIFIER_STATS_MULTIPLIER)
if mod then
mod:Destroy()
end
____exports.invalidateStatsMultiplierSumCache(nil, hero)
end
--- Регистрирует вклад в `modifier_stats_multiplier`.
-- - `kind === "attributes"` (по умолчанию): сумма % умножается на **базовые** Str/Agi/Int и даёт бонус к соответствующему атрибуту (карты).
-- - `all_stats_pct`: тот же % к **каждой** базовой характеристике Str/Agi/Int (арсенал).
-- - `strength_pct` / `agility_pct` / `intellect_pct`: % только к **базовому** Str/Agi/Int (арсенал).
-- - Остальные `kind`: сумма идёт в соответствующий `ModifierFunction` (исходящий/входящий урон, AS%/MS%, урон от базы атаки) — арсенал;
-- для `incoming_damage_reduction_pct` сумма **не** добавляется линейно в движок, а сжимается формулой с убыванием (см. `incoming_damage_reduction_combine`).
function ____exports.setStatsMultiplierSource(self, hero, sourceId, resolver, kind)
if kind == nil then
kind = "attributes"
end
if not hero or not IsValidEntity(hero) then
return
end
getStorage(nil, hero)[sourceId] = {resolver = resolver, kind = kind}
if kind == "incoming_damage_reduction_pct" then
setStatsMultiplierIncomingDamageReductionSource(nil, hero, sourceId, resolver)
end
____exports.invalidateStatsMultiplierSumCache(nil, hero)
ensureModifier(nil, hero)
end
function ____exports.removeStatsMultiplierSource(self, hero, sourceId)
if not hero or not IsValidEntity(hero) then
return
end
local storage = getStorage(nil, hero)
local removed = storage[sourceId]
if removed and sourceKind(nil, removed) == "incoming_damage_reduction_pct" then
removeStatsMultiplierIncomingDamageReductionSource(nil, hero, sourceId)
end
__TS__Delete(storage, sourceId)
____exports.invalidateStatsMultiplierSumCache(nil, hero)
removeModifierIfUnused(nil, hero)
end
____exports.modifier_stats_multiplier = __TS__Class()
local modifier_stats_multiplier = ____exports.modifier_stats_multiplier
modifier_stats_multiplier.name = "modifier_stats_multiplier"
modifier_stats_multiplier.____file_path = "scripts/vscripts/modifiers/modifier_stats_multiplier.lua"
__TS__ClassExtends(modifier_stats_multiplier, BaseModifier)
function modifier_stats_multiplier.prototype.____constructor(self, ...)
BaseModifier.prototype.____constructor(self, ...)
self.lock = false
end
function modifier_stats_multiplier.prototype.IsHidden(self)
local parent = self:GetParent()
if not parent or not IsValidEntity(parent) then
return true
end
return not hasAttributeSources(nil, parent)
end
function modifier_stats_multiplier.prototype.RemoveOnDeath(self)
return false
end
function modifier_stats_multiplier.prototype.IsPurgable(self)
return false
end
function modifier_stats_multiplier.prototype.GetTexture(self)
return "nevermore_dark_lord"
end
function modifier_stats_multiplier.prototype.DeclareFunctions(self)
return {
MODIFIER_PROPERTY_STATS_STRENGTH_BONUS,
MODIFIER_PROPERTY_STATS_AGILITY_BONUS,
MODIFIER_PROPERTY_STATS_INTELLECT_BONUS,
MODIFIER_PROPERTY_TOOLTIP,
MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE,
MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE,
MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE,
MODIFIER_PROPERTY_BASEDAMAGEOUTGOING_PERCENTAGE
}
end
function modifier_stats_multiplier.prototype.sumResolverPercentAttributes(self)
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
return sumForKind(nil, hero, "attributes")
end
function modifier_stats_multiplier.prototype.OnTooltip(self)
if not IsServer() then
return 0
end
return self:sumResolverPercentAttributes()
end
function modifier_stats_multiplier.prototype.GetModifierBonusStats_Strength(self)
if self.lock then
return 0
end
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
self.lock = true
local base = hero:GetStrength() or 0
local shared = sumForKind(nil, hero, "all_stats_pct")
local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "strength_pct")
local v = base * (pct / 100)
self.lock = false
return v
end
function modifier_stats_multiplier.prototype.GetModifierBonusStats_Agility(self)
if self.lock then
return 0
end
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
self.lock = true
local base = hero:GetAgility() or 0
local shared = sumForKind(nil, hero, "all_stats_pct")
local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "agility_pct")
local v = base * (pct / 100)
self.lock = false
return v
end
function modifier_stats_multiplier.prototype.GetModifierBonusStats_Intellect(self)
if self.lock then
return 0
end
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
self.lock = true
local base = hero:GetIntellect(true) or 0
local shared = sumForKind(nil, hero, "all_stats_pct")
local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "intellect_pct")
local v = base * (pct / 100)
self.lock = false
return v
end
function modifier_stats_multiplier.prototype.GetModifierDamageOutgoing_Percentage(self)
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
return sumForKind(nil, hero, "outgoing_damage_pct")
end
function modifier_stats_multiplier.prototype.GetModifierAttackSpeedPercentage(self)
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
return sumForKind(nil, hero, "attack_speed_pct")
end
function modifier_stats_multiplier.prototype.GetModifierMoveSpeedBonus_Percentage(self)
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
return sumForKind(nil, hero, "move_speed_pct")
end
function modifier_stats_multiplier.prototype.GetModifierBaseDamageOutgoing_Percentage(self)
local hero = self:GetParent()
if not hero or not IsValidEntity(hero) then
return 0
end
return sumForKind(nil, hero, "base_damage_pct")
end
modifier_stats_multiplier = __TS__Decorate(
modifier_stats_multiplier,
modifier_stats_multiplier,
{registerModifier(nil)},
{kind = "class", name = "modifier_stats_multiplier"}
)
____exports.modifier_stats_multiplier = modifier_stats_multiplier
return ____exports