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

2902 lines
159 KiB
Lua

local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local Map = ____lualib.Map
local __TS__New = ____lualib.__TS__New
local __TS__ArraySome = ____lualib.__TS__ArraySome
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith
local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach
local __TS__StringIncludes = ____lualib.__TS__StringIncludes
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf
local __TS__ArraySplice = ____lualib.__TS__ArraySplice
local __TS__StringReplace = ____lualib.__TS__StringReplace
local __TS__ParseInt = ____lualib.__TS__ParseInt
local __TS__Number = ____lualib.__TS__Number
local __TS__NumberIsNaN = ____lualib.__TS__NumberIsNaN
local __TS__ArrayReduce = ____lualib.__TS__ArrayReduce
local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom
local ____exports = {}
____exports.SpawnManager = __TS__Class()
local SpawnManager = ____exports.SpawnManager
SpawnManager.name = "SpawnManager"
SpawnManager.____file_path = "scripts/vscripts/SpawnManager.lua"
function SpawnManager.prototype.____constructor(self)
self.spawnConfigs = __TS__New(Map)
self.spawnTimers = __TS__New(Map)
self.spawnedUnits = __TS__New(Map)
self.unitGroups = __TS__New(Map)
self.zoneRespawnScheduled = __TS__New(Map)
self.zoneTimerHandles = __TS__New(Map)
self.debugZonesVisible = false
self.spawnConfigs = __TS__New(Map)
self.spawnedUnits = __TS__New(Map)
self.spawnTimers = __TS__New(Map)
ListenToGameEvent(
"entity_killed",
function(event)
local killedIndex = event.entindex_killed
if killedIndex == nil or killedIndex == nil then
return
end
local killedUnit = EntIndexToHScript(killedIndex)
if not killedUnit or not IsValidEntity(killedUnit) then
return
end
self.spawnConfigs:forEach(function(____, config, zoneId)
local units = self.spawnedUnits:get(zoneId) or ({})
local wasInZone = __TS__ArraySome(
units,
function(____, unit) return unit == killedUnit end
)
if wasInZone then
local aliveUnits = __TS__ArrayFilter(
units,
function(____, unit) return unit ~= nil and unit ~= killedUnit and unit:IsAlive() end
)
self.spawnedUnits:set(zoneId, aliveUnits)
self:removeUnitFromGroup(killedUnit)
self:QueueRespawn(zoneId)
end
end)
end,
nil
)
ListenToGameEvent(
"entity_hurt",
function(event)
local hurtIndex = event.entindex_victim
local attackerIndex = event.entindex_attacker
if hurtIndex == nil or hurtIndex == nil then
return
end
if attackerIndex == nil or attackerIndex == nil then
return
end
local hurtUnit = EntIndexToHScript(hurtIndex)
local attacker = EntIndexToHScript(attackerIndex)
if not hurtUnit or not attacker or not IsValidEntity(hurtUnit) or not IsValidEntity(attacker) then
return
end
self.spawnConfigs:forEach(function(____, config, zoneId)
local units = self.spawnedUnits:get(zoneId) or ({})
local wasInZone = __TS__ArraySome(
units,
function(____, unit) return unit == hurtUnit end
)
if wasInZone and hurtUnit:IsAlive() and attacker:IsAlive() then
if hurtUnit:GetTeamNumber() ~= attacker:GetTeamNumber() then
hurtUnit:SetContextNum(
"last_attacker",
attacker:entindex(),
5
)
if config.moveInGroups then
local group = self:getGroupForUnit(hurtUnit)
if group then
for ____, mate in ipairs(group) do
if mate ~= hurtUnit and mate:IsAlive() and IsValidEntity(mate) and mate:GetTeamNumber() ~= attacker:GetTeamNumber() then
if mate:GetAttackCapability() ~= DOTA_UNIT_CAP_NO_ATTACK then
ExecuteOrderFromTable({
UnitIndex = mate:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = attacker:entindex(),
Queue = false
})
end
end
end
end
end
end
end
end)
end,
nil
)
self:Think()
end
function SpawnManager.getInstance(self)
if not ____exports.SpawnManager.instance then
____exports.SpawnManager.instance = __TS__New(____exports.SpawnManager)
end
return ____exports.SpawnManager.instance
end
function SpawnManager.prototype.Initialize(self)
local spawnPoints = {}
local currentEntity = Entities:First()
while currentEntity ~= nil do
local entityName = currentEntity:GetName()
if __TS__StringStartsWith(entityName, "spawn_point_") then
if not spawnPoints[entityName] then
spawnPoints[entityName] = {}
end
local ____spawnPoints_entityName_0 = spawnPoints[entityName]
____spawnPoints_entityName_0[#____spawnPoints_entityName_0 + 1] = currentEntity:GetAbsOrigin()
end
currentEntity = Entities:Next(currentEntity)
end
if spawnPoints.spawn_point_witch then
__TS__ArrayForEach(
spawnPoints.spawn_point_witch,
function(____, position, index)
local zoneId = "zone_witch_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_witch", count = 1}},
position = position,
radius = 650,
interval = 300,
team = DOTA_TEAM_BADGUYS,
behavior = "aggressive"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_pigs then
__TS__ArrayForEach(
spawnPoints.spawn_point_pigs,
function(____, position, index)
local zoneId = "zone_pigs_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_pig", count = 5}},
position = position,
radius = 600,
width = 2400,
height = 1100,
interval = 5,
team = DOTA_TEAM_NEUTRALS,
behavior = "friendly",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_sheep then
__TS__ArrayForEach(
spawnPoints.spawn_point_sheep,
function(____, position, index)
local zoneId = "zone_sheep_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_sheep", count = 5, canUseAbilities = true}},
position = position,
radius = 600,
interval = 5,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_wolf then
__TS__ArrayForEach(
spawnPoints.spawn_point_wolf,
function(____, position, index)
local zoneId = "zone_wolf_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_wolf", count = 6, groupKey = "wolf"}, {unitName = "npc_ent", count = 2}},
position = position,
radius = 1500,
interval = 7,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
moveInGroups = {groupSize = 3, stayRadius = 280}
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_chicken then
__TS__ArrayForEach(
spawnPoints.spawn_point_chicken,
function(____, position, index)
local zoneId = "zone_chicken_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_chicken", count = 8, canUseAbilities = true}},
position = position,
radius = 1500,
interval = 8,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square",
width = 1000,
height = 2450
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_skeletons then
__TS__ArrayForEach(
spawnPoints.spawn_point_skeletons,
function(____, position, index)
local zoneId = "zone_skeletons_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_skeleton_zombie_undead", count = 1, canUseAbilities = true}, {unitName = "npc_skeleton_zombie_half_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_archer_undead", count = 1, canUseAbilities = true}},
position = position,
radius = 600,
width = 1000,
height = 3300,
interval = 5,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_skeletons2 then
__TS__ArrayForEach(
spawnPoints.spawn_point_skeletons2,
function(____, position, index)
local zoneId = "zone_skeletons2_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_skeleton_zombie_undead", count = 1, canUseAbilities = true}, {unitName = "npc_skeleton_zombie_half_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_archer_undead", count = 1, canUseAbilities = true}},
position = position,
radius = 600,
width = 2000,
height = 2000,
interval = 5,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_fish then
__TS__ArrayForEach(
spawnPoints.spawn_point_fish,
function(____, position, index)
local zoneId = "zone_fish_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_fish_1", count = 4}, {unitName = "npc_fish_2", count = 4}, {unitName = "npc_bomb", count = 6}},
position = position,
radius = 1500,
interval = 8,
team = DOTA_TEAM_NEUTRALS,
behavior = "friendly"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_wisps then
__TS__ArrayForEach(
spawnPoints.spawn_point_wisps,
function(____, position, index)
local zoneId = "zone_wisps_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_wisps", count = 6}},
position = position,
radius = 1200,
interval = 8,
team = DOTA_TEAM_NEUTRALS,
behavior = "friendly"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_thief then
__TS__ArrayForEach(
spawnPoints.spawn_point_thief,
function(____, position, index)
local zoneId = "zone_thief_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_thief_leader", count = 1, canUseAbilities = true, groupKey = "thief"}, {unitName = "npc_thief_archer", count = 2, canUseAbilities = true, groupKey = "thief"}, {
unitName = "npc_thief_backer",
count = 2,
canUseAbilities = true,
groupKey = "thief",
ignoreAIWhileHasModifiers = {"modifier_spirit_breaker_charge_of_darkness"}
}},
position = position,
radius = 600,
width = 1200,
height = 15500,
interval = 180,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square",
moveInGroups = {groupSize = 5, stayRadius = 450}
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_dragons then
__TS__ArrayForEach(
spawnPoints.spawn_point_dragons,
function(____, position, index)
local zoneId = "zone_dragons_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {
{unitName = "npc_black_dragon", count = 2, canUseAbilities = true, kiteRetreatDistance = 250},
{unitName = "npc_red_dragon", count = 2},
{unitName = "npc_blue_dragon_small", count = 2, canUseAbilities = true},
{unitName = "npc_blue_dragon", count = 2, canUseAbilities = true},
{unitName = "npc_red_dragon_small", count = 2, kiteRetreatDistance = 550}
},
position = position,
radius = 600,
width = 3400,
height = 3400,
interval = 15,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_small_frog then
__TS__ArrayForEach(
spawnPoints.spawn_point_small_frog,
function(____, position, index)
local zoneId = "zone_small_frog_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_frogman_magi", count = 3, canUseAbilities = true}, {unitName = "npc_frop_tadpole", count = 3, canUseAbilities = true}, {unitName = "npc_small_frog_froglet", count = 2, canUseAbilities = true}, {unitName = "npc_mini_frog", count = 2, canUseAbilities = true}},
position = position,
radius = 600,
width = 3400,
height = 3400,
interval = 6,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
shape = "square"
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
if spawnPoints.spawn_point_snakes_spiders then
__TS__ArrayForEach(
spawnPoints.spawn_point_snakes_spiders,
function(____, position, index)
local zoneId = "zone_snakes_spiders_" .. tostring(index)
self:AddSpawnZone(zoneId, {
units = {{unitName = "npc_venomancer_brute", count = 3, canUseAbilities = true, groupKey = "snakes"}, {unitName = "npc_ravenous_woodfang", count = 3, canUseAbilities = true, groupKey = "spiders"}, {unitName = "npc_lycosidae_stalker", count = 3, canUseAbilities = true, groupKey = "snakes"}},
position = position,
radius = 3500,
interval = 8,
team = DOTA_TEAM_NEUTRALS,
behavior = "aggressive",
moveInGroups = {groupSize = 5, stayRadius = 250}
})
local config = self.spawnConfigs:get(zoneId)
self:SpawnInitialUnits(zoneId, config)
end
)
end
end
function SpawnManager.prototype.AddSpawnZone(self, id, config)
self.spawnConfigs:set(id, config)
self.spawnedUnits:set(id, {})
if self.debugZonesVisible then
self:DrawSpawnZoneDebug(id, config)
end
end
function SpawnManager.prototype.CreateZoneFromAdmin(self, id, config)
self:AddSpawnZone(id, config)
self:SpawnInitialUnits(id, config)
end
function SpawnManager.prototype.ToggleDebugZones(self)
self.debugZonesVisible = not self.debugZonesVisible
if self.debugZonesVisible then
self.spawnConfigs:forEach(function(____, config, id)
self:DrawSpawnZoneDebug(id, config)
end)
else
DebugDrawClear()
end
end
function SpawnManager.prototype.Think(self)
Timers:CreateTimer(
1,
function()
self.spawnConfigs:forEach(function(____, _, zoneId)
local units = self.spawnedUnits:get(zoneId) or ({})
local validUnits = __TS__ArrayFilter(
units,
function(____, unit) return unit and IsValidEntity(unit) and unit:IsAlive() end
)
self.spawnedUnits:set(zoneId, validUnits)
end)
self.unitGroups:forEach(function(____, arr, groupId)
local valid = __TS__ArrayFilter(
arr,
function(____, u) return u and IsValidEntity(u) and u:IsAlive() end
)
if #valid == 0 then
self.unitGroups:delete(groupId)
else
self.unitGroups:set(groupId, valid)
end
end)
return 1
end
)
end
function SpawnManager.prototype.startZoneTimer(self, zoneId, delay, callback)
local handle = Timers:CreateTimer(
delay,
function()
if not self.spawnConfigs:has(zoneId) then
return nil
end
return callback(nil)
end
)
local list = self.zoneTimerHandles:get(zoneId)
if not list then
list = {}
self.zoneTimerHandles:set(zoneId, list)
end
list[#list + 1] = handle
end
function SpawnManager.prototype.cancelZoneTimers(self, zoneId)
local list = self.zoneTimerHandles:get(zoneId)
if not list then
return
end
for ____, h in ipairs(list) do
if h ~= nil and h ~= nil then
Timers:RemoveTimer(h)
end
end
self.zoneTimerHandles:delete(zoneId)
end
function SpawnManager.prototype.DrawSpawnZoneDebug(self, id, config)
local function getZoneColor(____, zoneId)
if __TS__StringIncludes(zoneId, "witch") then
return Vector(255, 0, 255)
end
if __TS__StringIncludes(zoneId, "pigs") then
return Vector(255, 200, 150)
end
if __TS__StringIncludes(zoneId, "sheep") then
return Vector(255, 255, 255)
end
if __TS__StringIncludes(zoneId, "wolf") then
return Vector(100, 100, 255)
end
if __TS__StringIncludes(zoneId, "skeletons") then
return Vector(200, 200, 200)
end
return Vector(0, 255, 0)
end
local zoneColor = getZoneColor(nil, id)
DebugDrawCircle(
config.position,
Vector(255, 255, 0),
255,
50,
true,
-1
)
if config.shape == "square" then
local halfWidth = config.width ~= nil and config.width / 2 or config.radius
local halfHeight = config.height ~= nil and config.height / 2 or config.radius
local z = config.position.z
local center = config.position
local topLeft = Vector(center.x - halfWidth, center.y + halfHeight, z)
local topRight = Vector(center.x + halfWidth, center.y + halfHeight, z)
local bottomRight = Vector(center.x + halfWidth, center.y - halfHeight, z)
local bottomLeft = Vector(center.x - halfWidth, center.y - halfHeight, z)
DebugDrawLine(
topLeft,
topRight,
zoneColor.x,
zoneColor.y,
zoneColor.z,
true,
-1
)
DebugDrawLine(
topRight,
bottomRight,
zoneColor.x,
zoneColor.y,
zoneColor.z,
true,
-1
)
DebugDrawLine(
bottomRight,
bottomLeft,
zoneColor.x,
zoneColor.y,
zoneColor.z,
true,
-1
)
DebugDrawLine(
bottomLeft,
topLeft,
zoneColor.x,
zoneColor.y,
zoneColor.z,
true,
-1
)
else
DebugDrawCircle(
config.position,
zoneColor,
100,
config.radius,
true,
-1
)
end
end
function SpawnManager.prototype.isWithinZoneBounds(self, config, pos)
local dx = pos.x - config.position.x
local dy = pos.y - config.position.y
if config.shape == "square" then
local halfWidth = config.width ~= nil and config.width / 2 or config.radius
local halfHeight = config.height ~= nil and config.height / 2 or config.radius
return math.abs(dx) <= halfWidth and math.abs(dy) <= halfHeight
end
local distanceSquared = dx * dx + dy * dy
return distanceSquared <= config.radius * config.radius
end
function SpawnManager.prototype.isWithinChaseRange(self, config, pos)
local dx = pos.x - config.position.x
local dy = pos.y - config.position.y
local margin = ____exports.SpawnManager.CHASE_LEASH_MARGIN
if config.shape == "square" then
local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) + margin
local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) + margin
return math.abs(dx) <= halfWidth and math.abs(dy) <= halfHeight
end
local distanceSquared = dx * dx + dy * dy
local maxRadius = config.radius + margin
return distanceSquared <= maxRadius * maxRadius
end
function SpawnManager.prototype.getDeepPositionInZone(self, config)
if config.shape == "square" then
local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) * 0.5
local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) * 0.5
local offsetX = RandomFloat(-halfWidth, halfWidth)
local offsetY = RandomFloat(-halfHeight, halfHeight)
return GetGroundPosition(
Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z),
nil
)
end
local angle = RandomFloat(0, 2 * math.pi)
local distance = RandomFloat(0, config.radius * 0.5)
return GetGroundPosition(
Vector(
config.position.x + distance * math.cos(angle),
config.position.y + distance * math.sin(angle),
config.position.z
),
nil
)
end
function SpawnManager.prototype.clampToZoneBounds(self, config, pos)
local dx = pos.x - config.position.x
local dy = pos.y - config.position.y
if config.shape == "square" then
local halfWidth = config.width ~= nil and config.width / 2 or config.radius
local halfHeight = config.height ~= nil and config.height / 2 or config.radius
local clampedX = math.max(
-halfWidth,
math.min(halfWidth, dx)
)
local clampedY = math.max(
-halfHeight,
math.min(halfHeight, dy)
)
return GetGroundPosition(
Vector(config.position.x + clampedX, config.position.y + clampedY, pos.z),
nil
)
end
local distanceSquared = dx * dx + dy * dy
if distanceSquared <= config.radius * config.radius then
return pos
end
local distance = math.sqrt(distanceSquared) or 1
local scale = config.radius / distance
return GetGroundPosition(
Vector(config.position.x + dx * scale, config.position.y + dy * scale, pos.z),
nil
)
end
function SpawnManager.prototype.getGroupForUnit(self, unit)
for ____, ____value in __TS__Iterator(self.unitGroups) do
local arr = ____value[2]
if __TS__ArraySome(
arr,
function(____, u) return u == unit end
) then
return arr
end
end
return nil
end
function SpawnManager.prototype.removeUnitFromGroup(self, unit)
for ____, ____value in __TS__Iterator(self.unitGroups) do
local groupId = ____value[1]
local arr = ____value[2]
local idx = __TS__ArrayIndexOf(arr, unit)
if idx >= 0 then
__TS__ArraySplice(arr, idx, 1)
if #arr == 0 then
self.unitGroups:delete(groupId)
end
return
end
end
end
function SpawnManager.prototype.findOrCreateGroupForSpawn(self, zoneId, config, groupKey)
local groupSize = config.moveInGroups.groupSize
local prefix = ((zoneId .. "_") .. groupKey) .. "_grp_"
local maxIndex = -1
for ____, ____value in __TS__Iterator(self.unitGroups) do
local gid = ____value[1]
local arr = ____value[2]
do
if not __TS__StringStartsWith(gid, prefix) then
goto __continue109
end
local alive = __TS__ArrayFilter(
arr,
function(____, u) return u and IsValidEntity(u) and u:IsAlive() end
)
if #alive < groupSize then
local center = self:getGroupCenter(alive)
return {groupId = gid, spawnNear = center}
end
local num = __TS__ParseInt(
__TS__StringReplace(gid, prefix, ""),
10
)
if not __TS__NumberIsNaN(__TS__Number(num)) and num > maxIndex then
maxIndex = num
end
end
::__continue109::
end
local newId = prefix .. tostring(maxIndex + 1)
self.unitGroups:set(newId, {})
return {groupId = newId}
end
function SpawnManager.prototype.getGroupCenter(self, units)
local alive = __TS__ArrayFilter(
units,
function(____, u) return u and IsValidEntity(u) and u:IsAlive() end
)
if #alive == 0 then
return Vector(0, 0, 0)
end
local x = 0
local y = 0
local z = 0
for ____, u in ipairs(alive) do
local p = u:GetAbsOrigin()
x = x + p.x
y = y + p.y
z = z + p.z
end
local n = #alive
return Vector(x / n, y / n, z / n)
end
function SpawnManager.prototype.getGroupLeader(self, units)
local alive = __TS__ArrayFilter(
units,
function(____, u) return u and IsValidEntity(u) and u:IsAlive() end
)
return alive[1]
end
function SpawnManager.prototype.isSpawnAllowed(self)
return GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS
end
function SpawnManager.prototype.RemoveZoneAtPosition(self, pos)
for ____, ____value in __TS__Iterator(self.spawnConfigs) do
local id = ____value[1]
local config = ____value[2]
if self:isWithinZoneBounds(config, pos) then
self:RemoveSpawnZone(id)
return id
end
end
return nil
end
function SpawnManager.prototype.SpawnInitialUnits(self, zoneId, config)
if not self:isSpawnAllowed() then
self:startZoneTimer(
zoneId,
0.5,
function()
self:SpawnInitialUnits(zoneId, config)
return nil
end
)
return
end
__TS__ArrayForEach(
config.units,
function(____, unitInfo)
do
local i = 0
while i < unitInfo.count do
self:startZoneTimer(
zoneId,
i * 0.1,
function()
self:SpawnUnit(zoneId, config, unitInfo)
return nil
end
)
i = i + 1
end
end
end
)
end
function SpawnManager.prototype.SpawnUnit(self, id, config, unitInfo)
if not self.spawnConfigs:has(id) then
return
end
local unitName = unitInfo.unitName
local units = self.spawnedUnits:get(id)
local aliveUnits = __TS__ArrayFilter(
units,
function(____, unit) return unit ~= nil and unit:IsAlive() end
)
local currentTypeCount = #__TS__ArrayFilter(
aliveUnits,
function(____, unit)
do
local function ____catch(____error)
return true, false
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
return true, unit and IsValidEntity(unit) and self:getSpawnUnitConfigName(unit) == unitName
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
end
)
if currentTypeCount >= unitInfo.count then
return
end
local totalMaxUnits = __TS__ArrayReduce(
config.units,
function(____, sum, unit) return sum + unit.count end,
0
)
if #aliveUnits >= totalMaxUnits then
return
end
local groupInfo
if config.moveInGroups and unitInfo.groupKey then
groupInfo = self:findOrCreateGroupForSpawn(id, config, unitInfo.groupKey)
end
local function isWithinRadius(____, pos)
return self:isWithinZoneBounds(config, pos)
end
local spawnPos
local attempts = 0
local maxAttempts = 20
while not spawnPos and attempts < maxAttempts do
attempts = attempts + 1
local testPos
if config.moveInGroups and (groupInfo and groupInfo.spawnNear) and attempts <= 5 then
local r = config.moveInGroups.stayRadius or 250
testPos = GetGroundPosition(
Vector(
groupInfo.spawnNear.x + RandomFloat(-r, r),
groupInfo.spawnNear.y + RandomFloat(-r, r),
groupInfo.spawnNear.z
),
nil
)
elseif config.shape == "square" then
local halfWidth = config.width ~= nil and config.width / 2 or config.radius
local halfHeight = config.height ~= nil and config.height / 2 or config.radius
local offsetX = RandomFloat(-halfWidth, halfWidth)
local offsetY = RandomFloat(-halfHeight, halfHeight)
testPos = GetGroundPosition(
Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z),
nil
)
else
local angle = RandomFloat(0, 2 * math.pi)
local distance = RandomFloat(0, config.radius)
testPos = GetGroundPosition(
Vector(
config.position.x + distance * math.cos(angle),
config.position.y + distance * math.sin(angle),
config.position.z
),
nil
)
end
if isWithinRadius(nil, testPos) then
spawnPos = testPos
break
end
end
if not spawnPos then
spawnPos = config.position
end
local unit = CreateUnitByName(
unitName,
spawnPos,
true,
nil,
nil,
config.team
)
if unit and IsValidEntity(unit) then
unit:SetEntityName((unitInfo.unitName .. "_") .. tostring(unit:entindex()))
unit.__spawnManagerUnitName = unitInfo.unitName
if config.moveInGroups and groupInfo then
local ____temp_3 = self.unitGroups:get(groupInfo.groupId)
____temp_3[#____temp_3 + 1] = unit
end
if unit:IsAlive() then
repeat
local ____switch153 = config.behavior
local friendlySpawnPosition, checkPosition, aggressiveSpawnPosition, canUseAbilities, ignoreAiModifiers, aggressiveLastAbilityUseTime, aggressiveIsApproachingForAbility, aggressiveIsCastingAbility, aggressiveCastingEndTime, aggressiveLastPos, aggressiveLastPosCheckTime, aggressiveStuckCounter, getKiteRetreatDistance, hasReadyAbilities, tryUseAbilitiesAggressive, checkAggressivePosition
local ____cond153 = ____switch153 == "friendly"
if ____cond153 then
unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK)
friendlySpawnPosition = unit:GetAbsOrigin()
checkPosition = function()
if not unit or not unit:IsAlive() then
return nil
end
local currentPos = unit:GetAbsOrigin()
if not self:isWithinZoneBounds(config, currentPos) then
local zoneCenter = config.position
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = zoneCenter,
Queue = false
})
return 1
end
return 1
end
self:startZoneTimer(id, 1, checkPosition)
break
end
____cond153 = ____cond153 or ____switch153 == "aggressive"
if ____cond153 then
aggressiveSpawnPosition = unit:GetAbsOrigin()
local ____unitInfo_canUseAbilities_4 = unitInfo.canUseAbilities
if ____unitInfo_canUseAbilities_4 == nil then
____unitInfo_canUseAbilities_4 = false
end
canUseAbilities = ____unitInfo_canUseAbilities_4
ignoreAiModifiers = unitInfo.ignoreAIWhileHasModifiers or ({})
aggressiveLastAbilityUseTime = 0
aggressiveIsApproachingForAbility = false
aggressiveIsCastingAbility = false
aggressiveCastingEndTime = 0
aggressiveLastPos = unit:GetAbsOrigin()
aggressiveLastPosCheckTime = GameRules:GetGameTime()
aggressiveStuckCounter = 0
getKiteRetreatDistance = function()
return unitInfo.kiteRetreatDistance
end
hasReadyAbilities = function(____, target)
if not canUseAbilities then
return false
end
if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then
return false
end
local now = GameRules:GetGameTime()
if now - aggressiveLastAbilityUseTime < 2 then
return false
end
do
local i = 0
while i < unit:GetAbilityCount() do
do
local ability = unit:GetAbilityByIndex(i)
if not ability or ability:IsNull() then
goto __continue162
end
if ability:IsPassive() or ability:IsHidden() then
goto __continue162
end
if not ability:IsCooldownReady() then
goto __continue162
end
if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then
goto __continue162
end
return true
end
::__continue162::
i = i + 1
end
end
return false
end
tryUseAbilitiesAggressive = function()
if #ignoreAiModifiers > 0 then
for ____, mod in ipairs(ignoreAiModifiers) do
if unit:HasModifier(mod) then
return false
end
end
end
if not canUseAbilities then
return false
end
if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then
return false
end
local now = GameRules:GetGameTime()
if now - aggressiveLastAbilityUseTime < 2 then
return false
end
local enemiesRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
1500,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local enemies = __TS__ArrayFilter(
enemiesRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #enemies == 0 then
return false
end
local target = enemies[1]
do
local i = 0
while i < unit:GetAbilityCount() do
do
local ability = unit:GetAbilityByIndex(i)
if not ability or ability:IsNull() then
goto __continue177
end
local abilityName = ability:GetAbilityName()
local suppressWarningParticles = abilityName == "spider_hunger"
if ability:IsPassive() or ability:IsHidden() then
goto __continue177
end
if not ability:IsCooldownReady() then
goto __continue177
end
local manaCost = ability:GetManaCost(ability:GetLevel())
local currentMana = unit:GetMana()
if manaCost > currentMana then
goto __continue177
end
local behavior = 0
do
local function ____catch(e)
local abilityName = ability:GetAbilityName()
if abilityName == "sheep_coil" then
behavior = 16
elseif abilityName == "black_dragon_fireball" then
behavior = 16
elseif abilityName == "frogmen_acid_jump" then
behavior = 16
else
local behaviorRaw = ability:GetBehavior()
if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then
behavior = behaviorRaw
end
end
end
local ____try, ____hasReturned = pcall(function()
local kv = ability:GetAbilityKeyValues()
if kv and kv.AbilityBehavior then
local behaviorStr = kv.AbilityBehavior
if __TS__StringIncludes(behaviorStr, "POINT") then
behavior = bit.bor(behavior, 16)
end
if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then
behavior = bit.bor(behavior, 32)
end
if __TS__StringIncludes(behaviorStr, "NO_TARGET") then
behavior = bit.bor(behavior, 4)
end
end
end)
if not ____try then
____catch(____hasReturned)
end
end
local pointFlag = 16
local unitTargetFlag = 32
local noTargetFlag = 4
local unitPos = unit:GetAbsOrigin()
local castRange = ability:GetCastRange(unitPos, target)
local targetPos = target:GetAbsOrigin()
local dx = unitPos.x - targetPos.x
local dy = unitPos.y - targetPos.y
local distance = math.sqrt(dx * dx + dy * dy)
local ____table_GetSpecialValueFor_5
if ability.GetSpecialValueFor then
____table_GetSpecialValueFor_5 = ability:GetSpecialValueFor("precast_warning_time") or 1
else
____table_GetSpecialValueFor_5 = 1
end
local warningTime = ____table_GetSpecialValueFor_5
local isThiefUnit = (function()
if not unit or unit:IsNull() then
return false
end
local name = unit:GetUnitName()
return name == "npc_thief_archer" or name == "npc_thief_leader"
end)(nil)
local function createPreCastParticle(____, particlePosition, radius, warningDuration)
if isThiefUnit then
return
end
local duration = warningDuration ~= nil and warningDuration or 1
local groundPos = GetGroundPosition(particlePosition, nil)
local aoeRadius = radius ~= nil and radius or 0
if aoeRadius > 0 then
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, groundPos)
ParticleManager:SetParticleControl(
particle,
1,
Vector(aoeRadius, 0, 0)
)
ParticleManager:SetParticleControl(
particle,
2,
Vector(6, 0, 1)
)
ParticleManager:SetParticleControl(
particle,
3,
Vector(200, 0, 0)
)
ParticleManager:SetParticleControl(particle, 4, groundPos)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
else
local casterPos = GetGroundPosition(
unit:GetAbsOrigin(),
nil
)
local targetPos = groundPos
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, casterPos)
ParticleManager:SetParticleControl(particle, 1, targetPos)
ParticleManager:SetParticleControl(
particle,
2,
Vector(duration, 0, 0)
)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
end
end
local function createDirectionArrow(____, caster, targetPosition, warningDuration)
if isThiefUnit then
return
end
local duration = warningDuration ~= nil and warningDuration or 1
local casterPos = GetGroundPosition(
caster:GetAbsOrigin(),
nil
)
local groundTarget = GetGroundPosition(targetPosition, nil)
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, casterPos)
ParticleManager:SetParticleControl(particle, 1, groundTarget)
ParticleManager:SetParticleControl(
particle,
2,
Vector(duration, 0, 0)
)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
end
if bit.band(behavior, pointFlag) ~= 0 then
if distance <= castRange and target:IsAlive() then
if abilityName ~= "frog_magi_wave" and not suppressWarningParticles then
local ____table_GetAOERadius_6
if ability.GetAOERadius then
____table_GetAOERadius_6 = ability:GetAOERadius()
else
____table_GetAOERadius_6 = 0
end
local aoeRadius = ____table_GetAOERadius_6
local ____temp_7
if aoeRadius > 0 then
____temp_7 = aoeRadius
else
____temp_7 = 200
end
createPreCastParticle(nil, targetPos, ____temp_7, warningTime)
end
local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
aggressiveIsCastingAbility = true
aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0)
self:startZoneTimer(
id,
warningTime,
function()
if not unit or not unit:IsAlive() or not target or not target:IsAlive() then
return nil
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = targetPos,
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
local castPoint = ability:GetCastPoint() or 0
self:startZoneTimer(
id,
castPoint,
function()
aggressiveIsCastingAbility = false
return nil
end
)
return nil
end
)
aggressiveLastAbilityUseTime = now
aggressiveIsApproachingForAbility = false
return true
end
elseif bit.band(behavior, unitTargetFlag) ~= 0 then
if distance <= castRange and target:IsAlive() then
if not suppressWarningParticles then
local overheadParticle = ParticleManager:CreateParticle("particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", PATTACH_OVERHEAD_FOLLOW, target)
if overheadParticle ~= nil then
self:startZoneTimer(
id,
warningTime + 0.5,
function()
if overheadParticle ~= nil then
ParticleManager:DestroyParticle(overheadParticle, false)
ParticleManager:ReleaseParticleIndex(overheadParticle)
end
return nil
end
)
end
end
local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
aggressiveIsCastingAbility = true
aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0)
self:startZoneTimer(
id,
warningTime,
function()
if not unit or not unit:IsAlive() or not target or not target:IsAlive() then
return nil
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
local castPoint = ability:GetCastPoint() or 0
self:startZoneTimer(
id,
castPoint,
function()
aggressiveIsCastingAbility = false
return nil
end
)
return nil
end
)
aggressiveLastAbilityUseTime = now
aggressiveIsApproachingForAbility = false
return true
end
elseif bit.band(behavior, noTargetFlag) ~= 0 then
local lockedTargetPos = target:GetAbsOrigin()
local ____table_GetAOERadius_8
if ability.GetAOERadius then
____table_GetAOERadius_8 = ability:GetAOERadius()
else
____table_GetAOERadius_8 = 0
end
local aoeRadius = ____table_GetAOERadius_8
if aoeRadius <= 0 and ability.GetSpecialValueFor then
local specialRadius = ability:GetSpecialValueFor("radius")
if specialRadius and specialRadius > 0 then
aoeRadius = specialRadius
end
end
if not suppressWarningParticles then
local ____temp_9
if aoeRadius > 0 then
____temp_9 = aoeRadius
else
____temp_9 = 200
end
local followRadius = ____temp_9
local followParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil)
if followParticle ~= nil then
ParticleManager:SetParticleControlEnt(
followParticle,
0,
unit,
PATTACH_ABSORIGIN_FOLLOW,
nil,
unit:GetAbsOrigin(),
true
)
ParticleManager:SetParticleControl(
followParticle,
1,
Vector(followRadius, 0, 0)
)
ParticleManager:SetParticleControl(
followParticle,
2,
Vector(6, 0, 1)
)
ParticleManager:SetParticleControl(
followParticle,
3,
Vector(200, 0, 0)
)
ParticleManager:SetParticleControl(
followParticle,
4,
unit:GetAbsOrigin()
)
self:startZoneTimer(
id,
warningTime,
function()
if followParticle ~= nil then
ParticleManager:DestroyParticle(followParticle, false)
ParticleManager:ReleaseParticleIndex(followParticle)
end
return nil
end
)
end
end
do
local direction = Vector(lockedTargetPos.x - unitPos.x, lockedTargetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
end
if not suppressWarningParticles then
createDirectionArrow(nil, unit, lockedTargetPos, warningTime)
end
self:startZoneTimer(
id,
warningTime,
function()
if not unit or not unit:IsAlive() then
return nil
end
local currentPos = unit:GetAbsOrigin()
local direction = Vector(lockedTargetPos.x - currentPos.x, lockedTargetPos.y - currentPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
aggressiveIsCastingAbility = true
aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
local castPoint = ability:GetCastPoint() or 0
self:startZoneTimer(
id,
castPoint,
function()
aggressiveIsCastingAbility = false
return nil
end
)
return nil
end
)
aggressiveLastAbilityUseTime = now
aggressiveIsApproachingForAbility = false
return true
end
end
::__continue177::
i = i + 1
end
end
return false
end
checkAggressivePosition = function()
if not unit or not unit:IsAlive() then
return nil
end
local currentHealth = unit:GetHealth()
local maxHealth = unit:GetMaxHealth()
local currentPos = unit:GetAbsOrigin()
local now = GameRules:GetGameTime()
if #ignoreAiModifiers > 0 then
for ____, mod in ipairs(ignoreAiModifiers) do
if unit:HasModifier(mod) then
return 0.2
end
end
end
if aggressiveIsCastingAbility then
if now < aggressiveCastingEndTime then
return 0.2
else
aggressiveIsCastingAbility = false
end
end
if config.moveInGroups then
local group = self:getGroupForUnit(unit)
if group and #group > 0 then
local leader = self:getGroupLeader(group)
if leader and leader ~= unit and leader:IsAlive() then
local leaderPos = leader:GetAbsOrigin()
local followRadius = config.moveInGroups.stayRadius or 250
local dxF = currentPos.x - leaderPos.x
local dyF = currentPos.y - leaderPos.y
local distToLeader = math.sqrt(dxF * dxF + dyF * dyF)
if distToLeader > followRadius * 0.7 and not unit:IsAttacking() and not unit:IsChanneling() then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = leaderPos,
Queue = false
})
return 0.2
end
end
end
end
if aggressiveLastPos then
local dt = now - aggressiveLastPosCheckTime
if dt >= 1 then
local dxStuck = currentPos.x - aggressiveLastPos.x
local dyStuck = currentPos.y - aggressiveLastPos.y
local distMoved = math.sqrt(dxStuck * dxStuck + dyStuck * dyStuck)
if distMoved < 10 and not unit:IsChanneling() and not unit:IsAttacking() then
aggressiveStuckCounter = aggressiveStuckCounter + 1
if aggressiveStuckCounter >= 2 then
local escapePos
if config.shape == "square" then
local halfWidth = config.width ~= nil and config.width / 2 or config.radius
local halfHeight = config.height ~= nil and config.height / 2 or config.radius
local offsetX = RandomFloat(-halfWidth, halfWidth)
local offsetY = RandomFloat(-halfHeight, halfHeight)
escapePos = GetGroundPosition(
Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z),
nil
)
else
local angle = RandomFloat(0, 2 * math.pi)
local distance = RandomFloat(0, config.radius)
escapePos = GetGroundPosition(
Vector(
config.position.x + distance * math.cos(angle),
config.position.y + distance * math.sin(angle),
config.position.z
),
nil
)
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = escapePos,
Queue = false
})
aggressiveStuckCounter = 0
end
else
aggressiveStuckCounter = 0
end
aggressiveLastPos = currentPos
aggressiveLastPosCheckTime = now
end
else
aggressiveLastPos = currentPos
aggressiveLastPosCheckTime = now
end
local isOutsideZone = not self:isWithinZoneBounds(config, currentPos)
local lastAttackerIndex = unit:GetContext("last_attacker")
local lastAttacker
if lastAttackerIndex and lastAttackerIndex > 0 then
lastAttacker = EntIndexToHScript(lastAttackerIndex)
if not lastAttacker or not IsValidEntity(lastAttacker) or not lastAttacker:IsAlive() then
lastAttacker = nil
elseif not self:isWithinChaseRange(
config,
lastAttacker:GetAbsOrigin()
) then
lastAttacker = nil
end
end
if lastAttacker and lastAttacker:IsAlive() then
local unitPos = unit:GetAbsOrigin()
local attackerPos = lastAttacker:GetAbsOrigin()
local dx = unitPos.x - attackerPos.x
local dy = unitPos.y - attackerPos.y
local distanceToAttacker = math.sqrt(dx * dx + dy * dy)
local hasAbilities = hasReadyAbilities(nil, lastAttacker)
if hasAbilities then
local used = tryUseAbilitiesAggressive(nil)
if used then
return 0.5
end
end
local retreatDistance = getKiteRetreatDistance(nil)
if not hasAbilities and retreatDistance == nil and distanceToAttacker < 140 then
local attackTarget = unit:GetAttackTarget()
local isAttacking = unit:IsAttacking()
local isInAttackRange = attackTarget == lastAttacker and isAttacking and distanceToAttacker <= unit:GetBaseAttackRange() * 1.2
if isInAttackRange then
return 0.5
end
end
if hasAbilities then
local maxCastRange = 0
local hasReadyAbility = false
do
local i = 0
while i < unit:GetAbilityCount() do
do
local ability = unit:GetAbilityByIndex(i)
if not ability or ability:IsNull() then
goto __continue271
end
if ability:IsPassive() or ability:IsHidden() then
goto __continue271
end
if not ability:IsCooldownReady() then
goto __continue271
end
local manaCost = ability:GetManaCost(ability:GetLevel())
if manaCost > unit:GetMana() then
goto __continue271
end
hasReadyAbility = true
local castRange = ability:GetCastRange(unitPos, lastAttacker)
if castRange > maxCastRange then
maxCastRange = castRange
end
end
::__continue271::
i = i + 1
end
end
if hasReadyAbility and distanceToAttacker > maxCastRange and maxCastRange > 0 then
aggressiveIsApproachingForAbility = true
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = attackerPos,
Queue = false
})
self:startZoneTimer(
id,
3,
function()
aggressiveIsApproachingForAbility = false
return nil
end
)
return 0.5
elseif hasReadyAbility and distanceToAttacker <= maxCastRange then
aggressiveIsApproachingForAbility = false
if retreatDistance ~= nil and distanceToAttacker < retreatDistance then
local dir = Vector(unitPos.x - attackerPos.x, unitPos.y - attackerPos.y, 0)
local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1
local normalizedDir = Vector(dir.x / len, dir.y / len, 0)
local retreatPos = Vector(attackerPos.x + normalizedDir.x * retreatDistance, attackerPos.y + normalizedDir.y * retreatDistance, attackerPos.z)
local ____temp_10
if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then
____temp_10 = GridNav:CanFindPath(unitPos, retreatPos)
else
____temp_10 = true
end
local canEscape = ____temp_10
if canEscape then
print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(attackerPos.x))) .. ",") .. tostring(math.floor(attackerPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [abilities]")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = retreatPos,
Queue = false
})
else
print(unit:GetUnitName() .. " cannot escape while kiting (no path / out of zone), fights back attacker")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = lastAttacker:entindex(),
Queue = false
})
end
else
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = lastAttacker:entindex(),
Queue = false
})
end
return 0.5
else
aggressiveIsApproachingForAbility = false
if retreatDistance ~= nil and distanceToAttacker < retreatDistance then
local dir = Vector(unitPos.x - attackerPos.x, unitPos.y - attackerPos.y, 0)
local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1
local normalizedDir = Vector(dir.x / len, dir.y / len, 0)
local retreatPos = Vector(attackerPos.x + normalizedDir.x * retreatDistance, attackerPos.y + normalizedDir.y * retreatDistance, attackerPos.z)
local ____temp_11
if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then
____temp_11 = GridNav:CanFindPath(unitPos, retreatPos)
else
____temp_11 = true
end
local canEscape = ____temp_11
if canEscape then
print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(attackerPos.x))) .. ",") .. tostring(math.floor(attackerPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [no_abilities]")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = retreatPos,
Queue = false
})
else
print(unit:GetUnitName() .. " cannot escape while kiting without abilities, fights back attacker")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = lastAttacker:entindex(),
Queue = false
})
end
else
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = lastAttacker:entindex(),
Queue = false
})
end
return 0.5
end
end
end
if not aggressiveIsApproachingForAbility then
local detectionRadius = 900
local nearbyEnemiesRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
detectionRadius,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local nearbyEnemies = __TS__ArrayFilter(
nearbyEnemiesRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #nearbyEnemies > 0 then
local closestEnemy = nearbyEnemies[1]
local unitPos = unit:GetAbsOrigin()
local enemyPos = closestEnemy:GetAbsOrigin()
local dx = unitPos.x - enemyPos.x
local dy = unitPos.y - enemyPos.y
local distanceToEnemy = math.sqrt(dx * dx + dy * dy)
if distanceToEnemy <= detectionRadius and closestEnemy:IsAlive() then
local retreatDistance = getKiteRetreatDistance(nil)
if retreatDistance ~= nil and distanceToEnemy < retreatDistance then
local dir = Vector(unitPos.x - enemyPos.x, unitPos.y - enemyPos.y, 0)
local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1
local normalizedDir = Vector(dir.x / len, dir.y / len, 0)
local retreatPos = Vector(enemyPos.x + normalizedDir.x * retreatDistance, enemyPos.y + normalizedDir.y * retreatDistance, enemyPos.z)
local ____temp_12
if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then
____temp_12 = GridNav:CanFindPath(unitPos, retreatPos)
else
____temp_12 = true
end
local canEscape = ____temp_12
if canEscape then
print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(enemyPos.x))) .. ",") .. tostring(math.floor(enemyPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [search]")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = retreatPos,
Queue = false
})
else
print(unit:GetUnitName() .. " cannot escape while kiting in search phase, attacks closest enemy")
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = closestEnemy:entindex(),
Queue = false
})
end
else
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = closestEnemy:entindex(),
Queue = false
})
end
if distanceToEnemy <= 800 then
local usedAbility = tryUseAbilitiesAggressive(nil)
if usedAbility then
return 0.5
end
end
end
end
end
local nearbyEnemiesCheckRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
1000,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local nearbyEnemiesCheck = __TS__ArrayFilter(
nearbyEnemiesCheckRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
local hasReadyAbilitiesForAnyEnemy = false
if #nearbyEnemiesCheck > 0 and canUseAbilities then
for ____, enemy in ipairs(nearbyEnemiesCheck) do
if hasReadyAbilities(nil, enemy) then
hasReadyAbilitiesForAnyEnemy = true
break
end
end
end
if currentHealth >= maxHealth * 0.95 and not aggressiveIsApproachingForAbility and #nearbyEnemiesCheck == 0 and not hasReadyAbilitiesForAnyEnemy then
local currentPos2 = unit:GetAbsOrigin()
local returnTarget = config.moveInGroups and self:getGroupForUnit(unit) and self:getGroupCenter(self:getGroupForUnit(unit)) or aggressiveSpawnPosition
local threshold = config.moveInGroups and (config.moveInGroups.stayRadius or 250) or 200
local dx = currentPos2.x - returnTarget.x
local dy = currentPos2.y - returnTarget.y
local distanceToSpawn = math.sqrt(dx * dx + dy * dy)
if distanceToSpawn > threshold then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = returnTarget,
Queue = false
})
end
end
if isOutsideZone and #nearbyEnemiesCheck == 0 and not hasReadyAbilitiesForAnyEnemy and not aggressiveIsApproachingForAbility then
local deepPos = self:getDeepPositionInZone(config)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = deepPos,
Queue = false
})
self:startZoneTimer(
id,
1,
function()
if not unit or not unit:IsAlive() then
return nil
end
local checkPos = unit:GetAbsOrigin()
if not self:isWithinZoneBounds(config, checkPos) then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self:getDeepPositionInZone(config),
Queue = false
})
end
return nil
end
)
return 0.5
end
return 0.5
end
self:startZoneTimer(id, 1, checkAggressivePosition)
break
end
____cond153 = ____cond153 or ____switch153 == "neutral"
if ____cond153 then
do
unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK)
local spawnPosition = unit:GetAbsOrigin()
local ____unitInfo_canUseAbilities_13 = unitInfo.canUseAbilities
if ____unitInfo_canUseAbilities_13 == nil then
____unitInfo_canUseAbilities_13 = false
end
local neutralCanUseAbilities = ____unitInfo_canUseAbilities_13
local isAggressive = false
local neutralLastAbilityUseTime = 0
local checkAggression
local function hasReadyAbilitiesNeutral(____, target)
if not neutralCanUseAbilities then
return false
end
if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then
return false
end
local now = GameRules:GetGameTime()
if now - neutralLastAbilityUseTime < 2 then
return false
end
local unitPos = unit:GetAbsOrigin()
do
local i = 0
while i < unit:GetAbilityCount() do
do
local ability = unit:GetAbilityByIndex(i)
if not ability or ability:IsNull() then
goto __continue315
end
if ability:IsPassive() or ability:IsHidden() then
goto __continue315
end
if not ability:IsCooldownReady() then
goto __continue315
end
if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then
goto __continue315
end
local behavior = 0
do
local function ____catch(e)
local abilityNameForCheck = ability:GetAbilityName()
if abilityNameForCheck == "sheep_coil" then
behavior = 16
elseif abilityNameForCheck == "black_dragon_fireball" then
behavior = 16
elseif abilityNameForCheck == "frogmen_acid_jump" then
behavior = 16
else
local behaviorRaw = ability:GetBehavior()
if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then
behavior = behaviorRaw
end
end
end
local ____try, ____hasReturned = pcall(function()
local kv = ability:GetAbilityKeyValues()
if kv and kv.AbilityBehavior then
local behaviorStr = kv.AbilityBehavior
if __TS__StringIncludes(behaviorStr, "POINT") then
behavior = bit.bor(behavior, 16)
end
if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then
behavior = bit.bor(behavior, 32)
end
if __TS__StringIncludes(behaviorStr, "NO_TARGET") then
behavior = bit.bor(behavior, 4)
end
end
end)
if not ____try then
____catch(____hasReturned)
end
end
local pointFlag = 16
local unitTargetFlag = 32
local noTargetFlag = 4
local castRange = ability:GetCastRange(unitPos, target)
local targetPos = target:GetAbsOrigin()
local dx = unitPos.x - targetPos.x
local dy = unitPos.y - targetPos.y
local distance = math.sqrt(dx * dx + dy * dy)
if bit.band(behavior, unitTargetFlag) ~= 0 or bit.band(behavior, pointFlag) ~= 0 then
if distance <= castRange and target:IsAlive() then
return true
end
elseif bit.band(behavior, noTargetFlag) ~= 0 then
return true
end
end
::__continue315::
i = i + 1
end
end
return false
end
local function tryUseAbilitiesNeutral()
if not neutralCanUseAbilities then
return false
end
if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then
return false
end
local now = GameRules:GetGameTime()
if now - neutralLastAbilityUseTime < 2 then
return false
end
local enemiesRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
600,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local enemies = __TS__ArrayFilter(
enemiesRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #enemies == 0 then
return false
end
local target = enemies[1]
do
local i = 0
while i < unit:GetAbilityCount() do
do
local ability = unit:GetAbilityByIndex(i)
if not ability or ability:IsNull() then
goto __continue340
end
local abilityName = ability:GetAbilityName()
local suppressWarningParticles = abilityName == "spider_hunger"
if ability:IsPassive() or ability:IsHidden() then
goto __continue340
end
if not ability:IsCooldownReady() then
goto __continue340
end
if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then
goto __continue340
end
local pointFlag = 16
local unitTargetFlag = 32
local noTargetFlag = 4
local knownAbilityBehavior = {sheep_coil = pointFlag, black_dragon_fireball = pointFlag, frogmen_acid_jump = pointFlag}
local behavior = 0
do
local function ____catch(e)
local abilityNameForCheck = ability:GetAbilityName()
local override = knownAbilityBehavior[abilityNameForCheck]
if override ~= nil then
behavior = override
else
local behaviorRaw = ability:GetBehavior()
if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then
behavior = behaviorRaw
end
end
end
local ____try, ____hasReturned = pcall(function()
local kv = ability:GetAbilityKeyValues()
if kv and kv.AbilityBehavior then
local behaviorStr = kv.AbilityBehavior
if __TS__StringIncludes(behaviorStr, "POINT") then
behavior = bit.bor(behavior, pointFlag)
end
if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then
behavior = bit.bor(behavior, unitTargetFlag)
end
if __TS__StringIncludes(behaviorStr, "NO_TARGET") then
behavior = bit.bor(behavior, noTargetFlag)
end
end
end)
if not ____try then
____catch(____hasReturned)
end
end
local unitPos = unit:GetAbsOrigin()
local castRange = ability:GetCastRange(unitPos, target)
local ____table_GetSpecialValueFor_14
if ability.GetSpecialValueFor then
____table_GetSpecialValueFor_14 = ability:GetSpecialValueFor("precast_warning_time") or 1
else
____table_GetSpecialValueFor_14 = 1
end
local warningTimeNeutral = ____table_GetSpecialValueFor_14
local function createPreCastParticle(____, particlePosition, radius, warningDuration)
local duration = warningDuration ~= nil and warningDuration or warningTimeNeutral
local groundPos = GetGroundPosition(particlePosition, nil)
local aoeRadius = radius ~= nil and radius or 0
if aoeRadius > 0 then
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, groundPos)
ParticleManager:SetParticleControl(
particle,
1,
Vector(aoeRadius, 0, 0)
)
ParticleManager:SetParticleControl(
particle,
2,
Vector(6, 0, 1)
)
ParticleManager:SetParticleControl(
particle,
3,
Vector(200, 0, 0)
)
ParticleManager:SetParticleControl(particle, 4, groundPos)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
else
local casterPos = GetGroundPosition(
unit:GetAbsOrigin(),
nil
)
local targetPos = groundPos
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, casterPos)
ParticleManager:SetParticleControl(particle, 1, targetPos)
ParticleManager:SetParticleControl(
particle,
2,
Vector(duration, 0, 0)
)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
end
end
local function createDirectionArrowNeutral(____, caster, targetPosition, warningDuration)
local duration = warningDuration ~= nil and warningDuration or warningTimeNeutral
local casterPos = GetGroundPosition(
caster:GetAbsOrigin(),
nil
)
local groundTarget = GetGroundPosition(targetPosition, nil)
local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil)
if particle ~= nil then
ParticleManager:SetParticleControl(particle, 0, casterPos)
ParticleManager:SetParticleControl(particle, 1, groundTarget)
ParticleManager:SetParticleControl(
particle,
2,
Vector(duration, 0, 0)
)
self:startZoneTimer(
id,
duration,
function()
if particle ~= nil then
ParticleManager:DestroyParticle(particle, false)
ParticleManager:ReleaseParticleIndex(particle)
end
return nil
end
)
end
end
local pointFlagNeutral = 16
local unitTargetFlagNeutral = 32
local noTargetFlagNeutral = 4
if bit.band(behavior, unitTargetFlagNeutral) ~= 0 then
local unitPos = unit:GetAbsOrigin()
local targetPos = target:GetAbsOrigin()
local dx = unitPos.x - targetPos.x
local dy = unitPos.y - targetPos.y
local distance = math.sqrt(dx * dx + dy * dy)
if distance <= castRange and target:IsAlive() then
if not suppressWarningParticles then
createDirectionArrowNeutral(nil, unit, targetPos, warningTimeNeutral)
end
if not suppressWarningParticles then
local overheadParticle = ParticleManager:CreateParticle("particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", PATTACH_OVERHEAD_FOLLOW, target)
if overheadParticle ~= nil then
self:startZoneTimer(
id,
warningTimeNeutral + 0.5,
function()
if overheadParticle ~= nil then
ParticleManager:DestroyParticle(overheadParticle, false)
ParticleManager:ReleaseParticleIndex(overheadParticle)
end
return nil
end
)
end
end
local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
self:startZoneTimer(
id,
warningTimeNeutral,
function()
if not unit or not unit:IsAlive() or not target or not target:IsAlive() then
return nil
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
return nil
end
)
neutralLastAbilityUseTime = now
return true
end
elseif bit.band(behavior, pointFlagNeutral) ~= 0 then
local unitPos = unit:GetAbsOrigin()
local targetPos = target:GetAbsOrigin()
local dx = unitPos.x - targetPos.x
local dy = unitPos.y - targetPos.y
local distance = math.sqrt(dx * dx + dy * dy)
if distance <= castRange and target:IsAlive() then
if abilityName ~= "frog_magi_wave" and not suppressWarningParticles then
local ____table_GetAOERadius_15
if ability.GetAOERadius then
____table_GetAOERadius_15 = ability:GetAOERadius()
else
____table_GetAOERadius_15 = 0
end
local aoeRadius = ____table_GetAOERadius_15
local ____temp_16
if aoeRadius > 0 then
____temp_16 = aoeRadius
else
____temp_16 = 200
end
createPreCastParticle(nil, targetPos, ____temp_16, warningTimeNeutral)
end
local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
self:startZoneTimer(
id,
warningTimeNeutral,
function()
if not unit or not unit:IsAlive() or not target or not target:IsAlive() then
return nil
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = targetPos,
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
return nil
end
)
neutralLastAbilityUseTime = now
return true
end
elseif bit.band(behavior, noTargetFlagNeutral) ~= 0 then
local unitPos = unit:GetAbsOrigin()
local targetPos = target:GetAbsOrigin()
local dx = unitPos.x - targetPos.x
local dy = unitPos.y - targetPos.y
local distance = math.sqrt(dx * dx + dy * dy)
local desiredDistance = 100
if distance > desiredDistance then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
TargetIndex = target:entindex(),
Queue = false
})
self:startZoneTimer(
id,
0.3,
function()
if not unit or not unit:IsAlive() or not target or not target:IsAlive() then
return nil
end
local newUnitPos = unit:GetAbsOrigin()
local newTargetPos = target:GetAbsOrigin()
local ndx = newUnitPos.x - newTargetPos.x
local ndy = newUnitPos.y - newTargetPos.y
local newDistance = math.sqrt(ndx * ndx + ndy * ndy)
if newDistance <= desiredDistance + 25 then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
neutralLastAbilityUseTime = now
return nil
end
return 0.2
end
)
return true
end
local lockedTargetPos = target:GetAbsOrigin()
local ____table_GetAOERadius_17
if ability.GetAOERadius then
____table_GetAOERadius_17 = ability:GetAOERadius()
else
____table_GetAOERadius_17 = 0
end
local aoeRadius = ____table_GetAOERadius_17
if aoeRadius <= 0 and ability.GetSpecialValueFor then
local specialRadius = ability:GetSpecialValueFor("radius")
if specialRadius and specialRadius > 0 then
aoeRadius = specialRadius
end
end
if not suppressWarningParticles then
local ____temp_18
if aoeRadius > 0 then
____temp_18 = aoeRadius
else
____temp_18 = 200
end
local followRadius = ____temp_18
local followParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil)
if followParticle ~= nil then
ParticleManager:SetParticleControlEnt(
followParticle,
0,
unit,
PATTACH_ABSORIGIN_FOLLOW,
nil,
unit:GetAbsOrigin(),
true
)
ParticleManager:SetParticleControl(
followParticle,
1,
Vector(followRadius, 0, 0)
)
ParticleManager:SetParticleControl(
followParticle,
2,
Vector(6, 0, 1)
)
ParticleManager:SetParticleControl(
followParticle,
3,
Vector(200, 0, 0)
)
ParticleManager:SetParticleControl(
followParticle,
4,
unit:GetAbsOrigin()
)
self:startZoneTimer(
id,
warningTimeNeutral,
function()
if followParticle ~= nil then
ParticleManager:DestroyParticle(followParticle, false)
ParticleManager:ReleaseParticleIndex(followParticle)
end
return nil
end
)
end
end
do
local direction = Vector(lockedTargetPos.x - unitPos.x, lockedTargetPos.y - unitPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
end
if not suppressWarningParticles then
createDirectionArrowNeutral(nil, unit, lockedTargetPos, warningTimeNeutral)
end
self:startZoneTimer(
id,
warningTimeNeutral,
function()
if not unit or not unit:IsAlive() then
return nil
end
local currentPos = unit:GetAbsOrigin()
local direction = Vector(lockedTargetPos.x - currentPos.x, lockedTargetPos.y - currentPos.y, 0)
local length = math.sqrt(direction.x * direction.x + direction.y * direction.y)
if length > 0 then
local normalizedDir = Vector(direction.x / length, direction.y / length, 0)
unit:SetForwardVector(normalizedDir)
end
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = ability:GetEntityIndex(),
Queue = false
})
return nil
end
)
neutralLastAbilityUseTime = now
return true
end
end
::__continue340::
i = i + 1
end
end
return false
end
checkAggression = function()
if not unit or not unit:IsAlive() then
return
end
local currentHealth = unit:GetHealth()
local maxHealth = unit:GetMaxHealth()
local currentPos = unit:GetAbsOrigin()
local now = GameRules:GetGameTime()
if config.moveInGroups then
local group = self:getGroupForUnit(unit)
if group and #group > 0 then
local leader = self:getGroupLeader(group)
if leader and leader ~= unit and leader:IsAlive() then
local leaderPos = leader:GetAbsOrigin()
local followRadius = config.moveInGroups.stayRadius or 250
local dxF = currentPos.x - leaderPos.x
local dyF = currentPos.y - leaderPos.y
local distToLeader = math.sqrt(dxF * dxF + dyF * dyF)
if distToLeader > followRadius * 0.7 and not unit:IsAttacking() and not unit:IsChanneling() then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = leaderPos,
Queue = false
})
return 0.2
end
end
end
end
if not self:isWithinZoneBounds(config, currentPos) then
local nearbyEnemiesRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
currentPos,
nil,
200,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local nearbyEnemies = __TS__ArrayFilter(
nearbyEnemiesRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #nearbyEnemies == 0 then
local deepPos = self:getDeepPositionInZone(config)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = deepPos,
Queue = false
})
self:startZoneTimer(
id,
1,
function()
if not unit or not unit:IsAlive() then
return nil
end
local checkPos = unit:GetAbsOrigin()
if not self:isWithinZoneBounds(config, checkPos) then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self:getDeepPositionInZone(config),
Queue = false
})
end
return nil
end
)
return 0.1
end
end
if currentHealth < maxHealth and not isAggressive then
isAggressive = true
unit:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK)
local nearbyUnitsRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
500,
DOTA_UNIT_TARGET_TEAM_ENEMY,
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local nearbyUnits = __TS__ArrayFilter(
nearbyUnitsRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #nearbyUnits > 0 then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = nearbyUnits[1]:entindex(),
Queue = false
})
end
self:startZoneTimer(
id,
5,
function()
if not unit or not unit:IsAlive() then
return nil
end
isAggressive = false
unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK)
local returnPos = config.moveInGroups and self:getGroupForUnit(unit) and self:getGroupCenter(self:getGroupForUnit(unit)) or spawnPosition
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = returnPos,
Queue = false
})
return nil
end
)
end
if isAggressive then
local detectionRadius = 2000
local nearbyEnemiesRaw = FindUnitsInRadius(
unit:GetTeamNumber(),
unit:GetAbsOrigin(),
nil,
detectionRadius,
DOTA_UNIT_TARGET_TEAM_ENEMY,
bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC),
DOTA_UNIT_TARGET_FLAG_NONE,
FIND_CLOSEST,
false
)
local nearbyEnemies = __TS__ArrayFilter(
nearbyEnemiesRaw,
function(____, e) return self:isWithinChaseRange(
config,
e:GetAbsOrigin()
) end
)
if #nearbyEnemies > 0 then
local closestEnemy = nearbyEnemies[1]
local unitPos = unit:GetAbsOrigin()
local enemyPos = closestEnemy:GetAbsOrigin()
local dx = unitPos.x - enemyPos.x
local dy = unitPos.y - enemyPos.y
local distanceToEnemy = math.sqrt(dx * dx + dy * dy)
if distanceToEnemy <= detectionRadius and closestEnemy:IsAlive() then
if distanceToEnemy > unit:GetBaseAttackRange() * 1.2 then
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = enemyPos,
Queue = false
})
else
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = closestEnemy:entindex(),
Queue = false
})
end
if distanceToEnemy <= 800 then
local usedAbility = tryUseAbilitiesNeutral(nil)
if usedAbility then
return 0.1
end
end
end
end
end
local currentPosCheck = unit:GetAbsOrigin()
if not self:isWithinZoneBounds(config, currentPosCheck) and unit:GetAttackCapability() == DOTA_UNIT_CAP_NO_ATTACK then
local newPos = self:clampToZoneBounds(config, currentPosCheck)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = newPos,
Queue = false
})
end
return 0.1
end
unit:SetContextThink("AggressionCheck", checkAggression, 0)
break
end
end
until true
local function getRandomWalkPosition()
local factor = config.patrolRadiusFactor ~= nil and math.max(
0.1,
math.min(1, config.patrolRadiusFactor)
) or 1
local basePos = config.position
if config.moveInGroups then
local group = self:getGroupForUnit(unit)
if group and #group > 0 then
basePos = self:getGroupCenter(group)
end
end
local validPos
if config.shape == "square" then
local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) * factor
local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) * factor
local offsetX = RandomFloat(-halfWidth, halfWidth)
local offsetY = RandomFloat(-halfHeight, halfHeight)
validPos = GetGroundPosition(
Vector(basePos.x + offsetX, basePos.y + offsetY, basePos.z),
nil
)
else
local angle = RandomFloat(0, 2 * math.pi)
local distance = RandomFloat(0, config.radius * factor)
validPos = GetGroundPosition(
Vector(
basePos.x + distance * math.cos(angle),
basePos.y + distance * math.sin(angle),
basePos.z
),
nil
)
end
if not self:isWithinZoneBounds(config, validPos) then
return self:clampToZoneBounds(config, validPos)
end
return validPos
end
local moveRandomly
moveRandomly = function()
if not self.spawnConfigs:has(id) then
return
end
if not unit or not unit:IsAlive() then
return
end
unit:SetForceAttackTarget(nil)
if unit:GetAttackCapability() == DOTA_UNIT_CAP_MELEE_ATTACK or unit:GetAttackCapability() == DOTA_UNIT_CAP_RANGED_ATTACK then
local newPos = getRandomWalkPosition(nil)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = newPos,
Queue = false
})
end
if unit:GetAttackCapability() == DOTA_UNIT_CAP_NO_ATTACK then
local newPos = getRandomWalkPosition(nil)
ExecuteOrderFromTable({
UnitIndex = unit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = newPos,
Queue = false
})
end
Timers:CreateTimer(
RandomFloat(3, 7),
function()
if not self.spawnConfigs:has(id) then
return nil
end
if unit and unit:IsAlive() then
moveRandomly(nil)
end
return nil
end
)
end
unit:SetMoveCapability(DOTA_UNIT_CAP_MOVE_GROUND)
Timers:CreateTimer(
1,
function()
if not self.spawnConfigs:has(id) then
return nil
end
moveRandomly(nil)
return nil
end
)
aliveUnits[#aliveUnits + 1] = unit
self.spawnedUnits:set(id, aliveUnits)
self.spawnTimers:set(
id,
GameRules:GetGameTime()
)
end
end
end
function SpawnManager.prototype.RemoveSpawnZone(self, id)
self:cancelZoneTimers(id)
local units = self.spawnedUnits:get(id) or ({})
for ____, unit in ipairs(units) do
if unit and IsValidEntity(unit) then
do
pcall(function()
unit:SetContextThink("AggressionCheck", nil, 0)
unit:RemoveSelf()
end)
end
end
end
local prefix = id .. "_grp_"
for ____, groupId in ipairs(__TS__ArrayFrom(self.unitGroups:keys())) do
if __TS__StringStartsWith(groupId, prefix) then
self.unitGroups:delete(groupId)
end
end
self.spawnConfigs:delete(id)
self.spawnTimers:delete(id)
self.spawnedUnits:delete(id)
self.zoneRespawnScheduled:delete(id)
if self.debugZonesVisible then
DebugDrawClear()
self.spawnConfigs:forEach(function(____, config, zoneId)
self:DrawSpawnZoneDebug(zoneId, config)
end)
end
end
function SpawnManager.prototype.RemoveSpawnZonesByType(self, ____type)
local zoneIds = __TS__ArrayFrom(self.spawnConfigs:keys())
__TS__ArrayForEach(
zoneIds,
function(____, zoneId)
if __TS__StringStartsWith(zoneId, ("zone_" .. ____type) .. "_") then
self:RemoveSpawnZone(zoneId)
end
end
)
end
function SpawnManager.prototype.RemoveAllSpawnZones(self)
local zoneIds = __TS__ArrayFrom(self.spawnConfigs:keys())
__TS__ArrayForEach(
zoneIds,
function(____, zoneId)
self:RemoveSpawnZone(zoneId)
end
)
end
function SpawnManager.prototype.GetSpawnedUnits(self, id)
return self.spawnedUnits:get(id) or ({})
end
function SpawnManager.prototype.GetZoneIdsByPrefixes(self, prefixes)
local result = {}
self.spawnConfigs:forEach(function(____, _, zoneId)
for ____, prefix in ipairs(prefixes) do
if __TS__StringStartsWith(zoneId, prefix) then
result[#result + 1] = zoneId
break
end
end
end)
return result
end
function SpawnManager.prototype.GetZoneCentersByPrefixes(self, prefixes)
local centers = {}
self.spawnConfigs:forEach(function(____, config, zoneId)
for ____, prefix in ipairs(prefixes) do
if __TS__StringStartsWith(zoneId, prefix) and config.position then
centers[#centers + 1] = config.position
break
end
end
end)
return centers
end
function SpawnManager.prototype.getSpawnUnitConfigName(self, unit)
do
local function ____catch()
return true, nil
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local stored = unit.__spawnManagerUnitName
if stored and #stored > 0 then
return true, stored
end
local name = unit:GetUnitName()
return true, name and #name > 0 and name or nil
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
function SpawnManager.prototype.getValidSpawnedUnits(self, zoneId)
local units = self.spawnedUnits:get(zoneId) or ({})
local validUnits = __TS__ArrayFilter(
units,
function(____, unit)
if not unit or not IsValidEntity(unit) then
return false
end
do
local function ____catch()
return true, false
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
return true, unit:IsAlive()
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
)
self.spawnedUnits:set(zoneId, validUnits)
return validUnits
end
function SpawnManager.prototype.countSpawnedUnitsByConfigName(self, zoneId)
local counts = __TS__New(Map)
for ____, unit in ipairs(self:getValidSpawnedUnits(zoneId)) do
do
local name = self:getSpawnUnitConfigName(unit)
if not name then
goto __continue478
end
counts:set(
name,
(counts:get(name) or 0) + 1
)
end
::__continue478::
end
return counts
end
function SpawnManager.prototype.getSpawnDelayForUnit(self, config, unitInfo)
return __TS__StringIncludes(unitInfo.unitName, "zombie") and config.interval * 2 or config.interval
end
function SpawnManager.prototype.replenishZoneMissingUnits(self, zoneId)
local config = self.spawnConfigs:get(zoneId)
if not config then
return
end
if not self:isSpawnAllowed() then
self:startZoneTimer(
zoneId,
0.5,
function()
self:replenishZoneMissingUnits(zoneId)
return nil
end
)
return
end
local currentUnitCounts = self:countSpawnedUnitsByConfigName(zoneId)
local toSpawn = {}
for ____, unitInfo in ipairs(config.units) do
local currentCount = currentUnitCounts:get(unitInfo.unitName) or 0
local deficit = math.max(0, unitInfo.count - currentCount)
do
local i = 0
while i < deficit do
toSpawn[#toSpawn + 1] = unitInfo
i = i + 1
end
end
end
if #toSpawn <= 0 then
return
end
do
local i = 0
while i < #toSpawn do
local unitInfo = toSpawn[i + 1]
local spawnDelay = self:getSpawnDelayForUnit(config, unitInfo) + i * 0.15
self:startZoneTimer(
zoneId,
spawnDelay,
function()
self:SpawnUnit(zoneId, config, unitInfo)
return nil
end
)
i = i + 1
end
end
end
function SpawnManager.prototype.QueueRespawn(self, zoneId)
if not self.spawnConfigs:get(zoneId) then
return
end
if self.zoneRespawnScheduled:get(zoneId) then
return
end
self.zoneRespawnScheduled:set(zoneId, true)
self:startZoneTimer(
zoneId,
0.5,
function()
self.zoneRespawnScheduled:delete(zoneId)
self:replenishZoneMissingUnits(zoneId)
return nil
end
)
end
function SpawnManager.prototype.log(self, message)
end
SpawnManager.CHASE_LEASH_MARGIN = 350
--- Маркеры зон спавна / визуалы событий
function ____exports.precacheSpawnManagerEffectParticles(self, context)
PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context)
PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", context)
PrecacheResource("particle", "particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", context)
end
return ____exports