context: // Copyright 2014 - 2014 Esk0r
  // SkillshotDetector.cs is part of Evade.
  // 
  // Evade 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.
  // 
  // Evade 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 Evade. If not, see <http://www.gnu.org/licenses/>.
  
  #region
  
  using System;
  using System.Linq;
  using System.Text.RegularExpressions;
  using LeagueSharp;
  using LeagueSharp.Common;
  using SharpDX;
  
  #endregion
  
  using EloBuddy; namespace EvadeYas
  {
      internal static class SkillshotDetector
      {
          public delegate void OnDeleteMissileH(Skillshot skillshot, MissileClient missile);
  
          public delegate void OnDetectSkillshotH(Skillshot skillshot);
  
          static SkillshotDetector()
          {
              //Detect when the skillshots are created.
              //Game.OnProcessPacket += GameOnOnGameProcessPacket; // Used only for viktor's Laser :^)
              Obj_AI_Base.OnSpellCast += ObjAiHeroOnOnProcessSpellCast;
              Obj_AI_Base.OnProcessSpellCast += ObjAiHeroOnOnProcessSpellCast;
  
              //Detect when projectiles collide.
              GameObject.OnDelete += ObjSpellMissileOnOnDelete;
              GameObject.OnCreate += ObjSpellMissileOnOnCreate;
              //GameObject.OnCreate += GameObject_OnCreate; //TODO: Detect lux R and other large skillshots.
              GameObject.OnDelete += GameObject_OnDelete;
  
              if (Config.TestOnAllies && ObjectManager.Get<AIHeroClient>().Count() == 1)
              {
                  Game.OnWndProc += Game_OnWndProc;
              }
          }
  
          private static void Game_OnWndProc(WndEventArgs args)
          {
              if (args.Msg == (uint)WindowsMessages.WM_KEYUP)
              {
                  TriggerOnDetectSkillshot(
                      DetectionType.ProcessSpell, SpellDatabase.GetByName("TestSkillShot"), Utils.TickCount,
                      Program.PlayerPosition, Game.CursorPos.To2D(), ObjectManager.Player);
              }
          }
  
          private static void GameObject_OnCreate(GameObject sender, EventArgs args) { }
  
          private static void GameObject_OnDelete(GameObject sender, EventArgs args)
          {
              if (!sender.IsValid || !Config.TestOnAllies && sender.Team == ObjectManager.Player.Team)
              {
                  return;
              }
  
              for (var i = Program.DetectedSkillshots.Count - 1; i >= 0; i--)
              {
                  var skillshot = Program.DetectedSkillshots[i];
                  if (skillshot.SpellData.ToggleParticleName != "" && new Regex(skillshot.SpellData.ToggleParticleName).IsMatch(sender.Name))
                  {
                      Program.DetectedSkillshots.RemoveAt(i);
                  }
              }
          }
  
          private static void ObjSpellMissileOnOnCreate(GameObject sender, EventArgs args)
          {
              var missile = sender as MissileClient;
  
              if (missile == null || !missile.IsValid)
              {
                  return;
              }
  
              var unit = missile.SpellCaster as AIHeroClient;
  
              if (unit == null || !unit.IsValid || (unit.Team == ObjectManager.Player.Team && !Config.TestOnAllies))
              {
                  return;
              }
  
  
          /*Console.WriteLine(
                  Utils.TickCount + " Projectile Created: " + missile.SData.Name + " distance: " +
                  missile.SData.CastRange + "Radius: " +
                  missile.SData.LineWidth + " Speed: " + missile.SData.MissileSpeed);  */
  
  
              var spellData = SpellDatabase.GetByMissileName(missile.SData.Name);
  
              if (spellData == null)
              {
                  return;
              }
  
              LeagueSharp.Common.Utility.DelayAction.Add(0, delegate
              {
                  ObjSpellMissionOnOnCreateDelayed(sender, args);
              });
          }
  
          private static void ObjSpellMissionOnOnCreateDelayed(GameObject sender, EventArgs args)
          {
              var missile = sender as MissileClient;
  
              if (missile == null || !missile.IsValid)
              {
                  return;
              }
  
              var unit = missile.SpellCaster as AIHeroClient;
  
              if (unit == null || !unit.IsValid || (unit.Team == ObjectManager.Player.Team && !Config.TestOnAllies))
              {
                  return;
              }
  
  
              /*Console.WriteLine(
                      Utils.TickCount + " Projectile Created: " + missile.SData.Name + " distance: " +
                      missile.SData.CastRange + "Radius: " +
                      missile.SData.LineWidth + " Speed: " + missile.SData.MissileSpeed);  */
  
  
              var spellData = SpellDatabase.GetByMissileName(missile.SData.Name);
  
              if (spellData == null)
              {
                  return;
              }
  
              var missilePosition = missile.Position.To2D();
              var unitPosition = missile.StartPosition.To2D();
              var endPos = missile.EndPosition.To2D();
  
  
              //Calculate the real end Point:
              var direction = (endPos - unitPosition).Normalized();
              if (unitPosition.Distance(endPos) > spellData.Range || spellData.FixedRange)
              {
                  endPos = unitPosition + direction * spellData.Range;
              }
  
              if (spellData.ExtraRange != -1)
              {
                  endPos = endPos +
                           Math.Min(spellData.ExtraRange, spellData.Range - endPos.Distance(unitPosition)) * direction;
              }
  
              var castTime = Utils.TickCount - Game.Ping / 2 - (spellData.MissileDelayed ? 0 : spellData.Delay) -
                             (int)(1000f * missilePosition.Distance(unitPosition) / spellData.MissileSpeed);
  
              //Trigger the skillshot detection callbacks.
              TriggerOnDetectSkillshot(DetectionType.RecvPacket, spellData, castTime, unitPosition, endPos, unit);
          }
  
          /// <summary>
          /// Delete the missiles that collide.
          /// </summary>
          private static void ObjSpellMissileOnOnDelete(GameObject sender, EventArgs args)
          {
              var missile = sender as MissileClient;
  
              if (missile == null || !missile.IsValid)
              {
                  return;
              }
  
              var caster = missile.SpellCaster as AIHeroClient;
  
              if (caster == null || !caster.IsValid || (caster.Team == ObjectManager.Player.Team && !Config.TestOnAllies))
              {
                  return;
              }
  
              var spellName = missile.SData.Name;
  
              if (OnDeleteMissile != null)
              {
                  foreach (var skillshot in Program.DetectedSkillshots)
                  {
                      if (skillshot.SpellData.MissileSpellName == spellName &&
                          (skillshot.Unit.NetworkId == caster.NetworkId &&
                           (missile.EndPosition.To2D() - missile.StartPosition.To2D()).AngleBetween(skillshot.Direction) <
                           10) && skillshot.SpellData.CanBeRemoved)
                      {
                          OnDeleteMissile(skillshot, missile);
                          break;
                      }
                  }
              }
  
  #if DEBUG
              //Console.WriteLine(
                  //"Missile deleted: " + missile.SData.Name + " D: " + missile.EndPosition.Distance(missile.Position));
  #endif
  
              Program.DetectedSkillshots.RemoveAll(
                  skillshot =>
                      (skillshot.SpellData.MissileSpellName == spellName ||
                       skillshot.SpellData.ExtraMissileNames.Contains(spellName)) &&
                      (skillshot.Unit.NetworkId == caster.NetworkId &&
                       ((missile.EndPosition.To2D() - missile.StartPosition.To2D()).AngleBetween(skillshot.Direction) < 10) &&
                       skillshot.SpellData.CanBeRemoved || skillshot.SpellData.ForceRemove)); // 
          }
  
          /// <summary>
          ///     This event is fired after a skillshot is detected.
          /// </summary>
          public static event OnDetectSkillshotH OnDetectSkillshot;
  
          /// <summary>
          ///     This event is fired after a skillshot missile collides.
          /// </summary>
          public static event OnDeleteMissileH OnDeleteMissile;
  
  
          private static void TriggerOnDetectSkillshot(DetectionType detectionType,
              SpellData spellData,
              int startT,
              Vector2 start,
              Vector2 end,
              Obj_AI_Base unit)
          {
              var skillshot = new Skillshot(detectionType, spellData, startT, start, end, unit);
  
              if (OnDetectSkillshot != null)
              {
                  OnDetectSkillshot(skillshot);
              }
          }
  
          /// <summary>
          ///     Gets triggered when a unit casts a spell and the unit is visible.
          /// </summary>
          private static void ObjAiHeroOnOnProcessSpellCast(Obj_AI_Base sender, GameObjectProcessSpellCastEventArgs args)
          {
  
gt:             if (sender == null || !sender.IsValid)