--[[ 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