255 lines
10 KiB
Lua
255 lines
10 KiB
Lua
local ____lualib = require("lualib_bundle")
|
|
local Set = ____lualib.Set
|
|
local __TS__New = ____lualib.__TS__New
|
|
local Map = ____lualib.Map
|
|
local __TS__Class = ____lualib.__TS__Class
|
|
local __TS__ClassExtends = ____lualib.__TS__ClassExtends
|
|
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
|
|
local __TS__Decorate = ____lualib.__TS__Decorate
|
|
local ____exports = {}
|
|
local isHeroAbilityStealable, getLastUsedAbilityFromUnit, UNSTEALABLE_NAMES
|
|
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
|
|
function isHeroAbilityStealable(self, hero, abilityName)
|
|
if not abilityName then
|
|
return false
|
|
end
|
|
if UNSTEALABLE_NAMES:has(abilityName) then
|
|
return false
|
|
end
|
|
local ability = hero:FindAbilityByName(abilityName)
|
|
if not ability or ability:IsNull() then
|
|
return false
|
|
end
|
|
if ability:IsItem() or ability:IsHidden() then
|
|
return false
|
|
end
|
|
if ability:IsPassive() then
|
|
return false
|
|
end
|
|
if ability:GetLevel() <= 0 then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
function getLastUsedAbilityFromUnit(self, unit)
|
|
local best = nil
|
|
do
|
|
local i = 0
|
|
while i < 16 do
|
|
do
|
|
local ab = unit:GetAbilityByIndex(i)
|
|
if not ab or ab:IsNull() then
|
|
goto __continue13
|
|
end
|
|
local name = ab:GetAbilityName()
|
|
if not isHeroAbilityStealable(nil, unit, name) then
|
|
goto __continue13
|
|
end
|
|
local cd = ab:GetCooldownTimeRemaining()
|
|
if cd > 0 and (not best or cd < best.cd) then
|
|
best = {name = name, cd = cd}
|
|
end
|
|
end
|
|
::__continue13::
|
|
i = i + 1
|
|
end
|
|
end
|
|
return best and best.name or ""
|
|
end
|
|
local STOLEN_SLOT_1 = 3
|
|
local STOLEN_SLOT_2 = 4
|
|
local EMPTY_SLOTS = {"rubick_empty1", "rubick_empty2"}
|
|
UNSTEALABLE_NAMES = __TS__New(Set, {"ability_rubick_spellsteal_custom"})
|
|
local lastAbilityByUnit = __TS__New(Map)
|
|
local function getStealableAbilityFromTarget(self, target)
|
|
if not target:IsRealHero() then
|
|
return ""
|
|
end
|
|
local heroTarget = target
|
|
local lastUsedName = lastAbilityByUnit:get(target:entindex()) or ""
|
|
if isHeroAbilityStealable(nil, heroTarget, lastUsedName) then
|
|
return lastUsedName
|
|
end
|
|
return getLastUsedAbilityFromUnit(nil, heroTarget)
|
|
end
|
|
if IsServer() then
|
|
ListenToGameEvent(
|
|
"dota_player_used_ability",
|
|
function(event)
|
|
local name = event.abilityname
|
|
local playerId = event.PlayerID
|
|
if not name or playerId == nil then
|
|
return
|
|
end
|
|
local ____opt_2 = PlayerResource:GetPlayer(playerId)
|
|
local hero = ____opt_2 and ____opt_2:GetAssignedHero()
|
|
if hero and not hero:IsNull() and hero:IsRealHero() and isHeroAbilityStealable(nil, hero, name) then
|
|
lastAbilityByUnit:set(
|
|
hero:entindex(),
|
|
name
|
|
)
|
|
print((((("[SpellSteal] dota_player_used_ability: " .. hero:GetUnitName()) .. " (") .. tostring(hero:entindex())) .. ") cast ") .. name)
|
|
end
|
|
end,
|
|
nil
|
|
)
|
|
end
|
|
____exports.ability_rubick_spellsteal_custom = __TS__Class()
|
|
local ability_rubick_spellsteal_custom = ____exports.ability_rubick_spellsteal_custom
|
|
ability_rubick_spellsteal_custom.name = "ability_rubick_spellsteal_custom"
|
|
ability_rubick_spellsteal_custom.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua"
|
|
__TS__ClassExtends(ability_rubick_spellsteal_custom, BaseAbility)
|
|
function ability_rubick_spellsteal_custom.prototype.Precache(self, context)
|
|
PrecacheResource("particle", "particles/econ/items/rubick/rubick_arcana/rubick_arc_loadout_spell_steal.vpcf", context)
|
|
PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_rubick.vsndevts", context)
|
|
end
|
|
function ability_rubick_spellsteal_custom.prototype.CastFilterResultTarget(self, target)
|
|
local caster = self:GetCaster()
|
|
if target == caster then
|
|
return UF_FAIL_CUSTOM
|
|
end
|
|
local result = UnitFilter(
|
|
target,
|
|
DOTA_UNIT_TARGET_TEAM_FRIENDLY,
|
|
DOTA_UNIT_TARGET_HERO,
|
|
DOTA_UNIT_TARGET_FLAG_NONE,
|
|
caster:GetTeamNumber()
|
|
)
|
|
if result ~= UF_SUCCESS then
|
|
return result
|
|
end
|
|
if not target:IsRealHero() then
|
|
return UF_FAIL_CUSTOM
|
|
end
|
|
local lastAbilityName = getStealableAbilityFromTarget(nil, target)
|
|
if not lastAbilityName then
|
|
return UF_FAIL_CUSTOM
|
|
end
|
|
if UNSTEALABLE_NAMES:has(lastAbilityName) then
|
|
return UF_FAIL_CUSTOM
|
|
end
|
|
return UF_SUCCESS
|
|
end
|
|
function ability_rubick_spellsteal_custom.prototype.GetCustomCastErrorTarget(self, target)
|
|
local caster = self:GetCaster()
|
|
if target == caster then
|
|
return "#dota_hud_error_rubick_spellsteal_self"
|
|
end
|
|
local lastAbilityName = getStealableAbilityFromTarget(nil, target)
|
|
if not lastAbilityName then
|
|
return "#dota_hud_error_rubick_spellsteal_no_ability"
|
|
end
|
|
if UNSTEALABLE_NAMES:has(lastAbilityName) then
|
|
return "#dota_hud_error_rubick_spellsteal_unstealable"
|
|
end
|
|
return ""
|
|
end
|
|
function ability_rubick_spellsteal_custom.prototype.OnSpellStart(self)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local ability = self
|
|
local caster = ability:GetCaster()
|
|
local target = ability:GetCursorTarget()
|
|
if not caster or not target or target:IsNull() or target == caster then
|
|
return
|
|
end
|
|
if not target:IsRealHero() then
|
|
EmitSoundOn("Hero_Rubick.SpellSteal.Fail", caster)
|
|
return
|
|
end
|
|
local duration = -1
|
|
print(((((("[SpellSteal] OnSpellStart: caster=" .. caster:GetUnitName()) .. ", target=") .. target:GetUnitName()) .. " (") .. tostring(target:entindex())) .. ")")
|
|
local lastAbilityName = getStealableAbilityFromTarget(nil, target)
|
|
if not lastAbilityName or UNSTEALABLE_NAMES:has(lastAbilityName) then
|
|
EmitSoundOn("Hero_Rubick.SpellSteal.Fail", caster)
|
|
return
|
|
end
|
|
print("[SpellSteal] stealing: " .. lastAbilityName)
|
|
local targetAbility = target:FindAbilityByName(lastAbilityName)
|
|
local stolenLevel = targetAbility and targetAbility:GetLevel() or 1
|
|
local hasAghs = caster:HasScepter()
|
|
local slot1Ability = caster:GetAbilityByIndex(STOLEN_SLOT_1)
|
|
local slot1Name = slot1Ability and not slot1Ability:IsNull() and slot1Ability:GetAbilityName() or nil
|
|
local slot2Ability = caster:GetAbilityByIndex(STOLEN_SLOT_2)
|
|
local slot2Name = slot2Ability and not slot2Ability:IsNull() and slot2Ability:GetAbilityName() or nil
|
|
if lastAbilityName ~= slot1Name and lastAbilityName ~= slot2Name then
|
|
local newAbility = caster:AddAbility(lastAbilityName)
|
|
if newAbility and not newAbility:IsNull() then
|
|
newAbility:SetLevel(math.min(
|
|
stolenLevel,
|
|
newAbility:GetMaxLevel()
|
|
))
|
|
newAbility:SetStolen(true)
|
|
if slot1Name then
|
|
caster:SwapAbilities(slot1Name, lastAbilityName, false, true)
|
|
if hasAghs and not __TS__ArrayIncludes(EMPTY_SLOTS, slot1Name) then
|
|
if slot2Name then
|
|
caster:SwapAbilities(slot2Name, slot1Name, false, true)
|
|
caster:RemoveAbility(slot2Name)
|
|
end
|
|
else
|
|
caster:RemoveAbility(slot1Name)
|
|
end
|
|
end
|
|
end
|
|
print((((("[SpellSteal] SUCCESS: stole " .. lastAbilityName) .. " (lvl ") .. tostring(stolenLevel)) .. ")") .. (hasAghs and " [Aghs]" or ""))
|
|
else
|
|
print(("[SpellSteal] already have " .. lastAbilityName) .. ", skipping swap")
|
|
end
|
|
caster:AddNewModifier(caster, ability, ____exports.modifier_rubick_spellsteal_stolen.name, {duration = duration})
|
|
local arcanaParticle = "particles/econ/items/rubick/rubick_arcana/rubick_arc_loadout_spell_steal.vpcf"
|
|
local particle = ParticleManager:CreateParticle(arcanaParticle, PATTACH_WORLDORIGIN, nil)
|
|
ParticleManager:SetParticleControlEnt(
|
|
particle,
|
|
0,
|
|
target,
|
|
PATTACH_POINT_FOLLOW,
|
|
"attach_hitloc",
|
|
Vector(0, 0, 0),
|
|
true
|
|
)
|
|
ParticleManager:SetParticleControlEnt(
|
|
particle,
|
|
1,
|
|
caster,
|
|
PATTACH_POINT_FOLLOW,
|
|
"attach_hitloc",
|
|
Vector(0, 0, 0),
|
|
true
|
|
)
|
|
ParticleManager:ReleaseParticleIndex(particle)
|
|
EmitSoundOn("Hero_Rubick.SpellSteal.Target", target)
|
|
EmitSoundOn("Hero_Rubick.SpellSteal", caster)
|
|
end
|
|
ability_rubick_spellsteal_custom = __TS__Decorate(
|
|
ability_rubick_spellsteal_custom,
|
|
ability_rubick_spellsteal_custom,
|
|
{registerAbility(nil)},
|
|
{kind = "class", name = "ability_rubick_spellsteal_custom"}
|
|
)
|
|
____exports.ability_rubick_spellsteal_custom = ability_rubick_spellsteal_custom
|
|
____exports.modifier_rubick_spellsteal_stolen = __TS__Class()
|
|
local modifier_rubick_spellsteal_stolen = ____exports.modifier_rubick_spellsteal_stolen
|
|
modifier_rubick_spellsteal_stolen.name = "modifier_rubick_spellsteal_stolen"
|
|
modifier_rubick_spellsteal_stolen.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua"
|
|
__TS__ClassExtends(modifier_rubick_spellsteal_stolen, BaseModifier)
|
|
function modifier_rubick_spellsteal_stolen.prototype.IsHidden(self)
|
|
return true
|
|
end
|
|
function modifier_rubick_spellsteal_stolen.prototype.IsPurgable(self)
|
|
return false
|
|
end
|
|
modifier_rubick_spellsteal_stolen = __TS__Decorate(
|
|
modifier_rubick_spellsteal_stolen,
|
|
modifier_rubick_spellsteal_stolen,
|
|
{registerModifier(nil)},
|
|
{kind = "class", name = "modifier_rubick_spellsteal_stolen"}
|
|
)
|
|
____exports.modifier_rubick_spellsteal_stolen = modifier_rubick_spellsteal_stolen
|
|
return ____exports
|