initial commit

This commit is contained in:
2021-10-24 15:36:18 -04:00
commit b9a5a8fe23
11982 changed files with 220468 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.fOrigModelScale = thisEntity:GetModelScale()
thisEntity:AddNewModifier( nil, nil, "modifier_phased", { duration = -1 } )
thisEntity.hSunRayAbility = thisEntity:FindAbilityByName( "assault_captain_sun_ray" )
thisEntity.hChainsAbility = thisEntity:FindAbilityByName( "assault_captain_searing_chains" )
thisEntity:SetContextThink( "AssaultCaptainThink", AssaultCaptainThink, 0.5 )
end
--------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "particle", "particles/creeps/lane_creeps/creep_dire_hulk_swipe_right.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_ember_spirit/ember_spirit_searing_chains_start.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_ember_spirit/ember_spirit_searing_chains_cast.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_ember_spirit/ember_spirit_searing_chains_debuff.vpcf", context )
end
--------------------------------------------------------------------------------
function AssaultCaptainThink()
if not IsServer() then
return
end
-- Search for items here instead of in Spawn, because they don't seem to exist yet when Spawn runs
if not thisEntity.bSearchedForItems then
SearchForItems()
thisEntity.bSearchedForItems = true
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
--[[
if thisEntity.fLastSearingCast then
print( "last searing cast: " .. thisEntity.fLastSearingCast )
end
if thisEntity.fLastSearingCast ~= nil and GameRules:GetGameTime() > ( thisEntity.fLastSearingCast + 1 ) then
ParticleManager:DestroyParticle( thisEntity.nPreviewFX, false )
thisEntity.fLastSearingCast = nil
end
]]
if thisEntity:HasModifier( "modifier_phoenix_sun_ray" ) then
return 0.25
else
thisEntity:SetModelScale( thisEntity.fOrigModelScale )
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1200, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_FARTHEST, false )
if #hEnemies == 0 then
return 0.5
end
if thisEntity.hChainsAbility ~= nil and thisEntity.hChainsAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 95 ) then
return CastSearingChains()
end
end
local hSunRayTarget = nil
for _, hEnemy in pairs( hEnemies ) do
if hEnemy and hEnemy:IsAlive() and hEnemy:IsRealHero() then
if hEnemy:HasModifier( "modifier_ember_spirit_searing_chains" ) or hEnemy:HasModifier( "modifier_rod_of_atos_debuff" ) then
hSunRayTarget = hEnemy
break
end
end
end
if hSunRayTarget ~= nil and thisEntity.hSunRayAbility ~= nil and thisEntity.hSunRayAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 95 ) then
return CastSunRay( hSunRayTarget )
end
end
--[[
if thisEntity.hRodOfAtosAbility and thisEntity.hRodOfAtosAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 100 ) then
print( "try to use atos" )
return UseRodOfAtos( hEnemies[ RandomInt( 1, #hEnemies ) ] )
end
end
]]
if thisEntity.hBlademailAbility and thisEntity.hBlademailAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 80 ) then
return UseBlademail()
end
end
return 0.5
end
--------------------------------------------------------------------------------
function SearchForItems()
for i = 0, 5 do
local item = thisEntity:GetItemInSlot( i )
if item then
if item:GetAbilityName() == "item_blade_mail" then
thisEntity.hBlademailAbility = item
end
if item:GetAbilityName() == "item_rod_of_atos" then
thisEntity.hRodOfAtosAbility = item
end
end
end
end
--------------------------------------------------------------------------------
function CastSunRay( hEnemy )
thisEntity:SetModelScale( 2 )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hEnemy:GetOrigin(),
AbilityIndex = thisEntity.hSunRayAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------
function CastSearingChains()
if IsServer() then
--[[
print( "creating a warning particle" )
thisEntity.fLastSearingCast = GameRules:GetGameTime()
thisEntity.nPreviewFX = ParticleManager:CreateParticle( "particles/darkmoon_creep_warning.vpcf", PATTACH_ABSORIGIN_FOLLOW, thisEntity )
ParticleManager:SetParticleControlEnt( thisEntity.nPreviewFX, 0, thisEntity, PATTACH_ABSORIGIN_FOLLOW, nil, thisEntity:GetOrigin(), true )
ParticleManager:SetParticleControl( thisEntity.nPreviewFX, 1, Vector( 120, 120, 120 ) )
ParticleManager:SetParticleControl( thisEntity.nPreviewFX, 15, Vector( 180, 40, 10 ) )
]]
end
--thisEntity:SetSequence( "hit" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hChainsAbility:entindex(),
Queue = false,
})
return 0.75
end
--------------------------------------------------------------------------------
function UseRodOfAtos( hEnemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hEnemy:entindex(),
AbilityIndex = thisEntity.hRodOfAtosAbility:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function UseBlademail()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hBlademailAbility:entindex(),
Queue = false,
})
return 2
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,108 @@
--[[
Catapult AI
]]
function Spawn( entityKeyValues )
if IsServer() == false then
return
end
thisEntity:SetContextThink( "CatapultAIThink", CatapultAIThink, 0.25 )
thisEntity.hEntityKilledGameEvent = ListenToGameEvent( "entity_killed", Dynamic_Wrap( thisEntity:GetPrivateScriptScope(), 'OnEntityKilled' ), nil )
end
function UpdateOnRemove()
StopListeningToGameEvent( thisEntity.hEntityKilledGameEvent )
end
function Precache( context )
PrecacheResource( "particle", "particles/creatures/catapult/catapult_projectile.vpcf", context )
PrecacheResource( "particle", "particles/siege_fx/siege_bad_death_01.vpcf", context )
end
function CatapultAIThink()
if IsServer() == false then
return
end
s_AbilityCatapultAttack = thisEntity:FindAbilityByName( "catapult_attack" )
-- Get the current time
local currentTime = GameRules:GetGameTime()
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 1
end
if s_AbilityCatapultAttack ~= nil and s_AbilityCatapultAttack:IsCooldownReady() then
local radius = s_AbilityCatapultAttack:GetSpecialValueFor("explosion_radius")
local minRange = s_AbilityCatapultAttack:GetSpecialValueFor("mindistance")
local range = s_AbilityCatapultAttack:GetCastRange()
local nMaxAdjacentEnemies = 0
local bestEnemy = nil
for _,enemy in pairs( enemies ) do
if enemy ~= nil and enemy:IsAlive() then
local flDistToEnemy = #(thisEntity:GetOrigin() - enemy:GetOrigin())
if range > flDistToEnemy and minRange < flDistToEnemy then
local nAdjacentEnemies = 1
for _,adjacentEnemy in pairs( enemies ) do
if adjacentEnemy ~= nil and adjacentEnemy ~= enemy and adjacentEnemy:IsAlive() then
local vSeparation = enemy:GetOrigin() - adjacentEnemy:GetOrigin()
local flDistBetweenEnemies = #vSeparation
if flDistBetweenEnemies < radius then
nAdjacentEnemies = nAdjacentEnemies + 1
end
end
end
if nMaxAdjacentEnemies < nAdjacentEnemies or ( nMaxAdjacentEnemies == nMaxAdjacentEnemies and RandomInt( 0,1 ) == 1 ) then
nMaxAdjacentEnemies = nAdjacentEnemies
bestEnemy = enemy
end
end
end
end
if bestEnemy ~= nil then
return CatapultAttack( bestEnemy )
end
end
return 1.0
end
function CatapultAttack( enemy )
local order = {}
order.UnitIndex = thisEntity:entindex()
order.OrderType = DOTA_UNIT_ORDER_CAST_POSITION
order.Position = enemy:GetOrigin()
order.AbilityIndex = s_AbilityCatapultAttack:entindex()
ExecuteOrderFromTable( order )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 2.0 } )
return 3.5
end
function OnEntityKilled( event )
local hVictim = nil
if event.entindex_killed ~= nil then
hVictim = EntIndexToHScript( event.entindex_killed )
end
if hVictim ~= thisEntity then
return
end
EmitSoundOn( "Creep_Siege_Dire.Destruction", hVictim )
hVictim:AddEffects( EF_NODRAW )
local nFXIndex = ParticleManager:CreateParticle( "particles/siege_fx/siege_bad_death_01.vpcf", PATTACH_CUSTOMORIGIN, nil )
ParticleManager:SetParticleControlEnt( nFXIndex, 0, hVictim, PATTACH_ABSORIGIN, nil, hVictim:GetOrigin(), true )
ParticleManager:ReleaseParticleIndex( nFXIndex )
end

View File

@@ -0,0 +1,162 @@
--[[
Tower Defense AI
These are the valid orders, in case you want to use them (easier here than to find them in the C code):
DOTA_UNIT_ORDER_NONE
DOTA_UNIT_ORDER_MOVE_TO_POSITION
DOTA_UNIT_ORDER_MOVE_TO_TARGET
DOTA_UNIT_ORDER_ATTACK_MOVE
DOTA_UNIT_ORDER_ATTACK_TARGET
DOTA_UNIT_ORDER_CAST_POSITION
DOTA_UNIT_ORDER_CAST_TARGET
DOTA_UNIT_ORDER_CAST_TARGET_TREE
DOTA_UNIT_ORDER_CAST_NO_TARGET
DOTA_UNIT_ORDER_CAST_TOGGLE
DOTA_UNIT_ORDER_HOLD_POSITION
DOTA_UNIT_ORDER_TRAIN_ABILITY
DOTA_UNIT_ORDER_DROP_ITEM
DOTA_UNIT_ORDER_GIVE_ITEM
DOTA_UNIT_ORDER_PICKUP_ITEM
DOTA_UNIT_ORDER_PICKUP_RUNE
DOTA_UNIT_ORDER_PURCHASE_ITEM
DOTA_UNIT_ORDER_SELL_ITEM
DOTA_UNIT_ORDER_DISASSEMBLE_ITEM
DOTA_UNIT_ORDER_MOVE_ITEM
DOTA_UNIT_ORDER_CAST_TOGGLE_AUTO
DOTA_UNIT_ORDER_STOP
DOTA_UNIT_ORDER_TAUNT
DOTA_UNIT_ORDER_BUYBACK
DOTA_UNIT_ORDER_GLYPH
DOTA_UNIT_ORDER_EJECT_ITEM_FROM_STASH
DOTA_UNIT_ORDER_CAST_RUNE
]]
AICore = {}
behaviorSystem = {} -- create the global so we can assign to it
function AICore:RandomEnemyHeroInRange( entity, range )
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, entity:GetOrigin(), entity, range, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, 0, 0, false )
if #enemies > 0 then
local index = RandomInt( 1, #enemies )
return enemies[index]
else
return nil
end
end
function AICore:ClosestEnemyHeroInRange( entity, range )
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, entity:GetOrigin(), entity, range, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies > 0 then
return enemies[1]
else
return nil
end
end
function AICore:WeakestEnemyHeroInRange( entity, range )
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, entity:GetOrigin(), entity, range, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, 0, 0, false )
local minHP = nil
local target = nil
for _,enemy in pairs(enemies) do
local distanceToEnemy = (entity:GetOrigin() - enemy:GetOrigin()):Length()
local HP = enemy:GetHealth()
if enemy:IsAlive() and (minHP == nil or HP < minHP) and distanceToEnemy < range then
minHP = HP
target = enemy
end
end
return target
end
function AICore:CreateBehaviorSystem( hEntity, behaviors )
local BehaviorSystem = {}
BehaviorSystem.hEntity = hEntity
BehaviorSystem.possibleBehaviors = behaviors
BehaviorSystem.thinkDuration = 0.3
BehaviorSystem.currentBehavior =
{
}
BehaviorSystem.currentOrder = { OrderType = DOTA_UNIT_ORDER_NONE }
function BehaviorSystem:Think( )
-- Don't do anything if we're in the middle of casting something
if self.hEntity:GetCurrentActiveAbility() ~= nil or GameRules:IsGamePaused() then
return 0.1
end
local bIsDone = self.currentBehavior.IsDone == nil or self.currentBehavior:IsDone()
local newOrder = nil
if bIsDone then
local newBehavior = self:ChooseNextBehavior()
if newBehavior == nil then
-- Do nothing here... this covers possible problems with ChooseNextBehavior
elseif newBehavior == self.currentBehavior then
newOrder = self.currentBehavior:Continue()
else
if self.currentBehavior.End then
self.currentBehavior:End()
end
self.currentBehavior = newBehavior
newOrder = self.currentBehavior:Begin()
end
else
if self.currentBehavior.Think then
newOrder = self.currentBehavior:Think()
end
end
if newOrder ~= nil and newOrder.OrderType ~= DOTA_UNIT_ORDER_NONE then
if self.currentOrder.OrderType ~= newOrder.OrderType or
self.currentOrder.TargetIndex ~= newOrder.TargetIndex or
self.currentOrder.AbilityIndex ~= newOrder.AbilityIndex or
self.currentOrder.Position ~= newOrder.Position then
--print( "Executing Order " .. tostring(newOrder.OrderType) .. "->" .. tostring(newOrder.TargetIndex).. "->" .. tostring(newOrder.AbilityIndex) .. "->" .. tostring( newOrder.Position ) )
ExecuteOrderFromTable( newOrder )
self.currentOrder = newOrder
end
end
return self.thinkDuration
end
function BehaviorSystem:ChooseNextBehavior()
local result = nil
local bestDesire = nil
for _,behavior in pairs( self.possibleBehaviors ) do
local thisDesire = behavior:Evaluate()
if bestDesire == nil or thisDesire > bestDesire then
result = behavior
bestDesire = thisDesire
end
end
return result
end
function BehaviorSystem:Deactivate()
if self.currentBehavior.End then
self.currentBehavior:End()
end
end
function BehaviorSystem:Destroy()
for _,behavior in pairs( self.possibleBehaviors ) do
if behavior.Destroy then
behavior:Destroy()
end
end
return result
end
return BehaviorSystem
end

View File

@@ -0,0 +1,96 @@
function Spawn( entityKeyValues )
if thisEntity == nil then
return
end
QuillAttack = thisEntity:FindAbilityByName( "ranged_quill_attack" )
thisEntity:SetContextThink( "DireHoundBossThink", DireHoundBossThink, 1 )
end
function DireHoundBossThink()
if GameRules:IsGamePaused() == true then
return 1
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1250, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BUILDING, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 1
end
local hAttackTarget = nil
local hApproachTarget = nil
for _,enemy in pairs( enemies ) do
if enemy ~= nil and enemy:IsAlive() then
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 200 then
return Retreat( enemy )
end
if flDist <= 1000 then
hAttackTarget = enemy
end
if flDist > 1000 then
hApproachTarget = enemy
end
end
end
if hAttackTarget == nil and hApproachTarget ~= nil then
return Approach( hApproachTarget )
end
if QuillAttack:IsCooldownReady() then
return Attack( hAttackTarget )
end
thisEntity:FaceTowards( hAttackTarget:GetOrigin() )
return 0.5
end
function Attack(unit)
thisEntity.bMoving = false
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.1 } )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = QuillAttack:entindex(),
Position = unit:GetOrigin(),
Queue = false,
})
return 1
end
function Approach(unit)
thisEntity.bMoving = true
local vToEnemy = unit:GetOrigin() - thisEntity:GetOrigin()
vToEnemy = vToEnemy:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToEnemy * thisEntity:GetIdealSpeed()
})
return 1
end
function Retreat(unit)
thisEntity.bMoving = true
local vAwayFromEnemy = thisEntity:GetOrigin() - unit:GetOrigin()
vAwayFromEnemy = vAwayFromEnemy:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vAwayFromEnemy * thisEntity:GetIdealSpeed()
})
return 1.25
end

View File

@@ -0,0 +1,88 @@
require( "ai/shared" )
require( "ai/ai_core" )
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hInfernalBlade = thisEntity:FindAbilityByName( "creature_doom_infernal_blade" )
thisEntity.nInfernalBladeSearchRange = 700
thisEntity.hDoomAbility = thisEntity:FindAbilityByName( "creature_doom_bringer_doom" )
thisEntity.nDoomAbilitySearchRange = 800
thisEntity.nDoomAbilityHealthPercentTrigger = 80
thisEntity:SetContextThink( "DoomThink", DoomThink, 1 )
end
--------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "particle", "particles/units/heroes/hero_doom_bringer/doom_infernal_blade_impact.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_doom_bringer/doom_infernal_blade_debuff.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_doom_bringer/doom_bringer_doom.vpcf", context )
PrecacheResource( "particle", "particles/status_fx/status_effect_doom.vpcf", context )
PrecacheResource( "particle", "particles/generic_gameplay/generic_muted.vpcf", context )
PrecacheResource( "particle", "particles/generic_gameplay/generic_break.vpcf", context )
end
--------------------------------------------------------------------------------
function DoomThink()
local flNow = GameRules:GetGameTime()
if thisEntity.hInfernalBlade and thisEntity.hInfernalBlade:IsFullyCastable() then
local hHeroes = GetEnemyHeroesInRange( thisEntity, thisEntity.nInfernalBladeSearchRange )
if #hHeroes > 0 then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hHeroes[1]:entindex(),
AbilityIndex = thisEntity.hInfernalBlade:entindex(),
Queue = false,
})
end
return 0.25
end
if thisEntity.hDoomAbility and thisEntity.hDoomAbility:IsFullyCastable() and thisEntity:GetHealthPercent() < thisEntity.nDoomAbilityHealthPercentTrigger then
local hHeroes = GetEnemyHeroesInRange( thisEntity, thisEntity.nDoomAbilitySearchRange )
if #hHeroes > 0 then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hHeroes[1]:entindex(),
AbilityIndex = thisEntity.hDoomAbility:entindex(),
Queue = false,
})
end
return 0.25
end
thisEntity.flLastAggroSwitch = thisEntity.flLastAggroSwitch and thisEntity.flLastAggroSwitch or 0
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 9000 )
if (flNow - thisEntity.flLastAggroSwitch) > 2 then
AttackTargetOrder( thisEntity, hTarget )
thisEntity.flLastAggroSwitch = flNow
end
return 0.25
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,291 @@
require( "ai/boss_base" )
--------------------------------------------------------------------------------
if CDrowRangerMiniboss == nil then
CDrowRangerMiniboss = class( {}, {}, CBossBase )
end
--------------------------------------------------------------------------------
function Precache( context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CDrowRangerMiniboss( thisEntity, 1.0 )
end
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:constructor( hUnit, flInterval )
CBossBase.constructor( self, hUnit, flInterval )
self.bEnraged = false
self.nEnragePct = 33
self.bTriggerShadowBlade = false
self.nShadowBladeHealthTriggerPct = 75
self.bTriggerEscape = false
self.me:SetThink( "OnDrowRangerMinibossThink", self, "OnDrowRangerMinibossThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:SetupAbilitiesAndItems()
CBossBase.SetupAbilitiesAndItems( self )
self.hGust = self.me:FindAbilityByName( "aghsfort_drow_ranger_wave_of_silence" )
if self.hGust == nil then
print( 'CDrowRangerMiniboss - Unable to find ability aghsfort_drow_ranger_wave_of_silence')
else
self.hGust.Evaluate = self.EvaluateGust
self.AbilityPriority[ self.hGust:GetAbilityName() ] = 4
end
self.hMultishot = self.me:FindAbilityByName( "aghsfort_drow_ranger_multishot" )
if self.hMultishot == nil then
print( 'CDrowRangerMiniboss - Unable to find ability aghsfort_drow_ranger_multishot')
else
self.hMultishot.Evaluate = self.EvaluateMultishot
self.AbilityPriority[ self.hMultishot:GetAbilityName() ] = 3
end
self.hHurricanePike = self.me:FindItemInInventory( "item_hurricane_pike" )
if self.hHurricanePike == nil then
print( 'CDrowRangerMiniboss - Unable to find ability item_hurricane_pike')
else
self.hHurricanePike.Evaluate = self.EvaluateHurricanePike
self.AbilityPriority[ self.hHurricanePike:GetAbilityName() ] = 2
end
self.hShadowBlade = self.me:FindItemInInventory( "item_aghsfort_drow_ranger_invis_sword" )
if self.hShadowBlade == nil then
print( 'CDrowRangerMiniboss - Unable to find ability item_aghsfort_drow_ranger_invis_sword')
else
self.hShadowBlade.Evaluate = self.EvaluateShadowBlade
self.AbilityPriority[ self.hShadowBlade:GetAbilityName() ] = 1
end
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:OnDrowRangerMinibossThink()
return self:OnBaseThink()
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:OnFirstSeen()
CBossBase.OnFirstSeen( self )
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:IsInvisible()
local hBuff = thisEntity:FindModifierByName( "modifier_item_invisibility_edge_windwalk" )
return hBuff ~= nil
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:OnHealthPercentThreshold( nPct )
CBossBase.OnHealthPercentThreshold( self, nPct )
if nPct <= self.nEnragePct and self.bEnraged == false then
self.bEnraged = true
end
if nPct <= self.nShadowBladeHealthTriggerPct then
print( 'Shadow Blade Health Trigger Hit at ' .. self.nShadowBladeHealthTriggerPct )
self.nShadowBladeHealthTriggerPct = self.nShadowBladeHealthTriggerPct - 25
self.bTriggerShadowBlade = true
end
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:EvaluateGust()
if self:IsInvisible() then
return nil
end
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hGust:GetCastRange()
printf( "EvaluateGust - nSearchRadius == %d", nSearchRadius )
Enemies = GetEnemyHeroesInRange( thisEntity, nSearchRadius )
--Enemies = FilterEntitiesOutsideOfRange( self.me:GetAbsOrigin(), Enemies, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
local hRandomEnemy = Enemies[ RandomInt( 1, #Enemies ) ]
local vTargetLocation = hRandomEnemy:GetAbsOrigin()
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hGust:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hGust )
end
end
--[[
local vTargetLocation = GetBestDirectionalPointTarget( self.hGust )
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hGust:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hGust )
end
]]
return Order
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:EvaluateMultishot()
if self:IsInvisible() then
return nil
end
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hMultishot:GetSpecialValueFor( "effective_range" )
--printf( "EvaluateMultishot - nSearchRadius == %d", nSearchRadius )
Enemies = GetEnemyHeroesInRange( thisEntity, nSearchRadius )
--Enemies = FilterEntitiesOutsideOfRange( self.me:GetAbsOrigin(), Enemies, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
local hRandomEnemy = Enemies[ RandomInt( 1, #Enemies ) ]
local vTargetLocation = hRandomEnemy:GetAbsOrigin()
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hMultishot:entindex(),
Queue = false,
}
Order.flOrderInterval = self.hMultishot:GetChannelTime()
--print( 'ORDER INTERVAL for Multishot is ' .. Order.flOrderInterval )
end
end
-- need to get this entity to issue some command to select the exit portal after the cast
return Order
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:EvaluateHurricanePike()
if self:IsInvisible() then
return nil
end
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hHurricanePike:GetCastRange()
printf( "EvaluateHurricanePike - nSearchRadius == %d", nSearchRadius )
Enemies = GetEnemyHeroesInRange( thisEntity, nSearchRadius )
--Enemies = FilterEntitiesOutsideOfRange( self.me:GetAbsOrigin(), Enemies, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
local hRandomEnemy = Enemies[ RandomInt( 1, #Enemies ) ]
local vTargetLocation = hRandomEnemy:GetAbsOrigin()
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hRandomEnemy:entindex(),
AbilityIndex = self.hHurricanePike:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hHurricanePike )
end
end
return Order
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:EvaluateShadowBlade()
if self:IsInvisible() then
return nil
end
local Order = nil
if self.bTriggerShadowBlade == true then
print( 'Triggering Shadow Blade!' )
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP,
Queue = false,
}
ExecuteOrderFromTable( Order )
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hShadowBlade:entindex(),
Queue = false,
}
Order.flOrderInterval = 1.0
self.bTriggerShadowBlade = false
self.bTriggerEscape = true
end
return Order
end
--------------------------------------------------------------------------------
function CDrowRangerMiniboss:GetNonAbilityOrder()
local Order = nil
-- if we've successfully shadow bladed and we're ready to escape we should move somewhere else
if self:IsInvisible() and self.bTriggerEscape == true then
print( 'INVIS! Setting new escape location' )
self.bTriggerEscape = false
local vEscapeLoc = FindPathablePositionNearby( thisEntity:GetAbsOrigin(), 1000, 2000 )
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vEscapeLoc,
}
Order.flOrderInterval = 10.0
if self.Encounter ~= nil then
self.Encounter:OnDrowShadowBladed()
else
print( 'CDrowRangerMiniboss - ENCOUNTER IS NIL' )
end
end
return Order
end

View File

@@ -0,0 +1,28 @@
LinkLuaModifier( "modifier_drow_ranger_skeleton_archer", "modifiers/creatures/modifier_drow_ranger_skeleton_archer", LUA_MODIFIER_MOTION_NONE )
--------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "particle", "particles/units/heroes/hero_clinkz/clinkz_burning_army_start.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_clinkz/clinkz_burning_army.vpcf", context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
-- reveal our position for spawning
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 0.5 } )
-- this modifier will kill them when it falls off
local flDuration = RandomFloat( 9, 12 )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_drow_ranger_skeleton_archer", { duration = flDuration } )
end

View File

@@ -0,0 +1,78 @@
require( "ai/shared" )
require( "ai/ai_core" )
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
local hUnit = thisEntity
thisEntity.hDismember = hUnit:FindAbilityByName( "creature_pudge_dismember" )
thisEntity:SetContextThink( "PudgeMinibossThink", PudgeMinibossThink, 1 )
end
--------------------------------------------------------------------------------
function PudgeMinibossThink()
local hUnit = thisEntity
if hUnit:IsChanneling() then
return 0.25
end
if thisEntity.hPhaseBoots == nil then
for j = 0,DOTA_ITEM_INVENTORY_SIZE-1 do
local hItem = thisEntity:GetItemInSlot( j )
if hItem and hItem:GetAbilityName() == "item_phase_boots" then
thisEntity.hPhaseBoots = hItem
break
end
end
else
if thisEntity.hPhaseBoots:IsFullyCastable() then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hPhaseBoots:entindex(),
Queue = false,
})
end
end
local flNow = GameRules:GetGameTime()
if thisEntity.hDismember and thisEntity.hDismember:IsFullyCastable() then
local hHeroes = GetEnemyHeroesInRange( hUnit, 9000 )
if #hHeroes >= 1 then
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hHeroes[1]:entindex(),
AbilityIndex = thisEntity.hDismember:entindex(),
Queue = false,
})
end
else
hUnit.flLastAggroSwitch = hUnit.flLastAggroSwitch and hUnit.flLastAggroSwitch or 0
local hTarget = AICore:ClosestEnemyHeroInRange( hUnit, 9000 )
if (flNow - hUnit.flLastAggroSwitch) > 2 then
AttackTargetOrder( hUnit, hTarget )
hUnit.flLastAggroSwitch = flNow
end
end
return 0.25
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,202 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
--thisEntity.hShadowPoisonAbility = thisEntity:FindAbilityByName( "aghsfort_shadow_demon_shadow_poison" )
thisEntity.hDisruptionAbility = thisEntity:FindAbilityByName( "aghsfort_shadow_demon_disruption" )
if thisEntity.hDisruptionAbility == nil then
print( 'MISSING aghsfort_shadow_demon_disruption on shadow demon ai' )
end
thisEntity.flRetreatRange = 500
thisEntity.flAttackRange = 850
thisEntity.flDisruptionDelayTime = GameRules:GetGameTime() + RandomFloat( 7, 12 ) -- need to live for this long before we can think about casting disruption
thisEntity.PreviousOrder = "no_order"
thisEntity:SetContextThink( "ShadowDemonThink", ShadowDemonThink, 0.5 )
end
--------------------------------------------------------------------------------
function ShadowDemonThink()
if not IsServer() then
return
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 5000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return HoldPosition()
end
local hAttackTarget = nil
local hApproachTarget = nil
for _, hEnemy in pairs( hEnemies ) do
if hEnemy ~= nil and hEnemy:IsAlive() then
local flDist = ( hEnemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < thisEntity.flRetreatRange then
if ( thisEntity.fTimeOfLastRetreat and ( GameRules:GetGameTime() < thisEntity.fTimeOfLastRetreat + 3 ) ) then
-- We already retreated recently, so just attack
hAttackTarget = hEnemy
else
return Retreat( hEnemy )
end
end
if flDist <= thisEntity.flAttackRange then
hAttackTarget = hEnemy
end
if flDist > thisEntity.flAttackRange then
hApproachTarget = hEnemy
end
end
end
if hAttackTarget == nil and hApproachTarget ~= nil then
return Approach( hApproachTarget )
end
if thisEntity.hDisruptionAbility ~= nil and thisEntity.hDisruptionAbility:IsFullyCastable() then
--print( 'disruption check' )
if GameRules:GetGameTime() > thisEntity.flDisruptionDelayTime and hAttackTarget then
--print( 'Shadow Demon using Disruption on ENEMY!' )
return CastDisruption( hAttackTarget )
end
end
--[[
if hAttackTarget and thisEntity.hShadowPoisonAbility ~= nil and thisEntity.hShadowPoisonAbility:IsFullyCastable() then
return CastPoison( hAttackTarget )
end
--]]
if hAttackTarget then
thisEntity:FaceTowards( hAttackTarget:GetOrigin() )
--return HoldPosition()
end
return 0.5
end
--------------------------------------------------------------------------------
function CastPoison( hEnemy )
--print( "ai_shadow_demon - CastPoison" )
local fDist = ( hEnemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local vTargetPos = hEnemy:GetOrigin()
--[[
if ( fDist > 400 ) and hEnemy and hEnemy:IsMoving() then
local vLeadingOffset = hEnemy:GetForwardVector() * RandomInt( 200, 400 )
vTargetPos = hEnemy:GetOrigin() + vLeadingOffset
end
--]]
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetPos,
AbilityIndex = thisEntity.hShadowPoisonAbility:entindex(),
Queue = false,
})
thisEntity.PreviousOrder = "poison"
return 1
end
--------------------------------------------------------------------------------
function CastDisruption( hEnemy )
--print( "ai_shadow_demon - CastDisruption" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hEnemy:entindex(),
AbilityIndex = thisEntity.hDisruptionAbility:entindex(),
Queue = false,
})
thisEntity.PreviousOrder = "disruption"
return 1
end
--------------------------------------------------------------------------------
function Approach(unit)
--print( "ai_shadow_demon - Approach" )
local vToEnemy = unit:GetOrigin() - thisEntity:GetOrigin()
vToEnemy = vToEnemy:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity:GetOrigin() + vToEnemy * thisEntity:GetIdealSpeed()
})
thisEntity.PreviousOrder = "approach"
return 1
end
--------------------------------------------------------------------------------
function Retreat(unit)
--print( "ai_shadow_demon - Retreat" )
local vAwayFromEnemy = thisEntity:GetOrigin() - unit:GetOrigin()
vAwayFromEnemy = vAwayFromEnemy:Normalized()
local vMoveToPos = thisEntity:GetOrigin() + vAwayFromEnemy * thisEntity:GetIdealSpeed()
-- if away from enemy is an unpathable area, find a new direction to run to
local nAttempts = 0
while ( ( not GridNav:CanFindPath( thisEntity:GetOrigin(), vMoveToPos ) ) and ( nAttempts < 5 ) ) do
vMoveToPos = thisEntity:GetOrigin() + RandomVector( thisEntity:GetIdealSpeed() )
nAttempts = nAttempts + 1
end
thisEntity.fTimeOfLastRetreat = GameRules:GetGameTime()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vMoveToPos,
})
thisEntity.PreviousOrder = "retreat"
return 1.25
end
--------------------------------------------------------------------------------
function HoldPosition()
--print( "ai_shadow_demon - Hold Position" )
if thisEntity.PreviousOrder == "hold_position" then
return 0.5
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_HOLD_POSITION,
Position = thisEntity:GetOrigin()
})
thisEntity.PreviousOrder = "hold_position"
return 0.5
end

View File

@@ -0,0 +1,97 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.bAcqRangeModified = false
thisEntity.hEntityKilledGameEvent = ListenToGameEvent( "entity_killed", Dynamic_Wrap( thisEntity:GetPrivateScriptScope(), 'OnEntityKilled' ), nil )
thisEntity:SetContextThink( "ShroomGiantThink", ShroomGiantThink, 1 )
end
function UpdateOnRemove()
StopListeningToGameEvent( thisEntity.hEntityKilledGameEvent )
end
function ShroomGiantThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
return 0.5
end
function OnEntityKilled( event )
local hVictim = nil
if event.entindex_killed ~= nil then
hVictim = EntIndexToHScript( event.entindex_killed )
end
if hVictim ~= thisEntity then
return
end
-- Check all of the other giants, and see if any others are aggroed.
-- If not, then we'll force aggro on the closest one
if thisEntity.Encounter == nil then
print( 'ai_shroom_giant - OnEntityKilled: Encounter is nil!')
return
end
local flNearDist = 60000
local hGiant = nil
local hGiants = thisEntity.Encounter:GetSpawnedUnitsOfType( "npc_dota_creature_shroom_giant" )
for i=1,#hGiants do
if hGiants[i] ~= thisEntity then
if hGiants[i].bAcqRangeModified then
hGiant = nil
break
end
local flDist = ( hGiants[i]:GetAbsOrigin() - hVictim:GetAbsOrigin() ):Length2D()
if flDist < flNearDist then
flNearDist = flDist
hGiant = hGiants[i]
end
end
end
if hGiant == nil then
return
end
hGiant:SetDayTimeVisionRange( 5000 )
hGiant:SetNightTimeVisionRange( 5000 )
hGiant:SetAcquisitionRange( 5000 )
hGiant.bAcqRangeModified = true
-- Order nearby shamans idle unonwned to start attacking also
local hShamans = thisEntity.Encounter:GetSpawnedUnitsOfType( "npc_dota_creature_shadow_shaman" )
print( 'Shroom Giant Death - Searching for Shadow Shamans... found ' .. #hShamans )
for i=1,#hShamans do
local hShaman = hShamans[i]
if hShaman:GetOwnerEntity() == nil and hShaman:GetAggroTarget() == nil and ( hGiant:GetAbsOrigin() - hShaman:GetAbsOrigin() ):Length2D() < 800 then
hShaman:SetDayTimeVisionRange( 5000 )
hShaman:SetNightTimeVisionRange( 5000 )
hShaman:SetAcquisitionRange( 5000 )
end
end
end

View File

@@ -0,0 +1,81 @@
require( "ai/shared" )
LinkLuaModifier( "modifier_shroomling_enrage", "modifiers/creatures/modifier_shroomling_enrage", LUA_MODIFIER_MOTION_NONE )
LinkLuaModifier( "modifier_shroomling_sleep", "modifiers/creatures/modifier_shroomling_sleep", LUA_MODIFIER_MOTION_NONE )
--------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "particle", "particles/items2_fx/mask_of_madness.vpcf", context )
PrecacheResource( "particle", "particles/generic_gameplay/generic_sleep.vpcf", context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if thisEntity == nil then
return
end
-- thisEntity.nShackledHeroSearchRadius = 900
-- thisEntity.hForceAttackTarget = nil
thisEntity:AddNewModifier( thisEntity, nil, "modifier_shroomling_sleep", { duration = -1.0 } )
-- mushrooms are 100% off limits for a bit after spawning
thisEntity:AddNewModifier( thisEntity, nil, "modifier_invulnerable", { duration = 1.5 } )
-- thisEntity:SetContextThink( "ShroomlingThink", ShroomlingThink, 1 )
end
--[[
function ShroomlingThink()
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity:IsAlive() == false then
return 1
end
if thisEntity.hForceAttackTarget ~= nil then
local hShackleDebuff = thisEntity.hForceAttackTarget:FindModifierByName( "modifier_aghsfort_shadow_shaman_shackles" )
if thisEntity.hForceAttackTarget:IsAlive() == false or hShackleDebuff == nil then
-- force attack target is dead or it's alive and the shackle has ended
thisEntity:RemoveModifierByName( "modifier_shroomling_enrage" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_shroomling_sleep", { duration = -1.0 } )
thisEntity.hForceAttackTarget = nil
return 1
end
-- target is still valid and shackled so have at it
AttackTargetOrder( thisEntity, thisEntity.hForceAttackTarget )
return 1
end
local enemies = GetEnemyHeroesInRange( thisEntity, thisEntity.nShackledHeroSearchRadius )
if #enemies == 0 then
return 1
end
for _,enemy in pairs( enemies ) do
if enemy ~= nil and enemy:IsAlive() then
local hShackleDebuff = enemy:FindModifierByName( "modifier_aghsfort_shadow_shaman_shackles" )
if hShackleDebuff ~= nil then
thisEntity.hForceAttackTarget = enemy
thisEntity:RemoveModifierByName( "modifier_shroomling_sleep" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_shroomling_enrage", { duration = -1.0 } )
--print( 'Shroomling Attacking!' )
AttackTargetOrder( thisEntity, enemy )
return 1
end
end
end
return 0.5
end
--]]

View File

@@ -0,0 +1,95 @@
--[[
Upheaval Urn AI
]]
function Spawn( entityKeyValues )
if IsServer() == false then
return
end
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = -1 } )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_magic_immune", { duration = -1 } )
AbilityUpheaval = thisEntity:FindAbilityByName( "urn_upheaval" )
thisEntity.nPreviewFX = nil
thisEntity.bCastSpell = false
thisEntity.fWarningTime = 2.5
local flInitialThinkDelay = RandomFloat( 1, 3 )
thisEntity:SetContextThink( "UrnAIThink", UrnAIThink, flInitialThinkDelay )
end
function Precache( context )
PrecacheResource( "particle", "particles/econ/items/warlock/warlock_staff_hellborn/warlock_upheaval_hellborn.vpcf", context )
PrecacheResource( "particle", "particles/econ/items/warlock/warlock_staff_hellborn/warlock_upheaval_hellborn_debuff.vpcf", context )
PrecacheResource( "particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_ring.vpcf", context )
PrecacheResource( "particle", "particles/dark_moon/darkmoon_creep_warning.vpcf", context )
end
function UrnAIThink()
if IsServer() == false then
return
end
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity:IsAlive() == false then
if thisEntity.nPreviewFX ~= nil then
ParticleManager:DestroyParticle( thisEntity.nPreviewFX, false )
thisEntity.nPreviewFX = nil
end
return
end
if thisEntity.bCastSpell == true then
thisEntity.bCastSpell = false
if thisEntity.nPreviewFX ~= nil then
ParticleManager:DestroyParticle( thisEntity.nPreviewFX, false )
thisEntity.nPreviewFX = nil
end
local order = {}
order.UnitIndex = thisEntity:entindex()
order.OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET
order.AbilityIndex = AbilityUpheaval:entindex()
ExecuteOrderFromTable( order )
local channelTime = AbilityUpheaval:GetChannelTime()
local downTime = RandomFloat( 3, 7 )
local flThinkDelay = channelTime + downTime
--print( 'Upheaval Urn sleeping for ' .. flThinkDelay )
return flThinkDelay
end
if AbilityUpheaval ~= nil and AbilityUpheaval:IsFullyCastable() then
thisEntity.bCastSpell = true
--[[
local radius = AbilityUpheaval:GetSpecialValueFor( "aoe" )
local nFXIndex = ParticleManager:CreateParticle( "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_ring.vpcf", PATTACH_CUSTOMORIGIN, nil )
ParticleManager:SetParticleControl( nFXIndex, 0, thisEntity:GetOrigin() )
ParticleManager:SetParticleControl( nFXIndex, 1, Vector( radius, radius, radius ) )
ParticleManager:SetParticleControl( nFXIndex, 2, Vector( thisEntity.fWarningTime, thisEntity.fWarningTime, thisEntity.fWarningTime ) )
ParticleManager:ReleaseParticleIndex( nFXIndex )
--]]
local warningRadius = 80
thisEntity.nPreviewFX = ParticleManager:CreateParticle( "particles/dark_moon/darkmoon_creep_warning.vpcf", PATTACH_ABSORIGIN_FOLLOW, thisEntity )
ParticleManager:SetParticleControlEnt( thisEntity.nPreviewFX, 0, thisEntity, PATTACH_ABSORIGIN_FOLLOW, nil, thisEntity:GetOrigin(), true )
ParticleManager:SetParticleControl( thisEntity.nPreviewFX, 1, Vector( warningRadius, warningRadius, warningRadius ) )
ParticleManager:SetParticleControl( thisEntity.nPreviewFX, 15, Vector( 255, 26, 26 ) )
return thisEntity.fWarningTime
end
return 1.0
end

View File

@@ -0,0 +1,395 @@
--[[ Alchemist AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
local hCurrentEncounter = GameRules.Aghanim:GetCurrentRoom():GetEncounter()
if hCurrentEncounter.activeTargets == nil then
hCurrentEncounter.activeTargets = {}
end
thisEntity:AddNewModifier( thisEntity, nil, "modifier_attack_speed_unslowable", { attack_speed_reduction_pct = 20 } )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_move_speed_unslowable", { move_speed_reduction_pct = 20 } )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorConcoction, BehaviorAcidSpray } ) --, BehaviorRunAway } )
thisEntity.nextTargetTime = GameRules:GetGameTime()
end
function UpdateOnRemove()
behaviorSystem:Destroy()
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
function RemoveAvailableTarget( availableHeroes, nEntIndexToRemove )
if #availableHeroes == 1 then
return
end
for i=1,#availableHeroes do
if availableHeroes[i]:entindex() == nEntIndexToRemove then
table.remove( availableHeroes, i )
break
end
end
end
--------------------------------------------------------------------------------------------------------
function SelectNewTarget( )
local hCurrentEncounter = GameRules.Aghanim:GetCurrentRoom():GetEncounter()
-- Mark current target as not active any more
-- Important to do prior to the code that grab a target which is not already being chased
local nEntIndex = -1
if thisEntity.hTarget ~= nil then
nEntIndex = thisEntity.hTarget:entindex()
hCurrentEncounter.activeTargets[ tostring( nEntIndex ) ] = nil
end
local availableHeroes = GetAliveHeroesInRoom()
-- Remove not visible heroes
for i=#availableHeroes,1,-1 do
if not thisEntity:CanEntityBeSeenByMyTeam( availableHeroes[i] ) then
table.remove( availableHeroes, i )
end
end
-- Prefer to grab a target which is not already being chased by another alchemist
for szEntIndex,bIsActive in pairs( hCurrentEncounter.activeTargets ) do
if bIsActive == true then
RemoveAvailableTarget( availableHeroes, tonumber( szEntIndex ) )
end
end
-- Prefer to pick a different target from last time
RemoveAvailableTarget( availableHeroes, nEntIndex )
-- Select a random target from the available ones
local hNewTarget = nil
if #availableHeroes > 0 then
hNewTarget = availableHeroes[ math.random( 1, #availableHeroes ) ]
end
thisEntity.hTarget = hNewTarget
thisEntity.nextTargetTime = GameRules:GetGameTime() + 10 -- Keep on this guy for 10 seconds at least
if thisEntity.hTarget ~= nil then
hCurrentEncounter.activeTargets[ tostring( thisEntity.hTarget:entindex() ) ] = true
end
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Destroy()
local hCurrentEncounter = GameRules.Aghanim:GetCurrentRoom():GetEncounter()
-- Mark current target as not active any more
if thisEntity.hTarget ~= nil then
hCurrentEncounter.activeTargets[ tostring( thisEntity.hTarget:entindex() ) ] = nil
thisEntity.hTarget = nil
end
end
function BehaviorNone:Begin()
--print( "BehaviorNone:Begin()" )
local orders = nil
-- Acquire a new target if necessary
if thisEntity.hTarget == nil or ( thisEntity.nextTargetTime <= GameRules:GetGameTime() ) or
not thisEntity.hTarget:IsAlive() or
( not thisEntity:CanEntityBeSeenByMyTeam( thisEntity.hTarget ) and ( ( thisEntity:GetAbsOrigin() - thisEntity.lastTargetPosition ):Length2D() < 250 ) ) then
SelectNewTarget()
end
if thisEntity.hTarget ~= nil then
thisEntity.lastTargetPosition = thisEntity.hTarget:GetAbsOrigin()
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = thisEntity.hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorConcoction = {}
function BehaviorConcoction:Evaluate()
--print( "BehaviorConcoction:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
-- Got to have a target
if thisEntity.hTarget == nil or not thisEntity.hTarget:IsAlive() or not thisEntity:CanEntityBeSeenByMyTeam( thisEntity.hTarget ) then
return desire
end
self.concoctionAbility = thisEntity:FindAbilityByName( "alchemist_unstable_concoction" )
self.nBrewTime = self.concoctionAbility:GetSpecialValueFor( "brew_time")
self.nSelfExplodeTime = self.concoctionAbility:GetSpecialValueFor( "brew_explosion")
if self.concoctionAbility and self.concoctionAbility:IsFullyCastable() then
-- Don't do it if other alchemists are currently conconting
local friends = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), thisEntity, 5000, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO, 0, 0, false )
for i = 1,#friends do
if friends[i] ~= thisEntity and friends[i].bInConcontion ~= nil and friends[i].bInConcontion == true then
return desire
end
end
--print( "Concoction:Evaluate: thisEntity.hTarget:entindex() == " .. thisEntity.hTarget:entindex() )
thisEntity.lastTargetPosition = thisEntity.hTarget:GetAbsOrigin()
if thisEntity.hTarget:IsStunned() then
desire = 2
else
desire = 4
end
end
return desire
end
function BehaviorConcoction:Begin()
--print( "BehaviorConcoction:Begin()" )
thisEntity.bInConcontion = true
if self.startConcoctionTime ~= nil then
return nil
end
self.shivasAbility = nil
self.phaseAbility = nil
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_shivas_guard" then
self.shivasAbility = item
end
if item and item:GetAbilityName() == "item_phase_boots" then
self.phaseAbility = item
end
end
self.concoctionThrowAbility = thisEntity:FindAbilityByName( "alchemist_unstable_concoction_throw" )
self.chemicalRageAbility = thisEntity:FindAbilityByName( "alchemist_chemical_rage" )
if self.concoctionAbility and self.concoctionAbility:IsFullyCastable() then
self.startConcoctionTime = GameRules:GetGameTime()
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.concoctionAbility:entindex(),
}
return order
end
return nil
end
BehaviorConcoction.Continue = BehaviorConcoction.Begin
function BehaviorConcoction:End()
thisEntity.bInConcontion = false
end
function BehaviorConcoction:IsDone()
return ( self.startConcoctionTime == nil )
end
function BehaviorConcoction:Think( )
if self.startConcoctionTime == nil then
return nil
end
--print( "BehaviorConcoction:Think( )" )
--print( "-----------------------------------" )
--print( "self.startConcoctionTime == " .. self.startConcoctionTime )
--print( "GameRules:GetGameTime() == " .. GameRules:GetGameTime() )
-- reacquire target if possible
if thisEntity.hTarget == nil or not thisEntity.hTarget:IsAlive() then
SelectNewTarget()
if thisEntity.hTarget == nil then
-- No target? Move to last valid target position
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
return order
end
end
thisEntity.lastTargetPosition = thisEntity.hTarget:GetAbsOrigin()
-- if we missed our cast window for some reason
if GameRules:GetGameTime() >= ( self.startConcoctionTime + self.nSelfExplodeTime ) then
--print( "ending")
self.startConcoctionTime = nil
return nil
end
-- If we're still waiting to throw, then try to close...
if GameRules:GetGameTime() < ( self.startConcoctionTime + self.nBrewTime ) then
-- Cast phase if we can
if self.phaseAbility and self.phaseAbility:IsFullyCastable() then
--print( "Casting phase" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.phaseAbility:entindex()
}
return order
end
-- Cast Shiva if we can
if self.shivasAbility and self.shivasAbility:IsFullyCastable() then
--print( "Casting shiva" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.shivasAbility:entindex()
}
return order
end
else
-- Ok, we're able to throw, so lets throw
if self.concoctionThrowAbility and not self.concoctionThrowAbility:IsHidden() and self.concoctionThrowAbility:IsFullyCastable() then
--print( "Casting throw" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = self.concoctionThrowAbility:entindex(),
TargetIndex = thisEntity.hTarget:entindex()
}
return order
end
-- Otherwise, try to cast chemical rage
if self.chemicalRageAbility and self.chemicalRageAbility:IsFullyCastable() then
--print( "Casting Chemical Rage" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.chemicalRageAbility:entindex(),
}
return order
end
end
-- Nothing better to do? Chase our target
--print( "Attacking" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = thisEntity.hTarget:entindex()
}
return order
end
--------------------------------------------------------------------------------------------------------
BehaviorAcidSpray = {}
function BehaviorAcidSpray:Evaluate()
--print( "BehaviorAcidSpray:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
-- Got to have a target
if thisEntity.hTarget == nil or not thisEntity.hTarget:IsAlive() or not thisEntity:CanEntityBeSeenByMyTeam( thisEntity.hTarget ) then
return desire
end
self.acidSprayAbility = thisEntity:FindAbilityByName( "alchemist_acid_spray" )
if self.acidSprayAbility and self.acidSprayAbility:IsFullyCastable() then
if thisEntity.hTarget:IsStunned() then
desire = 6
else
desire = 4
end
end
return desire
end
function BehaviorAcidSpray:Begin()
--print( "BehaviorAcidSpray:Begin()" )
if self.acidSprayAbility and self.acidSprayAbility:IsFullyCastable() then
--print( "Casting Acid Spray" )
local targetPoint = thisEntity.lastTargetPosition + RandomVector( 100 )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.acidSprayAbility:entindex(),
Position = targetPoint
}
return order
end
return nil
end
BehaviorAcidSpray.Continue = BehaviorAcidSpray.Begin

View File

@@ -0,0 +1,356 @@
--------------------------------------------------------------------------------
if CAnnouncerAghanim == nil then
CAnnouncerAghanim = class( {} )
end
--------------------------------------------------------------------------------
function Precache( context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CAnnouncerAghanim( thisEntity, 1.0 )
GameRules.Aghanim:SetAnnouncer( thisEntity.AI )
end
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:constructor( hUnit, flInterval )
self.me = hUnit
self.flDefaultInterval = flInterval
self.nHeroSelected = 1
self.flLastSpeakTime = -1000
self.flPostSpeechTime = -1000
self.flPostSpeechDelay = 0.5
self.bIsSpeaking = false
self.lastAbilityUpgradeHeroes = {}
self.hSpeakingUnitOverride = nil
self.nLastLaggingDepth = 0
self.nLastShopDepth = 0
self.nCallbacksIssued = 0
self.me:SetThink( "OnAnnouncerThink", self, "OnAnnouncerThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OverrideSpeakingUnit( hOverrideUnit )
self.hSpeakingUnitOverride = hOverrideUnit
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:SetServerAuthoritative( bServerAuthoritative )
self.me:SetServerAuthoritative( bServerAuthoritative )
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnAnnouncerThink()
-- Anything that needs thinking is here
return self.flDefaultInterval
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:Speak( flDelay, bForce, hCriteriaTable )
print( "CAnnouncerAghanim:Speak speaking:" .. tostring( self.bIsSpeaking ) .. " ( force: " .. tostring( bForce ) .. " ) " .. hCriteriaTable.announce_event )
-- Safety valve in case the callback breaks
if ( self.bIsSpeaking == true ) and ( self.flLastSpeakTime > 0 ) and ( GameRules:GetGameTime() - self.flLastSpeakTime ) > 30 then
print( "*** ERROR : CAnnouncerAghanim never got the OnSpeechComplete callback!" )
self.bIsSpeaking = false
end
-- Don't overlap lines unless this is a required line
if bForce == false and self:IsCurrentlySpeaking( ) == true then
print( "*** CAnnouncerAghanim discarding line -- " .. hCriteriaTable.announce_event .. " ( pst " .. self.flPostSpeechTime .. " cur " .. GameRules:GetGameTime() .. " ) " )
return false
end
-- Add standard criteria all speech has
hCriteriaTable[ "has_new_players" ] = GameRules.Aghanim:HasAnyNewPlayers()
hCriteriaTable[ "ascension_level" ] = GameRules.Aghanim:GetAscensionLevel()
hCriteriaTable[ "tournament_mode" ] = GameRules.Aghanim:IsInTournamentMode()
local hSpeakingUnit = self.me
if self.hSpeakingUnitOverride ~= nil then
hSpeakingUnit = self.hSpeakingUnitOverride
end
self.nCallbacksIssued = self.nCallbacksIssued + 1
self.flLastSpeakTime = GameRules:GetGameTime() + flDelay
hSpeakingUnit:QueueConcept( flDelay, hCriteriaTable, Dynamic_Wrap( CAnnouncerAghanim, 'OnSpeechComplete' ), self, { nCallbackIndex = self.nCallbacksIssued } )
self.bIsSpeaking = true
return true
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnSpeechComplete( bDidActuallySpeak, hCallbackInfo )
--print( "CAnnouncerAghanim:OnSpeechComplete " .. tostring( bDidActuallySpeak ) .. " " .. hCallbackInfo.nCallbackIndex .. " - " .. self.nCallbacksIssued )
if hCallbackInfo.nCallbackIndex == self.nCallbacksIssued then
self.bIsSpeaking = false
self.flPostSpeechTime = GameRules:GetGameTime() + self.flPostSpeechDelay
end
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:IsCurrentlySpeaking( )
return self.bIsSpeaking or ( self.flPostSpeechTime > GameRules:GetGameTime() )
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnHeroSelectionStarted( )
self:Speak( 1.0, true,
{
announce_event = "hero_selection",
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnHeroSelected( szHeroName )
self:Speak( 1.0, false,
{
announce_event = "hero_selected",
hero_name = szHeroName,
pick_number = self.nHeroSelected,
})
self.nHeroSelected = self.nHeroSelected + 1
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnGameStarted( )
self:Speak( 3.0, true,
{
announce_event = "game_started",
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnSelectRewards( )
self:Speak( 1.0, false,
{
announce_event = "select_rewards",
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnItemPurchased( szHeroName, szItemName )
local nDepth = GameRules.Aghanim:GetCurrentRoom():GetDepth()
if self.nLastShopDepth == nDepth then
return
end
self.nLastShopDepth = nDepth
self:Speak( 1.0, true,
{
announce_event = "item_purchased",
hero_name = szHeroName,
item = szItemName,
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnGameLost( )
self:Speak( 1.0, true,
{
announce_event = "game_lost",
depth = GameRules.Aghanim:GetCurrentRoom():GetDepth(),
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnGameWon( )
self:Speak( 1.0, true,
{
announce_event = "game_won",
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnEncounterSelected( hEncounter )
if hEncounter:GetRoom():GetDepth() == 1 then
return
end
self:Speak( 3.0, true,
{
announce_event = "encounter_selected",
encounter_type = hEncounter:GetRoom():GetType(),
encounter_name = hEncounter:GetName(),
encounter_act = hEncounter:GetRoom():GetAct(),
elite = hEncounter:GetRoom():GetEliteRank(),
depth = hEncounter:GetRoom():GetDepth(),
act_boss = GameRules.Aghanim:GetBossUnitForAct( hEncounter:GetRoom():GetAct() )
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnEncounterStarted( hEncounter )
if hEncounter:GetRoom():GetDepth() == 1 then
return
end
self:Speak( 3.0, false,
{
announce_event = "encounter_started",
encounter_type = hEncounter:GetRoom():GetType(),
encounter_name = hEncounter:GetName(),
encounter_act = hEncounter:GetRoom():GetAct(),
elite = hEncounter:GetRoom():GetEliteRank(),
depth = hEncounter:GetRoom():GetDepth(),
act_boss = GameRules.Aghanim:GetBossUnitForAct( hEncounter:GetRoom():GetAct() )
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnEncounterComplete( hEncounter )
-- local bForceSpeech = false
-- if GameRules.Aghanim:HasAnyNewPlayersForAnnouncer() == true and hEncounter:GetRoom():GetDepth() == 1 then
-- bForceSpeech = true
-- end
self:Speak( 2.0, true,
{
announce_event = "encounter_completed",
encounter_type = hEncounter:GetRoom():GetType(),
encounter_name = hEncounter:GetName(),
encounter_act = hEncounter:GetRoom():GetAct(),
elite = hEncounter:GetRoom():GetEliteRank(),
depth = hEncounter:GetRoom():GetDepth(),
act_boss = GameRules.Aghanim:GetBossUnitForAct( hEncounter:GetRoom():GetAct() )
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnRewardSelected( hHero, nDepth, eRewardType, szRewardName )
self:Speak( 1.0, false,
{
announce_event = "reward_selected",
hero_name = hHero:GetUnitName(),
depth = nDepth,
reward_type = eRewardType,
reward_name = szRewardName,
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnCreatureKilled( hEncounter, hUnit )
self:Speak( 1.0, false,
{
announce_event = "creature_killed",
unit = hUnit:GetUnitName(),
boss = hUnit:IsBoss(),
captain = hUnit:IsConsideredHero() == true and hUnit:IsBoss() == false,
encounter_name = hEncounter:GetName(),
encounter_act = hEncounter:GetRoom():GetAct(),
depth = hEncounter:GetRoom():GetDepth(),
})
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnHeroKilled( szHeroName, szKillerUnit, nRespawnsRemaining )
local kv =
{
announce_event = "hero_killed",
hero_name = szHeroName,
respawns_remaining = nRespawnsRemaining,
}
if szKillerUnit ~= nil then
kv.killer = szKillerUnit
end
-- Long delay to speak after the hero's own death line
self:Speak( 4.0, false, kv )
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnCowardlyHero( szUnitName, szHeroName )
local kv =
{
announce_event = "hero_cowardly",
unit = szUnitName,
hero_name = szHeroName,
}
self:Speak( 0.5, true, kv )
end
--------------------------------------------------------------------------------
function CAnnouncerAghanim:OnLaggingHero( szHeroName, nDepth )
if self.nLastLaggingDepth >= nDepth then
return
end
self.nLastLaggingDepth = nDepth
local kv =
{
announce_event = "lagging_hero",
hero_name = szHeroName,
}
self:Speak( 0.0, true, kv )
end

View File

@@ -0,0 +1,116 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
IgniteAbility = thisEntity:FindAbilityByName( "baby_ogre_magi_area_ignite" )
BloodlustAbility = thisEntity:FindAbilityByName( "ogre_magi_channelled_bloodlust" )
thisEntity:SetContextThink( "OgreMagiThink", OgreMagiThink, 1 )
end
--------------------------------------------------------------------------------
function OgreMagiThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if BloodlustAbility ~= nil and BloodlustAbility:IsChanneling() then
return 0.5
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
local bIgniteReady = ( #enemies > 0 and IgniteAbility ~= nil and IgniteAbility:IsFullyCastable() )
if BloodlustAbility ~= nil and BloodlustAbility:IsFullyCastable() then
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for _,friendly in pairs ( friendlies ) do
if friendly ~= nil then
if ( friendly:GetUnitName() == "npc_dota_creature_baby_ogre_tank" ) then
local fDist = ( friendly:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local fCastRange = BloodlustAbility:GetCastRange( thisEntity:GetOrigin(), nil )
--print( string.format( "fDist == %d, fCastRange == %d", fDist, fCastRange ) )
if ( fDist <= fCastRange ) and ( ( #enemies > 0 ) or ( friendly:GetAggroTarget() ) ) then
return Bloodlust( friendly )
elseif ( fDist > 400 ) and ( fDist < 900 ) then
if bIgniteReady == false then
return Approach( friendly )
end
end
end
end
end
end
if bIgniteReady then
return IgniteArea( enemies[ RandomInt( 1, #enemies ) ] )
end
local fFuzz = RandomFloat( -0.1, 0.1 ) -- Adds some timing separation to these magi
return 0.5 + fFuzz
end
--------------------------------------------------------------------------------
function Approach( hUnit )
--print( "Ogre Magi is approaching unit named " .. hUnit:GetUnitName() )
local vToUnit = hUnit:GetOrigin() - thisEntity:GetOrigin()
vToUnit = vToUnit:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToUnit * thisEntity:GetIdealSpeed()
})
return 1
end
--------------------------------------------------------------------------------
function Bloodlust( hUnit )
--print( "Casting bloodlust on " .. hUnit:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = BloodlustAbility:entindex(),
TargetIndex = hUnit:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function IgniteArea( hEnemy )
--print( "Casting ignite on " .. hEnemy:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = IgniteAbility:entindex(),
Position = hEnemy:GetOrigin(),
Queue = false,
})
return 0.55
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,101 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "baby_ogre_tank_melee_smash" )
JumpAbility = thisEntity:FindAbilityByName( "baby_ogre_tank_jump_smash" )
thisEntity:SetContextThink( "OgreTankThink", OgreTankThink, 1 )
end
function OgreTankThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
local hWintersCurseBuff = thisEntity:FindModifierByName( "modifier_aghsfort_winter_wyvern_winters_curse" )
if hWintersCurseBuff and hWintersCurseBuff:GetAuraOwner() ~= nil then
if SmashAbility ~= nil and SmashAbility:IsCooldownReady() then
return Smash( hWintersCurseBuff:GetAuraOwner() )
end
return 0.1
end
local nEnemiesRemoved = 0
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
for i = 1, #enemies do
local enemy = enemies[i]
if enemy ~= nil then
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 210 then
nEnemiesRemoved = nEnemiesRemoved + 1
table.remove( enemies, i )
end
end
end
if JumpAbility ~= nil and JumpAbility:IsFullyCastable() and nEnemiesRemoved > 0 then
return Jump()
end
if #enemies == 0 then
-- @todo: Could check whether there are ogre magi nearby that I should be positioning myself next to. Either that or have the magi come to me.
return 1
end
if SmashAbility ~= nil and SmashAbility:IsFullyCastable() then
return Smash( enemies[ 1 ] )
end
return 0.5
end
function Jump()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = JumpAbility:entindex(),
Queue = false,
})
return 2.5
end
function Smash( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = SmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 3 / thisEntity:GetHasteFactor()
end

View File

@@ -0,0 +1,203 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hArrowAbility = thisEntity:FindAbilityByName( "bandit_archer_arrow" )
thisEntity.flRetreatRange = 600
thisEntity.flAttackRange = thisEntity.hArrowAbility:GetCastRange( thisEntity:GetOrigin(), nil )
thisEntity.PreviousOrder = "no_order"
thisEntity.fRetreatCooldown = 5
thisEntity:SetContextThink( "BanditArcherThink", BanditArcherThink, 0.5 )
end
--------------------------------------------------------------------------------
function BanditArcherThink()
if not IsServer() then
return
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if not thisEntity.bInitialized then
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_bandit_archer_shadow_blade" then
thisEntity.hShadowBladeAbility = item
end
end
thisEntity.bInitialized = true
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.flAttackRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.1
end
local fHealthPctInvis = 15
if thisEntity:GetHealthPercent() <= fHealthPctInvis and thisEntity.hShadowBladeAbility and thisEntity.hShadowBladeAbility:IsFullyCastable() then
return CastShadowBlade()
end
if thisEntity:IsInvisible() then
-- Force a retreat, even if we've retreated recently
local hNearestEnemy = hEnemies[ 1 ]
return Retreat( hNearestEnemy )
end
local hAttackTarget = nil
local hApproachTarget = nil
for _, hEnemy in pairs( hEnemies ) do
if hEnemy ~= nil and hEnemy:IsAlive() then
local flDist = ( hEnemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < thisEntity.flRetreatRange then
if ( thisEntity.fTimeOfLastRetreat and ( GameRules:GetGameTime() < thisEntity.fTimeOfLastRetreat + thisEntity.fRetreatCooldown ) ) then
-- We already retreated recently, so just attack
hAttackTarget = hEnemy
else
return Retreat( hEnemy )
end
end
if flDist <= thisEntity.flAttackRange then
hAttackTarget = hEnemy
end
if flDist > thisEntity.flAttackRange then
hApproachTarget = hEnemy
end
end
end
if hAttackTarget == nil and hApproachTarget ~= nil then
return Approach( hApproachTarget )
end
if hAttackTarget and thisEntity.hArrowAbility ~= nil and thisEntity.hArrowAbility:IsFullyCastable() then
return CastArrow( hAttackTarget )
end
if hAttackTarget then
thisEntity:FaceTowards( hAttackTarget:GetOrigin() )
return HoldPosition()
end
return 0.1
end
--------------------------------------------------------------------------------
function CastArrow( hEnemy )
--print( "ai_bandit_archer - CastArrow" )
local fDist = ( hEnemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local vTargetPos = hEnemy:GetOrigin()
if ( fDist > 400 ) and hEnemy and hEnemy:IsMoving() then
local vLeadingOffset = hEnemy:GetForwardVector() * RandomInt( 200, 400 )
vTargetPos = hEnemy:GetOrigin() + vLeadingOffset
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetPos,
AbilityIndex = thisEntity.hArrowAbility:entindex(),
Queue = false,
})
return 2
end
--------------------------------------------------------------------------------
function Approach(unit)
--print( "ai_bandit_archer - Approach" )
local vToEnemy = unit:GetOrigin() - thisEntity:GetOrigin()
vToEnemy = vToEnemy:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToEnemy * thisEntity:GetIdealSpeed()
})
return 1
end
--------------------------------------------------------------------------------
function Retreat(unit)
--print( "ai_bandit_archer - Retreat" )
local vAwayFromEnemy = thisEntity:GetOrigin() - unit:GetOrigin()
vAwayFromEnemy = vAwayFromEnemy:Normalized()
local vMoveToPos = thisEntity:GetOrigin() + vAwayFromEnemy * thisEntity:GetIdealSpeed()
-- if away from enemy is an unpathable area, find a new direction to run to
local nAttempts = 0
while ( ( not GridNav:CanFindPath( thisEntity:GetOrigin(), vMoveToPos ) ) and ( nAttempts < 5 ) ) do
vMoveToPos = thisEntity:GetOrigin() + RandomVector( thisEntity:GetIdealSpeed() )
nAttempts = nAttempts + 1
end
thisEntity.fTimeOfLastRetreat = GameRules:GetGameTime()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vMoveToPos,
})
return 1.25
end
--------------------------------------------------------------------------------
function HoldPosition()
--print( "ai_bandit_archer - Hold Position" )
if thisEntity.PreviousOrder == "hold_position" then
return 0.5
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_HOLD_POSITION,
Position = thisEntity:GetOrigin()
})
thisEntity.PreviousOrder = "hold_position"
return 0.5
end
--------------------------------------------------------------------------------
function CastShadowBlade()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hShadowBladeAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,129 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hStiflingAbility = thisEntity:FindAbilityByName( "bandit_stifling_dagger" )
thisEntity.hBlinkStrikeAbility = thisEntity:FindAbilityByName( "creature_blink_strike" )
thisEntity.hStiflingAbility:SetLevel( 4 )
thisEntity.hBlinkStrikeAbility:SetLevel( 4 )
thisEntity:SetContextThink( "BanditCaptainThink", BanditCaptainThink, 0.5 )
end
--------------------------------------------------------------------------------
function BanditCaptainThink()
if not IsServer() then
return
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
-- Get last aggro timestamp
if ( not thisEntity.bHasAggro ) and thisEntity:GetAggroTarget() then
thisEntity.timeOfLastAggro = GameRules:GetGameTime()
thisEntity.bHasAggro = true
end
if thisEntity.bHasAggro and ( not thisEntity:GetAggroTarget() ) then
thisEntity.bHasAggro = false
end
if thisEntity.hStiflingAbility ~= nil and thisEntity.hStiflingAbility:IsChanneling() then
return 0.5
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 900, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.5
end
-- Categorize our enemies based on distance
local hMediumDistEnemies = { }
local hFarthestEnemy = nil
local fFarthestEnemyDist = 0
for _, hEnemy in pairs( hEnemies ) do
local fDist = ( hEnemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if fDist > fFarthestEnemyDist then
fFarthestEnemyDist = fDist
hFarthestEnemy = hEnemy
end
if fDist > 300 then
table.insert( hMediumDistEnemies, hEnemy )
end
end
-- If we've had aggro for a bit, we're willing to launch Stifling Dagger
local fDelayBeforeStifling = RandomFloat( 2, 4 )
if thisEntity.timeOfLastAggro and ( GameRules:GetGameTime() > ( thisEntity.timeOfLastAggro + fDelayBeforeStifling ) ) then
if thisEntity.hStiflingAbility ~= nil and thisEntity.hStiflingAbility:IsFullyCastable() then
if hFarthestEnemy ~= nil then
return CastStiflingDagger( hFarthestEnemy )
else
return CastStiflingDagger( hEnemies[ RandomInt( 1, #hEnemies ) ] )
end
end
end
-- If we've had aggro for a bit, we're willing to launch Blink Strike
local fDelayBeforeBlinkStrike = RandomFloat( 3, 5 )
if thisEntity.timeOfLastAggro and ( GameRules:GetGameTime() > ( thisEntity.timeOfLastAggro + fDelayBeforeBlinkStrike ) ) then
if thisEntity.hBlinkStrikeAbility ~= nil and thisEntity.hBlinkStrikeAbility:IsFullyCastable() then
-- Prefer to blinkstrike onto a unit we're not right next to
if #hMediumDistEnemies > 0 then
return CastBlinkStrike( hMediumDistEnemies[ RandomInt( 1, #hMediumDistEnemies ) ] )
else
return CastBlinkStrike( hEnemies[ RandomInt( 1, #hEnemies ) ] )
end
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastStiflingDagger( hEnemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hEnemy:GetOrigin(),
AbilityIndex = thisEntity.hStiflingAbility:entindex(),
Queue = false,
})
return 2
end
--------------------------------------------------------------------------------
function CastBlinkStrike( hEnemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hEnemy:entindex(),
AbilityIndex = thisEntity.hBlinkStrikeAbility:entindex(),
Queue = false,
})
return 5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,376 @@
require( "utility_functions" )
bBasicSpellDebug = false
--Casting heuristics
CAST_RANGE_BUFFER = 100
MOVE_TO_CAST_NO_TARGETS = false
NO_TARGET_AOE_ENEMIES_BASIC = 1
NO_TARGET_AOE_ENEMIES_ULTIMATE = 2
UNIT_TARGET_AOE_ENEMIES_BASIC = 1
UNIT_TARGET_AOE_ENEMIES_ULTIMATE = 2
POINT_TARGET_AOE_ENEMIES_BASIC = 1
POINT_TARGET_AOE_ENEMIES_ULTIMATE = 2
UNIT_TARGET_METHODS =
{
"closest",
"farthest",
"random",
}
UNIT_TARGET_METHOD = UNIT_TARGET_METHODS[3]
function IsNoTargetSpellCastValid( hSpell )
if bBasicSpellDebug then
print( "IsNoTargetSpellCastValid: " .. hSpell:GetAbilityName() )
end
-- Assume that this spell is centered on me.
local nEnemiesRequired = NO_TARGET_AOE_ENEMIES_BASIC
if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
nEnemiesRequired = NO_TARGET_AOE_ENEMIES_ULTIMATE
end
local nAbilityRadius = hSpell:GetAOERadius()
if nAbilityRadius == 0 then
-- Just slam it for now so the spell goes off. If spells don't have these in basic dota that is generally a bug
if bBasicSpellDebug then
print( "--WARNING - ability " .. hSpell:GetAbilityName() .. " has no defined AOE Radius! " )
end
nAbilityRadius = 250
end
local enemies = FindUnitsInRadius( hSpell:GetCaster():GetTeamNumber(), hSpell:GetCaster():GetOrigin(), hSpell:GetCaster(), nAbilityRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
if #enemies < nEnemiesRequired then
return false
end
if bBasicSpellDebug then
print( "--Found " .. #enemies .. " : " .. hSpell:GetAbilityName() )
print( "" )
end
return true
end
function GetBestAOEUnitTarget( hSpell )
--For now, just use normal unit targeting
return GetBestUnitTarget( hSpell )
end
function GetBestDirectionalPointTarget( hSpell )
if bBasicSpellDebug then
print( "GetBestDirectionalPointTarget: " .. hSpell:GetAbilityName() )
end
local nEnemiesRequired = UNIT_TARGET_AOE_ENEMIES_BASIC
if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
nEnemiesRequired = UNIT_TARGET_AOE_ENEMIES_ULTIMATE
end
local nAbilityRadius = hSpell:GetAOERadius()
if nAbilityRadius == 0 then
-- Just slam it for now so the spell goes off.
-- Linear spells will likely need a specific solution,
-- as their radiuses are not universally defined
nAbilityRadius = 250
if bBasicSpellDebug then
print( "--GetBestDirectionalPointTarget: WARNING - ability " .. hSpell:GetAbilityName() .. " has no defined AOE Radius! " )
end
end
local vLocation = GetTargetLinearLocation( hSpell:GetCaster():GetTeamNumber(),
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
DOTA_UNIT_TARGET_TEAM_ENEMY,
hSpell:GetCaster():GetOrigin(),
GetTryToUseSpellRange( hSpell:GetCaster(), hSpell ),
nAbilityRadius,
nEnemiesRequired )
if vLocation == vec3_invalid then
if bBasicSpellDebug then
print( "--GetBestDirectionalPointTarget: cannot find location with " .. nAbilityRadius .. " radius and " .. nEnemiesRequired .. " required enemies" )
end
return nil
end
if bBasicSpellDebug then
print( "--Found directional target point: (" .. vLocation.x .. ", " .. vLocation.y .. ", " .. vLocation.z .. " : " .. hSpell:GetAbilityName() )
print( "" )
end
return vLocation
end
function GetBestUnitTarget( hSpell )
if bBasicSpellDebug and hSpell then
print( "GetBestUnitTarget: " .. hSpell:GetAbilityName() )
end
local enemies = FindUnitsInRadius( hSpell:GetCaster():GetTeamNumber(), hSpell:GetCaster():GetOrigin(), hSpell:GetCaster(), GetTryToUseSpellRange( hSpell:GetCaster(), hSpell ), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
if #enemies == 0 then
if bBasicSpellDebug then
print( "--Found 0 enemies")
end
return nil
end
if UNIT_TARGET_METHOD == "closest" then
return enemies[1]
end
if UNIT_TARGET_METHOD == "random" then
return enemies[RandomInt(1, #enemies)]
end
if UNIT_TARGET_METHOD == "farthest" then
return enemies[#enemies]
end
return nil
end
function GetBestAOEPointTarget( hSpell )
if bBasicSpellDebug then
print( "GetBestAOEPointTarget: " .. hSpell:GetAbilityName() )
end
local nEnemiesRequired = UNIT_TARGET_AOE_ENEMIES_BASIC
if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
nEnemiesRequired = UNIT_TARGET_AOE_ENEMIES_ULTIMATE
end
local nAbilityRadius = hSpell:GetAOERadius()
if nAbilityRadius == 0 then
-- Just slam it for now so the spell goes off. If spells don't have these in basic dota that is generally a bug
nAbilityRadius = 250
if bBasicSpellDebug then
print( "--GetBestAOEPointTarget: WARNING - ability " .. hSpell:GetAbilityName() .. " has no defined AOE Radius! " )
end
end
local vLocation = GetTargetAOELocation( hSpell:GetCaster():GetTeamNumber(),
DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
DOTA_UNIT_TARGET_TEAM_ENEMY,
hSpell:GetCaster():GetOrigin(),
GetTryToUseSpellRange( hSpell:GetCaster(), hSpell ),
nAbilityRadius,
nEnemiesRequired )
if vLocation == vec3_invalid then
if bBasicSpellDebug then
print( "--GetBestAOEPointTarget: cannot find location with " .. nAbilityRadius .. " radius and " .. nEnemiesRequired .. " required enemies" )
end
return nil
end
if bBasicSpellDebug then
print( "--Found aoe target point: (" .. vLocation.x .. ", " .. vLocation.y .. ", " .. vLocation.z .. " : " .. hSpell:GetAbilityName() )
end
return vLocation
end
function GetBestPointTarget( hSpell )
-- for now just use AOE
return GetBestAOEPointTarget( hSpell )
end
function FindTreeTarget( hSpell )
local Trees = GridNav:GetAllTreesAroundPoint( hSpell:GetCaster():GetOrigin(), GetTryToUseSpellRange( hSpell:GetCaster(), hSpell ), false )
if #Trees == 0 then
return nil
end
return Trees[RandomInt( 1, #Trees )]
end
function CastSpellNoTarget( hSpell )
if bBasicSpellDebug and hSpell then
print ( "CastSpellNoTarget: " .. hSpell:GetAbilityName() )
end
ExecuteOrderFromTable({
UnitIndex = hSpell:GetCaster():entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hSpell:entindex()
})
return GetSpellCastTime( hSpell )
end
function CastSpellUnitTarget( hSpell, hTarget )
if bBasicSpellDebug and hSpell and hTarget then
print ( "CastSpellUnitTarget: " .. hSpell:GetAbilityName() .. ", target: ".. hTarget:GetUnitName() )
end
ExecuteOrderFromTable({
UnitIndex = hSpell:GetCaster():entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = hSpell:entindex()
})
return GetSpellCastTime( hSpell )
end
function CastSpellPointTarget( hSpell, vLocation )
if bBasicSpellDebug and hSpell and vLocation then
print ( "CastSpellPointTarget: " .. hSpell:GetAbilityName() .. ", location: (" .. vLocation.x .. ", " .. vLocation.y .. ", " .. vLocation.z )
end
ExecuteOrderFromTable({
UnitIndex = hSpell:GetCaster():entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vLocation,
AbilityIndex = hSpell:entindex(),
})
return GetSpellCastTime( hSpell )
end
function CastSpellTreeTarget( hSpell, hTree )
if bBasicSpellDebug and hSpell then
print( "CastSpellTreeTarget: " .. hSpell:GetAbilityName() )
end
ExecuteOrderFromTable({
UnitIndex = hSpell:GetCaster():entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET_TREE,
TargetIndex = GetTreeIdForEntityIndex( hTree:entindex() ),
AbilityIndex = hSpell:entindex(),
})
return GetSpellCastTime( hSpell )
end
function CastSpell( hSpell )
if bBasicSpellDebug and hSpell then
print ( "CastSpell: " .. hSpell:GetAbilityName() )
end
if hSpell == nil or hSpell:IsFullyCastable() == false then
if bBasicSpellDebug then
print ( "No valid spell or spell not ready!" )
end
return 0.1
end
local hTarget = nil
local vTargetLoc = nil
local nBehavior = hSpell:GetBehavior()
local nTargetTeam = hSpell:GetAbilityTargetTeam()
local nTargetType = hSpell:GetAbilityTargetType()
if bitand( nTargetTeam, DOTA_UNIT_TARGET_TEAM_FRIENDLY ) ~= 0 and hSpell:GetAbilityName() ~= "pugna_life_drain" and hSpell:GetAbilityName() ~= "frostivus2018_luna_eclipse" then
--Maybe target a minion?
if bBasicSpellDebug then
print( "Try to cast friendly spell on myself" )
end
return CastSpellUnitTarget( hSpell, hSpell:GetCaster() )
else
if bitand( nTargetType, DOTA_UNIT_TARGET_TREE ) ~= 0 then
if bBasicSpellDebug then
print( "try to cast tree targeting spell" )
end
local Tree = FindTreeTarget( hSpell )
if Tree ~= nil then
return CastSpellTreeTarget( hSpell, Tree )
end
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET ) ~= 0 or bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_IMMEDIATE ) ~= 0 and IsNoTargetSpellCastValid( hSpell ) then
if bBasicSpellDebug then
print( "Try to cast no target or immediate spell" )
end
return CastSpellNoTarget( hSpell )
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_DIRECTIONAL ) ~= 0 then
if bBasicSpellDebug then
print( "spell is directional" )
end
local vTargetLoc = GetBestDirectionalPointTarget( hSpell )
if vTargetLoc ~= nil then
if bBasicSpellDebug then
print( "Try to cast directional spell" )
end
return CastSpellPointTarget( hSpell, vTargetLoc )
end
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_POINT ) ~= 0 then
if bBasicSpellDebug then
print( "Stolen spell is point" )
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_AOE ) ~= 0 then
if bBasicSpellDebug then
print( "Stolen spell is point aoe " )
end
local vTargetLoc = GetBestAOEPointTarget( hSpell )
if vTargetLoc ~= nil then
if bBasicSpellDebug then
print( "Try to cast point aoe spell" )
end
return CastSpellPointTarget( hSpell, vTargetLoc )
end
else
if bBasicSpellDebug then
print( "spell is point non-aoe" )
end
local vTargetLoc = GetBestPointTarget( hSpell )
if vTargetLoc ~= nil then
if bBasicSpellDebug then
print( "Try to cast point non-aoe spell" )
end
return CastSpellPointTarget( hSpell, vTargetLoc )
end
end
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) ~= 0 then
if bBasicSpellDebug then
print( "spell is unit targeted" )
end
if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_AOE ) ~= 0 then
if bBasicSpellDebug then
print( "spell is unit targeted aoe " )
end
local hTarget = GetBestAOEUnitTarget( hSpell )
if hTarget ~= nil then
if bBasicSpellDebug then
print( "Try to cast unit aoe spell" )
end
return CastSpellUnitTarget( hSpell, hTarget )
end
else
if bBasicSpellDebug then
print( "spell is unit targeted non aoe" )
end
local hTarget = GetBestUnitTarget( hSpell )
if hTarget ~= nil then
if bBasicSpellDebug then
print( "Try to cast unit spell" )
end
return CastSpellUnitTarget( hSpell, hTarget )
end
end
end
end
if bBasicSpellDebug then
print ( "No valid spell casting for my spell" )
end
return 0.1
end
function GetSpellCastTime( hSpell )
local flCastPoint = math.max( 0.25, hSpell:GetCastPoint() )
if bBasicSpellDebug then
print( "GetSpellCastTime " .. hSpell:GetAbilityName() .. ": " .. flCastPoint + 0.01 )
end
return flCastPoint + 0.01
end
function GetTryToUseSpellRange( hCaster, hSpell )
local flCastRange = hSpell:GetCastRange( vec3_invalid, nil )
local flAcquisitionRange = hCaster:GetAcquisitionRange()
local flTryRange = flCastRange
if flAcquisitionRange > flCastRange then
flTryRange = flAcquisitionRange
end
return flTryRange + CAST_RANGE_BUFFER
end

View File

@@ -0,0 +1,174 @@
--[[ Beastmaster AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorWildAxes, BehaviorPrimalRoar } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorWildAxes = {}
function BehaviorWildAxes:Evaluate()
--print( "BehaviorWildAxes:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.wildAxesAbility = thisEntity:FindAbilityByName( "beastmaster_wild_axes" )
if self.wildAxesAbility and self.wildAxesAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hGushModifier = hUnit:FindModifierByName( "modifier_beastmaster_wild_axes" )
if hGushModifier ~= nil then
--print("Enemy is already axed")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorWildAxes:Begin()
--print( "BehaviorWildAxes:Begin()" )
if self.wildAxesAbility and self.wildAxesAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies == 0 then
return nil
end
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.1 } )
local target = enemies[#enemies]
local targetPoint = target:GetOrigin() + RandomVector( 100 )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = targetPoint,
AbilityIndex = self.wildAxesAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorWildAxes.Continue = BehaviorWildAxes.Begin
--------------------------------------------------------------------------------------------------------
BehaviorPrimalRoar = {}
function BehaviorPrimalRoar:Evaluate()
--print( "BehaviorPrimalRoar:Evaluate()" )
local desire = 0
self.primalRoarAbility = thisEntity:FindAbilityByName( "beastmaster_primal_roar" )
if self.primalRoarAbility and self.primalRoarAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() > 75 ) then
return desire
end
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hStunModifier = hUnit:FindModifierByName( "modifier_stunned" )
if hStunModifier ~= nil then
--print("Enemy is already stunned")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorPrimalRoar:Begin()
--print( "BehaviorPrimalRoar:Begin()" )
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies == 0 then
return nil
end
local target = enemies[#enemies]
if self.primalRoarAbility and self.primalRoarAbility:IsFullyCastable() then
--print( "Casting Primal Roar" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = self.primalRoarAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorPrimalRoar.Continue = BehaviorPrimalRoar.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorWildAxes, BehaviorPrimalRoar }

View File

@@ -0,0 +1,59 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
RockGolemSmashAbility = thisEntity:FindAbilityByName( "aghsfort_rock_golem_smash" )
thisEntity:SetContextThink( "BigGolemThink", BigGolemThink, 0.25 )
end
--------------------------------------------------------------------------------
function BigGolemThink()
if thisEntity == nil or thisEntity:IsNull() or thisEntity:IsAlive() == false then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local fSmashSearchRadius = RockGolemSmashAbility:GetCastRange()
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSmashSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 0.25
end
if RockGolemSmashAbility ~= nil and RockGolemSmashAbility:IsCooldownReady() then
local hTarget = enemies[ RandomInt( 1, #enemies ) ]
return CastRockSmash( hTarget )
end
return 0.25
end
--------------------------------------------------------------------------------
function CastRockSmash( hTarget )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.3 } )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = RockGolemSmashAbility:entindex(),
Position = hTarget:GetAbsOrigin(),
Queue = false,
})
return 2.0
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,147 @@
function Precache( context )
PrecacheUnitByNameSync( "npc_aghsfort_creature_bomb_squad_landmine", context, -1 )
PrecacheResource( "particle", "particles/units/heroes/hero_techies/techies_land_mine_explode.vpcf", context )
PrecacheResource( "particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_ring.vpcf", context )
end
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.AggroAbility = thisEntity:FindAbilityByName( "bomb_squad_self_cast" )
thisEntity.MineCharge = thisEntity:FindAbilityByName( "bomb_squad_mine_charge" )
thisEntity.StasisLaunch = thisEntity:FindAbilityByName( "bomb_squad_stasis_launch" )
thisEntity.flLastOrder = GameRules:GetGameTime()
thisEntity:SetContextThink( "BombSquadThink", BombSquadThink, 1 )
end
function BombSquadThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if (thisEntity:GetAggroTarget() == nil and GameRules:GetGameTime() - thisEntity.flLastOrder) > (4 - RandomFloat(0 ,1 )) then
thisEntity.flLastOrder = GameRules:GetGameTime()
return DoMove()
end
if thisEntity.StasisLaunch and thisEntity.StasisLaunch:IsFullyCastable() then
local StasisLaunchRadius = 650
local hEnemiesNearby = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, StasisLaunchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_FARTHEST, false )
if #hEnemiesNearby > 0 then
local hFarthestEnemy = hEnemiesNearby[ 1 ]
return CastStasisLaunch( hFarthestEnemy )
end
end
if thisEntity.MineCharge and thisEntity.MineCharge:IsFullyCastable() then
return CastMineCharge()
end
-- if AggroAbility ~= true and AggroAbility:IsFullyCastable() then
-- bIsAggro = true
-- return CastAggroAbility()
-- end
return 0.5
end
--function CastAggroAbility()
-- ExecuteOrderFromTable({
-- UnitIndex = thisEntity:entindex(),
-- OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
-- AbilityIndex = AggroAbility:entindex(),
-- Queue = false,
-- })
--
-- return 1
--end
function DoMove()
if IsServer() then
for i=1,4 do
local vLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 100, 400 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vLoc
})
break
end
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastMineCharge()
if IsServer() then
for i=1,12 do
local vLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 1800, 2400 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vLoc,
AbilityIndex = thisEntity.MineCharge:entindex(),
Queue = false,
})
break
end
end
return 3
end
end
--------------------------------------------------------------------------------
function CastStasisLaunch( hTarget )
if hTarget == nil or hTarget:IsNull() or hTarget:IsAlive() == false then
return 0.5
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetOrigin(),
AbilityIndex = thisEntity.StasisLaunch:entindex(),
Queue = false,
})
return 2
end

View File

@@ -0,0 +1,65 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
DetonateAblity = thisEntity:FindAbilityByName( "bomb_squad_landmine_detonate" )
thisEntity:SetContextThink( "LandmineThink", LandmineThink, 1 )
end
--------------------------------------------------------------------------------
function LandmineThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
if DetonateAblity and DetonateAblity:IsFullyCastable() then
local fDetonateRadius = DetonateAblity:GetSpecialValueFor( "detonate_radius" )
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fDetonateRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 0.5
end
if thisEntity:FindModifierByName("modifier_bomb_squad_landmine_detonate") then
--we're already exploding
return -1
end
return Detonate()
end
return 1
end
--------------------------------------------------------------------------------
function Detonate()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = DetonateAblity:entindex(),
Queue = false,
})
return -1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,444 @@
require( "ai/boss_base" )
--------------------------------------------------------------------------------
if CBossAghanim == nil then
CBossAghanim = class( {}, {}, CBossBase )
end
--------------------------------------------------------------------------------
function Precache( context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CBossAghanim( thisEntity, 1.0 )
end
end
--------------------------------------------------------------------------------
function CBossAghanim:constructor( hUnit, flInterval )
CBossBase.constructor( self, hUnit, flInterval )
self.bDefeated = false
self.ATTACKS_BETWEEN_TELEPORT = 3
self.ENRAGE_LESS_ATTACKS_BETWEEN_TELEPORT = 1
self.nCurrentAttacksBetweenTeleport = self.ATTACKS_BETWEEN_TELEPORT
self.PHASE_CRYSTAL_ATTACK = 1
self.PHASE_STAFF_BEAMS = 2
self.PHASE_SUMMON_PORTALS = 3
self.PHASE_SPELL_SWAP = 4
self.PHASE_SHARD_ATTACK = 5
self.nSpellSwapPct = 80
self.bSpellSwapEnabled = false
self.nShardAttackPct = 50
self.bShardAttackEnabled = false
self.nLevelUpPct = 66
self.bHasLeveledUp = false
self.AllowedPhases =
{
self.PHASE_CRYSTAL_ATTACK,
self.PHASE_SUMMON_PORTALS,
self.PHASE_STAFF_BEAMS,
}
self.flInitialAcquireRange = 5000
self.flAggroAcquireRange = 5000
self.nPhaseIndex = 1
self.nNumAttacksBeforeTeleport = self.nCurrentAttacksBetweenTeleport
self.bReturnHome = true
self.vLastBlinkLocation = Vector( -3328, 3264, 0 )
self.me:SetThink( "OnBossAghanimThink", self, "OnBossAghanimThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CBossAghanim:GetCurrentPhase()
return self.AllowedPhases[ self.nPhaseIndex ]
end
--------------------------------------------------------------------------------
function CBossAghanim:SetEncounter( hEncounter )
CBossBase.SetEncounter( self, hEncounter )
self.TeleportPositions = {}
local TeleportPositions = hEncounter:GetRoom():FindAllEntitiesInRoomByName( "teleport_position" )
for _,hEnt in pairs ( TeleportPositions ) do
table.insert( self.TeleportPositions, hEnt:GetAbsOrigin() )
end
local TeleportPositionMain = hEncounter:GetRoom():FindAllEntitiesInRoomByName( "teleport_position_main" )
if #TeleportPositionMain > 0 then
self.TeleportPositionMain = TeleportPositionMain[1]
self.me.vHomePosition = self.TeleportPositionMain:GetAbsOrigin()
table.insert( self.TeleportPositions, self.me.vHomePosition )
end
end
--------------------------------------------------------------------------------
function CBossAghanim:SetupAbilitiesAndItems()
CBossBase.SetupAbilitiesAndItems( self )
self.hBlink = self.me:FindAbilityByName( "aghanim_blink" )
if self.hBlink ~= nil then
self.hBlink.Evaluate = self.EvaluateBlink
self.AbilityPriority[ self.hBlink:GetAbilityName() ] = 5
end
self.hCrystalAttack = self.me:FindAbilityByName( "aghanim_crystal_attack" )
if self.hCrystalAttack ~= nil then
self.hCrystalAttack.nCrystalAttackPhase = 1
self.hCrystalAttack.hLastCrystalTarget = nil
self.hCrystalAttack.Evaluate = self.EvaluateCrystalAttack
self.AbilityPriority[ self.hCrystalAttack:GetAbilityName() ] = 4
end
self.hStaffBeams = self.me:FindAbilityByName( "aghanim_staff_beams" )
if self.hStaffBeams ~= nil then
self.hStaffBeams.Evaluate = self.EvaluateStaffBeams
self.AbilityPriority[ self.hStaffBeams:GetAbilityName() ] = 3
end
self.hSummonPortals = self.me:FindAbilityByName( "aghanim_summon_portals" )
if self.hSummonPortals ~= nil then
self.hSummonPortals.Evaluate = self.EvaluateSummonPortals
self.AbilityPriority[ self.hSummonPortals:GetAbilityName() ] = 2
end
self.hSpellSwap = self.me:FindAbilityByName( "aghanim_spell_swap" )
if self.hSpellSwap ~= nil then
self.hSpellSwap.Evaluate = self.EvaluateSpellSwap
self.AbilityPriority[ self.hSpellSwap:GetAbilityName() ] = 1
end
self.hShardAttack = self.me:FindAbilityByName( "aghanim_shard_attack" )
if self.hShardAttack ~= nil then
self.hShardAttack.Evaluate = self.EvaluateShardAttack
self.AbilityPriority[ self.hShardAttack:GetAbilityName() ] = 1
end
end
--------------------------------------------------------------------------------
function CBossAghanim:OnBossAghanimThink()
if self.bDefeated then
return -1
end
return self:OnBaseThink()
end
--------------------------------------------------------------------------------
function CBossAghanim:OnFirstSeen()
CBossBase.OnFirstSeen( self )
end
--------------------------------------------------------------------------------
function CBossAghanim:ChangePhase()
self.nNumAttacksBeforeTeleport = self.nNumAttacksBeforeTeleport - 1
--print ( "Aghanim is changing phase! old:" .. self:GetCurrentPhase() )
if self.nPhaseIndex == #self.AllowedPhases then
self.nPhaseIndex = 1
else
self.nPhaseIndex = self.nPhaseIndex + 1
end
self.nPhase = self.AllowedPhases[ self.nPhaseIndex ]
if self.nPhase == self.PHASE_SHARD_ATTACK then
self.nNumAttacksBeforeTeleport = 0
self.bReturnHome = true
end
end
--------------------------------------------------------------------------------
function CBossAghanim:OnHealthPercentThreshold( nPct )
CBossBase.OnHealthPercentThreshold( self, nPct )
if nPct < self.nSpellSwapPct and self.bSpellSwapEnabled == false then
self.nCurrentAttacksBetweenTeleport = math.max( 1, self.nCurrentAttacksBetweenTeleport - self.ENRAGE_LESS_ATTACKS_BETWEEN_TELEPORT )
self.bSpellSwapEnabled = true
self.AllowedPhases =
{
self.PHASE_SPELL_SWAP,
self.PHASE_SUMMON_PORTALS,
self.PHASE_STAFF_BEAMS,
self.PHASE_CRYSTAL_ATTACK,
}
self.nPhaseIndex = #self.AllowedPhases
self:ChangePhase()
self.nNumAttacksBeforeTeleport = 0
self.bReturnHome = true
end
if nPct < self.nLevelUpPct and self.bHasLeveledUp == false then
self.bHasLeveledUp = true
self.me:CreatureLevelUp( 1 )
end
if nPct < self.nShardAttackPct and self.bShardAttackEnabled == false then
self.nCurrentAttacksBetweenTeleport = math.max( 1, self.nCurrentAttacksBetweenTeleport - self.ENRAGE_LESS_ATTACKS_BETWEEN_TELEPORT )
self.bShardAttackEnabled = true
self.AllowedPhases =
{
self.PHASE_SHARD_ATTACK,
self.PHASE_STAFF_BEAMS,
self.PHASE_CRYSTAL_ATTACK,
self.PHASE_SPELL_SWAP,
self.PHASE_SUMMON_PORTALS,
}
self.nPhaseIndex = #self.AllowedPhases
self:ChangePhase()
self.nNumAttacksBeforeTeleport = 0
self.bReturnHome = true
end
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateCrystalAttack()
if self:GetCurrentPhase() ~= self.PHASE_CRYSTAL_ATTACK then
return nil
end
local Enemies = shallowcopy( self.hPlayerHeroes )
local Order = nil
if Enemies == nil or #Enemies == 0 then
return Order
end
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = Enemies[ #Enemies ]:GetAbsOrigin(),
AbilityIndex = self.hCrystalAttack:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hCrystalAttack )
self.hCrystalAttack.hLastCrystalTarget = Enemies[#Enemies]
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateStaffBeams()
if self:GetCurrentPhase() ~= self.PHASE_STAFF_BEAMS then
return nil
end
local vTargetPos = self.me.vHomePosition
if vTargetPos == self.vLastBlinkLocation then
local hEnemies = GetEnemyHeroesInRange( self.me, 5000 )
if #hEnemies > 0 then
vTargetPos = hEnemies[#hEnemies]:GetAbsOrigin()
end
end
local Order = nil
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetPos,
AbilityIndex = self.hStaffBeams:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hStaffBeams ) + self.hStaffBeams:GetChannelTime()
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateBlink()
if self.nNumAttacksBeforeTeleport > 0 then
return nil
end
if self.bReturnHome == true and self.vLastBlinkLocation == self.me.vHomePosition then
self.nNumAttacksBeforeTeleport = self.nCurrentAttacksBetweenTeleport
self.bReturnHome = false
return nil
end
local vTeleportLocations = shallowcopy( self.TeleportPositions )
for k,v in pairs ( vTeleportLocations ) do
if v == self.vLastBlinkLocation then
table.remove( vTeleportLocations, k )
break
end
end
local vPos = nil
if self.bReturnHome == true then
vPos = self.me.vHomePosition
else
if self:GetCurrentPhase() == self.PHASE_SUMMON_PORTALS then
local hEnemies = GetEnemyHeroesInRange( self.me, 5000 )
if #hEnemies > 0 then
local vFarthestEnemyPos = hEnemies[#hEnemies]:GetAbsOrigin()
local vClosestPosToEnemy = nil
local flShortestDist = 99999
for _,vTeleportPos in pairs ( vTeleportLocations ) do
local flDistToEnemy = ( vTeleportPos - vFarthestEnemyPos ):Length2D()
if flDistToEnemy < flShortestDist then
flShortestDist = flDistToEnemy
vClosestPosToEnemy = vTeleportPos
end
end
if vTeleportPos ~= nil then
vPos = vClosestPosToEnemy
end
end
end
if vPos == nil then
vPos = vTeleportLocations[ RandomInt( 1, #vTeleportLocations ) ]
end
end
if vPos == nil then
return nil
end
self.vLastBlinkLocation = vPos
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vPos,
AbilityIndex = self.hBlink:entindex(),
Queue = false,
}
Order.flOrderInterval = 3.0
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateSummonPortals()
if self:GetCurrentPhase() ~= self.PHASE_SUMMON_PORTALS then
return nil
end
local vTarget = self.me.vHomePosition
local hEnemies = GetEnemyHeroesInRange( self.me, 5000 )
if #hEnemies > 0 and self.vLastBlinkLocation == self.me.vHomePosition then
vTarget = hEnemies[ 1 ]:GetAbsOrigin()
end
local Order = nil
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTarget,
AbilityIndex = self.hSummonPortals:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hSummonPortals ) + self.hSummonPortals:GetChannelTime()
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateSpellSwap()
if self:GetCurrentPhase() ~= self.PHASE_SPELL_SWAP then
return nil
end
local vTargetPos = self.me.vHomePosition
if vTargetPos == self.vLastBlinkLocation then
local hEnemies = GetEnemyHeroesInRange( self.me, 5000 )
if #hEnemies > 0 then
vTargetPos = hEnemies[#hEnemies]:GetAbsOrigin()
end
end
local Order = nil
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetPos,
AbilityIndex = self.hSpellSwap:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hSpellSwap ) + self.hSpellSwap:GetChannelTime()
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:EvaluateShardAttack()
if self:GetCurrentPhase() ~= self.PHASE_SHARD_ATTACK then
return nil
end
local Order = nil
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = self.me.vHomePosition + Vector( 0, -150, 0 ),
AbilityIndex = self.hShardAttack:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hShardAttack ) + self.hShardAttack:GetChannelTime()
return Order
end
--------------------------------------------------------------------------------
function CBossAghanim:OnBossUsedAbility( szAbilityName )
if szAbilityName == "aghanim_blink" then
self.nNumAttacksBeforeTeleport = self.nCurrentAttacksBetweenTeleport
self.bReturnHome = false
return
end
if szAbilityName == "aghanim_crystal_attack" then
if self.hCrystalAttack.nPhase == 6 then
self:ChangePhase()
end
return
end
self:ChangePhase()
end

View File

@@ -0,0 +1,206 @@
require( "ai/shared" )
require( "ai/basic_spell_casting_ai" )
--------------------------------------------------------------------------------
if CBossBase == nil then
CBossBase = class({})
end
--------------------------------------------------------------------------------
function CBossBase:constructor( hUnit, flInterval )
self.me = hUnit
self.flDefaultInterval = flInterval
self.AbilityPriority = {}
self.hPlayerHeroes = {}
self.QueuedOrders = {}
self.Encounter = nil
self.bSeenAnyEnemy = false
self.nLastHealthPct = 10000
self.flInitialAcquireRange = 1800
self.flAggroAcquireRange = 4500
self:SetupAbilitiesAndItems()
self.nAbilityListener = ListenToGameEvent( "dota_non_player_used_ability", Dynamic_Wrap( getclass( self ), 'OnNonPlayerUsedAbility' ), self )
end
--------------------------------------------------------------------------------
function CBossBase:SetupAbilitiesAndItems()
--empty
end
--------------------------------------------------------------------------------
function CBossBase:SetEncounter( Encounter )
self.Encounter = Encounter
end
--------------------------------------------------------------------------------
function CBossBase:OnBaseThink()
if self.me == nil or self.me:IsNull() or self.me:IsAlive() == false then
StopListeningToGameEvent( self.nAbilityListener )
return -1
end
Order = nil
if self.Encounter == nil or self.Encounter:HasStarted() == false then
return 0.01
end
if GameRules:IsGamePaused() then
return 0.01
end
local flRange = self.flInitialAcquireRange
if self.bSeenAnyEnemy == true then
flRange = self.flAggroAcquireRange
end
self.hPlayerHeroes = GetVisibleEnemyHeroesInRange( self.me, flRange )
if #self.hPlayerHeroes == 0 then
goto autoattack
elseif self.bSeenAnyEnemy == false then
self.bSeenAnyEnemy = true
self:OnFirstSeen()
end
if self.nLastHealthPct > self.me:GetHealthPercent() then
self.nLastHealthPct = self.me:GetHealthPercent()
self:OnHealthPercentThreshold( self.nLastHealthPct )
end
AbilitiesReady = self:GetReadyAbilitiesAndItems()
if #AbilitiesReady == 0 then
goto autoattack
end
if #self.QueuedOrders > 0 then
Order = self.QueuedOrders[ 1 ]
table.remove( self.QueuedOrders, 1 )
goto execute_order
else
for _,Ability in pairs ( AbilitiesReady ) do
if Ability.Evaluate ~= nil then
local TryOrder = Ability.Evaluate( self )
if TryOrder ~= nil then
Order = TryOrder
break
end
end
end
end
::autoattack::
NonAbilityOrder = nil
if Order == nil then
NonAbilityOrder = self:GetNonAbilityOrder()
if NonAbilityOrder ~= nil then
--print( 'NON ABILITY ORDER' )
Order = NonAbilityOrder
end
end
if Order == nil and #self.hPlayerHeroes > 0 then
--print( 'autoattack for ' .. self.flDefaultInterval )
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = self.hPlayerHeroes[ 1 ]:GetAbsOrigin(),
}
Order.flOrderInterval = self.flDefaultInterval
end
::execute_order::
if Order then
--print( 'Executing Order ' .. Order.OrderType .. ' and sleeping for ' .. Order.flOrderInterval )
ExecuteOrderFromTable( Order )
return Order.flOrderInterval
end
::idle::
return self.flDefaultInterval
end
--------------------------------------------------------------------------------
function CBossBase:GetReadyAbilitiesAndItems()
local AbilitiesReady = {}
for n=0,self.me:GetAbilityCount() - 1 do
local hAbility = self.me:GetAbilityByIndex( n )
if hAbility and hAbility:IsFullyCastable() and not hAbility:IsPassive() and not hAbility:IsHidden() and hAbility:IsActivated() then
--print( 'Adding ABILITY ' .. hAbility:GetAbilityName() )
if self.AbilityPriority[ hAbility:GetAbilityName() ] ~= nil then
table.insert( AbilitiesReady, hAbility )
end
end
end
for i = 0, DOTA_ITEM_MAX - 1 do
local hItem = self.me:GetItemInSlot( i )
if hItem and hItem:IsFullyCastable() and not hItem:IsPassive() and not hItem:IsHidden() and hItem:IsActivated() then
--print( 'Adding ITEM ' .. hItem:GetAbilityName() )
if self.AbilityPriority[ hItem:GetAbilityName() ] ~= nil then
table.insert( AbilitiesReady, hItem )
end
end
end
if #AbilitiesReady > 1 then
table.sort( AbilitiesReady, function( h1, h2 )
local nAbility1Priority = self.AbilityPriority[ h1:GetAbilityName() ]
local nAbility2Priority = self.AbilityPriority[ h2:GetAbilityName() ]
return nAbility1Priority > nAbility2Priority
end
)
end
return AbilitiesReady
end
--------------------------------------------------------------------------------
function CBossBase:GetNonAbilityOrder()
return nil
end
--------------------------------------------------------------------------------
function CBossBase:OnFirstSeen()
-- empty
end
--------------------------------------------------------------------------------
function CBossBase:OnHealthPercentThreshold( nPct )
-- empty
end
---------------------------------------------------------
-- dota_non_player_used_ability
-- * abilityname
-- * caster_entindex
---------------------------------------------------------
function CBossBase:OnNonPlayerUsedAbility( event )
local hCaster = nil
if event.caster_entindex ~= nil and event.abilityname ~= nil then
hCaster = EntIndexToHScript( event.caster_entindex )
if hCaster ~= nil and hCaster == self.me then
self:OnBossUsedAbility( event.abilityname )
end
end
end
--------------------------------------------------------------------------------
function CBossBase:OnBossUsedAbility( szAbilityName )
-- empty
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,272 @@
require( "ai/boss_base" )
--------------------------------------------------------------------------------
if CBossTimbersaw == nil then
CBossTimbersaw = class( {}, {}, CBossBase )
end
--------------------------------------------------------------------------------
function Precache( context )
PrecacheUnitByNameSync( "npc_dota_furion_treant_4", context, -1 )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CBossTimbersaw( thisEntity, 1.0 )
end
end
--------------------------------------------------------------------------------
function CBossTimbersaw:constructor( hUnit, flInterval )
CBossBase.constructor( self, hUnit, flInterval )
self.nTreeListener = ListenToGameEvent( "tree_cut", Dynamic_Wrap( getclass( self ), 'OnTreeCut' ), self )
self.bEnraged = false
self.nEnragePct = 60
self.nNumTreantsPerTree = 1
self.nMaxTreants = 40
self.me:SetThink( "OnBossTimbersawThink", self, "OnBossTimbersawThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CBossTimbersaw:SetupAbilitiesAndItems()
CBossBase.SetupAbilitiesAndItems( self )
self.hWhirlingDeath = self.me:FindAbilityByName( "boss_timbersaw_whirling_death" )
if self.hWhirlingDeath ~= nil then
self.hWhirlingDeath.Evaluate = self.EvaluateWhirlingDeath
self.AbilityPriority[ self.hWhirlingDeath:GetAbilityName() ] = 4
end
self.hTimberChain = self.me:FindAbilityByName( "boss_timbersaw_timber_chain" )
if self.hTimberChain ~= nil then
self.hTimberChain.Evaluate = self.EvaluateTimberChain
self.AbilityPriority[ self.hTimberChain:GetAbilityName() ] = 5
end
self.hChakram = self.me:FindAbilityByName( "shredder_chakram" )
if self.hChakram ~= nil then
self.hChakram.Evaluate = self.EvaluateChakram
self.AbilityPriority[ self.hChakram:GetAbilityName() ] = 2
end
self.hReturnChakram = self.me:FindAbilityByName( "shredder_return_chakram" )
if self.hReturnChakram ~= nil then
self.hReturnChakram.Evaluate = self.EvaluateReturnChakram
self.AbilityPriority[ self.hReturnChakram:GetAbilityName() ] = 6
end
self.hChakram2 = self.me:FindAbilityByName( "shredder_chakram_2" )
if self.hChakram2 ~= nil then
self.hChakram2:SetActivated( true )
self.hChakram2:SetHidden( false )
self.hChakram2:UpgradeAbility( true )
self.hChakram2.Evaluate = self.EvaluateChakram2
self.AbilityPriority[ self.hChakram2:GetAbilityName() ] = 3
end
self.hReturnChakram2 = self.me:FindAbilityByName( "shredder_return_chakram_2" )
if self.hReturnChakram2 ~= nil then
self.hReturnChakram2.Evaluate = self.EvaluateReturnChakram2
self.AbilityPriority[ self.hReturnChakram2:GetAbilityName() ] = 7
end
self.hChakramDance = self.me:FindAbilityByName( "boss_timbersaw_chakram_dance" )
if self.hChakramDance then
self.hChakramDance.Evaluate = self.EvaluateChakramDance
self.AbilityPriority[ self.hChakramDance:GetAbilityName() ] = 1
end
end
--------------------------------------------------------------------------------
function CBossTimbersaw:OnTreeCut( event )
if self.me:IsNull() or not self.me:IsAlive() then
StopListeningToGameEvent( self.nTreeListener )
return
end
if self.Encounter and self.Encounter.SpawnedSecondaryEnemies and #self.Encounter.SpawnedSecondaryEnemies > self.nMaxTreants then
--print( "Timbersaw is not creating more treants; hit the maximum alive!" )
return
end
local vLocation = Vector( event.tree_x, event.tree_y, 0 )
for i=1,self.nNumTreantsPerTree do
local hTreant = CreateUnitByName( "npc_dota_creature_timbersaw_treant", vLocation, true, self.me, self.me:GetOwner(), self.me:GetTeamNumber() )
if hTreant ~= nil then
hTreant:SetControllableByPlayer( self.me:GetPlayerOwnerID(), false )
hTreant:SetOwner( self.me )
hTreant.bBossMinion = true
self.Encounter:SuppressRewardsOnDeath( hTreant )
if #self.hPlayerHeroes > 0 then
hTreant:SetInitialGoalEntity( self.hPlayerHeroes[ RandomInt( 1, #self.hPlayerHeroes ) ] )
end
end
end
end
--------------------------------------------------------------------------------
function CBossTimbersaw:OnBossTimbersawThink()
return self:OnBaseThink()
end
--------------------------------------------------------------------------------
function CBossTimbersaw:OnFirstSeen()
CBossBase.OnFirstSeen( self )
end
--------------------------------------------------------------------------------
function CBossTimbersaw:OnHealthPercentThreshold( nPct )
CBossBase.OnHealthPercentThreshold( self, nPct )
if nPct <= self.nEnragePct and self.bEnraged == false then
self.bEnraged = true
end
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateWhirlingDeath()
local Enemies = shallowcopy( self.hPlayerHeroes )
Enemies = FilterEntitiesOutsideOfRange( self.me:GetAbsOrigin(), Enemies, self.hWhirlingDeath:GetSpecialValueFor( "whirling_radius" ) )
local Order = nil
if #Enemies >= 1 then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hWhirlingDeath:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hWhirlingDeath )
end
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateTimberChain()
local Order = nil
local vTargetLocation = GetBestDirectionalPointTarget( self.hTimberChain )
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hTimberChain:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hTimberChain ) + 0.5 -- Factor in a little travel time
end
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateChakram()
local Order = nil
local vTargetLocation = GetBestAOEPointTarget( self.hChakram )
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hChakram:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hChakram )
self.hReturnChakram:StartCooldown( RandomFloat( 1.5, 4.5 ) )
end
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateChakram2()
local Order = nil
local vTargetLocation = GetBestAOEPointTarget( self.hChakram2 )
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hChakram2:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hChakram2 )
self.hReturnChakram2:StartCooldown( RandomFloat( 1.5, 4.5 ) )
end
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateReturnChakram()
local Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hReturnChakram:entindex(),
Queue = false,
}
Order.flOrderInterval = 0.1
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateReturnChakram2()
local Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hReturnChakram2:entindex(),
Queue = false,
}
Order.flOrderInterval = 0.1
return Order
end
--------------------------------------------------------------------------------
function CBossTimbersaw:EvaluateChakramDance()
local Order = nil
if self.bEnraged == true then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hChakramDance:entindex(),
Queue = false,
}
Order.flOrderInterval = self.hChakramDance:GetChannelTime() + 0.2
end
return Order
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,125 @@
require( "ai/boss_base" )
--------------------------------------------------------------------------------
if CBossVisage == nil then
CBossVisage = class( {}, {}, CBossBase )
end
--------------------------------------------------------------------------------
function Precache( context )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CBossVisage( thisEntity, 1.0 )
end
end
--------------------------------------------------------------------------------
function CBossVisage:constructor( hUnit, flInterval )
CBossBase.constructor( self, hUnit, flInterval )
self.bEnraged = false
self.nEnragePct = 90
self.me.bStone = false
self.me:SetThink( "OnBossVisageThink", self, "OnBossVisageThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CBossVisage:SetupAbilitiesAndItems()
CBossBase.SetupAbilitiesAndItems( self )
self.hRangedAttack = self.me:FindAbilityByName( "boss_visage_ranged_attack" )
if self.hRangedAttack ~= nil then
self.hRangedAttack.Evaluate = self.EvaluateRangedAttack
self.AbilityPriority[ self.hRangedAttack:GetAbilityName() ] = 2
end
self.hGraveChill = self.me:FindAbilityByName( "boss_visage_grave_chill" )
if self.hGraveChill ~= nil then
self.hGraveChill.Evaluate = self.EvaluateGraveChill
self.AbilityPriority[ self.hGraveChill:GetAbilityName() ] = 1
end
end
--------------------------------------------------------------------------------
function CBossVisage:OnBossVisageThink()
if self.me.bStone then
return self.flDefaultInterval
end
return self:OnBaseThink()
end
--------------------------------------------------------------------------------
function CBossVisage:OnFirstSeen()
CBossBase.OnFirstSeen( self )
end
--------------------------------------------------------------------------------
function CBossVisage:OnHealthPercentThreshold( nPct )
CBossBase.OnHealthPercentThreshold( self, nPct )
if nPct <= self.nEnragePct and self.bEnraged == false then
self.bEnraged = true
end
end
--------------------------------------------------------------------------------
function CBossVisage:EvaluateRangedAttack()
local Order = nil
local hTarget = GetBestUnitTarget( self.hRangedAttack )
if hTarget == nil then
return Order
end
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetAbsOrigin(),
AbilityIndex = self.hRangedAttack:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hRangedAttack )
return Order
end
--------------------------------------------------------------------------------
function CBossVisage:EvaluateGraveChill()
local Order = nil
local hTarget = GetBestUnitTarget( self.hGraveChill )
if hTarget == nil then
return Order
end
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hGraveChill:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hGraveChill )
return Order
end

View File

@@ -0,0 +1,319 @@
require( "ai/boss_base" )
--------------------------------------------------------------------------------
if CBossVoidSpirit == nil then
CBossVoidSpirit = class( {}, {}, CBossBase )
end
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
thisEntity.AI = CBossVoidSpirit( thisEntity, 1.0 )
end
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:constructor( hUnit, flInterval )
CBossBase.constructor( self, hUnit, flInterval )
self.fDissimilateStartTime = nil
self.nEnragePct = 50
self.bEnraged = false
self.nAstralPct = 90
self.bAstralEnabled = false
self.me:SetThink( "OnBossVoidSpiritThink", self, "OnBossVoidSpiritThink", self.flDefaultInterval )
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:SetupAbilitiesAndItems()
CBossBase.SetupAbilitiesAndItems( self )
self.hAetherRemnant = self.me:FindAbilityByName( "aghsfort_void_spirit_boss_aether_remnant" )
if self.hAetherRemnant ~= nil then
self.hAetherRemnant.Evaluate = self.EvaluateAetherRemnant
self.AbilityPriority[ self.hAetherRemnant:GetAbilityName() ] = 5
end
self.hDissimilate = self.me:FindAbilityByName( "aghsfort_void_spirit_boss_dissimilate" )
if self.hDissimilate ~= nil then
self.fDissimilatePhaseDuration = self.hDissimilate:GetSpecialValueFor( "phase_duration" )
self.fPctOfPhaseForSelection = self.hDissimilate:GetSpecialValueFor( "pct_of_phase_for_selection" )
local first_ring_distance_offset = self.hDissimilate:GetSpecialValueFor( "first_ring_distance_offset" )
local damage_radius = self.hDissimilate:GetSpecialValueFor( "damage_radius" )
self.fDissimilateFullRadius = first_ring_distance_offset + damage_radius
self.hDissimilate.Evaluate = self.EvaluateDissimilate
self.AbilityPriority[ self.hDissimilate:GetAbilityName() ] = 3
end
self.hResonantPulse = self.me:FindAbilityByName( "aghsfort_void_spirit_boss_resonant_pulse" )
if self.hResonantPulse ~= nil then
self.hResonantPulse.Evaluate = self.EvaluateResonantPulse
self.AbilityPriority[ self.hResonantPulse:GetAbilityName() ] = 4
end
self.hAstralStep = self.me:FindAbilityByName( "aghsfort_void_spirit_boss_astral_step" )
if self.hAstralStep ~= nil then
self.hAstralStep.Evaluate = self.EvaluateAstralStep
self.AbilityPriority[ self.hAstralStep:GetAbilityName() ] = 2
end
self.hActivateEarthSpirits = self.me:FindAbilityByName( "void_spirit_boss_activate_earth_spirits" )
if self.hActivateEarthSpirits then
self.hActivateEarthSpirits.Evaluate = self.EvaluateActivateEarthSpirits
self.AbilityPriority[ self.hActivateEarthSpirits:GetAbilityName() ] = 1
end
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:OnBossVoidSpiritThink()
if GameRules:IsGamePaused() then
return 0.1
end
-- Am I in Dissimilate?
if self.fDissimilateStartTime and ( GameRules:GetGameTime() < ( self.fDissimilateStartTime + self.fDissimilatePhaseDuration ) ) then
--printf( "I'm in Dissimilate phase, game time: %.2f; dissimilate start time: %.2f; dissimilate phase duration: %.2f", GameRules:GetGameTime(), self.fDissimilateStartTime, self.fDissimilatePhaseDuration )
-- Issue a move command that takes me near a random player within the full Dissimilate radius
local nSearchRangeReduction = self.hDissimilate:GetSpecialValueFor( "search_range_reduction" )
local fSearchRange = self.fDissimilateFullRadius - nSearchRangeReduction
--printf( "fSearchRange: %d, self.fDissimilateFullRadius: %d, nSearchRangeReduction: %d", fSearchRange, self.fDissimilateFullRadius, nSearchRangeReduction )
local enemiesInDissimilate = FindUnitsInRadius( self.me:GetTeamNumber(), self.me:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemiesInDissimilate > 0 then
--printf( "#enemiesInDissimilate: %d; self.fDissimilateFullRadius: %.2f", #enemiesInDissimilate, self.fDissimilateFullRadius )
local hRandomEnemy = enemiesInDissimilate[ RandomInt( 1, #enemiesInDissimilate ) ]
local Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = hRandomEnemy:GetOrigin(),
Queue = true,
}
ExecuteOrderFromTable( Order )
--printf( "-----" )
--printf( "Current time: %.2f, dissimilate phase ends at: %.2f", GameRules:GetGameTime(), ( self.fDissimilateStartTime + self.fDissimilatePhaseDuration ) )
--printf( "Found a random enemy in Dissimilate (%s); moving to: %s", hRandomEnemy:GetUnitName(), hRandomEnemy:GetOrigin() )
--printf( "%s was issued move order", self.me:GetUnitName() )
local fInterval = self.fDissimilatePhaseDuration + 0.2
--printf( "returning in %.2f seconds", fInterval )
return fInterval
else
-- Nobody's within Dissimilate's full radius, so just try to get to the closest player
local fSearchRadius = 4000
local enemies = FindUnitsInRadius( self.me:GetTeamNumber(), self.me:GetOrigin(), nil, fSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies > 0 then
local hNearestEnemy = enemies[ 1 ]
local vDir = hNearestEnemy:GetAbsOrigin() - self.me:GetAbsOrigin()
vDir.z = 0.0
vDir = vDir:Normalized()
local vRightClickPos = self.me:GetAbsOrigin() + ( vDir * ( self.me:GetDayTimeVisionRange() - 20 ) )
local Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vRightClickPos,
Queue = true,
}
ExecuteOrderFromTable( Order )
--printf( "-----" )
--printf( "Current time: %.2f, dissimilate phase ends at: %.2f", GameRules:GetGameTime(), ( self.fDissimilateStartTime + self.fDissimilatePhaseDuration ) )
--printf( "There are no enemies in Dissimilate; moving to nearest enemy \"%s\" at: %s", hNearestEnemy:GetUnitName(), hNearestEnemy:GetOrigin() )
--printf( "%s was issued move order", self.me:GetUnitName() )
local fInterval = self.fDissimilatePhaseDuration + 0.2
--printf( "returning in %.2f seconds", fInterval )
return fInterval
end
end
end
--printf( "OnBossVoidSpiritThink - Chain to OnBaseThink at time: %.2f", GameRules:GetGameTime() )
return self:OnBaseThink()
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:OnFirstSeen()
CBossBase.OnFirstSeen( self )
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:OnHealthPercentThreshold( nPct )
CBossBase.OnHealthPercentThreshold( self, nPct )
if IsServer() then
if nPct <= self.nEnragePct and self.bEnraged == false then
self.bEnraged = true
end
if nPct <= self.nAstralPct and self.bAstralEnabled == false then
self.bAstralEnabled = true
end
end
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:EvaluateAetherRemnant()
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hAetherRemnant:GetCastRange()
Enemies = GetEnemyHeroesInRange( self.me, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
-- Hack alert: the aghsfort Aether Remnant ability C++ stomps some of this with its own farthest target selection
local hFarthestEnemy = Enemies[ #Enemies ]
local vDir = hFarthestEnemy:GetAbsOrigin() - self.me:GetAbsOrigin()
local fDistance = vDir:Length2D()
vDir.z = 0
vDir = vDir:Normalized()
local vTargetLocation = hFarthestEnemy:GetAbsOrigin() - ( vDir * 350 )
if fDistance < 400 then
-- Place remnant behind the target enemy instead
vTargetLocation = hFarthestEnemy:GetAbsOrigin() + ( vDir * 200 )
end
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hAetherRemnant:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hAetherRemnant )
end
end
return Order
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:EvaluateDissimilate()
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hDissimilate:GetSpecialValueFor( "damage_radius" ) * 3 -- total radius is approx 3 portal radii
--printf( "EvaluateDissimilate - nSearchRadius == %d", nSearchRadius )
Enemies = GetEnemyHeroesInRange( self.me, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hDissimilate:entindex(),
Queue = false,
}
local fPhaseDuration = self.hDissimilate:GetSpecialValueFor( "phase_duration" )
Order.flOrderInterval = GetSpellCastTime( self.hDissimilate ) + ( fPhaseDuration * ( self.fPctOfPhaseForSelection / 100 ) )
--printf( "EvaluateDissimilate - order interval: %.2f (spell cast time: %.2f, phase duration: %.2f, pct_of_phase_for_selection: %.2f)", Order.flOrderInterval, GetSpellCastTime( self.hDissimilate ), fPhaseDuration, self.fPctOfPhaseForSelection )
self.fDissimilateStartTime = GameRules:GetGameTime()
end
return Order
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:EvaluateResonantPulse()
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hResonantPulse:GetSpecialValueFor( "radius" )
Enemies = GetEnemyHeroesInRange( self.me, nSearchRadius )
local Order = nil
if #Enemies >= 1 then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hResonantPulse:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hResonantPulse )
end
return Order
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:EvaluateAstralStep()
if not self.bAstralEnabled then
return
end
local Order = nil
local Enemies = shallowcopy( self.hPlayerHeroes )
local nSearchRadius = self.hAstralStep:GetSpecialValueFor( "max_travel_distance" )
Enemies = GetEnemyHeroesInRange( self.me, nSearchRadius )
if #Enemies >= 1 then
local vTargetLocation = Enemies[ #Enemies ]:GetAbsOrigin() -- target the farthest unit
if vTargetLocation ~= nil then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetLocation,
AbilityIndex = self.hAstralStep:entindex(),
Queue = false,
}
Order.flOrderInterval = GetSpellCastTime( self.hAstralStep ) + 0.5 -- Factor in a little travel time
end
end
return Order
end
--------------------------------------------------------------------------------
function CBossVoidSpirit:EvaluateActivateEarthSpirits()
local Order = nil
if self.bEnraged == true then
Order =
{
UnitIndex = self.me:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.hActivateEarthSpirits:entindex(),
Queue = false,
}
Order.flOrderInterval = self.hActivateEarthSpirits:GetChannelTime() + 0.2
end
return Order
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,165 @@
--[[ Brewmaster AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.bWantsToSplit = false
thisEntity.startSplitTime = -1
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorThunderClap, BehaviorPrimalSplit } )
end
function AIThink()
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
if thisEntity.bWantsToSplit then
return nil
end
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorThunderClap = {}
function BehaviorThunderClap:Evaluate()
--print( "BehaviorThunderClap:Evaluate()" )
local desire = 0
if thisEntity.bWantsToSplit then
return desire
end
self.thunderClapAbility = thisEntity:FindAbilityByName( "aghsfort_brewmaster_thunderclap" )
if self.thunderClapAbility and self.thunderClapAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hGushModifier = hUnit:FindModifierByName( "modifier_brewmaster_thunder_clap" )
if hGushModifier ~= nil then
--print("Enemy is already thunder clapped")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorThunderClap:Begin()
--print( "BehaviorThunderClap:Begin()" )
if self.thunderClapAbility and self.thunderClapAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 350, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.thunderClapAbility:entindex(),
}
return order
end
return nil
end
BehaviorThunderClap.Continue = BehaviorThunderClap.Begin
--------------------------------------------------------------------------------------------------------
BehaviorPrimalSplit = {}
function BehaviorPrimalSplit:Evaluate()
--print( "BehaviorPrimalSplit:Evaluate()" )
local desire = 0
if thisEntity.bWantsToSplit then
return 1000000
end
-- Bremaster can only use Primal Split once
self.primalSplitAbility = thisEntity:FindAbilityByName( "aghsfort_brewmaster_primal_split" )
if self.primalSplitAbility and self.primalSplitAbility:IsFullyCastable() then
if thisEntity:GetHealthPercent() < 40 and GameRules:GetGameTime() > thisEntity.startSplitTime then
thisEntity.bWantsToSplit = true
return 10000
end
end
return desire
end
function BehaviorPrimalSplit:Begin()
--print( "BehaviorPrimalSplit:Begin()" )
if self.primalSplitAbility and self.primalSplitAbility:IsFullyCastable() and GameRules:GetGameTime() > thisEntity.startSplitTime then
thisEntity.startSplitTime = GameRules:GetGameTime() + 0.7
--print( "execute order" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.primalSplitAbility:entindex()
}
ExecuteOrderFromTable( order )
return nil
end
return nil
end
function BehaviorPrimalSplit:IsDone()
return true
end
BehaviorPrimalSplit.Continue = BehaviorPrimalSplit.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorThunderClap, BehaviorPrimalSplit }

View File

@@ -0,0 +1,166 @@
--[[ Brewling Earth AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.bInitialized = false
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorThunderClap, BehaviorRunAway } )
end
function AIThink()
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorThunderClap = {}
function BehaviorThunderClap:Evaluate()
--print( "BehaviorThunderClap:Evaluate()" )
local desire = 0
self.thunderClapAbility = thisEntity:FindAbilityByName( "aghsfort_brewmaster_thunderclap" )
if self.thunderClapAbility and self.thunderClapAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 350, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hGushModifier = hUnit:FindModifierByName( "modifier_brewmaster_thunder_clap" )
if hGushModifier ~= nil then
--print("Enemy is already thunder clapped")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorThunderClap:Begin()
--print( "BehaviorThunderClap:Begin()" )
if self.thunderClapAbility and self.thunderClapAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 350, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.thunderClapAbility:entindex(),
}
return order
end
return nil
end
BehaviorThunderClap.Continue = BehaviorThunderClap.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
-- let's not choose this twice in a row, or even too close to another escape
if behaviorSystem.currentBehavior == self or
( self.startEscapeTime ~= nil and ( ( GameRules:GetGameTime() - self.startEscapeTime ) < 3 ) ) then
return desire
end
self.escapePoint = thisEntity.vInitialSpawnPos
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 2 ) and ( thisEntity:GetHealthPercent() < 30 ) then
desire = #enemies + 1
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- move towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 2 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorThunderClap, BehaviorRunAway }

View File

@@ -0,0 +1,115 @@
--[[ Brewling Fire AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.bInitialized = false
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorRunAway } )
end
function AIThink()
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
-- let's not choose this twice in a row, or even too close to another escape
if behaviorSystem.currentBehavior == self or
( self.startEscapeTime ~= nil and ( ( GameRules:GetGameTime() - self.startEscapeTime ) < 3 ) ) then
return desire
end
self.escapePoint = thisEntity.vInitialSpawnPos
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 2 ) and ( thisEntity:GetHealthPercent() < 30 ) then
desire = #enemies + 1
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- move towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 2 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorRunAway }

View File

@@ -0,0 +1,115 @@
--[[ Brewling Storm AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.bInitialized = false
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorRunAway } )
end
function AIThink()
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
-- let's not choose this twice in a row, or even too close to another escape
if behaviorSystem.currentBehavior == self or
( self.startEscapeTime ~= nil and ( ( GameRules:GetGameTime() - self.startEscapeTime ) < 3 ) ) then
return desire
end
self.escapePoint = thisEntity.vInitialSpawnPos
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 2 ) and ( thisEntity:GetHealthPercent() < 30 ) then
desire = #enemies + 1
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- move towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 2 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorRunAway }

View File

@@ -0,0 +1,62 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hWebAbility = thisEntity:FindAbilityByName( "broodmother_web" )
thisEntity:SetContextThink( "BroodmotherThink", BroodmotherThink, 0.5 )
end
--------------------------------------------------------------------------------
function BroodmotherThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local fSearchRange = thisEntity.hWebAbility:GetCastRange( thisEntity:GetOrigin(), nil )
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.1
end
if thisEntity.hWebAbility and thisEntity.hWebAbility:IsFullyCastable() then
local hFarthestEnemy = hEnemies[ #hEnemies ]
--local hRandomEnemy = hEnemies[ RandomInt( 1, #hEnemies ) ]
return CastWeb( hFarthestEnemy )
end
return 0.1
end
--------------------------------------------------------------------------------
function CastWeb( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetOrigin(),
AbilityIndex = thisEntity.hWebAbility:entindex(),
Queue = false,
})
local fReturnTime = thisEntity.hWebAbility:GetCastPoint() + 0.1
return fReturnTime
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,120 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.fMaxSearchRange = 1000
thisEntity.hDragonTailAbility = thisEntity:FindAbilityByName( "aghsfort_dragon_knight_dragon_tail" )
thisEntity.hDragonFormAbility = thisEntity:FindAbilityByName( "aghsfort_dragon_knight_elder_dragon_form" )
thisEntity:SetContextThink( "DragonKnightThink", DragonKnightThink, 0.5 )
end
--------------------------------------------------------------------------------
function DragonKnightThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
--[[
if not thisEntity.bInitialized then
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_creature_black_king_bar" then
thisEntity.bkbAbility = item
end
end
thisEntity.bInitialized = true
end
]]
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fMaxSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.25
end
local fHealthPctToDragon = 50
if thisEntity:GetHealthPercent() < fHealthPctToDragon and thisEntity.hDragonFormAbility and thisEntity.hDragonFormAbility:IsFullyCastable() then
return CastDragonForm()
end
--[[
local fHealthPctToBKB = 30
if thisEntity:GetHealthPercent() < fHealthPctToBKB and thisEntity.bkbAbility and thisEntity.bkbAbility:IsFullyCastable() then
return CastBKB()
end
]]
local fTailSearchRange = thisEntity.hDragonTailAbility:GetCastRange()
local hTailEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fTailSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hTailEnemies == 0 then
return 0.25
end
local fHealthPctToTail = 90
if thisEntity:GetHealthPercent() < fHealthPctToTail and thisEntity.hDragonTailAbility and thisEntity.hDragonTailAbility:IsFullyCastable() then
local hRandomTailTarget = hTailEnemies[ RandomInt( 1, #hTailEnemies ) ]
return CastDragonTail( hRandomTailTarget )
end
return 0.25
end
--------------------------------------------------------------------------------
function CastDragonTail( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = thisEntity.hDragonTailAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------
function CastDragonForm()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hDragonFormAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------
--[[
function CastBKB()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.bkbAbility:entindex()
})
return 0.5
end
]]
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,110 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.fMaxSearchRange = 5000
thisEntity.hStoneCallerAbility = thisEntity:FindAbilityByName( "aghsfort_earth_spirit_boss_stone_caller" )
thisEntity.hBoulderSmashAbility = thisEntity:FindAbilityByName( "aghsfort_earth_spirit_boss_boulder_smash" )
thisEntity:SetContextThink( "EarthSpiritStatueThink", EarthSpiritStatueThink, 0.5 )
end
--------------------------------------------------------------------------------
function EarthSpiritStatueThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.05
end
--printf( "EarthSpiritStatueThink - before stoneform modifier check" )
if thisEntity:HasModifier( "modifier_earth_spirit_statue_stoneform" ) then
return 0.05
end
--printf( "EarthSpiritStatueThink" )
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fMaxSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
--printf( "EarthSpiritStatueThink - hEnemies is empty" )
return 1
end
local hStone = nil
local hAllies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 200, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_CLOSEST, false )
for _, hAlly in pairs( hAllies ) do
if hAlly:GetUnitName() == "npc_dota_aghsfort_earth_spirit_boss_stone" then
hStone = hAlly
--printf( "earth_spirit_statue - found a stone" )
end
end
if hStone == nil then
if thisEntity.hStoneCallerAbility and thisEntity.hStoneCallerAbility:IsFullyCastable() then
local hRandomEnemy = hEnemies[ RandomInt( 1, #hEnemies ) ]
local vDir = hRandomEnemy:GetOrigin() - thisEntity:GetOrigin()
vDir.z = 0.0
vDir = vDir:Normalized()
local nStoneDistance = 100
local vStonePos = thisEntity:GetAbsOrigin() + ( vDir * nStoneDistance )
return CastStoneCaller( vStonePos )
end
else
if thisEntity.hBoulderSmashAbility and thisEntity.hBoulderSmashAbility:IsFullyCastable() then
return CastBoulderSmash( hStone )
end
end
return 0.05
end
--------------------------------------------------------------------------------
function CastStoneCaller( vPosition )
--printf( "CastStoneCaller" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hStoneCallerAbility:entindex(),
Position = vPosition,
Queue = false,
})
return 0.5
end
-------------------------------------------------------------------------------
function CastBoulderSmash( hStone )
--printf( "CastBoulderSmash on position: %s", hStone:GetAbsOrigin() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = thisEntity.hBoulderSmashAbility:entindex(),
TargetIndex = hStone:entindex(),
Queue = false,
})
return 2.0
end
-------------------------------------------------------------------------------

View File

@@ -0,0 +1,84 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hEchoStompAbility = thisEntity:FindAbilityByName( "creature_elder_titan_echo_stomp" )
thisEntity.hEarthSplitterAbility = thisEntity:FindAbilityByName( "creature_elder_titan_earth_splitter" )
thisEntity:SetContextThink( "ElderTitanThink", ElderTitanThink, 0.5 )
end
--------------------------------------------------------------------------------
function ElderTitanThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
if thisEntity.hEchoStompAbility and thisEntity.hEchoStompAbility:IsFullyCastable() then
local fEchoSearchRadius = 400
local hEnemiesToEcho = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, fEchoSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_CLOSEST, false )
if #hEnemiesToEcho > 0 then
return CastEchoStomp()
end
end
if thisEntity.hEarthSplitterAbility and thisEntity.hEarthSplitterAbility:IsFullyCastable() then
local fSplitterSearchRadius = thisEntity.hEarthSplitterAbility:GetCastRange() -- note: this range is more or less global
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, fSplitterSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_FARTHEST, false )
if #hEnemies > 0 then
local hFarthestEnemy = hEnemies[ 1 ]
return CastEarthSplitter( hFarthestEnemy )
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastEchoStomp()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hEchoStompAbility:entindex(),
Queue = false,
})
return 3
end
--------------------------------------------------------------------------------
function CastEarthSplitter( hTarget )
if hTarget == nil or hTarget:IsNull() or hTarget:IsAlive() == false then
return 0.5
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetOrigin(),
AbilityIndex = thisEntity.hEarthSplitterAbility:entindex(),
Queue = false,
})
return 2
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,65 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.TetherAbility = thisEntity:FindAbilityByName( "aghsfort_wisp_tether" )
thisEntity:SetContextThink( "ElementalIoThink", ElementalIoThink, 0.5 )
end
function ElementalIoThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity:GetOwner() == nil or thisEntity:GetOwner():IsAlive() ~= true then
-- We lost our owner Tiny. Let's find a new one.
local entities = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, DOTA_UNIT_TARGET_FLAG_NO_INVIS, FIND_CLOSEST, false )
local hPossibleOwner = nil
for _, hAlly in pairs( entities ) do
if hAlly ~= nil and not hAlly:IsNull() and hAlly:IsAlive() == true and hAlly:GetUnitName() == "npc_dota_creature_elemental_tiny" then
printf("Found a valid tiny")
hPossibleOwner = hAlly
break
end
end
if hPossibleOwner ~= nil then
thisEntity:SetOwner( hPossibleOwner )
else
--No new owners found. Not even worth thinking anymore
return -1
end
end
if thisEntity.TetherAbility ~= nil and thisEntity.TetherAbility:IsFullyCastable() then
if (thisEntity:GetAbsOrigin() - thisEntity:GetOwner():GetAbsOrigin() ):Length2D() > 350 then
return CastTether(thisEntity:GetOwner())
end
end
return 0.5
end
function CastTether( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = thisEntity.TetherAbility:entindex(),
TargetIndex = hTarget:entindex(),
Queue = false,
})
return 0.5
end

View File

@@ -0,0 +1,128 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hIos = {}
thisEntity.TossAbility = thisEntity:FindAbilityByName( "aghsfort_elemental_tiny_toss" )
thisEntity.CreateIoAbility = thisEntity:FindAbilityByName( "aghsfort_elemental_tiny_create_io" )
thisEntity:SetContextThink( "ElementalTinyTossThink", ElementalTinyTossThink, 1 )
end
function LastTargetTossTime( hTarget )
local flLastTime = thisEntity.Encounter.TossTargets[ tostring( hTarget:entindex() ) ]
if flLastTime == nil then
flLastTime = 0
end
return flLastTime
end
function ElementalTinyTossThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
--if thisEntity.Encounter.TossTargets == nil then
-- thisEntity.Encounter.TossTargets = {}
--end
if thisEntity.TossAbility ~= nil and thisEntity.TossAbility:IsFullyCastable() then
-- Select a unit in range to attack
-- And don't pick a target who was tossed at at in the last 2 seconds
-- Check to make sure we have an io to toss
local grab_radius = thisEntity.TossAbility:GetSpecialValueFor( "grab_radius")
local entities = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, grab_radius, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_NOT_ANCIENTS, FIND_CLOSEST, false )
local hPossibleProjectiles = {}
for _, hAlly in pairs( entities ) do
if hAlly ~= nil and not hAlly:IsNull() and hAlly:IsAlive() == true and hAlly:GetUnitName() == "npc_dota_creature_elemental_io" then
table.insert( hPossibleProjectiles, hAlly )
end
end
if #hPossibleProjectiles ~= 0 then
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil,
thisEntity.TossAbility:GetCastRange(thisEntity:GetAbsOrigin(), nil), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local hPossibleEnemies = {}
for _,hEnemy in pairs( enemies ) do
--local flTimeSinceLastToss = GameRules:GetGameTime() - LastTargetTossTime( hEnemy )
if hEnemy ~= nil and hEnemy:IsAlive() and hEnemy:IsInvulnerable() == false then --and
--hEnemy:IsMagicImmune() == false and ( flTimeSinceLastToss > 1.5 ) then
table.insert( hPossibleEnemies, hEnemy )
end
end
-- Pick a random one, but prefer one who is at least 500 away
local hNearEnemy = nil
while #hPossibleEnemies > 0 do
local nIndex = math.random( 1, #hPossibleEnemies )
local hEnemy = hPossibleEnemies[ nIndex ]
if ( ( hEnemy:GetAbsOrigin() - thisEntity:GetAbsOrigin() ):Length2D() > 500 ) then
return Toss( hEnemy )
end
hNearEnemy = hEnemy
table.remove( hPossibleEnemies, nIndex )
end
-- If not, then pick a close one
if hNearEnemy ~= nil then
return Toss( hNearEnemy )
end
end
end
if thisEntity.CreateIoAbility ~= nil and thisEntity.CreateIoAbility:IsFullyCastable() == true then
-- Only spawn ios if we haven't got too many already
local max_summons = thisEntity.CreateIoAbility:GetSpecialValueFor( "max_summons" )
if #thisEntity.hIos <= max_summons then
return CreateIos()
end
end
return 0.1
end
function Toss( hTarget )
local vTargetPos = hTarget:GetAbsOrigin() + RandomVector( RandomFloat( 25, 25 ) )
--thisEntity.Encounter.TossTargets[ tostring( hTarget:entindex() ) ] = GameRules:GetGameTime()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.TossAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
return 0.5
end
function CreateIos( )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.CreateIoAbility:entindex(),
Queue = false,
})
return 0.5
end

View File

@@ -0,0 +1,62 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hFireballAbility = thisEntity:FindAbilityByName( "ember_spirit_fireball" )
thisEntity:SetContextThink( "EmberSpiritThink", EmberSpiritThink, 0.1 )
end
--------------------------------------------------------------------------------
function EmberSpiritThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local fSearchRange = thisEntity:GetAcquisitionRange()
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.1
end
local fHealthPctFireball = 50
if thisEntity:GetHealthPercent() <= fHealthPctFireball then
if thisEntity.hFireballAbility and thisEntity.hFireballAbility:IsFullyCastable() then
local hRandomTarget = hEnemies[ RandomInt( 1, #hEnemies ) ]
return CastFireball( hRandomTarget:GetAbsOrigin() )
end
end
return 0.1
end
--------------------------------------------------------------------------------
function CastFireball( vTargetPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hFireballAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
return thisEntity.hFireballAbility:GetCastPoint() + 0.1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,117 @@
function Precache( context )
PrecacheUnitByNameSync( "npc_aghsfort_creature_tornado_harpy", context, -1 )
end
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.TornadoAbility = thisEntity:FindAbilityByName( "enraged_wildwing_create_tornado" )
thisEntity.BlastAbility = thisEntity:FindAbilityByName( "aghsfort_enraged_wildwing_tornado_blast" )
thisEntity.flLastOrder = GameRules:GetGameTime()
thisEntity.bHasSummonedTornado = false
thisEntity:SetContextThink( "EnragedWildwingThink", EnragedWildwingThink, 1 )
end
function EnragedWildwingThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity.TornadoAbility ~= true and thisEntity.TornadoAbility:IsFullyCastable() and thisEntity.bHasSummonedTornado == false then
thisEntity.bHasSummonedTornado = true
--return CastTornadoAbility()
end
if (GameRules:GetGameTime() - thisEntity.flLastOrder) > (20 - RandomFloat(0 ,5 )) then
thisEntity.flLastOrder = GameRules:GetGameTime()
return DoMove()
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 900, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.5
end
if thisEntity.BlastAbility ~= nil and thisEntity.BlastAbility:IsFullyCastable() then
return CastBlastAbility( hEnemies[ #hEnemies ] )
end
return 0.5
end
function CastTornadoAbility()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.TornadoAbility:entindex(),
Queue = true,
})
return 0.2
end
--------------------------------------------------------------------------------
function CastBlastAbility( enemy )
local vToTarget = enemy:GetOrigin() - thisEntity:GetOrigin()
vToTarget = vToTarget:Normalized()
local vTargetPos = thisEntity:GetOrigin() + vToTarget * 50
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.BlastAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
return 4
end
function DoMove()
if IsServer() then
for i=1,4 do
local vLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 1000, 2000 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vLoc
})
break
end
end
end
return 4
end

View File

@@ -0,0 +1,134 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
AttachEffects()
thisEntity.vInitialSpawnPos = nil
thisEntity.bInitialized = false
local retreatPoints = Entities:FindAllByName( "retreat_point" )
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return
end
local happyPlaceIndex = RandomInt( 1, #retreatPoints )
thisEntity.vRetreatPoint = retreatPoints[ happyPlaceIndex ]:GetAbsOrigin()
thisEntity.imprisonAbility = thisEntity:FindAbilityByName( "obsidian_destroyer_astral_imprisonment" )
thisEntity:SetContextThink( "EvilGreevilThink", EvilGreevilThink, 0.1 )
end
--------------------------------------------------------------------------------
function AttachEffects()
--local effect_name = "particles/units/unit_greevil/greevil_blackhole.vpcf"
local effect_name = "particles/creatures/greevil/greevil_prison_bottom_ring.vpcf"
local effect = ParticleManager:CreateParticle( effect_name, PATTACH_POINT_FOLLOW, thisEntity )
ParticleManager:SetParticleControlEnt( effect, 0, thisEntity, PATTACH_POINT_FOLLOW, nil, thisEntity:GetOrigin(), true )
--[[
local right_eyeTable =
{
origin = "0 0 0",
angles = "0 0 0",
targetname = "eye_model",
model = "models/particle/mesh/slumbering_terror_eyes.vmdl",
scales = "0.5 0.5 0.5",
}
local hRightEye = SpawnEntityFromTableSynchronous( "prop_dynamic", right_eyeTable )
hRightEye:SetParent( thisEntity, "attach_eye_r" )
local left_eyeTable =
{
origin = "0 0 0",
angles = "0 0 0",
targetname = "eye_model",
model = "models/particle/mesh/slumbering_terror_eyes.vmdl",
scales = "0.5 0.5 0.5",
}
local hLeftEye = SpawnEntityFromTableSynchronous( "prop_dynamic", left_eyeTable )
hLeftEye:SetParent( thisEntity, "attach_eye_l" )
]]
end
--------------------------------------------------------------------------------
function EvilGreevilThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 100, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies > 0 then
--print("Enemy is near")
local target = enemies[1]
if thisEntity.imprisonAbility ~= nil and thisEntity.imprisonAbility:IsCooldownReady() then
return CastImprison( target )
end
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetAbsOrigin()
thisEntity.bInitialized = true
end
local vPos = thisEntity:GetAbsOrigin()
local difference = vPos - thisEntity.vInitialSpawnPos
local distance = difference:Length()
if distance < 25 then
--print("Move to retreat point")
--RunAround( thisEntity.vRetreatPoint )
thisEntity:MoveToPosition( thisEntity.vRetreatPoint )
elseif distance > 150 then
--print("Move to home")
--RunAround( thisEntity.vInitialSpawnPos )
thisEntity:MoveToPosition( thisEntity.vInitialSpawnPos )
end
return .1
end
--------------------------------------------------------------------------------
function CastImprison( target )
--print("Casting Astral Imprisonment")
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.3 } )
local difference = target:GetAbsOrigin() - thisEntity:GetAbsOrigin()
local distance = difference:Length()
if distance < thisEntity.imprisonAbility:GetCastRange() then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = thisEntity.imprisonAbility:entindex(),
Queue = false,
})
else
print("Enemy escaped")
print(distance)
end
return 1
end
--------------------------------------------------------------------------------
function RunAround( position )
local destination = position
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = destination
})
return 1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,137 @@
--[[ ai/exploding_burrower.lua ]]
----------------------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "model", "models/heroes/nerubian_assassin/mound.vmdl", context )
end
----------------------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if thisEntity == nil then
return
end
hExplosionAbility = thisEntity:FindAbilityByName( "burrower_big_explosion" )
hBurrowAbility = thisEntity:FindAbilityByName( "nyx_assassin_burrow" )
hUnburrowAbility = thisEntity:FindAbilityByName( "nyx_assassin_unburrow" )
-- Start already burrowed
thisEntity:AddNewModifier( thisEntity, nil, "modifier_nyx_assassin_burrow", { duration = -1 } )
hUnburrowAbility:SetHidden( false )
thisEntity:SetContextThink( "ExplodingNyxThink", ExplodingNyxThink, 0.5 )
end
----------------------------------------------------------------------------------------------
function ExplodingNyxThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
local bIsBurrowed = ( thisEntity:FindModifierByName( "modifier_nyx_assassin_burrow" ) ~= nil )
if bIsBurrowed and hUnburrowAbility and hUnburrowAbility:IsFullyCastable() then
return CastUnburrow()
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies > 0 then
local enemy = hEnemies[1]
if enemy ~= nil then
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist <= 150 then
return CastExplosion()
else
return Approach( enemy )
end
end
end
return 0.5
end
----------------------------------------------------------------------------------------------
function CastBurrow()
--print( "ExplodingBurrower - CastBurrow()" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hBurrowAbility:entindex(),
})
return 2
end
----------------------------------------------------------------------------------------------
function CastUnburrow()
--print( "ExplodingBurrower - CastUnburrow()" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hUnburrowAbility:entindex(),
})
return 0.3
end
----------------------------------------------------------------------------------------------
function CastExplosion()
--print( "ExplodingBurrower - CastExplosion()" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hExplosionAbility:entindex(),
Queue = false,
})
return 2
end
----------------------------------------------------------------------------------------------
function Approach( unit )
--print( "ExplodingBurrower - Approach()" )
local vToEnemy = unit:GetOrigin() - thisEntity:GetOrigin()
vToEnemy = vToEnemy:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToEnemy * thisEntity:GetIdealSpeed()
})
return 0.4
end
----------------------------------------------------------------------------------------------
function RunToMom()
--print( "ExplodingBurrower - RunToMom()" )
if hUnburrowAbility and hUnburrowAbility:IsFullyCastable() then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hUnburrowAbility:entindex(),
})
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.hParent:GetOrigin(),
Queue = true,
})
return 1
end
----------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,106 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "baby_ogre_tank_melee_smash" )
JumpAbility = thisEntity:FindAbilityByName( "baby_ogre_tank_jump_smash" )
thisEntity:SetContextThink( "OgreTankThink", OgreTankThink, 1 )
end
--------------------------------------------------------------------------------
function OgreTankThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
local nEnemiesRemoved = 0
local fSearchRange = 700
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
-- Iterate backwards since we're removing entries
for i = #enemies, 1, -1 do
local enemy = enemies[ i ]
if enemy ~= nil then
if enemy:GetUnitName() == "npc_dota_explosive_barrel" or enemy:GetUnitName() == "npc_dota_crate" then
--printf( "OgreTankThink: removed invalid target named %s", enemy:GetUnitName() )
table.remove( enemies, i )
else
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 210 then
nEnemiesRemoved = nEnemiesRemoved + 1
table.remove( enemies, i )
end
end
end
end
if JumpAbility ~= nil and JumpAbility:IsFullyCastable() and nEnemiesRemoved > 0 then
return Jump()
end
if #enemies == 0 then
return 1
end
if SmashAbility ~= nil and SmashAbility:IsFullyCastable() then
local hSmashTarget = enemies[ 1 ]
return Smash( hSmashTarget )
end
return 0.5
end
--------------------------------------------------------------------------------
function Jump()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = JumpAbility:entindex(),
Queue = false,
})
return 2.5
end
--------------------------------------------------------------------------------
function Smash( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = SmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 3 / thisEntity:GetHasteFactor()
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,72 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hFlop = thisEntity:FindAbilityByName( "friendly_ogreseal_flop" )
thisEntity.flSearchRadius = 700
thisEntity:SetContextThink( "FriendlyOgreSealThink", FriendlyOgreSealThink, 0.5 )
end
--------------------------------------------------------------------------------
function FriendlyOgreSealThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.flSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
for i = #hEnemies, 1, -1 do
local hEnemy = hEnemies[ i ]
if hEnemy ~= nil then
if hEnemy:GetUnitName() == "npc_dota_explosive_barrel" or hEnemy:GetUnitName() == "npc_dota_crate" then
table.remove( hEnemies, i )
end
end
end
if #hEnemies == 0 then
return 0.25
end
--printf( "hEnemies > 0" )
if thisEntity.hFlop ~= nil and thisEntity.hFlop:IsFullyCastable() then
return CastBellyFlop( hEnemies[ #hEnemies ] )
end
return 0.25
end
--------------------------------------------------------------------------------
function CastBellyFlop( enemy )
printf( "CastBellyFlop" )
local vToTarget = enemy:GetOrigin() - thisEntity:GetOrigin()
vToTarget = vToTarget:Normalized()
local vTargetPos = thisEntity:GetOrigin() + vToTarget * 50
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hFlop:entindex(),
Position = vTargetPos,
Queue = false,
})
return 4
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,106 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "ogre_tank_melee_smash" )
JumpAbility = thisEntity:FindAbilityByName( "ogre_tank_jump_smash" )
thisEntity:SetContextThink( "OgreTankThink", OgreTankThink, 1 )
end
--------------------------------------------------------------------------------
function OgreTankThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
local nEnemiesRemoved = 0
local fSearchRange = 700
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
-- Iterate backwards since we're removing entries
for i = #enemies, 1, -1 do
local enemy = enemies[ i ]
if enemy ~= nil then
if enemy:GetUnitName() == "npc_dota_explosive_barrel" or enemy:GetUnitName() == "npc_dota_crate" then
--printf( "OgreTankThink: removed invalid target named %s", enemy:GetUnitName() )
table.remove( enemies, i )
else
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 210 then
nEnemiesRemoved = nEnemiesRemoved + 1
table.remove( enemies, i )
end
end
end
end
if JumpAbility ~= nil and JumpAbility:IsFullyCastable() and nEnemiesRemoved > 0 then
return Jump()
end
if #enemies == 0 then
return 1
end
if SmashAbility ~= nil and SmashAbility:IsFullyCastable() then
local hSmashTarget = enemies[ 1 ]
return Smash( hSmashTarget )
end
return 0.5
end
--------------------------------------------------------------------------------
function Jump()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = JumpAbility:entindex(),
Queue = false,
})
return 2.5
end
--------------------------------------------------------------------------------
function Smash( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = SmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 3 / thisEntity:GetHasteFactor()
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,73 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.fMaxSearchRange = 800
thisEntity.hInkSwellAbility = thisEntity:FindAbilityByName( "aghsfort_grimstroke_spirit_walk" )
thisEntity:SetContextThink( "GrimstrokeThink", GrimstrokeThink, 0.5 )
end
--------------------------------------------------------------------------------
function GrimstrokeThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fMaxSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 1
end
if thisEntity.hInkSwellAbility and thisEntity.hInkSwellAbility:IsFullyCastable() then
local hInkSwellTarget = nil
local fInkSwellRange = thisEntity.hInkSwellAbility:GetCastRange()
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fInkSwellRange, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for _, friendly in pairs ( friendlies ) do
if friendly ~= nil and friendly ~= thisEntity and friendly:GetUnitName() ~= "npc_dota_crate" then
hInkSwellTarget = friendly
if ( friendly:GetUnitName() == "npc_dota_creature_life_stealer" ) then
break
end
end
end
if hInkSwellTarget then
return CastInkSwell( hInkSwellTarget )
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastInkSwell( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = thisEntity.hInkSwellAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,80 @@
--[[ ai/healing_burrower.lua ]]
----------------------------------------------------------------------------------------------
function Precache( context )
PrecacheResource( "model", "models/heroes/nerubian_assassin/mound.vmdl", context )
end
----------------------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if thisEntity == nil then
return
end
hHealAbility = thisEntity:FindAbilityByName( "nyx_suicide_heal" )
hUnburrowAbility = thisEntity:FindAbilityByName( "nyx_assassin_unburrow" )
-- Start already burrowed
thisEntity:AddNewModifier( thisEntity, nil, "modifier_nyx_assassin_burrow", { duration = -1 } )
hUnburrowAbility:SetHidden( false )
thisEntity:SetContextThink( "HealingNyxThink", HealingNyxThink, 0.5 )
end
----------------------------------------------------------------------------------------------
function HealingNyxThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
local bIsBurrowed = ( thisEntity:FindModifierByName( "modifier_nyx_assassin_burrow" ) ~= nil )
if bIsBurrowed then
return CastUnburrow()
end
local hCreatures = Entities:FindAllByClassnameWithin( "npc_dota_creature", thisEntity:GetAbsOrigin(), 2000 )
local hGuardians = {}
for _, hCreature in pairs( hCreatures ) do
if ( hCreature:GetUnitName() == "npc_dota_creature_sand_king" ) and hCreature:IsAlive() then
return CastSuicideHeal( hCreature )
end
end
return 0.1
end
----------------------------------------------------------------------------------------------
function CastUnburrow()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hUnburrowAbility:entindex(),
})
return 0.3
end
----------------------------------------------------------------------------------------------
function CastSuicideHeal( hCreature )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = hHealAbility:entindex(),
TargetIndex = hCreature:entindex(),
})
return 1
end

View File

@@ -0,0 +1,102 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "hellbear_smash" )
thisEntity:SetContextThink( "HellbearThink", HellbearThink, 1 )
end
--------------------------------------------------------------------------------
function HellbearThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 750 )
thisEntity.bAcqRangeModified = true
end
if thisEntity:GetAggroTarget() then
thisEntity.fTimeWeLostAggro = nil
end
if thisEntity:GetAggroTarget() and ( thisEntity.fTimeAggroStarted == nil ) then
--print( "Do we have aggro and need to get a timestamp?" )
thisEntity.fTimeAggroStarted = GameRules:GetGameTime()
end
if ( not thisEntity:GetAggroTarget() ) and ( thisEntity.fTimeAggroStarted ~= nil ) then
--print( "We lost aggro." )
thisEntity.fTimeWeLostAggro = GameRules:GetGameTime()
thisEntity.fTimeAggroStarted = nil
end
if ( not thisEntity:GetAggroTarget() ) then
if thisEntity.fTimeWeLostAggro and ( GameRules:GetGameTime() > ( thisEntity.fTimeWeLostAggro + 1.0 ) ) then
return RetreatHome()
end
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 0.5
end
if SmashAbility ~= nil and SmashAbility:IsCooldownReady() then
return Smash()
end
return 0.5
end
--------------------------------------------------------------------------------
function Smash()
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.3 } )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = SmashAbility:entindex(),
Queue = false,
})
return 1.1 -- was 1.2
end
--------------------------------------------------------------------------------
function RetreatHome()
--print( "RetreatHome - " .. thisEntity:GetUnitName() .. " is returning to home position" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.vInitialSpawnPos,
})
return 0.5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,84 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hWebAbility = thisEntity:FindAbilityByName( "broodmother_web" )
thisEntity.hSummonEggsAbility = thisEntity:FindAbilityByName( "huge_brood_summon_eggs" )
thisEntity.nHealthPctForSummon = 90
thisEntity:SetContextThink( "HugeBroodmotherThink", HugeBroodmotherThink, 0.5 )
end
--------------------------------------------------------------------------------
function HugeBroodmotherThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local fSearchRange = thisEntity.hWebAbility:GetCastRange( thisEntity:GetOrigin(), nil )
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.1
end
if thisEntity:GetHealthPercent() <= thisEntity.nHealthPctForSummon then
if thisEntity.hSummonEggsAbility and thisEntity.hSummonEggsAbility:IsFullyCastable() then
return CastSummonEggs()
end
end
if thisEntity.hWebAbility and thisEntity.hWebAbility:IsFullyCastable() then
local hFarthestEnemy = hEnemies[ #hEnemies ]
--local hRandomEnemy = hEnemies[ RandomInt( 1, #hEnemies ) ]
return CastWeb( hFarthestEnemy )
end
return 0.1
end
--------------------------------------------------------------------------------
function CastWeb( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetOrigin(),
AbilityIndex = thisEntity.hWebAbility:entindex(),
Queue = false,
})
local fReturnTime = thisEntity.hWebAbility:GetCastPoint() + 0.1
return fReturnTime
end
--------------------------------------------------------------------------------
function CastSummonEggs()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hSummonEggsAbility:entindex(),
Queue = false,
})
local fReturnTime = thisEntity.hSummonEggsAbility:GetCastPoint() + 0.1
return fReturnTime
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,257 @@
require( "aghanim_utility_functions" )
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if thisEntity == nil then
return
end
thisEntity.hSummonEggsAbility = thisEntity:FindAbilityByName( "kidnap_spider_summon_eggs" )
thisEntity.hLassoAbility = thisEntity:FindAbilityByName( "aghsfort_batrider_flaming_lasso" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_phased", { duration = -1 } )
thisEntity.vCurrentRunAwayPos = nil
thisEntity.nLassoDragDistance = thisEntity.hLassoAbility:GetSpecialValueFor( "drag_distance" )
thisEntity.hEntityKilledGameEvent = ListenToGameEvent( "entity_killed", Dynamic_Wrap( thisEntity:GetPrivateScriptScope(), "OnEntityKilled" ), nil )
thisEntity:SetContextThink( "KidnapSpiderThink", KidnapSpiderThink, 1 )
end
--------------------------------------------------------------------------------
function UpdateOnRemove()
StopListeningToGameEvent( thisEntity.hEntityKilledGameEvent )
end
--------------------------------------------------------------------------------
function KidnapSpiderThink()
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
if not IsServer() then
return
end
-- This script was being broken by SetInitialGoalEntity in the encounter's OnSpawnerFinished
if not thisEntity.bGoalEntCleared then
thisEntity:SetInitialGoalEntity( nil )
thisEntity.bGoalEntCleared = true
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 5000,
DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_CLOSEST, false
)
if #hEnemies == 0 then
return 0.1
end
-- Summon Eggs
if thisEntity.hSummonEggsAbility and thisEntity.hSummonEggsAbility:IsFullyCastable() then
return CastSummonEggs()
end
-- Try to Lasso
if thisEntity.hLassoAbility and thisEntity.hLassoAbility:IsFullyCastable() then
for _, hEnemy in pairs( hEnemies ) do
if hEnemy ~= nil and hEnemy:IsRealHero() and hEnemy:IsAlive() and ( not hEnemy:HasModifier( "modifier_batrider_flaming_lasso" ) ) then
-- Ensure I have vision
local hVisionBuff = hEnemy:FindModifierByName( "modifier_provide_vision" )
if hVisionBuff == nil then
hVisionBuff = hEnemy:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 15 } )
end
return CastLasso( hEnemy )
end
end
end
if thisEntity.vCurrentRunAwayPos ~= nil then
--printf( "Have I arrived?" )
local fDistToRunAwayPos = ( thisEntity.vCurrentRunAwayPos - thisEntity:GetAbsOrigin() ):Length2D()
--printf( "fDistToRunAwayPos: %d", fDistToRunAwayPos )
local nDistThreshold = 50
if fDistToRunAwayPos <= nDistThreshold then
--printf( "I've arrived close enough to current RunAway position: %s", thisEntity.vCurrentRunAwayPos )
thisEntity.vCurrentRunAwayPos = nil
return 3.0
else
--printf( "Have not arrived at current RunAway position of: %s", thisEntity.vCurrentRunAwayPos )
return 0.1
end
end
-- Find eggs at appropriate distances
local nMinEggDistance = 1400
local nMinEggFarDistance = 2800
local nMaxEggDistance = 4500
local hEggs = {}
local hFarEggs = {}
local hAllies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, nMaxEggDistance,
DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_INVULNERABLE, FIND_FARTHEST, false
)
for _, hAlly in pairs( hAllies ) do
if hAlly:GetUnitName() == "npc_dota_spider_sac" then
--printf( "found \"npc_dota_spider_sac\" ally" )
local fDistToEgg = ( hAlly:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if fDistToEgg >= nMinEggFarDistance then
table.insert( hFarEggs, hAlly )
elseif fDistToEgg >= nMinEggDistance then
table.insert( hEggs, hAlly )
end
end
end
-- I can't cast Lasso, and whether I currently have a hero Lasso'd or not I want to run away
-- First try to find a good position using any distant eggs I found
local vRunTargetPos = nil
ShuffleListInPlace( hFarEggs )
if #hFarEggs > 0 then
--printf( "we have %d far egg candidates within %d-%d range", #hFarEggs, nMinEggDistance, nMaxEggDistance )
for _, hEgg in pairs( hFarEggs ) do
local vToEgg = hEgg:GetOrigin() - thisEntity:GetOrigin()
local fDistToEgg = vToEgg:Length2D()
vToEgg.z = 0.0
vToEgg = vToEgg:Normalized()
local nDistPastEgg = thisEntity.nLassoDragDistance
vRunTargetPos = thisEntity:GetAbsOrigin() + ( vToEgg * ( fDistToEgg + nDistPastEgg ) )
--printf( "evaluating far egg position's pathability" )
if GridNav:CanFindPath( thisEntity:GetOrigin(), vRunTargetPos ) then
--printf( "found valid far egg position, so break early -- %s", vRunTargetPos )
break
end
end
else
--printf( "no far egg candidates" )
end
-- If I didn't get a good distant egg position, look through any medium-range eggs
if vRunTargetPos == nil then
ShuffleListInPlace( hEggs )
if #hEggs > 0 then
--printf( "we have %d egg candidates within %d-%d range", #hEggs, nMinEggDistance, nMaxEggDistance )
for _, hEgg in pairs( hEggs ) do
local vToEgg = hEgg:GetOrigin() - thisEntity:GetOrigin()
local fDistToEgg = vToEgg:Length2D()
vToEgg.z = 0.0
vToEgg = vToEgg:Normalized()
local nDistPastEgg = thisEntity.nLassoDragDistance
vRunTargetPos = thisEntity:GetAbsOrigin() + ( vToEgg * ( fDistToEgg + nDistPastEgg ) )
--printf( "evaluating egg position's pathability" )
if GridNav:CanFindPath( thisEntity:GetOrigin(), vRunTargetPos ) then
--printf( "found valid egg position, so break early -- %s", vRunTargetPos )
break
end
end
else
--printf( "no medium distance egg candidates" )
end
end
-- If I didn't find any egg positions at all
if vRunTargetPos == nil then
vRunTargetPos = FindRandomPointInRoom( thisEntity:GetAbsOrigin(), 1500, 3000 )
--printf( "had no vRunTargetPos after all the egg searching, so trying random point in room: %s", vRunTargetPos )
end
if vRunTargetPos then
--printf( " going to position: %s", vRunTargetPos )
return RunAway( vRunTargetPos )
end
return 0.1
end
--------------------------------------------------------------------------------
function CastSummonEggs()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hSummonEggsAbility:entindex(),
Queue = false,
})
local fReturnTime = thisEntity.hSummonEggsAbility:GetCastPoint() + 0.2
return fReturnTime
end
--------------------------------------------------------------------------------
function CastLasso( unit )
--printf( "kidnap_spider - CastLasso" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = unit:entindex(),
AbilityIndex = thisEntity.hLassoAbility:entindex(),
Queue = false,
})
thisEntity.vCurrentRunAwayPos = nil -- find a new destination
return thisEntity.hLassoAbility:GetCastPoint() + 0.2
end
--------------------------------------------------------------------------------
function RunAway( vPos )
--printf( "kidnap_spider - RunAway" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vPos,
Queue = true,
})
thisEntity.vCurrentRunAwayPos = vPos
return 1 --11
end
--------------------------------------------------------------------------------
function OnEntityKilled( event )
local hVictim = nil
if event.entindex_killed ~= nil then
hVictim = EntIndexToHScript( event.entindex_killed )
end
if hVictim ~= thisEntity then
return
end
-- Cleanup
for nPlayerID = 0, ( AGHANIM_PLAYERS - 1 ) do
local hPlayerHero = PlayerResource:GetSelectedHeroEntity( nPlayerID )
if hPlayerHero then
local hMyBuff = hPlayerHero:FindModifierByNameAndCaster( "modifier_provide_vision", thisEntity )
if hMyBuff then
hMyBuff:Destroy()
--printf( "kidnap_spider - OnEntityKilled: removing vision buff" )
end
end
end
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,201 @@
--[[ Kunkka AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorTorrent, BehaviorGhostShip } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorTorrent = {}
function BehaviorTorrent:Evaluate()
--print( "BehaviorTorrent:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.torrentAbility = thisEntity:FindAbilityByName( "kunkka_torrent_dm" )
if self.torrentAbility and self.torrentAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorTorrent:Begin()
--print( "BehaviorTorrent:Begin()" )
if self.torrentAbility and self.torrentAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local vTarget = hTarget:GetAbsOrigin()
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.torrentAbility:entindex(),
Position = vTarget
}
return order
end
return nil
end
BehaviorTorrent.Continue = BehaviorTorrent.Begin
--------------------------------------------------------------------------------------------------------
--[[
BehaviorTidebringer = {}
function BehaviorTidebringer:Evaluate()
--print( "BehaviorTorrent:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.tidebringerAbility = thisEntity:FindAbilityByName( "kunkka_tidebringer" )
if self.tidebringerAbility and self.tidebringerAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 150, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies > 2 ) then
desire = #enemies + 2
end
end
return desire
end
function BehaviorTidebringer:Begin()
--print( "BehaviorTorrent:Begin()" )
if self.tidebringerAbility and self.tidebringerAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 150, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local vTarget = hTarget:GetAbsOrigin()
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = self.tidebringerAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorTidebringer.Continue = BehaviorTidebringer.Begin
]]
--------------------------------------------------------------------------------------------------------
BehaviorGhostShip = {}
function BehaviorGhostShip:Evaluate()
--print( "BehaviorGhostShip:Evaluate()" )
local desire = 0
self.ghostShipAbility = thisEntity:FindAbilityByName( "kunkka_ghostship" )
if self.ghostShipAbility and self.ghostShipAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 1000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 1 ) then
desire = #enemies + 5
end
end
return desire
end
function BehaviorGhostShip:Begin()
--print( "BehaviorGhostShip:Begin()" )
if self.ghostShipAbility and self.ghostShipAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local targetPoint = hTarget:GetOrigin() + RandomVector( 100 )
if self.ghostShipAbility and self.ghostShipAbility:IsFullyCastable() then
--print( "Casting Ghost Ship" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.ghostShipAbility:entindex(),
Position = targetPoint
}
return order
end
end
return nil
end
BehaviorGhostShip.Continue = BehaviorGhostShip.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorTorrent, BehaviorGhostShip }

View File

@@ -0,0 +1,59 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
DetonateAblity = thisEntity:FindAbilityByName( "creature_landmine_detonate" )
thisEntity:SetContextThink( "LandmineThink", LandmineThink, 1 )
end
--------------------------------------------------------------------------------
function LandmineThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
local fDetonateRadius = thisEntity.DetonateAblity:GetSpecialValueFor( "detonate_radius" )
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fDetonateRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #enemies == 0 then
return 0.5
end
return Detonate()
end
--------------------------------------------------------------------------------
function Detonate()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.DetonateAblity:entindex(),
Queue = false,
})
return 3
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,228 @@
--[[ Legion Commander AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.bInDuel = false
thisEntity.nDuelDuration = 0
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorOverwhelmingOdds, BehaviorBlademail, BehaviorDuel } )
end
function AIThink()
if thisEntity.bInDuel then
thisEntity.nDuelDuration = thisEntity.nDuelDuration + 1
end
if thisEntity.nDuelDuration > 6 then
thisEntity.bInDuel = false
thisEntity.nDuelDuration = 0
end
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorOverwhelmingOdds = {}
function BehaviorOverwhelmingOdds:Evaluate()
--print( "BehaviorOverwhelmingOdds:Evaluate()" )
local desire = 0
--[[ Testing without this ability for now
self.overwhelmingOddsAbility = thisEntity:FindAbilityByName( "legion_commander_overwhelming_odds" )
if self.overwhelmingOddsAbility and self.overwhelmingOddsAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 2 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hAbilityhModifier = hUnit:FindModifierByName( "modifier_legion_commander_overwhelming_odds" )
if hAbilityhModifier ~= nil then
--print("Enemy already has Overwhelming Odds")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
]]
return desire
end
function BehaviorOverwhelmingOdds:Begin()
--print( "BehaviorOverwhelmingOdds:Begin()" )
if self.overwhelmingOddsAbility and self.overwhelmingOddsAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies == 0 then
return nil
end
local target = enemies[#enemies]
local targetPoint = target:GetOrigin() + RandomVector( 100 )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.overwhelmingOddsAbility:entindex(),
Position = targetPoint
}
return order
end
return nil
end
BehaviorOverwhelmingOdds.Continue = BehaviorOverwhelmingOdds.Begin
--------------------------------------------------------------------------------------------------------
BehaviorBlademail = {}
function BehaviorBlademail:Evaluate()
--print( "BehaviorBlademail:Evaluate()" )
local desire = 0
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_blade_mail" then
self.blademailAbility = item
end
end
if self.blademailAbility and self.blademailAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 100 ) then
if ( #enemies > 0 ) then
if thisEntity.bInDuel then
desire = 8
else
desire = #enemies + 1
end
end
end
end
return desire
end
function BehaviorBlademail:Begin()
--print( "BehaviorBlademail:Begin()" )
if self.blademailAbility and self.blademailAbility:IsFullyCastable() then
--print( "Casting Blade Mail" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.blademailAbility:entindex(),
}
return order
end
return nil
end
BehaviorBlademail.Continue = BehaviorBlademail.Begin
--------------------------------------------------------------------------------------------------------
BehaviorDuel = {}
function BehaviorDuel:Evaluate()
--print( "BehaviorDuel:Evaluate()" )
local desire = 0
self.duelAbility = thisEntity:FindAbilityByName( "legion_commander_duel" )
if self.duelAbility and self.duelAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hAbilityModifier = hUnit:FindModifierByName( "modifier_legion_commander_duel" )
if hAbilityModifier ~= nil then
--print("Enemy is in a duel")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorDuel:Begin()
--print( "BehaviorDuel:Begin()" )
if self.duelAbility and self.duelAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 500, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hAbilityModifier = hUnit:FindModifierByName( "modifier_legion_commander_duel" )
if hAbilityModifier ~= nil then
return nil
else
--print( "Casting Duel" )
thisEntity.bInDuel = true
local target = hUnit
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = self.duelAbility:entindex()
}
return order
end
end
end
end
end
return nil
end
BehaviorDuel.Continue = BehaviorDuel.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorOverwhelmingOdds, BehaviorBlademail, BehaviorDuel }

View File

@@ -0,0 +1,279 @@
--[[ Mirana AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorShootArrow, BehaviorStarfall, BehaviorRunAway } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorShootArrow = {}
function BehaviorShootArrow:Evaluate()
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.arrowAbility = thisEntity:FindAbilityByName( "mirana_arrow" )
self.target = nil
local bestDistance = 0
if not self.arrowAbility or not self.arrowAbility:IsFullyCastable() then
return desire
end
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), thisEntity, self.arrowAbility:GetCastRange(), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
if #enemies == 0 then
return desire
end
for _,enemy in pairs(enemies) do
local enemyVec = enemy:GetOrigin() - thisEntity:GetOrigin()
local myForward = thisEntity:GetForwardVector()
local dotProduct = enemyVec:Dot( myForward )
local enemyDesire = 5
if enemy:IsStunned() then
enemyDesire = 3
elseif dotProduct > 0 then
enemyDesire = 8
end
local distance = enemyVec:Length2D()
if distance > 350 then
if ( enemyDesire == desire and bestDistance > distance ) or ( desire < enemyDesire ) then
desire = enemyDesire
bestDistance = distance
self.target = enemy
thisEntity.lastTargetPosition = enemy:GetAbsOrigin()
end
end
end
return desire
end
function BehaviorShootArrow:Begin()
if self.target and self.target:IsAlive() then
local targetPoint = self.target:GetOrigin() + RandomVector( 100 )
if self.arrowAbility and self.arrowAbility:IsFullyCastable() then
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.arrowAbility:entindex(),
Position = targetPoint
}
end
end
return nil
end
BehaviorShootArrow.Continue = BehaviorShootArrow.Begin
--------------------------------------------------------------------------------------------------------
BehaviorStarfall = {}
function BehaviorStarfall:Evaluate()
--print( "BehaviorStarfall:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.starFallAbility = thisEntity:FindAbilityByName( "mirana_starfall" )
if self.starFallAbility and self.starFallAbility:IsFullyCastable() then
local nRange = self.starFallAbility:GetSpecialValueFor( "starfall_radius" )
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorStarfall:Begin()
--print( "BehaviorStarfall:Begin()" )
if self.starFallAbility and self.starFallAbility:IsFullyCastable() then
--print( "Casting Star Fall" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.starFallAbility:entindex()
}
return order
end
return nil
end
BehaviorStarfall.Continue = BehaviorStarfall.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
local retreatPoints = thisEntity.Encounter:GetRetreatPoints()
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return 0
end
-- let's not choose this twice in a row, or even too close to another escape
if behaviorSystem.currentBehavior == self or
( self.startEscapeTime ~= nil and ( ( GameRules:GetGameTime() - self.startEscapeTime ) < 6 ) ) then
return desire
end
local happyPlaceIndex = RandomInt( 1, #retreatPoints )
self.escapePoint = retreatPoints[ happyPlaceIndex ]:GetAbsOrigin()
self.leapAbility = thisEntity:FindAbilityByName( "mirana_leap" )
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
local friendlies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_HERO, 0, 0, false )
--print( string.format( "found %d enemies and %d friendlies near us", #enemies, #friendlies ) )
-- Remember that mirana herself will be in the friendlies list, so it's one too big
if ( #enemies >= 2 ) and ( #friendlies <= 1 ) then
desire = #enemies + 1
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
self.bHasLeaped = false
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_phase_boots" then
self.phaseAbility = item
end
end
-- phase right away
if self.phaseAbility and self.phaseAbility:IsFullyCastable() then
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.phaseAbility:entindex()
}
end
-- move towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 6 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- give ourselves time to turn towards escape point before leaping
if GameRules:GetGameTime() >= self.startEscapeTime + 0.6 and self.bHasLeaped == false then
if self.leapAbility and self.leapAbility:IsFullyCastable() then
-- Make sure we're not going to leap out of the room or into
-- somewhere not navigable
local vMyForward = thisEntity:GetForwardVector()
local vTargetPos = thisEntity:GetOrigin() + vMyForward * self.leapAbility:GetSpecialValueFor( "leap_distance" )
if thisEntity.Encounter:GetRoom():IsValidSpawnPoint( vTargetPos ) and
GridNav:CanFindPath( thisEntity:GetOrigin(), vTargetPos ) then
self.bHasLeaped = true
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.leapAbility:entindex()
}
end
end
end
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
BehaviorRunAway.Continue = BehaviorRunAway.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorShootArrow, BehaviorRunAway }

View File

@@ -0,0 +1,322 @@
--[[ Naga Siren AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.nIllusionsCreated = 0
thisEntity.nMaxIllusions = 10
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorMirrorImage, BehaviorEnsnare, BehaviorRipTide, BehaviorSong, BehaviorRunAway } )
-- Turn on Radiance
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_radiance" then
thisEntity.RadianceAbility = item
end
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.RadianceAbility:entindex(),
Queue = false,
})
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorMirrorImage = {}
function BehaviorMirrorImage:Evaluate()
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
if thisEntity.nIllusionsCreated == thisEntity.nMaxIllusions then
return desire
end
self.mirrorImageAbility = thisEntity:FindAbilityByName( "aghsfort_naga_siren_mirror_image" )
if not self.mirrorImageAbility or not self.mirrorImageAbility:IsFullyCastable() then
return desire
end
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), thisEntity, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
return desire
end
function BehaviorMirrorImage:Begin()
--print( "BehaviorMirrorImage:Begin()" )
if self.mirrorImageAbility and self.mirrorImageAbility:IsFullyCastable() then
thisEntity.nIllusionsCreated = thisEntity.nIllusionsCreated + 1
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.mirrorImageAbility:entindex(),
}
return order
end
return nil
end
BehaviorMirrorImage.Continue = BehaviorMirrorImage.Begin
--------------------------------------------------------------------------------------------------------
BehaviorEnsnare = {}
function BehaviorEnsnare:Evaluate()
--print( "BehaviorEnsnare:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.ensnareAbility = thisEntity:FindAbilityByName( "naga_siren_ensnare" )
if self.ensnareAbility and self.ensnareAbility:IsFullyCastable() then
local nRange = 600
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorEnsnare:Begin()
--print( "BehaviorEnsnare:Begin()" )
self.target = nil
local bestDistance = 0
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), thisEntity, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local hTarget = enemies[#enemies]
if not hTarget:IsStunned() then
if self.ensnareAbility and self.ensnareAbility:IsFullyCastable() then
--print( "Casting Star Fall" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = self.ensnareAbility:entindex(),
Queue = false,
}
return order
end
end
return nil
end
BehaviorEnsnare.Continue = BehaviorEnsnare.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRipTide = {}
function BehaviorRipTide:Evaluate()
--print( "BehaviorRipTide:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.ripTideAbility = thisEntity:FindAbilityByName( "naga_siren_rip_tide" )
if self.ripTideAbility and self.ripTideAbility:IsFullyCastable() then
local nRange = 300
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorRipTide:Begin()
--print( "BehaviorRipTide:Begin()" )
if self.ripTideAbility and self.ripTideAbility:IsFullyCastable() then
--print( "Casting Star Fall" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.ripTideAbility:entindex(),
}
return order
end
return nil
end
BehaviorRipTide.Continue = BehaviorRipTide.Begin
--------------------------------------------------------------------------------------------------------
BehaviorSong = {}
function BehaviorSong:Evaluate()
--print( "BehaviorSong:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.songAbility = thisEntity:FindAbilityByName( "naga_siren_song_of_the_siren" )
if self.songAbility and self.songAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 65 ) then
local nRange = 1000
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
return desire
end
end
return desire
end
function BehaviorSong:Begin()
--print( "BehaviorSong:Begin()" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_invulnerable", { duration = 1 } )
if self.songAbility and self.songAbility:IsFullyCastable() then
--print( "Casting Song of the Siren" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.songAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorSong.Continue = BehaviorSong.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
local retreatPoints = thisEntity.Encounter:GetRetreatPoints()
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return 0
end
local happyPlaceIndex = RandomInt( 1, #retreatPoints )
self.escapePoint = retreatPoints[ happyPlaceIndex ]:GetAbsOrigin()
local hSongModifier = thisEntity:FindModifierByName( "modifier_naga_siren_song_of_the_siren_aura" )
if hSongModifier ~= nil then
--print("Naga Siren is singing!")
desire = 5
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- move towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 6 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
BehaviorRunAway.Continue = BehaviorRunAway.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorMirrorImage, BehaviorEnsnare, BehaviorRipTide, BehaviorSong, BehaviorRunAway }

View File

@@ -0,0 +1,176 @@
--[[ Naga Siren Illusion AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.nIllusionsCreated = 0
thisEntity.nMaxIllusions = 10
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorRipTide, BehaviorRunAway } )
-- Turn on Radiance
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_radiance" then
thisEntity.RadianceAbility = item
end
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.RadianceAbility:entindex(),
Queue = false,
})
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRipTide = {}
function BehaviorRipTide:Evaluate()
--print( "BehaviorRipTide:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.ripTideAbility = thisEntity:FindAbilityByName( "naga_siren_rip_tide" )
if self.ripTideAbility and self.ripTideAbility:IsFullyCastable() then
local nRange = 300
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies > 1 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorRipTide:Begin()
--print( "BehaviorRipTide:Begin()" )
if self.ripTideAbility and self.ripTideAbility:IsFullyCastable() then
--print( "Casting Star Fall" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.ripTideAbility:entindex(),
}
return order
end
return nil
end
BehaviorRipTide.Continue = BehaviorRipTide.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
local retreatPoints = thisEntity.Encounter:GetRetreatPoints()
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return 0
end
local creatures = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false )
for _,hUnit in pairs( creatures ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hSongModifier = hUnit:FindModifierByName( "modifier_naga_siren_song_of_the_siren_aura" )
if hSongModifier ~= nil then
--print("Naga Siren is singing!")
thisEntity.vNagaPosition = hUnit:GetAbsOrigin()
desire = 8
end
end
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- Move towards Naga
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.vNagaPosition
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 6 ) ) or
( ( thisEntity:GetAbsOrigin() - thisEntity.vNagaPosition ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.vNagaPosition
}
end
BehaviorRunAway.Continue = BehaviorRunAway.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorRipTide, BehaviorRunAway }

View File

@@ -0,0 +1,116 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
IgniteAbility = thisEntity:FindAbilityByName( "ogre_magi_area_ignite" )
BloodlustAbility = thisEntity:FindAbilityByName( "ogre_magi_channelled_bloodlust" )
thisEntity:SetContextThink( "OgreMagiThink", OgreMagiThink, 1 )
end
--------------------------------------------------------------------------------
function OgreMagiThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if BloodlustAbility ~= nil and BloodlustAbility:IsChanneling() then
return 0.5
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
local bIgniteReady = ( #enemies > 0 and IgniteAbility ~= nil and IgniteAbility:IsFullyCastable() )
if BloodlustAbility ~= nil and BloodlustAbility:IsFullyCastable() then
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for _,friendly in pairs ( friendlies ) do
if friendly ~= nil then
if ( friendly:GetUnitName() == "npc_dota_creature_ogre_tank" ) then
local fDist = ( friendly:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local fCastRange = BloodlustAbility:GetCastRange( thisEntity:GetOrigin(), nil )
--print( string.format( "fDist == %d, fCastRange == %d", fDist, fCastRange ) )
if ( fDist <= fCastRange ) and ( ( #enemies > 0 ) or ( friendly:GetAggroTarget() ) ) then
return Bloodlust( friendly )
elseif ( fDist > 400 ) and ( fDist < 900 ) then
if bIgniteReady == false then
return Approach( friendly )
end
end
end
end
end
end
if bIgniteReady then
return IgniteArea( enemies[ RandomInt( 1, #enemies ) ] )
end
local fFuzz = RandomFloat( -0.1, 0.1 ) -- Adds some timing separation to these magi
return 0.5 + fFuzz
end
--------------------------------------------------------------------------------
function Approach( hUnit )
--print( "Ogre Magi is approaching unit named " .. hUnit:GetUnitName() )
local vToUnit = hUnit:GetOrigin() - thisEntity:GetOrigin()
vToUnit = vToUnit:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToUnit * thisEntity:GetIdealSpeed()
})
return 1
end
--------------------------------------------------------------------------------
function Bloodlust( hUnit )
--print( "Casting bloodlust on " .. hUnit:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = BloodlustAbility:entindex(),
TargetIndex = hUnit:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function IgniteArea( hEnemy )
--print( "Casting ignite on " .. hEnemy:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = IgniteAbility:entindex(),
Position = hEnemy:GetOrigin(),
Queue = false,
})
return 0.55
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,60 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hFlop = thisEntity:FindAbilityByName( "ogreseal_flop" )
thisEntity.flSearchRadius = 700
thisEntity:SetContextThink( "OgreSealThink", OgreSealThink, 0.5 )
end
--------------------------------------------------------------------------------
function OgreSealThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.flSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.5
end
if thisEntity.hFlop ~= nil and thisEntity.hFlop:IsFullyCastable() then
return CastBellyFlop( hEnemies[ #hEnemies ] )
end
return 0.5
end
--------------------------------------------------------------------------------
function CastBellyFlop( enemy )
local vToTarget = enemy:GetOrigin() - thisEntity:GetOrigin()
vToTarget = vToTarget:Normalized()
local vTargetPos = thisEntity:GetOrigin() + vToTarget * 50
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hFlop:entindex(),
Position = vTargetPos,
Queue = false,
})
return 4
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,123 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
IgniteAbility = thisEntity:FindAbilityByName( "ogre_seer_area_ignite" )
BloodlustAbility = thisEntity:FindAbilityByName( "ogre_magi_channelled_bloodlust" )
thisEntity:SetContextThink( "OgreSeerThink", OgreSeerThink, 1 )
end
--------------------------------------------------------------------------------
function OgreSeerThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if BloodlustAbility ~= nil and BloodlustAbility:IsChanneling() then
return 0.5
end
--[[
local bPrioritizeIgnite = ( RandomFloat( 0, 1 ) > 0.5 )
print( "bPrioritizeIgnite == " .. tostring( bPrioritizeIgnite ) )
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 800, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if bPrioritizeIgnite and #enemies > 0 and IgniteAbility ~= nil and IgniteAbility:IsFullyCastable() then
return IgniteArea( enemies[ RandomInt( 1, #enemies ) ] )
end
]]
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 800, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
local bIgniteReady = ( #enemies > 0 and IgniteAbility ~= nil and IgniteAbility:IsFullyCastable() )
if BloodlustAbility ~= nil and BloodlustAbility:IsFullyCastable() then
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for _,friendly in pairs ( friendlies ) do
if friendly ~= nil then
if ( friendly:GetUnitName() == "npc_dota_creature_ogre_tank" ) or ( friendly:GetUnitName() == "npc_dota_creature_ogre_tank_boss" ) then
local fDist = ( friendly:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local fCastRange = BloodlustAbility:GetCastRange( thisEntity:GetOrigin(), nil )
--print( string.format( "fDist == %d, fCastRange == %d", fDist, fCastRange ) )
if ( fDist <= fCastRange ) and ( ( #enemies > 0 ) or ( friendly:GetAggroTarget() ) ) then
return Bloodlust( friendly )
elseif ( fDist > 400 ) and ( fDist < 1500 ) and friendly:GetUnitName() == "npc_dota_creature_ogre_tank_boss" then
if bIgniteReady == false then
return Approach( friendly )
end
end
end
end
end
end
if bIgniteReady then
return IgniteArea( enemies[ RandomInt( 1, #enemies ) ] )
end
local fFuzz = RandomFloat( -0.1, 0.1 ) -- Adds some timing separation to these seers
return 0.5 + fFuzz
end
--------------------------------------------------------------------------------
function Approach( hUnit )
--print( "Ogre Magi is approaching unit named " .. hUnit:GetUnitName() )
local vToUnit = hUnit:GetOrigin() - thisEntity:GetOrigin()
vToUnit = vToUnit:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToUnit * thisEntity:GetIdealSpeed()
})
return 1
end
--------------------------------------------------------------------------------
function Bloodlust( hUnit )
--print( "Casting bloodlust on " .. hUnit:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = BloodlustAbility:entindex(),
TargetIndex = hUnit:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function IgniteArea( hEnemy )
--print( "Casting ignite" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = IgniteAbility:entindex(),
Position = hEnemy:GetOrigin(),
Queue = false,
})
return 0.55
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,100 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "ogre_tank_melee_smash" )
JumpAbility = thisEntity:FindAbilityByName( "ogre_tank_jump_smash" )
thisEntity:SetContextThink( "OgreTankThink", OgreTankThink, 1 )
end
function OgreTankThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
local hWintersCurseBuff = thisEntity:FindModifierByName( "modifier_aghsfort_winter_wyvern_winters_curse" )
if hWintersCurseBuff and hWintersCurseBuff:GetAuraOwner() ~= nil then
if SmashAbility ~= nil and SmashAbility:IsCooldownReady() then
return Smash( hWintersCurseBuff:GetAuraOwner() )
end
return 0.1
end
local nEnemiesRemoved = 0
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
for i = 1, #enemies do
local enemy = enemies[i]
if enemy ~= nil then
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 300 then
nEnemiesRemoved = nEnemiesRemoved + 1
table.remove( enemies, i )
end
end
end
if JumpAbility ~= nil and JumpAbility:IsFullyCastable() and nEnemiesRemoved > 0 then
return Jump()
end
if #enemies == 0 then
-- @todo: Could check whether there are ogre magi nearby that I should be positioning myself next to. Either that or have the magi come to me.
return 1
end
if SmashAbility ~= nil and SmashAbility:IsFullyCastable() then
return Smash( enemies[ 1 ] )
end
return 0.5
end
function Jump()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = JumpAbility:entindex(),
Queue = false,
})
return 2.5
end
function Smash( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = SmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 3 / thisEntity:GetHasteFactor()
end

View File

@@ -0,0 +1,108 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
SmashAbility = thisEntity:FindAbilityByName( "ogre_tank_boss_melee_smash" )
JumpAbility = thisEntity:FindAbilityByName( "ogre_tank_boss_jump_smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_attack_speed_unslowable", { attack_speed_reduction_pct = 0 } )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_move_speed_unslowable", { move_speed_reduction_pct = 20 } )
thisEntity:SetContextThink( "OgreTankBossThink", OgreTankBossThink, 1 )
end
--------------------------------------------------------------------------------
function OgreTankBossThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
local hWintersCurseBuff = thisEntity:FindModifierByName( "modifier_aghsfort_winter_wyvern_winters_curse" )
if hWintersCurseBuff and hWintersCurseBuff:GetAuraOwner() ~= nil then
if SmashAbility ~= nil and SmashAbility:IsCooldownReady() then
return Smash( hWintersCurseBuff:GetAuraOwner() )
end
return 0.1
end
local nEnemiesRemoved = 0
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1200, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
for i = 1, #enemies do
local enemy = enemies[i]
if enemy ~= nil then
local flDist = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist < 300 then
nEnemiesRemoved = nEnemiesRemoved + 1
table.remove( enemies, i )
end
end
end
if JumpAbility ~= nil and JumpAbility:IsFullyCastable() and nEnemiesRemoved > 0 then
return Jump()
end
if #enemies == 0 then
return 0.5
end
if SmashAbility ~= nil and SmashAbility:IsFullyCastable() then
return Smash( enemies[ 1 ] )
end
return 0.5
end
--------------------------------------------------------------------------------
function Jump()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = JumpAbility:entindex(),
Queue = false,
})
return 2.5
end
--------------------------------------------------------------------------------
function Smash( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = SmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 3 / thisEntity:GetHasteFactor()
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,134 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hIcarusDiveAbility = thisEntity:FindAbilityByName( "aghsfort_phoenix_icarus_dive" )
thisEntity.hIcarusDiveStopAbility = thisEntity:FindAbilityByName( "aghsfort_phoenix_icarus_dive_stop" )
thisEntity.hActivateSpiritsAbility = thisEntity:FindAbilityByName( "aghsfort_phoenix_fire_spirits" )
thisEntity.hLaunchSpiritsAbility = thisEntity:FindAbilityByName( "aghsfort_phoenix_launch_fire_spirit" )
thisEntity.hSupernovaAbility = thisEntity:FindAbilityByName( "aghsfort_phoenix_supernova" )
thisEntity.fDiveRange = 1400
thisEntity.fSupernovaRange = thisEntity.hSupernovaAbility:GetCastRange( thisEntity:GetOrigin(), nil )
thisEntity:SetContextThink( "PhoenixThink", PhoenixThink, 0.5 )
end
--------------------------------------------------------------------------------
function PhoenixThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hDiveRangeEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fDiveRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hDiveRangeEnemies == 0 then
-- thisEntity.fDiveRange is our max ability range so just don't think if there are no enemies within that range
return 0.1
end
local hRandomSpiritsTarget = hDiveRangeEnemies[ RandomInt( 1, #hDiveRangeEnemies ) ]
if thisEntity.hActivateSpiritsAbility then
if thisEntity.hActivateSpiritsAbility:IsHidden() == false and thisEntity.hActivateSpiritsAbility:IsFullyCastable() then
return CastActivateSpirits()
end
end
if thisEntity.hLaunchSpiritsAbility then
if thisEntity.hLaunchSpiritsAbility:IsHidden() == false and thisEntity.hLaunchSpiritsAbility:IsFullyCastable() then
return CastLaunchSpirits( hRandomSpiritsTarget:GetAbsOrigin() )
end
end
local fHealthThresholdPctIcarus = 90
if thisEntity:GetHealthPercent() <= fHealthThresholdPctIcarus and thisEntity.hIcarusDiveAbility and thisEntity.hIcarusDiveAbility:IsFullyCastable() then
local hRandomDiveTarget = hDiveRangeEnemies[ RandomInt( 1, #hDiveRangeEnemies ) ]
return CastIcarusDive( hRandomDiveTarget:GetAbsOrigin() )
end
local hSupernovaRangeEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fSupernovaRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hSupernovaRangeEnemies > 0 then
local fHealthThresholdPctSupernova = 33
if thisEntity:GetHealthPercent() <= fHealthThresholdPctSupernova and thisEntity.hSupernovaAbility and thisEntity.hSupernovaAbility:IsFullyCastable() then
local hNearestTarget = hSupernovaRangeEnemies[ 1 ]
return CastSupernova( hNearestTarget )
end
end
return 0.1
end
--------------------------------------------------------------------------------
function CastIcarusDive( vTargetPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hIcarusDiveAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
local fReturnTime = thisEntity.hIcarusDiveAbility:GetCastPoint() + 0.2
return fReturnTime
end
--------------------------------------------------------------------------------
function CastActivateSpirits( vTargetPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hActivateSpiritsAbility:entindex(),
Queue = false,
})
return 0.0
end
--------------------------------------------------------------------------------
function CastLaunchSpirits( vTargetPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hLaunchSpiritsAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
return 1.0
end
--------------------------------------------------------------------------------
function CastSupernova( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = thisEntity.hSupernovaAbility:entindex(),
TargetIndex = hTarget:entindex(),
Queue = false,
})
local fReturnTime = thisEntity.hSupernovaAbility:GetCastPoint() + 0.2
return fReturnTime
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,506 @@
--[[ ai/rhyzik.lua ]]
PHASE_PHYSICAL = 0
PHASE_SANDSTORM = 1
PHASE_EPICENTER = 2
TRIGGER_PHASE_CD = 10
PHASE_DURATION = 12
BURROW_DURATION = 12
MIN_BURROWSTRIKES = 4
MAX_BURROWSTRIKES = 7
function Spawn( entityKeyValues )
if IsServer() then
if thisEntity == nil then
return
end
TailSwipeLeft = thisEntity:FindAbilityByName( "sand_king_tail_swipe_left" )
TailSwipeRight = thisEntity:FindAbilityByName( "sand_king_tail_swipe_right" )
BurrowStrike = thisEntity:FindAbilityByName( "sand_king_boss_burrowstrike" )
BurrowDown = thisEntity:FindAbilityByName( "sand_king_boss_burrow" )
BurrowUp = thisEntity:FindAbilityByName( "sand_king_boss_unburrow" )
SandStorm = thisEntity:FindAbilityByName( "sand_king_boss_sandstorm" )
Epicenter = thisEntity:FindAbilityByName( "sand_king_boss_epicenter" )
ForwardTailSwipe = thisEntity:FindAbilityByName( "sand_king_burrowed_forward_strike" )
BackwardsTailSwipe = thisEntity:FindAbilityByName( "sand_king_burrowed_backward_strike" )
DirectionalMoveLeft = thisEntity:FindAbilityByName( "sand_king_boss_move_left" )
DirectionalMoveRight = thisEntity:FindAbilityByName( "sand_king_boss_move_right" )
DirectionalMoveBack = thisEntity:FindAbilityByName( "sand_king_boss_move_back" )
--thisEntity.PHASE = PHASE_PHYSICAL
thisEntity.PHASE = PHASE_SANDSTORM
Blink = thisEntity:FindItemInInventory( "item_blink" )
Shivas = thisEntity:FindItemInInventory( "item_shivas_guard" )
thisEntity:SetContextThink( "SandKingThink", SandKingThink, 1 )
thisEntity.flNextPhaseTime = nil
thisEntity.flPhaseTriggerEndTime = 0
thisEntity.bBurrowStateQueued = false
thisEntity.nBurrowStrikesRemaining = MIN_BURROWSTRIKES
thisEntity.nCurrentBurrowStrikes = MIN_BURROWSTRIKES
thisEntity.flUnburrowTime = 0
thisEntity.BurrowSkill = BurrowStrike
--thisEntity.fOrigSpawnPos = Vector( 11136, 12160, 384 ) -- Removed in AghsFort until we figure what this should mean
--print( string.format( "saved SK's spawn point: %.2f, %.2f, %.2f", thisEntity.fOrigSpawnPos.x, thisEntity.fOrigSpawnPos.y, thisEntity.fOrigSpawnPos.z ) )
end
end
function GetNumberBurrowStrikes()
local nHealthPct = thisEntity:GetHealthPercent()
if nHealthPct > 80 then
return MIN_BURROWSTRIKES
end
if nHealthPct > 70 then
return MIN_BURROWSTRIKES + 1
end
if nHealthPct > 50 then
return MIN_BURROWSTRIKES + 2
end
return MAX_BURROWSTRIKES
end
function TailIsReady()
if thisEntity:FindModifierByName( "modifier_sand_king_boss_burrow" ) == nil then
if TailSwipeLeft ~= nil and TailSwipeRight ~= nil and TailSwipeLeft:IsCooldownReady() and TailSwipeRight:IsCooldownReady() then
return true
end
else
if ForwardTailSwipe ~= nil and BackwardsTailSwipe ~= nil and ForwardTailSwipe:IsCooldownReady() and BackwardsTailSwipe:IsCooldownReady() then
return true
end
end
return false
end
function WalkIsReady()
if DirectionalMoveLeft ~= nil and DirectionalMoveRight ~= nil and DirectionalMoveBack ~= nil then
if DirectionalMoveLeft:IsCooldownReady() or DirectionalMoveRight:IsCooldownReady() or DirectionalMoveBack:IsCooldownReady() then
return true
end
end
return false
end
function ChangePhase()
if thisEntity.flNextPhaseTime > GameRules:GetGameTime() then
return false
end
if thisEntity:FindModifierByName( "modifier_sand_king_boss_burrow" ) ~= nil then
return 0.1
end
if thisEntity.bBurrowStateQueued == true then
return false
end
printf( "Changing Phase " .. GameRules:GetGameTime() )
if thisEntity.PHASE == PHASE_EPICENTER then
thisEntity.PHASE = PHASE_PHYSICAL
else
thisEntity.PHASE = thisEntity.PHASE + 1
end
return true
end
function SandKingThink()
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity == nil then
return 1
end
if thisEntity:GetTeamNumber() == DOTA_TEAM_GOODGUYS then
return 1
end
if thisEntity:IsChanneling() == true then
return 0.1
end
--[[ Removed in Aghanim's Fortress
if thisEntity.bStarted ~= true then
return 0.1
end
]]
--[[ Removed in Aghanim's Fortress until we figure out what fOriginalSpawnPos should mean
local fDistFromSpawn = ( thisEntity:GetOrigin() - thisEntity.fOrigSpawnPos ):Length2D()
if fDistFromSpawn > 7000 then
--print( "teleport SK back to his spawn point" )
FindClearSpaceForUnit( thisEntity, thisEntity.fOrigSpawnPos, true )
return 0.1
end
]]
local hEndCamera = Entities:FindByName( nil, "boss_camera" )
if hEndCamera ~= nil then
hEndCamera:SetAbsOrigin( thisEntity:GetAbsOrigin() )
end
if thisEntity.flNextPhaseTime == nil then
thisEntity.bBurrowStateQueued = true
thisEntity.flNextPhaseTime = GameRules:GetGameTime() + TRIGGER_PHASE_CD
-- print( "Setting inital phase time to " .. thisEntity.flNextPhaseTime )
end
if ChangePhase() then
-- print( "Changing Phase..")
thisEntity.flPhaseTriggerEndTime = GameRules:GetGameTime() + PHASE_DURATION
thisEntity.bBurrowStateQueued = true
if thisEntity.BurrowSkill == BurrowStrike then
thisEntity.BurrowSkill = BurrowDown
thisEntity.flUnburrowTime = GameRules:GetGameTime() + PHASE_DURATION + BURROW_DURATION
thisEntity.flNextPhaseTime = GameRules:GetGameTime() + TRIGGER_PHASE_CD + PHASE_DURATION + BURROW_DURATION
-- print( "Setting next phase time to " .. thisEntity.flNextPhaseTime )
else
thisEntity.BurrowSkill = BurrowStrike
thisEntity.nBurrowStrikesRemaining = GetNumberBurrowStrikes()
thisEntity.nCurrentBurrowStrikes = GetNumberBurrowStrikes()
thisEntity.flNextPhaseTime = GameRules:GetGameTime() + TRIGGER_PHASE_CD + PHASE_DURATION --Burrowstrikes adds to this time later after tracking the duration of strikes
-- print( "Setting next phase time to " .. thisEntity.flNextPhaseTime )
end
end
if thisEntity.flPhaseTriggerEndTime > GameRules:GetGameTime() then
if thisEntity.PHASE == PHASE_SANDSTORM then
return SandstormThink()
end
if thisEntity.PHASE == PHASE_EPICENTER then
return EpicenterThink()
end
end
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 3000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies == 0 then
return 1
end
if thisEntity.bBurrowStateQueued == true then
return BurrowThink( enemies )
end
return PhysicalThink( enemies )
end
function BurrowThink( enemies )
if thisEntity.BurrowSkill == BurrowDown then
if GameRules:GetGameTime() > thisEntity.flUnburrowTime then
thisEntity.bBurrowStateQueued = false
return CastBurrowUp()
end
if thisEntity:FindModifierByName( "modifier_sand_king_boss_burrow" ) == nil then
return CastBurrowDown()
else
return PhysicalThink( enemies )
end
end
if thisEntity.BurrowSkill == BurrowStrike then
if thisEntity:FindModifierByName( "modifier_sand_king_boss_burrowstrike" ) ~= nil or thisEntity:FindModifierByName( "modifier_sand_king_boss_burrowstrike_end" ) ~= nil then
return 0.1
end
if thisEntity.nBurrowStrikesRemaining == thisEntity.nCurrentBurrowStrikes then
thisEntity.flBurrowStrikesStartTime = GameRules:GetGameTime()
end
if thisEntity.nBurrowStrikesRemaining <= 0 then
thisEntity.bBurrowStateQueued = false
thisEntity.flNextPhaseTime = thisEntity.flNextPhaseTime + ( GameRules:GetGameTime() - thisEntity.flBurrowStrikesStartTime )
return 0.1
end
thisEntity.nBurrowStrikesRemaining = thisEntity.nBurrowStrikesRemaining - 1
for _, enemy in pairs ( enemies ) do
if enemy ~= nil and enemy:IsInvulnerable() == false then
return CastBurrowstrike( enemy )
end
end
end
return 1
end
function CastBurrowstrike( enemy )
--print( "SandKingBoss - Cast Burrowstrike" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = BurrowStrike:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 2.5
end
function CastBurrowDown()
--print( "SandKingBoss - Cast BurrowDown" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = BurrowDown:entindex(),
Queue = false,
})
return 2.5
end
function CastBurrowUp()
--print( "SandKingBoss - Cast BurrowUp" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = BurrowUp:entindex(),
Queue = false,
})
return 2.5
end
function SandstormThink()
return CastSandstorm()
end
function CastSandstorm()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = SandStorm:entindex()
})
return 1.0
end
function EpicenterThink()
return CastEpicenter()
end
function CastEpicenter()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = Epicenter:entindex()
})
return 1.0
end
function PhysicalThink( enemies )
if thisEntity:FindModifierByName( "modifier_sand_king_boss_directional_move" ) then
return 0.2
end
local vDirection = thisEntity:GetForwardVector()
local vRight = thisEntity:GetRightVector()
local vLeft = -vRight
local flQuadrantDistance = 200
local bBurrowed = thisEntity:FindModifierByName( "modifier_sand_king_boss_burrow" ) ~= nil
local vFrontRightQuadrant = thisEntity:GetOrigin() + (( vDirection + vRight ) * flQuadrantDistance )
local vFrontLeftQuadrant = thisEntity:GetOrigin() + (( vDirection + vLeft ) * flQuadrantDistance )
local vBackRightQuadrant = thisEntity:GetOrigin() + (( -vDirection + vRight ) * flQuadrantDistance )
local vBackLeftQuadrant = thisEntity:GetOrigin() + (( -vDirection + vLeft ) * flQuadrantDistance )
local frontRightEnemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, vFrontRightQuadrant, enemies[1], 450, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local frontLeftEnemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, vFrontLeftQuadrant, enemies[1], 450, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local backRightEnemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, vBackRightQuadrant, enemies[1], 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local backLeftEnemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, vBackLeftQuadrant, enemies[1], 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
local bEnemiesBehind = true
if #backRightEnemies == 0 and #backLeftEnemies == 0 then
bEnemiesBehind = false
end
local bEnemiesInFront = true
if #frontRightEnemies == 0 and #frontLeftEnemies == 0 then
bEnemiesInFront = false
end
if bBurrowed then
return CastForwardsTailSwipe( enemies[1] )
end
if bBurrowed then
return 1
end
if Blink ~= nil and Blink:IsFullyCastable() then
--print ( "Use Blink" )
return CastBlink( enemies[#enemies]:GetOrigin() )
else
if Blink == nil then
--print ( "blink is nil" )
Blink = thisEntity:FindItemInInventory( "item_blink" )
end
end
if Shivas ~= nil and Shivas:IsFullyCastable() then
return CastShivas()
else
if Shivas == nil then
--print ( "shivas is nil" )
Shivas = thisEntity:FindItemInInventory( "item_shivas_guard" )
end
end
if bEnemiesBehind == true and TailIsReady() then
if #backLeftEnemies > 0 and #backRightEnemies > 0 then
local nRoll = RandomInt( 0, 1 )
if nRoll == 0 then
return CastTailSwipeLeft()
else
return CastTailSwipeRight()
end
else
if #backLeftEnemies > 0 then
return CastTailSwipeLeft()
end
if #backRightEnemies > 0 then
return CastTailSwipeRight()
end
end
end
if TailIsReady() == false and WalkIsReady() == true and RandomInt( 0, 3 ) == 0 then
if enemies[1] ~= nil then
local flDist = ( enemies[1]:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
if flDist <= 200 and DirectionalMoveBack:IsCooldownReady() then
return MoveBack( enemies[1] )
else
return MoveSideWays( enemies[1] )
end
end
end
return Attack( enemies[1] )
end
function CastBlink( vPos )
--print ( "Use Blink" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vPos,
AbilityIndex = Blink:entindex()
})
return 0.5
end
function CastShivas()
--print ( "Use Shivas" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = Shivas:entindex()
})
return 0.25
end
function Attack( enemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = enemy:GetOrigin(),
})
return 2.0
end
function MoveBack( enemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = DirectionalMoveBack:entindex(),
})
return 1.0
end
function MoveSideWays( enemy )
local Ability = DirectionalMoveRight
if RandomInt( 0, 1 ) == 1 then
Ability = DirectionalMoveLeft
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = Ability:entindex(),
})
return 1.0
end
function CastClawAttack( enemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = ClawAttack:entindex(),
})
return 1.1
end
function CastTailSwipeLeft()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = TailSwipeLeft:entindex()
})
return 2.5
end
function CastTailSwipeRight()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = TailSwipeRight:entindex()
})
return 2.5
end
function CastBackwardsTailSwipe()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = BackwardsTailSwipe:entindex()
})
return 2.5
end
function CastForwardsTailSwipe( enemy )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = ForwardTailSwipe:entindex(),
})
return 2.5
end

View File

@@ -0,0 +1,147 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.SpikedCarapaceAbility = thisEntity:FindAbilityByName( "aghsfort_creature_spiked_carapace" )
thisEntity.SummonZealotsAbility = thisEntity:FindAbilityByName( "scarab_priest_summon_zealots" )
thisEntity.nLastHealth = thisEntity:GetHealth()
thisEntity.bAcqRangeModified = false
thisEntity.hEntityKilledGameEvent = ListenToGameEvent( "entity_killed", Dynamic_Wrap( thisEntity:GetPrivateScriptScope(), 'OnEntityKilled' ), nil )
thisEntity:SetContextThink( "ScarabPriestThink", ScarabPriestThink, 1 )
end
function UpdateOnRemove()
StopListeningToGameEvent( thisEntity.hEntityKilledGameEvent )
end
function ScarabPriestThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 850 )
thisEntity.bAcqRangeModified = true
end
local nHealth = thisEntity:GetHealth()
if thisEntity.nLastHealth > nHealth then
if thisEntity.SpikedCarapaceAbility ~= nil and thisEntity.SpikedCarapaceAbility:IsFullyCastable() then
return SpikedCarapace( )
end
thisEntity.nLastHealth = nHealth
end
if thisEntity.SummonZealotsAbility ~= nil and thisEntity.SummonZealotsAbility:IsFullyCastable() == true and
thisEntity.bAcqRangeModified == true then
-- Only spawn zealots if we haven't got too many already
local nMyZealotCount = thisEntity.SummonZealotsAbility:GetSpecialValueFor( "max_summons" )
local hZealots = thisEntity.Encounter:GetSpawnedUnitsOfType( "npc_dota_creature_zealot_scarab" )
for i=1,#hZealots do
if hZealots[i]:GetOwnerEntity() == thisEntity then
nMyZealotCount = nMyZealotCount - 1
if nMyZealotCount == 0 then
break
end
end
end
if nMyZealotCount > 0 then
return SummonZealots()
end
end
return 0.5
end
function OnEntityKilled( event )
local hVictim = nil
if event.entindex_killed ~= nil then
hVictim = EntIndexToHScript( event.entindex_killed )
end
if hVictim ~= thisEntity then
return
end
-- Check all of the other priests, and see if any others are aggroed.
-- If not, then we'll force aggro on the closest one
local flNearDist = 60000
local hNearPriest = nil
local hPriests = thisEntity.Encounter:GetSpawnedUnitsOfType( "npc_dota_creature_scarab_priest" )
for i=1,#hPriests do
if hPriests[i] ~= thisEntity then
if hPriests[i].bAcqRangeModified then
hNearPriest = nil
break
end
local flDist = ( hPriests[i]:GetAbsOrigin() - hVictim:GetAbsOrigin() ):Length2D()
if flDist < flNearDist then
flNearDist = flDist
hNearPriest = hPriests[i]
end
end
end
if hNearPriest == nil then
return
end
hNearPriest:SetDayTimeVisionRange( 5000 )
hNearPriest:SetNightTimeVisionRange( 5000 )
hNearPriest:SetAcquisitionRange( 5000 )
hNearPriest.bAcqRangeModified = true
-- Order nearby zealots idle unonwned to start attacking also
local hZealots = thisEntity.Encounter:GetSpawnedUnitsOfType( "npc_dota_creature_zealot_scarab" )
for i=1,#hZealots do
local hZealot = hZealots[i]
if hZealot:GetOwnerEntity() == nil and hZealot:GetAggroTarget() == nil and ( hNearPriest:GetAbsOrigin() - hZealot:GetAbsOrigin() ):Length2D() < 800 then
hZealot:SetDayTimeVisionRange( 5000 )
hZealot:SetNightTimeVisionRange( 5000 )
hZealot:SetAcquisitionRange( 5000 )
end
end
end
function SpikedCarapace()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.SpikedCarapaceAbility:entindex(),
Queue = false,
})
return 0.5
end
function SummonZealots( )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.SummonZealotsAbility:entindex(),
Queue = false,
})
return 0.5
end

View File

@@ -0,0 +1,92 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.ImpaleAbility = thisEntity:FindAbilityByName( "aghsfort_creature_impale" )
thisEntity:SetContextThink( "ScarabZealotThink", ScarabZealotThink, 1 )
end
function LastTargetImpaleTime( hTarget )
local flLastTime = thisEntity.Encounter.ImpaleTargets[ tostring( hTarget:entindex() ) ]
if flLastTime == nil then
flLastTime = 0
end
return flLastTime
end
function ScarabZealotThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity.Encounter.ImpaleTargets == nil then
thisEntity.Encounter.ImpaleTargets = {}
end
if thisEntity.ImpaleAbility ~= nil and thisEntity.ImpaleAbility:IsFullyCastable() then
-- Select a unit in range to attack
-- And don't pick a target who was impaled at in the last second
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil,
thisEntity.ImpaleAbility:GetCastRange(), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, 0, false )
local hPossibleEnemies = {}
for _,hEnemy in pairs( enemies ) do
local flTimeSinceLastImpale = GameRules:GetGameTime() - LastTargetImpaleTime( hEnemy )
if hEnemy ~= nil and hEnemy:IsAlive() and hEnemy:IsInvulnerable() == false and
hEnemy:IsMagicImmune() == false and ( flTimeSinceLastImpale > 0.5 ) then
table.insert( hPossibleEnemies, hEnemy )
end
end
-- Pick a random one, but prefer one who is at least 500 away
local hNearEnemy = nil
while #hPossibleEnemies > 0 do
local nIndex = math.random( 1, #hPossibleEnemies )
local hEnemy = hPossibleEnemies[ nIndex ]
if ( ( hEnemy:GetAbsOrigin() - thisEntity:GetAbsOrigin() ):Length2D() > 500 ) then
return Impale( hEnemy )
end
hNearEnemy = hEnemy
table.remove( hPossibleEnemies, nIndex )
end
-- If not, then pick a close one
if hNearEnemy ~= nil then
return Impale( hNearEnemy )
end
end
return 0.1
end
function Impale( hTarget )
local vTargetPos = hTarget:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) )
thisEntity.Encounter.ImpaleTargets[ tostring( hTarget:entindex() ) ] = GameRules:GetGameTime()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.ImpaleAbility:entindex(),
Position = vTargetPos,
Queue = false,
})
return 0.5
end

View File

@@ -0,0 +1,584 @@
--------------------------------------------------------------------------------
function GetEnemyHeroesInRange( hUnit , flRange )
if flRange == nil then
flRange = 1250
end
local enemies = FindUnitsInRadius( hUnit:GetTeamNumber(), hUnit:GetAbsOrigin(), nil, flRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
return enemies
end
--------------------------------------------------------------------------------
function GetVisibleEnemyHeroesInRange( hUnit , flRange )
if flRange == nil then
flRange = 1250
end
local enemies = FindUnitsInRadius( hUnit:GetTeamNumber(), hUnit:GetAbsOrigin(), nil, flRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
return enemies
end
--------------------------------------------------------------------------------
function GetNoteworthyVisibleEnemiesNearby( hUnit, flRange )
if flRange == nil then
flRange = 1250
end
local eTARGETED_UNITS = DOTA_UNIT_TARGET_CREEP + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_HERO
local enemies = FindUnitsInRadius( hUnit:GetTeamNumber(), hUnit:GetAbsOrigin(), nil, flRange, DOTA_UNIT_TARGET_TEAM_ENEMY, eTARGETED_UNITS, DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for index, hEnemy in pairs( enemies ) do
if hEnemy:IsRealHero() == false and hEnemy:IsConsideredHero() == false and hEnemy:IsIllusion() == false then
-- Remove the most unimportant units
table.remove( enemies, index )
end
end
return enemies
end
--------------------------------------------------------------------------------
function FilterEntitiesOutsideOfRange( vPosition, hEntitiesTable, flRange )
if hEntitiesTable == nil or #hEntitiesTable == 0 then
return {}
end
for n=#hEntitiesTable,1,-1 do
local hEntity = hEntitiesTable[ n ]
if hEntity ~= nil then
local fDist = ( hEntity:GetOrigin() - vPosition ):Length2D()
if fDist > flRange then
table.remove( hEntitiesTable, n )
end
end
end
return hEntitiesTable
end
--------------------------------------------------------------------------------
-- General Utility Fucntions --
--------------------------------------------------------------------------------
function AdvancedRandomVector( MaxLength, MinLength, vAwayFromVec, vAwayFromDist )
local vCandidate = nil
repeat
vCandidate = RandomVector(1):Normalized()*RandomFloat(MinLength,MaxLength)
until (vAwayFromVec == nil or vAwayFromDist == nil) or ( (vAwayFromVec - vCandidate):Length2D() > vAwayFromDist )
return vCandidate
end
function MoveOrder( hUnit, vPos )
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vPos,
})
return 1.0
end
function StopOrder( hUnit )
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP,
})
end
--------------------------------------------------------------------------------
function AttackMoveOrder( hAttacker, hEnemy )
if (not hAttacker or not hEnemy or not hAttacker:HasAttackCapability() ) then
return 0.25
end
ExecuteOrderFromTable({
UnitIndex = hAttacker:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = hEnemy:GetOrigin(),
})
return 1.0
end
--------------------------------------------------------------------------------
function AttackTargetOrder( hAttacker, hEnemy )
if ( not hAttacker or not hEnemy or not hAttacker:HasAttackCapability() ) then
return 0.25
end
--printf( "AttackTargetOrder -- attacker: %s, target: %s", hAttacker:GetUnitName(), hEnemy:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = hAttacker:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hEnemy:entindex(),
})
return 1.0
end
--------------------------------------------------------------------------------
function SprintIsReady( hCaster )
local hAbility = hCaster.abilities.hSprint
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastSprint( hCaster )
local hAbility = hCaster.abilities.hSprint
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 5.0
end
--------------------------------------------------------------------------------
function TailIsReady( hCaster )
local hAbility = hCaster.abilities.hTailSpinCCW
if hCaster.bEnemiesInFront and hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastTailSpinCCW( hCaster )
local hAbility = hCaster.abilities.hTailSpinCCW
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex()
})
return 5.0
end
--------------------------------------------------------------------------------
function MindControlIsReady( hCaster, hEnemies )
local nEnemiesAlive = 0
for _, hEnemy in pairs( hEnemies ) do
if hEnemy:IsAlive() then
nEnemiesAlive = nEnemiesAlive + 1
end
end
if nEnemiesAlive <= 1 then
return false
end
local hAbility = hCaster.abilities.hMindControl
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastMindControl( hCaster, hEnemies )
local hAbility = hCaster.abilities.hMindControl
local hTarget = hEnemies[ RandomInt( 1, #hEnemies ) ]
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 4.0
end
--------------------------------------------------------------------------------
function TorrentsIsReady( hCaster )
local hAbility = hCaster.abilities.hTorrents
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastTorrents( hCaster )
local hAbility = hCaster.abilities.hTorrents
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 10
end
--------------------------------------------------------------------------------
function LineWaveIsReady( hCaster )
local hAbility = hCaster.abilities.hLineWave
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastLineWave( hCaster, hEnemies )
local hAbility = hCaster.abilities.hLineWave
local hTarget = hEnemies[ RandomInt( 1, #hEnemies ) ]
local fDist = -1
local fHighestDist = 0
local hFarthestEnemy = nil
for _, hEnemy in pairs( hEnemies ) do
fDist = ( hEnemy:GetOrigin() - hCaster:GetOrigin() ):Length2D()
if fDist > fHighestDist then
fHighestDist = fDist
hFarthestEnemy = hEnemy
end
end
if hFarthestEnemy == nil then
return 0.5
end
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hFarthestEnemy:entindex(),
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 4
end
--------------------------------------------------------------------------------
function WavesIsReady( hCaster )
local hAbility = hCaster.abilities.hWaves
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastWaves( hCaster )
local hAbility = hCaster.abilities.hWaves
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 14
end
--------------------------------------------------------------------------------
function SummonMinionsIsReady( hCaster )
local hAbility = hCaster.abilities.hSummonMinions
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastSummonMinions( hCaster )
local hAbility = hCaster.abilities.hSummonMinions
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 7
end
--------------------------------------------------------------------------------
function SummonMinionsMediumIsReady( hCaster )
local hAbility = hCaster.abilities.hSummonMinionsMedium
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastSummonMinionsMedium( hCaster )
local hAbility = hCaster.abilities.hSummonMinionsMedium
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 7
end
--------------------------------------------------------------------------------
function SummonCloneIsReady( hCaster )
local hAbility = hCaster.abilities.hSummonClone
if hAbility ~= nil and hAbility:IsFullyCastable() and hCaster.nSummonCloneCasts < ( hCaster.Phase - 1 ) then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastSummonClone( hCaster )
local hAbility = hCaster.abilities.hSummonClone
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 2
end
--------------------------------------------------------------------------------
function MouthBeamIsReady( hCaster )
local hAbility = hCaster.abilities.hMouthBeam
if hAbility ~= nil and hAbility:IsFullyCastable() then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastMouthBeam( hCaster, hEnemies )
local hAbility = hCaster.abilities.hMouthBeam
local hTarget = hEnemies[ RandomInt( 1, #hEnemies ) ]
if hTarget == nil then
return 1
end
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = hTarget:GetOrigin(),
AbilityIndex = hAbility:entindex(),
Queue = false,
})
return 10.0
end
--------------------------------------------------------------------------------
function FindItemAbility( hCaster, szItemName )
for i = 0, 5 do
local item = hCaster:GetItemInSlot( i )
if item then
if item:GetAbilityName() == szItemName then
return item
end
end
end
end
--------------------------------------------------------------------------------
function CastTargetedItem( hUnit, hTarget, szItemName )
local hItem = hUnit:FindItemInInventory( szItemName )
return CastTargetedAbility( hUnit, hTarget, hItem )
end
--------------------------------------------------------------------------------
function CastUntargetedItem( hUnit, szItemName )
local hItem = hUnit:FindItemInInventory( szItemName )
return CastUntargetedAbility( hUnit, hItem )
end
--------------------------------------------------------------------------------
function CastPositionalItem( hUnit, vPosition, szItemName )
local hItem = hUnit:FindItemInInventory( szItemName )
return CastPositionalAbility( hUnit, vPosition, hItem )
end
--------------------------------------------------------------------------------
function CastTargetedAbility( hUnit, hTarget, hAbility )
if type(hAbility) == "string" then
hAbility = hUnit:FindAbilityByName(hAbility)
printf("result of string ability is %s", hAbility:GetName())
end
if hAbility and hAbility:IsFullyCastable() then
--printf( "%s casting %s on %s", hUnit:GetName(), hAbility:GetName(), hTarget:GetName() )
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = hAbility:entindex()
})
return true
end
return false
end
--------------------------------------------------------------------------------
function CastPositionalAbility( hUnit, vPosition, hAbility )
if type(hAbility) == "string" then
hAbility = hUnit:FindAbilityByName(hAbility)
end
if hAbility and hAbility:IsFullyCastable() then
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vPosition,
AbilityIndex = hAbility:entindex()
})
return true
end
return false
end
--------------------------------------------------------------------------------
function CastUntargetedAbility( hUnit, hAbility )
if type(hAbility) == "string" then
hAbility = hUnit:FindAbilityByName(hAbility)
end
if hAbility and hAbility:IsFullyCastable() then
ExecuteOrderFromTable({
UnitIndex = hUnit:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = hAbility:entindex()
})
return true
end
return false
end
--------------------------------------------------------------------------------
function GetAlliesInRange( hCaster, flRange )
return FindUnitsInRadius( hCaster:GetTeamNumber(), hCaster:GetOrigin(), nil, flRange,
DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_OTHER,
DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
end
function LastAllyCastTime( hCaster, hAbility, flRange, hTarget )
if flRange == nil then
flRange = 1000
end
local flLastCastTime = -999999
local hAllies = GetAlliesInRange( hCaster, flRange )
for index,hAlly in pairs(hAllies) do
local flAllyTime = hAlly["last_cast_time_"..hAbility:GetName()]
local hAllyTarget = hAlly["last_cast_target_"..hAbility:GetName()]
if flAllyTime ~= nil and (hTarget == nil or hTarget == hAllyTarget) then
flLastCastTime = math.max(flAllyTime,flLastCastTime)
end
end
return flLastCastTime
end
--------------------------------------------------------------------------------
function UpdateLastCastTime( hCaster, hAbility, hTarget )
hCaster["last_cast_time_"..hAbility:GetName()] = GameRules:GetGameTime()
hCaster["last_cast_target_"..hAbility:GetName()] = hTarget
end
--------------------------------------------------------------------------------
function TeleportIsReady( hCaster )
if ( GameRules:GetGameTime() > ( hCaster.fLastTeleportChain + hCaster.nTeleportChainCD ) ) then
hCaster.nRecentTeleportsCast = 0
hCaster.fLastTeleportChain = GameRules:GetGameTime()
end
local hAbility = hCaster.abilities.hTeleport
if hAbility ~= nil and hAbility:IsFullyCastable() and ( hCaster.nRecentTeleportsCast < hCaster.nTeleportsToChain ) then
return true
end
return false
end
--------------------------------------------------------------------------------
function CastTeleport( hCaster, hEnemies )
local hAbility = hCaster.abilities.hTeleport
local hFilteredEnemies = {}
for _, hEnemy in pairs( hEnemies ) do
local fDist = ( hEnemy:GetOrigin() - hCaster:GetOrigin() ):Length2D()
if fDist > 500 then
table.insert( hFilteredEnemies, hEnemy )
end
end
local hTarget = nil
if #hFilteredEnemies > 0 then
hTarget = hFilteredEnemies[ RandomInt( 1, #hFilteredEnemies ) ]
else
hTarget = hEnemies[ RandomInt( 1, #hEnemies ) ]
end
local vTargetPos = hTarget:GetOrigin()
ExecuteOrderFromTable({
UnitIndex = hCaster:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vTargetPos,
AbilityIndex = hAbility:entindex(),
Queue = false,
})
hCaster.nRecentTeleportsCast = hCaster.nRecentTeleportsCast + 1
if ( hCaster.nRecentTeleportsCast >= hCaster.nTeleportsToChain ) then
return 4.0
end
return 1.8
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,73 @@
--[[ Sheep AI ]]
local bHasBeenSaved = false
local hPortal = nil
local vPortalPos = nil
local nHoldTime = 0
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
hPortal = Entities:FindByName( nil, "portal_path_track" )
vPortalPos = hPortal:GetOrigin()
thisEntity:SetContextThink( "SheepThink", SheepThink, 1 )
end
--------------------------------------------------------------------------------
function SheepThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
if bHasBeenSaved == false then
if hPortal ~= nil then
thisEntity:MoveToPosition( vPortalPos )
local vPos = thisEntity:GetOrigin()
local difference = vPos - vPortalPos
local distance = difference:Length()
if distance < 100 then
--print("Sheep has been saved!")
thisEntity:Hold()
local szPortalFX = "particles/econ/events/league_teleport_2014/teleport_start_league.vpcf"
local hPortalEffects = ParticleManager:CreateParticle( szPortalFX, PATTACH_CUSTOMORIGIN_FOLLOW, thisEntity )
ParticleManager:SetParticleControlEnt( hPortalEffects, 0, thisEntity, PATTACH_POINT_FOLLOW, "attach_hitloc", thisEntity:GetAbsOrigin(), false )
thisEntity:Attribute_SetIntValue( "effectsID", hPortalEffects )
bHasBeenSaved = true
EmitSoundOn("Creature.Sheep.Portal_Start", thisEntity)
end
end
else
if nHoldTime < 3 then
nHoldTime = nHoldTime + 1
--thisEntity:SetModelScale(nModelScale)
else
StopSoundOn("Creature.Sheep.Portal_Start", thisEntity)
EmitSoundOn("Creature.Sheep.Portal_End", thisEntity)
local hBase = Entities:FindByName( nil, "a1_1a_teamspawn_left" )
local vTargetPoint = hBase:GetOrigin()
thisEntity:SetAbsOrigin( vTargetPoint )
thisEntity:ForceKill(false)
end
end
return 1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,91 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hHellfireBlast = thisEntity:FindAbilityByName( "undead_woods_skeleton_king_hellfire_blast" )
thisEntity.hMortalStrike = thisEntity:FindAbilityByName( "undead_woods_skeleton_king_mortal_strike" )
thisEntity.bInitialSkeletons = false
thisEntity:SetContextThink( "SkeletonKingThink", SkeletonKingThink, 1.0 )
end
--------------------------------------------------------------------------------
function SkeletonKingThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 1
end
if thisEntity.hHellfireBlast and thisEntity.hHellfireBlast:IsFullyCastable() then
return CastHellFireBlast( hEnemies[#hEnemies] )
end
if thisEntity.hMortalStrike then
local hBuff = thisEntity:FindModifierByName( "modifier_skeleton_king_mortal_strike" )
if hBuff ~= nil then
local nMaxSkeletons = thisEntity.hMortalStrike:GetSpecialValueFor( "max_skeleton_charges" )
if thisEntity.bInitialSkeletons == false then
hBuff:SetStackCount( nMaxSkeletons )
thisEntity.bInitialSkeletons = true
return CastMortalStrike()
end
if hBuff:GetStackCount() < nMaxSkeletons then
hBuff:IncrementStackCount()
elseif thisEntity.hMortalStrike:IsFullyCastable() then
return CastMortalStrike()
end
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastHellFireBlast( hTarget )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = thisEntity.hHellfireBlast:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function CastMortalStrike( )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hMortalStrike:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,123 @@
--[[ Skywrath Mage AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorMysticFlare } )
end
function AIThink()
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorMysticFlare = {}
function BehaviorMysticFlare:Evaluate()
--print( "BehaviorMysticFlare:Evaluate()" )
local desire = 0
self.mysticFlareAbility = thisEntity:FindAbilityByName( "skywrath_mage_mystic_flare" )
if self.mysticFlareAbility and self.mysticFlareAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hAbilityModifier = hUnit:FindModifierByName( "modifier_skywrath_mage_mystic_flare" )
local hDuelModifier = hUnit:FindModifierByName( "modifier_legion_commander_duel" )
if hAbilityModifier ~= nil then
--print("Enemy is being Mystic Flared")
desire = 0
elseif hDuelModifier ~= nil then
--print("Enemy is in a Duel")
desire = 8
else
desire = #enemies + 8
end
end
end
end
end
return desire
end
function BehaviorMysticFlare:Begin()
--print( "BehaviorMysticFlare:Begin()" )
if self.mysticFlareAbility and self.mysticFlareAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 800, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hAbilityhModifier = hUnit:FindModifierByName( "modifier_legion_commander_duel" )
if hAbilityhModifier ~= nil then
--print( "Casting Mystic Flare" )
local target = hUnit
local targetPoint = target:GetAbsOrigin()
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = self.mysticFlareAbility:entindex(),
Position = targetPoint,
}
return order
else
return nil
end
end
end
end
end
return nil
end
BehaviorMysticFlare.Continue = BehaviorMysticFlare.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorMysticFlare }

View File

@@ -0,0 +1,298 @@
--[[ Slark Peon AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity.vNagaPosition = nil
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorDarkPact, BehaviorPounce, BehaviorShadowDance, BehaviorRunAway } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorDarkPact = {}
function BehaviorDarkPact:Evaluate()
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.darkPactAbility = thisEntity:FindAbilityByName( "slark_dark_pact" )
if not self.darkPactAbility or not self.darkPactAbility:IsFullyCastable() then
return desire
end
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 400, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hDarkPactModifier = hUnit:FindModifierByName( "modifier_slark_dark_pact" )
if hDarkPactModifier ~= nil then
--print("Enemy is already Dark Pacted")
desire = 0
else
local nRandomInt = RandomInt(1,5)
if nRandomInt == 1 then
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorDarkPact:Begin()
--print( "BehaviorDarkPact:Begin()" )
if self.darkPactAbility and self.darkPactAbility:IsFullyCastable() then
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.darkPactAbility:entindex(),
}
return order
end
return nil
end
BehaviorDarkPact.Continue = BehaviorDarkPact.Begin
--------------------------------------------------------------------------------------------------------
BehaviorPounce = {}
function BehaviorPounce:Evaluate()
--print( "BehaviorPounce:Evaluate()" )
local desire = 0
self.pounceAbility = thisEntity:FindAbilityByName( "slark_pounce" )
if self.pounceAbility and self.pounceAbility:IsFullyCastable() then
local hGoal = Entities:FindByName( nil, "slark_room_center" )
local vGoalPos = hGoal:GetOrigin()
local goalDifference = vGoalPos - thisEntity:GetOrigin()
local goalDistance = goalDifference:Length()
if goalDistance > 1500 then
--print("Too close to edge of room")
return desire
end
local nRange = 700
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
for _,enemy in pairs(enemies) do
local enemyVec = enemy:GetOrigin() - thisEntity:GetOrigin()
local myForward = thisEntity:GetForwardVector()
local dotProduct = enemyVec:Dot( myForward )
local enemyDesire = 0
if dotProduct > 0 then
enemyDesire = 8
end
local distance = enemyVec:Length2D()
if distance > 400 then
desire = enemyDesire
self.target = enemy
end
end
end
return desire
end
function BehaviorPounce:Begin()
--print( "BehaviorPounce:Begin()" )
if self.target and self.target:IsAlive() then
if self.pounceAbility and self.pounceAbility:IsFullyCastable() then
--print( "Casting Star Fall" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.pounceAbility:entindex(),
Queue = false,
}
return order
end
end
return nil
end
BehaviorPounce.Continue = BehaviorPounce.Begin
--------------------------------------------------------------------------------------------------------
BehaviorShadowDance = {}
function BehaviorShadowDance:Evaluate()
--print( "BehaviorShadowDance:Evaluate()" )
local desire = 0
local retreatPoints = thisEntity.Encounter:GetRetreatPoints()
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return 0
end
local happyPlaceIndex = RandomInt( 1, #retreatPoints )
self.escapePoint = retreatPoints[ happyPlaceIndex ]:GetAbsOrigin()
self.shadowDanceAbility = thisEntity:FindAbilityByName( "slark_shadow_dance" )
local nRange = 300
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if self.shadowDanceAbility and self.shadowDanceAbility:IsFullyCastable() then
if ( thisEntity:GetHealthPercent() < 25 ) and ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorShadowDance:Begin()
--print( "BehaviorShadowDance:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
if self.shadowDanceAbility and self.shadowDanceAbility:IsFullyCastable() then
--print( "Casting Shadow Dance" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.shadowDanceAbility:entindex(),
}
return order
end
return nil
end
function BehaviorShadowDance:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 6 ) ) or
( ( thisEntity:GetAbsOrigin() - self.escapePoint ):Length2D() < 200 )
end
function BehaviorShadowDance:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.escapePoint
}
end
BehaviorShadowDance.Continue = BehaviorShadowDance.Begin
--------------------------------------------------------------------------------------------------------
BehaviorRunAway = {}
function BehaviorRunAway:Evaluate()
local desire = 0
local retreatPoints = thisEntity.Encounter:GetRetreatPoints()
if retreatPoints == nil then
print( "*** WARNING: This AI requires info_targets named retreat_point in the map " .. thisEntity.Encounter:GetRoom():GetName() )
return 0
end
local creatures = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, FIND_UNITS_EVERYWHERE, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false )
for _,hUnit in pairs( creatures ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hSongModifier = hUnit:FindModifierByName( "modifier_naga_siren_song_of_the_siren_aura" )
if hSongModifier ~= nil then
--print("Naga Siren is singing!")
thisEntity.vNagaPosition = hUnit:GetAbsOrigin()
desire = 8
end
end
end
return desire
end
function BehaviorRunAway:Begin()
--print( "BehaviorRunAway:Begin()" )
self.startEscapeTime = GameRules:GetGameTime()
-- Move towards Naga
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.vNagaPosition
}
end
function BehaviorRunAway:IsDone( )
return ( GameRules:GetGameTime() > ( self.startEscapeTime + 6 ) ) or
( ( thisEntity:GetAbsOrigin() - thisEntity.vNagaPosition ):Length2D() < 200 )
end
function BehaviorRunAway:Think( )
-- keep moving towards our escape point
return
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.vNagaPosition
}
end
BehaviorRunAway.Continue = BehaviorRunAway.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorDarkPact, BehaviorPounce, BehaviorShadowDance, BehaviorRunAway }

View File

@@ -0,0 +1,77 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity:SetContextThink( "SmallHellbearThink", SmallHellbearThink, 1 )
end
--------------------------------------------------------------------------------
function SmallHellbearThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity.bInitialized then
thisEntity.vInitialSpawnPos = thisEntity:GetOrigin()
thisEntity.bInitialized = true
end
-- Increase acquisition range after the initial aggro
if ( not thisEntity.bAcqRangeModified ) and thisEntity:GetAggroTarget() then
thisEntity:SetAcquisitionRange( 750 )
thisEntity.bAcqRangeModified = true
end
if thisEntity:GetAggroTarget() then
thisEntity.fTimeWeLostAggro = nil
end
if thisEntity:GetAggroTarget() and ( thisEntity.fTimeAggroStarted == nil ) then
--print( "Do we have aggro and need to get a timestamp?" )
thisEntity.fTimeAggroStarted = GameRules:GetGameTime()
end
if ( not thisEntity:GetAggroTarget() ) and ( thisEntity.fTimeAggroStarted ~= nil ) then
--print( "We lost aggro." )
thisEntity.fTimeWeLostAggro = GameRules:GetGameTime()
thisEntity.fTimeAggroStarted = nil
end
if ( not thisEntity:GetAggroTarget() ) then
if thisEntity.fTimeWeLostAggro and ( GameRules:GetGameTime() > ( thisEntity.fTimeWeLostAggro + 1.0 ) ) then
return RetreatHome()
end
end
return 0.5
end
--------------------------------------------------------------------------------
function RetreatHome()
--print( "RetreatHome - " .. thisEntity:GetUnitName() .. " is returning to home position" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity.vInitialSpawnPos -- + RandomVector( RandomFloat( -300, 300 ) )
})
return 0.5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,111 @@
require( "ai/shared" )
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hTombstoneAbility = thisEntity:FindAbilityByName( "undead_tusk_mage_tombstone" )
thisEntity.hTombstones = {}
thisEntity.nMyMaxTombstones = 2
thisEntity.nMaxTombstonesInArea = 4
thisEntity.fSearchRadius = thisEntity:GetAcquisitionRange() + 200
thisEntity:SetContextThink( "UndeadSpectralTuskMageThink", UndeadSpectralTuskMageThink, 0.5 )
end
--------------------------------------------------------------------------------
function UndeadSpectralTuskMageThink()
if not IsServer() then
return
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
if ( not thisEntity:GetAggroTarget() ) then
return 1.0
end
-- Are we currently holding aggro?
if ( not thisEntity.bHasAggro ) and thisEntity:GetAggroTarget() then
thisEntity.timeOfLastAggro = GameRules:GetGameTime()
thisEntity.bHasAggro = true
elseif thisEntity.bHasAggro and ( not thisEntity:GetAggroTarget() ) then
thisEntity.bHasAggro = false
end
if ( not thisEntity.bHasAggro ) then
return 1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 1
end
if thisEntity.hTombstoneAbility ~= nil and thisEntity.hTombstoneAbility:IsFullyCastable() then
for k, hTombstone in pairs( thisEntity.hTombstones ) do
if hTombstone == nil or hTombstone:IsNull() or hTombstone:IsAlive() == false then
table.remove( thisEntity.hTombstones, k )
end
end
local nTombstonesAround = 0
local nFlags = DOTA_UNIT_TARGET_FLAG_NONE
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1000, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, nFlags, FIND_CLOSEST, false )
for _, friendly in pairs ( friendlies ) do
if friendly ~= nil and friendly:GetUnitName() == "npc_dota_undead_tusk_tombstone" then
nTombstonesAround = nTombstonesAround + 1
end
end
if ( #friendlies < 80 ) and ( nTombstonesAround < thisEntity.nMaxTombstonesInArea ) and ( #thisEntity.hTombstones < thisEntity.nMyMaxTombstones ) then
local fNow = GameRules:GetGameTime()
local flLastAllyCastTime = LastAllyCastTime( thisEntity, thisEntity.hTombstoneAbility, 1000, nil )
local fCastPoint = thisEntity.hTombstoneAbility:GetCastPoint()
if ( fNow - flLastAllyCastTime ) > ( fCastPoint + 0.1 ) then
local vCastLocation = thisEntity:GetAbsOrigin() + ( thisEntity:GetForwardVector() * 300 )
if GridNav:CanFindPath( thisEntity:GetAbsOrigin(), vCastLocation ) then
UpdateLastCastTime( thisEntity, thisEntity.hTombstoneAbility, nil )
return CastTombstone( vCastLocation )
end
end
end
end
return 0.5
end
--------------------------------------------------------------------------------
function CastTombstone( vPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vPos,
AbilityIndex = thisEntity.hTombstoneAbility:entindex(),
Queue = false,
})
return 1.0
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,56 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hDispersionAbility = thisEntity:FindAbilityByName( "aghsfort_spectre_active_dispersion" )
thisEntity:SetContextThink( "SpectreThink", SpectreThink, 0.5 )
end
--------------------------------------------------------------------------------
function SpectreThink()
if not IsServer() then
return
end
if thisEntity == nil or thisEntity:IsNull() or ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 1
end
if thisEntity.hDispersionAbility and thisEntity.hDispersionAbility:IsFullyCastable() then
return CastDispersion()
end
return 0.5
end
--------------------------------------------------------------------------------
function CastDispersion()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hDispersionAbility:entindex(),
Queue = false,
})
return 0.5
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,271 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity:AddNewModifier( nil, nil, "modifier_invulnerable", { duration = -1 } )
SlamAbility = thisEntity:FindAbilityByName( "storegga_arm_slam" )
GrabAbility = thisEntity:FindAbilityByName( "storegga_grab" )
ThrowAbility = thisEntity:FindAbilityByName( "storegga_grab_throw" )
GroundPoundAbility = thisEntity:FindAbilityByName( "storegga_ground_pound" )
AvalancheAbility = thisEntity:FindAbilityByName( "storegga_avalanche" )
thisEntity.flThrowTimer = 0.0 -- externally updated
thisEntity.fLongWaitTime = 5
thisEntity.fEnemySearchRange = 2500
thisEntity.fRockSearchRange = 2500
thisEntity:SetContextThink( "StoreggaThink", StoreggaThink, 1 )
end
--------------------------------------------------------------------------------
function StoreggaThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
if ( not thisEntity.bInitialInvulnRemoved ) then
thisEntity:RemoveModifierByName( "modifier_invulnerable" )
thisEntity.bInitialInvulnRemoved = true
end
if thisEntity:IsChanneling() then
return 0.1
end
if AvalancheAbility ~= nil and AvalancheAbility:IsFullyCastable() and thisEntity:GetHealthPercent() < 60 then
return CastAvalanche()
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fEnemySearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, FIND_CLOSEST, false )
local rocks = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fRockSearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_CREEP, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, FIND_CLOSEST, false )
local nEnemiesAliveInRange = 0
for i = 1, #enemies do
local enemy = enemies[ i ]
if enemy ~= nil then
if enemy:IsRealHero() and enemy:IsAlive() then
nEnemiesAliveInRange = nEnemiesAliveInRange + 1
if enemy:FindModifierByName( "modifier_storegga_grabbed_debuff" ) ~= nil then
--printf( "removed %s from enemies table", enemy:GetUnitName() )
table.remove( enemies, i )
end
end
end
end
local hNearestEnemy = enemies[ 1 ]
local hFarthestEnemy = enemies[ #enemies ]
local hGrabbedEnemyBuff = thisEntity:FindModifierByName( "modifier_storegga_grabbed_buff" )
local hGrabbedTarget = nil
if hGrabbedEnemyBuff == nil then
if GrabAbility ~= nil and GrabAbility:IsFullyCastable() then
if hNearestEnemy ~= nil and nEnemiesAliveInRange > 1 and RandomInt( 0, 1 ) == 0 then
printf( " Grab the nearest enemy (%s)", hNearestEnemy:GetUnitName() )
return CastGrab( hNearestEnemy )
elseif #rocks > 0 then
local hRandomRock = rocks[ RandomInt( 1, #rocks ) ]
if hRandomRock ~= nil then
printf( " Grab a random rock" )
return CastGrab( hRandomRock )
end
end
end
else
-- Note: hThrowObject and flThrowTimer are both set by the modifier
local hGrabbedTarget = hGrabbedEnemyBuff.hThrowObject
if GameRules:GetGameTime() > thisEntity.flThrowTimer and hGrabbedTarget ~= nil then
if ThrowAbility ~= nil and ThrowAbility:IsFullyCastable() then
if hFarthestEnemy ~= nil then
printf( " Throw at the farthest enemy; pos: %s", hFarthestEnemy:GetOrigin() )
return CastThrow( hFarthestEnemy:GetOrigin() )
elseif #rocks > 0 then
local hFarthestRock = rocks[ #rocks ]
if hFarthestRock ~= nil then
printf( " Throw at the farthest.. rock?; pos: %s", hFarthestRock:GetOrigin() )
return CastThrow( hFarthestRock:GetOrigin() )
end
elseif GameRules:GetGameTime() > ( thisEntity.flThrowTimer + thisEntity.fLongWaitTime ) then
printf( " a lot of time has passed and we're still holding onto an object, so just throw it somewhere pathable" )
-- If I've been waiting too long, throw grabbed object at some random location
local nMaxDistance = 1400
local vRandomThrowPos = nil
local nMaxAttempts = 7
local nAttempts = 0
repeat
if nAttempts > nMaxAttempts then
vRandomThrowPos = nil
printf( "WARNING - storegga - failed to find valid position for throw target pos" )
break
end
local vPos = thisEntity:GetAbsOrigin() + RandomVector( nMaxDistance )
vRandomThrowPos = FindPathablePositionNearby( vPos, 0, 500 )
nAttempts = nAttempts + 1
until ( GridNav:CanFindPath( thisEntity:GetOrigin(), vRandomThrowPos ) )
--until ( GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vRandomThrowPos ) )
if vRandomThrowPos == nil then
printf( " never found a good random pos to throw to, so just use my own origin" )
vRandomThrowPos = thisEntity:GetAbsOrigin()
end
return CastThrow( vRandomThrowPos )
end
end
end
end
if GroundPoundAbility and GroundPoundAbility:IsFullyCastable() then
local fGroundPoundSearchRadius = 1000
local hGroundPoundEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, fGroundPoundSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, FIND_CLOSEST, false )
if #hGroundPoundEnemies > 0 then
return CastGroundPound()
end
end
if SlamAbility ~= nil and SlamAbility:IsFullyCastable() then
if RandomInt( 0, 1 ) == 1 then
if hNearestEnemy ~= nil then
--printf( "Slam the nearest enemy (%s)", hNearestEnemy:GetUnitName() )
return CastSlam( hNearestEnemy )
end
else
if hFarthestEnemy ~= nil then
--printf( "Slam the farthest enemy (%s)", hFarthestEnemy:GetUnitName() )
return CastSlam( hFarthestEnemy )
end
end
end
return 0.1
end
--------------------------------------------------------------------------------
function CastSlam( enemy )
if enemy == nil or enemy:IsAlive() == false then
return 0.1
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = SlamAbility:entindex(),
})
local fInterval = SlamAbility:GetCastPoint() + 0.1
-- Enemies may run away from us so we don't want to try to calculate exactly what our return interval should
-- be (e.g. based on the distance and our movespeed), so for larger distances we're just adding a little extra time
local fDistToEnemy = ( enemy:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local fNearDistance = 800
local fMediumDistance = 1300
if fDistToEnemy > fMediumDistance then
printf( " enemy is beyond medium distance" )
fInterval = fInterval + 1.0
elseif fDistToEnemy > fNearDistance then
printf( " enemy is beyond near distance" )
fInterval = fInterval + 0.5
end
return fInterval
--return 1.2
end
--------------------------------------------------------------------------------
function CastGrab( enemy )
if enemy == nil or enemy:IsAlive() == false then
return 0.1
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = enemy:entindex(),
AbilityIndex = GrabAbility:entindex(),
})
local fInterval = GrabAbility:GetCastPoint() + 0.1
return fInterval
--return 1.5
end
--------------------------------------------------------------------------------
function CastThrow( vPos )
local vDir = vPos - thisEntity:GetOrigin()
local flDist = vDir:Length2D()
vDir.z = 0.0
vDir = vDir:Normalized()
local vFinalPos = vPos
if flDist < 200 then
-- The target's too close and we don't want to throw the unit into our feet, so throw it forward instead
vFinalPos = thisEntity:GetOrigin() + vDir * flDist
end
printf( "Casting throw at %s", vPos )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
Position = vFinalPos,
--Position = thisEntity:GetOrigin() + vDir * flDist,
AbilityIndex = ThrowAbility:entindex(),
Queue = false,
})
local fInterval = ThrowAbility:GetCastPoint() + 0.1
return fInterval
--return 1.5
end
--------------------------------------------------------------------------------
function CastAvalanche()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = AvalancheAbility:entindex(),
Queue = false,
})
local fInterval = AvalancheAbility:GetCastPoint() + AvalancheAbility:GetChannelTime() + 0.1
return fInterval
--return 11
end
--------------------------------------------------------------------------------
function CastGroundPound()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = GroundPoundAbility:entindex(),
Queue = false,
})
local fInterval = GroundPoundAbility:GetCastPoint() + GroundPoundAbility:GetChannelTime() + 0.1
return fInterval
--return 4.0
end

View File

@@ -0,0 +1,219 @@
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.bIsEnraged = false
thisEntity.nRageHealthPct = 30
thisEntity.bBrotherDied = false
thisEntity.Encounter = nil
HammerSmashAbility = thisEntity:FindAbilityByName( "temple_guardian_hammer_smash" )
HammerThrowAbility = thisEntity:FindAbilityByName( "temple_guardian_hammer_throw" )
PurificationAbility = thisEntity:FindAbilityByName( "temple_guardian_purification" )
WrathAbility = thisEntity:FindAbilityByName( "temple_guardian_wrath" )
RageHammerSmashAbility = thisEntity:FindAbilityByName( "temple_guardian_rage_hammer_smash" )
RageHammerSmashAbility:SetHidden( false )
thisEntity:SetContextThink( "TempleGuardianThink", TempleGuardianThink, 1 )
end
--------------------------------------------------------------------------------
function TempleGuardianThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if thisEntity.Encounter == nil then
return 1
end
if GameRules:IsGamePaused() == true then
return 0.1
end
if thisEntity:IsChanneling() == true then
return 0.1
end
local hWintersCurseBuff = thisEntity:FindModifierByName( "modifier_aghsfort_winter_wyvern_winters_curse" )
if hWintersCurseBuff and hWintersCurseBuff:GetAuraOwner() ~= nil then
if not thisEntity.bIsEnraged then
if HammerSmashAbility ~= nil and HammerSmashAbility:IsCooldownReady() then
return Smash( hWintersCurseBuff:GetAuraOwner() )
end
else
if RageHammerSmashAbility ~= nil and RageHammerSmashAbility:IsFullyCastable() then
return RageSmash( hWintersCurseBuff:GetAuraOwner() )
end
end
return 0.1
end
if ( not thisEntity.bIsEnraged ) and ( thisEntity:GetHealthPercent() <= thisEntity.nRageHealthPct ) then
thisEntity.bIsEnraged = true
thisEntity:SwapAbilities( "temple_guardian_hammer_smash", "temple_guardian_rage_hammer_smash", false, true )
--printf( "thisEntity.bIsEnraged: %s", tostring( thisEntity.bIsEnraged ) )
end
local hCreatures = Entities:FindAllByClassnameWithin( "npc_dota_creature", thisEntity:GetAbsOrigin(), 2000 )
local hGuardians = {}
for _, hCreature in pairs( hCreatures ) do
if ( hCreature:GetUnitName() == "npc_dota_creature_temple_guardian" ) and hCreature:IsAlive() then
table.insert( hGuardians, hCreature )
end
end
if #hGuardians == 1 and ( not thisEntity.bBrotherDied ) then
thisEntity.bBrotherDied = true
thisEntity.fTimeBrotherDied = GameRules:GetGameTime()
thisEntity.bIsEnraged = true
end
if WrathAbility ~= nil and WrathAbility:IsCooldownReady() and #hGuardians == 1 and thisEntity:GetHealthPercent() <= 100 then
local fTimeBeforeWrath = 3
if thisEntity.fTimeBrotherDied and ( GameRules:GetGameTime() > ( thisEntity.fTimeBrotherDied + fTimeBeforeWrath ) ) then
return Wrath()
end
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity:GetAcquisitionRange(), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
if #hEnemies == 0 then
return 0.1
end
if HammerThrowAbility ~= nil and HammerThrowAbility:IsCooldownReady() and thisEntity:GetHealthPercent() < 90 then
local hFarthestEnemy = hEnemies[ #hEnemies ]
if hFarthestEnemy ~= nil then
local flDist = (hFarthestEnemy:GetOrigin() - thisEntity:GetOrigin()):Length2D()
if flDist > 300 then
return Throw( hFarthestEnemy )
end
end
end
for _, hGuardian in pairs( hGuardians ) do
if hGuardian ~= nil and hGuardian:IsAlive() and ( hGuardian ~= thisEntity or #hGuardians == 1 ) and ( hGuardian:GetHealthPercent() < 80 ) and PurificationAbility ~= nil and PurificationAbility:IsFullyCastable() and #hEnemies >= 1 then
return Purification( hGuardian )
end
end
if not thisEntity.bIsEnraged then
if HammerSmashAbility ~= nil and HammerSmashAbility:IsCooldownReady() then
local hNearestEnemy = hEnemies[ 1 ]
return Smash( hNearestEnemy )
end
else
if RageHammerSmashAbility ~= nil and RageHammerSmashAbility:IsFullyCastable() then
local hNearestEnemy = hEnemies[ 1 ]
return RageSmash( hNearestEnemy )
end
end
return 0.1
end
--------------------------------------------------------------------------------
function Wrath()
--print( "temple_guardian - Wrath" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = WrathAbility:entindex(),
Queue = false,
})
local fReturnTime = WrathAbility:GetCastPoint() + WrathAbility:GetChannelTime() + 0.5
--printf( "Wrath - return in %.2f", fReturnTime )
return fReturnTime
--return 8
end
--------------------------------------------------------------------------------
function Throw( enemy )
--print( "temple_guardian - Throw" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = HammerThrowAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
local fReturnTime = HammerThrowAbility:GetCastPoint() + 1.8
--printf( "Throw - return in %.2f", fReturnTime )
return fReturnTime
--return 3
end
--------------------------------------------------------------------------------
function Purification( friendly )
--print( "temple_guardian - Purification" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = PurificationAbility:entindex(),
TargetIndex = friendly:entindex(),
Queue = false,
})
local fReturnTime = PurificationAbility:GetCastPoint() + 0.4
--printf( "Purification - return in %.2f", fReturnTime )
return fReturnTime
--return 1.3
end
--------------------------------------------------------------------------------
function Smash( enemy )
--printf( "temple_guardian - Smash" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = HammerSmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
local fReturnTime = HammerSmashAbility:GetCastPoint() + 0.4
--printf( "Smash - return in %.2f", fReturnTime )
return fReturnTime
--return 1.4
end
--------------------------------------------------------------------------------
function RageSmash( enemy )
--printf( "temple_guardian - RageSmash" )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = RageHammerSmashAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
local fReturnTime = RageHammerSmashAbility:GetCastPoint() + 0.4
--printf( "RageSmash - return in %.2f", fReturnTime )
return fReturnTime
--return 1.1
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,95 @@
--[[ Tidehunter AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorAnchorSmash } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1500 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorAnchorSmash = {}
function BehaviorAnchorSmash:Evaluate()
--print( "BehaviorAnchorSmash:Evaluate()" )
local desire = 0
self.anchorSmashAbility = thisEntity:FindAbilityByName( "tidehunter_anchor_smash" )
if self.anchorSmashAbility and self.anchorSmashAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 350, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 1 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorAnchorSmash:Begin()
--print( "BehaviorAnchorSmash:Begin()" )
if self.anchorSmashAbility and self.anchorSmashAbility:IsFullyCastable() then
--print( "Casting Anchor Smash" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.anchorSmashAbility:entindex()
}
return order
end
return nil
end
BehaviorAnchorSmash.Continue = BehaviorAnchorSmash.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorAnchorSmash }

View File

@@ -0,0 +1,193 @@
--[[ Tidehunter Miniboss AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorGush, BehaviorAnchorSmash } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1500 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorGush = {}
function BehaviorGush:Evaluate()
--print( "BehaviorGush:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.gushAbility = thisEntity:FindAbilityByName( "creature_tidehunter_gush" )
if self.gushAbility and self.gushAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 600, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
for _,hUnit in pairs( enemies ) do
if hUnit ~= nil and hUnit:IsAlive() then
local hGushModifier = hUnit:FindModifierByName( "modifier_tidehunter_gush" )
if hGushModifier ~= nil then
--print("Enemy is already gushed")
desire = 0
else
desire = #enemies + 1
end
end
end
end
end
return desire
end
function BehaviorGush:Begin()
--print( "BehaviorGush:Begin()" )
if self.gushAbility and self.gushAbility:IsFullyCastable() then
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies == 0 then
return nil
end
local hTarget = hEnemies[#hEnemies]
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = hTarget:entindex(),
AbilityIndex = self.gushAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorGush.Continue = BehaviorGush.Begin
--------------------------------------------------------------------------------------------------------
BehaviorAnchorSmash = {}
function BehaviorAnchorSmash:Evaluate()
--print( "BehaviorAnchorSmash:Evaluate()" )
local desire = 0
self.anchorSmashAbility = thisEntity:FindAbilityByName( "tidehunter_anchor_smash" )
if self.anchorSmashAbility and self.anchorSmashAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 350, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorAnchorSmash:Begin()
--print( "BehaviorAnchorSmash:Begin()" )
if self.anchorSmashAbility and self.anchorSmashAbility:IsFullyCastable() then
--print( "Casting Anchor Smash" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.anchorSmashAbility:entindex()
}
return order
end
return nil
end
BehaviorAnchorSmash.Continue = BehaviorAnchorSmash.Begin
--------------------------------------------------------------------------------------------------------
--[[ Ravage now placed in passive modifier
BehaviorRavage = {}
function BehaviorRavage:Evaluate()
--print( "BehaviorRavage:Evaluate()" )
local desire = 0
self.ravageAbility = thisEntity:FindAbilityByName( "tidehunter_ravage" )
if self.ravageAbility and self.ravageAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 1000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 75 ) then
if ( #enemies > 1 ) then
desire = #enemies + 1
end
end
end
return desire
end
function BehaviorRavage:Begin()
--print( "BehaviorRavage:Begin()" )
if self.ravageAbility and self.ravageAbility:IsFullyCastable() then
--print( "Casting Ravage" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.ravageAbility:entindex()
}
return order
end
return nil
end
BehaviorRavage.Continue = BehaviorRavage.Begin
]]
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorGush, BehaviorAnchorSmash }

View File

@@ -0,0 +1,276 @@
require( "ai/ai_core" )
function Precache( context )
PrecacheResource( "particle", "particles/neutral_fx/harpy_chain_lightning_head.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_dark_seer/dark_seer_surge.vpcf", context )
PrecacheResource( "particle", "particles/neutral_fx/harpy_chain_lightning.vpcf", context )
end
function Spawn( entityKeyValues )
thisEntity.hSurgeAbility = thisEntity:FindAbilityByName( "tornado_harpy_intrinsic" )
thisEntity.PreviousHealthPct = thisEntity:GetHealthPercent()
thisEntity.PreviousHealthPctGameTime = GameRules:GetGameTime()
thisEntity:SetContextThink( "TornadoHarpyThink", TornadoHarpyThink, 0.5 )
end
--------------------------------------------------------------------------------
function TornadoHarpyThink()
if not IsServer() then
return
end
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 0.5
end
--check our health pct every 4 seconds
if GameRules:GetGameTime() > thisEntity.PreviousHealthPctGameTime + 4 then
thisEntity.PreviousHealthPctGameTime = GameRules:GetGameTime()
thisEntity.PreviousHealthPct = thisEntity:GetHealthPercent()
end
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 5000, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #hEnemies > 0 then
for i = 1, #hEnemies do
thisEntity.avoidTarget = hEnemies[i]
if thisEntity.avoidTarget ~= nil then
if thisEntity:GetHealthPercent() < thisEntity.PreviousHealthPct then
return Retreat(thisEntity.avoidTarget )
else
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = thisEntity.avoidTarget:entindex(),
Queue = false
})
end
break
end
end
end
return 0.5
end
--------------------------------------------------------------------------------
function Retreat(unit)
local vAwayFromEnemy = thisEntity:GetOrigin() - unit:GetOrigin()
vAwayFromEnemy = vAwayFromEnemy:Normalized()
local vMoveToPos = thisEntity:GetOrigin() + vAwayFromEnemy * thisEntity:GetIdealSpeed()*4
-- if away from enemy is an unpathable area, find a new direction to run to
local nAttempts = 0
while ( ( not GridNav:CanFindPath( thisEntity:GetOrigin(), vMoveToPos ) ) and ( nAttempts < 5 ) ) do
vMoveToPos = thisEntity:GetOrigin() + RandomVector( thisEntity:GetIdealSpeed() * 4 )
nAttempts = nAttempts + 1
end
thisEntity.fTimeOfLastRetreat = GameRules:GetGameTime()
thisEntity:AddNewModifier(thisEntity, thisEntity.hSurgeAbility, "modifier_tornado_harpy_surge", {duration = 2.0})
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = vMoveToPos,
})
return 2.5
end
--[[
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
-- print( "BehaviorNone:Begin()" )
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
for i=1,6 do
local vLoc = FindPathablePositionNearby(hTarget:GetAbsOrigin(), 650, 650 )
--and thisEntity:IsMoving() == false
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
--orders =
--{
-- UnitIndex = thisEntity:entindex(),
-- OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
-- Position = vLoc,
-- Queue = true,
--}
break
end
end
elseif thisEntity.lastTargetPosition ~= nil then
for i=1,6 do
local vLoc = FindPathablePositionNearby(thisEntity.lastTargetPosition, 650, 650 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
--orders =
--{
-- UnitIndex = thisEntity:entindex(),
-- OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
-- Position = vLoc
--}
break
end
end
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorAttack = {}
function BehaviorAttack:Evaluate()
local desire = 6
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.chainLightningAbility = thisEntity:FindAbilityByName( "harpy_storm_chain_lightning" )
if self.chainLightningAbility and self.chainLightningAbility:IsFullyCastable() then
self.target = AICore:ClosestEnemyHeroInRange( thisEntity, thisEntity:GetDayTimeVisionRange() )
if self.target ~= nil and self.target:IsAlive() then
desire = 6
end
end
return desire
end
function BehaviorAttack:Begin()
-- print( "BehaviorAttack:Begin()" )
--if self.chainLightningAbility and self.chainLightningAbility:IsFullyCastable() then
if self.target and self.target:IsAlive() then
--print( "Casting Chain Lightning" )
local targetPoint = self.target:GetAbsOrigin() + RandomVector( 100 )
thisEntity.lastTargetPosition = self.target:GetAbsOrigin()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
--AbilityIndex = self.chainLightningAbility:entindex(),
TargetIndex = self.target:entindex(),
Queue = false
})
end
--end
return nil
end
BehaviorAttack.Continue = BehaviorAttack.Begin
--------------------------------------------------------------------------------------------------------
BehaviorAvoid = {}
function BehaviorAvoid:Evaluate()
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
--don't even think about running if you can damage!
if self.chainLightningAbility and self.chainLightningAbility:IsFullyCastable() then
return desire
end
--check our health pct every 5 seconds
if GameRules:GetGameTime() > thisEntity.PreviousHealthPctGameTime + 5 then
printf("reseting healthpct")
thisEntity.PreviousHealthPctGameTime = GameRules:GetGameTime()
thisEntity.PreviousHealthPct = thisEntity:GetHealthPercent()
end
self.avoidTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 650 )
if self.avoidTarget ~= nil then
desire = 4
end
if thisEntity:GetHealthPercent() < thisEntity.PreviousHealthPct then
printf("I'm hurt")
desire = 8
end
return desire
end
function BehaviorAvoid:Begin()
print( "BehaviorAvoid:Begin()" )
for i=1,6 do
self.vEscapeLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 350, 350 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( self.vEscapeLoc ) and ( self.vEscapeLoc - self.avoidTarget:GetAbsOrigin() ):Length2D() > 650 then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.vEscapeLoc
})
printf("function BehaviorAvoid: Executing order()")
break
end
end
return nil
end
function BehaviorAvoid:Think(dt)
-- keep moving towards our escape point
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = self.vEscapeLoc
})
return nil
end
BehaviorAvoid.Continue = BehaviorAvoid.Begin ]]--

View File

@@ -0,0 +1,182 @@
--[[ Troll Warlord Melee AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_troll_warlord_berserkers_rage", {} )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorWhirlingAxes, BehaviorBattleTrance, BehaviorBKB } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorWhirlingAxes = {}
function BehaviorWhirlingAxes:Evaluate()
--print( "BehaviorWhirlingAxes:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.whirlingAxesAbility = thisEntity:FindAbilityByName( "troll_warlord_whirling_axes_melee" )
if self.whirlingAxesAbility and self.whirlingAxesAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 450, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorWhirlingAxes:Begin()
--print( "BehaviorWhirlingAxes:Begin()" )
if self.whirlingAxesAbility and self.whirlingAxesAbility:IsFullyCastable() then
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.whirlingAxesAbility:entindex(),
}
return order
end
return nil
end
BehaviorWhirlingAxes.Continue = BehaviorWhirlingAxes.Begin
--------------------------------------------------------------------------------------------------------
BehaviorBattleTrance = {}
function BehaviorBattleTrance:Evaluate()
--print( "BehaviorBattleTrance:Evaluate()" )
local desire = 0
self.battleTranceAbility = thisEntity:FindAbilityByName( "troll_warlord_battle_trance" )
if self.battleTranceAbility and self.battleTranceAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 30 ) then
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
end
return desire
end
function BehaviorBattleTrance:Begin()
--print( "BehaviorBattleTrance:Begin()" )
if self.battleTranceAbility and self.battleTranceAbility:IsFullyCastable() then
--print( "Casting Battle Trance" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.battleTranceAbility:entindex(),
}
return order
end
return nil
end
BehaviorBattleTrance.Continue = BehaviorBattleTrance.Begin
--------------------------------------------------------------------------------------------------------
BehaviorBKB = {}
function BehaviorBKB:Evaluate()
--print( "BehaviorBKB:Evaluate()" )
local desire = 0
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_creature_black_king_bar" then
self.blackKingBarAbility = item
end
end
if self.blackKingBarAbility and self.blackKingBarAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 50 ) then
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
end
return desire
end
function BehaviorBKB:Begin()
--print( "BehaviorBKB:Begin()" )
if self.blackKingBarAbility and self.blackKingBarAbility:IsFullyCastable() then
--print( "Casting Primal Roar" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.blackKingBarAbility:entindex(),
}
return order
end
return nil
end
BehaviorBKB.Continue = BehaviorBKB.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorWhirlingAxes, BehaviorBattleTrance, BehaviorBKB }

View File

@@ -0,0 +1,189 @@
--[[ Troll Warlord Ranged AI ]]
require( "ai/ai_core" )
function Spawn( entityKeyValues )
thisEntity:SetContextThink( "AIThink", AIThink, 0.25 )
behaviorSystem = AICore:CreateBehaviorSystem( thisEntity, { BehaviorNone, BehaviorWhirlingAxes, BehaviorBattleTrance, BehaviorBKB } )
end
function AIThink() -- For some reason AddThinkToEnt doesn't accept member functions
return behaviorSystem:Think( )
end
--------------------------------------------------------------------------------------------------------
BehaviorNone = {}
function BehaviorNone:Evaluate()
return 1 -- must return a value > 0, so we have a default
end
function BehaviorNone:Begin()
local orders = nil
local hTarget = AICore:ClosestEnemyHeroInRange( thisEntity, 1000 )
if hTarget ~= nil then
thisEntity.lastTargetPosition = hTarget:GetAbsOrigin()
hTarget:MakeVisibleDueToAttack( DOTA_TEAM_BADGUYS, 100 )
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET,
TargetIndex = hTarget:entindex()
}
elseif thisEntity.lastTargetPosition ~= nil then
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = thisEntity.lastTargetPosition
}
else
orders =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_STOP
}
end
return orders
end
BehaviorNone.Continue = BehaviorNone.Begin
--------------------------------------------------------------------------------------------------------
BehaviorWhirlingAxes = {}
function BehaviorWhirlingAxes:Evaluate()
--print( "BehaviorWhirlingAxes:Evaluate()" )
local desire = 0
-- let's not choose this twice in a row
if behaviorSystem.currentBehavior == self then
return desire
end
self.whirlingAxesAbility = thisEntity:FindAbilityByName( "troll_warlord_whirling_axes_ranged" )
if self.whirlingAxesAbility and self.whirlingAxesAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
return desire
end
function BehaviorWhirlingAxes:Begin()
--print( "BehaviorWhirlingAxes:Begin()" )
if self.whirlingAxesAbility and self.whirlingAxesAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 700, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
if #enemies == 0 then
return nil
end
local target = enemies[#enemies]
local targetPoint = target:GetOrigin() + RandomVector( 100 )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
TargetIndex = target:entindex(),
AbilityIndex = self.whirlingAxesAbility:entindex(),
Queue = false,
}
return order
end
return nil
end
BehaviorWhirlingAxes.Continue = BehaviorWhirlingAxes.Begin
--------------------------------------------------------------------------------------------------------
BehaviorBattleTrance = {}
function BehaviorBattleTrance:Evaluate()
--print( "BehaviorBattleTrance:Evaluate()" )
local desire = 0
self.battleTranceAbility = thisEntity:FindAbilityByName( "troll_warlord_battle_trance" )
if self.battleTranceAbility and self.battleTranceAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 30 ) then
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
end
return desire
end
function BehaviorBattleTrance:Begin()
--print( "BehaviorBattleTrance:Begin()" )
if self.battleTranceAbility and self.battleTranceAbility:IsFullyCastable() then
--print( "Casting Battle Trance" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.battleTranceAbility:entindex(),
}
return order
end
return nil
end
BehaviorBattleTrance.Continue = BehaviorBattleTrance.Begin
--------------------------------------------------------------------------------------------------------
BehaviorBKB = {}
function BehaviorBKB:Evaluate()
--print( "BehaviorBKB:Evaluate()" )
local desire = 0
for i = 0, DOTA_ITEM_MAX - 1 do
local item = thisEntity:GetItemInSlot( i )
if item and item:GetAbilityName() == "item_creature_black_king_bar" then
self.blackKingBarAbility = item
end
end
if self.blackKingBarAbility and self.blackKingBarAbility:IsFullyCastable() then
local enemies = FindUnitsInRadius( DOTA_TEAM_BADGUYS, thisEntity:GetOrigin(), nil, 300, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NO_INVIS, 0, false )
if ( thisEntity:GetHealthPercent() < 50 ) then
if ( #enemies >= 0 ) then
desire = #enemies + 1
end
end
end
return desire
end
function BehaviorBKB:Begin()
--print( "BehaviorBKB:Begin()" )
if self.blackKingBarAbility and self.blackKingBarAbility:IsFullyCastable() then
--print( "Casting Primal Roar" )
local order =
{
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = self.blackKingBarAbility:entindex(),
}
return order
end
return nil
end
BehaviorBKB.Continue = BehaviorBKB.Begin
--------------------------------------------------------------------------------------------------------
AICore.possibleBehaviors = { BehaviorNone, BehaviorWhirlingAxes, BehaviorBattleTrance, BehaviorBKB }

View File

@@ -0,0 +1,118 @@
--------------------------------------------------------------------------------
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hDamageAbility = thisEntity:FindAbilityByName( "aghsfort_underlord_firestorm" )
thisEntity.hChannelledAbility = thisEntity:FindAbilityByName( "underlord_channelled_buff" )
thisEntity.fEnemySearchRange = 700
thisEntity:SetContextThink( "UnderlordThink", UnderlordThink, 1 )
end
--------------------------------------------------------------------------------
function UnderlordThink()
if thisEntity == nil or thisEntity:IsNull() or thisEntity:IsAlive() == false then
return -1
end
if GameRules:IsGamePaused() then
return 1
end
if thisEntity.hChannelledAbility ~= nil and thisEntity.hChannelledAbility:IsChanneling() then
return 0.5
end
local enemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, thisEntity.fEnemySearchRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, FIND_CLOSEST, false )
local bIgniteReady = ( #enemies > 0 and thisEntity.hDamageAbility ~= nil and thisEntity.hDamageAbility:IsFullyCastable() )
if thisEntity.hChannelledAbility ~= nil and thisEntity.hChannelledAbility:IsFullyCastable() then
local friendlies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), nil, 1500, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, FIND_CLOSEST, false )
for _,friendly in pairs ( friendlies ) do
if friendly ~= nil then
if ( friendly:GetUnitName() == "npc_dota_creature_dragon_knight" ) then
local fDist = ( friendly:GetOrigin() - thisEntity:GetOrigin() ):Length2D()
local fCastRange = thisEntity.hChannelledAbility:GetCastRange( thisEntity:GetOrigin(), nil )
--print( string.format( "fDist == %d, fCastRange == %d", fDist, fCastRange ) )
if ( fDist <= fCastRange ) and ( ( #enemies > 0 ) or ( friendly:GetAggroTarget() ) ) then
return CastChannelledBuff( friendly )
elseif ( fDist > 400 ) and ( fDist < 900 ) then
if bIgniteReady == false then
return Approach( friendly )
end
end
end
end
end
end
if bIgniteReady then
return IgniteArea( enemies[ RandomInt( 1, #enemies ) ] )
end
local fFuzz = RandomFloat( -0.1, 0.1 ) -- Adds some timing separation
return 0.5 + fFuzz
end
--------------------------------------------------------------------------------
function Approach( hUnit )
--printf( "\"%s\" is approaching unit named \"%s\"", thisEntity:GetUnitName(), hUnit:GetUnitName() )
local vToUnit = hUnit:GetOrigin() - thisEntity:GetOrigin()
vToUnit = vToUnit:Normalized()
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = thisEntity:GetOrigin() + vToUnit * thisEntity:GetIdealSpeed()
})
return 1
end
--------------------------------------------------------------------------------
function CastChannelledBuff( hUnit )
--print( "Casting CastChannelledBuff on " .. hUnit:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_TARGET,
AbilityIndex = thisEntity.hChannelledAbility:entindex(),
TargetIndex = hUnit:entindex(),
Queue = false,
})
return 1
end
--------------------------------------------------------------------------------
function IgniteArea( hEnemy )
--print( "Casting ignite on " .. hEnemy:GetUnitName() )
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hDamageAbility:entindex(),
Position = hEnemy:GetOrigin(),
Queue = false,
})
return 0.55
end
--------------------------------------------------------------------------------

View File

@@ -0,0 +1,95 @@
function Precache( context )
PrecacheResource( "particle", "particles/units/heroes/hero_pudge/pudge_meathook.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_pudge/pudge_meathook_impact.vpcf", context )
end
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
HookAbility = thisEntity:FindAbilityByName( "aghsfort_walrus_pudge_harpoon" )
flLastOrder = GameRules:GetGameTime()
thisEntity:SetContextThink( "WalrusPudgeThink", WalrusPudgeThink, 1 )
end
function WalrusPudgeThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
if GameRules:IsGamePaused() == true then
return 1
end
if not thisEntity:GetAggroTarget() then
if (GameRules:GetGameTime() - flLastOrder) > (4 - RandomFloat(0 ,1 )) then
flLastOrder = GameRules:GetGameTime()
return DoMove()
end
end
if HookAbility and HookAbility:IsFullyCastable() then
local fHookSearchRadius = HookAbility:GetCastRange()
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, fHookSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_FARTHEST, false )
if #hEnemies > 0 then
local hFarthestEnemy = hEnemies[ 1 ]
return ThrowHook( hFarthestEnemy )
end
end
return 0.5
end
function ThrowHook( enemy )
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my hook" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = HookAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 0.2
end
function DoMove()
if IsServer() then
for i=1,4 do
local vLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 200, 800 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE,
Position = vLoc
})
break
end
end
end
return 0.5
end

View File

@@ -0,0 +1,111 @@
function Precache( context )
PrecacheUnitByNameSync( "npc_aghsfort_creature_wave_blaster_ghost", context, -1 )
PrecacheResource( "particle", "particles/econ/items/ancient_apparition/ancient_apparation_ti8/ancient_ice_vortex_ti8.vpcf", context )
PrecacheResource( "particle", "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_explode.vpcf", context )
end
function Spawn( entityKeyValues )
if not IsServer() then
return
end
if thisEntity == nil then
return
end
thisEntity.hWaveAbility = thisEntity:FindAbilityByName( "aghsfort_wave_blast" )
thisEntity.hLeapAbility = thisEntity:FindAbilityByName( "aghsfort_waveblaster_leap" )
thisEntity.hSummonGhostAbility = thisEntity:FindAbilityByName( "aghsfort_waveblaster_summon_ghost" )
thisEntity:SetContextThink( "WaveBlasterThink", WaveBlasterThink, 1 )
end
function WaveBlasterThink()
if ( not thisEntity:IsAlive() ) then
return -1
end
--print ("thinking")
if GameRules:IsGamePaused() == true then
return 1
end
if thisEntity.hWaveAbility and thisEntity.hWaveAbility:IsFullyCastable() then
local fWaveSearchRadius = thisEntity.hWaveAbility:GetCastRange()
local hEnemies = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, fWaveSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_FARTHEST, false )
if #hEnemies > 0 then
local hFarthestEnemy = hEnemies[ 1 ]
return WaveBlast( hFarthestEnemy )
end
end
if thisEntity.hLeapAbility and thisEntity.hLeapAbility:IsFullyCastable() then
local fSearchRadius = 750
local hEnemiesToAvoid = FindUnitsInRadius( thisEntity:GetTeamNumber(), thisEntity:GetOrigin(), thisEntity, fSearchRadius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_NONE, FIND_CLOSEST, false )
if #hEnemiesToAvoid > 0 then
return CastLeap()
end
end
return 0.5
end
function WaveBlast( enemy )
if enemy == nil then
return
end
if ( not thisEntity:HasModifier( "modifier_provide_vision" ) ) then
--print( "If player can't see me, provide brief vision to his team as I start my Smash" )
thisEntity:AddNewModifier( thisEntity, nil, "modifier_provide_vision", { duration = 1.5 } )
end
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hWaveAbility:entindex(),
Position = enemy:GetOrigin(),
Queue = false,
})
return 0.2
end
--------------------------------------------------------------------------------
function CastLeap()
if IsServer() then
local bLeapSuccess = false
for i=1,6 do
local vLoc = FindPathablePositionNearby(thisEntity:GetAbsOrigin(), 650, 1550 )
if GameRules.Aghanim:GetCurrentRoom():IsInRoomBounds( vLoc ) then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_POSITION,
AbilityIndex = thisEntity.hLeapAbility:entindex(),
Position = vLoc,
Queue = false,
})
bLeapSuccess = true
break
end
end
if bLeapSuccess == true and thisEntity.hSummonGhostAbility and thisEntity.hSummonGhostAbility:IsFullyCastable() then
ExecuteOrderFromTable({
UnitIndex = thisEntity:entindex(),
OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET,
AbilityIndex = thisEntity.hSummonGhostAbility:entindex(),
Queue = false,
})
end
end
return 3
end