/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	Jacob's Matrix Development
 *
 * NAME:        main.cpp ( Jacob's Matrix, C++ )
 *
 * COMMENTS:
 */

#include <libtcod.hpp>
#undef CLAMP
#include <stdio.h>
#include <time.h>

//#define USE_AUDIO

#define USE_VOICE 1

#define GROUND_ITEMNO -5

#define POPUP_DELAY 1000

#ifdef WIN32
#include <windows.h>
#include <process.h>
#define usleep(x) Sleep((x)*1000)
// #pragma comment(lib, "SDL.lib")
// #pragma comment(lib, "SDLmain.lib")
#include <sapi.h>
#include <string.h>

ISpVoice *glbVoice = nullptr;

// I really hate windows.
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif

#else
#include <unistd.h>
#endif

#include <fstream>
using namespace std;

int		 glbLevelStartMS = -1;

#include "rand.h"
#include "gfxengine.h"
#include "config.h"
#include "builder.h"
#include "dircontrol.h"
#include "thesaurus.h"
#include "level.h"

//#include <SDL.h>
#ifdef USE_AUDIO
#include <SDL_mixer.h>

Mix_Music		*glbTunes = 0;
#endif
bool			 glbMusicActive = false;

// Cannot be bool!
// libtcod does not accept bools properly.

volatile int		glbItemCache = -1;

SPELL_NAMES		glbLastSpell = SPELL_NONE;
int			glbLastItemIdx = 0;
int			glbLastItemAction = 0;
const char		*glbWorldName = "wtf";
bool			glbReloadedLevel = true;

BUF			 glbPlayerName;
BUF			 glbStatLineFormat;
bool			 glbStatLineSwap;

void endOfMusic()
{
    // Keep playing.
    // glbMusicActive = false;
}

void
setMusicVolume(int volume)
{
#ifdef USE_AUDIO
    volume = BOUND(volume, 0, 10);

    glbConfig->myMusicVolume = volume;

    Mix_VolumeMusic((MIX_MAX_VOLUME * volume) / 10);
#endif
}

void
stopMusic(bool atoncedamnit = false)
{
#ifdef USE_AUDIO
    if (glbMusicActive)
    {
	if (atoncedamnit)
	    Mix_HaltMusic();
	else
	    Mix_FadeOutMusic(2000);
	glbMusicActive = false;
    }
#endif
}


void
startMusic()
{
#ifdef USE_AUDIO
    if (glbMusicActive)
	stopMusic();

    if (!glbTunes)
	glbTunes = Mix_LoadMUS(glbConfig->musicFile());
    if (!glbTunes)
    {
	printf("Failed to load %s, error %s\n", 
		glbConfig->musicFile(), 
		Mix_GetError());
    }
    else
    {
	glbMusicActive = true;
	glbLevelStartMS = TCOD_sys_elapsed_milli();

	if (glbConfig->musicEnable())
	{
	    Mix_PlayMusic(glbTunes, -1);		// Infinite
	    Mix_HookMusicFinished(endOfMusic);

	    setMusicVolume(glbConfig->myMusicVolume);
	}
    }
#endif
}


#ifdef main
#undef main
#endif

#include "mob.h"
#include "map.h"
#include "text.h"
#include "display.h"
#include "engine.h"
#include "panel.h"
#include "msg.h"
#include "firefly.h"
#include "item.h"
#include "chooser.h"

// Probably a sign I should be factoring this out...
int printAllInventory(MOB *mob);
int printItemList(const ITEMLIST &items);

PANEL		*glbMessagePanel = 0;
DISPLAY		*glbDisplay = 0;
MAP		*glbMap = 0;
ENGINE		*glbEngine = 0;
PANEL		*glbPopUp = 0;
PANEL		*glbJacob = 0;
PANEL		*glbStatLine = 0;
PANEL		*glbHelp = 0;
PANEL		*glbVictoryInfo = 0;
bool		 glbPopUpActive = false;
bool		 glbRoleActive = false;
FIREFLY		*glbCoinStack = 0;
int		 glbLevel = 0;
int		 glbMaxLevel = 0;
PTRLIST<int>	 glbLevelTimes;
bool		 glbVeryFirstRun = true;

CHOOSER		*glbLevelBuildStatus = 0;

CHOOSER		*glbChooser = 0;
bool		 glbChooserActive = false;

int		 glbInvertX = -1, glbInvertY = -1;
int		 glbMeditateX = -1, glbMeditateY = -1;
int		 glbWaypointX = -1, glbWaypointY = -1;
IVEC2		 glbUnitWaypoint[10];
bool		 glbHasDied = false;

struct SEENOBJECT
{
    POS pos;
    int uid;

    bool 	operator==(const SEENOBJECT &cmp) const
    {	return pos == cmp.pos && uid == cmp.uid; }
    bool 	operator!=(const SEENOBJECT &cmp) const
    {	return !(*this == cmp); }
};

PTRLIST<SEENOBJECT>	glbLastSeenMobs, glbLastSeenItems, glbLastSeenDoorways;
LOCK	glbReportLock;

void reportMobs(MAP *map, bool ignoreseen);
void reportItems(MAP *map, bool ignoreseen);
void reportDoorways(MAP *map, bool ignoreseen);
void reportTiles(MAP *map, TILE_NAMES tile, bool requiremap, bool requirefov);

void
updateSpeechFromConfig()
{
#ifdef WIN32
    if (!glbVoice)
	return;

    BUF		setspeed;
    setspeed.sprintf("<rate absspeed=\"%d\"/>",
		    glbConfig->myTTSSpeed);

    wstring	text;
    string ntext;
    ntext.assign(setspeed.buffer());
    text.assign(ntext.begin(), ntext.end());

    glbVoice->Speak(text.data(), SPF_IS_XML | SPF_PERSIST_XML | SPF_ASYNC, nullptr);
#endif
}

#define DEATHTIME 15000

ATTR_NAMES
colourFromRatio(int cur, int max)
{
    if (cur >= max)
	return ATTR_LTGREY;
    if (cur < 5 || (cur < max / 6))
	return ATTR_RED;
    if (cur < max / 2)
	return ATTR_LTYELLOW;
    return ATTR_LTGREEN;
}

BUF
mstotime(int ms)
{
    int		sec, min;
    BUF		buf;

    sec = ms / 1000;
    ms -= sec * 1000;
    min = sec / 60;
    sec -= min * 60;

    if (min)
	buf.sprintf("%dm%d.%03ds", min, sec, ms);
    else if (sec)
	buf.sprintf("%d.%03ds", sec, ms);
    else
	buf.sprintf("0.%03ds", ms);

    return buf;
}

class MOBPTR_SORT
{
public:
    bool operator()(MOB *a, MOB *b)
    {
	return a->getUID() < b->getUID();
    }
};

class ITEMPTR_SORT
{
public:
    bool operator()(ITEM *a, ITEM *b)
    {
	return a->getUID() < b->getUID();
    }
};

KEY_NAMES
cookKey(u8 rawkey)
{
    KEY_NAMES	key;
    FOREACH_KEY(key)
    {
	if (GAMEDEF::keydef(key)->val == rawkey)
	    return key;
    }

    return NUM_KEYS;
}

void
swapKeys(int keya, int keyb)
{
    KEY_NAMES	a, b;
    a = cookKey(keya);
    b = cookKey(keyb);
    if (a == NUM_KEYS || b == NUM_KEYS)
    {
	J_ASSERT(!"Invalid key given!");
	return;
    }
    GAMEDEF::keydef(a)->val = keyb;
    GAMEDEF::keydef(b)->val = keya;
}

bool
cookDirKey(int &key, int &dx, int &dy, bool allowshift)
{
    // Do not try to cook vi keys as we want to handl them ourselves!
    if (!strchr("yuhjklbn", key))
    {
	// Comment this out to avoid giving free keys for numerics & arrows.
	if (gfx_cookDir(key, dx, dy, GFX_COOKDIR_ALL, allowshift, 5))
	    return true;
    }
    KEY_NAMES	cookkey = cookKey(key);
    switch (cookkey)
    {
	case KEY_MOVE_NW:
	    dx = -1;	dy = -1;
	    key = 0;
	    return true;
	case KEY_MOVE_N:
	    dx =  0;	dy = -1;
	    key = 0;
	    return true;
	case KEY_MOVE_NE:
	    dx = +1;	dy = -1;
	    key = 0;
	    return true;
	case KEY_MOVE_W:
	    dx = -1;	dy =  0;
	    key = 0;
	    return true;
	case KEY_MOVE_STAY:
	    dx =  0;	dy =  0;
	    key = 0;
	    return true;
	case KEY_MOVE_E:
	    dx = +1;	dy =  0;
	    key = 0;
	    return true;
	case KEY_MOVE_SW:
	    dx = -1;	dy = +1;
	    key = 0;
	    return true;
	case KEY_MOVE_S:
	    dx =  0;	dy = +1;
	    key = 0;
	    return true;
	case KEY_MOVE_SE:
	    dx = +1;	dy = +1;
	    key = 0;
	    return true;
	default:
	    // Ignore other ones.
	    break;
    }
    // Don't eat.
    return false;
}

void
updateHelp(PANEL *panel)
{
    // By doing this here we can be a bit fancier.
    panel->clear();

    // No help as all a raw output!
    // panel->appendText("[?] Keyboard Help\n");
    // panel->appendText("\n");
}

// Should call this in all of our loops.
void
redrawWorldNoFlush()
{
    static MAP	*centermap = 0;
    static POS	 mapcenter;
    bool	 isblind = false;
    bool	 isvictory = false;
    static WORLD_NAMES lastworld = (WORLD_NAMES) 0;

    gfx_updatepulsetime();

    if (glbMap)
	glbMap->decRef();
    glbMap = glbEngine ? glbEngine->copyMap() : 0;

    if (glbMap)
    {
	if (glbMap->world() != lastworld)
	{
	    // Swap worlds, currently just change font.
	    for (int i = 64; i < 128; i++)
	    {
		TCODConsole::mapAsciiCodeToFont(i,
			(i >> 4) + glb_worlddefs[glbMap->world()].font * 4,
			i & 15);
	    }
	    // Clear the tcod cache:
	    for (int y = 0; y < SCR_HEIGHT; y++)
		for (int x = 0; x < SCR_WIDTH; x++)
		    gfx_printchar(x, y, ' ', ATTR_NORMAL);
	    TCODConsole::flush();

	    lastworld = glbMap->world();
	}
    }

    // Wipe the world
    for (int y = 0; y < SCR_HEIGHT; y++)
	for (int x = 0; x < SCR_WIDTH; x++)
	    gfx_printchar(x, y, ' ', ATTR_NORMAL);


    glbStatLine->clear();
    glbHelp->clear();

    updateHelp(glbHelp);

    glbMeditateX = -1;
    glbMeditateY = -1;
    int			avataralive = 0;
    if (glbMap && glbMap->avatar())
    {
	MOB		*avatar = glbMap->avatar();
	int		 timems;

	isvictory = false;

	avataralive = avatar->alive();

	if (avatar->alive() && avatar->isMeditating() && avatar->isMeditateDecoupled())
	{
	    glbMeditateX = glbDisplay->x() + glbDisplay->width()/2;
	    glbMeditateY = glbDisplay->y() + glbDisplay->height()/2;
	}

	timems = TCOD_sys_elapsed_milli();

	if (centermap)
	    centermap->decRef();
	centermap = glbMap;
	centermap->incRef();
	mapcenter = avatar->meditatePos();

	isblind = avatar->hasItem(ITEM_BLIND);

	if (glbCoinStack)
	{
	    int		ngold = avatar->getHP() / 2;

	    glbCoinStack->setParticleCount(BOUND(ngold, 0, 22 * SCR_HEIGHT));
	    glbCoinStack->setBloodCoins(0);
	    glbCoinStack->setRatioLiving((float) (BOUND(ngold, 0, 20*(SCR_HEIGHT-2)) / float(20*(SCR_HEIGHT-2))));
	}

	BUF		buf;

	{
	    MOBLIST	atlist;

	    glbStatLine->setTextAttr(ATTR_WHITE);
	    glbStatLine->clear();
	}
    }
    
    glbDisplay->display(mapcenter, isblind);

    glbMessagePanel->redraw();

    {
	glbJacob->clear();

	// Empty, as all messages.
    }

    glbJacob->redraw();

    glbHelp->redraw();

    glbStatLine->clear();
    if (glbMap && glbMap->avatar())
    {
	MOB		*avatar = glbMap->avatar();

	glbStatLine->setTextAttr(ATTR_NORMAL);
	BUF		 buf;
	buf.sprintf("Location: %-6s Floor: %2d/%d Health: %3d/%d Level: %2d (%d/%d) Status: ",
		glb_worlddefs[glbMap->world()].name,
		glbMap->world() == WORLD_FIGHTER 
		    ? avatar->pos().depth() 
		    : (GAMEDEF::rules().bosslevel - avatar->pos().depth()+1), 
		GAMEDEF::rules().bosslevel,
		avatar->getHP(), avatar->getMaxHP(),
		avatar->getLevel(), avatar->getExp(), LEVEL::expFromLevel(avatar->getLevel()+1)
		);
	glbStatLine->appendText(buf);

	bool		anystatus = false;

	if (avatar->healthStatus() != HEALTHLEVEL_FULL)
	{
	    buf.clear();
	    glbStatLine->setTextAttr(glb_healthleveldefs[avatar->healthStatus()].attr);
	    buf.strcat(gram_capitalize(avatar->healthStatusDescr()));
	    buf.strcat(" ");
	    glbStatLine->appendText(buf);
	    anystatus = true;
	}
	if (avatar->foodStatus() != FOODLEVEL_FULL)
	{
	    buf.clear();
	    glbStatLine->setTextAttr(glb_foodleveldefs[avatar->foodStatus()].attr);
	    buf.strcat(gram_capitalize(avatar->foodStatusDescr()));
	    buf.strcat(" ");
	    glbStatLine->appendText(buf);
	    anystatus = true;
	}

	buf.clear();
	glbStatLine->setTextAttr(ATTR_NORMAL);
	for (auto && item : avatar->inventory())
	{
	    if (!item->isFlag())
		continue;
            if (item->getDefinition() == ITEM_RESIST || item->getDefinition() == ITEM_VULNERABLE)
            {
                glbStatLine->setTextAttr(GAMEDEF::elementdef(item->element())->attr);
                if (item->getDefinition() == ITEM_RESIST)
                    buf.strcat("Resist ");
                else
                    buf.strcat("Vuln ");
                buf.strcat(gram_capitalize(GAMEDEF::elementdef(item->element())->name));
		buf.strcat(" ");
            }
            else
            {
		glbStatLine->setTextAttr(ATTR_NORMAL);
                buf.strcat(gram_capitalize(item->getName()));
                buf.strcat(" ");
            }

            glbStatLine->appendText(buf);
            buf.clear();
	    anystatus = true;
	}
	glbStatLine->setTextAttr(ATTR_NORMAL);

	if (!anystatus)
	{
	    buf.strcat("None");
	}

	glbStatLine->appendText(buf);
    }

    glbStatLine->redraw();

    if (isvictory)
	glbVictoryInfo->redraw();

    {
	// we have to clear the RHS.
	static FIRETEX		 *cleartex = 0;
	if (!cleartex)
	{
	    cleartex = new FIRETEX(30, (SCR_HEIGHT-2));
	    cleartex->buildFromConstant(false, 0, FIRE_HEALTH);
	}
	cleartex->redraw(SCR_WIDTH-30, 2);
    }
    if (glbCoinStack)
    {
	FIRETEX		*tex = glbCoinStack->getTex();
	tex->redraw(0, 0);
	tex->decRef();
    }

#if 0
    if (glbMeditateX >= 0 && glbMeditateY >= 0)
    {
	gfx_printattrback(glbMeditateX, glbMeditateY, ATTR_AVATARMEDITATE);
    }
#endif

    if (glbInvertX >= 0 && glbInvertY >= 0)
    {
	gfx_printattr(glbInvertX, glbInvertY, ATTR_HILITE);
    }

    if (glbWaypointX >= 0 && glbWaypointY >= 0)
    {
	gfx_printattrback(glbWaypointX, glbWaypointY, ATTR_LTWAYPOINT);
    }

    if (glbItemCache >= 0)
    {
	glbLevelBuildStatus->clear();

	glbLevelBuildStatus->appendChoice("   Building Distance Caches   ", glbItemCache);
	glbLevelBuildStatus->redraw();
    }

    if (glbChooserActive)
    {
	glbChooser->redraw();
    }

    if (glbPopUpActive)
	glbPopUp->redraw();
}

void
redrawWorld()
{
    redrawWorldNoFlush();
    TCODConsole::flush();
}

// Pops up the given text onto the screen.
// The key used to dismiss the text is the return code.
int
popupText(const char *buf, int delayms = 0)
{
if (!glbConfig->myUseMenus)
{
    msg_report(buf);
    msg_newturn();

    return ' ';
}
else
{
    int		key = 0;
    int		startms = TCOD_sys_elapsed_milli();

    glbPopUp->clear();
    glbPopUp->resize(66, SCR_HEIGHT-2);
    glbPopUp->move(SCR_WIDTH/2 - 66/2, 1);

    // A header matches the footer.
    glbPopUp->appendText("\n");

    // Must make active first because append text may trigger a 
    // redraw if it is long!
    glbPopUpActive = true;
    glbPopUp->appendText(buf);

    // In case we drew due to too long text
    glbPopUp->erase();

    // Determine the size of the popup and resize.
    glbPopUp->shrinkToFit();

    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);
	if (delayms)
	{
	    int		currentms = (int)TCOD_sys_elapsed_milli() - startms;
	    float	ratio = currentms / (float)delayms;
	    ratio = BOUND(ratio, 0, 1);

	    glbPopUp->fillRect(0, glbPopUp->height()-1, (int)(glbPopUp->width()*ratio+0.5), 1, ATTR_WAITBAR);
	}
	if (key)
	{
	    // Don't let them abort too soon.
	    if (((int)TCOD_sys_elapsed_milli() - startms) >= delayms)
		break;
	}
    }

    glbPopUpActive = false;
    glbPopUp->erase();

    glbPopUp->resize(50, 15);
    glbPopUp->move(15, 5);

    redrawWorld();

    return key;
}
}

int
popupText(BUF buf, int delayms = 0)
{
    return popupText(buf.buffer(), delayms);
}

// Pops up the given text onto the screen.
// Allows the user to enter a response.
// Returns what they typed.
BUF
popupQuestion(const char *buf)
{
#define RESPONSE_LEN 50
    char	response[RESPONSE_LEN];

    glbPopUp->clear();
    glbPopUp->resize(50, SCR_HEIGHT-2);
    glbPopUp->move(15, 1);

    // A header matches the footer.
    glbPopUp->appendText("\n");

    // Must make active first because append text may trigger a 
    // redraw if it is long!
    glbPopUpActive = true;
    glbPopUp->appendText(buf);
    glbPopUp->appendText("\n");

    // In case we drew due to too long text
    glbPopUp->erase();

    // Determine the size of the popup and resize.
    glbPopUp->shrinkToFit();
    glbPopUp->resize( MAX(RESPONSE_LEN + 2, glbPopUp->getW()),
		      glbPopUp->getH() + 3 );

    redrawWorld();
    glbPopUp->getString("\n> ", response, RESPONSE_LEN);

    BUF		result;
    result.strcpy(response);

    glbPopUpActive = false;
    glbPopUp->erase();

    glbPopUp->resize(50, 30);
    glbPopUp->move(15, 10);

    redrawWorld();

    return result;
}

BUF
popupQuestion(BUF buf)
{
    return popupQuestion(buf.buffer());
}

template <typename T>
T
runMenuSelection(const PTRLIST<T> &list, T fallback, 
		 const PTRLIST<int> &hotkeys)
{
    // Run the chooser...
    if (!list.entries())
	return fallback;

    glbChooserActive = true;

    int		key;
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	glbChooser->processKey(key);

	if (key)
	{
	    int		itemno = glbChooser->getChoice();
	    // User selects this?
	    if (key == '\n' || key == ' ')
	    {
		fallback = list(itemno);
		break;
	    }
	    else if (key == '\x1b')
	    {
		// Esc on options is to go back to play.
		// Play...
		break;
	    }
	    else
	    {
		// Ignore other options.
		for (int i = 0; i < hotkeys.entries(); i++)
		{
		    if (key == hotkeys(i))
		    {
			fallback = list(i);
			glbChooserActive = false;
			return fallback;
		    }
		}
	    }
	}
    }
    glbChooserActive = false;
    return fallback;
}

//
// Prints the welcome text, with proper varialbes
//
void
welcomeText(const char *key)
{
    popupText(text_lookup("welcome", key));
}

bool
awaitAlphaKey(int &key)
{
    key = 0;
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	if (key >= 'a' && key <= 'z')
	    return true;
	else if (key)
	    return false;
    }

    return false;
}

//
// Freezes the game until a key is pressed.
// If key is not direction, false returned and it is eaten.
// else true returned with dx/dy set.  Can be 0,0.
bool
awaitDirection(int &dx, int &dy)
{
    int			key;

    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();

	key = gfx_getKey(false);

	if (cookDirKey(key, dx, dy, false))
	{
	    return true;
	}
	else if (key)
	{
	    // Invalid direction.  We are very mean and just pick
	    // a random direction.  Hopefully I remember to remove this
	    // from future versions :>
	    // I came very close to forgetting!
	    return false;
	}
	if (key)
	    return false;
    }

    return false;
}

void
buildIntrinsics(ITEMLIST &items, MOB *mob)
{
    if (!mob)
        return;

    for (auto && item : mob->inventory())
    {
	if (item->isFlag())
	    items.append(item);
    }
}

template <typename OP>
void
buildFilteredInventory(ITEMLIST &items, MOB *mob, const OP &op)
{
    if (!mob)
	return;

    for (auto && item : mob->inventory())
    {
	if (item->isFlag())
	    continue;
	if (op(item))
	    items.append(item);
    }

    items.stableSort([](ITEM *a, ITEM *b) -> bool
    {
	return a->inventoryLetter() < b->inventoryLetter();
    });
}

template <typename OP>
void
buildFilteredGroundItems(ITEMLIST &items, POS pos, const OP &op)
{
    if (!pos.valid())
	return;

    ITEMLIST	potential;
    pos.getAllItems(potential);
    for (auto && item : potential)
    {
	if (item->isFlag())
	    continue;
	if (op(item))
	    items.append(item);
    }
}


ITEM_NAMES
itemFromItemno(int itemno)
{
    if (!glbMap) return ITEM_NONE;
    MOB *mob = glbMap->avatar();
    if (!mob) return ITEM_NONE;
    ITEM        *item = mob->getItemFromNo(itemno);
    if (item)
        return item->getDefinition();
    return ITEM_NONE;
}

template <typename OP>
int
chooseItem(const char *prompt, const OP &op, bool includeground=false, int *number = nullptr)
{
    if (number)
	*number = 0;		// All.

    if (!glbMap) return -1;
    MOB *mob = glbMap->avatar();
    if (!mob) return -1;

    ITEMLIST		filteritems;
    buildFilteredInventory(filteritems, mob, op);

if (!glbConfig->myUseMenus)
{
    BUF		report;
    report.sprintf("%s [", prompt);
    if (number)
	report.strcat("#");
    for (auto && item : filteritems)
    {
	report.append(item->inventoryLetter());
    }
    if (includeground)
	report.strcat(".");
    report.strcat("?*]");

    while (true)
    {
	msg_report(report);
	msg_newturn();
	redrawWorld();

	int		key;
	bool		chosen = awaitAlphaKey(key);

	if (chosen)
	{
	    ITEM *chosen = mob->itemFromLetter(key);
	    if (!chosen)
	    {
		msg_report("You have nothing in that slot.");
		msg_newturn();
		return -1;
	    }
	    msg_format("%S\n", chosen);
	    return mob->itemNoFromItem(chosen);
	}

	if (key == '?')
	{
	    // List inventory of matching.
	    printInventory(mob, op);
	}
	else if (key == '*')
	{
	    // List full inventory
	    printAllInventory(mob);
	}
	else if (key == '.' && includeground)
	{
	    return GROUND_ITEMNO;
	}
	else if (MYisdigit(key) && number)
	{
	    *number = key - '0';
	    BUF	amt;
	    if (*number)
		amt.sprintf("Limit to %d.", *number);
	    else
		amt.sprintf("No limit.", *number);
	    msg_report(amt);
	}
	else
	{
	    // We cancel for unknown
	    msg_report("Cancelled.");
	    msg_newturn();
	    break;
	}
    }
    return -1;
}
else
{
    glbChooser->clear();
    glbChooser->setPrequel(prompt);
    PTRLIST<int>	itemnolist;
    PTRLIST<int>	hotkeys;
    for (auto && item : filteritems)
    {
	itemnolist.append(mob->itemNoFromItem(item));
	hotkeys.append(item->inventoryLetter());
	BUF		buf;
	buf.sprintf("[%c] %s%s%s%s", item->inventoryLetter(),
		gram_capitalize(item->getName()).buffer(),
		item->isEquipped() ? " (equipped)" : "",
		item->isWielded() ? " (wielded)" : "",
		(item->isSpellbook() && mob->isSpellMemorized(item->asSpell()))  ? " (known)" : ""
		);
	glbChooser->appendChoice(buf);
    }
    if (includeground)
    {
	itemnolist.append(GROUND_ITEMNO);
	hotkeys.append('.');

	glbChooser->appendChoice("[.] From the ground...");
    }
    itemnolist.append(-1);
    hotkeys.append(0);
    glbChooser->appendChoice("    Nothing");

    glbChooser->setChoice(0);
    int itemno = runMenuSelection(itemnolist, -1, hotkeys);
    return itemno;
}
}

template <typename OP>
int
chooseGroundItem(const char *prompt, const OP &op, bool includeinventory=false)
{
    if (!glbMap) return -1;
    MOB *mob = glbMap->avatar();
    if (!mob) return -1;

    ITEMLIST		filteritems;
    buildFilteredGroundItems(filteritems, mob->pos(), op);

if (!glbConfig->myUseMenus)
{
    while (true)
    {
        msg_report(prompt);
        msg_report("\n");
	printItemList(filteritems);
	if (includeinventory)
	    msg_report("[.] From inventory\n");
	msg_report("[?] List Matching\n");
	msg_report("[*] List All\n");
	msg_newturn();
	redrawWorld();

	int		key;
	bool		chosen = awaitAlphaKey(key);

	if (chosen)
	{
	    int		idx = key - 'a';
	    if (idx >= filteritems.entries())
	    {
		msg_report("That doesn't refer to an item.");
		msg_newturn();
		return -1;
	    }
	    if (key == 'z')
	    {
		// Move to next page.
		for (int i = 0; i < 25; i++)
		    filteritems.removeAt(0);
		continue;
	    }
	    else
		return filteritems(idx)->getUID();
	}

	if (key == '?')
	{
	    // List inventory of matching.
            filteritems.clear();
	    buildFilteredGroundItems(filteritems, mob->pos(), op);
	}
	else if (key == '*')
	{
	    // List full
            filteritems.clear();
	    buildFilteredGroundItems(filteritems, mob->pos(), ITEM::filterAll());
	}
	else if (key == '.' && includeinventory)
	{
	    return GROUND_ITEMNO;
	}
	else
	{
	    // We cancel for unknown
	    msg_report("Cancelled.");
	    msg_newturn();
	    break;
	}
    }
    return -1;
}
else
{
    glbChooser->clear();
    glbChooser->setPrequel(prompt);
    PTRLIST<int>	itemnolist;
    PTRLIST<int>	hotkeys;		// none
    for (auto && item : filteritems)
    {
	itemnolist.append(item->getUID());
	BUF		buf;
	buf.sprintf("%s%s%s%s", 
		gram_capitalize(item->getName()).buffer(),
		item->isEquipped() ? " (equipped)" : "",
		item->isWielded() ? " (wielded)" : "",
		(item->isSpellbook() && mob->isSpellMemorized(item->asSpell()))  ? " (known)" : ""
		);
	glbChooser->appendChoice(buf);
    }
    if (includeinventory)
    {
	itemnolist.append(GROUND_ITEMNO);
	glbChooser->appendChoice("From your backpack...");
    }
    itemnolist.append(-1);
    glbChooser->appendChoice("Nothing");

    glbChooser->setChoice(0);
    int uuid = runMenuSelection(itemnolist, -1, hotkeys);
    return uuid;
}
}

template <typename OP>
void
actionFromChosenItem(ACTION_NAMES action, const char *prompt, const OP &filter, int dy=-1)
{
    int		itemno = chooseItem(prompt, filter);
    if (itemno >= 0)
    {
	glbEngine->queue().append(COMMAND(action, itemno, dy));
    }
}

template <typename OP>
void
actionFromChosenNumberedItem(ACTION_NAMES action, const char *prompt, const OP &filter, int dy=-1)
{
    int		number = 0;
    int		itemno = chooseItem(prompt, filter, /*ground*/false, &number);
    if (itemno >= 0)
    {
	glbEngine->queue().append(COMMAND(action, itemno, dy, number));
    }
}

template <typename OP>
void
actionFromChosenItemOrGround(ACTION_NAMES invaction, ACTION_NAMES groundaction, bool doinventory, const char *prompt, const OP &filter)
{
    while (1)
    {
	if (doinventory)
	{
	    int		itemno = chooseItem(prompt, filter, true);
	    if (itemno >= 0)
	    {
		glbEngine->queue().append(COMMAND(invaction, itemno));
		return;
	    }
	    else if (itemno == GROUND_ITEMNO)
		doinventory = false;
	    else
		return;
	}
	else
	{
	    int		itemid = chooseGroundItem(prompt, filter, true);
	    if (itemid > 0)
	    {
		glbEngine->queue().append(COMMAND(groundaction, itemid));
		return;
	    }
	    else if (itemid == GROUND_ITEMNO)
		doinventory = true;
	    else
		return;
	}
    }
}

template <typename ACT, typename OP>
void
specificActionFromChosenItem(ACT actop, const char *prompt, const OP &filter)
{
    int		itemno = chooseItem(prompt, filter);
    if (itemno < 0)
	return;
    if (!glbMap) return;
    MOB *mob = glbMap->avatar();
    if (!mob) return;
    ACTION_NAMES action = actop(mob->inventory()(itemno));
    glbEngine->queue().append(COMMAND(action, itemno));
}

template <typename OP>

void
actionOnChosenItem(ACTION_NAMES action, const char *prompt, const OP &filter,
                    int subject)
{
    int		itemno = chooseItem(prompt, filter);
    if (itemno >= 0)
    {
	glbEngine->queue().append(COMMAND(action, 0, 0, subject, itemno));
    }
}


template <typename OP>
void
actionWithGroundItem(ACTION_NAMES action, const char *prompt, const OP &filter)
{
    int		itemid = chooseGroundItem(prompt, filter);
    if (itemid > 0)
    {
	glbEngine->queue().append(COMMAND(action, itemid));
    }
}

void
actionWithDirection(ACTION_NAMES action, const char *prompt, int dz = 0, int dw = 0)
{
    int		dx, dy;

    msg_report(prompt);
    msg_newturn();
    if (awaitDirection(dx, dy) && (dx || dy))
    {
	msg_report(rand_dirtoname(dx, dy));
	msg_newturn();
	glbEngine->queue().append(COMMAND(action, dx, dy, dz, dw));
    }
    else
    {
	msg_report("Cancelled.  ");
	msg_newturn();
    }
}

bool
doApply(int itemno)
{
    if (itemno < 0)
	return false;

    ITEM_NAMES item = itemFromItemno(itemno);
    if (item == ITEM_WRITINGSET)
    {
	actionOnChosenItem(ACTION_APPLY, "Apply on what?  ", ITEM::filterWriteable(), itemno);
    }
    else if (item == ITEM_KNOWLEDGE_ORB)
    {
	glbEngine->queue().append(COMMAND(ACTION_APPLY, 0, 0, itemno));
    }
    else if (item == ITEM_MIDAS_WAND)
    {
	actionOnChosenItem(ACTION_APPLY, "Apply on what?  ", ITEM::filterAll(), itemno);
    }
    else if (itemno >= 0)
	actionWithDirection(ACTION_APPLY, "Apply in what direction?  ", itemno, -1);

    return true;
}

void
displayMob(MOB *mob)
{
if (!glbConfig->myUseMenus)
{
    BUF		longtext = mob->getLongDescription();
    msg_report(longtext);
}
else
{
    bool		done = false;

    while (!TCODConsole::isWindowClosed() && !done)
    {
	// Display description with option to edit...
	glbChooser->clear();
	glbChooser->setTextAttr(ATTR_NORMAL);

	glbChooser->setPrequelAttr(ATTR_WHITE);

	BUF		longtext = mob->getLongDescription();
	longtext = longtext.wordwrap(WORDWRAP_LEN);
	longtext.stripWS();
	longtext.append('\n');
	longtext.append('\n');
	glbChooser->setPrequel(longtext);

	glbChooser->appendChoice("Done");

	glbChooser->setChoice(0);
	glbChooserActive = true;

	// Run the chooser...
	while (!TCODConsole::isWindowClosed())
	{
	    redrawWorld();
	    int key = gfx_getKey(false);

	    glbChooser->processKey(key);

	    if (key)
	    {
		if (key == '\n' || key == ' ')
		{
		    if (glbChooser->getChoice() == 0)
		    {
			// done!
			done = true;
			break;
		    }
		}
		else if (key == '\x1b')
		{
		    // Esc on options is to go back to play.
		    // Play...
		    done = true;
		    break;
		}
	    }
	}
    }
    glbChooserActive = false;
}
}

void
displayItem(ITEM *item)
{
if (!glbConfig->myUseMenus)
{
    BUF		longtext = item->getLongDescription();
    msg_report(longtext);
}
else
{
    bool		done = false;

    while (!TCODConsole::isWindowClosed() && !done)
    {
	// Display description with option to edit...
	glbChooser->clear();
	glbChooser->setTextAttr(ATTR_NORMAL);

	glbChooser->setPrequelAttr(ATTR_WHITE);

	BUF		longtext = item->getLongDescription();
	longtext = longtext.wordwrap(WORDWRAP_LEN);
	longtext.stripWS();
	longtext.append('\n');
	longtext.append('\n');
	glbChooser->setPrequel(longtext);

	int		lastchoice = 0;

	glbChooser->appendChoice("Done");



	glbChooser->setChoice(lastchoice);
	glbChooserActive = true;

	// Run the chooser...
	while (!TCODConsole::isWindowClosed())
	{
	    redrawWorld();
	    int key = gfx_getKey(false);

	    glbChooser->processKey(key);

	    if (key)
	    {
		if (key == '\n' || key == ' ')
		{
		    if (glbChooser->getChoice() == lastchoice)
		    {
			// done!
			done = true;
			break;
		    }
		}
		else if (key == '\x1b')
		{
		    // Esc on options is to go back to play.
		    // Play...
		    done = true;
		    break;
		}
	    }
	}
    }
    glbChooserActive = false;
}
}

void
examineTopItem()
{
    if (!glbMap || !glbMap->avatar())
	return;

    if (glbMap->avatar()->hasItem(ITEM_BLIND))
    {
	msg_report("You can't see anything when blind.");
	msg_newturn();
	return;
    }

    MOB *avatar = glbMap->avatar();
    ITEM *item = avatar->getTopBackpackItem();
    if (!item)
    {
	// Intentionally conflated with the other examine.
	msg_report("You see nothing to look at.");
    }
    else
    {
	msg_format("You look closely at %S", item);
	displayItem(item);
    }
    msg_newturn();
}

void
doExamine()
{
    int		x = glbDisplay->x() + glbDisplay->width()/2;
    int		y = glbDisplay->y() + glbDisplay->height()/2;
    int		cx = glbDisplay->x() + glbDisplay->width()/2;
    int		cy = glbDisplay->y() + glbDisplay->height()/2;
    int		key;
    int		dx, dy;
    POS		p;
    BUF		buf;
    bool	blind;
    bool	lookclosely = false;

    msg_report("You start looking around.");
    msg_newturn();

    glbInvertX = x;
    glbInvertY = y;
    blind = false;
    if (glbMap && glbMap->avatar())
	blind = glbMap->avatar()->hasItem(ITEM_BLIND);
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();

	key = gfx_getKey(false);

	// We eat space below, so check here!
	if (key == ' ')
	{
	    lookclosely = true;
	    break;
	}

	if (key == '.')
	{
	    // request for location.
	    msg_report(rand_describeoffset(x - cx, y - cy));
	    msg_newturn();
	    continue;
	}

	if (cookDirKey(key, dx, dy, true))
	{
	    // Space we want to select.
	    if (!dx && !dy)
	    {
		break;
	    }
	    x += dx;
	    y += dy;
	    x = BOUND(x, glbDisplay->x(), glbDisplay->x()+glbDisplay->width()-1);
	    y = BOUND(y, glbDisplay->y(), glbDisplay->y()+glbDisplay->height()-1);

	    glbInvertX = x;
	    glbInvertY = y;
	    p = glbDisplay->lookup(x, y);

	    // Spammy
	    if (p.isMapped())
	    {
		p.describeSquare(blind || !p.isFOV());
	    }
	    else
	    {
		msg_report("Unmapped.");
	    }

	    msg_newturn();
	}

	// Any other key stops the look.
	if (key)
	{
	    if (key == '\n' || key == ' ' || key == 'x')
	    {
		// Request to look closely
		lookclosely = true;
	    }
	    break;
	}
    }

    if (lookclosely)
    {
	// Check if we left on a mob.
	// Must re look up as displayWorld may have updated the map.
	p = glbDisplay->lookup(x, y);
	if (p.mob())
	{
	    displayMob(p.mob());
	}
	else if (p.item())
	{
	    displayItem(p.item());
	}
    }
    else
	msg_report("You stop looking.");
    msg_newturn();

    glbInvertX = -1;
    glbInvertY = -1;
}

void
reportLocations(MAP *map, POSLIST poslist, const char *verb, bool domobs, bool doitems, bool dolegends)
{
    // One true
    J_ASSERT(domobs|| doitems|| dolegends);
    // Not all true
    J_ASSERT(!domobs || !doitems || !dolegends);
    // Not two true.
    J_ASSERT((domobs ^ doitems ^ dolegends));

    if (!map || !map->avatar())
	return;

    // Clean up ones without valid offsets.
    POS		avatarpos = map->avatar()->pos();
    for (int i = poslist.entries(); i --> 0; )
    {
	int		dx, dy;
	bool hasdelta = avatarpos.deltaTo(
			    poslist(i),
			    dx, dy);
	if (!hasdelta)
	    poslist.removeAt(i);
	else if (domobs && !poslist(i).mob())
	    poslist.removeAt(i);
	else if (doitems && !poslist(i).item())
	    poslist.removeAt(i);
    }

    if (!poslist.entries())
	return;

    poslist.stableSort([avatarpos](POS a, POS b) -> bool
    {
	if (a == b) return false;

	return avatarpos.dist(a) < avatarpos.dist(b);
    });

    BUF		report;
    report.sprintf("You %s ", verb);
    bool		comma = false;
    ITEMLIST items;
    MOBLIST mobs;
    for (auto && pos : poslist)
    {
	int		dx, dy;
	bool hasdelta = avatarpos.deltaTo(
			    pos,
			    dx, dy);
	J_ASSERT(hasdelta);

	if (domobs)
	{
	    mobs.clear();
	    pos.getAllMobs(mobs);
	    bool subcomma = comma;

	    for (auto && mob : mobs)
	    {
		report.appendSprintf("%s%s", subcomma ? ", " : "",
			mob->getArticleName().buffer());

		subcomma = true;
	    }
	    report.appendSprintf(" at %s", 
		    rand_describeoffset(dx, dy).buffer());

	}  
	else if (doitems)
	{
	    items.clear();
	    pos.getAllItems(items);
	    bool subcomma = comma;

	    for (auto && item : items)
	    {
		report.appendSprintf("%s%s", subcomma ? ", " : "",
			item->getArticleName().buffer());

		subcomma = true;
	    }
	    report.appendSprintf(" at %s", 
		    rand_describeoffset(dx, dy).buffer());

	}
	else if (dolegends)
	{
	    report.appendSprintf("%s%s at %s", comma ? ", " : "",
		    pos.defn().legend, 
		    rand_describeoffset(dx, dy).buffer());
	}
	comma = true;
    }
    report.strcat(".  ");
    msg_report(report);
}

void
reportMobs(MAP *map, bool ignoreseen)
{
    AUTOLOCK	a(glbReportLock);
    if (!map || !map->avatar())
	return;

    // Clean up ones without valid offsets.
    POS		avatarpos = map->avatar()->pos();

    MOBLIST	mobs;
    POSLIST	poslist;
    PTRLIST<SEENOBJECT>	seenlist;

    map->getVisibleMobs(mobs);
    mobs.removePtr(map->avatar());

    poslist.clear();
    for (auto && mob : mobs)
    {
	seenlist.append(SEENOBJECT{mob->pos(), mob->getUID()});
	if (ignoreseen && glbLastSeenMobs.contains(SEENOBJECT{mob->pos(), mob->getUID()}))
	    continue;
	if (poslist.contains(mob->pos()))
	    continue;
	poslist.append(mob->pos());
    }
    glbLastSeenMobs = seenlist;

    reportLocations(map, poslist, "see", /*domobs*/true, /*doitems*/false, /*dolegends*/false);
}

void
reportItems(MAP *map, bool ignoreseen)
{
    AUTOLOCK	a(glbReportLock);
    if (!map || !map->avatar())
	return;

    // Clean up ones without valid offsets.
    POS		avatarpos = map->avatar()->pos();

    POSLIST	poslist;
    ITEMLIST	items;
    PTRLIST<SEENOBJECT>	seenlist;

    map->getVisibleItems(items);
    poslist.clear();
    for (auto && item : items)
    {
	seenlist.append(SEENOBJECT{item->pos(), item->getUID()});
	if (ignoreseen && glbLastSeenItems.contains(SEENOBJECT{item->pos(), item->getUID()}))
	    continue;
	if (poslist.contains(item->pos()))
	    continue;

	poslist.append(item->pos());
    }
    glbLastSeenItems = seenlist;
    reportLocations(map, poslist, "see", /*domobs*/false, /*doitems*/true, /*dolegends*/false);
}

void
reportDoorways(MAP *map, bool ignoreseen)
{
    AUTOLOCK	a(glbReportLock);
    if (!map || !map->avatar())
	return;

    // Clean up ones without valid offsets.
    POS		avatarpos = map->avatar()->pos();

    POSLIST	poslist, doorways;
    PTRLIST<SEENOBJECT>	seenlist;

    map->getVisibleExits(doorways);
    for (auto && doorway : doorways)
    {
	seenlist.append(SEENOBJECT{doorway, doorway.tile()});
	if (ignoreseen && glbLastSeenDoorways.contains(SEENOBJECT{doorway, doorway.tile()}))
	    continue;
	if (poslist.contains(doorway))
	    continue;

	poslist.append(doorway);
    }
    glbLastSeenDoorways = seenlist;
    reportLocations(map, poslist, "see", /*domobs*/false, /*doitems*/false, /*dolegends*/true);
}


void
reportTiles(MAP *map, TILE_NAMES tile, bool requiremap, bool requirefov)
{
    if (!map || !map->avatar())
	return;

    // Clean up ones without valid offsets.
    POS		avatarpos = map->avatar()->pos();

    POSLIST	poslist;

    if (!avatarpos.rawRoom())
	return;

    poslist.clear();
    avatarpos.rawRoom()->getAllTiles(tile, poslist);

    // Remove unmapped.
    for (int i = poslist.entries(); i --> 0; )
    {
	if (requiremap && !poslist(i).isMapped())
	{
	    poslist.removeAt(i);
	    continue;
	}
	if (requirefov && !poslist(i).isFOV())
	{
	    poslist.removeAt(i);
	    continue;
	}
    }

    reportLocations(map, poslist, requirefov ? "see" : "mapped",
		/*domobs*/false, /*doitems*/false, /*dolegends*/true);
}

void
doLookAround(MAP *map, bool onlynew)
{
    reportMobs(map, onlynew);

    reportItems(map, onlynew);

    reportDoorways(map, onlynew);

    // Report the stair cases.
    if (!onlynew)
    {
	reportTiles(map, TILE_DOWNSTAIRS, /*requiremap*/true, /*requirefov*/false);
	reportTiles(map, TILE_UPSTAIRS, /*requiremap*/true, /*requirefov*/false);
    }
}

void
doChamberScan(MAP *map)
{
    if (!map || !map->avatar())
	return;

    POS		avatarpos = map->avatar()->pos();

    avatarpos.describeChamber("You are in");
    POSLIST	exits, moblocs, itemlocs;
    avatarpos.scanChamber(exits, moblocs, itemlocs);

    moblocs.removePtr(avatarpos);

    reportLocations(map, moblocs, "see", /*domobs*/true, /*doitems*/false, /*dolegends*/false);
    reportLocations(map, itemlocs, "see", /*domobs*/false, /*doitems*/true, /*dolegends*/false);
    reportLocations(map, exits, "mapped", /*domobs*/false, /*doitems*/false, /*dolegends*/true);

    // UPdate our seen list, we don't just reset it because this is a subset
    // of what we can see.
    {
	AUTOLOCK	a(glbReportLock);
	for (auto && mobloc : moblocs)
	{
	    MOBLIST	mobs;
	    mobloc.getAllMobs(mobs);
	    for (auto && mob : mobs)
	    {
		SEENOBJECT	obj{mobloc, mob->getUID()};
		if (!glbLastSeenMobs.contains(obj))
		    glbLastSeenMobs.append(obj);
	    }
	}
	for (auto && itemloc : itemlocs)
	{
	    ITEMLIST	items;
	    itemloc.getAllItems(items);
	    for (auto && item : items)
	    {
		SEENOBJECT	obj{itemloc, item->getUID()};
		if (!glbLastSeenItems.contains(obj))
		    glbLastSeenItems.append(obj);
	    }
	}
	for (auto && exit : exits)
	{
	    SEENOBJECT	obj{exit, exit.tile()};
	    if (!glbLastSeenDoorways.contains(obj))
		glbLastSeenDoorways.append(obj);
	}
    }
}

void
doStatus(MAP *map)
{
    if (!map || !map->avatar())
	return;

    MOB *avatar = map->avatar();
    avatar->describe();
}


bool
doThrow(int itemno)
{
    int			dx, dy;

    msg_report("Throw in which direction?  ");
    if (awaitDirection(dx, dy) && (dx || dy))
    {
	msg_report(rand_dirtoname(dx, dy));
	msg_newturn();
    }
    else
    {
	msg_report("Cancelled.  ");
	msg_newturn();
	return false;
    }

    // Do the throw
    glbEngine->queue().append(COMMAND(ACTION_THROW, dx, dy, itemno));

    return true;
}

void
buildAction(ITEM *item, ACTION_NAMES *actions)
{
    glbChooser->clear();

    glbChooser->setTextAttr(ATTR_NORMAL);
    int			choice = 0;
    ACTION_NAMES	*origaction = actions;
    if (!item)
    {
	glbChooser->appendChoice("Nothing to do with nothing.");
	*actions++ = ACTION_NONE;
    }
    else
    {
	glbChooser->appendChoice("[ ] Do nothing");
	if (glbLastItemAction == ACTION_NONE)
	    choice = (int)(actions - origaction);
	*actions++ = ACTION_NONE;

	glbChooser->appendChoice("[X] Examine");
	if (glbLastItemAction == ACTION_EXAMINE)
	    choice = (int)(actions - origaction);
	*actions++ = ACTION_EXAMINE;

	if (item->isPotion())
	{
	    glbChooser->appendChoice("[Q] Quaff");
	    if (glbLastItemAction == ACTION_QUAFF)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_QUAFF;
	}
	if (item->defn().throwable)
	{
	    glbChooser->appendChoice("[T] Throw");
	    if (glbLastItemAction == ACTION_THROW)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_THROW;
	}
	if (item->isFood())
	{
	    glbChooser->appendChoice("[E] Eat");
	    if (glbLastItemAction == ACTION_EAT)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_EAT;
	}
	if (item->isWeapon())
	{
	    glbChooser->appendChoice("[W] Wield");
	    if (glbLastItemAction == ACTION_WIELD)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_WEAR;
	}
        else if (item->isRanged())
	{
	    glbChooser->appendChoice("[R] Ready");
	    if (glbLastItemAction == ACTION_READYRANGED)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_READYRANGED;
	}
	else if (item->isRing() || item->isArmour())
	{
	    glbChooser->appendChoice("[W] Wear");
	    if (glbLastItemAction == ACTION_WEAR)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_WEAR;
	}
	if (item->isWeapon() || item->isRing() || item->isArmour() || item->isRanged())
	{
	    glbChooser->appendChoice("[F] Fuse into Equipped");
	    if (glbLastItemAction == ACTION_TRANSMUTE)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_TRANSMUTE;
	}
	if (item->isSpellbook() || item->isScroll())
	{
	    glbChooser->appendChoice("[R] Read");
	    if (glbLastItemAction == ACTION_READ)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_READ;
	}
	if (item->isTool())
	{
	    glbChooser->appendChoice("[A] Apply");
	    if (glbLastItemAction == ACTION_APPLY)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_APPLY;
	}
	if (!item->defn().isflag)
	{
	    glbChooser->appendChoice("[D] Drop");
	    if (glbLastItemAction == ACTION_DROP)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_DROP;
	}
    }
    glbChooser->setChoice(choice);

    glbChooserActive = true;
}

bool
useItem(MOB *mob, ITEM *item, int itemno)
{
    ACTION_NAMES		actions[NUM_ACTIONS+1];
    bool			done = false;
    int				key;

    buildAction(item, actions);
    glbChooserActive = true;

    // Run the chooser...
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	glbChooser->processKey(key);

	if (key)
	{
	    if (key == 'd' || key == 'D')
	    {
		glbEngine->queue().append(COMMAND(ACTION_DROP, itemno));
		done = true;
		break;
	    }
	    else if (key == 'a' || key == 'A')
	    {
		glbChooserActive = false;
		doApply(itemno);
		done = true;
		break;
	    }
	    else if (key == 'q' || key == 'Q')
	    {
		glbEngine->queue().append(COMMAND(ACTION_QUAFF, itemno));
		done = true;
		break;
	    }
	    else if (key == 'e' || key == 'E')
	    {
		glbEngine->queue().append(COMMAND(ACTION_EAT, itemno));
		done = true;
		break;
	    }
	    else if (key == 'r' || key == 'R')
	    {
		glbEngine->queue().append(COMMAND(item->isRanged() ? ACTION_READYRANGED : ACTION_READ, itemno));
		done = true;
		break;
	    }
	    else if (key == 'w' || key == 'W')
	    {
		glbEngine->queue().append(COMMAND(item->isWeapon() ? ACTION_WIELD : ACTION_WEAR, itemno));
		done = true;
		break;
	    }
	    else if (key == 'f' || key == 'F')
	    {
		glbEngine->queue().append(COMMAND(ACTION_TRANSMUTE, itemno, -1));
		done = true;
		break;
	    }
	    else if (key == 'x' || key == 'X')
	    {
		displayItem(item);
		done = false;
		break;
	    }
	    else if (key == 't' || key == 'T')
	    {
		glbChooserActive = false;
		doThrow(itemno);
		done = true;
		break;
	    }
	    // User selects this?
	    else if (key == '\n' || key == ' ')
	    {
		ACTION_NAMES	action;

		action = actions[glbChooser->getChoice()];
		glbLastItemAction = action;
		switch (action)
		{
		    case ACTION_DROP:
			glbEngine->queue().append(COMMAND(ACTION_DROP, itemno));
			done = true;
			break;
		    case ACTION_QUAFF:
			glbEngine->queue().append(COMMAND(ACTION_QUAFF, itemno));
			done = true;
			break;
		    case ACTION_BREAK:
			glbEngine->queue().append(COMMAND(ACTION_BREAK, itemno));
			done = true;
			break;
		    case ACTION_WEAR:
			glbEngine->queue().append(COMMAND(ACTION_WEAR, itemno));
			done = true;
			break;
		    case ACTION_WIELD:
			glbEngine->queue().append(COMMAND(ACTION_WIELD, itemno));
			done = true;
			break;
		    case ACTION_READYRANGED:
			glbEngine->queue().append(COMMAND(ACTION_READYRANGED, itemno));
			done = true;
			break;
		    case ACTION_READ:
			glbEngine->queue().append(COMMAND(ACTION_READ, itemno));
			done = true;
			break;
		    case ACTION_TRANSMUTE:
			glbEngine->queue().append(COMMAND(ACTION_TRANSMUTE, itemno, -1));
			done = true;
			break;
		    case ACTION_EAT:
			glbEngine->queue().append(COMMAND(ACTION_EAT, itemno));
			done = true;
			break;
		    case ACTION_SEARCH:
			glbEngine->queue().append(COMMAND(ACTION_SEARCH, itemno));
			done = true;
			break;
		    case ACTION_MEDITATE:
			glbEngine->queue().append(COMMAND(ACTION_MEDITATE, itemno));
			done = true;
			break;
		    case ACTION_EXAMINE:
			displayItem(item);
			done = false;
			break;
		    case ACTION_THROW:
			glbChooserActive = false;
			doThrow(itemno);
			done = true;
			break;
		    case ACTION_APPLY:
			glbChooserActive = false;
			doApply(itemno);
			done = true;
			break;
		}
		break;
	    }
	    else if (key == '\x1b')
	    {
		// Esc on options is to go back to play.
		// Play...
		break;
	    }
	    else
	    {
		// Ignore other options.
	    }
	}
    }
    glbChooserActive = false;

    return done;
}

void
buildInventory(MOB *mob, PTRLIST<int> &items, bool dointrinsics)
{
    glbChooser->clear();
    items.clear();

    glbChooser->setTextAttr(ATTR_NORMAL);
    if (!mob)
    {
	glbChooser->appendChoice("The dead own nothing.");
    }
    else
    {
	for (int i = 0; i < mob->inventory().entries(); i++)
	{
	    ITEM 		*item = mob->inventory()(i);

	    if (dointrinsics && !item->isFlag())
		continue;
	    if (!dointrinsics && item->isFlag())
		continue;

	    items.append(i);
	}
	if (!items.entries())
	{
	    glbChooser->appendChoice("Nothing");
	}

	// TFW you are about to hook into std::stalbe_sort and find
	// PTRLIST already has the hook.
	items.stableSort([&](int a, int b) -> int
	{
	    if (a == b) return false;

	    ITEM		*ia = mob->inventory()(a);
	    ITEM		*ib = mob->inventory()(b);

	    if (!ia && !ib) return false;
	    if (!ia) return false;
	    if (!ib) return true;

	    // Equipped first.
	    if (ia->isWielded() != ib->isWielded())
		return ia->isWielded();
	    if (ia->isEquipped() != ib->isEquipped())
		return ia->isEquipped();

	    // Sort by class
	    if (ia->itemClass() != ib->itemClass())
		return ia->itemClass() < ib->itemClass();

	    // Magic items sort by their innate class... Hmm...
	    // this is a security leak as the magic class is fixed,
	    // so expert players can sort-id!
	    // I think this is the sort of meta-puzzle that is like
	    // price-id, and I price-ided scrolls of identify in Nethack
	    // so consider this an intentional decision!
	    // (AKA: It's not a bug if documented.)
	    // Especially as it can be closed by doing
	    // the definition sort first!
	    if ((ia->isRing() || ia->isPotion()) &&
		(ia->getMagicClass() != ib->getMagicClass()))
	    {
		return ia->getMagicClass() < ib->getMagicClass();
	    }

	    // Sort by raw defintion
	    if (ia->getDefinition() != ib->getDefinition())
		return ia->getDefinition() < ib->getDefinition();

	    // Sort by material.
	    if (ia->material() != ib->material())
		return ia->material() > ib->material();

	    // Sort by element
	    if (ia->element() != ib->element())
		return ia->element() < ib->element();

	    // Sort by corpse mob
	    if (ia->mobType() != ib->mobType())
		return ia->mobType() < ib->mobType();

	    // Sort by magic bonus
	    if (ia->getBonus() != ib->getBonus())
		return ia->getBonus() > ib->getBonus();

	    // Sort by broken
	    if (ia->isBroken() != ib->isBroken())
		return ia->isBroken();

	    // If we don't find another differentiator, sort by
	    // position in inventory.
	    return a < b;
	});

	for (int i = 0; i < items.entries(); i++)
	{
	    ITEM 		*item = mob->inventory()(items(i));
	    BUF		 name = item->getName();
	    name = gram_capitalize(name);
	    if (item->isWielded())
	    {
		BUF		equip;
		equip.sprintf("%s (wielded)", name.buffer());
		name.strcpy(equip);
	    }
	    if (item->isEquipped())
	    {
		BUF		equip;
		equip.sprintf("%s (equipped)", name.buffer());
		name.strcpy(equip);
	    }
	    if (item->isSpellbook() && mob->isSpellMemorized(item->asSpell()))
	    {
		BUF		equip;
		equip.sprintf("%s (known)", name.buffer());
		name.strcpy(equip);
	    }

	    glbChooser->appendChoice(name);
	}
    }
    glbChooser->setChoice(glbLastItemIdx);

    glbChooserActive = true;
}

void
inventoryMenu(bool dointrinsics)
{
    MOB		*avatar = (glbMap ? glbMap->avatar() : 0);
    int		key;
    bool	setlast = false;
    PTRLIST<int> itemlist;
    buildInventory(avatar, itemlist, dointrinsics);

    // Run the chooser...
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	glbChooser->processKey(key);

	if (key)
	{
	    int		choiceno = glbChooser->getChoice();
	    int		itemno = -1;
	    if (choiceno >= 0 && choiceno < itemlist.entries())
		itemno = itemlist(choiceno);
	    bool	done = false;
	    // User selects this?
	    if (key == '\n' || key == ' ')
	    {
		done = true;

		glbLastItemIdx = glbChooser->getChoice();
		setlast = true;
		if (itemno >= 0 && itemno < avatar->inventory().entries())
		{
		    if (dointrinsics)
		    {
			// Only thing to do is examine, so skip extra
			// menu
			if (itemno >= 0 && itemno < avatar->inventory().entries())
			{
			    // This corrupts our chooser so we have
			    // do abort.
			    displayItem(avatar->inventory()(itemno));
			    buildInventory(avatar, itemlist, dointrinsics);
			    glbChooser->setChoice(choiceno);
			    done = false;
			}
		    }
		    else
		    {
			done = useItem(avatar, avatar->inventory()(itemno), itemno);
		    }
		}
		// Finish the inventory menu.
		if (done)
		{
		    break;
		}
		else
		{
		    buildInventory(avatar, itemlist, dointrinsics);
		    setlast = false;
		}
	    }
	    else if (key == '\x1b' || key == 'i' || key == 'I')
	    {
		// Esc on options is to go back to play.
		// Play...
		break;
	    }
	    else if (itemno >= 0 && itemno < avatar->inventory().entries())
	    {
		ITEM		*item = avatar->inventory()(itemno);
		if (key == 'd' || key == 'D')
		{
		    glbEngine->queue().append(COMMAND(ACTION_DROP, itemno));
		    done = true;
		}
		else if (key == 'a' || key == 'A')
		{
		    glbLastItemIdx = glbChooser->getChoice();
		    setlast = true;
		    glbChooserActive = false;
		    doApply(itemno);
		    done = true;
		    break;
		}
		else if (key == 'q' || key == 'Q')
		{
		    glbEngine->queue().append(COMMAND(ACTION_QUAFF, itemno));
		    done = true;
		}
		else if (key == 'e' || key == 'E')
		{
		    glbEngine->queue().append(COMMAND(ACTION_EAT, itemno));
		    done = true;
		}
		else if (key == 'r' || key == 'R')
		{
		    glbEngine->queue().append(COMMAND(item->isRanged() ? ACTION_READYRANGED : ACTION_READ, itemno));
		    done = true;
		    break;
		}
		else if (key == 'w' || key == 'W')
		{
		    glbEngine->queue().append(COMMAND(item->isWeapon() ? ACTION_WIELD : ACTION_WEAR, itemno));
		    done = true;
		}
		else if (key == 'x' || key == 'X')
		{
		    if (itemno >= 0 && itemno < avatar->inventory().entries())
		    {
			// This corrupts our chooser so we have
			// do abort.
			displayItem(avatar->inventory()(itemno));
			buildInventory(avatar, itemlist, dointrinsics);
			glbChooser->setChoice(choiceno);
			done = false;
		    }
		}
		else if (key == 't' || key == 'T')
		{
		    glbLastItemIdx = glbChooser->getChoice();
		    setlast = true;
		    glbChooserActive = false;
		    doThrow(itemno);
		    done = true;
		    break;
		}
		else if (key == 'f' || key == 'F')
		{
		    glbLastItemIdx = glbChooser->getChoice();
		    glbEngine->queue().append(COMMAND(ACTION_TRANSMUTE, itemno, -1));
		    done = true;
		    break;
		}
	    }
	    else
	    {
		// Ignore other options.
	    }

	    if (done)
		break;
	}
    }
    if (!setlast)
	glbLastItemIdx = glbChooser->getChoice();
    glbChooserActive = false;
}

void
castSpell(MOB *mob, SPELL_NAMES spell)
{
    int			dx = 0;
    int			dy = 0;

    if (GAMEDEF::spelldef(spell)->needsdir)
    {
	msg_report("Cast in which direction?  ");
        msg_newturn();
	if (awaitDirection(dx, dy))
	{
	    msg_report(rand_dirtoname(dx, dy));
	    msg_newturn();
	}
	else
	{
	    msg_report("Cancelled.  ");
	    msg_newturn();
	    return;
	}
    }
    glbLastSpell = spell;
    glbEngine->queue().append(COMMAND(ACTION_CAST, dx, dy, spell));
}

void
castCoinSpell(int selection)
{
    int			dx = 0;
    int			dy = 0;

    if (selection & (1 << MATERIAL_STONE) && 
	(selection != ((1 << MATERIAL_STONE) | (1 << MATERIAL_WOOD))))
    {
	msg_report("Cast in which direction?  ");
	if (awaitDirection(dx, dy))
	{
	    msg_report(rand_dirtoname(dx, dy));
	    msg_newturn();
	}
	else
	{
	    msg_report("Cancelled.  ");
	    msg_newturn();
	    return;
	}
    }
    glbEngine->queue().append(COMMAND(ACTION_CAST, dx, dy, selection));
}

void
buildZapList(MOB *mob, PTRLIST<SPELL_NAMES> &list)
{
    glbChooser->clear();
    list.clear();
    int				chosen = 0;

    glbChooser->setTextAttr(ATTR_NORMAL);
    if (!mob)
    {
	glbChooser->appendChoice("The dead can cast nothing.");
	list.append(SPELL_NONE);
    }
    else
    {
	SPELL_NAMES		spell;

	for (spell = SPELL_NONE; spell < GAMEDEF::getNumSpell(); spell = (SPELL_NAMES) (spell + 1))
	{
	    ITEM_NAMES		itemname;
	    if (spell == SPELL_NONE)
		continue;
	    itemname = GAMEDEF::spelldef(spell)->item;
	    // None now implies cantrips you always have.
	    if (itemname == ITEM_NONE ||
		mob->hasItem(itemname))
	    {
		BUF		buf;

		buf.sprintf("Cast %s", GAMEDEF::spelldef(spell)->name);
		glbChooser->appendChoice(buf);
		list.append(spell);
		if (spell == glbLastSpell)
		    chosen = list.entries()-1;
	    }
	}
    }
    if (!glbChooser->entries())
    {
	glbChooser->appendChoice("You know no magic spells.");
	list.append(SPELL_NONE);
    }

    glbChooser->setChoice(chosen);

    glbChooserActive = true;
}

void
buildZapCoinList(MOB *mob, PTRLIST<int> &list, int selection)
{
    glbChooser->clear();
    list.clear();
    int				chosen = 0;

    glbChooser->setTextAttr(ATTR_NORMAL);
    if (!mob)
    {
	glbChooser->appendChoice("The dead can cast nothing.");
	list.append(0);
    }
    else
    {
	MATERIAL_NAMES		material;

	FOREACH_MATERIAL(material)
	{
	    if (material == MATERIAL_NONE)
		continue;

	    ITEM		*coins;
	    int			 bitmask = 1 << material;
	    coins = mob->lookupItem(ITEM_COIN, material);
	    if (coins && coins->getStackCount())
	    {
		BUF		name, choice;
		name = gram_capitalize(coins->getName());
		choice.sprintf("[%s] %s",
			(selection & bitmask) ? "X" : " ",
			name.buffer());
		glbChooser->appendChoice(choice);
		list.append(bitmask);
	    }
	}
    }
    if (!glbChooser->entries())
    {
	glbChooser->appendChoice("You have no material to cast with.");
	list.append(0);
    }
    else
    {
	glbChooser->appendChoice("Cast With Selected Materials.");
	list.append(0);
    }

    glbChooser->setChoice(chosen);

    glbChooserActive = true;
}

int
zapMenu(bool actuallycast)
{
    MOB		*avatar = (glbMap ? glbMap->avatar() : 0);

    int			selection = 0;
    bool		done = false;
    bool		docast = false;
    int			chosen = 0;
    while (!done)
    {
	PTRLIST<int>		list;

	buildZapCoinList(avatar, list, selection);
	glbChooser->setChoice(chosen);
	int		key;
	while (!TCODConsole::isWindowClosed())
	{
	    redrawWorld();
	    key = gfx_getKey(false);

	    glbChooser->processKey(key);

	    if (key)
	    {
		int		itemno = glbChooser->getChoice();
		chosen = itemno;
		// User selects this?
		if (key == '\n' || key == ' ')
		{
		    if (list(itemno))
		    {
			selection ^= list(itemno);
		    }
		    else
		    {
			docast = true;
			done = true;
		    }
		    break;
		}
		else if (key == '\x1b')
		{
		    // Esc on options is to go back to play.
		    // Play...
		    done = true;
		    break;
		}
	    }
	}
	glbChooserActive = false;
    }

    if (docast && actuallycast)
    {
	castCoinSpell(selection);
    }
    if (!docast)
	selection = 0;
    return selection;

#if 0
    PTRLIST<SPELL_NAMES>	spelllist;
    PTRLIST<int>		hotkeys;
    buildZapList(avatar, spelllist);

    SPELL_NAMES	spell = runMenuSelection(spelllist, SPELL_NONE, hotkeys);

    if (spell != SPELL_NONE)
    {
	castSpell(avatar, spell);
    }
    glbChooserActive = false;
#endif
}

SPELL_NAMES
chooseSpell(const char *prompt)
{
    if (!glbMap) return SPELL_NONE;
    MOB *mob = glbMap->avatar();
    if (!mob) return SPELL_NONE;

    PTRLIST<SPELL_NAMES>        spelllist;

    mob->getKnownSpells(spelllist);

    if (!spelllist.entries())
    {
        msg_report("You know no spells.");
        msg_newturn();
        return SPELL_NONE;
    }

if (!glbConfig->myUseMenus)
{
    BUF fullprompt;
    fullprompt.strcpy(prompt);
    fullprompt.strcat(" [");
    for (auto && spell : spelllist)
    {
        fullprompt.append(mob->spellLetter(spell));
    }
    fullprompt.append('?');     // no * because ? is *
    fullprompt.strcat("]\n");
    while (true)
    {
	msg_report(fullprompt);
	msg_newturn();
	redrawWorld();

	int		key;
	bool		valid = awaitAlphaKey(key);

	if (valid)
	{
	    SPELL_NAMES chosen = mob->spellFromLetter(key);
            if (chosen == SPELL_NONE)
            {
                msg_report("You have no spell memorized in that slot.");
                msg_newturn();
                return chosen;
            }
            msg_report(GAMEDEF::spelldef(chosen)->name);
            msg_report("\n");
            return chosen;
        }
        // We only prompt ?, but might as well accept * if muscle memory
        // is a thing.
        if (key == '?' || key == '*')
        {
            for (auto && spell : spelllist)
            {
                BUF     line;
                line.sprintf("[%c] %s\n",
                        mob->spellLetter(spell),
                        GAMEDEF::spelldef(spell)->name);
                msg_report(line);
            }
        }
        else
        {
            msg_report("Cancelled.");
            msg_newturn();
            break;
        }
    }
    return SPELL_NONE;
}
else
{
    glbChooser->clear();
    glbChooser->setPrequel(prompt);
    PTRLIST<SPELL_NAMES>	menulist;
    PTRLIST<int>		hotkeys;
    for (auto && spell : spelllist)
    {
	menulist.append(spell);
	hotkeys.append(mob->spellLetter(spell));
	BUF     line;
	line.sprintf("[%c] %s\n",
		mob->spellLetter(spell),
		GAMEDEF::spelldef(spell)->name);
	glbChooser->appendChoice(line);
    }
    menulist.append(SPELL_NONE);
    glbChooser->appendChoice("Nothing");

    glbChooser->setChoice(0);
    return runMenuSelection(menulist, SPELL_NONE, hotkeys);
}
}

void
buildHelpMenu(HELP_NAMES d)
{
    HELP_NAMES	option;
    glbChooser->clear();

    glbChooser->setTextAttr(ATTR_NORMAL);
    FOREACH_HELP(option)
    {
	glbChooser->appendChoice(glb_helpdefs[option].name);
    }
    glbChooser->setChoice(d);

    glbChooserActive = true;
}

void
buildOptionsMenu(OPTION_NAMES d)
{
    OPTION_NAMES	option;
    glbChooser->clear();

    glbChooser->setTextAttr(ATTR_NORMAL);
    FOREACH_OPTION(option)
    {
	if (option == OPTION_PLAY)
	{
	    BUF		buf;
	    buf.sprintf("Play", glbWorldName);
	    glbChooser->appendChoice(buf);
	}
	else
	    glbChooser->appendChoice(glb_optiondefs[option].name);
    }
    glbChooser->setChoice(d);

    glbChooserActive = true;
}

bool
terminalOutput(const char *text)
{
    gfx_faketerminal_setheader(1);
    gfx_faketerminal_write("+++\n");
    gfx_faketerminal_write("OK\n");
    gfx_faketerminal_write(text);
    gfx_faketerminal_write("\nEOF\n");

    while (!TCODConsole::isWindowClosed())
    {
	int key = gfx_getKey(true);
	if (key)
	    break;
    }

    gfx_faketerminal_write("\nATO\n");
    gfx_delay(200);
    redrawWorld();
    return true;
}

bool terminalOutput(BUF buf) { return terminalOutput(buf.buffer()); }

bool
helpMenu()
{
if (!glbConfig->myUseMenus)
{
    // List the help, building the relevant prompt
    BUF		optquery;
    bool	done = false;

    msg_report("Help on what topic?\n");

    HELP_NAMES opt;
    FOREACH_HELP(opt)
    {
	u8		key = 'a' + opt;
	optquery.appendSprintf("[%c] %s\n", key, glb_helpdefs[opt].name);
    }
    msg_report(optquery);
    msg_newturn();

    int key;
    bool chosen = awaitAlphaKey(key);
    WORLD_NAMES	world = WORLD_WIZARD;
    if (glbMap) world = glbMap->world();

    if (chosen)
    {
	key -= 'a';

	if (key >= 0 && key < NUM_HELPS)
	{
	    switch ( (HELP_NAMES) key )
	    {
		case HELP_WTF:
		    popupText(text_lookup("game", "wtf"));
		    break;
		case HELP_ROGUELIKES:
		    popupText(text_lookup("game", "roguelikes"));
		    break;
		case HELP_INVENTORY:
		    popupText(text_lookup("game", "inventory"));
		    break;
		case HELP_MOVEMENT:
		    popupText(text_lookup("game", "movement"));
		    break;
		case HELP_KEYBOARD:
		    popupText(text_lookup("game", "keyboard"));
		    break;
		case HELP_WELCOME:
		    popupText(text_lookup("welcome", glb_worlddefs[world].name));
		    break;
		case HELP_ABOUT:
		    popupText(text_lookup("game", "about"));
		    break;
	    }
	}
	else
	{
	    msg_report("Invalid help choice.\n");
	    msg_newturn();
	}
    }
    else
    {
	msg_report("Cancelled.\n");
	msg_newturn();
    }

    return done;
}
else
{
    int			key;
    bool		done = false;

    WORLD_NAMES	world = WORLD_WIZARD;
    if (glbMap) world = glbMap->world();

    buildHelpMenu(HELP_KEYBOARD);

    // Run the chooser...
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	glbChooser->processKey(key);

	if (key)
	{
	    // User selects this?
	    if (key == '\n' || key == ' ')
	    {
		switch ((HELP_NAMES)glbChooser->getChoice())
		{
		    case HELP_WTF:
			popupText(text_lookup("game", "wtf"));
			break;
		    case HELP_ROGUELIKES:
			popupText(text_lookup("game", "roguelikes"));
			break;
		    case HELP_INVENTORY:
			popupText(text_lookup("game", "inventory"));
			break;
		    case HELP_MOVEMENT:
			popupText(text_lookup("game", "movement"));
			break;
		    case HELP_KEYBOARD:
			popupText(text_lookup("game", "keyboard"));
			break;
		    case HELP_WELCOME:
			popupText(text_lookup("welcome", glb_worlddefs[world].name));
			break;
		    case HELP_ABOUT:
			popupText(text_lookup("game", "about"));
			break;
		}
	    }
	    else if (key == '\x1b')
	    {
		// Esc on help is to go back to play.
		// Play...
		break;
	    }
	    else
	    {
		// Ignore other options.
	    }
	}
    }
    glbChooserActive = false;

    return done;
}
}

bool
optionsMenu()
{
if (!glbConfig->myUseMenus)
{
    // List the options, building the relevant prompt
    BUF		optquery;
    bool	done = false;

    msg_report("Options Menu:\n");

    OPTION_NAMES opt;
    FOREACH_OPTION(opt)
    {
	u8		key = 'a' + opt;
	optquery.appendSprintf("[%c] %s\n", key, glb_optiondefs[opt].name);
    }
    msg_report(optquery);
    msg_newturn();

    int key;
    bool chosen = awaitAlphaKey(key);
    WORLD_NAMES	world = WORLD_WIZARD;
    if (glbMap) world = glbMap->world();

    if (chosen)
    {
	key -= 'a';

	if (key >= 0 && key < NUM_OPTIONS)
	{
	    switch ( (OPTION_NAMES) key )
	    {
		case OPTION_HELP:
		    helpMenu();
		    break;
		case OPTION_PLAY:
		    // Do nothing, just return.
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		case OPTION_USEMENUS:
		{
		    glbConfig->myUseMenus = !glbConfig->myUseMenus;
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		}
		case OPTION_VERBOSITY:
		{
		    glbConfig->myVerbose = !glbConfig->myVerbose;
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		}
		case OPTION_WALLSLIDE:
		{
		    glbConfig->myWallSlide = !glbConfig->myWallSlide;
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		}
		case OPTION_FULLSCREEN:
		{
		    glbConfig->myFullScreen = !glbConfig->myFullScreen;
		    // This is intentionally unrolled to work around a
		    // bool/int problem in libtcod
		    if (glbConfig->myFullScreen)
			TCODConsole::setFullscreen(true);
		    else
			TCODConsole::setFullscreen(false);
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		}
		case OPTION_QUIT:
		    // Quit
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    done = true;
		    break;
#if 0
		case OPTION_DISPLAYMAP:
		    glbConfig->myDisplayMap = !glbConfig->myDisplayMap;
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		case OPTION_SPEECH_OFF:
		    // Do this BEFORE so we cancel the talk.
		    msg_report("Speech Turned Off.");
		    msg_newturn();
		    glbConfig->myTTSEnable = false;
		    break;
		case OPTION_SPEECH_SLOW:
		    glbConfig->myTTSEnable = true;
		    glbConfig->myTTSSpeed = -5;
		    updateSpeechFromConfig();
		    // Echo what you picked.
		    // Do this *AFTER* we've updated speach speeds
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		case OPTION_SPEECH_NORMAL:
		    glbConfig->myTTSEnable = true;
		    glbConfig->myTTSSpeed = 0;
		    updateSpeechFromConfig();
		    // Echo what you picked.
		    // Do this *AFTER* we've updated speach speeds
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		case OPTION_SPEECH_FAST:
		    glbConfig->myTTSEnable = true;
		    glbConfig->myTTSSpeed = 5;
		    updateSpeechFromConfig();
		    // Echo what you picked.
		    // Do this *AFTER* we've updated speach speeds
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
		case OPTION_SPEECH_VERYFAST:
		    glbConfig->myTTSEnable = true;
		    glbConfig->myTTSSpeed = 10;
		    updateSpeechFromConfig();
		    // Echo what you picked.
		    // Do this *AFTER* we've updated speach speeds
		    msg_report(glb_optiondefs[key].name);
		    msg_newturn();
		    break;
#endif
	    }
	}
	else
	{
	    msg_report("Invalid option choice.\n");
	    msg_newturn();
	}
    }
    else
    {
	msg_report("Cancelled.\n");
	msg_newturn();
    }

    return done;
}
else
{
    int			key;
    bool		done = false;

    WORLD_NAMES	world = WORLD_WIZARD;
    if (glbMap) world = glbMap->world();

    buildOptionsMenu(OPTION_PLAY);

    // Run the chooser...
    while (!TCODConsole::isWindowClosed())
    {
	redrawWorld();
	key = gfx_getKey(false);

	glbChooser->processKey(key);

	if (key)
	{
	    // User selects this?
	    if (key == '\n' || key == ' ')
	    {
		if (glbChooser->getChoice() == OPTION_HELP)
		{
		    // Instructions.
		    helpMenu();
		    // return to play.
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_PLAY)
		{
		    // Play...
		    break;
		}
#if 0
		else if (glbChooser->getChoice() == OPTION_VOLUME)
		{
		    int			i;
		    BUF			buf;
		    // Volume
		    glbChooser->clear();

		    for (i = 0; i <= 10; i++)
		    {
			buf.sprintf("%3d%% Volume", (10 - i) * 10);

			glbChooser->appendChoice(buf);
		    }
		    glbChooser->setChoice(10 - glbConfig->myMusicVolume);

		    while (!TCODConsole::isWindowClosed())
		    {
			redrawWorld();
			key = gfx_getKey(false);

			glbChooser->processKey(key);

			setMusicVolume(10 - glbChooser->getChoice());
			if (key)
			{
			    break;
			}
		    }
		    buildOptionsMenu(OPTION_VOLUME);
		}
#endif
		else if (glbChooser->getChoice() == OPTION_USEMENUS)
		{
		    glbConfig->myUseMenus = !glbConfig->myUseMenus;
		    msg_report(glb_optiondefs[glbChooser->getChoice()].name);
		    msg_newturn();
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_VERBOSITY)
		{
		    glbConfig->myVerbose = !glbConfig->myVerbose;
		    msg_report(glb_optiondefs[glbChooser->getChoice()].name);
		    msg_newturn();
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_WALLSLIDE)
		{
		    glbConfig->myWallSlide = !glbConfig->myWallSlide;
		    msg_report(glb_optiondefs[glbChooser->getChoice()].name);
		    msg_newturn();
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_FULLSCREEN)
		{
		    glbConfig->myFullScreen = !glbConfig->myFullScreen;
		    // This is intentionally unrolled to work around a
		    // bool/int problem in libtcod
		    if (glbConfig->myFullScreen)
			TCODConsole::setFullscreen(true);
		    else
			TCODConsole::setFullscreen(false);
		}
		else if (glbChooser->getChoice() == OPTION_QUIT)
		{
		    // Quit
		    done = true;
		    break;
		}
	    }
	    else if (key == '\x1b')
	    {
		// Esc on options is to go back to play.
		// Play...
		break;
	    }
	    else
	    {
		// Ignore other options.
	    }
	}
    }
    glbChooserActive = false;

    return done;
}
}

bool
reloadLevel(bool alreadybuilt, bool alreadywaited = false, bool reload=false)
{
    if (TCODConsole::isWindowClosed())
	return true;

    // Build a new game world!
    glbLevel = 1;
    msg_newturn();
    glbMessagePanel->clear();
    glbMessagePanel->appendText("> ");

    for (int i = 0; i < 10; i++)
    {
	glbUnitWaypoint[i] = IVEC2(-1, -1);
    }

    glbVeryFirstRun = false;

    if (!alreadybuilt)
	glbEngine->queue().append(COMMAND(ACTION_RESTART, glbLevel));

    startMusic();

    // Wait for the map to finish.
    if (!alreadywaited)
	reload = glbEngine->awaitRebuild();

    // Redrawing the world updates our glbMap so everyone will see it.
    redrawWorld();
    msg_newturn();

    WORLD_NAMES		world = WORLD_WIZARD;
    if (glbMap)
	world = glbMap->world();
    text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
    text_registervariable("PRIZE", GAMEDEF::itemdef(glb_worlddefs[world].macguffin)->name);
    if (reload)
    {
	welcomeText("Back");
    }
    else
    {
	welcomeText(glb_worlddefs[world].name);
    }

    // Look around initially!
    if (glbConfig->myVerbose)
    {
	msg_preempt_suppress();
	doChamberScan(glbMap);
	doLookAround(glbMap, true);
    }
    msg_newturn();

    // Redraw again for output from looking around to display.
    redrawWorld();

    return false;
}

// Let the avatar watch the end...
void
deathCoolDown()
{
    int		coolstartms = TCOD_sys_elapsed_milli();
    int		lastenginems = coolstartms;
    int		curtime;
    bool	done = false;
    double	ratio;

    msg_report("Dead?  How?  Time to think on what happened...\n");
    msg_preempt_suppress();
    msg_newturn();

    glbDisplay->fadeToBlack(true);
    while (!done)
    {
	curtime = TCOD_sys_elapsed_milli();

	// Queue wait events, one per 100 ms.
	while (lastenginems + 200 < curtime)
	{
	    glbEngine->queue().append(COMMAND(ACTION_WAIT));
	    lastenginems += 200;
	}

	// Set our fade.
	if (!glbDisplay->fadeToBlack(true))
	{
	    done = true;
	    ratio = 0;
	}

	// Keep updating
	redrawWorld();
    }

    gfx_clearKeyBuf();
}

void
shutdownEverything(bool save)
{
#if 1
    glbConfig->save("../wtf.cfg");

    // Save the game.  A no-op if dead.
    if (save)
    {
	glbEngine->queue().append(COMMAND(ACTION_SAVE));
	glbEngine->awaitSave();
    }
    else
    {
	glbEngine->queue().append(COMMAND(ACTION_SHUTDOWN));
	glbEngine->awaitSave();
    }

#if 0
    BUF		path;
    path.sprintf("../save/%s.txt", glbWorldName);
    GAMEDEF::save(path.buffer());
#endif
#else
    glbEngine->queue().append(COMMAND(ACTION_SHUTDOWN));
    glbEngine->awaitSave();
#endif

    stopMusic(true);
#ifdef USE_AUDIO
    if (glbTunes)
	Mix_FreeMusic(glbTunes);
#endif

    gfx_shutdown();

#ifdef USE_AUDIO
    SDL_QuitSubSystem(SDL_INIT_AUDIO);
#endif

    //SDL_Quit();
}

void
queryOfferPrice(COMMAND &cmd)
{
    MOB		*avatar = (glbMap ? glbMap->avatar() : 0);
    if (!avatar || !avatar->alive())
    {
	cmd.action() = ACTION_NONE;
	return;
    }

    MOB		*at = avatar->map()->findMob(cmd.dy());
    ITEM	*item = avatar->getItemFromId(cmd.dz());
    if (!item || !at)
    {
	cmd.action() = ACTION_NONE;
	return;
    }

    BUF		query;
    BUF		itemname = item->getSingleName();

    query.sprintf("You notice %s has %d gold.\nHow much will you demand for %s?\n",
	    at->getName().buffer(),
	    at->getGold(),
	    itemname.buffer() );

    BUF		amount = popupQuestion(query);

    cmd.dw() = atoi(amount.buffer());
    if (cmd.dw() <= 0)
	cmd.action() = ACTION_NONE;
}

void
queryPurchasePrice(COMMAND &cmd)
{
    MOB		*avatar = (glbMap ? glbMap->avatar() : 0);
    if (!avatar || !avatar->alive())
    {
	cmd.action() = ACTION_NONE;
	return;
    }

    MOB		*at = avatar->map()->findMob(cmd.dy());
    ITEM	*item = at->getItemFromId(cmd.dz());

    if (!item || !at)
    {
	cmd.action() = ACTION_NONE;
	return;
    }
    BUF		query;
    BUF		itemname = item->getSingleName();

    query.sprintf("You have %d gold.\nHow much will you offer for %s?\n",
	    avatar->getGold(),
	    itemname.buffer() );

    BUF		amount = popupQuestion(query);

    cmd.dw() = atoi(amount.buffer());
    if (cmd.dw() <= 0)
	cmd.action() = ACTION_NONE;
}

void
buildShopMenu(MOB_NAMES keeper, PTRLIST<COMMAND> &list, PTRLIST<int> &hotkeys)
{
    glbChooser->clear();
    list.clear();
    BUF			buf;
    MOB		*avatar = (glbMap ? glbMap->avatar() : 0);
    bool	valid = false;

    if (!avatar || !avatar->alive())
	return;

    glbChooser->setPrequelAttr(glb_mobdefs[keeper].attr);

    if (valid)
    {
	glbChooser->appendChoice("No thank you.");
	list.append(COMMAND(ACTION_NONE));
	glbChooser->setChoice(list.entries()-1);
	glbChooserActive = true;
    }
    else
    {
	glbChooser->clear();
	list.clear();
    }
}


void
runShop(int shopkeeper_uid)
{
    PTRLIST<COMMAND>	shoppinglist;
    PTRLIST<int>	hotkeys;

    if (!glbMap)
	return;
    MOB		*shopkeeper_mob = glbMap->findMob(shopkeeper_uid);
    if (!shopkeeper_mob)
	return;

    // buildShopMenu(shopkeeper, shoppinglist, hotkeys);
    
    if (shoppinglist.entries() == 0)
	return;

    // Run the chooser...
    COMMAND	cmd = runMenuSelection(shoppinglist, COMMAND(ACTION_NONE), hotkeys);

    if (cmd.action() != ACTION_NONE)
    {
	glbEngine->queue().append(cmd);
    }
}

void
stopWaiting()
{
    if (glbMap && glbMap->avatar() && 
	glbMap->avatar()->alive())
    {
	if (glbMap->avatar()->isWaiting())
	{
	    glbEngine->queue().append(COMMAND(ACTION_STOPWAITING));
	}
    }
}

int
printItemList(const ITEMLIST &items)
{
    BUF		line;
    int         count = 0;
    for (auto && item : items)
    {
	if (count == 25)
	{
            msg_report("[z] Next Page\n");
	    break;
	}
	line.sprintf("[%c] %s\n",
		count + 'a',
		gram_capitalize(item->getName()).buffer()
                );
	msg_report(line);
        count++;
    }
    return count;
}

template <typename OP>
int
printInventory(MOB *mob, const OP &op)
{
    if (!mob)
	return 0;

    ITEMLIST		filteritems;
    buildFilteredInventory(filteritems, mob, op);

    BUF		line;
    int         count = 0;
    for (auto && item : filteritems)
    {
	line.sprintf("[%c] %s%s%s%s\n",
		item->inventoryLetter(),
		gram_capitalize(item->getName()).buffer(),
		item->isEquipped() ? " (equipped)" : "",
		item->isWielded() ? " (wielded)" : "",
		(item->isSpellbook() && mob->isSpellMemorized(item->asSpell()))  ? " (known)" : ""
                );
	msg_report(line);
        count++;
    }
    return count;
}

void
identifyAll(MAP *map)
{
    if (!map) return;
    MOB *mob = map->avatar();
    if (!mob) return;

    // This is very sketch as we don't own it... But a feature I guess
    // is that it doesn't actually affect the game?
    // Oh except I think magic class is global :<
    ITEMLIST		filteritems;
    buildFilteredInventory(filteritems, mob, ITEM::filterAll());
    for (auto && item : filteritems)
    {
	item->markBonusKnown();
	item->markMagicClassKnown(/*silent*/true);
    }
}

int
printAllInventory(MOB *mob)
{
    return printInventory(mob, ITEM::filterAll());
}

int
printAllIntrinsics(MOB *mob)
{
    ITEMLIST items;

    buildIntrinsics(items, mob);

    if (!items.entries())
    {
        msg_report("You are under no status effects.  ");
        return 0;
    }

    BUF         report;
    report.strcat("You are ");
    int         count = 0;
    for (int i = 0; i < items.entries(); i++)
    {
        if (i)
        {
            report.strcat(", ");
            if (i == items.entries()-1)
            {
                // Note this implies Oxford comman like God intended.
                report.strcat("and ");
            }
        }
        report.strcat(items(i)->getName());
        count++;
    }
    report.strcat(".  ");
    msg_report(report);
    return count;
}


void
verifyAllHelpWritten()
{
    // Disable for release!
    if (0)
        return;

#define CHECKHELP( KEY, TYPE, DEFS, TRANSMUTE ) \
{ \
    TYPE##_NAMES   mob; \
    BUF            transmute; \
    FOREACH_##TYPE(mob) \
    { \
        if (mob == TYPE##_NONE) \
            continue; \
 \
        transmute.sprintf(TRANSMUTE, GAMEDEF::DEFS(mob)->name); \
	if (!text_hasentry(KEY, transmute)) \
        { \
            fprintf(stderr, "Missing help for %s::%s\r\n", KEY, transmute.buffer()); \
        } \
    } \
} \
/**/

    CHECKHELP("mob", MOB, mobdef, "%s")
    CHECKHELP("item", ITEM, itemdef, "%s")
    CHECKHELP("item", SPELL, spelldef, "spellbook of %s")
    CHECKHELP("item", SCROLL, scrolldef, "%s")
    CHECKHELP("item", RING, ringdef, "%s")
    CHECKHELP("item", POTION, potiondef, "%s")

#undef CHECKHELP
}

#ifdef WIN32
int WINAPI
WinMain(HINSTANCE hINstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCMdSHow)
#else
int 
main(int argc, char **argv)
#endif
{
    bool		done = false;

    // Clamp frame rate.
    /// Has to be high to fix keyrepeat problem :<
    TCOD_sys_set_fps(200);

    glbConfig = new CONFIG();
    glbConfig->load("../wtf.cfg");

    // Dear Microsoft,
    // The following code in optimized is both a waste and seems designed
    // to ensure that if we call a bool where the callee pretends it is
    // an int we'll end up with garbage data.
    //
    // 0040BF4C  movzx       eax,byte ptr [eax+10h] 

    // TCODConsole::initRoot(SCR_WIDTH, SCR_HEIGHT, "The Smith's Hand", myFullScreen);
    // 0040BF50  xor         ebp,ebp 
    // 0040BF52  cmp         eax,ebp 
    // 0040BF54  setne       cl   
    // 0040BF57  mov         dword ptr [esp+28h],eax 
    // 0040BF5B  push        ecx  

    TCODConsole::setCustomFont("chimera.png", TCOD_FONT_LAYOUT_ASCII_INCOL | TCOD_FONT_TYPE_GREYSCALE);

    // My work around is to constantify the fullscreen and hope that
    // the compiler doesn't catch on.
    if (glbConfig->myFullScreen)
	TCODConsole::initRoot(SCR_WIDTH, SCR_HEIGHT, "WTF", true);
    else
	TCODConsole::initRoot(SCR_WIDTH, SCR_HEIGHT, "WTF", false);

    // TCOD doesn't do audio.
#ifdef USE_AUDIO
    SDL_InitSubSystem(SDL_INIT_AUDIO);
    if (Mix_OpenAudio(22050, AUDIO_S16, 2, 4096))
    {
	printf("Failed to open audio!\n");
	exit(1);
    }
#endif

#if USE_VOICE
#ifdef WIN32
    if (glbConfig->myTTSConnect)
    {
	HRESULT hr;
	hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (FAILED(hr))
	{
	    printf("Failed to connect COM\n");
	    exit(3);
	}
	hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&glbVoice);
	if (SUCCEEDED(hr))
	{
	    updateSpeechFromConfig();
	}
	else
	{
	    printf("Failed to connect TTS system.\n");
	    glbVoice = nullptr;
	}
    }
#endif
#endif

#ifdef WIN32
    // Create a console...
    if (glbConfig->myConsoleConnect)
    {
	if (AllocConsole() == 0)
	{
	    printf("Failed to alloc console, ironically this is suppressed\n");
	    exit(2);
	}

	FILE *pNewStdOut = nullptr;
	FILE *pNewStdErr = nullptr;
	FILE *pNewStdIn = nullptr;
	::freopen_s(&pNewStdOut, "CONOUT$", "w", stdout);
	::freopen_s(&pNewStdErr, "CONOUT$", "w", stderr);
	::freopen_s(&pNewStdIn, "CONIN$", "r", stdin);
	std::cout.clear();
	std::cerr.clear();
	std::cin.clear();
	std::wcout.clear();
	std::wcerr.clear();
	std::wcin.clear();
    }
#endif

    // SDL_InitSubSystem(SDL_INIT_JOYSTICK);

    setMusicVolume(glbConfig->musicVolume());

    //rand_setseed((long) time(0));
    rand_truerandomseed();
    for (int i = 0; i < 10000; i++)
	rand_choice(2);

    text_init();

    gfx_init();
    MAP::init();

    GAMEDEF::reset(true);

    thesaurus_init();

    glbDisplay = new DISPLAY(0, 0, 72, 39, 0);

    glbMessagePanel = new PANEL(65, 38);
    msg_registerpanel(glbMessagePanel);
    glbMessagePanel->move(SCR_WIDTH-68, SCR_HEIGHT-40);

    glbPopUp = new PANEL(66, 80);
    glbPopUp->move(SCR_WIDTH/2 - 66/2, 5);
    glbPopUp->setBorder(true, ' ', ATTR_BORDER);
    // And so the mispelling is propagated...
    glbPopUp->setRigthMargin(1);
    glbPopUp->setIndent(1);

    glbJacob = new PANEL(65, SCR_HEIGHT-22);
    glbJacob->move(SCR_WIDTH-68, 0);
    glbJacob->setTransparent(true);
    glbJacob->setAllowScroll(false);

    glbHelp = new PANEL(65, 2);
    glbHelp->move(SCR_WIDTH-68, SCR_HEIGHT-22);
    glbHelp->setTransparent(true);
    glbHelp->setAllowScroll(false);

    glbStatLine = new PANEL(SCR_WIDTH, 2);
    glbStatLine->move(0, SCR_HEIGHT-1);
    glbStatLine->setTransparent(true);

    glbVictoryInfo = new PANEL(26, 5);
    glbVictoryInfo->move(SCR_WIDTH/2 - 13, 2);
    glbVictoryInfo->setIndent(2);
    glbVictoryInfo->setBorder(true, ' ', ATTR_VICTORYBORDER);
    glbVictoryInfo->clear();
    glbVictoryInfo->newLine();
    glbVictoryInfo->appendText("The battle has ended!");
    glbVictoryInfo->newLine();
    glbVictoryInfo->newLine();
    glbVictoryInfo->appendText("Press [V] to finish.");

    glbLevel = 1;

    glbChooser = new CHOOSER();
    glbChooser->move(SCR_WIDTH/2, SCR_HEIGHT/2, CHOOSER::JUSTCENTER, CHOOSER::JUSTCENTER);
    glbChooser->setBorder(true, ' ', ATTR_BORDER);
    glbChooser->setIndent(1);
    glbChooser->setRigthMargin(1);
    glbChooser->setAttr(ATTR_NORMAL, ATTR_HILITE, ATTR_LIGHTBLACK, ATTR_DKHILITE);

    glbLevelBuildStatus = new CHOOSER();
    glbLevelBuildStatus->move(SCR_WIDTH/2, SCR_HEIGHT/2, CHOOSER::JUSTCENTER, CHOOSER::JUSTCENTER);
    glbLevelBuildStatus->setBorder(true, ' ', ATTR_BORDER);
    glbLevelBuildStatus->setIndent(1);
    glbLevelBuildStatus->setRigthMargin(1);
    glbLevelBuildStatus->setAttr(ATTR_NORMAL, ATTR_HILITE, ATTR_HILITE,
				ATTR_HILITE);

    glbEngine = new ENGINE(glbDisplay);

    if (1)
    {
	glbCoinStack = 0;
    }
    else
    {
	glbCoinStack = new FIREFLY(0, 20, SCR_HEIGHT, FIRE_HEALTH, false);
	glbCoinStack->setBarGraph(false);

	{
	    ATTR_DEF	*def = GAMEDEF::attrdef(GAMEDEF::itemdef(ITEM_COIN)->attr);
	    glbCoinStack->setColor( def->fg_r, def->fg_g, def->fg_b );
	}
    }

    // Run our first level immediately so it can be built or 
    // loaded from disk while people wait.
    // The problem is we expect to be able to create/restore
    // worlds from the options menu, so we had better have
    // finished it prior to that!
    glbEngine->queue().append(COMMAND(ACTION_RESTART, 1));

    glbReloadedLevel = glbEngine->awaitRebuild();
    glbHasDied = false;

    done = reloadLevel(true, true, glbReloadedLevel);
    if (done)
    {
	shutdownEverything(true);
	return 0;
    }

    verifyAllHelpWritten();

    bool		clearkeybuf = false;
    unsigned int			framestartMS = TCOD_sys_elapsed_milli();
    do
    {
	int		key;
	int		dx, dy;

	// our own frame governer.
	while (TCOD_sys_elapsed_milli() - framestartMS < 33)
	{
	    THREAD::yield();
	    TCOD_sys_sleep_milli(5);
	}
	framestartMS = TCOD_sys_elapsed_milli();

	redrawWorld();

	while (1)
	{
	    BUF		pop;

	    pop = glbEngine->getNextPopup();
	    if (!pop.isstring())
		break;

	    popupText(pop, POPUP_DELAY);
	}

	while (1)
	{
	    ENGINE::Question		query;

	    if (glbEngine->getQuestion(query))
	    {
		glbEngine->answerQuestion(engine_askQuestion(query));
	    }
	    else
		break;
	}


	if (glbMap && glbMap->avatar() &&
	    !glbMap->avatar()->alive())
	{
	    // You have died...
	    deathCoolDown();

	    // See if there is another life...
	    MOB		*avatar = glbMap->avatar();
	    WORLD_NAMES	world;
	    bool  	 stillalive = false;
	    FOREACH_WORLD(world)
	    {
		if (avatar->aliveInWorld(world))
		{
		    J_ASSERT(world != glbMap->world());

		    MOB_NAMES	avatarname = glb_worlddefs[world].avatar;
		    BUF		msg;
		    msg.sprintf("Hit a key to shift to another vantage point.",
			    glb_roledefs[GAMEDEF::mobdef(avatarname)->role].name);
		    msg_report(msg);
		    msg_newturn();
		    awaitAlphaKey(key);
		    glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, world));
		    glbEngine->awaitWorldShift();
		    stillalive = true;
		    break;
		}
	    }

	    // Show the map again.
	    glbDisplay->fadeToBlack(false);

	    gfx_clearKeyBuf();
	    clearkeybuf = true;

	    if (!stillalive)
	    {
		text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
		text_registervariable("PRIZE", GAMEDEF::itemdef(ITEM_MACGUFFIN)->name);

		msg_preempt_suppress();
		popupText(text_lookup("game", "lose"));

		msg_newturn();
		awaitAlphaKey(key);

		msg_report("Your final status:\n");
		doStatus(glbMap);
		msg_report("\nYou died carrying:\n");
		identifyAll(glbMap);
		printAllInventory(glbMap->avatar());
		msg_report("\nPress a key to play again.\n");
		msg_newturn();
		awaitAlphaKey(key);

		stopMusic();

		// Don't prep a reload.
		done = reloadLevel(false);
		glbHasDied = false;
	    }
	}
	else if (glbMap && glbMap->avatar() &&
		glbMap->avatar()->hasWon())
	{
	    // Traditional win.
	    gfx_clearKeyBuf();
	    clearkeybuf = true;
	    MOB		*avatar = glbMap->avatar();

	    WORLD_NAMES		world = glbMap->world();

	    text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
	    text_registervariable("PRIZE", GAMEDEF::itemdef(glb_worlddefs[world].macguffin)->name);
	    msg_preempt_suppress();

	    BUF		buf;
	    buf.sprintf("The story of the %s.\n\n", glb_worlddefs[world].name);
	    msg_report(buf);

	    popupText(text_lookup("game", "victory", glb_worlddefs[world].name));
	    WORLD_NAMES		other;
	    FOREACH_WORLD(other)
	    {
		if (other == world)
		    continue;
		msg_report("Hit a key to see the next vantage point...");
		msg_newturn();
		awaitAlphaKey(key);
		buf.sprintf("The story of the %s.\n\n", glb_worlddefs[other].name);
		msg_report(buf);

		if (!avatar->aliveInWorld(other))
		{
		    popupText(text_lookup("game", "lose", glb_worlddefs[other].name));
		}
		else if (!avatar->hasItemInWorld(other, glb_worlddefs[other].macguffin))
		{
		    popupText(text_lookup("game", "escape", glb_worlddefs[other].name));
		}
		else
		{
		    popupText(text_lookup("game", "victory", glb_worlddefs[other].name));
		}
	    }
	    glbHasDied = false;

	    msg_report("Hit a key to see your final status.");
	    msg_newturn();
	    awaitAlphaKey(key);

	    msg_report("Your status on victory:\n");
	    doStatus(glbMap);
	    msg_report("\nYou won with:\n");
	    identifyAll(glbMap);
	    printAllInventory(glbMap->avatar());
	    msg_report("\nPress a key to play again.\n");
	    msg_newturn();
	    awaitAlphaKey(key);

	    stopMusic();
	    done = reloadLevel(false);
	}
	else if (glbMap && !glbMap->avatar())
	{
	    stopMusic();
	    done = reloadLevel(false);
	    glbHasDied = false;
	}

	if (glbMap)
	{
	    if (!glbHasDied)
	    {
		// See if the game is over!
	    }
	}

	// Wait for queue to empty so we don't repeat.
	// glbEngine->awaitQueueEmpty();
	if (clearkeybuf)
	{
	    gfx_clearKeyBuf();
	    clearkeybuf = false;
	}

	key = gfx_getKey(false);

	// Continously rebuild.
	// key = '~';

	if (!key)
	    continue;

	if (cookDirKey(key, dx, dy, false))
	{
	    // If no motion, it is a normal wait.
	    if (!dx && !dy)
	    {
		glbEngine->queue().appendIfEmpty(COMMAND(ACTION_WAIT));
	    }
	    else
	    {
		// Direction.
		glbEngine->queue().appendIfEmpty(COMMAND(ACTION_BUMP, dx, dy));
	    }
	    // empty key buffer.
	    // DAMN YOU TCOD!
	    gfx_clearKeyBuf();
	    clearkeybuf = true;
	}

	KEY_NAMES cookkey = cookKey(key);

	auto list_inventory = [](auto op, const char *empty)
	{
	    int nitem = printInventory(glbMap->avatar(), op);
	    if (!nitem) msg_report(empty);
	    msg_newturn();
	};

	// Unbindable:
	switch (key)
	{
	    case '\x1b':
		// If meditating, abort.
		if (glbMap && glbMap->avatar() && 
		    glbMap->avatar()->alive() && glbMap->avatar()->isMeditating())
		{
		    glbEngine->queue().append(COMMAND(ACTION_GHOSTRECENTER));
		    break;
		}
		// If waiting, abort.
		if (glbMap && glbMap->avatar() && 
		    glbMap->avatar()->alive() && glbMap->avatar()->isWaiting())
		{
		    glbEngine->queue().append(COMMAND(ACTION_STOPWAITING));
		    break;
		}
		// Trigger options
		done = optionsMenu();
		break;

            case 'p':
                msg_replay_message();
                break;
            case '{':
                msg_play_previous_message();
                break;
            case '}':
                msg_play_next_message();
                break;

	    case GFX_KEYF1:
		stopWaiting();
		helpMenu();
		break;

	    case '*':
                msg_report("Your inventory:\n");
		list_inventory(ITEM::filterAll(), "You are carrying nothing.");
		break;
	    case '@':
                msg_report("Your equipped items:\n");
		list_inventory(ITEM::filterWieldedOrWorn(), "You are wielding and wearing nothing.");
		break;
	    case '!':
                msg_report("Your drinkables:\n");
		list_inventory(ITEM::filterDrinkable(), "You have no drinkables.");
		break;
	    case '%':
                msg_report("Your edibles:\n");
		list_inventory(ITEM::filterEdible(), "You have no edibles.");
		break;
	    case '$':
                msg_report("Your treasure:\n");
		list_inventory(ITEM::filterTreasure(), "You have no treasure.");
		break;
	    case '+':
                msg_report("Your spellbooks:\n");
		list_inventory(ITEM::filterSpellbooks(), "You have no spellbooks.");
		break;
	    case '?':
                msg_report("Your scrolls:\n");
		list_inventory(ITEM::filterScrolls(), "You have no scrolls.");
		break;
	    case '/':		// Probably want wands some day :<
                msg_report("Your ammo:\n");
		list_inventory(ITEM::filterAmmo(), "You have no ammo.");
		break;
	    case '[':
                msg_report("Your armour:\n");
		list_inventory(ITEM::filterArmour(), "You have no armour.");
		break;
	    case '=':
                msg_report("Your rings:\n");
		list_inventory(ITEM::filterRings(), "You have no rings.");
		break;
	    case ')':
                msg_report("Your weapons:\n");
		list_inventory(ITEM::filterWeaponOrRanged(), "You have no weapons.");
		break;
	    case '(':
                msg_report("Your tools:\n");
		list_inventory(ITEM::filterAppliable(), "You have no tools.");
		break;

	    case GFX_KEYF2:
		msg_report("Switch to the wizard's viewpoint");
		msg_newturn();
		glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_WIZARD));
		glbEngine->awaitWorldShift();
		break;
	    case GFX_KEYF3:
		msg_report("Switch to the thief's viewpoint");
		msg_newturn();
		glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_THIEF));
		glbEngine->awaitWorldShift();
		break;
	    case GFX_KEYF4:
            case GFX_KEYF5:     // My f4 key is broken :<
		msg_report("Switch to the fighter's viewpoint");
		msg_newturn();
		glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_FIGHTER));
		glbEngine->awaitWorldShift();
		break;
	}

	int itemno = -1;

	switch (cookkey)
	{
	    case KEY_OPTIONS:
		stopWaiting();
		done = optionsMenu();
		break;
	    case KEY_QUIT:
		done = true;
		break;
	    case KEY_RELOAD:
	    {
		// It is rather dangerous to leave reload, let people
		// just die.  Or if I care, add with a prompt on the
		// options menu.
#if 1
		int choice = 1;
if (!glbConfig->myUseMenus)
{
		msg_report("Are you sure you want to abandon this game and restart from the begining?  [y] to restart.");
		msg_newturn();
		int key = 0;
		if (awaitAlphaKey(key))
		{
		    if (key == 'y')
			choice = 0;
		}
		if (choice != 0)
		{
		    msg_report("Cancelled.");
		    msg_newturn();
		}
}
else
{
		BUF		restart_query;
		restart_query.reference("Are you sure you wish to restart from the beginning?");
		const char *answers[] = { "Yes, a fresh start!", "No, I wish to keep playing.", 0 };
		choice = glbEngine->askQuestionList(restart_query, answers, 1, true);
}
		if (choice == 0)
		{
		    done = reloadLevel(false);
		}
#else
		done = reloadLevel(false);
#endif
		break;
	    }

	    case KEY_INVENTORY:
		if (glbMap && glbMap->avatar())
		{
if (!glbConfig->myUseMenus)
{
                    msg_report("Your inventory:\n");
		    int nitem = printAllInventory(glbMap->avatar());
                    if (nitem)
                    {
                        itemno = chooseItem("Inspect what? ", ITEM::filterAll());
                        if (itemno >= 0)
                            displayItem(glbMap->avatar()->inventory()(itemno));
                    }
                    else
                    {
                        msg_report("You are carrying nothing.");
                    }
		    msg_newturn();
}
else
{
		    inventoryMenu(false);
}
		}
		break;

	    case KEY_INTRINSICS:
		if (glbMap && glbMap->avatar())
		{
if (!glbConfig->myUseMenus)
{
		    printAllIntrinsics(glbMap->avatar());
		    msg_newturn();
}
else
{
		    inventoryMenu(false);
}
		}
		break;

	    case KEY_CLIMBUP:
	    case KEY_CLIMBDOWN:
		glbEngine->queue().append(COMMAND(ACTION_CLIMB));
		break;

	    case KEY_WORLDSHIFT:
	    {
		msg_report("Switch to [w]izard, [t]hief, or [f]ighter?  ");
		msg_newturn();
		int		key;
		awaitAlphaKey(key);

		if (key == 'w')
		{
		    msg_report("Wizard");
		    msg_newturn();
		    glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_WIZARD));
		    glbEngine->awaitWorldShift();
		}
		else if (key == 't')
		{
		    msg_report("Thief");
		    msg_newturn();
		    glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_THIEF));
		    glbEngine->awaitWorldShift();
		}
		else if (key == 'f')
		{
		    msg_report("Fighter");
		    msg_newturn();
		    glbEngine->queue().append(COMMAND(ACTION_WORLDSHIFT, WORLD_FIGHTER));
		    glbEngine->awaitWorldShift();
		}
		else
		{
		    msg_report("Cancelled");
		    msg_newturn();
		}
		break;
	    }
	    case KEY_EXAMINE:
		doExamine();
		break;

	    case KEY_LOOKAROUND:
		doLookAround(glbMap, false);
		msg_newturn();
		break;

	    case KEY_STATUS:
		doStatus(glbMap);
		msg_newturn();
		break;

	    case KEY_CHAMBERSCAN:
		doChamberScan(glbMap);
		msg_newturn();
		break;

	    case KEY_PICKUP:
	    case KEY_PICKUP2:
		glbEngine->queue().append(COMMAND(ACTION_PICKUP));
		break;

	    case KEY_DROPALL:
		glbEngine->queue().append(COMMAND(ACTION_DROPALL));
		break;

	    case KEY_PICKUPSPECIFIC:
		actionWithGroundItem(ACTION_PICKUPSPECIFIC, "Pick up what?  ", ITEM::filterAll());
		break;

	    case KEY_KICK:
		actionWithDirection(ACTION_KICK, "Kick in what direction?  ");
		break;
	    case KEY_CLOSE:
		actionWithDirection(ACTION_CLOSE, "Close in what direction?  ");
		break;
	    case KEY_OPEN:
		actionWithDirection(ACTION_OPEN, "Open in what direction?  ");
		break;
	    case KEY_FIRE:
		actionWithDirection(ACTION_FIRE, "Shoot in what direction?  ");
		break;
	    case KEY_EAT:
		actionFromChosenItemOrGround(ACTION_EAT, ACTION_EATGROUND, /*start_with_invenotry*/true, "Eat what? ", ITEM::filterEdible());
		break;
	    case KEY_READYRANGED:
		actionFromChosenItem(ACTION_READYRANGED, "Ready what for ranged combat?  ", ITEM::filterRanged());
		break;
	    case KEY_DROP:
		actionFromChosenNumberedItem(ACTION_DROP, "Drop what? ", ITEM::filterDroppable());
		break;
	    case KEY_QUAFF:
		actionFromChosenItem(ACTION_QUAFF, "Drink what? ", ITEM::filterDrinkable());
		break;
	    case KEY_READWRITING:
		actionFromChosenItem(ACTION_READ, "Read what? ", ITEM::filterReadable());
		break;
	    case KEY_ENHANCE:
#if 0
		// Enhance wielded menu:
		actionFromChosenItem(ACTION_TRANSMUTE, "Enhance with what? ",
		    [map = glbMap](ITEM *item) -> bool
		    {
			if (!map || !map->avatar()) return false;
			MOB *avatar = map->avatar();
			if (item->isEquipped() || item->isWielded()) return false;
			ITEM *target = avatar->lookupWeapon();
			if (item->isRing()) target = avatar->lookupRing();
			if (item->isArmour()) target = avatar->lookupArmour();
                        if (!target) return false;
			return target->getDefinition() == item->getDefinition();
		    },
		    -1);
#else
		// Enhance selected..
		itemno = chooseItem("Enhance what? ", 
		    [map = glbMap](ITEM *item) ->bool
		    {
			if (!map || !map->avatar()) return false;
			MOB *avatar = map->avatar();
			// Filter if there is a target...
			// Stacks always have a target...
			if (item->getStackCount() > 1) return true;
			// Otherwise search for matching definition
			// that isn't us..
			for (auto && test : avatar->inventory())
			{
			    if (test != item &&
				test->getDefinition() == item->getDefinition())
				return true;
			}
			// Nothing found.
			return false;
		    });
		if (itemno >= 0)
		{
		    BUF		prompt;
		    ITEM 	*target = glbMap->avatar()->inventory()(itemno);
		    prompt.sprintf("Enhance %s with what?",
			    target->getSingleArticleName().buffer());
		    actionFromChosenItem(ACTION_TRANSMUTE, prompt.buffer(),
			[target = target, map = glbMap]
			(ITEM *item) -> bool
			{
			    if (target == item)
			    {
				if (item->getStackCount() > 1) return true;
				return false;
			    }
			    return target->getDefinition() == item->getDefinition();
			},
			itemno);
		}

#endif
		break;
	    case KEY_WIELD:
		actionFromChosenItem(ACTION_WIELD, "Wield what? ", ITEM::filterWieldable());
		break;
	    case KEY_WEAR:
		actionFromChosenItem(ACTION_WEAR, "Wear what? ", ITEM::filterWearable());
		break;
	    case KEY_TAKEOFF:
		specificActionFromChosenItem([](ITEM *item) ->ACTION_NAMES
		    {
			if (item->isWielded())
			    return ACTION_WIELD;
			if (item->isRanged())
			    return ACTION_READYRANGED;
			return ACTION_WEAR;
		    }, "Take off what? ", 
		    ITEM::filterWieldedOrWorn());
		break;
	    case KEY_THROW:
		itemno = chooseItem("Throw what? ", ITEM::filterThrowable());
		if (itemno >= 0)
		    actionWithDirection(ACTION_THROW, "Throw in what direction?  ", itemno);
		break;
	    case KEY_APPLY:
            {
		itemno = chooseItem("Apply what? ", ITEM::filterAppliable());
		doApply(itemno);
		break;
            }
            case KEY_CAST:
            {
                SPELL_NAMES spell = chooseSpell("Cast which spell? ");
                if (spell != SPELL_NONE)
                {
                    // Must be non-null or we'd not have found a spell.
                    castSpell(glbMap->avatar(), spell);
                }
                break;
            }
            case KEY_FORGET:
            {
                SPELL_NAMES spell = chooseSpell("Forget which spell? ");
                if (spell != SPELL_NONE)
                {
                    glbEngine->queue().append(COMMAND(ACTION_FORGET, spell));
                }
                break;
            }
	    case KEY_SEARCH:
		glbEngine->queue().append(COMMAND(ACTION_SEARCH));
		break;
	    case KEY_MEDITATE:
		glbEngine->queue().append(COMMAND(ACTION_WAITUNTIL));
		break;
	    case NUM_KEYS:
		break;
	}
    } while (!done && !TCODConsole::isWindowClosed());

    shutdownEverything(true);

    return 0;
}
