/*

MQ2MoveUtils plugin (2004.06.23) - tonio

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

*/

#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;

VOID MoveToCommand(PSPAWNINFO pChar, PCHAR szLine);
bool bMoving=false;
double LocX=0.0f;
double LocY=0.0f;
float moveDist=10.0;
float moveDistMod=0.0;

int millisDiff(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

VOID StickCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID DoUnstickBind(PCHAR Name, BOOL Down);
void DoFwd(bool hold);
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);
float StateHeightMultiplier(DWORD StandState);
float angularDistance(float h1, float h2);

bool bCreateBinds=false;
bool stickOn=false;
bool setDist=false;
bool stickPaused=false;
bool stickHold=false;
bool moveBehind=false;
bool movePin=false;
bool moveBack=false;
bool casting=false;
bool mPause=false;
bool prevMoveBehind=false;
bool prevMovePin=false;
bool looseStick=false;
bool underwater=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;
SYSTEMTIME stPrevStick;

bool autoPauseEnabled=true;
bool breakDistEnabled=true;
bool breakOnWarpEnabled=true;
bool breakOnGateEnabled=true;

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;
}

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=bMoving;
            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( bMoving ) {
         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 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 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;

   if (bCircling) {
      WriteChatColor("Cannot /moveto while circling",USERCOLOR_DEFAULT);
      return;
   }
   if (stickOn) {
      WriteChatColor("Cannot /moveto while sticking",USERCOLOR_DEFAULT);
      return;
   }

   GetArg(szTemp,szLine,1);
   if (!stricmp(szTemp,"help")) {
      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;
         }
      }

      bMoving=true;
      sprintf(szMsg, "Moving to loc %g %g", LocY, LocX);
      if (stickVerbosity) WriteChatColor(szMsg, CONCOLOR_YELLOW);
      DoFwd(true);

   } else if (!stricmp(szTemp,"off")) {
      bMoving = false;
   } else {
      if (!ppTarget || !pTarget) {
         MoveToHelp();
         return;
      }
      if (strlen(szTemp)) {
         if( isdigit(szTemp[0]) || szTemp[0]=='.' ) {
            moveDist = (float)atof(szTemp);
         } else if( szTemp[0]=='-' ) {
            moveDistMod = (float)atof(szTemp);
            moveDist += moveDistMod;
         }
      }
      PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
      bMoving=true;
      LocY=psTarget->Y;
      LocX=psTarget->X;
      sprintf(szMsg, "Moving to %s, loc %g %g", psTarget->DisplayedName, LocY, LocX);
      if (stickVerbosity) WriteChatColor(szMsg, CONCOLOR_YELLOW);
      DoFwd(true);

   }
}

void StickHelp()
{
   WriteChatColor("Usage: /stick [on|hold|off|pause|unpause|reload] [<dist>] [behind] [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 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) ) {
      DoFwd(false);
      stickPaused = true;
      GetArg(currentArg,szLine,argn++);
      return;
   } else if( !strncmp( currentArg,"unpause",8 ) ) {
      stickPaused = false;
      GetArg(currentArg,szLine,argn++);
      return;
   }

   stickOn=true;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickTarget=NULL;
   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 ) {
            stickHold = true;
            stickTarget = (PSPAWNINFO)pTarget;
         }
      } else if( !strncmp(currentArg,"behind",7) ) {
         stickOn=true;
         moveBehind=true;
      } else if( !strncmp(currentArg,"pin",3) ) {
         stickOn=true;
         movePin=true;
      } else if( !strncmp(currentArg,"reload",7) ) {
         breakStick();
         Load_INI();
         WriteChatColor("Ini file reloaded.");
         break;
      } else {
         breakStick();
         StickHelp();
         break;
      }
      GetArg(currentArg,szLine,argn++);
   }
   if( stickOn ) {
      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;
               movePin = 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;
      }
   }
}

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) {
      if( FindSpeed(pChSpawn) <= 0 ) {
         DoFwd(true);
      }
   } else {
      bMoving=false;
      if( FindSpeed(pChSpawn) > 0 ) {
         DoFwd(false);
      }
   }

}

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))) || (!stickHold && ((!ppTarget) || (!pTarget))) ) {
      breakStick();
   } else {
      PSPAWNINFO psTarget = stickHold?stickTarget:(PSPAWNINFO)pTarget;
      PSPAWNINFO pChSpawn = (PSPAWNINFO) pCharSpawn;
      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()) ) {
         if( !casting ) {
            casting = true;
            DoFwd(false);
         }
      } 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( looseStick ) {
            gFaceAngle = newHeading;
         } else {
            pChSpawn->Heading = newHeading;
         }
         //underwater = pChSpawn->pActorInfo->UnderWater==5;
         if( underwater ) {
            double Distance = DistanceToSpawn(pChSpawn,psTarget);
            double lookAngle = atan2(psTarget->Z + psTarget->AvatarHeight*StateHeightMultiplier(psTarget->StandState) -
               pChSpawn->Z - pChSpawn->AvatarHeight*StateHeightMultiplier(pChSpawn->StandState),
               (FLOAT)Distance) * 256.0f / PI;
            if ( looseStick ) {
               gLookAngle = lookAngle;
            } else {
               pChSpawn->CameraAngle = (FLOAT)lookAngle;
            }
         }
         if( moveBehind ) {
            if( fabs(angularDistance(psTarget->Heading,pChSpawn->Heading)) > 45.0 ) {
               if(angularDistance(psTarget->Heading,pChSpawn->Heading) < 0.0) {
                  // strafe left
                  DoLft(true);
               } else {
                  // strage right
                  DoRgt(true);
               }
            } else {
               DoLft(false);
               DoRgt(false);
            }
         }
         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( GetDistance(pChSpawn,psTarget) > stickDist) {
            if( FindSpeed(pChSpawn) <= 0 ) {
               DoFwd(true);
            }
         } else if( moveBack && GetDistance(pChSpawn,psTarget) < (stickDist-5.0) ) {
            if( FindSpeed(pChSpawn) >= 0 ) {
               DoBck(true);
            }
         } else if( FindSpeed(pChSpawn) > 0 ) {
            DoFwd(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(GetCharInfo()->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 (bMoving) {
      HandleMoveTo();
   }
}

void DoFwd(bool hold)
{
   MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),1,0);
   if( !hold )
      MQ2Globals::ExecuteCmd(FindMappableCommand("forward"),0,0);
}

void DoBck(bool hold)
{
   MQ2Globals::ExecuteCmd(FindMappableCommand("back"),1,0);
   if( !hold )
      MQ2Globals::ExecuteCmd(FindMappableCommand("back"),0,0);
}

void DoLft(bool hold)
{
   MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),1,0);
   if( !hold )
      MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_left"),0,0);
}

void DoRgt(bool hold)
{
   MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),1,0);
   if( !hold )
      MQ2Globals::ExecuteCmd(FindMappableCommand("strafe_right"),0,0);
}

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)
{
   stickOn=false;
   stickPaused=false;
   stickHold=false;
   setDist=false;
   moveBehind=false;
   prevMoveBehind=false;
   movePin=false;
   prevMovePin=false;
   moveBack=false;
   mPause=false;
   looseStick=false;
   underwater=false;
   stickTarget=NULL;
   stickDistMod=0.0;
   stickDistModP=1.0;
   if( stopMoving ) {
      DoFwd(false);
      DoBck(false);
      DoLft(false);
      DoRgt(false);
   }
   stickText();
}

VOID CreateBinds()
{
   bCreateBinds=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()
{
   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);
   AddCommand("/moveto",MoveToCommand,0,1,1);
   AddMQ2Data("Stick",dataStick);
   AddMQ2Data("MoveTo",dataMoveTo);

   pStickType = new MQ2StickType;
   pMoveToType = new MQ2MoveToType;

   srand((unsigned int)time(NULL));
   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("MoveTo");
   RemoveMQ2Data("Stick");
   RemoveCommand("/circle");
   RemoveCommand("/stick");
   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) {
      if (!bCreateBinds) CreateBinds();
      Load_INI();
   } else {
      stickOn=false;
      stickPaused=false;
      stickHold=false;
      setDist=false;
      stickTarget=NULL;
      bCircling=false;
   }
}

float StateHeightMultiplier(DWORD StandState)
{
   switch (StandState)
   {
      case STANDSTATE_BIND:
      case STANDSTATE_DUCK:
         return 0.5f;
      case STANDSTATE_SIT:
         return 0.3f;
      case STANDSTATE_FEIGN:
      case STANDSTATE_DEAD:
         return 0.1f;
      case STANDSTATE_STAND:
      default:
      return 0.9f;
   }
}