All pastes #962809 Raw Edit

Etwas

public text v1 · immutable
#962809 ·published 2008-03-30 00:52 UTC
rendered paste body

/* Release Notes
 * 
 * You will need to add these keys to get the associated behaviors...
 * <Key KeyName="Hunter.Disengage" BarState="Combat" ShiftState="None" Char="YOURKEYHERE" />
 * <Key KeyName="Hunter.Cheetah" BarState="Rest" ShiftState="None" Char="YOURKEYHERE" />
 * <Key KeyName="Hunter.Monkey" BarState="Rest" ShiftState="None" Char="YOURKEYHERE" />
 * 
 * Features:
 * -attempts to conserve some mana for pet healing.
 * -pulls the pet back to the player if the pet is in danger of agro
 * -pulls mobs to the player if they have friends, sends in the pet if not.
 * -allows the pet to eat prior to combat if needed.
 * -uses "MobRadar" to attempt to pick a safe spot when repositioning the player.
 * -pulls a ranged target to finish them via melee if configured
 * - conserves ammo when low on ammo
 * -switches to melee only when out of ammo
 * 
 * Ver: 0.1.2
 * - Updated the distance management and the direction management to not do a "double take" when backin away from a mob and the mob advances enough to require another back away.
 * -changed the kill loop to remove all the continue statements and use a consumed flag to control the speed of the loop.
 * -Extracted the meat of KillTarget into 2 subs called KillMonster, KillPlayer.  KillPlayer is currently completely non-functional but I will be adding some basic PVP actions here.
 * -Updated the config a bit to reflect the current changes.
 * -Organized some
 * -Changed the "vanished" check to only cover vanishes if they are original targets and not ambushes.  This code was added to minimized the target disappearing error when your pet kills the oneshot adds around the "swarmer" type of mobs.
 * 
 * Ver: 0.1.3
 * - Added pull pet on low mob health to a hardcoded test.
 * 
 * Ver: 0.1.4
 * -Fixed targetting issue if we ever lose our current target.
 * -fixed some very basic and mostly untested feign code.
 * 
 * Ver: 0.1.5
 * -Fixed a movement bug that didn't allow the toon to chase a mob to finish it off when the pet was recalled.
 * -Fixed a resuming shooting bug when disengage removed the target
 * 
 * Ver: 0.1.6
 * -Changed the key pressing to use CastSpell seems to be a bit more stable.
 * - Casts mark on all mobs.
 * 
 * Version 0.1.7
 * -Removed some old config values
 * -Fixed bug forcing the config values back to defaults each time
 * -added config for recall pet to finish combat with melee
 * -pet operates in passive or defensive
 * 
 * Version 0.1.8
 * -Added ammo check and ammo conserve mode
 * -Added ammo threshhold to config
 * 
 * Version 0.1.9
 * -Added aspect of the hawk buff check rather than spamming the button
 * -Added KeepAwayList file code for testing
 * -Added Hearth and log on ammo and configuration
 * 
 * Version 0.1.10
 * -Added support for Intimidation and Wrath.
 * -Added 10% chance it skips the first shot
 * -Added 30% chance it skips the second shot
 * -then it mixes intimidation, wrath, and arcane a little during combat.
 * -also small chance that we will pull the pet back for a melee finish even when you don't have that option configured.
 * 
 * Version 0.1.11
 * -Added a feed pet check at the end of combat and then wait for the combat flag to clear and then feed.
 * -Took out some pet targeting stuff that messes with the glider next target stuff sometimes.
 * -Removed the GConfig.dll stuff and am running the builtin config that is in 1.5.2 beta.
 * 
 * Version 0.1.12
 * -Added Cheetah, Monkey, Hawk aspect management, if configured.
 * -Added Cheetah clear distance to config
 * -Added UseIntimidate to config.  Currently It uses intimidation when a mob starts casting and when finishing to catch runners.
 * -Added UseWrath to config.
 * -Added some timing tweaks to the end of combat to help with loot timing... seems to help TurboLoot not fail so much and xp tracking to be more accurate.
 * -Other various minor tweaks.
 * -Still requires 1.5.2 beta for config.
 * 
 * Version 0.1.13
 * -Added configurable aspects.  You can choose your ranged, melee and travel aspects as you wish now via the config.
 * -Removed Aspect management config option.  Setting all aspects to None should have the same affect as disabling in the old version.
 * -minor combat tweaks.
  * * -Added Pet delay to the config dialog.  set the time to wait before starting shooting.
 * -Uses the current profile coords as a homebase for our radar.
 * -Still requires 1.5.2 RC3 (maybe RC2?) for config.
 * 
 * Version 0.1.14
 * -Changed aspect buffs to check by name.  You need to reload your config values if you used 0.1.13 to set these by ID.
 * 
 * Version 0.1.5
 * -MobRadar updated to watch for the path between points.  Should be significantly safer points picked.
 * -Added backup when disengaging and if that fails then we turn and run.
 * -Added a movement err check and if we get stuck once, we mark the position in our keepaway list and finish via melee.
 * -Added a check for elevation and if the monster is more than 2 yards different elevation than us we pull him to us at 50% health for a melee finish.
 * 
 * Version 0.1.16
 * -refined the elevation check to do a standard melee finish and up the limit to 5 yards.
 * -changed intimidation to be activated on pet agro management. (when we have agro and we are trying to dump it on the pet, and we have multiple attackers)
 * -moved wrath up in the priority list.
 * -added a first attempt at pet agro management for adds.
 *  
 * 0.2.0 Features:
 * -Uses mob radar to track movement issues and mob danger when moving.
 * -Switches between three aspects if configured, (ranged, melee, travel)
 * -Auto logs on ammo.
 * -Can switch to ammo conservation mode if configured
 * -Can pull the mob back to the player for a finish via melee if configured
 * -Handles adds using pet targetting.
 * -configurable mana conservation
 * -first try at PVP
 * 
 * 0.2.2
 * -PVP exception fix.
 * 
 * 0.2.3
 * -Added strafe "backup" and fixed a chase that resulted in a long backup timeout. Now
 * if you are retreating from a mob and he retargets you it will immediately stop backing 
 * away and re engage.
 * -Relaxed my limits when facing a mob
 * -Added the elevation limit to config.
 * -Should use our combat start location as our homebase for combat in PvP and PvE.
 * 
 * 0.2.4
 * -Change the strafe backup code a little to choose right or left or forward.
 * -Added some checks for live pet to prevent some weirdness with a dead pet.
 * 
 * 0.2.5 
 * -Changed from xml file for config to an integral xml string.
 * -Added a check to remove the old xml file.
 * -Changed the keep away list to be more forgiving in tight spots.
 * -Added a repeat on second shot every 15 seconds when the mob is above 50% health to renew the sting. if mana allows.
 * 
 * 0.2.6
 * -Fixed a "Face" bug that causes the combat loop to stall at face everytime.  When heading was near
 * north (0) my math was falling apart
 * 
 * 0.2.7
 * -Added some code to increase your pet mend threshold by 20% for each add you get.
 * -Fixed some null pet exceptions.
 * -Changed the facing logic a touch.
 * 
 * 0.2.8
 * -Added some pet travelling checks, 1) watches the heading of the pet and if it deviates from the actual direction of the mob
 * we assume he is travelling around something 2) check the distance the pet is from a straight line path between the player and
 * the mob.  if either of these is violated we pull back for a melee finish
 * -changed to always use the current profile location in our running around code
 * 
 * 0.2.9
 * -Removed ambush from the pet recall code.  was causing problems with my pet tracking recall with ambushes.
 * -added a slight delay to help the timing of the casting
 * -fixed the pet tracking messages spamming the log
 * 
 * 0.2.10
 * -Removed the keepawaypoints file.  they seem to be causing more harm than good.  removed for now.
 * -moved the conserve ammo threshold down a bit in health.
 * 
 * 0.2.11
 * -Added Config for Approach for melee finish.
 * 
 * 0.2.12
 * -Added a check so that we don't approach for melee finish when we get adds.
 * -Moved FirstShot up the priority list a bit.  it seemed to be taking a while to get the first shot off some times.
 * 
 * 0.2.13 (unstable release)
 * -Trapped any exceptions in KillMonster and then post a message and retry combat
 * -unwinds waypoints and follows them as we retreat during combat.  At the end of combat it resets the waypoints to the
 * way they were before.
 * 
 * 0.2.14
 * -fixed frozen shutdown error
 * -Changed pet attack to be WAY earlier (should speed up loop)
 * 
 * 0.2.15
 * -Actually fixed the frozen shutdown error...
 */
 
//!Reference: WowTriangles.dll
//!Reference: System.Drawing.dll
//!Reference: PPather.dll

using Pather;
using System;
using System.Threading;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Glider.Common.Objects;

namespace Glider.Common.Objects
{

    
    public class SimpleHunter : PPather
    {

        #region TendencyManager
        private class TendencyManager
        {

            // Class to manage tendency of one mob
            private class MobTendency
            {
                private string Name;
                private List<string> TendencyList = new List<string>();
                private bool locked = false;

                // create and decode a line from the tendency file
                public MobTendency(string Line)
                {
                    //split it
                    char[] splitter = { ':', ',' };
                    string[] fields = Line.Split(splitter);

                    if (fields != null && fields.Length > 1)
                    {
                        Name = fields[0];

                        for (int x = 1; x < fields.Length; x++)
                        {
                            AddTendency(fields[x]);
                        }
                    }
                }

                public MobTendency(string MobName, string InitialTendency)
                {
                    Name = MobName;
                    AddTendency(InitialTendency);
                }

                public string MobName
                {
                    get { return Name; }
                }

                public bool HasTendency(string Tendency)
                {
                    return TendencyList.Contains(Tendency);
                }

                public bool AddTendency(string Tendency)
                {
                    if (locked) return false;
                    if (Tendency == "") return false;
                    if (Tendency == "locked")
                    {
                        locked = true;
                        //GContext.Main.Log(MobName + " is locked");
                    }
                    bool retval;
                    if (!HasTendency(Tendency))
                    {
                        TendencyList.Add(Tendency);
                        //GContext.Main.Log(MobName + " is '" + Tendency + "'");
                        retval = true;
                    }
                    else
                    {
                        retval = false;
                    }
                    return retval;
                }

                public override string ToString()
                {
                    StringBuilder x = new StringBuilder();

                    x.Append(Name + ":");

                    for (int idx = 0; idx < TendencyList.Count; idx++)
                    {
                        x.Append(TendencyList[idx]);
                        if (idx < TendencyList.Count - 1)
                            x.Append(",");
                    }
                    if (locked)
                        x.Append(",locked");

                    return x.ToString();
                }
            }
            private Dictionary<string, MobTendency> MobList = new Dictionary<string, MobTendency>();

            public bool SaveToFile()
            {
                return SaveToFile("Classes/MobTendencies.txt");
            }

            public bool SaveToFile(string FileName)
            {
                bool retval;

                if (MobList != null)
                {
                    System.IO.StreamWriter fileout = System.IO.File.CreateText(FileName);

                    if (fileout != null)
                    {
                        retval = true;

                        foreach (KeyValuePair<string, MobTendency> kvp in MobList)
                        {
                            MobTendency x = kvp.Value;
                            fileout.WriteLine(x.ToString());
                        }

                        fileout.Flush();
                        fileout.Close();

                    }
                    else
                    {
                        retval = false;
                    }

                }
                else
                {
                    retval = false;
                }
                return retval;

            }


            public bool LoadFromFile()
            {
                return LoadFromFile("Classes/MobTendencies.txt");
            }

            public bool LoadFromFile(string FileName)
            {

                //System.IO.StreamWriter fileout = System.IO.File.CreateText(FileName);
                bool retval = true;
                MobList.Clear();

                if (FileName != "" && System.IO.File.Exists(FileName))
                {
                    System.IO.StreamReader filein = System.IO.File.OpenText(FileName);

                    if (filein != null)
                    {
                        //read the lines of the file....

                        while (!filein.EndOfStream)
                        {
                            try
                            {
                                MobTendency t = new MobTendency(filein.ReadLine());
                                MobList.Add(t.MobName, t);
                            }
                            catch
                            {
                                //error parsing the line...
                            }
                        }

                        filein.Close();
                    }
                    else
                    {
                        retval = false;
                    }


                }
                else
                {
                    retval = false;
                }
                //Context.Log("Loaded info for " + MobList.Count + " mobs");

                return retval;
            }

            public bool KnownMob(string MobName)
            {
                return MobList.ContainsKey(MobName);
            }

            public int GetMobCount()
            {
                return MobList.Count;
            }

            public bool MobHasTendency(string MobName, string Tendency)
            {
                MobTendency x;
                if (MobList.TryGetValue(MobName, out x))
                {
                    return x.HasTendency(Tendency);
                }
                return false;
            }

            public bool AddTendency(string MobName, string Tendency)
            {
                MobTendency x;
                if (MobList.TryGetValue(MobName, out x))
                {
                    if (x.HasTendency(Tendency) || x.AddTendency(Tendency))
                        return true;
                }
                else
                {
                    MobList.Add(MobName, new MobTendency(MobName, Tendency));
                }
                return false;
            }
        }
        #endregion

        #region MobRadar Class
        class MobRadar
        {
            float EffectiveDist = 100f;

            ArrayList KeepAwayList = new ArrayList();

            private GContext Context = null;


            public MobRadar(GContext c, float Dist)
            {
                EffectiveDist = Dist;
                Context = c;

                /*
                String curDir = System.IO.Directory.GetCurrentDirectory();
                if (System.IO.File.Exists(curDir + "\\KeepAwayPoints.txt"))
                {
                    string[] temp = System.IO.File.ReadAllLines(curDir + "\\KeepAwayPoints.txt");
                    foreach (string currentline in temp)
                    {
                        try
                        {
                            KeepAwayList.Add(new GLocation(currentline));
                        }
                        catch
                        {
                            Context.Log("Error loading keep away. " + currentline);
                        }
                    }
                }
                */
            }


            public void SaveKeepAway()
            {
                /*
                String curDir = System.IO.Directory.GetCurrentDirectory();

                if (System.IO.Directory.Exists(curDir))
                {
                    System.IO.StreamWriter fileout = System.IO.File.CreateText(curDir + "\\KeepAwayPoints.txt");
                    foreach (GLocation loc in KeepAwayList)
                    {
                        fileout.WriteLine(loc.ToString());
                    }
                    fileout.Close();
                }
                */
            }

            private int Round(float x)
            {
                if (x >= 0.0f)
                    return (int)(x + 0.5f);
                else
                    return (int)(x - 0.5f);
            }

            private float AngleBetween(GLocation p1, GLocation p2)
            {
                return (float)Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
            }


            //---------------------------------------------------------------------
            //  Method: FindSafePlace
            //  Purpose:  Use the current snapshot to find the safest place that 
            //      is within our radii provided.
            //
            //  Notes:  Search goes like this.  Draw a line from where we are
            //      to where we want to be.  Then find the point on this line that
            //      is on the MinRadius around our target.  This is our start point
            //      since it should be the easiest to get to and closest to our
            //      target.  Then start sweeping to both sides around the radius
            //      until we find a place where danger is 0.  Moving out until we
            //      find one or hit the max radius.
            //---------------------------------------------------------------------
            public GLocation FindSafePlace(GLocation NearTo, GLocation PrefLoc, float MinRadius, float MaxRadius, GUnit[] MobList, long ExcludeGUID, GPlayerSelf Me)
            {
                float AngleOffset = 0.0f;
                float CurrentDanger = float.MaxValue;

                //find the angle from where we are to where we want to be near...

                //from there to where we are....
                float BaseAngle = (float)Math.Atan2(PrefLoc.Y - NearTo.Y, PrefLoc.X - NearTo.X);

                BaseAngle += (float)(Context.RNG.NextDouble() * Math.PI / 8.0) - (float)(Math.PI / 16.0);
                float CurrentRadius = MinRadius;

                float MinSoFarAngle = BaseAngle;
                float MinSoFarRadius = MinRadius;
                float MinSoFarDanger = CurrentDanger;

                int CurrentX, CurrentY;

                while (MinSoFarDanger > 0f && CurrentRadius < MaxRadius)
                {

                    AngleOffset = 0.0f;
                    while (MinSoFarDanger > 0 && AngleOffset <= (Math.PI / 2.0))
                    {

                        //find the danger here...

                        //first find the coords.
                        CurrentX = Round((float)(NearTo.X + CurrentRadius * Math.Cos(BaseAngle + AngleOffset)));
                        CurrentY = Round((float)(NearTo.Y + CurrentRadius * Math.Sin(BaseAngle + AngleOffset)));


                        CurrentDanger = GetDangerAt(CurrentX, CurrentY, MobList, ExcludeGUID, Me);

                        if (CurrentDanger < MinSoFarDanger)
                        {
                            MinSoFarDanger = CurrentDanger;
                            MinSoFarAngle = BaseAngle + AngleOffset;
                            MinSoFarRadius = CurrentRadius;

                        }

                        if (MinSoFarDanger > 0)
                        {
                            //first find the coords.
                            CurrentX = Round((float)(NearTo.X + CurrentRadius * Math.Cos(BaseAngle - AngleOffset)));
                            CurrentY = Round((float)(NearTo.Y + CurrentRadius * Math.Sin(BaseAngle - AngleOffset)));


                            CurrentDanger = GetDangerAt(CurrentX, CurrentY, MobList, ExcludeGUID, Me);

                            if (CurrentDanger < MinSoFarDanger)
                            {
                                MinSoFarDanger = CurrentDanger;
                                MinSoFarAngle = BaseAngle - AngleOffset;
                                MinSoFarRadius = CurrentRadius;

                            }

                        }

                        AngleOffset += ((float)Math.PI / 8.0f);

                    }
                    CurrentRadius += 1.0f;
                }

                float fCurrentX = (float)(NearTo.X + MinSoFarRadius * Math.Cos(MinSoFarAngle));
                float fCurrentY = (float)(NearTo.Y + MinSoFarRadius * Math.Sin(MinSoFarAngle));

                Context.Debug("Angle: " + MinSoFarAngle.ToString("0.00") + "  Radius: " + MinSoFarRadius.ToString("0.00"));

                //now find our MinRadius at our angle... that would be our preferred spot...
                return new GLocation(fCurrentX, fCurrentY);

            }

            //testing some new search pattern ideas...  prob not safe to use IRL
            public GLocation FindSafePlace2(GLocation NearTo, GLocation PrefLoc, float MinRadius, float MaxRadius, GUnit[] MobList, long ExcludeGUID, GPlayerSelf Me)
            {
                float RadiusLimit = DistanceBetween(NearTo, PrefLoc) + MaxRadius;

                float AngleOffset = 0.0f;
                float CurrentDanger = float.MaxValue;

                //find the angle from where we are to where we want to be near...

                //from pref to NearTo....
                float BaseAngle = AngleBetween(PrefLoc, NearTo);

                //float CurrentAngle = BaseAngle;
                float CurrentRadius = 1;// DistanceBetween(NearTo, PrefLoc) - MinRadius;


                float MinSoFarAngle = BaseAngle;
                float MinSoFarRadius = MinRadius;
                float MinSoFarDanger = CurrentDanger;

                int CurrentX, CurrentY;

                while (MinSoFarDanger > 0f && CurrentRadius < RadiusLimit)
                {

                    foreach (GUnit x in MobList)
                        x.Refresh(true);
                    AngleOffset = 0.0f;
                    while (MinSoFarDanger > 0 && AngleOffset <= Math.PI)
                    {

                        //find the danger here...

                        //first find the coords.
                        CurrentX = Round((float)(PrefLoc.X + CurrentRadius * Math.Cos(BaseAngle + AngleOffset)));
                        CurrentY = Round((float)(PrefLoc.Y + CurrentRadius * Math.Sin(BaseAngle + AngleOffset)));

                        if (DistanceBetween(CurrentX, CurrentY, NearTo) >= MinRadius && DistanceBetween(CurrentX, CurrentY, NearTo) <= MaxRadius)
                        {
                            CurrentDanger = GetDangerTo(new GLocation(CurrentX, CurrentY), MobList, ExcludeGUID, Me);

                            if (CurrentDanger < MinSoFarDanger)
                            {
                                MinSoFarDanger = CurrentDanger;
                                MinSoFarAngle = BaseAngle + AngleOffset;
                                MinSoFarRadius = CurrentRadius;

                            }
                        }

                        if (MinSoFarDanger > 0)
                        {
                            //first find the coords.
                            CurrentX = Round((float)(PrefLoc.X + CurrentRadius * Math.Cos(BaseAngle - AngleOffset)));
                            CurrentY = Round((float)(PrefLoc.Y + CurrentRadius * Math.Sin(BaseAngle - AngleOffset)));

                            if (DistanceBetween(CurrentX, CurrentY, NearTo) >= MinRadius && DistanceBetween(CurrentX, CurrentY, NearTo) <= MaxRadius)
                            {

                                CurrentDanger = GetDangerTo(new GLocation(CurrentX, CurrentY), MobList, ExcludeGUID, Me);

                                if (CurrentDanger < MinSoFarDanger)
                                {
                                    MinSoFarDanger = CurrentDanger;
                                    MinSoFarAngle = BaseAngle - AngleOffset;
                                    MinSoFarRadius = CurrentRadius;

                                }
                            }

                        }

                        AngleOffset += ((float)Math.PI / 16.0f);

                    }
                    CurrentRadius += 1.0f;
                }

                float fCurrentX = (float)(PrefLoc.X + MinSoFarRadius * Math.Cos(MinSoFarAngle));
                float fCurrentY = (float)(PrefLoc.Y + MinSoFarRadius * Math.Sin(MinSoFarAngle));

                //Context.Log("Base: " + BaseAngle.ToString("0.00"));
                //Context.Log("Angle: " + MinSoFarAngle.ToString("0.00") + "  Radius: " + MinSoFarRadius.ToString("0.00"));

                //now find our MinRadius at our angle... that would be our preferred spot...
                return new GLocation(fCurrentX, fCurrentY);

            }

            public void AddKeepAway(GLocation Loc)
            {
                KeepAwayList.Add(Loc);
                SaveKeepAway();
            }

            public float GetDangerTo(GLocation Loc, GUnit[] MobList, long ExcludeGUID, GPlayerSelf Me)
            {
                //check points along the way
                float steps = (int)((Loc.X - Me.Location.X) / 5f);

                float stepsy = (int)((Loc.Y - Me.Location.Y) / 5f);

                if (stepsy > steps)
                    steps = stepsy;



                float dX = (Loc.X - Me.Location.X) / steps;
                float dY = (Loc.Y - Me.Location.Y) / steps;

                bool isDecreasing = true;
                float retval = GetDangerAt(Me.Location, MobList, ExcludeGUID, Me);

                for (float Idx = 1f; Idx < steps; Idx += 1f)
                {
                    GLocation cLoc = new GLocation(Me.Location.X + dX * Idx, Me.Location.Y + dY * Idx);
                    float currentdanger = GetDangerAt(cLoc, MobList, ExcludeGUID, Me);

                    if (currentdanger < retval && isDecreasing)
                    {
                        retval = currentdanger;
                    }
                    else
                    {
                        isDecreasing = false;
                        if (currentdanger > retval)
                            retval = currentdanger;
                    }
                }

                //walk along the path from here to there and as long as the danger is 
                //decreasing then continue.




                return retval;
            }

            public float GetDangerAt(GLocation Loc, GUnit[] MobList, long ExcludeGUID, GPlayerSelf Me)
            {
                return GetDangerAt(Loc.X, Loc.Y, MobList, ExcludeGUID, Me);
            }

            private float GetDangerAt(float x, float y, GUnit[] MobList, long ExcludeGUID, GPlayerSelf Me)
            {
                float retval = 0f;

                foreach (GLocation keepaway in KeepAwayList)
                {
                    float dist = DistanceBetween(x, y, keepaway);

                    if (dist < 5f)
                    {
                        retval += EffectiveDist;
                    }
                }

                foreach (GUnit mob in MobList)
                {
                    //only check hostiles....
                    if (mob.Reaction == GReaction.Hostile && !mob.IsTargetingMe && !mob.IsTargetingMyPet && mob.GUID != ExcludeGUID)
                    {
                        //mob.Refresh(true);
                        float dist = DistanceBetween(x, y, mob.Location);

                        if (dist < EffectiveDist)
                        {
                            retval += (EffectiveDist - dist);
                            //float currentdanger = EffectiveDist - dist;
                            //if (retval < currentdanger)
                            //    retval = currentdanger;
                        }
                    }
                }
                return retval;
            }

            private float DistanceBetween(int X1, int Y1, int X2, int Y2)
            {
                return (float)Math.Sqrt((X1 - X2) * (X1 - X2) + (Y1 - Y2) * (Y1 - Y2));
            }

            private float DistanceBetween(float X1, float Y1, GLocation L2)
            {
                return (float)Math.Sqrt((X1 - L2.X) * (X1 - L2.X) + (Y1 - L2.Y) * (Y1 - L2.Y));
            }

            public static float DistanceBetween(GLocation L1, GLocation L2)
            {
                return (float)Math.Sqrt((L1.X - L2.X) * (L1.X - L1.X) + (L1.Y - L2.Y) * (L1.Y - L2.Y));

            }

        }
        #endregion

        #region GConfigManager
        private class GConfigManager
        {
            private class GConfigCheckbox : GConfigType
            {
                public GConfigCheckbox(int idx, string ns, string n, string l, string def) : base(idx, ns, n, l, def)
                {
                }

                public override string ToXML()
                {
                    StringBuilder retval = new StringBuilder();

                    retval.AppendLine("<checkbox>");
                    retval.AppendLine("<label>" + Label + "</label>");
                    retval.AppendLine("<target>" + LongName + "</target>");
                    retval.AppendLine("<tabindex>" + TabIdx + "</tabindex>");
                    retval.AppendLine("</checkbox>");
                    
                    return retval.ToString();
                }
            }

            private class GConfigTextInput : GConfigType
            {
                public GConfigTextInput(int idx, string ns, string n, string l, string def)
                    : base(idx, ns, n, l, def)
                {
                }
                public override string ToXML()
                {
                    StringBuilder retval = new StringBuilder();

                    retval.AppendLine("<input>");
                    retval.AppendLine("<label>" + Label + "</label>");
                    retval.AppendLine("<target>" + LongName + "</target>");
                    retval.AppendLine("<tabindex>" + TabIdx + "</tabindex>");
                    retval.AppendLine("</input>");
                    return retval.ToString();
                }
            }

            private class GConfigLongInput : GConfigType
            {
                public GConfigLongInput(int idx, string ns, string n, string l, string def)
                    : base(idx, ns, n, l, def)
                {
                }
                public override string ToXML()
                {
                    StringBuilder retval = new StringBuilder();

                    retval.AppendLine("<longinput>");
                    retval.AppendLine("<label>" + Label + "</label>");
                    retval.AppendLine("<target>" + LongName + "</target>");
                    retval.AppendLine("<tabindex>" + TabIdx + "</tabindex>");
                    retval.AppendLine("</longinput>");
                    return retval.ToString();
                }
            }

            private class GConfigComboBox : GConfigType
            {
                string[] enumvals;

                public GConfigComboBox(int idx, string ns, string n, string l, string def, string[] vals)
                    : base(idx, ns, n, l, def)
                {
                    enumvals = new string[vals.Length];
                    for (int x = 0; x < vals.Length; x++)
                        enumvals[x] = vals[x];

                }

                public override string ToXML()
                {
                    StringBuilder retval = new StringBuilder();

                    retval.AppendLine("<combobox>");
                    retval.AppendLine("<label>" + Label + "</label>");
                    retval.AppendLine("<target>" + LongName + "</target>");
                    retval.AppendLine("<tabindex>" + TabIdx + "</tabindex>");
                    retval.AppendLine("<options>");
                    foreach (string option in enumvals)
                        retval.AppendLine("<option>" + option + "</option>");
                    retval.AppendLine("</options>");
                    retval.AppendLine("</combobox>");


                    return retval.ToString();
                }
            }
            
            private abstract class GConfigType
            {
                protected int TabIdx;
                private string target;
                private string ns;
                protected string Label;
                
                protected string defaultval;

                abstract public string ToXML();
                public GConfigType(int idx, string ns, string n, string l, string def)
                {
                    TabIdx = idx;
                    target = n;
                    this.ns = ns;
                    Label = l;
                    defaultval = def;
                }

                public void WriteDefault(GContext c)
                {
                    c.SetConfigValue(LongName, defaultval, false);
                }

                public string LongName
                {
                    get { return ns + "." + target;}
                }

                public string ShortName
                {
                    get { return target;}
                }
            }

            private string Namespace;
            private string ConfigTitle;
            private GContext c;
            List<GConfigType> Lines = new List<GConfigType>();
            List<string> Tabs = new List<string>();

            const int PlayerTab = 0;
            const int PetTab = 1;

            public GConfigManager(string NameSpace, GContext c)
            {
                this.Namespace = NameSpace;
                this.ConfigTitle = NameSpace + " Config";
                this.c = c;

                AssembleDefaults();
            }

            public void AddTab(string desc)
            {
                Tabs.Add(desc);
            }

            public void ClearTabs()
            {
                Tabs.Clear();
            }

            void AssembleDefaults()
            {
                string[] aspects = { "None", "Aspect of the Hawk", "Aspect of the Viper", "Aspect of the Monkey", "Aspect of the Cheetah", "Aspect of the Pack", "Aspect of the Beast" };

                ClearTabs();
                Lines.Clear();
                
                AddTab("Pet");


                AddTextInput(1, "RadarDist", "(y) Effective Radar Range", "0");
                AddTextInput(1, "DangerThresh", "Danger Threshold:", "25");

                AddTextInput(1, "PullDistance", "(y) Pull distance", "33");
                AddTextInput(1, "SendPetDelay", "(mS) Pet traveling delay", "5000");

                AddTextInput(1, "ManaConserve", "(% mana) Mana Conserve Level", "60");
                AddTextInput(1, "HealPercent", "(% health) Health Potion Level", "20");
                AddTextInput(1, "FeignThresh", "(% health) Health Level To Feign", "50");
                AddTextInput(1, "ApproachThresh", "(% mob health) Approach mob limit", "0");
                AddTextInput(1, "AvoidMeleeThresh", "(% mob health) Ranged pref limit", "25");

                AddComboBox(1, "RangedAspect", "Aspect - Ranged:", aspects[1], aspects);
                AddComboBox(1, "MeleeAspect", "Aspect - Melee:", aspects[0], aspects);
                AddComboBox(1, "TravelAspect", "Aspect - Travel:", aspects[0], aspects);
                AddTextInput(1, "CheetahThresh", "(y) Clearance for travel aspect", "80");
                AddCheckBox(1, "MountBetween", "Mount between kills.", "false");
                AddLongInput(1, "MountName", "The name of your mount.", "Brown Kodo");
                AddTextInput(1, "QuitAmmoThresh", "Quit Ammo Level", "20");
                AddTextInput(1, "LowAmmoThresh", "Conserve Ammo Level", "0");


                AddLongInput(2, "PetName", "Your pet name:", "");

                AddCheckBox(2, "UseIntimidation", "Use Intimidation", "false");
                AddCheckBox(2, "UseWrath", "Use Beastial Wrath", "false");

                AddTextInput(2, "MendPetThresh", "(% pet health) Mend pet level:", "50");
                AddTextInput(2, "FeedPetThresh", "(% pet happiness) Feed pet level:", "70");

                AddTextInput(2, "RecallPetThresh", "(% mob health) Recall Pet for finish", "25");
                AddTextInput(2, "PetAttackClearance", "(y) Pet attack clearance", "25");
                AddTextInput(2, "PetAvoidClearance", "(y) Pet avoid clearance", "25");
                
                AddTextInput(2, "ElevationThresh", "(y) Max Elevation dif allowed", "8");

            }

            void AddLongInput(int tabidx, string name, string label, string defvalue)
            {
                Lines.Add(new GConfigLongInput(tabidx, Namespace, name, label, defvalue));
            }
            void AddTextInput(int tabidx, string name, string label, string defvalue)
            {
                Lines.Add(new GConfigTextInput(tabidx, Namespace, name, label, defvalue));
            }

            void AddCheckBox(int tabidx, string name, string label, string defvalue)
            {
                Lines.Add(new GConfigCheckbox(tabidx, Namespace, name, label, defvalue));
            }


            void AddComboBox(int tabidx, string name, string label, string defvalue, string[] values )
            {
                Lines.Add(new GConfigComboBox(tabidx, Namespace, name, label, defvalue, values));
            }

            public void PushDefaultConfig()
            {
                //for (int x = 0; x < ConfigLines.Count; x++)
                foreach (GConfigType line in Lines)
                {
                    line.WriteDefault(c);
                }
            }

            public GConfigResult ShowConfig()
            {
                PushDefaultConfig();

                string TargetDir = AppDomain.CurrentDomain.BaseDirectory;
                Assembly asm = System.Reflection.Assembly.LoadFile(TargetDir + "Classes\\GConfig.dll");
                WriteConfig(TargetDir + "Classes\\SimpleConfig.xml");

                foreach (Type loadedType in asm.GetTypes())
                {
                    if (loadedType.Name == "GConfig")
                    {
                        GConfigResult retval = GConfigResult.Cancel;
                        PropertyInfo pi;
                        object configDialog = loadedType.GetConstructor(new Type[] { }).Invoke(new object[] { });
                        MethodInfo showDialogMethod = loadedType.GetMethod("ShowDialog", new Type[] { });
                        Type type = configDialog.GetType();

                        // SECTION1: TELL GCONFIG THE CURRENT VALUES

                        foreach (GConfigType value in Lines)
                        {
                            SetConfigValue(configDialog, value.LongName);
                        }
                        
                        //Set Config File
                        pi = type.GetProperty("ConfigXML");
                        if (pi != null) pi.SetValue(configDialog, "SimpleConfig.xml", null);

                        
                        //Popup Dialog
                        object modalResult = showDialogMethod.Invoke(configDialog, new object[] { });
                        if ((int)modalResult == 1)
                        {
                            // SECTION2: TELL GLIDER THE NEW VALUES

                            foreach (GConfigType value in Lines)
                            {
                                GetConfigValue(configDialog, value.LongName);
                            }

                            retval = GConfigResult.Accept;
                        }

                        if (System.IO.File.Exists(TargetDir + "\\Classes\\SimpleConfig.xml"))
                        {
                            System.IO.File.Delete(TargetDir + "\\Classes\\SimpleConfig.xml");
                        } 
                        
                        return retval;
                    }
                }

                return GConfigResult.Cancel;

            }
            
            private void WriteConfig(string FileName)
            {

                System.IO.StreamWriter fileout = System.IO.File.CreateText(FileName);
                if (fileout != null)
                {
                    fileout.WriteLine("<?xml version=\"1.0\"?>");
                    fileout.WriteLine("<GConfig><config>");
                    fileout.WriteLine("<height>660</height>");
                    fileout.WriteLine("<width>990</width>");
                    fileout.WriteLine("<colwidth>320</colwidth>");
                    fileout.WriteLine("<title>" + ConfigTitle + "</title>");

                    fileout.WriteLine("<tabs>");
                    foreach(string tab in Tabs)
                        fileout.WriteLine("<tab>" + tab + "</tab>");
                    fileout.WriteLine("</tabs>");

                    fileout.WriteLine("</config>");


                    foreach (GConfigType Line in Lines)
                    {
                        fileout.WriteLine(Line.ToXML());
                    }

                    fileout.WriteLine("</GConfig>");
                    fileout.Flush();
                    fileout.Close();
                }
            }

            //read the value from the context and push it to our config dialog.
            private void SetConfigValue(object configDialog, string vKey)
            {
                PropertyInfo pKey;
                PropertyInfo pValue;
                string vValue = c.GetConfigString(vKey);
                Type type = configDialog.GetType();
                //Setup Entry Points to pass Values
                pKey = type.GetProperty("ConfigKey");
                pValue = type.GetProperty("ConfigValue");
                if (pKey != null && pValue != null)
                {
                    pKey.SetValue(configDialog, vKey, null);
                    pValue.SetValue(configDialog, vValue, null);
                }
            }

            private void GetConfigValue(object configDialog, string vKey)
            {
                PropertyInfo pKey;
                PropertyInfo pValue;
                Type type = configDialog.GetType();
                //Setup Entry Points to pass Values
                pKey = type.GetProperty("ConfigKey");
                pValue = type.GetProperty("ConfigValue");
                if (pKey != null && pValue != null)
                {
                    pKey.SetValue(configDialog, vKey, null);
                    c.SetConfigValue(vKey, pValue.GetValue(configDialog, null).ToString(), true);
                }
            }

        }
        #endregion
        
        #region ConfigWriter
        private class ConfigWriter
        {
            private class ConfigLine
            {
                private string Name, Display, Label, Type;
                private string Required;
                string[] enumvals;
                private string defaultval;

                public ConfigLine(string n, string d, string l, string t, bool r, string def)
                {
                    Name = n;
                    Display = d;
                    Label = l;
                    Type = t;
                    if (r)
                        Required = "true";
                    else
                        Required = "false";
                    enumvals = null;
                    defaultval = def;
                }

                public ConfigLine(string n, string l, string t, string[] evals, bool r, string def)
                {
                    Name = n;
                    Display = n;
                    Label = l;
                    Type = t;
                    if (r)
                        Required = "true";
                    else
                        Required = "false";
                    enumvals = new string[evals.Length];
                    for (int x = 0; x < evals.Length; x++)
                        enumvals[x] = evals[x];

                    defaultval = def;


                }

                public void WriteDefault(GContext c)
                {
                    c.SetConfigValue(Name, defaultval, false);
                }

                public string ToXml()
                {
                    string retval = "<Field name=\"" + Name + "\"";

                    if (Display != null && Display != "")
                        retval += " displayname=\"" + Display + "\"";

                    if (Label != null && Label != "")
                        retval += " label=\"" + Label + "\"";

                    if (Type != null && Type != "")
                        retval += " type=\"" + Type + "\"";

                    if (Required != null && Required != "")
                        retval += " required=\"" + Required + "\"";

                    if (enumvals != null && enumvals.Length > 0)
                    {
                        retval += " values=\"";
                        foreach (string val in enumvals)
                            retval += val + "/";
                        retval += "\"";
                    }

                    retval += " />";
                    return retval;
                }
            }
            private string Prefix;
            ArrayList Lines = new ArrayList();


            public ConfigWriter()
            {
                Prefix = "SimpleHunter.";
                AssembleDefaults();
            }

            void AssembleDefaults()
            {
                Lines.Clear();
                string[] aspects = { "None", "Aspect of the Hawk", "Aspect of the Viper", "Aspect of the Monkey", "Aspect of the Cheetah", "Aspect of the Pack", "Aspect of the Beast" };


                AddInt("RadarDist", "Radar Distance", "Effective Radar Range.(yards):", true, "0");
                AddInt("DangerThresh", "Danger Threshold", "Danger Threshold:", true, "25");

                AddInt("PullDistance", "Pull distance", "Pull distance (yards):", true, "33");
                AddInt("SendPetDelay", "SendPetDelay", "Time to delay after sending pet (mS):", true, "5000");

                AddInt("ManaConserve", "Mana Conserve Level", "Mana Conserve Level (%):", true, "60");
                AddInt("HealPercent", "Heal Percent", "Health Potion Level (%):", true, "20");
                AddInt("FeignThresh", "Feign Thresh", "Health Level To Feign:", true, "50");
                AddInt("MendPetThresh", "Mend Pet Thresh", "Mend pet level (%):", true, "50");
                AddInt("FeedPetThresh", "Feed Pet Thresh", "Feed pet level (%):", true, "70");
                AddInt("ApproachThresh", "Approach Thresh", "Approach for melee finish threshhold (%):", true, "0");
                AddInt("AvoidMeleeThresh", "Avoid Melee Thresh", "Avoid Melee combat above (% mob health)", true, "25");

                AddEnum("RangedAspect", "Ranged Aspect:", true, aspects, aspects[1]);
                AddEnum("MeleeAspect", "Melee Aspect:", true, aspects, aspects[0]);
                AddEnum("TravelAspect", "Travel Aspect:", true, aspects, aspects[0]);
                AddInt("CheetahThresh", "Travel Thresh", "Clearance for travel aspect (y):", true, "80");


                AddString("PetName", "Your Pet Name", "Your pet name:", true, "");
                AddBool("UseIntimidation", "UseIntimidation", "Use Intimidation:", true, "false");
                AddBool("UseWrath", "UseWrath", "Use Wrath:", true, "false");
                AddBool("PetRecallFinish", "PetRecallFinish", "Recall pet for melee finish:", true, "false");
                AddBool("PetRecall", "PetRecall", "Recall pet when adds are close:", true, "false");
                AddInt("PetRecallDistance", "PetRecallDistance", "Pet recall radius (yards):", true, "25");
                AddInt("ElevationThresh", "ElevationThresh", "Max Elevation difference allowed before melee. (y):", true, "8");
                AddInt("QuitAmmoThresh", "QuitAmmoThresh", "Quit Ammo Level:", true, "20");
                AddInt("LowAmmoThresh", "LowAmmoThresh", "Conserve Ammo Level:", true, "0");


            }

            void AddBool(string name, string displayname, string label, bool req, string defvalue)
            {
                Lines.Add(new ConfigLine(Prefix + name, displayname, label, "bool", req, defvalue));
            }

            void AddInt(string name, string displayname, string label, bool req, string defvalue)
            {
                Lines.Add(new ConfigLine(Prefix + name, displayname, label, "int", req, defvalue));
            }

            void AddEnum(string name, string label, bool req, string[] values, string defvalue)
            {
                Lines.Add(new ConfigLine(Prefix + name, label, "enum", values, req, defvalue));
            }

            void AddString(string name, string displayname, string label, bool req, string defvalue)
            {
                Lines.Add(new ConfigLine(Prefix + name, displayname, label, "string", req, defvalue));
            }

            public void PushDefaultConfig(GContext c)
            {
                //for (int x = 0; x < ConfigLines.Count; x++)
                foreach (ConfigLine line in Lines)
                {
                    line.WriteDefault(c);
                }
            }

            public string ToXML()
            {
                StringBuilder retval = new StringBuilder();
                retval.AppendLine("<?xml version=\"1.0\"?>");

                retval.AppendLine("<GliderConfigWindow toplabel=\"Simple Hunter Config Dialog\" columns=\"2\">");

                foreach (ConfigLine Line in Lines)
                {
                    retval.AppendLine(Line.ToXml());
                }

                retval.AppendLine("</GliderConfigWindow>");
                return retval.ToString();

            }

            public void WriteConfig(string FileName)
            {
                System.IO.StreamWriter fileout = System.IO.File.CreateText(FileName);
                if (fileout != null)
                {
                    fileout.WriteLine("<?xml version=\"1.0\"?>");
                    fileout.WriteLine("<GliderConfigWindow toplabel=\"Simple Hunter Config Dialog\" columns=\"1\">");

                    foreach (ConfigLine Line in Lines)
                    {
                        fileout.WriteLine(Line.ToXml());
                    }

                    fileout.WriteLine("</GliderConfigWindow>");
                    fileout.Flush();
                    fileout.Close();
                }
            }


        }
        #endregion

        #region Log Handling Class
        private class SmartLogHandler
        {
            GContext Context;
            GPlayerSelf Me;
            string PetName;
            
            public SmartLogHandler(GContext C, GPlayerSelf M, string pn)
            {
                Context = C;
                this.Me = M;
                PetName = pn;

                Context.CombatLog += new GContext.GCombatLogHandler(CombatLogHandler);
                Context.ChatLog += new GContext.GChatLogHandler(ChatLogHandler);
            }

            ~SmartLogHandler()
            {
                Context.CombatLog -= new GContext.GCombatLogHandler(CombatLogHandler);
                Context.ChatLog -= new GContext.GChatLogHandler(ChatLogHandler);
            }

            #region Event Defines
            public delegate void NotifyEvent(string s);
            //public delegate void PoisionEvent(SmartPoisonType Type, string s);

            public event NotifyEvent OnAoEDamage;
            public event NotifyEvent OnCampFireDamage;
            public event NotifyEvent OnMonsterEvade;
            //public event NotifyEvent OnDoTDamage;
            //public event NotifyEvent OnRangedDamage;
            public event NotifyEvent OnPetHit;
            public event NotifyEvent OnYouHit;
            //public event NotifyEvent OnPetCrit;
            public event NotifyEvent OnYouCrit;

            public event NotifyEvent OnGainEffect;
            public event NotifyEvent OnPetGainsEffect;
            public event NotifyEvent OnLoseEffect;
            public event NotifyEvent OnPetLosesEffect;

            public event NotifyEvent OnRunner;
            public event NotifyEvent OnCallsForHelp;

            public event NotifyEvent OnWhisper;
            public event NotifyEvent OnLoot;



            public event NotifyEvent OnUnhandledString;
            public event NotifyEvent OnUnknownChatString;

            #endregion


            private bool fActive = false;
            public bool Active
            {
                get { return fActive; }
                set
                {
                    fActive = value;
                }
            }


            private string[] AoEPrimaryKeywords = {"you are afflicted", "you suffer"};
            private string[] AoESecondaryKeywords = {"spore cloud", "chemical flames", "flames wave"};


            bool EvalStatement(string Original, string[] Primary, string[] Secondary)
            {
                return ContainsWords(Original, Primary) && ContainsWords(Original, Secondary); 
            }
            
            bool ContainsWords(string Original, string[] Words)
            {
                bool retval = false;

                foreach (string word in Words)
                {
                    if (Original.Contains(word))
                    {
                        retval = true;
                        break;
                    }
                }
                return retval;
            }
            
            void CombatLogHandler(string RawTextOriginal)
            {
                if (fActive)
                {
                    string RawText = RawTextOriginal.ToLower();
                    bool handled = false;

                    if (RawText.Contains("you hit") || (RawText.Contains("your") && RawText.Contains("hits")))
                    {
                        if (OnYouHit != null)
                            OnYouHit(RawText);
                        handled = true;
                    }

                    if (RawText.Contains("you crit") || (RawText.Contains("your") && RawText.Contains("crits")))
                    {
                        if (OnYouCrit != null)
                            OnYouCrit(RawText);
                        handled = true;
                    }


                    if (Me.Pet != null && RawText.Contains(PetName) && RawText.Contains("hits"))
                    {
                        if (RawText.IndexOf(PetName) < RawText.IndexOf("hits"))
                        {
                            if (OnPetHit != null)
                                OnPetHit(RawText);
                            handled = true;
                        }
                    }

                    if (Me.Pet != null && RawText.Contains(PetName) && RawText.Contains("casts"))
                    {
                        if (OnPetHit != null)
                            OnPetHit(RawText);
                        handled = true;
                    }

                    if (RawText.Contains("you gain "))
                    {
                        if (OnGainEffect != null)
                            OnGainEffect(RawText);
                        handled = true;
                    }

                    if (RawText.Contains(PetName + " gains ") && !RawText.Contains("health"))
                    {
                        if (OnPetGainsEffect != null)
                            OnPetGainsEffect(RawText);
                    }

                    if (RawText.Contains("fades from " + PetName ))
                    {
                        if (OnPetLosesEffect != null)
                            OnPetLosesEffect(RawText);
                    }

                    if (RawText.Contains("fades from you"))
                    {
                        if (OnLoseEffect != null)
                            OnLoseEffect(RawText);
                        handled = true;
                    }


                    //ur standing in a camp fire idiot
                    if (RawText.Contains("fire") && !RawText.Contains("from"))
                    {
                        if (OnCampFireDamage != null)
                            OnCampFireDamage(RawText);
                        handled = true;
                    }

                    //seems to match our known aoe keywords
                    if(EvalStatement(RawText, AoEPrimaryKeywords, AoESecondaryKeywords))
                    {
                        if(OnAoEDamage != null)
                            OnAoEDamage(RawText);
                        handled = true;
                    }

                    if (RawText.Contains("evade") && !RawText.Contains("was evaded"))
                    {
                        if (OnMonsterEvade != null)
                            OnMonsterEvade(RawText);
                        handled = true;
                    }

                    /*
                    if (RawText.Contains("is afflicted by") && RawText.Contains("poison"))
                    {
                        int i = RawTextOriginal.IndexOf(" is aff");
                        if (i != -1)
                        {
                            string MonsterName = RawTextOriginal.Substring(0, i);
                            if (RawTextOriginal.Contains("Crippling Poison"))
                            {
                                // 12s
                                //Spam(MonsterName + " is crippled for 12s");
                                PoisonTimer.SetTicksLeft(PoisonTimer.Duration - 12 * 1000);
                            }
                            else if (RawTextOriginal.Contains("Wound Poison"))
                            {
                                // 15s
                                //Spam(MonsterName + " is wounded for 15s");
                                PoisonTimer.SetTicksLeft(PoisonTimer.Duration - 15 * 1000);
                            }
                            else if (RawTextOriginal.Contains("Deadly Poison"))
                            {
                                // 12s
                                //Spam(MonsterName + " is afflicted by Deadly poison for 12s");
                                PoisonTimer.SetTicksLeft(PoisonTimer.Duration - 12 * 1000);
                                DeadlyPoisonTimer.Reset();
                                // 00:44 Cursed Ooze is afflicted by Deadly Poison IV.
                                // 00:44 Cursed Ooze is afflicted by Deadly Poison IV (2).
                                // 00:44 Cursed Ooze is afflicted by Deadly Poison IV (3).
                                DeadlyPoisonCount = 1;
                                int j = RawTextOriginal.IndexOf("(");
                                if (j != -1)
                                {
                                    string number = RawTextOriginal.Substring(j + 1, 1);
                                    DeadlyPoisonCount = Int32.Parse(number);
                                }

                            }
                            else if (RawTextOriginal.Contains("Instant Poison"))
                            {
                                // not my problem
                            }
                            else if (RawTextOriginal.Contains("Anesthetic Poison"))
                            {
                                // not my problem			
                            }
                            else if (RawTextOriginal.Contains("Mind-numbing Poison"))
                            {
                                //10,12,14s
                                //Spam(MonsterName + " is afflicted Mind-numbed for 10-14s");
                                PoisonTimer.SetTicksLeft(PoisonTimer.Duration - 14 * 1000); // This is used for mutilate, se we can assume its a high ranged poison used
                            }
                            else
                            {
                                Context.Log(MonsterName + " is afflicted by unknown poison");
                            }
                        }
                    }

                    */

                    if (!handled)
                    {
                        if (OnUnhandledString != null)
                            OnUnhandledString(RawText);
                    }
                }
            }
            
            void ChatLogHandler(string RawText, string ParsedText)
            {
                if (fActive)
                {
                    if (ParsedText.Contains("attempts to run away") ||
                       ParsedText.Contains("senses danger and flees"))
                    {
                        int i = ParsedText.IndexOf(" attempts");
                        if (i != -1)
                        {
                            string MonsterName = ParsedText.Substring(0, i);
                            if (OnRunner != null)
                                OnRunner(MonsterName);

                        }
                    }
                    else if (ParsedText.Contains("calls for help") ||
                        ParsedText.Contains("calling for help"))
                    {
                        int i = ParsedText.IndexOf(" calls");
                        if (i == -1) i = ParsedText.IndexOf(" lets out");
                        if (i != -1)
                        {
                            string MonsterName = ParsedText.Substring(0, i);
                            if (OnCallsForHelp != null)
                            {
                                OnCallsForHelp(MonsterName);
                            }
                        }
                    }
                    else if (ParsedText.Contains("whisper"))
                    {
                        if (OnWhisper != null)
                            OnWhisper(ParsedText);
                    }
                    else if (ParsedText.Contains("on loot"))
                    {
                        if (OnLoot != null)
                            OnLoot(ParsedText);
                    }
                    else
                    {
                        if (OnUnknownChatString != null)
                            OnUnknownChatString(ParsedText);
                    }
                }
            }



            /*
                            else if (RawText.Contains("hits you") ||
                                RawText.Contains("crits you"))
                            {

                                int i = RawTextOriginal.IndexOf("\'s");
                                if (i != -1)
                                {
                                    string MonsterName = RawTextOriginal.Substring(0, i);

                                    if (RawText.Contains("throw") ||
                                       RawText.Contains("shoot"))
                                    {
                                        // Ranged mob...
                                        AddRanged(MonsterName);
                                    }
                                    if (Me.Target != null && Me.Target.Name == MonsterName)
                                    {
                                        // my target
                                        if (Me.Target.DistanceToSelf > Context.MeleeDistance + 2)
                                        {
                                            Spam(Me.Target.Name + " hit me from distance " + Me.Target.DistanceToSelf);
                                            //if(IsCaster(Me.Target.Name))
                                            AddUnmovable(Me.Target.Name);
                                        }
                                    }
                                }
                            }

                            if (RawText.Contains("you are afflicted by") &&
                        (RawText.Contains("terror") ||
                         RawText.Contains("fear") ||
                         RawText.Contains("terrify") ||
                         RawText.Contains("psychic scream") ||
                         RawText.Contains("shriek") ||
                         RawText.Contains("charm") ||
                         RawText.Contains("sleep") ||
                         RawText.Contains("charm") ||
                         RawText.Contains("sleep") ||
                         RawText.Contains("seduce") ||
                         RawText.Contains("slumber")))
                            {
                                Spam("Got feared/charmed/sleeped.");
                                Feared.Reset();
                            }
                            if (RawText.Contains("fades from you"))
                            {
                                if (RawText.Contains("terror") ||
                                    RawText.Contains("fear") ||
                                    RawText.Contains("terrify") ||
                                    RawText.Contains("psychic scream") ||
                                    RawText.Contains("shriek"))
                                {
                                    Spam("Fear Ran out.");
                                    Feared.ForceReady();
                                }
                            }
                            if (RawText.Contains("your") &&
                               (RawText.Contains("backstab") ||
                            RawText.Contains("hemorrhage") ||
                            RawText.Contains("mutilate") ||
                            RawText.Contains("ambush") ||
                            RawText.Contains("ghostly strike") ||
                            RawText.Contains("gouge") ||
                            RawText.Contains("kick") ||
                            RawText.Contains("eviscerate") ||
                            RawText.Contains("kidney shot") ||
                            RawText.Contains("riposte") ||
                                // RawText.Contains("rupture") || SPAM!
                            RawText.Contains("sinister strike") ||
                            RawText.Contains("envenom")))
                            {
                                Spam("  " + RawTextOriginal);
                            }

                            if (RawText.Contains("afflicted by") &&
                               (RawText.Contains("kidney shot") ||
                            RawText.Contains("cheap shot") ||
                            RawText.Contains("riposte") ||
                            RawText.Contains("rupture")))
                            {
                                Spam("  " + RawTextOriginal);
                            }

                            if (RawText.Contains("you gain"))
                            {
                                Spam("  " + RawTextOriginal);
                                if (RawText.Contains("combat potency"))
                                {
                                }
                            }
                        }

                            */

        }
        #endregion

       
        private const string fDisplayName = "SimpleHunter 0.3.6";
        private const float MovementSens = 2f;

        //player management vars.
        private int fPullDistance = 35;
        private float fManaConserve = 0.60f;
        private float fHealPercent = 0.15f;
        private float fFeignThresh = 0.75f;
        private float fApproachThresh = 0.0f;
        private float fAvoidMeleeThresh = 1.0f;
        private int LowAmmoThresh = 200;
        private int CheetahThresh = 100;
        private int SendPetDelay = 500;
        string RangedAspect = "Aspect of the Hawk";
        string MeleeAspect = "None";
        string TravelAspect = "None";
        private float ElevationThresh = 5f;
        private float RadarDist = 50f, DangerThresh = 25f;
        private int QuitAmmoThresh = 20;
        bool fMountBetween = true;
        string fMountName = "";
                    
        
        //pet management vars.
        //private bool PetRecall = true;
        //private bool QuitWhenOoA = false;
        private int fPetAttackClearance = 25;
        private int fPetAvoidClearance = 25;
        private double fMendPetThresh = 0.50f;
        private double fFeedPetThresh = 0.70f;
        private double fRecallPetThresh = 0.0f;
        //private bool PetRecallFinish = true;
        private bool UseIntimidation = true;
        private bool UseWrath = true;
        private double PetDeviationThresh = 7.5;

        private bool PetEating = false;
        private string PetName = "";
        //private string PetFoodName = "";
        GSpellTimer MendPetTimer = new GSpellTimer((int)(17.5 * 1000));
        GSpellTimer FeedPetTimer = new GSpellTimer(20 * 1000);
        GSpellTimer FirstShotTimer = new GSpellTimer(15 * 1000);
        GSpellTimer SecondShotTimer = new GSpellTimer(20 * 1000);
        GSpellTimer PetWaitTimer = new GSpellTimer(500);

        //location management vars.
        GSpellTimer RadarTimer = new GSpellTimer(1000);
        MobRadar Radar;

        SmartLogHandler LogHandler;
        //TendencyManager MobTendencies = new TendencyManager();

        #region Configuration Methods
        public override void CreateDefaultConfig()
        {

            //ConfigWriter cwriter = new ConfigWriter();
            //cwriter.PushDefaultConfig(Context);

            //GConfigManager test = new GConfigManager("SimpleHunter", Context);
            //test.PushDefaultConfig();

            base.CreateDefaultConfig();


        }

        
        

        public override GConfigResult ShowConfiguration()
        {
            //GConfigWindow w = new GConfigWindow();

            GConfigManager test = new GConfigManager("SimpleHunter", Context);
            Context.Log("Generating Config Dialog");
            return test.ShowConfig();
            
            //ConfigWriter cwriter= new ConfigWriter();

            //w.SetHelpHandler(new GConfigWindow.HandleHelp(MyHelpHandler));      // Optional, if missing then "Help" is disabled.
            //w.SetValidateHandler(new GConfigWindow.ValidateForm(MyValidator));  // Optional, if missing then no custom validation is done.
            //w.LoadXml(cwriter.ToXML());
            //w.Load("Classes\\SimpleHunter.xml");
            
            // TODO: base64 the XML and include it as a giant string in class file to simplify distribution
            // as a single .cs source file...

        }
        
        public override void LoadConfig()
        {
            //drop our default keys in the key map if they are missing.
            Context.SetKeyValue("SimpleHunter.HuntersMark", GBarState.Combat, 0, '1');
            Context.SetKeyValue("SimpleHunter.AutoShot", GBarState.Combat, 0, '2');
            Context.SetKeyValue("SimpleHunter.FirstShot", GBarState.Combat, 0, '3');
            Context.SetKeyValue("SimpleHunter.SecondShot", GBarState.Combat, 0, '4');
            Context.SetKeyValue("SimpleHunter.RepeatShot", GBarState.Combat, 0, '5');
            Context.SetKeyValue("SimpleHunter.BeastialWrath", GBarState.Combat, 0, '6');
            Context.SetKeyValue("SimpleHunter.Intimidation", GBarState.Combat, 0, '7');
            Context.SetKeyValue("SimpleHunter.KillCommand", GBarState.Combat, 0, '8');
            Context.SetKeyValue("SimpleHunter.RaptorStrike", GBarState.Combat, 0, '9');
            Context.SetKeyValue("SimpleHunter.Disengage", GBarState.Combat, 0, '0');
            Context.SetKeyValue("SimpleHunter.MendPet", GBarState.Combat, 0, '-');
            Context.SetKeyValue("SimpleHunter.Feign", GBarState.Rest, 0, '4');
            Context.SetKeyValue("SimpleHunter.AddTrap", GBarState.Rest, 0, '5');
            Context.SetKeyValue("SimpleHunter.FeedPet", GBarState.Rest, 0, '7');
            Context.SetKeyValue("SimpleHunter.CallPet", GBarState.Rest, 0, '8');
            Context.SetKeyValue("SimpleHunter.RevivePet", GBarState.Rest, 0, '9');
            Context.SetKeyValue("SimpleHunter.MeleeAspect", GBarState.Rest, 0, '0');
            Context.SetKeyValue("SimpleHunter.RangedAspect", GBarState.Rest, 0, '-');
            Context.SetKeyValue("SimpleHunter.TravelAspect", GBarState.Rest, 0, '=');

            //visible
            try { RadarDist = Context.GetConfigInt("SimpleHunter.RadarDist"); }
            catch { Context.Log("ERROR Loading RadarDist"); }

            try { DangerThresh = Context.GetConfigInt("SimpleHunter.DangerThresh"); }
            catch { Context.Log("ERROR Loading DangerThresh"); }

            try { fPullDistance = Context.GetConfigInt("SimpleHunter.PullDistance"); }
            catch { Context.Log("ERROR Loading PullDistance"); }

            try { SendPetDelay = Context.GetConfigInt("SimpleHunter.SendPetDelay"); }
            catch { Context.Log("ERROR Loading SendPetDelay"); }

            try { fManaConserve = (float)Context.GetConfigInt("SimpleHunter.ManaConserve") / 100f; }
            catch { Context.Log("ERROR Loading ManaConserve"); }

            try { fHealPercent = (float)Context.GetConfigInt("SimpleHunter.HealPercent") / 100f; }
            catch { Context.Log("ERROR Loading HealPercent"); }

            try { fFeignThresh = (float)Context.GetConfigInt("SimpleHunter.FeignThresh") / 100f; }
            catch { Context.Log("ERROR Loading HealPercent"); }

            try { LowAmmoThresh = Context.GetConfigInt("SimpleHunter.LowAmmoThresh"); }
            catch { Context.Log("ERROR Loading LowAmmoThresh"); }
            
            try { QuitAmmoThresh = Context.GetConfigInt("SimpleHunter.QuitAmmoThresh"); }
            catch { Context.Log("ERROR Loading QuitAmmoThresh"); }

            
            try { CheetahThresh = Context.GetConfigInt("SimpleHunter.CheetahThresh"); }
            catch { Context.Log("ERROR Loading CheetahThresh"); }

            try { RangedAspect = Context.GetConfigString("SimpleHunter.RangedAspect"); }
            catch { Context.Log("ERROR Loading RangedAspect"); }

            try { MeleeAspect = Context.GetConfigString("SimpleHunter.MeleeAspect"); }
            catch { Context.Log("ERROR Loading MeleeAspect"); }

            try { TravelAspect = Context.GetConfigString("SimpleHunter.TravelAspect"); }
            catch { Context.Log("ERROR Loading TravelAspect"); }

            try { fMountBetween = Context.GetConfigBool("SimpleHunter.MountBetween"); }
            catch { Context.Log("ERROR Loading MountBetween"); }

            try { fMountName = Context.GetConfigString("SimpleHunter.MountName"); }
            catch { Context.Log("ERROR Loading MountName"); }



            try { PetName = Context.GetConfigString("SimpleHunter.PetName"); }
            catch { Context.Log("ERROR Loading PetName"); }

            try { UseIntimidation = Context.GetConfigBool("SimpleHunter.UseIntimidation"); }
            catch { Context.Log("ERROR Loading UseIntimidation"); }

            try { UseWrath = Context.GetConfigBool("SimpleHunter.UseWrath"); }
            catch { Context.Log("ERROR Loading UseWrath"); }

            try { fPetAttackClearance = Context.GetConfigInt("SimpleHunter.PetAttackClearance"); }
            catch { Context.Log("ERROR Loading PetAttackClearance"); }

            try { fPetAvoidClearance = Context.GetConfigInt("SimpleHunter.PetAvoidClearance"); }
            catch { Context.Log("ERROR Loading PetAvoidClearance"); }

            try { ElevationThresh = Context.GetConfigInt("SimpleHunter.ElevationThresh"); }
            catch { Context.Log("ERROR Loading ElevationThresh"); }

            try { fMendPetThresh = (float)Context.GetConfigInt("SimpleHunter.MendPetThresh") / 100f; }
            catch { Context.Log("ERROR Loading MendPetPercent"); }

            try { fFeedPetThresh = (float)Context.GetConfigInt("SimpleHunter.FeedPetThresh") /100.0f;}
            catch { Context.Log("ERROR Loading FeedPetThresh"); }

            try { fApproachThresh = (float)Context.GetConfigInt("SimpleHunter.ApproachThresh") / 100.0f; }
            catch { Context.Log("ERROR Loading ApproachThresh"); }

            try { fAvoidMeleeThresh = (float)Context.GetConfigInt("SimpleHunter.AvoidMeleeThresh") / 100.0f; }
            catch { Context.Log("ERROR Loading AvoidMeleeThresh"); }

            try { fRecallPetThresh = (float)Context.GetConfigInt("SimpleHunter.RecallPetThresh") / 100.0f; }
            catch { Context.Log("ERROR Loading RecallPetThresh"); }

            //do any housekeeping on the new values...
            Radar = new MobRadar(Context, RadarDist);

            PetWaitTimer = new GSpellTimer(SendPetDelay);

            base.LoadConfig();
        }

 
        #endregion

        #region Gliding Support Overrides

        public override string DisplayName
        {
            get { return fDisplayName; }
        }
        public override int PullDistance
        {
            get
            {
                return fPullDistance;
            }
        }

        public override void OnAttach()
        {

            base.OnAttach();
        }
		
////////////////////
// START - NEEDED ?
////////////////////		
		        void Pather_ChatLog(string RawText, string ParsedText)
        {
            
            if (ParsedText.StartsWith("You tell a joke."))
            {
                GLocation loc = GContext.Main.Me.Location;
                //Location isAt2 = new Location(loc.X, loc.Y, loc.Z);
                //Context.Log("Add new hotspot");
                //hotSpots.Add(isAt2);
                Context.Log(String.Format(PPather.numberFormat, "[ {0,2:#0.0}, {1,2:#0.0}, {2,2:#0.0}]", loc.X, loc.Y, loc.Z));
            }
            else if (ParsedText.StartsWith("Dump"))
            {
                GNode[] nodes = GObjectList.GetNodes();
                foreach (GNode node in nodes)
                {
                    node.Refresh(true);
                    Context.Log("node: " + node.Name + " at " + node.Location.Z);
                }
                GUnit[] units = GObjectList.GetUnits();
                foreach (GUnit unit in units)
                {
                    Context.Log("unit: " + unit.Name);
                }
                
              
            }
            else if(ParsedText.Contains("The Horde wins!") || 
                    ParsedText.Contains("The Alliance wins!"))
            {
                Context.Log("BG ended. Stop glider");
                Context.KillAction("BG ended", false);                
            }

        }

void Pather_CombatLog(string rawText)
        {
            if (rawText == null) return;
            if (rawText.Contains("dies, you gain"))
            {
                int start = 0;
                int end = rawText.IndexOf("dies,") - 1;
                String mob = rawText.Substring(start, end - start);
                Context.Log("Killed monster '" + mob + "'");
                if (CurrentPullTask != null)
                {
                    CurrentPullTask.KilledMob(mob);
                    Context.Log(CurrentPullTask.ToString());
                }
                else
                {
                    Context.Log("No Pull Task");
                }
                 
            }

        }

        private string FigureOutZone()
        {
            // danbopes supplies these 2 lines to remove the macro
            GInterfaceObject ZoneTextString = GContext.Main.Interface.GetByName("ZoneTextFrame").GetChildObject("ZoneTextString"); 
            GInterfaceObject SubZoneTextString = GContext.Main.Interface.GetByName("SubZoneTextFrame").GetChildObject("SubZoneTextString"); 
            return SubZoneTextString.LabelText + ":" + ZoneTextString.LabelText;
        }
		
////////////////////
// END - NEEDED ?
////////////////////

		
		
        public override void OnStartGlide()
        {
            base.OnStartGlide();
        }

        void LogHandler_OnUnknownChatString(string s)
        {
            Context.Debug("OnUnknownChatString: " + s);
        }

        void LogHandler_OnLoot(string s)
        {
            Context.Debug("OnLoot: " + s);
            //throw new NotImplementedException();
        }

        void LogHandler_OnWhisper(string s)
        {
            Context.Debug("OnWhisper: " + s);
            //throw new NotImplementedException();
        }

        void LogHandler_OnPetLosesEffect(string s)
        {
            Context.Debug("OnPetLosesEffect: " + s);
            if (s.Contains("feed pet effect"))
            {
                PetEating = false;
            }

            if (s.Contains("mend pet"))
            {
                Context.Log("Finished mending Pet");
                MendPetTimer.ForceReady();
            }
        }

        void LogHandler_OnPetGainsEffect(string s)
        {
            Context.Debug("OnPetGainsEffect: " + s);
            if (s.Contains("gains feed pet effect"))
            {
                Context.Log("PetFeeding = true");
                PetEating = true;
            }

            if (s.Contains("gains mend pet"))
            {
                Context.Log("Mending Pet");
                MendPetTimer.Reset();
            }
        }


        void LogHandler_OnLoseEffect(string s)
        {
            Context.Debug("OnLoseEffect: " + s);
        }

        void LogHandler_OnGainEffect(string s)
        {
            Context.Debug("OnGainEffect: " + s);
        }

        void LogHandler_OnPetHit(string s)
        {
            Context.Debug("OnPetHit: " + s);
            if (!PetWaitTimer.IsReady)
            {
                Context.Log("Pet engaged after " + PetWaitTimer.TicksSinceLastReset + " ticks elapsed.");
                PetWaitTimer.ForceReady();
            }
        }

        
        void LogHandler_OnYouHit(string s)
        {
            Context.Debug("OnYouHit: " + s);
        }

        void LogHandler_OnYouCrit(string s)
        {
            Context.Debug("OnYouCrit: " + s);
        }


        void LogHandler_OnUnhandledString(string s)
        {
            Context.Debug("OnUnhandledString: " + s);
        }

        void LogHandler_OnRunner(string s)
        {
            Context.Debug("OnRunner: " + s);
        }

        void LogHandler_OnMonsterEvade(string s)
        {
            Context.Debug("OnMonsterEvade: " + s);
        }

        void LogHandler_OnCampFireDamage(string s)
        {
            Context.Debug("OnCampFireDamage: " + s);
        }

        void LogHandler_OnCallsForHelp(string s)
        {
            Context.Debug("OnCallsForHelp: " + s);

        }

        void LogHandler_OnAoEDamage(string s)
        {
            Context.Debug("OnAoEDamage: " + s);
        }

        public override void OnStopGlide()
        {
           base.OnStopGlide();
        }
        public override void OnResurrect()
        {
            Context.Debug("SimpleHunter::OnResurrect");

            //rest all our timers
            MendPetTimer.ForceReady();
            FeedPetTimer.ForceReady();
            SecondShotTimer.ForceReady();
            
            base.OnResurrect();
        }
        public override bool Rest()
        {
            Context.Debug("SimpleHunter::Rest");
            if (!Me.HasLivePet)
            {
                Context.CastSpell("SimpleHunter.CallPet");
                Thread.Sleep(3000);
            }

            if (!Me.HasLivePet && Interface.IsKeyPopulated("SimpleHunter.RevivePet"))
            {
                
                //wait for the proper amount of mana?
                int retry = 30;
                while (retry-- > 0 && !Interface.IsKeyEnabled("SimpleHunter.RevivePet"))
                {
                    Context.Log("Waiting for RevivePet");
                    Thread.Sleep(1000);
                }
                
                
                CheckAndFireKey("SimpleHunter.RevivePet");
                Thread.Sleep(500);
                FeedPetTimer.ForceReady();

                CheckAndFireKey("SimpleHunter.MendPet");
                Thread.Sleep(500);

                
            }

            if(!FeedPetTimer.IsReady)
                ConsiderFeedPet(fFeedPetThresh);

            if (!Context.IsManualKill)
            {
                if (Me.AmmoCount < QuitAmmoThresh)
                    Context.HearthSoon();
            }
            
            
            Thread.Sleep(1000);
            return base.Rest();

            //return true;
        }
        public override void ApproachingTarget(GUnit Target)
        {
            //consider sending our pet int first...
            if (fMountBetween && fMountName != "" && HasBuffByName(fMountName))
            {
                Context.Log("Dropping off mount.");
                Context.CastSpell("Common.Mount");
            }

            if (RangedAspect != "None" && !HasBuffByName(RangedAspect))
            {
                Context.Log("Fixing ranged aspect.");
                CheckAndFireKey("SimpleHunter.RangedAspect");
            }
            if (Target != null && (Me.Target == null || Me.Target.GUID != Target.GUID))
            {
                Target.SetAsTarget(false);
                if (!PetEating)
                {
                    if (ConsiderPetAttack(Target))
                    {
                        
                        PetWaitTimer.Reset();
                        Context.Log("Sending in pet on approach."); ;
                    }
                    else
                    {
                        PetWaitTimer.ForceReady();
                    }
                }
            }



            
            Target.Approach(PullDistance - (Context.RNG.NextDouble() * 4.0), false);
            Context.ReleaseSpinRun();


            base.ApproachingTarget(Target);
        }
        public override void RunningAction()
        {
            
            bool mobclose = false;
            //check for mobs...
            GMonster[] closemobs = GObjectList.GetMonsters();
            foreach (GMonster mob in closemobs)
            {
                if ((Me.HasLivePet && mob.GUID != Me.Pet.GUID) || !Me.HasLivePet)
                {
                    //Context.Log(mob.ToString() + mob.DistanceToSelf.ToString("0.0"));

                    if (mob.DistanceToSelf < CheetahThresh)
                    {
                        mobclose = true;
                        continue;
                    }
                }
            }


            if(fMountBetween)
            {

                if(fMountName != "" && !HasBuffByName(fMountName))
                {

                    if (!mobclose)
                    {
                        Context.ReleaseSpinRun();

                        //Context.CastSpell("Common.Mount");
                        //Context.Log("not close to mobs");
                        //are we already mounted?
                        Context.CastSpell("Common.Mount");

                       
                    }

                }
            }
            else
            {
                if (TravelAspect != "None" && !HasBuffByName(TravelAspect))
                {

                    if (!mobclose)
                    {
                        //Context.ReleaseSpinRun();

                        //Context.CastSpell("Common.Mount");
                        //Context.Log("not close to mobs");
                        //are we already mounted?
                        CheckAndFireKey("SimpleHunter.TravelAspect");


                    }
                }
            }
                base.RunningAction();
        }
        #endregion

        #region KillPlayer
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        //  Class:  SimpleHunter
        //  Method: KillMonster
        //  Purpose:    
        //  Returns:    
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        public GCombatResult KillPlayer(GUnit Target, bool IsAmbush)
        {
            Context.Debug("SimpleHunter: Killing Player " + Target.ToString());

            GLocation CombatStart = Me.Location;
            GCombatResult retval = GCombatResult.Unknown;

            bool MoveErr = false;

            bool LoopConsumed, NeedFirstShot = true;

            Target.Refresh(true);
            Me.Refresh(true);

            Context.SendKey("Common.PetAttack");
            Context.CastSpell("SimpleHunter.HuntersMark");
            Thread.Sleep(Context.RNG.Next(100, 200));
            GSpellTimer ForceMelee = new GSpellTimer(3000);
            ForceMelee.ForceReady();
            //if we have enough ammo do our pull.
            if (Me.AmmoCount > 3 && Target.DistanceToSelf > Context.RangedDistance && Target.DistanceToSelf < PullDistance)
            {

                //don't first shot if this is an ambush
                if (Context.RNG.NextDouble() <= 0.9 && !IsAmbush)
                {
                    Context.CastSpell("SimpleHunter.FirstShot");
                    Thread.Sleep(100 + Context.RNG.Next(100, 200));
                }

                //second shot most of the time.
                if (Context.RNG.NextDouble() <= 0.7)
                {
                    Context.CastSpell("SimpleHunter.SecondShot");
                    Thread.Sleep(100 + Context.RNG.Next(100, 200));
                }
                NeedFirstShot = false;
            }
            else
            {
                NeedFirstShot = true;

            }

            while (!Me.IsDead && !Target.IsDead && Me.IsInCombat)
            {
                Me.Refresh(true);
                Target.Refresh(true);
                LoopConsumed = false;


                if (Me.Pet.Target == null || Me.Pet.Target.GUID != Target.GUID)
                    {
                        Context.Debug("Agro Target");
                        Target.SetAsTarget(false);
                        Thread.Sleep(200);
                        Context.SendKey("Common.PetAttack");
                        Thread.Sleep(200);
                    }

                if (Me.Target != Target)
                    Target.SetAsTarget(true);

                //why aren't we targetting our monster?





                //check our health and pop a potion if needed.
                if (!LoopConsumed && Me.Health < fHealPercent)
                {
                    LoopConsumed = CheckAndFireKey("Common.Potion");
                }


                if (!LoopConsumed && Me.HasLivePet)
                {
                    if (MendPetTimer.IsReady && Me.Pet.Health < fMendPetThresh)
                    {
                        if (CheckAndFireKey("SimpleHunter.MendPet"))
                        {
                            Context.Debug("SimpleHunter.MendPet");
                            MendPetTimer.Reset();
                            LoopConsumed = true;
                        }
                    }
                }


                if (!LoopConsumed && UseWrath)
                {
                    if (!LoopConsumed)
                    {
                        if (CheckAndFireKey("SimpleHunter.BeastialWrath"))
                        {
                            Context.Debug("SimpleHunter.BeastialWrath");
                            LoopConsumed = true;
                        }
                    }
                }


                //first things first check our radar
                if (!LoopConsumed && !MoveErr)
                {
                    //Context.Log("Radar");
                    if (Radar != null && RadarTimer.IsReady)
                    {
                        LoopConsumed = ConsiderDanger(Target, Me.Location, out MoveErr);
                        if (MoveErr)
                        {
                            ForceMelee.Reset();
                            Context.Log("Move Err");
                        }
                        RadarTimer.Reset();
                    }
                }

                //position management
                if (!LoopConsumed && !Target.IsDead)
                {
                    //Context.Log("Position");

                    //if we are too far away... approach
                    if (Target.DistanceToSelf > PullDistance )
                    {
                        Context.Debug("Approach");
                        Target.Approach(PullDistance - (Context.RNG.NextDouble() * 4.0));
                        LoopConsumed = true;
                    }

                    else if (Target.DistanceToSelf < Context.RangedDistance && !Target.IsTargetingMe && !Interface.IsKeyFiring("SimpleHunter.RaptorStrike") && Target.Health > 0.1 && ForceMelee.IsReady)
                    {

                        Context.Debug("Backup");
                        GLocation SafePlace;

                       
                        //if (Target.Location != null && Me.CombatStartLocation != null)
                        //{
                        SafePlace = Radar.FindSafePlace2(Target.Location, CombatStart, (float)Context.RangedDistance + MovementSens + ((float)(Context.RNG.Next() % 10) / 5.0f), (float)PullDistance - MovementSens - ((float)(Context.RNG.Next() % 10) / 5.0f), GObjectList.GetMonsters(), Target.GUID, Me);
                        //}
                        //else
                        //{

                        //}
                            Context.ReleaseSpinRun();
                            Context.CastSpell("Common.PetFollow");

                            //should check heading and run forward if needed
                            Context.PressKey("Common.Back");
                            GSpellTimer BackTimer = new GSpellTimer(3500);
                            BackTimer.Reset();
                            Movement.SetHeading(GetAngle(Me.Location, SafePlace) + Math.PI, 0);

                            CheckAndFireKey("SimpleHunter.TrapAdd");

                            while (Target.DistanceToSelf < (Context.RangedDistance + MovementSens) && !BackTimer.IsReady)
                            {
                                //float angle = GetAngle(Me.Location, SafePlace);
                                //Movement.SetHeading(GetAngle(Me.Location, SafePlace) + Math.PI, 0);
                                Me.Refresh(true);
                                Thread.Sleep(100);
                            }
                            Context.ReleaseKey("Common.Back");


                        if (Target.DistanceToSelf < (Context.RangedDistance + MovementSens))
                        {
                            Context.Log("Force Melee");//force approach...
                            ForceMelee.Reset();

                       }

                        LoopConsumed = true;
                    }

                    else if (((Target.DistanceToSelf > Context.MeleeDistance || Target.DistanceToSelf < 2.0) && (Target.DistanceToSelf < Context.RangedDistance) && (Target.IsTargetingMe || !ForceMelee.IsReady)))
                    {
                        Context.Debug("Tweek");
                        Movement.MoveToUnit(Target, Context.MeleeDistance, false);
                        LoopConsumed = true;
                    }

                }

                //face the mob if we aren't
                if (!LoopConsumed)
                {
                    //Context.Log("Face");
                    if (Math.Abs(Me.GetHeadingTo(Target) - Me.Heading) >= Math.PI/3)
                    {
                        Target.Face((Math.PI / 4));
                        LoopConsumed = true;
                    }
                }

                //melee combat options
                if (!LoopConsumed && Target.DistanceToSelf < Context.MeleeDistance)
                {
                    Context.Debug("AddTrap");
                    if (CheckAndFireKey("SimpleHunter.AddTrap"))
                    {
                        LoopConsumed = true;
                        Context.SendKey("Common.Back");
                        Thread.Sleep(500);
                        Context.ReleaseKey("Common.Back");
                    }

                }



                //melee combat options
                if (!LoopConsumed && Target.DistanceToSelf < Context.MeleeDistance)
                {

                    if (MeleeAspect != "None" && !HasBuffByName(MeleeAspect))
                        LoopConsumed = CheckAndFireKey("SimpleHunter.MeleeAspect");

    
                    if ((Me.Mana > fManaConserve || NeedFirstShot) && !Interface.IsKeyFiring("SimpleHunter.RaptorStrike"))
                        {
                            if (CheckAndFireKey("SimpleHunter.RaptorStrike"))
                            {
                                Context.Debug("RaptorStrike");
                                LoopConsumed = true;
                                NeedFirstShot = false;
                            }

                        }
                }


                if (UseIntimidation && !LoopConsumed)
                {
                    if (CheckAndFireKey("SimpleHunter.Intimidation"))
                    {
                        Context.Debug("Intimidation");
                        LoopConsumed = true;
                    }
                }


                if (!LoopConsumed && Target.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0)
                {

                    if (RangedAspect != "None" && !HasBuffByName(RangedAspect))
                        LoopConsumed = CheckAndFireKey("SimpleHunter.RangedAspect");

                    if (Me.Mana > 0.2 || NeedFirstShot)
                    {
                        if (CheckAndFireKey("SimpleHunter.RepeatShot"))
                        {
                            Context.Debug("Repeat");
                            LoopConsumed = true;
                            NeedFirstShot = false;
                        }
                    }
                }


                //Context.Log("LoopConsumed: " + LoopConsumed.ToString());

                Thread.Sleep(100);

                //bottom of kill loop...
            }

            if (Me.IsDead)
                retval = GCombatResult.Died;
            else
                retval = GCombatResult.Success;

            return retval;
        }
        #endregion
        
        #region KillMonster
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        //  Class:  SimpleHunter
        //  Method: KillMonster
        //  Purpose:    
        //  Returns:    
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        
        public GCombatResult KillMonster(GUnit Target, bool IsAmbush)
        {
            Context.Log("SimpleHunter: Killing Target " + Target.Name + " (" + Target.Level + ")");

            int StartingWaypoint = Context.Profile.CurrentIndex;

            //set our waypoint to the one that we just previously
            //passed.  then use this as our starting point.
            Context.Profile.SetPreviousWaypoint();
            

            GCombatResult retval = GCombatResult.Unknown;

            bool MoveErr = false, StayHere = false;

            int PetPullAttempt = 0;//, LoopCount = 0;
            bool LoopConsumed, NeedFirstShot = true;
            float PetRecallThresh = 0f; //defaults to no recall
            float ApproachThresh = 0f;
            GMonster Monster = null;
            if(Target.IsMonster)
                Monster = (GMonster)Target;

            // our base pet recall
            PetRecallThresh = (float)fRecallPetThresh;
            ApproachThresh = (float)fApproachThresh;

            //if we are low on ammo... pull the mob back soon
            if (Me.AmmoCount < LowAmmoThresh)
                PetRecallThresh = 0.75f;

            
            //if we are out of ammo... kick it up to 1.0 health
            if (Me.AmmoCount <= 10)
                PetRecallThresh = 1.0f;
            
            if (Math.Abs(Me.Location.Z - Monster.Location.Z) > ElevationThresh)
            {
                Context.Log("Monster Elevation Limit: Melee Finish");
                if(PetRecallThresh < 0.2f)
                    PetRecallThresh = 0.2f;
            }

            if (Target.Level > Me.Level && PetRecallThresh > 0.1f)
                PetRecallThresh -= 0.1f;

            
            Target.Refresh(true);
            Me.Refresh(true);

            if (Target.IsMonster && Monster != null)
            {
                //check to see if someone else has our mob tagged.
                if (Monster.IsTagged && !Monster.IsMine && !IsAmbush)
                    return GCombatResult.OtherPlayerTag;
            }

            GLocation CombatStart = Me.Location;




                CheckAndFireKey("SimpleHunter.HuntersMark");
                //Thread.Sleep(Context.RNG.Next(50, 100));

            //face our monster
            Target.Face(0.2);

            NeedFirstShot = !IsAmbush;

            //combat loop
            if(Monster != null)
                retval = Context.CheckCommonCombatResult(Monster, IsAmbush);

            while (retval == GCombatResult.Unknown)
            {
                Me.Refresh(true);
                Target.Refresh(true);
                //if (Me.Pet != null)
                //    Me.Pet.Refresh(true);

                LoopConsumed = false;

                if (PetRecallThresh < 1.0f && Context.RedMessage.ToLower().Contains("line of sight"))
                {
                    Context.Log("Line of sight error.  Recalling pet.");
                    PetRecallThresh = 1.0f;
                }
                
                //roll our current waypoint back to give us a safe place to run to
                if (Context.Profile.CurrentWaypoint.DistanceToSelf < Context.RangedDistance)
                {
                    Context.Debug("Previous Waypoint");
                    Context.Profile.SetPreviousWaypoint();
                }

                //ok...
                GUnit TempMonster = null;
                GMonster PetAdd = GObjectList.GetNearestHostile(Monster.Location, Monster.GUID, false);
                GLocation LastWayPoint = Context.Profile.Waypoints[Context.Profile.CurrentIndex];
                GUnit[] Attackers = GObjectList.GetAttackers(true);
                int TargetingMe = 0, AttackerCount = 0;

                if(Attackers != null)
                {
                    foreach (GUnit x in Attackers)
                    {
                        AttackerCount++;
                        if (x.Target == Me)
                        {
                            TargetingMe++;
                            TempMonster = x;
                        }
                    }
                }


                if (Me.Pet != null && Me.Pet.Target != null && Me.Pet.Target.GUID == Target.GUID)
                {


                    /*
                    if(ConsiderPetHeading(Math.PI / 3, Target))
                    {
                        if (PetRecallThresh < 0.3)
                        {
                            Context.Log("Pet deviated from our exptected heading:  Forcing a melee finish via pet recall.");
                            PetRecallThresh = 0.3f;
                        }
                    }
                    
                    */
                    
                    if (ConsiderPetDeviation(PetDeviationThresh, Target))
                    {
                        if (PetRecallThresh < 0.3)
                        {
                            Context.Log("Pet deviated from our expected path:  Forcing a melee finish via pet recall.");
                            PetRecallThresh = 0.3f;
                        }
                    }

                }

                if ( Monster.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0 && PetWaitTimer.IsReady)
                {

                        if (!Interface.IsKeyFiring("SimpleHunter.AutoShot") && Interface.IsKeyEnabled("SimpleHunter.AutoShot"))
                        {
                            Context.SendKey("SimpleHunter.AutoShot");
                            Context.Log("Starting AutoShot.");
                            //Thread.Sleep(250);
                            //LoopConsumed = true;
                            //NeedFirstShot = false;

                        }
                }

                //Context.Log(AttackerCount.ToString());
                bool BringPetHome = (Monster.Health < PetRecallThresh) && (AttackerCount <= 1) ;
                bool Charge = (Monster.Health < ApproachThresh && AttackerCount <= 1 && PetRecallThresh <= 0.01) || (Me.AmmoCount <= 10);

                if (Me.Pet != null && Me.HasLivePet)
                {
                    if (TempMonster != null)
                    {
                        if (Me.Pet.Target == null || Me.Pet.Target.GUID != TempMonster.GUID)
                        {
                            Context.Debug("Agro Target");
                            TempMonster.SetAsTarget(false);
                            Thread.Sleep(250);
                            Context.SendKey("Common.PetAttack");
                            Thread.Sleep(250);
                            Monster.SetAsTarget(true);
                        }
                    }
                    else
                    {

                        if (Me.Pet.Target != null && Me.Pet.Target.GUID != Monster.GUID)
                        {
                            Monster.SetAsTarget(false);
                            Thread.Sleep(250);
                            Context.SendKey("Common.PetAttack");
                        }
                    }
                }

                //why aren't we targetting our monster?
                if (Me.Target != Monster)
                {
                    //don't just set our target...
                    //glider just tabs to our target and sometimes
                    //autoshot shoots an add on accident.
                    //so we clear the target first.
                    //Context.ClearTarget();
                    //Thread.Sleep(500);
                    Monster.SetAsTarget(false);
                    //LoopConsumed = true;
                }
                




                //check our health and pop a potion if needed.
                if (!LoopConsumed && Me.Health < fHealPercent )
                {
                    Context.Log("Potion");
                    LoopConsumed = CheckAndFireKey("Common.Potion");

                }


                if (!LoopConsumed && Me.HasLivePet && Me.Pet!=null)
                {
                    if (MendPetTimer.IsReady && Me.Pet.Health < (fMendPetThresh + (0.2 * (double)(AttackerCount - 1))))
                    {
                        if (CheckAndFireKey("SimpleHunter.MendPet"))
                        {
                            Context.Log("Mend");
                            MendPetTimer.Reset();
                            LoopConsumed = true;
                        }
                    }
                }


                if (!LoopConsumed && BringPetHome && Me.HasLivePet && Me.Pet != null)
                {
                    //Context.Log("BringPetHome");
                    //if pet is more than x distance recall him

                    //GMonster PetAdd = GObjectList.GetNearestHostile(Me.Pet.Location, Monster.GUID, false);
                    if ((PetPullAttempt < 3) && (Me.Pet.Target != null) && Me.GetDistanceTo(Monster.Location) > Context.MeleeDistance && (PetAdd == null || !PetAdd.IsTargetingMyPet))
                    {
                        Context.CastSpell("Common.PetFollow", false, false);
                        LoopConsumed = true;
                        PetPullAttempt++;

                    }
                    else if ((Me.Pet.Target == null) && Me.GetDistanceTo(Monster.Location) < Context.MeleeDistance)
                    {
                        Context.CastSpell("Common.PetAttack", false, false);
                        LoopConsumed = true;
                    }

                }
                else if (!LoopConsumed && !Monster.IsDead && Me.HasLivePet && Me.Pet != null)
                {
                    //Context.Log("NOT BringPetHome");
                    //if the pet is engaged and has agro and we have a walker approachin pull him back
                    if ( Me.Pet.Target != null && (PetPullAttempt < 3))
                    {
                        if ((Monster.IsTargetingMyPet && PetAdd != null && PetAdd.Location.GetDistanceTo(Monster.Location) < fPetAvoidClearance && !PetAdd.IsTargetingMyPet && !PetAdd.IsTargetingMe))
                        {
                            Context.CastSpell("Common.PetFollow", false, false);
                            
                            PetPullAttempt++;
                            LoopConsumed = true;
                        }
                    }
                    //if no one is close send the pet in
                    else if (Me.Pet != null && Me.Pet.Target == null)
                    {
                        
                        if (PetAdd == null || PetAdd.Location.GetDistanceTo(Monster.Location) > fPetAvoidClearance || Monster.DistanceToSelf < Context.RangedDistance)   // Far enough.
                        {
                            Context.CastSpell("Common.PetAttack", false, false);
                            if (NeedFirstShot)
                            {
                                Context.Log("Sending pet in for first attack.");
                                PetWaitTimer.Reset();
                            }
                            LoopConsumed = true;
                        }
                    }
                }
                
                //if (!LoopConsumed && Monster.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0 && Monster.Health < 0.15 && Monster.Health > PetRecallThresh)
                //{
                    //don't first shot if this is an ambush


                        //if (CheckAndFireKey("SimpleHunter.FirstShot"))
                        //{
                        //    LoopConsumed = true;
                        //    NeedFirstShot = false;
                        //}
                //}



                if (!LoopConsumed && Monster.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0)
                {
                    //don't first shot if this is an ambush

                    if ((NeedFirstShot) && PetWaitTimer.IsReady)
                    {

                        if (CheckAndFireKey("SimpleHunter.FirstShot"))
                        {
                            LoopConsumed = true;
                            NeedFirstShot = false;
                            Thread.Sleep(500);
                        }
                    }
                }

                if (!LoopConsumed )
                {
                    //don't first shot if this is an ambush

                        if (CheckAndFireKey("SimpleHunter.KillCommand"))
                        //if(Interface.IsKeyPopulated("SimpleHunter.KillCommand") && Interface.IsKeyReady("SimpleHunter.KillCommand"))
                        {
                            //Context.SendKey("SimpleHunter.KillCommand");
                            Context.Log("SimpleHunter.KillCommand");
                            LoopConsumed = true;
                        }
                }




                
                if (!LoopConsumed && UseWrath && Me.HasLivePet && Me.Pet != null)
                {
                    //Context.Log("Wrath");
                    if (!LoopConsumed && Monster.Health > 0.25 && Me.Pet.Target != null)
                    {
                        if (CheckAndFireKey("SimpleHunter.BeastialWrath"))
                        {
                            LoopConsumed = true;
                        }
                    }
                }




                //first things first check our radar
                if (!LoopConsumed && !MoveErr)
                {
                    //Context.Log("Radar");
                    if (Radar != null && RadarTimer.IsReady)
                    {
                        LoopConsumed = ConsiderDanger(Monster, GetPreferredLoc(), out MoveErr);
                        if (MoveErr)
                        {
                            PetRecallThresh = 1.0f;
                            Context.Log("Move Err");
                        }
                        RadarTimer.Reset();
                    }
                }

                //position management
                if (!LoopConsumed && !Monster.IsDead)
                {
                    //Context.Log("Position");


                    
                    //if we are too far away... approach
                    if (Monster.DistanceToSelf > PullDistance && !BringPetHome && Monster.Health > ApproachThresh)
                    {
                        Context.Debug("Approach");
                        Monster.Approach(PullDistance - (Context.RNG.NextDouble() * 2.0));
                        LoopConsumed = true;
                    }

                    else if (Monster.DistanceToSelf < Context.RangedDistance && !StayHere && !Monster.IsTargetingMe && !Interface.IsKeyFiring("SimpleHunter.RaptorStrike") && ((!Charge && !BringPetHome && Monster.Health > ApproachThresh && TargetingMe == 0) || (AttackerCount >= 2)))
                    {

                        Context.Debug("Backup");
                        Context.Debug("heading: " + Me.Heading.ToString("0.00"));
                        Context.Debug("to mob: " + GetAngle(Me.Location, Monster.Location).ToString("0.00"));
                        GLocation SafePlace = Radar.FindSafePlace(Monster.Location, GetPreferredLoc(), (float)Context.RangedDistance + MovementSens + ((float)(Context.RNG.Next() % 10) / 5.0f), (float)PullDistance - MovementSens - ((float)(Context.RNG.Next() % 10) / 5.0f), GObjectList.GetMonsters(), Target.GUID, Me);
                        Context.Debug("to safe: " + GetAngle(Me.Location, SafePlace).ToString("0.00"));

                        //now...

                        //float distance = MobRadar2.DistanceBetween(SafePlace, Monster.Location);

                        //float angle = GetAngle(Me.Location, SafePlace);
                        //Movement.SetHeading(angle + Math.PI, 0);
                        
                        //Context.StartSpinTowards(angle + Math.PI);

                        //Movement.BackAway(Monster, Context.RangedDistance + Context.RNG.NextDouble() + 1.0f);
                        
                        //if (SafePlace.DistanceToSelf < 20 )
                        {

                            Context.ReleaseSpinRun();
                            double headingdelta = Me.GetHeadingDelta(SafePlace);
                            Context.Debug(headingdelta.ToString());

                            string keyname = "";
                            if (headingdelta < (Math.PI * 0.25) && headingdelta > -(Math.PI * 0.25))
                            {
                                keyname = "Common.Forward";
                                Movement.SetHeading(GetAngle(Me.Location, SafePlace), 0);
                            }
                            else if (headingdelta > (Math.PI * 0.75) || headingdelta < -(Math.PI * 0.75))
                            {
                                keyname = "Common.Back";
                                Movement.SetHeading(GetAngle(Me.Location, SafePlace)+Math.PI, 0);
                            }
                            else if (headingdelta < 0)
                            {
                                keyname = "Common.StrafeLeft";
                                Movement.SetHeading(GetAngle(Me.Location, SafePlace) - (Math.PI/2), 0);
                            }
                            else
                            {
                                keyname = "Common.StrafeRight";
                                Movement.SetHeading(GetAngle(Me.Location, SafePlace) + (Math.PI/2), 0);
                            }
                            
                            if(keyname != "")
                            {
                                Context.Debug("Retreat using: " + keyname);

                                Context.PressKey(keyname);
                                GSpellTimer BackTimer = new GSpellTimer(7500);
                                BackTimer.Reset();
                                while (!Target.IsTargetingMe && Target.DistanceToSelf < (Context.RangedDistance ) && !BackTimer.IsReady)
                                {
                                    Thread.Sleep(200);
                                    Me.Refresh(true);
                                }
                                Context.ReleaseKey(keyname);

                            }
                        }


                        if(Monster.DistanceToSelf < (Context.RangedDistance))
                        {
                        
                        if (!Movement.MoveToLocation(SafePlace, MovementSens, false))
                        {
                            //drop a keep away "bump" in the radar.
                            Radar.AddKeepAway(Me.Location);
                            Context.Log("AddKeepAway");
                            PetRecallThresh = 1.0f;
                        }
                        }

                        Monster.Face(Math.PI / 4);
                        //NeedFirstShot = true;
                        LoopConsumed = true;
                    }

                    else if ((((Monster.DistanceToSelf > Context.MeleeDistance ) && ((Monster.DistanceToSelf < Context.RangedDistance) && (Monster.IsTargetingMe || BringPetHome) || Charge || StayHere))))
                    {
                        if (!StayHere && Monster.Health <= fAvoidMeleeThresh)
                        {
                            Context.Log("Stay Here");
                            StayHere = true;
                        }

                        Context.Debug("Tweek");
                        Movement.MoveToUnit(Monster, Context.MeleeDistance, false);
                        LoopConsumed = true;
                    }

                }

                if(!LoopConsumed && Monster.DistanceToSelf < 2.0)
                {

                    Context.PressKey("Common.Back");
                    Thread.Sleep(200);
                    Context.ReleaseKey("Common.Back");
                    LoopConsumed = true;
                }

                //face the mob if we aren't
                if (!LoopConsumed)
                {
                    //double mHeading = Me.GetHeadingTo(Monster);
                    //while (mHeading >= Math.PI)
                    //    mHeading -= (Math.PI * 2);


                    double dHeading = Me.GetHeadingTo(Monster) - Me.Heading;
                    while (dHeading > Math.PI)
                        dHeading -= (Math.PI * 2);

                    while (dHeading < -Math.PI)
                        dHeading += (Math.PI * 2);

                    if (dHeading >= (Math.PI / 3) || dHeading <= -(Math.PI/3))
                    {
                        //Context.Log(Me.GetHeadingTo(Monster).ToString("0.00"));
                        //Context.Log(Me.Heading.ToString("0.00"));
                        Context.Debug("Face");
                        Monster.Face(Math.PI / 6);
                        LoopConsumed = true;
                    }
                }



                //do we have attackers... and if so are we below 50% and if so... feign
                
                if (!LoopConsumed)
                {
                    if (Attackers != null && TargetingMe > 0 && Me.Health < fFeignThresh)
                    {
                        if (CheckAndFireKey("SimpleHunter.Feign"))
                        {
                            Context.Debug("Feign");
                            Thread.Sleep(3000);
                            LoopConsumed = true;
                            //NeedFirstShot = true;
                        }
                    }

                }
                
                
                
                //melee combat options
                if (!LoopConsumed && !Monster.IsDead && Monster.DistanceToSelf < Context.MeleeDistance)
                {
                    //Context.Log("Melee");
                    if (Me.HasLivePet && (Monster.Health > PetRecallThresh) && (Monster.Health > 0.2) && !Charge && !StayHere)
                    {
                        if (Me.Mana > 0.15 && !Interface.IsKeyFiring("SimpleHunter.Disengage"))
                        {
                            if (CheckAndFireKey("SimpleHunter.Disengage"))
                            {
                                Context.Log("Disengage");
                                LoopConsumed = true;
                                //NeedFirstShot = true;
                            }
                        }
                    }
                    else
                    {

                        if ((Me.Mana > fManaConserve || NeedFirstShot) && !Interface.IsKeyFiring("SimpleHunter.RaptorStrike"))
                        {
                            if (CheckAndFireKey("SimpleHunter.RaptorStrike"))
                            {
                                Context.Log("RaptorStrike");
                                LoopConsumed = true;
                                NeedFirstShot = false;
                            }
                            
                        }
                    }
                }


                if (UseIntimidation && !LoopConsumed && Me.HasLivePet && ((Attackers != null && (Attackers.Length > 1 || Me.Health < 0.25) && TempMonster != null && Me.Pet.Target != null && Me.Pet.Target.GUID == TempMonster.GUID) || (Monster.IsTargetingMe && Me.Pet.Target != null && Me.Pet.Target.GUID == Monster.GUID)) )
                {
                    if (CheckAndFireKey("SimpleHunter.Intimidation"))
                    {
                        Context.Debug("Intimidation");
                        LoopConsumed = true;
                    }
                }





                if (!LoopConsumed && Monster.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0 && Monster.Health > 0.25f)
                {
                    //don't first shot if this is an ambush

                    if ((Me.Mana > fManaConserve) && (SecondShotTimer.IsReady) && PetWaitTimer.IsReady)
                    {

                        if (CheckAndFireKey("SimpleHunter.SecondShot"))
                        {
                            SecondShotTimer.Reset();
                            LoopConsumed = true;
                            NeedFirstShot = false;
                        }
                    }
                }


                if (!LoopConsumed && Monster.DistanceToSelf > Context.RangedDistance && Me.AmmoCount > 0)
                {
                    //Context.Log("Repeat");
                    if ((Me.Mana > fManaConserve) && PetWaitTimer.IsReady)
                    {
                        if (CheckAndFireKey("SimpleHunter.RepeatShot"))
                        {
                            LoopConsumed = true;
                            NeedFirstShot = false;
                        }
                    }
                }



                //aspect management...
                if (!LoopConsumed)
                {
                    if (Attackers != null && (Attackers.Length > 1 || !Me.HasLivePet) && TargetingMe > 0)
                    {


                        if (MeleeAspect != "None" && !HasBuffByName(MeleeAspect))
                        {
                            if(CheckAndFireKey("SimpleHunter.MeleeAspect"))
                            {
                                Context.Debug("SimpleHunter.MeleeAspect");
                                LoopConsumed = true;
                            }
                        }
                    }
                    else
                    {

                        if (RangedAspect != "None" && !HasBuffByName(RangedAspect))
                        {
                            Context.Log("Ranged Aspect");
                            if (CheckAndFireKey("SimpleHunter.RangedAspect"))
                            {
                                Context.Debug("SimpleHunter.RangedAspect");
                                LoopConsumed = true;
                            }
                        }
                    }
                }


                //Context.Log("LoopConsumed: " + LoopConsumed.ToString());

                Thread.Sleep(250);

                retval = Context.CheckCommonCombatResult(Monster, IsAmbush);
                //bottom of kill loop...
            }

            Context.Profile.CurrentIndex = StartingWaypoint;
            return retval;

        }

        //public bool Wait
        #endregion 

        #region KillTarget
        public override GCombatResult KillTarget(GUnit Target, bool IsAmbush)
        {
            GCombatResult retval = GCombatResult.Unknown ;

            if (Target == null)
                return GCombatResult.Success;

            //update the status of the monster... if it returns false
            //the monster is no longer valid in the game.
            if (!Target.Refresh(true))
            {
                //the monster has vanished...
                //but there is a good possibility that our pet killed it if it
                //was a weak add....
                if (IsAmbush)
                    return GCombatResult.Success;
                else
                    return GCombatResult.Vanished;
            }


            Context.ReleaseAllKeys();

            if (retval == GCombatResult.Unknown)
            {
                if (Target.IsPlayer)
                {
                    // go straight to kill
                    retval = KillPlayer(Target, IsAmbush);
                }
                else
                {
                    //check for a delay if out pet is eating.
                    //ConsiderPetEating(7500);
                    if (ConsiderPetEating(5000))
                    {
                        
                        retval = GCombatResult.Retry;
                    }
                    //we haven't got some other reason to exit
                    if (retval == GCombatResult.Unknown)
                    {

                        try
                        {
                            retval = KillMonster(Target, IsAmbush);
                        }
                        catch (ThreadInterruptedException E)
                        {
                            throw E;
                        }
                        catch (Exception E)
                        {
                            Context.Log("Exception Trapped in KillMonster");
                            Context.Log("Target: " + Target.ToString());
                            Context.Log("IsAmbush: " + IsAmbush.ToString());
                            Context.Log(E.Message);
                            Context.Log(E.StackTrace);
                            Context.Log("Retrying Combat");
                            retval = GCombatResult.Bugged;
                        }
                    }

                }
            }
            
            //check to see if we have attackers on the pet.
            Me.Refresh(true);
            if (retval == GCombatResult.Success &&  !Me.IsDead && Me.Pet != null && Me.HasLivePet )
            {
                //GUnit PetMob = Me.Pet.Target;
                GUnit PetMob = GObjectList.GetNearestHostile(Me.Pet.Location, Target.GUID, true);
                if (PetMob != null && PetMob.Refresh(true) && (PetMob.IsTargetingMe || PetMob.IsTargetingMyPet))
                {

                    
                    if (!Context.Profile.Blacklist.Contains(PetMob.GUID) && retval != GCombatResult.Bugged)
                    {
                        Context.Log("We have an attacker...");
                        //CommonResult = KillTarget(PetMob, true);
                        if (PetMob.IsTargetingMe || PetMob.IsTargetingMyPet)
                        {
                            PetMob.SetAsTarget(false);

                            Context.SendKey("Common.PetAttack");
                            if (PetMob.DistanceToSelf > Context.RangedDistance)
                                Context.CastSpell("SimpleHunter.AutoShot");
                            retval = GCombatResult.SuccessWithAdd;
                        }
                    }
                    else
                    {
                        Context.Log("Pet has blacklisted attacker...");
                        //Context.SendKey("Common.PetFollow");

                    }
                }
                //combat is done and pet doesn't have an attacker
                else
                {
                    //if we need to feed our pet...
                    //throw a wait for combat to end in
                    //and then try to feed him
                    if (retval == GCombatResult.Success && Me.Pet.Happiness < fFeedPetThresh)
                    {
                        int retry = 10;

                        while (retry-- > 0 && Me.IsInCombat)
                            Thread.Sleep(150);

                        ConsiderFeedPet(fFeedPetThresh);
                    }
                }

                //if (retval == GCombatResult.Success)
                //    Thread.Sleep(1000);
            }


            if (retval == GCombatResult.Success && Me.Target != null && !Me.Target.IsTargetingMe && !Me.Target.IsTargetingMyPet)
            {
                Context.Log("Targetting Error.  Clearing");
                Context.ClearTarget();
            }
            return retval;
        }
        #endregion

        #region SimpleHunter Helper Methods

        private GLocation SubtractLocation(GLocation P1, GLocation P2)
        {
            return new GLocation(P1.X - P2.X, P1.Y - P2.Y, P1.Z - P2.Z);
        }
        
        private double DotLocation(GLocation P1, GLocation P2)
        {
            return (P1.X * P2.X + P1.Y * P2.Y + P1.Z * P2.Z);
        }

        private GLocation AddLocation(GLocation P1, GLocation P2)
        {
            return new GLocation(P1.X + P2.X, P1.Y + P2.Y, P1.Z + P2.Z);
        }

        private GLocation MultLocation(GLocation P1, float weight)
        {
            return new GLocation(P1.X * weight, P1.Y * weight, P1.Z * weight);
        }
        private double Normal(GLocation P1)
        {
            return Math.Sqrt(DotLocation(P1, P1));
        }


        private double DistancePointToLine(GLocation P, GLocation Start, GLocation End)
        {

            GLocation v = SubtractLocation(End, Start);
            GLocation w = SubtractLocation(P, Start);

            double c1 = DotLocation(w, v);
            double c2 = DotLocation(v, v);

            double b = c1 / c2;

            GLocation PB = AddLocation(Start, MultLocation(v, (float)b));
            return Normal(SubtractLocation(P, PB));
        }


        /*
        private BuffID CheckAspectBuff(string KeyName)
        {
            Context.Debug(KeyName);
            BuffID retval = BuffID.None;
            try
            {
                if (Interface.IsKeyPopulated(KeyName))
                {
                    Thread.Sleep(100);   
                    int retry = 2;
                       while (retry-- > 0 && retval == BuffID.None)
                       {
                           if (CheckAndFireKey(KeyName))
                           {
                               int retry2 = 10;
                               while (retry2-- > 0 && retval == BuffID.None)
                               {
                                   Thread.Sleep(200);
                                   Me.Refresh(true);
                                   //key fired
                                   if (Me.HasBuff((int)BuffID.AspectHawk))
                                   {
                                       Context.Debug("AspectHawk");
                                       retval = BuffID.AspectHawk;
                                   }
                                   else if (Me.HasBuff((int)BuffID.AspectMonkey))
                                   {
                                       Context.Debug("AspectMonkey");
                                       retval = BuffID.AspectMonkey;
                                   }
                                   else if (Me.HasBuff((int)BuffID.AspectCheetah))
                                   {
                                       Context.Debug("AspectCheetah");
                                       retval = BuffID.AspectCheetah;
                                   }
                                   else if (Me.HasBuff((int)BuffID.AspectViper))
                                   {
                                       Context.Debug("AspectViper");
                                       retval = BuffID.AspectViper;
                                   }
                               }
                           }
                           else
                           {
                               Thread.Sleep(2000);

                           }
                       }
                }
            }
            catch
            {
                //key doesn't exist.?
            }

            return retval;
        }
        */
        public bool CheckAndFireKey(string key)
        {
            bool retval = false;
            if (Interface.IsKeyPopulated(key) && Interface.IsKeyReady(key) && Interface.IsKeyEnabled(key))
            {
                    Context.CastSpell(key);
                    retval = true;
            }
            return retval;
        }

        private float GetAngle(GLocation From, GLocation To)
        {
            return (float)Math.Atan2(To.Y - From.Y, To.X - From.X);
        }

        private bool HasBuffByName(string BuffName)
        {
            bool retval = false;
            GBuff[] currBuffs = Me.GetBuffSnapshot(true);
            foreach (GBuff currBuff in currBuffs)
            {
                //Context.Log(currBuff.SpellName.ToString() + ": " + currBuff.SpellID.ToString());
                if (currBuff.SpellName.ToString() == BuffName)
                {
                    retval = true;
                    continue;
                }
            }
            return retval;
        }

        private GLocation GetPreferredLoc()
        {
            if (Context.Party.Mode == GPartyMode.Follower)
            {
                return CombatStartLocation;
            }
            else
            {
                return Context.Profile.CurrentWaypoint;
            }
        }

        private bool FeedPet()
        {
            //just press the button and ... rock and roll.
            Context.PressKey("SimpleHunter.FeedPet");
            Thread.Sleep(200);
            Context.ReleaseKey("SimpleHunter.FeedPet");

            //if (CheckAndFireKey("SimpleHunter.FeedPet"))
            //    {
                    //Interface.SendString("/cast Feed Pet\r");
                    //Interface.SendString("/use " + Food + "\r");


                    int retry = 10;
                    while (retry-- > 0 && !PetEating)
                        Thread.Sleep(200);
             //   }

            return PetEating;
        }

        
        /*
        private string FindFood()
        {
            Context.Log("Finding Food...");
            string retval = "";

            if (Me.HasLivePet && Me.Pet != null && !Me.IsInCombat)
            {
                long[] Items = Me.BagContents;

                foreach (long ItemId in Items)
                {

                    if (ItemId != 0)
                    {
                        GItem CurItem = (GItem)GObjectList.FindObject(ItemId);

                        Context.Log("Checking Food: " + CurItem.Name);
                        if (FeedPet(CurItem.Name))
                        {
                            retval = CurItem.Name;
                            break;
                        }


                    }
                }



            }

            return retval;
        }
        */
        private bool ConsiderPetHeading(double DeviationThresh, GUnit Target)
        {
            bool retval = false;
            if (Me.Pet.Location.GetDistanceTo(Target.Location) > 5 && Me.Pet.Location.GetDistanceTo(Me.Location) > 5)
            {
                double angle = Me.Pet.Heading - GetAngle(Me.Pet.Location, Target.Location);
                while (angle > Math.PI)
                    angle -= (Math.PI * 2);

                while (angle < -Math.PI)
                    angle += (Math.PI * 2);



                /*
                 double dHeading = Me.GetHeadingTo(Monster) - Me.Heading;
                        while (dHeading > Math.PI)
                            dHeading -= (Math.PI * 2);

                        while (dHeading < -Math.PI)
                            dHeading += (Math.PI * 2);

                        if (dHeading >= (Math.PI / 3) || dHeading <= -(Math.PI/3))
                        {
                            //Context.Log(Me.GetHeadingTo(Monster).ToString("0.00"));
                            //Context.Log(Me.Heading.ToString("0.00"));
                            Context.Log("Face");
                            Monster.Face(Math.PI / 6);
                            LoopConsumed = true;
                        }
                */

                if (Math.Abs(angle) > DeviationThresh)
                {
                    Context.Debug("angle:" + angle.ToString("0.00"));
                    retval = true;
                }

            }
            return retval;
        }
        
        private bool ConsiderPetDeviation(double DeviationThresh, GUnit Target)
        {
            bool retval = false;
            //watch our pets patch and make it be the correct one.
            if (Target.Location.GetDistanceTo(Me.Pet.Location) > DeviationThresh)
            {
                double Dist = DistancePointToLine(Me.Pet.Location, Me.Location, Target.Location);
                Context.Debug("Pet Deviation: " + Dist.ToString("0.00"));
                if (Dist > DeviationThresh)
                {
                    retval = true;
                }
            }
            return retval;
        }
        private bool ConsiderFeedPet(double HappyThresh)
        {
            bool retval = false;
            if (Me.Pet != null && Me.HasLivePet && Me.Pet.Happiness < HappyThresh && !PetEating)
            {
                int retry = 10;
                while (retry-- > 0 && Me.Pet.DistanceToSelf > 5.0)
                    Thread.Sleep(200);


                if (!FeedPet())
                {
                    Context.Log("Feed Pet failure.  Out of food?  Macro broken?");

                    //PetFoodName = FindFood();

                }

            }
            return retval;

        }

        private bool ConsiderPetAttack(GUnit m)
        {
            bool retval = false;
            if (Me.HasLivePet && Me.Pet != null)
            {
                if (m.DistanceToSelf < Context.MeleeDistance)
                    retval = true;


                if (!retval)
                {

                    GMonster PossibleAdd = GObjectList.GetNearestHostile(m.Location, m.GUID, false);
                    if (PossibleAdd == null || PossibleAdd.Location.GetDistanceTo(m.Location) > fPetAvoidClearance)
                    {
                        Context.CastSpell("Common.PetAttack", false, true);
                        Thread.Sleep(100);

                        retval = true;
                    }
                }
            }

            return retval;
        }
        
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        //  Class:  SimpleHunter
        //  Method: ConsiderPetEating
        //  Purpose:    Checks to see if we have recently fed our pet and
        //      delays combat until the specified mS threshold is crossed.
        //  Returns:    true if the pet was eating and combat was delayed
        //              false if the pet was not fed recently
        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        private bool ConsiderPetEating(int TickThreshold)
        {
            if (PetEating)
            {

                //how long are we willing to wait.

                GSpellTimer DelayTimer = new GSpellTimer(TickThreshold);

                while (PetEating && !DelayTimer.IsReady && !Me.IsInCombat)
                    Thread.Sleep(100);

                //we waited long enough...  force pet ready
                if (PetEating)
                    PetEating = false;

                return !Me.IsInCombat;
            }
            else
            {
                return false;
            }

        }

        private bool ConsiderDanger(GUnit Target, GLocation CloseTo, out bool MoveErr)
        {
            bool retval = false;
            MoveErr = false;
            GUnit[] MobList = GObjectList.GetMonsters();

            //radar tick happened... we should check for some danger
            float CurrentDanger = Radar.GetDangerAt(Me.Location, MobList, Target.GUID, Me);

            if (CurrentDanger > 0f)
            {
                Context.Debug("InDanger: " + CurrentDanger.ToString("0.0") + " Count: " + MobList.Length.ToString());
            }
            if (CurrentDanger > DangerThresh)
            {

                //consider moving but we should stay close enough to our pet
                //for combat.
                GLocation SafePlace = Radar.FindSafePlace(Target.Location, CloseTo, (float)Context.RangedDistance + MovementSens, (float)PullDistance - MovementSens, MobList, Target.GUID, Me);
                if (SafePlace.Bearing > MovementSens)
                {
                    if (!Movement.MoveToLocation(SafePlace, MovementSens, false))
                    {
                        //our move failed.... something hazardous to movement here
                        Radar.AddKeepAway(Me.Location);
                        MoveErr = true;
                    }
                    retval = true;
                }
            }
            return retval;
        }

        #endregion


    }


}