327 lines
12 KiB
Lua
327 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 ____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 ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit")
|
|
local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit
|
|
--- R: ульта по мотивам dota1x6 — фаза помечает врагов в AoE у курсора (прицел + обзор),
|
|
-- выстрел снарядом по каждой отмеченной цели; если в зоне никого нет — по одной цели каста.
|
|
____exports.ability_sniper_assassinate_custom = __TS__Class()
|
|
local ability_sniper_assassinate_custom = ____exports.ability_sniper_assassinate_custom
|
|
ability_sniper_assassinate_custom.name = "ability_sniper_assassinate_custom"
|
|
ability_sniper_assassinate_custom.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua"
|
|
__TS__ClassExtends(ability_sniper_assassinate_custom, BaseAbility)
|
|
function ability_sniper_assassinate_custom.prototype.____constructor(self, ...)
|
|
BaseAbility.prototype.____constructor(self, ...)
|
|
self.phaseTargets = {}
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.Precache(self, context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_crosshair.vpcf", context)
|
|
PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_assassinate.vpcf", context)
|
|
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sniper.vsndevts", context)
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.GetBehavior(self)
|
|
return bit.bor(
|
|
bit.bor(DOTA_ABILITY_BEHAVIOR_UNIT_TARGET, DOTA_ABILITY_BEHAVIOR_AOE),
|
|
DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING
|
|
)
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.GetAOERadius(self)
|
|
local r = self:GetSpecialValueFor("aoe_radius")
|
|
if r <= 0 then
|
|
r = 400
|
|
end
|
|
return r
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.OnAbilityPhaseStart(self)
|
|
if not IsServer() then
|
|
return true
|
|
end
|
|
local caster = self:GetCaster()
|
|
EmitSoundOn("Ability.AssassinateLoad", caster)
|
|
self.phaseTargets = {}
|
|
local point = self:GetCursorPosition()
|
|
local aoe = self:GetSpecialValueFor("aoe_radius")
|
|
if aoe <= 0 then
|
|
aoe = 400
|
|
end
|
|
local debuffDur = self:GetSpecialValueFor("debuff_duration")
|
|
if debuffDur <= 0 then
|
|
debuffDur = self:GetCastPoint() + 0.5
|
|
end
|
|
local enemies = FindUnitsInRadius(
|
|
caster:GetTeamNumber(),
|
|
point,
|
|
nil,
|
|
aoe,
|
|
DOTA_UNIT_TARGET_TEAM_ENEMY,
|
|
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
|
|
DOTA_UNIT_TARGET_FLAG_INVULNERABLE + DOTA_UNIT_TARGET_FLAG_NO_INVIS,
|
|
FIND_CLOSEST,
|
|
false
|
|
)
|
|
for ____, enemy in ipairs(enemies) do
|
|
do
|
|
if not enemy or not enemy:IsAlive() then
|
|
goto __continue10
|
|
end
|
|
local ____self_phaseTargets_0 = self.phaseTargets
|
|
____self_phaseTargets_0[#____self_phaseTargets_0 + 1] = enemy
|
|
enemy:AddNewModifier(caster, self, ____exports.modifier_sniper_assassinate_aim.name, {duration = debuffDur})
|
|
end
|
|
::__continue10::
|
|
end
|
|
return true
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.OnAbilityPhaseInterrupted(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self:ClearPhaseMarks()
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.OnSpellStart(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local caster = self:GetCaster()
|
|
local targets = {}
|
|
if #self.phaseTargets > 0 then
|
|
for ____, u in ipairs(self.phaseTargets) do
|
|
if u and u:IsAlive() and not u:IsNull() then
|
|
targets[#targets + 1] = u
|
|
end
|
|
end
|
|
else
|
|
local t = self:GetCursorTarget()
|
|
if t and t:IsAlive() then
|
|
targets[#targets + 1] = t
|
|
end
|
|
end
|
|
self.phaseTargets = {}
|
|
if #targets == 0 then
|
|
return
|
|
end
|
|
EmitSoundOn("Ability.Assassinate", caster)
|
|
EmitSoundOn("Hero_Sniper.AssassinateProjectile", caster)
|
|
local speed = self:GetSpecialValueFor("projectile_speed")
|
|
for ____, target in ipairs(targets) do
|
|
ProjectileManager:CreateTrackingProjectile({
|
|
Ability = self,
|
|
EffectName = "particles/units/heroes/hero_sniper/sniper_assassinate.vpcf",
|
|
Source = caster,
|
|
Target = target,
|
|
iMoveSpeed = speed,
|
|
bDodgeable = true,
|
|
bVisibleToEnemies = true,
|
|
bProvidesVision = true,
|
|
iVisionRadius = 400,
|
|
iVisionTeamNumber = caster:GetTeamNumber(),
|
|
iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_1
|
|
})
|
|
end
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.ClearPhaseMarks(self)
|
|
for ____, u in ipairs(self.phaseTargets) do
|
|
if u and not u:IsNull() and u:IsAlive() then
|
|
u:RemoveModifierByName(____exports.modifier_sniper_assassinate_aim.name)
|
|
end
|
|
end
|
|
self.phaseTargets = {}
|
|
end
|
|
function ability_sniper_assassinate_custom.prototype.OnProjectileHit(self, target, _location)
|
|
if not IsServer() then
|
|
return true
|
|
end
|
|
if not target or not target:IsAlive() then
|
|
return true
|
|
end
|
|
local caster = self:GetCaster()
|
|
if not caster or not caster:IsAlive() then
|
|
return true
|
|
end
|
|
target:RemoveModifierByName(____exports.modifier_sniper_assassinate_aim.name)
|
|
if target:TriggerSpellAbsorb(self) then
|
|
return true
|
|
end
|
|
EmitSoundOn("Hero_Sniper.AssassinateDamage", caster)
|
|
local stunDur = self:GetSpecialValueFor("ministun_duration")
|
|
if stunDur > 0 then
|
|
target:AddNewModifier(
|
|
caster,
|
|
self,
|
|
"modifier_stunned",
|
|
{duration = stunDur * (1 - target:GetStatusResistance())}
|
|
)
|
|
end
|
|
local ____opt_1 = modifier_stacking_crit:GetForUnit(caster)
|
|
if ____opt_1 ~= nil then
|
|
____opt_1:GuaranteeNextCrit(1)
|
|
end
|
|
caster:PerformAttack(
|
|
target,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true
|
|
)
|
|
local armorDebuffDur = self:GetSpecialValueFor("armor_mark_duration")
|
|
if armorDebuffDur > 0 then
|
|
target:AddNewModifier(caster, self, "modifier_sniper_assassinate_mark", {duration = armorDebuffDur})
|
|
end
|
|
return true
|
|
end
|
|
ability_sniper_assassinate_custom = __TS__Decorate(
|
|
ability_sniper_assassinate_custom,
|
|
ability_sniper_assassinate_custom,
|
|
{registerAbility(nil)},
|
|
{kind = "class", name = "ability_sniper_assassinate_custom"}
|
|
)
|
|
____exports.ability_sniper_assassinate_custom = ability_sniper_assassinate_custom
|
|
--- Метка на фазе прицеливания: крест + краткий обзор (как в 1x6).
|
|
____exports.modifier_sniper_assassinate_aim = __TS__Class()
|
|
local modifier_sniper_assassinate_aim = ____exports.modifier_sniper_assassinate_aim
|
|
modifier_sniper_assassinate_aim.name = "modifier_sniper_assassinate_aim"
|
|
modifier_sniper_assassinate_aim.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua"
|
|
__TS__ClassExtends(modifier_sniper_assassinate_aim, BaseModifier)
|
|
function modifier_sniper_assassinate_aim.prototype.IsHidden(self)
|
|
return false
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.IsDebuff(self)
|
|
return true
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.IsPurgable(self)
|
|
return false
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.GetTexture(self)
|
|
return "sniper_assassinate"
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.OnCreated(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local parent = self:GetParent()
|
|
local caster = self:GetCaster()
|
|
if not caster then
|
|
return
|
|
end
|
|
self.fx = ParticleManager:CreateParticleForTeam(
|
|
"particles/units/heroes/hero_sniper/sniper_crosshair.vpcf",
|
|
PATTACH_OVERHEAD_FOLLOW,
|
|
parent,
|
|
caster:GetTeamNumber()
|
|
)
|
|
ParticleManager:SetParticleControl(
|
|
self.fx,
|
|
1,
|
|
Vector(0, 0, 0)
|
|
)
|
|
self:AddParticle(
|
|
self.fx,
|
|
false,
|
|
false,
|
|
-1,
|
|
false,
|
|
false
|
|
)
|
|
self:StartIntervalThink(0.1)
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.OnIntervalThink(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local caster = self:GetCaster()
|
|
local parent = self:GetParent()
|
|
if not caster then
|
|
return
|
|
end
|
|
AddFOWViewer(
|
|
caster:GetTeamNumber(),
|
|
parent:GetAbsOrigin(),
|
|
10,
|
|
0.1,
|
|
true
|
|
)
|
|
end
|
|
function modifier_sniper_assassinate_aim.prototype.OnDestroy(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self.fx = nil
|
|
end
|
|
modifier_sniper_assassinate_aim = __TS__Decorate(
|
|
modifier_sniper_assassinate_aim,
|
|
modifier_sniper_assassinate_aim,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_sniper_assassinate_aim"}
|
|
)
|
|
____exports.modifier_sniper_assassinate_aim = modifier_sniper_assassinate_aim
|
|
____exports.modifier_sniper_assassinate_mark = __TS__Class()
|
|
local modifier_sniper_assassinate_mark = ____exports.modifier_sniper_assassinate_mark
|
|
modifier_sniper_assassinate_mark.name = "modifier_sniper_assassinate_mark"
|
|
modifier_sniper_assassinate_mark.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua"
|
|
__TS__ClassExtends(modifier_sniper_assassinate_mark, BaseModifier)
|
|
function modifier_sniper_assassinate_mark.prototype.____constructor(self, ...)
|
|
BaseModifier.prototype.____constructor(self, ...)
|
|
self.reduction = 0
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.IsHidden(self)
|
|
return false
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.IsDebuff(self)
|
|
return true
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.GetTexture(self)
|
|
return "sniper_assassinate"
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.DeclareFunctions(self)
|
|
return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS}
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.OnCreated(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self:StartIntervalThink(0.25)
|
|
self:RefreshArmorReduction()
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.OnRefresh(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self:RefreshArmorReduction()
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.OnIntervalThink(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
self:RefreshArmorReduction()
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.RefreshArmorReduction(self)
|
|
local p = self:GetParent()
|
|
local displayed = p:GetPhysicalArmorValue(false)
|
|
local trueArmor = displayed + self.reduction
|
|
self.reduction = math.max(
|
|
0,
|
|
math.floor(trueArmor * 0.5)
|
|
)
|
|
end
|
|
function modifier_sniper_assassinate_mark.prototype.GetModifierPhysicalArmorBonus(self)
|
|
return -self.reduction
|
|
end
|
|
modifier_sniper_assassinate_mark = __TS__Decorate(
|
|
modifier_sniper_assassinate_mark,
|
|
modifier_sniper_assassinate_mark,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_sniper_assassinate_mark"}
|
|
)
|
|
____exports.modifier_sniper_assassinate_mark = modifier_sniper_assassinate_mark
|
|
return ____exports
|