initial commit
This commit is contained in:
203
aghanim_singleplayer/scripts/vscripts/ai/ai_assault_captain.lua
Executable file
203
aghanim_singleplayer/scripts/vscripts/ai/ai_assault_captain.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
108
aghanim_singleplayer/scripts/vscripts/ai/ai_catapult.lua
Executable file
108
aghanim_singleplayer/scripts/vscripts/ai/ai_catapult.lua
Executable 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
|
||||
162
aghanim_singleplayer/scripts/vscripts/ai/ai_core.lua
Executable file
162
aghanim_singleplayer/scripts/vscripts/ai/ai_core.lua
Executable 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
|
||||
96
aghanim_singleplayer/scripts/vscripts/ai/ai_dire_hound_boss.lua
Executable file
96
aghanim_singleplayer/scripts/vscripts/ai/ai_dire_hound_boss.lua
Executable 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
|
||||
|
||||
88
aghanim_singleplayer/scripts/vscripts/ai/ai_doom.lua
Executable file
88
aghanim_singleplayer/scripts/vscripts/ai/ai_doom.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
291
aghanim_singleplayer/scripts/vscripts/ai/ai_drow_ranger_miniboss.lua
Executable file
291
aghanim_singleplayer/scripts/vscripts/ai/ai_drow_ranger_miniboss.lua
Executable 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
|
||||
28
aghanim_singleplayer/scripts/vscripts/ai/ai_drow_ranger_skeleton_archer.lua
Executable file
28
aghanim_singleplayer/scripts/vscripts/ai/ai_drow_ranger_skeleton_archer.lua
Executable 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
|
||||
78
aghanim_singleplayer/scripts/vscripts/ai/ai_pudge_miniboss.lua
Executable file
78
aghanim_singleplayer/scripts/vscripts/ai/ai_pudge_miniboss.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
202
aghanim_singleplayer/scripts/vscripts/ai/ai_shadow_demon.lua
Executable file
202
aghanim_singleplayer/scripts/vscripts/ai/ai_shadow_demon.lua
Executable 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
|
||||
97
aghanim_singleplayer/scripts/vscripts/ai/ai_shroom_giant.lua
Executable file
97
aghanim_singleplayer/scripts/vscripts/ai/ai_shroom_giant.lua
Executable 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
|
||||
81
aghanim_singleplayer/scripts/vscripts/ai/ai_shroomling.lua
Executable file
81
aghanim_singleplayer/scripts/vscripts/ai/ai_shroomling.lua
Executable 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
|
||||
--]]
|
||||
95
aghanim_singleplayer/scripts/vscripts/ai/ai_upheaval_urn.lua
Executable file
95
aghanim_singleplayer/scripts/vscripts/ai/ai_upheaval_urn.lua
Executable 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
|
||||
|
||||
395
aghanim_singleplayer/scripts/vscripts/ai/alchemist_ai.lua
Executable file
395
aghanim_singleplayer/scripts/vscripts/ai/alchemist_ai.lua
Executable 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
|
||||
356
aghanim_singleplayer/scripts/vscripts/ai/announcer_aghanim.lua
Executable file
356
aghanim_singleplayer/scripts/vscripts/ai/announcer_aghanim.lua
Executable 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
|
||||
|
||||
|
||||
|
||||
116
aghanim_singleplayer/scripts/vscripts/ai/baby_ogre_magi.lua
Executable file
116
aghanim_singleplayer/scripts/vscripts/ai/baby_ogre_magi.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
101
aghanim_singleplayer/scripts/vscripts/ai/baby_ogre_tank.lua
Executable file
101
aghanim_singleplayer/scripts/vscripts/ai/baby_ogre_tank.lua
Executable 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
|
||||
|
||||
203
aghanim_singleplayer/scripts/vscripts/ai/bandit_archer.lua
Executable file
203
aghanim_singleplayer/scripts/vscripts/ai/bandit_archer.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
129
aghanim_singleplayer/scripts/vscripts/ai/bandit_captain.lua
Executable file
129
aghanim_singleplayer/scripts/vscripts/ai/bandit_captain.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
376
aghanim_singleplayer/scripts/vscripts/ai/basic_spell_casting_ai.lua
Executable file
376
aghanim_singleplayer/scripts/vscripts/ai/basic_spell_casting_ai.lua
Executable 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
|
||||
174
aghanim_singleplayer/scripts/vscripts/ai/beastmaster.lua
Executable file
174
aghanim_singleplayer/scripts/vscripts/ai/beastmaster.lua
Executable 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 }
|
||||
59
aghanim_singleplayer/scripts/vscripts/ai/big_golem.lua
Executable file
59
aghanim_singleplayer/scripts/vscripts/ai/big_golem.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
147
aghanim_singleplayer/scripts/vscripts/ai/bomb_squad_ai.lua
Executable file
147
aghanim_singleplayer/scripts/vscripts/ai/bomb_squad_ai.lua
Executable 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
|
||||
65
aghanim_singleplayer/scripts/vscripts/ai/bomb_squad_landmine_ai.lua
Executable file
65
aghanim_singleplayer/scripts/vscripts/ai/bomb_squad_landmine_ai.lua
Executable 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
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
444
aghanim_singleplayer/scripts/vscripts/ai/boss_aghanim.lua
Executable file
444
aghanim_singleplayer/scripts/vscripts/ai/boss_aghanim.lua
Executable 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
|
||||
|
||||
206
aghanim_singleplayer/scripts/vscripts/ai/boss_base.lua
Executable file
206
aghanim_singleplayer/scripts/vscripts/ai/boss_base.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
272
aghanim_singleplayer/scripts/vscripts/ai/boss_timbersaw.lua
Executable file
272
aghanim_singleplayer/scripts/vscripts/ai/boss_timbersaw.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
125
aghanim_singleplayer/scripts/vscripts/ai/boss_visage.lua
Executable file
125
aghanim_singleplayer/scripts/vscripts/ai/boss_visage.lua
Executable 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
|
||||
|
||||
|
||||
|
||||
|
||||
319
aghanim_singleplayer/scripts/vscripts/ai/boss_void_spirit.lua
Executable file
319
aghanim_singleplayer/scripts/vscripts/ai/boss_void_spirit.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
165
aghanim_singleplayer/scripts/vscripts/ai/brewmaster.lua
Executable file
165
aghanim_singleplayer/scripts/vscripts/ai/brewmaster.lua
Executable 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 }
|
||||
166
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_earth.lua
Executable file
166
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_earth.lua
Executable 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 }
|
||||
115
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_fire.lua
Executable file
115
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_fire.lua
Executable 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 }
|
||||
115
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_storm.lua
Executable file
115
aghanim_singleplayer/scripts/vscripts/ai/brewmaster_storm.lua
Executable 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 }
|
||||
62
aghanim_singleplayer/scripts/vscripts/ai/broodmother.lua
Executable file
62
aghanim_singleplayer/scripts/vscripts/ai/broodmother.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
120
aghanim_singleplayer/scripts/vscripts/ai/dragon_knight.lua
Executable file
120
aghanim_singleplayer/scripts/vscripts/ai/dragon_knight.lua
Executable 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
|
||||
]]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
110
aghanim_singleplayer/scripts/vscripts/ai/earth_spirit_statue.lua
Executable file
110
aghanim_singleplayer/scripts/vscripts/ai/earth_spirit_statue.lua
Executable 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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
84
aghanim_singleplayer/scripts/vscripts/ai/elder_titan.lua
Executable file
84
aghanim_singleplayer/scripts/vscripts/ai/elder_titan.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
65
aghanim_singleplayer/scripts/vscripts/ai/elemental_io_ai.lua
Executable file
65
aghanim_singleplayer/scripts/vscripts/ai/elemental_io_ai.lua
Executable 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
|
||||
128
aghanim_singleplayer/scripts/vscripts/ai/elemental_tiny_ai.lua
Executable file
128
aghanim_singleplayer/scripts/vscripts/ai/elemental_tiny_ai.lua
Executable 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
|
||||
|
||||
62
aghanim_singleplayer/scripts/vscripts/ai/ember_spirit.lua
Executable file
62
aghanim_singleplayer/scripts/vscripts/ai/ember_spirit.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
117
aghanim_singleplayer/scripts/vscripts/ai/enraged_wildwing_ai.lua
Executable file
117
aghanim_singleplayer/scripts/vscripts/ai/enraged_wildwing_ai.lua
Executable 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
|
||||
134
aghanim_singleplayer/scripts/vscripts/ai/evil_greevil.lua
Executable file
134
aghanim_singleplayer/scripts/vscripts/ai/evil_greevil.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
137
aghanim_singleplayer/scripts/vscripts/ai/exploding_big_burrower.lua
Executable file
137
aghanim_singleplayer/scripts/vscripts/ai/exploding_big_burrower.lua
Executable 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
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
106
aghanim_singleplayer/scripts/vscripts/ai/friendly_baby_ogre_tank.lua
Executable file
106
aghanim_singleplayer/scripts/vscripts/ai/friendly_baby_ogre_tank.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
72
aghanim_singleplayer/scripts/vscripts/ai/friendly_ogre_seal.lua
Executable file
72
aghanim_singleplayer/scripts/vscripts/ai/friendly_ogre_seal.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
106
aghanim_singleplayer/scripts/vscripts/ai/friendly_ogre_tank.lua
Executable file
106
aghanim_singleplayer/scripts/vscripts/ai/friendly_ogre_tank.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
73
aghanim_singleplayer/scripts/vscripts/ai/grimstroke.lua
Executable file
73
aghanim_singleplayer/scripts/vscripts/ai/grimstroke.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
80
aghanim_singleplayer/scripts/vscripts/ai/healing_burrower.lua
Executable file
80
aghanim_singleplayer/scripts/vscripts/ai/healing_burrower.lua
Executable 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
|
||||
|
||||
|
||||
102
aghanim_singleplayer/scripts/vscripts/ai/hellbear.lua
Executable file
102
aghanim_singleplayer/scripts/vscripts/ai/hellbear.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
84
aghanim_singleplayer/scripts/vscripts/ai/huge_broodmother.lua
Executable file
84
aghanim_singleplayer/scripts/vscripts/ai/huge_broodmother.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
257
aghanim_singleplayer/scripts/vscripts/ai/kidnap_spider.lua
Executable file
257
aghanim_singleplayer/scripts/vscripts/ai/kidnap_spider.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
201
aghanim_singleplayer/scripts/vscripts/ai/kunkka.lua
Executable file
201
aghanim_singleplayer/scripts/vscripts/ai/kunkka.lua
Executable 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 }
|
||||
59
aghanim_singleplayer/scripts/vscripts/ai/landmine_ai.lua
Executable file
59
aghanim_singleplayer/scripts/vscripts/ai/landmine_ai.lua
Executable 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
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
228
aghanim_singleplayer/scripts/vscripts/ai/legion_commander.lua
Executable file
228
aghanim_singleplayer/scripts/vscripts/ai/legion_commander.lua
Executable 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 }
|
||||
279
aghanim_singleplayer/scripts/vscripts/ai/mirana_ai.lua
Executable file
279
aghanim_singleplayer/scripts/vscripts/ai/mirana_ai.lua
Executable 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 }
|
||||
322
aghanim_singleplayer/scripts/vscripts/ai/naga_siren.lua
Executable file
322
aghanim_singleplayer/scripts/vscripts/ai/naga_siren.lua
Executable 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 }
|
||||
176
aghanim_singleplayer/scripts/vscripts/ai/naga_siren_illusion.lua
Executable file
176
aghanim_singleplayer/scripts/vscripts/ai/naga_siren_illusion.lua
Executable 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 }
|
||||
116
aghanim_singleplayer/scripts/vscripts/ai/ogre_magi.lua
Executable file
116
aghanim_singleplayer/scripts/vscripts/ai/ogre_magi.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
60
aghanim_singleplayer/scripts/vscripts/ai/ogre_seal.lua
Executable file
60
aghanim_singleplayer/scripts/vscripts/ai/ogre_seal.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
123
aghanim_singleplayer/scripts/vscripts/ai/ogre_seer.lua
Executable file
123
aghanim_singleplayer/scripts/vscripts/ai/ogre_seer.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
100
aghanim_singleplayer/scripts/vscripts/ai/ogre_tank.lua
Executable file
100
aghanim_singleplayer/scripts/vscripts/ai/ogre_tank.lua
Executable 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
|
||||
|
||||
108
aghanim_singleplayer/scripts/vscripts/ai/ogre_tank_boss.lua
Executable file
108
aghanim_singleplayer/scripts/vscripts/ai/ogre_tank_boss.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
134
aghanim_singleplayer/scripts/vscripts/ai/phoenix.lua
Executable file
134
aghanim_singleplayer/scripts/vscripts/ai/phoenix.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
506
aghanim_singleplayer/scripts/vscripts/ai/rhyzik.lua
Executable file
506
aghanim_singleplayer/scripts/vscripts/ai/rhyzik.lua
Executable 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
|
||||
147
aghanim_singleplayer/scripts/vscripts/ai/scarab_priest.lua
Executable file
147
aghanim_singleplayer/scripts/vscripts/ai/scarab_priest.lua
Executable 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
|
||||
|
||||
92
aghanim_singleplayer/scripts/vscripts/ai/scarab_zealot.lua
Executable file
92
aghanim_singleplayer/scripts/vscripts/ai/scarab_zealot.lua
Executable 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
|
||||
584
aghanim_singleplayer/scripts/vscripts/ai/shared.lua
Executable file
584
aghanim_singleplayer/scripts/vscripts/ai/shared.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
73
aghanim_singleplayer/scripts/vscripts/ai/sheep.lua
Executable file
73
aghanim_singleplayer/scripts/vscripts/ai/sheep.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
91
aghanim_singleplayer/scripts/vscripts/ai/skeleton_king.lua
Executable file
91
aghanim_singleplayer/scripts/vscripts/ai/skeleton_king.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
123
aghanim_singleplayer/scripts/vscripts/ai/skywrath_mage.lua
Executable file
123
aghanim_singleplayer/scripts/vscripts/ai/skywrath_mage.lua
Executable 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 }
|
||||
298
aghanim_singleplayer/scripts/vscripts/ai/slark.lua
Executable file
298
aghanim_singleplayer/scripts/vscripts/ai/slark.lua
Executable 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 }
|
||||
77
aghanim_singleplayer/scripts/vscripts/ai/small_hellbear.lua
Executable file
77
aghanim_singleplayer/scripts/vscripts/ai/small_hellbear.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
111
aghanim_singleplayer/scripts/vscripts/ai/spectral_tusk_mage.lua
Executable file
111
aghanim_singleplayer/scripts/vscripts/ai/spectral_tusk_mage.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
56
aghanim_singleplayer/scripts/vscripts/ai/spectre.lua
Executable file
56
aghanim_singleplayer/scripts/vscripts/ai/spectre.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
271
aghanim_singleplayer/scripts/vscripts/ai/storegga.lua
Executable file
271
aghanim_singleplayer/scripts/vscripts/ai/storegga.lua
Executable 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
|
||||
219
aghanim_singleplayer/scripts/vscripts/ai/temple_guardian.lua
Executable file
219
aghanim_singleplayer/scripts/vscripts/ai/temple_guardian.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
95
aghanim_singleplayer/scripts/vscripts/ai/tidehunter.lua
Executable file
95
aghanim_singleplayer/scripts/vscripts/ai/tidehunter.lua
Executable 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 }
|
||||
193
aghanim_singleplayer/scripts/vscripts/ai/tidehunter_miniboss.lua
Executable file
193
aghanim_singleplayer/scripts/vscripts/ai/tidehunter_miniboss.lua
Executable 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 }
|
||||
276
aghanim_singleplayer/scripts/vscripts/ai/tornado_harpy_ai.lua
Executable file
276
aghanim_singleplayer/scripts/vscripts/ai/tornado_harpy_ai.lua
Executable 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 ]]--
|
||||
182
aghanim_singleplayer/scripts/vscripts/ai/troll_warlord_melee.lua
Executable file
182
aghanim_singleplayer/scripts/vscripts/ai/troll_warlord_melee.lua
Executable 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 }
|
||||
189
aghanim_singleplayer/scripts/vscripts/ai/troll_warlord_ranged.lua
Executable file
189
aghanim_singleplayer/scripts/vscripts/ai/troll_warlord_ranged.lua
Executable 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 }
|
||||
118
aghanim_singleplayer/scripts/vscripts/ai/underlord.lua
Executable file
118
aghanim_singleplayer/scripts/vscripts/ai/underlord.lua
Executable 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
95
aghanim_singleplayer/scripts/vscripts/ai/walrus_pudge_ai.lua
Executable file
95
aghanim_singleplayer/scripts/vscripts/ai/walrus_pudge_ai.lua
Executable 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
|
||||
111
aghanim_singleplayer/scripts/vscripts/ai/wave_blaster_ai.lua
Executable file
111
aghanim_singleplayer/scripts/vscripts/ai/wave_blaster_ai.lua
Executable 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
|
||||
Reference in New Issue
Block a user