// This plugin is intended for VIP members of MacroQuest2.com only
// and may not be redistributed without permission of the author

#include "../MQ2Plugin.h"
const char* MODULE_NAME = "MQ2Spawns";
PreSetup(MODULE_NAME);

time_t Seconds;
__time64_t tCurrentTime;
unsigned long ulDelay = 10;
bool bSaveByChar = true;
bool bZoning = false;
bool bWatch = true;
bool bSpawned = true;
bool bDespawned = true;
bool bLocation = true;
bool bTimestamp = true;
bool bLoaded = false;
bool bWatchPC = true;
bool bWatchNPC = true;
bool bWatchMount = false;
bool bWatchPet = false;
bool bWatchMerc = false;
bool bWatchFlyer = false;
bool bWatchCampfire = false;
bool bWatchBanner = false;
bool bWatchAura = false;
bool bWatchObject = false;
bool bWatchUntarget = false;
bool bWatchChest = true;
bool bWatchTrap = true;
bool bWatchTimer = false;
bool bWatchTrigger = false;
bool bWatchCorpse = false;
bool bWatchItem = false;
bool bWatchUnknown = true;
bool bWatchAll = false;

// UI window
class CSpawnWnd;
CSpawnWnd* OurWnd = NULL;
class CSpawnWnd : public CCustomWnd
{
public:
    CStmlWnd *StmlOut;
    CXWnd *OutWnd;
    struct _CSIDLWND *OutStruct;

    CSpawnWnd(CXStr *Template):CCustomWnd(Template)
    {
        SetWndNotification(CSpawnWnd);
        StmlOut = (CStmlWnd *)GetChildItem("CWChatOutput");
        OutWnd = (CXWnd*)StmlOut;
        OutWnd->Clickable = 1;
        OutStruct = (_CSIDLWND*)GetChildItem("CWChatOutput");
        BitOff(WindowStyle, CWS_CLOSE);
        CloseOnESC = 0;
        *(DWORD*)&(((PCHAR)StmlOut)[0x1f0]) = 400;
    }

    int WndNotification(CXWnd *pWnd, unsigned int Message, void *data)
    {
        if (pWnd == NULL && Message == XWM_CLOSE)
        {
            Show = 1;
            return 0;
        }
        return CSidlScreenWnd::WndNotification(pWnd,Message,data);
    };

    void SetFontSize(unsigned int size)
    {
        struct FONTDATA
        {
            DWORD NumFonts;
            PCHAR* Fonts;
        };
        FONTDATA* Fonts;    // font array structure
        CXStr* str;         // contents of stml window
        DWORD* 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 (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", "Spawns", 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", "Spawns", szWindowText, MAX_STRING, INIFileName);
        }

        class CXStr ChatWnd("ChatWindow");
        OurWnd= new CSpawnWnd(&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);
        ((CXWnd*)OurWnd)->Show(1, 1);
        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 WriteSpawn(char* szText)
{
    if (OurWnd)
    {
        ((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);
    }
}

void HandleConfig(bool bSave)
{
    if (bSave)
    {
        char szSave[MAX_STRING] = {0};
        WritePrivateProfileString("Settings", "Delay",        itoa((int)ulDelay, szSave, 10), INIFileName);
        WritePrivateProfileString("Settings", "Enabled",      bWatch ? "on" : "off",          INIFileName);
        WritePrivateProfileString("Settings", "Spawn",        bSpawned ? "on" : "off",        INIFileName);
        WritePrivateProfileString("Settings", "Despawn",      bDespawned ? "on" : "off",      INIFileName);
        WritePrivateProfileString("Settings", "SaveByChar",   bSaveByChar ? "on" : "off",     INIFileName);
        WritePrivateProfileString("Settings", "ShowLoc",      bLocation ? "on" : "off",       INIFileName);
        WritePrivateProfileString("Settings", "Timestamp",    bTimestamp ? "on" : "off",      INIFileName);

        WritePrivateProfileString("Announce", "ALL",          bWatchAll ? "on" : "off",       INIFileName);
        WritePrivateProfileString("Announce", "PC",           bWatchPC ? "on" : "off",        INIFileName);
        WritePrivateProfileString("Announce", "NPC",          bWatchNPC ? "on" : "off",       INIFileName);
        WritePrivateProfileString("Announce", "MOUNT",        bWatchMount ? "on" : "off",     INIFileName);
        WritePrivateProfileString("Announce", "PET",          bWatchPet ? "on" : "off",       INIFileName);
        WritePrivateProfileString("Announce", "MERCENARY",    bWatchMerc ? "on" : "off",      INIFileName);
        WritePrivateProfileString("Announce", "FLYER",        bWatchFlyer ? "on" : "off",     INIFileName);
        WritePrivateProfileString("Announce", "CAMPFIRE",     bWatchCampfire ? "on" : "off",  INIFileName);
        WritePrivateProfileString("Announce", "BANNER",       bWatchBanner ? "on" : "off",    INIFileName);
        WritePrivateProfileString("Announce", "AURA",         bWatchAura ? "on" : "off",      INIFileName);
        WritePrivateProfileString("Announce", "OBJECT",       bWatchObject ? "on" : "off",    INIFileName);
        WritePrivateProfileString("Announce", "UNTARGETABLE", bWatchUntarget ? "on" : "off",  INIFileName);
        WritePrivateProfileString("Announce", "CHEST",        bWatchChest ? "on" : "off",     INIFileName);
        WritePrivateProfileString("Announce", "TRAP",         bWatchTrap ? "on" : "off",      INIFileName);
        WritePrivateProfileString("Announce", "TIMER",        bWatchTimer ? "on" : "off",     INIFileName);
        WritePrivateProfileString("Announce", "TRIGGER",      bWatchTrigger ? "on" : "off",   INIFileName);
        WritePrivateProfileString("Announce", "CORPSE",       bWatchCorpse ? "on" : "off",    INIFileName);
        WritePrivateProfileString("Announce", "ITEM",         bWatchItem ? "on" : "off",      INIFileName);
        WritePrivateProfileString("Announce", "UNKNOWN",      bWatchUnknown ? "on" : "off",   INIFileName);

        SaveOurWnd();
    }
    else
    {
        char szLoad[MAX_STRING] = {0};
        int iValid = GetPrivateProfileInt("Settings", "Delay", (int)ulDelay, INIFileName);
        if (iValid > 0) ulDelay = iValid;

        GetPrivateProfileString("Settings", "Enabled",      bWatch ? "on" : "off",         szLoad, MAX_STRING, INIFileName);
        bWatch = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Settings", "Spawn",        bSpawned ? "on" : "off",       szLoad, MAX_STRING, INIFileName);
        bSpawned = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Settings", "Despawn",      bDespawned ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bDespawned = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Settings", "SaveByChar",   bSaveByChar ? "on" : "off",    szLoad, MAX_STRING, INIFileName);
        bSaveByChar = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Settings", "ShowLoc",      bLocation ? "on" : "off",      szLoad, MAX_STRING, INIFileName);
        bLocation = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Settings", "Timestamp",    bTimestamp ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bTimestamp = (!strnicmp(szLoad, "on", 3));

        GetPrivateProfileString("Announce", "ALL",          bWatchAll ? "on" : "off",      szLoad, MAX_STRING, INIFileName);
        bWatchAll = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "PC",           bWatchPC ? "on" : "off",       szLoad, MAX_STRING, INIFileName);
        bWatchPC = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "NPC",          bWatchNPC ? "on" : "off",      szLoad, MAX_STRING, INIFileName);
        bWatchNPC = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "MOUNT",        bWatchMount ? "on" : "off",    szLoad, MAX_STRING, INIFileName);
        bWatchMount = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "PET",          bWatchPet ? "on" : "off",      szLoad, MAX_STRING, INIFileName);
        bWatchPet = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "MERCENARY",    bWatchMerc ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bWatchMerc = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "FLYER",        bWatchFlyer ? "on" : "off",    szLoad, MAX_STRING, INIFileName);
        bWatchFlyer = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "CAMPFIRE",     bWatchCampfire ? "on" : "off", szLoad, MAX_STRING, INIFileName);
        bWatchCampfire = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "BANNER",       bWatchBanner ? "on" : "off",   szLoad, MAX_STRING, INIFileName);
        bWatchBanner = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "AURA",         bWatchAura ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bWatchAura = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "OBJECT",       bWatchObject ? "on" : "off",   szLoad, MAX_STRING, INIFileName);
        bWatchObject = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "UNTARGETABLE", bWatchUntarget ? "on" : "off", szLoad, MAX_STRING, INIFileName);
        bWatchUntarget = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "CHEST",        bWatchChest ? "on" : "off",    szLoad, MAX_STRING, INIFileName);
        bWatchChest = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "TRAP",         bWatchTrap ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bWatchTrap = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "TIMER",        bWatchTimer ? "on" : "off",    szLoad, MAX_STRING, INIFileName);
        bWatchTimer = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "TRIGGER",      bWatchTrigger ? "on" : "off",  szLoad, MAX_STRING, INIFileName);
        bWatchTrigger = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "CORPSE",       bWatchCorpse ? "on" : "off",   szLoad, MAX_STRING, INIFileName);
        bWatchCorpse = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "ITEM",         bWatchItem ? "on" : "off",     szLoad, MAX_STRING, INIFileName);
        bWatchItem = (!strnicmp(szLoad, "on", 3));
        GetPrivateProfileString("Announce", "UNKNOWN",      bWatchUnknown ? "on" : "off",  szLoad, MAX_STRING, INIFileName);
        bWatchUnknown = (!strnicmp(szLoad, "on", 3));

        KillOurWnd(false);
    }
    if (bLoaded) WriteChatf("\ay%s\aw:: Configuration file %s.", MODULE_NAME, bSave ? "saved" : "loaded");
}

void WatchState(bool bSpawns)
{
    char szOn[10] = "\agON\ax";
    char szOff[10] = "\arOFF\ax";
    WriteChatf("\ay%s\aw:: %s - Spawn(%s) Despawn(%s) Loc(%s) Timestamp(%s) ZoneDelay(\ag%u\ax)", MODULE_NAME, bWatch ? szOn : szOff, bSpawned ? szOn : szOff, bDespawned ? szOn : szOff, bLocation ? szOn : szOff, bTimestamp ? szOn : szOff, ulDelay);
    if (bSpawns)
    {
        WriteChatf("\ay%s\aw:: PC(%s) NPC(%s) Pet(%s) Mount(%s) Merc(%s) Flyer(%s)", MODULE_NAME, bWatchPC ? szOn : szOff, bWatchNPC ? szOn : szOff, bWatchPet ? szOn : szOff, bWatchMount ? szOn : szOff, bWatchMerc ? szOn : szOff, bWatchFlyer ? szOn : szOff);
        WriteChatf("\ay%s\aw:: Campfire(%s) Banner(%s) Aura(%s) Object(%s) Untarget(%s) Chest(%s)", MODULE_NAME, bWatchCampfire ? szOn : szOff, bWatchBanner ? szOn : szOff, bWatchAura ? szOn : szOff, bWatchObject ? szOn : szOff, bWatchUntarget ? szOn : szOff, bWatchChest ? szOn : szOff);
        WriteChatf("\ay%s\aw:: Trap(%s) Timer(%s) Trigger(%s) Corpse(%s) Item(%s) Unknown(%s)", MODULE_NAME, bWatchTrap ? szOn : szOff, bWatchTimer ? szOn : szOff, bWatchTrigger ? szOn : szOff, bWatchCorpse ? szOn : szOff, bWatchItem ? szOn : szOff, bWatchUnknown ? szOn : szOff);
        if (bWatchAll) WriteChatf("\ay%s\aw:: \ayOVERRIDE\ax - Currently announcing \agALL\ax types", MODULE_NAME);
    }
}

void ToggleSetting(const char* pszToggleOutput, bool* pbEvalThis)
{
    char szTheMsg[MAX_STRING] = {0};
    *pbEvalThis = !*pbEvalThis;
    sprintf(szTheMsg, "\ay%s\aw:: %s %s", MODULE_NAME, *pbEvalThis ? "\agNow announcing\ax" : "\arNo longer announcing\ax", pszToggleOutput);
    WriteChatf(szTheMsg);
    return;
}

void WatchSpawns(PSPAWNINFO pChar, char* szLine)
{
    char szCurArg[MAX_STRING] = {0};

    GetArg(szCurArg, szLine, 1);
    if (!*szCurArg)
    {
        bWatch = !bWatch;
    }
    else if (!strnicmp(szCurArg, "on", 3))
    {
        bWatch = true;
    }
    else if (!strnicmp(szCurArg, "off", 4))
    {
        bWatch = false;
    }
    else if (!strnicmp(szCurArg, "spawn", 6))
    {
        bSpawned = !bSpawned;
    }
    else if (!strnicmp(szCurArg, "despawn", 8))
    {
        bDespawned = !bDespawned;
    }
    else if (!strnicmp(szCurArg, "loc", 4))
    {
        bLocation = !bLocation;
    }
    else if (!strnicmp(szCurArg, "timestamp", 10))
    {
        bTimestamp = !bTimestamp;
    }
    else if (!strnicmp(szCurArg, "savebychar", 11))
    {
        bSaveByChar = !bSaveByChar;
        return;
    }
    else if (!strnicmp(szCurArg, "min", 4))
    {
        MinOurWnd();
        return;
    }
    else if (!strnicmp(szCurArg, "clear", 6))
    {
        ClearOurWnd();
        return;
    }
    else if (!strnicmp(szCurArg, "load", 5))
    {
        HandleConfig(false);
        return;
    }
    else if (!strnicmp(szCurArg, "save", 5))
    {
        HandleConfig(true);
        return;
    }
    else if (!strnicmp(szCurArg, "status", 7))
    {
        WatchState(true);
        return;
    }
    else if (!strnicmp(szCurArg, "delay", 6))
    {
        char* pNotNum = NULL;
        GetArg(szCurArg, szLine, 2);
        int iValid = (int)strtoul(szCurArg, &pNotNum, 10);
        if (iValid < 1 || *pNotNum)
        {
            WriteChatf("\ay%s\aw:: \arError\ax - Delay must be a positive numerical value.", MODULE_NAME);
            return;
        }
        ulDelay = iValid;
    }
    else if (!strnicmp(szCurArg, "font", 5))
    {
        char* pNotNum = NULL;
        GetArg(szCurArg, szLine, 2);
        int iValid = (int)strtoul(szCurArg, &pNotNum, 10);
        if (iValid < 1 || *pNotNum)
        {
            WriteChatf("\ay%s\aw:: \arError\ax - Font must be a positive numerical value.", MODULE_NAME);
            return;
        }
        if (OurWnd) OurWnd->SetFontSize(iValid);
        return;
    }
    else if (!strnicmp(szCurArg, "help", 5))
    {
        WriteChatf("\ay%s\aw:: \ag/spawn\ax [ \agon\ax | \agoff\ax | \agspawn\ax | \agdespawn\ax | \agloc\ax | \agtimestamp\ax ]", MODULE_NAME);
        WriteChatf("\ay%s\aw:: \ag/spawn\ax [ \agsave\ax | \agload\ax | \agsavebychar\ax | \agstatus\ax | \aghelp\ax ]", MODULE_NAME);
        WriteChatf("\ay%s\aw:: \ag/spawn\ax [ \agclear\ax | \agmin\ax | \agfont #\ax ]", MODULE_NAME);
        WriteChatf("\ay%s\aw:: \ag/spawn\ax [ \agdelay #\ax ] - Sets zone time delay.", MODULE_NAME);
        WriteChatf("\ay%s\aw:: \agValid toggles:\ax all - pc - npc - mount - pet - merc - flyer - campfire - banner - aura - object - untargetable - chest - trap - timer - trigger - corpse - item - unknown", MODULE_NAME);
        return;
    }
    else if (!strnicmp(szCurArg, "all", 4))
    {
        ToggleSetting("ALL TYPES", &bWatchAll);
        return;
    }
    else if (!strnicmp(szCurArg, "pc", 3))
    {
        ToggleSetting("PC", &bWatchPC);
        return;
    }
    else if (!strnicmp(szCurArg, "npc", 4))
    {
        ToggleSetting("NPC", &bWatchNPC);
        return;
    }
    else if (!strnicmp(szCurArg, "mount", 6))
    {
        ToggleSetting("MOUNT", &bWatchMount);
        return;
    }
    else if (!strnicmp(szCurArg, "pet", 4))
    {
        ToggleSetting("PET", &bWatchPet);
        return;
    }
    else if (!strnicmp(szCurArg, "merc", 5))
    {
        ToggleSetting("MERCENARY", &bWatchMerc);
        return;
    }
    else if (!strnicmp(szCurArg, "flyer", 6))
    {
        ToggleSetting("FLYER", &bWatchFlyer);
        return;
    }
    else if (!strnicmp(szCurArg, "campfire", 9))
    {
        ToggleSetting("CAMPFIRE", &bWatchCampfire);
        return;
    }
    else if (!strnicmp(szCurArg, "banner", 7))
    {
        ToggleSetting("BANNER", &bWatchBanner);
        return;
    }
    else if (!strnicmp(szCurArg, "aura", 5))
    {
        ToggleSetting("AURA", &bWatchAura);
        return;
    }
    else if (!strnicmp(szCurArg, "object", 7))
    {
        ToggleSetting("OBJECT", &bWatchObject);
        return;
    }
    else if (!strnicmp(szCurArg, "untargetable", 14))
    {
        ToggleSetting("UNTARGETABLE", &bWatchUntarget);
        return;
    }
    else if (!strnicmp(szCurArg, "chest", 6))
    {
        ToggleSetting("CHEST", &bWatchChest);
        return;
    }
    else if (!strnicmp(szCurArg, "trap", 5))
    {
        ToggleSetting("TRAP", &bWatchTrap);
        return;
    }
    else if (!strnicmp(szCurArg, "timer", 6))
    {
        ToggleSetting("TIMER", &bWatchTimer);
        return;
    }
    else if (!strnicmp(szCurArg, "trigger", 8))
    {
        ToggleSetting("TRIGGER", &bWatchTrigger);
        return;
    }
    else if (!strnicmp(szCurArg, "corpse", 7))
    {
        ToggleSetting("CORPSE", &bWatchCorpse);
        return;
    }
    else if (!strnicmp(szCurArg, "item", 5))
    {
        ToggleSetting("ITEM", &bWatchItem);
        return;
    }
    else if (!strnicmp(szCurArg, "unknown", 7))
    {
        ToggleSetting("UNKNOWN", &bWatchUnknown);
        return;
    }
    else
    {
        WriteChatf("\ay%s\aw:: Invalid parameter. Use \ag/spawn help\ax for valid options.", MODULE_NAME);
        return;
    }
    WatchState(false);
}

bool GetOurType(PSPAWNINFO pNewSpawn, char* szType, char* szSpawnLoc)
{
    switch(GetSpawnType(pNewSpawn))
    {
    case PC:
        if (!bWatchAll && !bWatchPC) return false;
        sprintf(szType, "PC");
        break;
    case MOUNT:
        if (!bWatchAll && !bWatchMount) return false;
        sprintf(szType, "MOUNT");
        break;
    case PET:
        if (!bWatchAll && !bWatchPet) return false;
        sprintf(szType, "PET");
        break;
    case MERCENARY:
        if (!bWatchAll && !bWatchMerc) return false;
        sprintf(szType, "MERCENARY");
        break;
    case FLYER:
        if (!bWatchAll && !bWatchFlyer) return false;
        sprintf(szSpawnLoc, "INVALID");
        sprintf(szType, "FLYER");
        return true;
    case NPC:
        if (!bWatchAll && !bWatchNPC) return false;
        sprintf(szType, "NPC");
        break;
    case CAMPFIRE:
        if (!bWatchAll && !bWatchCampfire) return false;
        sprintf(szType, "CAMPFIRE");
        break;
    case BANNER:
        if (!bWatchAll && !bWatchBanner) return false;
        sprintf(szType, "GUILDBANNER");
        break;
    case AURA:
        if (!bWatchAll && !bWatchAura) return false;
        sprintf(szType, "AURA");
        break;
    case OBJECT:
        if (!bWatchAll && !bWatchObject) return false;
        sprintf(szType, "OBJECT");
        break;
    case UNTARGETABLE:
        if (!bWatchAll && !bWatchUntarget) return false;
        sprintf(szType, "UNTARGETABLE");
        break;
    case CHEST:
        if (!bWatchAll && !bWatchChest) return false;
        sprintf(szType, "CHEST");
        break;
    case TRAP:
        if (!bWatchAll && !bWatchTrap) return false;
        sprintf(szType, "TRAP");
        break;
    case TIMER:
        if (!bWatchAll && !bWatchTimer) return false;
        sprintf(szType, "TIMER");
        break;
    case TRIGGER:
        if (!bWatchAll && !bWatchTrigger) return false;
        sprintf(szType, "TRIGGER");
        break;
    case CORPSE:
        if (!bWatchAll && !bWatchCorpse) return false;
        sprintf(szType, "CORPSE");
        break;
    case ITEM:
        if (!bWatchAll && !bWatchItem) return false;
        sprintf(szType, "ITEM");
        break;
    default:
        if (!bWatchAll && !bWatchUnknown) return false;
        sprintf(szSpawnLoc, "UNKNOWN");
        sprintf(szType, "UNKNOWN");
        return true;
    }
    sprintf(szSpawnLoc, " @ %.2f, %.2f, %.2f", pNewSpawn->Y, pNewSpawn->X, pNewSpawn->Z);
    return true;
}

PLUGIN_API void OnAddSpawn(PSPAWNINFO pNewSpawn)
{
    if (GetGameState() == GAMESTATE_INGAME && !bZoning && bWatch && bSpawned && time(NULL) > Seconds + ulDelay && pNewSpawn->SpawnID)
    {
        char szType[MAX_STRING] = {0};
        char szSpawnLoc[MAX_STRING] = {0};
        if (GetOurType(pNewSpawn, szType, szSpawnLoc))
        {
            char szMsg[MAX_STRING] = {0};
            char szTime[30] = {0};
            if (bTimestamp)
            {
                struct tm* THE_TIME;
                _time64(&tCurrentTime);
                THE_TIME = _localtime64(&tCurrentTime);
                strftime(szTime, 20, "[%H:%M:%S]  ", THE_TIME);
            }
            sprintf(szMsg, "%s\arSPAWNED:\ax [\ar %d %s %s \ax] <\ay %s \ax> (\ar%s\ax)%s", bTimestamp ? szTime : "", pNewSpawn->Level, pEverQuest->GetRaceDesc(pNewSpawn->Race), GetClassDesc(pNewSpawn->Class), pNewSpawn->DisplayedName, szType, bLocation ? szSpawnLoc : "");
            WriteSpawn(szMsg);
        }
    }
}

PLUGIN_API void OnRemoveSpawn(PSPAWNINFO pOldSpawn)
{
    if (GetGameState() == GAMESTATE_INGAME && !bZoning && bWatch && bDespawned && time(NULL) > Seconds + ulDelay && pOldSpawn->SpawnID)
    {
        char szType[MAX_STRING] = {0};
        char szSpawnLoc[MAX_STRING] = {0};
        if (GetOurType(pOldSpawn, szType, szSpawnLoc))
        {
            char szMsg[MAX_STRING] = {0};
            char szTime[30] = {0};
            if (bTimestamp)
            {
                struct tm* THE_TIME;
                _time64(&tCurrentTime);
                THE_TIME = _localtime64(&tCurrentTime);
                strftime(szTime, 20, "[%H:%M:%S]  ", THE_TIME);
            }
            sprintf(szMsg, "%s\agDESPAWN:\ax [\ag %d %s %s \ax] <\ay %s \ax> (\ag%s\ax)%s", bTimestamp ? szTime : "", pOldSpawn->Level, pEverQuest->GetRaceDesc(pOldSpawn->Race), GetClassDesc(pOldSpawn->Class), pOldSpawn->DisplayedName, szType, bLocation ? szSpawnLoc : "");
            WriteSpawn(szMsg);
        }
    }
}

PLUGIN_API void OnBeginZone()
{
    bZoning = true;
}

PLUGIN_API void OnEndZone()
{
    Seconds = time(NULL);
    bZoning = false;
}

PLUGIN_API void SetGameState(unsigned long ulGameState)
{
    if (ulGameState == GAMESTATE_CHARSELECT)
    {
        KillOurWnd(true);
    }
    else if (ulGameState == GAMESTATE_INGAME)
    {
        CreateOurWnd();
    }
}

PLUGIN_API void OnReloadUI()
{
    if (GetGameState() == GAMESTATE_INGAME) CreateOurWnd();
}

PLUGIN_API void OnCleanUI()
{
    KillOurWnd(true);
}

PLUGIN_API void OnPulse()
{
    if(InHoverState() && OurWnd)
    {
        ((CXWnd*)OurWnd)->DoAllDrawing();
    }
}

PLUGIN_API void InitializePlugin()
{
    AddCommand("/spawn", WatchSpawns);
    Seconds = time(NULL);
    HandleConfig(false);
    bLoaded = true;
}

PLUGIN_API void ShutdownPlugin()
{
    RemoveCommand("/spawn");
    HandleConfig(true);
    KillOurWnd(false);
}
