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

521 lines
16 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 ____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 ____hero_rage_config = require("abilities.system.hero_rage_config")
local shouldHeroUseRageResource = ____hero_rage_config.shouldHeroUseRageResource
function ____exports.heroRageGetModifier(self, hero)
return hero:FindModifierByName(____exports.modifier_hero_rage.name)
end
function ____exports.heroRageSet(self, hero, value)
if not IsServer() then
return
end
local m = ____exports.heroRageGetModifier(nil, hero)
if not m then
return
end
m:SetStackCount(math.max(
0,
math.min(
math.floor(value),
m.maxRage
)
))
m:pushRageNetClient()
end
--- Списание ярости после успешного каста (стоимость из `rage_cost` в KV или своё число).
function ____exports.heroRageTrySpend(self, hero, cost)
if not IsServer() then
return false
end
if cost <= 0 then
return true
end
local m = ____exports.heroRageGetModifier(nil, hero)
if not m then
return false
end
if m:GetStackCount() < cost then
return false
end
m:addRageInternal(-cost)
return true
end
local function defaultSpawnParams()
return {
max_rage = 100,
rage_per_attack = 6,
rage_per_damage = 1,
attack_speed_per_stack = 0,
time_out_of_combat = 4,
tick = 0.01
}
end
____exports.ability_hero_rage = __TS__Class()
local ability_hero_rage = ____exports.ability_hero_rage
ability_hero_rage.name = "ability_hero_rage"
ability_hero_rage.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua"
__TS__ClassExtends(ability_hero_rage, BaseAbility)
function ability_hero_rage.prototype.GetIntrinsicModifierName(self)
return ____exports.modifier_hero_rage.name
end
function ability_hero_rage.prototype.OnOwnerDied(self)
if not IsServer() then
return
end
local c = self:GetCaster()
____exports.heroRageSet(nil, c, 0)
end
ability_hero_rage = __TS__Decorate(
ability_hero_rage,
ability_hero_rage,
{registerAbility(nil)},
{kind = "class", name = "ability_hero_rage"}
)
____exports.ability_hero_rage = ability_hero_rage
____exports.modifier_hero_rage = __TS__Class()
local modifier_hero_rage = ____exports.modifier_hero_rage
modifier_hero_rage.name = "modifier_hero_rage"
modifier_hero_rage.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua"
__TS__ClassExtends(modifier_hero_rage, BaseModifier)
function modifier_hero_rage.prototype.____constructor(self, ...)
BaseModifier.prototype.____constructor(self, ...)
self.maxRage = 100
self.ragePerAttack = 0
self.ragePerDamage = 0
self.attackSpeedPerStack = 0
self.timeOutOfCombatThreshold = 4
self.tick = 0.01
self.outOfCombatTime = 0
end
function modifier_hero_rage.prototype.pushRageNetClient(self)
if not IsServer() then
return
end
CustomNetTables:SetTableValue(
"custom_stats",
"rage_ent_" .. tostring(self.parentHero:entindex()),
{
cur = self:GetStackCount(),
max = self.maxRage
}
)
end
function modifier_hero_rage.prototype.clearRageNetClient(self)
if not IsServer() then
return
end
CustomNetTables:SetTableValue(
"custom_stats",
"rage_ent_" .. tostring(self.parentHero:entindex()),
{off = 1}
)
end
function modifier_hero_rage.prototype.IsHidden(self)
return true
end
function modifier_hero_rage.prototype.IsPurgable(self)
return false
end
function modifier_hero_rage.prototype.RemoveOnDeath(self)
return false
end
function modifier_hero_rage.prototype.DeclareFunctions(self)
return {
MODIFIER_EVENT_ON_ATTACK_LANDED,
MODIFIER_EVENT_ON_TAKEDAMAGE,
MODIFIER_EVENT_ON_ABILITY_EXECUTED,
MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT,
MODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE,
MODIFIER_PROPERTY_FIXED_MANA_REGEN,
MODIFIER_PROPERTY_FORCE_MAX_MANA,
MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE,
MODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING
}
end
function modifier_hero_rage.prototype.GetModifierPercentageManacostStacking(self, ...)
local args = {...}
local event = args[1]
if event and event.ability and not event.ability:IsNull() and event.ability:IsItem() then
return 100
end
return 0
end
function modifier_hero_rage.prototype.GetModifierDamageOutgoing_Percentage(self, event)
return self:GetStackCount() * 0.25
end
function modifier_hero_rage.prototype.GetModifierTotalPercentageManaRegen(self)
return 0
end
function modifier_hero_rage.prototype.GetModifierFixedManaRegen(self)
return 0
end
function modifier_hero_rage.prototype.GetModifierForceMaxMana(self)
return self.maxRage
end
function modifier_hero_rage.prototype.syncManaToRage(self)
if not IsServer() then
return
end
local hero = self.parentHero
local rage = self:GetStackCount()
local maxM = hero:GetMaxMana()
hero:SetMaxMana(100)
hero:SetMana(math.min(rage, maxM))
end
function modifier_hero_rage.prototype.reapplyManaPoolAfterStatBump(self)
if not IsServer() then
return
end
local hero = self.parentHero
local rage = self:GetStackCount()
hero:SetMaxMana(self.maxRage)
hero:SetMana(math.min(
rage,
hero:GetMaxMana()
))
end
function modifier_hero_rage.prototype.OnTakeDamage(self, event)
if not IsServer() then
return
end
if event.damage <= 0 then
return
end
local victim = event.unit
local attacker = event.attacker
if attacker == self.parentHero and victim and victim:GetTeamNumber() ~= self.parentHero:GetTeamNumber() then
if self.ragePerAttack ~= 0 then
self:addRageInternal(self.ragePerAttack)
end
self:reapplyManaPoolAfterStatBump()
return
end
if victim == self.parentHero then
if self.ragePerDamage ~= 0 then
self:addRageInternal(self.ragePerDamage)
end
self:reapplyManaPoolAfterStatBump()
end
end
function modifier_hero_rage.prototype.getAbilityRageSpendCost(self, ability)
local customRageCost = math.max(
0,
math.floor(ability:GetSpecialValueFor("rage_cost"))
)
if customRageCost > 0 then
return customRageCost
end
return math.max(
0,
math.floor(ability:GetManaCost(ability:GetLevel()))
)
end
function modifier_hero_rage.prototype.OnAbilityExecuted(self, event)
if not IsServer() then
return
end
if event.unit ~= self.parentHero then
return
end
local ability = event.ability
if not ability or ability:IsNull() then
return
end
if ability:IsItem() then
return
end
if ability == self:GetAbility() then
return
end
local cost = self:getAbilityRageSpendCost(ability)
if cost <= 0 then
return
end
____exports.heroRageTrySpend(nil, self.parentHero, cost)
end
function modifier_hero_rage.prototype.OnCreated(self, params)
local parent = self:GetParent()
self.parentHero = parent
local rawAbility = self:GetAbility()
local ability = rawAbility and rawAbility:GetAbilityName() == "ability_hero_rage" and rawAbility or nil
local def = defaultSpawnParams(nil)
if ability then
self.maxRage = math.max(
1,
math.floor(ability:GetSpecialValueFor("max_rage"))
)
self.ragePerAttack = ability:GetSpecialValueFor("rage_per_attack")
self.ragePerDamage = ability:GetSpecialValueFor("rage_per_damage")
self.attackSpeedPerStack = ability:GetSpecialValueFor("attack_speed_per_stack")
self.timeOutOfCombatThreshold = math.max(
0.1,
ability:GetSpecialValueFor("time_out_of_combat")
)
self.tick = math.max(
0.03,
ability:GetSpecialValueFor("tick")
)
else
self.maxRage = math.max(
1,
math.floor(params.max_rage or def.max_rage)
)
self.ragePerAttack = params.rage_per_attack or def.rage_per_attack
self.ragePerDamage = params.rage_per_damage or def.rage_per_damage
self.attackSpeedPerStack = params.attack_speed_per_stack or def.attack_speed_per_stack
self.timeOutOfCombatThreshold = math.max(0.1, params.time_out_of_combat or def.time_out_of_combat)
self.tick = math.max(0.03, params.tick or def.tick)
end
if not IsServer() then
return
end
parent.__zHeroRage = self
self:SetStackCount(0)
self.outOfCombatTime = 0
parent:AddNewModifier(
parent,
self:GetAbility() or nil,
____exports.modifier_hero_rage_max.name,
{}
)
self:StartIntervalThink(self.tick)
self:pushRageNetClient()
self:syncManaToRage()
self.levelUpListener = ListenToGameEvent(
"dota_player_gained_level",
function(event)
if not IsServer() then
return
end
if not IsValidEntity(self.parentHero) then
return
end
if not self.parentHero:IsRealHero() or self.parentHero:IsIllusion() then
return
end
local pid = self.parentHero:GetPlayerOwnerID()
if event.player ~= pid then
return
end
self:reapplyManaPoolAfterStatBump()
end,
nil
)
end
function modifier_hero_rage.prototype.OnDestroy(self)
if not IsServer() then
return
end
if self.levelUpListener ~= nil then
StopListeningToGameEvent(self.levelUpListener)
self.levelUpListener = nil
end
self:clearRageNetClient()
local p = self:GetParent()
if p.__zHeroRage == self then
p.__zHeroRage = nil
end
p:RemoveModifierByName(____exports.modifier_hero_rage_max.name)
end
function modifier_hero_rage.prototype.OnRefresh(self)
local rawAbility = self:GetAbility()
local ability = rawAbility and rawAbility:GetAbilityName() == "ability_hero_rage" and rawAbility or nil
if ability then
self.maxRage = math.max(
1,
math.floor(ability:GetSpecialValueFor("max_rage"))
)
self.ragePerAttack = ability:GetSpecialValueFor("rage_per_attack")
self.ragePerDamage = ability:GetSpecialValueFor("rage_per_damage")
self.attackSpeedPerStack = ability:GetSpecialValueFor("attack_speed_per_stack")
self.timeOutOfCombatThreshold = math.max(
0.1,
ability:GetSpecialValueFor("time_out_of_combat")
)
self.tick = math.max(
0.03,
ability:GetSpecialValueFor("tick")
)
end
if not IsServer() then
return
end
self:clampRageStack()
local maxMod = self.parentHero:FindModifierByName(____exports.modifier_hero_rage_max.name)
if maxMod then
maxMod:SetStackCount(self.maxRage)
end
self:pushRageNetClient()
self:syncManaToRage()
end
function modifier_hero_rage.prototype.OnStackCountChanged(self, _stack)
if not IsServer() then
return
end
self:clampRageStack()
self:pushRageNetClient()
self:syncManaToRage()
end
function modifier_hero_rage.prototype.OnIntervalThink(self)
if not IsServer() then
return
end
self:syncManaToRage()
self.outOfCombatTime = self.outOfCombatTime + self.tick
if self.outOfCombatTime >= self.timeOutOfCombatThreshold then
self:addRageInternal(-1)
end
end
function modifier_hero_rage.prototype.OnAttackLanded(self, event)
if not IsServer() then
return
end
end
function modifier_hero_rage.prototype.GetModifierAttackSpeedBonus_Constant(self)
return self.attackSpeedPerStack * self:GetStackCount()
end
function modifier_hero_rage.prototype.resetOutOfCombatTimer(self)
self.outOfCombatTime = 0
end
function modifier_hero_rage.prototype.addRageInternal(self, delta)
self:resetOutOfCombatTimer()
local next = self:GetStackCount() + delta
if next > self.maxRage then
next = self.maxRage
end
if next < 0 then
next = 0
end
self:SetStackCount(next)
end
function modifier_hero_rage.prototype.clampRageStack(self)
local v = self:GetStackCount()
if v < 0 then
v = 0
end
if v > self.maxRage then
v = self.maxRage
end
if v ~= self:GetStackCount() then
self:SetStackCount(v)
end
end
modifier_hero_rage = __TS__Decorate(
modifier_hero_rage,
modifier_hero_rage,
{registerModifier(nil)},
{kind = "class", name = "modifier_hero_rage"}
)
____exports.modifier_hero_rage = modifier_hero_rage
____exports.modifier_hero_rage_max = __TS__Class()
local modifier_hero_rage_max = ____exports.modifier_hero_rage_max
modifier_hero_rage_max.name = "modifier_hero_rage_max"
modifier_hero_rage_max.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua"
__TS__ClassExtends(modifier_hero_rage_max, BaseModifier)
function modifier_hero_rage_max.prototype.____constructor(self, ...)
BaseModifier.prototype.____constructor(self, ...)
self.manaBonusLock = false
end
function modifier_hero_rage_max.prototype.IsHidden(self)
return true
end
function modifier_hero_rage_max.prototype.IsPurgable(self)
return false
end
function modifier_hero_rage_max.prototype.RemoveOnDeath(self)
return false
end
function modifier_hero_rage_max.prototype.DeclareFunctions(self)
return {MODIFIER_PROPERTY_MANA_BONUS}
end
function modifier_hero_rage_max.prototype.GetModifierManaBonus(self)
if self.manaBonusLock then
return 0
end
self.manaBonusLock = true
local maxWithoutThis = self:GetParent():GetMaxMana()
self.manaBonusLock = false
local targetMax = self:GetStackCount()
return targetMax - maxWithoutThis
end
function modifier_hero_rage_max.prototype.OnCreated(self)
if not IsServer() then
return
end
local parent = self:GetParent()
local rage = parent:FindModifierByName(____exports.modifier_hero_rage.name)
self:SetStackCount(rage and rage.maxRage or 0)
end
modifier_hero_rage_max = __TS__Decorate(
modifier_hero_rage_max,
modifier_hero_rage_max,
{registerModifier(nil)},
{kind = "class", name = "modifier_hero_rage_max"}
)
____exports.modifier_hero_rage_max = modifier_hero_rage_max
function ____exports.heroRageGetCurrent(self, hero)
local m = ____exports.heroRageGetModifier(nil, hero)
return m and m:GetStackCount() or 0
end
function ____exports.heroRageGetMax(self, hero)
local m = ____exports.heroRageGetModifier(nil, hero)
return m and m.maxRage or 0
end
function ____exports.heroRageIncrease(self, hero, amount)
if not IsServer() then
return
end
local m = ____exports.heroRageGetModifier(nil, hero)
if not m then
return
end
m:addRageInternal(amount)
end
function ____exports.heroRageDecrement(self, hero, amount)
____exports.heroRageIncrease(
nil,
hero,
-math.abs(amount)
)
end
--- Вешает систему ярости на героя из конфига `HERO_RAGE_UNIT_NAMES`.
-- Если в `game/` уже есть `ability_hero_rage` в KV — берётся она, иначе — модификатор с дефолтами.
function ____exports.attachHeroRageSystemForConfiguredHero(self, hero)
if not IsServer() then
return
end
if not hero:IsRealHero() or hero:IsIllusion() then
return
end
if not shouldHeroUseRageResource(
nil,
hero:GetUnitName()
) then
return
end
if hero:FindModifierByName(____exports.modifier_hero_rage.name) then
return
end
hero:AddAbility("ability_hero_rage")
local rageAb = hero:FindAbilityByName("ability_hero_rage")
if rageAb ~= nil then
rageAb:SetLevel(1)
return
end
hero:AddNewModifier(
hero,
getModifierSourceAbility(nil, hero),
____exports.modifier_hero_rage.name,
defaultSpawnParams(nil)
)
end
return ____exports