521 lines
16 KiB
Lua
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
|