• You've discovered RedGuides 📕 an EverQuest multi-boxing community 🛡️🧙🗡️. We want you to play several EQ characters at once, come join us and say hello! 👋
  • IS THIS SITE UGLY? Change the look. To dismiss this notice, click the X --->
Resource icon

Plugin - MQ2Twist (1 Viewer)

lonsosh

New member
Joined
Mar 22, 2005
RedCents
30¢
I got newest compile from Soul and loaded mq2twist with sucess but now when I < /twist 1 2 3 > it might sing 1st song then I get mq2 window message that the other songs weren't there to cast??? and since that initial time I have been CTD everytime I think it's going to work. :confused:
I read on another site <MQ2site> that alot of people are CTD with twist commands...
I hope it gets fixed one way or another. :D
 
Starting a post of what plugins/Macro's I have fixed to meet new Struct issues. Feel free to add on as you find fixes as well.

Mq2Twist:

Rich (BB code):
// MQ2Twist.cpp - Bard song twisting plugin for MacroQuest2
//
//    koad 03-24-04 Original plugin (http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=5962&start=2)
//    CyberTech 03-31-04 w/ code/ideas from Falco72 & Space-boy
//    Cr4zyb4rd 08-19-04 taking over janitorial duties
//    Pheph 08-24-04 cleaning up use of MQ2Data

/*
   MQ2Twist Version 1.3

      Usage:   
         /twist # # # # # - Twists in the order given.
            Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.
            These may be mixed in any order, and repeats are allowable. Up to 10 may be
            specified.
            If a song is specified with a duration longer than standard (ie, selos)
            that song will be twisted based on it's duration.  For example, riz+mana+selos
            would be a 2 song twist with selos pulsed every 2.5 min.
         /twist once # # # # # - Twists in the order given, then reverts to original twist
         /twist hold <gem #> - Pause twisting and sing only the specified song
            /sing <gem#> - alias for /twist hold
         /twist stop/end/off - stop twisting, does not clear the twist queue
            /stoptwist - alias for above
         /twist or /twist start - Resume the twist after using /twist hold or /twist stop
       /twist reset - Reset timers for item clicks and long duration songs
         /twist delay # - 10ths of a second, minimum of 30, default 33
         /twist adjust # - in ticks, how early to recast long duration songs
         /twist reload - reload the INI file to update item clicks
         /twist slots - List the slots/items defined in the INI and their #'s
         /twist quiet - Toggles songs listing and start/stop messages for one-shot twists

      ----------------------------
      Item Click Method:
         MQ2Twist uses /itemnotify slotname rightmouseup to perform item clicks.

         The INI file allows you to specify items by name (with name=itemname), or by
       inventory slot (with slot=slotname).  If both a name and slot are defined for an
       item, the plugin will attempt to swap the item into that slot (via the /exchange
       command) and replace the original item when casting is complete.
      
       The example INI file below contains examples of the types of usage.

      ----------------------------
      Examples:
         /twist 1
            Sing gem 1 forever
         /twist 1 2 3
            Twist gems 1,2, and 3 forever
         /twist 1 2 3 10
            Twist gems 1,2,3, and clicky 10, forever
         /twist hold 4 or /sing 4
            Sing gem 4 until another singing-related /twist command is given

      ----------------------------
      MQ2Data Variables:
         bool   Twist         Currently Twisting: true/false, if NULL plugin is not loaded
         Members:
            bool     Twisting  Currently twisting: true/false.
            int      Current   Returns the curent gem number being sung, -1 for item, or 0 if not twisting
            int      Next      Returns the next gem number to be sung, -1 for item, or 0 if not twistsing
            string   List      Returns the twist sequence in a format suitable for /twist

      ----------------------------
     
     The ini file has the format:
         [MQ2Twist]
         Delay=32       Delay between twists. Lag & System dependant.
       Adjust=1       This defines  how many ticks before the 'normal' recast time to cast a long song.
                        Long songs are defined as songs greater than 3 ticks in length.  If set to 1 tick,
                        and a song lasts 10 ticks, the song will be recast at the 8 tick mark, instead of
                        at the 9 tick mark as it normally would.

         [Click_10] thru [Click_19]
         CastTime=30              Casting Time, -1 to use the normal song delay
         ReCastTime=0             How often to recast, 0 to twist normally.
         Name="Fife of Battle"    Item name for /itemnotify
       Slot=neck                Slot name for /itemnotify

         Delay, CastTime and ReCastTime are specified in 10ths of a
         second, so 10 = 1 second, and so on.

         INI File Example:
            [MQ2Twist]
            Delay=31
         Quiet=0

            ;Shadowsong cloak
            [Click_10]
            CastTime=30
            ReCastTime=350
            Name=Shadowsong Cloak
            Slot=DISABLED

            ;girdle of living thorns (current belt will be swapped out)
            [Click_11]
            CastTime=0
            ReCastTime=11600
            Name=Girdle of Living Thorns
            Slot=waist

            ;nature's melody
            [Click_12]
            CastTime=-1
            ReCastTime=135
            Name=DISABLED
            Slot=mainhand

            ;lute of the flowing waters
            [Click_13]
            CastTime=0
            ReCastTime=0
            Name=Lute of the Flowing Waters
            Slot=DISABLED

            [Click_14] ... [Click_19]
            CastTime=33
            ReCastTime=0
            Name=DISABLED
            Slot=DISABLED

      ----------------------------

Changes:
   10-05-04
      Support "swap in and click" items

    09-15-04
      Support extra spell slot from Omens of War AA

    09-01-04
      Command: /twist quiet to toggle some of the spam on/off
      Various code fixes/speedups

    08-29-04
      Moved LONGSONG_ADJUST into INI file and made /twist adjust command to set it on
      the fly

   08-25-04
      Changed output for /twist once to be slightly less misleading
      Reset click/song timers every time they're called with /twist hold or /twist once;
      if the user's specifying that song, they obviously want to cast it anyway.
      Removed the variable MissedNote as close inspection revealed the only place it was
      checked for was the line that set it. /boggle
      Minor code tweaks, cleanups, formatting changes, etc

   08-24-04 (Pheph)
      Modified it to use only one TLO, as I found it somewhat messy having 4 different ones.
      All the functionality of the old TLO's are now members of ${Twist}
      ${Twising} is now ${Twist.Twisting}, or just ${Twist}
      ${TwistCurrent} is now ${Twist.Current}
      ${TwistNext} is now ${Twist.Next}
      ${TwistList} is now ${Twist.List}

   08-23-04
      Reset_ItemClick_Timers was being called far too often.  Now the only time we reset
     is if a new list of songs are specified.  "/twist ${TwistList}" is a useful alias
     if you for some reason want the old behavior.
      Sing or /twist hold now resets the cast/item timer for that song only, rather than
     the entire list.
     Command: /twist reset calls Reset_ItemClick_Timers without interfering with the
     state of the current twists.
    
   08-22-04
      Command: /twist once [songlist] will cycle through the songs entered once, then
     revert to the old twist, starting with the song that was interrupted.
     Removed command "/twist on", it was making the string compare for "once" annoying,
     and I didn't think it was worth the effort for a redundant command.
      /twist delay with no argument now returns the delay without resetting it.  Values
     less than 30 now give a warning...maybe they're not bards or have some other
     reason for using a low value.

   08-19-04
      Minor revamp of item notification.  Removed ITEMNOTIFY define and kludged in some
      changes from Virtuoso65 to get casting by item name working.  /cast is no longer
      used.
      Added INI file support for above change.  File now uses distinct entries for item
      names and slots.  *Quotes not required for multi-word item names in INI.*
      Fixed the MQ2Data value TwistCurrent to display the current song as-advertised, and
      added a new value TwistNext with the old behavior of showing the next song in the
      queue. (Useful in scripting)
      Removed a few DebugSpews that were mega-spamming my debugger output.
      CastTime of -1 in the INI file now causes the default delay to be used.
   
   06-01-04
      Added LONGSONG_ADJUST (default to 1 tick) to help with the timing of recasting long
      songs, such as selo's.
      Twisting is now paused when you sit (this would include camping).  This fixes
      problems reported by Chyld989 (twisting across chars) and Kiniktoo (new autostand on
      cast 'feature' in EQ makes twisting funky)

   05-19-04
      Added workaround for incorrect duration assumption for durationtype=5 songs, such as
      Cassindra's Chant of Clarity or Cassindra's Chorus of Clarity.
      Added check of char state before casting a song. Actually added for 1.05
         Checked states and resulting action are:
            Feigned, or Ducking = /stand
            Stunned = Delay
            Dead - Stop twisting.
         If you're a monk using this to click your epic, you'll want to disable the autostand on feign code =)


   05-05-05
      Fixed CTD on song unmem or death, while twisting.  Oops
      Removed circle functionality.  It's better suited for a plugin like the MQ2MoveUtils
         plugin by tonio at http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=6973

   05-01-04
      Fixed problem with using pchar before state->ingame causing CTD on eq load (thanks MTBR)
      Fixed vc6 compile error w/ reset_itemclick_timers
      Replaced various incantations of pChar and pSpawn with GetCharInfo()
      Fixed /circle behavior w/ unspecified y/x
      Fixed /circle on when already circling and you want to update loc
      Added output of parsed circle parameters on start.

   04-25-04
      Converted to MQ2Data
         Top Level Objects:
            bool   Twisting      (if NULL plugin is not loaded)
            int      TwistCurrent
            string   TwistList
      Removed $Param synatax for above
      Added check to make sure item twists specified are defined
      Fixed error with twist parameter processing
      Changed twist startup output to be more verbose
      Command: /twist on added as alias for /twist start
      INI File is now named per-character (MQ2Twist_Charname.ini)
         * Be sure to rename existing ini files
      Modified twist routine to take into account songs with
         non-0 recast times or longer than 3 tick durations,
         and only re-cast them after the appropriate delay.
         This is for songs like Selos 2.5 min duration, etc.
         * Note that this makes no attempt to recover if the song
         effect is dispelled, your macro will need to take care
         of that.
      Added ability to compile-time change the method used for
         clicking items.

   04-13-04
      Changed /circle command to allow calling w/o specifying loc
      Corrected a problem with multiple consecutive missed notes
      Added handling of attempting to sing while stunned
      Command: /twist slots, to list the slot to # associations
      Command: /twist reload, to reload the ini file on the fly
      Command: /twist end, /twist off as aliases for /twist stop
      Command: /sing #, as an alias for /twist hold #

      Added support for item clickies.  Clickies are specified
      as "gem" 10-19. For example, /twist 1 2 10 12

      Added INI file support for storing item clicky info
      and default twist delay.

   04-11-04
      Integrated the /circle code from Easar, runs in a circle.  type
      /circle for help.
*/

#include "../MQ2Plugin.h"

PreSetup("MQ2Twist");

typedef struct _ITEMCLICK {
   int cast_time;
   int recast;
   long castdue;
   int disabled;
   int nousename;
   CHAR slot[MAX_STRING];
   CHAR name[MAX_STRING];
} ITEMCLICK;

int MQ2TwistEnabled = 0;
const int MAX_SONG=10;
int LONGSONG_ADJUST=1; // In TICKS, not seconds.  Used for long songs (greater than 3 ticks in duration). See docs.
int CAST_TIME=33;
int NumSongs=0;
int AltNumSongs=0;
int Song[MAX_SONG*2];
int AltSong[MAX_SONG*2];
long SongNextCast[MAX_SONG*2];
ITEMCLICK ItemClick[MAX_SONG];
int CurrSong=0;
int AltCurrSong=0;
int PrevSong=0;
int HoldSong=0;
long CastDue=0;
bool bTwist=false;
bool altTwist=false;
bool quiet;
CHAR SwappedOutItem[MAX_STRING];
CHAR SwappedOutSlot[MAX_STRING];

long GetTime();
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine);
BOOL dataTwist(PCHAR szIndex, MQ2TYPEVAR &Ret);
CHAR MQ2TwistTypeTemp[MAX_STRING]={0};

//get current timestamp in tenths of a second
long GetTime()
{
   SYSTEMTIME st;
   ::GetSystemTime(&st);
   long lCurrent=0;
   lCurrent  = st.wDay    * 24 * 60 * 60 * 10;
   lCurrent += st.wHour        * 60 * 60 * 10;
   lCurrent += st.wMinute           * 60 * 10;
   lCurrent += st.wSecond                * 10;
   lCurrent += (long)(st.wMilliseconds/100);
   return (lCurrent);
}

VOID MQ2TwistDoCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   HideDoCommand(pChar, szLine, FromPlugin);
}

VOID DoSwapOut()
{
   CHAR szTemp[MAX_STRING];
   if (SwappedOutItem[0]) {
      sprintf(szTemp,"/exchange \"%s\" %s",SwappedOutItem,SwappedOutSlot);
      MQ2TwistDoCommand(NULL, szTemp);
      SwappedOutItem[0]=0;
   }
}

VOID DoSwapIn(int Index)
{
   CHAR szTemp[MAX_STRING];
   if (strnicmp(ItemClick[Index].slot,"DISABLED",8)) {
      sprintf(szTemp,"${InvSlot[%s].Item.Name}",ItemClick[Index].slot);
      ParseMacroData(szTemp);
      strcpy(SwappedOutItem,szTemp);
      strcpy(SwappedOutSlot,ItemClick[Index].slot);
      sprintf(szTemp,"/exchange \"%s\" %s",ItemClick[Index].name,ItemClick[Index].slot);
      MQ2TwistDoCommand(NULL, szTemp);
   }
}

VOID Reset_ItemClick_Timers()
{
   int i;
   for (i=0;i<10;i++) {
      ItemClick.castdue = 0;
   }
   for (i=0;i<MAX_SONG*2;i++) {
      SongNextCast = 0;
   }
}


VOID Update_INIFileName() {
   if (GetCharInfo()) {
      sprintf(INIFileName,"%s\\MQ2Twist_%s.ini",gszINIPath,GetCharInfo()->Name);
   } else {
      sprintf(INIFileName,"%s\\MQ2Twist.ini",gszINIPath);
   }
}

VOID Load_MQ2Twist_INI()
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szSection[MAX_STRING]={0};

   Update_INIFileName();

   CAST_TIME = GetPrivateProfileInt("MQ2Twist","Delay",33,INIFileName);
   sprintf(szTemp, "%d", CAST_TIME);
   WritePrivateProfileString("MQ2Twist","Delay",szTemp,INIFileName);
   quiet = GetPrivateProfileInt("MQ2Twist","Quiet",0,INIFileName)? 1 : 0;
   sprintf(szTemp, "%d", quiet);
   WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
   
   LONGSONG_ADJUST = GetPrivateProfileInt("MQ2Twist","Adjust",1,INIFileName);
   sprintf(szTemp, "%d", LONGSONG_ADJUST);
   WritePrivateProfileString("MQ2Twist","Adjust",szTemp,INIFileName);
   
   for (int i=0;i<10;i++) {
      sprintf(szSection, "Click_%d", i+10);
      ItemClick.cast_time = GetPrivateProfileInt(szSection,"CastTime",0,INIFileName);
      ItemClick.recast = GetPrivateProfileInt(szSection,"ReCastTime",0,INIFileName);

      GetPrivateProfileString(szSection,"Name","DISABLED",ItemClick.name,MAX_STRING,INIFileName);
      GetPrivateProfileString(szSection,"Slot","DISABLED",ItemClick.slot,MAX_STRING,INIFileName);
      if(!strnicmp("DISABLED", ItemClick.name, 8)) {
         if (!strnicmp("DISABLED", ItemClick.slot, 8)) {
            ItemClick.disabled = true;
            DebugSpew("MQ2Twist: Slot %d disabled",i+1);
         } else {
            ItemClick.nousename = true;
            ItemClick.disabled = false;
         }
      } else ItemClick.disabled = false;
      // Write the values above back to disk, mostly to initialize it for easy editing.
      sprintf(szTemp, "%d", ItemClick.cast_time);
      WritePrivateProfileString(szSection,"CastTime",szTemp,INIFileName);
      // If the CastTime is set to -1 in the INI file, use the default.
      ItemClick.cast_time = ItemClick.cast_time==-1 ? CAST_TIME : ItemClick.cast_time;

      sprintf(szTemp, "%d", ItemClick.recast);
      WritePrivateProfileString(szSection,"ReCastTime",szTemp,INIFileName);
      WritePrivateProfileString(szSection,"Name",ItemClick.name,INIFileName);
      WritePrivateProfileString(szSection,"Slot",ItemClick.slot,INIFileName);
      DebugSpewAlways("Initializing MQ2Twist: Processed %s", szSection);
   }
}

VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   int i;

   GetArg(szTemp,szLine,1);
   i=atoi(szTemp);

   if (i>=1 && i<=19) { // valid range?
      HoldSong = i;
      bTwist=true;
      CastDue = -1;
      sprintf(szMsg, "MQ2Twist::Holding Twist and casting gem %d", HoldSong);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      MQ2TwistDoCommand(pChar,"/stopsong");
      if (i>10) { //item?
         ItemClick[i-10].castdue = 0;
      } else SongNextCast = 0; //nope, song
   } else WriteChatColor("MQ2Twist::Invalid gem specified, ignoring",USERCOLOR_DEFAULT);
}

VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   bTwist=false;
   HoldSong=0;
   MQ2TwistDoCommand(pChar,"/stopsong");
   WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
}

VOID PrepNextSong() {
   if (CurrSong>NumSongs) {
      if (altTwist) {
         NumSongs=AltNumSongs;
         CurrSong=PrevSong=AltCurrSong;
         for (int i=0; i<NumSongs; i++) Song=AltSong;
         altTwist=false;
         if (!quiet) WriteChatColor("MQ2Twist::One-shot twist ended, normal twist will resume next pulse",USERCOLOR_DEFAULT);
      } else CurrSong=1;
   }
}

VOID DisplayTwistHelp() {
   WriteChatColor("MQ2Twist - Twist song or songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage:   /twist <gem#> - Twists in the order given.",USERCOLOR_DEFAULT);
   WriteChatColor("  Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.",USERCOLOR_DEFAULT);
   WriteChatColor("  These may be mixed in any order, and repeats are allowable.",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist hold <gem #> - Pause twisting and sing only the specified song",USERCOLOR_DEFAULT);
   WriteChatColor("  /sing <gem#> - alias for /twist hold",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist once <gem#> Twists once in the order given, then reverts to original twist",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist or /twist start - Resume the twist after using /twist hold or /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reset - Reset timers for item clicks and long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist delay # - 10ths of a second, minimum of 30, default 33",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist adjust # - in ticks, how early to recast long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist stop/end/off - stop twisting, does not clear the twist queue",USERCOLOR_DEFAULT);
   WriteChatColor("  /stoptwist - alias for /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reload - reload the INI file to update item clicks",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist slots - List the slots defined in the INI and their #'s",USERCOLOR_DEFAULT);
}

// ***************************************************************************
// Function:      TwistCommand
// Description:   Our /twist command. sing for me!
// ***************************************************************************
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   CHAR szChat[MAX_STRING]={0};
   PSPELL pSpell;
   int i;

   GetArg(szTemp,szLine,1);

   if (NumSongs && (!strlen(szTemp) || !strnicmp(szTemp,"start", 5))) {
      WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
      DoSwapOut();
      bTwist=true;
      HoldSong=0;
      CastDue = -1;
      return;
   }

   if (!strnicmp(szTemp,"stop", 4) || !strnicmp(szTemp,"end", 3) || !strnicmp(szTemp,"off", 3)) {
      DoSwapOut();
      StopTwistCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"slots", 5)) {
      WriteChatColor("MQ2Twist 'Song' Numbers for right click effects:",USERCOLOR_DEFAULT);
      for (i=0;i<10;i++) {
         if (ItemClick.disabled) break;
         if (ItemClick.nousename) {
            sprintf(szMsg, "  %d = %s (slot)", i+10, ItemClick.slot);
         } else {
            sprintf(szMsg, "  %d = %s (name) %s (slot)", i+10, ItemClick.name, ItemClick.slot);
       }
         WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      }
      WriteChatColor("---",USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"reload", 6)) {
      WriteChatColor("MQ2Twist::Re-Loading INI Values",USERCOLOR_DEFAULT);
      Load_MQ2Twist_INI();
      return;
   }

   if (!strnicmp(szTemp,"delay", 5)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         if (i<=30) {
            WriteChatColor("MQ2Twist::WARNING delay specified is less than standard song cast time",CONCOLOR_RED);
         }
         CAST_TIME=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Delay",itoa(CAST_TIME, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Set delay to %d, INI updated", CAST_TIME);
      } else sprintf(szMsg, "MQ2Twist::Delay %d", CAST_TIME);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"quiet", 5)) {
      quiet=!quiet;
      sprintf(szTemp,"%d",quiet);
      WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
      sprintf(szMsg,"MQ2Twist::Now being %s",quiet ? "quiet" : "noisy");
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"adjust", 6)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         LONGSONG_ADJUST=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Adjust",itoa(LONGSONG_ADJUST, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Long song adjustment set to %d, INI updated", LONGSONG_ADJUST);
      } else sprintf(szMsg, "MQ2Twist::Long song adjustment: %d", LONGSONG_ADJUST);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"hold", 4)) {
      GetArg(szTemp,szLine,2);
      SingCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"reset", 5)) {
      Reset_ItemClick_Timers();
      WriteChatColor("MQ2Twist::Timers reset",CONCOLOR_YELLOW);
      return;
   }

   // check help arg, or display if we have no songs defined and /twist was used
   if (!strlen(szTemp) || !strnicmp(szTemp,"help", 4)) {
      DisplayTwistHelp();
      return;
   }

   // if we are "one-shot twisting", save the current song array and current song
   if (!strnicmp(szTemp,"once", 4)) {
      WriteChatColor("MQ2Twist one-shot twisting:",CONCOLOR_YELLOW);
      if (altTwist) {
         CurrSong=NumSongs+1;
         PrepNextSong(); // If CurrSong > NumSongs relaod the song list
      }
      if (NumSongs) {
         AltNumSongs=NumSongs;
         AltCurrSong=CurrSong;
         for (i=0; i<NumSongs; i++) AltSong=Song;
      }
      altTwist=true;
   } else altTwist=false;

   DoSwapOut();
   DebugSpew("MQ2Twist::TwistCommand Parsing twist order");
   NumSongs=0;
   HoldSong=0;
   if (!altTwist) {
      if (!quiet) {
         WriteChatColor("MQ2Twist Twisting:",CONCOLOR_YELLOW);
      } else WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
   }
   for (i=0 + altTwist ? 1 : 0; i<20; i++)
   {
      GetArg(szTemp,szLine,i+1);
      if (!strlen(szTemp))  break;

      Song[NumSongs]=atoi(szTemp);
      if (Song[NumSongs]>=1 && Song[NumSongs]<=19) {
         if ((Song[NumSongs]>9) && ItemClick[Song[NumSongs]-10].disabled) {
            sprintf(szChat, " Undefined item specified (%s) - ignoring (see INI file)", szTemp);
            WriteChatColor(szChat,CONCOLOR_RED);
         } else {
            sprintf(szMsg, " %s - ", szTemp);

            if (Song[NumSongs]<=9) {
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[NumSongs]-1]);
               if (altTwist) SongNextCast[NumSongs] = 0;
               if (pSpell) strcat(szMsg, pSpell->Name);
            } else {
               if (ItemClick[Song[NumSongs]-10].nousename) {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].slot);
               } else {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].name);
               }
               if (altTwist) ItemClick[NumSongs].castdue = 0;
            }
            if (!quiet) WriteChatColor(szMsg,COLOR_LIGHTGREY);
            NumSongs++;
         }
      } else {
         sprintf(szChat, " Invalid gem specified (%s) - ignoring", szTemp);
         WriteChatColor(szChat,CONCOLOR_RED);
      }
   }

   sprintf(szTemp, "Twisting %d song%s", NumSongs, NumSongs>1 ? "s" : "");
   if (!quiet) WriteChatColor(szTemp,CONCOLOR_YELLOW);

   if (NumSongs>0) bTwist=true;
   CurrSong = 1;
   PrevSong = 1;
   CastDue = -1;
   MQ2TwistDoCommand(pChar,"/stopsong");
   if (!altTwist) Reset_ItemClick_Timers();
}

/*
Checks to see if character is in a fit state to cast next song/item

Note 1: Do not try to correct SIT state, or you will have to stop the
twist before re-memming songs

Note 2: Since the auto-stand-on-cast bullcrap added to EQ a few patches ago,
chars would stand up every time it tried to twist a song.  So now
we stop twisting at sit.
*/
BOOL CheckCharState() {
   if (!bTwist) return FALSE;

   if (GetCharInfo()) {
      if (GetCharInfo()->Stunned==1) return FALSE;
      switch (GetCharInfo()->standstate) {
       case STANDSTATE_SIT:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       case STANDSTATE_FEIGN:
          MQ2TwistDoCommand(NULL,"/stand");
          return FALSE;
          break;
       case STANDSTATE_DEAD:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       default:
          break;
      }
   }

   if (pCastingWnd) {
      PCSIDLWND pCastingWindow = (PCSIDLWND)pCastingWnd;
      if (pCastingWindow->Show == 1) return FALSE;
      // Don't try to twist if the casting window is up, it implies the previous song
      // is still casting, or the user is manually casting a song between our twists
   }
   return TRUE;
}
class MQ2TwistType *pTwistType=0;

class MQ2TwistType : public MQ2Type
{
public:
   enum TwistMembers
   {
      Twisting=1,
      Next=2,
      Current=3,
      List=4,
   };

   MQ2TwistType():MQ2Type("twist")
   {
      TypeMember(Twisting);
      TypeMember(Next);
      TypeMember(Current);
      TypeMember(List);
   }
   ~MQ2TwistType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2TwistType::FindMember(Member);
      if (!pMember)
         return false;
      switch((TwistMembers)pMember->ID)
      {
      case Twisting:
         /* Returns: bool
            0 - Not Twisting
            1 - Twisting
         */
         Dest.Int=bTwist;
         Dest.Type=pBoolType;
         return true;
      case Next:
         /* Returns: int
            0 - Not Twisting
            -1 - Casting Item
            1-9 - Current Gem
         */
         Dest.Int=HoldSong ? HoldSong : Song[CurrSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case Current:
         Dest.Int=HoldSong ? HoldSong : Song[PrevSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case List:
         /* Returns: string
            Space separated list of gem and item #'s being twisted, in order
         */
         int a;
         CHAR szTemp[MAX_STRING] = {0};

         MQ2TwistTypeTemp[0] = 0;
         for (a=0; a<NumSongs; a++) {
            sprintf(szTemp, "%d ", Song[a]);
            strcat(MQ2TwistTypeTemp, szTemp);
         }

         Dest.Ptr=&MQ2TwistTypeTemp[0];
         Dest.Type=pStringType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if (bTwist)
         strcpy(Destination,"TRUE");
      else
         strcpy(Destination,"FALSE");
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataTwist(PCHAR szName, MQ2TYPEVAR &Dest)
{
   Dest.DWord=1;
   Dest.Type=pTwistType;
   return true;
}


// ******************************
// **** MQ2 API Calls Follow ****
// ******************************

PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2Twist");

   AddCommand("/twist",TwistCommand,0,1,1);
   AddCommand("/sing",SingCommand,0,1,1);
   AddCommand("/stoptwist",StopTwistCommand,0,0,1);;
    AddMQ2Data("Twist",dataTwist);

    pTwistType = new MQ2TwistType;

}

PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("MQ2Twist::Shutting down");

   RemoveCommand("/twist");
   RemoveCommand("/sing");
   RemoveCommand("/stoptwist");
    RemoveMQ2Data("Twist");

    delete pTwistType;
}

PLUGIN_API VOID OnPulse(VOID)
{
   CHAR szTemp[MAX_STRING] = {0};
   PSPELL pSpell;
   int a,b;

   if (!MQ2TwistEnabled || !CheckCharState()) return;

   if ((HoldSong>0) || ((NumSongs==1) && !altTwist)) {
      // DebugSpew("MQ2Twist::Pulse - Single Song");
      if ( CastDue<0 || ( ((CastDue-GetTime()) <= 0 ) && (GetCharInfo()->pSpawn->pActorInfo->CastingSpellID == -1) ) ) {
         int SongTodo = HoldSong ? HoldSong : Song[0];
         if (SongTodo <= 9) {
            DebugSpew("MQ2Twist::Pulse - Single Song (Casting Gem %d)", SongTodo);
            sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
            MQ2TwistDoCommand(NULL,szTemp);
            CastDue = GetTime()+CAST_TIME;
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = ItemClick[SongTodo-10].castdue;
            }
         }
      }
   } else {
      int SongTodo = Song[CurrSong-1];
      if (NumSongs && ((CastDue-GetTime()) <= 0)) {
         DoSwapOut();
         if (SongTodo <= 9) {
            if (SongNextCast[CurrSong-1]-GetTime() <= 0) {
               DebugSpew("MQ2Twist::OnPulse - Next Song = %s", szTemp);
               sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
               MQ2TwistDoCommand(NULL,szTemp);
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[CurrSong-1]-1]);
               if(!pSpell) {
                  WriteChatColor("Songs not present - suspending twist.  /twist to resume",CONCOLOR_RED);
                  bTwist = FALSE;
                  return;
               }
               a = pSpell->RecastTime/1000 * 60;                     // recasttime in 10's of a second
               b = GetSpellDuration(pSpell,GetCharInfo()->pSpawn) * 60;   // duration in 10's of a second
               if (pSpell->DurationType == 5 && !pSpell->DurationValue1) {
                  b = 18;   //FIXME - Remove once GetSpellDuration handles duration type5
               }
               CastDue = GetTime()+CAST_TIME;
               if (a > 0 || b > 180) { // We only care about songs with > 3 tick durations or non-0 recast times
                  SongNextCast[CurrSong-1] = GetTime() + (a > b ? a : b) - CAST_TIME - (LONGSONG_ADJUST*60);   // Cast next after greater of recasttime or duration, minus LONGSONG_ADJUST ticks.
               } else {
                  SongNextCast[CurrSong-1] = CastDue;
               }
               PrevSong=CurrSong;
            } // if it's not time for currsong to be re-sung, skip it in the twist
            CurrSong++;
            PrepNextSong();
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Slot %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = GetTime()+ItemClick[SongTodo-10].cast_time;
            }
            PrevSong=CurrSong;   // Increment twist position even if we didn't do an itemnotify - this might have a long recast
            CurrSong++;         // interval set, and we just skip it until it's time to recast, rather than keep a separate timer.
            PrepNextSong();
         }   
      }
   }     
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if (!bTwist || !MQ2TwistEnabled) return 0;
   // DebugSpew("MQ2Twist::OnIncomingChat(%s)",Line);
   
   if ( !strcmp(Line,"You miss a note, bringing your song to a close!") ||
      !strcmp(Line,"You haven't recovered yet...") ||
      !strcmp(Line,"Your spell is interrupted.") ) {
         DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event");
         if (!HoldSong) CurrSong=PrevSong;
         CastDue = -1;
         SongNextCast[CurrSong-1] = -1;
         return 0;
      }

   if (!strcmp(Line,"You can't cast spells while stunned!") ) {
      DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event (stun)");
      if (!HoldSong) CurrSong=PrevSong;
      CastDue = GetTime() + 10;
      // Wait one second before trying again, to avoid spamming the trigger text w/ cast attempts
      return 0;
   }
   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   DebugSpew("MQ2Twist::SetGameState()");
   if (GameState==GAMESTATE_INGAME)
   {
      MQ2TwistEnabled = true;
      Load_MQ2Twist_INI();
   } else {
      MQ2TwistEnabled = false;
   }
}
 
Re: Fixed

I already have 2 kids of my own, but here:

Rich (BB code):
/*
MQ2MoveUtils plugin (2004.06.23) - tonio
- Updated 2005.06.26 by Quagmire
- Updated 2005.07.09 by Outlander
- Updated 2005.07.14 by Outlander

*Corrections and additions 2005.07.14
Added break Circle functionality so that when you manually move Circle is turned off
Added MoveStuck logic to MoveTo and Circle commands
Adjusted the stuck distance to take into account the speedmodifier currently on the character.
Adjusted the the MoveStuck stuckDist to be 1/3 of what it normally is if your under water.

*Corrections and additions 2005.07.08
Added Version Number at top of help commands
Added /stick id <spawn id> functionality
Added break MoveTo functionality so that when you manually move MoveTo is turned off

Currently contains three commands:
/stick -- /follow-like command, works for any pc/npc, default distance is melee range ("/stick help" for more options)
/circle -- autofaces character to run in a circle
/moveto -- moves character to a specific loc, such as an anchor spot

"/stick" command by me
"/circle" command lifted from CyberTech's MQ2Twister plugin
"/moveto" command from rswiders


*/

#include "../MQ2Plugin.h"

PreSetup("MQ2MoveUtils");
CHAR szVersion[30]="MQ2MoveUtils Version 20050714";
CHAR szMsg[MAX_STRING]={0};

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine);
float getRand(float n);

bool bCircling=false;
bool bDrunken=false;
float CircleX=0.0f;
float CircleY=0.0f;
float CircleRadius=0.0f;
SYSTEMTIME stPrevCirc;

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void ReleaseKeys();
void DoWalk(bool walk = false);
void DoFwd(bool hold, bool walk = false);
void DoBck(bool hold);
void DoLft(bool hold);
void DoRgt(bool hold);
void Load_INI(void);
bool IsBardClass(void);
void stickText();
void breakStick(bool stopMoving = true, bool quite = false);
float angularDistance(float h1, float h2);

bool MoveBindsLoaded=false;
bool stickOn=false;
bool setDist=false;
bool stickPaused=false;
bool stickHold=false;
bool moveBehind=false;
bool moveBehindOnce=false;
bool moveBack=false;
bool movePin=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=false;
bool stickhasmovedfwd=false;
short stickVerbosity=1;
short keysDown=0;
float stickDist=0.0;
float breakDist=250.0;
float currentDist=0.0;
float stickDistMod=0.0;
float stickDistModP=1.0;
PSPAWNINFO stickTarget;
eSpawnType stickTarget_Type;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

// stuck added by Outlander
int stuck=0;
int stuckCheck=0;
int stuckFree=0;
float prevX=0.0f;
float prevY=0.0f;
float prevZ=0.0f;
float stuckDist=0.0f;
float turnDirection=0.0f;
float pulseAvg=0.0f;
bool stuckLogic;

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMoveTo();
bool bMoveToOn=false;
float LocX=0.0f;
float LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

class MQ2StickType *pStickType = 0;

class MQ2StickType : public MQ2Type
{
public:
   enum StickMembers {
      Status=1,
      Active=2,
      Distance=3,
      MoveBehind=4,
      MovePause=5,
      MoveBack=6,
      Loose=7,
      Paused=8,
      Behind=9,
      Stopped=10,
      Pin=11,
//      Stuck=12,
   };

   MQ2StickType():MQ2Type("stick")
   {
      TypeMember(Status);
      TypeMember(Active);
      TypeMember(Distance);
      TypeMember(MoveBehind);
      TypeMember(MovePause);
      TypeMember(MoveBack);
      TypeMember(Loose);
      TypeMember(Paused);
      TypeMember(Behind);
      TypeMember(Stopped);
      TypeMember(Pin);
//      TypeMember(Stuck);
   }

   ~MQ2StickType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2StickType::FindMember(Member);
      if (!pMember)
         return false;
      switch((StickMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( stickOn ) {
            strcpy(DataTypeTemp,"ON");
         }
         if( stickPaused ) {
            strcpy(DataTypeTemp,"PAUSED");
         }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Active:
         Dest.DWord=stickOn;
         Dest.Type=pBoolType;
         return true;
      case Distance:
         Dest.Float=stickDist;
         Dest.Type=pFloatType;
         return true;
      case MoveBehind:
         Dest.DWord=moveBehind;
         Dest.Type=pBoolType;
         return true;
      case MovePause:
         Dest.DWord=mPause;
         Dest.Type=pBoolType;
         return true;
      case MoveBack:
         Dest.DWord=moveBack;
         Dest.Type=pBoolType;
         return true;
      case Loose:
         Dest.DWord=looseStick;
         Dest.Type=pBoolType;
         return true;
      case Paused:
         Dest.DWord=stickPaused;
         Dest.Type=pBoolType;
         return true;
      case Behind:
         if (ppTarget && pTarget) {
            PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget) > stickDist || fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 )?false:true;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         if( ppTarget && pTarget ) {
            PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget)<=stickDist)?true:false;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Pin:
         Dest.DWord=movePin;
         Dest.Type=pBoolType;
         return true;
//      case Stuck:
//         Dest.DWord=stuck;
//         Dest.Type=pBoolType;
//         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( stickOn ) {
         strcpy(Destination,"ON");
      }
      if( stickPaused ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataStick(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pStickType;
   return true;
}

VOID CircleHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /circle on|off|drunken <radius> [<y> <x>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are optional, if not specified will use your currect loc.",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while not circling, it will start with your current loc and specified radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while already circling, it will update with your new loc and radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on' while already circling, it will update with your new loc using original radius.",USERCOLOR_DEFAULT);
}

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0) {
      CircleHelp();
      return;
   } else if (!stricmp(szTemp,"on") || !stricmp(szTemp,"drunken")) {
      if( !stricmp(szTemp,"drunken") )
         bDrunken=true;
      else
         bDrunken=false;
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         if (!bCircling) {
            CircleHelp();
            return;
         } else if (!CircleRadius) {
            // /circle on was called while we were already circling, but no radius was defined. oddddddd.
            CircleHelp();
            return;
         }
      } else {
         CircleRadius = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         CircleY = pChSpawn->Y + CircleRadius * (float)sin(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleY = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (!strlen(szTemp)) {
         CircleX = pChSpawn->X - CircleRadius * (float)cos(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleX = (float)atof(szTemp);
      }

      if (CircleRadius) bCircling = true;
      sprintf(szMsg, "Circling Radius %g, center %g %g", CircleRadius, CircleY, CircleX);
      WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else {
      if (!stricmp(szTemp,"off")) {
         bCircling = false;
      }
   }

}

void StickHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload|id] [<dist>|<spawnid>] [behind|behindonce|pin] [mpause] [moveback] [loose] [-<dist>] [<perc>%] [uw]");
   WriteChatColor("   /stick            - Sticks you within melee range of your target");
   WriteChatColor("   /stick hold       - Stores your current target, sticks to it even if you lose/change target");
   WriteChatColor("   /stick off        - Breaks off from stick (moving manually also breaks off from stick");
   WriteChatColor("   /stick pause      - Pauses following (can move normally while paused)");
   WriteChatColor("   /stick unpause    - Resumes following");
   WriteChatColor("   /stick reload     - Reload values from ini file");
   WriteChatColor("   /stick <dist>     - Sticks you within <dist> units of your target");
   WriteChatColor("   /stick behind     - Keeps you behind your target");
   WriteChatColor("   /stick behindonce - Moves behind your target once");
   WriteChatColor("   /stick pin        - Keeps you to the side of your target");
   WriteChatColor("   /stick mpause     - Causes manual movement to pause stick instead of breaking it");
   WriteChatColor("   /stick moveback   - Moves character back to try to stay at exactly stick distance");
   WriteChatColor("   /stick loose      - Checks distance/angle less often, and turns slower, for a more human-controlled look");
   WriteChatColor("   /stick uw         - Looks up or down to track target, useful for underwater /stick");
   WriteChatColor("   /stick -<dist>    - Substracts <dist> from the stick distance");
   WriteChatColor("   /stick <perc>%    - Multiplies stick distance by <perc> percent");
   WriteChatColor("   /stick id <spawn> - Sticks to the pc/npc with the spawn ID");
}

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char currentArg[MAX_STRING];
   int argn=1;

   srand ( time(NULL));
   int trand = rand()%100;
   turnDirection *= (trand>50)?1.0f:-1.0f;

   GetArg(currentArg,szLine,argn++);

   if( !strncmp(currentArg,"pause",6) ) {
      ReleaseKeys();
      stickPaused = true;
      return;
   } else if( !strncmp( currentArg,"unpause",8 ) ) {
      stickPaused = false;
      return;
   }

   if (!stickOn)
      DoWalk(false);
   ReleaseKeys();
   stickOn=true;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   moveBehindOnce=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickhasmovedfwd=false;
   stuck=0;
   stickTarget=NULL;
   stickTarget_Type=NONE;
   stickDistMod=0.0f;
   stickDistModP=1.0f;


   while( *currentArg ) {
      if( !strncmp(currentArg,"on",3) ) {
         stickOn = true;
      } else if( strstr(currentArg,"%") ) {
         stickDistModP = (float)atof(currentArg) / 100.0f;
         if( setDist )
            stickDist *= stickDistModP;
         stickOn = true;
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.' ) {
         setDist = true;
         stickDist = (float)atof(currentArg) * stickDistModP + stickDistMod;
         stickOn = true;
      } else if( currentArg[0]=='-' ) {
         stickDistMod = (float)atof(currentArg);
         if( setDist )
            stickDist += stickDistMod;
         stickOn = true;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"moveback",9) ) {
         moveBack = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"loose",6) ) {
         looseStick = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"uw",3 ) ) {
         underwater=true;
         stickOn=true;
      } else if( !strncmp(currentArg,"off",4) ) {
         breakStick();
         break;
      } else if( !strncmp(currentArg,"hold",5) ) {
         stickOn = true;
         if( ppTarget && pTarget ) {
         if (((PSPAWNINFO) pTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
            WriteChatColor("You cannot stick hold to yourself.");
            breakStick(true, true);
            return;
         }
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
         stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
         }
      } else if( !strncmp(currentArg,"id",3) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]) ) {
             stickTarget = (PSPAWNINFO) GetSpawnByID(atoi(currentArg));
             if( stickTarget ) {
                 if (((PSPAWNINFO) stickTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
                    WriteChatColor("You cannot stick id to yourself.");
                    breakStick(true, true);
                    return;
                 }
                 stickOn = true;
                 stickHold = true;
                 stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
             }
          } else {
              WriteChatColor("When using ID the next parameter MUST be the spawn ID.");
              breakStick(true, true);
              return;
          }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn=true;
         moveBehind=true;
       moveBehindOnce=false;
       movePin=false;
      } else if( !strncmp(currentArg,"behindonce",11) ) {
         stickOn=true;
         moveBehindOnce=true;
       moveBehind=false;
       movePin=false;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn=true;
         movePin=true;
       moveBehind=false;
       moveBehindOnce=false;
      } else if( !strncmp(currentArg,"reload",7) ) {
         breakStick();
         Load_INI();
         WriteChatColor("Ini file reloaded.");
         break;
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      stickHold = stickHold && stickTarget && stickTarget->SpawnID;
      if( (ppTarget && pTarget) || (stickHold && stickTarget && stickTarget->SpawnID) ) {
         if( FindSpeed((PSPAWNINFO)pCharSpawn) < 0 ) {
            DoFwd(false);
         }
         currentDist=GetDistance((PSPAWNINFO)pCharSpawn,stickHold?stickTarget:((PSPAWNINFO)pTarget));
      }
      stickText();
   }
}

VOID DoUnstickBind(PCHAR Name, BOOL Down)
{
   if( ! Down ) {
      keysDown--;
      if( keysDown == 0 && mPause ) {
         stickPaused = false;
         moveBehind = prevMoveBehind;
         movePin = prevMovePin;
      }
   } else {
      keysDown++;
      if((!stickPaused && stickOn) || bMoveToOn || bCircling) {
         if( mPause && strncmp(Name,"UNSTICK_STRAFE_RGT",12) && strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            stickPaused = true;
         } else if( !strncmp(Name,"UNSTICK_STRAFE_RGT",12) || !strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            if( mPause ) {
               prevMoveBehind = moveBehind || prevMoveBehind;
               prevMovePin = movePin || prevMovePin;
               moveBehind = false;
            } else {
               prevMoveBehind = moveBehind = false;
               prevMovePin = movePin = false;
            }
         } else {
            breakStick((strncmp(Name,"UNSTICK_BCK",12) && strncmp(Name,"UNSTICK_FWD",12)));
         }
      }
   }
}

VOID HandleCircle() {
   static int counter = 0;
   float distance;
   float heading;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if (!GetCharInfo() || !bCircling) return;

   float X = pChSpawn->X - CircleX;
   float Y = pChSpawn->Y - CircleY;
   distance = sqrt(X*X + Y*Y);

   if( !casting || !autoPauseEnabled ) {
      if (distance>(CircleRadius*(2.0/3.0))) {
         float newHeading;
         float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
         if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
         prevX=pChSpawn->X;
         prevY=pChSpawn->Y;
         prevZ=pChSpawn->Z;
   
         float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
         if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) || (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&& GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
             stuck++;
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading + turnDirection);
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 pChSpawn->Heading = newHeading;

                 //check to see if we are heading directly away from our target if so then go back
                 newHeading=(float) atan2(pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
                 newHeading += (float) 90.0f * (CircleRadius/distance);
                 newHeading *= 512.0f/360.0f;
                 if( newHeading >= 512.0f ) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;

                 newHeading += 265.0f;
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                     newHeading -= 265.0f;
                     if( newHeading >= 512.0f) newHeading -= 512.0f;
                     if( newHeading < 0.0f ) newHeading += 512.0f;
                     pChSpawn->Heading = newHeading;
                     stuck = stuckCheck;
                     turnDirection *= -1.0f;
                 }
             }
             stuckFree=0;

         } else if( stuck > 0 ) {
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading - turnDirection );
              pChSpawn->Heading = newHeading;
             }
             stuck--;
             if(stuckFree++ > stuckCheck*3 ) {
                 stuck=0;
             }
         } else {
            heading=(float) atan2( pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
            heading += 90.0f * (CircleRadius/distance);
            heading *= 512.0f/360.0f;
            if( heading >= 512.0f ) heading -= 512.0f;
            if( heading < 0.0f ) heading += 512.0f;
            if( bDrunken ) {
               gFaceAngle = (float)heading;
            } else {
               pChSpawn->Heading = (float)heading;
            }
         }
         DoFwd(true);
      }
   }
}

float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

void HandleStick()
{
   if( (stickHold && (!stickTarget || !(stickTarget->SpawnID) || stickTarget_Type!=GetSpawnType(stickTarget))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
      return;
   }
   PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !psTarget || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   float newHeading=0.0f;
   float prevDist = currentDist;

   if( !setDist )
      stickDist = (psTarget->StandState?get_melee_range(pLocalPlayer,(EQPlayer *)psTarget):15.0f) * stickDistModP + stickDistMod;

   currentDist=GetDistance(pChSpawn,psTarget);
   if( breakOnWarpEnabled && (currentDist-prevDist) > breakDist ) {
      breakStick();
      return;
   }
   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 || psTarget->SpawnID == pChSpawn->SpawnID ) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( (!stickPaused && !casting) || !autoPauseEnabled ) {
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) || (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&& currentDist > stickDist && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f) newHeading -= 512.0f;
          if( newHeading < 0.0f ) newHeading += 512.0f;
          if (stickhasmovedfwd && fabs(pChSpawn->Heading - newHeading) >= 200 && currentDist < 10) {
             DoBck(true);
             return;
          } else {
             DoBck(false);
          }
          if( looseStick ) {
             gFaceAngle = newHeading;
          } else {
             pChSpawn->Heading = newHeading;
          }

          //underwater = pChSpawn->pActorInfo->UnderWater==5;
          if( underwater ) {
             double lookAngle = (float) atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
             pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
             currentDist) * 256.0f / (float)PI;
             if ( looseStick ) {
                gLookAngle = lookAngle;
             } else {
                pChSpawn->CameraAngle = (FLOAT)lookAngle;
             }
          }
          if ( currentDist > (stickDist * 3) ) {
             // too far away, dont do pin or behind
          } else if( moveBehind || moveBehindOnce ) {
             float angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if( fabs(angDist) > 45.0 ) {
                if(angDist < 0.0) {
                   // strafe left
                   DoLft(true);
                } else {
                   // strage right
                   DoRgt(true);
                }
             } else {
                moveBehindOnce = false;
                DoLft(false);
                DoRgt(false);
             }
          } else if (movePin) {
             FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if((angDist > 0 && angDist <= 112) || angDist < -144) { //blahblah
                DoLft(true);
             } else if ((angDist < 0 && angDist > -112) || angDist > 144) {
                DoRgt(true);
             } else {
                DoLft(false);
                DoRgt(false);
             }
          }
      }
      if(pChSpawn->StandState == STANDSTATE_DUCK && stuckLogic ) pChSpawn->StandState = STANDSTATE_STAND;
      if( currentDist > stickDist) {
         // if distance is less than 10, walk
         DoFwd(true, ((currentDist - stickDist) <= 10.0));
      } else if( moveBack && currentDist < (stickDist-5.0) ) {
         DoBck(true);
      } else {
         DoFwd(false);
         DoBck(false);
      }
   }
}

void
Load_INI(VOID)
{
   char szTemp[MAX_STRING], szTemp2[MAX_STRING];

   // Defaults
   GetPrivateProfileString("Defaults","AutoPause","on",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",autoPauseEnabled?"on":"off");
   WritePrivateProfileString("Defaults","AutoPause",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnWarp","on",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnWarpEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnWarp",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakDist","250.0",szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Defaults","BreakDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnGate","on",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnGateEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnGate",szTemp,INIFileName);

   stickVerbosity=(short)GetPrivateProfileInt("Defaults","Verbosity",1,INIFileName);
   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Defaults","Verbosity",szTemp,INIFileName);

   // Character specific
   GetPrivateProfileString(GetCharInfo()->Name,"AutoPause",autoPauseEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnWarp",breakOnWarpEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",breakDist);
   GetPrivateProfileString(GetCharInfo()->Name,"BreakDist",szTemp2,szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnGate",breakOnGateEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);

   GetPrivateProfileString("Defaults","stuckDist","0.8",szTemp,MAX_STRING,INIFileName);
   stuckDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",stuckDist);
   WritePrivateProfileString("Defaults","stuckDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","turnDirection","10.0",szTemp,MAX_STRING,INIFileName);
   turnDirection = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",turnDirection);
   WritePrivateProfileString("Defaults","turnDirection",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","stuckCheck","5",szTemp,MAX_STRING,INIFileName);
   stuckCheck = (int)atoi(szTemp);
   sprintf(szTemp,"%i",stuckCheck);
   WritePrivateProfileString("Defaults","stuckCheck",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","StuckLogic","on",szTemp,MAX_STRING,INIFileName);
   stuckLogic=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",stuckLogic?"on":"off");
   WritePrivateProfileString("Defaults","StuckLogic",szTemp,INIFileName);
}

bool IsBardClass()
{
   if(strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class),"Bard",5))
      return false;
   else
      return true;
}

PLUGIN_API VOID OnPulse(VOID)
{

   if (bCircling) {
      if( bDrunken ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevCirc) > 900 + (int)getRand(600.0) ) {
            GetSystemTime(&stPrevCirc);
            HandleCircle();
         }
      } else {
         HandleCircle();
      }
   }
   if( stickOn ) {
      if( looseStick ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevStick) > 100 + (int)getRand(200.0) ) {
            GetSystemTime(&stPrevStick);
            HandleStick();
         }
      } else {
         HandleStick();
      }
   }
   if (bMoveToOn) {
      HandleMoveTo();
   }
}

void ReleaseKeys() {
   DoWalk(false);
   DoFwd(false);
   DoBck(false);
   DoRgt(false);
   DoLft(false);
}

void DoWalk(bool walk) {
//   if (!(pLocalPlayer)) {
//      WriteChatColor("pLocalPlayer is null");
//      return;
//   }
//   if (!(((PSPAWNINFO) pLocalPlayer)->pActorInfo)) {
//      WriteChatColor("pLocalPlayer->pActorInfo is null");
//      return;
//   }
   bool state_walking = (*EQADDR_RUNWALKSTATE) ? false : true;
   float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
//   float SpeedModifier = ((PSPAWNINFO) pLocalPlayer)->pActorInfo->SpeedModifier;
   if (SpeedModifier < 0)
      walk = false; // we're snared, dont go into walk mode no matter what
   if ( (walk && !state_walking) || (!walk && state_walking) ) {
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),1,0);
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),0,0);
   }
}

void DoFwd(bool hold, bool walk) {
   static bool held = false;
   if ( hold ) {
      stickhasmovedfwd = true;
      DoWalk(walk);
      DoBck(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
      held = true;
   } else {
      DoWalk(false);
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
      held = false;
   }
}

void DoBck(bool hold) {
   static bool held = false;
   if( hold ) {
      DoFwd(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
      held = false;
   }
}

void DoLft(bool hold) {
   static bool held = false;
   if( hold ) {
      DoRgt(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
      held = false;
   }
}

void DoRgt(bool hold) {
   static bool held = false;
   if( hold ) {
      DoLft(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
      held = false;
   }
}

float getRand(float n)
{
   return (n * rand() / (RAND_MAX+1.0f));
}

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev)
{
   SYSTEMTIME stResult;
   FILETIME ftPrev, ftCurr, ftResult;
   ULARGE_INTEGER prev,curr,result;

   GetSystemTime(&stCurr);
   SystemTimeToFileTime(&stPrev,&ftPrev);
   SystemTimeToFileTime(&stCurr,&ftCurr);
   prev.HighPart = ftPrev.dwHighDateTime;
   prev.LowPart = ftPrev.dwLowDateTime;
   curr.HighPart = ftCurr.dwHighDateTime;
   curr.LowPart = ftCurr.dwLowDateTime;
   result.QuadPart = curr.QuadPart - prev.QuadPart;
   ftResult.dwHighDateTime = result.HighPart;
   ftResult.dwLowDateTime = result.LowPart;
   FileTimeToSystemTime(&ftResult,&stResult);

   return ((int)(stResult.wSecond * 1000 + stResult.wMilliseconds));
}

void stickText()
{
   char szTemp[MAX_STRING];

   if( stickVerbosity == 1 ) {
      if( stickPaused ) {
         WriteChatColor("Stick paused.");
      } else if( stickOn ) {
         if( stickHold ) {
            sprintf(szTemp,"You are now sticking to %s.",stickTarget->DisplayedName);
         } else if( ppTarget && pTarget ) {
            sprintf(szTemp,"You are now sticking to %s.",((PSPAWNINFO)pTarget)->DisplayedName);
         } else {
            sprintf(szTemp,"Need a target for stick.");
         }
         WriteChatColor(szTemp);
      } else {
         WriteChatColor("You are no longer sticking to anything.");
      }
   }
}

void breakStick(bool stopMoving, bool quite)
{
   stickOn=false;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   moveBehindOnce=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   bMoveToOn=false;
   bCircling=false;
   stuck=0;
   stickTarget=NULL;
   stickDistMod=0.0;
   stickDistModP=1.0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
   if (!quite)
      stickText();
}

class MQ2MoveToType *pMoveToType = 0;

class MQ2MoveToType : public MQ2Type
{
public:
   enum MoveToMembers {
      Moving=1,
      Stopped=2
   };

   MQ2MoveToType():MQ2Type("moveto")
   {
      TypeMember(Moving);
      TypeMember(Stopped);
   }

   ~MQ2MoveToType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MoveToType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MoveToMembers)pMember->ID)
      {
      case Moving:
         Dest.DWord=bMoveToOn;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
         Dest.DWord=(GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY)<=moveDist)?true:false;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if( bMoveToOn ) {
         strcpy(Destination,"ON");
      } else {
         strcpy(Destination,"OFF");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMoveTo(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMoveToType;
   return true;
}

VOID MoveToHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /moveto loc|off <y> <x> [<dist>|-<dist>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  You can not call '/moveto <y> <x>' while circling or sticking.",USERCOLOR_DEFAULT);
   WriteChatColor("  /moveto <dist>   - Moves you within <dist> units of your target, default is 50");
   WriteChatColor("  /moveto -<dist>  - Subtracts <dist> units from the move distance");
}

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0 || bCircling || stickOn) {
      MoveToHelp();
      return;
   } else if (!stricmp(szTemp,"loc")) {
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocY = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocX = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (strlen(szTemp)) {
         if( isdigit(szTemp[0]) || szTemp[0]=='.' ) {
            moveDist = (float)atof(szTemp);
         } else if( szTemp[0]=='-' ) {
            moveDistMod = (float)atof(szTemp);
            moveDist += moveDistMod;
         }
      }

      bMoveToOn=true;
      sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
      if (stickVerbosity)
         WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else if (!stricmp(szTemp,"off")) {
      bMoveToOn = false;
      if (stickVerbosity)
         WriteChatColor("MoveTo off.", CONCOLOR_YELLOW);
   } else {
      MoveToHelp();
      return;
   }
}

void HandleMoveTo()
{
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( !casting || !autoPauseEnabled ) {
      float newHeading;
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) || (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&& GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f)
             newHeading -= 512.0f;
          if( newHeading < 0.0f )
             newHeading += 512.0f;
          pChSpawn->Heading = newHeading;
      }
      if( GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist) {
         DoFwd(true);
      } else {
         bMoveToOn=false;
         if (stickVerbosity)
            WriteChatColor("Arrived at MoveTo location", CONCOLOR_YELLOW);
         DoFwd(false);
      }
   }
}

VOID CreateBinds(){
   if (MoveBindsLoaded)
      return;
   MoveBindsLoaded=true;
   AddMQ2KeyBind("UNSTICK_FWD",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_BCK",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_RGT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_RGT",DoUnstickBind);
   SetMQ2KeyBind("UNSTICK_FWD",false,pKeypressHandler->NormalKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_FWD",true,pKeypressHandler->AltKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_BCK",false,pKeypressHandler->NormalKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_BCK",true,pKeypressHandler->AltKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_right")]);
}

VOID DestroyBinds()
{
   if (!MoveBindsLoaded)
      return;
   RemoveMQ2KeyBind("UNSTICK_FWD");
   RemoveMQ2KeyBind("UNSTICK_BCK");
   RemoveMQ2KeyBind("UNSTICK_LFT");
   RemoveMQ2KeyBind("UNSTICK_RGT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_LFT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_RGT");
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2MoveUtils");

   // Add commands, macro parameters, hooks, etc.
   AddCommand("/circle",CircleCommand,0,1,1);
   AddCommand("/stick",StickCommand);
   AddMQ2Data("Stick",dataStick);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("MoveTo",dataMoveTo);

   pStickType = new MQ2StickType;

   srand((unsigned int)time(NULL));
   if (gGameState==GAMESTATE_INGAME) {
      CreateBinds();
   }
   GetSystemTime(&stPrevCirc);
   GetSystemTime(&stPrevStick);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("Shutting down MQ2MoveUtils");

   // Remove commands, macro parameters, hooks, etc.
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   RemoveMQ2Data("MoveTo");
   RemoveCommand("/moveto");

   delete pStickType;
   delete pMoveToType;

   DestroyBinds();
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if( breakOnGateEnabled && (stickHold?(stickTarget!=NULL):(ppTarget && pTarget)) ) {
      char szTemp[MAX_STRING];

      sprintf(szTemp,"%s Gates.",stickHold?stickTarget->DisplayedName:((PSPAWNINFO)pTarget)->DisplayedName);
      if( ! strcmp(szTemp,Line) ) {
         DoFwd(false);
         stickOn = false;
      }
   }

   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   if (GameState==GAMESTATE_INGAME) {
      CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      stickPaused=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
   }
}

// Called after entering a new zone
PLUGIN_API VOID OnZoned(VOID)
{
   DoWalk(false);
}
 
Re: Fixed

Rich (BB code):
/*
MQ2MoveUtils plugin (2004.06.23) - tonio
- Updated 2005.06.26 by Quagmire
- Updated 2005.07.09 by Outlander
- Updated 2005.07.14 by Outlander
- Updated 2005.07.19 by Outlander

* Corrections 2005.07.19
Modified /stick movestuck logic when you are close to mob and are switched to walk mode, this was causing
movestuck logic to be executed incorrectly.

*Corrections and additions 2005.07.14
Added break Circle functionality so that when you manually move Circle is turned off
Added MoveStuck logic to MoveTo and Circle commands
Adjusted the stuck distance to take into account the speedmodifier currently on the character.
Adjusted the the MoveStuck stuckDist to be 1/3 of what it normally is if your under water.

*Corrections and additions 2005.07.08
Added Version Number at top of help commands
Added /stick id <spawn id> functionality
Added break MoveTo functionality so that when you manually move MoveTo is turned off

Currently contains three commands:
/stick -- /follow-like command, works for any pc/npc, default distance is melee range ("/stick help" for more options)
/circle -- autofaces character to run in a circle
/moveto -- moves character to a specific loc, such as an anchor spot

"/stick" command by me
"/circle" command lifted from CyberTech's MQ2Twister plugin
"/moveto" command from rswiders


*/

#include "../MQ2Plugin.h"

PreSetup("MQ2MoveUtils");
CHAR szVersion[30]="MQ2MoveUtils Version 20050714";
CHAR szMsg[MAX_STRING]={0};

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine);
float getRand(float n);

bool bCircling=false;
bool bDrunken=false;
float CircleX=0.0f;
float CircleY=0.0f;
float CircleRadius=0.0f;
SYSTEMTIME stPrevCirc;

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void ReleaseKeys();
void DoWalk(bool walk = false);
void DoFwd(bool hold, bool walk = false);
void DoBck(bool hold);
void DoLft(bool hold);
void DoRgt(bool hold);
void Load_INI(void);
bool IsBardClass(void);
void stickText();
void breakStick(bool stopMoving = true, bool quite = false);
float angularDistance(float h1, float h2);

bool MoveBindsLoaded=false;
bool stickOn=false;
bool setDist=false;
bool stickPaused=false;
bool stickHold=false;
bool moveBehind=false;
bool moveBehindOnce=false;
bool moveBack=false;
bool movePin=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=false;
bool stickhasmovedfwd=false;
short stickVerbosity=1;
short keysDown=0;
float stickDist=0.0;
float breakDist=250.0;
float currentDist=0.0;
float stickDistMod=0.0;
float stickDistModP=1.0;
PSPAWNINFO stickTarget;
eSpawnType stickTarget_Type;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

// stuck added by Outlander
int stuck=0;
int stuckCheck=0;
int stuckFree=0;
float prevX=0.0f;
float prevY=0.0f;
float prevZ=0.0f;
float stuckDist=0.0f;
float turnDirection=0.0f;
float pulseAvg=0.0f;
bool stuckLogic;

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMoveTo();
bool bMoveToOn=false;
float LocX=0.0f;
float LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

class MQ2StickType *pStickType = 0;

class MQ2StickType : public MQ2Type
{
public:
   enum StickMembers {
      Status=1,
      Active=2,
      Distance=3,
      MoveBehind=4,
      MovePause=5,
      MoveBack=6,
      Loose=7,
      Paused=8,
      Behind=9,
      Stopped=10,
      Pin=11,
   };

   MQ2StickType():MQ2Type("stick")
   {
      TypeMember(Status);
      TypeMember(Active);
      TypeMember(Distance);
      TypeMember(MoveBehind);
      TypeMember(MovePause);
      TypeMember(MoveBack);
      TypeMember(Loose);
      TypeMember(Paused);
      TypeMember(Behind);
      TypeMember(Stopped);
      TypeMember(Pin);
   }

   ~MQ2StickType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2StickType::FindMember(Member);
      if (!pMember)
         return false;
      switch((StickMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( stickOn ) {
            strcpy(DataTypeTemp,"ON");
         }
         if( stickPaused ) {
            strcpy(DataTypeTemp,"PAUSED");
         }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Active:
         Dest.DWord=stickOn;
         Dest.Type=pBoolType;
         return true;
      case Distance:
         Dest.Float=stickDist;
         Dest.Type=pFloatType;
         return true;
      case MoveBehind:
         Dest.DWord=moveBehind;
         Dest.Type=pBoolType;
         return true;
      case MovePause:
         Dest.DWord=mPause;
         Dest.Type=pBoolType;
         return true;
      case MoveBack:
         Dest.DWord=moveBack;
         Dest.Type=pBoolType;
         return true;
      case Loose:
         Dest.DWord=looseStick;
         Dest.Type=pBoolType;
         return true;
      case Paused:
         Dest.DWord=stickPaused;
         Dest.Type=pBoolType;
         return true;
      case Behind:
         if (ppTarget && pTarget) {
            PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget) > stickDist || fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 )?false:true;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         if( ppTarget && pTarget ) {
            PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget)<=stickDist)?true:false;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Pin:
         Dest.DWord=movePin;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( stickOn ) {
         strcpy(Destination,"ON");
      }
      if( stickPaused ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataStick(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pStickType;
   return true;
}

VOID CircleHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /circle on|off|drunken <radius> [<y> <x>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are optional, if not specified will use your currect loc.",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while not circling, it will start with your current loc and specified radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while already circling, it will update with your new loc and radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on' while already circling, it will update with your new loc using original radius.",USERCOLOR_DEFAULT);
}

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0) {
      CircleHelp();
      return;
   } else if (!stricmp(szTemp,"on") || !stricmp(szTemp,"drunken")) {
      if( !stricmp(szTemp,"drunken") )
         bDrunken=true;
      else
         bDrunken=false;
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         if (!bCircling) {
            CircleHelp();
            return;
         } else if (!CircleRadius) {
            // /circle on was called while we were already circling, but no radius was defined. oddddddd.
            CircleHelp();
            return;
         }
      } else {
         CircleRadius = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         CircleY = pChSpawn->Y + CircleRadius * (float)sin(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleY = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (!strlen(szTemp)) {
         CircleX = pChSpawn->X - CircleRadius * (float)cos(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleX = (float)atof(szTemp);
      }

      if (CircleRadius) bCircling = true;
      sprintf(szMsg, "Circling Radius %g, center %g %g", CircleRadius, CircleY, CircleX);
      WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else {
      if (!stricmp(szTemp,"off")) {
         bCircling = false;
      }
   }

}

void StickHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload|id] [<dist>|<spawnid>] [behind|behindonce|pin] [mpause] [moveback] [loose] [-<dist>] [<perc>%] [uw]");
   WriteChatColor("   /stick            - Sticks you within melee range of your target");
   WriteChatColor("   /stick hold       - Stores your current target, sticks to it even if you lose/change target");
   WriteChatColor("   /stick off        - Breaks off from stick (moving manually also breaks off from stick");
   WriteChatColor("   /stick pause      - Pauses following (can move normally while paused)");
   WriteChatColor("   /stick unpause    - Resumes following");
   WriteChatColor("   /stick reload     - Reload values from ini file");
   WriteChatColor("   /stick <dist>     - Sticks you within <dist> units of your target");
   WriteChatColor("   /stick behind     - Keeps you behind your target");
   WriteChatColor("   /stick behindonce - Moves behind your target once");
   WriteChatColor("   /stick pin        - Keeps you to the side of your target");
   WriteChatColor("   /stick mpause     - Causes manual movement to pause stick instead of breaking it");
   WriteChatColor("   /stick moveback   - Moves character back to try to stay at exactly stick distance");
   WriteChatColor("   /stick loose      - Checks distance/angle less often, and turns slower, for a more human-controlled look");
   WriteChatColor("   /stick uw         - Looks up or down to track target, useful for underwater /stick");
   WriteChatColor("   /stick -<dist>    - Substracts <dist> from the stick distance");
   WriteChatColor("   /stick <perc>%    - Multiplies stick distance by <perc> percent");
   WriteChatColor("   /stick id <spawn> - Sticks to the pc/npc with the spawn ID");
}

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char currentArg[MAX_STRING];
   int argn=1;

   srand ( time(NULL));
   int trand = rand()%100;
   turnDirection *= (trand>50)?1.0f:-1.0f;

   GetArg(currentArg,szLine,argn++);

   if( !strncmp(currentArg,"pause",6) ) {
      ReleaseKeys();
      stickPaused = true;
      return;
   } else if( !strncmp( currentArg,"unpause",8 ) ) {
      stickPaused = false;
      return;
   }

   if (!stickOn)
      DoWalk(false);
   ReleaseKeys();
   stickOn=true;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   moveBehindOnce=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickhasmovedfwd=false;
   stuck=0;
   stickTarget=NULL;
   stickTarget_Type=NONE;
   stickDistMod=0.0f;
   stickDistModP=1.0f;


   while( *currentArg ) {
      if( !strncmp(currentArg,"on",3) ) {
         stickOn = true;
      } else if( strstr(currentArg,"%") ) {
         stickDistModP = (float)atof(currentArg) / 100.0f;
         if( setDist )
            stickDist *= stickDistModP;
         stickOn = true;
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.' ) {
         setDist = true;
         stickDist = (float)atof(currentArg) * stickDistModP + stickDistMod;
         stickOn = true;
      } else if( currentArg[0]=='-' ) {
         stickDistMod = (float)atof(currentArg);
         if( setDist )
            stickDist += stickDistMod;
         stickOn = true;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"moveback",9) ) {
         moveBack = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"loose",6) ) {
         looseStick = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"uw",3 ) ) {
         underwater=true;
         stickOn=true;
      } else if( !strncmp(currentArg,"off",4) ) {
         breakStick();
         break;
      } else if( !strncmp(currentArg,"hold",5) ) {
         stickOn = true;
         if( ppTarget && pTarget ) {
         if (((PSPAWNINFO) pTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
            WriteChatColor("You cannot stick hold to yourself.");
            breakStick(true, true);
            return;
         }
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
         stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
         }
      } else if( !strncmp(currentArg,"id",3) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]) ) {
             stickTarget = (PSPAWNINFO) GetSpawnByID(atoi(currentArg));
             if( stickTarget ) {
                 if (((PSPAWNINFO) stickTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
                    WriteChatColor("You cannot stick id to yourself.");
                    breakStick(true, true);
                    return;
                 }
                 stickOn = true;
                 stickHold = true;
                 stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
             }
          } else {
              WriteChatColor("When using ID the next parameter MUST be the spawn ID.");
              breakStick(true, true);
              return;
          }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn=true;
         moveBehind=true;
       moveBehindOnce=false;
       movePin=false;
      } else if( !strncmp(currentArg,"behindonce",11) ) {
         stickOn=true;
         moveBehindOnce=true;
       moveBehind=false;
       movePin=false;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn=true;
         movePin=true;
       moveBehind=false;
       moveBehindOnce=false;
      } else if( !strncmp(currentArg,"reload",7) ) {
         breakStick();
         Load_INI();
         WriteChatColor("Ini file reloaded.");
         break;
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      stickHold = stickHold && stickTarget && stickTarget->SpawnID;
      if( (ppTarget && pTarget) || (stickHold && stickTarget && stickTarget->SpawnID) ) {
         if( FindSpeed((PSPAWNINFO)pCharSpawn) < 0 ) {
            DoFwd(false);
         }
         currentDist=GetDistance((PSPAWNINFO)pCharSpawn,stickHold?stickTarget:((PSPAWNINFO)pTarget));
      }
      stickText();
   }
}

VOID DoUnstickBind(PCHAR Name, BOOL Down)
{
   if( ! Down ) {
      keysDown--;
      if( keysDown == 0 && mPause ) {
         stickPaused = false;
         moveBehind = prevMoveBehind;
         movePin = prevMovePin;
      }
   } else {
      keysDown++;
      if((!stickPaused && stickOn) || bMoveToOn || bCircling) {
         if( mPause && strncmp(Name,"UNSTICK_STRAFE_RGT",12) && strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            stickPaused = true;
         } else if( !strncmp(Name,"UNSTICK_STRAFE_RGT",12) || !strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            if( mPause ) {
               prevMoveBehind = moveBehind || prevMoveBehind;
               prevMovePin = movePin || prevMovePin;
               moveBehind = false;
            } else {
               prevMoveBehind = moveBehind = false;
               prevMovePin = movePin = false;
            }
         } else {
            breakStick((strncmp(Name,"UNSTICK_BCK",12) && strncmp(Name,"UNSTICK_FWD",12)));
         }
      }
   }
}

VOID HandleCircle() {
   static int counter = 0;
   float distance;
   float heading;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if (!GetCharInfo() || !bCircling) return;

   float X = pChSpawn->X - CircleX;
   float Y = pChSpawn->Y - CircleY;
   distance = sqrt(X*X + Y*Y);

   if( !casting || !autoPauseEnabled ) {
      if (distance>(CircleRadius*(2.0/3.0))) {
         float newHeading;
         float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
         if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
         prevX=pChSpawn->X;
         prevY=pChSpawn->Y;
         prevZ=pChSpawn->Z;
   
         float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
         if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
             (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
              GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
             stuck++;
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading + turnDirection);
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 pChSpawn->Heading = newHeading;

                 //check to see if we are heading directly away from our target if so then go back
                 newHeading=(float) atan2(pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
                 newHeading += (float) 90.0f * (CircleRadius/distance);
                 newHeading *= 512.0f/360.0f;
                 if( newHeading >= 512.0f ) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;

                 newHeading += 265.0f;
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                     newHeading -= 265.0f;
                     if( newHeading >= 512.0f) newHeading -= 512.0f;
                     if( newHeading < 0.0f ) newHeading += 512.0f;
                     pChSpawn->Heading = newHeading;
                     stuck = stuckCheck;
                     turnDirection *= -1.0f;
                 }
             }
             stuckFree=0;

         } else if( stuck > 0 ) {
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading - turnDirection );
              pChSpawn->Heading = newHeading;
             }
             stuck--;
             if(stuckFree++ > stuckCheck*3 ) {
                 stuck=0;
             }
         } else {
            heading=(float) atan2( pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
            heading += 90.0f * (CircleRadius/distance);
            heading *= 512.0f/360.0f;
            if( heading >= 512.0f ) heading -= 512.0f;
            if( heading < 0.0f ) heading += 512.0f;
            if( bDrunken ) {
               gFaceAngle = (float)heading;
            } else {
               pChSpawn->Heading = (float)heading;
            }
         }
         DoFwd(true);
      }
   }
}

float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

void HandleStick()
{
   if( (stickHold && (!stickTarget || !(stickTarget->SpawnID) || stickTarget_Type!=GetSpawnType(stickTarget))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
      return;
   }
   PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !psTarget || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   float newHeading=0.0f;
   float prevDist = currentDist;

   if( !setDist )
      stickDist = (psTarget->StandState?get_melee_range(pLocalPlayer,(EQPlayer *)psTarget):15.0f) * stickDistModP + stickDistMod;

   currentDist=GetDistance(pChSpawn,psTarget);
   if( breakOnWarpEnabled && (currentDist-prevDist) > breakDist ) {
      breakStick();
      return;
   }
   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 || psTarget->SpawnID == pChSpawn->SpawnID ) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( (!stickPaused && !casting) || !autoPauseEnabled ) {
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
          (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
          ((currentDist - stickDist) > 10.0) && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f) newHeading -= 512.0f;
          if( newHeading < 0.0f ) newHeading += 512.0f;
          if (stickhasmovedfwd && fabs(pChSpawn->Heading - newHeading) >= 200 && currentDist < 10) {
             DoBck(true);
             return;
          } else {
             DoBck(false);
          }
          if( looseStick ) {
             gFaceAngle = newHeading;
          } else {
             pChSpawn->Heading = newHeading;
          }

          //underwater = pChSpawn->pActorInfo->UnderWater==5;
          if( underwater ) {
             double lookAngle = (float) atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
             pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
             currentDist) * 256.0f / (float)PI;
             if ( looseStick ) {
                gLookAngle = lookAngle;
             } else {
                pChSpawn->CameraAngle = (FLOAT)lookAngle;
             }
          }
          if ( currentDist > (stickDist * 3) ) {
             // too far away, dont do pin or behind
          } else if( moveBehind || moveBehindOnce ) {
             float angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if( fabs(angDist) > 45.0 ) {
                if(angDist < 0.0) {
                   // strafe left
                   DoLft(true);
                } else {
                   // strage right
                   DoRgt(true);
                }
             } else {
                moveBehindOnce = false;
                DoLft(false);
                DoRgt(false);
             }
          } else if (movePin) {
             FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if((angDist > 0 && angDist <= 112) || angDist < -144) {
                DoLft(true);
             } else if ((angDist < 0 && angDist > -112) || angDist > 144) {
                DoRgt(true);
             } else {
                DoLft(false);
                DoRgt(false);
             }
          }
      }
      if(pChSpawn->StandState == STANDSTATE_DUCK && stuckLogic ) pChSpawn->StandState = STANDSTATE_STAND;
      if( currentDist > stickDist) {
         // if distance is less than 10, walk
         DoFwd(true, ((currentDist - stickDist) <= 10.0));
      } else if( moveBack && currentDist < (stickDist-5.0) ) {
         DoBck(true);
      } else {
         DoFwd(false);
         DoBck(false);
      }
   }
}

void
Load_INI(VOID)
{
   char szTemp[MAX_STRING], szTemp2[MAX_STRING];

   // Defaults
   GetPrivateProfileString("Defaults","AutoPause","on",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",autoPauseEnabled?"on":"off");
   WritePrivateProfileString("Defaults","AutoPause",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnWarp","on",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnWarpEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnWarp",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakDist","250.0",szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Defaults","BreakDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnGate","on",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnGateEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnGate",szTemp,INIFileName);

   stickVerbosity=(short)GetPrivateProfileInt("Defaults","Verbosity",1,INIFileName);
   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Defaults","Verbosity",szTemp,INIFileName);

   // Character specific
   GetPrivateProfileString(GetCharInfo()->Name,"AutoPause",autoPauseEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnWarp",breakOnWarpEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",breakDist);
   GetPrivateProfileString(GetCharInfo()->Name,"BreakDist",szTemp2,szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnGate",breakOnGateEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);

   GetPrivateProfileString("Defaults","stuckDist","0.8",szTemp,MAX_STRING,INIFileName);
   stuckDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",stuckDist);
   WritePrivateProfileString("Defaults","stuckDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","turnDirection","10.0",szTemp,MAX_STRING,INIFileName);
   turnDirection = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",turnDirection);
   WritePrivateProfileString("Defaults","turnDirection",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","stuckCheck","5",szTemp,MAX_STRING,INIFileName);
   stuckCheck = (int)atoi(szTemp);
   sprintf(szTemp,"%i",stuckCheck);
   WritePrivateProfileString("Defaults","stuckCheck",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","StuckLogic","on",szTemp,MAX_STRING,INIFileName);
   stuckLogic=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",stuckLogic?"on":"off");
   WritePrivateProfileString("Defaults","StuckLogic",szTemp,INIFileName);
}

bool IsBardClass()
{
   if(strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class),"Bard",5))
      return false;
   else
      return true;
}

PLUGIN_API VOID OnPulse(VOID)
{
//  *((float *)&(((PSPAWNINFO)pLocalPlayer)->pActorInfo->Unknown0x0af[25])) = 0.95f;
   if (bCircling) {
      if( bDrunken ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevCirc) > 900 + (int)getRand(600.0) ) {
            GetSystemTime(&stPrevCirc);
            HandleCircle();
         }
      } else {
         HandleCircle();
      }
   }
   if( stickOn ) {
      if( looseStick ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevStick) > 100 + (int)getRand(200.0) ) {
            GetSystemTime(&stPrevStick);
            HandleStick();
         }
      } else {
         HandleStick();
      }
   }
   if (bMoveToOn) {
      HandleMoveTo();
   }
}

void ReleaseKeys() {
   DoWalk(false);
   DoFwd(false);
   DoBck(false);
   DoRgt(false);
   DoLft(false);
}

void DoWalk(bool walk) {
   bool state_walking = (*EQADDR_RUNWALKSTATE) ? false : true;
   float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
   if (SpeedModifier < 0)
      walk = false; // we're snared, dont go into walk mode no matter what
   if ( (walk && !state_walking) || (!walk && state_walking) ) {
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),1,0);
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),0,0);
   }
}

void DoFwd(bool hold, bool walk) {
   static bool held = false;
   if ( hold ) {
      stickhasmovedfwd = true;
      DoWalk(walk);
      DoBck(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
      held = true;
   } else {
      DoWalk(false);
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
      held = false;
   }
}

void DoBck(bool hold) {
   static bool held = false;
   if( hold ) {
      DoFwd(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
      held = false;
   }
}

void DoLft(bool hold) {
   static bool held = false;
   if( hold ) {
      DoRgt(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
      held = false;
   }
}

void DoRgt(bool hold) {
   static bool held = false;
   if( hold ) {
      DoLft(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
      held = false;
   }
}

float getRand(float n)
{
   return (n * rand() / (RAND_MAX+1.0f));
}

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev)
{
   SYSTEMTIME stResult;
   FILETIME ftPrev, ftCurr, ftResult;
   ULARGE_INTEGER prev,curr,result;

   GetSystemTime(&stCurr);
   SystemTimeToFileTime(&stPrev,&ftPrev);
   SystemTimeToFileTime(&stCurr,&ftCurr);
   prev.HighPart = ftPrev.dwHighDateTime;
   prev.LowPart = ftPrev.dwLowDateTime;
   curr.HighPart = ftCurr.dwHighDateTime;
   curr.LowPart = ftCurr.dwLowDateTime;
   result.QuadPart = curr.QuadPart - prev.QuadPart;
   ftResult.dwHighDateTime = result.HighPart;
   ftResult.dwLowDateTime = result.LowPart;
   FileTimeToSystemTime(&ftResult,&stResult);

   return ((int)(stResult.wSecond * 1000 + stResult.wMilliseconds));
}

void stickText()
{
   char szTemp[MAX_STRING];

   if( stickVerbosity == 1 ) {
      if( stickPaused ) {
         WriteChatColor("Stick paused.");
      } else if( stickOn ) {
         if( stickHold ) {
            sprintf(szTemp,"You are now sticking to %s.",stickTarget->DisplayedName);
         } else if( ppTarget && pTarget ) {
            sprintf(szTemp,"You are now sticking to %s.",((PSPAWNINFO)pTarget)->DisplayedName);
         } else {
            sprintf(szTemp,"Need a target for stick.");
         }
         WriteChatColor(szTemp);
      } else {
         WriteChatColor("You are no longer sticking to anything.");
      }
   }
}

void breakStick(bool stopMoving, bool quite)
{
   stickOn=false;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   moveBehindOnce=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   bMoveToOn=false;
   bCircling=false;
   stuck=0;
   stickTarget=NULL;
   stickDistMod=0.0;
   stickDistModP=1.0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
   if (!quite)
      stickText();
}

class MQ2MoveToType *pMoveToType = 0;

class MQ2MoveToType : public MQ2Type
{
public:
   enum MoveToMembers {
      Moving=1,
      Stopped=2
   };

   MQ2MoveToType():MQ2Type("moveto")
   {
      TypeMember(Moving);
      TypeMember(Stopped);
   }

   ~MQ2MoveToType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MoveToType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MoveToMembers)pMember->ID)
      {
      case Moving:
         Dest.DWord=bMoveToOn;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
         Dest.DWord=(GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY)<=moveDist)?true:false;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if( bMoveToOn ) {
         strcpy(Destination,"ON");
      } else {
         strcpy(Destination,"OFF");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMoveTo(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMoveToType;
   return true;
}

VOID MoveToHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /moveto loc|off <y> <x> [<dist>|-<dist>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  You can not call '/moveto <y> <x>' while circling or sticking.",USERCOLOR_DEFAULT);
   WriteChatColor("  /moveto <dist>   - Moves you within <dist> units of your target, default is 50");
   WriteChatColor("  /moveto -<dist>  - Subtracts <dist> units from the move distance");
}

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0 || bCircling || stickOn) {
      MoveToHelp();
      return;
   } else if (!stricmp(szTemp,"loc")) {
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocY = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocX = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (strlen(szTemp)) {
         if( isdigit(szTemp[0]) || szTemp[0]=='.' ) {
            moveDist = (float)atof(szTemp);
         } else if( szTemp[0]=='-' ) {
            moveDistMod = (float)atof(szTemp);
            moveDist += moveDistMod;
         }
      }

      bMoveToOn=true;
      sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
      if (stickVerbosity)
         WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else if (!stricmp(szTemp,"off")) {
      bMoveToOn = false;
      if (stickVerbosity)
         WriteChatColor("MoveTo off.", CONCOLOR_YELLOW);
   } else {
      MoveToHelp();
      return;
   }
}

void HandleMoveTo()
{
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( !casting || !autoPauseEnabled ) {
      float newHeading;
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
          (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
          GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f)
             newHeading -= 512.0f;
          if( newHeading < 0.0f )
             newHeading += 512.0f;
          pChSpawn->Heading = newHeading;
      }
      if( GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist) {
         DoFwd(true);
      } else {
         bMoveToOn=false;
         if (stickVerbosity)
            WriteChatColor("Arrived at MoveTo location", CONCOLOR_YELLOW);
         DoFwd(false);
      }
   }
}

VOID CreateBinds(){
   if (MoveBindsLoaded)
      return;
   MoveBindsLoaded=true;
   AddMQ2KeyBind("UNSTICK_FWD",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_BCK",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_RGT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_RGT",DoUnstickBind);
   SetMQ2KeyBind("UNSTICK_FWD",false,pKeypressHandler->NormalKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_FWD",true,pKeypressHandler->AltKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_BCK",false,pKeypressHandler->NormalKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_BCK",true,pKeypressHandler->AltKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_right")]);
}

VOID DestroyBinds()
{
   if (!MoveBindsLoaded)
      return;
   RemoveMQ2KeyBind("UNSTICK_FWD");
   RemoveMQ2KeyBind("UNSTICK_BCK");
   RemoveMQ2KeyBind("UNSTICK_LFT");
   RemoveMQ2KeyBind("UNSTICK_RGT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_LFT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_RGT");
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2MoveUtils");

   // Add commands, macro parameters, hooks, etc.
   AddCommand("/circle",CircleCommand,0,1,1);
   AddCommand("/stick",StickCommand);
   AddMQ2Data("Stick",dataStick);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("MoveTo",dataMoveTo);

   pStickType = new MQ2StickType;

   srand((unsigned int)time(NULL));
   if (gGameState==GAMESTATE_INGAME) {
      CreateBinds();
   }
   GetSystemTime(&stPrevCirc);
   GetSystemTime(&stPrevStick);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("Shutting down MQ2MoveUtils");

   // Remove commands, macro parameters, hooks, etc.
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   RemoveMQ2Data("MoveTo");
   RemoveCommand("/moveto");

   delete pStickType;
   delete pMoveToType;

   DestroyBinds();
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if( breakOnGateEnabled && (stickHold?(stickTarget!=NULL):(ppTarget && pTarget)) ) {
      char szTemp[MAX_STRING];

      sprintf(szTemp,"%s Gates.",stickHold?stickTarget->DisplayedName:((PSPAWNINFO)pTarget)->DisplayedName);
      if( ! strcmp(szTemp,Line) ) {
         DoFwd(false);
         stickOn = false;
      }
   }

   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   if (GameState==GAMESTATE_INGAME) {
      CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      stickPaused=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
   }
}

// Called after entering a new zone
PLUGIN_API VOID OnZoned(VOID)
{
   DoWalk(false);
}


EDIT: Damn you....
 
Re: Fixed

"listed compile" meaning...the zip from the mq2 site or my compile? If you mean my compile, they're there
 
Re: Fixed

Sure :)

Rich (BB code):
// MQ2AutoSkills.cpp : Auto-activate various skills
//
// Updated by Cade 9/19/05
//
//   version 1.94
//
//   Project started 8/30/2004 by Cr4zyb4rd
//
// Commands:
//  /autoskills help - lists options
//
// INI file will be auto-created, MQ2AutoSkills_Charname_server.ini
// Syntax is simply Skill=on/off and MeleeRange=distance
//
// Currently suported skills:
//  Backstab, Bash, Begging, Disarm, Dragon Punch, Eagle Strike, Flying Kick,
//  Frenzy, Evade, Forage, Feign Death, Intimidation, Kick, Mend, Pick Pockets,
//  Round Kick, Sense Traps, Slam, Taunt, Tiger Claw
//
//  /attack off on mob ENRAGE/infuriate is also supported, treated as the "ENRAGE"
//  and "infuriate" skills in the INI.
//
// Known issues: Trade/Vendor windows and possibly some other events may cause some
// skills to fire when not appropriate and spam you
//
//////////////////////////////////////////////////////////////////////////////////////
#include "../MQ2Plugin.h"

#ifndef nSkillsNonInnate
#define nSkillsNonInnate            100
#endif
#ifndef nSkillsMax
#define nSkillsMax                  132
#endif

PSPAWNINFO psOldTarget = NULL;

bool MQ2ASenabled = false;

bool DoBackstab = false;
bool DoBash = false;
bool DoBegging = false;
bool DoDisarm = false;
bool DoDragonPunch = false;
bool DoEagleStrike = false;
bool DoENRAGE;
bool DoEvade = false;
bool DoForage = false;
bool DoFeignDeath = false;
bool DoFlyingKick = false;
bool DoFrenzy = false;
bool DoIntimidation = false;
bool DoInfuriate = false;
bool DoKick = false;
bool DoMend = false;
bool DoPickPockets = false;
bool DoRoundKick = false;
bool DoSenseTraps = false;
bool DoSlam = false;
bool DoTaunt = false;
bool DoTigerClaw = false;
bool DoingBegging = false;
bool DoingENRAGE = false;
bool DoingEvade = false;
bool DoingForage = false;
bool DoingInfuriate = false;
bool DoingPickPockets = false;
bool DoingFeignDeath = false;

float MeleeRange = 15;
int   BackOffHP  = 0;
int   DoMendHP  = 80;

PreSetup("MQ2AutoSkills");

//shamelessly stolen from MQ2MoveUtils
float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

void ASDoAbility(PCHAR Ability)
{
   if (!strncmp(szSkills[112],"Slam",4)) {
      DWORD Index, DoIndex, ListIndex = 0xFFFFFFFF;
      for (Index=0;Index<10;Index++) {
         ListIndex = EQADDR_DOABILITYLIST[Index];
         if (ListIndex!= 0xFFFFFFFF) {
            if (ListIndex>nSkillsNonInnate) ListIndex++;
            if (!strnicmp(Ability,szSkills[ListIndex],strlen(szSkills[ListIndex]))) {
               if (Index<4) {
                  DoIndex = Index+7; // 0-3 = Combat abilities (7-10)
               } else {
                  DoIndex = Index-3; // 4-9 = Abilities (1-6)
               }
            }
         }
      }
      if (DoIndex!=0xFFFFFFFF) {
         CHAR szBuffer[MAX_STRING];
         cmdDoAbility(GetCharInfo()->pSpawn,itoa(DoIndex,szBuffer,10));
      } else {
         WriteChatColor("You do not seem to have that ability on a /doability button",USERCOLOR_DEFAULT);
      }

   } else DoAbility(GetCharInfo()->pSpawn,Ability);
}

bool BoolFromINI(PCHAR Section, PCHAR Key)
{
   char szTemp[MAX_STRING];
   GetPrivateProfileString(Section,Key,"off",szTemp,MAX_STRING,INIFileName);
   if (!strnicmp(szTemp,"off",3)) {
      WritePrivateProfileString(Section,Key,"off",INIFileName);
      return false;
   }
   WritePrivateProfileString(Section,Key,"on",INIFileName);
   return true;
}

bool CheckAbilityReady(PCHAR szSkillName)
{
   for (DWORD nSkill=0;nSkill<=nSkillsMax;nSkill++) {
      if (!stricmp(szSkillName,szSkills[nSkill])) {
         // Check for old skills.h
         if (nSkill>nSkillsNonInnate && !strncmp(szSkills[112],"Slam",4)) nSkill++;
//         if (GetCharInfo()->Skill[nSkill]>252) return false;
//         for (DWORD nAbility=0;nAbility<10;nAbility++) {
//            if (EQADDR_DOABILITYLIST[nAbility] == nSkill) {
               if (nSkill>nSkillsNonInnate || SkillDict[nSkill]->AltTimer==2)
                  return gbAltTimerReady?true:false;
               else
                  return EQADDR_DOABILITYAVAILABLE[nSkill]?true:false;
//            }
//         }
      }
   }
   return false;
}

VOID LoadINI()
{
   char szTemp[MAX_STRING];
   char MeleeRangeTemp[MAX_STRING];

   DoBackstab = BoolFromINI("Skills","Backstab");
   DoBash = BoolFromINI("Skills","Bash");
   DoBegging = BoolFromINI("Skills","Begging");
   DoDisarm = BoolFromINI("Skills","Disarm");
   DoDragonPunch = BoolFromINI("Skills","DragonPunch");
   DoEagleStrike = BoolFromINI("Skills","EagleStrike");
   DoENRAGE = BoolFromINI("Skills","ENRAGE");
   DoEvade = BoolFromINI("Skills","Evade");
   DoFeignDeath = BoolFromINI("Skills","FeignDeath");
   DoForage = BoolFromINI("Skills","Forage");
   DoFlyingKick = BoolFromINI("Skills","FlyingKick");
   DoFrenzy = BoolFromINI("Skills","Frenzy");
   DoIntimidation = BoolFromINI("Skills","Intimidation");
   DoInfuriate = BoolFromINI("Skills","Infuriate");
   DoKick = BoolFromINI("Skills","Kick");
   DoMend = BoolFromINI("Skills","Mend");
   DoRoundKick = BoolFromINI("Skills","RoundKick");
   DoPickPockets = BoolFromINI("Skills","PickPockets");
   DoSenseTraps = BoolFromINI("Skills","SenseTraps");
   DoSlam = BoolFromINI("Skills","Slam");
   DoTaunt = BoolFromINI("Skills","Taunt");
   DoTigerClaw = BoolFromINI("Skills","TigerClaw");
   sprintf(MeleeRangeTemp,"%3.3f",MeleeRange);
   GetPrivateProfileString("Settings","MeleeRange",MeleeRangeTemp,szTemp,MAX_STRING,INIFileName);
   MeleeRange = (float)atof(szTemp);
   WritePrivateProfileString("Settings","MeleeRange",szTemp,INIFileName);
   BackOffHP = GetPrivateProfileInt("Settings","BackOffHP",0,INIFileName);
   sprintf(szTemp,"%d",BackOffHP);
   WritePrivateProfileString("Settings","BackOffHP",szTemp,INIFileName);
   DoMendHP = GetPrivateProfileInt("Settings","DoMendHP",80,INIFileName);
   sprintf(szTemp,"%d",DoMendHP);
   WritePrivateProfileString("Settings","DoMendHP",szTemp,INIFileName);
}   

VOID DisplayAutoSkillsHelp()
{
   WriteChatColor("MQ2AutoSkills - Auto-Use Abilities",USERCOLOR_DEFAULT);
   WriteChatColor("Usage:",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills list        - List available skills and whether they are enabled",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills settings    - List settings/thresholds",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills backoff [#] - %HP at which to not re-engage after FD/Evade",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills melee   [#] - Range to be considered 'in melee'",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills mendhp  [#] - %HP at which to use the mend ability",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills [skill]     - Toggle a skill on/off and update the INI",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills [skill] on  - Enable a skill",USERCOLOR_DEFAULT);
   WriteChatColor("/autoskills [skill] off - Disable a skill",USERCOLOR_DEFAULT);
}

VOID MQ2AutoSkillsCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char szTemp[MAX_STRING];
   char szTemp2[MAX_STRING];
   char szArg2[MAX_STRING];
   GetArg(szTemp,szLine,1);

   if (strlen(szTemp)==0 || !strnicmp(szTemp,"help",4)) {
      DisplayAutoSkillsHelp();
      return;
   }

   if (!strnicmp(szTemp,"backoff",7)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)) {
         BackOffHP=atoi(szTemp);
         sprintf(szTemp, "%d",BackOffHP);
         WritePrivateProfileString("Settings","BackOffHP",szTemp,INIFileName);
      }
      WriteChatf("MQ2AutoSkills:: Backing off at %d%%",BackOffHP);
      return;
   }

   if (!strnicmp(szTemp,"mendhp",7)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)) {
         DoMendHP=atoi(szTemp);
         sprintf(szTemp, "%d",DoMendHP);
         WritePrivateProfileString("Settings","DoMendHP",szTemp,INIFileName);
      }
      WriteChatf("MQ2AutoSkills:: Mending at %d%%",DoMendHP);
      return;
   }

   if (!strnicmp(szTemp,"melee",7)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)) {
         MeleeRange=(float)atof(szTemp);
         sprintf(szTemp, "%3.3f",MeleeRange);
         WritePrivateProfileString("Settings","MeleeRange",szTemp,INIFileName);
      }
      WriteChatf("MQ2AutoSkills:: Melee range set to  %3.3f%%",MeleeRange);
      return;
   }

   if (!strnicmp(szTemp,"settings",8)) {
      WriteChatf("MQ2AutoSkills:: Melee range set to  %3.3f%%",MeleeRange);
      WriteChatf("MQ2AutoSkills:: Backing off at %d%%",BackOffHP);
      WriteChatf("MQ2AutoSkills:: Mending at %d%%",DoMendHP);
      return;
   }

   if (!strnicmp(szTemp,"list", 4)) {
      WriteChatColor("MQ2AutoSkills:: Supported abilites and current status",CONCOLOR_RED);
      sprintf(szTemp," Backstab (%s)",DoBackstab?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Bash (%s)",DoBash?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Begging (%s)",DoBegging?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Disarm (%s)",DoDisarm?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," DragonPunch (%s)",DoDragonPunch?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," EagleStrike (%s)",DoEagleStrike?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," ENRAGE (%s)",DoENRAGE?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Evade (%s)",DoEvade?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," FeignDeath (%s)",DoFeignDeath?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Forage (%s)",DoForage?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," FlyingKick (%s)",DoFlyingKick?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Frenzy (%s)",DoFrenzy?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Intimidation (%s)",DoIntimidation?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Infuriate (%s)",DoInfuriate?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Kick (%s)",DoKick?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Mend (%s)",DoMend?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," PickPockets (%s)",DoPickPockets?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," RoundKick (%s)",DoRoundKick?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," SenseTraps (%s)",DoSenseTraps?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Slam (%s)",DoSlam?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," Taunt (%s)",DoTaunt?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      sprintf(szTemp," TigerClaw (%s)",DoTigerClaw?"\agon\ax":"\aroff\ax");
      WriteChatColor(szTemp,USERCOLOR_WHO);
      return;
   }

   // Check if entry exists in INI
   GetPrivateProfileString("Skills",szTemp,"NULL",szTemp2,MAX_STRING,INIFileName);

   if (!strnicmp(szTemp2,"NULL", 4)) { // unsupported skill
      WriteChatColor("MQ2AutoSkills::Unsupported argument",CONCOLOR_RED);
      WriteChatColor("Use '/autoskills list' for a list",CONCOLOR_RED);
      return;
   }

   GetArg(szArg2,szLine,2);
   if ((!strlen(szArg2) && !strnicmp(szTemp2,"off",3)) || !strnicmp(szArg2,"on",2)) {
      WritePrivateProfileString("Skills",szTemp,"on",INIFileName);
      sprintf(szTemp2,"MQ2AutoSkills::auto %s is now ON",szTemp);
      WriteChatColor(szTemp2,CONCOLOR_YELLOW);
   } else if ((!strlen(szArg2) && !strnicmp(szTemp2,"on",2)) || !strnicmp(szArg2,"off",3)) {
      WritePrivateProfileString("Skills",szTemp,"off",INIFileName);
      sprintf(szTemp2,"MQ2AutoSkills::auto %s is now OFF",szTemp);
      WriteChatColor(szTemp2,CONCOLOR_YELLOW);
   } else {
      DisplayAutoSkillsHelp();
      return;
   }
   LoadINI();
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2AutoSkills");

   AddCommand("/autoskills",MQ2AutoSkillsCommand);
   if (MQ2Globals::gGameState==GAMESTATE_INGAME) {
      if (GetCharInfo()) {
         sprintf(INIFileName,"%s\\MQ2AutoSkills_%s_%s.ini",gszINIPath,GetCharInfo()->Name,EQADDR_SERVERNAME);
         LoadINI();
         MQ2ASenabled=true;
         return;
      }
   } else MQ2ASenabled = false;

}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("Shutting down MQ2AutoSkills");

   RemoveCommand("/autoskills");
}

// Called every frame that the "HUD" is drawn -- e.g. net status / packet loss bar
PLUGIN_API VOID OnDrawHUD(VOID)
{
   // DONT leave in this debugspew, even if you leave in all the others
   //   DebugSpewAlways("MQ2AutoSkills::OnDrawHUD()");
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   DebugSpewAlways("MQ2AutoSkills::SetGameState()");
   if (GameState==GAMESTATE_INGAME) {
      if (GetCharInfo()) {
         sprintf(INIFileName,"%s\\MQ2AutoSkills_%s_%s.ini",gszINIPath,GetCharInfo()->Name,EQADDR_SERVERNAME);
         LoadINI();
         MQ2ASenabled=true;
         return;
      }
   } else MQ2ASenabled = false;
}

BOOL IsWindowOpen(PCHAR WindowName)
{
   PCSIDLWND pWnd=(PCSIDLWND)FindMQ2Window(WindowName);
   if (!pWnd) return false;
   return (BOOL)pWnd->Show;
}

PLUGIN_API VOID OnPulse(VOID)
{
   if (!MQ2ASenabled) return;

   bool mounted;

   PSPAWNINFO psTarget = NULL;
   PSPAWNINFO pChSpawn = GetCharInfo()->pSpawn;
   BYTE MyStandstate = GetCharInfo()->standstate;
   int pctHP = pChSpawn->HPCurrent*100/pChSpawn->HPMax;

   if (psOldTarget!=(PSPAWNINFO)pTarget && (DoingENRAGE || DoingInfuriate)) {
      WriteChatColor("MQ2AutoSkills::Target changed, ENRAGE/Infuriate monitors reset.",CONCOLOR_YELLOW);
      DoingENRAGE = false;
      DoingInfuriate = false;
   }

   if (GetCharInfo()->Stunned || MyStandstate==STANDSTATE_DEAD || IsWindowOpen("BigBankWnd") || IsWindowOpen("MerchantWnd") || IsWindowOpen("TradeWnd")) return;

   if (DoMend && pctHP < DoMendHP && CheckAbilityReady("Mend")) ASDoAbility("Mend");

   //check if stunned, feigned, ducking, dead, binding, sitting
   if (MyStandstate==STANDSTATE_FEIGN && DoingFeignDeath) {
      if (pctHP > BackOffHP) {
         HideDoCommand(pChSpawn,"/stand",FromPlugin);
         if (psOldTarget==(PSPAWNINFO)pTarget) HideDoCommand(pChSpawn,"/attack on",FromPlugin);
      } else WriteChatf("MQ2AutoSkills::HP lower than %d%%, staying down",BackOffHP);
      DoingFeignDeath = 0;
      return;
   }

   // check if not standing
   if (MyStandstate!=STANDSTATE_STAND) return;

   // check if mounted
   mounted = pChSpawn->pActorInfo && pChSpawn->pActorInfo->Mount ? true : false;

   //check if casting (and not a bard)
   if ((pChSpawn->pActorInfo->CastingSpellID!=-1) && strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class),"Bard",5)) return;

   if (DoSenseTraps && CheckAbilityReady("Sense Traps")) ASDoAbility("\"Sense Traps\"");
   if (!mounted && !DoingForage && DoForage && !*EQADDR_ATTACK && CheckAbilityReady("Forage")) {
      Sleep(50);
      DoAbility(pChSpawn,"Forage");
   }

   //check for a target
   if (ppTarget && pTarget) {
      psTarget = (PSPAWNINFO)pTarget;
   } else return;
   

   //make sure target isn't me
   if (psTarget!=pChSpawn) {
   } else return;

   //check melee distance
   if (GetDistance(pChSpawn,psTarget) < MeleeRange) {
   } else return;

   //check if in combat
   if (*EQADDR_ATTACK) {
   } else if (DoingBegging || DoingEvade || DoingPickPockets || DoingFeignDeath || DoingForage) {
      if (DoingBegging){
         DoAbility(pChSpawn,"Begging");
         DoingBegging = 0;
      }
      if (DoingEvade){
         DoAbility(pChSpawn,"Hide");
      }
      if (DoingPickPockets){
         DoAbility(pChSpawn,"\"Pick Pockets\"");
         DoingPickPockets =0;
      }
      if (DoingFeignDeath){
         DoAbility(pChSpawn,"\"Feign Death\"");
         return; //no attack on here
      }
      if (DoingForage){
         DoAbility(pChSpawn,"Forage");
         DoingForage=0;
      }
      if (psOldTarget==(PSPAWNINFO)pTarget) {
         if (DoingEvade && pctHP < BackOffHP) {
            WriteChatf("MQ2AutoSkills::HP lower than %d%%, staying hidden",BackOffHP);
         } else HideDoCommand(pChSpawn,"/attack on",FromPlugin);
      }
      DoingEvade =0;
      psOldTarget = psTarget;
   } else return;

   if (DoBash && CheckAbilityReady("Bash")) ASDoAbility("Bash");
   if (DoDisarm && CheckAbilityReady("Disarm")) ASDoAbility("Disarm");
   if (DoDragonPunch && CheckAbilityReady("Dragon Punch")) ASDoAbility("\"Dragon Punch\"");
   if (DoEagleStrike && CheckAbilityReady("Eagle Strike")) ASDoAbility("\"Eagle Strike\"");
   if (DoFlyingKick && CheckAbilityReady("Flying Kick")) ASDoAbility("\"Flying Kick\"");
   if (DoFrenzy && CheckAbilityReady("Frenzy")) ASDoAbility("\"Frenzy\"");
   if (pChSpawn!=pChSpawn->pActorInfo->pTargetOfTarget && DoTaunt && CheckAbilityReady("Taunt")) ASDoAbility("Taunt");
   if (DoIntimidation && CheckAbilityReady("Intimidation")) ASDoAbility("Intimidation");
   if (DoKick && CheckAbilityReady("Kick")) ASDoAbility("Kick");
   if (DoRoundKick && CheckAbilityReady("Round Kick")) ASDoAbility("\"Round Kick\"");
   if (DoSlam && CheckAbilityReady("Slam")) ASDoAbility("Slam");
   if (DoTigerClaw && CheckAbilityReady("Tiger Claw")) ASDoAbility("\"Tiger Claw\"");

   // These skills need attack to be turned off
   if (DoBegging && CheckAbilityReady("Begging")) {
      HideDoCommand(pChSpawn, "/attack off",FromPlugin);
      DoingBegging = 1;
   }
   if (DoEvade && CheckAbilityReady("Hide") && FindSpeed(pChSpawn)==0) {
      HideDoCommand(pChSpawn, "/attack off",FromPlugin);
      DoingEvade = 1;
   }
   if (DoPickPockets && CheckAbilityReady("Pick Pockets")) {
      HideDoCommand(pChSpawn, "/attack off",FromPlugin);
      DoingPickPockets = 1;
   }
   if (DoFeignDeath && CheckAbilityReady("Feign Death")) {
      HideDoCommand(pChSpawn, "/attack off",FromPlugin);
      DoingFeignDeath = 1;
   }
   if (DoForage && CheckAbilityReady("Forage")) {
      HideDoCommand(pChSpawn, "/attack off",FromPlugin);
      DoingForage = 1;
   }
   psOldTarget = psTarget;

   //check if behind target
   if (fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) < 45.0) {
   } else return;

   if (DoBackstab && CheckAbilityReady("Backstab")) ASDoAbility("Backstab");
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if (MQ2Globals::gGameState != GAMESTATE_INGAME || !MQ2ASenabled) return 0;
   // DebugSpewAlways("MQ2AutoSkills::OnIncomingChat(%s)",Line);
   if(Color == USERCOLOR_NPC_ENRAGE && (DoENRAGE || DoInfuriate) && ppTarget && pTarget) {
        WriteChatColor(Line,CONCOLOR_YELLOW);
      PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;   
      if (strncmp(Line,psTarget->DisplayedName,strlen(psTarget->DisplayedName))||(strstr(Line,"pet")&&!strstr(psTarget->DisplayedName,"pet"))) return 0; // not our target
      if (DoENRAGE && strstr(Line,"has become ENRAGED")) {
         WriteChatColor("MQ2AutoSkills::\arENRAGE\ax detected, turning attack off.",CONCOLOR_YELLOW);
         HideDoCommand(GetCharInfo()->pSpawn, "/attack off",FromPlugin);
         DoingENRAGE = true;
         psOldTarget = psTarget;
         return 0;
      }
      if (DoInfuriate && strstr(Line,"is infuriated"))  {
         WriteChatColor("MQ2AutoSkills::\arInfuriate\ax detected, turning attack off.",CONCOLOR_YELLOW);
         HideDoCommand(GetCharInfo()->pSpawn, "/attack off",FromPlugin);
         DoingInfuriate = true;
         psOldTarget = psTarget;
         return 0;
      }
      if (strstr(Line,"is no longer enraged")) {
         if (psOldTarget==(PSPAWNINFO)pTarget && DoingENRAGE) {
            if (!DoingInfuriate) {
               WriteChatColor("MQ2AutoSkills::ENRAGE/infuriate ended, resuming attack.",CONCOLOR_YELLOW);
               HideDoCommand(GetCharInfo()->pSpawn, "/attack on",FromPlugin);
            } else {
               WriteChatColor("MQ2AutoSkills::\arENRAGE\ax ended, but mob still seems infuriated.",CONCOLOR_YELLOW);
            }
            DoingENRAGE = false;
         }
         return 0;
      }
      if (strstr(Line,"no longer infuriated")) {
         if (psOldTarget==(PSPAWNINFO)pTarget && DoingInfuriate) {
            if (!DoingENRAGE) {
               WriteChatColor("MQ2AutoSkills::ENRAGE/infuriate ended, resuming attack.",CONCOLOR_YELLOW);
               HideDoCommand(GetCharInfo()->pSpawn, "/attack on",FromPlugin);
            } else {
               WriteChatColor("MQ2AutoSkills::\arInfuriate\ax ended, but mob still seems ENRAGED.",CONCOLOR_YELLOW);
            }
            DoingInfuriate = false;
         }
         return 0;
      }
   }            
   return 0;
}
 
Re: Fixed

For some reason both of the Moveutils posted gave me a bunch of errors, but this compiled fine.
Rich (BB code):
#include "../MQ2Plugin.h"

PreSetup("MQ2MoveUtils");
CHAR szVersion[30]="MQ2MoveUtils Version 20050714";
CHAR szMsg[MAX_STRING]={0};

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine);
float getRand(float n);

bool bCircling=false;
bool bDrunken=false;
float CircleX=0.0f;
float CircleY=0.0f;
float CircleRadius=0.0f;
SYSTEMTIME stPrevCirc;

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void ReleaseKeys();
void DoWalk(bool walk = false);
void DoFwd(bool hold, bool walk = false);
void DoBck(bool hold);
void DoLft(bool hold);
void DoRgt(bool hold);
void Load_INI(void);
bool IsBardClass(void);
void stickText();
void breakStick(bool stopMoving = true, bool quite = false);
float angularDistance(float h1, float h2);

bool MoveBindsLoaded=false;
bool stickOn=false;
bool setDist=false;
bool stickPaused=false;
bool stickHold=false;
bool moveBehind=false;
bool moveBehindOnce=false;
bool moveBack=false;
bool movePin=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=false;
bool stickhasmovedfwd=false;
short stickVerbosity=1;
short keysDown=0;
float stickDist=0.0;
float breakDist=250.0;
float currentDist=0.0;
float stickDistMod=0.0;
float stickDistModP=1.0;
PSPAWNINFO stickTarget;
eSpawnType stickTarget_Type;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

// stuck added by Outlander
int stuck=0;
int stuckCheck=0;
int stuckFree=0;
float prevX=0.0f;
float prevY=0.0f;
float prevZ=0.0f;
float stuckDist=0.0f;
float turnDirection=0.0f;
float pulseAvg=0.0f;
bool stuckLogic;

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMoveTo();
bool bMoveToOn=false;
float LocX=0.0f;
float LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

class MQ2StickType *pStickType = 0;

class MQ2StickType : public MQ2Type
{
public:
   enum StickMembers {
      Status=1,
      Active=2,
      Distance=3,
      MoveBehind=4,
      MovePause=5,
      MoveBack=6,
      Loose=7,
      Paused=8,
      Behind=9,
      Stopped=10,
      Pin=11,
   };

   MQ2StickType():MQ2Type("stick")
   {
      TypeMember(Status);
      TypeMember(Active);
      TypeMember(Distance);
      TypeMember(MoveBehind);
      TypeMember(MovePause);
      TypeMember(MoveBack);
      TypeMember(Loose);
      TypeMember(Paused);
      TypeMember(Behind);
      TypeMember(Stopped);
      TypeMember(Pin);
   }

   ~MQ2StickType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2StickType::FindMember(Member);
      if (!pMember)
         return false;
      switch((StickMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( stickOn ) {
            strcpy(DataTypeTemp,"ON");
         }
         if( stickPaused ) {
            strcpy(DataTypeTemp,"PAUSED");
         }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Active:
         Dest.DWord=stickOn;
         Dest.Type=pBoolType;
         return true;
      case Distance:
         Dest.Float=stickDist;
         Dest.Type=pFloatType;
         return true;
      case MoveBehind:
         Dest.DWord=moveBehind;
         Dest.Type=pBoolType;
         return true;
      case MovePause:
         Dest.DWord=mPause;
         Dest.Type=pBoolType;
         return true;
      case MoveBack:
         Dest.DWord=moveBack;
         Dest.Type=pBoolType;
         return true;
      case Loose:
         Dest.DWord=looseStick;
         Dest.Type=pBoolType;
         return true;
      case Paused:
         Dest.DWord=stickPaused;
         Dest.Type=pBoolType;
         return true;
      case Behind:
         if (ppTarget && pTarget) {
            PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget) > stickDist || fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 )?false:true;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         if( ppTarget && pTarget ) {
            PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget)<=stickDist)?true:false;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Pin:
         Dest.DWord=movePin;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( stickOn ) {
         strcpy(Destination,"ON");
      }
      if( stickPaused ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataStick(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pStickType;
   return true;
}

VOID CircleHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /circle on|off|drunken <radius> [<y> <x>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are optional, if not specified will use your currect loc.",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while not circling, it will start with your current loc and specified radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while already circling, it will update with your new loc and radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on' while already circling, it will update with your new loc using original radius.",USERCOLOR_DEFAULT);
}

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0) {
      CircleHelp();
      return;
   } else if (!stricmp(szTemp,"on") || !stricmp(szTemp,"drunken")) {
      if( !stricmp(szTemp,"drunken") )
         bDrunken=true;
      else
         bDrunken=false;
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         if (!bCircling) {
            CircleHelp();
            return;
         } else if (!CircleRadius) {
            // /circle on was called while we were already circling, but no radius was defined. oddddddd.
            CircleHelp();
            return;
         }
      } else {
         CircleRadius = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         CircleY = pChSpawn->Y + CircleRadius * (float)sin(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleY = (float)atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (!strlen(szTemp)) {
         CircleX = pChSpawn->X - CircleRadius * (float)cos(pChSpawn->Heading * (float)PI / 256.0);
      } else {
         CircleX = (float)atof(szTemp);
      }

      if (CircleRadius) bCircling = true;
      sprintf(szMsg, "Circling Radius %g, center %g %g", CircleRadius, CircleY, CircleX);
      WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else {
      if (!stricmp(szTemp,"off")) {
         bCircling = false;
      }
   }

}

void StickHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload|id] [<dist>|<spawnid>] [behind|behindonce|pin] [mpause] [moveback] [loose] [-<dist>] [<perc>%] [uw]");
   WriteChatColor("   /stick            - Sticks you within melee range of your target");
   WriteChatColor("   /stick hold       - Stores your current target, sticks to it even if you lose/change target");
   WriteChatColor("   /stick off        - Breaks off from stick (moving manually also breaks off from stick");
   WriteChatColor("   /stick pause      - Pauses following (can move normally while paused)");
   WriteChatColor("   /stick unpause    - Resumes following");
   WriteChatColor("   /stick reload     - Reload values from ini file");
   WriteChatColor("   /stick <dist>     - Sticks you within <dist> units of your target");
   WriteChatColor("   /stick behind     - Keeps you behind your target");
   WriteChatColor("   /stick behindonce - Moves behind your target once");
   WriteChatColor("   /stick pin        - Keeps you to the side of your target");
   WriteChatColor("   /stick mpause     - Causes manual movement to pause stick instead of breaking it");
   WriteChatColor("   /stick moveback   - Moves character back to try to stay at exactly stick distance");
   WriteChatColor("   /stick loose      - Checks distance/angle less often, and turns slower, for a more human-controlled look");
   WriteChatColor("   /stick uw         - Looks up or down to track target, useful for underwater /stick");
   WriteChatColor("   /stick -<dist>    - Substracts <dist> from the stick distance");
   WriteChatColor("   /stick <perc>%    - Multiplies stick distance by <perc> percent");
   WriteChatColor("   /stick id <spawn> - Sticks to the pc/npc with the spawn ID");
}

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char currentArg[MAX_STRING];
   int argn=1;

   srand ( time(NULL));
   int trand = rand()%100;
   turnDirection *= (trand>50)?1.0f:-1.0f;

   GetArg(currentArg,szLine,argn++);

   if( !strncmp(currentArg,"pause",6) ) {
      ReleaseKeys();
      stickPaused = true;
      return;
   } else if( !strncmp( currentArg,"unpause",8 ) ) {
      stickPaused = false;
      return;
   }

   if (!stickOn)
      DoWalk(false);
   ReleaseKeys();
   stickOn=true;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   moveBehindOnce=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickhasmovedfwd=false;
   stuck=0;
   stickTarget=NULL;
   stickTarget_Type=NONE;
   stickDistMod=0.0f;
   stickDistModP=1.0f;


   while( *currentArg ) {
      if( !strncmp(currentArg,"on",3) ) {
         stickOn = true;
      } else if( strstr(currentArg,"%") ) {
         stickDistModP = (float)atof(currentArg) / 100.0f;
         if( setDist )
            stickDist *= stickDistModP;
         stickOn = true;
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.' ) {
         setDist = true;
         stickDist = (float)atof(currentArg) * stickDistModP + stickDistMod;
         stickOn = true;
      } else if( currentArg[0]=='-' ) {
         stickDistMod = (float)atof(currentArg);
         if( setDist )
            stickDist += stickDistMod;
         stickOn = true;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"moveback",9) ) {
         moveBack = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"loose",6) ) {
         looseStick = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"uw",3 ) ) {
         underwater=true;
         stickOn=true;
      } else if( !strncmp(currentArg,"off",4) ) {
         breakStick();
         break;
      } else if( !strncmp(currentArg,"hold",5) ) {
         stickOn = true;
         if( ppTarget && pTarget ) {
         if (((PSPAWNINFO) pTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
            WriteChatColor("You cannot stick hold to yourself.");
            breakStick(true, true);
            return;
         }
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
         stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
         }
      } else if( !strncmp(currentArg,"id",3) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]) ) {
             stickTarget = (PSPAWNINFO) GetSpawnByID(atoi(currentArg));
             if( stickTarget ) {
                 if (((PSPAWNINFO) stickTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
                    WriteChatColor("You cannot stick id to yourself.");
                    breakStick(true, true);
                    return;
                 }
                 stickOn = true;
                 stickHold = true;
                 stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
             }
          } else {
              WriteChatColor("When using ID the next parameter MUST be the spawn ID.");
              breakStick(true, true);
              return;
          }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn=true;
         moveBehind=true;
       moveBehindOnce=false;
       movePin=false;
      } else if( !strncmp(currentArg,"behindonce",11) ) {
         stickOn=true;
         moveBehindOnce=true;
       moveBehind=false;
       movePin=false;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn=true;
         movePin=true;
       moveBehind=false;
       moveBehindOnce=false;
      } else if( !strncmp(currentArg,"reload",7) ) {
         breakStick();
         Load_INI();
         WriteChatColor("Ini file reloaded.");
         break;
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      stickHold = stickHold && stickTarget && stickTarget->SpawnID;
      if( (ppTarget && pTarget) || (stickHold && stickTarget && stickTarget->SpawnID) ) {
         if( FindSpeed((PSPAWNINFO)pCharSpawn) < 0 ) {
            DoFwd(false);
         }
         currentDist=GetDistance((PSPAWNINFO)pCharSpawn,stickHold?stickTarget:((PSPAWNINFO)pTarget));
      }
      stickText();
   }
}

VOID DoUnstickBind(PCHAR Name, BOOL Down)
{
   if( ! Down ) {
      keysDown--;
      if( keysDown == 0 && mPause ) {
         stickPaused = false;
         moveBehind = prevMoveBehind;
         movePin = prevMovePin;
      }
   } else {
      keysDown++;
      if((!stickPaused && stickOn) || bMoveToOn || bCircling) {
         if( mPause && strncmp(Name,"UNSTICK_STRAFE_RGT",12) && strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            stickPaused = true;
         } else if( !strncmp(Name,"UNSTICK_STRAFE_RGT",12) || !strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            if( mPause ) {
               prevMoveBehind = moveBehind || prevMoveBehind;
               prevMovePin = movePin || prevMovePin;
               moveBehind = false;
            } else {
               prevMoveBehind = moveBehind = false;
               prevMovePin = movePin = false;
            }
         } else {
            breakStick((strncmp(Name,"UNSTICK_BCK",12) && strncmp(Name,"UNSTICK_FWD",12)));
         }
      }
   }
}

VOID HandleCircle() {
   static int counter = 0;
   float distance;
   float heading;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState !=

STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if (!GetCharInfo() || !bCircling) return;

   float X = pChSpawn->X - CircleX;
   float Y = pChSpawn->Y - CircleY;
   distance = sqrt(X*X + Y*Y);

   if( !casting || !autoPauseEnabled ) {
      if (distance>(CircleRadius*(2.0/3.0))) {
         float newHeading;
         float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
         if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
         prevX=pChSpawn->X;
         prevY=pChSpawn->Y;
         prevZ=pChSpawn->Z;
   
         float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
         if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
             (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
              GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
             stuck++;
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading + turnDirection);
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 pChSpawn->Heading = newHeading;

                 //check to see if we are heading directly away from our target if so then go back
                 newHeading=(float) atan2(pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
                 newHeading += (float) 90.0f * (CircleRadius/distance);
                 newHeading *= 512.0f/360.0f;
                 if( newHeading >= 512.0f ) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;

                 newHeading += 265.0f;
                 if( newHeading >= 512.0f) newHeading -= 512.0f;
                 if( newHeading < 0.0f ) newHeading += 512.0f;
                 if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                     newHeading -= 265.0f;
                     if( newHeading >= 512.0f) newHeading -= 512.0f;
                     if( newHeading < 0.0f ) newHeading += 512.0f;
                     pChSpawn->Heading = newHeading;
                     stuck = stuckCheck;
                     turnDirection *= -1.0f;
                 }
             }
             stuckFree=0;

         } else if( stuck > 0 ) {
             if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                 newHeading =(float) ( pChSpawn->Heading - turnDirection );
              pChSpawn->Heading = newHeading;
             }
             stuck--;
             if(stuckFree++ > stuckCheck*3 ) {
                 stuck=0;
             }
         } else {
            heading=(float) atan2( pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
            heading += 90.0f * (CircleRadius/distance);
            heading *= 512.0f/360.0f;
            if( heading >= 512.0f ) heading -= 512.0f;
            if( heading < 0.0f ) heading += 512.0f;
            if( bDrunken ) {
               gFaceAngle = (float)heading;
            } else {
               pChSpawn->Heading = (float)heading;
            }
         }
         DoFwd(true);
      }
   }
}

float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

void HandleStick()
{
   if( (stickHold && (!stickTarget || !(stickTarget->SpawnID) || stickTarget_Type!=GetSpawnType(stickTarget))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
      return;
   }
   PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !psTarget || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   float newHeading=0.0f;
   float prevDist = currentDist;

   if( !setDist )
      stickDist = (psTarget->StandState?get_melee_range(pLocalPlayer,(EQPlayer *)psTarget):15.0f) * stickDistModP + stickDistMod;

   currentDist=GetDistance(pChSpawn,psTarget);
   if( breakOnWarpEnabled && (currentDist-prevDist) > breakDist ) {
      breakStick();
      return;
   }
   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState !=

STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 || psTarget->SpawnID == pChSpawn->SpawnID ) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( (!stickPaused && !casting) || !autoPauseEnabled ) {
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
          (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
          ((currentDist - stickDist) > 10.0) && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f) newHeading -= 512.0f;
          if( newHeading < 0.0f ) newHeading += 512.0f;
          if (stickhasmovedfwd && fabs(pChSpawn->Heading - newHeading) >= 200 && currentDist < 10) {
             DoBck(true);
             return;
          } else {
             DoBck(false);
          }
          if( looseStick ) {
             gFaceAngle = newHeading;
          } else {
             pChSpawn->Heading = newHeading;
          }

          //underwater = pChSpawn->pActorInfo->UnderWater==5;
          if( underwater ) {
             double lookAngle = (float) atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
             pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
             currentDist) * 256.0f / (float)PI;
             if ( looseStick ) {
                gLookAngle = lookAngle;
             } else {
                pChSpawn->CameraAngle = (FLOAT)lookAngle;
             }
          }
          if ( currentDist > (stickDist * 3) ) {
             // too far away, dont do pin or behind
          } else if( moveBehind || moveBehindOnce ) {
             float angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if( fabs(angDist) > 45.0 ) {
                if(angDist < 0.0) {
                   // strafe left
                   DoLft(true);
                } else {
                   // strage right
                   DoRgt(true);
                }
             } else {
                moveBehindOnce = false;
                DoLft(false);
                DoRgt(false);
             }
          } else if (movePin) {
             FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
             if((angDist > 0 && angDist <= 112) || angDist < -144) {
                DoLft(true);
             } else if ((angDist < 0 && angDist > -112) || angDist > 144) {
                DoRgt(true);
             } else {
                DoLft(false);
                DoRgt(false);
             }
          }
      }
      if(pChSpawn->StandState == STANDSTATE_DUCK && stuckLogic ) pChSpawn->StandState = STANDSTATE_STAND;
      if( currentDist > stickDist) {
         // if distance is less than 10, walk
         DoFwd(true, ((currentDist - stickDist) <= 10.0));
      } else if( moveBack && currentDist < (stickDist-5.0) ) {
         DoBck(true);
      } else {
         DoFwd(false);
         DoBck(false);
      }
   }
}

void
Load_INI(VOID)
{
   char szTemp[MAX_STRING], szTemp2[MAX_STRING];

   // Defaults
   GetPrivateProfileString("Defaults","AutoPause","on",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",autoPauseEnabled?"on":"off");
   WritePrivateProfileString("Defaults","AutoPause",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnWarp","on",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnWarpEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnWarp",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakDist","250.0",szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Defaults","BreakDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnGate","on",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnGateEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnGate",szTemp,INIFileName);

   stickVerbosity=(short)GetPrivateProfileInt("Defaults","Verbosity",1,INIFileName);
   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Defaults","Verbosity",szTemp,INIFileName);

   // Character specific
   GetPrivateProfileString(GetCharInfo()->Name,"AutoPause",autoPauseEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnWarp",breakOnWarpEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",breakDist);
   GetPrivateProfileString(GetCharInfo()->Name,"BreakDist",szTemp2,szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnGate",breakOnGateEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);

   GetPrivateProfileString("Defaults","stuckDist","0.8",szTemp,MAX_STRING,INIFileName);
   stuckDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",stuckDist);
   WritePrivateProfileString("Defaults","stuckDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","turnDirection","10.0",szTemp,MAX_STRING,INIFileName);
   turnDirection = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",turnDirection);
   WritePrivateProfileString("Defaults","turnDirection",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","stuckCheck","5",szTemp,MAX_STRING,INIFileName);
   stuckCheck = (int)atoi(szTemp);
   sprintf(szTemp,"%i",stuckCheck);
   WritePrivateProfileString("Defaults","stuckCheck",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","StuckLogic","on",szTemp,MAX_STRING,INIFileName);
   stuckLogic=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",stuckLogic?"on":"off");
   WritePrivateProfileString("Defaults","StuckLogic",szTemp,INIFileName);
}

bool IsBardClass()
{
   if(strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class),"Bard",5))
      return false;
   else
      return true;
}

PLUGIN_API VOID OnPulse(VOID)
{
//  *((float *)&(((PSPAWNINFO)pLocalPlayer)->pActorInfo->Unknown0x0af[25])) = 0.95f;
   if (bCircling) {
      if( bDrunken ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevCirc) > 900 + (int)getRand(600.0) ) {
            GetSystemTime(&stPrevCirc);
            HandleCircle();
         }
      } else {
         HandleCircle();
      }
   }
   if( stickOn ) {
      if( looseStick ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevStick) > 100 + (int)getRand(200.0) ) {
            GetSystemTime(&stPrevStick);
            HandleStick();
         }
      } else {
         HandleStick();
      }
   }
   if (bMoveToOn) {
      HandleMoveTo();
   }
}

void ReleaseKeys() {
   DoWalk(false);
   DoFwd(false);
   DoBck(false);
   DoRgt(false);
   DoLft(false);
}

void DoWalk(bool walk) {
   bool state_walking = (*EQADDR_RUNWALKSTATE) ? false : true;
   float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
   if (SpeedModifier < 0)
      walk = false; // we're snared, dont go into walk mode no matter what
   if ( (walk && !state_walking) || (!walk && state_walking) ) {
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),1,0);
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),0,0);
   }
}

void DoFwd(bool hold, bool walk) {
   static bool held = false;
   if ( hold ) {
      stickhasmovedfwd = true;
      DoWalk(walk);
      DoBck(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
      held = true;
   } else {
      DoWalk(false);
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
      held = false;
   }
}

void DoBck(bool hold) {
   static bool held = false;
   if( hold ) {
      DoFwd(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
      held = false;
   }
}

void DoLft(bool hold) {
   static bool held = false;
   if( hold ) {
      DoRgt(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
      held = false;
   }
}

void DoRgt(bool hold) {
   static bool held = false;
   if( hold ) {
      DoLft(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
      held = false;
   }
}

float getRand(float n)
{
   return (n * rand() / (RAND_MAX+1.0f));
}

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev)
{
   SYSTEMTIME stResult;
   FILETIME ftPrev, ftCurr, ftResult;
   ULARGE_INTEGER prev,curr,result;

   GetSystemTime(&stCurr);
   SystemTimeToFileTime(&stPrev,&ftPrev);
   SystemTimeToFileTime(&stCurr,&ftCurr);
   prev.HighPart = ftPrev.dwHighDateTime;
   prev.LowPart = ftPrev.dwLowDateTime;
   curr.HighPart = ftCurr.dwHighDateTime;
   curr.LowPart = ftCurr.dwLowDateTime;
   result.QuadPart = curr.QuadPart - prev.QuadPart;
   ftResult.dwHighDateTime = result.HighPart;
   ftResult.dwLowDateTime = result.LowPart;
   FileTimeToSystemTime(&ftResult,&stResult);

   return ((int)(stResult.wSecond * 1000 + stResult.wMilliseconds));
}

void stickText()
{
   char szTemp[MAX_STRING];

   if( stickVerbosity == 1 ) {
      if( stickPaused ) {
         WriteChatColor("Stick paused.");
      } else if( stickOn ) {
         if( stickHold ) {
            sprintf(szTemp,"You are now sticking to %s.",stickTarget->DisplayedName);
         } else if( ppTarget && pTarget ) {
            sprintf(szTemp,"You are now sticking to %s.",((PSPAWNINFO)pTarget)->DisplayedName);
         } else {
            sprintf(szTemp,"Need a target for stick.");
         }
         WriteChatColor(szTemp);
      } else {
         WriteChatColor("You are no longer sticking to anything.");
      }
   }
}

void breakStick(bool stopMoving, bool quite)
{
   stickOn=false;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   moveBehindOnce=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   bMoveToOn=false;
   bCircling=false;
   stuck=0;
   stickTarget=NULL;
   stickDistMod=0.0;
   stickDistModP=1.0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
   if (!quite)
      stickText();
}

class MQ2MoveToType *pMoveToType = 0;

class MQ2MoveToType : public MQ2Type
{
public:
   enum MoveToMembers {
      Moving=1,
      Stopped=2
   };

   MQ2MoveToType():MQ2Type("moveto")
   {
      TypeMember(Moving);
      TypeMember(Stopped);
   }

   ~MQ2MoveToType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MoveToType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MoveToMembers)pMember->ID)
      {
      case Moving:
         Dest.DWord=bMoveToOn;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
         Dest.DWord=(GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY)<=moveDist)?true:false;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if( bMoveToOn ) {
         strcpy(Destination,"ON");
      } else {
         strcpy(Destination,"OFF");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMoveTo(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMoveToType;
   return true;
}

VOID MoveToHelp()
{
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   WriteChatColor("Usage: /moveto loc|off <y> <x> [<dist>|-<dist>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  You can not call '/moveto <y> <x>' while circling or sticking.",USERCOLOR_DEFAULT);
   WriteChatColor("  /moveto <dist>   - Moves you within <dist> units of your target, default is 50");
   WriteChatColor("  /moveto -<dist>  - Subtracts <dist> units from the move distance");
}

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   breakStick();
   CHAR szTemp[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0 || bCircling || stickOn) {
      MoveToHelp();
      return;
   } else if (!stricmp(szTemp,"loc")) {
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocY = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocX = (float) atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (strlen(szTemp)) {
         if( isdigit(szTemp[0]) || szTemp[0]=='.' ) {
            moveDist = (float)atof(szTemp);
         } else if( szTemp[0]=='-' ) {
            moveDistMod = (float)atof(szTemp);
            moveDist += moveDistMod;
         }
      }

      bMoveToOn=true;
      sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
      if (stickVerbosity)
         WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else if (!stricmp(szTemp,"off")) {
      bMoveToOn = false;
      if (stickVerbosity)
         WriteChatColor("MoveTo off.", CONCOLOR_YELLOW);
   } else {
      MoveToHelp();
      return;
   }
}

void HandleMoveTo()
{
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState !=

STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( !casting || !autoPauseEnabled ) {
      float newHeading;
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
     
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->pActorInfo->UnderWater) ||
          (pulseAvg < ((stuckDist + SpeedModifier)/3) && pChSpawn->pActorInfo->UnderWater))&&
          GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
          stuck++;
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading + turnDirection);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              pChSpawn->Heading = newHeading;

              //check to see if we are heading directly away from our target if so then go back
              newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;

              newHeading += 265.0f;
              if( newHeading >= 512.0f) newHeading -= 512.0f;
              if( newHeading < 0.0f ) newHeading += 512.0f;
              if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
              }
          }
          stuckFree=0;
      } else if( stuck > 0 ) {
          if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
              newHeading =(float) ( pChSpawn->Heading - turnDirection );
           pChSpawn->Heading = newHeading;
          }
          stuck--;
          if(stuckFree++ > stuckCheck*3 ) {
              stuck=0;
          }
      } else {
          newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
          if( newHeading >= 512.0f)
             newHeading -= 512.0f;
          if( newHeading < 0.0f )
             newHeading += 512.0f;
          pChSpawn->Heading = newHeading;
      }
      if( GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist) {
         DoFwd(true);
      } else {
         bMoveToOn=false;
         if (stickVerbosity)
            WriteChatColor("Arrived at MoveTo location", CONCOLOR_YELLOW);
         DoFwd(false);
      }
   }
}

VOID CreateBinds(){
   if (MoveBindsLoaded)
      return;
   MoveBindsLoaded=true;
   AddMQ2KeyBind("UNSTICK_FWD",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_BCK",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_RGT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_RGT",DoUnstickBind);
   SetMQ2KeyBind("UNSTICK_FWD",false,pKeypressHandler->NormalKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_FWD",true,pKeypressHandler->AltKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_BCK",false,pKeypressHandler->NormalKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_BCK",true,pKeypressHandler->AltKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_right")]);
}

VOID DestroyBinds()
{
   if (!MoveBindsLoaded)
      return;
   RemoveMQ2KeyBind("UNSTICK_FWD");
   RemoveMQ2KeyBind("UNSTICK_BCK");
   RemoveMQ2KeyBind("UNSTICK_LFT");
   RemoveMQ2KeyBind("UNSTICK_RGT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_LFT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_RGT");
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2MoveUtils");

   // Add commands, macro parameters, hooks, etc.
   AddCommand("/circle",CircleCommand,0,1,1);
   AddCommand("/stick",StickCommand);
   AddMQ2Data("Stick",dataStick);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("MoveTo",dataMoveTo);

   pStickType = new MQ2StickType;

   srand((unsigned int)time(NULL));
   if (gGameState==GAMESTATE_INGAME) {
      CreateBinds();
   }
   GetSystemTime(&stPrevCirc);
   GetSystemTime(&stPrevStick);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("Shutting down MQ2MoveUtils");

   // Remove commands, macro parameters, hooks, etc.
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   RemoveMQ2Data("MoveTo");
   RemoveCommand("/moveto");

   delete pStickType;
   delete pMoveToType;

   DestroyBinds();
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if( breakOnGateEnabled && (stickHold?(stickTarget!=NULL):(ppTarget && pTarget)) ) {
      char szTemp[MAX_STRING];

      sprintf(szTemp,"%s Gates.",stickHold?stickTarget->DisplayedName:((PSPAWNINFO)pTarget)->DisplayedName);
      if( ! strcmp(szTemp,Line) ) {
         DoFwd(false);
         stickOn = false;
      }
   }

   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   if (GameState==GAMESTATE_INGAME) {
      CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      stickPaused=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
   }
}

// Called after entering a new zone
PLUGIN_API VOID OnZoned(VOID)
{
   DoWalk(false);
}
 
Re: Fixed

i use the moveutils in cades compile.. works fine for me...

but i am limited on commands i use with it 8-)

is there specific commands that did not work?
 
Re: Fixed

Errors when i used TONES Code

Line 453 there are extra Spaces .. colored red...

Cronic had the same problem with a post

Rich (BB code):
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      stickHold = stickHold && stickTarget && stickTarget->SpawnID;
      if( (ppTarget && pTarget) || (stickHold && stickTarget && stickTarget->SpawnID) ) {
         if( FindSpeed((PSPAWNINFO)pCharSpawn) < 0 ) {
            DoFwd(false);
         }
         currentDist=GetDistance((PSPAWNINFO)pCharSpawn,stickHold?stickTarget:((PSPAWNINFO)pTarget));
      }
      stickText();
   }
}

Rich (BB code):
stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);
 
Re: Fixed

Anyone know where the MemorizedSpells offset is in the CharInfo structure? I can't get twist to compile on the 9/20 and now 9/23 release of MQ2. The member seems to be gone.

Thanks

*edit*
NM, looks like it's in CharInfo2 now

These are both working fine for me now...

Rich (BB code):
// MQ2Twist.cpp - Bard song twisting plugin for MacroQuest2
//
//    koad 03-24-04 Original plugin (http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=5962&start=2)
//    CyberTech 03-31-04 w/ code/ideas from Falco72 & Space-boy
//    Cr4zyb4rd 08-19-04 taking over janitorial duties
//    Pheph 08-24-04 cleaning up use of MQ2Data

/*
   MQ2Twist Version 1.3

      Usage:   
         /twist # # # # # - Twists in the order given.
            Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.
            These may be mixed in any order, and repeats are allowable. Up to 10 may be
            specified.
            If a song is specified with a duration longer than standard (ie, selos)
            that song will be twisted based on it's duration.  For example, riz+mana+selos
            would be a 2 song twist with selos pulsed every 2.5 min.
         /twist once # # # # # - Twists in the order given, then reverts to original twist
         /twist hold <gem #> - Pause twisting and sing only the specified song
            /sing <gem#> - alias for /twist hold
         /twist stop/end/off - stop twisting, does not clear the twist queue
            /stoptwist - alias for above
         /twist or /twist start - Resume the twist after using /twist hold or /twist stop
       /twist reset - Reset timers for item clicks and long duration songs
         /twist delay # - 10ths of a second, minimum of 30, default 33
         /twist adjust # - in ticks, how early to recast long duration songs
         /twist reload - reload the INI file to update item clicks
         /twist slots - List the slots/items defined in the INI and their #'s
         /twist quiet - Toggles songs listing and start/stop messages for one-shot twists

      ----------------------------
      Item Click Method:
         MQ2Twist uses /itemnotify slotname rightmouseup to perform item clicks.

         The INI file allows you to specify items by name (with name=itemname), or by
       inventory slot (with slot=slotname).  If both a name and slot are defined for an
       item, the plugin will attempt to swap the item into that slot (via the /exchange
       command) and replace the original item when casting is complete.
      
       The example INI file below contains examples of the types of usage.

      ----------------------------
      Examples:
         /twist 1
            Sing gem 1 forever
         /twist 1 2 3
            Twist gems 1,2, and 3 forever
         /twist 1 2 3 10
            Twist gems 1,2,3, and clicky 10, forever
         /twist hold 4 or /sing 4
            Sing gem 4 until another singing-related /twist command is given

      ----------------------------
      MQ2Data Variables:
         bool   Twist         Currently Twisting: true/false, if NULL plugin is not loaded
         Members:
            bool     Twisting  Currently twisting: true/false.
            int      Current   Returns the curent gem number being sung, -1 for item, or 0 if not twisting
            int      Next      Returns the next gem number to be sung, -1 for item, or 0 if not twistsing
            string   List      Returns the twist sequence in a format suitable for /twist

      ----------------------------
     
     The ini file has the format:
         [MQ2Twist]
         Delay=32       Delay between twists. Lag & System dependant.
       Adjust=1       This defines  how many ticks before the 'normal' recast time to cast a long song.
                        Long songs are defined as songs greater than 3 ticks in length.  If set to 1 tick,
                        and a song lasts 10 ticks, the song will be recast at the 8 tick mark, instead of
                        at the 9 tick mark as it normally would.

         [Click_10] thru [Click_19]
         CastTime=30              Casting Time, -1 to use the normal song delay
         ReCastTime=0             How often to recast, 0 to twist normally.
         Name="Fife of Battle"    Item name for /itemnotify
       Slot=neck                Slot name for /itemnotify

         Delay, CastTime and ReCastTime are specified in 10ths of a
         second, so 10 = 1 second, and so on.

         INI File Example:
            [MQ2Twist]
            Delay=31
         Quiet=0

            ;Shadowsong cloak
            [Click_10]
            CastTime=30
            ReCastTime=350
            Name=Shadowsong Cloak
            Slot=DISABLED

            ;girdle of living thorns (current belt will be swapped out)
            [Click_11]
            CastTime=0
            ReCastTime=11600
            Name=Girdle of Living Thorns
            Slot=waist

            ;nature's melody
            [Click_12]
            CastTime=-1
            ReCastTime=135
            Name=DISABLED
            Slot=mainhand

            ;lute of the flowing waters
            [Click_13]
            CastTime=0
            ReCastTime=0
            Name=Lute of the Flowing Waters
            Slot=DISABLED

            [Click_14] ... [Click_19]
            CastTime=33
            ReCastTime=0
            Name=DISABLED
            Slot=DISABLED

      ----------------------------

Changes:
   10-05-04
      Support "swap in and click" items

    09-15-04
      Support extra spell slot from Omens of War AA

    09-01-04
      Command: /twist quiet to toggle some of the spam on/off
      Various code fixes/speedups

    08-29-04
      Moved LONGSONG_ADJUST into INI file and made /twist adjust command to set it on
      the fly

   08-25-04
      Changed output for /twist once to be slightly less misleading
      Reset click/song timers every time they're called with /twist hold or /twist once;
      if the user's specifying that song, they obviously want to cast it anyway.
      Removed the variable MissedNote as close inspection revealed the only place it was
      checked for was the line that set it. /boggle
      Minor code tweaks, cleanups, formatting changes, etc

   08-24-04 (Pheph)
      Modified it to use only one TLO, as I found it somewhat messy having 4 different ones.
      All the functionality of the old TLO's are now members of ${Twist}
      ${Twising} is now ${Twist.Twisting}, or just ${Twist}
      ${TwistCurrent} is now ${Twist.Current}
      ${TwistNext} is now ${Twist.Next}
      ${TwistList} is now ${Twist.List}

   08-23-04
      Reset_ItemClick_Timers was being called far too often.  Now the only time we reset
     is if a new list of songs are specified.  "/twist ${TwistList}" is a useful alias
     if you for some reason want the old behavior.
      Sing or /twist hold now resets the cast/item timer for that song only, rather than
     the entire list.
     Command: /twist reset calls Reset_ItemClick_Timers without interfering with the
     state of the current twists.
    
   08-22-04
      Command: /twist once [songlist] will cycle through the songs entered once, then
     revert to the old twist, starting with the song that was interrupted.
     Removed command "/twist on", it was making the string compare for "once" annoying,
     and I didn't think it was worth the effort for a redundant command.
      /twist delay with no argument now returns the delay without resetting it.  Values
     less than 30 now give a warning...maybe they're not bards or have some other
     reason for using a low value.

   08-19-04
      Minor revamp of item notification.  Removed ITEMNOTIFY define and kludged in some
      changes from Virtuoso65 to get casting by item name working.  /cast is no longer
      used.
      Added INI file support for above change.  File now uses distinct entries for item
      names and slots.  *Quotes not required for multi-word item names in INI.*
      Fixed the MQ2Data value TwistCurrent to display the current song as-advertised, and
      added a new value TwistNext with the old behavior of showing the next song in the
      queue. (Useful in scripting)
      Removed a few DebugSpews that were mega-spamming my debugger output.
      CastTime of -1 in the INI file now causes the default delay to be used.
   
   06-01-04
      Added LONGSONG_ADJUST (default to 1 tick) to help with the timing of recasting long
      songs, such as selo's.
      Twisting is now paused when you sit (this would include camping).  This fixes
      problems reported by Chyld989 (twisting across chars) and Kiniktoo (new autostand on
      cast 'feature' in EQ makes twisting funky)

   05-19-04
      Added workaround for incorrect duration assumption for durationtype=5 songs, such as
      Cassindra's Chant of Clarity or Cassindra's Chorus of Clarity.
      Added check of char state before casting a song. Actually added for 1.05
         Checked states and resulting action are:
            Feigned, or Ducking = /stand
            Stunned = Delay
            Dead - Stop twisting.
         If you're a monk using this to click your epic, you'll want to disable the autostand on feign code =)


   05-05-05
      Fixed CTD on song unmem or death, while twisting.  Oops
      Removed circle functionality.  It's better suited for a plugin like the MQ2MoveUtils
         plugin by tonio at http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=6973

   05-01-04
      Fixed problem with using pchar before state->ingame causing CTD on eq load (thanks MTBR)
      Fixed vc6 compile error w/ reset_itemclick_timers
      Replaced various incantations of pChar and pSpawn with GetCharInfo()
      Fixed /circle behavior w/ unspecified y/x
      Fixed /circle on when already circling and you want to update loc
      Added output of parsed circle parameters on start.

   04-25-04
      Converted to MQ2Data
         Top Level Objects:
            bool   Twisting      (if NULL plugin is not loaded)
            int      TwistCurrent
            string   TwistList
      Removed $Param synatax for above
      Added check to make sure item twists specified are defined
      Fixed error with twist parameter processing
      Changed twist startup output to be more verbose
      Command: /twist on added as alias for /twist start
      INI File is now named per-character (MQ2Twist_Charname.ini)
         * Be sure to rename existing ini files
      Modified twist routine to take into account songs with
         non-0 recast times or longer than 3 tick durations,
         and only re-cast them after the appropriate delay.
         This is for songs like Selos 2.5 min duration, etc.
         * Note that this makes no attempt to recover if the song
         effect is dispelled, your macro will need to take care
         of that.
      Added ability to compile-time change the method used for
         clicking items.

   04-13-04
      Changed /circle command to allow calling w/o specifying loc
      Corrected a problem with multiple consecutive missed notes
      Added handling of attempting to sing while stunned
      Command: /twist slots, to list the slot to # associations
      Command: /twist reload, to reload the ini file on the fly
      Command: /twist end, /twist off as aliases for /twist stop
      Command: /sing #, as an alias for /twist hold #

      Added support for item clickies.  Clickies are specified
      as "gem" 10-19. For example, /twist 1 2 10 12

      Added INI file support for storing item clicky info
      and default twist delay.

   04-11-04
      Integrated the /circle code from Easar, runs in a circle.  type
      /circle for help.
*/

#include "../MQ2Plugin.h"

PreSetup("MQ2Twist");

typedef struct _ITEMCLICK {
   int cast_time;
   int recast;
   long castdue;
   int disabled;
   int nousename;
   CHAR slot[MAX_STRING];
   CHAR name[MAX_STRING];
} ITEMCLICK;

int MQ2TwistEnabled = 0;
const int MAX_SONG=10;
int LONGSONG_ADJUST=1; // In TICKS, not seconds.  Used for long songs (greater than 3 ticks in duration). See docs.
int CAST_TIME=33;
int NumSongs=0;
int AltNumSongs=0;
int Song[MAX_SONG*2];
int AltSong[MAX_SONG*2];
long SongNextCast[MAX_SONG*2];
ITEMCLICK ItemClick[MAX_SONG];
int CurrSong=0;
int AltCurrSong=0;
int PrevSong=0;
int HoldSong=0;
long CastDue=0;
bool bTwist=false;
bool altTwist=false;
bool quiet;
CHAR SwappedOutItem[MAX_STRING];
CHAR SwappedOutSlot[MAX_STRING];

long GetTime();
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine);
BOOL dataTwist(PCHAR szIndex, MQ2TYPEVAR &Ret);
CHAR MQ2TwistTypeTemp[MAX_STRING]={0};

//get current timestamp in tenths of a second
long GetTime()
{
   SYSTEMTIME st;
   ::GetSystemTime(&st);
   long lCurrent=0;
   lCurrent  = st.wDay    * 24 * 60 * 60 * 10;
   lCurrent += st.wHour        * 60 * 60 * 10;
   lCurrent += st.wMinute           * 60 * 10;
   lCurrent += st.wSecond                * 10;
   lCurrent += (long)(st.wMilliseconds/100);
   return (lCurrent);
}

VOID MQ2TwistDoCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   HideDoCommand(pChar, szLine, FromPlugin);
}

VOID DoSwapOut()
{
   CHAR szTemp[MAX_STRING];
   if (SwappedOutItem[0]) {
      sprintf(szTemp,"/exchange \"%s\" %s",SwappedOutItem,SwappedOutSlot);
      MQ2TwistDoCommand(NULL, szTemp);
      SwappedOutItem[0]=0;
   }
}

VOID DoSwapIn(int Index)
{
   CHAR szTemp[MAX_STRING];
   if (strnicmp(ItemClick[Index].slot,"DISABLED",8)) {
      sprintf(szTemp,"${InvSlot[%s].Item.Name}",ItemClick[Index].slot);
      ParseMacroData(szTemp);
      strcpy(SwappedOutItem,szTemp);
      strcpy(SwappedOutSlot,ItemClick[Index].slot);
      sprintf(szTemp,"/exchange \"%s\" %s",ItemClick[Index].name,ItemClick[Index].slot);
      MQ2TwistDoCommand(NULL, szTemp);
   }
}

VOID Reset_ItemClick_Timers()
{
   int i;
   for (i=0;i<10;i++) {
      ItemClick.castdue = 0;
   }
   for (i=0;i<MAX_SONG*2;i++) {
      SongNextCast = 0;
   }
}


VOID Update_INIFileName() {
   if (GetCharInfo()) {
      sprintf(INIFileName,"%s\\MQ2Twist_%s.ini",gszINIPath,GetCharInfo()->Name);
   } else {
      sprintf(INIFileName,"%s\\MQ2Twist.ini",gszINIPath);
   }
}

VOID Load_MQ2Twist_INI()
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szSection[MAX_STRING]={0};

   Update_INIFileName();

   CAST_TIME = GetPrivateProfileInt("MQ2Twist","Delay",33,INIFileName);
   sprintf(szTemp, "%d", CAST_TIME);
   WritePrivateProfileString("MQ2Twist","Delay",szTemp,INIFileName);
   quiet = GetPrivateProfileInt("MQ2Twist","Quiet",0,INIFileName)? 1 : 0;
   sprintf(szTemp, "%d", quiet);
   WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
   
   LONGSONG_ADJUST = GetPrivateProfileInt("MQ2Twist","Adjust",1,INIFileName);
   sprintf(szTemp, "%d", LONGSONG_ADJUST);
   WritePrivateProfileString("MQ2Twist","Adjust",szTemp,INIFileName);
   
   for (int i=0;i<10;i++) {
      sprintf(szSection, "Click_%d", i+10);
      ItemClick.cast_time = GetPrivateProfileInt(szSection,"CastTime",0,INIFileName);
      ItemClick.recast = GetPrivateProfileInt(szSection,"ReCastTime",0,INIFileName);

      GetPrivateProfileString(szSection,"Name","DISABLED",ItemClick.name,MAX_STRING,INIFileName);
      GetPrivateProfileString(szSection,"Slot","DISABLED",ItemClick.slot,MAX_STRING,INIFileName);
      if(!strnicmp("DISABLED", ItemClick.name, 8)) {
         if (!strnicmp("DISABLED", ItemClick.slot, 8)) {
            ItemClick.disabled = true;
            DebugSpew("MQ2Twist: Slot %d disabled",i+1);
         } else {
            ItemClick.nousename = true;
            ItemClick.disabled = false;
         }
      } else ItemClick.disabled = false;
      // Write the values above back to disk, mostly to initialize it for easy editing.
      sprintf(szTemp, "%d", ItemClick.cast_time);
      WritePrivateProfileString(szSection,"CastTime",szTemp,INIFileName);
      // If the CastTime is set to -1 in the INI file, use the default.
      ItemClick.cast_time = ItemClick.cast_time==-1 ? CAST_TIME : ItemClick.cast_time;

      sprintf(szTemp, "%d", ItemClick.recast);
      WritePrivateProfileString(szSection,"ReCastTime",szTemp,INIFileName);
      WritePrivateProfileString(szSection,"Name",ItemClick.name,INIFileName);
      WritePrivateProfileString(szSection,"Slot",ItemClick.slot,INIFileName);
      DebugSpewAlways("Initializing MQ2Twist: Processed %s", szSection);
   }
}

VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   int i;

   GetArg(szTemp,szLine,1);
   i=atoi(szTemp);

   if (i>=1 && i<=19) { // valid range?
      HoldSong = i;
      bTwist=true;
      CastDue = -1;
      sprintf(szMsg, "MQ2Twist::Holding Twist and casting gem %d", HoldSong);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      MQ2TwistDoCommand(pChar,"/stopsong");
      if (i>10) { //item?
         ItemClick[i-10].castdue = 0;
      } else SongNextCast = 0; //nope, song
   } else WriteChatColor("MQ2Twist::Invalid gem specified, ignoring",USERCOLOR_DEFAULT);
}

VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   bTwist=false;
   HoldSong=0;
   MQ2TwistDoCommand(pChar,"/stopsong");
   WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
}

VOID PrepNextSong() {
   if (CurrSong>NumSongs) {
      if (altTwist) {
         NumSongs=AltNumSongs;
         CurrSong=PrevSong=AltCurrSong;
         for (int i=0; i<NumSongs; i++) Song=AltSong;
         altTwist=false;
         if (!quiet) WriteChatColor("MQ2Twist::One-shot twist ended, normal twist will resume next pulse",USERCOLOR_DEFAULT);
      } else CurrSong=1;
   }
}

VOID DisplayTwistHelp() {
   WriteChatColor("MQ2Twist - Twist song or songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage:   /twist <gem#> - Twists in the order given.",USERCOLOR_DEFAULT);
   WriteChatColor("  Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.",USERCOLOR_DEFAULT);
   WriteChatColor("  These may be mixed in any order, and repeats are allowable.",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist hold <gem #> - Pause twisting and sing only the specified song",USERCOLOR_DEFAULT);
   WriteChatColor("  /sing <gem#> - alias for /twist hold",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist once <gem#> Twists once in the order given, then reverts to original twist",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist or /twist start - Resume the twist after using /twist hold or /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reset - Reset timers for item clicks and long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist delay # - 10ths of a second, minimum of 30, default 33",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist adjust # - in ticks, how early to recast long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist stop/end/off - stop twisting, does not clear the twist queue",USERCOLOR_DEFAULT);
   WriteChatColor("  /stoptwist - alias for /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reload - reload the INI file to update item clicks",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist slots - List the slots defined in the INI and their #'s",USERCOLOR_DEFAULT);
}

// ***************************************************************************
// Function:      TwistCommand
// Description:   Our /twist command. sing for me!
// ***************************************************************************
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   CHAR szChat[MAX_STRING]={0};
   PSPELL pSpell;
   int i;

   GetArg(szTemp,szLine,1);

   if (NumSongs && (!strlen(szTemp) || !strnicmp(szTemp,"start", 5))) {
      WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
      DoSwapOut();
      bTwist=true;
      HoldSong=0;
      CastDue = -1;
      return;
   }

   if (!strnicmp(szTemp,"stop", 4) || !strnicmp(szTemp,"end", 3) || !strnicmp(szTemp,"off", 3)) {
      DoSwapOut();
      StopTwistCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"slots", 5)) {
      WriteChatColor("MQ2Twist 'Song' Numbers for right click effects:",USERCOLOR_DEFAULT);
      for (i=0;i<10;i++) {
         if (ItemClick.disabled) break;
         if (ItemClick.nousename) {
            sprintf(szMsg, "  %d = %s (slot)", i+10, ItemClick.slot);
         } else {
            sprintf(szMsg, "  %d = %s (name) %s (slot)", i+10, ItemClick.name, ItemClick.slot);
       }
         WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      }
      WriteChatColor("---",USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"reload", 6)) {
      WriteChatColor("MQ2Twist::Re-Loading INI Values",USERCOLOR_DEFAULT);
      Load_MQ2Twist_INI();
      return;
   }

   if (!strnicmp(szTemp,"delay", 5)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         if (i<=30) {
            WriteChatColor("MQ2Twist::WARNING delay specified is less than standard song cast time",CONCOLOR_RED);
         }
         CAST_TIME=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Delay",itoa(CAST_TIME, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Set delay to %d, INI updated", CAST_TIME);
      } else sprintf(szMsg, "MQ2Twist::Delay %d", CAST_TIME);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"quiet", 5)) {
      quiet=!quiet;
      sprintf(szTemp,"%d",quiet);
      WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
      sprintf(szMsg,"MQ2Twist::Now being %s",quiet ? "quiet" : "noisy");
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"adjust", 6)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         LONGSONG_ADJUST=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Adjust",itoa(LONGSONG_ADJUST, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Long song adjustment set to %d, INI updated", LONGSONG_ADJUST);
      } else sprintf(szMsg, "MQ2Twist::Long song adjustment: %d", LONGSONG_ADJUST);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"hold", 4)) {
      GetArg(szTemp,szLine,2);
      SingCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"reset", 5)) {
      Reset_ItemClick_Timers();
      WriteChatColor("MQ2Twist::Timers reset",CONCOLOR_YELLOW);
      return;
   }

   // check help arg, or display if we have no songs defined and /twist was used
   if (!strlen(szTemp) || !strnicmp(szTemp,"help", 4)) {
      DisplayTwistHelp();
      return;
   }

   // if we are "one-shot twisting", save the current song array and current song
   if (!strnicmp(szTemp,"once", 4)) {
      WriteChatColor("MQ2Twist one-shot twisting:",CONCOLOR_YELLOW);
      if (altTwist) {
         CurrSong=NumSongs+1;
         PrepNextSong(); // If CurrSong > NumSongs relaod the song list
      }
      if (NumSongs) {
         AltNumSongs=NumSongs;
         AltCurrSong=CurrSong;
         for (i=0; i<NumSongs; i++) AltSong=Song;
      }
      altTwist=true;
   } else altTwist=false;

   DoSwapOut();
   DebugSpew("MQ2Twist::TwistCommand Parsing twist order");
   NumSongs=0;
   HoldSong=0;
   if (!altTwist) {
      if (!quiet) {
         WriteChatColor("MQ2Twist Twisting:",CONCOLOR_YELLOW);
      } else WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
   }
   for (i=0 + altTwist ? 1 : 0; i<20; i++)
   {
      GetArg(szTemp,szLine,i+1);
      if (!strlen(szTemp))  break;

      Song[NumSongs]=atoi(szTemp);
      if (Song[NumSongs]>=1 && Song[NumSongs]<=19) {
         if ((Song[NumSongs]>9) && ItemClick[Song[NumSongs]-10].disabled) {
            sprintf(szChat, " Undefined item specified (%s) - ignoring (see INI file)", szTemp);
            WriteChatColor(szChat,CONCOLOR_RED);
         } else {
            sprintf(szMsg, " %s - ", szTemp);

            if (Song[NumSongs]<=9) {
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[NumSongs]-1]);
               if (altTwist) SongNextCast[NumSongs] = 0;
               if (pSpell) strcat(szMsg, pSpell->Name);
            } else {
               if (ItemClick[Song[NumSongs]-10].nousename) {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].slot);
               } else {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].name);
               }
               if (altTwist) ItemClick[NumSongs].castdue = 0;
            }
            if (!quiet) WriteChatColor(szMsg,COLOR_LIGHTGREY);
            NumSongs++;
         }
      } else {
         sprintf(szChat, " Invalid gem specified (%s) - ignoring", szTemp);
         WriteChatColor(szChat,CONCOLOR_RED);
      }
   }

   sprintf(szTemp, "Twisting %d song%s", NumSongs, NumSongs>1 ? "s" : "");
   if (!quiet) WriteChatColor(szTemp,CONCOLOR_YELLOW);

   if (NumSongs>0) bTwist=true;
   CurrSong = 1;
   PrevSong = 1;
   CastDue = -1;
   MQ2TwistDoCommand(pChar,"/stopsong");
   if (!altTwist) Reset_ItemClick_Timers();
}

/*
Checks to see if character is in a fit state to cast next song/item

Note 1: Do not try to correct SIT state, or you will have to stop the
twist before re-memming songs

Note 2: Since the auto-stand-on-cast bullcrap added to EQ a few patches ago,
chars would stand up every time it tried to twist a song.  So now
we stop twisting at sit.
*/
BOOL CheckCharState() {
   if (!bTwist) return FALSE;

   if (GetCharInfo()) {
      if (GetCharInfo()->Stunned==1) return FALSE;
      switch (GetCharInfo()->standstate) {
       case STANDSTATE_SIT:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       case STANDSTATE_FEIGN:
          MQ2TwistDoCommand(NULL,"/stand");
          return FALSE;
          break;
       case STANDSTATE_DEAD:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       default:
          break;
      }
   }

   if (pCastingWnd) {
      PCSIDLWND pCastingWindow = (PCSIDLWND)pCastingWnd;
      if (pCastingWindow->Show == 1) return FALSE;
      // Don't try to twist if the casting window is up, it implies the previous song
      // is still casting, or the user is manually casting a song between our twists
   }
   return TRUE;
}
class MQ2TwistType *pTwistType=0;

class MQ2TwistType : public MQ2Type
{
public:
   enum TwistMembers
   {
      Twisting=1,
      Next=2,
      Current=3,
      List=4,
   };

   MQ2TwistType():MQ2Type("twist")
   {
      TypeMember(Twisting);
      TypeMember(Next);
      TypeMember(Current);
      TypeMember(List);
   }
   ~MQ2TwistType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2TwistType::FindMember(Member);
      if (!pMember)
         return false;
      switch((TwistMembers)pMember->ID)
      {
      case Twisting:
         /* Returns: bool
            0 - Not Twisting
            1 - Twisting
         */
         Dest.Int=bTwist;
         Dest.Type=pBoolType;
         return true;
      case Next:
         /* Returns: int
            0 - Not Twisting
            -1 - Casting Item
            1-9 - Current Gem
         */
         Dest.Int=HoldSong ? HoldSong : Song[CurrSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case Current:
         Dest.Int=HoldSong ? HoldSong : Song[PrevSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case List:
         /* Returns: string
            Space separated list of gem and item #'s being twisted, in order
         */
         int a;
         CHAR szTemp[MAX_STRING] = {0};

         MQ2TwistTypeTemp[0] = 0;
         for (a=0; a<NumSongs; a++) {
            sprintf(szTemp, "%d ", Song[a]);
            strcat(MQ2TwistTypeTemp, szTemp);
         }

         Dest.Ptr=&MQ2TwistTypeTemp[0];
         Dest.Type=pStringType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if (bTwist)
         strcpy(Destination,"TRUE");
      else
         strcpy(Destination,"FALSE");
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataTwist(PCHAR szName, MQ2TYPEVAR &Dest)
{
   Dest.DWord=1;
   Dest.Type=pTwistType;
   return true;
}


// ******************************
// **** MQ2 API Calls Follow ****
// ******************************

PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2Twist");

   AddCommand("/twist",TwistCommand,0,1,1);
   AddCommand("/sing",SingCommand,0,1,1);
   AddCommand("/stoptwist",StopTwistCommand,0,0,1);;
    AddMQ2Data("Twist",dataTwist);

    pTwistType = new MQ2TwistType;

}

PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("MQ2Twist::Shutting down");

   RemoveCommand("/twist");
   RemoveCommand("/sing");
   RemoveCommand("/stoptwist");
    RemoveMQ2Data("Twist");

    delete pTwistType;
}

PLUGIN_API VOID OnPulse(VOID)
{
   CHAR szTemp[MAX_STRING] = {0};
   PSPELL pSpell;
   int a,b;

   if (!MQ2TwistEnabled || !CheckCharState()) return;

   if ((HoldSong>0) || ((NumSongs==1) && !altTwist)) {
      // DebugSpew("MQ2Twist::Pulse - Single Song");
      if ( CastDue<0 || ( ((CastDue-GetTime()) <= 0 ) && (GetCharInfo()->pSpawn->pActorInfo->CastingSpellID == -1) ) ) {
         int SongTodo = HoldSong ? HoldSong : Song[0];
         if (SongTodo <= 9) {
            DebugSpew("MQ2Twist::Pulse - Single Song (Casting Gem %d)", SongTodo);
            sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
            MQ2TwistDoCommand(NULL,szTemp);
            CastDue = GetTime()+CAST_TIME;
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = ItemClick[SongTodo-10].castdue;
            }
         }
      }
   } else {
      int SongTodo = Song[CurrSong-1];
      if (NumSongs && ((CastDue-GetTime()) <= 0)) {
         DoSwapOut();
         if (SongTodo <= 9) {
            if (SongNextCast[CurrSong-1]-GetTime() <= 0) {
               DebugSpew("MQ2Twist::OnPulse - Next Song = %s", szTemp);
               sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
               MQ2TwistDoCommand(NULL,szTemp);
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[CurrSong-1]-1]);
               if(!pSpell) {
                  WriteChatColor("Songs not present - suspending twist.  /twist to resume",CONCOLOR_RED);
                  bTwist = FALSE;
                  return;
               }
               a = pSpell->RecastTime/1000 * 60;                     // recasttime in 10's of a second
               b = GetSpellDuration(pSpell,GetCharInfo()->pSpawn) * 60;   // duration in 10's of a second
               if (pSpell->DurationType == 5 && !pSpell->DurationValue1) {
                  b = 18;   //FIXME - Remove once GetSpellDuration handles duration type5
               }
               CastDue = GetTime()+CAST_TIME;
               if (a > 0 || b > 180) { // We only care about songs with > 3 tick durations or non-0 recast times
                  SongNextCast[CurrSong-1] = GetTime() + (a > b ? a : b) - CAST_TIME - (LONGSONG_ADJUST*60);   // Cast next after greater of recasttime or duration, minus LONGSONG_ADJUST ticks.
               } else {
                  SongNextCast[CurrSong-1] = CastDue;
               }
               PrevSong=CurrSong;
            } // if it's not time for currsong to be re-sung, skip it in the twist
            CurrSong++;
            PrepNextSong();
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Slot %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = GetTime()+ItemClick[SongTodo-10].cast_time;
            }
            PrevSong=CurrSong;   // Increment twist position even if we didn't do an itemnotify - this might have a long recast
            CurrSong++;         // interval set, and we just skip it until it's time to recast, rather than keep a separate timer.
            PrepNextSong();
         }   
      }
   }     
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if (!bTwist || !MQ2TwistEnabled) return 0;
   // DebugSpew("MQ2Twist::OnIncomingChat(%s)",Line);
   
   if ( !strcmp(Line,"You miss a note, bringing your song to a close!") ||
      !strcmp(Line,"You haven't recovered yet...") ||
      !strcmp(Line,"Your spell is interrupted.") ) {
         DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event");
         if (!HoldSong) CurrSong=PrevSong;
         CastDue = -1;
         SongNextCast[CurrSong-1] = -1;
         return 0;
      }

   if (!strcmp(Line,"You can't cast spells while stunned!") ) {
      DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event (stun)");
      if (!HoldSong) CurrSong=PrevSong;
      CastDue = GetTime() + 10;
      // Wait one second before trying again, to avoid spamming the trigger text w/ cast attempts
      return 0;
   }
   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   DebugSpew("MQ2Twist::SetGameState()");
   if (GameState==GAMESTATE_INGAME)
   {
      MQ2TwistEnabled = true;
      Load_MQ2Twist_INI();
   } else {
      MQ2TwistEnabled = false;
   }
}



The /circle command was freaking out on my box, so I compiled this version of MoveUtils, and it works for me now....

Rich (BB code):
/*
MQ2MoveUtils plugin (2004.06.23) - tonio
- Updated 2005.06.26 by Quagmire

Currently contains three commands:
/stick -- /follow-like command, works for any pc/npc, default distance is melee range ("/stick help" for more options)
/circle -- autofaces character to run in a circle
/moveto -- moves character to a specific loc, such as an anchor spot

"/stick" command by me
"/circle" command lifted from CyberTech's MQ2Twister plugin
"/moveto" command from rswiders


*/

#include "../MQ2Plugin.h"

PreSetup("MQ2MoveUtils");


VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine);
float getRand(float n);

bool bCircling=FALSE;
bool bDrunken=false;
double CircleX=0.0f;
double CircleY=0.0f;
double CircleRadius=0.0f;
SYSTEMTIME stPrevCirc;

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void ReleaseKeys();
void DoWalk(bool walk = false);
void DoFwd(bool hold, bool walk = false);
void DoBck(bool hold);
void DoLft(bool hold);
void DoRgt(bool hold);
void Load_INI(void);
bool IsBardClass(void);
void stickText();
void breakStick(bool stopMoving = true, bool quite = false);
float angularDistance(float h1, float h2);

bool MoveBindsLoaded=false;
bool stickOn=false;
bool setDist=false;
bool stickPaused=false;
bool stickHold=false;
bool moveBehind=false;
bool moveBehindOnce=false;
bool moveBack=false;
bool movePin=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=false;
bool stickhasmovedfwd=false;
short stickVerbosity=1;
short keysDown=0;
float stickDist=0.0;
float breakDist=250.0;
float currentDist=0.0;
float stickDistMod=0.0;
float stickDistModP=1.0;
PSPAWNINFO stickTarget;
eSpawnType stickTarget_Type;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMoveTo();
bool bMoveToOn=false;
double LocX=0.0f;
double LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

class MQ2StickType *pStickType = 0;

class MQ2StickType : public MQ2Type
{
public:
   enum StickMembers {
      Status=1,
      Active=2,
      Distance=3,
      MoveBehind=4,
      MovePause=5,
      MoveBack=6,
      Loose=7,
      Paused=8,
      Behind=9,
      Stopped=10,
      Pin=11,
   };

   MQ2StickType():MQ2Type("stick")
   {
      TypeMember(Status);
      TypeMember(Active);
      TypeMember(Distance);
      TypeMember(MoveBehind);
      TypeMember(MovePause);
      TypeMember(MoveBack);
      TypeMember(Loose);
      TypeMember(Paused);
      TypeMember(Behind);
      TypeMember(Stopped);
      TypeMember(Pin);
   }

   ~MQ2StickType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2StickType::FindMember(Member);
      if (!pMember)
         return false;
      switch((StickMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( stickOn ) {
            strcpy(DataTypeTemp,"ON");
         }
         if( stickPaused ) {
            strcpy(DataTypeTemp,"PAUSED");
         }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Active:
         Dest.DWord=stickOn;
         Dest.Type=pBoolType;
         return true;
      case Distance:
         Dest.Float=stickDist;
         Dest.Type=pFloatType;
         return true;
      case MoveBehind:
         Dest.DWord=moveBehind;
         Dest.Type=pBoolType;
         return true;
      case MovePause:
         Dest.DWord=mPause;
         Dest.Type=pBoolType;
         return true;
      case MoveBack:
         Dest.DWord=moveBack;
         Dest.Type=pBoolType;
         return true;
      case Loose:
         Dest.DWord=looseStick;
         Dest.Type=pBoolType;
         return true;
      case Paused:
         Dest.DWord=stickPaused;
         Dest.Type=pBoolType;
         return true;
      case Behind:
         if (ppTarget && pTarget) {
            PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget) > stickDist || fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 )?false:true;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         if( ppTarget && pTarget ) {
            PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget)<=stickDist)?true:false;
         } else
         Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Pin:
         Dest.DWord=movePin;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( stickOn ) {
         strcpy(Destination,"ON");
      }
      if( stickPaused ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataStick(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pStickType;
   return true;
}

VOID CircleHelp()
{
   WriteChatColor("Usage: /circle on|off|drunken <radius> [<y> <x>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are optional, if not specified will use your currect loc.",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while not circling, it will start with your current loc and specified radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on <radius>' while already circling, it will update with your new loc and radius.",USERCOLOR_DEFAULT);
   WriteChatColor("  If you call '/circle on' while already circling, it will update with your new loc using original radius.",USERCOLOR_DEFAULT);
}

VOID CircleCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0) {
      CircleHelp();
      return;
   } else if (!stricmp(szTemp,"on") || !stricmp(szTemp,"drunken")) {
      if( !stricmp(szTemp,"drunken") )
         bDrunken=true;
      else
         bDrunken=false;
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         if (!bCircling) {
            CircleHelp();
            return;
         } else if (!CircleRadius) {
            // /circle on was called while we were already circling, but no radius was defined. oddddddd.
            CircleHelp();
            return;
         }
      } else {
         CircleRadius = atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         CircleY = pChSpawn->Y + CircleRadius * sin(pChSpawn->Heading * PI / 256.0);
      } else {
         CircleY = atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (!strlen(szTemp)) {
         CircleX = pChSpawn->X - CircleRadius * cos(pChSpawn->Heading * PI / 256.0);
      } else {
         CircleX = atof(szTemp);
      }

      if (CircleRadius) bCircling = true;
      sprintf(szMsg, "Circling Radius %g, center %g %g", CircleRadius, CircleY, CircleX);
      WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else {
      if (!stricmp(szTemp,"off")) {
         bCircling = false;
      }
   }

}

void StickHelp()
{
   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload] [<dist>] [behind|behindonce|pin] [mpause] [moveback] [loose] [-<dist>] [<perc>%] [uw]");
   WriteChatColor("   /stick            - Sticks you within melee range of your target");
   WriteChatColor("   /stick hold       - Stores your current target, sticks to it even if you lose/change target");
   WriteChatColor("   /stick off        - Breaks off from stick (moving manually also breaks off from stick");
   WriteChatColor("   /stick pause      - Pauses following (can move normally while paused)");
   WriteChatColor("   /stick unpause    - Resumes following");
   WriteChatColor("   /stick reload     - Reload values from ini file");
   WriteChatColor("   /stick <dist>     - Sticks you within <dist> units of your target");
   WriteChatColor("   /stick behind     - Keeps you behind your target");
   WriteChatColor("   /stick behindonce - Moves behind your target once");
   WriteChatColor("   /stick pin        - Keeps you to the side of your target");
   WriteChatColor("   /stick mpause     - Causes manual movement to pause stick instead of breaking it");
   WriteChatColor("   /stick moveback   - Moves character back to try to stay at exactly stick distance");
   WriteChatColor("   /stick loose      - Checks distance/angle less often, and turns slower, for a more human-controlled look");
   WriteChatColor("   /stick uw         - Looks up or down to track target, useful for underwater /stick");
   WriteChatColor("   /stick -<dist>    - Substracts <dist> from the stick distance");
   WriteChatColor("   /stick <perc>%    - Multiplies stick distance by <perc> percent");
}

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char currentArg[MAX_STRING];
   int argn=1;

   GetArg(currentArg,szLine,argn++);

   if( !strncmp(currentArg,"pause",6) ) {
      ReleaseKeys();
      stickPaused = true;
      return;
   } else if( !strncmp( currentArg,"unpause",8 ) ) {
      stickPaused = false;
      return;
   }

   if (!stickOn)
      DoWalk(false);
   ReleaseKeys();
   stickOn=true;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   moveBehindOnce=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickhasmovedfwd=false;
   stickTarget=NULL;
   stickTarget_Type=NONE;
   stickDistMod=0.0f;
   stickDistModP=1.0f;


   while( *currentArg ) {
      if( !strncmp(currentArg,"on",3) ) {
         stickOn = true;
      } else if( strstr(currentArg,"%") ) {
         stickDistModP = (float)atof(currentArg) / 100.0f;
         if( setDist )
            stickDist *= stickDistModP;
         stickOn = true;
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.' ) {
         setDist = true;
         stickDist = (float)atof(currentArg) * stickDistModP + stickDistMod;
         stickOn = true;
      } else if( currentArg[0]=='-' ) {
         stickDistMod = (float)atof(currentArg);
         if( setDist )
            stickDist += stickDistMod;
         stickOn = true;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"moveback",9) ) {
         moveBack = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"loose",6) ) {
         looseStick = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"uw",3 ) ) {
         underwater=true;
         stickOn=true;
      } else if( !strncmp(currentArg,"off",4) ) {
         breakStick();
         break;
      } else if( !strncmp(currentArg,"hold",5) ) {
         stickOn = true;
         if( ppTarget && pTarget ) {
         if (((PSPAWNINFO) pTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
            WriteChatColor("You cannot stick hold to yourself.");
            breakStick(true, true);
            return;
         }
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
         stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
         }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn=true;
         moveBehind=true;
       moveBehindOnce=false;
       movePin=false;
      } else if( !strncmp(currentArg,"behindonce",11) ) {
         stickOn=true;
         moveBehindOnce=true;
       moveBehind=false;
       movePin=false;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn=true;
         movePin=true;
       moveBehind=false;
       moveBehindOnce=false;
      } else if( !strncmp(currentArg,"reload",7) ) {
         breakStick();
         Load_INI();
         WriteChatColor("Ini file reloaded.");
         break;
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      stickHold = stickHold && stickTarget && stickTarget->SpawnID;
      if( (ppTarget && pTarget) || (stickHold && stickTarget && stickTarget->SpawnID) ) {
         if( FindSpeed((PSPAWNINFO)pCharSpawn) < 0 ) {
            DoFwd(false);
         }
         currentDist=GetDistance((PSPAWNINFO)pCharSpawn,stickHold?stickTarget:((PSPAWNINFO)pTarget));
      }
      stickText();
   }
}

VOID DoUnstickBind(PCHAR Name, BOOL Down)
{
   if( ! Down ) {
      keysDown--;
      if( keysDown == 0 && mPause ) {
         stickPaused = false;
         moveBehind = prevMoveBehind;
       movePin = prevMovePin;
      }
   } else {
      keysDown++;
      if(!stickPaused && stickOn ) {
         if( mPause && strncmp(Name,"UNSTICK_STRAFE_RGT",12) && strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            stickPaused = true;
         } else if( !strncmp(Name,"UNSTICK_STRAFE_RGT",12) || !strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            if( mPause ) {
               prevMoveBehind = moveBehind || prevMoveBehind;
               prevMovePin = movePin || prevMovePin;
               moveBehind = false;
            } else {
               prevMoveBehind = moveBehind = false;
               prevMovePin = movePin = false;
            }
         } else {
            breakStick((strncmp(Name,"UNSTICK_BCK",12) && strncmp(Name,"UNSTICK_FWD",12)));
         }
      }
   }
}

VOID HandleCircle() {
   static int counter = 0;
   double distance;
   double heading;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;

   if (!GetCharInfo() || !bCircling) return;
   double X = pChSpawn->X - CircleX;
   double Y = pChSpawn->Y - CircleY;
   distance = sqrt(X*X + Y*Y);

   if (distance>(CircleRadius*(2.0/3.0))) {
      heading=atan2(pChSpawn->Y - CircleY, CircleX - pChSpawn->X) * 180.0f / PI + 90.0f;
      heading += 90.0f * (CircleRadius/distance);
      heading *= 512.0f/360.0f;
      if( heading >= 512.0f ) heading -= 512.0f;
      if( heading < 0.0f ) heading += 512.0f;
      if( bDrunken ) {
         gFaceAngle = (float)heading;
      } else {
         pChSpawn->Heading = (float)heading;
      }
   }
}

float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

void HandleStick()
{
   if( (stickHold && (!stickTarget || !(stickTarget->SpawnID) || stickTarget_Type!=GetSpawnType(stickTarget))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
      return;
   }
   PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn->pActorInfo || !psTarget || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }
   float prevDist = currentDist;

   if( !setDist )
      stickDist = (psTarget->StandState?get_melee_range(pLocalPlayer,(EQPlayer *)psTarget):15.0f) * stickDistModP + stickDistMod;

   currentDist=GetDistance(pChSpawn,psTarget);
   if( breakOnWarpEnabled && (currentDist-prevDist) > breakDist ) {
      breakStick();
      return;
   }
   if ( ( ((long) (pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) || pChSpawn->StandState != STANDSTATE_STAND || pLPlayer->pCharInfo->Stunned==1 || psTarget->SpawnID == pChSpawn->SpawnID ) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }
   if( (!stickPaused && !casting) || !autoPauseEnabled ) {
      float newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / PI);
      if( newHeading >= 512.0f) newHeading -= 512.0f;
      if( newHeading < 0.0f ) newHeading += 512.0f;
      if (stickhasmovedfwd && fabs(pChSpawn->Heading - newHeading) >= 200 && currentDist < 10) {
         DoBck(true);
         return;
      } else {
         DoBck(false);
      }
      if( looseStick ) {
         gFaceAngle = newHeading;
      } else {
         pChSpawn->Heading = newHeading;
      }
      //underwater = pChSpawn->pActorInfo->UnderWater==5;
      if( underwater ) {
         double lookAngle = atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
         pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
         currentDist) * 256.0f / PI;
         if ( looseStick ) {
            gLookAngle = lookAngle;
         } else {
            pChSpawn->CameraAngle = (FLOAT)lookAngle;
         }
      }
      if ( currentDist > (stickDist * 3) ) {
         // too far away, dont do pin or behind
      } else if( moveBehind || moveBehindOnce ) {
         float angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
         if( fabs(angDist) > 45.0 ) {
            if(angDist < 0.0) {
               // strafe left
               DoLft(true);
            } else {
               // strage right
               DoRgt(true);
            }
         } else {
            moveBehindOnce = false;
            DoLft(false);
            DoRgt(false);
         }
      } else if (movePin) {
         FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
         if((angDist > 0 && angDist <= 112) || angDist < -144) { //blahblah
            DoLft(true);
         } else if ((angDist < 0 && angDist > -112) || angDist > 144) {
            DoRgt(true);
         } else {
            DoLft(false);
            DoRgt(false);
         }
      }
      if( currentDist > stickDist) {
         // if distance is less than 10, walk
         DoFwd(true, ((currentDist - stickDist) <= 10.0));
      } else if( moveBack && currentDist < (stickDist-5.0) ) {
         DoBck(true);
      } else {
         DoFwd(false);
         DoBck(false);
      }
   }
}

void
Load_INI(VOID)
{
   char szTemp[MAX_STRING], szTemp2[MAX_STRING];

   // Defaults
   GetPrivateProfileString("Defaults","AutoPause","on",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",autoPauseEnabled?"on":"off");
   WritePrivateProfileString("Defaults","AutoPause",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnWarp","on",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnWarpEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnWarp",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakDist","250.0",szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);
   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Defaults","BreakDist",szTemp,INIFileName);

   GetPrivateProfileString("Defaults","BreakOnGate","on",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);
   sprintf(szTemp,"%s",breakOnGateEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnGate",szTemp,INIFileName);

   stickVerbosity=(short)GetPrivateProfileInt("Defaults","Verbosity",1,INIFileName);
   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Defaults","Verbosity",szTemp,INIFileName);

   // Character specific
   GetPrivateProfileString(GetCharInfo()->Name,"AutoPause",autoPauseEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnWarp",breakOnWarpEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",breakDist);
   GetPrivateProfileString(GetCharInfo()->Name,"BreakDist",szTemp2,szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnGate",breakOnGateEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);
}

bool IsBardClass()
{
   if(strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class),"Bard",5))
      return false;
   else
      return true;
}

PLUGIN_API VOID OnPulse(VOID)
{

   if (bCircling) {
      if( bDrunken ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevCirc) > 900 + (int)getRand(600.0) ) {
            GetSystemTime(&stPrevCirc);
            HandleCircle();
         }
      } else {
         HandleCircle();
      }
   }
   if( stickOn ) {
      if( looseStick ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevStick) > 100 + (int)getRand(200.0) ) {
            GetSystemTime(&stPrevStick);
            HandleStick();
         }
      } else {
         HandleStick();
      }
   }
   if (bMoveToOn) {
      HandleMoveTo();
   }
}

void ReleaseKeys() {
   DoWalk(false);
   DoFwd(false);
   DoBck(false);
   DoRgt(false);
   DoLft(false);
}

void DoWalk(bool walk) {
//   if (!(pLocalPlayer)) {
//      WriteChatColor("pLocalPlayer is null");
//      return;
//   }
//   if (!(((PSPAWNINFO) pLocalPlayer)->pActorInfo)) {
//      WriteChatColor("pLocalPlayer->pActorInfo is null");
//      return;
//   }
   bool state_walking = (*EQADDR_RUNWALKSTATE) ? false : true;
   float SpeedModifier = *((float*) &(((PSPAWNINFO) pLocalPlayer)->pActorInfo->Unknown0x0af[25]));
//   float SpeedModifier = ((PSPAWNINFO) pLocalPlayer)->pActorInfo->SpeedModifier;
   if (SpeedModifier < 0)
      walk = false; // we're snared, dont go into walk mode no matter what
   if ( (walk && !state_walking) || (!walk && state_walking) ) {
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),1,0);
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),0,0);
   }
}

void DoFwd(bool hold, bool walk) {
   static bool held = false;
   if ( hold ) {
      stickhasmovedfwd = true;
      DoWalk(walk);
      DoBck(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
      held = true;
   } else {
      DoWalk(false);
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
      held = false;
   }
}

void DoBck(bool hold) {
   static bool held = false;
   if( hold ) {
      DoFwd(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
      held = false;
   }
}

void DoLft(bool hold) {
   static bool held = false;
   if( hold ) {
      DoRgt(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
      held = false;
   }
}

void DoRgt(bool hold) {
   static bool held = false;
   if( hold ) {
      DoLft(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
      held = false;
   }
}

float getRand(float n)
{
   return (n * rand() / (RAND_MAX+1.0f));
}

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev)
{
   SYSTEMTIME stResult;
   FILETIME ftPrev, ftCurr, ftResult;
   ULARGE_INTEGER prev,curr,result;

   GetSystemTime(&stCurr);
   SystemTimeToFileTime(&stPrev,&ftPrev);
   SystemTimeToFileTime(&stCurr,&ftCurr);
   prev.HighPart = ftPrev.dwHighDateTime;
   prev.LowPart = ftPrev.dwLowDateTime;
   curr.HighPart = ftCurr.dwHighDateTime;
   curr.LowPart = ftCurr.dwLowDateTime;
   result.QuadPart = curr.QuadPart - prev.QuadPart;
   ftResult.dwHighDateTime = result.HighPart;
   ftResult.dwLowDateTime = result.LowPart;
   FileTimeToSystemTime(&ftResult,&stResult);

   return ((int)(stResult.wSecond * 1000 + stResult.wMilliseconds));
}

void stickText()
{
   char szTemp[MAX_STRING];

   if( stickVerbosity == 1 ) {
      if( stickPaused ) {
         WriteChatColor("Stick paused.");
      } else if( stickOn ) {
         if( stickHold ) {
            sprintf(szTemp,"You are now sticking to %s.",stickTarget->DisplayedName);
         } else if( ppTarget && pTarget ) {
            sprintf(szTemp,"You are now sticking to %s.",((PSPAWNINFO)pTarget)->DisplayedName);
         } else {
            sprintf(szTemp,"Need a target for stick.");
         }
         WriteChatColor(szTemp);
      } else {
         WriteChatColor("You are no longer sticking to anything.");
      }
   }
}

void breakStick(bool stopMoving, bool quite)
{
   stickOn=false;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   moveBehindOnce=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickTarget=NULL;
   stickDistMod=0.0;
   stickDistModP=1.0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
   if (!quite)
      stickText();
}

class MQ2MoveToType *pMoveToType = 0;

class MQ2MoveToType : public MQ2Type
{
public:
   enum MoveToMembers {
      Moving=1,
      Stopped=2
   };

   MQ2MoveToType():MQ2Type("moveto")
   {
      TypeMember(Moving);
      TypeMember(Stopped);
   }

   ~MQ2MoveToType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MoveToType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MoveToMembers)pMember->ID)
      {
      case Moving:
         Dest.DWord=bMoveToOn;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
         Dest.DWord=(GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY)<=moveDist)?true:false;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

    bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if( bMoveToOn ) {
         strcpy(Destination,"ON");
      } else {
         strcpy(Destination,"OFF");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMoveTo(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMoveToType;
   return true;
}

VOID MoveToHelp()
{
   WriteChatColor("Usage: /moveto loc|off <y> <x> [<dist>|-<dist>]",USERCOLOR_DEFAULT);
   WriteChatColor("  Y and X are in the same order that /location prints them.",USERCOLOR_DEFAULT);
   WriteChatColor("  You can not call '/moveto <y> <x>' while circling or sticking.",USERCOLOR_DEFAULT);
   WriteChatColor("  /moveto <dist>   - Moves you within <dist> units of your target, default is 50");
   WriteChatColor("  /moveto -<dist>  - Subtracts <dist> units from the move distance");
}

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(szTemp,szLine,1);

   if (!stricmp(szTemp,"help") || szLine[0]==0 || bCircling || stickOn) {
      MoveToHelp();
      return;
   } else if (!stricmp(szTemp,"loc")) {
      GetArg(szTemp,szLine,2);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocY = atof(szTemp);
      }

      GetArg(szTemp,szLine,3);
      if (!strlen(szTemp)) {
         MoveToHelp();
         return;
      } else {
         LocX = atof(szTemp);
      }

      GetArg(szTemp,szLine,4);
      if (strlen(szTemp)) {
         if( isdigit(szTemp[0]) || szTemp[0]=='.' ) {
            moveDist = (float)atof(szTemp);
         } else if( szTemp[0]=='-' ) {
            moveDistMod = (float)atof(szTemp);
            moveDist += moveDistMod;
         }
      }

      bMoveToOn=true;
      sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
      if (stickVerbosity)
         WriteChatColor(szMsg, CONCOLOR_YELLOW);
   } else if (!stricmp(szTemp,"off")) {
      bMoveToOn = false;
      if (stickVerbosity)
         WriteChatColor("MoveTo off.", CONCOLOR_YELLOW);
   } else {
      MoveToHelp();
      return;
   }
}

void HandleMoveTo()
{
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   if(((long)(pChSpawn->pActorInfo->CastingSpellID)) >= 0 && !(IsBardClass()) ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
      }
   } else if( casting ){
      casting = false;
   }

   float newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / PI);
   if( newHeading >= 512.0f)
      newHeading -= 512.0f;
   if( newHeading < 0.0f )
      newHeading += 512.0f;
   pChSpawn->Heading = newHeading;

   if( GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist) {
      DoFwd(true);
   } else {
      bMoveToOn=false;
      if (stickVerbosity)
         WriteChatColor("Arrived at MoveTo location", CONCOLOR_YELLOW);
      DoFwd(false);
   }
}

VOID CreateBinds(){
   if (MoveBindsLoaded)
      return;
   MoveBindsLoaded=true;
   AddMQ2KeyBind("UNSTICK_FWD",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_BCK",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_RGT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_RGT",DoUnstickBind);
   SetMQ2KeyBind("UNSTICK_FWD",false,pKeypressHandler->NormalKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_FWD",true,pKeypressHandler->AltKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_BCK",false,pKeypressHandler->NormalKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_BCK",true,pKeypressHandler->AltKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_right")]);
}

VOID DestroyBinds()
{
   if (!MoveBindsLoaded)
      return;
   RemoveMQ2KeyBind("UNSTICK_FWD");
   RemoveMQ2KeyBind("UNSTICK_BCK");
   RemoveMQ2KeyBind("UNSTICK_LFT");
   RemoveMQ2KeyBind("UNSTICK_RGT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_LFT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_RGT");
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2MoveUtils");

   // Add commands, macro parameters, hooks, etc.
   AddCommand("/circle",CircleCommand,0,1,1);
   AddCommand("/stick",StickCommand);
   AddMQ2Data("Stick",dataStick);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("MoveTo",dataMoveTo);

   pStickType = new MQ2StickType;

   srand((unsigned int)time(NULL));
   if (gGameState==GAMESTATE_INGAME) {
      CreateBinds();
   }
   GetSystemTime(&stPrevCirc);
   GetSystemTime(&stPrevStick);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("Shutting down MQ2MoveUtils");

   // Remove commands, macro parameters, hooks, etc.
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   RemoveMQ2Data("MoveTo");
   RemoveCommand("/moveto");

   delete pStickType;
   delete pMoveToType;

   DestroyBinds();
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if( breakOnGateEnabled && (stickHold?(stickTarget!=NULL):(ppTarget && pTarget)) ) {
      char szTemp[MAX_STRING];

      sprintf(szTemp,"%s Gates.",stickHold?stickTarget->DisplayedName:((PSPAWNINFO)pTarget)->DisplayedName);
      if( ! strcmp(szTemp,Line) ) {
         DoFwd(false);
         stickOn = false;
      }
   }

   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   if (GameState==GAMESTATE_INGAME) {
      CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      stickPaused=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
   }
}

// Called after entering a new zone
PLUGIN_API VOID OnZoned(VOID)
{
   DoWalk(false);
}
 
Last edited:
Re: Fixed

i keep getting this message when I try to compile

Compiling...
MQ2Moveutils.cpp
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(486) : error C2065: 'sti' : undeclared identifier
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(486) : error C2146: syntax error : missing ')' before identifier 'ckHold'
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(486) : error C2059: syntax error : ')'
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(821) : error C2065: 'GetChar' : undeclared identifier
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(821) : error C2146: syntax error : missing ')' before identifier 'Info'
C:\M2\MQ2Moveutils\MQ2Moveutils.cpp(821) : error C2059: syntax error : ')'
Error executing cl.exe.

MQ2Moveutils.dll - 6 error(s), 0 warning(s)


I went to command prompt and used the mkplugin mq2moveutils, then copied the code posted in this thread over the stuff that was in the mq2moveutils.cpp file. I then added the project to the mq2 workspace and tried to compile it, but get this error, what am I doing wrong?
 
Re: Fixed

Yep, I've had that trouble with several pieces of code in this thread.

Sti ckHold should be StickHold
Char Info should be CharInfo

I wonder what's inserting the spaces. Trying to edit them out now, but hey, they're not there in the edit box. :(

Oh well. Go to the errors, and look at the spots where they occur. There will be a space or two in there. Delete the spaces. I wonder if a space has krept in to the other movutils code somewhere less noticeable, and that's why it's acting strangely.
 
Re: Fixed

this is the moveutils i am currently using and works

for those that need it
 
Last edited:
Re: Fixed

armysoldier said:
this is the moveutils i am currently using and works

for those that need it


THKS THKS Armysoldier
i will use yours if i can t solve issue myself
:cool:
 
Re: Fixed

I am getting a problem with cades compile, anyone have a fix for this:

Null Pointer, breaking stick
You are no longer sticking to anything.

I see it when i run a macro that calls moveutils
 
Re: Fixed

posting .CPP and .DLL
for

moveutil
twist
autoskill

moveutil and twist tested... and working... circle is choppy .. but works ...
 
Re: Fixed

Twist fixed for new structs:

Rich (BB code):
// MQ2Twist.cpp - Bard song twisting plugin for MacroQuest2
//
//    koad 03-24-04 Original plugin (http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=5962&start=2)
//    CyberTech 03-31-04 w/ code/ideas from Falco72 & Space-boy
//    Cr4zyb4rd 08-19-04 taking over janitorial duties
//    Pheph 08-24-04 cleaning up use of MQ2Data

/*
   MQ2Twist Version 1.3

      Usage:   
         /twist # # # # # - Twists in the order given.
            Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.
            These may be mixed in any order, and repeats are allowable. Up to 10 may be
            specified.
            If a song is specified with a duration longer than standard (ie, selos)
            that song will be twisted based on it's duration.  For example, riz+mana+selos
            would be a 2 song twist with selos pulsed every 2.5 min.
         /twist once # # # # # - Twists in the order given, then reverts to original twist
         /twist hold <gem #> - Pause twisting and sing only the specified song
            /sing <gem#> - alias for /twist hold
         /twist stop/end/off - stop twisting, does not clear the twist queue
            /stoptwist - alias for above
         /twist or /twist start - Resume the twist after using /twist hold or /twist stop
       /twist reset - Reset timers for item clicks and long duration songs
         /twist delay # - 10ths of a second, minimum of 30, default 33
         /twist adjust # - in ticks, how early to recast long duration songs
         /twist reload - reload the INI file to update item clicks
         /twist slots - List the slots/items defined in the INI and their #'s
         /twist quiet - Toggles songs listing and start/stop messages for one-shot twists

      ----------------------------
      Item Click Method:
         MQ2Twist uses /itemnotify slotname rightmouseup to perform item clicks.

         The INI file allows you to specify items by name (with name=itemname), or by
       inventory slot (with slot=slotname).  If both a name and slot are defined for an
       item, the plugin will attempt to swap the item into that slot (via the /exchange
       command) and replace the original item when casting is complete.
      
       The example INI file below contains examples of the types of usage.

      ----------------------------
      Examples:
         /twist 1
            Sing gem 1 forever
         /twist 1 2 3
            Twist gems 1,2, and 3 forever
         /twist 1 2 3 10
            Twist gems 1,2,3, and clicky 10, forever
         /twist hold 4 or /sing 4
            Sing gem 4 until another singing-related /twist command is given

      ----------------------------
      MQ2Data Variables:
         bool   Twist         Currently Twisting: true/false, if NULL plugin is not loaded
         Members:
            bool     Twisting  Currently twisting: true/false.
            int      Current   Returns the curent gem number being sung, -1 for item, or 0 if not twisting
            int      Next      Returns the next gem number to be sung, -1 for item, or 0 if not twistsing
            string   List      Returns the twist sequence in a format suitable for /twist

      ----------------------------
     
     The ini file has the format:
         [MQ2Twist]
         Delay=32       Delay between twists. Lag & System dependant.
       Adjust=1       This defines  how many ticks before the 'normal' recast time to cast a long song.
                        Long songs are defined as songs greater than 3 ticks in length.  If set to 1 tick,
                        and a song lasts 10 ticks, the song will be recast at the 8 tick mark, instead of
                        at the 9 tick mark as it normally would.

         [Click_10] thru [Click_19]
         CastTime=30              Casting Time, -1 to use the normal song delay
         ReCastTime=0             How often to recast, 0 to twist normally.
         Name="Fife of Battle"    Item name for /itemnotify
       Slot=neck                Slot name for /itemnotify

         Delay, CastTime and ReCastTime are specified in 10ths of a
         second, so 10 = 1 second, and so on.

         INI File Example:
            [MQ2Twist]
            Delay=31
         Quiet=0

            ;Shadowsong cloak
            [Click_10]
            CastTime=30
            ReCastTime=350
            Name=Shadowsong Cloak
            Slot=DISABLED

            ;girdle of living thorns (current belt will be swapped out)
            [Click_11]
            CastTime=0
            ReCastTime=11600
            Name=Girdle of Living Thorns
            Slot=waist

            ;nature's melody
            [Click_12]
            CastTime=-1
            ReCastTime=135
            Name=DISABLED
            Slot=mainhand

            ;lute of the flowing waters
            [Click_13]
            CastTime=0
            ReCastTime=0
            Name=Lute of the Flowing Waters
            Slot=DISABLED

            [Click_14] ... [Click_19]
            CastTime=33
            ReCastTime=0
            Name=DISABLED
            Slot=DISABLED

      ----------------------------

Changes:
   10-05-04
      Support "swap in and click" items

    09-15-04
      Support extra spell slot from Omens of War AA

    09-01-04
      Command: /twist quiet to toggle some of the spam on/off
      Various code fixes/speedups

    08-29-04
      Moved LONGSONG_ADJUST into INI file and made /twist adjust command to set it on
      the fly

   08-25-04
      Changed output for /twist once to be slightly less misleading
      Reset click/song timers every time they're called with /twist hold or /twist once;
      if the user's specifying that song, they obviously want to cast it anyway.
      Removed the variable MissedNote as close inspection revealed the only place it was
      checked for was the line that set it. /boggle
      Minor code tweaks, cleanups, formatting changes, etc

   08-24-04 (Pheph)
      Modified it to use only one TLO, as I found it somewhat messy having 4 different ones.
      All the functionality of the old TLO's are now members of ${Twist}
      ${Twising} is now ${Twist.Twisting}, or just ${Twist}
      ${TwistCurrent} is now ${Twist.Current}
      ${TwistNext} is now ${Twist.Next}
      ${TwistList} is now ${Twist.List}

   08-23-04
      Reset_ItemClick_Timers was being called far too often.  Now the only time we reset
     is if a new list of songs are specified.  "/twist ${TwistList}" is a useful alias
     if you for some reason want the old behavior.
      Sing or /twist hold now resets the cast/item timer for that song only, rather than
     the entire list.
     Command: /twist reset calls Reset_ItemClick_Timers without interfering with the
     state of the current twists.
    
   08-22-04
      Command: /twist once [songlist] will cycle through the songs entered once, then
     revert to the old twist, starting with the song that was interrupted.
     Removed command "/twist on", it was making the string compare for "once" annoying,
     and I didn't think it was worth the effort for a redundant command.
      /twist delay with no argument now returns the delay without resetting it.  Values
     less than 30 now give a warning...maybe they're not bards or have some other
     reason for using a low value.

   08-19-04
      Minor revamp of item notification.  Removed ITEMNOTIFY define and kludged in some
      changes from Virtuoso65 to get casting by item name working.  /cast is no longer
      used.
      Added INI file support for above change.  File now uses distinct entries for item
      names and slots.  *Quotes not required for multi-word item names in INI.*
      Fixed the MQ2Data value TwistCurrent to display the current song as-advertised, and
      added a new value TwistNext with the old behavior of showing the next song in the
      queue. (Useful in scripting)
      Removed a few DebugSpews that were mega-spamming my debugger output.
      CastTime of -1 in the INI file now causes the default delay to be used.
   
   06-01-04
      Added LONGSONG_ADJUST (default to 1 tick) to help with the timing of recasting long
      songs, such as selo's.
      Twisting is now paused when you sit (this would include camping).  This fixes
      problems reported by Chyld989 (twisting across chars) and Kiniktoo (new autostand on
      cast 'feature' in EQ makes twisting funky)

   05-19-04
      Added workaround for incorrect duration assumption for durationtype=5 songs, such as
      Cassindra's Chant of Clarity or Cassindra's Chorus of Clarity.
      Added check of char state before casting a song. Actually added for 1.05
         Checked states and resulting action are:
            Feigned, or Ducking = /stand
            Stunned = Delay
            Dead - Stop twisting.
         If you're a monk using this to click your epic, you'll want to disable the autostand on feign code =)


   05-05-05
      Fixed CTD on song unmem or death, while twisting.  Oops
      Removed circle functionality.  It's better suited for a plugin like the MQ2MoveUtils
         plugin by tonio at http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=6973

   05-01-04
      Fixed problem with using pchar before state->ingame causing CTD on eq load (thanks MTBR)
      Fixed vc6 compile error w/ reset_itemclick_timers
      Replaced various incantations of pChar and pSpawn with GetCharInfo()
      Fixed /circle behavior w/ unspecified y/x
      Fixed /circle on when already circling and you want to update loc
      Added output of parsed circle parameters on start.

   04-25-04
      Converted to MQ2Data
         Top Level Objects:
            bool   Twisting      (if NULL plugin is not loaded)
            int      TwistCurrent
            string   TwistList
      Removed $Param synatax for above
      Added check to make sure item twists specified are defined
      Fixed error with twist parameter processing
      Changed twist startup output to be more verbose
      Command: /twist on added as alias for /twist start
      INI File is now named per-character (MQ2Twist_Charname.ini)
         * Be sure to rename existing ini files
      Modified twist routine to take into account songs with
         non-0 recast times or longer than 3 tick durations,
         and only re-cast them after the appropriate delay.
         This is for songs like Selos 2.5 min duration, etc.
         * Note that this makes no attempt to recover if the song
         effect is dispelled, your macro will need to take care
         of that.
      Added ability to compile-time change the method used for
         clicking items.

   04-13-04
      Changed /circle command to allow calling w/o specifying loc
      Corrected a problem with multiple consecutive missed notes
      Added handling of attempting to sing while stunned
      Command: /twist slots, to list the slot to # associations
      Command: /twist reload, to reload the ini file on the fly
      Command: /twist end, /twist off as aliases for /twist stop
      Command: /sing #, as an alias for /twist hold #

      Added support for item clickies.  Clickies are specified
      as "gem" 10-19. For example, /twist 1 2 10 12

      Added INI file support for storing item clicky info
      and default twist delay.

   04-11-04
      Integrated the /circle code from Easar, runs in a circle.  type
      /circle for help.
*/

#include "../MQ2Plugin.h"

PreSetup("MQ2Twist");

typedef struct _ITEMCLICK {
   int cast_time;
   int recast;
   long castdue;
   int disabled;
   int nousename;
   CHAR slot[MAX_STRING];
   CHAR name[MAX_STRING];
} ITEMCLICK;

int MQ2TwistEnabled = 0;
const int MAX_SONG=10;
int LONGSONG_ADJUST=1; // In TICKS, not seconds.  Used for long songs (greater than 3 ticks in duration). See docs.
int CAST_TIME=33;
int NumSongs=0;
int AltNumSongs=0;
int Song[MAX_SONG*2];
int AltSong[MAX_SONG*2];
long SongNextCast[MAX_SONG*2];
ITEMCLICK ItemClick[MAX_SONG];
int CurrSong=0;
int AltCurrSong=0;
int PrevSong=0;
int HoldSong=0;
long CastDue=0;
bool bTwist=false;
bool altTwist=false;
bool quiet;
CHAR SwappedOutItem[MAX_STRING];
CHAR SwappedOutSlot[MAX_STRING];

long GetTime();
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine);
BOOL dataTwist(PCHAR szIndex, MQ2TYPEVAR &Ret);
CHAR MQ2TwistTypeTemp[MAX_STRING]={0};

//get current timestamp in tenths of a second
long GetTime()
{
   SYSTEMTIME st;
   ::GetSystemTime(&st);
   long lCurrent=0;
   lCurrent  = st.wDay    * 24 * 60 * 60 * 10;
   lCurrent += st.wHour        * 60 * 60 * 10;
   lCurrent += st.wMinute           * 60 * 10;
   lCurrent += st.wSecond                * 10;
   lCurrent += (long)(st.wMilliseconds/100);
   return (lCurrent);
}

VOID MQ2TwistDoCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   HideDoCommand(pChar, szLine, FromPlugin);
}

VOID DoSwapOut()
{
   CHAR szTemp[MAX_STRING];
   if (SwappedOutItem[0]) {
      sprintf(szTemp,"/exchange \"%s\" %s",SwappedOutItem,SwappedOutSlot);
      MQ2TwistDoCommand(NULL, szTemp);
      SwappedOutItem[0]=0;
   }
}

VOID DoSwapIn(int Index)
{
   CHAR szTemp[MAX_STRING];
   if (strnicmp(ItemClick[Index].slot,"DISABLED",8)) {
      sprintf(szTemp,"${InvSlot[%s].Item.Name}",ItemClick[Index].slot);
      ParseMacroData(szTemp);
      strcpy(SwappedOutItem,szTemp);
      strcpy(SwappedOutSlot,ItemClick[Index].slot);
      sprintf(szTemp,"/exchange \"%s\" %s",ItemClick[Index].name,ItemClick[Index].slot);
      MQ2TwistDoCommand(NULL, szTemp);
   }
}

VOID Reset_ItemClick_Timers()
{
   int i;
   for (i=0;i<10;i++) {
      ItemClick.castdue = 0;
   }
   for (i=0;i<MAX_SONG*2;i++) {
      SongNextCast = 0;
   }
}


VOID Update_INIFileName() {
   if (GetCharInfo()) {
      sprintf(INIFileName,"%s\\MQ2Twist_%s.ini",gszINIPath,GetCharInfo()->Name);
   } else {
      sprintf(INIFileName,"%s\\MQ2Twist.ini",gszINIPath);
   }
}

VOID Load_MQ2Twist_INI()
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szSection[MAX_STRING]={0};

   Update_INIFileName();

   CAST_TIME = GetPrivateProfileInt("MQ2Twist","Delay",33,INIFileName);
   sprintf(szTemp, "%d", CAST_TIME);
   WritePrivateProfileString("MQ2Twist","Delay",szTemp,INIFileName);
   quiet = GetPrivateProfileInt("MQ2Twist","Quiet",0,INIFileName)? 1 : 0;
   sprintf(szTemp, "%d", quiet);
   WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
   
   LONGSONG_ADJUST = GetPrivateProfileInt("MQ2Twist","Adjust",1,INIFileName);
   sprintf(szTemp, "%d", LONGSONG_ADJUST);
   WritePrivateProfileString("MQ2Twist","Adjust",szTemp,INIFileName);
   
   for (int i=0;i<10;i++) {
      sprintf(szSection, "Click_%d", i+10);
      ItemClick.cast_time = GetPrivateProfileInt(szSection,"CastTime",0,INIFileName);
      ItemClick.recast = GetPrivateProfileInt(szSection,"ReCastTime",0,INIFileName);

      GetPrivateProfileString(szSection,"Name","DISABLED",ItemClick.name,MAX_STRING,INIFileName);
      GetPrivateProfileString(szSection,"Slot","DISABLED",ItemClick.slot,MAX_STRING,INIFileName);
      if(!strnicmp("DISABLED", ItemClick.name, 8)) {
         if (!strnicmp("DISABLED", ItemClick.slot, 8)) {
            ItemClick.disabled = true;
            DebugSpew("MQ2Twist: Slot %d disabled",i+1);
         } else {
            ItemClick.nousename = true;
            ItemClick.disabled = false;
         }
      } else ItemClick.disabled = false;
      // Write the values above back to disk, mostly to initialize it for easy editing.
      sprintf(szTemp, "%d", ItemClick.cast_time);
      WritePrivateProfileString(szSection,"CastTime",szTemp,INIFileName);
      // If the CastTime is set to -1 in the INI file, use the default.
      ItemClick.cast_time = ItemClick.cast_time==-1 ? CAST_TIME : ItemClick.cast_time;

      sprintf(szTemp, "%d", ItemClick.recast);
      WritePrivateProfileString(szSection,"ReCastTime",szTemp,INIFileName);
      WritePrivateProfileString(szSection,"Name",ItemClick.name,INIFileName);
      WritePrivateProfileString(szSection,"Slot",ItemClick.slot,INIFileName);
      DebugSpewAlways("Initializing MQ2Twist: Processed %s", szSection);
   }
}

VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   int i;

   GetArg(szTemp,szLine,1);
   i=atoi(szTemp);

   if (i>=1 && i<=19) { // valid range?
      HoldSong = i;
      bTwist=true;
      CastDue = -1;
      sprintf(szMsg, "MQ2Twist::Holding Twist and casting gem %d", HoldSong);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      MQ2TwistDoCommand(pChar,"/stopsong");
      if (i>10) { //item?
         ItemClick[i-10].castdue = 0;
      } else SongNextCast = 0; //nope, song
   } else WriteChatColor("MQ2Twist::Invalid gem specified, ignoring",USERCOLOR_DEFAULT);
}

VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   bTwist=false;
   HoldSong=0;
   MQ2TwistDoCommand(pChar,"/stopsong");
   WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
}

VOID PrepNextSong() {
   if (CurrSong>NumSongs) {
      if (altTwist) {
         NumSongs=AltNumSongs;
         CurrSong=PrevSong=AltCurrSong;
         for (int i=0; i<NumSongs; i++) Song=AltSong;
         altTwist=false;
         if (!quiet) WriteChatColor("MQ2Twist::One-shot twist ended, normal twist will resume next pulse",USERCOLOR_DEFAULT);
      } else CurrSong=1;
   }
}

VOID DisplayTwistHelp() {
   WriteChatColor("MQ2Twist - Twist song or songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage:   /twist <gem#> - Twists in the order given.",USERCOLOR_DEFAULT);
   WriteChatColor("  Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.",USERCOLOR_DEFAULT);
   WriteChatColor("  These may be mixed in any order, and repeats are allowable.",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist hold <gem #> - Pause twisting and sing only the specified song",USERCOLOR_DEFAULT);
   WriteChatColor("  /sing <gem#> - alias for /twist hold",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist once <gem#> Twists once in the order given, then reverts to original twist",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist or /twist start - Resume the twist after using /twist hold or /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reset - Reset timers for item clicks and long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist delay # - 10ths of a second, minimum of 30, default 33",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist adjust # - in ticks, how early to recast long duration songs",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist stop/end/off - stop twisting, does not clear the twist queue",USERCOLOR_DEFAULT);
   WriteChatColor("  /stoptwist - alias for /twist stop",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist reload - reload the INI file to update item clicks",USERCOLOR_DEFAULT);
   WriteChatColor("Usage: /twist slots - List the slots defined in the INI and their #'s",USERCOLOR_DEFAULT);
}

// ***************************************************************************
// Function:      TwistCommand
// Description:   Our /twist command. sing for me!
// ***************************************************************************
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   CHAR szTemp[MAX_STRING]={0};
   CHAR szMsg[MAX_STRING]={0};
   CHAR szChat[MAX_STRING]={0};
   PSPELL pSpell;
   int i;

   GetArg(szTemp,szLine,1);

   if (NumSongs && (!strlen(szTemp) || !strnicmp(szTemp,"start", 5))) {
      WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
      DoSwapOut();
      bTwist=true;
      HoldSong=0;
      CastDue = -1;
      return;
   }

   if (!strnicmp(szTemp,"stop", 4) || !strnicmp(szTemp,"end", 3) || !strnicmp(szTemp,"off", 3)) {
      DoSwapOut();
      StopTwistCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"slots", 5)) {
      WriteChatColor("MQ2Twist 'Song' Numbers for right click effects:",USERCOLOR_DEFAULT);
      for (i=0;i<10;i++) {
         if (ItemClick.disabled) break;
         if (ItemClick.nousename) {
            sprintf(szMsg, "  %d = %s (slot)", i+10, ItemClick.slot);
         } else {
            sprintf(szMsg, "  %d = %s (name) %s (slot)", i+10, ItemClick.name, ItemClick.slot);
       }
         WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      }
      WriteChatColor("---",USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"reload", 6)) {
      WriteChatColor("MQ2Twist::Re-Loading INI Values",USERCOLOR_DEFAULT);
      Load_MQ2Twist_INI();
      return;
   }

   if (!strnicmp(szTemp,"delay", 5)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         if (i<=30) {
            WriteChatColor("MQ2Twist::WARNING delay specified is less than standard song cast time",CONCOLOR_RED);
         }
         CAST_TIME=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Delay",itoa(CAST_TIME, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Set delay to %d, INI updated", CAST_TIME);
      } else sprintf(szMsg, "MQ2Twist::Delay %d", CAST_TIME);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"quiet", 5)) {
      quiet=!quiet;
      sprintf(szTemp,"%d",quiet);
      WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
      sprintf(szMsg,"MQ2Twist::Now being %s",quiet ? "quiet" : "noisy");
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }
   
   if (!strnicmp(szTemp,"adjust", 6)) {
      GetArg(szTemp,szLine,2);
      if (strlen(szTemp)>0) {
         i=atoi(szTemp);
         LONGSONG_ADJUST=i;
         Update_INIFileName();
         WritePrivateProfileString("MQ2Twist","Adjust",itoa(LONGSONG_ADJUST, szTemp, 10),INIFileName);
         sprintf(szMsg, "MQ2Twist::Long song adjustment set to %d, INI updated", LONGSONG_ADJUST);
      } else sprintf(szMsg, "MQ2Twist::Long song adjustment: %d", LONGSONG_ADJUST);
      WriteChatColor(szMsg,USERCOLOR_DEFAULT);
      return;
   }

   if (!strnicmp(szTemp,"hold", 4)) {
      GetArg(szTemp,szLine,2);
      SingCommand(pChar, szTemp);
      return;
   }

   if (!strnicmp(szTemp,"reset", 5)) {
      Reset_ItemClick_Timers();
      WriteChatColor("MQ2Twist::Timers reset",CONCOLOR_YELLOW);
      return;
   }

   // check help arg, or display if we have no songs defined and /twist was used
   if (!strlen(szTemp) || !strnicmp(szTemp,"help", 4)) {
      DisplayTwistHelp();
      return;
   }

   // if we are "one-shot twisting", save the current song array and current song
   if (!strnicmp(szTemp,"once", 4)) {
      WriteChatColor("MQ2Twist one-shot twisting:",CONCOLOR_YELLOW);
      if (altTwist) {
         CurrSong=NumSongs+1;
         PrepNextSong(); // If CurrSong > NumSongs relaod the song list
      }
      if (NumSongs) {
         AltNumSongs=NumSongs;
         AltCurrSong=CurrSong;
         for (i=0; i<NumSongs; i++) AltSong=Song;
      }
      altTwist=true;
   } else altTwist=false;

   DoSwapOut();
   DebugSpew("MQ2Twist::TwistCommand Parsing twist order");
   NumSongs=0;
   HoldSong=0;
   if (!altTwist) {
      if (!quiet) {
         WriteChatColor("MQ2Twist Twisting:",CONCOLOR_YELLOW);
      } else WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
   }
   for (i=0 + altTwist ? 1 : 0; i<20; i++)
   {
      GetArg(szTemp,szLine,i+1);
      if (!strlen(szTemp))  break;

      Song[NumSongs]=atoi(szTemp);
      if (Song[NumSongs]>=1 && Song[NumSongs]<=19) {
         if ((Song[NumSongs]>9) && ItemClick[Song[NumSongs]-10].disabled) {
            sprintf(szChat, " Undefined item specified (%s) - ignoring (see INI file)", szTemp);
            WriteChatColor(szChat,CONCOLOR_RED);
         } else {
            sprintf(szMsg, " %s - ", szTemp);

            if (Song[NumSongs]<=9) {
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[NumSongs]-1]);
               if (altTwist) SongNextCast[NumSongs] = 0;
               if (pSpell) strcat(szMsg, pSpell->Name);
            } else {
               if (ItemClick[Song[NumSongs]-10].nousename) {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].slot);
               } else {
                  strcat(szMsg, ItemClick[Song[NumSongs]-10].name);
               }
               if (altTwist) ItemClick[NumSongs].castdue = 0;
            }
            if (!quiet) WriteChatColor(szMsg,COLOR_LIGHTGREY);
            NumSongs++;
         }
      } else {
         sprintf(szChat, " Invalid gem specified (%s) - ignoring", szTemp);
         WriteChatColor(szChat,CONCOLOR_RED);
      }
   }

   sprintf(szTemp, "Twisting %d song%s", NumSongs, NumSongs>1 ? "s" : "");
   if (!quiet) WriteChatColor(szTemp,CONCOLOR_YELLOW);

   if (NumSongs>0) bTwist=true;
   CurrSong = 1;
   PrevSong = 1;
   CastDue = -1;
   MQ2TwistDoCommand(pChar,"/stopsong");
   if (!altTwist) Reset_ItemClick_Timers();
}

/*
Checks to see if character is in a fit state to cast next song/item

Note 1: Do not try to correct SIT state, or you will have to stop the
twist before re-memming songs

Note 2: Since the auto-stand-on-cast bullcrap added to EQ a few patches ago,
chars would stand up every time it tried to twist a song.  So now
we stop twisting at sit.
*/
BOOL CheckCharState() {
   if (!bTwist) return FALSE;

   if (GetCharInfo()) {
      if (GetCharInfo()->Stunned==1) return FALSE;
      switch (GetCharInfo()->standstate) {
       case STANDSTATE_SIT:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       case STANDSTATE_FEIGN:
          MQ2TwistDoCommand(NULL,"/stand");
          return FALSE;
          break;
       case STANDSTATE_DEAD:
          WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
          bTwist = FALSE;
          return FALSE;
          break;
       default:
          break;
      }
   }

   if (pCastingWnd) {
      PCSIDLWND pCastingWindow = (PCSIDLWND)pCastingWnd;
      if (pCastingWindow->Show == 1) return FALSE;
      // Don't try to twist if the casting window is up, it implies the previous song
      // is still casting, or the user is manually casting a song between our twists
   }
   return TRUE;
}
class MQ2TwistType *pTwistType=0;

class MQ2TwistType : public MQ2Type
{
public:
   enum TwistMembers
   {
      Twisting=1,
      Next=2,
      Current=3,
      List=4,
   };

   MQ2TwistType():MQ2Type("twist")
   {
      TypeMember(Twisting);
      TypeMember(Next);
      TypeMember(Current);
      TypeMember(List);
   }
   ~MQ2TwistType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2TwistType::FindMember(Member);
      if (!pMember)
         return false;
      switch((TwistMembers)pMember->ID)
      {
      case Twisting:
         /* Returns: bool
            0 - Not Twisting
            1 - Twisting
         */
         Dest.Int=bTwist;
         Dest.Type=pBoolType;
         return true;
      case Next:
         /* Returns: int
            0 - Not Twisting
            -1 - Casting Item
            1-9 - Current Gem
         */
         Dest.Int=HoldSong ? HoldSong : Song[CurrSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case Current:
         Dest.Int=HoldSong ? HoldSong : Song[PrevSong-1];
         if (Dest.Int>9) Dest.Int = -1;
         if (!bTwist) Dest.Int = 0;

         Dest.Type=pIntType;
         return true;
      case List:
         /* Returns: string
            Space separated list of gem and item #'s being twisted, in order
         */
         int a;
         CHAR szTemp[MAX_STRING] = {0};

         MQ2TwistTypeTemp[0] = 0;
         for (a=0; a<NumSongs; a++) {
            sprintf(szTemp, "%d ", Song[a]);
            strcat(MQ2TwistTypeTemp, szTemp);
         }

         Dest.Ptr=&MQ2TwistTypeTemp[0];
         Dest.Type=pStringType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if (bTwist)
         strcpy(Destination,"TRUE");
      else
         strcpy(Destination,"FALSE");
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataTwist(PCHAR szName, MQ2TYPEVAR &Dest)
{
   Dest.DWord=1;
   Dest.Type=pTwistType;
   return true;
}


// ******************************
// **** MQ2 API Calls Follow ****
// ******************************

PLUGIN_API VOID InitializePlugin(VOID)
{
   DebugSpewAlways("Initializing MQ2Twist");

   AddCommand("/twist",TwistCommand,0,1,1);
   AddCommand("/sing",SingCommand,0,1,1);
   AddCommand("/stoptwist",StopTwistCommand,0,0,1);;
    AddMQ2Data("Twist",dataTwist);

    pTwistType = new MQ2TwistType;

}

PLUGIN_API VOID ShutdownPlugin(VOID)
{
   DebugSpewAlways("MQ2Twist::Shutting down");

   RemoveCommand("/twist");
   RemoveCommand("/sing");
   RemoveCommand("/stoptwist");
    RemoveMQ2Data("Twist");

    delete pTwistType;
}

PLUGIN_API VOID OnPulse(VOID)
{
   CHAR szTemp[MAX_STRING] = {0};
   PSPELL pSpell;
   int a,b;

   if (!MQ2TwistEnabled || !CheckCharState()) return;

   if ((HoldSong>0) || ((NumSongs==1) && !altTwist)) {
      // DebugSpew("MQ2Twist::Pulse - Single Song");
      if ( CastDue<0 || ( ((CastDue-GetTime()) <= 0 ) && (GetCharInfo()->pSpawn->CastingSpellID == -1) ) ) {
         int SongTodo = HoldSong ? HoldSong : Song[0];
         if (SongTodo <= 9) {
            DebugSpew("MQ2Twist::Pulse - Single Song (Casting Gem %d)", SongTodo);
            sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
            MQ2TwistDoCommand(NULL,szTemp);
            CastDue = GetTime()+CAST_TIME;
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = ItemClick[SongTodo-10].castdue;
            }
         }
      }
   } else {
      int SongTodo = Song[CurrSong-1];
      if (NumSongs && ((CastDue-GetTime()) <= 0)) {
         DoSwapOut();
         if (SongTodo <= 9) {
            if (SongNextCast[CurrSong-1]-GetTime() <= 0) {
               DebugSpew("MQ2Twist::OnPulse - Next Song = %s", szTemp);
               sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
               MQ2TwistDoCommand(NULL,szTemp);
               pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[CurrSong-1]-1]);
               if(!pSpell) {
                  WriteChatColor("Songs not present - suspending twist.  /twist to resume",CONCOLOR_RED);
                  bTwist = FALSE;
                  return;
               }
               a = pSpell->RecastTime/1000 * 60;                     // recasttime in 10's of a second
               b = GetSpellDuration(pSpell,GetCharInfo()->pSpawn) * 60;   // duration in 10's of a second
               if (pSpell->DurationType == 5 && !pSpell->DurationValue1) {
                  b = 18;   //FIXME - Remove once GetSpellDuration handles duration type5
               }
               CastDue = GetTime()+CAST_TIME;
               if (a > 0 || b > 180) { // We only care about songs with > 3 tick durations or non-0 recast times
                  SongNextCast[CurrSong-1] = GetTime() + (a > b ? a : b) - CAST_TIME - (LONGSONG_ADJUST*60);   // Cast next after greater of recasttime or duration, minus LONGSONG_ADJUST ticks.
               } else {
                  SongNextCast[CurrSong-1] = CastDue;
               }
               PrevSong=CurrSong;
            } // if it's not time for currsong to be re-sung, skip it in the twist
            CurrSong++;
            PrepNextSong();
         } else {
            if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
               if (ItemClick[SongTodo-10].nousename) {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Slot %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
               } else {
                  DebugSpew("MQ2Twist::Pulse - Next Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
                  DoSwapIn(SongTodo-10);
                  sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
               }
               MQ2TwistDoCommand(NULL,szTemp);
               ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
               CastDue = GetTime()+ItemClick[SongTodo-10].cast_time;
            }
            PrevSong=CurrSong;   // Increment twist position even if we didn't do an itemnotify - this might have a long recast
            CurrSong++;         // interval set, and we just skip it until it's time to recast, rather than keep a separate timer.
            PrepNextSong();
         }   
      }
   }     
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if (!bTwist || !MQ2TwistEnabled) return 0;
   // DebugSpew("MQ2Twist::OnIncomingChat(%s)",Line);
   
   if ( !strcmp(Line,"You miss a note, bringing your song to a close!") ||
      !strcmp(Line,"You haven't recovered yet...") ||
      !strcmp(Line,"Your spell is interrupted.") ) {
         DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event");
         if (!HoldSong) CurrSong=PrevSong;
         CastDue = -1;
         SongNextCast[CurrSong-1] = -1;
         return 0;
      }

   if (!strcmp(Line,"You can't cast spells while stunned!") ) {
      DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event (stun)");
      if (!HoldSong) CurrSong=PrevSong;
      CastDue = GetTime() + 10;
      // Wait one second before trying again, to avoid spamming the trigger text w/ cast attempts
      return 0;
   }
   return 0;
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
   DebugSpew("MQ2Twist::SetGameState()");
   if (GameState==GAMESTATE_INGAME)
   {
      MQ2TwistEnabled = true;
      Load_MQ2Twist_INI();
   } else {
      MQ2TwistEnabled = false;
   }
}
 
Re: Fixed

ok I suck...too new at this, how do I fix the pActor is not a member of _SPAWNINFO
refering to eqdata.h looks like.
 
Re: Fixed

I haven't tested the result yet, but for MQ2Combat for example, I just replaced "pSpawn->pActor->" with "pSpawn->" and it compiled cleanly.
 
Re: Fixed

Moveutils :

Rich (BB code):
/*
MQ2MoveUtils plugin (2004.06.23) - tonio
- Updated 2005.06.26 by Quagmire
- Updated 2005.07.09 by Outlander
- Updated 2005.07.14 by Outlander
- Updated 2005.07.19 by Outlander
- Updated 2005.09.19 by Outlander
- Updated 2005.09.21 by Outlander
- Updated 2005.09.26 by Outlander
- Updated 2005.09.28 by Outlander
- Updated 2005.10.22 by Outlander
- Updated 2005.10.23 by Outlander

* Corrections 2005.10.23
/makecamp is now a toggle for on/off
with mpause enabled any movement keys would spew a "no longer sticking to anything" message this has been fixed.
mpause option is now a toggle so if it was on doing /makecamp mpause will turn it off
INI load and save were reversed, this is now corrected
maxdelay variable being loaded from mindelay INI value, this has been corrected

* Addition 2005.10.22
Added /makecamp functionality
Added /stick help, /circle help, /moveto help, and of course /makecamp help
Added some commands to all the functions
Changed the way some commands are parsed and changed the help command look

* Corrections 2005.09.28
Corrected problem with IsBardClass function, thanks BardOMatic and DKAA

* Addition 2005.09.28
Added HoTT check to stick, if sticking to a MOB or PC and you are their target
then if you are doing stick !front, behind, or pin you just do a a normal stick until
you are not the HoTT target then you go back to what ever option you had before.

* Addition 2005.09.26
Added new option:
/stick !front
This option will keep you not in front of the mob, so off to the side or behind.
This option should reduce the amount your toon moves around during a fight with a mob when you are not the
target as your will only adjust your position if you you are in the front 180 degrees of the mob, as long as you are
in the behind 180 degrees you will not move around just because the mob shifts.


* Corrections 2005.09.21
Changed movestuck logic for underwater to not kick in till you are not moving at all; pulse average = 0.

* Corrections 2005.09.19
Changed IsBardClass function to use GetCharInfo2() instead of the old GetCharInfo() function.

* Corrections 2005.07.19
Modified /stick movestuck logic when you are close to mob and are switched to walk mode, this was causing
movestuck logic to be executed incorrectly.

* Corrections and additions 2005.07.14
Added break Circle functionality so that when you manually move Circle is turned off
Added MoveStuck logic to MoveTo and Circle commands
Adjusted the stuck distance to take into account the speedmodifier currently on the character.
Adjusted the the MoveStuck stuckDist to be 1/3 of what it normally is if your under water.

* Corrections and additions 2005.07.08
Added Version Number at top of help commands
Added /stick id <spawn id> functionality
Added break MoveTo functionality so that when you manually move MoveTo is turned off

/stick" command by tonio
/circle" command lifted from CyberTech's MQ2Twister plugin
/moveto" command from rswiders
/makecamp command by Outlander
*/

#include "../MQ2Plugin.h"

PreSetup("MQ2MoveUtils");
CHAR szVersion[30]="MQ2MoveUtils Version 20051023";
CHAR szMsg[MAX_STRING]={0};

bool bDebug=false;
bool bCircling=false;
bool bCirclingWasOn=false;
bool bDrunken=false;
float CircleX=0.0f;
float CircleY=0.0f;
float CircleRadius=0.0f;
SYSTEMTIME stPrevCirc;

float getRand(float n);
int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);
void CircleCommand(PSPAWNINFO pChar, PCHAR szLine);
void StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void MakeCampCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMakeCamp();
void MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
void HandleMoveTo();
void ReleaseKeys();
void DoWalk(bool walk = false);
void DoFwd(bool hold, bool walk = false);
void DoBck(bool hold);
void DoLft(bool hold);
void DoRgt(bool hold);
void Load_INI(void);
void Save_INI(void);
bool IsBardClass(void);
void stickText();
void breakStick(bool stopMoving = true, bool quite = false);
void breakCircle(bool stopMoving = true, bool quite = false);
void breakMoveTo(bool stopMoving = true, bool quite = false);
float angularDistance(float h1, float h2);
void WriteSetting(PCHAR Setting, int Current, PCHAR ShowHelp, bool BoolType);
void DebugSpew();

bool MoveBindsLoaded=false;
bool stickOn=false;
bool stickWasOn=false;
bool setDist=false;
bool PauseLogic=false;
bool stickHold=false;
bool moveBehind=false;
bool moveBehindOnce=false;
bool moveBack=false;
bool movePin=false;
bool moveNotFront=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=false;
bool stickhasmovedfwd=false;
short stickVerbosity=0;
short keysDown=0;
float stickDist=0.0;
float breakDist=250.0;
float currentDist=0.0;
float stickDistMod=0.0;
float stickDistModP=1.0;
PSPAWNINFO stickTarget;
eSpawnType stickTarget_Type;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

// stuck added by Outlander
int stuck=0;
int stuckCheck=0;
int stuckFree=0;
float prevX=0.0f;
float prevY=0.0f;
float prevZ=0.0f;
float stuckDist=0.0f;
float turnDirection=0.0f;
float pulseAvg=0.0f;
bool stuckLogic;

bool bMoveToOn=false;
float LocX=0.0f;
float LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

//Make Camp Variables
bool bMakeCamp=false;
bool bLeash=false;
bool bMakeCampReturn=false;
float AnchorX=0.0f;
float AnchorY=0.0f;
float LeashLength=0.0f;
float CampRadius=0.0f;
int mindelay=0;
int maxdelay=0;
int returnTime=0;
SYSTEMTIME stReturnTime;

void DebugSpew() {
   char szTemp[MAX_STRING];

   sprintf(szTemp,"%s",bCircling?"true":"false");
   WritePrivateProfileString("Debug","bCircling",szTemp,INIFileName);
   sprintf(szTemp,"%s",bCirclingWasOn?"true":"false");
   WritePrivateProfileString("Debug","bCirclingWasOn",szTemp,INIFileName);
   sprintf(szTemp,"%s",bDrunken?"true":"false");
   WritePrivateProfileString("Debug","bDrunken",szTemp,INIFileName);
   sprintf(szTemp,"%s",MoveBindsLoaded?"true":"false");
   WritePrivateProfileString("Debug","MoveBindsLoaded",szTemp,INIFileName);
   sprintf(szTemp,"%s",stickOn?"true":"false");
   WritePrivateProfileString("Debug","stickOn",szTemp,INIFileName);
   sprintf(szTemp,"%s",stickWasOn?"true":"false");
   WritePrivateProfileString("Debug","stickWasOn",szTemp,INIFileName);
   sprintf(szTemp,"%s",setDist?"true":"false");
   WritePrivateProfileString("Debug","setDist",szTemp,INIFileName);
   sprintf(szTemp,"%s",PauseLogic?"true":"false");
   WritePrivateProfileString("Debug","PauseLogic",szTemp,INIFileName);
   sprintf(szTemp,"%s",stickHold?"true":"false");
   WritePrivateProfileString("Debug","stickHold",szTemp,INIFileName);
   sprintf(szTemp,"%s",moveBehind?"true":"false");
   WritePrivateProfileString("Debug","moveBehind",szTemp,INIFileName);
   sprintf(szTemp,"%s",moveBehindOnce?"true":"false");
   WritePrivateProfileString("Debug","moveBehindOnce",szTemp,INIFileName);
   sprintf(szTemp,"%s",moveBack?"true":"false");
   WritePrivateProfileString("Debug","moveBack",szTemp,INIFileName);
   sprintf(szTemp,"%s",movePin?"true":"false");
   WritePrivateProfileString("Debug","movePin",szTemp,INIFileName);
   sprintf(szTemp,"%s",moveNotFront?"true":"false");
   WritePrivateProfileString("Debug","moveNotFront",szTemp,INIFileName);
   sprintf(szTemp,"%s",casting?"true":"false");
   WritePrivateProfileString("Debug","casting",szTemp,INIFileName);
   sprintf(szTemp,"%s",mPause?"true":"false");
   WritePrivateProfileString("Debug","mPause",szTemp,INIFileName);
   sprintf(szTemp,"%s",prevMoveBehind?"true":"false");
   WritePrivateProfileString("Debug","prevMoveBehind",szTemp,INIFileName);
   sprintf(szTemp,"%s",prevMovePin?"true":"false");
   WritePrivateProfileString("Debug","prevMovePin",szTemp,INIFileName);
   sprintf(szTemp,"%s",looseStick?"true":"false");
   WritePrivateProfileString("Debug","looseStick",szTemp,INIFileName);
   sprintf(szTemp,"%s",underwater?"true":"false");
   WritePrivateProfileString("Debug","underwater",szTemp,INIFileName);
   sprintf(szTemp,"%s",stickhasmovedfwd?"true":"false");
   WritePrivateProfileString("Debug","stickhasmovedfwd",szTemp,INIFileName);
   sprintf(szTemp,"%s",autoPauseEnabled?"true":"false");
   WritePrivateProfileString("Debug","autoPauseEnabled",szTemp,INIFileName);
   sprintf(szTemp,"%s",breakDistEnabled?"true":"false");
   WritePrivateProfileString("Debug","breakDistEnabled",szTemp,INIFileName);
   sprintf(szTemp,"%s",breakOnWarpEnabled?"true":"false");
   WritePrivateProfileString("Debug","breakOnWarpEnabled",szTemp,INIFileName);
   sprintf(szTemp,"%s",breakOnGateEnabled?"true":"false");
   WritePrivateProfileString("Debug","breakOnGateEnabled",szTemp,INIFileName);
   sprintf(szTemp,"%s",bMoveToOn?"true":"false");
   WritePrivateProfileString("Debug","bMoveToOn",szTemp,INIFileName);
   sprintf(szTemp,"%s",stuckLogic?"true":"false");
   WritePrivateProfileString("Debug","stuckLogic",szTemp,INIFileName);
   sprintf(szTemp,"%s",bMakeCamp?"true":"false");
   WritePrivateProfileString("Debug","bMakeCamp",szTemp,INIFileName);
   sprintf(szTemp,"%s",bLeash?"true":"false");
   WritePrivateProfileString("Debug","bLeash",szTemp,INIFileName);
   sprintf(szTemp,"%s",bMakeCampReturn?"true":"false");
   WritePrivateProfileString("Debug","bMakeCampReturn",szTemp,INIFileName);

   sprintf(szTemp,"%.1f",CircleX);
   WritePrivateProfileString("Debug","CircleX",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",CircleY);
   WritePrivateProfileString("Debug","CircleY",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",CircleRadius);
   WritePrivateProfileString("Debug","CircleRadius",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",stickDist);
   WritePrivateProfileString("Debug","stickDist",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Debug","breakDist",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",currentDist);
   WritePrivateProfileString("Debug","currentDist",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",stickDistMod);
   WritePrivateProfileString("Debug","stickDistMod",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",stickDistModP);
   WritePrivateProfileString("Debug","stickDistModP",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",prevX);
   WritePrivateProfileString("Debug","prevX",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",prevY);
   WritePrivateProfileString("Debug","prevY",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",prevZ);
   WritePrivateProfileString("Debug","prevZ",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",stuckDist);
   WritePrivateProfileString("Debug","stuckDist",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",turnDirection);
   WritePrivateProfileString("Debug","turnDirection",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",pulseAvg);
   WritePrivateProfileString("Debug","pulseAvg",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",LocX);
   WritePrivateProfileString("Debug","LocX",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",LocY);
   WritePrivateProfileString("Debug","LocY",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",moveDist);
   WritePrivateProfileString("Debug","moveDist",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",moveDistMod);
   WritePrivateProfileString("Debug","moveDistMod",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",AnchorX);
   WritePrivateProfileString("Debug","AnchorX",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",AnchorY);
   WritePrivateProfileString("Debug","AnchorY",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",LeashLength);
   WritePrivateProfileString("Debug","LeashLength",szTemp,INIFileName);
   sprintf(szTemp,"%.1f",CampRadius);
   WritePrivateProfileString("Debug","CampRadius",szTemp,INIFileName);

   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Debug","stickVerbosity",szTemp,INIFileName);
   sprintf(szTemp,"%d",keysDown);
   WritePrivateProfileString("Debug","keysDown",szTemp,INIFileName);

   sprintf(szTemp,"%i",stuck);
   WritePrivateProfileString("Debug","stuck",szTemp,INIFileName);
   sprintf(szTemp,"%i",stuckCheck);
   WritePrivateProfileString("Debug","stuckCheck",szTemp,INIFileName);
   sprintf(szTemp,"%i",stuckFree);
   WritePrivateProfileString("Debug","stuckFree",szTemp,INIFileName);
   sprintf(szTemp,"%i",mindelay);
   WritePrivateProfileString("Debug","mindelay",szTemp,INIFileName);
   sprintf(szTemp,"%i",maxdelay);
   WritePrivateProfileString("Debug","maxdelay",szTemp,INIFileName);
   sprintf(szTemp,"%i",returnTime);
   WritePrivateProfileString("Debug","returnTime",szTemp,INIFileName);

   bDebug=false;
   //SYSTEMTIME stReturnTime;
   //SYSTEMTIME stPrevCirc;
   //PSPAWNINFO stickTarget;
   //eSpawnType stickTarget_Type;
   //SYSTEMTIME stPrevStick;

}

//MakeCamp Related Functionality
class MQ2MakeCampType *pMakeCampType = 0;

class MQ2MakeCampType : public MQ2Type {
public:
   enum MakeCampMembers {
      Status=1,
      Leash=2,
      AnchorX=3,
      AnchorY=4,
      LeashLength=5,
      CampRadius=6,
      MinDelay=7,
      MaxDelay=8,
      Returning=9,
   };

   MQ2MakeCampType():MQ2Type("makecamp")
   {
      TypeMember(Status);
      TypeMember(Leash);
      TypeMember(AnchorX);
      TypeMember(AnchorY);
      TypeMember(LeashLength);
      TypeMember(CampRadius);
      TypeMember(MinDelay);
      TypeMember(MaxDelay);
      TypeMember(Returning);
   }

   ~MQ2MakeCampType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MakeCampType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MakeCampMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( bMakeCamp ) {
            strcpy(DataTypeTemp,"ON");
       }
         if( PauseLogic ) {
            strcpy(DataTypeTemp,"PAUSED");
       }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Leash:
         Dest.DWord=bLeash;
         Dest.Type=pBoolType;
         return true;
      case AnchorX:
         Dest.DWord=AnchorX;
         Dest.Type=pFloatType;
         return true;
      case AnchorY:
         Dest.DWord=AnchorY;
         Dest.Type=pFloatType;
         return true;
      case LeashLength:
         Dest.DWord=LeashLength;
         Dest.Type=pFloatType;
         return true;
      case CampRadius:
         Dest.DWord=CampRadius;
         Dest.Type=pFloatType;
         return true;
      case MinDelay:
         Dest.DWord=mindelay;
         Dest.Type=pIntType;
         return true;
      case MaxDelay:
         Dest.DWord=maxdelay;
         Dest.Type=pIntType;
         return true;
      case Returning:
         Dest.DWord=bMakeCampReturn;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( bMakeCamp ) {
         strcpy(Destination,"ON");
      }
      if( PauseLogic ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMakeCamp(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMakeCampType;
   return true;
}

void MakeCampHelp() {
   char Buffer[64];
   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   sprintf(Buffer,(bMakeCamp)?"\agon\ax":"\agoff\ax");
   WriteChatf("/makecamp [on|off] (%s) on drops anchor at current location", Buffer);
   WriteChatf("/makecamp [<y> <x>] (\ag%.2f\ax \ag%.2f\ax) anchor location",AnchorY, AnchorX);
   WriteChatf("/makecamp [mindelay <time in ms>] (\ag%d\ax) return to camp", mindelay);
   WriteChatf("/makecamp [maxdelay <time in ms>] (\ag%d\ax) return to camp", maxdelay);
   sprintf(Buffer,(bLeash)?"\agon\ax":"\agoff\ax");
   WriteChatf("/makecamp [leash] (%s) toggle", Buffer);
   WriteChatf("/makecamp [leash [<dist>]] (\ag%.2f\ax) implicit on",LeashLength);
   WriteChatf("/makecamp [radius <dist>] (\ag%.2f\ax)",CampRadius);
   sprintf(Buffer,(mPause)?"\agon\ax":"\agoff\ax");
   WriteChatf("/makecamp [mpause] (%s) manual control pause or break", Buffer);
   sprintf(Buffer,(PauseLogic)?"\agon\ax":"\agoff\ax");
   WriteChatf("/makecamp [pause|unpause] (%s) MoveUtils", Buffer);
   sprintf(Buffer,(bMakeCampReturn)?"\agon\ax":"\agoff\ax");
   WriteChatf("/makecamp [return] (%s) immediatly to camp", Buffer);
   WriteChatColor("/makecamp [save|load] INI file");
}

void MakeCampCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   if(stickOn)breakStick();
   if(bCircling) breakCircle();
   if(bMoveToOn) breakMoveTo();
   char currentArg[MAX_STRING];
   int argn=1;

   GetArg(currentArg,szLine,argn++);
   if( !*currentArg ) {
      if(!bMakeCamp) {
         bMakeCamp=true;
         AnchorY=pChar->Y;
         AnchorX=pChar->X;
         CampRadius = (CampRadius<10)?10:CampRadius;
         LeashLength = (LeashLength>=CampRadius)?LeashLength:CampRadius+10;
         mindelay = (mindelay < 125)?125:mindelay;
         maxdelay = (maxdelay <= mindelay)?mindelay+125:maxdelay;
      } else bMakeCamp = false;
   }


   while( *currentArg ) {

      if( !strncmp(currentArg,"on",3) ) {
         bMakeCamp=true;
         AnchorY=pChar->Y;
         AnchorX=pChar->X;
         CampRadius = (CampRadius<10)?10:CampRadius;
         LeashLength = (LeashLength>=CampRadius)?LeashLength:CampRadius+10;
         mindelay = (mindelay < 125)?125:mindelay;
         maxdelay = (maxdelay <= mindelay)?mindelay+125:maxdelay;
      } else if( !strncmp(currentArg,"off",4) ) {
         bMakeCamp = false;
         break;
      } else if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.') {
         bMakeCamp = true;
         AnchorY=(float)atof(currentArg);
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.')
            AnchorX=(float)atof(currentArg);
         else {
            bMakeCamp = false;
            MakeCampHelp();
            return;
       }
      } else if( !strncmp(currentArg,"mindelay",9) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]))
            mindelay=(int)atoi(currentArg);
         else {
            bMakeCamp = false;
            MakeCampHelp();
            return;
         }
      } else if( !strncmp(currentArg,"maxdelay",9) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]))
            maxdelay=(int)atoi(currentArg);
         else {
            bMakeCamp = false;
            MakeCampHelp();
            return;
         }
      } else if( !strncmp(currentArg,"leash",6) ) {
         GetArg(currentArg,szLine,argn);
         if( isdigit(currentArg[0])) {
            LeashLength=(float)atof(currentArg);
            argn++;
         } else {
            bLeash = bLeash ? false : true;
         }
      } else if( !strncmp(currentArg,"radius",7) ) {
         GetArg(currentArg,szLine,argn);
         if( isdigit(currentArg[0])) {
            CampRadius=(float)atof(currentArg);
            argn++;
         } else {
            bMakeCamp = false;
            MakeCampHelp();
            return;
         }
      } else if( !strncmp(currentArg,"return",7) ) {
         bMakeCampReturn=true;
         break;
      } else if( !strncmp(currentArg,"save",5 ) ) {
         Save_INI();
      } else if( !strncmp(currentArg,"load",5) ) {
         Load_INI();
      } else if( !strncmp(currentArg,"pause",6) ) {
         ReleaseKeys();
         PauseLogic = true;
      } else if( !strncmp( currentArg,"unpause",8 ) ) {
         PauseLogic = false;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = mPause?false:true;
      } else {
         MakeCampHelp();
         return;
      }
      GetArg(currentArg,szLine,argn++);
   }
}


void HandleMakeCamp() {
   PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   SYSTEMTIME stCurr;
   GetSystemTime(&stCurr);
   int elapsedTime = millisDiff(stCurr,stReturnTime);
   float newHeading=0.0f;

   if (!pChSpawn || !pChSpawn || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking HandleMakeCamp");
      return;
   }
   if ( ( ((long) (pChSpawn->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( (!PauseLogic && !casting) ) {
      if( (GetDistance(pChSpawn->X,pChSpawn->Y,AnchorX,AnchorY) > CampRadius && !bMoveToOn && !pTarget && elapsedTime >= returnTime && returnTime != 0) || bMakeCampReturn ) {
         returnTime=0;
         LocY = (int)rand()%(int)(CampRadius*2) + (AnchorY-CampRadius);
         LocX = (int)rand()%(int)(CampRadius*2) + (AnchorX-CampRadius);
         bMoveToOn = true;
         bMakeCampReturn=false;
      } else if(GetDistance(pChSpawn->X,pChSpawn->Y,AnchorX,AnchorY) > CampRadius && !bMoveToOn && !pTarget && returnTime == 0) {
         GetSystemTime(&stReturnTime);
         returnTime = (int)rand()%(maxdelay-mindelay+1) + mindelay;
      }

      if( GetDistance(pChSpawn->X,pChSpawn->Y,AnchorX,AnchorY) > LeashLength && bLeash && (stickOn || bCircling)) {
         if(stickOn){
            DoFwd(false);
            ReleaseKeys();
            stickOn=false;
            stickWasOn=true;
         }
         if(bCircling) {
            DoFwd(false);
            ReleaseKeys();
            bCircling=false;
            bCirclingWasOn=true;
         }
      } else if(bLeash && psTarget && ( stickWasOn || bCirclingWasOn) ) {
         if(GetDistance(psTarget->X,psTarget->Y,AnchorX,AnchorY) > LeashLength && bLeash) {
            WriteChatf("stick dist %.2f",stickDist);
            newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
            if( newHeading >= 512.0f) newHeading -= 512.0f;
            if( newHeading < 0.0f ) newHeading += 512.0f;
            pChSpawn->Heading = newHeading;
         } else if(stickWasOn) {
            DoFwd(true);
            stickOn=true;
            stickWasOn=false;
         } else if(bCirclingWasOn) {
            DoFwd(true);
            bCircling=true;
            bCirclingWasOn=false;
         }
      }
   }
}

//Stick Related Functionality
class MQ2StickType *pStickType = 0;

class MQ2StickType : public MQ2Type
{
public:
   enum StickMembers {
      Status=1,
      Active=2,
      Distance=3,
      MoveBehind=4,
      MovePause=5,
      MoveBack=6,
      Loose=7,
      Paused=8,
      Behind=9,
      Stopped=10,
      Pin=11,
   };

   MQ2StickType():MQ2Type("stick")
   {
      TypeMember(Status);
      TypeMember(Active);
      TypeMember(Distance);
      TypeMember(MoveBehind);
      TypeMember(MovePause);
      TypeMember(MoveBack);
      TypeMember(Loose);
      TypeMember(Paused);
      TypeMember(Behind);
      TypeMember(Stopped);
      TypeMember(Pin);
   }
   ~MQ2StickType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2StickType::FindMember(Member);
      if (!pMember)
         return false;
      switch((StickMembers)pMember->ID)
      {
      case Status:
         strcpy(DataTypeTemp,"OFF");
         if( stickOn ) {
            strcpy(DataTypeTemp,"ON");
       }
         if( PauseLogic ) {
            strcpy(DataTypeTemp,"PAUSED");
       }
         Dest.Ptr=DataTypeTemp;
         Dest.Type=pStringType;
         return true;
      case Active:
         Dest.DWord=stickOn;
         Dest.Type=pBoolType;
         return true;
      case Distance:
         Dest.Float=stickDist;
         Dest.Type=pFloatType;
         return true;
      case MoveBehind:
         Dest.DWord=moveBehind;
         Dest.Type=pBoolType;
         return true;
      case MovePause:
         Dest.DWord=mPause;
         Dest.Type=pBoolType;
         return true;
      case MoveBack:
         Dest.DWord=moveBack;
         Dest.Type=pBoolType;
         return true;
      case Loose:
         Dest.DWord=looseStick;
         Dest.Type=pBoolType;
         return true;
      case Paused:
         Dest.DWord=PauseLogic;
         Dest.Type=pBoolType;
         return true;
      case Behind:
         if (ppTarget && pTarget) {
            PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget) > stickDist || fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 )?false:true;
         } else
            Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         if( ppTarget && pTarget ) {
            PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
            PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
            Dest.DWord=(GetDistance(pChSpawn,psTarget)<=stickDist)?true:false;
         } else
            Dest.DWord=false;
         Dest.Type=pBoolType;
         return true;
      case Pin:
         Dest.DWord=movePin;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      strcpy(Destination,"OFF");
      if( stickOn ) {
         strcpy(Destination,"ON");
      }
      if( PauseLogic ) {
         strcpy(Destination,"PAUSED");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataStick(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pStickType;
   return true;
}

void StickHelp()
{
   char Buffer[64];

   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   //   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload|id <spawnid>] [<dist>] [behind|behindonce|pin] [mpause] [moveback] [loose] [-<dist>] [<perc>%] [uw]");
   sprintf(Buffer,(stickOn)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [on|off] (%s)",Buffer);
   sprintf(Buffer,(stickHold)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [hold] (%s) current target even if you lose/change target",Buffer);
   WriteChatf("/stick [<dist>] (\ag%.2f\ax) units of your target", stickDist);
   WriteChatf("/stick [[-]<dist>[%%]] (\ag%.2f\ax) - Substract or percentage modifier",stickDistMod);
   sprintf(Buffer,(moveBehind)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [behind] (%s) your target",Buffer);
   sprintf(Buffer,(moveBehindOnce)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [behindonce] (%s) your target once",Buffer);
   sprintf(Buffer,(movePin)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [pin] (%s) your target",Buffer);
   sprintf(Buffer,(moveNotFront)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [!front] (%s) not in front of your target",Buffer);
   sprintf(Buffer,(moveBack)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [moveback] (%s) to <dist> from target",Buffer);
   sprintf(Buffer,(looseStick)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [loose] (%s) to target",Buffer);
   sprintf(Buffer,(underwater)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [uw] (%s) look up/down at target",Buffer);
   WriteChatf("/stick [id <spawn>] (\ag%i\ax) to target with ID", stickTarget);
   sprintf(Buffer,(mPause)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [mpause] (%s) manual control pause toggle", Buffer);
   sprintf(Buffer,(PauseLogic)?"\agon\ax":"\agoff\ax");
   WriteChatf("/stick [pause|unpause] (%s) MoveUtils", Buffer);
   WriteChatColor("/stick [save|load] INI file");
}

void StickCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   char currentArg[MAX_STRING];
   int argn=1;
   int trand = rand()%100;
   turnDirection *= (trand>50)?1.0f:-1.0f;

   GetArg(currentArg,szLine,argn++);
   if( !strncmp(currentArg,"debug",6) ) {
      bDebug=true;
      return;
   }

   //no arguements
   if(bCircling) breakCircle();
   if(bMoveToOn) breakMoveTo();
   if( !*currentArg ) stickOn=true;

   while( *currentArg ) {
      if( !strncmp(currentArg,"on",3) ) {
         stickOn = true;
      } else if( strstr(currentArg,"%") ) {
         stickDistModP = (float)atof(currentArg) / 100.0f;
         if( setDist ) stickDist *= stickDistModP;
         stickOn = true;
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.' ) {
         setDist = true;
         stickDist = (float)atof(currentArg) * stickDistModP + stickDistMod;
         stickOn = true;
      } else if( currentArg[0]=='-' ) {
         stickDistMod = (float)atof(currentArg);
         if( setDist ) stickDist += stickDistMod;
         stickOn = true;
      } else if( !strncmp(currentArg,"pause",6) ) {
         ReleaseKeys();
         PauseLogic = true;
      } else if( !strncmp( currentArg,"unpause",8 ) ) {
         PauseLogic = false;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = mPause?false:true;
      } else if( !strncmp(currentArg,"moveback",9) ) {
         moveBack = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"loose",6) ) {
         looseStick = true;
         stickOn = true;
      } else if( !strncmp(currentArg,"uw",3 ) ) {
         underwater=true;
         stickOn = true;
      } else if( !strncmp(currentArg,"save",5 ) ) {
         Save_INI();
      } else if( !strncmp(currentArg,"load",5) ) {
         Load_INI();
      } else if( !strncmp(currentArg,"off",4) ) {
         breakStick();
         break;
      } else if( !strncmp(currentArg,"hold",5) ) {
         if( ppTarget && pTarget ) {
            if (((PSPAWNINFO) pTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
               WriteChatColor("You cannot stick hold to yourself.");
               breakStick(true, true);
               return;
            }
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
            stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
            //            stickHold = stickHold && stickTarget && stickTarget->SpawnID;
         }
      } else if( !strncmp(currentArg,"id",3) ) {
         GetArg(currentArg,szLine,argn++);
         if( isdigit(currentArg[0]) ) {
            stickTarget = (PSPAWNINFO) GetSpawnByID(atoi(currentArg));
            if( stickTarget ) {
               if (((PSPAWNINFO) stickTarget)->SpawnID == ((PSPAWNINFO) pLocalPlayer)->SpawnID) {
                  WriteChatColor("You cannot stick id to yourself.");
                  breakStick(true, true);
                  return;
               }
               stickHold = true;
               stickTarget_Type = stickTarget ? GetSpawnType(stickTarget) : NONE;
            }
         } else {
            WriteChatColor("When using ID the next parameter MUST be the spawn ID.");
            breakStick(true, true);
            return;
         }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn = true;
         moveBehind=true;
         moveBehindOnce=false;
         movePin=false;
      } else if( !strncmp(currentArg,"behindonce",11) ) {
         stickOn = true;
         moveBehindOnce=true;
         moveBehind=false;
         movePin=false;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn = true;
         movePin=true;
         moveBehind=false;
         moveBehindOnce=false;
      } else if( !strncmp(currentArg,"!front",6) ) {
         stickOn = true;
         moveNotFront=true;
         moveBehind=false;
         moveBehindOnce=false;
      } else if( !strncmp(currentArg,"help",5) ) {
         StickHelp();
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
}

void HandleStick() {
   if( (stickHold && (!stickTarget || !(stickTarget->SpawnID) || stickTarget_Type!=GetSpawnType(stickTarget))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
      return;
   }
   PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn || !psTarget || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking stick");
      breakStick();
      return;
   }

   float newHeading=0.0f;
   float prevDist = currentDist;
   bool mounted;

   mounted = pChSpawn && pChSpawn->Mount ? true : false;

   if( !setDist )
      stickDist = (psTarget->StandState?get_melee_range(pLocalPlayer,(EQPlayer *)psTarget):15.0f) * stickDistModP + stickDistMod;

   currentDist=GetDistance(pChSpawn,psTarget);
   if( breakOnWarpEnabled && (currentDist-prevDist) > breakDist ) {
      breakStick();
      return;
   }
   if ( ( ((long) (pChSpawn->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 || psTarget->SpawnID == pChSpawn->SpawnID ) {
      if( !casting ) {
         casting = true;
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( (!PauseLogic && !casting) || !autoPauseEnabled ) {
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO)pLocalPlayer)->Unknown0x100[0x10]));
      if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->UnderWater) ||
         (pulseAvg == 0 && pChSpawn->UnderWater) || (pulseAvg < (stuckDist + SpeedModifier)/3 && mounted )) &&
         ((currentDist - stickDist) > 10.0) && stuckLogic ) {
            stuck++;  
			   if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
               newHeading =(float) ( pChSpawn->Heading + turnDirection);
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;
               pChSpawn->Heading = newHeading;

               //check to see if we are heading directly away from our target if so then go back
               newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;

               newHeading += 265.0f;
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;
               if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <   (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
           }
            }
            stuckFree=0;
      } else if( stuck > 0 ) {
         if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
            newHeading =(float) ( pChSpawn->Heading - turnDirection );
            pChSpawn->Heading = newHeading;
         }
         stuck--;
         if(stuckFree++ > stuckCheck*3 ) {
            stuck=0;
         }
      } else {
         newHeading = (float) (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * 256.0 / (float)PI);
         if( newHeading >= 512.0f) newHeading -= 512.0f;
         if( newHeading < 0.0f ) newHeading += 512.0f;
         if (stickhasmovedfwd && fabs(pChSpawn->Heading - newHeading) >= 200 && currentDist < 10) {
            DoBck(true);
            return;
         } else {
            DoBck(false);
         }
         if( looseStick ) {
            gFaceAngle = newHeading;
         } else {
            pChSpawn->Heading = newHeading;
         }

         //underwater = pChSpawn->UnderWater==5;
         if( underwater ) {
            double lookAngle = (float) atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
               pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
               currentDist) * 256.0f / (float)PI;
            if ( looseStick ) {
               gLookAngle = lookAngle;
            } else {
               pChSpawn->CameraAngle = (FLOAT)lookAngle;
          }
         }
         if ( currentDist > (stickDist * 3) ) {
            // too far away, dont do pin or behind
         } else if( (moveBehind || moveBehindOnce) && pChSpawn!=pChSpawn->pTargetOfTarget ) {
            float angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
            if( fabs(angDist) > 45.0 ) {
               if(angDist < 0.0) {
                  // strafe left
                  DoLft(true);
               } else {
                  // strage right
                  DoRgt(true);
               }
            } else {
               moveBehindOnce = false;
               DoLft(false);
               DoRgt(false);
          }
         } else if (movePin && pChSpawn!=pChSpawn->pTargetOfTarget ) {
            FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
            if((angDist > 0 && angDist <= 112) || angDist < -144) {
               DoLft(true);
            } else if ((angDist < 0 && angDist > -112) || angDist > 144) {
               DoRgt(true);
            } else {
               DoLft(false);
               DoRgt(false);
          }
         } else if (moveNotFront && pChSpawn!=pChSpawn->pTargetOfTarget ) {
            FLOAT angDist = angularDistance(psTarget->Heading,pChSpawn->Heading);
            if( fabs(angDist) > 135.0 ) {
               moveBehindOnce = true;
               //             if(angDist < 0.0) {
               //                // strafe left
               //                DoLft(true);
               //              } else {
               //                // strage right
               //                DoRgt(true);
               //              }
            } else {
               //            moveBehindOnce = false;
               DoLft(false);
               DoRgt(false);
          }
         }
      }
      if(pChSpawn->StandState == STANDSTATE_DUCK && stuckLogic ) pChSpawn->StandState = STANDSTATE_STAND;
      if( currentDist > stickDist) {
         // if distance is less than 10, walk
         DoFwd(true, ((currentDist - stickDist) <= 10.0));
      } else if( moveBack && currentDist < (stickDist-5.0) ) {
         DoBck(true);
      } else {
         DoFwd(false);
         DoBck(false);
      }
   }
}
void stickText()
{
   char szTemp[MAX_STRING];

   if( stickVerbosity == 1 ) {
      if( PauseLogic ) {
         WriteChatColor("Stick paused.");
      } else if( stickOn ) {
         if( stickHold ) {
            sprintf(szTemp,"You are now sticking to %s.",stickTarget->DisplayedName);
         } else if( ppTarget && pTarget ) {
            sprintf(szTemp,"You are now sticking to %s.",((PSPAWNINFO)pTarget)->DisplayedName);
         } else {
            sprintf(szTemp,"Need a target for stick.");
       }
         WriteChatColor(szTemp);
      } else {
         WriteChatColor("You are no longer sticking to anything.");
      }
   }
}

void breakStick(bool stopMoving, bool quite) {
   if(!bMakeCamp) {
      PauseLogic=false;
      mPause=false;
      bMoveToOn=false;
      bCircling=false;
   }

   stickOn=false;
   stickWasOn=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   moveBehindOnce=false;
   movePin=false;
   moveNotFront=false;
   prevMovePin=false;
   moveBack=false;
   looseStick=false;
   underwater=false;
   stickTarget=NULL;
   stickDistMod=0.0f;
   stickDistModP=1.0f;
   stuck=0;
   stickhasmovedfwd=false;
   stickTarget_Type=NONE;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
   if (!quite)
      stickText();
}
//MoveTo Related Functionality
class MQ2MoveToType *pMoveToType = 0;

class MQ2MoveToType : public MQ2Type
{
public:
   enum MoveToMembers {
      Moving=1,
      Stopped=2
   };

   MQ2MoveToType():MQ2Type("moveto")
   {
      TypeMember(Moving);
      TypeMember(Stopped);
   }

   ~MQ2MoveToType()
   {
   }

   bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
   {
      PMQ2TYPEMEMBER pMember=MQ2MoveToType::FindMember(Member);
      if (!pMember)
         return false;
      switch((MoveToMembers)pMember->ID)
      {
      case Moving:
         Dest.DWord=bMoveToOn;
         Dest.Type=pBoolType;
         return true;
      case Stopped:
         PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
         Dest.DWord=(GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY)<=moveDist)?true:false;
         Dest.Type=pBoolType;
         return true;
      }
      return false;
   }

   bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
   {
      if( bMoveToOn ) {
         strcpy(Destination,"ON");
      } else {
         strcpy(Destination,"OFF");
      }
      return true;
   }

   bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
   {
      return false;
   }
   bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
   {
      return false;
   }
};

BOOL dataMoveTo(PCHAR szName, MQ2TYPEVAR &Ret)
{
   Ret.DWord=1;
   Ret.Type=pMoveToType;
   return true;
}

void MoveToHelp()
{
   char Buffer[64];

   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   sprintf(Buffer,(bMoveToOn)?"\agon\ax":"\agoff\ax");
   WriteChatf("/moveto [loc Y X|off] (%s \ag%.2f\ax \ag%.2f\ax)",Buffer,LocY, LocX);
   WriteChatf("/moveto [<dist>] (\ag%.2f\ax) area your anchor", moveDist);
   WriteChatf("/moveto [[-]<dist>] (\ag%.2f\ax) - Substract modifier",moveDistMod);
   sprintf(Buffer,(mPause)?"\agon\ax":"\agoff\ax");
   WriteChatf("/moveto [mpause] (%s) manual control pause toggle", Buffer);
   sprintf(Buffer,(PauseLogic)?"\agon\ax":"\agoff\ax");
   WriteChatf("/moveto [pause|unpause] (%s) MoveUtils", Buffer);
   WriteChatColor("/moveto [save|load] INI file");
}

void MoveToCommand(PSPAWNINFO pChar, PCHAR szLine) {
   breakStick();
   breakCircle();
   char currentArg[MAX_STRING];
   int argn=1;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   GetArg(currentArg,szLine,argn++);

   while( *currentArg ) {
      if (!stricmp(currentArg,"help")) {
         MoveToHelp();
      } else if (!stricmp(currentArg,"loc")) {
         GetArg(currentArg,szLine,argn);
         if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.' ) {
            LocY = (float) atof(currentArg);
            argn++;
            GetArg(currentArg,szLine,argn);
            if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.') {
               LocX = (float) atof(currentArg);
               argn++;
               bMoveToOn=true;
            } else {
               MoveToHelp();
               breakMoveTo();
               return;
            }
         } else {
            MoveToHelp();
            breakMoveTo();
            return;
         }
      } else if( isdigit(currentArg[0]) || currentArg[0]=='.') {
         moveDist = (float)atof(currentArg);
      } else if( currentArg[0]=='-' ) {
         moveDistMod = (float)atof(currentArg);
         moveDist += moveDistMod;
      } else if( !stricmp(currentArg,"pause") ) {
         ReleaseKeys();
         PauseLogic = true;
      } else if( !stricmp( currentArg,"unpause" ) ) {
         PauseLogic = false;
      } else if( !stricmp(currentArg,"mpause") ) {
         mPause = mPause?false:true;
      } else if( !strncmp(currentArg,"save",5 ) ) {
         Save_INI();
      } else if( !strncmp(currentArg,"load",5) ) {
         Load_INI();
      } else if (!stricmp(currentArg,"off")) {
         breakMoveTo();
         return;
      } else {
         MoveToHelp();
         breakMoveTo();
         return;
      }
      GetArg(currentArg,szLine,argn++);
   }

   sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
   if (stickVerbosity && bMoveToOn) WriteChatColor(szMsg, CONCOLOR_YELLOW);
   else if (stickVerbosity) WriteChatColor("MoveTo off.", CONCOLOR_YELLOW);
}

void HandleMoveTo()
{
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking MoveTo");
      breakMoveTo();
      return;
   }

   if ( ( ((long) (pChSpawn->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if( !casting || !autoPauseEnabled ) {
      float newHeading;
      float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
      if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
      prevX=pChSpawn->X;
      prevY=pChSpawn->Y;
      prevZ=pChSpawn->Z;

      float SpeedModifier = *((float*) &(((PSPAWNINFO)pLocalPlayer)->Unknown0x100[0x10]));
      
	  if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->UnderWater) ||
         (pulseAvg == 0 && pChSpawn->UnderWater))&&
         GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
            stuck++;
            if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
               newHeading =(float) ( pChSpawn->Heading + turnDirection);
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;
               pChSpawn->Heading = newHeading;

               //check to see if we are heading directly away from our target if so then go back
               newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;

               newHeading += 265.0f;
               if( newHeading >= 512.0f) newHeading -= 512.0f;
               if( newHeading < 0.0f ) newHeading += 512.0f;
               if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <   (newHeading + fabs(turnDirection/2)) ) {
                  newHeading -= 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;
                  stuck = stuckCheck;
                  turnDirection *= -1.0f;
           }
            }
            stuckFree=0;
      } else if( stuck > 0 ) {
         if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
            newHeading =(float) ( pChSpawn->Heading - turnDirection );
            pChSpawn->Heading = newHeading;
         }
         stuck--;
         if(stuckFree++ > stuckCheck*3 ) {
            stuck=0;
         }
      } else {
         newHeading = (float) (atan2(LocX - pChSpawn->X, LocY - pChSpawn->Y) * 256.0 / (float)PI);
         if( newHeading >= 512.0f)
            newHeading -= 512.0f;
         if( newHeading < 0.0f )
            newHeading += 512.0f;
         pChSpawn->Heading = newHeading;
      }
      if( GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist) {
         DoFwd(true);
      } else {
         bMoveToOn=false;
         if (stickVerbosity)
            WriteChatColor("Arrived at MoveTo location", CONCOLOR_YELLOW);
         DoFwd(false);
      }
   }
}

void breakMoveTo(bool stopMoving, bool quite) {
   bMoveToOn=false;
   bCircling=false;
   stuck=0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
}

//Circle Related Functionality
void CircleHelp()
{
   char Buffer[64];

   WriteChatColor(szVersion,CONCOLOR_YELLOW);
   sprintf(Buffer,(bCircling)?"\agon\ax":"\agoff\ax");
   WriteChatf("/circle [on|off] (%s) set anchor use current radius.", Buffer);
   WriteChatf("/circle [on <radius>] (\ag%.2f\ax) set radius at current||existing loc", CircleRadius);
   WriteChatf("/circle [<y> <x>] (\ag%.2f\ax \ag%.2f\ax) anchor location",LocY, LocX);   
   sprintf(Buffer,(mPause)?"\agon\ax":"\agoff\ax");
   WriteChatf("/circle [mpause]   (%s) manual control pause toggle", Buffer);
   sprintf(Buffer,(PauseLogic)?"\agon\ax":"\agoff\ax");
   WriteChatf("/circle [pause|unpause] (%s) MoveUtils", Buffer);
   sprintf(Buffer,(bDrunken)?"\agon\ax":"\agoff\ax");
   WriteChatf("/circle [drunken] (%s) toggle", Buffer);
   WriteChatColor("/circle [save|load] INI file");
}

void CircleCommand(PSPAWNINFO pChar, PCHAR szLine)
{
   if(stickOn)breakStick();
   if(bMoveToOn) breakMoveTo();
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   char currentArg[MAX_STRING];
   CHAR szTemp[MAX_STRING]={0};
   int argn=1;

   GetArg(currentArg,szLine,argn++);

   while( *currentArg ) {
      if( !strncmp(currentArg,"pause",6) ) {
         ReleaseKeys();
         PauseLogic = true;
      } else if( !strncmp( currentArg,"unpause",8 ) ) {
         PauseLogic = false;
      } else if( !strncmp(currentArg,"mpause",7) ) {
         mPause = mPause?false:true;
      } else if (!strncmp(currentArg,"help",5) ) {
         CircleHelp();
         return;
      } else if( !strncmp(currentArg,"save",5 ) ) {
         Save_INI();
      } else if( !strncmp(currentArg,"load",5) ) {
         Load_INI();
      } else if (!strncmp(currentArg,"drunken",8)) {
         bDrunken=bDrunken?false:true;
      } else if( !strncmp(currentArg,"on",3) ) {
         GetArg(currentArg,szLine,argn);
         if( isdigit(currentArg[0]) ) {
            CircleRadius = (float)atof(currentArg);
            argn++;
         }
         if(!bCircling) {
            CircleY = pChSpawn->Y + CircleRadius * (float)sin(pChSpawn->Heading * (float)PI / 256.0);
            CircleX = pChSpawn->X - CircleRadius * (float)cos(pChSpawn->Heading * (float)PI / 256.0);
         }
         bCircling=true;
      } else if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.') {
         strcpy(szTemp,currentArg);
         GetArg(currentArg,szLine,argn);
         if( isdigit(currentArg[0]) || currentArg[0] == '-' || currentArg[0] == '.') {
            CircleY = (float)atof(szTemp);
            CircleX = (float)atof(currentArg);
            argn++;
         } else {
            CircleHelp();
            return;
         }
      } else if (!strncmp(currentArg,"off",4)) {
         bCircling = false;
      } else {
         CircleHelp();
         return;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if(bCircling) {
      sprintf(szMsg, "%g Circling Radius %g, center %g %g", bCircling?"You are ":"You are NOT ",CircleRadius, CircleY, CircleX);
      WriteChatColor(szMsg, CONCOLOR_YELLOW);
   }
}

void HandleCircle() {
   static int counter = 0;
   float distance;
   float heading;
   PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
   PSPAWNINFO pLPlayer = (PSPAWNINFO) pLocalPlayer;
   if (!pChSpawn || !pChSpawn || !pLPlayer || !pLPlayer->pCharInfo) {
      WriteChatColor("Null pointer, breaking Circle");
      breakCircle();
      return;
   }

   if ( ( ((long) (pChSpawn->CastingSpellID)) >= 0 && !(IsBardClass()) ) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK)|| pLPlayer->pCharInfo->Stunned==1 ) {
      if( !casting ) {
         casting = true;
         DoFwd(false);
         ReleaseKeys();
      }
   } else if( casting ) {
      casting = false;
   }

   if (!GetCharInfo() || !bCircling) return;

   float X = pChSpawn->X - CircleX;
   float Y = pChSpawn->Y - CircleY;
   distance = sqrt(X*X + Y*Y);

   if( !casting || !autoPauseEnabled ) {
      if (distance>(CircleRadius*(2.0/3.0))) {
         float newHeading;
         float pulseMoved=GetDistance(pChSpawn->X,pChSpawn->Y,prevX,prevY);
         if( pulseMoved < 5) pulseAvg = (pulseAvg + pulseMoved)/2;
         prevX=pChSpawn->X;
         prevY=pChSpawn->Y;
         prevZ=pChSpawn->Z;

         float SpeedModifier = *((float*) &(((PSPAWNINFO)pLocalPlayer)->Unknown0x100[0x10]));

         if( stickhasmovedfwd && ((pulseAvg < (stuckDist + SpeedModifier) && !pChSpawn->UnderWater) ||
            (pulseAvg == 0 && pChSpawn->UnderWater))&&
            GetDistance(pChSpawn->X,pChSpawn->Y,(float)LocX,(float)LocY) > moveDist && stuckLogic ) {
               stuck++;
               if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
                  newHeading =(float) ( pChSpawn->Heading + turnDirection);
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  pChSpawn->Heading = newHeading;

                  //check to see if we are heading directly away from our target if so then go back
                  newHeading=(float) atan2(pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
                  newHeading += (float) 90.0f * (CircleRadius/distance);
                  newHeading *= 512.0f/360.0f;
                  if( newHeading >= 512.0f ) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;

                  newHeading += 265.0f;
                  if( newHeading >= 512.0f) newHeading -= 512.0f;
                  if( newHeading < 0.0f ) newHeading += 512.0f;
                  if( pChSpawn->Heading > (newHeading - fabs(turnDirection/2)) &&  pChSpawn->Heading <  (newHeading + fabs(turnDirection/2)) ) {
                     newHeading -= 265.0f;
                     if( newHeading >= 512.0f) newHeading -= 512.0f;
                     if( newHeading < 0.0f ) newHeading += 512.0f;
                     pChSpawn->Heading = newHeading;
                     stuck = stuckCheck;
                     turnDirection *= -1.0f;
                  }
               }
               stuckFree=0;

         } else if( stuck > 0 ) {
            if(fmod((float)stuck,(float)stuckCheck) == 0.0f) {
               newHeading =(float) ( pChSpawn->Heading - turnDirection );
               pChSpawn->Heading = newHeading;
          }
            stuck--;
            if(stuckFree++ > stuckCheck*3 ) {
               stuck=0;
          }
         } else {
            heading=(float) atan2( pChSpawn->Y - CircleY,  CircleX - pChSpawn->X) * 180.0f / (float)PI + 90.0f;
            heading += 90.0f * (CircleRadius/distance);
            heading *= 512.0f/360.0f;
            if( heading >= 512.0f ) heading -= 512.0f;
            if( heading < 0.0f ) heading += 512.0f;
            if( bDrunken ) {
               gFaceAngle = (float)heading;
            } else {
               pChSpawn->Heading = (float)heading;
            }
       }
         DoFwd(true);
      }
   }
}

void breakCircle(bool stopMoving, bool quite) {
   bCircling=false;
   bCirclingWasOn=false;
   stuck=0;
   if( stopMoving )
      ReleaseKeys();
   else {
      DoWalk(false);
      DoLft(false);
      DoRgt(false);
   }
}

// Utility Functions
float angularDistance(float h1, float h2)
{
   if( h1 == h2 ) return 0.0;

   if( fabs(h1-h2) > 256.0 )
      *(h1<h2?&h1:&h2) += 512.0;

   return (fabs(h1-h2)>256.0)?(h2-h1):(h1-h2);
}

float getRand(float n)
{
   return (n * rand() / (RAND_MAX+1.0f));
}

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev)
{
   SYSTEMTIME stResult;
   FILETIME ftPrev, ftCurr, ftResult;
   ULARGE_INTEGER prev,curr,result;

   GetSystemTime(&stCurr);
   SystemTimeToFileTime(&stPrev,&ftPrev);
   SystemTimeToFileTime(&stCurr,&ftCurr);
   prev.HighPart = ftPrev.dwHighDateTime;
   prev.LowPart = ftPrev.dwLowDateTime;
   curr.HighPart = ftCurr.dwHighDateTime;
   curr.LowPart = ftCurr.dwLowDateTime;
   result.QuadPart = curr.QuadPart - prev.QuadPart;
   ftResult.dwHighDateTime = result.HighPart;
   ftResult.dwLowDateTime = result.LowPart;
   FileTimeToSystemTime(&ftResult,&stResult);

   return ((int)(stResult.wSecond * 1000 + stResult.wMilliseconds));
}

bool IsBardClass()
{
   if(strncmp(pEverQuest->GetClassDesc(GetCharInfo2()->Class & 0xff),"Bard",5))
      return false;
   else
      return true;
}

//INI Functions
void Save_INI(void) {
   char szTemp[MAX_STRING];

   sprintf(szTemp,"%s",autoPauseEnabled?"on":"off");
   WritePrivateProfileString("Defaults","AutoPause",szTemp,INIFileName);

   sprintf(szTemp,"%s",breakOnWarpEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnWarp",szTemp,INIFileName);

   sprintf(szTemp,"%.1f",breakDist);
   WritePrivateProfileString("Defaults","BreakDist",szTemp,INIFileName);

   sprintf(szTemp,"%s",breakOnGateEnabled?"on":"off");
   WritePrivateProfileString("Defaults","BreakOnGate",szTemp,INIFileName);

   sprintf(szTemp,"%d",stickVerbosity);
   WritePrivateProfileString("Defaults","Verbosity",szTemp,INIFileName);

   sprintf(szTemp,"%.1f",stuckDist);
   WritePrivateProfileString("Defaults","stuckDist",szTemp,INIFileName);

   sprintf(szTemp,"%.1f",turnDirection);
   WritePrivateProfileString("Defaults","turnDirection",szTemp,INIFileName);

   sprintf(szTemp,"%i",stuckCheck);
   WritePrivateProfileString("Defaults","stuckCheck",szTemp,INIFileName);

   sprintf(szTemp,"%s",stuckLogic?"on":"off");
   WritePrivateProfileString("Defaults","StuckLogic",szTemp,INIFileName);

   sprintf(szTemp,"%s",bLeash?"on":"off");
   GetPrivateProfileString("Defaults","UseLeash","on",szTemp,MAX_STRING,INIFileName);
   bLeash=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp,"%.1f",LeashLength);
   WritePrivateProfileString("Defaults","LeashLength",szTemp,INIFileName);

   sprintf(szTemp,"%.1f",CampRadius);
   WritePrivateProfileString("Defaults","CampRadius",szTemp,INIFileName);

   sprintf(szTemp,"%i",mindelay);
   WritePrivateProfileString("Defaults","MinDelay",szTemp,INIFileName);

   sprintf(szTemp,"%i",maxdelay);
   WritePrivateProfileString("Defaults","MaxDelay",szTemp,INIFileName);
}

void Load_INI(void)
{
   char szTemp[MAX_STRING], szTemp2[MAX_STRING];

   // Defaults
   GetPrivateProfileString("Defaults","AutoPause","on",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString("Defaults","BreakOnWarp","on",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString("Defaults","BreakDist","250.0",szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString("Defaults","BreakOnGate","on",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt("Defaults","Verbosity",1,INIFileName);

   GetPrivateProfileString("Defaults","stuckDist","0.8",szTemp,MAX_STRING,INIFileName);
   stuckDist = (float)atof(szTemp);

   GetPrivateProfileString("Defaults","turnDirection","10.0",szTemp,MAX_STRING,INIFileName);
   turnDirection = (float)atof(szTemp);

   GetPrivateProfileString("Defaults","stuckCheck","5",szTemp,MAX_STRING,INIFileName);
   stuckCheck = (int)atoi(szTemp);

   GetPrivateProfileString("Defaults","StuckLogic","on",szTemp,MAX_STRING,INIFileName);
   stuckLogic=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString("Defaults","UseLeash","on",szTemp,MAX_STRING,INIFileName);
   bLeash=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",LeashLength);
   GetPrivateProfileString("Defaults","LeashLength",szTemp2,szTemp,MAX_STRING,INIFileName);
   LeashLength = (float)atof(szTemp);

   sprintf(szTemp2,"%.1f",CampRadius);
   GetPrivateProfileString("Defaults","CampRadius",szTemp2,szTemp,MAX_STRING,INIFileName);
   CampRadius = (float)atof(szTemp);

   mindelay=GetPrivateProfileInt("Defaults","MinDelay",500,INIFileName);

   maxdelay=GetPrivateProfileInt("Defaults","MaxDelay",5000,INIFileName);

   // Character specific
   GetPrivateProfileString(GetCharInfo()->Name,"AutoPause",autoPauseEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   autoPauseEnabled=(strncmp(szTemp,"on",3)==0);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnWarp",breakOnWarpEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnWarpEnabled=(strncmp(szTemp,"on",3)==0);

   sprintf(szTemp2,"%.1f",breakDist);
   GetPrivateProfileString(GetCharInfo()->Name,"BreakDist",szTemp2,szTemp,MAX_STRING,INIFileName);
   breakDist = (float)atof(szTemp);

   GetPrivateProfileString(GetCharInfo()->Name,"BreakOnGate",breakOnGateEnabled?"on":"off",szTemp,MAX_STRING,INIFileName);
   breakOnGateEnabled=(strncmp(szTemp,"on",3)==0);

   stickVerbosity=(short)GetPrivateProfileInt(GetCharInfo()->Name,"Verbosity",stickVerbosity,INIFileName);
}

//Movement Related Functions
void ReleaseKeys() {
   DoWalk(false);
   DoFwd(false);
   DoBck(false);
   DoRgt(false);
   DoLft(false);
}

void DoWalk(bool walk) {
   bool state_walking = (*EQADDR_RUNWALKSTATE) ? false : true;
   float SpeedModifier = *((float*) &(((PSPAWNINFO)pLocalPlayer)->Unknown0x100[0x10]));
   if (SpeedModifier < 0)
      walk = false; // we're snared, dont go into walk mode no matter what
   if ( (walk && !state_walking) || (!walk && state_walking) ) {
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),1,0);
      MQ2Globals::ExecuteCmd(FindMappableCommand("run_walk"),0,0);
   }
}

void DoFwd(bool hold, bool walk) {
   static bool held = false;
   if ( hold ) {
      stickhasmovedfwd = true;
      DoWalk(walk);
      DoBck(false);
      if (!held) {
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
      }
      held = true;
   } else {
      DoWalk(false);
      if (held) {
         MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
      }
      held = false;
   }
}

void DoBck(bool hold) {
   static bool held = false;
   if( hold ) {
      DoFwd(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
      held = false;
   }
}

void DoLft(bool hold) {
   static bool held = false;
   if( hold ) {
      DoRgt(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
      held = false;
   }
}

void DoRgt(bool hold) {
   static bool held = false;
   if( hold ) {
      DoLft(false);
      if (!held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
      held = true;
   } else {
      if (held)
         MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
      held = false;
   }
}

//MQ2Plugin Taps
VOID DoUnstickBind(PCHAR Name, BOOL Down)
{
   if( ! Down ) {
      keysDown--;
      if( keysDown == 0 && mPause ) {
         DoFwd(false);
         PauseLogic = false;
         stuck=0;
         stickhasmovedfwd=false;
         moveBehind = prevMoveBehind;
         movePin = prevMovePin;
      }
   } else {
      keysDown++;
      if(!PauseLogic && (stickOn || bMoveToOn || bCircling || bMakeCamp) ) {
         if( mPause && strncmp(Name,"UNSTICK_STRAFE_RGT",12) && strncmp(Name,"UNSTICK_STRAFE_LFT",19) ) {
            PauseLogic = true;
            if(bMakeCamp && bMoveToOn) breakMoveTo();
         } else if( mPause ) {
            prevMoveBehind = moveBehind || prevMoveBehind;
            prevMovePin = movePin || prevMovePin;
            moveBehind = false;
         } else {
            if(stickOn)breakStick();
//            if(stickOn)breakStick((strncmp(Name,"UNSTICK_BCK",12) && strncmp(Name,"UNSTICK_FWD",12)));
            if(bCircling) breakCircle();
            if(bMoveToOn) breakMoveTo();
       }
      }
   }
}

void CreateBinds(){
   if (MoveBindsLoaded)
      return;
   MoveBindsLoaded=true;
   AddMQ2KeyBind("UNSTICK_FWD",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_BCK",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_RGT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_LFT",DoUnstickBind);
   AddMQ2KeyBind("UNSTICK_STRAFE_RGT",DoUnstickBind);
   SetMQ2KeyBind("UNSTICK_FWD",false,pKeypressHandler->NormalKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_FWD",true,pKeypressHandler->AltKey[FindMappableCommand("forward")]);
   SetMQ2KeyBind("UNSTICK_BCK",false,pKeypressHandler->NormalKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_BCK",true,pKeypressHandler->AltKey[FindMappableCommand("back")]);
   SetMQ2KeyBind("UNSTICK_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("left")]);
   SetMQ2KeyBind("UNSTICK_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_LFT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_left")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",false,pKeypressHandler->NormalKey[FindMappableCommand("strafe_right")]);
   SetMQ2KeyBind("UNSTICK_STRAFE_RGT",true,pKeypressHandler->AltKey[FindMappableCommand("strafe_right")]);
}

void DestroyBinds()
{
   if (!MoveBindsLoaded)
      return;
   RemoveMQ2KeyBind("UNSTICK_FWD");
   RemoveMQ2KeyBind("UNSTICK_BCK");
   RemoveMQ2KeyBind("UNSTICK_LFT");
   RemoveMQ2KeyBind("UNSTICK_RGT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_LFT");
   RemoveMQ2KeyBind("UNSTICK_STRAFE_RGT");
}

// Called once, when the plugin is to initialize
PLUGIN_API void InitializePlugin(void)
{
   DebugSpewAlways("Initializing MQ2MoveUtils");

   // Add commands, macro parameters, hooks, etc.
   AddCommand("/circle",CircleCommand,0,1,1);
   AddCommand("/stick",StickCommand);
   AddMQ2Data("Stick",dataStick);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("MoveTo",dataMoveTo);
   AddCommand("/makecamp",MakeCampCommand,0,1,1);
   AddMQ2Data("MakeCamp",dataMakeCamp);

   pStickType = new MQ2StickType;
   pMakeCampType = new MQ2MakeCampType;
   pMoveToType = new MQ2MoveToType;

   srand((unsigned int)time(NULL));
   if (gGameState==GAMESTATE_INGAME) {
      CreateBinds();
   }
   GetSystemTime(&stPrevCirc);
   GetSystemTime(&stPrevStick);
}

// Called once, when the plugin is to shutdown
PLUGIN_API void ShutdownPlugin(void)
{
   DebugSpewAlways("Shutting down MQ2MoveUtils");

   // Remove commands, macro parameters, hooks, etc.
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   RemoveMQ2Data("MoveTo");
   RemoveCommand("/moveto");
   RemoveMQ2Data("MakeCamp");
   RemoveCommand("/makecamp");

   delete pStickType;
   delete pMoveToType;
   delete pMakeCampType;

   DestroyBinds();
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
   if( breakOnGateEnabled && (stickHold?(stickTarget!=NULL):(ppTarget && pTarget)) ) {
      char szTemp[MAX_STRING];

      sprintf(szTemp,"%s Gates.",stickHold?stickTarget->DisplayedName:((PSPAWNINFO)pTarget)->DisplayedName);
      if( ! strcmp(szTemp,Line) ) {
         DoFwd(false);
         stickOn = false;
      }
   }

   return 0;
}

PLUGIN_API void SetGameState(DWORD GameState)
{
   if (GameState==GAMESTATE_INGAME) {
      CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      PauseLogic=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
      bMakeCamp=false;
      bMoveToOn=false;
   }
}

// Called after entering a new zone
PLUGIN_API void OnZoned(void)
{
   DoWalk(false);
}

PLUGIN_API void OnPulse(void)
{
   //   *((float *)&(((PSPAWNINFO)pLocalPlayer)->Unknown0x0af[25])) = 0.95f;
   if( bDebug ) DebugSpew();
   if( bCircling && !PauseLogic) {
      if( bDrunken ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevCirc) > 900 + (int)getRand(600.0) ) {
            GetSystemTime(&stPrevCirc);
            HandleCircle();
       }
      } else {
         HandleCircle();
      }
   }
   if( stickOn && !PauseLogic) {
      if( looseStick ) {
         SYSTEMTIME stCurr;
         GetSystemTime(&stCurr);
         if( millisDiff(stCurr,stPrevStick) > 100 + (int)getRand(200.0) ) {
            GetSystemTime(&stPrevStick);
            HandleStick();
       }
      } else {
         HandleStick();
      }
   }
   if( bMoveToOn  && !PauseLogic ) {
      HandleMoveTo();
   }
   if (bMakeCamp) {
      HandleMakeCamp();
   }
}
 
Re: Fixed

Getting

Compiling...
MQ2AutoSkills.cpp
EQLIB_IMPORTS
MQ2AutoSkills.cpp(385) : warning C4244: 'initializing' : conversion from 'DWORD' to 'BYTE', possible loss of data
MQ2AutoSkills.cpp(412) : error C2039: 'pActorInfo' : is not a member of 'EQData::_SPAWNINFO'
c:\Documents and Settings\Larry Allen\Desktop\MQ2-20060420\MQ2Main\EQData.h(946) : see declaration of 'EQData::_SPAWNINFO'
MQ2AutoSkills.cpp(412) : error C2039: 'pActorInfo' : is not a member of 'EQData::_SPAWNINFO'
c:\Documents and Settings\Larry Allen\Desktop\MQ2-20060420\MQ2Main\EQData.h(946) : see declaration of 'EQData::_SPAWNINFO'
MQ2AutoSkills.cpp(412) : error C2227: left of '->Mount' must point to class/struct/union
MQ2AutoSkills.cpp(415) : error C2039: 'pActorInfo' : is not a member of 'EQData::_SPAWNINFO'
c:\Documents and Settings\Larry Allen\Desktop\MQ2-20060420\MQ2Main\EQData.h(946) : see declaration of 'EQData::_SPAWNINFO'
MQ2AutoSkills.cpp(415) : error C2227: left of '->CastingSpellID' must point to class/struct/union
MQ2AutoSkills.cpp(474) : error C2039: 'pActorInfo' : is not a member of 'EQData::_SPAWNINFO'
c:\Documents and Settings\Larry Allen\Desktop\MQ2-20060420\MQ2Main\EQData.h(946) : see declaration of 'EQData::_SPAWNINFO'
MQ2AutoSkills.cpp(474) : error C2227: left of '->pTargetOfTarget' must point to class/struct/union

Build log was saved at "file://c:\Documents and Settings\Larry Allen\Desktop\Mq2-20060420\MQ2AutoSkills\Intermediate\BuildLog.htm"
MQ2AutoSkills - 7 error(s), 1 warning(s)

When i try to compile the MQ2AutoSkills.cpp; any idea whats wrong?
 
Plugin - MQ2Twist

Users who are viewing this thread

Back
Top