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