/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	Jacob's Matrix Development
 *
 * NAME:        engine.cpp ( Jacob's Matrix, C++ )
 *
 * COMMENTS:
 *	Our game engine.  Grabs commands from its command
 *	queue and processes them as fast as possible.  Provides
 *	a mutexed method to get a recent map from other threads.
 *	Note that this will run on its own thread!
 */

#include <libtcod.hpp>
#undef CLAMP

#include "mygba.h"

#ifdef USE_MIMALLOC
#include <mimalloc.h>
#endif

#include "engine.h"

#include "msg.h"
#include "rand.h"
#include "map.h"
#include "mob.h"
#include "item.h"
#include "text.h"
#include "chooser.h"
#include "gfxengine.h"
#include "config.h"
#include <time.h>

// #define DO_TIMING

// Avoid asking to quickly for things like AI and what not.
#define QUESTION_TIMEOUT	30

// Prototypes stolen from Main
extern int popupText(const char *buf, int delayms = 0);
extern int popupText(BUF buf, int delayms = 0);
extern BUF popupQuestion(const char *buf);
extern BUF popupQuestion(BUF buf);
extern void redrawWorld();

extern void doLookAround(MAP *map, bool onlynew);

extern CHOOSER		*glbChooser;
extern bool		 glbChooserActive;

ENGINE::Response
engine_askQuestion(const ENGINE::Question &query)
{
    ENGINE::Response	response;

    switch (query.type)
    {
	case ENGINE::QUERY_TEXT:
	{
	    BUF		text = query.query;
	    text.stripWS();
	    response.type = query.type;
	    response.answer = popupQuestion(text);
	    break;
	}
	case ENGINE::QUERY_CHOICE:
	{
	    glbChooser->clear();

	    glbChooser->setTextAttr(ATTR_NORMAL);
	    BUF		wrapped;

	    wrapped = query.query.wordwrap(WORDWRAP_LEN);
	    wrapped.stripWS();
	    wrapped.append('\n');
	    wrapped.append('\n');

	    glbChooser->setPrequel(wrapped);
	    glbChooser->setPrequelAttr(ATTR_WHITE);
	    for (int i = 0; i < query.choices.entries(); i++)
		glbChooser->appendChoice(query.choices(i));
	    glbChooser->setChoice(query.defaultchoice);

	    response.choice = -1;

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

		glbChooser->processKey(key);

		if (key)
		{
		    // User selects this?
		    if (key == '\n' || key == ' ')
		    {
			response.choice = glbChooser->getChoice();
			break;
		    }
		    else if (key == '\x1b')
		    {
			// Choose nothing.
			break;
		    }
		}
	    }
	    glbChooserActive = false;
	    if (response.choice >= 0)
		response.type = ENGINE::QUERY_CHOICE;

	    break;
	}
	case ENGINE::QUERY_INVALID:
	    // Leave default
	    break;
    }

    return response;
}

static void *
threadstarter(void *vdata)
{
    ENGINE	*engine = (ENGINE *) vdata;

    engine->mainLoop();

    return 0;
}

ENGINE::ENGINE(DISPLAY *display)
{
    myDisplay = display;
    enableQuestions(true);
    myQuestionTimeOut = QUESTION_TIMEOUT;

    myThread = THREAD::alloc();
    myThread->start(threadstarter, this);

    myVerse = std::make_shared<MULTIVERSE>();
}

ENGINE::~ENGINE()
{
}

MULTIVERSE_PTR
ENGINE::copyMap()
{
    return myOldVerse;
}

void
ENGINE::updateMap(bool forcepost)
{
    bool		postedmap = true;

    {
	AUTOLOCK	a(myMapLock);

	if (myOldVerse)
	{
	    if (myOldVerse.use_count() == 1 && !forcepost)
	    {
		// No one has copied it out, no point updating to the
		// most recent until they have!
		postedmap = false;
	    }
	    else
	    {
		// Post ourselves!
	        myOldVerse = myVerse;
	    }
	}
	else
	{
	    myOldVerse = myVerse;
	}
    }
    // If we posted ourselves as oldmap, we have to copy into a new
    // version.  This is safe to do outside the lock as we've ideally
    // got read only.
    if (myOldVerse && postedmap)
    {
#ifdef DO_TIMING
    int 		movestart = TCOD_sys_elapsed_milli();
#endif
	myVerse = std::make_shared<MULTIVERSE>(*myOldVerse);
	myVerse->uniqueMap();
#ifdef DO_TIMING
    int 		moveend = TCOD_sys_elapsed_milli();
    cerr << "Map update " << moveend - movestart << endl;
#endif
    }
}

#define VERIFY_ALIVE() \
    if (avatar && !avatar->alive()) \
    {			\
	msg_report("Dead people can't move.  ");	\
	break;			\
    }

void redrawWorld();

void
ENGINE::awaitQueueEmpty()
{
    int		value = 0;
    queue().append(COMMAND(ACTION_MARK_QUEUE_EMPTY));

    while (!myEngineQueueEmpty.remove(value))
    {
	redrawWorld();
    }
    myEngineQueueEmpty.clear();
    return;
}

bool
ENGINE::awaitRebuild(bool redraw)
{
    int		value = 0;

    while (!myRebuildQueue.remove(value))
    {
	answerAnyQuestions();
	if (redraw)
	    redrawWorld();
    }

    myRebuildQueue.clear();

    return value ? true : false;
}

void
ENGINE::awaitSave(bool redraw)
{
    int		value;

    while (!mySaveQueue.remove(value))
    {
	answerAnyQuestions();
	if (redraw)
	    redrawWorld();
    }

    mySaveQueue.clear();
}

void
ENGINE::awaitWorldShift(bool redraw)
{
    int		value;

    while (!myWorldShiftQueue.remove(value))
    {
	answerAnyQuestions();
	if (redraw)
	    redrawWorld();
    }

    myWorldShiftQueue.clear();
}

BUF
ENGINE::getNextPopup()
{
    BUF		result;

    myPopupQueue.remove(result);

    return result;
}

void
ENGINE::popupText(const char *text)
{
    BUF		buf;
    
    buf.strcpy(text);

    myPopupQueue.append(buf);
}

BUF
ENGINE::askQuestionText(BUF text, bool isclient)
{
    Question		query;
    Response		response;
    query.query = text;
    query.type = QUERY_TEXT;
    response = askQuestion(query, isclient);

    if (response.type == QUERY_TEXT)
    {
	return response.answer;
    }
    BUF		empty;
    return empty;
}

bool
ENGINE::askQuestionNum(BUF text, int &result, bool isclient)
{
    Question		query;
    Response		response;
    query.query = text;
    query.type = QUERY_TEXT;
    response = askQuestion(query, isclient);

    if (response.type == QUERY_TEXT)
    {
	if (response.answer.isstring())
	{
	    if (response.answer.buffer()[0] == '+' ||
	        response.answer.buffer()[0] == '-' ||
	        MYisdigit(response.answer.buffer()[0]) )
	    {
		result = atoi(response.answer.buffer());
		return true;
	    }
	}
    }
    return false;
}

BUF
ENGINE::askQuestionTextCanned(const char *dict, const char *key, bool isclient)
{
    return askQuestionText(text_lookup(dict, key), isclient);
}

int
ENGINE::askQuestionList(BUF text, const PTRLIST<BUF> &choices, int defchoice, bool isclient)
{
    Question	query;
    query.type = QUERY_CHOICE;
    query.query = text;
    query.choices = choices;
    query.defaultchoice = defchoice;

    Response	response;
    if (isclient)
	response = engine_askQuestion(query);
    else
	response = askQuestion(query, isclient);

    if (response.type != QUERY_CHOICE)
	return -1;
    return response.choice;
}

int
ENGINE::askQuestionList(BUF text, const char **choices, int defchoice, bool isclient)
{
    PTRLIST<BUF>	list;

    for (int i = 0; choices[i]; i++)
    {
	BUF	tmp;
	tmp.reference(choices[i]);
	list.append(tmp);
    }

    return askQuestionList(text, list, defchoice, isclient);
}

ENGINE::Response
ENGINE::askQuestion(Question query, bool isclient)
{
    if (isclient)
	return engine_askQuestion(query);

    if (!questionsEnabled())
    {
	Response	dummy;
	return dummy;
    }

    myQuestionTimeOut = QUESTION_TIMEOUT;
    myQuestionQueue.append(query);

    return myResponseQueue.waitAndRemove();
}

bool
ENGINE::getQuestion(Question &query)
{
    return myQuestionQueue.remove(query);
}

void
ENGINE::answerQuestion(Response response)
{
    myResponseQueue.append(response);
}

void
ENGINE::answerAnyQuestions()
{
    Question	query;
    Response	response;

    while (getQuestion(query))
	answerQuestion(response);
}

void
ENGINE::mainLoop()
{
    // rand_setseed((long) time(0));
    rand_truerandomseed();
    for (int i = 0; i < 10000; i++)
	rand_choice(2);

    COMMAND		cmd;
    MOB			*avatar;
    bool		timeused = false;
    bool		doheartbeat = true;

    while (1)
    {
	avatar = 0;
	MAP		*map = myVerse->map().get();
	if (map) avatar = map->avatar();

	bool		possiblystop = false;

	// If there is a command waiting, we should stop waiting on
	// the avatar!
	if (avatar && !queue().isEmpty())
	{
	    possiblystop = avatar->isWaiting();
	    avatar->stopWaiting(0);
	}

	timeused = false;
	if (doheartbeat && avatar && avatar->aiForcedAction())
	{
	    // Make sure we have lots of new turns here!
	    if (avatar && avatar->isWaiting())
	    {
		msg_newturn();
		msg_preempt_suppress();
	    }
	    cmd = COMMAND(ACTION_NONE);
	    timeused = true;
	}
	else
	{
	    // Allow the other thread a chance to redraw.
	    // Possible we might want a delay here?

	    // Restore the wait state that we turned off for the force
	    // action pass...
	    if (possiblystop)
		avatar->startWaiting();

	    updateMap(queue().isEmpty());
	    avatar = 0;
	    map = myVerse->map().get();
	    if (map)
		avatar = map->avatar();

	    if (possiblystop && avatar)
		avatar->stopWaiting(0);

	    if (doheartbeat)
	    {
		if (glbConfig->myVerbose)
		{
		    doLookAround(map, true);
		}

		msg_newturn();
	    }

	    cmd = queue().waitAndRemove();

	    doheartbeat = false;
	}

	if (avatar && possiblystop && 
	    (cmd.action() == ACTION_MARK_QUEUE_EMPTY ||
	     cmd.action() == ACTION_GHOSTMOVEABS ||
	     cmd.action() == ACTION_NONE))
	{
	    // Resume.
	    avatar->startWaiting();
	    timeused = true;
	}

	switch (cmd.action())
	{
	    case ACTION_MARK_QUEUE_EMPTY:
		// Regardless if the queue is now empty, we mark it.
		myEngineQueueEmpty.append(1);
		break;
	    case ACTION_WAIT:
		// The dead are allowed to wait!
		if (avatar)
                {
                    if (avatar->alive())
                        avatar->formatAndReport("%S <wait>.");
		    avatar->stopRunning();
                }
		timeused = true;
		break;

	    case ACTION_RESTART:
	    {
		enableQuestions(false);
		int		loaded = 1;

		myVerse->releaseMaps();
		myVerse->load();
		if (!myVerse->map())
		{
		    loaded = 0;
		    ITEM::initSystem();
		    MOB::initSystem();

		    myVerse->setMap(std::make_shared<MAP>(cmd.dx(),
					    myDisplay));
		    myVerse->map()->setVerse(myVerse.get());
		    map = myVerse->map().get();
		    avatar = map->avatar();
		}

#if 0
		// Force lots of loads
		while (1)
		{
		    MAP_PTR	temp;
		    temp = std::make_shared<MAP>(cmd.dx(), myDisplay);
		}
#endif

		{
		    MAP_PTR wmap = myVerse->map();
		    wmap->setDisplay(myDisplay);
		    wmap->rebuildFOV();
		    wmap->cacheItemPos();
		}

		// Flag we've rebuilt.
		myRebuildQueue.append(loaded);
		enableQuestions(true);
		break;
	    }

	    case ACTION_SAVE:
	    {
		// Only save good games
		if (map && avatar && avatar->alive())
		{
		    myVerse->save();
		}
		mySaveQueue.append(0);
		break;
	    }

	    case ACTION_SHUTDOWN:
	    {
		// Only save good games
		mySaveQueue.append(0);
		break;
	    }

	    case ACTION_REBOOTAVATAR:
	    {
		if (avatar)
		    avatar->gainHP(avatar->getMaxHP());
		break;
	    }

	    case ACTION_CLIMB:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionClimb();
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_OPEN:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionOpen(cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();

		break;
	    }

	    case ACTION_KICK:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionKick(cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();

		break;
	    }

	    case ACTION_CLOSE:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionClose(cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();

		break;
	    }

	    case ACTION_BUMP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionBump(cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();

		break;
	    }
	
	    case ACTION_DROP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionDrop(avatar->getItemFromNo(cmd.dx()), cmd.dz());
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_WEARTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionWearTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_EATTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionEatTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_DROPTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionDropTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_DROPALL:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionDropAll();
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_BAGSHAKE:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionBagShake();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_BAGSWAPTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionBagSwapTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_BREAKTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionBreakTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_BREAK:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionBreak(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_WEAR:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionWear(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_WORLDSHIFT:
	    {
		VERIFY_ALIVE()

		MULTIVERSE_PTR	newverse;

		bool		doublerun = false;

		// if it is now fast or quick phase, run again so
		// we get one about one step per wait.
		PHASE_NAMES phase = myVerse->phase();
		if (phase == PHASE_FAST || phase == PHASE_QUICK)
		{
		    doublerun = true;
		}
		
		// It is important we wipe myVerse after this...
		newverse = runAllWorlds(myVerse, 0, doublerun ? 2 : 1);
		myVerse = newverse;
		newverse.reset();

		if (doublerun)
		{
		    newverse = runAllWorlds(myVerse, 1, 2);
		    myVerse = newverse;
		    newverse.reset();
		}

		timeused = false;
		break;
	    }

	    case ACTION_WORLDFOCUS:
	    {
		VERIFY_ALIVE()

		myVerse->focusWorld(cmd.dx(), cmd.dy(), cmd.dz());
		// Reload.
		map = myVerse->map().get();
		avatar = map->avatar();

		timeused = false;
		break;
	    }

	    case ACTION_WIELD:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionWield(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_READYRANGED:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionReadyRanged(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_EAT:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionEat(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_EATGROUND:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    ITEM *item = map->findItem(cmd.dx());
		    timeused = avatar->actionEat(item);
		}
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_WAITUNTIL:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    timeused = true;
		    avatar->formatAndReport("%S <rest> and <meditate>...");
		    avatar->startWaiting();
		}
		break;
	    }

	    case ACTION_STOPWAITING:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    avatar->stopWaiting(0);
		break;
	    }

	    case ACTION_MEDITATE:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionMeditate();
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_AUTOMATE:
	    {
		VERIFY_ALIVE()
		// Tempting to use the auto for your current form
		// but that usually is less useful, so we assume
		// you over come your instincts...
		if (avatar)
		    timeused = avatar->aiDoAIType(AI_ADVENTURER);
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_PICKUP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionPickup();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_PICKUPSPECIFIC:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    ITEM *item = map->findItem(cmd.dx());
		    timeused = avatar->actionPickup(item);
		}
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_SEARCH:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionSearch();
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_CREATEITEM:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    ITEM	*item = ITEM::create(myVerse->map().get(),
						(ITEM_NAMES) cmd.dx());
		    item->setMaterial((MATERIAL_NAMES) cmd.dy());
		    item->setStackCount(10);
		    avatar->addItem(item);
		}
		break;
	    }
	
	    case ACTION_QUAFFTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionQuaffTop();
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_QUAFF:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionQuaff(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_READ:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionRead(avatar->getItemFromNo(cmd.dx()));
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_TRANSMUTE:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    if (cmd.dy() >= 0)
		    {
			timeused = avatar->actionTransmute(avatar->getItemFromNo(cmd.dx()), avatar->getItemFromNo(cmd.dy()));
		    }
		    else if (cmd.dx() >= 0)
			timeused = avatar->actionTransmute(avatar->getItemFromNo(cmd.dx()));
		    else
			timeused = avatar->actionTransmute();
		}
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_THROW:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionThrow(avatar->getItemFromNo(cmd.dz()),
					cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_APPLY:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionApplyTool(avatar->getItemFromNo(cmd.dz()),
					cmd.dx(), cmd.dy(), avatar->getItemFromNo(cmd.dw()));
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_THROWTOP:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionThrowTop(cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_CAST:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionCast((SPELL_NAMES)cmd.dz(),
					cmd.dx(), cmd.dy());
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_FORGET:
	    {
		VERIFY_ALIVE()
		if (avatar)
		    timeused = avatar->actionForget((SPELL_NAMES)cmd.dx());
		if (!timeused)
		    msg_newturn();
		break;
	    }
	
	    case ACTION_ROTATE:
	    {
		if (avatar)
		    timeused = avatar->actionRotate(cmd.dx());
		break;
	    }

	    case ACTION_FIRE:
	    {
		VERIFY_ALIVE()
		if (avatar)
		{
		    timeused = avatar->actionFire(cmd.dx(), cmd.dy());
		}
		if (!timeused)
		    msg_newturn();
		break;
	    }

	    case ACTION_SUICIDE:
	    {
		if (avatar)
		{
		    if (avatar->alive())
		    {
			// We want the flame to die.
			timeused = avatar->actionSuicide();
		    }
		}
		break;
	    }

	    case ACTION_GHOSTMOVE:
	    {
		if (avatar)
		{
		    POS		newpos = avatar->meditatePos().delta(cmd.dx(), cmd.dy());
		    if (newpos.valid())
			avatar->meditateMove(newpos, true);
		    // Er, no!
		    // map->rebuildFOV();

		    timeused = false;
		}
		break;
	    }
	    case ACTION_GHOSTMOVEABS:
	    {
		if (avatar)
		{
		}
		break;
	    }
	    case ACTION_GHOSTRECENTER:
	    {
		if (avatar && map)
		{
		}
		break;
	    }
	}

	map = myVerse->map().get();
	if (map) avatar = map->avatar();

	if (map && timeused &&
	    // This stops the game on death, so you lose the death
	    // dance...
	    map->avatar() && map->avatar()->alive())
	{
	    avatar = map->avatar();

	    myQuestionTimeOut--;
	    if (myQuestionTimeOut < 0)
		myQuestionTimeOut = 0;

	    if (cmd.action() != ACTION_SEARCH &&
		cmd.action() != ACTION_NONE)
	    {
		// Depower searching
		if (avatar)
		    avatar->setSearchPower(0);
	    }
	    if (cmd.action() != ACTION_AUTOMATE &&
		cmd.action() != ACTION_NONE)
	    {
		// Reset automation so we don't remember
		// stale targets.
		if (avatar)
		    avatar->resetAIState();
	    }

	    // We need to build the FOV for the monsters as they
	    // rely on the updated FOV to track, etc.
	    // Rebuild the FOV map
	    // Don't do this if no avatar, or the avatar is dead,
	    // as we want the old fov.
	    // We do want to do this if we are wiating as that is how
	    // we can find out about monsters and stop waiting!
	    if (avatar && avatar->alive())
		map->rebuildFOV();

	    // Update the world.
	    map->doMoveNPC();
	    avatar = map->avatar();

	    map->doHeartbeat();
	    avatar = map->avatar();

	    // Rebuild the FOV map
	    if (avatar && avatar->alive())
	    {
		// Track the body!
		if (!avatar->isMeditateDecoupled())
		{
		    avatar->meditateMove(avatar->pos(), false);
		}
	
		map->rebuildFOV();
	    }

	    doheartbeat = true;
	}

	// Allow the other thread a chance to redraw.
	// Possible we might want a delay here?
	updateMap(false);
    }
}

extern volatile int glbDefocusProgress;

class WORKITEM
{
public:
    MAP_PTR		myMap;
    int			myNumRuns;
};

class ENGINEPOOL
{
public:
    ENGINEPOOL();

    void		mainLoop()
    {
	// Ensure this thread has a good seed!
	rand_truerandomseed();
	for (int i = 0; i < 10000; i++)
	    rand_choice(2);

	while (1)
	{
	    auto workitem = myWorkItems.waitAndRemove();

	    processWorkItem(workitem);
	}
    }

    void		postWork(MAP_PTR base, int numruns)
    { myWorkItems.append(WORKITEM{base, numruns}); }
    void		postMap(MAP_PTR map)
    { myResults.append(map); }

    MAP_PTR		getMap()
    { return myResults.waitAndRemove(); }

    bool		isPoolEmpty() { return myResults.isEmpty() && myWorkItems.isEmpty(); }

protected:
    void		processWorkItem(const WORKITEM &item);

    QUEUE<WORKITEM>	myWorkItems;
    QUEUE<MAP_PTR>	myResults;
};

static void *
pool_threadstarter(void *vdata)
{
    ENGINEPOOL	*engine = (ENGINEPOOL *) vdata;

    engine->mainLoop();

    return 0;
}

ENGINEPOOL::ENGINEPOOL()
{
    int		poolsize = THREAD::numProcessors();

    for (int i = 0; i < poolsize; i++)
    {
	THREAD	*thread = THREAD::alloc();
	thread->start(pool_threadstarter, this);
    }
}

void
ENGINEPOOL::processWorkItem(const WORKITEM &workitem)
{
    MAP_PTR	oldmap = workitem.myMap;
    int		runpermap = workitem.myNumRuns;

    if (!oldmap->avatar())
    {
	// No avatar, bad map so just discard.
	// We do have to post empty maps as the caller expects
	// this number!
	MAP_PTR	empty;
	for (int i = 0; i < runpermap; i++)
	    postMap(empty);
	return;
    }

    if (!oldmap->avatar()->alive())
    {
	// Dead avatar, just add directly with no copies.
	for (int i = 0; i < runpermap; i++)
	    postMap(oldmap);

	return;
    }

    // We have a live avatar...
    MAP_PTR		basemap = std::make_shared<MAP>(*oldmap);

    // We store with no FOV for less memory usage, so build it here.
    basemap->rebuildFOV();

    for (int i = 0; i < runpermap; i++)
    {
	// Duplicate so we can write to it....
	MAP_PTR map;
	if (i < runpermap-1)
	{
	    map = std::make_shared<MAP>(*basemap);
	}
	else
	{
	    map = basemap;
	}

	// Run on this map...
	// This is a bit annoying as one defocus isn't one move
	// depending on your phase!  But if we want consistent
	// time, and as we want to have a global clock so do,
	// we must...
	MOB *avatar = map->avatar();
	if (avatar && avatar->alive())
	{
	    if (avatar->aiForcedAction())
	    {
		avatar = map->avatar();
		if (avatar && avatar->alive())
		{
		    if (rand_chance(5))
			avatar->aiDoAIType(AI_RANDOMBUMP);
		    else
			avatar->aiDoAIType(AI_ADVENTURER);
		}
	    }
	}
	// Update the world.
	map->doMoveNPC();
	map->doHeartbeat();

	// No point saving the FOV.
	map->clearFOV();

	postMap(map);
    }
}

class ENGINEPOOL_OWNER
{
public:
    ENGINEPOOL_OWNER()
    {
        myPool = new ENGINEPOOL();
    }
    ~ENGINEPOOL_OWNER()
    {
        // Leaky pool on purpose!
    }

    ENGINEPOOL *myPool;
};

MULTIVERSE_PTR
ENGINE::runAllWorlds(MULTIVERSE_PTR oldverse, int pass, int numpass)
{
    // So, it is undefined behaviour to destroy a condition variable
    // to which a thread is pointing.  But this is a bit of a mess
    // if you statically construct an enginepool to make a singleton,
    // as it contains queues and we try to end them during our static
    // destruction while our helper threads are idling.
    // So instead we just leak the pool like a sieve.
    // This shows up __pthread_cond_destroy deadlock on glibc 2.25
    static ENGINEPOOL_OWNER	poolowner;
    ENGINEPOOL *pool = poolowner.myPool;

    MULTIVERSE_PTR newverse = std::make_shared<MULTIVERSE>();
    newverse->setMap(oldverse->map());
    // Warning: Also updates old verse?  But we will be wiping it...
    newverse->map()->setVerse(newverse.get());

    MAPLIST	alloldmaps;

    glbDefocusProgress = (int)(100 * (pass / (float)numpass));

    oldverse->allMaps(alloldmaps);
    
    int		runpermap;
    int		maxmapperpos = glbConfig->myMaxMapsPerPos;

    runpermap = MAX(glbConfig->myMinBranchFactor, 
		    (int)(maxmapperpos/alloldmaps.entries()));

    // Disable events
    msg_disable();
    if (newverse->map())
	newverse->map()->getDisplay()->disableEvents();

    int		totalmaps = alloldmaps.entries() * runpermap;
    int		curmap = 0;

    for (auto && oldmap : alloldmaps)
    {
	if (!oldmap->avatar())
	{
	    // No avatar, bad map so just discard.
	    curmap += runpermap;
	    continue;
	}

	// Post the map.
	pool->postWork(oldmap, runpermap);
    }

    // Now retrieve all the done maps...
    while (curmap < totalmaps)
    {
	MAP_PTR		map = pool->getMap();
	curmap++;
	glbDefocusProgress = (int) (100 * 
			    (pass + (curmap / (float)totalmaps)) / numpass);

	MOB *avatar = map->avatar();
	if (avatar)
	    newverse->appendMap(avatar->pos(), map,
				    maxmapperpos);

    }

    // Queue should be empty now!
    J_ASSERT(pool->isPoolEmpty());

    // Likely culled during append...:
    newverse->cullWorlds(maxmapperpos);

#ifdef USE_MIMALLOC
    mi_collect(true);
#endif

    if (newverse->map())
	newverse->map()->getDisplay()->enableEvents();
    msg_enable();

    glbDefocusProgress = -1;

    return newverse;
}

