182 lines
6.4 KiB
Lua
182 lines
6.4 KiB
Lua
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
|
|
local ____exports = {}
|
|
local ____dota_ts_adapter = require("lib.dota_ts_adapter")
|
|
local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction
|
|
--- Как в npc_classic_snowman Ability1 / KV abilities.
|
|
local SNOWBALL_ABILITY_NAME = "ability_yuki_snowball"
|
|
--- Широкий поиск союзных героев вокруг снеговика.
|
|
local SEARCH_RADIUS = 1100
|
|
--- Поиск героя для преследования, если в радиусе каста снежка никого нет.
|
|
local CHASE_SCAN_RADIUS = 12000
|
|
--- Частые тики — пока цель подходит под HP и кулдаун 0, касты идут подряд.
|
|
local THINK_INTERVAL = 0.05
|
|
--- Не спамить приказ движения при преследовании.
|
|
local CHASE_MOVE_INTERVAL = 0.35
|
|
--- Кидать снежок только если у героя строго меньше этого % HP.
|
|
local SNOWBALL_ONLY_IF_TARGET_HP_PCT_BELOW = 98
|
|
local snowballAbility
|
|
local lastChaseMoveGameTime = -999
|
|
local function resolveSnowballAbility(self)
|
|
if not thisEntity or thisEntity:IsNull() then
|
|
return nil
|
|
end
|
|
return thisEntity:FindAbilityByName(SNOWBALL_ABILITY_NAME) or thisEntity:GetAbilityByIndex(0) or nil
|
|
end
|
|
local function tryCastSnowballOnAlly(self, target)
|
|
if not snowballAbility or snowballAbility:IsNull() or not thisEntity or thisEntity:IsNull() then
|
|
return
|
|
end
|
|
local pid = thisEntity:GetPlayerOwnerID()
|
|
if pid >= 0 then
|
|
thisEntity:CastAbilityOnTarget(target, snowballAbility, pid)
|
|
return
|
|
end
|
|
ExecuteOrderFromTable({
|
|
UnitIndex = thisEntity:entindex(),
|
|
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
|
|
TargetIndex = target:entindex(),
|
|
AbilityIndex = snowballAbility:entindex(),
|
|
Queue = false
|
|
})
|
|
end
|
|
--- Союзные герои в радиусе; без других снеговиков (тот же unit name).
|
|
local function getFriendlyHeroesInRadius(self, radius)
|
|
if not thisEntity or thisEntity:IsNull() then
|
|
return {}
|
|
end
|
|
local units = FindUnitsInRadius(
|
|
thisEntity:GetTeamNumber(),
|
|
thisEntity:GetAbsOrigin(),
|
|
nil,
|
|
radius,
|
|
DOTA_UNIT_TARGET_TEAM_FRIENDLY,
|
|
DOTA_UNIT_TARGET_HERO,
|
|
bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE),
|
|
FIND_CLOSEST,
|
|
false
|
|
)
|
|
local selfName = thisEntity:GetUnitName()
|
|
local out = {}
|
|
for ____, u in ipairs(units) do
|
|
do
|
|
if not u or u:IsNull() or not u:IsAlive() then
|
|
goto __continue9
|
|
end
|
|
if u:GetUnitName() == selfName then
|
|
goto __continue9
|
|
end
|
|
out[#out + 1] = u
|
|
end
|
|
::__continue9::
|
|
end
|
|
return out
|
|
end
|
|
--- Ближайший союзный герой в большом радиусе (порядок FIND_CLOSEST).
|
|
local function findClosestFriendlyHero(self, maxRadius)
|
|
local list = getFriendlyHeroesInRadius(nil, maxRadius)
|
|
return #list > 0 and list[1] or nil
|
|
end
|
|
local function tryChaseNearestHero(self)
|
|
if not thisEntity or thisEntity:IsNull() then
|
|
return
|
|
end
|
|
local hero = findClosestFriendlyHero(nil, CHASE_SCAN_RADIUS)
|
|
if not hero then
|
|
return
|
|
end
|
|
local now = GameRules:GetGameTime()
|
|
if now - lastChaseMoveGameTime < CHASE_MOVE_INTERVAL then
|
|
return
|
|
end
|
|
local dest = GetGroundPosition(
|
|
hero:GetAbsOrigin(),
|
|
hero
|
|
)
|
|
thisEntity:MoveToPosition(dest)
|
|
lastChaseMoveGameTime = now
|
|
end
|
|
local function snowmanThink(self)
|
|
if not IsServer() or not thisEntity or thisEntity:IsNull() or not thisEntity:IsAlive() then
|
|
return THINK_INTERVAL
|
|
end
|
|
if GameRules:IsGamePaused() or GameRules:State_Get() == DOTA_GAMERULES_STATE_POST_GAME then
|
|
return THINK_INTERVAL
|
|
end
|
|
if not snowballAbility or snowballAbility:IsNull() then
|
|
snowballAbility = resolveSnowballAbility(nil)
|
|
if not snowballAbility then
|
|
return THINK_INTERVAL
|
|
end
|
|
end
|
|
local closeHeroes = getFriendlyHeroesInRadius(nil, SEARCH_RADIUS)
|
|
local selfName = thisEntity:GetUnitName()
|
|
local origin = thisEntity:GetAbsOrigin()
|
|
local ab = snowballAbility
|
|
--- В радиусе каста снежка (AbilityCastRange), не считая порог HP.
|
|
local heroesInSnowballRange = {}
|
|
for ____, ally in ipairs(closeHeroes) do
|
|
do
|
|
if not ally or ally:IsNull() or not ally:IsAlive() then
|
|
goto __continue23
|
|
end
|
|
if ally:GetUnitName() == selfName then
|
|
goto __continue23
|
|
end
|
|
local maxCast = ab:GetCastRange(origin, ally)
|
|
local dist = (ally:GetAbsOrigin() - origin):Length2D()
|
|
if dist <= maxCast + 32 then
|
|
heroesInSnowballRange[#heroesInSnowballRange + 1] = ally
|
|
end
|
|
end
|
|
::__continue23::
|
|
end
|
|
if #heroesInSnowballRange == 0 then
|
|
tryChaseNearestHero(nil)
|
|
return THINK_INTERVAL
|
|
end
|
|
local lowestAlly
|
|
local lowestHpPct = 101
|
|
for ____, ally in ipairs(heroesInSnowballRange) do
|
|
do
|
|
if not ally or ally:IsNull() or not ally:IsAlive() then
|
|
goto __continue29
|
|
end
|
|
local hpPct = ally:GetHealth() / math.max(
|
|
1,
|
|
ally:GetMaxHealth()
|
|
) * 100
|
|
if hpPct >= SNOWBALL_ONLY_IF_TARGET_HP_PCT_BELOW then
|
|
goto __continue29
|
|
end
|
|
if hpPct < lowestHpPct then
|
|
lowestHpPct = hpPct
|
|
lowestAlly = ally
|
|
end
|
|
end
|
|
::__continue29::
|
|
end
|
|
if lowestAlly and ab:IsFullyCastable() then
|
|
tryCastSnowballOnAlly(nil, lowestAlly)
|
|
end
|
|
return THINK_INTERVAL
|
|
end
|
|
registerEntityFunction(
|
|
nil,
|
|
"Spawn",
|
|
function()
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
if not thisEntity or thisEntity:IsNull() then
|
|
return
|
|
end
|
|
snowballAbility = resolveSnowballAbility(nil)
|
|
if not snowballAbility then
|
|
print(("[ai_snowman] не найдена способность " .. SNOWBALL_ABILITY_NAME) .. " — ИИ не запущен")
|
|
return
|
|
end
|
|
thisEntity:SetContextThink("SnowmanThink", snowmanThink, THINK_INTERVAL)
|
|
end
|
|
)
|
|
return ____exports
|