/*
 * 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 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")

// 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 <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 = "1812";
bool			glbReloadedLevel = true;
bool		 	glbOvermapDisplay = true;
bool			glbViewMySide = true;

volatile bool			glbAutoPilot = false;

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"

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 = 1;
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;
int		 glbSelectedUnit = -1;
IVEC2		 glbUnitWaypoint[10];
bool		 glbHasDied = false;

#define DEATHTIME 15000


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();
    }
};

class ITEMPTR_QUALITYSORT
{
public:
    bool operator()(ITEM *a, ITEM *b)
    {
	if (a->isBroken() && !b->isBroken())
	    return true;
	if (!a->isBroken() && b->isBroken())
	    return false;
	if (a->isMelee())
	{
	    if (b->isArmour())
		return true;
	    if (b->isMelee())
		return (a->attackdefn().damage < b->attackdefn().damage);
	    return true;
	}
	if (a->isArmour())
	{
	    if (b->isMelee())
		return false;
	    if (b->isArmour())
		return (a->getDamageReduction() < b->getDamageReduction());
	    return true;
	}
	return a->getUID() < b->getUID();
    }
};

int
verifyKeys()
{
    int		keycount[26];
    int		totalkey = 0;
    memset(keycount, 0, sizeof(int) * 26);
    
    KEY_NAMES	key;
    FOREACH_KEY(key)
    {
	u8	sym = GAMEDEF::keydef(key)->val;
	if (sym < 'a' || sym > 'z')
	{
	    J_ASSERT(!"Invalid key binding!");
	}
	keycount[sym-'a']++;
	if (keycount[sym-'a'] > 1)
	{
	    J_ASSERT(!"Duplicate key!");
	}
	totalkey++;
    }
    return totalkey;
}

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!
    // Also don't cook numerics, we need this for the troop selection.
    if (!strchr("yuhjklbn", key) && !strchr("0123456789", 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;
}

static void
displayMarker(PANEL *panel, const OVERMAP::MARKER &marker, int maptime, bool nested)
{
    BUF		buf;

    if (marker.team() < 0)
	panel->setTextAttr(ATTR_WHITE);
    else if (marker.team())
	panel->setTextAttr(ATTR_CYAN);
    else
	panel->setTextAttr(ATTR_ORANGE);

    u8	symbol;
    if (marker.unit() < 0)
    {
	J_ASSERT(!nested);
	// Combined
	symbol = 'A' - marker.unit() - 1;
	PTRLIST<OVERMAP::MARKER>	allmarkers;
	glbMap->getOvermap()->markersAt(glbViewMySide, allmarkers, marker.x(), marker.y());

	buf.sprintf("%c:\n", symbol);
	panel->appendText(buf);
	for (int j = 0; j < allmarkers.entries(); j++)
	{
	    displayMarker(panel, allmarkers(j), maptime, true);
	}
    }
    else
    {
	bool dead = false;
	MOB	*commander = glbMap->findCommander(marker.team(), marker.unit());
	if (!commander)
	{
	    // news travels fast!
	    dead = true;
	}
	if (dead)
	{
	    if (marker.team())
		panel->setTextAttr(ATTR_DKCYAN);
	    else
		panel->setTextAttr(ATTR_DKORANGE);
	}
	// Single regiment.
	BUF		place = gram_createplace(marker.unit());
	symbol = '0' + marker.unit();
	if (nested)
	    buf.sprintf("   %s Regiment", place.buffer());
	else
	    buf.sprintf("%c: %s Regiment", symbol, place.buffer());
	panel->appendText(buf); 
	panel->setTextAttr(ATTR_NORMAL);
	buf.sprintf(" [%2d %3d] ", marker.officers(), marker.soldiers());
	panel->appendText(buf);
	panel->setTextAttr(ATTR_NORMAL);
	int		time = marker.time();

	if (maptime - time < 60)
	{
	    // Less than a minute is real time!
	    panel->setTextAttr(ATTR_WHITE);
	}
	else if (maptime - time < 60 * 5)
	{
	    panel->setTextAttr(ATTR_LTGREY);
	}
	else if (maptime - time < 60 * 15)
	{
	    panel->setTextAttr(ATTR_GREY);
	}
	else if (maptime - time < 60 * 60)
	{
	    panel->setTextAttr(ATTR_DKGREY);
	}
	else
	{
	    panel->setTextAttr(ATTR_LIGHTBLACK);
	}

	buf.sprintf("(updated %2d:%02d:%02d)",
			(time / (60*60)) + 7,
			(time / 60) % 60,
			time % 60);
	panel->appendText(buf);

	if (0)
	{
	    // Find the commander's comset
	    int comset = -1;
	    MOB	*commander = glbMap->findCommander(marker.team(), marker.unit());
	    if (commander)
		comset = commander->getComset();
	    buf.sprintf(" {%d} ", comset);
	    panel->appendText(buf);
	}

	if (glbSelectedUnit == marker.unit() && marker.team() == (int)glbViewMySide)
	{
	    panel->setTextAttr(ATTR_YELLOW);
	    panel->appendText(" (*)");
	}
	panel->appendText("\n");
    }
}

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

    panel->setTextAttr(ATTR_NORMAL);
    panel->appendText("You command ");
    panel->setTextAttr(ATTR_CYAN);
    panel->appendText("Cyan");
    panel->setTextAttr(ATTR_NORMAL);
    panel->appendText(", your foe commands ");
    panel->setTextAttr(ATTR_ORANGE);
    panel->appendText("Orange");
    panel->setTextAttr(ATTR_NORMAL);
    panel->appendText(".\n");
    panel->appendText("\n");

    panel->appendText("[0-9] Select regiment.  0 is the command regiment.\n");
    panel->appendText("[s] Toggle passing of time\n");
    panel->appendText("[c] Command waypoint (");
    panel->setTextAttr(ATTR_WAYPOINT);
    panel->appendText("*");
    panel->setTextAttr(ATTR_NORMAL);
    panel->appendText(") for regiment\n");
    panel->appendText("[t] Telescope to inspect distant terrain\n");
    panel->appendText("[o] Toggle between map table and telescope\n");
    panel->appendText("[r] Recenter telescope on command regiment\n");
    panel->appendText("[m] Game menu\n");
    panel->appendText("\n");
    panel->appendText("Note: Telescope is real time, but your orders and ");
    panel->appendText("status reports are sent by (real) messengers from ");
    panel->appendText("your command regiment.\n");
    panel->appendText("\n");
    panel->appendText("Shift-Arrow for faster tile selection.\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;

    gfx_updatepulsetime();

    // 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);

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

    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();
	}
    }
    
    if (glbOvermapDisplay)
    {
	glbDisplay->displayOvermap(mapcenter, glbViewMySide);
	// If no live selection (like telescope) then run selected unit.
	if (glbMap && glbSelectedUnit >= 0)
	{
	    if (glbInvertX < 0)
	    {
		OVERMAP::MARKER marker = glbMap->getOvermap()->findMarker(glbViewMySide, glbViewMySide, glbSelectedUnit);
		gfx_printattr(marker.x()+glbDisplay->x(), marker.y()+glbDisplay->y(), ATTR_HILITE);
	    }

	    if (glbWaypointX < 0)
	    {
		if (glbViewMySide)
		{
		    gfx_printattr(glbUnitWaypoint[glbSelectedUnit].x()+glbDisplay->x(), 
				  glbUnitWaypoint[glbSelectedUnit].y()+glbDisplay->y(), ATTR_WAYPOINT);
		}
		else
		{
		    gfx_printattr(glbMap->getGeneral().waypoint(glbSelectedUnit).x()+glbDisplay->x(), 
				  glbMap->getGeneral().waypoint(glbSelectedUnit).y()+glbDisplay->y(), ATTR_WAYPOINT);

		}
	    }
	}
    }
    else
	glbDisplay->display(mapcenter, isblind);
    glbMessagePanel->redraw();

    {
	glbJacob->clear();

	if (glbMap)
	{
	    glbJacob->setTextAttr(ATTR_NORMAL);
	    glbJacob->appendText("\n");
	}
	

	if (glbMap && glbMap->avatar())
	{
	    MOB	*avatar = glbMap->avatar();
	    BUF		buf;

	    if (avatar->isWaiting())
	    {
		glbJacob->setTextAttr(ATTR_YELLOW);
		buf.sprintf("Live\n");
		glbJacob->appendText(buf);
	    }
	    else
	    {
		glbJacob->setTextAttr(ATTR_NORMAL);
		buf.sprintf("** Paused **\n");
		glbJacob->appendText(buf);
	    }
	    if (glbAutoPilot)
	    {
		glbJacob->setTextAttr(ATTR_NORMAL);
		buf.sprintf("Field Commander in Control\n");
		glbJacob->appendText(buf);
	    }

	    if (!glbViewMySide)
	    {
		glbJacob->setTextAttr(ATTR_RED);
		buf.sprintf("Opponents Map View\n");
		glbJacob->appendText(buf);
	    }
	    if (!glbViewMySide || glbAutoPilot)
	    {
		glbJacob->setTextAttr(ATTR_YELLOW);
		buf.sprintf("Strategy: %s\n", glb_strategydefs[glbMap->getEitherGeneral(glbViewMySide).strategy()].name);
		glbJacob->appendText(buf);
	    }

	    // Current time
	    int		time = glbMap->getTime();

	    glbJacob->setTextAttr(ATTR_NORMAL);
	    buf.sprintf("Time: %2d:%02d:%02d\n\n",
			    (time / (60*60)) + 7,
			    (time / 60) % 60,
			    time % 60);
	    glbJacob->appendText(buf);
	    
	    // Marker defintions.
	    glbJacob->setTextAttr(ATTR_NORMAL);
	    buf.sprintf("\nMarkers:\n\n");
	    glbJacob->appendText(buf);

	    PTRLIST<OVERMAP::MARKER> markers;
	    glbMap->getOvermap()->consolidatedMarkers(glbViewMySide, markers);

	    for (int i = 0; i < markers.entries(); i++)
	    {
		auto && marker = markers(i);

		displayMarker(glbJacob, marker, glbMap->getTime(), false);
	    }
	}
    }

    glbJacob->redraw();

    glbHelp->redraw();

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

	stats.sprintf("  Cyan Officers: %5d   Cyan Soldiers: %5d\nOrange Officers: %5d Orange Soldiers: %5d",
		glbMap->getMobCount(MOB_CYAN_OFFICER),
		glbMap->getMobCount(MOB_CYAN_SOLDIER),
		glbMap->getMobCount(MOB_ORANGE_OFFICER),
		glbMap->getMobCount(MOB_ORANGE_SOLDIER));

	glbStatLine->appendText(stats);
    }

    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)
{
    int		key = 0;
    int		startms = TCOD_sys_elapsed_milli();

    glbPopUp->clear();
    glbPopUp->resize(50, SCR_HEIGHT-2);
    glbPopUp->move(SCR_WIDTH/2 - 25, 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;

    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())
    {
	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 :>
	    dx = rand_range(-1, 1);
	    dy = rand_range(-1, 1);
	    return true;
	}
	if (key)
	    return false;
    }

    return false;
}


void
displayMob(MOB *mob)
{
    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)
{
    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
doTelescope()
{
    int		key;
    int		dx, dy;

    if (!glbMap)
	return;

    // Force overmap.
    glbOvermapDisplay = true;

    // If no waypoint, update to the location of the folks.
    // If selected unit, go there.  If no selected unit, use the
    // meditation pos.
    if (glbSelectedUnit >= 0)
    {
	OVERMAP::MARKER marker = glbMap->getOvermap()->findMarker(glbViewMySide, glbViewMySide, glbSelectedUnit);

	glbInvertX = glbDisplay->x() + marker.x();
	glbInvertY = glbDisplay->y() + marker.y();
    }
    else
    {
	if (!glbMap->avatar())
	    return;

	POS		pos = glbMap->avatar()->meditatePos();

	glbInvertX = glbDisplay->x() + pos.roomAtlasX();
	glbInvertY = glbDisplay->y() + pos.roomAtlasY();
    }

    msg_report("Choose where to focuse your telescope.  Enter to select, Esc to cancel.");
    msg_newturn();

    int		x = glbInvertX;
    int		y = glbInvertY;

    bool	docommand = false;

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

	key = gfx_getKey(false);

	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;
	}

	// Any other key stops the look.
	if (key)
	{
	    if (key == '\n')
	    {
		// Actually move waypoint.
		docommand = true;
	    }
	    break;
	}
    }

    if (docommand)
    {
	// Check if we left on a mob.
	// Must re look up as displayWorld may have updated the map.
	msg_report("Telescope alignment adjusted!");
	msg_newturn();
	glbEngine->queue().append(COMMAND(ACTION_GHOSTMOVEABS, 
		    (x - glbDisplay->x()) * 100 + 50,
		    (y - glbDisplay->y()) * 100 + 50));

	// Force a dive in!
	glbOvermapDisplay = false;
    }
    else
    {
	msg_report("Telescope alignment canceled.");
	msg_newturn();
    }

    glbInvertX = -1;
    glbInvertY = -1;
}

void
doWaypointChange()
{
    int		key;
    int		dx, dy;

    if (!glbMap)
	return;

    // Force overmap.
    glbOvermapDisplay = true;
    glbViewMySide = true;

    if (glbSelectedUnit < 0)
    {
	msg_report("First select a regiment to command with [0-9].");
	msg_newturn();
	return;
    }

    {
	BUF		buf;
	buf.sprintf("Update waypoint for %s Regiment.  Enter to send new orders, Esc to cancel.", gram_createplace(glbSelectedUnit).buffer());
	msg_report(buf);
    }

    // If no waypoint, update to the location of the folks.

    if (glbUnitWaypoint[glbSelectedUnit].x() < 0)
    {
	OVERMAP::MARKER marker = glbMap->getOvermap()->findMarker(glbViewMySide, glbViewMySide, glbSelectedUnit);

	glbUnitWaypoint[glbSelectedUnit] = IVEC2(marker.x(), marker.y());
    }

    glbWaypointX = glbDisplay->x() + glbUnitWaypoint[glbSelectedUnit].x();
    glbWaypointY = glbDisplay->y() + glbUnitWaypoint[glbSelectedUnit].y();

    int		x = glbWaypointX;
    int		y = glbWaypointY;

    bool	docommand = false;

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

	key = gfx_getKey(false);

	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);

	    glbWaypointX = x;
	    glbWaypointY = y;
	}

	// Any other key stops the look.
	if (key)
	{
	    if (key == '\n')
	    {
		// Actually move waypoint.
		docommand = true;
	    }
	    break;
	}
    }

    if (docommand)
    {
	// Check if we left on a mob.
	// Must re look up as displayWorld may have updated the map.
	glbUnitWaypoint[glbSelectedUnit] = IVEC2(x - glbDisplay->x(),
						 y - glbDisplay->y());
	msg_report("Orders cut!");
	msg_newturn();
	glbEngine->queue().append(COMMAND(ACTION_ORDER, 
		    ORDER_MARCH,
		    glbSelectedUnit,
		    glbUnitWaypoint[glbSelectedUnit].x(),
		    glbUnitWaypoint[glbSelectedUnit].y()));
	glbSelectedUnit = -1;
    }
    else
    {
	msg_report("No new orders cut.");
	msg_newturn();
    }

    glbWaypointX = -1;
    glbWaypointY = -1;
}

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

    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 (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);

	    if (p.tile() != TILE_INVALID && p.isFOV())
	    {
		p.describeSquare(blind);
	    }

	    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());
	}
    }

    glbInvertX = -1;
    glbInvertY = -1;
}

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->isRanged())
	{
	    glbChooser->appendChoice("[B] Break");
	    if (glbLastItemAction == ACTION_BREAK)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_BREAK;
	}
	if (item->isPotion())
	{
	    glbChooser->appendChoice("[Q] Quaff");
	    if (glbLastItemAction == ACTION_QUAFF)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_QUAFF;
	    glbChooser->appendChoice("[F] Fire");
	    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->isMelee())
	{
	    glbChooser->appendChoice("[W] Wield");
	    if (glbLastItemAction == ACTION_WEAR)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_WEAR;
	}
	else if (item->isRing() || item->isArmour())
	{
	    glbChooser->appendChoice("[W] Wear");
	    if (glbLastItemAction == ACTION_WEAR)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_WEAR;
	}
	if (item->material() != MATERIAL_NONE)
	{
	    glbChooser->appendChoice("[T] Transmute");
	    if (glbLastItemAction == ACTION_TRANSMUTE)
		choice = (int)(actions - origaction);
	    *actions++ = ACTION_TRANSMUTE;
	}
	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 == '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 == 'b' || key == 'B')
	    {
		glbEngine->queue().append(COMMAND(ACTION_BREAK, itemno));
		done = true;
		break;
	    }
	    else if (key == 'w' || key == 'W')
	    {
		glbEngine->queue().append(COMMAND(ACTION_WEAR, itemno));
		done = true;
		break;
	    }
	    else if (key == 't' || key == 'T')
	    {
		glbEngine->queue().append(COMMAND(ACTION_TRANSMUTE, itemno));
		done = true;
		break;
	    }
	    else if (key == 'x' || key == 'X')
	    {
		displayItem(item);
		done = false;
		break;
	    }
	    else if (key == 'f' || key == 'F')
	    {
		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_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;
		}
		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)
{
    glbChooser->clear();

    glbChooser->setTextAttr(ATTR_NORMAL);
    if (!mob)
    {
	glbChooser->appendChoice("The dead own nothing.");
    }
    else
    {
	if (!mob->inventory().entries())
	{
	    glbChooser->appendChoice("nothing");
	}
	else
	{
	    for (int i = 0; i < mob->inventory().entries(); i++)
	    {
		ITEM 		*item = mob->inventory()(i);
		BUF		 name = item->getName();
		if (item->isEquipped())
		{
		    BUF		equip;
		    equip.sprintf("%s (equipped)", name.buffer());
		    name.strcpy(equip);
		}

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

    glbChooserActive = true;
}

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

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

	glbChooser->processKey(key);

	if (key)
	{
	    int		itemno = glbChooser->getChoice();
	    bool	done = false;
	    // User selects this?
	    if (key == '\n' || key == ' ')
	    {
		done = true;

		glbLastItemIdx = glbChooser->getChoice();
		setlast = true;
		if (itemno >= 0 && itemno < avatar->inventory().entries())
		{
		    done = useItem(avatar, avatar->inventory()(itemno), itemno);
		}
		// Finish the inventory menu.
		if (done)
		{
		    break;
		}
		else
		{
		    buildInventory(avatar);
		    setlast = false;
		}
	    }
	    else if (key == '\x1b' || key == 'i')
	    {
		// Esc on options is to go back to play.
		// Play...
		break;
	    }
	    else if (itemno >= 0 && itemno < avatar->inventory().entries())
	    {
		if (key == 'd' || key == 'D')
		{
		    glbEngine->queue().append(COMMAND(ACTION_DROP, itemno));
		    done = true;
		}
		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 == 'b' || key == 'B')
		{
		    glbEngine->queue().append(COMMAND(ACTION_BREAK, itemno));
		    done = true;
		}
		else if (key == 'w' || key == 'W')
		{
		    glbEngine->queue().append(COMMAND(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);
			glbChooser->setChoice(itemno);
			done = false;
		    }
		}
		else if (key == 'f' || key == 'F')
		{
		    glbLastItemIdx = glbChooser->getChoice();
		    setlast = true;
		    glbChooserActive = false;
		    doThrow(itemno);
		    done = true;
		    break;
		}
		else if (key == 't' || key == 'T')
		{
		    glbLastItemIdx = glbChooser->getChoice();
		    glbEngine->queue().append(COMMAND(ACTION_TRANSMUTE, itemno));
		    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?  ");
	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 = (ITEM_NAMES) 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
}

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 if (option == OPTION_TOGGLEBULLETS)
	{
	    if (glbConfig->myBulletsDisplay)
		glbChooser->appendChoice("Turn Off Bullet Display");
	    else
		glbChooser->appendChoice("Turn On Bullet Display");
	}
	else if (option == OPTION_AUTOPILOT)
	{
	    if (glbAutoPilot)
		glbChooser->appendChoice("Take back Control");
	    else
		glbChooser->appendChoice("Turn Control over to Field");
	}
	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
optionsMenu()
{
    int			key;
    bool		done = false;

    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_INSTRUCTIONS)
		{
		    // Instructions.
		    popupText(text_lookup("game", "help"));
		}
		if (glbChooser->getChoice() == OPTION_WELCOME)
		{
		    // Instructions.
		    popupText(text_lookup("welcome", "You1"));
		}
		if (glbChooser->getChoice() == OPTION_ABOUT)
		{
		    // Instructions.
		    popupText(text_lookup("game", "about"));
		}
		else if (glbChooser->getChoice() == OPTION_PLAY)
		{
		    // Play...
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_TOGGLEBULLETS)
		{
		    // Play...
		    glbConfig->myBulletsDisplay = !glbConfig->myBulletsDisplay;
		    break;
		}
		else if (glbChooser->getChoice() == OPTION_AUTOPILOT)
		{
		    // Play...
		    glbAutoPilot = !glbAutoPilot;
		    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_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();

    text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
    text_registervariable("PRIZE", GAMEDEF::itemdef(ITEM_MACGUFFIN)->name);
    if (reload)
    {
	welcomeText("Back");
    }
    else
    {
	welcomeText("You1");
    }

    // Redrawing the world updates our glbMap so everyone will see it.
    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;

    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 0
    glbConfig->save("../1812.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();
    }

    BUF		path;
    path.sprintf("../save/%s.txt", glbWorldName);
    GAMEDEF::save(path.buffer());
#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((ATTR_NAMES) 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));
	}
    }
}

#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("../1812.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("terminal.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, "1812", true);
    else
	TCODConsole::initRoot(SCR_WIDTH, SCR_HEIGHT, "1812", 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

    // SDL_InitSubSystem(SDL_INIT_JOYSTICK);

    setMusicVolume(glbConfig->musicVolume());

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

    text_init();

    gfx_init();
    MAP::init();

    GAMEDEF::reset(true);

    thesaurus_init();

    verifyKeys();

    glbDisplay = new DISPLAY(0, 0, 100, 100, 0);

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

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

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

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

    glbStatLine = new PANEL(65, 2);
    glbStatLine->move(SCR_WIDTH-68, SCR_HEIGHT-12);
    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;
    }

    // Get first reports.
    glbEngine->queue().append(COMMAND(ACTION_WAIT));
    glbEngine->queue().append(COMMAND(ACTION_GHOSTRECENTER));

    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();

	    gfx_clearKeyBuf();
	    clearkeybuf = true;

	    text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
	    text_registervariable("PRIZE", GAMEDEF::itemdef(ITEM_MACGUFFIN)->name);
	    popupText(text_lookup("game", "lose"));

	    stopMusic();

	    // Don't prep a reload.
	    done = reloadLevel(false);
	    glbHasDied = false;

	    glbDisplay->fadeToBlack(false);
	}
	else if (glbMap && glbMap->avatar() &&
		glbMap->avatar()->hasWon())
	{
	    // Traditional win.
	    gfx_clearKeyBuf();
	    clearkeybuf = true;

	    text_registervariable("FOE", GAMEDEF::mobdef(MOB::getBossName())->name);
	    text_registervariable("PRIZE", GAMEDEF::itemdef(ITEM_MACGUFFIN)->name);
	    popupText(text_lookup("game", "victory"));
	    glbHasDied = false;

	    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!
		// First see if the main command is dead.
		if (!glbMap->findCommander(1, 0))
		{
		    gfx_clearKeyBuf();
		    clearkeybuf = true;
		    stopWaiting();
		    popupText(text_lookup("game", "lose"), POPUP_DELAY);
		    glbHasDied = true;
		}
		else
		{
		    // Check all foes.
		    int unit;
		    for (unit = 0; unit < 10; unit++)
		    {
			if (glbMap->findCommander(0,unit))
			{
			    break;
			}
		    }
		    if (unit == 10)
		    {
			gfx_clearKeyBuf();
			clearkeybuf = true;
			stopWaiting();
			popupText(text_lookup("game", "victory"), POPUP_DELAY);
			glbHasDied = true;
		    }
		}
	    }
	}

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

	key = gfx_getKey(false);

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

	KEY_NAMES cookkey = cookKey(key);

	// Unbindable:
	switch (key)
	{
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
	    {
		int	select = key - '0';
		if (glbSelectedUnit == select)
		    glbSelectedUnit = -1;
		else
		    glbSelectedUnit = select;
		break;
	    }
#if 0
	    case 'Q':
		done = true;
		break;

	    case 'R':
		done = reloadLevel(false);
		break;
	    case 'I':
		inventoryMenu();
		break;

	    case 'W':
	    {
		welcomeText("You1");
		break;
	    }

	    case 'P':
		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);
		break;
#endif
	    case 'm':
		stopWaiting();
		done = optionsMenu();
		break;
	    case '\x1b':
		// If meditating, abort.
#if 0
		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;
		}
#endif
		// Break out of any selection.
		glbSelectedUnit = -1;

		break;

	    case GFX_KEYF1:
		stopWaiting();
		popupText(text_lookup("game", "help"));
		break;
	}

	switch (cookkey)
	{
	    case KEY_OVERMAPTOGGLE:
		glbOvermapDisplay = !glbOvermapDisplay;
		break;
	    case KEY_WAYPOINT:
		doWaypointChange();
		break;
	    case KEY_TELESCOPE:
		doTelescope();
		break;
	    case KEY_RECENTER:
		glbEngine->queue().append(COMMAND(ACTION_GHOSTRECENTER));
		break;
	    case KEY_SIMULATETOGGLE:
		if (glbMap && glbMap->avatar() && 
		    glbMap->avatar()->alive())
		{
		    if (glbMap->avatar()->isWaiting())
		    {
			glbEngine->queue().append(COMMAND(ACTION_STOPWAITING));
		    }
		    else
		    {
			glbEngine->queue().append(COMMAND(ACTION_WAITUNTIL));
		    }
		}
		break;
	    case KEY_SWITCHSIDE:
		glbViewMySide = !glbViewMySide;
		break;
	    case NUM_KEYS:
		break;
	}
    } while (!done && !TCODConsole::isWindowClosed());

    shutdownEverything(true);

    return 0;
}
