/// <summary>
        ///     The end radius.
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <returns>
        ///     The <see cref="float" />.
        /// </returns>
        public static float EndRadius(this Ability ability)
        {
            var data = ability.CommonProperties();
            if (data == null)
            {
                return ability.GetRadius();
            }

            var radius = ability.GetAbilityData(data.EndWidth);
            return radius > 0 ? radius : ability.GetRadius();
        }
        /// <summary>
        ///     The travel distance.
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <returns>
        ///     The <see cref="float" />.
        /// </returns>
        public static float TravelDistance(this Ability ability)
        {
            var data = ability.CommonProperties();
            if (data == null)
            {
                return ability.GetCastRange();
            }

            var distance = ability.GetAbilityData(data.Distance);
            return distance > 0 ? distance : ability.GetCastRange();
        }
        /// <summary>
        ///     Uses prediction to cast given skill shot ability
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="target">
        ///     The target.
        /// </param>
        /// <param name="sourcePosition">
        ///     The source Position.
        /// </param>
        /// <param name="abilityName">
        ///     The ability Name.
        /// </param>
        /// <param name="soulRing">
        ///     The soul Ring.
        /// </param>
        /// <param name="otherTargets">
        ///     Targets which are supposed to be hit by AOE Skill Shot
        /// </param>
        /// <returns>
        ///     returns true in case of successful cast
        /// </returns>
        public static bool CastSkillShot(
            this Ability ability, 
            Unit target, 
            Vector3 sourcePosition, 
            string abilityName = null, 
            Ability soulRing = null, 
            List<Unit> otherTargets = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return false;
            }

            if (target == null || !target.IsValid)
            {
                return false;
            }

            if (!Utils.SleepCheck("CastSkillshot" + ability.Handle))
            {
                return false;
            }

            var name = abilityName ?? ability.StoredName();
            var owner = ability.Owner as Unit;
            var position = sourcePosition;
            var delay = ability.GetHitDelay(target, name);
            var data = ability.CommonProperties();

            // delay += data.AdditionalDelay;
            if (target.IsInvul() && !Utils.ChainStun(target, delay, null, false))
            {
                return false;
            }

            var xyz = ability.GetPrediction(target, abilityName: name);
            if (otherTargets != null)
            {
                var avPosX = otherTargets.Average(x => ability.GetPrediction(x, abilityName: name).X);
                var avPosY = otherTargets.Average(x => ability.GetPrediction(x, abilityName: name).Y);
                xyz = (xyz + new Vector3(avPosX, avPosY, 0)) / 2;
            }

            var radius = ability.GetRadius(name);
            var range = ability.TravelDistance();

            if (data.AllyBlock)
            {
                if (
                    Creeps.All.Any(
                        x =>
                        x.IsValid && x.IsAlive && x.Team == owner.Team && x.Distance2D(xyz) <= range
                        && x.Distance2D(owner) < owner.Distance2D(target)
                        && x.Position.ToVector2().DistanceToLineSegment(sourcePosition.ToVector2(), xyz.ToVector2())
                        <= radius + x.HullRadius))
                {
                    return false;
                }

                if (
                    Heroes.GetByTeam(owner.Team)
                        .Any(
                            hero =>
                            hero.IsAlive && !hero.Equals(owner) && !hero.Equals(target) && hero.Distance2D(xyz) <= range
                            && hero.Distance2D(owner) < owner.Distance2D(target)
                            && hero.Position.ToVector2()
                                   .DistanceToLineSegment(sourcePosition.ToVector2(), xyz.ToVector2())
                            <= radius + hero.HullRadius))
                {
                    return false;
                }
            }

            if (data.EnemyBlock)
            {
                if (
                    Creeps.All.Any(
                        x =>
                        x.IsValid && x.IsAlive && x.Team != owner.Team && x.Distance2D(xyz) <= range
                        && x.Distance2D(owner) < owner.Distance2D(target)
                        && x.Position.ToVector2().DistanceToLineSegment(sourcePosition.ToVector2(), xyz.ToVector2())
                        <= radius + x.HullRadius))
                {
                    return false;
                }

                if (
                    Heroes.GetByTeam(owner.GetEnemyTeam())
                        .Any(
                            hero =>
                            hero.IsAlive && !hero.Equals(target) && hero.Distance2D(xyz) <= range
                            && hero.Distance2D(owner) < owner.Distance2D(target)
                            && hero.Position.ToVector2()
                                   .DistanceToLineSegment(sourcePosition.ToVector2(), xyz.ToVector2())
                            <= radius + hero.HullRadius))
                {
                    return false;
                }
            }

            var speed = ability.GetProjectileSpeed(name);
            var distanceXyz = xyz.Distance2D(position);
            var lion = name == "lion_impale" ? ability.GetAbilityData("length_buffer") : 0;
            if (!(distanceXyz <= range + radius + lion + target.HullRadius))
            {
                return false;
            }

            if (distanceXyz > range)
            {
                xyz = xyz - position;
                xyz /= xyz.Length();
                xyz *= range;
                xyz += position;
            }

            // Console.WriteLine(ability.GetCastRange() + " " + radius);
            if (name.StartsWith("nevermore_shadowraze"))
            {
                xyz = Prediction.SkillShotXYZ(
                    owner, 
                    target, 
                    (float)((delay + (float)owner.GetTurnTime(xyz)) * 1000), 
                    speed, 
                    radius);

                // Console.WriteLine(distanceXyz + " " + range + " " + radius);
                if (distanceXyz < range + radius && distanceXyz > range - radius)
                {
                    if (owner.GetTurnTime(xyz) > 0.01)
                    {
                        owner.Move((owner.Position - xyz) * 25 / distanceXyz + xyz);
                        owner.Stop();
                    }
                    else
                    {
                        ability.UseAbility();
                    }

                    return true;
                }

                return false;
            }

            if (name == "invoker_ice_wall" && distanceXyz - 50 > 200 && distanceXyz - 50 < 610)
            {
                var mepred = (position - target.Position) * 50 / position.Distance2D(target) + target.Position;
                var v1 = xyz.X - mepred.X;
                var v2 = xyz.Y - mepred.Y;
                var a = Math.Acos(175 / xyz.Distance(mepred));
                var x1 = v1 * Math.Cos(a) - v2 * Math.Sin(a);
                var y1 = v2 * Math.Cos(a) + v1 * Math.Sin(a);
                var b = Math.Sqrt(x1 * x1 + y1 * y1);
                var k1 = x1 * 50 / b;
                var k2 = y1 * 50 / b;
                var vec1 = new Vector3((float)(k1 + mepred.X), (float)(k2 + mepred.Y), mepred.Z);
                if (vec1.Distance2D(mepred) > 0)
                {
                    owner.Move(mepred);
                    owner.Move(vec1, true);
                    ability.UseAbility(true);

                    return true;
                }

                return false;
            }

            if (ability.ManaCost > 0 && soulRing.CanBeCasted())
            {
                soulRing.UseAbility();
            }

            ability.UseAbility(xyz);
            return true;
        }
 /// <summary>
 ///     The is slow.
 /// </summary>
 /// <param name="ability">
 ///     The ability.
 /// </param>
 /// <returns>
 ///     The <see cref="bool" />.
 /// </returns>
 public static bool IsSlow(this Ability ability)
 {
     var data = ability.CommonProperties();
     return data != null && data.IsSlow;
 }
 /// <summary>
 ///     The pierces magic immunity.
 /// </summary>
 /// <param name="ability">
 ///     The ability.
 /// </param>
 /// <returns>
 ///     The <see cref="bool" />.
 /// </returns>
 public static bool PiercesMagicImmunity(this Ability ability)
 {
     var data = ability.CommonProperties();
     return data != null && data.MagicImmunityPierce;
 }
 /// <summary>
 ///     The is manaburn.
 /// </summary>
 /// <param name="ability">
 ///     The ability.
 /// </param>
 /// <returns>
 ///     The <see cref="bool" />.
 /// </returns>
 public static bool IsManaburn(this Ability ability)
 {
     var data = ability.CommonProperties();
     return data != null && data.ManaBurn;
 }
        /// <summary>
        ///     Checks if given ability can be used
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="target">
        ///     The target.
        /// </param>
        /// <returns>
        ///     returns true in case ability can be used
        /// </returns>
        public static bool CanBeCasted(this Ability ability, Unit target)
        {
            if (ability == null || !ability.IsValid)
            {
                return false;
            }

            if (target == null || !target.IsValid)
            {
                return false;
            }

            if (!target.IsValidTarget())
            {
                return false;
            }

            var canBeCasted = ability.CanBeCasted();
            if (!target.IsMagicImmune())
            {
                return canBeCasted;
            }

            var data = ability.CommonProperties();

            return data == null ? canBeCasted : data.MagicImmunityPierce;
        }
        /// <summary>
        ///     Returns impact radius of given ability
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="abilityName">
        ///     The ability Name.
        /// </param>
        /// <returns>
        ///     The <see cref="float" />.
        /// </returns>
        public static float GetRadius(this Ability ability, string abilityName = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return 0;
            }

            var name = abilityName ?? ability.StoredName();
            float radius;
            if (radiusDictionary.TryGetValue(name + " " + ability.Level, out radius))
            {
                return radius;
            }

            var data = ability.CommonProperties();
            if (data == null)
            {
                radius = 0;
                radiusDictionary.Add(name + " " + ability.Level, radius);
                return radius;
            }

            if (data.Width != null)
            {
                radius = ability.GetAbilityData(data.Width, abilityName: name);
                radiusDictionary.Add(name + " " + ability.Level, radius);
                return radius;
            }

            if (data.StringRadius != null)
            {
                radius = ability.GetAbilityData(data.StringRadius, abilityName: name);
                radiusDictionary.Add(name + " " + ability.Level, radius);
                return radius;
            }

            if (data.Radius > 0)
            {
                radius = data.Radius;
                radiusDictionary.Add(name + " " + ability.Level, radius);
                return radius;
            }

            if (!data.IsBuff)
            {
                return radius;
            }

            radius = (ability.Owner as Hero).GetAttackRange() + 150;
            radiusDictionary.Add(name + " " + ability.Level, radius);
            return radius;
        }
        /// <summary>
        /// Returns projectile speed of the ability
        /// </summary>
        /// <param name="ability">
        /// The ability.
        /// </param>
        /// <param name="abilityLevel">
        /// The ability Level.
        /// </param>
        /// <param name="abilityName">
        /// The ability Name.
        /// </param>
        /// <returns>
        /// The <see cref="float"/>.
        /// </returns>
        public static float GetProjectileSpeed(this Ability ability, uint abilityLevel, string abilityName = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return 0;
            }

            var level = abilityLevel != 0 ? abilityLevel : ability.Level;
            var name = abilityName ?? ability.StoredName();
            float speed;
            if (speedDictionary.TryGetValue(name + " " + level, out speed))
            {
                return speed;
            }

            var data = ability.CommonProperties();
            if (data == null)
            {
                speed = float.MaxValue;
                speedDictionary.Add(name + " " + level, speed);
                return speed;
            }

            if (data.Speed == null)
            {
                return speed;
            }

            speed = ability.GetAbilityData(data.Speed, abilityName: name);
            speedDictionary.Add(name + " " + level, speed);

            return speed;
        }
        /// <summary>
        ///     Returns prediction for given target after given ability hit delay
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="target">
        ///     The target.
        /// </param>
        /// <param name="customDelay">
        ///     enter your custom delay
        /// </param>
        /// <param name="abilityName">
        ///     The ability Name.
        /// </param>
        /// <returns>
        ///     The <see cref="Vector3" />.
        /// </returns>
        public static Vector3 GetPrediction(
            this Ability ability, 
            Unit target, 
            double customDelay = 0, 
            string abilityName = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return new Vector3();
            }

            if (target == null || !target.IsValid)
            {
                return new Vector3();
            }

            var name = abilityName ?? ability.StoredName();
            var data = ability.CommonProperties();
            var owner = ability.Owner as Unit;
            var delay = ability.GetCastDelay(owner as Hero, target, true, abilityName: name, useChannel: true);
            if (data != null)
            {
                delay += data.AdditionalDelay;
            }

            var speed = ability.GetProjectileSpeed(name);
            var radius = ability.GetRadius(name);
            Vector3 xyz;
            if (speed > 0 && speed < 6000)
            {
                xyz = Prediction.SkillShotXYZ(
                    owner, 
                    target, 
                    (float)((delay + owner.GetTurnTime(target.Position)) * 1000), 
                    speed, 
                    radius);
                if (!ability.IsAbilityBehavior(AbilityBehavior.NoTarget, name))
                {
                    xyz = Prediction.SkillShotXYZ(
                        owner, 
                        target, 
                        (float)((delay + (float)owner.GetTurnTime(xyz)) * 1000), 
                        speed, 
                        radius);
                }
            }
            else
            {
                xyz = Prediction.PredictedXYZ(target, (float)(delay * 1000));
            }

            return xyz;
        }
        /// <summary>
        ///     Checks all aspects and returns full delay before target gets hit by given ability
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="target">
        ///     The target.
        /// </param>
        /// <param name="abilityName">
        ///     The ability Name.
        /// </param>
        /// <returns>
        ///     The <see cref="double" />.
        /// </returns>
        public static double GetHitDelay(this Ability ability, Unit target, string abilityName = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return 0;
            }

            if (target == null || !target.IsValid)
            {
                return 0;
            }

            var name = abilityName ?? ability.StoredName();
            var owner = ability.Owner as Unit;
            var n = name + owner.StoredName() + target.StoredName();
            double storedDelay;
            var found = hitDelayDictionary.TryGetValue(n, out storedDelay);
            if (!found)
            {
                hitDelayDictionary.Add(n, 0);
            }

            if (found && !Utils.SleepCheck(n))
            {
                return storedDelay;
            }

            var data = ability.CommonProperties();
            var delay = ability.GetCastDelay(owner as Hero, target, true, abilityName: name);
            if (data != null)
            {
                delay += data.AdditionalDelay;
            }

            var speed = ability.GetProjectileSpeed(name);
            var radius = ability.GetRadius(name);
            if (!ability.IsAbilityBehavior(AbilityBehavior.NoTarget, name) && speed < 6000 && speed > 0)
            {
                var xyz = ability.GetPrediction(target, abilityName: name);
                delay += Math.Max((int)(owner.Distance2D(xyz) - radius / 2), 100) / speed;
            }

            if (name == "tinker_heat_seeking_missile")
            {
                var xyz = ability.GetPrediction(target, abilityName: name);
                delay += Math.Max(owner.Distance2D(xyz), 100) / speed;
            }

            hitDelayDictionary[n] = delay;
            Utils.Sleep(40, n);
            return delay;
        }
        /// <summary>
        ///     Returns cast range of ability, if ability is NonTargeted it will return its radius!
        /// </summary>
        /// <param name="ability">
        ///     The ability.
        /// </param>
        /// <param name="abilityName">
        ///     The ability Name.
        /// </param>
        /// <returns>
        ///     The <see cref="float" />.
        /// </returns>
        public static float GetCastRange(this Ability ability, string abilityName = null)
        {
            if (ability == null || !ability.IsValid)
            {
                return 0;
            }

            var name = abilityName ?? ability.StoredName();
            var owner = ability.Owner;
            var n = name + owner.Handle;
            if (castRangeDictionary.ContainsKey(n) && !Utils.SleepCheck("Common.GetCastRange." + n))
            {
                return castRangeDictionary[n];
            }

            if (name == "templar_assassin_meld")
            {
                return (ability.Owner as Unit).GetAttackRange() + 50;
            }

            var data = ability.CommonProperties();
            if (!ability.IsAbilityBehavior(AbilityBehavior.NoTarget, name))
            {
                var castRange = (float)ability.CastRange;
                var bonusRange = 0f;
                if (data != null && data.RealCastRange != null)
                {
                    castRange = ability.GetAbilityData(data.RealCastRange, abilityName: name);
                }

                if (castRange <= 0)
                {
                    castRange = 999999;
                }

                var hero = owner as Hero;
                if (hero != null && name == "dragon_knight_dragon_tail"
                    && hero.HasModifier("modifier_dragon_knight_dragon_form"))
                {
                    bonusRange = 250;
                }
                else if (hero != null && name == "beastmaster_primal_roar" && hero.AghanimState())
                {
                    bonusRange = 350;
                }

                var aetherLens = hero != null ? hero.FindItem("item_aether_lens", true) : null;
                if (aetherLens != null)
                {
                    bonusRange += aetherLens.GetAbilityData("cast_range_bonus");
                }

                if (!castRangeDictionary.ContainsKey(n))
                {
                    castRangeDictionary.Add(n, castRange + bonusRange);
                    Utils.Sleep(5000, "Common.GetCastRange." + n);
                }
                else
                {
                    castRangeDictionary[n] = castRange + bonusRange;
                    Utils.Sleep(5000, "Common.GetCastRange." + n);
                }

                return castRange + bonusRange;
            }

            float radius;
            if (data == null)
            {
                return ability.CastRange;
            }

            if (ability.StoredName() == "earthshaker_enchant_totem" && (owner as Hero).AghanimState())
            {
                radius = ability.GetAbilityData("scepter_distance") + 100;
            }
            else if (!data.FakeCastRange)
            {
                radius = ability.GetRadius(name);
            }
            else
            {
                radius = ability.GetAbilityData(data.RealCastRange, abilityName: name);
            }

            if (!castRangeDictionary.ContainsKey(n))
            {
                castRangeDictionary.Add(n, radius);
                Utils.Sleep(5000, "Common.GetCastRange." + n);
            }
            else
            {
                castRangeDictionary[n] = radius;
                Utils.Sleep(5000, "Common.GetCastRange." + n);
            }

            return radius;
        }