require( "utility_functions" ) modifier_passive_autocast = class({}) ----------------------------------------------------------------------------------------- function modifier_passive_autocast:IsHidden() return true end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:IsPurgable() return false end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:OnCreated( kv ) if IsServer() then self.nCastBehavior = kv[ "cast_behavior" ] self.nTargetType = kv[ "target_type" ] self.flHealthPercent = kv[ "health_percent" ] self.nRange = kv[ "range" ] self.flNextCastTime = GameRules:GetGameTime() -- If waiting for CD, then don't cast immediately if self.nCastBehavior == ASCENSION_CAST_WHEN_COOLDOWN_READY then local flCooldownTime = self:GetAbility():GetCooldownTimeRemaining() if flCooldownTime > 0 then self.flNextCastTime = GameRules:GetGameTime() + flCooldownTime + RandomFloat( 0.1, 1.0 ); else self.flNextCastTime = GameRules:GetGameTime() + RandomFloat( 1.0, 3.0 ); end end --print( "modifier_passive_autocast created " .. self:GetParent():GetUnitName() .. " " .. self:GetAbility():GetAbilityName() .. " " .. self.nCastBehavior .. " " .. self.nTargetType ) if self.nCastBehavior == ASCENSION_CAST_WHEN_COOLDOWN_READY or self.nCastBehavior == ASCENSION_CAST_ON_LOW_HEALTH or self.nCastBehavior == ASCENSION_CAST_ON_NEARBY_ENEMY then self:StartIntervalThink( 0.1 ) elseif self.nCastBehavior == ASCENSION_CAST_ON_DEATH then if bitand( self:GetAbility():GetBehavior(), DOTA_ABILITY_BEHAVIOR_UNRESTRICTED ) == 0 then print( "*** WARNING: OnDeath behaviors must have DOTA_ABILITY_BEHAVIOR_UNRESTRICTED set " .. self:GetAbility():GetAbilityName() ) end end if self.nTargetType == ASCENSION_TARGET_NO_TARGET then if bitand( self:GetAbility():GetBehavior(), DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ) ~= 0 then self.nTargetType = ASCENSION_TARGET_RANDOM_PLAYER print( "*** WARNING: DOTA_ABILITY_BEHAVIOR_UNIT_TARGET didn't specify a target type " .. self:GetAbility():GetAbilityName() ) end end end end -------------------------------------------------------------------------------- function modifier_passive_autocast:DeclareFunctions() local funcs = { MODIFIER_EVENT_ON_DEATH, MODIFIER_EVENT_ON_TAKEDAMAGE, } return funcs end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:GetAttributes() -- Needed because we may have many ascension abilities on the same unit -- and we need one passive_autocast modifier per ability return MODIFIER_ATTRIBUTE_MULTIPLE end -------------------------------------------------------------------------------- function modifier_passive_autocast:OnTakeDamage( params ) if IsServer() == false then return 0 end if params.unit ~= self:GetParent() then return 0 end if self.nCastBehavior ~= ASCENSION_CAST_ON_TAKE_MAGIC_DAMAGE then return 0 end if params.damage_type ~= DAMAGE_TYPE_MAGICAL then return 0 end self:CastModifierAbility( params.attacker ) return 0 end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:OnIntervalThink() if not IsServer() then return end local hCaster = self:GetCaster() if hCaster == nil then return end if self.flNextCastTime < 0 or self.flNextCastTime > GameRules:GetGameTime() then return end if self.nCastBehavior == ASCENSION_CAST_ON_LOW_HEALTH then if hCaster:GetHealthPercent() > self.flHealthPercent then return end end -- For CAST ON NEARBY ENEMY, check for enemies within range if self.nCastBehavior == ASCENSION_CAST_ON_NEARBY_ENEMY then local vecEnemies = FindUnitsInRadius( hCaster:GetTeamNumber(), hCaster:GetAbsOrigin(), nil, self.nRange, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_INVULNERABLE, 0, false ) if #vecEnemies == 0 then return end end -- Specifically for firefily + wave blaster, wait until leap if self:GetAbility():GetAbilityName() == "aghsfort_ascension_firefly" then if hCaster:GetUnitName() == "npc_dota_creature_wave_blaster" then if hCaster:FindModifierByName( "modifier_aghsfort_waveblaster_leap" ) == nil then return end end end self:CastModifierAbility( nil ) end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:SelectClumpedPlayer() local vecPlayers = GetAliveHeroesInRoom( ) local hBestTarget = nil local flClumpAmount = 1000000000 for i=1,#vecPlayers do local hTarget = vecPlayers[i] local flDist = 0 for j=1,#vecPlayers do if i~=j then local flCurDist = ( hTarget:GetAbsOrigin() - vecPlayers[j]:GetAbsOrigin() ):Length2D() if self.nRange ~= nil and flCurDist > self.nRange then flCurDist = self.nRange end flDist = flDist + flCurDist end end if flDist < flClumpAmount then hBestTarget = hTarget flClumpAmount = flDist end end return hBestTarget end ----------------------------------------------------------------------------------------- function modifier_passive_autocast:CastModifierAbility( hAttackerUnit ) if self.flNextCastTime < 0 or self.flNextCastTime > GameRules:GetGameTime() then return end if self:GetAbility() == nil or self:GetAbility():IsFullyCastable() == false then return end local hCaster = self:GetCaster() if hCaster == nil then return end -- NOTE: This is a little tricky. We're trying to completely avoid using -- orders / behaviors for the passive so it doesn't interrupt normal AI behavior -- However, for dummy casters, it's no problem since we know those are the -- global encounter entity used for environmental effects so there's nothing to interrupt local bIsDummyCaster = hCaster:GetUnitName() == "npc_dota_dummy_caster" local nBehavior = self:GetAbility():GetBehavior() --print( "modifier_passive_autocast cooldown-based cast " .. self:GetParent():entindex() .. " " .. self:GetParent():GetUnitName() .. " " .. self:GetAbility():GetAbilityName() ) if bitand( nBehavior, DOTA_ABILITY_BEHAVIOR_CHANNELLED ) ~= 0 then if not bIsDummyCaster then print( "*** WARNING: Channeled behaviors cannot be autocast from normal units " .. self:GetAbility():GetAbilityName() ) return end ExecuteOrderFromTable({ UnitIndex = hCaster:entindex(), OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, AbilityIndex = self:GetAbility():entindex(), Queue = false, }) else local hTarget = nil if self.nTargetType == ASCENSION_TARGET_RANDOM_PLAYER then local vecPlayers = GetAliveHeroes( ) local nPlayerCount = #vecPlayers if nPlayerCount > 0 then local nPlayer = math.random( 1, nPlayerCount ) hTarget = vecPlayers[nPlayer] end elseif self.nTargetType == ASCENSION_TARGET_CLUMPED_PLAYER then hTarget = self:SelectClumpedPlayer() elseif self.nTargetType == ASCENSION_TARGET_ATTACKER then hTarget = hAttackerUnit end if hTarget == nil and self.nTargetType ~= ASCENSION_TARGET_NO_TARGET then return end hCaster:SetCursorCastTarget( hTarget ) self:GetAbility():CastAbility() end if self.nCastBehavior ~= ASCENSION_CAST_ON_LOW_HEALTH then local flCooldown = self:GetAbility():GetEffectiveCooldown( -1 ) self.flNextCastTime = GameRules:GetGameTime() + flCooldown + RandomFloat( 0.01, 1.0 ) else -- Cast on low health only gets to cast once self.flNextCastTime = -1 end end -------------------------------------------------------------------------------- function modifier_passive_autocast:OnDeath( params ) if IsServer() then if self.nCastBehavior == ASCENSION_CAST_ON_DEATH then local hUnit = params.unit if hUnit:entindex() == self:GetParent():entindex() then --print( "modifier_passive_autocast death-based cast " .. self:GetParent():GetUnitName() .. " " .. self:GetAbility():GetAbilityName() ) self:GetAbility():EndCooldown() self.flNextCastTime = GameRules:GetGameTime() self:CastModifierAbility( params.attacker ) end end end end