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

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