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