context: #region LICENSE
  
  /*
   Copyright 2014 - 2015 LeagueSharp
   Orbwalking.cs is part of LeagueSharp.Common.
   
   LeagueSharp.Common is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   
   LeagueSharp.Common is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with LeagueSharp.Common. If not, see <http://www.gnu.org/licenses/>.
  */
  
  #endregion
  
  #region
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using SharpDX;
  using Color = System.Drawing.Color;
  using LeagueSharp;
  using LeagueSharp.Common;
  #endregion
  
  using EloBuddy; 
   using LeagueSharp.Common; 
   namespace HoolaRengar
  {
  
      /// <summary>
      ///     This class offers everything related to auto-attacks and orbwalking.
      /// </summary>
      public static class Orbwalking
      {
          internal static bool HasBuff2(this Obj_AI_Base unit,
              string buffName,
              bool dontUseDisplayName = false)
          {
              return
                  unit.Buffs.Any(
                      buff =>
                          ((dontUseDisplayName &&
                            String.Equals(buff.Name, buffName, StringComparison.CurrentCultureIgnoreCase)) ||
                           (!dontUseDisplayName &&
                            String.Equals(buff.DisplayName, buffName, StringComparison.CurrentCultureIgnoreCase))) &&
                          buff.IsValidBuff());
          }
  
          public delegate void BeforeAttackEvenH(BeforeAttackEventArgs args);
  
          public delegate void OnAttackEvenH(AttackableUnit unit, AttackableUnit target);
  
          public delegate void OnNonKillableMinionH(AttackableUnit minion);
  
          public delegate void OnTargetChangeH(AttackableUnit oldTarget, AttackableUnit newTarget);
          public enum OrbwalkingMode
          {
              LastHit,
              Mixed,
              LaneClear,
              Combo,
              None
          }
  
  
          //Spells that are not attacks even if they have the "attack" word in their name.
          private static readonly string[] NoAttacks =
          {
              "volleyattack", "volleyattackwithsound", "jarvanivcataclysmattack",
              "monkeykingdoubleattack", "shyvanadoubleattack",
              "shyvanadoubleattackdragon", "zyragraspingplantattack",
              "zyragraspingplantattack2", "zyragraspingplantattackfire",
              "zyragraspingplantattack2fire", "viktorpowertransfer",
              "sivirwattackbounce", "asheqattacknoonhit",
              "elisespiderlingbasicattack", "heimertyellowbasicattack",
              "heimertyellowbasicattack2", "heimertbluebasicattack",
              "annietibbersbasicattack", "annietibbersbasicattack2",
              "yorickdecayedghoulbasicattack", "yorickravenousghoulbasicattack",
              "yorickspectralghoulbasicattack", "malzaharvoidlingbasicattack",
              "malzaharvoidlingbasicattack2", "malzaharvoidlingbasicattack3",
              "kindredwolfbasicattack", "kindredbasicattackoverridelightbombfinal"
          };
  
          //Spells that are attacks even if they dont have the "attack" word in their name.
          private static readonly string[] Attacks =
          {
              "caitlynheadshotmissile", "frostarrow", "garenslash2",
              "kennenmegaproc", "lucianpassiveattack", "masteryidoublestrike", "quinnwenhanced", "renektonexecute",
              "renektonsuperexecute", "rengarnewpassivebuffdash", "trundleq", "xenzhaothrust", "xenzhaothrust2",
              "xenzhaothrust3", "viktorqbuff"
          };
  
          // Champs whose auto attacks can't be cancelled
          public static int LastAATick;
          public static bool Attack = true;
          public static bool DisableNextAttack;
          public static bool Move = true;
          public static int LastMoveCommandT;
          public static Vector3 LastMoveCommandPosition = Vector3.Zero;
          public static AttackableUnit _lastTarget;
          private static readonly AIHeroClient Player;
          private static float _minDistance = 400;
          private static readonly Random _random = new Random(DateTime.Now.Millisecond);
  
          static Orbwalking()
          {
              Player = ObjectManager.Player;
              Obj_AI_Base.OnProcessSpellCast += OnProcessSpell;
              Spellbook.OnStopCast += SpellbookOnStopCast;
          }
          /// <summary>
          ///     This event is fired before the player auto attacks.
          /// </summary>
          public static event BeforeAttackEvenH BeforeAttack;
  
          /// <summary>
          ///     This event is fired when a unit is about to auto-attack another unit.
          /// </summary>
          public static event OnAttackEvenH OnAttack;
  
          /// <summary>
          ///     Gets called on target changes
          /// </summary>
          public static event OnTargetChangeH OnTargetChange;
  
          //  <summary>
          //      Gets called if you can't kill a minion with auto attacks
          //  </summary>
          public static event OnNonKillableMinionH OnNonKillableMinion;
  
          private static void FireBeforeAttack(AttackableUnit target)
          {
              if (BeforeAttack != null)
              {
                  BeforeAttack(new BeforeAttackEventArgs { Target = target });
              }
              else
              {
                  DisableNextAttack = false;
              }
          }
  
          private static void FireOnAttack(AttackableUnit unit, AttackableUnit target)
          {
              if (OnAttack != null)
              {
                  OnAttack(unit, target);
              }
          }
  
  
          private static void FireOnTargetSwitch(AttackableUnit newTarget)
          {
              if (OnTargetChange != null && (!_lastTarget.IsValidTarget() || _lastTarget != newTarget))
              {
                  OnTargetChange(_lastTarget, newTarget);
              }
          }
  
          private static void FireOnNonKillableMinion(AttackableUnit minion)
          {
              if (OnNonKillableMinion != null)
              {
                  OnNonKillableMinion(minion);
              }
          }
  
          /// <summary>
          ///     Returns true if the spellname is an auto-attack.
          /// </summary>
          public static bool IsAutoAttack(string name)
          {
              return (name.ToLower().Contains("attack") && !NoAttacks.Contains(name.ToLower())) ||
                     Attacks.Contains(name.ToLower());
          }
  
          /// <summary>
          ///     Returns the auto-attack range of local player with respect to the target.
          /// </summary>
          public static float GetRealAutoAttackRange(AttackableUnit target)
          {
              var result = Player.AttackRange + Player.BoundingRadius;
              if (target.IsValidTarget())
              {
                  return result + target.BoundingRadius;
              }
              return result;
          }
  
          /// <summary>
          ///     Returns the auto-attack range of the target.
          /// </summary>
          public static float GetAttackRange(AIHeroClient target)
          {
              var result = target.AttackRange + target.BoundingRadius;
              return result;
          }
  
          /// <summary>
          ///     Returns true if the target is in auto-attack range.
          /// </summary>
          public static bool InAutoAttackRange(AttackableUnit target)
          {
              if (!target.IsValidTarget())
              {
                  return false;
              }
              var myRange = GetRealAutoAttackRange(target);
              return
                  Vector2.DistanceSquared(
                      (target is Obj_AI_Base) ? ((Obj_AI_Base)target).ServerPosition.To2D() : target.Position.To2D(),
                      Player.ServerPosition.To2D()) <= myRange * myRange;
          }
  
  
          /// <summary>
          ///     Returns if the player's auto-attack is ready.
          /// </summary>
          public static bool CanAttack()
          {
              return Utils.GameTimeTickCount >= LastAATick + Player.AttackDelay * 1000 && Attack;
          }
  
          /// <summary>
          ///     Returns true if moving won't cancel the auto-attack.
          /// </summary>
          public static bool CanMove(float extraWindup)
          {
              if (!Move)
              {
                  return false;
              }
  
              return (Utils.GameTimeTickCount >= LastAATick + Player.AttackCastDelay * 980 + extraWindup);
          }
  
  
          public static void SetMinimumOrbwalkDistance(float d)
          {
              _minDistance = d;
          }
  
          public static float GetLastMoveTime()
          {
              return LastMoveCommandT;
          }
  
          public static Vector3 GetLastMovePosition()
          {
              return LastMoveCommandPosition;
          }
  
          public static void MoveTo(Vector3 position,
              float holdAreaRadius = 0,
              bool overrideTimer = false,
              bool useFixedDistance = true,
              bool randomizeMinDistance = true)
          {
              var playerPosition = Player.ServerPosition;
  
              if (playerPosition.Distance(position, true) < holdAreaRadius * holdAreaRadius)
              {
                  if (Player.Path.Length > 0)
                  {
                      EloBuddy.Player.IssueOrder(GameObjectOrder.Stop, playerPosition);
                      LastMoveCommandPosition = playerPosition;
                      LastMoveCommandT = Utils.GameTimeTickCount - 70;
                  }
                  return;
              }
  
              var point = position;
  
              if (Player.Distance(point, true) < 150 * 150)
              {
                  point = playerPosition.Extend(position, (randomizeMinDistance ? (_random.NextFloat(0.6f, 1) + 0.2f) * _minDistance : _minDistance));
              }
              var angle = 0f;
              var currentPath = Player.GetWaypoints();
              if (currentPath.Count > 1 && currentPath.PathLength() > 100)
              {
                  var movePath = Player.GetPath(point);
  
                  if (movePath.Length > 1)
                  {
                      var v1 = currentPath[1] - currentPath[0];
                      var v2 = movePath[1] - movePath[0];
                      angle = v1.AngleBetween(v2.To2D());
                      var distance = movePath.Last().To2D().Distance(currentPath.Last(), true);
  
                      if ((angle < 10 && distance < 500 * 500) || distance < 50 * 50)
                      {
                          return;
                      }
                  }
              }
  
              if (Utils.GameTimeTickCount - LastMoveCommandT < (70 + Math.Min(60, Game.Ping)) && !overrideTimer && angle < 60)
              {
                  return;
              }
  
              if (angle >= 60 && Utils.GameTimeTickCount - LastMoveCommandT < 60)
              {
                  return;
              }
  
              EloBuddy.Player.IssueOrder(GameObjectOrder.MoveTo, point);
              LastMoveCommandPosition = point;
              LastMoveCommandT = Utils.GameTimeTickCount;
          }
  
          /// <summary>
          ///     Orbwalk a target while moving to Position.
          /// </summary>
          public static void Orbwalk(AttackableUnit target,
              Vector3 position,
              float extraMoveup = 5,
              float holdAreaRadius = 0,
              bool useFixedDistance = true,
              bool randomizeMinDistance = true)
          {
              if (target.IsValidTarget() && CanAttack() && Attack)
              {
                  DisableNextAttack = false;
                  FireBeforeAttack(target);
  
                  if (!DisableNextAttack)
                  {
                      EloBuddy.Player.IssueOrder(GameObjectOrder.AttackUnit, target);
                      _lastTarget = target;
                      return;
                  }
              }
  
              if (CanMove(extraMoveup) && Move)
              {
                  MoveTo(position, holdAreaRadius, false, useFixedDistance, randomizeMinDistance);
              }
          }
  
          /// <summary>
          ///     Resets the Auto-Attack timer.
          /// </summary>
          public static void ResetAutoAttackTimer()
          {
              LastAATick = 0;
          }
  
          private static void SpellbookOnStopCast(Obj_AI_Base spellbook, SpellbookStopCastEventArgs args)
          {
              if (spellbook.IsValid && spellbook.IsMe && args.DestroyMissile && args.StopAnimation)
              {
                  ResetAutoAttackTimer();
              }
          }
  
          private static void OnProcessSpell(Obj_AI_Base unit, GameObjectProcessSpellCastEventArgs Spell)
          {
              var spellName = Spell.SData.Name;
  
              if (!IsAutoAttack(spellName))
              {
                  return;
              }
  
              if (unit.IsMe &&
                  (Spell.Target is Obj_AI_Base || Spell.Target is Obj_BarracksDampener || Spell.Target is Obj_HQ))
              {
                  LastAATick = Utils.GameTimeTickCount;
  
                  if (Spell.Target is Obj_AI_Base)
                  {
                      var target = (Obj_AI_Base)Spell.Target;
                      if (target.IsValid)
                      {
                          FireOnTargetSwitch(target);
                          _lastTarget = target;
                      }
                  }
              }
  
              FireOnAttack(unit, _lastTarget);
          }
  
          public class BeforeAttackEventArgs
          {
              private bool _process = true;
              public AttackableUnit Target;
              public Obj_AI_Base Unit = ObjectManager.Player;
  
              public bool Process
              {
                  get { return _process; }
                  set
                  {
                      DisableNextAttack = !value;
                      _process = value;
                  }
              }
          }
  
          /// <summary>
          ///     This class allows you to add an instance of "Orbwalker" to your assembly in order to control the orbwalking in an
          ///     easy way.
          /// </summary>
          public class Orbwalker
          {
              private const float LaneClearWaitTimeMod = 2f;
              private static Menu _config;
              private readonly AIHeroClient Player;
              private Obj_AI_Base _forcedTarget;
              private OrbwalkingMode _mode = OrbwalkingMode.None;
              private Vector3 _orbwalkingPoint;
              private Obj_AI_Minion _prevMinion;
              public static List<Orbwalker> Instances = new List<Orbwalker>();
  
              public Orbwalker(Menu attachToMenu)
              {
                  _config = attachToMenu;
                  /* Drawings submenu */
                  var drawings = new Menu("Drawings", "drawings");
                  drawings.AddItem(
                      new MenuItem("AACircle", "AACircle").SetShared()
                          .SetValue(new Circle(true, Color.FromArgb(155, 255, 255, 0))));
                  drawings.AddItem(
                      new MenuItem("AACircle2", "Enemy AA circle").SetShared()
                          .SetValue(new Circle(false, Color.FromArgb(155, 255, 255, 0))));
                  drawings.AddItem(
                      new MenuItem("HoldZone", "HoldZone").SetShared()
                          .SetValue(new Circle(false, Color.FromArgb(155, 255, 255, 0))));
                  drawings.AddItem(
                      new MenuItem("AALineWidth", "Line Width")).SetShared()
                      .SetValue(new Slider(2, 1, 6));
                  _config.AddSubMenu(drawings);
  
                  /* Misc options */
                  var misc = new Menu("Misc", "Misc");
                  misc.AddItem(
                      new MenuItem("HoldPosRadius", "Hold Position Radius").SetValue(new Slider(65, 0, 250)));
                  misc.AddItem(new MenuItem("PriorizeFarm", "Priorize farm over harass").SetValue(true));
                  misc.AddItem(new MenuItem("AttackWards", "Auto attack wards").SetShared().SetValue(false));
                  misc.AddItem(new MenuItem("AttackPetsnTraps", "Auto attack pets & traps").SetShared().SetValue(true));
                  misc.AddItem(new MenuItem("Smallminionsprio", "Jungle clear small first").SetShared().SetValue(false));
                  _config.AddSubMenu(misc);
  
                  _config.AddItem(
                      new MenuItem("ExtraMoveup", "Move delay After AA").SetValue(new Slider(50, 0, 100)));
  
  
                  /*Load the menu*/
                  _config.AddItem(
                      new MenuItem("LastHit", "Last hit").SetValue(new KeyBind('X', KeyBindType.Press)));
  
                  _config.AddItem(new MenuItem("Harass", "Harass").SetValue(new KeyBind('C', KeyBindType.Press)));
  
                  _config.AddItem(new MenuItem("Harass.MLH", "Lasthit While Harass").SetValue(true));
  
                  _config.AddItem(
                      new MenuItem("LaneClear", "LaneClear").SetValue(new KeyBind('V', KeyBindType.Press)));
  
                  _config.AddItem(
                      new MenuItem("Orbwalk", "Combo").SetValue(new KeyBind(32, KeyBindType.Press)));
  
                  Player = ObjectManager.Player;
                  Game.OnUpdate += GameOnOnGameUpdate;
                  Drawing.OnDraw += DrawingOnOnDraw;
                  Instances.Add(this);
              }
  
              public virtual bool InAutoAttackRange(AttackableUnit target)
              {
                  return Orbwalking.InAutoAttackRange(target);
              }
  
              public OrbwalkingMode ActiveMode
              {
                  get
                  {
                      if (_mode != OrbwalkingMode.None)
                      {
                          return _mode;
                      }
  
                      if (_config.Item("Orbwalk").GetValue<KeyBind>().Active)
                      {
                          return OrbwalkingMode.Combo;
                      }
  
                      if (_config.Item("LaneClear").GetValue<KeyBind>().Active)
                      {
                          return OrbwalkingMode.LaneClear;
                      }
  
                      if (_config.Item("Harass").GetValue<KeyBind>().Active)
                      {
                          return OrbwalkingMode.Mixed;
                      }
  
                      if (_config.Item("LastHit").GetValue<KeyBind>().Active)
                      {
                          return OrbwalkingMode.LastHit;
                      }
  
                      return OrbwalkingMode.None;
                  }
                  set { _mode = value; }
              }
  
              /// <summary>
              ///     Enables or disables the auto-attacks.
              /// </summary>
              public void SetAttack(bool b)
              {
                  Attack = b;
              }
  
              /// <summary>
              ///     Enables or disables the movement.
              /// </summary>
              public void SetMovement(bool b)
              {
                  Move = b;
              }
  
              /// <summary>
              ///     Forces the orbwalker to attack the set target if valid and in range.
              /// </summary>
              public void ForceTarget(Obj_AI_Base target)
              {
                  _forcedTarget = target;
              }
  
              /// <summary>
              ///     Forces the orbwalker to move to that point while orbwalking (Game.CursorPos by default).
              /// </summary>
              public void SetOrbwalkingPoint(Vector3 point)
              {
                  _orbwalkingPoint = point;
              }
  
              private bool ShouldWait()
              {
                  return
                      ObjectManager.Get<Obj_AI_Minion>()
                          .Any(
                              minion =>
                                  minion.IsValidTarget() && minion.Team != GameObjectTeam.Neutral &&
                                  InAutoAttackRange(minion) && MinionManager.IsMinion(minion, false) &&
                                  HealthPrediction.LaneClearHealthPrediction(
                                      minion, (int)((Player.AttackDelay * 1000) * LaneClearWaitTimeMod), 0) <=
                                  Player.GetAutoAttackDamage(minion));
              }
  
              public virtual AttackableUnit GetTarget()
              {
                  AttackableUnit result = null;
  
                  if ((ActiveMode == OrbwalkingMode.Mixed || ActiveMode == OrbwalkingMode.LaneClear) &&
                      !_config.Item("PriorizeFarm").GetValue<bool>())
                  {
                      var target = TargetSelector.GetTarget(-1, TargetSelector.DamageType.Physical);
                      if (target != null && InAutoAttackRange(target))
                      {
                          return target;
                      }
                  }
  
                  /*Killable Minion*/
                  if (ActiveMode == OrbwalkingMode.LaneClear || ActiveMode == OrbwalkingMode.Mixed && _config.Item("Harass.MLH").GetValue<bool>() ||
                      ActiveMode == OrbwalkingMode.LastHit)
                  {
                      var MinionList =
                          ObjectManager.Get<Obj_AI_Minion>()
                              .Where(
                                  minion =>
                                      minion.IsValidTarget() && InAutoAttackRange(minion))
                                      .OrderByDescending(minion => minion.CharData.BaseSkinName.Contains("Siege"))
                                      .ThenBy(minion => minion.CharData.BaseSkinName.Contains("Super"))
                                      .ThenBy(minion => minion.Health)
                                      .ThenByDescending(minion => minion.MaxHealth);
  
                      foreach (var minion in MinionList)
                      {
                          var t = (int)(Player.AttackCastDelay * 1000) - 100 +
                                  1000 * (int)Math.Max(0, Player.Distance(minion) - Player.BoundingRadius) / int.MaxValue;
                          var predHealth = HealthPrediction.GetHealthPrediction(minion, t, 0);
  
                          if (minion.Team != GameObjectTeam.Neutral && (_config.Item("AttackPetsnTraps").GetValue<bool>() || MinionManager.IsMinion(minion, _config.Item("AttackWards").GetValue<bool>())))
                          {
                              if (predHealth <= 0)
                              {
                                  FireOnNonKillableMinion(minion);
                              }
  
                              if (predHealth > 0 && predHealth <= Player.GetAutoAttackDamage(minion, true))
                              {
                                  return minion;
                              }
                          }
                      }
                  }
  
                  //Forced target
                  if (_forcedTarget.IsValidTarget() && InAutoAttackRange(_forcedTarget))
                  {
                      return _forcedTarget;
                  }
  
                  /* turrets / inhibitors / nexus */
                  if (ActiveMode == OrbwalkingMode.LaneClear)
                  {
                      /* turrets */
                      foreach (var turret in
                          ObjectManager.Get<Obj_AI_Turret>().Where(t => t.IsValidTarget() && InAutoAttackRange(t)))
                      {
                          return turret;
                      }
  
                      /* inhibitor */
                      foreach (var turret in
                          ObjectManager.Get<Obj_BarracksDampener>().Where(t => t.IsValidTarget() && InAutoAttackRange(t)))
                      {
                          return turret;
                      }
  
                      /* nexus */
                      foreach (var nexus in
                          ObjectManager.Get<Obj_HQ>().Where(t => t.IsValidTarget() && InAutoAttackRange(t)))
                      {
                          return nexus;
                      }
                  }
  
                  /*Champions*/
                  if (ActiveMode != OrbwalkingMode.LastHit)
                  {
                      var target = TargetSelector.GetTarget(-1, TargetSelector.DamageType.Physical);
                      if (target.IsValidTarget() && InAutoAttackRange(target))
                      {
                          return target;
                      }
                  }
  
                  /*Jungle minions*/
                  if (ActiveMode == OrbwalkingMode.LaneClear || ActiveMode == OrbwalkingMode.Mixed)
                  {
                      var jminions =
                          ObjectManager.Get<Obj_AI_Minion>()
                              .Where(
                                  mob =>
                                      mob.IsValidTarget() && mob.Team == GameObjectTeam.Neutral && InAutoAttackRange(mob) &&
                                      mob.CharData.BaseSkinName != "gangplankbarrel");
  
                      result = _config.Item("Smallminionsprio").GetValue<bool>() ? jminions.MinOrDefault(mob => mob.MaxHealth) : jminions.MaxOrDefault(mob => mob.MaxHealth);
  
                      if (result != null)
                      {
                          return result;
                      }
                  }
  
                  /*Lane Clear minions*/
                  if (ActiveMode == OrbwalkingMode.LaneClear)
                  {
                      if (!ShouldWait())
                      {
                          if (_prevMinion.IsValidTarget() && InAutoAttackRange(_prevMinion))
                          {
                              var predHealth = HealthPrediction.LaneClearHealthPrediction(
                                  _prevMinion, (int)((Player.AttackDelay * 1000) * LaneClearWaitTimeMod), 0);
                              if (predHealth >= 2 * Player.GetAutoAttackDamage(_prevMinion) ||
                                  Math.Abs(predHealth - _prevMinion.Health) < float.Epsilon)
                              {
                                  return _prevMinion;
                              }
                          }
  
                          result = (from minion in
                                        ObjectManager.Get<Obj_AI_Minion>()
                                            .Where(minion => minion.IsValidTarget() && InAutoAttackRange(minion) &&
                                            (_config.Item("AttackWards").GetValue<bool>() || !MinionManager.IsWard(minion.CharData.BaseSkinName.ToLower())) &&
                                            (_config.Item("AttackPetsnTraps").GetValue<bool>() && minion.CharData.BaseSkinName != "jarvanivstandard" || MinionManager.IsMinion(minion, _config.Item("AttackWards").GetValue<bool>())) &&
                                            minion.CharData.BaseSkinName != "gangplankbarrel")
                                    let predHealth =
                                        HealthPrediction.LaneClearHealthPrediction(
                                            minion, (int)((Player.AttackDelay * 1000) * LaneClearWaitTimeMod), 0)
                                    where
                                        predHealth >= 2 * Player.GetAutoAttackDamage(minion) ||
  
gt:                                       Math.Abs(predHealth - minion.Health) < float.Epsilon