// INCLUDES---------------------------------------------------------------

#define _WIN32_WINNT 0x0501
#include <windows.h>

#include "resource.h"

#include <commctrl.h>
#include <richedit.h>

#include "ocidl.h"
#include "olectl.h"

#include <stdio.h>
#include <fcntl.h>              // _O_TEXT
#include <io.h>                 // for _open_osfhandle()

#include "tnt.h"

typedef struct tagImgInfo
{   IPicture* Ipic;
    SIZE      sizeInHiMetric,
              sizeInPix;
    char*     Path;
} IMG_INFO;

// DEFINES----------------------------------------------------------------

#define USR_NOTIFYICON (WM_USER + 1)
#define IDK_F12        0

#define SCAN_ESCAPE    1

#define argc           __argc
#define argv           __argv

// EXPORTED VARIABLES-----------------------------------------------------

EXPORT       FLAG      bigwin,
                       reactable      = TRUE; // always TRUE for IBM
EXPORT       TEXT      ProgDir[MAX_PATH + 1],
                       colourscreen[65536 + 1],
                       userinput[MAX_PATH + 1] = "";
EXPORT       HWND      SheetWindowPtr = NULL;

// IMPORTED VARIABLES-----------------------------------------------------

IMPORT       FLAG      avail_armour,
                       avail_cast,
                       avail_drop,
                       avail_fight,
                       avail_get,
                       avail_hands,
                       avail_look,
                       avail_options,
                       avail_proceed,
                       avail_autofight,
                       avail_use,
                       avail_view,
                       colours,
                       gotman,
                       logconsole,
                       showunimp;
IMPORT       UBYTE     gfx;
IMPORT       int       column,
                       st, max_st,
                       iq,
                       lk,
                       con, max_con,
                       dex,
                       chr,
                       spd,
                       class,
                       gp, sp, cp,
                       level,
                       minutes,
                       module,
                       race,
                       room,
                       row,
                       sex,
                       sheet_chosen,
                       textcolour,
                       winx, winy,
                       xp;
IMPORT       TEXT      ck_filename[MAX_PATH + 1],
                       name[80 + 1],
                       pathname[MAX_PATH + 1],
                       imagename[MAX_PATH + 1],
                       pictitle[60 + 1],
                       specialopt_long[8][20 + 1],
                       specialopt_short[8][10 + 1],
                       vanillascreen[65536 + 1];
IMPORT       FILE*     LogfileHandle;
IMPORT       STRPTR    rank;
IMPORT const STRPTR    table_classes[4],
                       table_sex[2];
IMPORT struct AbilityStruct    ability[ABILITIES];
IMPORT struct ItemStruct       items[ITEMS];
IMPORT struct ModuleInfoStruct moduleinfo[MODULES];
IMPORT struct RacesStruct      races[RACES];
IMPORT struct SpellStruct      spell[SPELLS];

// MODULE VARIABLES-------------------------------------------------------

MODULE       FLAG       globaldone,
                        iconified        = FALSE;
MODULE       TEXT       SysInfoName[MAX_PATH + 1];
MODULE       HANDLE     GfxHandle        = NULL;
MODULE       HCURSOR    hArrow,
                        hBusy;
MODULE       HFONT      hBoldFont        = NULL,
                        hOutputFont      = NULL;
MODULE       HICON      BigIconPtr,
                        SmallIconPtr;
MODULE       HIMAGELIST himl;
MODULE       HINSTANCE  InstancePtr      = NULL;
MODULE       HMENU      TrayMenuPtr      = NULL;
MODULE       HWND       ConsoleWindowPtr = NULL,
                        GfxWindowPtr     = NULL,
                        TipsPtr          = NULL;
MODULE       int        picwidth, picheight,
                        winwidth, winheight;
MODULE       IMG_INFO   ImageInfo;
MODULE       RECT       globalrect;
MODULE       WNDPROC    wpOldEditProc; // Stores the old original window procedure for the edit control

// MODULE ARRAYS----------------------------------------------------------

PERSIST const int glyphs[ITEMCATEGORIES] =
{ IDI_NONWEAPON,
  IDI_SWORD,
  IDI_HAFTED,
  IDI_POLE,
  IDI_SPEAR,
  IDI_DAGGER,
  IDI_BOW,
  IDI_CROSSBOW,
  IDI_POWDER,
  IDI_SLING,
  IDI_THROWN,
  IDI_STAFF,
  IDI_GUNNE,
  IDI_ARROW,
  IDI_STONE,
  IDI_OTHER,
  IDI_QUARREL,
  IDI_DART,
  IDI_BLOWPIPE,
  IDI_ARMOUR,
  IDI_SHIELD,
  IDI_POTION,
  IDI_RING,
  IDI_SLAVE,
  IDI_POISON,
  IDI_JEWEL,
  IDI_ARMS,
  IDI_HEAD,
  IDI_LEGS,
  IDI_CHEST,
};

MODULE const int textpen_to_rgb[] =
{ RGB(  0,   0,   0), // TEXTPEN_BLACK             0
  RGB(171,   0,   0), // TEXTPEN_DARKRED           1
  RGB(  0, 171,   0), // TEXTPEN_DARKGREEN         2
  RGB(171,  87,   0), // TEXTPEN_BROWN             3
  RGB(  0,   0, 171), // TEXTPEN_DARKBLUE          4
  RGB(171,   0, 171), // TEXTPEN_DARKPURPLE        5
  RGB(  0, 171, 171), // TEXTPEN_DARKCYAN          6
  RGB(171, 171, 171), // TEXTPEN_LIGHTGREY         7
  RGB( 87,  87,  87), // TEXTPEN_DARKGREY          8
  RGB(255,  87,  87), // TEXTPEN_PINK              9
  RGB( 87, 255,  87), // TEXTPEN_GREEN            10
  RGB(255, 255,  87), // TEXTPEN_YELLOW           11
  RGB( 87,  87, 255), // TEXTPEN_BLUE             12
  RGB(255,  87, 255), // TEXTPEN_PURPLE           13
  RGB( 87, 255, 255), // TEXTPEN_CYAN             14
  RGB(255, 255, 255), // TEXTPEN_WHITE            15
  RGB(  0,   0,   0), // TEXTPEN_BLACKONDARKCYAN  16
  RGB(255, 255,  87), // TEXTPEN_YELLOWONDARKBLUE 17
  RGB(  0,   0,   0), // TEXTPEN_BLACKONDARKRED   18
};

MODULE const WORD nt[] =
{   0                                                                         , //  0 black
                           FOREGROUND_RED                                     , //  1 red
                                            FOREGROUND_GREEN                  , //  2 green
                           FOREGROUND_RED | FOREGROUND_GREEN                  , //  3 brown
                                                               FOREGROUND_BLUE, //  4 blue
                           FOREGROUND_RED |                    FOREGROUND_BLUE, //  5 purple
                                            FOREGROUND_GREEN | FOREGROUND_BLUE, //  6 cyan
                           FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, //  7 light  grey
    FOREGROUND_INTENSITY                                                      , //  8 dark   grey
    FOREGROUND_INTENSITY | FOREGROUND_RED                                     , //  9 bright red
    FOREGROUND_INTENSITY |                  FOREGROUND_GREEN                  , // 10 bright green
    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN                  , // 11 bright yellow
    FOREGROUND_INTENSITY |                                     FOREGROUND_BLUE, // 12 bright blue
    FOREGROUND_INTENSITY | FOREGROUND_RED |                    FOREGROUND_BLUE, // 13 bright purple
    FOREGROUND_INTENSITY |                  FOREGROUND_GREEN | FOREGROUND_BLUE, // 14 bright cyan
    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // 15 white
                                            BACKGROUND_GREEN | BACKGROUND_BLUE, // 16 black on dark cyan
    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE, // 16 yellow on dark blue
                           BACKGROUND_RED                                     , // 16 black on dark red
};

// MODULE STRUCTURES------------------------------------------------------

#define MIB_ICONIFY 0
#define MIB_MANUAL  2
#define MIB_UPDATES 3
#define MIB_ABOUT   4
#define MENUBITMAPS 5
MODULE struct
{   const DWORD   menuitem,
                  resource;
// uninitialized
          HBITMAP bitmap;
} mib[MENUBITMAPS] =
{ { ID_FILE_ICONIFY, IDB_M_ICONIFY}, // 0 (MIB_ICONIFY)
  { ID_FILE_EXIT,    IDB_M_EXIT   }, // 1
  { ID_HELP_MANUAL,  IDB_M_MANUAL }, // 2 (MIB_MANUAL)
  { ID_HELP_UPDATE,  IDB_M_UPDATE }, // 3 (MIB_UPDATES)
  { ID_HELP_ABOUT,   IDB_M_ABOUT  }, // 4 (MIB_ABOUT)
};

MODULE CHARFORMAT2 fgformat =
{   sizeof(CHARFORMAT2),
    CFM_COLOR | CFM_FACE | CFM_BACKCOLOR,
    0, // dwEffects
    0, // yHeight
    0, // yOffset
    RGB(255, 255, 255), // this is used when colours are turned off
    0, // charset
    FIXED_PITCH | FF_MODERN,
    "TopazPlus",
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

// MODULE FUNCTIONS-------------------------------------------------------

MODULE BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
MODULE BOOL CALLBACK GfxDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
MODULE BOOL CALLBACK SheetDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CustomEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

MODULE void setup_columns(void);
MODULE void iconify(void);
MODULE void uniconify(FLAG exiting);
MODULE void traymenu(void);
MODULE void make_glyphs(void);

MODULE void* OpenGraphic(char* name);
MODULE void DisplayGraphic(HWND hwnd, HDC pDC);
MODULE void CloseGraphic(void* Ipict);

// CODE-------------------------------------------------------------------

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{   TRANSIENT int     i;
    TRANSIENT WSADATA wsaData;
    PERSIST   TEXT    tempstring[MAX_PATH + 1];

    // Start of program.

    engine_init();

    InitCommonControls();
    DISCARD CoInitialize(NULL);
    LoadLibrary("RICHED20.DLL");
    InstancePtr = GetModuleHandle(NULL);
    SystemParametersInfo(SPI_GETWORKAREA, 0, &globalrect, 0);
    bigwin = (globalrect.bottom - globalrect.top >= 860) ? TRUE : FALSE;
    DISCARD GetCurrentDirectory(MAX_PATH, ProgDir);

    for (i = 0; i < MENUBITMAPS; i++)
    {   mib[i].bitmap      = NULL;
    }

    if (AddFontResource("TopazPlus.fon"))
    {   SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
    }

    hOutputFont = CreateFont
    (   16,                     // int nHeight
        0,                      // int nWidth
        0,                      // int nEscapement
        0,                      // int nOrientation
        FW_MEDIUM,              // int nWeight
        FALSE,                  // BYTE bItalic
        FALSE,                  // BYTE bUnderline
        0,                      // BYTE cStrikeOut
        ANSI_CHARSET,           // BYTE nCharSet
        OUT_DEFAULT_PRECIS,     // BYTE nOutPrecision
        CLIP_CHARACTER_PRECIS,  // BYTE nClipPrecision
        DEFAULT_QUALITY,        // BYTE nQuality
        FF_MODERN | FIXED_PITCH, // BYTE nPitchAndFamily
        "TopazPlus"             // LPCTSTR lpszFacename
    );
    hBoldFont = CreateFont
    (   8,                      // int nHeight
        0,                      // int nWidth
        0,                      // int nEscapement
        0,                      // int nOrientation
        FW_EXTRABOLD,           // int nWeight
        FALSE,                  // BYTE bItalic
        FALSE,                  // BYTE bUnderline
        0,                      // BYTE cStrikeOut
        ANSI_CHARSET,           // BYTE nCharSet
        OUT_DEFAULT_PRECIS,     // BYTE nOutPrecision
        CLIP_CHARACTER_PRECIS,  // BYTE nClipPrecision
        DEFAULT_QUALITY,        // BYTE nQuality
        FF_DECORATIVE,          // BYTE nPitchAndFamily
        "MS Sans Serif"         // LPCTSTR lpszFacename
    );
    // should we check these return codes?
    // should we destroy these fonts later?

    SmallIconPtr = LoadImage(InstancePtr, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
    BigIconPtr   = LoadImage(InstancePtr, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 32, 32, 0);

    for (i = 0; i < MENUBITMAPS; i++)
    {   mib[i].bitmap = LoadBitmap(InstancePtr, MAKEINTRESOURCE(mib[i].resource));
    }

    hArrow = LoadCursor(NULL, IDC_ARROW);
    hBusy  = LoadCursor(NULL, IDC_WAIT);

    if (argc >= 3)
    {   aprintf("Usage: %s [<filename>]\n", argv[0]);
        quit(EXIT_FAILURE);
    } elif (argc == 2)
    {   strcpy(name, argv[1]);
        name[0] = toupper(name[0]);
    }

    DISCARD WSAStartup(MAKEWORD(2,2), &wsaData); // initialize Winsock

    engine_main();
}

EXPORT void open_sheet(void)
{   // assert(!SheetWindowPtr);

    SheetWindowPtr = CreateDialog
    (   InstancePtr,
        MAKEINTRESOURCE(bigwin ? IDD_SHEET_LARGE : IDD_SHEET_SMALL), // we are getting 860 in 1600*900 screenmode
        NULL,
        SheetDlgProc
    );

    // DISCARD SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_SETFONT, (WPARAM) hTopazPlusFont, MAKELPARAM(FALSE, 0));

    wpOldEditProc = (WNDPROC) SetWindowLong
    (   GetDlgItem(SheetWindowPtr, IDC_INPUT),
        GWL_WNDPROC,
        (LONG) CustomEditProc
    );

    setup_columns();
    update_sheet();
}

EXPORT void open_gfx(void)
{   if (gfx && Exists(imagename))
    {   close_gfx(FALSE);

        GfxHandle = OpenGraphic(imagename);
        GfxWindowPtr = CreateDialog
        (   InstancePtr,
            MAKEINTRESOURCE(IDD_GFX),
            NULL,
            GfxDlgProc
        );
        DISCARD SetWindowText(GfxWindowPtr, pictitle);
}   }

BOOL CALLBACK SheetDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{   int           i;
    PAINTSTRUCT   localps;
    RECT          therect;
    LPNMLISTVIEW  lpnmlv;
    LVHITTESTINFO HitTestInfo;
    POINT         thepoint;
    WORD          gid;
    HMENU         SystemMenuPtr;

    switch (Message)
    {
    case WM_INITDIALOG:
        SendDlgItemMessage(hwnd, IDC_OUTPUT, WM_SETFONT, (WPARAM) hOutputFont, (DWORD) TRUE);

        SendMessage(hwnd, WM_SETICON, ICON_BIG,   (LPARAM) BigIconPtr);
        SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM) SmallIconPtr);

        SystemMenuPtr = GetSystemMenu(hwnd, FALSE);
        if (SystemMenuPtr)
        {   DISCARD InsertMenu(SystemMenuPtr,  6, MF_BYPOSITION | MF_STRING   , ID_HELP_MANUAL , "Manual...");
            DISCARD InsertMenu(SystemMenuPtr,  7, MF_BYPOSITION | MF_SEPARATOR, 0              , 0);
            DISCARD InsertMenu(SystemMenuPtr,  8, MF_BYPOSITION | MF_STRING   , ID_HELP_UPDATE , "Check for &updates...");
            DISCARD InsertMenu(SystemMenuPtr,  9, MF_BYPOSITION | MF_SEPARATOR, 0              , 0);
            DISCARD InsertMenu(SystemMenuPtr, 10, MF_BYPOSITION | MF_STRING   , ID_HELP_ABOUT  , "&About T&&T...");
            DISCARD InsertMenu(SystemMenuPtr, 11, MF_BYPOSITION | MF_SEPARATOR, 0              , 0);
            DISCARD InsertMenu(SystemMenuPtr, 12, MF_BYPOSITION | MF_STRING   , ID_FILE_ICONIFY, "&Iconify\tCtrl+F12");

            for (i = 0; i < MENUBITMAPS; i++)
            {   DISCARD SetMenuItemBitmaps
                (   SystemMenuPtr,   // handle of menu
                    mib[i].menuitem, // menu item to receive new bitmaps
                    MF_BYCOMMAND,    // menu item flags
                    mib[i].bitmap,   // handle of unchecked bitmap
                    NULL             // handle of checked bitmap
                );
        }   }

        DISCARD RegisterHotKey(hwnd, IDK_F12, MOD_CONTROL, VK_F12); // this will fail if there are multiple instances running
        // presumably this gets automatically freed by Windows when we destroy the window

        GetWindowRect(hwnd, &therect);
        winwidth  = (therect.right  - therect.left + 1);
        winheight = (therect.bottom - therect.top  + 1);
        if (winx < 0)
        {   winx = 0;
        }
        if (winy < 0)
        {   winy = globalrect.bottom - winheight - 4;
        }
        if (winx + winwidth  > globalrect.right  - 4)
        {   winx = globalrect.right  - winwidth  - 4;
        }
        if (winy + winheight > globalrect.bottom - 4)
        {   winy = globalrect.bottom - winheight - 4;
        }
        DISCARD SetWindowPos
        (   hwnd,
            HWND_TOP,
            winx,
            winy,
            winwidth,
            winheight,
            SWP_NOCOPYBITS | SWP_NOSIZE
        );

        SheetWindowPtr = hwnd;
        make_glyphs();
        ListView_SetImageList(GetDlgItem(hwnd, IDC_ITEMS), himl, LVSIL_SMALL);
        DISCARD RedrawWindow(GetDlgItem(hwnd, IDC_ITEMS), NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); // maybe not needed?

        SendDlgItemMessage(hwnd, IDC_OUTPUT, EM_SETBKGNDCOLOR, 0      , RGB(0, 0, 0)      );
        SendDlgItemMessage(hwnd, IDC_OUTPUT, EM_SETCHARFORMAT, SCF_ALL, (LPARAM) &fgformat);

        ShowWindow(hwnd, SW_SHOW);
        return FALSE;
    case WM_CLOSE:
        quit(EXIT_SUCCESS);
        return TRUE;
    case WM_PAINT:
        DISCARD BeginPaint(hwnd, &localps);
        DISCARD EndPaint(hwnd, &localps);
        return FALSE; // important!
    case WM_COMMAND:
        gid = LOWORD(wParam);
        switch (gid)
        {
        case IDC_OUTPUT:
            if (HIWORD(wParam) == EN_SETFOCUS)
            {   SetFocus(GetDlgItem(SheetWindowPtr, IDC_INPUT));
            }
        acase IDC_OPT1:
        case IDC_OPT2:
        case IDC_OPT3:
        case IDC_OPT4:
        case IDC_OPT5:
        case IDC_OPT6:
        case IDC_OPT7:
        case IDC_OPT8:
            sprintf(userinput, "%s", specialopt_short[gid - IDC_OPT1]);
            globaldone = TRUE;
        acase IDC_OPT9:  strcpy(userinput, "A"); globaldone = TRUE;
        acase IDC_OPT10: strcpy(userinput, "C"); globaldone = TRUE;
        acase IDC_OPT11: strcpy(userinput, "D"); globaldone = TRUE;
        acase IDC_OPT12: strcpy(userinput, "F"); globaldone = TRUE;
        acase IDC_OPT13: strcpy(userinput, "G"); globaldone = TRUE;
        acase IDC_OPT14: strcpy(userinput, "H"); globaldone = TRUE;
        acase IDC_OPT15: strcpy(userinput, "L"); globaldone = TRUE;
        acase IDC_OPT16: strcpy(userinput, "O"); globaldone = TRUE;
        acase IDC_OPT17: strcpy(userinput, "P"); globaldone = TRUE;
        acase IDC_OPT18: quit(EXIT_SUCCESS);  // globaldone = TRUE;
        acase IDC_OPT19: strcpy(userinput, "T"); globaldone = TRUE;
        acase IDC_OPT20: strcpy(userinput, "U"); globaldone = TRUE;
        acase IDC_OPT21: strcpy(userinput, "V"); globaldone = TRUE;
        acase ID_FILE_UNICONIFY:
            uniconify(FALSE);
        acase ID_FILE_EXIT:
            quit(EXIT_SUCCESS);
        }
        return TRUE;
    case WM_MOUSEWHEEL:
        SendDlgItemMessage(hwnd, IDC_OUTPUT, EM_SCROLL, ((signed short) HIWORD(wParam) < 0) ? SB_LINEDOWN : SB_LINEUP, 0);
        return FALSE;
    case WM_NOTIFY:
        switch (((LPNMHDR) lParam)->code)
        {
        case LVN_BEGINLABELEDIT:
            return 1; // this disallows label editing
        case LVN_KEYDOWN:
            return 1; // this disallows keyboard input
        case NM_SETFOCUS:
        case NM_CLICK:
        case NM_DBLCLK:
        case NM_RCLICK:
            lpnmlv = (LPNMLISTVIEW) lParam;
            if (lpnmlv->hdr.hwndFrom == GetDlgItem(SheetWindowPtr, IDC_ITEMS))
            {   GetCursorPos(&thepoint);
                DISCARD ScreenToClient(GetDlgItem(SheetWindowPtr, IDC_ITEMS), &thepoint);
                HitTestInfo.pt.x = thepoint.x;
                HitTestInfo.pt.y = thepoint.y;
                ListView_SubItemHitTest(GetDlgItem(SheetWindowPtr, IDC_ITEMS), &HitTestInfo);
                sheet_chosen = HitTestInfo.iItem;

                strcpy(userinput, "U");
                globaldone = TRUE;
            } elif (lpnmlv->hdr.hwndFrom == GetDlgItem(SheetWindowPtr, IDC_SPELLS))
            {   GetCursorPos(&thepoint);
                DISCARD ScreenToClient(GetDlgItem(SheetWindowPtr, IDC_SPELLS), &thepoint);
                HitTestInfo.pt.x = thepoint.x;
                HitTestInfo.pt.y = thepoint.y;
                ListView_SubItemHitTest(GetDlgItem(SheetWindowPtr, IDC_SPELLS), &HitTestInfo);
                sheet_chosen = HitTestInfo.iItem;

                strcpy(userinput, "C");
                globaldone = TRUE;
        }   }
        return FALSE;
    case WM_HOTKEY:
        iconify();
        return TRUE;
    case WM_SYSCOMMAND:
        switch (LOWORD(wParam))
        {
        acase ID_FILE_ICONIFY:
            iconify();
        acase ID_HELP_MANUAL:
            help_manual();
        acase ID_HELP_UPDATE:
            help_update();
        acase ID_HELP_ABOUT:
            help_about();
        adefault:
            return 0;
        }
        return 0;
    case USR_NOTIFYICON:
        // assert(iconified);
        switch (lParam)
        {
        case WM_LBUTTONDBLCLK:
            uniconify(FALSE);
        acase WM_RBUTTONUP:
            traymenu();
        }
        return 0;
    default:
        return FALSE;
}   }

BOOL CALLBACK GfxDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{   TRANSIENT PAINTSTRUCT localps;
    TRANSIENT HDC         hDC;

    switch (Message)
    {
    case WM_INITDIALOG:
        DISCARD SetWindowPos
        (   hwnd,
            HWND_TOP,
            globalrect.right - (picwidth + (GetSystemMetrics(SM_CXDLGFRAME) * 2)) - 4,
            globalrect.top                                                        + 4,
            picwidth  + (GetSystemMetrics(SM_CXDLGFRAME) * 2),
            picheight + (GetSystemMetrics(SM_CYDLGFRAME) * 2) + GetSystemMetrics(SM_CYCAPTION),
            SWP_NOCOPYBITS
        );
        ShowWindow(hwnd, SW_SHOW);
        return FALSE; // we don't want anything focussed
    case WM_CLOSE:
        close_gfx(TRUE);
        return TRUE;
    case WM_PAINT:
        hDC = BeginPaint(hwnd, &localps);
        DisplayGraphic(hwnd, hDC);
        DISCARD EndPaint(hwnd, &localps);
        return FALSE; // important!
    default: // no need for adefault
        return FALSE;
}   }

MODULE void setup_columns(void)
{   LVCOLUMN lvc;

    // assert(SheetWindowPtr);

    lvc.mask     = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; // *not* LVCF_IMAGE!
    lvc.iSubItem = 0;

    lvc.fmt      = LVCFMT_LEFT; // first column is always left-aligned
    lvc.cx       = 40;
    lvc.pszText  = "Hand";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_ITEMS), 0, &lvc);

    lvc.fmt      = LVCFMT_RIGHT;
    lvc.cx       = 35;
    lvc.pszText  = "Qty";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_ITEMS), 1, &lvc);

    lvc.fmt      = LVCFMT_RIGHT;
    lvc.cx       = 50;
    lvc.pszText  = "Weight";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_ITEMS), 2, &lvc);

    lvc.fmt      = LVCFMT_CENTER;
    lvc.cx       = 50;
    lvc.pszText  = "D+A";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_ITEMS), 3, &lvc);

    lvc.fmt      = LVCFMT_LEFT;
    lvc.cx       = 190;
    lvc.pszText  = "Item";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_ITEMS), 4, &lvc);

    lvc.fmt      = LVCFMT_RIGHT; // first column is always left-aligned
    lvc.cx       = 30;
    lvc.pszText  = "Lvl";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_SPELLS), 0, &lvc);

    lvc.fmt      = LVCFMT_RIGHT;
    lvc.cx       = 30;
    lvc.pszText  = "ST";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_SPELLS), 1, &lvc);

    lvc.fmt      = LVCFMT_LEFT;
    lvc.cx       = 35;
    lvc.pszText  = "Abr";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_SPELLS), 2, &lvc);

    lvc.fmt      = LVCFMT_LEFT;
    lvc.cx       = 201;
    lvc.pszText  = "Spell";
    ListView_InsertColumn(GetDlgItem(SheetWindowPtr, IDC_SPELLS), 3, &lvc);
}

EXPORT void update_sheet(void)
{   PERSIST   TEXT   tempstring[80 + 1];
    TRANSIENT int    i, j;
    TRANSIENT LVITEM lvi;

    if (!SheetWindowPtr)
    {   return;
    }

    // make_tips();

    if (gotman)
    {   getrank();
        sprintf
        (   tempstring,
            "%s%s (%s %s %s)",
            rank,
            name,
            table_sex[sex],
            races[race].singular,
            table_classes[class]
        );
    } else
    {   sprintf(tempstring, "Tunnels & Trolls " DECIMALVERSION);
    }
    SetWindowText(SheetWindowPtr, tempstring);   

    sprintf(tempstring, "%d", level  ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_LEVEL   , tempstring);
    sprintf(tempstring, "%d", xp     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_AP      , tempstring);
    sprintf(tempstring, "%d.%d", carrying() / 10, carrying() % 10);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_CARRIED , tempstring);
    sprintf(tempstring, "%d", getlimit() / 10);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_POSSIBLE, tempstring);
    sprintf(tempstring, "%d", gp     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_GP      , tempstring);
    sprintf(tempstring, "%d", sp     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_SP      , tempstring);
    sprintf(tempstring, "%d", cp     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_CP      , tempstring);
    sprintf(tempstring, "%d", gotman ? calc_personaladds(st, lk, dex) : 0);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_MELEE   , tempstring);
    sprintf(tempstring, "%d", gotman ? calc_missileadds( st, lk, dex) : 0);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_MISSILE , tempstring);
    sprintf(tempstring, "%s%d", moduleinfo[module].name, room);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_LOCATION, tempstring);
    sprintf(tempstring, "%02d:%02d on day %d", (minutes % ONE_DAY) / 60, minutes % 60, (minutes / ONE_DAY) + 1);
                                        DISCARD SetDlgItemText(SheetWindowPtr, IDC_TIME    , tempstring);
    sprintf(tempstring, "%d", st     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_CURSTR  , tempstring);
    sprintf(tempstring, "%d", max_st ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_MAXSTR  , tempstring);
    sprintf(tempstring, "%d", iq     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_IQ      , tempstring);
    sprintf(tempstring, "%d", lk     ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_LK      , tempstring);
    sprintf(tempstring, "%d", con    ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_CURCON  , tempstring);
    sprintf(tempstring, "%d", max_con); DISCARD SetDlgItemText(SheetWindowPtr, IDC_MAXCON  , tempstring);
    sprintf(tempstring, "%d", dex    ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_DEX     , tempstring);
    sprintf(tempstring, "%d", chr    ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_CHR     , tempstring);
    sprintf(tempstring, "%d", spd    ); DISCARD SetDlgItemText(SheetWindowPtr, IDC_SPD     , tempstring);

    lvi.iSubItem    = 0;
    lvi.state       = 0;
    lvi.stateMask   = 0;
    lvi.mask        = LVIF_TEXT | LVIF_IMAGE;

    SendMessage(GetDlgItem(SheetWindowPtr, IDC_ITEMS), WM_SETREDRAW, FALSE, 0);
    DISCARD ListView_DeleteAllItems(GetDlgItem(SheetWindowPtr, IDC_ITEMS));
    j = 0;
    for (i = 0; i < ITEMS; i++)
    {   if (items[i].owned)
        {   lvi.iItem     = j;
            lvi.iSubItem  = 0;
            lvi.state     =
            lvi.stateMask = 0;
            lvi.iImage    = items[i].type;
            lvi.mask      = LVIF_TEXT | LVIF_IMAGE;
            tempstring[0] = getsymbol(i);
            tempstring[1] = EOS;
            lvi.pszText = tempstring;
            DISCARD ListView_InsertItem(GetDlgItem(SheetWindowPtr, IDC_ITEMS), &lvi);
            sprintf(tempstring, "%d", items[i].owned);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_ITEMS), j, 1, tempstring);
            sprintf(tempstring, "%d.%d#", (items[i].owned * items[i].weight) / 10, (items[i].owned * items[i].weight) % 10);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_ITEMS), j, 2, tempstring);
            if (items[i].dice || items[i].adds)
            {   sprintf(tempstring, "%d+%d", items[i].dice, items[i].adds);
            } elif (items[i].hits)
            {   sprintf(tempstring, "%d", items[i].hits);
            } else
            {   strcpy(tempstring, "-");
            }
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_ITEMS), j, 3, tempstring);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_ITEMS), j, 4, items[i].name);
            items[j].lookup = i;
            j++;
    }   }
    SendMessage(GetDlgItem(SheetWindowPtr, IDC_ITEMS), WM_SETREDRAW, TRUE, 0);

    SendMessage(GetDlgItem(SheetWindowPtr, IDC_SPELLS), WM_SETREDRAW, FALSE, 0);
    DISCARD ListView_DeleteAllItems(GetDlgItem(SheetWindowPtr, IDC_SPELLS));
    j = 0;
    for (i = 0; i < SPELLS; i++)
    {   if (spell[i].known)
        {   lvi.iItem = j;
            sprintf(tempstring, "%d", spell[i].level);
            lvi.pszText = tempstring;
            DISCARD ListView_InsertItem(GetDlgItem(SheetWindowPtr, IDC_SPELLS), &lvi);
            sprintf(tempstring, "%d", spell[i].st);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_SPELLS), j, 1, tempstring);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_SPELLS), j, 2, spell[i].abbrev);
            ListView_SetItemText(GetDlgItem(SheetWindowPtr, IDC_SPELLS), j, 3, spell[i].corginame);
            spell[j].lookup = i;
            j++;
    }   }
    SendMessage(GetDlgItem(SheetWindowPtr, IDC_SPELLS), WM_SETREDRAW, TRUE, 0);
}

EXPORT void close_gfx(FLAG zeroname)
{   if (GfxWindowPtr)
    {   DestroyWindow(GfxWindowPtr);
        GfxWindowPtr = NULL;
    }
    if (GfxHandle)
    {   CloseGraphic(GfxHandle);
        GfxHandle = NULL;
    }

    if (zeroname)
    {   imagename[0] = EOS;
}   }

EXPORT void close_sheet(void)
{   RECT therect;

    if (SheetWindowPtr)
    {   SetWindowLong(GetDlgItem(SheetWindowPtr, IDC_INPUT), GWL_WNDPROC, (LONG) wpOldEditProc);

        DISCARD GetWindowRect(SheetWindowPtr, &therect);
        winx = therect.left;
        winy = therect.top;
        DestroyWindow(SheetWindowPtr);
        SheetWindowPtr = NULL;
}   }

MODULE void* OpenGraphic(char* name)
{         IPicture*      Ipic              = NULL;
          SIZE           sizeInHiMetric,
                         sizeInPix;
    const int            HIMETRIC_PER_INCH = 2540;
          HDC            hDCScreen         = GetDC(NULL);
          HRESULT        hr;
          int            nPixelsPerInchX   = GetDeviceCaps(hDCScreen, LOGPIXELSX);
          int            nPixelsPerInchY   = GetDeviceCaps(hDCScreen, LOGPIXELSY);
          unsigned short OlePathName[MAX_PATH + 1];

    ReleaseDC(NULL, hDCScreen);
    mbstowcs(OlePathName, name, strlen(name) + 1);
    hr = OleLoadPicturePath // requires Win95SE+? (aka Win95OSR2+)
    (   OlePathName, // it needs the *full* path!
        NULL,
        0,
        0,
        &IID_IPicture,
        (void *) (&Ipic)
    );
    if (hr || !Ipic)
    {   return 0;
    }

    // get width and height of picture
    hr = Ipic->lpVtbl->get_Width( Ipic, &sizeInHiMetric.cx);
    if (!SUCCEEDED(hr))
    {   return 0;
    }
    hr = Ipic->lpVtbl->get_Height(Ipic, &sizeInHiMetric.cy);
    if (!SUCCEEDED(hr))
    {   return 0;
    }

    // convert himetric to pixels
    sizeInPix.cx             = (nPixelsPerInchX * sizeInHiMetric.cx + HIMETRIC_PER_INCH / 2) / HIMETRIC_PER_INCH;
    sizeInPix.cy             = (nPixelsPerInchY * sizeInHiMetric.cy + HIMETRIC_PER_INCH / 2) / HIMETRIC_PER_INCH;
    ImageInfo.sizeInPix      = sizeInPix;
    ImageInfo.sizeInHiMetric = sizeInHiMetric;
    ImageInfo.Ipic           = Ipic;
    ImageInfo.Path           = name;

    picwidth  = sizeInPix.cx;
    picheight = sizeInPix.cy;

    return Ipic;
}

MODULE void DisplayGraphic(HWND hwnd, HDC pDC)
{   IPicture*  Ipic   = ImageInfo.Ipic;
    DWORD      dwAttr = 0;
    HRESULT    hr;
    OLE_HANDLE hPal       = 0;
    HPALETTE   hPalOld    = NULL,
               hPalMemOld = NULL;

    if (!Ipic)
    {   return;
    }

    // get palette
    hr = Ipic->lpVtbl->get_hPal(Ipic, &hPal);
    if (!SUCCEEDED(hr))
    {   return;
    }
    if (hPal != 0)
    {   hPalOld = SelectPalette(pDC, (HPALETTE) hPal, FALSE);
        RealizePalette(pDC);
    }

    // display picture
    Ipic->lpVtbl->Render
    (   Ipic,
        pDC,
        0,
        0,
        picwidth,
        picheight,
        0,
        ImageInfo.sizeInHiMetric.cy,
        ImageInfo.sizeInHiMetric.cx,
        -ImageInfo.sizeInHiMetric.cy,
        NULL
    );

    if (hPalOld != NULL)
    {   SelectPalette(pDC, hPalOld, FALSE);
    }
    if (hPal)
    {   DeleteObject((HPALETTE)hPal);
}   }

MODULE void CloseGraphic(void* Ipict)
{   IPicture* ip = (IPicture*) Ipict;

    if (ip == NULL)
    {   ip = ImageInfo.Ipic;
    }
    if (ip == NULL)
    {   return;
    }
    ip->lpVtbl->Release(ip);
    memset(&ImageInfo, 0, sizeof(ImageInfo));
}

EXPORT FLAG Exists(STRPTR name)
{   if (GetFileAttributes(name) != 0xFFFFFFFF)
    {   return TRUE;
    } else
    {   return FALSE;
}   }

EXPORT void help_manual(void)
{   PERSIST TEXT localolddir[MAX_PATH + 1]; // PERSISTent so as not to overflow the stack

    DISCARD GetCurrentDirectory(MAX_PATH + 1, localolddir);
    DISCARD SetCurrentDirectory(ProgDir);
    DISCARD ShellExecute(0, NULL, "TnT.htm", NULL, NULL, SW_SHOW);
    DISCARD SetCurrentDirectory(localolddir);
}

EXPORT void show_output(void)
{   TRANSIENT int   i,
                    length,
                    newruncolour,
                    newstartrun,
                    runcolour,
                    startrun,
                    vanillacursor;
    PERSIST   int   oldruncolour = TEXTPEN_LIGHTGREY;
    PERSIST   POINT thepoint     = { 0, 0 };

    if (!SheetWindowPtr)
    {   return;
    }

    length = strlen(vanillascreen);
    // remove trailing newline, if there is one
    if (length >= 1 && vanillascreen[length - 1] == '\n')
    {   vanillascreen[length - 1] = EOS;
    }

    length = strlen(colourscreen);
    // remove trailing newline, if there is one
    if (length >= 1 && colourscreen[length - 1] == '\n')
    {   colourscreen[length - 1] = EOS;
    }

    wrappass(colourscreen,  81);
    wrappass(vanillascreen, 81);

    DISCARD SetDlgItemText(SheetWindowPtr, IDC_OUTPUT, vanillascreen);

    if (colours)
    {   startrun      =
        vanillacursor =
        i             = 0;
        runcolour     = oldruncolour;

        for (;;)
        {   if (colourscreen[i] == EOS)
            {   break;
            } elif (code_to_colour((UBYTE) colourscreen[i]) == -1) // not a colour
            {   vanillacursor++;
            } else
            {   switch (colourscreen[i])
                {
                case  (TEXT) '®': newstartrun = vanillacursor; newruncolour = TEXTPEN_BLACK;
                acase (TEXT) '§': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKRED;
                acase (TEXT) 'ç': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKGREEN;        // "c" with hook on Amiga
                acase (TEXT) '¹': newstartrun = vanillacursor; newruncolour = TEXTPEN_BROWN;            // superscript "1" on Amiga
                acase (TEXT) 'Ø': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKBLUE;         // crossed "O" on Amiga
                acase (TEXT) 'Þ': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKPURPLE;       // uppercase thorn on Amiga
                acase (TEXT) '¿': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKCYAN;         // upside down question mark on Amiga
                acase (TEXT) '¶': newstartrun = vanillacursor; newruncolour = TEXTPEN_LIGHTGREY;
                acase (TEXT) 'þ': newstartrun = vanillacursor; newruncolour = TEXTPEN_DARKGREY;         // lowercase thorn on Amiga
                acase (TEXT) 'ð': newstartrun = vanillacursor; newruncolour = TEXTPEN_PINK;             // "d" with crossbar on Amiga
                acase (TEXT) '³': newstartrun = vanillacursor; newruncolour = TEXTPEN_GREEN;
                acase (TEXT) 'æ': newstartrun = vanillacursor; newruncolour = TEXTPEN_YELLOW;           // "ae" on Amiga
                acase (TEXT) '²': newstartrun = vanillacursor; newruncolour = TEXTPEN_PURPLE;
                acase (TEXT) '¥': newstartrun = vanillacursor; newruncolour = TEXTPEN_BLUE;
                acase (TEXT) 'ø': newstartrun = vanillacursor; newruncolour = TEXTPEN_CYAN;             // crossed "o" on Amiga
                acase (TEXT) '¢': newstartrun = vanillacursor; newruncolour = TEXTPEN_WHITE;            // cent symbol on Amiga
                acase (TEXT) 'ü': newstartrun = vanillacursor; newruncolour = TEXTPEN_BLACKONDARKCYAN;  // "y" with two dots on Amiga
                acase (TEXT) 'û': newstartrun = vanillacursor; newruncolour = TEXTPEN_YELLOWONDARKBLUE; // "z" with one dot  on Amiga
                acase (TEXT) 'ý': newstartrun = vanillacursor; newruncolour = TEXTPEN_BLACKONDARKRED;   // "y/" on Amiga
                }

                fgformat.crTextColor = textpen_to_rgb[runcolour];
                switch (runcolour)
                {
                case  TEXTPEN_BLACKONDARKCYAN:  fgformat.crBackColor = RGB(  0, 171, 171);
                acase TEXTPEN_YELLOWONDARKBLUE: fgformat.crBackColor = RGB(  0,   0, 171);
                acase TEXTPEN_BLACKONDARKRED:   fgformat.crBackColor = RGB(171,   0,   0);
                adefault:                       fgformat.crBackColor = RGB(  0,   0,   0);
                }

                SendDlgItemMessage(SheetWindowPtr, IDC_OUTPUT, EM_SETSEL, startrun, vanillacursor);
                SendDlgItemMessage(SheetWindowPtr, IDC_OUTPUT, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &fgformat);
                startrun = newstartrun;
                runcolour = newruncolour;
            }
            i++;
        }

        fgformat.crTextColor = textpen_to_rgb[runcolour];
        SendDlgItemMessage(SheetWindowPtr, IDC_OUTPUT, EM_SETSEL, startrun, vanillacursor);
        SendDlgItemMessage(SheetWindowPtr, IDC_OUTPUT, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &fgformat);
        oldruncolour = runcolour;
    }

    SendDlgItemMessage(SheetWindowPtr, IDC_OUTPUT, EM_SETSCROLLPOS, (WPARAM) 0, (LPARAM) &thepoint);

    colourscreen[0] = vanillascreen[0] = EOS;
}

EXPORT void loop(FLAG onekeyable)
{   MSG Msg;

    userinput[0] = EOS;
    DISCARD SetDlgItemText(SheetWindowPtr, IDC_INPUT, userinput);
    SetFocus(GetDlgItem(SheetWindowPtr, IDC_INPUT));

    update_sheet();
    column = 0;

    sheet_chosen = -1;
    globaldone = FALSE;
    do
    {   DISCARD WaitMessage();
        while (PeekMessage(&Msg, SheetWindowPtr, 0, 0, PM_REMOVE))
        {   TranslateMessage(&Msg);
            DISCARD DispatchMessage(&Msg);
        }
        if (GfxWindowPtr)
        {   while (PeekMessage(&Msg, GfxWindowPtr, 0, 0, PM_REMOVE))
            {   TranslateMessage(&Msg);
                DISCARD DispatchMessage(&Msg);
        }   }
        Sleep(0);
    } while (!globaldone);
    if (!stricmp(userinput, "Q") || !stricmp(userinput, "QUIT"))
    {   quit(EXIT_SUCCESS);
}   }

LRESULT CALLBACK CustomEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   PERSIST ctrl  = FALSE,
            shift = FALSE;

    switch (msg)
    {
    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_RETURN:
            GetDlgItemText(SheetWindowPtr, IDC_INPUT, userinput, MAX_PATH + 1);
            globaldone = TRUE;
        acase VK_UP:
            if (ctrl)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_TOP     , 0);
            } elif (shift)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_PAGEUP  , 0);
            } else
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_LINEUP  , 0);
            }
        acase VK_DOWN:
            if (ctrl)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_BOTTOM  , 0);
            } elif (shift)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_PAGEDOWN, 0);
            } else
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_LINEDOWN, 0);
            }
        acase VK_PRIOR: // ie. PgUp
            if (ctrl || shift)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_TOP     , 0);
            } else
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_PAGEUP  , 0);
            }
        acase VK_NEXT: // ie. PgDn
            if (ctrl || shift)
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_BOTTOM  , 0);
            } else
            {   SendMessage(GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_PAGEDOWN, 0);
            }
        acase VK_HOME:
            SendMessage(    GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_TOP     , 0);
        acase VK_END:
            SendMessage(    GetDlgItem(SheetWindowPtr, IDC_OUTPUT), WM_VSCROLL, SB_BOTTOM  , 0);
        acase VK_SHIFT:
            shift = TRUE;
        acase VK_CONTROL:
            ctrl = TRUE;
        }
    acase WM_KEYUP:
        switch (wParam)
        {
        case VK_SHIFT:
            shift = FALSE;
        acase VK_CONTROL:
            ctrl = FALSE;
    }   }

    // Pass the messages we don't process here on to the
    // original window procedure for default handling.
    return CallWindowProc(wpOldEditProc, hWnd, msg, wParam, lParam);
}

EXPORT void openconsole(void)
{   int     hScrn, hKybd;
    FILE*   hf;
    PERSIST FLAG consoleopen = FALSE;

    if (consoleopen)
    {   return;
    }

    // assert(!consoleopen);

    AllocConsole(); // might need freeing at program exit
    consoleopen = TRUE;
    hScrn = _open_osfhandle((long) GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT); // might need freeing at program exit
    hKybd = _open_osfhandle((long) GetStdHandle(STD_INPUT_HANDLE ), _O_TEXT); // might need freeing at program exit
    hf = _fdopen(hScrn, "w"); // might need freeing at program exit
    *stdout = *hf;
    hf = _fdopen(hKybd, "r"); // might need freeing at program exit
    *stdin = *hf;
    DISCARD setvbuf(stdout, NULL, _IONBF, 0);
    DISCARD setvbuf(stdin , NULL, _IONBF, 0);
    DISCARD SetConsoleTitle("T&T Output");
    Sleep(50); // to ensure the window title was updated
    ConsoleWindowPtr = FindWindow(NULL, "T&T Output");
    SetConsoleOutputCP(1252);

    DISCARD SetForegroundWindow(SheetWindowPtr);
}

EXPORT void say(STRPTR thestring)
{   if (logconsole)
    {   OC; printf("%s\n", thestring);
    }
    if (LogfileHandle)
    {   DISCARD fwrite(thestring, strlen(thestring), 1, LogfileHandle);
        DISCARD fwrite("\n",                      1, 1, LogfileHandle);
    }

    MessageBox(SheetWindowPtr, thestring, "T&T: Error", MB_ICONEXCLAMATION | MB_OK);
}

/* EXPORT void msg(STRPTR thestring)
{   if (logconsole)
    {   OC; printf("%s\n", thestring);
    }
    if (LogfileHandle)
    {   DISCARD fwrite(thestring, strlen(thestring), 1, LogfileHandle);
        DISCARD fwrite("\n",                      1, 1, LogfileHandle);
    }

    MessageBox(SheetWindowPtr, thestring, "T&T: Message", MB_ICONINFORMATION | MB_OK);
} */

EXPORT void enable_spells(FLAG enabled)
{   EnableWindow(GetDlgItem(SheetWindowPtr, IDC_SPELLS), enabled ? TRUE : FALSE);
}
EXPORT void enable_items(FLAG enabled)
{   EnableWindow(GetDlgItem(SheetWindowPtr, IDC_ITEMS ), enabled ? TRUE : FALSE);
}

EXPORT void do_opts(void)
{   int i;

    for (i = 0; i < 8; i++)
    {   DISCARD SetDlgItemText(SheetWindowPtr, IDC_OPT1 + i, specialopt_long[i]);
        EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT1 + i), (strcmp(specialopt_long[i], "-") ? TRUE : FALSE));
    }

    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT9 ), avail_armour    ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT10), avail_cast      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT11), avail_drop      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT12), avail_fight     ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT13), avail_get       ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT14), avail_hands     ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT15), avail_look      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT16), avail_options   ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT17), avail_proceed   ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT19), avail_autofight ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT20), avail_use       ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT21), avail_view      ? TRUE : FALSE);
}

EXPORT void undo_opts(void)
{   int i;

    for (i = 0; i < 8; i++)
    {   DISCARD SetDlgItemText(SheetWindowPtr, IDC_OPT1 + i, "-");
        EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT1 + i), FALSE);
        strcpy(specialopt_long[i], "-");
    }

    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT9 ), avail_armour    ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT10), avail_cast      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT11), avail_drop      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT12), avail_fight     ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT13), avail_get       ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT14), avail_hands     ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT15), avail_look      ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT16), avail_options   ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT17), avail_proceed   ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT19), avail_autofight ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT20), avail_use       ? TRUE : FALSE);
    EnableWindow(GetDlgItem(SheetWindowPtr, IDC_OPT21), avail_view      ? TRUE : FALSE);
}

EXPORT void system_die1(void)
{   if (iconified)
    {   uniconify(TRUE); // must be done *before* sheet window is closed
    }

#ifdef AMIGA
    textpen(TEXTPEN_LIGHTGREY);
#endif

    DISCARD WSACleanup();
}

EXPORT void system_die2(void)
{   int i;

    for (i = 0; i < MENUBITMAPS; i++)
    {   if (mib[i].bitmap)
        {   DeleteObject(mib[i].bitmap);
            mib[i].bitmap = NULL;
    }   }

    DISCARD RemoveFontResource("TopazPlus.fon");
}

MODULE void iconify(void)
{   NOTIFYICONDATA tnid;

    if (iconified)
    {   return;
    }

    tnid.cbSize           = sizeof(NOTIFYICONDATA);
    tnid.hWnd             = SheetWindowPtr;
    tnid.uID              = 1;
    tnid.uFlags           = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    tnid.uCallbackMessage = USR_NOTIFYICON;
    tnid.hIcon            = (HICON) LoadImage(InstancePtr, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
    strcpy(tnid.szTip, "T&&&T"); // becomes "T&T" (apparently there's no way to use TTS_NOPREFIX for a tray icon tooltip)
    DISCARD Shell_NotifyIcon(NIM_ADD, &tnid);

    ShowWindow(SheetWindowPtr,   SW_HIDE);
    ShowWindow(GfxWindowPtr,     SW_HIDE);
    ShowWindow(ConsoleWindowPtr, SW_HIDE);

    iconified = TRUE;
}

MODULE void uniconify(FLAG exiting)
{   NOTIFYICONDATA tnid;

    // assert(iconified);

    if (!exiting)
    {   ShowWindow(SheetWindowPtr,   SW_SHOW);
        ShowWindow(GfxWindowPtr,     SW_SHOW);
        ShowWindow(ConsoleWindowPtr, SW_SHOW);
        SetActiveWindow(SheetWindowPtr);
    }

    tnid.cbSize = sizeof(NOTIFYICONDATA);
    tnid.hWnd   = SheetWindowPtr;
    tnid.uID    = 1;
    DISCARD Shell_NotifyIcon(NIM_DELETE, &tnid);
    DestroyMenu(TrayMenuPtr);
    TrayMenuPtr = NULL;

    iconified = FALSE;
}

MODULE void traymenu(void)
{   int          i;
    HMENU        TraySubMenuPtr;
    POINT        ThePoint;
    MENUITEMINFO mii =
    {   44,
        MIIM_STATE,
        0,
        MFS_DEFAULT, // to embolden it
        ID_FILE_ICONIFY,
        NULL,
        NULL,
        NULL,
        ID_FILE_ICONIFY,
        0,
        0
    };

    // assert(iconified);

    DISCARD SetForegroundWindow(SheetWindowPtr);
    DISCARD GetCursorPos(&ThePoint);

    TrayMenuPtr = LoadMenu(InstancePtr, MAKEINTRESOURCE(IDR_TRAYMENU));
    for (i = 0; i < MENUBITMAPS; i++)
    {   DISCARD SetMenuItemBitmaps
        (   TrayMenuPtr,     // handle of menu
            mib[i].menuitem, // menu item to receive new bitmaps
            MF_BYCOMMAND,    // menu item flags
            mib[i].bitmap,   // handle of unchecked bitmap
            NULL             // handle of checked bitmap
        );
    }
    TraySubMenuPtr = GetSubMenu(TrayMenuPtr, 0);
    DISCARD SetMenuItemInfo
    (   TraySubMenuPtr,
        ID_FILE_UNICONIFY,
        FALSE,
        &mii
    );
    TrackPopupMenu
    (   TraySubMenuPtr,
        TPM_RIGHTALIGN | TPM_RIGHTBUTTON,
        ThePoint.x,
        ThePoint.y,
        0,
        SheetWindowPtr,
        NULL
    );
    PostMessage(SheetWindowPtr, WM_NULL, 0, 0);
}

EXPORT void help_about(void)
{   DISCARD EnableWindow(SheetWindowPtr, FALSE);
    DialogBox(InstancePtr, MAKEINTRESOURCE(IDD_ABOUT), SheetWindowPtr, AboutDlgProc);
    DISCARD EnableWindow(SheetWindowPtr, TRUE);
}

MODULE BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{   HKEY hKey;
    int  thesize = MAX_PATH;

    switch (Message)
    {
    case WM_INITDIALOG:
        DISCARD SendMessage(GetDlgItem(hwnd, IDL_ABOUT), WM_SETFONT, (WPARAM) hBoldFont, MAKELPARAM(TRUE, 0));
        DISCARD SetDlgItemText(hwnd, IDL_ABOUT,       "Tunnels & Trolls " DECIMALVERSION);
        DISCARD SetDlgItemText(hwnd, IDL_RELEASEDATE, RELEASEDATE);
        SetFocus(GetDlgItem(hwnd, IDC_OK));

        if (RegOpenKeyEx
        (   HKEY_LOCAL_MACHINE,
            "SOFTWARE\\Microsoft\\Shared Tools\\MSInfo",
            0,
            KEY_READ,
            &hKey
        ) == ERROR_SUCCESS)
        {   if (RegQueryValueEx
            (   hKey,
                "Path",
                0,
                NULL,
                SysInfoName,
                (LPDWORD) &thesize
            ) == ERROR_SUCCESS && SysInfoName[0])
            {   EnableWindow(GetDlgItem(hwnd, IDC_SYSINFO), TRUE);
            }
            DISCARD RegCloseKey(hKey);
        }

        DISCARD SetFocus(GetDlgItem(hwnd, IDC_OK));
        return FALSE; // must be FALSE so that our SetFocus()sing is respected
    acase WM_CLOSE:
        EndDialog(hwnd, 0);
        SetFocus(GetDlgItem(SheetWindowPtr, IDC_INPUT));
        return TRUE;
    acase WM_KEYDOWN:
        if (((lParam & 33488896) >> 16) == SCAN_ESCAPE) // hm, this doesn't work
        {   EndDialog(hwnd, 0);
            return TRUE;
        }
    acase WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDC_LINK1:
        case IDC_AMIGANSOFTWARE:
            DISCARD ShellExecute(0, NULL, "http://amigan.1emu.net/releases/", NULL, NULL, SW_SHOW);
        acase IDC_LINK2:
            ShellExecute(0, NULL, "mailto:amigansoftware@gmail.com", NULL, NULL, SW_SHOW);
        acase IDC_LINK3:
            DISCARD ShellExecute(0, NULL, "https://archive.org/details/tunnelsandtrolls", NULL, NULL, SW_SHOW);
        acase IDC_SYSINFO:
            DISCARD ShellExecute(0, NULL, SysInfoName, NULL, NULL, SW_SHOW);
        acase IDC_OK: // NOT IDOK!
            EndDialog(hwnd, 0);
        }
        return TRUE;
    adefault:
        return FALSE;
    }
    return TRUE;
}

EXPORT void openurl(STRPTR command)
{   DISCARD ShellExecute(0, NULL, command, NULL, NULL, SW_SHOW);
}

EXPORT ULONG getsize(const STRPTR filename)
{   HANDLE hFile /* = NULL */ ;
    ULONG  localsize;

    hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {   return 0;
    }
    localsize = GetFileSize(hFile, NULL);
    DISCARD CloseHandle(hFile);
    // hFile = NULL;

    if (localsize == (ULONG) -1)
    {   return 0;
    }

    return localsize;
}

EXPORT void showfiles(void)
{   TRANSIENT FLAG            found = FALSE;
    TRANSIENT int             length;
    TRANSIENT WIN32_FIND_DATA FindData;
    TRANSIENT HANDLE          FindHandle;
    PERSIST   TEXT            tempstring[MAX_PATH + 1]; // PERSISTent so as not to blow the stack

    FindHandle = FindFirstFile("*", &FindData);
    if (FindHandle == INVALID_HANDLE_VALUE)
    {   return;
    }
    do
    {   if (!(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {   strcpy(tempstring, FindData.cFileName);
            length = strlen(tempstring);
            if (length >= 5 && !stricmp(&tempstring[length - 4], ".tnt"))
            {   found = TRUE;
                tempstring[length - 4] = EOS;
                aprintf("%s\n", tempstring);
    }   }   }
    while (FindNextFile(FindHandle, &FindData));
    DISCARD FindClose(FindHandle);

    if (!found)
    {   aprintf("None\n");
    }
    aprintf("\n");
}

#define ICONS 27
MODULE void make_glyphs(void)
{   TRANSIENT int   i;
    TRANSIENT HICON hicon;

    himl = ImageList_Create(16, 16, ILC_COLOR8, ICONS, 0); // items listview control automatically destroys this image list for us when it is itself destroyed

    for (i = 0; i < 27; i++)
    {   hicon = LoadImage(InstancePtr, MAKEINTRESOURCE(glyphs[i]), IMAGE_ICON, 16, 16, 0);
        DISCARD ImageList_AddIcon(himl, hicon); // should check this return code. Returns -1 for failure
        DeleteObject(hicon);
}   }

EXPORT void busypointer(void)
{   SetCursor(hBusy);
}
EXPORT void normalpointer(void)
{   SetCursor(hArrow);
}

EXPORT void setconcolour(int fg)
{   if (!colours || textcolour == fg)
    {   return;
    }
    textcolour = fg;

    DISCARD SetConsoleTextAttribute
    (   GetStdHandle(STD_OUTPUT_HANDLE),
        nt[textcolour]
    );
}

EXPORT void maybelf(void)
{   strcat(colourscreen, "\n"); \
    strcat(vanillascreen, "\n");
}

EXPORT void cls(void) // this doesn't actually work
{   COORD coordScreen = { 0, 0 };    /* here's where we'll home the
                                        cursor */
    BOOL bSuccess;
    DWORD cCharsWritten;
    CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */
    DWORD dwConSize;                 /* number of character cells in
                                        the current buffer */

    /* get the number of character cells in the current buffer */
    bSuccess = GetConsoleScreenBufferInfo(ConsoleWindowPtr, &csbi);
    dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
    bSuccess = FillConsoleOutputCharacter(ConsoleWindowPtr, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten);
    bSuccess = GetConsoleScreenBufferInfo(ConsoleWindowPtr, &csbi);
    bSuccess = FillConsoleOutputAttribute(ConsoleWindowPtr, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
    bSuccess = SetConsoleCursorPosition(  ConsoleWindowPtr, coordScreen);

    row = column = 0;
}

EXPORT FLAG istrouble(TEXT thechar)
{   if
    (   (    thechar == 161
         ||  thechar == 175
         || (thechar >= 180 && !(thechar & 1))
         ||  thechar >= 192
        )
     && code_to_colour(thechar) == -1
    )
    {   return TRUE;
    } else
    {   return FALSE;
}   }

EXPORT FLAG asl_load(void)
{   int          length;
    OPENFILENAME ofn;

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = SheetWindowPtr;
    ofn.lpstrTitle      = "Load Character";
    ofn.lpstrFilter     = "T&T Characters (*.TNT)\0*.TNT\0" \
                          "All Files (*.*)\0*.*\0";
    ofn.lpstrFile       = pathname;
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrFileTitle  = name;
    ofn.nMaxFileTitle   = 80;
    ofn.Flags           = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    if (GetOpenFileName(&ofn))
    {   length = strlen(name);
        if (length >= 5 && !stricmp(&name[length - 4], ".TNT"))
        {   name[length - 4] = EOS;
        }
        return TRUE;
    } else
    {   return FALSE;
}   }

EXPORT FLAG asl_import(void)
{   OPENFILENAME ofn;

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = SheetWindowPtr;
    ofn.lpstrTitle      = "Import Character";
    ofn.lpstrFilter     = "Cruaders of Khazan Characters (CROS.?)\0CROS.?\0" \
                          "All Files (*.*)\0*.*\0";
    ofn.lpstrFile       = ck_filename;
    ofn.nMaxFile        = MAX_PATH;
    ofn.Flags           = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    if (GetOpenFileName(&ofn))
    {   return TRUE;
    } else
    {   return FALSE;
}   }

EXPORT FLAG asl_delete(void)
{   OPENFILENAME ofn;

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.hwndOwner       = SheetWindowPtr;
    ofn.lpstrTitle      = "Delete Character";
    ofn.lpstrFilter     = "T&T Characters (*.TNT,CROS.?)\0*.TNT;CROS.?\0" \
                          "All Files (*.*)\0*.*\0";
    ofn.lpstrFile       = ck_filename;
    ofn.nMaxFile        = MAX_PATH;
    ofn.Flags           = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    if (GetOpenFileName(&ofn))
    {   return TRUE;
    } else
    {   return FALSE;
}   }
    
/* EXPORT void make_tips(void)
{   TOOLINFO ti;

    if (!(TipsPtr = CreateWindowEx
    (   WS_EX_TOPMOST,
        TOOLTIPS_CLASS,
        (LPSTR) NULL,
        WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        SheetWindowPtr,
        (HMENU) NULL,
        InstancePtr,
        NULL
    )))
    {   return;
    } // implied else

    SetWindowPos(TipsPtr, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // apparently not needed

    SendMessage(TipsPtr, TTM_SETMAXTIPWIDTH, 0,            32767); // to enable multi-line support
    SendMessage(TipsPtr, TTM_SETDELAYTIME,   TTDT_AUTOPOP, 32767); // tooltip duration
    SendMessage(TipsPtr, TTM_SETDELAYTIME,   TTDT_INITIAL,     0); // tooltip delay
    SendMessage(TipsPtr, TTM_SETDELAYTIME,   TTDT_RESHOW,      0); // tooltip delay

    ti.cbSize   = sizeof(TOOLINFO);
    ti.uFlags   = TTF_IDISHWND | TTF_SUBCLASS;
    ti.hwnd     = SheetWindowPtr;
    ti.hinst    = InstancePtr;

    ti.uId      = (UINT_PTR) GetDlgItem(SheetWindowPtr, IDC_ITEMS);
    ti.lpszText = "Items";
    SendMessage(TipsPtr, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

    ti.uId      = (UINT_PTR) GetDlgItem(SheetWindowPtr, IDC_SPELLS);
    ti.lpszText = "Spells";
    SendMessage(TipsPtr, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

    SendMessage(TipsPtr, TTM_ACTIVATE, (WPARAM) TRUE, 0); // it's active anyway by default
} */

EXPORT FLAG confirmed(void)
{   if (MessageBoxEx
    (   SheetWindowPtr, // safe even if NULL
        "Really quit?",
        "Tunnels & Trolls",
        MB_ICONQUESTION | MB_YESNO | MB_APPLMODAL,
        (WORD) MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK)
    ) == IDYES)
    {   return TRUE;
    } // implied else
    return FALSE;
}
