/***** NOTICE ******
Many functions included in this source code are not copyrighted to the developer and are used with permissions 
restricting their release to VIP board members of MacroQuest2.com. These functions have comments above them stating 
to whom the copyright belongs to. If you intend to redistribute this source or binaries compiled from this source 
outside of a direct link to the forum post in which it is released, you must get permission from these authors. Otherwise 
link to the forum post directly to encourage any users of this plugin to donate to the developers at MacroQuest2.com 
as required by the copyright holders of these functions, and desired by the developer. Please show your support!
****************/

#include "../MQ2Plugin.h"
#include "math.h"
#include <vector>

//uncomment this line to enable debugspew spam
//#define DEBUGALL //not really all
//debugspew for stucklogic
//#define DEBUGSTUCK
//debugspew for trivial messages
//#define DEBUGOTHER
//debugspew for nohott
//#define DEBUGNOHOTT

// version information
const char*  MODULE_NAME    = "MQ2MoveUtils";
const double MODULE_VERSION = 9.0526;
PreSetup(MODULE_NAME);
PLUGIN_VERSION(MODULE_VERSION); // will truncate, hence we use our own TLO

// constants
// ----------------------------------------
// array defines
#define LocY 0
#define LocX 1
#define LocZ 2
#define MIN 0
#define MAX 1
// check calling command (aesthetics)
const unsigned char USED_STICK    = 1;
const unsigned char USED_MOVETO   = 2;
const unsigned char USED_CIRCLE   = 3;
const unsigned char USED_MAKECAMP = 4;
const unsigned char APPLY_TO_ALL  = 5;
// reset altcamp or make new altcamp
const unsigned char SET_ALT       = 20;
const unsigned char RESET_ALT     = 21;
// direction to move
const unsigned char GO_FORWARD    = 30;
const unsigned char GO_BACKWARD   = 31;
const unsigned char GO_LEFT       = 32;
const unsigned char GO_RIGHT      = 33;
const unsigned char KILL_STRAFE   = 34;
const unsigned char KILL_FB       = 35;
// WriteLine verbosity level to check
const unsigned char VERB_ALL      = 40;
const unsigned char VERB_VERB     = 41;
const unsigned char VERB_FULL     = 42;
const unsigned char VERB_ENFORCE  = 43;
// help output
const unsigned char HELP_SETTINGS = 50;
// debug output
const unsigned char DBG_ALL       = 200;
const unsigned char DBG_STUCK     = 201;
const unsigned char DBG_OTHER     = 202;
const unsigned char DBG_NOHOTT    = 203;
const unsigned char DBG_DISABLE   = 204;
// for formulas & randomization
const float CIRCLE_QUARTER        = 90.0f;
const float CIRCLE_HALF           = 180.0f;
const float CIRCLE_MAX            = 360.0f;
const float HEADING_QUARTER       = 128.0f;
const float HEADING_HALF          = 256.0f;
const float HEADING_MAX           = 512.0f;
const int BEHIND_ARC              = 45;
const int FRONT_ARC               = 240;
const int NOT_FRONT_ARC           = 135;
const int PIN_ARC_MIN             = 112;
const int PIN_ARC_MAX             = 144;
// ----------------------------------------
// import / export
PLUGIN_API bool bStickOn = false; // stick active or off, exported for other plugins to have a sure way of always knowing
// note to any developers: if you intend to use these exports and want to shut off stick, do not flip this bool off directly,
// call StickCommand(pChar, "off") or call EndPreviousCmd(bool), where bool= do you want to halt movement?
PLUGIN_API void EndPreviousCmd(bool bKillMovement, unsigned char ucCmdUsed = APPLY_TO_ALL, bool bPreserveSelf = false);
PLUGIN_API void StickCommand(PSPAWNINFO pChar, char* szLine); // exported wrapper for MQ2Melee support
bool* pbMULoaded = NULL; // imported from mq2melee in Initialize
// ----------------------------------------
// main variables
char szMsg[MAX_STRING]       = {0};  // use for generic msg output
char szDebugName[MAX_STRING] = {0};  // debug file name
const char szOn[7]           = "\agON\ax";  // used in outputs
const char szOff[8]          = "\arOFF\ax"; // used in outputs
char szReturnMsg[200]        = "/moveto location"; // used to swap messages for moveto and makecamp [alt]return

bool bKeyBindsSet      = false; // loaded keybinds or not, used on plugin load/unload
bool bImAggro          = false; // TLO: used by MoveUtils.Aggro
bool bAllPaused        = false; // pauses all operations, 'pause' command param
bool bManualMove       = false; // if 'mpause' active, true while keys are pressed
bool bManualMouse      = false; // if mouselook is active, dont campreturn
bool bIsBard           = false; // so we dont call bard class checking function onpulse
bool bBrokeOnSummon    = false; // is true if bBreakOnSummon fired
bool bBrokenMoveTo     = false; // TLO: true if moveto has been broken from by aggro / being hit
// ----------------------------------------
// bools controlled by command usage
// ----------------------------------------
// makecamp
bool bMakeCamp         = false; // makecamp active or off, makecamp works in conjunction with stick, circle, moveto
bool bAltCamp          = false; // true if alt camp values are in place
bool bCampPlayer       = false; // makecamp player on or off
bool bMakeCampReturn   = false; // true when manually forcing a return to camp
bool bAltCampReturn    = false; // true when manually forcing a return to altcamp
bool bReturnActive     = false; // true when return is active (both auto and manual)
bool bAltRetActive     = false; // true when altreturn is active
bool bAutoReturning    = false; // state awareness (is camp return auto or manual)
bool bStickWasOn       = false; // set by makecamp if stick was on and needs to resume
bool bCirclingWasOn    = false; // set by makecamp if circle was on and needs to resume
// moveto
bool bMoveToOn         = false; // moveto active or off
bool bLooseMoveTo      = true;  // moveto simulates more natural movement
bool bYPrecision       = false; // moveto arrivaldist is only checked against Y
bool bXPrecision       = false; // moveto arrivaldist is only checked against X
bool bUWMoveTo         = false; // moveto underwater
// stick
bool bSetDist          = false; // stick (has dist to mob has been set)
bool bLooseStick       = true;  // stick simulates more natural movement
bool bMoveBack         = false; // stick maintains distance (if too close, back up)
bool bStickBehind      = false; // stick uses rear arc of target
bool bStickBehindOnce  = false; // stick moves to rear arc of target then position is ignored
bool bStickFront       = false; // stick uses front arc of target
bool bStickNotFront    = false; // stick uses anywhere but the front arc of target (/stick !front)
bool bStickPin         = false; // stick uses left or right arc of the target
bool bStickSnaproll    = false; // stick snaprolls to a fixed loc (polarmath) instead of strafing
bool bStickHold        = false; // stick maintains sticking to previous target if it changes
bool bUnderwater       = false; // stick to determine if we want to look up or down on z axis (/stick uw)
bool bStickNewMob      = false; // keep stick active, and restick when new target acquired (wont work with hold) (/stick __ always)
bool bStickNewTarget   = false; // true if bStickNewMob true and have a target
// circle
bool bCircling         = false; // circle active or off
bool bBackwards        = false; // circle runs backwards or forwards
bool bClockwise        = true;  // circle moves clockwise or counter-clockwise
bool bDrunken          = false; // circle turns at random intervals instead of a perfect circle
// ----------------------------------------
// bools used by stucklogic
bool bCmdMovedFwd      = false; // awareness if plugin has attempted to move forward
bool bCmdMovedSide     = false; // awareness if plugin has attempted to strafe
bool bTryToJump        = true;  // INI: if true, try to jump when stuck
bool bTurnHalf         = true;  // INI: if true, reset heading and try other dir if gone halfway without freeing
// ----------------------------------------
// plugin feature-related bools
bool bAutoPause        = true;  // INI: pause automatically when casting/stunned/self targeted
bool bAutoPauseOutput  = true;  // INI: output msg when autopaused
bool bAutoUW           = false; // automatically use 'uw' when underwater
bool bPauseOnMove      = false; // INI: mpause active or off
bool bPauseOnMouse     = false; // INI: mousepause active or off
bool bBreakOnMouse     = false; // INI: break from command if mouselooking
bool bBreakOnKB        = true;  // INI: break from command if key pressed
bool bCheckStuck       = true;  // INI: stucklogic active or off
bool bVerbosity        = true;  // INI: command basic verbosity
bool bFullVerbosity    = true;  // INI: command enhanced verbosity and other plugin output
bool bTotalSilence     = false; // INI: silences majority of plugin output except critical messages
bool bUseWindow        = false; // INI: use plugin-dedicated UI window
bool bHideHelp         = false; // INI: never auto-display help output
bool bAutoSave         = true;  // INI: autosave ini file if using 'toggle' or 'set'
bool bFeignSupport     = false; // INI: dont stand if currently FD
bool bSaveByChar       = true;  // INI: save some settings for individual characters
bool bDisregardMe      = false; // INI: ignore char a specific character's individual settings (savebychar on, but dont use this char)
bool bBreakOnSummon    = false; // INI: command breaks if you move too far in a single pulse (summoned)
bool bBreakOnWarp      = true;  // INI: stick breaks if target warps out of fBreakDist
bool bPauseOnWarp      = false; // INI: stick pauses if target warps out of fBreakDist
bool bBreakOnGate      = true;  // INI: stick breaks if "mob_name Gates." message
// ----------------------------------------
// command feature-related bools loaded with INI
bool bLeash            = false; // INI: camp leashing active or off
bool bScatter          = false; // INI: camp return scattering active or off
bool bReturnNoAggro    = false; // INI: if true return to camp only if not aggro
bool bReturnNotLoot    = false; // INI: if true, check to be sure we are not looting before returning to camp
bool bReturnHaveTarget = false; // INI: if true, target check does not apply, return even with a target
bool bTrueHead         = true;  // INI: if true, use actual turn right/left for heading
bool bPermTrueHead     = true;  // INI: if true, always use true turning
bool bPermLooseMoveTo  = false; // INI: always use loose /moveto (meaning loose is implied, dont have to type it)
bool bPermLooseStick   = false; // INI: always use loose /stick
bool bSpinInCircles    = false; // INI: if true, stick front ignores requiring being on HoTT
bool bAlwaysBack       = false; // INI: circle always backwards
bool bAlwaysDrunk      = false; // INI: circle always drunken
bool bAlwaysCCW        = false; // INI: circle always counter-clockwise
bool bWalkMoveTo       = true;  // INI: moveto walks if close to arrivaldist
bool bBreakOnAggro     = false; // INI: break moveto if aggro gained
bool bBreakOnHit       = false; // INI: break moveto if attacked (blech event)
bool bRandomizeArc     = false; // INI: randomize customangle arcs during stick
bool bStrafeDelay      = false; // INI: customangle sticks use delay timer
bool bUseBack          = true;  // INI: use backwards walking if close to target
// ----------------------------------------
// stucklogic: state
unsigned int uiPulseCheck   = 6;      // INI: # of pulses to average distance for stuck awareness
unsigned int uiPulseUnstuck = 10;     // INI: if sistuckdec == this, consider unstuck
unsigned int uiStuck        = 0;      // increments each pulse we haven't moved beyond fDistStuck until we reach uiPulseCheck value which == we are stuck
unsigned int uiStuckDec     = 0;      // increments each pulse we've moved again after being stuck, enough consecutive increments == consider unstuck
// stucklogic: distance
float fDistStuck            = 0.2f;   // INI: dist needed to move compared against pulse average
float fPrevYXZ[3]           = {0.0f}; // stores your Y loc for comparison per pulse
float fPulseAvg             = 1.0f;   // result of average movement per pulse, 1.0 is a baseline, it will autoadjust as needed
float fStuckTurn            = 10.0f;  // stores turn increment values for left/right
float fOrigHead             = 0.0f;   // stores original heading for optional half turning
float fHalfHead             = 0.0f;   // stores value for halfway from orig head
float fCompPosHead          = 0.0f;   // stores value for comparison for halfhead
float fCompNegHead          = 0.0f;   // stores values for comparison for halfhead
// MovingAvg max pulses to average
#define MAXRINGSIZE 32
// ----------------------------------------
// makecamp numerics
float fAnchorYX[2]         = {0.0f}; // anchor planted at YX
float fAltAnchorYX[2]      = {0.0f}; // altcamp anchor YX
float fLeashLength         = 50.0f;  // length of leash checked against anchor if bLeash on
float fCampRadius          = 40.0f;  // if we exceed this radius and have no target, makecamp returns
float fAltCampRadius       = 0.0f;   // altcamp radius
float fBearing             = 0.0f;   // bearing for camp return scattering
float fScatDist            = 10.0f;  // dist for camp return scattering
float fScatter             = 10.0f;  // size of camp return scattering
// moveto numerics
float fMoveLocYXZ[3]       = {0.0f}; // Y location for moveto
float fReturnLocYX[2]      = {0.0f}; // YX location for moveto if camp returning
float fMoveDist            = 10.0f;  // INI: how close to moveto location is considered acceptable
float fMoveDistY           = 10.0f;  // INI: how close to moveto Y location is acceptable
float fMoveDistX           = 10.0f;  // INI: how close to moveto X location is acceptable
float fMoveDistINI         = 10.0f;  // preserves value read from ini
float fMoveDistMod         = 0.0f;   // INI: moveto percent modifier
float fMoveDistModINI      = 0.0f;   // preserves value read from ini
// stick numerics
float fStickDist           = 0.0f;   // defaults to melee range or set to 10 by /stick 10 for example
float fBreakDist           = 250.0f; // INI: distance target can get away from you before you break from command
float fCurrentDist         = 0.0f;   // set onpulse, stores your current distance from stick target
float fStickDistMod        = 0.0f;   // set by user, modifier to stickdist (best used if plugin is auto-setting dist)
float fStickDistModP       = 1.0f;   // set by user, modifier to stickdist using percent (best used if plugin is auto-setting dist)
float fBackupDist          = 10.0f;  // INI: default dist to use backward movement positioning
float fNotFrontArc         = 135.0f; // default arc for stick !front
float fBehindArc           = 45.0f;  // default arc for stick behind
float fRandArcMin          = 0.0f;   // default min arc for customangle randomization
float fRandArcMax          = 0.0f;   // default max arc for customangle randomization
float fRandArcFlag         = 1.0f;   // which arc to randomize (min or max)
unsigned long ulOldStickID = 0;      // used in wegetsignal to compare stick target change
// circle numerics
float fCircleYX[2]         = {0.0f}; // center YX location of circle
float fCircleRadiusMod     = 0.0f;  // circle radius percent modifer
float fCircleRadius        = 30.0f; // INI: circle radius size
// breakonsummon numerics
float fBreakSummonDist     = 8.0f; // INI: distance your character can move in a single pulse before plugin halting
float fNewCompare          = 0.0f; // set onpulse, stores your current distance comparison to previous pulse
float fMyLastYX[2]         = {0.0f}; // set every MainProcess call for BreakOnSummon, location Y X
// AdjustHeading()
float fTurnRate            = 14.0f;    // INI: rate at which to turn using loose heading (14 is max default now)
float fAdjustHead          = 10000.0f; // if != 10000.0f, call AdjustHeading() onpulse
float fAllowMove           = 32.0f;    // allow forward movement while turning, CanLooseMove()
// snaproll numerics
float fSnapHeading         = 0.0f;         // heading to snaproll
float fSnapYX[2]           = {0.0f};       // Y X location to snaproll to
float fSnapBearing         = HEADING_HALF; // bearing of target to snaproll to
float fSnapDist            = 10.0f;        // INI: default distance from target to snaproll to
// ----------------------------------------
// time calculations
int iPauseDelay[2]   = {500, 5000};  // random delay for resume from mpause/mousepause
int iCampDelay[2]    = {500, 1500};  // random delay for returning to camp
int iStrafeDelay[2]  = {1500, 3000}; // min random delay for adjusting stick
int iCampReturnTime  = 0;    // diff_in_ms result is stored here, return to camp
int iPauseReturnTime = 0;    // diff_in_ms result stored here, return from mpause/mousepause
int iStrafeDelayTime = 0;    // diff_in_ms for stick delay time before moving again after stopping
// used for timers
SYSTEMTIME stReturnTime;
SYSTEMTIME stPrevCirc;
SYSTEMTIME stStickStart;
// ----------------------------------------
// events
unsigned int Event_AggroNorm   = NULL;
unsigned int Event_MissNorm    = NULL;
unsigned int Event_AggroAbbrev = NULL;
unsigned int Event_MissAbbrev  = NULL;
unsigned int Event_MissNumOnly = NULL;
unsigned int Event_Gates       = NULL;
// ----------------------------------------
// keys & pointers
int iAutoRun     = NULL;
unsigned long* pulAutoRun     = NULL;
int iForward     = NULL;
unsigned long* pulForward     = NULL;
int iBackward    = NULL;
unsigned long* pulBackward    = NULL;
int iTurnLeft    = NULL;
unsigned long* pulTurnLeft    = NULL;
int iTurnRight   = NULL;
unsigned long* pulTurnRight   = NULL;
int iStrafeLeft  = NULL;
unsigned long* pulStrafeLeft  = NULL;
int iStrafeRight = NULL;
unsigned long* pulStrafeRight = NULL;

int iJumpKey     = NULL;
int iRunWalk     = NULL;
int iDuckKey     = NULL;

char* szFailedLoad[] = {
    "No Error",            // 0
    "TurnRight Address",   // 1
    "TurnRight",           // 2
    "StafeLeft",           // 3
    "StrafeRight",         // 4
    "AutoRun",             // 5
    "TurnLeft",            // 6
    "MoveForward Address", // 7
    "Forward",             // 8
    "AutoRun Mismatch",    // 9
    "Backward"             // 10
};
unsigned long addrTurnRight     = NULL;
PBYTE patternTurnRight          = (PBYTE)"\xA3\x00\x00\x00\x00\x89\x1D\x00\x00\x00\x00\x89\x1D\x00\x00\x00"
                                         "\x00\x0F\x84\x00\x00\x00\x00\xF6\x05\x00\x00\x00\x00\x00\x74\x00"
                                         "\x89\x1D\x00\x00\x00\x00\x89\x1D";
char maskTurnRight[]            = "x????xx????xx????xx????xx?????x?xx????xx";
unsigned long addrMoveForward   = NULL;
PBYTE patternMoveForward        = (PBYTE)"\xA3\x00\x00\x00\x00\x0F\x84\x00\x00\x00\x00\xF6\x05\x00\x00\x00"
                                         "\x00\x00\x74\x00\x89\x1D";
char maskMoveForward[]          = "x????xx????xx?????x?xx";
// ----------------------------------------
// stick id/hold & makecamp player retention
unsigned long ulStickID      = 0; // stores id of target when using stick hold
unsigned long ulCampPlayerID = 0; // stores id of makecamp player
eSpawnType stickTarget_Type; // stores spawn type of target when using stick hold (ie if target changes to corpse it will stop sticking to it)
eSpawnType campPlayer_Type;  // stores spawn type of makecamp player so you dont camp a player that has become a corpse
// ----------------------------------------
// function prototypes
void SpewMTError(unsigned char ucErrorNum, unsigned char ucFilterLevel);
void SpewDebug(unsigned char ucDbgType, char* szOuput, ...);
void OutputHelp(unsigned char ucCmdUsed, bool bOnlyCmdHelp = false);
void WriteLine(char szOutput[MAX_STRING], unsigned char ucFilterLevel = VERB_ALL);

void HandleOurCmd(unsigned char ucCmdUsed, char* szInput);
void ChangeSetting(unsigned char ucCmdUsed, bool bToggle, char szSetting[MAX_STRING]);
bool ToggleSetting(const char* szToggleOutput, bool* bEvalThis, bool* bUsedToggle, bool* bTurnOn, unsigned char ucFilterLevel = VERB_VERB);

inline void ResetFromPause();
inline void AdjustHeading(float fNewHead);
inline void CampReturn(float fY, float fX, float fUseRadius, float* fUseY, float* fUseX);
void ResetCamp(bool bOffMsg, unsigned char ucAltCamp = 0);
void DoMovement(unsigned char ucDirection, bool bHoldKey = true, bool bUseWalking = false);

void DoKeybinds();
void UndoKeybinds();
void KeybindPressed(char* szName, int iKeyPressed);

void SaveConfig();
void LoadConfig();
void DebugToWnd(unsigned char ucCmdUsed);
void DebugToINI(unsigned char ucCmdUsed);

void SetupEvents(bool bAddEvent, bool bForceRemove = false);
inline bool ValidIngame(bool bCheckDead = true);
inline float GetRandomNumber(float fN);
inline float CalcAngularDist(float fH1, float fH2);
int diff_in_ms(SYSTEMTIME &stCurr, SYSTEMTIME &stPrev);

///////////////////////////////////////
// Begin Custom Top-Level Objects

class MQ2MakeCampType*   pMakeCampType   = NULL;
class MQ2StickType*      pStickType      = NULL;
class MQ2MoveToType*     pMoveToType     = NULL;
class MQ2CircleType*     pCircleType     = NULL;
class MQ2MoveUtilsType*  pMoveUtilsType  = NULL;

class MQ2MakeCampType : public MQ2Type
{
public:
    enum MakeCampMembers
    {
        Status = 1,
        Leash = 2,
        AnchorY = 3,
        AnchorX = 4,
        LeashLength = 5,
        CampRadius = 6,
        MinDelay = 7,
        MaxDelay = 8,
        Returning = 9,
        AltAnchorY = 10,
        AltAnchorX = 11,
        CampDist = 12,
        AltCampDist = 13,
        AltRadius = 14,
        Scatter = 15,
        ReturnNoAggro = 16,
        ReturnNotLooting = 17,
        ReturnHaveTarget = 18,
        Bearing = 19,
        ScatDist = 20,
        ScatSize = 21,
    };

    MQ2MakeCampType():MQ2Type("makecamp")
    {
        TypeMember(Status);
        TypeMember(Leash);
        TypeMember(AnchorY);
        TypeMember(AnchorX);
        TypeMember(LeashLength);
        TypeMember(CampRadius);
        TypeMember(MinDelay);
        TypeMember(MaxDelay);
        TypeMember(Returning);
        TypeMember(AltAnchorY);
        TypeMember(AltAnchorX);
        TypeMember(CampDist);
        TypeMember(AltCampDist);
        TypeMember(AltRadius);
        TypeMember(Scatter);
        TypeMember(ReturnNoAggro);
        TypeMember(ReturnNotLooting);
        TypeMember(ReturnHaveTarget);
        TypeMember(Bearing);
        TypeMember(ScatDist);
        TypeMember(ScatSize);
    }

    ~MQ2MakeCampType()
    {
    }

    bool GetMember(MQ2VARPTR VarPtr, char* Member, char* Index, MQ2TYPEVAR &Dest)
    {
        PMQ2TYPEMEMBER pMember = MQ2MakeCampType::FindMember(Member);
        if (!pMember) return false;
        switch((MakeCampMembers)pMember->ID)
        {
        case Status:
            strcpy(DataTypeTemp, "OFF");
            if (bAllPaused)
            {
                strcpy(DataTypeTemp, "PAUSED");
            }
            else if (bMakeCamp)
            {
                strcpy(DataTypeTemp, "ON");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case Leash:
            Dest.DWord = bLeash;
            Dest.Type = pBoolType;
            return true;
        case AnchorY:
            Dest.Float = fAnchorYX[LocY];
            Dest.Type = pFloatType;
            return true;
        case AnchorX:
            Dest.Float = fAnchorYX[LocX];
            Dest.Type = pFloatType;
            return true;
        case LeashLength:
            Dest.Float = fLeashLength;
            Dest.Type = pFloatType;
            return true;
        case CampRadius:
            Dest.Float = fCampRadius;
            Dest.Type = pFloatType;
            return true;
        case MinDelay:
            Dest.DWord = iCampDelay[MIN];
            Dest.Type = pIntType;
            return true;
        case MaxDelay:
            Dest.DWord = iCampDelay[MAX];
            Dest.Type = pIntType;
            return true;
        case Returning:
            Dest.DWord = bMakeCampReturn;
            Dest.Type = pBoolType;
            return true;
        case AltAnchorY:
            Dest.Float = fAltAnchorYX[LocY];
            Dest.Type = pFloatType;
            return true;
        case AltAnchorX:
            Dest.Float = fAltAnchorYX[LocX];
            Dest.Type = pFloatType;
            return true;
        case CampDist:
            Dest.Float = 0.0f;
            if (bMakeCamp)
            {
                PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
                Dest.Float = GetDistance(pChSpawn->Y, pChSpawn->X, fAnchorYX[LocY], fAnchorYX[LocX]);
            }
            Dest.Type = pFloatType;
            return true;
        case AltCampDist:
            Dest.Float = 0.0f;
            if (bAltCamp)
            {
                PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
                Dest.Float = GetDistance(pChSpawn->Y, pChSpawn->X, fAltAnchorYX[LocY], fAltAnchorYX[LocX]);
            }
            Dest.Type = pFloatType;
            return true;
        case AltRadius:
            Dest.Float = fAltCampRadius;
            Dest.Type = pFloatType;
            return true;
        case Scatter:
            Dest.DWord = bScatter;
            Dest.Type = pBoolType;
            return true;
        case ReturnNoAggro:
            Dest.DWord = bReturnNoAggro;
            Dest.Type = pBoolType;
            return true;
        case ReturnNotLooting:
            Dest.DWord = bReturnNotLoot;
            Dest.Type = pBoolType;
            return true;
        case ReturnHaveTarget:
            Dest.DWord = bReturnHaveTarget;
            Dest.Type = pBoolType;
            return true;
        case Bearing:
            Dest.Float = fBearing;
            Dest.Type = pFloatType;
            return true;
        case ScatDist:
            Dest.Float = fScatDist;
            Dest.Type = pFloatType;
            return true;
        case ScatSize:
            Dest.Float = fScatter;
            Dest.Type = pFloatType;
            return true;
        }
        return false;
    }

    bool ToString(MQ2VARPTR VarPtr, char* Destination)
    {
        strcpy(Destination, "OFF");
        if (bAllPaused)
        {
            strcpy(Destination, "PAUSED");
        }
        else if (bMakeCamp)
        {
            strcpy(Destination, "ON");
        }
        return true;
    }

    bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
    {
        return false;
    }
    bool FromString(MQ2VARPTR &VarPtr, char* Source)
    {
        return false;
    }
};

int dataMakeCamp(char* szName, MQ2TYPEVAR &Ret)
{
    Ret.DWord = 1;
    Ret.Type = pMakeCampType;
    return true;
}

class MQ2StickType : public MQ2Type
{
public:
    enum StickMembers
    {
        Status = 1,
        Active = 2,
        Distance = 3,
        MoveBehind = 4,
        MoveBack = 5,
        Loose = 6,
        Paused = 7,
        Behind = 8,
        Stopped = 9,
        Pin = 10,
        StickTarget = 11,
        StickTargetName = 12,
        DistMod = 13,
        DistModPercent = 14,
        Always = 15,
    };

    MQ2StickType():MQ2Type("stick")
    {
        TypeMember(Status);
        TypeMember(Active);
        TypeMember(Distance);
        TypeMember(MoveBehind);
        TypeMember(MoveBack);
        TypeMember(Loose);
        TypeMember(Paused);
        TypeMember(Behind);
        TypeMember(Stopped);
        TypeMember(Pin);
        TypeMember(StickTarget);
        TypeMember(StickTargetName);
        TypeMember(DistMod);
        TypeMember(DistModPercent);
        TypeMember(Always);
    }

    ~MQ2StickType()
    {
    }

    bool GetMember(MQ2VARPTR VarPtr, char* Member, char* Index, MQ2TYPEVAR &Dest)
    {
        PMQ2TYPEMEMBER pMember = MQ2StickType::FindMember(Member);
        if (!pMember) return false;
        switch((StickMembers)pMember->ID)
        {
        case Status:
            strcpy(DataTypeTemp, "OFF");
            if (bAllPaused)
            {
                strcpy(DataTypeTemp, "PAUSED");
            }
            else if (bStickOn)
            {
                strcpy(DataTypeTemp, "ON");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case Active:
            Dest.DWord = bStickOn;
            Dest.Type = pBoolType;
            return true;
        case Distance:
            Dest.Float = fStickDist;
            Dest.Type = pFloatType;
            return true;
        case MoveBehind:
            Dest.DWord = bStickBehind;
            Dest.Type = pBoolType;
            return true;
        case MoveBack:
            Dest.DWord = bMoveBack;
            Dest.Type = pBoolType;
            return true;
        case Loose:
            Dest.DWord = bLooseStick;
            Dest.Type = pBoolType;
            return true;
        case Paused:
            Dest.DWord = bAllPaused;
            Dest.Type = pBoolType;
            return true;
        case Behind:
            Dest.DWord = false;
            if (PSPAWNINFO psTarget = (PSPAWNINFO)(bStickHold ? GetSpawnByID(ulStickID) : pTarget))
            {
                PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
                Dest.DWord = (fabs(GetDistance(pChSpawn, psTarget)) <= fStickDist && fabs(CalcAngularDist(psTarget->Heading, pChSpawn->Heading)) <= fBehindArc) ? true : false;
            }
            Dest.Type = pBoolType;
            return true;
        case Stopped:
            Dest.DWord = false;
            if (PSPAWNINFO psTarget = (PSPAWNINFO)(bStickHold ? GetSpawnByID(ulStickID) : pTarget))
            {
                Dest.DWord = (fabs(GetDistance((PSPAWNINFO)pCharSpawn, psTarget)) <= fStickDist) ? true : false;
            }
            Dest.Type = pBoolType;
            return true;
        case Pin:
            Dest.DWord = bStickPin;
            Dest.Type = pBoolType;
            return true;
        case StickTarget:
            Dest.Int = 0;
            if (PSPAWNINFO psTarget = (PSPAWNINFO)(bStickHold ? GetSpawnByID(ulStickID) : pTarget))
            {
                Dest.Int = psTarget->SpawnID;
            }
            Dest.Type = pIntType;
            return true;
        case StickTargetName:
            strcpy(DataTypeTemp, "NONE");
            if (PSPAWNINFO psTarget = (PSPAWNINFO)(bStickHold ? GetSpawnByID(ulStickID) : pTarget))
            {
                strcpy(DataTypeTemp, psTarget->DisplayedName);
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case DistMod:
            Dest.Float = fStickDistMod;
            Dest.Type = pFloatType;
            return true;
        case DistModPercent:
            Dest.Float = fStickDistModP;
            Dest.Type = pFloatType;
            return true;
        case Always:
            Dest.DWord = bStickNewMob;
            Dest.Type = pBoolType;
            return true;
        }
        return false;
    }

    bool ToString(MQ2VARPTR VarPtr, char* Destination)
    {
        strcpy(Destination, "OFF");
        if (bAllPaused)
        {
            strcpy(Destination, "PAUSED");
        }
        else if (bStickOn)
        {
            strcpy(Destination, "ON");
        }

        return true;
    }

    bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
    {
        return false;
    }
    bool FromString(MQ2VARPTR &VarPtr, char* Source)
    {
        return false;
    }
};

int dataStick(char* szName, MQ2TYPEVAR &Ret)
{
    Ret.DWord = 1;
    Ret.Type = pStickType;
    return true;
}

class MQ2MoveToType : public MQ2Type
{
public:
    enum MoveToMembers
    {
        Moving = 1,
        Stopped = 2,
        CampStopped = 3,
        UseWalk = 4,
        ArrivalDist = 5,
        ArrivalDistY = 6,
        ArrivalDistX = 7,
        Broken = 8,
    };

    MQ2MoveToType():MQ2Type("moveto")
    {
        TypeMember(Moving);
        TypeMember(Stopped);
        TypeMember(CampStopped);
        TypeMember(UseWalk);
        TypeMember(ArrivalDist);
        TypeMember(ArrivalDistY);
        TypeMember(ArrivalDistX);
        TypeMember(Broken);
    }

    ~MQ2MoveToType()
    {
    }

    bool GetMember(MQ2VARPTR VarPtr, char* Member, char* 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:
            Dest.DWord = false;
            if (pLocalPlayer)
            {
                Dest.DWord = (fabs(GetDistance(((PSPAWNINFO)pCharSpawn)->Y, ((PSPAWNINFO)pCharSpawn)->X, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX])) <= fMoveDist) ? true : false;
            }
            Dest.Type = pBoolType;
            return true;
        case CampStopped:
            Dest.DWord = false;
            if (pLocalPlayer)
            {
                Dest.DWord = (fabs(GetDistance(((PSPAWNINFO)pCharSpawn)->Y, ((PSPAWNINFO)pCharSpawn)->X, fReturnLocYX[LocY], fReturnLocYX[LocX])) <= fMoveDist) ? true : false;
            }
            Dest.Type = pBoolType;
            return true;
        case UseWalk:
            Dest.DWord = bWalkMoveTo;
            Dest.Type = pBoolType;
            return true;
        case ArrivalDist:
            Dest.Float = fMoveDist;
            Dest.Type = pFloatType;
            return true;
        case ArrivalDistY:
            Dest.Float = fMoveDistY;
            Dest.Type = pFloatType;
            return true;
        case ArrivalDistX:
            Dest.Float = fMoveDistX;
            Dest.Type = pFloatType;
            return true;
        case Broken:
            Dest.DWord = bBrokenMoveTo;
            Dest.Type = pBoolType;
            return true;
        }
        return false;
    }

    bool ToString(MQ2VARPTR VarPtr, char* Destination)
    {
        if (bMoveToOn)
        {
            strcpy(Destination, "ON");
        }
        else
        {
            strcpy(Destination, "OFF");
        }
        return true;
    }

    bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
    {
        return false;
    }
    bool FromString(MQ2VARPTR &VarPtr, char* Source)
    {
        return false;
    }
};

int dataMoveTo(char* szName, MQ2TYPEVAR &Ret)
{
    Ret.DWord = 1;
    Ret.Type = pMoveToType;
    return true;
}

class MQ2CircleType : public MQ2Type
{
public:
    enum CircleMembers
    {
        Status = 1,
        CircleY = 2,
        CircleX = 3,
        Drunken = 4,
        Rotation = 5,
        Direction = 6,
        Clockwise = 7,
        Backwards = 8,
    };

    MQ2CircleType():MQ2Type("circle")
    {
        TypeMember(Status);
        TypeMember(CircleY);
        TypeMember(CircleX);
        TypeMember(Drunken);
        TypeMember(Rotation);
        TypeMember(Direction);
        TypeMember(Clockwise);
        TypeMember(Backwards);
    }

    ~MQ2CircleType()
    {
    }

    bool GetMember(MQ2VARPTR VarPtr, char* Member, char* Index, MQ2TYPEVAR &Dest)
    {
        PMQ2TYPEMEMBER pMember = MQ2CircleType::FindMember(Member);
        if (!pMember) return false;
        switch((CircleMembers)pMember->ID)
        {
        case Status:
            strcpy(DataTypeTemp, "OFF");
            if (bAllPaused)
            {
                strcpy(DataTypeTemp, "PAUSED");
            }
            else if (bCircling)
            {
                strcpy(DataTypeTemp, "ON");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case CircleY:
            Dest.Float = fCircleYX[LocY];
            Dest.Type = pFloatType;
            return true;
        case CircleX:
            Dest.Float = fCircleYX[LocX];
            Dest.Type = pFloatType;
            return true;
        case Drunken:
            Dest.DWord = bDrunken;
            Dest.Type = pBoolType;
            return true;
        case Rotation:
            strcpy(DataTypeTemp, "CW");
            if (!bClockwise)
            {
                strcpy(DataTypeTemp, "CCW");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case Direction:
            strcpy(DataTypeTemp, "FORWARDS");
            if (bBackwards)
            {
                strcpy(DataTypeTemp, "BACKWARDS");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case Clockwise:
            Dest.DWord = bClockwise;
            Dest.Type = pBoolType;
            return true;
        case Backwards:
            Dest.DWord = bBackwards;
            Dest.Type = pBoolType;
            return true;
        }
        return false;
    }

    bool ToString(MQ2VARPTR VarPtr, char* Destination)
    {
        strcpy(Destination, "OFF");
        if (bAllPaused)
        {
            strcpy(Destination, "PAUSED");
        }
        else if (bCircling)
        {
            strcpy(Destination, "ON");
        }
        return true;
    }

    bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
    {
        return false;
    }
    bool FromString(MQ2VARPTR &VarPtr, char* Source)
    {
        return false;
    }
};

int dataCircling(char* szName, MQ2TYPEVAR &Ret)
{
    Ret.DWord = 1;
    Ret.Type = pCircleType;
    return true;
}

class MQ2MoveUtilsType : public MQ2Type
{
public:
    enum MoveUtilsMembers
    {
        Command = 1,
        Stuck = 2,
        Summoned = 3,
        StuckLogic = 4,
        Verbosity = 5,
        FullVerbosity = 6,
        TotalSilence = 7,
        Aggro = 8,
        PauseMinDelay = 9,
        PauseMaxDelay = 10,
        PulseCheck = 11,
        PulseUnstuck = 12,
        TryToJump = 13,
        DistStuck = 14,
        Version = 15,
        MovePause = 16,
    };

    MQ2MoveUtilsType():MQ2Type("MoveUtils")
    {
        TypeMember(Command);
        TypeMember(Stuck);
        TypeMember(Summoned);
        TypeMember(StuckLogic);
        TypeMember(Verbosity);
        TypeMember(FullVerbosity);
        TypeMember(TotalSilence);
        TypeMember(Aggro);
        TypeMember(PauseMinDelay);
        TypeMember(PauseMaxDelay);
        TypeMember(PulseCheck);
        TypeMember(PulseUnstuck);
        TypeMember(TryToJump);
        TypeMember(DistStuck);
        TypeMember(Version);
        TypeMember(MovePause);
    }

    ~MQ2MoveUtilsType()
    {
    }

    bool GetMember(MQ2VARPTR VarPtr, char* Member, char* Index, MQ2TYPEVAR &Dest)
    {
        PMQ2TYPEMEMBER pMember = MQ2MoveUtilsType::FindMember(Member);
        if (!pMember) return false;
        switch((MoveUtilsMembers)pMember->ID)
        {
        case Command:
            strcpy(DataTypeTemp, "NONE");
            if (bStickOn)
            {
                strcpy(DataTypeTemp, "STICK");
            }
            else if (bCircling)
            {
                strcpy(DataTypeTemp, "CIRCLE");
            }
            else if (bMoveToOn)
            {
                strcpy(DataTypeTemp, "MOVETO");
            }
            else if (bMakeCamp)
            {
                strcpy(DataTypeTemp, "MAKECAMP");
            }
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case Stuck:
            Dest.DWord = false;
            if (uiStuck)
            {
                Dest.DWord = true;
            }
            Dest.Type = pBoolType;
            return true;
        case Summoned:
            Dest.DWord = bBrokeOnSummon;
            Dest.Type = pBoolType;
            return true;
        case StuckLogic:
            Dest.DWord = bCheckStuck;
            Dest.Type = pBoolType;
            return true;
        case Verbosity:
            Dest.DWord = bVerbosity;
            Dest.Type = pBoolType;
            return true;
        case FullVerbosity:
            Dest.DWord = bFullVerbosity;
            Dest.Type = pBoolType;
            return true;
        case TotalSilence:
            Dest.DWord = bTotalSilence;
            Dest.Type = pBoolType;
            return true;
        case Aggro:
            Dest.DWord = bImAggro;
            Dest.Type = pBoolType;
            return true;
        case PauseMinDelay:
            Dest.Int = iPauseDelay[MIN];
            Dest.Type = pIntType;
            return true;
        case PauseMaxDelay:
            Dest.Int = iPauseDelay[MAX];
            Dest.Type = pIntType;
            return true;
        case PulseCheck:
            Dest.Int = uiPulseCheck;
            Dest.Type = pIntType;
            return true;
        case PulseUnstuck:
            Dest.Int = uiPulseUnstuck;
            Dest.Type = pIntType;
            return true;
        case TryToJump:
            Dest.DWord = bTryToJump;
            Dest.Type = pBoolType;
            return true;
        case DistStuck:
            Dest.Float = fDistStuck;
            Dest.Type = pFloatType;
            return true;
        case Version:
            sprintf(DataTypeTemp, "%1.4f", MODULE_VERSION);
            Dest.Ptr = DataTypeTemp;
            Dest.Type = pStringType;
            return true;
        case MovePause:
            Dest.DWord = bPauseOnMove;
            Dest.Type = pBoolType;
            return true;
        }
        return false;
    }

    bool ToString(MQ2VARPTR VarPtr, char* Destination)
    {
        strcpy(Destination, "NONE");
        if (bStickOn)
        {
            strcpy(Destination, "STICK");
        }
        else if (bCircling)
        {
            strcpy(Destination, "CIRCLE");
        }
        else if (bMoveToOn)
        {
            strcpy(Destination, "MOVETO");
        }
        else if (bMakeCamp)
        {
            strcpy(Destination, "MAKECAMP");
        }
        return true;
    }

    bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
    {
        return false;
    }
    bool FromString(MQ2VARPTR &VarPtr, char* Source)
    {
        return false;
    }
};

int dataMoveUtils(char* szName, MQ2TYPEVAR &Ret)
{
    Ret.DWord = 1;
    Ret.Type = pMoveUtilsType;
    return true;
}

// End Custom Top-Level Objects
///////////////////////////////////////
// Begin Custom UI Window
class CMoveUtilsWnd;
CMoveUtilsWnd* OurWnd = NULL;

class CMoveUtilsWnd : public CCustomWnd
{
public:
    CTextEntryWnd *InputBox;
    CStmlWnd *StmlOut;
    CXWnd *OutWnd;
    struct _CSIDLWND *OutStruct;
private:
    vector<string> CommandHistory;
    int curCommand;
public:
    CMoveUtilsWnd(CXStr *Template):CCustomWnd(Template)
    {
        SetWndNotification(CMoveUtilsWnd);
        StmlOut = (CStmlWnd *)GetChildItem("CWChatOutput");
        OutWnd = (CXWnd*)StmlOut;
        OutWnd->Clickable = 1;
        OutStruct = (_CSIDLWND*)GetChildItem("CWChatOutput");
        InputBox=(CTextEntryWnd*)GetChildItem("CWChatInput");
        InputBox->WindowStyle|=0x800C0;
        InputBox->UnknownCW|=0xFFFFFFFF;
        InputBox->SetMaxChars(512);
        BitOff(WindowStyle, CWS_CLOSE);
        CloseOnESC = 0;
        *(DWORD*)&(((PCHAR)StmlOut)[0x1f0]) = 400;
    }

    ~CMoveUtilsWnd()
    {
    };

    int WndNotification(CXWnd *pWnd, unsigned int Message, void *data)
    {
        if (pWnd == (CXWnd*)InputBox)
        {
            if (Message == XWM_HITENTER)
            {
                char szBuffer[513] = {0};
                GetCXStr((PCXSTR)InputBox->InputText, szBuffer, 512);
                if (szBuffer[0])
                {
                    if (CommandHistory.size() == 0 || CommandHistory.front().compare(szBuffer) != 0)
                    {
                        if (CommandHistory.size() > 15)
                        {
                            CommandHistory.pop_back();
                        }
                        CommandHistory.insert(CommandHistory.begin(), string(szBuffer));
                        curCommand = -1;
                    }
                    if (szBuffer[0] == '/')
                    {
                        DoCommand((PSPAWNINFO)pLocalPlayer, szBuffer);
                    }
                    SetCXStr(&InputBox->InputText,"");
                }
                ((CXWnd*)InputBox)->ClrFocus();
            }
            else if (Message == 0x16 && data)
            {
                int *pInt = (int *)data;
                int keyPress = pInt[1];
                if (keyPress == 200) // KeyUp: 0xC8
                {
                    if (CommandHistory.size() > 0)
                    {
                        curCommand++;
                        if (curCommand < ((int)CommandHistory.size()) && curCommand >= 0)
                        {
                            string s = (string)CommandHistory.at(curCommand);
                            SetCXStr(&InputBox->InputText, (char *)s.c_str());
                        }
                        else
                        {
                            curCommand = ((int)CommandHistory.size())-1;
                        }
                    }
                }
                else if (keyPress == 208) // KeyDown: 0xD0
                {
                    if (CommandHistory.size() > 0)
                    {
                        curCommand--;
                        if (curCommand >= 0 && CommandHistory.size() > 0)
                        {
                            string s = (string)CommandHistory.at(curCommand);
                            SetCXStr(&InputBox->InputText, (char *)s.c_str());
                        }
                        else if (curCommand < 0)
                        {
                            curCommand = -1;
                            SetCXStr(&InputBox->InputText, "");
                        }
                    }
                }
            }
        }
        else if (pWnd == NULL && Message == XWM_CLOSE)
        {
            Show = 1;
            return 0;
        }
        return CSidlScreenWnd::WndNotification(pWnd,Message,data);
    };

    void SetFontSize(unsigned int size)
    {
        struct FONTDATA
        {
            unsigned long NumFonts;
            char** Fonts; 
        };
        FONTDATA* Fonts;            // font array structure
        CXStr* str;                 // contents of stml window
        unsigned long* SelFont;     // selected font
        Fonts = (FONTDATA*)&(((char*)pWndMgr)[0xF4]);

        if (size < 0 || size >= (int) Fonts->NumFonts)
        {
            return;
        }
        if (Fonts->Fonts == NULL || OurWnd == NULL)
        {
            return;
        }

        SelFont = (DWORD*)Fonts->Fonts[size];
        ((CStmlWnd*)OurWnd->OutWnd)->GetSTMLText(str);
        ((CXWnd*)OurWnd->OutWnd)->SetFont(SelFont);
        ((CStmlWnd*)OurWnd->OutWnd)->SetSTMLText(*str, 1, 0);
        ((CStmlWnd*)OurWnd->OutWnd)->ForceParseNow();
        DebugTry(((CXWnd*)OurWnd->OutWnd)->SetVScrollPos(OurWnd->OutWnd->VScrollMax));
        OurWnd->FontSize = size;
    };

    unsigned long FontSize;
};

void SaveOurWnd()
{
    PCSIDLWND UseWnd = (PCSIDLWND)OurWnd;
    if (!UseWnd) return;

    char szTemp[20] = {0};
    int iOurWndTop                = UseWnd->Location.top;
    int iOurWndBot                = UseWnd->Location.bottom;
    int iOurWndLeft               = UseWnd->Location.left;
    int iOurWndRight              = UseWnd->Location.right;
    int iOurWndFades              = UseWnd->Fades;
    int iOurWndAlpha              = UseWnd->Alpha;
    int iOurWndFadeToAlpha        = UseWnd->FadeToAlpha;
    int iOurWndFadeDuration       = UseWnd->FadeDuration;
    int iOurWndLocked             = UseWnd->Locked;
    int iOurWndTimeMouseOver      = UseWnd->TimeMouseOver;
    int iOurWndBGType             = UseWnd->BGType;
    int iOurWndBGColorR           = UseWnd->BGColor.R;
    int iOurWndBGColorG           = UseWnd->BGColor.G;
    int iOurWndBGColorB           = UseWnd->BGColor.B;
    unsigned int uiOurWndFontSize = OurWnd->FontSize;

    WritePrivateProfileString("Window", "ChatTop",      itoa(iOurWndTop,            szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "ChatBottom",   itoa(iOurWndBot,            szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "ChatLeft",     itoa(iOurWndLeft,           szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "ChatRight",    itoa(iOurWndRight,          szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "Fades",        itoa(iOurWndFades,          szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "Alpha",        itoa(iOurWndAlpha,          szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "FadeToAlpha",  itoa(iOurWndFadeToAlpha,    szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "Duration",     itoa(iOurWndFadeDuration,   szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "Locked",       itoa(iOurWndLocked,         szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "Delay",        itoa(iOurWndTimeMouseOver,  szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "BGType",       itoa(iOurWndBGType,         szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "BGTint.red",   itoa(iOurWndBGColorR,       szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "BGTint.green", itoa(iOurWndBGColorG,       szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "BGTint.blue",  itoa(iOurWndBGColorB,       szTemp, 10), INIFileName);
    WritePrivateProfileString("Window", "FontSize",     itoa((int)uiOurWndFontSize, szTemp, 10), INIFileName);
    GetCXStr(UseWnd->WindowText, szTemp);
    WritePrivateProfileString("Window", "WindowTitle", szTemp, INIFileName);
    if (bSaveByChar)
    {
        char szCharName[MAX_STRING] = {0};
        sprintf(szCharName, "%s.%s", EQADDR_SERVERNAME, ((PCHARINFO)pCharData)->Name);

        WritePrivateProfileString(szCharName, "ChatTop",      itoa(iOurWndTop,            szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "ChatBottom",   itoa(iOurWndBot,            szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "ChatLeft",     itoa(iOurWndLeft,           szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "ChatRight",    itoa(iOurWndRight,          szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "Fades",        itoa(iOurWndFades,          szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "Alpha",        itoa(iOurWndAlpha,          szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "FadeToAlpha",  itoa(iOurWndFadeToAlpha,    szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "Duration",     itoa(iOurWndFadeDuration,   szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "Locked",       itoa(iOurWndLocked,         szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "Delay",        itoa(iOurWndTimeMouseOver,  szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "BGType",       itoa(iOurWndBGType,         szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "BGTint.red",   itoa(iOurWndBGColorR,       szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "BGTint.green", itoa(iOurWndBGColorG,       szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "BGTint.blue",  itoa(iOurWndBGColorB,       szTemp, 10), INIFileName);
        WritePrivateProfileString(szCharName, "FontSize",     itoa((int)uiOurWndFontSize, szTemp, 10), INIFileName);
        GetCXStr(UseWnd->WindowText, szTemp);
        WritePrivateProfileString("Window", "WindowTitle", szTemp, INIFileName);
    }
}
void CreateOurWnd()
{
    if (!bUseWindow) return;

    if (OurWnd == NULL && GetGameState() == GAMESTATE_INGAME)
    {
        char szWindowText[MAX_STRING] = {0};
        int iOurWndTop                = GetPrivateProfileInt("Window", "ChatTop",      10,   INIFileName);
        int iOurWndBot                = GetPrivateProfileInt("Window", "ChatBottom",   210,  INIFileName);
        int iOurWndLeft               = GetPrivateProfileInt("Window", "ChatLeft",     10,   INIFileName);
        int iOurWndRight              = GetPrivateProfileInt("Window", "ChatRight",    410,  INIFileName);
        int iOurWndFades              = GetPrivateProfileInt("Window", "Fades",        0,    INIFileName);
        int iOurWndAlpha              = GetPrivateProfileInt("Window", "Alpha",        255,  INIFileName);
        int iOurWndFadeToAlpha        = GetPrivateProfileInt("Window", "FadeToAlpha",  255,  INIFileName);
        int iOurWndFadeDuration       = GetPrivateProfileInt("Window", "Duration",     500,  INIFileName);
        int iOurWndLocked             = GetPrivateProfileInt("Window", "Locked",       0,    INIFileName);
        int iOurWndTimeMouseOver      = GetPrivateProfileInt("Window", "Delay",        2000, INIFileName);
        int iOurWndBGType             = GetPrivateProfileInt("Window", "BGType",       1,    INIFileName);
        int iOurWndBGColorR           = GetPrivateProfileInt("Window", "BGTint.red",   255,  INIFileName);
        int iOurWndBGColorG           = GetPrivateProfileInt("Window", "BGTint.green", 255,  INIFileName);
        int iOurWndBGColorB           = GetPrivateProfileInt("Window", "BGTint.blue",  255,  INIFileName);
        unsigned int uiOurWndFontSize = GetPrivateProfileInt("Window", "FontSize",     2,    INIFileName);
        GetPrivateProfileString("Window", "WindowTitle", "MoveUtils", szWindowText, MAX_STRING, INIFileName);
        if (bSaveByChar)
        {
            char szCharName[MAX_STRING] = {0};
            sprintf(szCharName, "%s.%s", EQADDR_SERVERNAME, ((PCHARINFO)pCharData)->Name);

            iOurWndTop           = GetPrivateProfileInt(szCharName, "ChatTop",      iOurWndTop,           INIFileName);
            iOurWndBot           = GetPrivateProfileInt(szCharName, "ChatBottom",   iOurWndBot,           INIFileName);
            iOurWndLeft          = GetPrivateProfileInt(szCharName, "ChatLeft",     iOurWndLeft,          INIFileName);
            iOurWndRight         = GetPrivateProfileInt(szCharName, "ChatRight",    iOurWndRight,         INIFileName);
            iOurWndFades         = GetPrivateProfileInt(szCharName, "Fades",        iOurWndFades,         INIFileName);
            iOurWndAlpha         = GetPrivateProfileInt(szCharName, "Alpha",        iOurWndAlpha,         INIFileName);
            iOurWndFadeToAlpha   = GetPrivateProfileInt(szCharName, "Fades",        iOurWndFadeToAlpha,   INIFileName);
            iOurWndFadeDuration  = GetPrivateProfileInt(szCharName, "Duration",     iOurWndFadeDuration,  INIFileName);
            iOurWndLocked        = GetPrivateProfileInt(szCharName, "Locked",       iOurWndLocked,        INIFileName);
            iOurWndTimeMouseOver = GetPrivateProfileInt(szCharName, "Delay",        iOurWndTimeMouseOver, INIFileName);
            iOurWndBGType        = GetPrivateProfileInt(szCharName, "BGType",       iOurWndBGType,        INIFileName);
            iOurWndBGColorR      = GetPrivateProfileInt(szCharName, "BGTint.red",   iOurWndBGColorR,      INIFileName);
            iOurWndBGColorG      = GetPrivateProfileInt(szCharName, "BGTint.green", iOurWndBGColorG,      INIFileName);
            iOurWndBGColorB      = GetPrivateProfileInt(szCharName, "BGTint.blue",  iOurWndBGColorB,      INIFileName);
            uiOurWndFontSize     = (unsigned int)GetPrivateProfileInt(szCharName, "FontSize", uiOurWndFontSize, INIFileName);
            GetPrivateProfileString(szCharName, "WindowTitle", "MoveUtils", szWindowText, MAX_STRING, INIFileName);
        }

        class CXStr ChatWnd("ChatWindow");
        OurWnd= new CMoveUtilsWnd(&ChatWnd);
        OurWnd->Location.top    = iOurWndTop;
        OurWnd->Location.bottom = iOurWndBot;
        OurWnd->Location.left   = iOurWndLeft;
        OurWnd->Location.right  = iOurWndRight;
        OurWnd->Fades           = iOurWndFades;
        OurWnd->Alpha           = iOurWndAlpha;
        OurWnd->FadeToAlpha     = iOurWndFadeToAlpha;
        OurWnd->FadeDuration    = iOurWndFadeDuration;
        OurWnd->Locked          = iOurWndLocked;
        OurWnd->TimeMouseOver   = iOurWndTimeMouseOver;
        OurWnd->BGType          = iOurWndBGType;
        OurWnd->BGColor.R       = iOurWndBGColorR;
        OurWnd->BGColor.G       = iOurWndBGColorG;
        OurWnd->BGColor.B       = iOurWndBGColorB;
        OurWnd->SetFontSize(uiOurWndFontSize);
        SetCXStr(&OurWnd->WindowText, szWindowText);
        DebugTry(((CXWnd*)OurWnd)->Show(1, 1));
        DebugTry(BitOff(OurWnd->OutStruct->WindowStyle, CWS_CLOSE));
    }
}

void KillOurWnd(bool bSave)
{
    if (OurWnd)
    {
        if (bSave) SaveOurWnd();
        delete OurWnd;
        OurWnd = NULL;
    }
}

void MinOurWnd()
{
    if (OurWnd)
    {
        ((CXWnd*)OurWnd)->OnMinimizeBox();
    }
}

void ClearOurWnd()
{
    if (OurWnd)
    {
        ((CChatWindow*)OurWnd)->Clear();
    }
}

void WriteToOurWnd(char* szText)
{
    if (OurWnd)
    {
        DebugTry(((CXWnd*)OurWnd)->Show(1, 1));
        char szProcessed[MAX_STRING] = {0};
        StripMQChat(szText, szProcessed);
        CheckChatForEvent(szProcessed);
        MQToSTML(szText, szProcessed, MAX_STRING);
        strcat(szProcessed, "<br>");
        CXStr NewText(szProcessed);
        CXSize Whatever;
        (OurWnd->StmlOut)->AppendSTML(&Whatever, NewText);
        (OurWnd->OutWnd)->SetVScrollPos(OurWnd->OutStruct->VScrollMax);
    }
}

// End Custom UI Window
///////////////////////////////////////
// Generic math & utility functions
inline float CalcAngularDist(float fH1, float fH2)
{
    if(fabs(fH1 - fH2) > HEADING_HALF)
    {
        (fH1 < fH2 ? fH1 : fH2) += HEADING_MAX;
    }
    return (fabs(fH1 - fH2) > HEADING_HALF) ? (fH2 - fH1) : (fH1 - fH2);
}

inline float GetRandomNumber(float fN)
{
    // used by drunken circle
    return (float)(fN * rand() / (RAND_MAX + 1.0f));
}

inline void SetRandArc(const int iArcType)
{
    float fRandArcTemp = 0.0f;
    fRandArcFlag *= -1.0f;

    if (iArcType == PIN_ARC_MIN)
    {
        if (fRandArcFlag > 0.0f)
        {
            fRandArcMax = (float)(rand() % 32 + PIN_ARC_MIN);
            fRandArcMin = (float)PIN_ARC_MIN;
        }
        else
        {
            fRandArcMin = (float)(rand() % 14 + PIN_ARC_MIN);
            fRandArcMax = (float)PIN_ARC_MAX;
        }
        return;
    }

    if (iArcType == BEHIND_ARC)
    {
        fRandArcTemp = (float)(rand() % 40 + 5);
    }
    else if (iArcType == NOT_FRONT_ARC)
    {
        fRandArcTemp = (float)(rand() % 120 + 5);
    }

    if (fRandArcFlag > 0.0f)
    {
        fRandArcMin = fRandArcTemp;
        fRandArcMax = (float)iArcType;
    }
    else
    {
        fRandArcMax = fRandArcTemp;
        fRandArcMin = (float)iArcType;
    }
    sprintf(szMsg, "\ay%s\aw:: Arcs Randomized! Max: %.2f  Min: %.2f", MODULE_NAME, fRandArcMax, fRandArcMin);
    WriteLine(szMsg, VERB_FULL);
}

int diff_in_ms(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));
}

inline bool IsABard()
{
    PCHARINFO2 pChar = GetCharInfo2();
    if (pChar && pChar->Class == Bard)
    {
        return true;
    }
    return false;
}

inline bool InCombat()
{

    if (((PCPLAYERWND)pPlayerWnd)->CombatState == 0 && ((CXWnd*)pPlayerWnd)->GetChildItem("PW_CombatStateAnim"))
    {
        return true;
    }
    return false;
}

inline bool CheckMoveToAggro()
{
    if (!bMakeCamp && bMoveToOn && InCombat())
    {
        bBrokenMoveTo = true;
        EndPreviousCmd(true);
        sprintf(szMsg, "\ay%s\aw:: Aggro gained during /moveto, Halting command...", MODULE_NAME);
        WriteLine(szMsg, VERB_ALL);
        return true;
    }
    return false;
}

void __stdcall CheckAggro_Event(unsigned int ID, void *pData, PBLECHVALUE pValues)
{
    // do not process if makecamp is on, as that would interfere with camp returns
    if (!ValidIngame() || !bMoveToOn || !bBreakOnHit || bMakeCamp) return;
    bBrokenMoveTo = true;
    EndPreviousCmd(true);
    sprintf(szMsg, "\ay%s\aw:: Aggro gained during /moveto, Halting command...", MODULE_NAME);
    WriteLine(szMsg, VERB_ALL);
}

void __stdcall CheckGates_Event(unsigned int ID, void *pData, PBLECHVALUE pValues)
{
    if (!ValidIngame() || !bStickOn || !bBreakOnGate) return;
    PSPAWNINFO psTarget = (PSPAWNINFO)((bStickHold && ulStickID) ? GetSpawnByID(ulStickID) : pTarget);
    if (psTarget && pValues)
    {
        if (!strcmp(pValues->Value, psTarget->DisplayedName))
        {
            EndPreviousCmd(true);
            sprintf(szMsg, "\ay%s\aw:: Mob gating ended previous command.", MODULE_NAME);
            WriteLine(szMsg, VERB_FULL);
        }
    }
}

void SetupEvents(bool bAddEvent, bool bForceRemove)
{
    if (bAddEvent)
    {
        if (bBreakOnHit)
        {
            if (!Event_AggroNorm)    Event_AggroNorm    = pMQ2Blech->AddEvent("#Name# #Hurts# YOU for #Damage#",          CheckAggro_Event);
            if (!Event_MissNorm)     Event_MissNorm     = pMQ2Blech->AddEvent("#Name# tries to #Hit# YOU, but #YouRock#", CheckAggro_Event);
            if (!Event_AggroAbbrev)  Event_AggroAbbrev  = pMQ2Blech->AddEvent("#Name# #Hurt#s for #Damage#",              CheckAggro_Event);
            if (!Event_MissAbbrev)   Event_MissAbbrev   = pMQ2Blech->AddEvent("#Name# missed",                            CheckAggro_Event);
            if (!Event_MissNumOnly)  Event_MissNumOnly  = pMQ2Blech->AddEvent("miss",                                     CheckAggro_Event);
            // not going to parse for just numbers as that would require parsing every line
            // and evaluating if its only numerical - this would be a bit over the top
        }
        if (bBreakOnGate)
        {
            if (!Event_Gates)        Event_Gates        = pMQ2Blech->AddEvent("#Name# Gates.",                            CheckGates_Event);
        }
    }
    else
    {
        if (bForceRemove || !bBreakOnHit)
        {
            if (Event_AggroNorm)    pMQ2Blech->RemoveEvent(Event_AggroNorm);
            Event_AggroNorm = NULL;
            if (Event_MissNorm)     pMQ2Blech->RemoveEvent(Event_MissNorm);
            Event_MissNorm = NULL;
            if (Event_AggroAbbrev)  pMQ2Blech->RemoveEvent(Event_AggroAbbrev);
            Event_AggroAbbrev = NULL;
            if (Event_MissAbbrev)   pMQ2Blech->RemoveEvent(Event_MissAbbrev);
            Event_MissAbbrev = NULL;
            if (Event_MissNumOnly)  pMQ2Blech->RemoveEvent(Event_MissNumOnly);
            Event_MissNumOnly = NULL;
        }
        if (bForceRemove || !bBreakOnGate)
        {
            if (Event_Gates) pMQ2Blech->RemoveEvent(Event_Gates);
            Event_Gates = NULL;
        }
    }
}

// we process this 10+ times
inline float SaneHeading(float fHeading)
{
    if (fHeading >= HEADING_MAX) fHeading -= HEADING_MAX;
    if (fHeading < 0.0f) fHeading += HEADING_MAX;
    return fHeading;
}

// getting sick of retyping this as bool evaluation every time
inline int ValidDelay(int iDelay, int iCompare)
{
    int iUseDelay = 0;
    // compare valid pause ranges, call with (min, 0) to return valid min
    // and (max, min) to return valid max
    if (iCompare != 0)
    {
        iDelay <= iCompare + 125 ? iUseDelay = iCompare + 125 : iUseDelay = iDelay;
    }
    else
    {
        iDelay < 125 ? iUseDelay = 125 : iUseDelay = iDelay;
    }
    return iUseDelay;
}

// same for this
inline void ValidCampRad()
{
    fCampRadius = fCampRadius < 10.0f ? 10.0f : fCampRadius; // enforce min value of 10
    fLeashLength = fLeashLength >= fCampRadius ? fLeashLength : fCampRadius + 10.0f; // enforce leash greater than/equal to camp radius
}

// want to be able to call this where needed
inline bool CanLooseMove(float fLooseHead, float fY, float fX)
{
    if (!ValidIngame()) return false;
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;

    float fAdjHead = fabs(pChSpawn->Heading - fLooseHead);
    if (fAdjHead > fAllowMove)
    {
        // if we are more than an 1/8th turn
        return false;
    }
    if ((fAdjHead / 2.0f) > fabs(GetDistance(pChSpawn->Y, pChSpawn->X, fY, fX)))
    {
        // if half our needed adjustment is > distance between us and destination
        return false;
    }

    // else safe to move
    return true;
}

// CampReturn copyright: deadchicken
// not to be released outside of VIP without permission
inline void CampReturn(float fY, float fX, float fUseRadius, float* fUseY, float* fUseX)
{
    float fRandHead = rand() / RAND_MAX * CIRCLE_MAX;
    float fRandDist = rand() / RAND_MAX * fUseRadius;

    SpewDebug(DBG_OTHER, "MoveUtils::CampReturn() fRandHead = %.2f, fRandDist = %.2f", fRandHead, fRandDist);

    *fUseY = fY + (fRandDist * cos(fRandHead));
    *fUseX = fX + (fRandDist * sin(fRandHead));
}

// PolarSpot copyright: deadchicken
// not to be released outside of VIP without permission
// MOB:  PolarSpot(targetX, targetY, targetHeading, desiredHeading, distanceAway, scatter);
// CAMP: PolarSpot(anchorX, anchorY, heading doesnt matter, bearing = which dir from center, dist = how far from center, scatter=size of scatter);
inline void PolarSpot(float fY, float fX, float fPHeading, float fPBearing, float fPDist, float fPScatter, float* fUseY, float* fUseX)
{
    // if camp returning
    if (fPScatter != 0.0f)
    {
        float fRandHead = rand() / RAND_MAX * HEADING_MAX;
        fRandHead = SaneHeading(fRandHead);
        float fRandDist = rand() / RAND_MAX * fPScatter;

        *fUseY = fY + (fRandDist * cos(fRandHead));
        *fUseX = fX + (fRandDist * sin(fRandHead));
        // 0.0175f converts degrees to radians which sinf/cosf expect
        /*fUseX = fOurGotoX + fRandDist * sinf(fRandHead*0.0175f));
        fUseY = fOurGotoY + fRandDist * cosf(fRandHead*0.0175f));*/
        return;
    }
    // else snaproll

    //float fRelHead = (fPHeading / fPBearing) * -(float)PI;
    //fSnapYX[LocY] = fY  - (float)cos(fRelHead)* fPDist;
    //fSnapYX[LocX] = fX  + (float)sin(fRelHead)* fPDist;
    float fRelHead = fPHeading - fPBearing;
    fRelHead = SaneHeading(fRelHead);
    fRelHead = ((fRelHead / HEADING_MAX) * CIRCLE_MAX) * 0.0175f;
    *fUseY = fY + (fPDist * cos(fRelHead));
    *fUseX = fX + (fPDist * sin(fRelHead));
}

// MovingAvg copyright: deadchicken
// not to be released outside of VIP without permission
// returns a moving average of size iEntries by adding fNew to the ring and computing
// fNew = New value to add to the ring
// iEntries = number of entries in ring, used for divisior and to re-init ring on change/init.
// Returns the moving average based on said values
// Notes: MaxRing size is 32 by #define up top and not checked, should be
float MovingAvg(float fNew, int iEntries)
{
    static float fRing[MAXRINGSIZE];
    static int iOldStuck = 0;
    static int iRinger = 0;
    static int iRingFull = 0;
    int i = 0;
    float fAvg = 0;

    // Bounds checking
    if (iEntries > MAXRINGSIZE || iEntries < 2 ) return fNew;
    // Do we have a new ring size?
    if (iOldStuck != iEntries)
    {
        // Do some shit to make us right
        SpewDebug(DBG_OTHER, "MoveUtils::MovingAvg() Entry # changed, filling ring with %3.2f!  %d != %d", fNew, iOldStuck, iEntries);
        // Fill the array with this latest value
        // maybe this should be our default preload value of 2.0f?
        for(i = 0; i < iEntries; i++) fRing[i] = fNew;
        // update iOldStuck and reset counter to 0
        iRinger = 0;
        iOldStuck = iEntries;
        return fNew;
    }
    else
    {
        // Plain old ring buffer
        fRing[iRinger] = fNew;
        SpewDebug(DBG_OTHER, "MoveUtils::MovingAvg() Added %3.2f to fRing[%d]", fNew, iRinger);
        // Increment iRinger and wrap if needed, if we wrap then it's full
        iRinger++;
        if (iRinger >= iEntries)
        {
            iRinger = 0;
            iRingFull = 1;
        }
        // Get the sum of the ring
        //for( i=0; i<(iRingFull?iEntries:iRinger); i++) {  <-- this was a bad idea
        for(i = 0; i < iEntries; i++)
        {
            SpewDebug(DBG_OTHER, "MoveUtils::MovingAvg() i=%d and fRing[%d]=%3.2f", i, i, fRing[i]);
            fAvg += fRing[i];
        }
    }
    return (fAvg / (float)iEntries);
}

inline bool ValidIngame(bool bCheckDead)
{
    // CTD prevention function
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
    if (GetGameState() != GAMESTATE_INGAME || !pLocalPlayer || !pChSpawn->SpawnID || (bCheckDead && pChSpawn->RespawnTimer > 0))
    {
        return false;
    }
    return true;
}
// End math & utility functions
///////////////////////////////////////
// Begin Player adjustment functions

// Our version of gFaceAngle, used by loose heading
// set fAdjustHead and this will be called onpulse
inline void AdjustHeading(float fNewHead)
{
    if (!ValidIngame()) return;
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
    gFaceAngle = 10000.0f; // reset use of /face command

    if (bTrueHead)
    {
        if (fabs(pChSpawn->Heading - fNewHead) < 14.0f)
        {
            pKeypressHandler->CommandState[iTurnLeft] = 0;
            *pulTurnLeft = 0;
            pKeypressHandler->CommandState[iTurnRight] = 0;
            *pulTurnRight = 0;
            pChSpawn->Heading = fNewHead;
            pChSpawn->SpeedHeading = 0.0f;
            fAdjustHead = 10000.0f;
        }
        else
        {
            float fCompHead = pChSpawn->Heading + HEADING_HALF;

            if (fNewHead < pChSpawn->Heading) fNewHead += HEADING_MAX;
            if (fNewHead < fCompHead)
            {
                pKeypressHandler->CommandState[iTurnRight] = 0;
                *pulTurnRight = 0;
                pKeypressHandler->CommandState[iTurnLeft] = 1;
                *pulTurnLeft = 1;
            }
            else
            {
                pKeypressHandler->CommandState[iTurnLeft] = 0;
                *pulTurnLeft = 0;
                pKeypressHandler->CommandState[iTurnRight] = 1;
                *pulTurnRight = 1;
            }
        }
    }
    else
    {
        if (fabs(pChSpawn->Heading - fNewHead) < fTurnRate)
        {
            // if we are within one turn away, set heading to desired heading
            // and reset fAdjustHead so this function is not called again onpulse
            pChSpawn->Heading = fNewHead;
            pChSpawn->SpeedHeading = 0.0f;
            fAdjustHead = 10000.0f;
        }
        else
        {
            float fCompHead = pChSpawn->Heading + HEADING_HALF;

            if (fNewHead < pChSpawn->Heading) fNewHead += HEADING_MAX;
            if (fNewHead < fCompHead)
            {
                pChSpawn->Heading = SaneHeading(pChSpawn->Heading + fTurnRate);
                pChSpawn->SpeedHeading = 12.0f;
            }
            else
            {
                pChSpawn->Heading = SaneHeading(pChSpawn->Heading - fTurnRate);
                pChSpawn->SpeedHeading = -12.0f;
            }
        }
    }
}

void StandIfNeeded()
{
    if (!ValidIngame()) return; // ExecuteCmd in any other state = CTD
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
    SpewDebug(DBG_OTHER, "MoveUtils::StandIfNeeded() pCharSpawn->StandState = %d", pChSpawn->StandState);

    switch (pChSpawn->StandState)
    {
    case STANDSTATE_SIT:
        EzCommand("/stand"); // fix for preventing sit/stand bug
        break;
    case STANDSTATE_FEIGN:
        if (bFeignSupport)
        {
            sprintf(szMsg, "\ay%s\aw:: Not standing as you are currently Feign Death", MODULE_NAME);
            WriteLine(szMsg, VERB_FULL);
        }
        else
        {
            EzCommand("/stand");
        }
        break;
    case STANDSTATE_DUCK:
        MQ2Globals::ExecuteCmd(iDuckKey, 1, 0);
        MQ2Globals::ExecuteCmd(iDuckKey, 0, 0);
        break;
    case STANDSTATE_STAND:
    case STANDSTATE_DEAD:
    case STANDSTATE_BIND:
    case STANDSTATE_CASTING:
        break;
    default:
        SpewDebug(DBG_ALL, "MoveUtils::StandIfNeeded() no StandState matches for %d", pChSpawn->StandState);
        break;
    }
}

void AdjustWalking(bool bTurnWalkOn)
{
    if (!ValidIngame()) return; // ExecuteCmd in any other state = CTD
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;

    bool bWalking = (*EQADDR_RUNWALKSTATE) ? false : true;
    if (pChSpawn->SpeedMultiplier < 0 || pChSpawn->RunSpeed < 0)
    {
        //if negative speed, we are snared, and do not want walk on
        bTurnWalkOn = false;
    }
    if (bTurnWalkOn != bWalking)
    {
        SpewDebug(DBG_OTHER, "MoveUtils::AdjustWalking() Toggled Run/Walk key");
        MQ2Globals::ExecuteCmd(iRunWalk, 1, 0);
        MQ2Globals::ExecuteCmd(iRunWalk, 0, 0);
    }
}

// DoMovement(GO_FORWARD) will imply DoMovement(GO_FORWARD, true, false)
void DoMovement(unsigned char ucDirection, bool bTurnOn, bool bUseWalking)
{
    if (!ValidIngame()) return;

    if (bTurnOn)
    {
        switch(ucDirection)
        {
        case GO_FORWARD:
            bCmdMovedFwd = true;
            if (bUseWalking) AdjustWalking(bUseWalking); // only applies to moveforward
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iBackward] = 0;
            *pulBackward = 0;
            pKeypressHandler->CommandState[iForward] = 1;
            *pulForward = 1;
            break;
        case GO_BACKWARD:
            bCmdMovedFwd = false;
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iForward] = 0;
            *pulForward = 0;
            pKeypressHandler->CommandState[iBackward] = 1;
            *pulBackward = 1;
            break;
        case GO_LEFT:
            bCmdMovedSide = true;
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iStrafeRight] = 0;
            *pulStrafeRight = 0;
            pKeypressHandler->CommandState[iStrafeLeft] = 1;
            *pulStrafeLeft = 1;
            break;
        case GO_RIGHT:
            bCmdMovedSide = true;
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iStrafeLeft] = 0;
            *pulStrafeLeft = 0;
            pKeypressHandler->CommandState[iStrafeRight] = 1;
            *pulStrafeRight = 1;
            break;
        }
    }
    else
    {
        switch(ucDirection)
        {
        case APPLY_TO_ALL:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iStrafeLeft] = 0;
            *pulStrafeLeft = 0;
            pKeypressHandler->CommandState[iStrafeRight] = 0;
            *pulStrafeRight = 0;
            pKeypressHandler->CommandState[iForward] = 0;
            *pulForward = 0;
            pKeypressHandler->CommandState[iBackward] = 0;
            *pulBackward = 0;
            bCmdMovedFwd = bCmdMovedSide = false;
            iStrafeDelayTime = 0;
            break;
        case GO_FORWARD:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iForward] = 0;
            *pulForward = 0;
            bCmdMovedFwd = false;
            break;
        case GO_BACKWARD:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iBackward] = 0;
            *pulBackward = 0;
            bCmdMovedFwd = false;
            break;
        case GO_LEFT:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iStrafeLeft] = 0;
            *pulStrafeLeft = 0;
            bCmdMovedSide = false;
            iStrafeDelayTime = 0;
            break;
        case GO_RIGHT:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iStrafeRight] = 0;
            *pulStrafeRight = 0;
            bCmdMovedSide = false;
            iStrafeDelayTime = 0;
            break;
        case KILL_STRAFE:
            pKeypressHandler->CommandState[iStrafeLeft] = 0;
            *pulStrafeLeft = 0;
            pKeypressHandler->CommandState[iStrafeRight] = 0;
            *pulStrafeRight = 0;
            bCmdMovedSide = false;
            iStrafeDelayTime = 0;
            break;
        case KILL_FB:
            pKeypressHandler->CommandState[iAutoRun] = 0;
            *pulAutoRun = 0;
            pKeypressHandler->CommandState[iForward] = 0;
            *pulForward = 0;
            pKeypressHandler->CommandState[iBackward] = 0;
            *pulBackward = 0;
            bCmdMovedFwd = false;
            break;
        }
        AdjustWalking(bUseWalking);
    }
}

void TimedStrafe(unsigned char ucDirection, SYSTEMTIME &stCurr)
{
    if (!bCmdMovedSide && iStrafeDelayTime == 0)
    {
        DoMovement(KILL_STRAFE, false, false);
        GetSystemTime(&stStickStart);
        iStrafeDelayTime = rand() % (iStrafeDelay[MAX] - iStrafeDelay[MIN] + 1) + iStrafeDelay[MIN];
        return; // return if we are start moving, not continue moving
    }

    if (iStrafeDelayTime != 0)
    {
        int iElapsedTime = diff_in_ms(stCurr, stStickStart);
        if (iElapsedTime >= iStrafeDelayTime)
        {
            DoMovement(ucDirection);
        }
    }
}


// End Player adjustment functions
///////////////////////////////////////
// Begin Command input handling

// exported wrapper for MQ2Melee support
void StickCommand(PSPAWNINFO pChar, char* szLine)
{
    HandleOurCmd(USED_STICK, szLine);
}

// our command wrappers
void StickWrapper(PSPAWNINFO pChar, char* szLine)
{
    HandleOurCmd(USED_STICK, szLine);
    bRunNextCommand = true;
}
void MoveToWrapper(PSPAWNINFO pChar, char* szLine)
{
    HandleOurCmd(USED_MOVETO, szLine);
    bRunNextCommand = true;
}
void CircleWrapper(PSPAWNINFO pChar, char* szLine)
{
    HandleOurCmd(USED_CIRCLE, szLine);
    bRunNextCommand = true;
}
void MakeCampWrapper(PSPAWNINFO pChar, char* szLine)
{
    HandleOurCmd(USED_MAKECAMP, szLine);
    bRunNextCommand = true;
}

void CalcOurAngle(PSPAWNINFO pChar, char* szLine)
{
    if (!ValidIngame() || !pTarget) return;
    PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;
    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
    float fAngle = CalcAngularDist(psTarget->Heading, pChSpawn->Heading);
    float fReqHead = atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * HEADING_HALF / (float)PI;
    fReqHead = SaneHeading(fReqHead);
    fReqHead = pChSpawn->Heading - fReqHead;
    float fDist = GetDistance(pChSpawn, psTarget);
    float fDist3D = GetDistance3D(pChSpawn->X, pChSpawn->Y, pChSpawn->Z, psTarget->X, psTarget->Y, psTarget->Z);
    char szTempOut[MAX_STRING] = {0};
    sprintf(szTempOut, "\ay%s\aw:: Angular Dist ( \ag%.2f\ax ) Adjust ( \ag%.2f\ax ) Dist ( \ag%.2f\ax ) Dist3D ( \ag%.2f\ax )", MODULE_NAME, fAngle, fReqHead, fDist, fDist3D);
    WriteLine(szTempOut, VERB_ENFORCE);
}

void HandleOurCmd(unsigned char ucCmdUsed, char* szInput)
{
    // don't allow commands from char select or cfg files that load before entering world
    if (!ValidIngame(false)) return;

    char szCurrentArg[MAX_STRING]       = {0};  // stores current argument from szInput
    char szTempID[MAX_STRING]           = {0};  // stores output msg for stick
    static char sszCampName[MAX_STRING] = {0};  // stores makecamp player displayedname
    unsigned int uiArgNum               = 1;    // argument number to evaluate
    float fTempY                        = 0.0f; // store cmd input to temp var before setting to prevent inconsistency on failed input

    PSPAWNINFO pTargetUsed = NULL; // stick id, moveto id
    PSPAWNINFO pCampPlayer = NULL; // makecamp player
    PSPAWNINFO psTarget    = (PSPAWNINFO)pTarget;
    PSPAWNINFO pLPlayer    = (PSPAWNINFO)pLocalPlayer;
    PSPAWNINFO pChSpawn    = (PSPAWNINFO)pCharSpawn;

    if (rand() % 100 > 50) fStuckTurn *= -1.0f; // switch direction of turnhalf randomly
    // call bardcheck function upon command usage instead of onpulse
    bIsBard = IsABard();

    // reset state of ${MoveTo.Broken} upon next '/moveto' command
    if (ucCmdUsed == USED_MOVETO)
    {
        bBrokenMoveTo = false;
    }

    GetArg(szCurrentArg, szInput, uiArgNum++);

    // if no argument supplied
    if (!*szCurrentArg)
    {
        bAllPaused = false;

        if (ucCmdUsed == USED_MAKECAMP)
        {
            //EndPreviousCmd(); // dont reset any commands if using makecamp
            if (!bMakeCamp)
            {
                bAllPaused = false;
                iPauseReturnTime = 0;
                ResetCamp(false);
                bMakeCamp = true;
                fAnchorYX[LocY] = pChSpawn->Y;
                fAnchorYX[LocX] = pChSpawn->X;
                // ValidCampRad(); unless they are mem hacking, should only need to do this upon radius change param or ini read
                // what if defaults up top are changed by user and no ini? f them
                iCampDelay[MIN] = ValidDelay(iCampDelay[MIN], 0); //(iCampDelay[MIN] < 125) ? 125 : iCampDelay[MIN];
                iCampDelay[MAX] = ValidDelay(iCampDelay[MAX], iCampDelay[MIN]); //(iCampDelay[MAX] < iCampDelay[MIN] + 125) ? iCampDelay[MIN] + 125 : iCampDelay[MAX];
                sprintf(szMsg, "\ay%s\aw:: MakeCamp actived. Y(\ag%.2f\ax) X(\ag%.2f\ax) Radius(\ag%.2f\ax) Leash(%s) LeashLen(\ag%.2f\ax) Min(\ag%d\ax) Max(\ag%d\ax)", MODULE_NAME, fAnchorYX[LocY], fAnchorYX[LocX], fCampRadius, bLeash ? "\agon\ax" : "\aroff\ax", fLeashLength, iCampDelay[MIN], iCampDelay[MAX]);
                WriteLine(szMsg, VERB_FULL);
                return;
            }
            else
            {
                ResetCamp(true, SET_ALT);
                return;
            }
        }
        else if (ucCmdUsed == USED_STICK) 
        {
            EndPreviousCmd(true);
            if (psTarget)
            {
                // prevent sticking to self/mount
                if (psTarget->SpawnID == pLPlayer->SpawnID || psTarget->SpawnID == pChSpawn->SpawnID)
                {
                    SpewMTError(1, VERB_VERB);
                    return;
                }
                bStickOn = true;
                StandIfNeeded();
                sprintf(szMsg, "\ay%s\aw:: You are now sticking to \ag%s\ax.", MODULE_NAME, psTarget->DisplayedName);
            }
            else
            {
                SpewMTError(2, VERB_VERB);
                return;
            }
        }
        else if (ucCmdUsed == USED_MOVETO || ucCmdUsed == USED_CIRCLE)
        {
            EndPreviousCmd(true);
            // possible future use, currently '/circle' and '/moveto' designed to fail
            sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) /moveto or /circle command used with no parameter.", MODULE_NAME);
        }
        WriteLine(szMsg, VERB_VERB);
        return;
    }

    if (*szCurrentArg)
    {
        // generic parameters that we want to enforce first-parameter syntax
        if (!strnicmp(szCurrentArg, "help", 5))
        {
            unsigned char ucCaller = ucCmdUsed;
            GetArg(szCurrentArg, szInput, uiArgNum);

            if (!strnicmp(szCurrentArg, "settings", 9))
            {
                ucCaller = HELP_SETTINGS;
            }

            if (bHideHelp)
            {
                bHideHelp = false;
                OutputHelp(ucCaller);
                bHideHelp = true;
            }
            else
            {
                OutputHelp(ucCaller);
            }
            return;
        }
        else if (!strnicmp(szCurrentArg, "debug", 6))
        {
            DebugToINI(ucCmdUsed);
            sprintf(szMsg, "\ay%s\aw:: Debug file created.", MODULE_NAME);
            WriteLine(szMsg, VERB_ENFORCE);
            return;
        }
        else if (!strnicmp(szCurrentArg, "status", 7))
        {
            GetArg(szCurrentArg, szInput, uiArgNum);
            if (!strnicmp(szCurrentArg, "all", 4))
            {
                DebugToWnd(APPLY_TO_ALL);
            }
            else
            {
                DebugToWnd(ucCmdUsed);
            }
            return;
        }
        else if (!strnicmp(szCurrentArg, "pause", 6))
        {
            if (!bAllPaused)
            {
                bAllPaused = true;
                if (fAdjustHead != 10000.0f)
                {
                    fAdjustHead = ((PSPAWNINFO)pCharSpawn)->Heading;
                }
                DoMovement(APPLY_TO_ALL, false);
                sprintf(szMsg, "\ay%s\aw:: \arPAUSED", MODULE_NAME);
                WriteLine(szMsg, VERB_ALL);
            }
            else
            {
                sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) Plugin was already paused.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
            }
            return;
        }
        else if (!strnicmp(szCurrentArg, "unpause", 8))
        {
            if (bAllPaused)
            {
                bAllPaused = false;
                iPauseReturnTime= 0;
                //bAutoReturning = bReturnActive = false; // reset moveto and camp returns
                iCampReturnTime = 0;
                ResetFromPause();
                sprintf(szMsg, "\ay%s\aw:: \agRESUMED", MODULE_NAME);
                WriteLine(szMsg, VERB_ALL);
            }
            else
            {
                sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) Plugin was not paused.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
            }
            return;
        }
        else if (!strnicmp(szCurrentArg, "save", 5))
        {
            SaveConfig();
            sprintf(szMsg, "\ay%s\aw:: Saved settings to %s", MODULE_NAME, INIFileName);
            WriteLine(szMsg, VERB_FULL);
            return;
        }
        else if (!strnicmp(szCurrentArg, "load", 5))
        {
            LoadConfig();
            sprintf(szMsg, "\ay%s\aw:: Loaded settings from %s", MODULE_NAME, INIFileName);
            WriteLine(szMsg, VERB_FULL);
            return;
        }
        else if (!strnicmp(szCurrentArg, "imsafe", 7))
        {
            bBrokeOnSummon = false;
            EndPreviousCmd(true);
            sprintf(szMsg, "\ay%s\aw:: Command usage allowed once again.", MODULE_NAME);
            WriteLine(szMsg, VERB_ENFORCE);
            return;
        }
        else if (!strnicmp(szCurrentArg, "set", 4))
        {
            char szTempSet[MAX_STRING] = {0};
            sprintf(szTempSet, "%s", GetNextArg(szInput, 1, FALSE, 0));
            ChangeSetting(ucCmdUsed, false, szTempSet);
            return;
        }
        else if (!strnicmp(szCurrentArg, "toggle", 7))
        {
            char szTempSet[MAX_STRING] = {0};
            sprintf(szTempSet, "%s", GetNextArg(szInput, 1, FALSE, 0));
            ChangeSetting(ucCmdUsed, true, szTempSet);
            return;
        }
        else if (!strnicmp(szCurrentArg, "min", 4))
        {
            MinOurWnd();
            return;
        }
        else if (!strnicmp(szCurrentArg, "clear", 6))
        {
            ClearOurWnd();
            return;
        }

        // non-generic parameters that we want to enforce first-parameter syntax
        if (!strnicmp(szCurrentArg, "on", 3) && (ucCmdUsed == USED_MAKECAMP || ucCmdUsed == USED_CIRCLE))
        {
            if (ucCmdUsed == USED_MAKECAMP)
            {
                bAllPaused = false;
                iPauseReturnTime = 0;
                ResetCamp(false, SET_ALT);
                bMakeCamp = true;
                fAnchorYX[LocY] = pChSpawn->Y;
                fAnchorYX[LocX] = pChSpawn->X;
                iCampDelay[MIN] = ValidDelay(iCampDelay[MIN], 0);
                iCampDelay[MAX] = ValidDelay(iCampDelay[MAX], iCampDelay[MIN]);
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    fCampRadius = (float)atof(szCurrentArg);
                    ValidCampRad();
                    GetArg(szCurrentArg, szInput, uiArgNum); // because when we break from this we enter 'while' for NEW args
                }
            }
            else
            {
                EndPreviousCmd(true);
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    fCircleRadius = (float)atof(szCurrentArg);
                    GetArg(szCurrentArg, szInput, uiArgNum); // because when we break from this we enter 'while' for NEW args
                }
                bCircling = true;
                fCircleYX[LocY] = pChSpawn->Y + fCircleRadius * sin(pChSpawn->Heading * (float)PI / HEADING_HALF);
                fCircleYX[LocX] = pChSpawn->X + fCircleRadius * cos(pChSpawn->Heading * (float)PI / HEADING_HALF);
            }
        }
        else if (!strnicmp(szCurrentArg, "mod", 4) && ucCmdUsed == USED_STICK)
        {
            GetArg(szCurrentArg, szInput, uiArgNum++);
            if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
            {
                fStickDistMod = (float)atof(szCurrentArg);
                sprintf(szMsg, "\ay%s\aw:: Stick modifier changed to Mod(\ag%.2f\ax) Mod%%%%(\ag%.2f%%%%\ax)", MODULE_NAME, fStickDistMod, fStickDistModP); // % = 2 for printf, 2 for eq xml - thanks dkaa!
            }
            else
            {
                //EndPreviousCmd(true); this command doesnt turn on stick, so why turn it off, just spit error out
                sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) \ay/stick mod [#]\ax supplied incorrectly.", MODULE_NAME);
            }
            WriteLine(szMsg, VERB_ALL);
            return;
        }
        else if(!strnicmp(szCurrentArg, "loc", 4) && ucCmdUsed != USED_STICK)
        {
            GetArg(szCurrentArg, szInput, uiArgNum++);
            if (ucCmdUsed == USED_MOVETO)
            {
                EndPreviousCmd(true);
                if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                {
                    fTempY = (float)atof(szCurrentArg);
                    GetArg(szCurrentArg, szInput, uiArgNum++);
                    if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                    {
                        fMoveLocYXZ[LocY] = fTempY;
                        fMoveLocYXZ[LocX] = (float)atof(szCurrentArg);
                        GetArg(szCurrentArg, szInput, uiArgNum++);
                        if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                        {
                            fMoveLocYXZ[LocZ] = (float)atof(szCurrentArg);
                            GetArg(szCurrentArg, szInput, uiArgNum);
                        }
                        else
                        {
                            fMoveLocYXZ[LocZ] = 0.0f;
                        }
                        bMoveToOn = true;
                        
                    }
                    else
                    {
                        SpewMTError(3, VERB_ALL);
                        return;
                    }
                }
                else
                {
                    SpewMTError(3, VERB_ALL);
                    return;
                }
            }
            else if (ucCmdUsed == USED_MAKECAMP)
            {
                bAllPaused = false;
                iPauseReturnTime = 0;
                ResetCamp(false, SET_ALT);
                if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                {
                    fTempY = (float)atof(szCurrentArg);
                    GetArg(szCurrentArg, szInput, uiArgNum++);
                    if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                    {
                        fAnchorYX[LocY] = fTempY;
                        fAnchorYX[LocX] = (float)atof(szCurrentArg);
                        bMakeCamp = true;
                        GetArg(szCurrentArg, szInput, uiArgNum);
                    }
                    else
                    {
                        SpewMTError(4, VERB_ALL);
                        return;
                    }
                }
                else
                {
                    SpewMTError(4, VERB_ALL);
                    return;
                }
            }
            else if (ucCmdUsed == USED_CIRCLE)
            {
                EndPreviousCmd(true, ucCmdUsed, true); // dont reset circle variables
                if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                {
                    fTempY = (float)atof(szCurrentArg);
                    GetArg(szCurrentArg, szInput, uiArgNum++);
                    if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.')
                    {
                        fCircleYX[LocY] = fTempY;
                        fCircleYX[LocX] = (float)atof(szCurrentArg);
                        bCircling = true;
                        GetArg(szCurrentArg, szInput, uiArgNum);
                    }
                    else
                    {
                        SpewMTError(5, VERB_ALL);
                        return;
                    }
                }
                else
                {
                    SpewMTError(5, VERB_ALL);
                    return;
                }
            }
        }
        else if((!strnicmp(szCurrentArg, "yloc", 5) || !strnicmp(szCurrentArg, "xloc", 5)) && ucCmdUsed == USED_MOVETO)
        {
            bool bUsingY = (!strnicmp(szCurrentArg, "yloc", 5)) ? true : false;
            EndPreviousCmd(true);
            GetArg(szCurrentArg, szInput, uiArgNum++);
            if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '-' || szCurrentArg[0] == '.' )
            {
                if (bUsingY)
                {
                    fMoveLocYXZ[LocY] = (float)atof(szCurrentArg);
                    fMoveLocYXZ[LocX] = pChSpawn->X;
                    fMoveLocYXZ[LocZ] = 0.0f;
                }
                else
                {
                    fMoveLocYXZ[LocY] = pChSpawn->Y;
                    fMoveLocYXZ[LocX] = (float)atof(szCurrentArg);
                    fMoveLocYXZ[LocZ] = 0.0f;
                }
                bMoveToOn = true;
                GetArg(szCurrentArg, szInput, uiArgNum);
            }
            else
            {
                sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) \ay/moveto %s\ax was supplied incorrectly.", MODULE_NAME, bUsingY ? "yloc [Y]" : "xloc [X]");
                WriteLine(szMsg, VERB_ALL);
                EndPreviousCmd(true);
                return;
            }
        }

        // reset stick vars one time
        if (ucCmdUsed == USED_STICK)
        {
            EndPreviousCmd(true);
        }
    }

    while (*szCurrentArg)
    {
        if (!strnicmp(szCurrentArg, "off", 4))
        {
            if (ucCmdUsed == USED_MAKECAMP)
            {
                ResetCamp(true, SET_ALT);
                return;
            }
            // dont want to end current command/movement if disabling makecamp
            // so endpreviouscmd comes after makecamp processing
            EndPreviousCmd(true);
            if (ucCmdUsed == USED_STICK)
            {
                sprintf(szMsg, "\ay%s\aw:: You are no longer sticking to anything.", MODULE_NAME);
            }
            else if (ucCmdUsed == USED_CIRCLE)
            {
                sprintf(szMsg, " \ay%s\aw:: Circling radius (\ag%.2f\ax), center (\ag%.2f\ax, \ag%.2f\ax) : \arOFF", MODULE_NAME, fCircleRadius, fCircleYX[LocY], fCircleYX[LocX]);
            }
            else if (ucCmdUsed == USED_MOVETO)
            {
                sprintf(szMsg, "\ay%s\aw:: Moveto off.", MODULE_NAME);
            }
            WriteLine(szMsg, VERB_VERB);
            return;
        }
        else if (!strnicmp(szCurrentArg, "id", 3) && (ucCmdUsed == USED_STICK || ucCmdUsed == USED_MOVETO))
        {
            EndPreviousCmd(true, ucCmdUsed, true);
            PSPAWNINFO pByID = NULL;
            GetArg(szCurrentArg, szInput, uiArgNum);
            if (*szCurrentArg)
            {
                char* pNotNum = NULL;
                int iValid = strtoul(szCurrentArg, &pNotNum, 10);
                // strtoul verifies the arg is 100% numerical, atoi/atof do not
                if (iValid < 1 || *pNotNum)
                {
                    EndPreviousCmd(true);
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) SpawnID must be a positive numerical value.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
                pByID = (PSPAWNINFO)GetSpawnByID((unsigned long)iValid);
                if (pByID)
                {
                    if (pByID->SpawnID == pLPlayer->SpawnID || pByID->SpawnID == pChSpawn->SpawnID)
                    {
                        EndPreviousCmd(true);
                        sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You cannot use yourself or your mount.", MODULE_NAME);
                        WriteLine(szMsg, VERB_ALL);
                        return;
                    }
                    pTargetUsed = (PSPAWNINFO)pByID;
                    uiArgNum++; // incremeted if # is valid, but not otherwise so that someone can use '/stick id behind' to use target. bad form but nonetheless
                }
                else
                {
                    pTargetUsed = NULL;
                }
            }
            else if (psTarget)
            {
                if (psTarget->SpawnID == pLPlayer->SpawnID || psTarget->SpawnID == pChSpawn->SpawnID)
                {
                    SpewMTError(6, VERB_ALL);
                    return;
                }
                pTargetUsed = psTarget; // only use target if its not ourself
            }
            if (!pTargetUsed)
            {
                SpewMTError(6, VERB_ALL);
                return;
            }
            //if we've made it this far, pTargetUsed is valid
            if (ucCmdUsed == USED_STICK)
            {
                bStickNewMob = false; // turns off 'always' when using 'id'
                ulStickID = pTargetUsed->SpawnID;
                stickTarget_Type = GetSpawnType(pTargetUsed);
                bStickOn = bStickHold = true;
            }
            else if (ucCmdUsed == USED_MOVETO)
            {
                fMoveLocYXZ[LocY] = pTargetUsed->Y;
                fMoveLocYXZ[LocX] = pTargetUsed->X;
                // fMoveLocYXZ[LocZ] = pTargetUsed->Z;
                // moveto id with GetDistance3D is causing some users issues
                fMoveLocYXZ[LocZ] = 0.0f;
                bMoveToOn = true;
            }
        }
        // stick specific parameters
        else if (ucCmdUsed == USED_STICK)
        {
            if (strstr(szCurrentArg, "%"))
            {
                fStickDistModP = (float)atof(szCurrentArg) / 100.0f;
                // shouldnt do this here, need logic to output this only if used by itself
                // cant do it on an 'else' for the pTarget 'if' because of 'always' param
                sprintf(szMsg, "\ay%s\aw:: Stick mod changed Mod(\ag%.2f\ax) ModPercent(\ag%.2f%%%%\ax)", MODULE_NAME, fStickDistMod, fStickDistModP); // % = 2 for printf, 2 for eq xml processing. thanks dkaa!
                WriteLine(szMsg, VERB_ALL);
                if (bSetDist && fStickDist * fStickDistModP > 0.0f) fStickDist *= fStickDistModP; // possible float math error here?
                bStickOn = true;
            }
            else if (szCurrentArg[0] == '-')
            {
                fStickDistMod = (float)atof(szCurrentArg);
                sprintf(szMsg, "\ay%s\aw:: Stick mod changed Mod(\ag%.2f\ax) ModPercent(\ag%.2f%%%%\ax)", MODULE_NAME, fStickDistMod, fStickDistModP);
                WriteLine(szMsg, VERB_ALL);
                if (bSetDist && fStickDist + fStickDistMod >= 0.0f) fStickDist += fStickDistMod; // possible float math error here?
                bStickOn = true;
            }
            else if (isdigit(szCurrentArg[0]) || szCurrentArg[0] == '.' )
            {
                if ((float)atof(szCurrentArg) * fStickDistModP + fStickDistMod > 0.0f)
                {
                    fStickDist = (float)atof(szCurrentArg) * fStickDistModP + fStickDistMod;
                }
                bSetDist = bStickOn = true;
            }
            else if (!strnicmp(szCurrentArg, "moveback", 9))
            {
                bMoveBack = bStickOn = true;
            }
            else if (!strnicmp(szCurrentArg, "loose", 6))
            {
                bLooseStick = bStickOn = true;
                bTrueHead = false;
            }
            else if (!strnicmp(szCurrentArg, "truehead", 9))
            {
                bTrueHead = bStickOn = true;
                bLooseStick = false;
            }
            else if (!strnicmp(szCurrentArg, "uw", 3) || !strnicmp(szCurrentArg, "underwater", 11))
            {
                bUnderwater = bStickOn = true;
            }
            else if (!strnicmp(szCurrentArg, "hold", 5))
            {
                if (psTarget)
                {
                    if (psTarget->SpawnID == pLPlayer->SpawnID || psTarget->SpawnID == pChSpawn->SpawnID)
                    {
                        sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You cannot stick hold to yourself.", MODULE_NAME);
                        WriteLine(szMsg, VERB_VERB);
                        EndPreviousCmd(true);
                        return;
                    }
                    ulStickID = psTarget->SpawnID;
                    stickTarget_Type = GetSpawnType(psTarget);
                    bStickOn = bStickHold = true;
                    pTargetUsed = (PSPAWNINFO)psTarget;
                }
                else
                {
                    EndPreviousCmd(true);
                    SpewMTError(2, VERB_VERB);
                    return;
                }
            }
            else if (!strnicmp(szCurrentArg, "on", 3))
            {
                // useless param, but removing it breaks popular macros 
                bStickOn = true;
            }
            else if (!strnicmp(szCurrentArg, "behind", 7))
            {
                bStickBehind = bStickOn = true;
                bStickSnaproll = bStickBehindOnce = bStickPin = bStickNotFront = bStickFront = false;
            }
            else if (!strnicmp(szCurrentArg, "behindonce", 11))
            {
                bStickBehindOnce = bStickOn = true;
                bStickSnaproll = bStickBehind = bStickPin = bStickNotFront = bStickFront = false;
            }
            else if (!strnicmp(szCurrentArg, "!front", 7))
            {
                bStickNotFront = bStickOn = true;
                bStickSnaproll = bStickBehind = bStickPin = bStickBehindOnce = bStickFront = false;
            }
            else if (!strnicmp(szCurrentArg, "front", 6))
            {
                bStickFront = bStickOn = true;
                bStickSnaproll = bStickBehind = bStickPin = bStickBehindOnce = bStickNotFront = false;
            }
            else if (!strnicmp(szCurrentArg, "pin", 4))
            {
                bStickPin = bStickOn = true;
                bStickSnaproll = bStickBehind = bStickBehindOnce = bStickNotFront = bStickFront = false;
            }
            else if (!strnicmp(szCurrentArg, "snaproll", 9))
            {
                bStickSnaproll = bStickOn = true;
                bStickBehind = bStickBehindOnce = bStickNotFront = bStickFront = bStickPin = false;
                fSnapBearing = HEADING_HALF;
                GetArg(szCurrentArg, szInput, uiArgNum);
                if (!strnicmp(szCurrentArg, "face", 6))
                {
                    uiArgNum++;
                    fSnapBearing = 1.0f;
                }
                else if (!strnicmp(szCurrentArg, "left", 5))
                {
                    uiArgNum++;
                    fSnapBearing = HEADING_QUARTER;
                }
                else if (!strnicmp(szCurrentArg, "right", 6))
                {
                    uiArgNum++;
                    fSnapBearing = (HEADING_HALF + HEADING_QUARTER);
                }
                else if (!strnicmp(szCurrentArg, "rear", 5))
                {
                    uiArgNum++; // uses HEADING_HALF (set above)
                }
            }
            else if (!strnicmp(szCurrentArg, "always", 7))
            {
                ulStickID = 0;
                stickTarget_Type = NONE;
                bStickHold = false; // reset hold values, dont allow 'hold' or 'id' with 'always'
                bStickNewMob = bStickOn = true;
                if (psTarget) bStickNewTarget = true;
                sprintf(szMsg, "\ay%s\aw:: You will now stick to every valid NPC target supplied.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
                StandIfNeeded();
                return; // 'always' must be the last parameter
            }
            else
            {
                EndPreviousCmd(true);
                if (!bTotalSilence) OutputHelp(ucCmdUsed, true);
                return;
            }
        }
        // moveto specific parameters
        else if (ucCmdUsed == USED_MOVETO)
        {
            if (!strnicmp(szCurrentArg, "loose", 6))
            {
                bLooseMoveTo = true;
                bTrueHead = false;
            }
            else if (!strnicmp(szCurrentArg, "truehead", 9))
            {
                bTrueHead = true;
                bLooseMoveTo = false;
            }
            else if (!strnicmp(szCurrentArg, "dist", 5))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (szCurrentArg[0] == '-')
                {
                    fMoveDistMod = (float)atof(szCurrentArg);
                    fMoveDist += fMoveDistMod;
                }
                else if (isdigit(szCurrentArg[0]))
                {
                    fMoveDist = ((float)atof(szCurrentArg) >= 1.0f) ? (float)atof(szCurrentArg) : fMoveDist;
                }
                else
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) Incorrectly used \ay/moveto dist [#]\ax", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
                fMoveDistINI = fMoveDist;
                fMoveDistModINI = fMoveDistMod;
                sprintf(szMsg, "\ay%s\aw:: Moveto distance mod changed to \ag%.2f\ax.", MODULE_NAME, fMoveDist);
                WriteLine(szMsg, VERB_ALL);
                return;
            }
            else if (!strnicmp(szCurrentArg, "precisey", 9))
            {
                bYPrecision = true;
                bXPrecision = false;
            }
            else if (!strnicmp(szCurrentArg, "precisex", 9))
            {
                bXPrecision = true;
                bYPrecision = false;
            }
            else if (!strnicmp(szCurrentArg, "uw", 3) || !strnicmp(szCurrentArg, "underwater", 11))
            {
                bUWMoveTo = true;
            }
            else
            {
                EndPreviousCmd(true);
                if (!bTotalSilence) OutputHelp(ucCmdUsed, true);
                return;
            }
        }
        // makecamp specific parameters
        else if (ucCmdUsed == USED_MAKECAMP)
        {
            if (!strnicmp(szCurrentArg, "leash", 6))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    fLeashLength = (float)atof(szCurrentArg);
                    bLeash = true;
                }
                else
                {
                    bLeash = !bLeash;
                }
                ValidCampRad();
            }
            else if (!strnicmp(szCurrentArg, "radius", 7))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    fCampRadius = (float)atof(szCurrentArg);
                    ValidCampRad();
                }
                else
                {
                    ResetCamp(false);
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) \ay/makecamp [radius <dist>]\ax was supplied incorrectly.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
            }
            else if (!strnicmp(szCurrentArg, "mindelay", 9))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    iCampDelay[MIN] = ValidDelay(atoi(szCurrentArg), 0);
                }
                else
                {
                    SpewMTError(7, VERB_ALL);
                    return;
                }
            }
            else if (!strnicmp(szCurrentArg, "maxdelay", 9))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    iCampDelay[MAX] = ValidDelay(atoi(szCurrentArg), iCampDelay[MIN]);
                }
                else
                {
                    SpewMTError(7, VERB_ALL);
                    return;
                }
            }
            else if (!strnicmp(szCurrentArg, "return", 7))
            {
                if (!bMakeCamp && !bCampPlayer)
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You do not have an active camp.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
                bMakeCampReturn = true;
                StandIfNeeded();
                sprintf(szMsg, "\ay%s\aw:: MakeCamp returning to within camp radius immediately.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
                sprintf(szReturnMsg, "camp from /makecamp return"); // this gets output upon arrival in wegetsignal
                return;
            }
            else if (!strnicmp(szCurrentArg, "altreturn", 10))
            {
                if (bCampPlayer)
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You cannot use this command with a player-camp active.", MODULE_NAME);
                    WriteLine(szMsg, VERB_FULL);
                    return;
                }
                if (!bAltCamp || (fAltAnchorYX[LocX] == 0.0f && fAltAnchorYX[LocY] == 0.0f))
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You cannot use this command until you've established an altcamp location.", MODULE_NAME);
                    WriteLine(szMsg, VERB_FULL);
                    return;
                }
                sprintf(szMsg, "\ay%s\aw:: MakeCamp returning to altcamp immediately.%s", MODULE_NAME, bMakeCamp ? " Current camp now \arOFF\ax." : "");
                ResetCamp(false);
                bAltCampReturn = true;
                StandIfNeeded();
                WriteLine(szMsg, VERB_FULL);
                sprintf(szReturnMsg, "camp from /makecamp altreturn"); // this gets output upon arrival in wegetsignal
                return;
            }
            else if (!strnicmp(szCurrentArg, "player", 7))
            {
                ResetCamp(false);
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (*szCurrentArg)
                {
                    pCampPlayer = (PSPAWNINFO)GetSpawnByName(szCurrentArg);
                }
                else if (psTarget && psTarget->Type == SPAWN_PLAYER)
                {
                    pCampPlayer = (PSPAWNINFO)GetSpawnByID(psTarget->SpawnID);
                }
                if (!pCampPlayer)
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) Invalid player name and do not have a valid player target.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
                if (pCampPlayer->SpawnID == pLPlayer->SpawnID || pCampPlayer->SpawnID == pChSpawn->SpawnID)
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) You cannot makecamp yourself.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
                // if we made it this far, pCampPlayer is valid
                sprintf(sszCampName, "%s", pCampPlayer->DisplayedName); // to save it for output msgs when issuing future changes
                ulCampPlayerID = pCampPlayer->SpawnID;
                campPlayer_Type = GetSpawnType(pCampPlayer);
                bMakeCamp = bCampPlayer = true;
                bMakeCampReturn = bAltCampReturn = false;
            }
            else
            {
                ResetCamp(false);
                if (!bTotalSilence) OutputHelp(ucCmdUsed, true);
                return;
            }
        }
        // circle specific parameters
        else if (ucCmdUsed == USED_CIRCLE)
        {
            if (!strnicmp(szCurrentArg, "drunken", 8))
            {
                bDrunken = !bDrunken;
            }
            else if (!strnicmp(szCurrentArg, "clockwise", 10) || !strnicmp(szCurrentArg, "cw", 3))
            {
                bClockwise = true;
            }
            else if (!strnicmp(szCurrentArg, "ccw", 4) || !strnicmp(szCurrentArg, "counterclockwise", 17) || !strnicmp(szCurrentArg, "reverse", 8))
            {
                bClockwise = false;
            }
            else if (!strnicmp(szCurrentArg, "forwards", 9))
            {
                bBackwards = false;
            }
            else if (!strnicmp(szCurrentArg, "backwards", 10))
            {
                bBackwards = true;
            }
            else if (!strnicmp(szCurrentArg, "radius", 7))
            {
                GetArg(szCurrentArg, szInput, uiArgNum++);
                if (isdigit(szCurrentArg[0]))
                {
                    fCircleRadius = (float)atof(szCurrentArg);
                    GetArg(szCurrentArg, szInput, uiArgNum);
                }
                else
                {
                    sprintf(szMsg, "\ay%s\aw:: (\arERROR\ax) Use \ay/circle radius [#]\ax to set radius.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
            }
            else
            {
                EndPreviousCmd(true);
                if (!bTotalSilence) OutputHelp(ucCmdUsed, true);
                return;
            }
        }
        // no valid parameter given
        else
        {
            EndPreviousCmd(true);
            if (!bTotalSilence) OutputHelp(ucCmdUsed);
            return;
        }
        GetArg(szCurrentArg, szInput, uiArgNum++);
    }

    // Output Messages
    if (ucCmdUsed == USED_STICK)
    {
        if (!bStickHold)
        {
            if (!psTarget)
            {
                // dont continue if no target unless 'always' (returns) or 'id || hold'
                EndPreviousCmd(true);
                SpewMTError(2, VERB_VERB);
                return;
            }
            else
            {
                // if self targeted and not 'always' or 'id || hold'
                if (psTarget->SpawnID == pLPlayer->SpawnID || psTarget->SpawnID == pChSpawn->SpawnID)
                {
                    EndPreviousCmd(true);
                    SpewMTError(1, VERB_VERB);
                    return;
                }
                // else setup output msg
                sprintf(szTempID, "%s", psTarget->DisplayedName);
            }
        }
        else if (pTargetUsed || (bStickHold && psTarget))
        {
            // setup output msg for 'id || hold'
            sprintf(szTempID, "%s", pTargetUsed->DisplayedName);
        }
        else
        {
            // error checking in command line parsing should prevent this from ever happening
            // if user reports this error, needs investigation
            EndPreviousCmd(true);
            char szTempOut[MAX_STRING] = {0};
            sprintf(szTempOut, "\ay%s\aw:: \ar/stick NO TARGET ERROR", MODULE_NAME);
            WriteLine(szTempOut, VERB_ENFORCE);
            return;
        }

        if (bRandomizeArc)
        {
            if (bStickNotFront)
            {
                SetRandArc(NOT_FRONT_ARC);
            }
            else if (bStickBehind)
            {
                SetRandArc(BEHIND_ARC);
            }
            else if (bStickPin)
            {
                SetRandArc(PIN_ARC_MIN);
            }
        }

        char szDir[25] = "\agAny\ax";
        if (bStickBehind)
        {
            sprintf(szDir, "\ayBehind\ax");
        }
        else if (bStickPin) 
        {
            sprintf(szDir, "\aySide\ax");
        }
        else if (bStickNotFront) 
        {
            sprintf(szDir, "\ayNot Front\ax");
        }
        else if (bStickFront)
        {
            sprintf(szDir, "\ayFront\ax");
        }

        char szTempHold[25] = {0};
        if (bStickHold && pTargetUsed) sprintf(szTempHold, " ID(\ay%d\ax)", pTargetUsed->SpawnID);

        sprintf(szMsg, "\ay%s\aw:: You are now sticking to %s.", MODULE_NAME, szTempID);
        WriteLine(szMsg, VERB_VERB);
        sprintf(szMsg, "\ay%s\aw:: Dir(%s) Dist(\ag%.2f\ax) Mod(\ag%.2f\ax | \ag%.2f%%%%\ax) Head(%s)%s%s%s", MODULE_NAME, szDir, fStickDist, fStickDistMod, fStickDistModP, bTrueHead ? "\agTrue\ax" : (bLooseStick ? "\ayLoose\ax" : "\arFast\ax"), bStickHold ? szTempHold : "", bUnderwater ? " \agUW\ax" : "", bMoveBack ? " \agMB\ax" : "");
        WriteLine(szMsg, VERB_FULL);
    }
    else if (ucCmdUsed == USED_MOVETO)
    {
        char szInfoY[35] = {0};
        char szInfoX[35] = {0};
        char szZInfo[35] = {0};
        sprintf(szZInfo, " %.2f.", fMoveLocYXZ[LocZ]);
        sprintf(szInfoY, " YDist(\ag%.2f\ax)", fMoveDistY);
        sprintf(szInfoX, " XDist(\ag%.2f\ax)", fMoveDistX);
        sprintf(szReturnMsg, "/moveto location");
        sprintf(szMsg, "\ay%s\aw:: Moving to loc %.2f %.2f%s  Dist(\ag%.2f\ax) Head(%s)%s", MODULE_NAME, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX], fMoveLocYXZ[LocZ] != 0.0f ? szZInfo : ".", fMoveDist, bTrueHead ? "\agTrue\ax" : (bLooseStick ? "\ayLoose\ax" : "\arFast\ax"), bYPrecision ? szInfoY : (bXPrecision ? szInfoX : ""));
        WriteLine(szMsg, VERB_VERB);
    }
    else if (ucCmdUsed == USED_CIRCLE)
    {
        sprintf(szMsg, " \ay%s\aw:: Circling radius (\ag%.2f\ax), center (\ag%.2f\ax, \ag%.2f\ax)%s%s : %s", MODULE_NAME, fCircleRadius, fCircleYX[LocY], fCircleYX[LocX], bClockwise ? "" : ", Reverse", bBackwards ? ", Backwards" : "", bCircling ? "\agON" : "\arOFF");
        WriteLine(szMsg, VERB_VERB);
    }
    else if (ucCmdUsed == USED_MAKECAMP)
    {
        if (!bCampPlayer)
        {
            sprintf(szMsg, "\ay%s\aw:: MakeCamp (%s). Y(\ag%.2f\ax) X(\ag%.2f\ax) Radius(\ag%.2f\ax) Leash(%s - \ag%.2f\ax) Delay(\ag%d\ax to \ag%d\ax)", MODULE_NAME, bMakeCamp ? "\agon\ax" : "\aroff\ax", fAnchorYX[LocY], fAnchorYX[LocX], fCampRadius, bLeash ? "\agon\ax" : "\aroff\ax", fLeashLength, iCampDelay[MIN], iCampDelay[MAX]);
        }
        else
        {
            sprintf(szMsg, "\ay%s\aw:: MakeCamp Player (\ag%s\ax). Radius(\ag%.2f\ax) Leash(%s - \ag%.2f\ax) Delay(\ag%d\ax to \ag%d\ax)", MODULE_NAME, sszCampName, fCampRadius, bLeash ? "\agon\ax" : "\aroff\ax", fLeashLength, iCampDelay[MIN], iCampDelay[MAX]);
        }
        WriteLine(szMsg, VERB_ALL);
    }

    StandIfNeeded();
}

bool ToggleSetting(const char* pszToggleOutput, bool* pbEvalThis, bool* pbUsedToggle, bool* pbTurnOn, unsigned char ucFilterLevel)
{
    char szTheMsg[MAX_STRING] = {0};

    // setting we are changing = did we use 'toggle' ? setting toggled ELSE set to 'on' or 'off'
    *pbEvalThis = *pbUsedToggle ? !*pbEvalThis : *pbTurnOn;
    sprintf(szTheMsg, "\ay%s\aw:: %s turned %s", MODULE_NAME, pszToggleOutput, *pbEvalThis ? szOn : szOff);
    WriteLine(szTheMsg, ucFilterLevel);
    return *pbEvalThis; // changesetting is only evaluating
}

void ChangeSetting(unsigned char ucCmdUsed, bool bToggle, char szSetting[MAX_STRING])
{
    char szCommand[MAX_STRING] = {0};
    switch(ucCmdUsed)
    {
    case USED_MAKECAMP:
        sprintf(szCommand, "/makecamp");
        break;
    case USED_STICK:
        sprintf(szCommand, "/stick");
        break;
    case USED_MOVETO:
        sprintf(szCommand, "/moveto");
        break;
    case USED_CIRCLE:
        sprintf(szCommand, "/circle");
        break;
    default:
        return;
    }

    char szParameter[MAX_STRING] = {0};
    char szSetState[MAX_STRING] = {0};
    char szSetDigit[MAX_STRING] = {0};
    char szSetError[MAX_STRING] = {0};
    bool bTurnOn = false;
    bool bSetDigit = false;
    bool bCustomMsg = false;
    unsigned int uiArgNum = 1;

    GetArg(szParameter, szSetting, uiArgNum++);

    // check for valid parameter if "set" used (on, off, number)
    if (!bToggle)
    {
        GetArg(szSetState, szSetting, uiArgNum);
        if (isdigit(szSetState[0]) || szSetState[0] == '.' || szSetState[0] == '-')
        {
            bSetDigit = true;
            sprintf(szSetDigit, szSetState); // for naming clarification only, waste ram ftw.
        }
        else if (!strnicmp(szSetState, "on", 3))
        {
            bTurnOn = true;
        }
        else if (!strnicmp(szSetState, "off", 4))
        {
            bTurnOn = false; // serves no point other than to confirm valid input
        }
        else
        {
            bCustomMsg = true;
            sprintf(szSetError, "\ay%s\aw:: \arERROR\ax: Invalid '%s set' syntax ( \ar%s\ax ) [on|off|number]", MODULE_NAME, szCommand, szParameter);
        }
    }

    if (!bCustomMsg && (bToggle || !bSetDigit))
    {
        if (!strnicmp(szParameter, "mpause", 7))
        {
            if (ToggleSetting("Pause from manual movement", &bPauseOnMove, &bToggle, &bTurnOn))
            {
                bBreakOnKB = false;
            }
        }
        else if (!strnicmp(szParameter, "mousepause", 11))
        {
            if (ToggleSetting("Pause from mouse movement", &bPauseOnMouse, &bToggle, &bTurnOn))
            {
                bBreakOnMouse = false;
            }
        }
        else if (!strnicmp(szParameter, "breakonkb", 10))
        {
            if (ToggleSetting("Break from keyboard movement", &bBreakOnKB, &bToggle, &bTurnOn))
            {
                bPauseOnMove = false;
            }
        }
        else if (!strnicmp(szParameter, "breakonmouse", 13))
        {
            if (ToggleSetting("Break from mouse movement", &bBreakOnMouse, &bToggle, &bTurnOn))
            {
                bPauseOnMouse = false;
            }
        }
        else if (!strnicmp(szParameter, "autosave", 9))
        {
            ToggleSetting("Auto-save settings to INI file", &bAutoSave, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "savebychar", 11))
        {
            ToggleSetting("Save INI file Character Name section", &bSaveByChar, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "feign", 6))
        {
            ToggleSetting("Remain feign support", &bFeignSupport, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "autopause", 10))
        {
            ToggleSetting("AutoPause upon spell cast", &bAutoPause, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "autopauseoutput", 16))
        {
            ToggleSetting("Display AutoPause message", &bAutoPauseOutput, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "stucklogic", 13))
        {
            ToggleSetting("Stuck-checking logic", &bCheckStuck, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "trytojump", 10))
        {
            ToggleSetting("Try to jump when stuck", &bTryToJump, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "turnhalf", 9))
        {
            ToggleSetting("Switch direction if turned halfway (stucklogic)", &bTurnHalf, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "verbosity", 10))
        {
            if (ToggleSetting("Stick/Moveto verbosity", &bVerbosity, &bToggle, &bTurnOn, VERB_ENFORCE))
            {
                bTotalSilence = false;
            }
        }
        else if (!strnicmp(szParameter, "fullverbosity", 14))
        {
            if (ToggleSetting("Plugin enhanced Verbosity", &bFullVerbosity, &bToggle, &bTurnOn, VERB_ENFORCE))
            {
                bTotalSilence = false;
            }
        }
        else if (!strnicmp(szParameter, "totalsilence", 13))
        {
            if (ToggleSetting("Plugin Silence", &bTotalSilence, &bToggle, &bTurnOn, VERB_ENFORCE))
            {
                bVerbosity = bFullVerbosity = false;
            }
        }
        else if (!strnicmp(szParameter, "window", 7))
        {
            if (ToggleSetting("Dedicated UI Window", &bUseWindow, &bToggle, &bTurnOn, VERB_ENFORCE))
            {
                CreateOurWnd();
            }
            else
            {
                KillOurWnd(true);
            }
        }
        else if (!strnicmp(szParameter, "hidehelp", 9))
        {
            ToggleSetting("Hide all help output", &bHideHelp, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "breakonsummon", 14))
        {
            ToggleSetting("Break command when summoned", &bBreakOnSummon, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "breakonwarp", 12))
        {
            if (ToggleSetting("Break command when NPC warps away", &bBreakOnWarp, &bToggle, &bTurnOn))
            {
                bPauseOnWarp = false;
            }
        }
        else if (!strnicmp(szParameter, "pauseonwarp", 12))
        {
            if (ToggleSetting("Pause command when NPC warps away", &bPauseOnWarp, &bToggle, &bTurnOn))
            {
                bBreakOnWarp = false;
            }
        }
        else if (!strnicmp(szParameter, "breakongate", 12))
        {
            SetupEvents(ToggleSetting("Break command when NPC gates", &bBreakOnGate, &bToggle, &bTurnOn));
        }
        else if (!strnicmp(szParameter, "breakonhit", 11))
        {
            SetupEvents(ToggleSetting("Break MoveTo if attacked", &bBreakOnHit, &bToggle, &bTurnOn));
        }
        else if (!strnicmp(szParameter, "breakonaggro", 13))
        {
            SetupEvents(ToggleSetting("Break MoveTo if aggro", &bBreakOnAggro, &bToggle, &bTurnOn));
        }
        else if (!strnicmp(szParameter, "alwaysdrunk", 12))
        {
            bDrunken = ToggleSetting("Circle always drunken", &bAlwaysDrunk, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "alwaysbackwards", 16))
        {
            bBackwards = ToggleSetting("Circle always backwards", &bBackwards, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "alwaysccw", 10))
        {
            bClockwise = !(ToggleSetting("Circle always counter-clockwise", &bAlwaysCCW, &bToggle, &bTurnOn));
        }
        else if (!strnicmp(szParameter, "alwaystruehead", 15))
        {
            bTrueHead = ToggleSetting("Always use true turning", &bPermTrueHead, &bToggle, &bTurnOn);
            if (bTrueHead) bLooseStick = bLooseMoveTo = bPermLooseMoveTo = bPermLooseStick = false;
        }
        else if (!strnicmp(szParameter, "alwaysloose", 12) && (ucCmdUsed == USED_MOVETO || ucCmdUsed == USED_STICK))
        {
            char szTemp[10] = {0};
            sprintf(szTemp, "Always loose %s", ucCmdUsed == USED_MOVETO ? "MoveTo" : "Stick");
            if (ucCmdUsed == USED_MOVETO)
            {
                bLooseMoveTo = ToggleSetting(szTemp, &bPermLooseMoveTo, &bToggle, &bTurnOn);
                if (bLooseMoveTo) bTrueHead = bPermTrueHead = false;
            }
            else
            {
                bLooseStick = ToggleSetting(szTemp, &bPermLooseStick, &bToggle, &bTurnOn);
                if (bLooseStick) bTrueHead = bPermTrueHead = false;
            }
        }
        else if (!strnicmp(szParameter, "loose", 6) && (ucCmdUsed == USED_MOVETO || ucCmdUsed == USED_STICK))
        {
            char szTemp[10] = {0};
            sprintf(szTemp, "%s loose", ucCmdUsed == USED_MOVETO ? "MoveTo" : "Stick");
            if (ToggleSetting(szTemp, ucCmdUsed == USED_MOVETO ? &bLooseStick : &bLooseMoveTo, &bToggle, &bTurnOn))
            {
                bTrueHead = bPermTrueHead = false;
            }
        }
        else if (!strnicmp(szParameter, "truehead", 9))
        {
            if (ToggleSetting("True heading adjustment", &bTrueHead, &bToggle, &bTurnOn))
            {
                bPermLooseStick = bPermLooseMoveTo = bLooseStick = bLooseMoveTo = false;
            }
        }
        else if (!strnicmp(szParameter, "nohottfront", 12))
        {
            ToggleSetting("Spin in circles when I lose aggro with no HoTT using '/stick front'", &bSpinInCircles, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "returnnoaggro", 14))
        {
            ToggleSetting("Return to camp when not aggro", &bReturnNoAggro, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "returnnotlooting", 17))
        {
            ToggleSetting("Return to camp when not looting", &bReturnNotLoot, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "returnhavetarget", 17))
        {
            ToggleSetting("Return to camp regardless of target", &bReturnHaveTarget, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "leash", 6))
        {
            ToggleSetting("Leash to camp", &bLeash, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "usewalk", 8))
        {
            ToggleSetting("Walk when close to moveto/camp return destination", &bWalkMoveTo, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "randomize", 10))
        {
            ToggleSetting("Randomize custom arcs", &bRandomizeArc, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "delaystrafe", 12))
        {
            ToggleSetting("Enable custom stick angle delay", &bStrafeDelay, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "autouw", 7))
        {
            ToggleSetting("Automatically use 'uw' when underwater", &bAutoUW, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "useback", 8))
        {
            ToggleSetting("Use backwards movement when close", &bUseBack, &bToggle, &bTurnOn);
        }
        else if (!strnicmp(szParameter, "usescatter", 11))
        {
            bCustomMsg = true;
            bScatter = bToggle ? !bScatter : bTurnOn;
            sprintf(szMsg, "\ay%s\aw:: Scatter camp returns (%s) ScatDist(\ag%.2f\ax) Bearing(\ag%.2f\ax) ScatDist(\ag%.2f\ax) ScatSize(\ag%.2f\ax)", MODULE_NAME, bScatter ? szOn : szOff, fScatDist, fBearing, fScatter);
        }
        else
        {
            bCustomMsg = true;
            sprintf(szSetError, "\ay%s\aw:: \arERROR\ax: Not a valid %s %s ( \ar%s\ax ).", MODULE_NAME, szCommand, bToggle ? "toggle" : "set option", szParameter);
        }

        if (!bCustomMsg)
        {
            if (bAutoSave) SaveConfig();
            return;
        }
    }
    else if (!bCustomMsg && bSetDigit)
    {
        float fDigit = (float)atof(szSetDigit);
        if (!strnicmp(szParameter, "pulsecheck", 11))
        {
            uiPulseCheck = (unsigned int)fDigit > 1 ? (unsigned int)fDigit : uiPulseCheck;
            sprintf(szMsg, "\ay%s\aw:: StuckLogic pulse check rate set to \ag%d\ax pulses.", MODULE_NAME, uiPulseCheck);
        }
        else if (!strnicmp(szParameter, "pulseunstuck", 13))
        {
            uiPulseUnstuck = (unsigned int)fDigit > 1 ? (unsigned int)fDigit : uiPulseUnstuck;
            sprintf(szMsg, "\ay%s\aw:: StuckLogic pulse unstuck value set to \ag%d\ax pulses.", MODULE_NAME, uiPulseUnstuck);
        }
        else if (!strnicmp(szParameter, "diststuck", 10))
        {
            fDistStuck = fDigit > 0.0f ? fDigit : fDistStuck;
            sprintf(szMsg, "\ay%s\aw:: StuckLogic distance moved per pulse (else stuck) set to \ag%.3f", MODULE_NAME, fDistStuck);
        }
        else if (!strnicmp(szParameter, "campmindelay", 13))
        {
            iCampDelay[MIN] = ValidDelay((int)fDigit, 0);
            sprintf(szMsg, "\ay%s\aw:: Mindelay for camp return set to \ag%d", MODULE_NAME, iCampDelay[MIN]);
        }
        else if (!strnicmp(szParameter, "campmaxdelay", 13))
        {
            iCampDelay[MAX] = ValidDelay((int)fDigit, iCampDelay[MIN]);
            sprintf(szMsg, "\ay%s\aw:: Maxdelay for camp return set to \ag%d", MODULE_NAME, iCampDelay[MAX]);
        }
        else if (!strnicmp(szParameter, "pausemindelay", 14))
        {
            iPauseDelay[MIN] = ValidDelay((int)fDigit, 0);
            sprintf(szMsg, "\ay%s\aw:: Mindelay for mpause/mousepause set to \ag%d", MODULE_NAME, iPauseDelay[MIN]);
        }
        else if (!strnicmp(szParameter, "pausemaxdelay", 14))
        {
            iPauseDelay[MAX] = ValidDelay((int)fDigit, iPauseDelay[MIN]);
            sprintf(szMsg, "\ay%s\aw:: Maxdelay for mpause/mousepause set to \ag%d", MODULE_NAME, iPauseDelay[MAX]);
        }
        else if (!strnicmp(szParameter, "strafemindelay", 15))
        {
            iStrafeDelay[MIN] = (int)fDigit > 10 ? (int)fDigit : 10;
            sprintf(szMsg, "\ay%s\aw:: Mindelay for strafe resume set to \ag%d", MODULE_NAME, iStrafeDelay[MIN]);
        }
        else if (!strnicmp(szParameter, "strafemaxdelay", 15))
        {
            iStrafeDelay[MAX] = (int)fDigit > iStrafeDelay[MIN] + 50 ? (int)fDigit : iStrafeDelay[MIN] + 50;
            sprintf(szMsg, "\ay%s\aw:: Maxdelay for strafe resume set to \ag%d", MODULE_NAME, iStrafeDelay[MAX]);
        }
        else if (!strnicmp(szParameter, "ydist", 6))
        {
            fMoveDistY = fDigit >= 1.0f ? fDigit : fMoveDistY;
            sprintf(szMsg, "\ay%s\aw:: MoveTo Y-Precision set to \ag%.2f", MODULE_NAME, fMoveDistY);
        }
        else if (!strnicmp(szParameter, "xdist", 6))
        {
            fMoveDistX = fDigit >= 1.0f ? fDigit : fMoveDistX;
            sprintf(szMsg, "\ay%s\aw:: MoveTo X-Precision set to \ag%.2f", MODULE_NAME, fMoveDistX);
        }
        else if (!strnicmp(szParameter, "dist", 5) && ucCmdUsed == USED_MOVETO)
        {
            fMoveDistINI = fDigit >= 1.0f ? fDigit : fMoveDistINI;
            fMoveDist = fMoveDistINI;
            sprintf(szMsg, "\ay%s\aw:: MoveTo ArrivalDist set to \ag%.2f", MODULE_NAME, fMoveDistINI);
        }
        else if (!strnicmp(szParameter, "snapdist", 9))
        {
            fSnapDist = fDigit >= 1.0f ? fDigit : fSnapDist;
            sprintf(szMsg, "\ay%s\aw:: Snaproll Distance from target set to \ag%.2f", MODULE_NAME, fSnapDist);
        }
        else if (!strnicmp(szParameter, "summondist", 11))
        {
            fBreakSummonDist = fDigit >= 1.0f ? fDigit : fBreakSummonDist;
            sprintf(szMsg, "\ay%s\aw:: BreakOnSummon distance set to \ag%.2f", MODULE_NAME, fBreakSummonDist);
        }
        else if (!strnicmp(szParameter, "turnrate", 9))
        {
            fTurnRate = fDigit >= 1.0f && fDigit <= 100.0f ? fDigit : fTurnRate;
            sprintf(szMsg, "\ay%s\aw:: Loose Turn Rate set to \ag%.2f", MODULE_NAME, fTurnRate);
        }
        else if (!strnicmp(szParameter, "!frontarc", 10))
        {
            fNotFrontArc = fDigit <= 260.0f && fDigit > 1.0f ? fDigit : fNotFrontArc;
            sprintf(szMsg, "\ay%s\aw:: !front arc set to \ag%.2f", MODULE_NAME, fNotFrontArc);
        }
        else if (!strnicmp(szParameter, "behindarc", 10))
        {
            fBehindArc = fDigit <= 260.0f && fDigit > 1.0f ? fDigit : fBehindArc;
            sprintf(szMsg, "\ay%s\aw:: behind arc set to \ag%.2f", MODULE_NAME, fBehindArc);
        }
        else if (!strnicmp(szParameter, "breakdist", 10))
        {
            fBreakDist = fDigit >= 1.0f ? fDigit : fBreakDist;
            sprintf(szMsg, "\ay%s\aw:: BreakOnWarp dist set to \ag%.2f", MODULE_NAME, fBreakDist);
        }
        else if (!strnicmp(szParameter, "campradius", 11))
        {
            fCampRadius = fDigit >= 10.0f ? fDigit : fCampRadius;
            ValidCampRad(); // forces leash increase if needed
            sprintf(szMsg, "\ay%s\aw:: Camp radius set to \ag%.2f", MODULE_NAME, fCampRadius);
        }
        else if (!strnicmp(szParameter, "circleradius", 13))
        {
            fCircleRadius = fDigit >= 5.0f ? fDigit : fCircleRadius;
            sprintf(szMsg, "\ay%s\aw:: Circle radius set to \ag%.2f", MODULE_NAME, fCircleRadius);
        }
        else if (!strnicmp(szParameter, "leashlength", 12))
        {
            fLeashLength = fDigit >= fCampRadius ? fDigit : fLeashLength;
            sprintf(szMsg, "\ay%s\aw:: Leash length set to \ag%.2f", MODULE_NAME, fLeashLength);
        }
        else if (!strnicmp(szParameter, "bearing", 8))
        {
            fBearing = fDigit;
            sprintf(szMsg, "\ay%s\aw:: Camp return scatter bearing set to \ag%.2f", MODULE_NAME, fBearing);
        }
        else if (!strnicmp(szParameter, "scatsize", 9))
        {
            fScatter = fDigit >= 1.0f ? fDigit : fScatter;
            sprintf(szMsg, "\ay%s\aw:: Camp return scatter size set to \ag%.2f", MODULE_NAME, fScatter);
        }
        else if (!strnicmp(szParameter, "scatdist", 9))
        {
            fScatDist = fDigit >= 1.0f ? fDigit : fScatDist;
            sprintf(szMsg, "\ay%s\aw:: Camp return scatter dist set to \ag%.2f", MODULE_NAME, fScatDist);
        }
        else if (!strnicmp(szParameter, "backupdist", 11))
        {
            fBackupDist = fDigit > 1.0f ? fDigit : fBackupDist;
            sprintf(szMsg, "\ay%s\aw:: Range to use backwards positioning set to \ag%.2f", MODULE_NAME, fBackupDist);
        }
        else if (!strnicmp(szParameter, "allowmove", 10))
        {
            fAllowMove = fDigit > 10.0f ? fDigit : fAllowMove;
            sprintf(szMsg, "\ay%s\aw:: Allow movement when turning at \ag%.2f", MODULE_NAME, fAllowMove);
        }
        else if (!strnicmp(szParameter, "font", 5))
        {
            int iValid = (int)fDigit > 0 && (int)fDigit < 11 ? (int)fDigit : 0;
            if (iValid == 0)
            {
                sprintf(szMsg, "\ay%s\aw:: \arError\ax - Font must be between 1 and 10.", MODULE_NAME);
                WriteLine(szMsg, VERB_ENFORCE);
                return;
            }
            if (OurWnd) OurWnd->SetFontSize(iValid);
            return;
        }
        else
        {
            bCustomMsg = true;
            sprintf(szSetError, "\ay%s\aw:: \arERROR\ax: Invalid '%s set' parameter ( \ar%s\ax )", MODULE_NAME, szCommand, szParameter);
        }
    }

    if (bAutoSave) SaveConfig();
    if (bCustomMsg)
    {
        WriteLine(szSetError, VERB_ALL);
        return;
    }
    WriteLine(szMsg, VERB_ALL);
}

inline void ResetFromPause()
{
    iCampReturnTime = 0;
    // stucklogic
    bCmdMovedFwd = bCmdMovedSide = false;
    uiStuck = 0;
    fPulseAvg = 1.0f;
    fOrigHead = 0.0f;
    fCompNegHead = 0.0f;
    fCompPosHead = 0.0f;
    fHalfHead = 0.0f;
    fPrevYXZ[LocY] = 0.0f;
    fPrevYXZ[LocX] = 0.0f;
    fPrevYXZ[LocZ] = 0.0f;
    // breakonsummon
    fNewCompare = 0.0f;
    fMyLastYX[LocY] = 0.0f;
    fMyLastYX[LocX] = 0.0f;
    // stick related
    fCurrentDist = 0.0f;
    iStrafeDelayTime = 0;
    // snaproll related
    fSnapHeading = 0.0f;
    fSnapYX[LocY] = 0.0f;
    fSnapYX[LocX] = 0.0f;
    // loose heading related
    if (fAdjustHead != 10000.0f) 
    {
        if (ValidIngame())
        {
            fAdjustHead = ((PSPAWNINFO)pCharSpawn)->Heading;
        }
        else
        {
            fAdjustHead = 10000.0f;
        }
    }
}

void EndPreviousCmd(bool bKillMovement, unsigned char ucCmdUsed, bool bPreserveSelf)
{
    //reset pause
    bAllPaused = false;
    iPauseReturnTime = 0;
    // reset all other vars
    ResetFromPause();

    // break any active commands
    if (ucCmdUsed != USED_CIRCLE || !bPreserveSelf)
    {
        bCircling = bCirclingWasOn = false;
        bDrunken = bAlwaysDrunk;
        bBackwards = bAlwaysBack;
        bClockwise = !bAlwaysCCW;
    }
    if (ucCmdUsed != USED_MOVETO || !bPreserveSelf)
    {
        bMoveToOn = bMakeCampReturn = bAltCampReturn = bReturnActive = bAltRetActive = false;
        fMoveDist = fMoveDistINI;
        fMoveDistMod = fMoveDistModINI;
        bTrueHead = bPermTrueHead;
        bUWMoveTo = false;
        if (!bTrueHead) bLooseMoveTo = bPermLooseMoveTo;
        bXPrecision = bYPrecision = false;
    }
    if (ucCmdUsed != USED_STICK || !bPreserveSelf)
    {
        bStickHold = bSetDist = false;
        ulStickID = 0;
        stickTarget_Type = NONE;
        fSnapBearing = HEADING_HALF;
        bStickOn = bStickWasOn = false;
        bStickBehind = bStickBehindOnce = bStickSnaproll = false;
        bStickPin = bStickNotFront = bStickFront = bMoveBack = false;
        bUnderwater = false;
        bStickNewMob = bStickNewTarget = false;;
        bTrueHead = bPermTrueHead;
        if (!bTrueHead) bLooseStick = bPermLooseStick;
    }

    if (bKillMovement) DoMovement(APPLY_TO_ALL, false, false);
}

void ResetCamp(bool bOffMsg, unsigned char ucAltCamp)
{
    // reset main camp vars
    bMakeCamp = bCampPlayer = false;
    iCampReturnTime = 0;
    ulCampPlayerID = 0;
    campPlayer_Type = NONE;

    if (ucAltCamp == SET_ALT)
    {
        //setup alt camp values if they exist
        if (fAnchorYX[LocY] != 0.0f && fAnchorYX[LocX] != 0.0f)
        {
            bAltCamp = true;
            fAltAnchorYX[LocY] = fAnchorYX[LocY];
            fAltAnchorYX[LocX] = fAnchorYX[LocX];
            fAltCampRadius = fCampRadius;
        }
    }
    else if (ucAltCamp == RESET_ALT)
    {
        // reset alt camp
        bAltCamp = false;
        fAltAnchorYX[LocY] = 0.0f;
        fAltAnchorYX[LocX] = 0.0f;
        fAltCampRadius = 0.0f;
    }

    // reset camp coords
    fAnchorYX[LocY] = 0.0f;
    fAnchorYX[LocX] = 0.0f;

    if (bMoveToOn && (bMakeCampReturn || bAltCampReturn || bAutoReturning))
    {
        EndPreviousCmd(true);
        bAutoReturning = false;
    }

    // call ResetCamp(false) if resetting before setting up new camp to not display this
    if (bOffMsg)
    {
        sprintf(szMsg, "\ay%s\aw:: MakeCamp off.", MODULE_NAME);
        WriteLine(szMsg, VERB_VERB);
    }
}

// End Command input handling
///////////////////////////////////////
// Begin Main function

void MainProcess(unsigned char ucCmdUsed)
{
    if (bBrokeOnSummon) return;
    PCHARINFO  pChData     = (PCHARINFO)pCharData;
    PSPAWNINFO pChSpawn    = (PSPAWNINFO)pCharSpawn;
    PSPAWNINFO pLPlayer    = (PSPAWNINFO)pLocalPlayer;
    PSPAWNINFO psTarget    = (PSPAWNINFO)(bStickHold ? GetSpawnByID(ulStickID) : pTarget);
    PSPAWNINFO pCampPlayer = (PSPAWNINFO)GetSpawnByID(ulCampPlayerID);

    // handle null stick pointers
    if (ucCmdUsed == USED_STICK)
    {
        // prevent sticking to a target that does not exist or stick id to target that has changed types
        if (bStickOn && (!psTarget || (bStickHold && stickTarget_Type != GetSpawnType(psTarget))))
        {
            EndPreviousCmd(true);
            sprintf(szMsg, "\ay%s\aw:: You are no longer sticking to anything.", MODULE_NAME);
            WriteLine(szMsg, VERB_VERB);
            return;
        }
    }
    // end null stick pointers

    // handle /makecamp player if the player no longer exists or has died
    if (bCampPlayer && (!pCampPlayer || campPlayer_Type != GetSpawnType(pCampPlayer)))
    {
        sprintf(szMsg, "\ay%s\aw:: MakeCamp player ended due to player leaving/death.", MODULE_NAME);
        WriteLine(szMsg, VERB_ALL);
        ResetCamp(false);
        return;
    }
    // end /makecamp player handling

    // handle null pointers
    if (!pChData || !pLPlayer || !pChSpawn->SpawnID || !GetCharInfo2())
    {
        sprintf(szMsg, "\ay%s\aw:: Null pointer, turning off current command", MODULE_NAME);
        WriteLine(szMsg, VERB_ENFORCE);
        EndPreviousCmd(false);
        return;
    }
    // end null pointers

    // setup variables used in MainProcess
    SYSTEMTIME stCurr;
    GetSystemTime(&stCurr);

    float fDistance = 0.0f; // used by circle for stuck logic
    float fGetMoveToDist = 0.0f; // used by MoveTo to check if in arrival dist
    float fNewHeading = 0.0f; // heading changes used by all commands
    bool bMoveToOOR = true; // used by moveto, set to false if in range
    static bool sbJumping = false; // if we executed a jump in stucklogic

    // variables reflecting character state
    float fSpeedMultiplier = pChSpawn->SpeedMultiplier;
    bool bSwimming  = (pChSpawn->UnderWater)    ? true : false;
    bool bMounted   = (pChSpawn->Mount)         ? true : false; // used by stucklogic
    bool bStunned   = (pChData->Stunned)        ? true : false; // used by stucklogic and autopause
    bool bLevitated = (pChSpawn->Levitate == 2) ? true : false;
    bool bRooted    = (fSpeedMultiplier == -10000.0f || pChSpawn->RunSpeed < -0.4f) ? true : false;
    bool bSnared    = (fSpeedMultiplier < 0.0f       || pChSpawn->RunSpeed < 0.0f)  ? true : false;
    bool bInJump    = (pChSpawn->Animation == 19     || pChSpawn->Animation == 20)  ? true : false;

    if (!bInJump) sbJumping = false;
    if (bSwimming && bAutoUW) bUnderwater = bUWMoveTo = true;
    bool bUseStuck = (!bRooted && !bSnared) ? true : false; // we set false if stucklogic is not desired this pulse
    // end MainProcess variables

    // handle breakonsummon
    if (bBreakOnSummon && ucCmdUsed != USED_MAKECAMP)
    {
        if (fNewCompare == 0.0f && fMyLastYX[LocY] == 0.0f && fMyLastYX[LocX] == 0.0f)
        {
            // set last loc to current loc if this is first evaluation for current command
            // so that fNewCompare is near-zero and next 'if' will be false
            fMyLastYX[LocY] = pChSpawn->Y;
            fMyLastYX[LocX] = pChSpawn->X;
        }

        // get the distance moved
        fNewCompare = fabs(GetDistance(fMyLastYX[LocY], fMyLastYX[LocX], pChSpawn->Y, pChSpawn->X));
        // store current location for next pulse
        fMyLastYX[LocY] = pChSpawn->Y;
        fMyLastYX[LocX] = pChSpawn->X;

        // if distance moved is larger than user value, halt command
        if (fNewCompare > fBreakSummonDist)
        {
            sprintf(szMsg, "\ay%s\aw:: \arWARNING\ax Command ended from character summoned \ag%.2f\ax distance in a pulse.", MODULE_NAME, fNewCompare);
            EndPreviousCmd(true);
            WriteLine(szMsg, VERB_ENFORCE);
            sprintf(szMsg, "\ay%s\aw:: \arWARNING\ax Verify you are not being monitored and type \ag/stick imsafe\ax to allow command usage.", MODULE_NAME);
            WriteLine(szMsg, VERB_ENFORCE);
            bBrokeOnSummon = bAllPaused = true;
            SpewDebug(DBG_ALL, "Summon Detection fired. All Commands Paused. fBreakSummonDist(%.2f) fCompareMath(%.2f)", fBreakSummonDist, fNewCompare);
            return;
        }
    }
    //end breakonsummon

    // assign needed stick values
    if (ucCmdUsed == USED_STICK)
    {
        if (!bSetDist)
        {
            fStickDist = (psTarget->StandState ? get_melee_range(pLocalPlayer, (EQPlayer *)psTarget) : 15.0f) * fStickDistModP + fStickDistMod;
            bSetDist = true;
        }

        // assign distance
        float fPrevDist = fCurrentDist;
        fCurrentDist = fabs(GetDistance(pChSpawn, psTarget));

        // if we've changed targets mid-stick, dont trigger stucklogic or breakonwarp
        if (ulOldStickID != psTarget->SpawnID)
        {
            bUseStuck = false;
            fPrevDist = 0.0f;
        }
        ulOldStickID = psTarget->SpawnID;

        // if this is not the first pass and target has not changed
        // handle pauseonwarp or breakonwarp
        if ((bPauseOnWarp || bBreakOnWarp) && fPrevDist != 0.0f)
        {
            static bool sbBWOutput = false;
            // if the distance between us and the desired stick distance is > user break distance
            // (we compare prevdist so that user can initially stick from distances
            //       larger than breakdist as long as the distance keeps decreasing)
            if (sbBWOutput)
            {
                if (fabs(fCurrentDist - fStickDist) > fBreakDist) return;
                // return until back in range
            }
            else if (fabs(fCurrentDist - fPrevDist -fStickDist) > fBreakDist)
            {
                if (bPauseOnWarp)
                {
                    if (!sbBWOutput)
                    {
                        DoMovement(APPLY_TO_ALL, false);
                        sprintf(szMsg, "\ay%s\aw: Stick pausing until target back in BreakDist range...", MODULE_NAME);
                        WriteLine(szMsg, VERB_ALL);
                        sbBWOutput = true;
                        iStrafeDelayTime = 0;
                        iCampReturnTime = 0;
                    }
                    // return until above is false
                    return;
                }
                else
                {
                    EndPreviousCmd(true);
                    sprintf(szMsg, "\ay%s\aw:: Stick ending from target warping out of BreakDist range.", MODULE_NAME);
                    WriteLine(szMsg, VERB_ALL);
                    return;
                }
            }
            sbBWOutput = false;
        }
        // end pauseonwarp/breakonwarp
    }
    // end stick values assignment

    // handle autopause
    if (bAutoPause)
    {
        static bool sbAPOutput = false;
        // convert to long because spellid is defined as unsigned but data can be negative to represent not casting
        if (((long)(pChSpawn->CastingData.SpellID) >= 0 && !bIsBard) || (pChSpawn->StandState != STANDSTATE_STAND && pChSpawn->StandState != STANDSTATE_DUCK) || bStunned || bRooted || (ucCmdUsed == USED_STICK && (psTarget->SpawnID == pChSpawn->SpawnID || psTarget->SpawnID == pLPlayer->SpawnID)))
        {
            if (bAutoPauseOutput && !sbAPOutput)
            {
                sprintf(szMsg, "\ay%s\aw:: AutoPause halting movement...", MODULE_NAME);
                WriteLine(szMsg, VERB_ENFORCE);
                sbAPOutput = true;
                fAdjustHead = pChSpawn->Heading;
            }
            iStrafeDelayTime = 0; // reset delays
            iCampReturnTime = 0;
            DoMovement(APPLY_TO_ALL, false, false);
            return; // return until above is false
        }
        sbAPOutput = false;
    }
    // end autopause


    // handle makecamp altreturn
    if (bAltCampReturn)
    {
        iCampReturnTime = 0;
        if (!bScatter)
        {
            CampReturn(fAltAnchorYX[LocY], fAltAnchorYX[LocX], fAltCampRadius, &fReturnLocYX[LocY], &fReturnLocYX[LocX]);
        }
        else
        {
            PolarSpot(fAltAnchorYX[LocY], fAltAnchorYX[LocX], 0.0f, fBearing, fScatDist, fScatter, &fReturnLocYX[LocY], &fReturnLocYX[LocX]);
        }
        bMoveToOn = bAltRetActive = true;
        bXPrecision = bYPrecision = false;
        bMakeCampReturn = bAltCampReturn = bAutoReturning = bReturnActive = false;
        return;
    }
    // end altreturn

    // makecamp handling
    if (!bReturnActive && (bMakeCamp || bCampPlayer))
    {
        float fMyDistFromCamp = GetDistance(pChSpawn->Y, pChSpawn->X, bCampPlayer ? pCampPlayer->Y : fAnchorYX[LocY], bCampPlayer ? pCampPlayer->X : fAnchorYX[LocX]);
        float fDestDistFromCamp = GetDistance(bStickOn ? psTarget->Y : fMoveLocYXZ[LocY], bStickOn ? psTarget->X : fMoveLocYXZ[LocX], bCampPlayer ? pCampPlayer->Y : fAnchorYX[LocY], bCampPlayer ? pCampPlayer->X : fAnchorYX[LocX]);

        // break from command if it would exceed active leash
        if (bLeash && (bStickOn || bMoveToOn))
        {
            if (fMyDistFromCamp < fDestDistFromCamp && fDestDistFromCamp > fLeashLength)
            {
                EndPreviousCmd(true);
                sprintf(szMsg, "\ay%s\aw:: Outside of leash length, breaking from current command", MODULE_NAME);
                WriteLine(szMsg, VERB_VERB);
                return;
            }
        }
        // end leash new command check

        // if makecamp return issued, or if makecamp on check to see if we need to move back
        if (!bManualMove && !bManualMouse && !bStickOn && !bMoveToOn && !bCircling)
        {
            // normal return
            if (fMyDistFromCamp > fCampRadius + 2.0f || bMakeCampReturn) // give leeway to avoid teetering
            {
                bool bDoReturn = true;
                if (!bMakeCampReturn)
                {
                    if (bReturnNoAggro)
                    {
                        if (InCombat()) bDoReturn = false;
                    }
                    else if (!bReturnHaveTarget && pTarget)
                    {
                        bDoReturn = false;
                    }

                    if (bReturnNotLoot && pActiveCorpse) bDoReturn = false;
                }

                // processed conditions in which not to return, if none are met, begin returning
                if (bDoReturn)
                {
                    int iElapsedTime = diff_in_ms(stCurr, stReturnTime); // used in calculating min/max return times
                    if (bMakeCampReturn || (iCampReturnTime != 0 && iElapsedTime >= iCampReturnTime))
                    {
                        iCampReturnTime = 0;
                        if (!bScatter)
                        {
                            CampReturn(bCampPlayer ? pCampPlayer->Y : fAnchorYX[LocY], bCampPlayer ? pCampPlayer->X : fAnchorYX[LocX], fCampRadius, &fReturnLocYX[LocY], &fReturnLocYX[LocX]);
                        }
                        else
                        {
                            PolarSpot(bCampPlayer ? pCampPlayer->Y : fAnchorYX[LocY], bCampPlayer ? pCampPlayer->X : fAnchorYX[LocX], 0.0f, fBearing, fScatDist, fScatter, &fReturnLocYX[LocY], &fReturnLocYX[LocX]);
                        }
                        bMoveToOn = bReturnActive = true;
                        bXPrecision = bYPrecision = false;
                        if (!bMakeCampReturn) bAutoReturning = true; // so the output msg isnt displayed unless user/macro issued command and used in pause
                        bMakeCampReturn = bAltCampReturn = bAltRetActive = false;
                    }
                    else if (!iCampReturnTime)
                    {
                        bAutoReturning = false;
                        GetSystemTime(&stReturnTime);
                        iCampReturnTime = rand() % (iCampDelay[MAX] - iCampDelay[MIN] + 1) + iCampDelay[MIN];
                        return; // return here to begin waiting for return time
                    }
                }
            }
            // end normal return
        }

        // begin leash processing with active stick/circle
        if (!bManualMove && !bManualMouse && bLeash && (bStickOn || bCircling || bStickWasOn || bCirclingWasOn))
        {
            float fHeadBackYX[2] = { (bCampPlayer ? pCampPlayer->Y : fAnchorYX[LocY]), (bCampPlayer ? pCampPlayer->X : fAnchorYX[LocX]) };

            if (fMyDistFromCamp > fLeashLength + 2.0f) // give leeway if teetering
            {
                if (bStickOn || bCircling)
                {
                    if (bStickOn)
                    {
                        EndPreviousCmd(true, USED_STICK, true);
                        bStickOn = bImAggro = false; // disable stick but don't reset current cmd settings
                        bStickBehindOnce = bStickSnaproll = false; // disable these as well since locations will no longer be accurate
                        bStickWasOn = true;
                    }
                    else
                    {
                        EndPreviousCmd(true, USED_CIRCLE, true);
                        bCircling = false; // disable circling but don't reset current cmd settings
                        bCirclingWasOn = true;
                    }
                }
                if (psTarget && (bStickWasOn || bCirclingWasOn))
                {
                    bool bSafeReturn = false;
                    fNewHeading = (atan2(fHeadBackYX[LocY] - pChSpawn->Y, fHeadBackYX[LocX] - pChSpawn->X) * HEADING_HALF / (float)PI);
                    fNewHeading = SaneHeading(fNewHeading);
                    if ((bDrunken && bCirclingWasOn) || ((bLooseStick || bTrueHead) && bStickWasOn))
                    {
                        fAdjustHead = fNewHeading;
                        bSafeReturn = CanLooseMove(fNewHeading, fHeadBackYX[LocY], fHeadBackYX[LocX]);
                    }
                    else
                    {
                        pChSpawn->Heading = fNewHeading;
                        bSafeReturn = true;
                    }
                    DoMovement(GO_FORWARD, bSafeReturn);
                }
            }
            else if (bStickWasOn || bCirclingWasOn)
            {
                EndPreviousCmd(false, (bStickWasOn ? USED_STICK : USED_CIRCLE), true);
                bStickWasOn ? bStickOn = true : bCircling = true;
                bStickWasOn = bCirclingWasOn = false;
                return;
            }
        }
        // end return to camp handling
        if (ucCmdUsed == USED_MAKECAMP) return; // nothing below applies makecamp, return turns bMoveTo on which calls with USED_MOVETO
    }

    // reset use of /face command
    gFaceAngle = 10000.0f;

    // assign values for circle
    if (ucCmdUsed == USED_CIRCLE)
    {
        float fUseCirYX[2] = {(pChSpawn->Y - fCircleYX[LocY]), (pChSpawn->X - fCircleYX[LocX])};
        fDistance = sqrt(fUseCirYX[LocY] * fUseCirYX[LocY] + fUseCirYX[LocX] * fUseCirYX[LocX]);
        
        SpewDebug(DBG_DISABLE, "Setup Circle StuckLogic: fDistance = %.2f", fDistance);
        
        if (fDistance < fCircleRadius * (2.0f / 3.0f)) bUseStuck = false;
    }
    // end circle assignments

    if (ucCmdUsed == USED_MOVETO)
    {
        float fRangeCheck = 0.0f;
        if (bYPrecision)
        {
            fGetMoveToDist = fabs(GetDistance(pChSpawn->Y, 0.0f, fMoveLocYXZ[LocY], 0.0f));
            fRangeCheck = fMoveDistY;
        }
        else if (bXPrecision)
        {
            fGetMoveToDist = fabs(GetDistance(0.0f, pChSpawn->X, 0.0f, fMoveLocYXZ[LocX]));
            fRangeCheck = fMoveDistX;
        }
        else
        {
            if (bAltRetActive || bReturnActive)
            {
                fGetMoveToDist = fabs(GetDistance(pChSpawn->Y, pChSpawn->X, fReturnLocYX[LocY], fReturnLocYX[LocX]));
            }
            else if (fMoveLocYXZ[LocZ] == 0.0f)
            {
                fGetMoveToDist = fabs(GetDistance(pChSpawn->Y, pChSpawn->X, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX]));
            }
            else
            {
                fGetMoveToDist = fabs(GetDistance3D(pChSpawn->Y, pChSpawn->X, pChSpawn->Z, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX], fMoveLocYXZ[LocZ]));
            }
            fRangeCheck = fMoveDist;
        }

        // check for stucklogic and MoveToAggro
        if (fGetMoveToDist <= fRangeCheck)
        {
            bMoveToOOR = bUseStuck = false;
        }
        else
        {
            bMoveToOOR = true;
            if (bBreakOnAggro && CheckMoveToAggro()) return;
        }
    }

    // main stucklogic check
    if (bCheckStuck)
    {
        // use 3D to compare Z so stucklogic doesn't fire if we are moving more z than y/x (up/down slopes)
        // if bJumping then dont check z axis movement
        float fPulseMoved = (bLevitated || (sbJumping && bInJump)) ? GetDistance(pChSpawn->Y, pChSpawn->X, fPrevYXZ[LocY], fPrevYXZ[LocX]) : GetDistance3D(pChSpawn->Y, pChSpawn->X, pChSpawn->Z, fPrevYXZ[LocY], fPrevYXZ[LocX], fPrevYXZ[LocZ]);

        // sanity check, if we've moved more than 5 (summon, knockback, user)
        // it will throw off our readjustment, so keep the last value instead
        if (fPulseMoved < 5.0f) fPulseAvg = MovingAvg(fPulseMoved, uiPulseCheck);
        //SpewDebug(DBG_STUCK, "fPulseAvg = %.2f, fPulseMoved %.2f", fPulseAvg, fPulseMoved);

        fPrevYXZ[LocY] = pChSpawn->Y;
        fPrevYXZ[LocX] = pChSpawn->X;
        fPrevYXZ[LocZ] = pChSpawn->Z;

        //SpewDebug(DBG_DISABLE, "runspeed %.2f and walkspeed %.2f and speedrun %.2f and speedmultiplier %.2f", pChSpawn->RunSpeed, pChSpawn->WalkSpeed, pChSpawn->SpeedRun, pChSpawn->SpeedMultiplier);
        if (bSnared || bRooted)
        {
            // dont use stucklogic if snared
            bUseStuck = false;
            uiStuck = 0;
            uiStuckDec = 0;
        }
        else if (fSpeedMultiplier < 0.25f)
        {
            fSpeedMultiplier = 0.25f; // boost multiplier of those without runspeed
        }

        // if we were stuck last pulse and we moved more than stuckdist since (making progress to get unstuck)
        if (uiStuck && (fPulseAvg > fDistStuck))
        {
            uiStuck--;
            //SpewDebug(DBG_STUCK, "Decremented uiStuck to (%d) because we moved (%3.2f)", uiStuck, fPulseAvg);

            //force unstuck after defined increments of not being stuck. we reset uiStuckDec if we get stuck again
            uiStuckDec++;
            if (uiStuckDec > uiPulseUnstuck)
            {
                uiStuck = 0;
                uiStuckDec = 0;
                //SpewDebug(DBG_STUCK, "Zeroed uiStuck and uiStuckDec after consecutive decrements");
            }
        }

        if (bStunned || fAdjustHead != 10000.0f) bUseStuck = false; // dont analyze if stunned or plugin is adjusting heading
        //SpewDebug(DBG_STUCK, "if fPulseAvg(%.2f) < fDistStuck(%.2f) * fSpeedMultiplier(%.2f) && bUse(%s) && Fwd(%s) or Side(%s)", fPulseAvg, fDistStuck, fSpeedMultiplier, bUseStuck ? "true" : "false", bCmdMovedFwd ? "true" : "false", bCmdMovedSide ? "true" : "false");

        if ((bCmdMovedFwd || bCmdMovedSide) && bUseStuck &&
            // if moved forward or strafe (not backwards)
            // bUseStuck is set false if using stucklogic this pulse is not desired

            // main stucklogic formula here
            ( (fPulseAvg < fDistStuck * fSpeedMultiplier && !bSwimming && !bMounted) ||

            // maybe handle water and mounts on their own system?
            (bSwimming && (double)fPulseAvg < 0.0010) ||

            (bMounted && fPulseAvg < (fDistStuck + fSpeedMultiplier) / 3.0f) )

            )
        {
            //SpewDebug(DBG_STUCK, "Im Stuck (uiStuck = %d) -- if fpulsemoved (%.2f) < fDistStuck(%.2f) * fSpeedMultiplier(%.2f) then increment", uiStuck, fPulseMoved, fDistStuck, fSpeedMultiplier);

            // 'big if' is true, if we've moved less than user-set dist * current speed multiplier
            if (fPulseMoved < fDistStuck * fSpeedMultiplier) // check if our movement this single pulse is still bad
            {
                uiStuck++;
                uiStuckDec = 0;
                //SpewDebug(DBG_STUCK, "incremented uiStuck %d, reset uiStuckDec to zero", uiStuck);

                // bTryToJump user set value in INI
                // try to jump early (but not 1-2pulse misfires) and again after a few seconds/turns
                if (bTryToJump && !sbJumping && !bLevitated && !bSwimming && ((uiStuck % 5) == 0))
                {
                    MQ2Globals::ExecuteCmd(iJumpKey, 1, 0);
                    MQ2Globals::ExecuteCmd(iJumpKey, 0, 0);
                    sbJumping = true;
                }

                // calculate original heading for turnhalf before first turn attempt
                if (uiStuck == 4)
                {
                    if (ucCmdUsed == USED_STICK)
                    {
                        fOrigHead = (atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * HEADING_HALF) / (float)PI;
                    }
                    else if (ucCmdUsed == USED_MOVETO)
                    {
                        if (bReturnActive || bAltRetActive)
                        {
                            fOrigHead = (atan2(fReturnLocYX[LocX] - pChSpawn->X, fReturnLocYX[LocY] - pChSpawn->Y) * HEADING_HALF) / (float)PI;
                        }
                        else
                        {
                            fOrigHead = (atan2(fMoveLocYXZ[LocX] - pChSpawn->X, fMoveLocYXZ[LocY] - pChSpawn->Y) * HEADING_HALF) / (float)PI;
                        }
                    }
                    else if (ucCmdUsed == USED_CIRCLE)
                    {
                        fOrigHead = (atan2(pChSpawn->Y - fCircleYX[LocY], fCircleYX[LocX] - pChSpawn->X) * CIRCLE_HALF) / (float)PI * CIRCLE_QUARTER;
                        fOrigHead += CIRCLE_QUARTER * (fCircleRadius / fDistance);
                        fOrigHead *= HEADING_MAX / CIRCLE_MAX;
                    }
                    fOrigHead = SaneHeading(fOrigHead);
                    fHalfHead = fOrigHead + HEADING_HALF;
                    fHalfHead = SaneHeading(fHalfHead);
                    //SpewDebug(DBG_STUCK, "We've determined that halfway from destination is %.2f", fHalfHead);
                    fCompNegHead = fHalfHead - fabs(fStuckTurn / 2.0f);
                    fCompNegHead = SaneHeading(fCompNegHead);
                    fCompPosHead = fHalfHead + fabs(fStuckTurn / 2.0f);
                    fCompPosHead = SaneHeading(fCompPosHead);
                }

                // if uiStuck == multiple of 4 (try to turn 1 increment, every 4 pulses of being stuck)
                if ((uiStuck & 3) == 0)
                {
                    fNewHeading = pChSpawn->Heading + fStuckTurn;
                    fNewHeading = SaneHeading(fNewHeading);
                    //SpewDebug(DBG_STUCK, "Stucklogic turned, New heading is %.2f", fNewHeading);

                    // if enabled, check if we are halfway away from our destination, reset heading to original heading and start again
                    if (bTurnHalf)
                    {
                        //SpewDebug(DBG_STUCK, "TURNHALF: Comparing desired heading (%.2f) > %.2f and < %.2f", fNewHeading, fCompNegHead, fCompPosHead);
                        if (fNewHeading > fCompNegHead && fNewHeading < fCompPosHead)
                        {
                            fNewHeading = fOrigHead;
                            fStuckTurn *= -1.0f;
                            SpewDebug(DBG_STUCK, "TRUE, so flipped fStuckTurn to other way (%.2f) and reset heading to %.2f", fStuckTurn, fNewHeading);
                        }
                    }
                    //set heading, either by increment or based on half-way logic
                    fAdjustHead = fNewHeading;
                    //SpewDebug(DBG_STUCK, "Stucklogic heading change: %.2f", fAdjustHead);
                }
            }
            // end fPulseMoved < fDistStuck * speedmulti
        }
        // end 'big if'
    }
    // end if bCheckStuck (meaning use stucklogic or not)

    // if we are stuck or rooted dont process normal command handling
    if (uiStuck || bRooted)
    {
        //SpewDebug(DBG_STUCK, "uiStuck %d bRooted %s fired, returning without trying to move", uiStuck, bRooted ? "true" : "false");
        return;
    }
    // end of main stucklogic checking

    // we arent stuck, or stucklogic is disabled, handle heading if not snaprolling (all commands)
    if (!bStickSnaproll)
    {
        //AdjustWalking(false); // to turn off stuck walking. stick will turn walk back on at the bottom of this function if it needs it
        if (ucCmdUsed == USED_STICK)
        {
            fNewHeading = atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * HEADING_HALF / (float)PI;
        }
        else if (ucCmdUsed == USED_MOVETO)
        {
            if (bReturnActive || bAltRetActive)
            {
                fNewHeading = atan2(fReturnLocYX[LocX] - pChSpawn->X, fReturnLocYX[LocY] - pChSpawn->Y) * HEADING_HALF / (float)PI;
            }
            else
            {
                fNewHeading = atan2(fMoveLocYXZ[LocX] - pChSpawn->X, fMoveLocYXZ[LocY] - pChSpawn->Y) * HEADING_HALF / (float)PI;
            }
        }
        else if (ucCmdUsed == USED_CIRCLE)
        {
            fNewHeading = (bClockwise != bBackwards) ? (atan2(pChSpawn->Y - fCircleYX[LocY], fCircleYX[LocX] - pChSpawn->X) * CIRCLE_HALF) / (float)PI : (atan2(fCircleYX[LocY] - pChSpawn->Y, pChSpawn->X - fCircleYX[LocX]) * CIRCLE_HALF) / (float)PI;
            bClockwise ?  fNewHeading += CIRCLE_QUARTER + CIRCLE_QUARTER * (fCircleRadius / fDistance) : fNewHeading -= CIRCLE_QUARTER + CIRCLE_QUARTER * (fCircleRadius / fDistance);
            fNewHeading *= HEADING_MAX / CIRCLE_MAX;
        }
        fNewHeading = SaneHeading(fNewHeading);

        if (ucCmdUsed != USED_STICK)
        {
            (bTrueHead || (bLooseMoveTo && bMoveToOn) || (bDrunken && bCircling)) ? fAdjustHead = fNewHeading : pChSpawn->Heading = fNewHeading;
            if (ucCmdUsed == USED_MOVETO && bUWMoveTo && psTarget)
            {
                double dLookAngle = (double)atan2(psTarget->Z + psTarget->AvatarHeight * StateHeightMultiplier(psTarget->StandState) -
                    pChSpawn->Z - pChSpawn->AvatarHeight * StateHeightMultiplier(pChSpawn->StandState), fabs(GetDistance3D(pChSpawn->Y, pChSpawn->X, pChSpawn->Z, psTarget->Y, psTarget->X, psTarget->Z))) * HEADING_HALF / (double)PI;
                (bTrueHead || bLooseMoveTo) ? gLookAngle = dLookAngle : pChSpawn->CameraAngle = (float)dLookAngle;
            }
        }
        else
        {
            if (!bTrueHead && !bLooseStick) pChSpawn->Heading = fNewHeading;
        }
    }

    // handle circle movement and exit, moveto handling follows
    if (ucCmdUsed == USED_CIRCLE)
    {
        bBackwards ? DoMovement(GO_BACKWARD) : DoMovement(GO_FORWARD);
        return;
    }
    // end of all circle processing

    // handle moveto and exit, stick handling follows
    if (ucCmdUsed == USED_MOVETO)
    {
        if (bMoveToOOR)
        {
            bool bSafeToMove = false;
            bool bCloseWalk = false;
            if (bTrueHead || bLooseMoveTo)
            {
                if (bReturnActive || bAltRetActive)
                {
                    bSafeToMove = CanLooseMove(fNewHeading, fReturnLocYX[LocY], fReturnLocYX[LocX]);
                }
                else
                {
                    bSafeToMove = CanLooseMove(fNewHeading, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX]);
                }
            }
            else
            {
                bSafeToMove = true;
            }
            (bWalkMoveTo && fGetMoveToDist < 20.0f) ? bCloseWalk = true : bCloseWalk = false;
            DoMovement(GO_FORWARD, bSafeToMove, bCloseWalk);
        }
        else
        {
            // bAutoReturning set true if auto return (in which case we dont want to spew msg)
            if (!bAutoReturning)
            {
                sprintf(szMsg, "\ay%s\aw:: Arrived at %s", MODULE_NAME, szReturnMsg);
                WriteLine(szMsg, VERB_VERB);
                sprintf(szReturnMsg, "/moveto location");
            }
            bAutoReturning = false;
            EndPreviousCmd(true);
        }
        return;
    }
    // end of all moveto processing

    // everything below this point relates to '/stick' ONLY
    if (ucCmdUsed != USED_STICK) return; // this should be redundant but just in case

    // if we are close to the mob and our heading change required is large, move backwards instead
    if (bUseBack && !bStickSnaproll && fAdjustHead == 10000.0f && fCurrentDist < fStickDist + fBackupDist)
    {
        // we check the fAdjustHead so not to affect turns already in motion
        float fBackAdjust = fabs(pChSpawn->Heading - fNewHeading);
        if (fBackAdjust > 200.0f && fBackAdjust < 350.0f)
        {
            DoMovement(GO_BACKWARD);
            return;
        }
    }

    // adjust heading otherwise
    if (bTrueHead || bLooseStick) fAdjustHead = fNewHeading;

    // uw or underwater parameter in stick
    if (bUnderwater)
    {
        double dLookAngle = (double)atan2(psTarget->Z + psTarget->AvatarHeight * StateHeightMultiplier(psTarget->StandState) -
            pChSpawn->Z - pChSpawn->AvatarHeight * StateHeightMultiplier(pChSpawn->StandState), fCurrentDist) * HEADING_HALF / (double)PI;
        (bTrueHead || bLooseStick) ? gLookAngle = dLookAngle : pChSpawn->CameraAngle = (float)dLookAngle;
        //SpewDebug(DBG_ALL, "Stick-Underwater: gLookAngle %g CameraAngle %.2f", gLookAngle, pChSpawn->CameraAngle);
    }
    // end underwater handling

    // run forward only until in stick range, if heading towards target
    if (fCurrentDist > fStickDist && !bStickSnaproll)
    {
        bool bSafeToMove = (bTrueHead || bLooseStick) ? CanLooseMove(fNewHeading, psTarget->Y, psTarget->X) : true;
        DoMovement(GO_FORWARD, bSafeToMove);
        return;
    }
    // everything below this relates only to being in stick range

    bool bCustomAngle = (bStickBehind || bStickBehindOnce || bStickPin || bStickNotFront);

    // calculate our angular distance
    float fAngDist = CalcAngularDist(psTarget->Heading, pChSpawn->Heading);
    float fFabsAngDist = fabs(fAngDist);

    // if too far away, don't do pin or behind or aggro checking (if awareness on)
    if ((bStickFront || bCustomAngle) && fCurrentDist < fStickDist * 3)
    {
        // handling for behind/pin/!front
        if (bCustomAngle)
        {
            // if we are on HoTT
            if (bCmdMovedSide && (int)pLPlayer->SpawnID == pChSpawn->TargetOfTarget)
            {
                // halt strafing
                DoMovement(KILL_STRAFE, false);
            }
            // we are not on HoTT
            else
            {
                // if randomize is enabled and using /stick behind or /stick !front, but not /stick pin
                if (bRandomizeArc && !bStickBehindOnce)
                {
                    bool bPosFlag = (rand() % 100 > 50);
                    if (!bStickPin)
                    {
                        // our randomized "flag" is negative or positive sign - to determine which arc was modified
                        // we make this eval multiple times, so set it to a bool first
                        if (fAngDist < 0.0f && fFabsAngDist > (bPosFlag ? fRandArcMax : fRandArcMin))
                        {
                            bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_LEFT);
                        }
                        else if (fAngDist > 0.0f && fFabsAngDist > (bPosFlag ? fRandArcMin : fRandArcMax))
                        {
                            bStrafeDelay ? TimedStrafe(GO_RIGHT, stCurr) : DoMovement(GO_RIGHT);
                        }
                        // else we are within the arc range, so stop strafing
                        else
                        {
                            DoMovement(KILL_STRAFE, false);
                            //DebugSpew("Halted Movement at %.2f", fAngDist);
                        }
                    }
                    else
                    {
                        if (fFabsAngDist > PIN_ARC_MAX || fFabsAngDist < PIN_ARC_MIN)
                        {
                            if ((fAngDist < 0.0f && fAngDist > (bPosFlag ? (PIN_ARC_MIN * -1.0f) : (fRandArcMin * -1.0f))) || fAngDist > (bPosFlag ? fRandArcMax : PIN_ARC_MAX))
                            {
                                bStrafeDelay ? TimedStrafe(GO_RIGHT, stCurr) : DoMovement(GO_RIGHT);
                            }
                            if ((fAngDist > 0.0f && fAngDist < (bPosFlag ? PIN_ARC_MIN : fRandArcMin)) || fAngDist < (bPosFlag ? (PIN_ARC_MAX * -1.0f) : (fRandArcMax * -1.0f)))
                            {
                                bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_LEFT); // strafe left
                            }
                        }
                        else
                        {
                            DoMovement(KILL_STRAFE, false);
                            //DebugSpew("Halted Movement at %.2f", fAngDist);
                        }
                    }
                }
                // else randomize is not on, or we used /stick pin
                else
                {
                    // normal processing for '/stick behind' and '/stick behindonce'
                    if (bStickBehind || bStickBehindOnce)
                    {
                        if (fFabsAngDist > BEHIND_ARC)
                        {
                            if (fAngDist < 0.0f)
                            {
                                //SpewDebug(DBG_OTHER, "MoveUtils CustAngle: %.2f - Going Left", fAngDist);
                                bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_LEFT); // strafe left
                            }
                            else
                            {
                                //SpewDebug(DBG_OTHER, "MoveUtils CustAngle: %.2f - Going Right", fAngDist);
                                bStrafeDelay ? TimedStrafe(GO_RIGHT, stCurr) : DoMovement(GO_RIGHT); // strafe right
                            }
                        }
                        else
                        {
                            bStickBehindOnce = false;
                            DoMovement(KILL_STRAFE, false);
                        }
                    }
                    // processing for '/stick pin'
                    else if (bStickPin)
                    {
                        if (fFabsAngDist > PIN_ARC_MAX || fFabsAngDist < PIN_ARC_MIN)
                        {
                            if ((fAngDist < 0.0f && fAngDist > (PIN_ARC_MIN * -1.0f)) || fAngDist > PIN_ARC_MAX)
                            {
                                 bStrafeDelay ? TimedStrafe(GO_RIGHT, stCurr) : DoMovement(GO_RIGHT);
                            }
                            if ((fAngDist > 0.0f && fAngDist < PIN_ARC_MIN) || fAngDist < (PIN_ARC_MAX * -1.0f))
                            {
                                bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_LEFT); // strafe left
                            }
                        }
                        else
                        {
                            DoMovement(KILL_STRAFE, false);
                        }
                    }
                    // else non-random processing for stick !front
                    else if (bStickNotFront)
                    {
                        if (fFabsAngDist > fNotFrontArc)
                        {
                            int iRandT = rand() % 100;
                            if (iRandT > 85)
                            {
                                bStickBehindOnce = true;
                            }
                            else if (fAngDist < 0.0)
                            {
                                bStrafeDelay ? TimedStrafe(GO_RIGHT, stCurr) : DoMovement(GO_LEFT); // strafe left
                            }
                            else
                            {
                                bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_RIGHT); // strafe right
                            }
                        }
                        else
                        {
                            DoMovement(KILL_STRAFE, false);
                        }
                    }
                }
            }
        }
        else if (bStickFront)
        {
            if (bSpinInCircles || (int)pLPlayer->SpawnID == pChSpawn->TargetOfTarget)
            {
                // if im the target of target or deliberately want to spin if lose aggro
                if (fFabsAngDist < FRONT_ARC)
                {
                    if (fAngDist < 0.0f)
                    {
                        bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_RIGHT);
                    }
                    else
                    {
                        bStrafeDelay ? TimedStrafe(GO_LEFT, stCurr) : DoMovement(GO_LEFT);
                    }
                }
                else
                {
                    DoMovement(KILL_STRAFE, false);
                }
            }
            else
            {
                DoMovement(KILL_STRAFE, false);
            }
        }
    }
    else
    {
        DoMovement(KILL_STRAFE, false);
    }
    //end pin/!front/behind/front

    // if stucklogic is on and we are ducking while sticking, un-duck
    if (bCheckStuck && pChSpawn->StandState == STANDSTATE_DUCK) StandIfNeeded();

    // handling for stick snaproll
    if (bStickSnaproll)
    {
        PolarSpot(psTarget->Y, psTarget->X, psTarget->Heading, fSnapBearing, fSnapDist, 0.0f, &fSnapYX[LocY], &fSnapYX[LocX]); // calculate location we want
        float fGetSnapDist = fabs(GetDistance3D(pChSpawn->Y, pChSpawn->X, pChSpawn->Z, fSnapYX[LocY], fSnapYX[LocX], psTarget->Z)); // get distance from that location
        if (fGetSnapDist <= 5.0f) // if distance is close enough on first pass, no need to snaproll, this also breaks in future pulses
        {
            DoMovement(APPLY_TO_ALL, false, false);
            bStickSnaproll = false;
            bStickOn       = true;
            fNewHeading    = atan2(psTarget->X - pChSpawn->X, psTarget->Y - pChSpawn->Y) * HEADING_HALF / (float)PI;
            fNewHeading    = SaneHeading(fNewHeading);
            fAdjustHead    = fNewHeading;
            fSnapHeading   = 0.0f;
            fSnapYX[LocY]  = 0.0f;
            fSnapYX[LocX]  = 0.0f;
            return;
        }
        // determine heading to that location
        fSnapHeading = (atan2(fSnapYX[LocX] - pChSpawn->X, fSnapYX[LocY] - pChSpawn->Y) * HEADING_HALF) / (float)PI;
        fSnapHeading = SaneHeading(fSnapHeading);
        fAdjustHead  = fSnapHeading;
        // start movement
        if (fGetSnapDist > 4.0f)
        {
            bool bSnapCanMove = CanLooseMove(fSnapHeading, fSnapYX[LocY], fSnapYX[LocX]);
            DoMovement(GO_FORWARD, bSnapCanMove);
        }
        return;
    }
    // end snaproll handling

    // ******** main movement handling for stick *******
    if (fCurrentDist > fStickDist) // if we are outside stick distance
    {
        // if distance is less than 10 and target not fleeing, walk
        bool bWalkMode = false;
        if (fFabsAngDist > 64.0f && fCurrentDist - fStickDist < 11.0f) 
        {
            bWalkMode = true;
            //SpewDebug(DBG_OTHER, "WalkMode Check: fabs(CalcAngularDist(pChSpawn->Heading, psTarget->Heading)(%.2f) > 64 && fCurrentDist (%.2f) - fStickDist (%.2f) <= 10", fDistFlee, fCurrentDist, fStickDist);
        }
        DoMovement(GO_FORWARD, true, bWalkMode);
        //SpewDebug(DBG_DISABLE, "Stick: moving forwards using walk mode");
    }
    else if (bMoveBack && fabs(pChSpawn->Heading - fNewHeading) < 14.0f && fCurrentDist < fStickDist - 5.0f)
    {
        DoMovement(GO_BACKWARD, true, true);
        //SpewDebug(DBG_DISABLE, "Stick: moving backwards");
    }
    else
    {
        // we are in the desired place, turn off movement until needed
        DoMovement(KILL_FB, false, false);
        //SpewDebug(DBG_DISABLE, "Stick: turning movement keys off");
    }
    // *************************************************
}
// End Main function
///////////////////////////////////////
//Begin Output functions

void WriteLine(char szOutput[MAX_STRING], unsigned char ucFilterLevel)
{
    if (ucFilterLevel == VERB_ALL)
    {
        if (!bTotalSilence)
        {
            if (bUseWindow)
            {
                WriteToOurWnd(szOutput);
            }
            else
            {
                WriteChatf(szOutput);
            }
        }
    }
    else if (ucFilterLevel == VERB_VERB)
    {
        if (bVerbosity && !bTotalSilence)
        {
            if (bUseWindow)
            {
                WriteToOurWnd(szOutput);
            }
            else
            {
                WriteChatf(szOutput);
            }
        }
    }
    else if (ucFilterLevel == VERB_FULL)
    {
        if (bFullVerbosity && !bTotalSilence)
        {
            if (bUseWindow)
            {
                WriteToOurWnd(szOutput);
            }
            else
            {
                WriteChatf(szOutput);
            }
        }
    }
    else if (ucFilterLevel == VERB_ENFORCE)
    {
        if (bUseWindow)
        {
            WriteToOurWnd(szOutput);
        }
        else
        {
            WriteChatf(szOutput);
        }
    }
}

void OutputHelp(unsigned char ucCmdUsed, bool bOnlyCmdHelp)
{
    if (!ValidIngame(false) || bHideHelp) return;

    char szCommand[MAX_STRING] = {0};
    bool bDisplaySettings = false;
    if (ucCmdUsed == USED_MAKECAMP)
    {
        sprintf(szCommand, "/makecamp");
    }
    else if (ucCmdUsed == USED_STICK)
    {
        sprintf(szCommand, "/stick");
    }
    else if (ucCmdUsed == USED_MOVETO)
    {
        sprintf(szCommand, "/moveto");
    }
    else if (ucCmdUsed == USED_CIRCLE)
    {
        sprintf(szCommand, "/circle");
    }
    else if (ucCmdUsed == HELP_SETTINGS)
    {
        bDisplaySettings = true;
    }

    char szTempOut[MAX_STRING] = {0};
    sprintf(szTempOut, "\ay%s \agv%1.4f", MODULE_NAME, MODULE_VERSION);
    WriteLine(szTempOut, VERB_ENFORCE);

    if (bDisplaySettings)
    {
        WriteLine("\arThe following settings can be changed with any of the 4 commands. There are three options (using /stick as an example).", VERB_ENFORCE);
        WriteLine("\ay/stick [set] [\agsetting\ax] [on | off]\ax - This will turn the setting on or off", VERB_ENFORCE);
        WriteLine("\ay/stick [set] [\agsetting\ax] [#]\ax - This will change settings that use numerical values", VERB_ENFORCE);
        WriteLine("\ay/stick [toggle] [\agsetting\ax]\ax - This will toggle the setting on and off.", VERB_ENFORCE);
        WriteLine("\arThe following settings can be set on/off OR toggled:", VERB_ENFORCE);
        WriteLine("\ag[autosave]\ax - Automatic saving of configuration file each time you change a setting.", VERB_ENFORCE);
        WriteLine("\ag[feign]\ax - Stay-FD support, awaiting you to manually stand before resuming command.", VERB_ENFORCE);
        WriteLine("\ag[breakonkb|breakonmouse]\ax - End command from manual keyboard/mouse movement.", VERB_ENFORCE);
        WriteLine("\ag[mpause|mousepause]\ax - Pause from manual keyboard/mouse movement. Resumes after pausemindelay/pausemaxdelay values.", VERB_ENFORCE);
        WriteLine("\ag[autopause]\ax - Automatic pause&resume command when casting(non-bard), ducking, stunned, targeting self.", VERB_ENFORCE);
        WriteLine("\ag[verbosity|fullverbosity]\ax - MQ2ChatWnd command output and detailed ouput.", VERB_ENFORCE);
        WriteLine("\ag[hidehelp]\ax - Hides help output from showing automatically on command failures.", VERB_ENFORCE);
        WriteLine("\ag[totalsilence]\ax - Hide all MQ2ChatWnd output except critial warnings and requested information.", VERB_ENFORCE);
        WriteLine("\ag[stucklogic]\ax - Automatic detection of stuck movement and attempted correction.", VERB_ENFORCE);
        WriteLine("\ag[trytojump]\ax - Try to jump as part of stucklogic getting over obstacles.", VERB_ENFORCE);
        WriteLine("\ag[turnhalf]\ax - Reset heading and try the other way if halfway from destination in stucklogic.", VERB_ENFORCE);
        WriteLine("\ag[nohottfront]\ax - Awareness for stick front to not spin if loose aggro.", VERB_ENFORCE);
        WriteLine("\ag[savebychar]\ax - Save character-specific settings to [CharName] section of configuration file.", VERB_ENFORCE);
        WriteLine("\ag[breakonsummon]\ax - Halt plugin if character summoned beyond certain distance in a single pulse.", VERB_ENFORCE);
        WriteLine("\ag[usewalk]\ax - Walk when closing in on moveto / camp return locations for precision.", VERB_ENFORCE);
        WriteLine("\ag[alwaystruehed]\ax - Use legit heading adjustments with right/left keys.", VERB_ENFORCE);
        WriteLine("\arThe following settings use a numerical value:", VERB_ENFORCE);
        WriteLine("\ag[pulsecheck] [#]\ax - Set number of frames used to calculate moving average for stucklogic. (default 4)", VERB_ENFORCE);
        WriteLine("\ag[pulseunstuck] [#]\ax - Set number of frames successfully moved forward to consider unstuck (default 5)", VERB_ENFORCE);
        WriteLine("\ag[diststuck] [#.###]\ax - Set distance needed to move per pulse to not be considered stuck. (default 0.5)", VERB_ENFORCE);
        WriteLine("\ag[campmindelay|campmaxdelay|pausemindelay|pausemaxdelay] [#]\ax - Set camp return or mpause/mousepause delays.", VERB_ENFORCE);
        WriteLine("\ag[turnrate] [#.##]\ax (\ay1.0\ax to \ay100.0\ax) - Set increment used for loose heading turns.", VERB_ENFORCE);
        return; // dont re-spam the output below
    }

    if (!bOnlyCmdHelp)
    {
        WriteLine("\arThe following options work for all commands (\ay/makecamp\ax, \ay/stick\ax, \ay/moveto\ax, \ay/circle\ax)", VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [help]\ax - Displays help output for the command.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [debug]\ax - Outputs debugging information to file.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [load|save]\ax - Load settings from or save settings to the configuration file. (MQ2MoveUtils.ini)", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [pause|unpause]\ax - Pauses/Unpauses all aspects of this plugin.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\arFor detailed information on plugin settings, use \ay%s [help] [settings]\ax for more information.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\arThe remaining options apply to \ay%s\ax only.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
    }

    sprintf(szTempOut, "\ay%s [off]\ax - Ends the command.", szCommand);
    WriteLine(szTempOut, VERB_ENFORCE);

    if (ucCmdUsed == USED_STICK)
    {
        sprintf(szTempOut, "\ay%s\ax - Sticks to current target using default values.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [id #]\ax - Sticks to a target by its SpawnID or your target if the SpawnID is invalid.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [<#>]\ax - Sticks to target using the distance you supply, i.e. /stick 10 starts 10 range away.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [<#%%%>]\ax - Sticks to given %% of max melee range from target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [moveback]\ax - Backs you up to current distance value if you get closer to your target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [uw/underwater]\ax - Look Up/Down to face your target (underwater or not).", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [truehead|loose]\ax - Adjusts your heading legit/close to legit instead of instantly.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [snaproll]\ax - Moves you behind the target in a straight line, then proceeds with a basic stick.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [behind]\ax - Sticks you behind your target. \ar*Will spin in circles if you get aggro and no HoTT", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [behindonce]\ax - Sticks you behind target immediately, then proceeds with a basic stick.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [hold]\ax - Sticks you to current target even if your target changes.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [!front]\ax - Sticks you anywhere but front melee arc of target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [front]\ax - Sticks you to the front melee arc of the target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [pin]\ax - Sticks you to the sides of target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [breakongate|breakonwarp]\ax - Toggle command ending on mob gate/warp.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [breakdist] [#]\ax - Set distance for breakonwarp to trigger.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [alwaysloose]\ax - Toggle always using loose heading.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s <your_normal_options> [always]\ax - \arMust be at the end\ax. Will continue to stick to all new NPC targets supplied. Turns off hold/id.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
    }
    else if (ucCmdUsed == USED_MOVETO)
    {
        sprintf(szTempOut, "\ay%s [loc Y X [Z]|off]\ax - Move to location | stop moving", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s id [SpawnID]\ax - Move to spawnid. If not numeric value then move to current target", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [loose]\ax - Toggles more realistic movement", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [dist <#|-#>]\ax - Furthest distance to be considered 'at location', negative subtracts from value", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [ydist|xdist] [#.##]\ax - Set distance used for precisey/precisex distance checking.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [alwaysloose]\ax - Toggle always using loose heading.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
    }
    else if (ucCmdUsed == USED_MAKECAMP)
    {
        sprintf(szTempOut, "\ay%s [on|off]\ax - Drops anchor at current location | removes anchor", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [loc <y> <x>]\ax - Makes camp at supplied anchor location", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [leash]\ax - Toggles leashing to current camp", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [leash [<dist>]]\ax - Set distance to exceed camp before leashing, always turns leash on", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [radius <dist>]\ax - Sets radius/size of camp", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [maxdelay <#>|mindelay <#>]\ax - Sets the max/minimum amount of time before returning to camp.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [return]\ax - Immediately return to camp", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [altreturn]\ax - Immediately return to alternate camp (if established)", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [player [<name>]]\ax - Set dynamic camp around player name in zone, or current target", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [leashlength] [#.##]\ax - Set leash length size.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [returnnoaggro] [on|off]\ax - Return to camp if not aggro (regardless of target).", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [returnnotlooting] [on|off]\ax - Do not return to camp if looting.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [returnhavetarget] [on|off]\ax - Return to camp even if have target.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [campradius] [#]\ax - Change radius of existing camp.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [scatdist] [#.##]\ax - Set scatter distance from center.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [scatsize] [#.##]\ax - Set scatter radius size.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [bearing] [#.##]\ax - Set scatter bearing from center.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [usescatter]\ax - Use user-defined scatter values for camp returns.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
    }
    else if (ucCmdUsed == USED_CIRCLE)
    {
        sprintf(szTempOut, "\ay%s [on <radius>]\ax - Turn circle on at current loc. If radius supplied sets size of circle.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [loc <y> <x>]\ax - Turn on circle at given anchor location. (Y X are the order /loc prints them).", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [radius <#>]\ax - Change the circle radius without resetting circle.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [drunken]\ax - Toggles random turns.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [clockwise|cw]\ax - Toggles circling clockwise.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [counterclockwise|ccw|reverse]\ax - Toggles circling counter-clockwise.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [forwards|backwards]\ax - Set movement direction forwards or backwards.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [alwaysccw]\ax - Always use counter-clockwise (default is clockwise).", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [alwaysbackwards]\ax - Always use backwards (default is forwards).", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [toggle] [alwaysdrunk]\ax - Always use drunken movement.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
        sprintf(szTempOut, "\ay%s [set] [circleradius] [#]\ax - Change the circle radius without resetting circle.", szCommand);
        WriteLine(szTempOut, VERB_ENFORCE);
    }
}

void DebugToWnd(unsigned char ucCmdUsed)
{
    char szTemp[MAX_STRING] = {0};
    char szTempID[MAX_STRING] = "NULL";
    char szYes[8] = "\agyes\ax";
    char szNo[7] = "\arno\ax";
    char szDir[25] = "\agNormal\ax";
    char szLongLine[48] = "\ay---------------------------------------------";

    sprintf(szTemp, "\ay%s v%1.4f - Current Status", MODULE_NAME, MODULE_VERSION);
    WriteLine(szTemp, VERB_ENFORCE);
    if (ucCmdUsed == USED_STICK || ucCmdUsed == APPLY_TO_ALL)
    {
        if (bStickBehind)
        {
            sprintf(szDir, "\agBehind\ax");
        }
        else if (bStickPin) 
        {
            sprintf(szDir, "\agSide\ax");
        }
        else if (bStickNotFront) 
        {
            sprintf(szDir, "\agNot Front\ax");
        }
        else if (bStickFront)
        {
            sprintf(szDir, "\agFront\ax");
        }
        WriteLine(szLongLine, VERB_ENFORCE);
        sprintf(szTemp, "\ayStick\ax: Status(%s) Dir(%s) Dist(\ag%.2f\ax) Mod(\ag%.2f\ax) Mod%%%%(\ag%.2f%%%%\ax) Head(%s) Water(%s) MoveBack(%s) Hold(%s) Always(%s)", bStickOn ? szOn : szOff, szDir, fStickDist, fStickDistMod, fStickDistModP, bTrueHead ? "\agTrue\ax" : (bLooseStick ? "\ayLoose\ax" : "\arFast\ax"), bUnderwater ? szYes : szNo, bMoveBack ? szYes : szNo, bStickHold ? szYes : szNo, bStickNewMob ? szOn : szOff);
        WriteLine(szTemp, VERB_ENFORCE);
        if (bStickHold)
        {
            PSPAWNINFO pStickThis = (PSPAWNINFO)GetSpawnByID(ulStickID);
            if (pStickThis)
            {
                sprintf(szTempID, "%s", pStickThis->DisplayedName);
            }
            sprintf(szTemp, "\ayStick\ax: Holding to ID(\ag%d\ax) Name(\ag%s\ax)", ulStickID, szTempID);
            WriteLine(szTemp, VERB_ENFORCE);
        }
        sprintf(szTemp, "\ayStick Options\ax: AlwaysHead(%s) BreakOnWarp(%s) BreakDist(\ag%.2f\ax) BreakOnGate(%s) ", bPermTrueHead ? "\agTrue\ax" : (bPermLooseStick ? "\ayLoose\ax" : "\arFast\ax"), bBreakOnWarp ? szOn : szOff, fBreakDist, bBreakOnGate ? szOn : szOff);
        WriteLine(szTemp, VERB_ENFORCE);
    }
    if (ucCmdUsed == USED_MOVETO || ucCmdUsed == APPLY_TO_ALL)
    {
        WriteLine(szLongLine, VERB_ENFORCE);
        sprintf(szTemp, "\ayMoveto\ax: Status(%s) Y(\ag%.2f\ax) X(\ay%.2f\ax) Dist(\ag%.2f\ax) Mod(\ag%.2f\ax) Loose(%s) YPrecise(%s) XPrecise(%s)", bMoveToOn ? szOn : szOff, fMoveLocYXZ[LocY], fMoveLocYXZ[LocX], fMoveDist, fMoveDistMod, bLooseMoveTo ? szYes : szNo, bYPrecision ? szYes : szNo, bXPrecision ? szYes : szNo);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayMoveto Options\ax: AlwaysLoose(%s) Y-Dist(\ag%.2f\ax) X-Dist(\ag%.2f\ax)", bPermLooseMoveTo ? szYes : szNo, fMoveDistY, fMoveDistX);
        WriteLine(szTemp, VERB_ENFORCE);
    }
    if (ucCmdUsed == USED_CIRCLE || ucCmdUsed == APPLY_TO_ALL)
    {
        WriteLine(szLongLine, VERB_ENFORCE);
        sprintf(szTemp, "\ayCircle\ax: Status(%s) Y(\ag%.2f\ax) X(\ag%.2f\ax) Radius(\ag%.2f\ax) Drunken(%s) Backwards(%s) Clockwise(%s)", bCircling ? szOn : szOff, fCircleYX[LocY], fCircleYX[LocX], fCircleRadius, bDrunken ? szYes : szNo, bBackwards ? szYes : szNo, bClockwise ? szYes : szNo);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayCircle Options\ax: AlwaysDrunk(%s) AlwaysBackwards(%s) AlwaysCCW(%s)", bAlwaysDrunk ? szYes : szNo, bAlwaysBack ? szYes : szNo, bAlwaysCCW ? szYes : szNo);
        WriteLine(szTemp, VERB_ENFORCE);
    }
    if (ucCmdUsed == USED_MAKECAMP || ucCmdUsed == APPLY_TO_ALL)
    {
        WriteLine(szLongLine, VERB_ENFORCE);
        sprintf(szTemp, "\ayMakeCamp\ax: Status(%s) Player(%s) Y(\ag%.2f\ax) X(\ag%.2f\ax) Radius(\ag%.2f\ax) Leash(%s) LeashLen(\ag%.2f\ax) Returning(%s) ", bMakeCamp ? szOn : szOff, bCampPlayer ? szYes : szNo, fAnchorYX[LocY], fAnchorYX[LocX], fCampRadius, bLeash ? szOn : szOff, fLeashLength, bAutoReturning ? szYes : szNo);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayMakeCamp\ax: Scatter(%s) Bearing(\ag%.2f\ax) ScatDist(\ag%.2f\ax) ScatSize(\ag%.2f\ax)", bScatter ? szOn : szOff, fBearing, fScatDist, fScatter);
        WriteLine(szTemp, VERB_ENFORCE);
        if (bCampPlayer)
        {
            PSPAWNINFO pCampThis = (PSPAWNINFO)GetSpawnByID(ulCampPlayerID);
            if (pCampThis)
            {
                sprintf(szTempID, "%s", pCampThis->DisplayedName);
            }
            sprintf(szTemp, "\ayMakeCamp\ax: Player Name(\ag%s\ax) ID(\ag%d\ax)", szTempID, ulCampPlayerID);
            WriteLine(szTemp, VERB_ENFORCE);
        }
        if (bAltCamp)
        {
            sprintf(szTemp, "\ayMakeCamp\ax: AlternateCamp(\agon\ax) AltAnchorY(\ag%.2f\ax) AltAnchorX(\ag%.2f\ax) AltRadius(\ag%.2f\ax)", fAltAnchorYX[LocY], fAltAnchorYX[LocX], fAltCampRadius);
            WriteLine(szTemp, VERB_ENFORCE);
        }
        sprintf(szTemp, "\ayMakeCamp Options\ax: ReturnNoAggro(%s) MinDelay(\ag%d\ax) MaxDelay(\ag%d\ax)", bReturnNoAggro ? szOn : szOff, iCampDelay[MIN], iCampDelay[MAX]);
        WriteLine(szTemp, VERB_ENFORCE);
    }

    if (ucCmdUsed == APPLY_TO_ALL)
    {
        WriteLine(szLongLine, VERB_ENFORCE);
        sprintf(szTemp, "\ayPlugin Status\ax: Paused(%s) MinPause(\ag%d\ax) MaxPause(\ag%d\ax)", bAllPaused ? szYes : szNo, iPauseDelay[MIN], iPauseDelay[MAX]);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayStuckLogic\ax: Enabled(%s) DistStuck(\ag%.3f\ax) PulseCheck(\ag%d\ax) PulseUnstuck(\ag%d\ax)", bCheckStuck ? szYes : szNo, fDistStuck, uiPulseCheck, uiPulseUnstuck);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayPause Options\ax: AutoPause(%s) MPause(%s) BreakOnKB(%s) MousePause(%s) BreakOnMouse(%s)", bAutoPause ? szOn : szOff, bPauseOnMove ? szOn : szOff, bPauseOnMouse ? szOn : szOff, bBreakOnKB ? szOn : szOff, bBreakOnMouse ? szOn : szOff);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayOutput Options\ax: Silent(%s) Verbosity(%s) FullVerbosity(%s) HideHelp(%s)", bTotalSilence ? szYes : szNo, bVerbosity ? szYes : szNo, bFullVerbosity ? szYes : szNo, bHideHelp ? szOn : szOff);
        WriteLine(szTemp, VERB_ENFORCE);
        sprintf(szTemp, "\ayPlugin Options\ax: AutoSave(%s) FeignSupport(%s) BreakOnSummon(%s) BreakSummonDist(\ag%.2f\ax) AwareNotAggro(%s)", bAutoSave ? szOn : szOff, bFeignSupport ? szOn : szOff, bBreakOnSummon ? szOn : szOff, fBreakSummonDist, bSpinInCircles ? szOn : szOff);
        WriteLine(szTemp, VERB_ENFORCE);
    }
}

void DebugToINI(unsigned char ucCmdUsed)
{
    char szTemp[MAX_STRING] = {0};
    char szCommand[MAX_STRING] = {0};
    if (ucCmdUsed == USED_MAKECAMP)
    {
        sprintf(szCommand, "/makecamp");
    }
    else if (ucCmdUsed == USED_STICK)
    {
        sprintf(szCommand, "/stick");
    }
    else if (ucCmdUsed == USED_MOVETO)
    {
        sprintf(szCommand, "/moveto");
    }
    else if (ucCmdUsed == USED_CIRCLE)
    {
        sprintf(szCommand, "/circle");
    }

    sprintf(szTemp, "%s v%1.4f", MODULE_NAME, MODULE_VERSION);
    WritePrivateProfileString("Version", "Number", szTemp, szDebugName);
    WritePrivateProfileString("Commands", "CommandUsed", szCommand, szDebugName);
    WritePrivateProfileString("GenericBOOL", "bKeyBindsSet", bKeyBindsSet ? "true" : "false", szDebugName);
    WritePrivateProfileString("GenericBOOL", "bAllPaused", bAllPaused ? "true" : "false", szDebugName);
    WritePrivateProfileString("GenericBOOL", "bBrokeOnSummon", bBrokeOnSummon ? "true" : "false", szDebugName);
    WritePrivateProfileString("Strings", "szMsg", szMsg, szDebugName);
    WritePrivateProfileString("Strings", "szReturnMsg", szReturnMsg, szDebugName);
    WritePrivateProfileString("AggroChecking", "bSpinInCircles", bSpinInCircles ? "true" : "false", szDebugName);
    WritePrivateProfileString("AggroChecking", "bImAggro", bImAggro ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bAutoPause", bAutoPause ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bPauseOnMove", bPauseOnMove ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bPauseOnMouse", bPauseOnMouse ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bBreakOnKB", bBreakOnKB ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bBreakOnMouse", bBreakOnMouse ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bCheckStuck", bCheckStuck ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bVerbosity", bVerbosity ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bFullVerbosity", bFullVerbosity ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bTotalSilence", bTotalSilence ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bAutoSave", bAutoSave ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bFeignSupport", bFeignSupport ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bSaveByChar", bSaveByChar ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bHideHelp", bHideHelp ? "true" : "false", szDebugName);
    WritePrivateProfileString("INISettings", "bBreakOnSummon", bBreakOnSummon ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fBreakSummonDist);
    WritePrivateProfileString("INISettings", "fBreakSummonDist", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fTurnRate);
    WritePrivateProfileString("INISettings", "fTurnRate", szTemp, szDebugName);
    WritePrivateProfileString("INISettings", "iPauseDelay[MIN]", itoa(iPauseDelay[MIN], szTemp, 10), szDebugName);
    WritePrivateProfileString("INISettings", "iPauseDelay[MAX]", itoa(iPauseDelay[MAX], szTemp, 10), szDebugName);
    WritePrivateProfileString("INISettings", "iCampDelay[MIN]", itoa(iCampDelay[MIN], szTemp, 10), szDebugName);
    WritePrivateProfileString("INISettings", "iCampDelay[MAX]", itoa(iCampDelay[MAX], szTemp, 10), szDebugName);
    sprintf(szTemp, "%.2f", fAdjustHead);
    WritePrivateProfileString("SetOnPulse", "fAdjustHead", szTemp, szDebugName);
    WritePrivateProfileString("SetOnPulse", "bManualMove", bManualMove ? "true" : "false", szDebugName);
    WritePrivateProfileString("SetOnPulse", "bReturnActive", bReturnActive ? "true" : "false", szDebugName);
    WritePrivateProfileString("SetOnPulse", "bAutoReturning", bAutoReturning ? "true" : "false", szDebugName);
    WritePrivateProfileString("SetOnPulse", "bStickNewTarget", bStickNewTarget ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fCurrentDist);
    WritePrivateProfileString("SetOnPulse", "fCurrentDist", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fNewCompare);
    WritePrivateProfileString("SetOnPulse", "fNewCompare", szTemp, szDebugName);
    WritePrivateProfileString("SetOnPulse", "iCampReturnTime", itoa(iCampReturnTime, szTemp, 10), szDebugName);
    WritePrivateProfileString("SetOnPulse", "iPauseReturnTime", itoa(iPauseReturnTime, szTemp, 10), szDebugName);
    WritePrivateProfileString("Stick", "bBreakOnGate", bBreakOnGate ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bBreakOnWarp", bBreakOnWarp ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fBreakDist);
    WritePrivateProfileString("Stick", "fBreakDist", szTemp, szDebugName);
    WritePrivateProfileString("Stick", "bStickOn", bStickOn ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bSetDist", bSetDist ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fStickDist);
    WritePrivateProfileString("Stick", "fStickDist", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fStickDistMod);
    WritePrivateProfileString("Stick", "fStickDistMod", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fStickDistModP);
    WritePrivateProfileString("Stick", "fStickDistModP", szTemp, szDebugName);
    WritePrivateProfileString("Stick", "bLooseStick", bLooseStick ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bPermLooseStick", bPermLooseStick ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bMoveBack", bMoveBack ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickBehind", bStickBehind ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickBehindOnce", bStickBehindOnce ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickFront", bStickFront ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickNotFront", bStickNotFront ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickPin", bStickPin ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickSnaproll", bStickSnaproll ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickHold", bStickHold ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "ulStickID", itoa((int)ulStickID, szTemp, 10), szDebugName);
    WritePrivateProfileString("Stick", "bUnderwater", bUnderwater ? "true" : "false", szDebugName);
    WritePrivateProfileString("Stick", "bStickNewMob", bStickNewMob ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fSnapHeading);
    WritePrivateProfileString("StickSnaproll", "fSnapHeading", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fSnapYX[LocY]);
    WritePrivateProfileString("StickSnaproll", "fSnapYX[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fSnapYX[LocX]);
    WritePrivateProfileString("StickSnaproll", "fSnapYX[LocX]", szTemp, szDebugName);
    WritePrivateProfileString("MakeCamp", "bMakeCamp", bMakeCamp ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fAnchorYX[LocY]);
    WritePrivateProfileString("MakeCamp", "fAnchorYX[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fAnchorYX[LocX]);
    WritePrivateProfileString("MakeCamp", "fAnchorYX[LocX]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fCampRadius);
    WritePrivateProfileString("MakeCamp", "fCampRadius", szTemp, szDebugName);
    WritePrivateProfileString("MakeCamp", "bAltCamp", bAltCamp ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fAltAnchorYX[LocY]);
    WritePrivateProfileString("MakeCamp", "fAltAnchorYX[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fAltAnchorYX[LocX]);
    WritePrivateProfileString("MakeCamp", "fAltAnchorYX[LocX]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fAltCampRadius);
    WritePrivateProfileString("MakeCamp", "fAltCampRadius", szTemp, szDebugName);
    WritePrivateProfileString("MakeCamp", "bCampPlayer", bCampPlayer ? "true" : "false", szDebugName);
    WritePrivateProfileString("MakeCamp", "ulCampPlayerID", itoa((int)ulCampPlayerID, szTemp, 10), szDebugName);
    WritePrivateProfileString("MakeCamp", "bMakeCampReturn", bMakeCampReturn ? "true" : "false", szDebugName);
    WritePrivateProfileString("MakeCamp", "bAltCampReturn", bAltCampReturn ? "true" : "false", szDebugName);
    WritePrivateProfileString("MakeCamp", "bLeash", bLeash ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fLeashLength);
    WritePrivateProfileString("MakeCamp", "fLeashLength", szTemp, szDebugName);
    WritePrivateProfileString("MakeCamp", "bScatter", bScatter ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fBearing);
    WritePrivateProfileString("MakeCamp", "fBearing", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fScatter);
    WritePrivateProfileString("MakeCamp", "fScatter", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fScatDist);
    WritePrivateProfileString("MakeCamp", "fScatDist", szTemp, szDebugName);
    WritePrivateProfileString("MakeCamp", "bReturnNoAggro", bReturnNoAggro ? "true" : "false", szDebugName);
    WritePrivateProfileString("MakeCamp", "bReturnHaveTarget", bReturnHaveTarget ? "true" : "false", szDebugName);
    WritePrivateProfileString("MakeCamp", "bReturnNotLoot", bReturnNotLoot ? "true" : "false", szDebugName);
    WritePrivateProfileString("MoveTo", "bMoveToOn", bMoveToOn ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fMoveLocYXZ[LocY]);
    WritePrivateProfileString("MoveTo", "fMoveLocYXZ[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fMoveLocYXZ[LocX]);
    WritePrivateProfileString("MoveTo", "fMoveLocYXZ[LocX]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fMoveDist);
    WritePrivateProfileString("MoveTo", "fMoveDist", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fMoveDistINI);
    WritePrivateProfileString("MoveTo", "fMoveDistINI", szTemp, szDebugName);
    WritePrivateProfileString("MoveTo", "bYPrecision", bYPrecision ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fMoveDistY);
    WritePrivateProfileString("MoveTo", "fMoveDistY", szTemp, szDebugName);
    WritePrivateProfileString("MoveTo", "bXPrecision", bXPrecision ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fMoveDistX);
    WritePrivateProfileString("MoveTo", "fMoveDistX", szTemp, szDebugName);
    WritePrivateProfileString("MoveTo", "bWalkMoveTo", bWalkMoveTo ? "true" : "false", szDebugName);
    WritePrivateProfileString("MoveTo", "bLooseMoveTo", bLooseMoveTo ? "true" : "false", szDebugName);
    WritePrivateProfileString("MoveTo", "bPermLooseMoveTo", bPermLooseMoveTo ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fMoveDistMod);
    WritePrivateProfileString("MoveTo", "fMoveDistMod", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fMoveDistModINI);
    WritePrivateProfileString("MoveTo", "fMoveDistModINI", szTemp, szDebugName);
    WritePrivateProfileString("Circle", "bCircling", bCircling ? "true" : "false", szDebugName);
    sprintf(szTemp, "%.2f", fCircleYX[LocY]);
    WritePrivateProfileString("Circle", "fCircleYX[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fCircleYX[LocX]);
    WritePrivateProfileString("Circle", "fCircleYX[LocX]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fCircleRadius);
    WritePrivateProfileString("Circle", "fCircleRadius", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fCircleRadiusMod);
    WritePrivateProfileString("Circle", "fCircleRadiusMod", szTemp, szDebugName);
    WritePrivateProfileString("Circle", "bBackwards", bBackwards ? "true" : "false", szDebugName);
    WritePrivateProfileString("Circle", "bClockwise", bClockwise ? "true" : "false", szDebugName);
    WritePrivateProfileString("Circle", "bDrunken", bDrunken ? "true" : "false", szDebugName);
    WritePrivateProfileString("Circle", "bAlwaysDrunk", bAlwaysDrunk ? "true" : "false", szDebugName);
    WritePrivateProfileString("Circle", "bAlwaysBack", bAlwaysBack ? "true" : "false", szDebugName);
    WritePrivateProfileString("Circle", "bAlwaysCCW", bAlwaysCCW ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "bStickWasOn", bStickWasOn ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "bCirclingWasOn", bCirclingWasOn ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "bCmdMovedFwd", bCmdMovedFwd ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "bTryToJump", bTryToJump ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "bTurnHalf", bTurnHalf ? "true" : "false", szDebugName);
    WritePrivateProfileString("StuckLogic", "uiPulseCheck", itoa((int)uiPulseCheck, szTemp, 10), szDebugName);
    WritePrivateProfileString("StuckLogic", "uiPulseUnstuck", itoa((int)uiPulseUnstuck, szTemp, 10), szDebugName);
    WritePrivateProfileString("StuckLogic", "uiStuck", itoa((int)uiStuck, szTemp, 10), szDebugName);
    WritePrivateProfileString("StuckLogic", "uiStuckDec", itoa((int)uiStuckDec, szTemp, 10), szDebugName);
    sprintf(szTemp, "%.2f", fDistStuck);
    WritePrivateProfileString("StuckLogic", "fDistStuck", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fPulseAvg);
    WritePrivateProfileString("StuckLogic", "fPulseAvg", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fStuckTurn);
    WritePrivateProfileString("StuckLogic", "fStuckTurn", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fOrigHead);
    WritePrivateProfileString("StuckLogic", "fOrigHead", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fPrevYXZ[LocY]);
    WritePrivateProfileString("StuckLogic", "fPrevYXZ[LocY]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fPrevYXZ[LocX]);
    WritePrivateProfileString("StuckLogic", "fPrevYXZ[LocX]", szTemp, szDebugName);
    sprintf(szTemp, "%.2f", fPrevYXZ[LocZ]);
    WritePrivateProfileString("StuckLogic", "fPrevYXZ[LocZ]", szTemp, szDebugName);
}

void SaveConfig()
{
    PCHARINFO pOurChar = (PCHARINFO)pCharData;
    if (!pOurChar) return;
    char szTemp[MAX_STRING] = {0};
    char szCharName[MAX_STRING] = {0};
    sprintf(szCharName, "%s.%s", EQADDR_SERVERNAME, pOurChar->Name);

    // default settings
    sprintf(szTemp, "%.2f", fAllowMove);
    WritePrivateProfileString("Defaults", "AllowMove", szTemp, INIFileName);
    WritePrivateProfileString("Defaults", "AutoPause", bAutoPause ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "AutoPauseMsg", bAutoPauseOutput ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "AutoSave", bAutoSave ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "AutoUW", bAutoUW ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "BreakKeyboard", bBreakOnKB ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "BreakMouse", bBreakOnMouse ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "BreakOnSummon", bBreakOnSummon ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fBreakSummonDist);
    WritePrivateProfileString("Defaults", "BreakSummonDist", szTemp, INIFileName);
    WritePrivateProfileString("Defaults", "FeignSupport", bFeignSupport ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "HideHelp", bHideHelp ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "KeyboardPause", bPauseOnMove ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "MousePause", bPauseOnMouse ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "PauseMinDelay", itoa(iPauseDelay[MIN], szTemp, 10), INIFileName);
    WritePrivateProfileString("Defaults", "PauseMaxDelay", itoa(iPauseDelay[MAX], szTemp, 10), INIFileName);
    WritePrivateProfileString("Defaults", "SaveByChar", bSaveByChar ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "TrueHeading", bPermTrueHead ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fTurnRate);
    WritePrivateProfileString("Defaults", "TurnRate", szTemp, INIFileName);
    WritePrivateProfileString("Defaults", "Verbosity", bVerbosity ? "on" : "off", INIFileName);
    WritePrivateProfileString("Defaults", "UseWindow", bUseWindow ? "on" : "off", INIFileName);
    // FullVerbosity is more frequent, detailed output, and differs from Verbosity
    // Setting one does not include the text of the other.
    WritePrivateProfileString("Defaults", "FullVerbosity", bFullVerbosity ? "on" : "off", INIFileName);
    // Total Silence writes no output except critical or user-requested
    WritePrivateProfileString("Defaults", "TotalSilence", bTotalSilence ? "on" : "off", INIFileName);

    // stick settings
    WritePrivateProfileString("Stick", "AwareNotAggro", bSpinInCircles ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fBackupDist);
    WritePrivateProfileString("Stick", "BackupDist", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fBehindArc);
    WritePrivateProfileString("Stick", "BehindArc", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fBreakDist);
    WritePrivateProfileString("Stick", "BreakDist", szTemp, INIFileName);
    WritePrivateProfileString("Stick", "BreakOnGate", bBreakOnGate ? "on" : "off", INIFileName);
    WritePrivateProfileString("Stick", "BreakOnWarp", bBreakOnWarp ? "on" : "off", INIFileName);
    WritePrivateProfileString("Stick", "PauseOnWarp", bPauseOnWarp ? "on" : "off", INIFileName);
    WritePrivateProfileString("Stick", "DelayStrafe", bStrafeDelay ? "on" : "off", INIFileName);
    WritePrivateProfileString("Stick", "StrafeMinDelay", itoa(iStrafeDelay[MIN], szTemp, 10), INIFileName);
    WritePrivateProfileString("Stick", "StrafeMaxDelay", itoa(iStrafeDelay[MAX], szTemp, 10), INIFileName);
    WritePrivateProfileString("Stick", "LooseStick", bPermLooseStick ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fNotFrontArc);
    WritePrivateProfileString("Stick", "NotFrontArc", szTemp, INIFileName);
    WritePrivateProfileString("Stick", "RandomArc", bRandomizeArc ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fSnapDist);
    WritePrivateProfileString("Stick", "SnaprollDist", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fStickDistMod);
    WritePrivateProfileString("Stick", "StickDistMod", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fStickDistModP);
    WritePrivateProfileString("Stick", "StickDistModPercent", szTemp, INIFileName);
    WritePrivateProfileString("Stick", "UseBackward", bUseBack ? "on" : "off", INIFileName);

    // makecamp settings
    sprintf(szTemp, "%.2f", fCampRadius);
    WritePrivateProfileString("MakeCamp", "CampRadius", szTemp, INIFileName);
    WritePrivateProfileString("MakeCamp", "MinDelay", itoa(iCampDelay[MIN], szTemp, 10), INIFileName);
    WritePrivateProfileString("MakeCamp", "MaxDelay", itoa(iCampDelay[MAX], szTemp, 10), INIFileName);
    WritePrivateProfileString("MakeCamp", "ReturnNoAggro", bReturnNoAggro ? "on" : "off", INIFileName);
    WritePrivateProfileString("MakeCamp", "ReturnHaveTarget", bReturnHaveTarget ? "on" : "off", INIFileName);
    WritePrivateProfileString("MakeCamp", "ReturnNotLooting", bReturnNotLoot ? "on" : "off", INIFileName);
    WritePrivateProfileString("MakeCamp", "UseLeash", bLeash ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fLeashLength);
    WritePrivateProfileString("MakeCamp", "LeashLength", szTemp, INIFileName);
    // scatter values
    WritePrivateProfileString("MakeCamp", "UseScatter", bScatter ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fBearing);
    WritePrivateProfileString("MakeCamp", "Bearing", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fScatDist);
    WritePrivateProfileString("MakeCamp", "ScatDist", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fScatter);
    WritePrivateProfileString("MakeCamp", "ScatSize", szTemp, INIFileName);

    // moveto settings
    sprintf(szTemp, "%.2f", fMoveDistINI);
    WritePrivateProfileString("MoveTo", "ArrivalDist", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fMoveDistX);
    WritePrivateProfileString("MoveTo", "ArrivalDistX", szTemp, INIFileName);
    sprintf(szTemp, "%.2f", fMoveDistY);
    WritePrivateProfileString("MoveTo", "ArrivalDistY", szTemp, INIFileName);
    WritePrivateProfileString("MoveTo", "BreakOnAggro", bBreakOnAggro ? "on" : "off", INIFileName);
    WritePrivateProfileString("MoveTo", "BreakOnHit", bBreakOnHit ? "on" : "off", INIFileName);
    WritePrivateProfileString("MoveTo", "LooseMoveTo", bPermLooseMoveTo ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fMoveDistMod);
    WritePrivateProfileString("MoveTo", "MoveToMod", szTemp, INIFileName);
    WritePrivateProfileString("MoveTo", "UseWalk", bWalkMoveTo ? "on" : "off", INIFileName);

    // circle settings
    WritePrivateProfileString("Circle", "AlwaysBackwards",  bAlwaysBack ? "on" : "off", INIFileName);
    WritePrivateProfileString("Circle", "AlwaysCounterClockwise", bAlwaysCCW ? "on" : "off", INIFileName);
    WritePrivateProfileString("Circle", "AlwaysDrunken", bAlwaysDrunk ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fCircleRadius);
    WritePrivateProfileString("Circle", "RadiusSize", szTemp, INIFileName);

    // stuck logic related
    WritePrivateProfileString("StuckLogic", "StuckLogic", bCheckStuck ? "on" : "off", INIFileName);
    sprintf(szTemp, "%.2f", fDistStuck);
    WritePrivateProfileString("StuckLogic", "DistStuck", szTemp, INIFileName);
    WritePrivateProfileString("StuckLogic", "PulseCheck", itoa((int)uiPulseCheck, szTemp, 10), INIFileName);
    WritePrivateProfileString("StuckLogic", "PulseUnstuck", itoa((int)uiPulseUnstuck, szTemp, 10), INIFileName);
    WritePrivateProfileString("StuckLogic", "TryToJump", bTryToJump ? "on" : "off", INIFileName);
    WritePrivateProfileString("StuckLogic", "TurnHalf", bTurnHalf ? "on" : "off", INIFileName);

    // Character specific
    if (bSaveByChar)
    {
        sprintf(szTemp, "%.2f", fAllowMove);
        WritePrivateProfileString(szCharName, "AllowMove", szTemp, INIFileName);
        WritePrivateProfileString(szCharName, "AutoSave", bAutoSave ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "AutoUW", bAutoUW ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fBehindArc);
        WritePrivateProfileString(szCharName, "BehindArc", szTemp, INIFileName);
        sprintf(szTemp, "%.2f", fBreakDist);
        WritePrivateProfileString(szCharName, "BreakDist", szTemp, INIFileName);
        WritePrivateProfileString(szCharName, "BreakOnGate", bBreakOnGate ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "BreakOnWarp", bBreakOnWarp ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "PauseOnWarp", bPauseOnWarp ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fNotFrontArc);
        WritePrivateProfileString(szCharName, "NotFrontArc", szTemp, INIFileName);
        WritePrivateProfileString(szCharName, "FeignSupport", bFeignSupport ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fSnapDist);
        WritePrivateProfileString(szCharName, "SnaprollDist", szTemp, INIFileName);
        WritePrivateProfileString(szCharName, "TrueHeading", bPermTrueHead ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fLeashLength);
        WritePrivateProfileString(szCharName, "LeashLength", szTemp, INIFileName);
        WritePrivateProfileString(szCharName, "UseLeash", bLeash ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "UseWindow", bUseWindow ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "Verbosity", bVerbosity ? "on" : "off", INIFileName);
        WritePrivateProfileString(szCharName, "FullVerbosity", bFullVerbosity ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fCampRadius);
        WritePrivateProfileString(szCharName, "CampRadius", szTemp, INIFileName);
        // scatter values
        WritePrivateProfileString(szCharName, "UseScatter", bScatter ? "on" : "off", INIFileName);
        sprintf(szTemp, "%.2f", fBearing);
        WritePrivateProfileString(szCharName, "Bearing", szTemp, INIFileName);
        sprintf(szTemp, "%.2f", fScatDist);
        WritePrivateProfileString(szCharName, "ScatDist", szTemp, INIFileName);
        sprintf(szTemp, "%.2f", fScatter);
        WritePrivateProfileString(szCharName, "ScatSize", szTemp, INIFileName);
    }

    if (bUseWindow) SaveOurWnd();
}

void LoadConfig()
{
    PCHARINFO pOurChar = (PCHARINFO)pCharData;
    char szTemp[MAX_STRING] = {0};
    char szTemp2[MAX_STRING] = {0};
    char szCharName[MAX_STRING] = {0};
    sprintf(szCharName, "%s.%s", EQADDR_SERVERNAME, pOurChar->Name);
    bool bRewriteIni = false; // re-save if bad values were read

    // default settings
    sprintf(szTemp2, "%.2f", fAllowMove);
    GetPrivateProfileString("Defaults", "AllowMove", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) > 10.0f)
    {
        fAllowMove = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("Defaults", "AutoPause", bAutoPause ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAutoPause = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "AutoPauseMsg", bAutoPauseOutput ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAutoPauseOutput = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "AutoSave", bAutoSave ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAutoSave = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "AutoUW", bAutoUW ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAutoUW = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "BreakKeyboard", bBreakOnKB ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnKB = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "BreakMouse", bBreakOnMouse ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnMouse = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "BreakOnSummon", bBreakOnSummon ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnSummon = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fBreakSummonDist);
    GetPrivateProfileString("Defaults", "BreakSummonDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 2.0f)
    {
        fBreakSummonDist = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("Defaults", "FeignSupport", bFeignSupport ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bFeignSupport = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "HideHelp", bHideHelp ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bHideHelp = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "KeyboardPause", bPauseOnMove ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bPauseOnMove = (!strnicmp(szTemp, "on", 3));
    if (bPauseOnMove) bBreakOnKB = false;
    GetPrivateProfileString("Defaults", "MousePause", bPauseOnMouse ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bPauseOnMouse = (!strnicmp(szTemp, "on", 3));
    if (bPauseOnMouse) bBreakOnMouse = false;
    iPauseDelay[MAX] = GetPrivateProfileInt("Defaults", "PauseMaxDelay", iPauseDelay[MAX], INIFileName);
    iPauseDelay[MIN] = GetPrivateProfileInt("Defaults", "PauseMinDelay", iPauseDelay[MIN], INIFileName);
    GetPrivateProfileString("Defaults", "SaveByChar", bSaveByChar ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bSaveByChar = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Defaults", "TrueHeading", bPermTrueHead ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bPermTrueHead = (!strnicmp(szTemp, "on", 3));
    bTrueHead = bPermTrueHead;
    if (bTrueHead) bLooseStick = bLooseMoveTo = bPermLooseStick = bPermLooseMoveTo = false;
    sprintf(szTemp2, "%.2f", fTurnRate);
    GetPrivateProfileString("Defaults", "TurnRate", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f && (float)atof(szTemp) <= 100.0f)
    {
        fTurnRate = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("Defaults", "UseWindow", bUseWindow ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bUseWindow = (!strnicmp(szTemp, "on", 3));
    //support conversion of Verbosity to on/off
    GetPrivateProfileString("Defaults", "Verbosity", bVerbosity ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    if (!strnicmp(szTemp, "1", 2)) 
    {
        bVerbosity = bRewriteIni = true;
    }
    else if (!strnicmp(szTemp, "0", 2))
    {
        bVerbosity = false;
        bRewriteIni = true;
    }
    else
    {
        bVerbosity = (!strnicmp(szTemp, "on", 3));
    }
    // FullVerbosity is more frequent, detailed output, and differs from Verbosity
    // Setting one does not include the text of the other.
    GetPrivateProfileString("Defaults", "FullVerbosity", bFullVerbosity ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bFullVerbosity = (!strnicmp(szTemp, "on", 3));
    // Total Silence disables all output except extreme error messages and BreakOnSummon
    GetPrivateProfileString("Defaults", "TotalSilence", bTotalSilence ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bTotalSilence = (!strnicmp(szTemp, "on", 3));
    if (bTotalSilence && (bVerbosity || bFullVerbosity))
    {
        bRewriteIni = true;
        bVerbosity = bFullVerbosity = false;
    }

    // stick settings
    GetPrivateProfileString("Stick", "AwareNotAggro", bSpinInCircles ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bSpinInCircles = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fBackupDist);
    GetPrivateProfileString("Stick", "BackupDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fBackupDist = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fBehindArc);
    GetPrivateProfileString("Stick", "BehindArc", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) > 5.0f && (float)atof(szTemp) < 260.0f)
    {
        fBehindArc = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fBreakDist);
    GetPrivateProfileString("Stick", "BreakDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fBreakDist = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("Stick", "BreakOnGate", bBreakOnGate ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnGate = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("Stick", "BreakOnWarp", bBreakOnWarp ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnWarp = (!strnicmp(szTemp, "on", 3));
    if (bBreakOnWarp) bPauseOnWarp = false;
    GetPrivateProfileString("Stick", "PauseOnWarp", bPauseOnWarp ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bPauseOnWarp = (!strnicmp(szTemp, "on", 3));
    if (bPauseOnWarp) bBreakOnWarp = false;
    if (!bTrueHead)
    {
        GetPrivateProfileString("Stick", "LooseStick", bPermLooseStick ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bPermLooseStick = (!strnicmp(szTemp, "on", 3));
        bLooseStick = bPermLooseStick;
    }
    sprintf(szTemp2, "%.2f", fNotFrontArc);
    GetPrivateProfileString("Stick", "NotFrontArc", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) > 5.0f && (float)atof(szTemp) < 260.0f)
    {
        fNotFrontArc = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    // stick delays
    GetPrivateProfileString("Stick", "DelayStrafe", bStrafeDelay ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bStrafeDelay = (!strnicmp(szTemp, "on", 3));
    iStrafeDelay[MAX] = GetPrivateProfileInt("Stick", "StrafeMaxDelay", iStrafeDelay[MAX], INIFileName);
    iStrafeDelay[MIN] = GetPrivateProfileInt("Stick", "StrafeMinDelay", iStrafeDelay[MIN], INIFileName);
    // end stick delays
    GetPrivateProfileString("Stick", "RandomArc", bRandomizeArc ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bRandomizeArc = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fSnapDist);
    GetPrivateProfileString("Stick", "SnaprollDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fSnapDist = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fStickDistMod);
    GetPrivateProfileString("Stick", "StickDistMod", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 0.0f)
    {
        fStickDistMod = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fStickDistModP);
    GetPrivateProfileString("Stick", "StickDistModPercent", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 0)
    {
        fStickDistModP = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("Stick", "UseBackward", bUseBack ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bUseBack = (!strnicmp(szTemp, "on", 3));

    // makecamp settings
    sprintf(szTemp2, "%.2f", fCampRadius);
    GetPrivateProfileString("MakeCamp", "CampRadius", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 10.0f)
    {
        fCampRadius = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    iCampDelay[MAX] = GetPrivateProfileInt("MakeCamp", "MaxDelay", iCampDelay[MAX], INIFileName);
    iCampDelay[MIN] = GetPrivateProfileInt("MakeCamp", "MinDelay", iCampDelay[MIN], INIFileName);
    GetPrivateProfileString("MakeCamp", "ReturnHaveTarget", bReturnHaveTarget ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bReturnHaveTarget = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("MakeCamp", "ReturnNoAggro", bReturnNoAggro ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bReturnNoAggro = (!strnicmp(szTemp, "on", 3));;
    GetPrivateProfileString("MakeCamp", "ReturnNotLooting", bReturnNotLoot ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bReturnNotLoot = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("MakeCamp", "UseLeash", bLeash ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bLeash = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fLeashLength);
    GetPrivateProfileString("MakeCamp", "LeashLength", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= fCampRadius)
    {
        fLeashLength = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    // scatter configuration (makecamp)
    GetPrivateProfileString("MakeCamp", "UseScatter", bScatter ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bScatter = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fBearing);
    GetPrivateProfileString("MakeCamp", "Bearing", szTemp2, szTemp, MAX_STRING, INIFileName);
    fBearing = (float)atof(szTemp);
    sprintf(szTemp2, "%.2f", fScatDist);
    GetPrivateProfileString("MakeCamp", "ScatDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fScatDist = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fScatter);
    GetPrivateProfileString("MakeCamp", "ScatSize", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fScatter = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }

    // moveto settings
    sprintf(szTemp2, "%.2f", fMoveDistINI);
    GetPrivateProfileString("MoveTo", "ArrivalDist", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fMoveDistINI = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    fMoveDist = fMoveDistINI; // hold settings for endpreviouscmd
    sprintf(szTemp2, "%.2f", fMoveDistX);
    GetPrivateProfileString("MoveTo", "ArrivalDistX", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fMoveDistX = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%.2f", fMoveDistY);
    GetPrivateProfileString("MoveTo", "ArrivalDistY", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 1.0f)
    {
        fMoveDistY = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("MoveTo", "BreakOnAggro", bBreakOnAggro ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnAggro = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("MoveTo", "BreakOnHit", bBreakOnHit ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bBreakOnHit = (!strnicmp(szTemp, "on", 3));
    if (!bTrueHead)
    {
        GetPrivateProfileString("MoveTo", "LooseMoveTo", bPermLooseMoveTo ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bPermLooseMoveTo = (!strnicmp(szTemp, "on", 3));
        bLooseMoveTo = bPermLooseMoveTo;
    }
    sprintf(szTemp2, "%.2f", fMoveDistModINI);
    GetPrivateProfileString("MoveTo", "MoveToMod", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 0.0f)
    {
        fMoveDistModINI = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    fMoveDistMod = fMoveDistModINI; // save value for endpreviouscmd
    GetPrivateProfileString("MoveTo", "UseWalk", bWalkMoveTo ? "on" : "off", szTemp, MAX_STRING, INIFileName);

    // circle settings
    GetPrivateProfileString("Circle", "AlwaysBackwards", bAlwaysBack ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAlwaysBack = (!strnicmp(szTemp, "on", 3));
    if (bAlwaysBack) bBackwards = true; // turn these on initially, use state bool in endcmd to enforce
    GetPrivateProfileString("Circle", "AlwaysCounterClockwise", bAlwaysCCW ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAlwaysCCW = (!strnicmp(szTemp, "on", 3));
    if (bAlwaysCCW) bClockwise = false; // turn these on initially, use state bool in endcmd to enforce
    GetPrivateProfileString("Circle", "AlwaysDrunken", bAlwaysDrunk ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bAlwaysDrunk = (!strnicmp(szTemp, "on", 3));
    if (bAlwaysDrunk) bDrunken = true; // turn these on initially, use state bool in endcmd to enforce
    sprintf(szTemp, "%.2f", fCircleRadius);
    GetPrivateProfileString("Circle", "RadiusSize", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) >= 5.0f)
    {
        fCircleRadius = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }

    // stuck logic related
    GetPrivateProfileString("StuckLogic", "StuckLogic", "on", szTemp, MAX_STRING, INIFileName);
    bCheckStuck = (!strnicmp(szTemp, "on", 3));
    sprintf(szTemp2, "%.2f", fDistStuck);
    GetPrivateProfileString("StuckLogic", "DistStuck", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((float)atof(szTemp) > 0.0f)
    {
        fDistStuck = (float)atof(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%uh", uiPulseCheck);
    GetPrivateProfileString("StuckLogic", "PulseCheck", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((unsigned int)atoi(szTemp) > 1)
    {
        uiPulseCheck = (unsigned int)atoi(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    sprintf(szTemp2, "%uh", uiPulseUnstuck);
    GetPrivateProfileString("StuckLogic", "PulseUnstuck", szTemp2, szTemp, MAX_STRING, INIFileName);
    if ((unsigned int)atoi(szTemp) > 1)
    {
        uiPulseUnstuck = (unsigned int)atoi(szTemp);
    }
    else
    {
        bRewriteIni = true;
    }
    GetPrivateProfileString("StuckLogic", "TryToJump", bTryToJump ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bTryToJump = (!strnicmp(szTemp, "on", 3));
    GetPrivateProfileString("StuckLogic", "TurnHalf", bTurnHalf ? "on" : "off", szTemp, MAX_STRING, INIFileName);
    bTurnHalf = (!strnicmp(szTemp, "on", 3));

    // check if we want to explicitly ignore this char's custom ini
    GetPrivateProfileString(szCharName, "DisregardMe", "false", szTemp, MAX_STRING, INIFileName);
    bDisregardMe = (!strnicmp(szTemp, "true", 4));

    // Character specific
    if (bSaveByChar && !bDisregardMe)
    {
        sprintf(szTemp2, "%.2f", fAllowMove);
        GetPrivateProfileString(szCharName, "AllowMove", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) > 10.0f)
        {
            fAllowMove = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        GetPrivateProfileString(szCharName, "AutoSave", bAutoSave ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bAutoSave = (!strnicmp(szTemp, "on", 3));
        GetPrivateProfileString(szCharName, "AutoUW", bAutoUW ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bAutoUW = (!strnicmp(szTemp, "on", 3));
        sprintf(szTemp2, "%.2f", fBreakDist);
        GetPrivateProfileString(szCharName, "BreakDist", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= 1.0f)
        {
            fBreakDist = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        GetPrivateProfileString(szCharName, "BreakOnGate", bBreakOnGate ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bBreakOnGate = (!strnicmp(szTemp, "on", 3));
        GetPrivateProfileString(szCharName, "BreakOnWarp", bBreakOnWarp ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bBreakOnWarp = (!strnicmp(szTemp, "on", 3));
        if (bBreakOnWarp) bPauseOnWarp = false;
        GetPrivateProfileString(szCharName, "PauseOnWarp", bPauseOnWarp ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bPauseOnWarp = (!strnicmp(szTemp, "on", 3));
        if (bPauseOnWarp) bBreakOnWarp = false;
        sprintf(szTemp2, "%.2f", fBehindArc);
        GetPrivateProfileString(szCharName, "BehindArc", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) > 5.0f && (float)atof(szTemp) < 260.0f)
        {
            fBehindArc = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        sprintf(szTemp2, "%.2f", fNotFrontArc);
        GetPrivateProfileString(szCharName, "NotFrontArc", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) > 5.0f && (float)atof(szTemp) < 260.0f)
        {
            fNotFrontArc = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        sprintf(szTemp2, "%.2f", fSnapDist);
        GetPrivateProfileString(szCharName, "SnaprollDist", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= 1.0f)
        {
            fSnapDist = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        GetPrivateProfileString(szCharName, "FeignSupport", bFeignSupport ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bFeignSupport = (!strnicmp(szTemp, "on", 3));
        GetPrivateProfileString(szCharName, "TrueHeading", bPermTrueHead ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bPermTrueHead = (!strnicmp(szTemp, "on", 3));
        bTrueHead = bPermTrueHead;
        if (bTrueHead) bLooseStick = bLooseMoveTo = bPermLooseStick = bPermLooseMoveTo = false;
        sprintf(szTemp2, "%.2f", fLeashLength);
        GetPrivateProfileString(szCharName, "LeashLength", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= fCampRadius)
        {
            fLeashLength = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        GetPrivateProfileString(szCharName, "UseLeash", bLeash ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bLeash = (!strnicmp(szTemp, "on", 3));
        GetPrivateProfileString(szCharName, "UseWindow", bUseWindow ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bUseWindow = (!strnicmp(szTemp, "on", 3));
        //support conversion of Verbosity to on/off
        GetPrivateProfileString(szCharName, "Verbosity", bVerbosity ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        if (!strnicmp(szTemp, "1", 2))
        {
            bVerbosity = bRewriteIni = true;
        }
        else if (!strnicmp(szTemp, "0", 2))
        {
            bVerbosity = false;
            bRewriteIni = true;
        }
        else
        {
            GetPrivateProfileString(szCharName, "Verbosity", bVerbosity ? "on" : "off", szTemp, MAX_STRING, INIFileName);
            bVerbosity = (!strnicmp(szTemp, "on", 3));
        }
        sprintf(szTemp2, "%.2f", fCampRadius);
        GetPrivateProfileString(szCharName, "CampRadius", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= 10.0f)
        {
            fCampRadius = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        // scatter configuration (makecamp)
        GetPrivateProfileString(szCharName, "UseScatter", bScatter ? "on" : "off", szTemp, MAX_STRING, INIFileName);
        bScatter = (!strnicmp(szTemp, "on", 3));
        sprintf(szTemp2, "%.2f", fBearing);
        GetPrivateProfileString(szCharName, "Bearing", szTemp2, szTemp, MAX_STRING, INIFileName);
        fBearing = (float)atof(szTemp);
        sprintf(szTemp2, "%.2f", fScatDist);
        GetPrivateProfileString(szCharName, "ScatDist", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= 1.0f)
        {
            fScatDist = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
        sprintf(szTemp2, "%.2f", fScatter);
        GetPrivateProfileString(szCharName, "ScatSize", szTemp2, szTemp, MAX_STRING, INIFileName);
        if ((float)atof(szTemp) >= 1.0f)
        {
            fScatter = (float)atof(szTemp);
        }
        else
        {
            bRewriteIni = true;
        }
    }

    if (bRewriteIni) SaveConfig();
    if (bUseWindow) CreateOurWnd();
}

void SpewMTError(unsigned char ucErrorNum, unsigned char ucFilterLevel)
{
    // this function exists to avoid the duplicate code for error messages that are used multiple places
    // any one-time error will still be in its own logic for clarity
    char szErrorMsg[MAX_STRING] = {0};

    switch(ucErrorNum)
    {
    case 1:
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) You cannot stick to yourself!", MODULE_NAME);
        break;
    case 2:
        sprintf(szErrorMsg, "\ay%s\aw:: You must specify something to stick to!", MODULE_NAME);
        break;
    case 3:
        EndPreviousCmd(true);
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) \ay/moveto loc [ <Y> <X> [z] ]\ax was supplied incorrectly.", MODULE_NAME);
        break;
    case 4:
        ResetCamp(false);
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) \ay/makecamp loc [ <Y> <X> ]\ax was supplied incorrectly.", MODULE_NAME);
        break;
    case 5:
        EndPreviousCmd(true);
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) Usage \ay/circle loc [ <y> <x> ] [other options]\ax", MODULE_NAME);
        break;
    case 6:
        EndPreviousCmd(true);
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) Invalid SpawnID and do not have a valid target.", MODULE_NAME);
        break;
    case 7:
        ResetCamp(false);
        sprintf(szErrorMsg, "\ay%s\aw:: (\arERROR\ax) \ay/makecamp [mindelay|maxdelay] [#]\ax was supplied incorrectly.", MODULE_NAME);
        break;
    default:
        // return if we didnt pass a valid msg number
        return;
    }
    WriteLine(szErrorMsg, ucFilterLevel);
}

void SpewDebug(unsigned char ucDbgType, char* szOutput, ...)
{
    int i = 1;
    if (ucDbgType == DBG_ALL)
    {
#ifdef DEBUGALL
        DebugSpew(szOutput);
#endif
    }
    else if (ucDbgType == DBG_STUCK)
    {
#ifdef DEBUGSTUCK
        DebugSpew(szOutput);
#endif
    }
    else if (ucDbgType == DBG_OTHER)
    {
#ifdef DEBUGOTHER
        DebugSpew(szOutput);
#endif
    }
    else if (ucDbgType == DBG_DISABLE)
    {
        // do nothing with output
        i++;
    }
    else if (ucDbgType == DBG_NOHOTT)
    {
#ifdef DEBUGNOHOTT
        DebugSpew(szOutput);
#endif
    }
    else
    {
        i++;
    }
}

// End Output functions
///////////////////////////////////////
// Begin MQ2 Functions

inline void KeyKiller(int iKeyPressed)
{
    if (iKeyPressed == iForward)
    {
        DoMovement(KILL_STRAFE, false, false);
        DoMovement(GO_BACKWARD, false, false);
    }
    else if (iKeyPressed == iBackward)
    {
        DoMovement(KILL_STRAFE, false, false);
        DoMovement(GO_FORWARD, false, false);
    }
    else if (iKeyPressed == iStrafeLeft)
    {
        DoMovement(KILL_FB, false, false);
        DoMovement(GO_RIGHT, false, false);
    }
    else if (iKeyPressed == iStrafeRight)
    {
        DoMovement(KILL_FB, false, false);
        DoMovement(GO_LEFT, false, false);
    }
    else if (iKeyPressed == iAutoRun)
    {
        DoMovement(KILL_STRAFE, false, false);
    }
    else if (iKeyPressed == iTurnRight)
    {
        DoMovement(APPLY_TO_ALL, false, false);
        fAdjustHead = 10000.0f;
        *pulTurnLeft = 0;
        pKeypressHandler->CommandState[iTurnLeft] = 0;
        return;
    }
    else
    {
        // iTurnLeft
        DoMovement(APPLY_TO_ALL, false, false);
        fAdjustHead = 10000.0f;
        *pulTurnRight = 0;
        pKeypressHandler->CommandState[iTurnRight] = 0;
        return;
    }
    if (fAdjustHead != 10000.0f) fAdjustHead = ((PSPAWNINFO)pCharSpawn)->Heading;
}

void KeybindPressed(int iKeyPressed, int iKeyDown)
{
    if (!ValidIngame(false)) return;

    //fAdjustHead = 10000.0f; // true/loose fights for heading if not reset
    bool bCmdActive = ((bStickOn && (!bStickNewMob || bStickNewTarget)) || bMoveToOn || bCircling);
    static bool sbMoveKilled = false;

    if (!iKeyDown)
    {
        if (*pulForward || *pulBackward || *pulTurnLeft || *pulTurnRight || *pulStrafeLeft || *pulStrafeRight || *pulAutoRun)
        {
            return;
        }

        bManualMove = sbMoveKilled = false;
        if (!bPauseOnMove || (!bCmdActive && !bAutoReturning)) return; // dont trigger mpause if cmds inactive
        GetSystemTime(&stReturnTime);
        iPauseReturnTime = rand() % (iPauseDelay[MAX] - iPauseDelay[MIN] + 1) + iPauseDelay[MIN];
    }
    else
    {
        bManualMove = true; // so makecamp wont auto return
        // stop return time from kicking in if you let go of the key and repress again.
        iPauseReturnTime = 0;
        if (!bCmdActive && !bAutoReturning) return; // dont process if cmds inactive

        if (bPauseOnMove)
        {
            if (bMoveToOn && bLeash && bMakeCamp && !bAutoReturning) 
            {
                sprintf(szMsg, "\ay%s\aw:: Ended '/moveto' or '/makecamp return' because leash is on.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
                EndPreviousCmd(false);
            }
            bAllPaused = true;
            if (!sbMoveKilled)
            {
                KeyKiller(iKeyPressed);
                sbMoveKilled = true;
            }
        }
        else if (bBreakOnKB)
        {
            if (!bAutoReturning)
            {
                sprintf(szMsg, "\ay%s\aw:: Current command ended from manual movement.", MODULE_NAME);
                WriteLine(szMsg, VERB_FULL);
            }
            EndPreviousCmd(false); //EndPreviousCmd(true); // this stops kb input
            if (!sbMoveKilled)
            {
                KeyKiller(iKeyPressed);
                sbMoveKilled = true;
            }
        }
    }
}

void FwdWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iForward, iKeyDown);
}

void BckWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iBackward, iKeyDown);
}

void LftWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iTurnLeft, iKeyDown);
}

void RgtWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iTurnRight, iKeyDown);
}

void StrafeLftWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iStrafeLeft, iKeyDown);
}

void StrafeRgtWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iStrafeRight, iKeyDown);
}

void AutoRunWrapper(char* szName, int iKeyDown)
{
    KeybindPressed(iAutoRun, iKeyDown);
}

void DoKeybinds()
{
    if (bKeyBindsSet) return;
    AddMQ2KeyBind("MUTILS_FWD",        FwdWrapper);
    AddMQ2KeyBind("MUTILS_BCK",        BckWrapper);
    AddMQ2KeyBind("MUTILS_LFT",        LftWrapper);
    AddMQ2KeyBind("MUTILS_RGT",        RgtWrapper);
    AddMQ2KeyBind("MUTILS_STRAFE_LFT", StrafeLftWrapper);
    AddMQ2KeyBind("MUTILS_STRAFE_RGT", StrafeRgtWrapper);
    AddMQ2KeyBind("MUTILS_AUTORUN",    AutoRunWrapper);
    SetMQ2KeyBind("MUTILS_FWD",        FALSE, pKeypressHandler->NormalKey[iForward]);
    SetMQ2KeyBind("MUTILS_BCK",        FALSE, pKeypressHandler->NormalKey[iBackward]);
    SetMQ2KeyBind("MUTILS_LFT",        FALSE, pKeypressHandler->NormalKey[iTurnLeft]);
    SetMQ2KeyBind("MUTILS_RGT",        FALSE, pKeypressHandler->NormalKey[iTurnRight]);
    SetMQ2KeyBind("MUTILS_STRAFE_LFT", FALSE, pKeypressHandler->NormalKey[iStrafeLeft]);
    SetMQ2KeyBind("MUTILS_STRAFE_RGT", FALSE, pKeypressHandler->NormalKey[iStrafeRight]);
    SetMQ2KeyBind("MUTILS_AUTORUN",    FALSE, pKeypressHandler->NormalKey[iAutoRun]);
    SetMQ2KeyBind("MUTILS_FWD",        TRUE,  pKeypressHandler->AltKey[iForward]);
    SetMQ2KeyBind("MUTILS_BCK",        TRUE,  pKeypressHandler->AltKey[iBackward]);
    SetMQ2KeyBind("MUTILS_LFT",        TRUE,  pKeypressHandler->AltKey[iTurnLeft]);
    SetMQ2KeyBind("MUTILS_RGT",        TRUE,  pKeypressHandler->AltKey[iTurnRight]);
    SetMQ2KeyBind("MUTILS_STRAFE_LFT", TRUE,  pKeypressHandler->AltKey[iStrafeLeft]);
    SetMQ2KeyBind("MUTILS_STRAFE_RGT", TRUE,  pKeypressHandler->AltKey[iStrafeRight]);
    SetMQ2KeyBind("MUTILS_AUTORUN",    TRUE,  pKeypressHandler->AltKey[iAutoRun]);
    bKeyBindsSet = true;
}

inline void UndoKeybinds()
{
    if (!bKeyBindsSet) return;
    RemoveMQ2KeyBind("MUTILS_FWD");
    RemoveMQ2KeyBind("MUTILS_BCK");
    RemoveMQ2KeyBind("MUTILS_LFT");
    RemoveMQ2KeyBind("MUTILS_RGT");
    RemoveMQ2KeyBind("MUTILS_STRAFE_LFT");
    RemoveMQ2KeyBind("MUTILS_STRAFE_RGT");
    RemoveMQ2KeyBind("MUTILS_AUTORUN");
}

// credit: radioactiveman/bunny771/(dom1n1k?) --------------------------------
bool DataCompare(const unsigned char* pucData, const unsigned char* pucMask, const char* pszMask)
{
    for (; *pszMask; ++pszMask, ++pucData, ++pucMask)
        if (*pszMask == 'x' && *pucData != *pucMask) return false;
    return (*pszMask) == NULL;
}

unsigned long FindPattern(unsigned long ulAddress, unsigned long ulLen, unsigned char* pucMask, char* pszMask)
{
    for (unsigned long i = 0; i < ulLen; i++)
    {
        if (DataCompare((unsigned char*)(ulAddress + i), pucMask, pszMask)) return (unsigned long)(ulAddress + i);
    }
    return 0;
}
// --------------------------------------------------------------------------

// copyright: ieatacid ---------------------------------------------------------
unsigned long GetDWordAt(unsigned long ulAddress, unsigned long ulNumBytes)
{
    if (ulAddress)
    {
        ulAddress += ulNumBytes;
        return *(unsigned long*)ulAddress;
    }
    return 0;
}
// --------------------------------------------------------------------------

inline unsigned long FindPointers()
{
    if ((addrTurnRight   = FindPattern(0x420000, 0x100000, patternTurnRight, maskTurnRight)) == 0)     return 1;
    if ((pulTurnRight    = (unsigned long*)GetDWordAt(addrTurnRight, 1)) == 0)                         return 2;
    if ((pulStrafeLeft   = (unsigned long*)GetDWordAt(addrTurnRight, 7)) == 0)                         return 3;
    if ((pulStrafeRight  = (unsigned long*)GetDWordAt(addrTurnRight, 13)) == 0)                        return 4;
    if ((pulAutoRun      = (unsigned long*)GetDWordAt(addrTurnRight, 25)) == 0)                        return 5;
    if ((pulTurnLeft     = (unsigned long*)GetDWordAt(addrTurnRight, 40)) == 0)                        return 6;
    if ((addrMoveForward = FindPattern(0x420000, 0x100000, patternMoveForward, maskMoveForward)) == 0) return 7;
    if ((pulForward      = (unsigned long*)GetDWordAt(addrMoveForward, 1)) == 0)                       return 8;
    if (pulAutoRun      != (unsigned long*)GetDWordAt(addrMoveForward, 13))                            return 9;
    if ((pulBackward     = (unsigned long*)GetDWordAt(addrMoveForward, 28)) == 0)                      return 10;
    return 0;
}

inline void FindKeys()
{
    iForward     = FindMappableCommand("forward");
    iBackward    = FindMappableCommand("back");
    iAutoRun     = FindMappableCommand("autorun");
    iStrafeLeft  = FindMappableCommand("strafe_left");
    iStrafeRight = FindMappableCommand("strafe_right");
    iTurnLeft    = FindMappableCommand("left");
    iTurnRight   = FindMappableCommand("right");
    iJumpKey     = FindMappableCommand("jump");
    iDuckKey     = FindMappableCommand("duck");
    iRunWalk     = FindMappableCommand("run_walk");
}

PMQPLUGIN FindPlugin(char* PluginName)
{
    unsigned int uiLength = strlen(PluginName) + 1;
    PMQPLUGIN pLook = pPlugins;
    while (pLook && strnicmp(PluginName, pLook->szFilename, uiLength))
    {
        pLook = pLook->pNext;
    }
    return pLook;
}

PLUGIN_API void InitializePlugin()
{
    unsigned long ulFailedLoad = FindPointers();
    if (ulFailedLoad)
    {
        char szFailOutput[MAX_STRING] = {0};
        sprintf(szFailOutput, "\ay%s\aw:: Couldn't find movement pointer: \ar%s\ax -Unloading Plugin.", MODULE_NAME, szFailedLoad[ulFailedLoad]);
        WriteChatf(szFailOutput);
        MessageBox(NULL, szFailOutput, "MQ2MoveUtils v9.x", MB_OK);
        EzCommand("/timed 1 /plugin mq2moveutils unload");
    }
    else
    {
        AddCommand("/makecamp",  MakeCampWrapper, FALSE, TRUE, TRUE);
        AddCommand("/moveto",    MoveToWrapper,   FALSE, TRUE, TRUE);
        AddCommand("/stick",     StickWrapper,    FALSE, TRUE, TRUE);
        AddCommand("/circle",    CircleWrapper,   FALSE, TRUE, TRUE);
        AddCommand("/calcangle", CalcOurAngle,    FALSE, TRUE, TRUE);

        AddMQ2Data("Stick",     dataStick);
        AddMQ2Data("MakeCamp",  dataMakeCamp);
        AddMQ2Data("MoveTo",    dataMoveTo);
        AddMQ2Data("Circle",    dataCircling);
        AddMQ2Data("MoveUtils", dataMoveUtils);

        pStickType     = new MQ2StickType;
        pMakeCampType  = new MQ2MakeCampType;
        pMoveToType    = new MQ2MoveToType;
        pCircleType    = new MQ2CircleType;
        pMoveUtilsType = new MQ2MoveUtilsType;

        srand((unsigned int)time(0));
        GetSystemTime(&stPrevCirc);

        sprintf(szDebugName, "%s\\MQ2MoveUtils-debug.ini", gszINIPath);

        pbMULoaded = NULL;
        if (PMQPLUGIN pLook = FindPlugin("mq2melee"))
        {
            pbMULoaded = (bool *)GetProcAddress(pLook->hModule, "bMULoaded");
            if (pbMULoaded) *pbMULoaded = true;
        }

        DebugSpewAlways("%s v%1.4f Loaded", MODULE_NAME, MODULE_VERSION);
    }
}

PLUGIN_API void ShutdownPlugin()
{
    RemoveCommand("/makecamp");
    RemoveCommand("/moveto");
    RemoveCommand("/stick");
    RemoveCommand("/circle");
    RemoveCommand("/calcangle");

    RemoveMQ2Data("Stick");
    RemoveMQ2Data("MakeCamp");
    RemoveMQ2Data("MoveTo");
    RemoveMQ2Data("Circle");
    RemoveMQ2Data("MoveUtils");

    delete pStickType;
    delete pMoveToType;
    delete pMakeCampType;
    delete pCircleType;
    delete pMoveUtilsType;

    UndoKeybinds();
    SetupEvents(false, true);
    AdjustWalking(false); // sometimes the plugin turns walk on and if plugin unloaded while in game, turn walk off

    pbMULoaded = NULL;
    if (PMQPLUGIN pLook = FindPlugin("mq2melee"))
    {
        pbMULoaded = (bool *)GetProcAddress(pLook->hModule, "bMULoaded");
        if (pbMULoaded) *pbMULoaded = false;
    }

    DebugSpewAlways("%s v%1.4f Unloaded", MODULE_NAME, MODULE_VERSION);

    KillOurWnd(false);
}

PLUGIN_API void SetGameState(unsigned long ulGameState)
{
    if (ulGameState == GAMESTATE_INGAME)
    {
        FindKeys();
        DoKeybinds();
        LoadConfig();
        SetupEvents(true);
        // randomly flip arc flag
        if (rand() % 100 > 50) fRandArcFlag *= -1.0f;

        if (bUseWindow) CreateOurWnd();
    }
    else
    {
        if (bStickOn || bMoveToOn || bCircling || bMakeCamp)
        {
            sprintf(szMsg, "\ay%s\aw:: GameState change ended previous command.", MODULE_NAME);
            WriteLine(szMsg, VERB_FULL);
        }
        EndPreviousCmd(false); // using true here causes ctd
        ResetCamp(false, RESET_ALT);
        if (ulGameState == GAMESTATE_CHARSELECT)
        {
            KillOurWnd(true);
        }
    }
}

PLUGIN_API void OnReloadUI()
{
    if (bUseWindow) CreateOurWnd();
}

PLUGIN_API void OnCleanUI()
{
    KillOurWnd(true);
}

PLUGIN_API void OnZoned()
{
    EndPreviousCmd(false); // no need to use true here
    ResetCamp(false, RESET_ALT);
}

PLUGIN_API void OnRemoveSpawn(PSPAWNINFO pSpawn)
{
    if (!pSpawn->SpawnID) return;

    if (pSpawn->SpawnID == ulStickID)
    {
        ulStickID = 0;
        stickTarget_Type = NONE;
    }
    if (pSpawn->SpawnID == ulCampPlayerID)
    {
        ResetCamp(true);
    }
}

PLUGIN_API void OnPulse()
{
    // don't process commands if dead
    if (!ValidIngame())
    {
        if (bStickOn || bMoveToOn || bCircling || bMakeCampReturn || bAltCampReturn || bAutoReturning)
        {
            sprintf(szMsg, "\ay%s\aw:: \arYour untimely death has ended the previous command\ax.", MODULE_NAME);
            WriteLine(szMsg, VERB_ALL);
            EndPreviousCmd(false);
        }
        return;
    }

    PSPAWNINFO pChSpawn = (PSPAWNINFO)pCharSpawn;
    PSPAWNINFO psTarget = (PSPAWNINFO)pTarget;

    //DebugSpew("For - %x - Back - %x - Left - %x - Right - %x", pKeypressHandler->CommandState[iForward], pKeypressHandler->CommandState[iBackward], pKeypressHandler->CommandState[iStrafeLeft], pKeypressHandler->CommandState[iStrafeRight]);
    /*char szHead[100] = {0};
    sprintf(szHead, "%.4f", pChSpawn->Heading);
    DebugSpew("Heading %s", szHead);
    return;*/
    /*if (!psTarget) return;
    float fTempCalc = CalcAngularDist(psTarget->Heading, pChSpawn->Heading);
    DebugSpew("fAngDist: %.2f", fTempCalc);
    //return;*/
    // 19 = jump while moving, 20 = downwards fall from jump only
    // 24 = duck and START of jump, 71/32 = normal stand, 34 = turning left/right
    // 33 = on the way to sit, 38 = has sat down
    // 72 = shake the head shit after 5-6 seconds of standing still
    // 18 = run forward, 17=slow run (backwards, or end of run forward as decelerating, or strafe sideways)
    // 21 = falling through air
    //DebugSpew("Anim: %d, StandState: %d, pspawn anim: %d", pChSpawn->Animation, pChSpawn->StandState, pChSpawn->pSpawn->Animation);
    // values for  ->Animation and ->pSpawn->Animation are always the same
    // uncomment to spew real turn values using right/left keys
    // they do gradually increase if any want to try to solve the real scaling formula
    // otherwise 16.0 seems to be the max and a safe turn value
    /*static float fHeadChangeTime = pChSpawn->Heading;
    char szAnotherTemp[MAX_STRING] = {0};
    sprintf(szAnotherTemp, "%.2f", fHeadChangeTime - pChSpawn->Heading);
    DebugSpew("Heading Change %s Speed %.2f", szAnotherTemp, pChSpawn->SpeedHeading);
    fHeadChangeTime = pChSpawn->Heading;
    //return;*/

    // if fAdjustHead is set, continue heading adjustment
    if (fAdjustHead != 10000.0f) AdjustHeading(fAdjustHead);

    SYSTEMTIME stCurr;
    GetSystemTime(&stCurr);

    static bool sbStickNewMobMsg = false; // output if '/stick __ always' is on
    static bool sbHoldingMouse = false; // used by mousepause

    // check for ${MoveUtils.Aggro} TLO
    if (psTarget)
    {
        float fAngDist = CalcAngularDist(psTarget->Heading, pChSpawn->Heading);
        if (fabs(fAngDist) > 190.0f)
        {
            bImAggro = true;
        }
        else
        {
            bImAggro = false;
        }
    }
    else
    {
        bImAggro = false;
    }

    if (bMouseLook)
    {
        bManualMouse = true;
        if ((bPauseOnMouse || bBreakOnMouse) && (bCircling || (bStickOn && !bStickNewMob) || (bStickOn && bStickNewMob && bStickNewTarget) || bMoveToOn || bAutoReturning))
        {
            iPauseReturnTime = 0;   // stops return time from kicking in if you let go of mouse and reclick
            iCampReturnTime = 0;
            //fAdjustHead = 10000.0f; // stops loose from trying to fight with pause
            if (fAdjustHead != 10000.0f) fAdjustHead = ((PSPAWNINFO)pCharSpawn)->Heading;
            if (bPauseOnMouse)
            {
                if (!sbHoldingMouse)
                {
                    bAllPaused = sbHoldingMouse = true;
                    DoMovement(APPLY_TO_ALL, false, false); // stop walk/move when holding mouse
                }
            }
            else    //breakonmouse
            {
                EndPreviousCmd(true);
                sprintf(szMsg, "\ay%s\aw:: Current command ended from mouse movement.", MODULE_NAME);
                if (!bAutoReturning) WriteLine(szMsg, VERB_FULL);
            }
            return;
        }
    }
    else
    {
        bManualMouse = false;
    }

    if (sbHoldingMouse)     // mouselook has been letgo, set returntime, sbHoldingMouse is only set true if mousepause enabled
    {
        sbHoldingMouse = false;
        if (bMoveToOn || bCircling || bStickOn || bAutoReturning)
        {
            GetSystemTime(&stReturnTime);
            iPauseReturnTime = rand() % (iPauseDelay[MAX] - iPauseDelay[MIN] + 1) + iPauseDelay[MIN];
        }
    }

    //if currently paused, mpause on, manually moving released, and current time >= time set by release of movement key
    //checking against !zero value as /cmd pause will set iPauseReturnTime to 0
    if (bAllPaused && (bPauseOnMove || bPauseOnMouse) && !bManualMove && iPauseReturnTime != 0)
    {
        int iElapsedTime = diff_in_ms(stCurr, stReturnTime);
        // stReturnTime set from releasing keys in KeybindPressed
        if (iElapsedTime >= iPauseReturnTime)
        {
            bAllPaused = false;
            sprintf(szMsg, "\ay%s\aw:: Resuming previous command from movement pause.", MODULE_NAME);
            if (!bAutoReturning) WriteLine(szMsg, VERB_FULL);
            ResetFromPause();
        }
    }

    // don't process commands if plugin is paused
    if (!bAllPaused)
    {
        if (bStickOn)
        {
            // handling for /stick __ always
            if (bStickNewMob && (!psTarget || (psTarget && psTarget->Type != SPAWN_NPC)))
            {
                if (!sbStickNewMobMsg)
                {
                    sprintf(szMsg, "\ay%s\aw:: Stick awaiting next valid NPC target...", MODULE_NAME);
                    WriteLine(szMsg, VERB_FULL);
                    sbStickNewMobMsg = true;
                    bStickNewTarget = false;
                    DoMovement(APPLY_TO_ALL, false, false);
                    if (bRandomizeArc)
                    {
                        if (bStickNotFront)    SetRandArc(NOT_FRONT_ARC);
                        else if (bStickBehind) SetRandArc(BEHIND_ARC);
                        else if (bStickPin)    SetRandArc(PIN_ARC_MIN);
                    }
                }
                return;
            }
            else if (bStickNewMob && psTarget && psTarget->Type == SPAWN_NPC)
            {
                if (sbStickNewMobMsg)
                {
                    EndPreviousCmd(true, USED_STICK, true);
                    sbStickNewMobMsg = false;
                    bStickNewTarget = true;
                    ResetFromPause();
                    StandIfNeeded();
                    return;
                }
            }
            //end handling for /stick ___ always
            MainProcess(USED_STICK);
        }
        else if (bMoveToOn)
        {
            MainProcess(USED_MOVETO);
        }
        else if (bCircling)
        {
            if (bDrunken)
            {
                if (diff_in_ms(stCurr, stPrevCirc) > 900 + (int)GetRandomNumber(600.0f))
                {
                    GetSystemTime(&stPrevCirc);
                }
                else
                {
                    return;
                }
            }
            MainProcess(USED_CIRCLE);
        }
        else if (!bManualMouse && (bMakeCamp || bMakeCampReturn || bAltCampReturn))
        {
            MainProcess(USED_MAKECAMP); // this will process camp returns when no other commands active
        }
    }
}
// End MQ2 Functions
///////////////////////////////////////
