/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	7DRL Development
 *
 * NAME:        mob.cpp ( Live Once Library, C++ )
 *
 * COMMENTS:
 */

#include "mob.h"

#include "map.h"
#include "msg.h"
#include "text.h"
#include "item.h"
#include "event.h"
#include "display.h"
#include "engine.h"
#include "thesaurus.h"
#include "level.h"
#include "config.h"

#include <stdio.h>

#include <iostream>
using namespace std;

static int glbTransmuteTaint = 0;
static int	glbLetterTimeStamp = 0;
static int	glbLastSeen[26];

// Disable wall sliding as sloppy motion only makes sense in tiled
// environment.
#define ENABLE_WALLSLIDE (glbConfig->myWallSlide)

//
// Fireball operators
//
class ATTACK_OP
{
public:
    ATTACK_OP(MOB *src, ITEM *item, ITEM *arrow, ATTACK_NAMES attack)
    {
	mySrc = src;
	myItem = item;
	myArrow = arrow;
	myAttack = attack;
    }

    bool operator()(POS p) const
    {
	bool hit = false;
        if (GAMEDEF::attackdef(myAttack)->element == ELEMENT_FIRE)
        {
            p.burnSquare();
        }
	if (p.mob() && p.mob() != mySrc)
	{
	    if (mySrc->evaluateWillHit(myAttack, myItem, myArrow, p.mob()))
	    {
		mySrc->formatAndReport("%S %v %O.", GAMEDEF::attackdef(myAttack)->verb, p.mob());
		if (!p.mob()->applyDamage(mySrc, 
			    mySrc->evaluateAttackDamage(myAttack, myItem, myArrow),
			    GAMEDEF::attackdef(myAttack)->element,
			    ATTACKSTYLE_RANGE))
		{
		    // Mob still alive.
		    if (p.mob() && GAMEDEF::attackdef(myAttack)->effect != EFFECT_NONE)
		    {
			p.mob()->applyEffect(mySrc,
			    (EFFECT_NAMES) GAMEDEF::attackdef(myAttack)->effect,
			    ATTACKSTYLE_RANGE);
		    }
		}
		hit = true;
	    }
	    else
	    {
		mySrc->formatAndReport("%S <miss> %O.", p.mob());
	    }
	}
	return hit;
    }

private:
    ATTACK_NAMES	 myAttack;
    MOB			*mySrc;
    ITEM		*myItem;
    ITEM		*myArrow;
};

class POTION_OP
{
public:
    POTION_OP(MOB *src, ITEM_NAMES potion, bool *interest) 
    { mySrc = src; myPotion = potion; myInterest = interest; }

    bool operator()(POS p) const
    {
	MOB		*mob = p.mob();

	bool hit = false;
	if (mob)
	{
	    hit = true;	// always hit with potions?
            if (ITEM::asPotion(myPotion) == POTION_SLEEP)
            {
                if (mob->putToSleep(true,/*dc*/ 20, 15))
                    if (myInterest) *myInterest = true;

            }
	    if (ITEM::potionEffect(myPotion) != EFFECT_NONE)
	    {
		if (myInterest)
		    *myInterest = true;
	    }
	    mob->applyEffect(mySrc, ITEM::potionEffect(myPotion),
		ATTACKSTYLE_RANGE);
	}
	if (p.soakSquare(ITEM::asPotion(myPotion)))
	{
	    if (myInterest) *myInterest = true;
	}
	return hit;
    }

private:
    bool		*myInterest;
    ITEM_NAMES	 	myPotion;
    MOB			*mySrc;
};

class EFFECT_OP
{
public:
    EFFECT_OP(MOB *src, EFFECT_NAMES effect, bool hitsself) 
    { mySrc = src; myEffect = effect; myHitsSelf = hitsself; }

    bool operator()(POS p)
    {
	MOB		*mob = p.mob();

	bool		hit = false;

	if (mob == mySrc && !myHitsSelf)
	    return false;
	if (mob)
	{
	    hit = true;
	    mob->applyEffect(mySrc, 
		myEffect,
		ATTACKSTYLE_RANGE);
	}
	return hit;
    }

private:
    EFFECT_NAMES	myEffect;
    MOB			*mySrc;
    bool		myHitsSelf;
};


//
// MOB Implementation
//

MOB::MOB()
{
    myFleeCount = 0;
    myBoredom = 0;
    myYellHystersis = 0;
    myAIState = 0;
    myHP = 0;
    myMaxHP = 1;
    // Enter the dungeon full!
    myFood = getMaxFood() - 1;
    myIsSwallowed = false;
    myNumDeaths = 0;
    myUID = INVALID_UID;

    mySkipNextTurn = false;
    myDelayMob = false;
    myDelayMobIdx = -1;
    myCollisionTarget = 0;
    mySearchPower = 0;
    myAngryWithAvatar = false;

    WORLD_NAMES	world;
    FOREACH_WORLD(world)
    {
	myWorldStash[world] = nullptr;
    }

    YELL_NAMES	yell;
    FOREACH_YELL(yell)
    {
	myHeardYell[yell] = false;
	myHeardYellSameRoom[yell] = false;
    }
    SPELL_NAMES spell;
    FOREACH_SPELL(spell)
    {
	myKnownSpells[spell] = 0;
    }
    mySawMurder = false;
    mySawMeanMurder = false;
    mySawVictory = false;
    myAvatarHasRanged = false;
    myWaiting = false;
    myCowardice = 0;
    myKills = 0;
    myHasWon = false;
    myHasTriedSuicide = false;
    myRangeTimeout = 0;
    mySpellTimeout = 0;

    myLastDir = 0;
    myLastDirCount = 0;

    mySeed = 0;

    myLevel = 1;
    myExp = 0;
    myExtraLives = 0;

    myDecoupledFromBody = true;
}

MOB::~MOB()
{
    int		i;

    if (myPos.map() && myPos.map()->avatar() == this)
	myPos.map()->setAvatar(0);

    myPos.removeMob(this);

    for (i = 0; i < myInventory.entries(); i++)
	delete myInventory(i);

    WORLD_NAMES	world;
    FOREACH_WORLD(world)
    {
	delete myWorldStash[world];
    }

}

#define MAX_TAINT		200

bool
MOB::isAvatarDef(MOB_NAMES mob)
{
    return defn(mob).isavatarform;
}

void
MOB::initSystem()
{
    glbTransmuteTaint = 0;

    glbLetterTimeStamp = 0;
    for (int i = 0; i < 26; i++)
	glbLastSeen[i] = 0;
}

void
MOB::saveGlobal(ostream &os)
{
    int		val;

    val = glbTransmuteTaint;
    os.write((const char *)&val, sizeof(int));

    val = glbLetterTimeStamp;
    os.write((const char *)&val, sizeof(int));
    for (int i = 0; i < 26; i++)
    {
	val = glbLastSeen[i];
	os.write((const char *)&val, sizeof(int));
    }
}

void
MOB::loadGlobal(istream &is)
{
    int		val;

    is.read((char *) &val, sizeof(int));
    glbTransmuteTaint = val;

    is.read((char *) &val, sizeof(int));
    glbLetterTimeStamp = val;
    for (int i = 0; i < 26; i++)
    {
	is.read((char *) &val, sizeof(int));
	glbLastSeen[i] = val;
    }
}

float
MOB::transmuteTaint()
{
    return ((float)glbTransmuteTaint) / MAX_TAINT;
}

void
MOB::incTransmuteTaint(int amount)
{
    glbTransmuteTaint += amount;
}

static u8
mob_findsymbol(const char *name)
{
    // Find letter before space.
    if (!name || !*name)
	return '!';
    const char *idx = &name[strlen(name)-1];

    // Eat trailing spaces (which there shouldn't be any!)
    while (idx > name)
    {
	if (*idx == ' ')
	    idx--;
	else
	    break;
    }

    while (idx > name)
    {
	if (*idx == ' ')
	    return *(idx+1);
	idx--;
    }
    return *name;
}

static ELEMENT_NAMES
mob_randelementresist()
{
    // Intentionally skip physical as that is too unbalanced both ways.
    ELEMENT_NAMES	elem = (ELEMENT_NAMES) rand_range(ELEMENT_PHYSICAL+1, NUM_ELEMENTS-1);

    // Light needs to be pretty rare as it is very odd.
    if (elem == ELEMENT_LIGHT && rand_chance(30))
	return mob_randelementresist();

    return elem;
}

static ELEMENT_NAMES
mob_randelementattack()
{
    // We already gate to bias to physical in the caller!
    // So only roll for non-physical
    ELEMENT_NAMES	elem = (ELEMENT_NAMES) rand_range(ELEMENT_PHYSICAL+1, NUM_ELEMENTS-1);

    // Light needs to be pretty rare as it is very odd.
    if (elem == ELEMENT_LIGHT && rand_chance(50))
	return mob_randelementattack();

    return elem;
}

static ATTR_NAMES
mob_randattr()
{
    static const ATTR_NAMES attrnames[20] =
    {
	ATTR_GOLD,
	ATTR_YELLOW,
	ATTR_PINK,
	ATTR_PURPLE,
	ATTR_NORMAL,
	ATTR_LIGHTBLACK,
	ATTR_WHITE,
	ATTR_ORANGE,
	ATTR_LIGHTBROWN,
	ATTR_BROWN,
	ATTR_RED,
	ATTR_DKRED,
	ATTR_GREEN,
	ATTR_DKGREEN,
	ATTR_BLUE,
	ATTR_LIGHTBLUE,
	ATTR_TEAL,
	ATTR_CYAN,
	ATTR_FIRE,
	ATTR_DKCYAN
    };

    return attrnames[rand_choice(20)];
}

static ATTR_NAMES
mob_randattrfromname(const char *name)
{
    if (strstr(name, "white") || strstr(name, "arctic") || strstr(name, "snowy"))
	return ATTR_WHITE;
    if (strstr(name, "brown"))
	return ATTR_BROWN;
    if (strstr(name, "red"))
	return ATTR_RED;
    if (strstr(name, "black"))
	return ATTR_LIGHTBLACK;
    if (strstr(name, "gold"))
	return ATTR_GOLD;
    if (strstr(name, "blue"))
	return ATTR_BLUE;
    if (strstr(name, "purple"))
	return ATTR_PURPLE;
    if (strstr(name, "green") || strstr(name, "malachite") || strstr(name, "emerald"))
	return ATTR_GREEN;
    if (strstr(name, "teal"))	//YES, I know!
	return ATTR_TEAL;
    if (strstr(name, "yellow"))
	return ATTR_YELLOW;
    if (strstr(name, "grey"))
	return ATTR_GREY;
    if (strstr(name, "silver"))
	return ATTR_LTGREY;
    if (strstr(name, "olive"))
	return ATTR_DKGREEN;

    return mob_randattr();
}

void
mob_adddescr(MOB_DEF *def, const char *descr)
{
    BUF		buf;

    // Don't double add!
    if (strstr(def->descr, descr))
	return;

    buf.sprintf("%s%s  ", def->descr, descr);
    def->descr = glb_harden(buf);
}


MOB_NAMES glbBossName = MOB_BAEZLBUB;

MOB *
MOB::create(MOB_NAMES def, bool allowitems, WORLD_NAMES world)
{
    MOB		*mob;

    mob = new MOB();

    mob->myDefinition = def;

    mob->myMaxHP = defn(def).max_hp.roll();
    mob->myHP = mob->myMaxHP;
    mob->myMP = defn(def).max_mp;

    mob->mySeed = rand_int();

    // We don't currently support this!
    J_ASSERT(!mob->defn().swallows);

    // Determine a starting set...
    if (allowitems)
    {
	int choice = 0;

	STARTITEMS_NAMES startitems = GAMEDEF::mobdef(def)->startitems;
	STARTITEMS_NAMES beststart = STARTITEMS_NONE;
	while (startitems != STARTITEMS_NONE)
	{
	    int rarity = glb_startitemsdefs[startitems].rarity;
	    if (rand_choice(choice + rarity) < rarity)
		beststart = startitems;
	    choice += rarity;
	    startitems = glb_startitemsdefs[startitems].next;
	}

	if (beststart != STARTITEMS_NONE)
	{
	    ITEMLIST_NAMES itemlist = glb_startitemsdefs[beststart].itemlist;

	    while (itemlist != ITEMLIST_NONE)
	    {
		if (rand_chance(glb_itemlistdefs[itemlist].chance))
		{
		    int		count = glb_itemlistdefs[itemlist].count.roll();
		    while (count --> 0)
		    {
			ITEM *item = nullptr;
			ITEM_NAMES itemname = glb_itemlistdefs[itemlist].item;
			if (itemname != ITEM_NONE)
			    item = ITEM::create(itemname, 1);
			ITEMCLASS_NAMES itemclass = glb_itemlistdefs[itemlist].itemclass;
			if (itemclass != ITEMCLASS_NONE && !item)
			    item = ITEM::createItemClass(itemclass, 1, world);

			if (item)
			{
			    item->markStartingItem(true);
			    mob->addItem(item);
			}
		    }
		}

		itemlist = glb_itemlistdefs[itemlist].next;
	    }
	}
    }

    return mob;
}

MOB *
MOB::copy() const
{
    MOB		*mob;

    mob = new MOB();
    
    *mob = *this;

    // Copy world mobs.
    WORLD_NAMES world;
    FOREACH_WORLD(world)
    {
	if (mob->myWorldStash[world])
	    mob->myWorldStash[world] = myWorldStash[world]->copy();
    }

    // Copy inventory one item at a time.
    // We are careful to maintain the same list order here so restoring
    // won't shuffle things unexpectadly
    mob->myInventory.clear();
    for (int i = 0; i < myInventory.entries(); i++)
    {
	mob->myInventory.append(myInventory(i)->copy());
    }
    
    return mob;
}

void
MOB::setPos(POS pos)
{
    pos.moveMob(this, myPos);
    myPos = pos;
}


void
MOB::setMap(MAP *map)
{
    myPos.setMap(map);
    myTarget.setMap(map);
    myHome.setMap(map);
    myMeditatePos.setMap(map);
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i))
	    myInventory(i)->setMap(map);
    }
    WORLD_NAMES world;
    FOREACH_WORLD(world)
    {
	if (myWorldStash[world])
	    myWorldStash[world]->setMap(map);
    }
}

void
MOB::transferToWorld(MAP *dest)
{
    WORLD_NAMES		newworld = dest->world();
    WORLD_NAMES		curworld = pos().world();

    POS	p = pos();
    POS	npos = dest->findBijectivePos(p);
    p.removeMob(this);

    myTarget = dest->findBijectivePos(myTarget);
    myHome = dest->findBijectivePos(myHome);
    myMeditatePos = dest->findBijectivePos(myMeditatePos);
    // Add to the new map
    // We can't just do setPos because that will do moveMob, but
    // we must actually add/remove.
    npos.addMob(this);
    myPos = npos;

    setMap(dest);

    // If world changed and polymorphic, polymorph!
    if (curworld != newworld)
    {
	if (myWorldStash[curworld] &&
	    myWorldStash[newworld])
	{
	    stashInto(myWorldStash[curworld]);
	    myWorldStash[newworld]->stashInto(this);
	}
	else
	{
	    // Non-polymorphic creature, but might have polymorphic
	    // items.
	    for (auto && item : inventory())
	    {
		item->transferToWorld(curworld, dest);
	    }
	}
    }
}

void
MOB::stashInto(MOB *dst)
{
    dst->myInventory = myInventory;
    myInventory.clear();

    dst->myDefinition = myDefinition;
    dst->myName = myName;
    dst->myNumDeaths = myNumDeaths;

    SPELL_NAMES spell;
    FOREACH_SPELL(spell)
    {
	dst->myKnownSpells[spell] = myKnownSpells[spell];
    }

    dst->myHP = myHP;
    dst->myMaxHP = myMaxHP;
    dst->myMP = myMP;
    dst->myFood = myFood;

    dst->mySeed = mySeed;
    

    dst->myExp = myExp;
    dst->myLevel = myLevel;
    dst->myExtraLives = myExtraLives;
}

void
MOB::clearAllPos()
{
    myPos = POS();
    myTarget = POS();
    myHome = POS();
    myMeditatePos = POS();
}

MOB *
MOB::createNPC(int difficulty, WORLD_NAMES curworld)
{
    WORLD_NAMES		world;

    MOB_NAMES		basetype = MOB::chooseNPCType(difficulty, curworld);

    MOB			*base = MOB::create(basetype, /* items */ true, curworld);
    FOREACH_WORLD(world)
    {
	if (world != curworld)
	{
	    base->myWorldStash[world] = MOB::create(chooseNPCType(difficulty, world), true, world);
	}
	else
	{
	    // Already picked & got items in base.
	    base->myWorldStash[world] = MOB::create(basetype, /*items*/ false, world);
	}
    }

    return base;
}

MOB_NAMES
MOB::chooseNPCType(int difficulty, WORLD_NAMES world)
{
    int		i;
    MOB_NAMES	mob = MOB_NONE;
    int		choice = 0;
    int		matchingdepth = 0;

    // Given the letter choice, choose a mob that matches it appropriately.
    choice = 0;
    for (i = 0; i < GAMEDEF::getNumMob(); i++)
    {
	// depth 0 isn't generated.
	if (!defn(i).depth)
	    continue;
	if (!MAP::worldListContains(defn(i).worlds, world))
	    continue;
	// Use the baseletter to bias the creation.
	if (defn(i).depth <= difficulty)
	{
	    // Usually a uniform chance for every new level works,
	    // but in WTF you can world-scum so we want to bias
	    // to harder monsters.
	    int		rarity = defn(i).rarity;
	    int		scale = 15 - (difficulty - defn(i).depth);
	    if (scale < 1) scale = 1;
	    rarity *= scale;
	    if (rand_choice(choice + rarity) < rarity)
		mob = (MOB_NAMES) i;
	    choice += rarity;
	}
    }

    // Testing..
    // mob = MOB_KOBOLD_THIEF;

    return mob;
}

MOB *
MOB::createAvatar(WORLD_NAMES curworld)
{
    MOB *m = MOB::create(glb_worlddefs[curworld].avatar, true, curworld);

    WORLD_NAMES world;
    FOREACH_WORLD(world)
    {
	m->myWorldStash[world] = MOB::create(glb_worlddefs[world].avatar,
				    world != curworld, world);
	m->myWorldStash[world]->avatarStartup();
    }

    for (int i = 0; i < 0; i++)
    {
	// ITEM *item = ITEM::createRandom(5);
	ITEM *item = ITEM::create(ITEM::chooseRandomBalanced(1, curworld));
	if (item)
	    m->addItem(item);
    }

    ITEM_NAMES item = ITEM_COIN;
    if (item != ITEM_NONE)
    {
	ITEM *i = ITEM::create(item, 1);
	if (i)
	    m->addItem(i);
    }

    // Equip what is sensible...
    m->avatarStartup();

    // DEBUG:
    // Add spells
    // m->memorizeSpell(SPELL_TELEPORT);
    // m->memorizeSpell(SPELL_TSUNAMI);

    return m;
}

void
MOB::avatarStartup()
{
    bool		donewield=false, donearmour=false, donebow=false;
    for (auto && item : inventory())
    {
	if (!donewield && item->isWeapon())
	{
	    item->setWielded(true);
            item->markBonusKnown();
	    donewield = true;
	}
	if (!donearmour && item->isArmour())
	{
	    item->setEquipped(true);
            item->markBonusKnown();
	    donearmour = true;
	}
	if (!donebow && item->isRanged())
	{
	    item->setEquipped(true);
            item->markBonusKnown();
	    donebow = true;
	}
        item->markMagicClassKnown(true);
    }
}

void
MOB::getLook(u8 &symbol, ATTR_NAMES &attr) const
{
    symbol = defn().symbol;
    attr = (ATTR_NAMES) defn().attr;

    if (!alive())
    {
	// Dead creatures override their attribute to blood
	attr = ATTR_RED;
    }
}

BUF
MOB::getName() const
{
    BUF		result;

    if (isAvatar())
	result.reference("you");
    else if (myName.isstring())
	result = myName;
    else
    {
	result = thesaurus_expand(mySeed, getDefinition());
    }

    if (hasItem(ITEM_ASLEEP) && !isAvatar())
    {
	BUF	tmp;
	tmp.sprintf("sleeping %s", result.buffer());
	result = tmp;
    }

    return result;
}

BUF
MOB::getRawName() const
{
    BUF		result;
    result.reference(defn().name);
    return result;
}

BUF
MOB::getArticleName() const
{
    BUF		name = getName();
    BUF		result;

    result.sprintf("%s%s", gram_getarticle(name.buffer()), name.buffer());

    return result;
}


void
MOB::setupTextVariables() const
{
    BUF		tmp;

    tmp.sprintf("%s%s", gram_getarticle(getName()), getName().buffer());
    text_registervariable("A_MOBNAME", tmp);
    text_registervariable("MOBNAME", getName());
    text_registervariable("MOBNAMES", gram_makepossessive(getName()));
}

MOB_NAMES
MOB::getBossName()
{
    return glbBossName;
}

BUF
MOB::buildSpellDescr(SPELL_NAMES spell)
{
    BUF		stats, descr;

    SPELL_DEF		*def = GAMEDEF::spelldef(spell);

    stats.sprintf("Spell: %s (%s)\n", def->name, def->verb);
    descr.strcat(stats);
    if (def->timeout)
    {
	stats.sprintf("Timeout: %d  ", def->timeout);
	descr.strcat(stats);
    }
    if (def->mana || def->reqfullmana)
    {
	if (def->reqfullmana)
	    stats.strcpy("Mana: All  ");
	else
	    stats.sprintf("Mana: %d  ", def->mana);
	descr.strcat(stats);
    }
    if (def->radius > 1)
    {
	stats.sprintf("Radius: %d  ", def->radius);
	descr.strcat(stats);
    }

    if (def->piercing)
    {
	descr.strcat("Piercing ");
    }

    if (def->blast)
    {
	stats.sprintf("Close Blast\n");
	descr.strcat(stats);
    }
    else if (def->cone)
    {
	stats.sprintf("Cone\n");
	descr.strcat(stats);
    }
    else if (def->range > 1)
    {
	stats.sprintf("Range: %d\n", def->range);
	descr.strcat(stats);
    }
    else
    {
	descr.strcat("\n");
    }

    if (!def->needsdir)
	descr.strcat("Targets Self\n");

    stats = buildEffectDescr((EFFECT_NAMES) def->effect);
    if (stats.isstring())
    {
	descr.strcat("Effect: ");
	descr.strcat(stats);
	descr.strcat("\n");
    }

    return descr;
}

BUF
MOB::buildAttackDescr(ATTACK_NAMES attack)
{
    BUF		stats, descr;

    ATTACK_DEF		*def = GAMEDEF::attackdef(attack);
    stats.sprintf("Attack: %s (%s)\n", def->noun, def->verb);
    descr.strcat(stats);
    stats.sprintf("   Damage: %s\n", def->damage.format().buffer());
    descr.strcat(stats);
    if (def->chancebonus)
    {
	stats.sprintf("   Bonus to Hit: %d\n", def->chancebonus);
	descr.strcat(stats);
    }
    if (def->element != ELEMENT_PHYSICAL)
    {
	stats.sprintf("   Element: %s\n", GAMEDEF::elementdef(def->element)->name);
	descr.strcat(stats);
    }
    stats = buildEffectDescr((EFFECT_NAMES) def->effect);
    if (stats.isstring())
    {
	descr.strcat("   Effect: ");
	descr.strcat(stats);
	descr.append('\n');
    }

    return descr;
}

BUF
MOB::buildSpecialDescr(MOB_NAMES mob)
{
    BUF		descr, stats;

    descr.strcat(defn(mob).descr);

    if (defn(mob).resistance != ELEMENT_NONE)
    {
	stats.sprintf("They are unaffected by %s.  ", GAMEDEF::elementdef(defn(mob).resistance)->name);
	descr.strcat(stats);
    }
    if (defn(mob).vulnerability != ELEMENT_NONE)
    {
	stats.sprintf("They are particularly sensitive to %s.  ", GAMEDEF::elementdef(defn(mob).vulnerability)->name);
	descr.strcat(stats);
    }

    if (defn(mob).isslow)
    {
	descr.strcat("They move with a slow plodding pace.  ");
    }
    if (defn(mob).isfast)
    {
	descr.strcat("They move with shocking speed.  ");
    }
    if (defn(mob).isvampire)
    {
	descr.strcat("They drink the blood of their foes, gaining energy therefrom.  ");
    }
    if (defn(mob).isregenerate)
    {
	descr.strcat("They heal wounds with astonishing speed.  ");
    }
    if (defn(mob).candig)
    {
	descr.strcat("They can dig through solid rock.  ");
    }
    if (defn(mob).swallows)
    {
	descr.strcat("They can swallow their foes whole.  ");
    }
    if (defn(mob).breeder)
    {
	descr.strcat("The rate of their reproduction leaves rabbits embarrassed.  ");
    }
    if (defn(mob).isthief)
    {
	descr.strcat("They steal interesting objects.  ");
    }
    if (defn(mob).canleap)
    {
	descr.strcat("They can leap long distances.  ");
    }
    if (defn(mob).corrosiveblood)
    {
	descr.strcat("Their corrosive blood will damage weapons that strike them.  ");
    }
    if (defn(mob).passwall)
    {
	descr.strcat("They move through walls.  ");
    }
    if (defn(mob).sightrange < 5)
    {
	descr.strcat("They are known to be short-sighted.  ");
    }

    return descr;
}

void
MOB::describe() const
{
    BUF		descr;

    // Always in current tense as you are dead, not were dead.
    formatAndReport("%S <be> %o.", healthStatusDescr());

    BUF		be_buf = gram_conjugate("be", getPerson(), !alive());
    const char *be = be_buf.buffer();
    BUF		have_buf = gram_conjugate("have", getPerson(), !alive());
    const char *have = have_buf.buffer();

    descr.sprintf("%%S %s %%o.", be);
    formatAndReport(descr, foodStatusDescr());
    descr.clear();

    if (myInventory.entries())
    {
	int		any = 0;
	for (auto && item : inventory())
	{
	    if (!item->isFlag())
		continue;
	    BUF		 name = item->getName();
	    if (any)
		descr.strcat(", ");
	    else
		descr.appendSprintf("%%S %s ", be);
	    any++;
	    descr.strcat(name);
	}
	if (any)
	    descr.strcat(".  ");

	// Describe anything held or worn...
	for (auto && item : inventory())
	{
	    if (item->isFlag())
		continue;
	    if (item->isEquipped() || item->isWielded())
	    {
		BUF		 name = item->getArticleName();
		descr.strcat("%S ");
		if (item->isWielded())
		    descr.appendSprintf("%s wielding ", be);
		else if (item->isRanged())
		    descr.appendSprintf("%s readied ", have);
		else
		    descr.appendSprintf("%s wearing ", be);
		descr.strcat(name);
		descr.strcat(".  ");
		any++;
	    }
	}
	if (any)
	    descr.strcat("\n");
    }

    if (isAvatar())
    {
	BUF buf;
	buf.sprintf("%%S %s level %d with %d out of %d experience.\n",
		be, getLevel(), getExp(), LEVEL::expFromLevel(getLevel()+1));
	descr.strcat(buf);
	int		story = GAMEDEF::rules().bosslevel - pos().depth() + 1;
	if (pos().world() == WORLD_FIGHTER)
	    story = pos().depth();

	buf.sprintf("%%S %s on floor %d.\n",
		    be, story);
	descr.strcat(buf);
    }
    formatAndReport(descr);
}

BUF
MOB::getLongDescription() const
{
    BUF		descr, detail;
    BUF		stats, buf;
    const char 	*Pronoun = "They";
    const char  *possessive = "their";
    const char  *are = "are";
    const char  *have = "have";

    descr.strcpy(gram_capitalize(getName()));
    stats.sprintf(", %s\n", healthStatusDescr());
    descr.strcat(stats);
    if (getMaxMP())
    {
	stats.sprintf("  Mana: %d / %d", getMP(), getMaxMP());
	descr.strcat(stats);
    }
    descr.strcat("\n");

    if (isAvatar())
    {
	Pronoun = "You";
	possessive = "your";
    }
    if (!alive())
    {
	are = "were";
	have = "had";
    }

    if (1)
    {
	BUF		text = text_lookup("mob", getRawName());
	descr.strcat("\n");
	text.stripTrailingWS();
	descr.strcat(text);
	descr.strcat("\n");
    }

    if (!isAvatar())
    {
	const char *rarity = "a unique creature";
	if (defn().rarity > 0)
	{
	    if (defn().rarity >= 100)
		rarity = "a common creature";
	    else
		rarity = "a rare creature";
	}
	stats.sprintf("\nThe %s is %s.  %s  ",
		getName().buffer(), rarity, glb_aidefs[getAI()].descr);

	descr.strcat(stats);

	stats = buildSpecialDescr(getDefinition());
	descr.strcat(stats);
	descr.strcat("\n");
    }
    if (isAvatar())
    {
	stats.sprintf("%s %s %s.\n", Pronoun, are, foodStatusDescr());
	descr.strcat(stats);
    }

    int		reduction = getDamageReduction(ELEMENT_PHYSICAL);
    if (reduction)
    {
	stats.sprintf("Damage Reduction: %d", reduction);
	descr.strcat(stats);
    }
    if (defn().dodgebonus)
    {
	if (reduction)
	    descr.strcat("; ");
	stats.sprintf("Dodge Bonus: %d", defn().dodgebonus);
	descr.strcat(stats);
    }
    if (defn().dodgebonus || reduction)
	descr.append('\n');

    if (getMeleeAttack() != ATTACK_NONE)
    {
	descr.append('\n');
	ATTACK_DEF		*def = GAMEDEF::attackdef(getMeleeAttack());
	stats.sprintf("%s %s %s foes for %s damage.",
			Pronoun,
			def->verb, 
			possessive,
			def->damage.format().buffer());
	descr.strcat(stats);
    }

    if ((defn().range_valid && defn().range_range) || lookupWand())
    {
	stats.sprintf("\n\n%s can also attack at a up to %d distance", Pronoun, getRangedRange());
	if (defn().range_recharge)
	    stats.appendSprintf(", every %d turns.  ", defn().range_recharge);
	else
	    stats.strcat(".  ");
	descr.strcat(stats);

	ATTACK_DEF		*def = GAMEDEF::attackdef(getRangedAttack());
	stats.sprintf("%s %s %s foes for %s damage.",
			Pronoun,
			def->verb, 
			possessive,
			def->damage.format().buffer());
	descr.strcat(stats);
    }

    descr.append('\n');

    // Describe intrinsics
    if (myInventory.entries())
    {
	int		any = 0;
	for (auto && item : inventory())
	{
	    if (!item->isFlag())
		continue;
	    BUF		 name = item->getName();
	    if (any)
		descr.strcat(", ");
	    else
	    {
		descr.strcat(Pronoun);
		descr.strcat(" ");
		descr.strcat(are);
		descr.strcat(" ");
	    }
	    any++;
	    descr.strcat(name);
	}
	if (any)
	    descr.strcat(".  ");

	// Describe anything held or worn...
	for (auto && item : inventory())
	{
	    if (item->isFlag())
		continue;
	    if (item->isEquipped() || item->isWielded())
	    {
		BUF		 name = item->getArticleName();
		descr.strcat(Pronoun);
		if (item->isWielded())
		    descr.appendSprintf(" %s wielding ", are);
		else if (item->isRanged())
		    descr.appendSprintf(" %s readied ", have);
		else
		    descr.appendSprintf(" %s wearing ", are);
		descr.strcat(name);
		descr.strcat(".  ");
		any++;
	    }
	}
	if (any)
	    descr.strcat("\n");
    }

    if (isAvatar())
    {
	buf.sprintf("\nLevel: %d   Exp: %d / %d\n",
		getLevel(), getExp(), LEVEL::expFromLevel(getLevel()+1));
	descr.strcat(buf);
    }
    if (getExtraLives() > 0)
    {
	buf.sprintf("%s %s %d extra lives.\n", Pronoun, have, getExtraLives());
	descr.strcat(buf);
    }

    return descr;
}

ITEM *
MOB::getRandomItem(bool allowequipped) const
{
    ITEM	*result;
    int		choice = 0;

    result = 0;

    if (myInventory.entries())
    {
	// I hope I was drunk when I did multi-sample here?
	// Admittedly, this is the only code which has generated hate
	// mail, so it must be doing something right!
	int		tries = 10;
	while (tries--)
	{
	    choice = rand_choice(myInventory.entries());

	    // Ignore flags
	    if (myInventory(choice)->isFlag())
		continue;

	    // Ignore equipped if asked
	    if (myInventory(choice)->isEquipped() && !allowequipped)
		continue;
	    if (myInventory(choice)->isWielded() && !allowequipped)
		continue;

	    return myInventory(choice);
	}
    }
    return result;
}

ITEM *
MOB::itemFromLetter(u8 letter) const
{
    // No letter is unassigned, which may be a lot of these!
    if (!letter)
	return nullptr;

    for (auto && item : inventory())
    {
	if (item->inventoryLetter() == letter)
	    return item;
    }
    return nullptr;
}

bool
MOB::assignInventoryLetter(ITEM *assignee)
{
    if (!isAvatar())
	return false;

    glbLetterTimeStamp++;

    // It is unlikely we overflow an int, but it would be somewhat
    // catastrophic as we'd lock open.  So just reset when we are big.
    if (glbLetterTimeStamp > 1000*1000*1000)
    {
	glbLetterTimeStamp = 1;
	for (int i = 0; i < 26; i++)
	    glbLastSeen[i] = 0;
    }

    for (auto && item : inventory())
    {
	u8 sym = item->inventoryLetter();
	if (sym >= 'a' && sym <= 'z')
	{
	    glbLastSeen[sym - 'a'] = glbLetterTimeStamp;
	}
    }

    int		minstamp = glbLastSeen[0];
    u8		lastletter = 0;
    for (int i = 0; i < 26; i++)
    {
	if (glbLastSeen[i] < minstamp)
	{
	    minstamp = glbLastSeen[i];
	    lastletter = i;
	}
    }

    // If this is the current water mark we failed.
    if (minstamp == glbLetterTimeStamp)
    {
	// No new slots!
	return false;
    }

    glbLastSeen[lastletter] = glbLetterTimeStamp;
    assignee->setInventoryLetter(lastletter+'a');

    return true;
}

void
MOB::stealNonEquipped(MOB *src)
{
    // NO-ops
    if (!src || src == this)
	return;

    for (int i = src->myInventory.entries(); i --> 0;)
    {
	ITEM *item = src->myInventory(i);

	if (item->isEquipped())
	    continue;
	if (item->isWielded())
	    continue;

	if (item->isFlag())
	    continue;

	src->removeItem(item, true);
	addItem(item);
    }
}

bool
MOB::hasAnyItems() const
{
    for (auto && item : inventory())
	if (!item->isFlag())
	    return true;

    return false;
}

bool
MOB::hasItem(ITEM_NAMES itemname) const
{
    if (lookupItem(itemname))
	return true;

    return false;
}

bool
MOB::hasItemInWorld(WORLD_NAMES world, ITEM_NAMES itemname) const
{
    if (!myWorldStash[world])
	return hasItem(itemname);
    if (pos().world() == world)
	return hasItem(itemname);
    return myWorldStash[world]->hasItem(itemname);
}

bool
MOB::hasUnbrokenItem(ITEM_NAMES itemname) const
{
    if (lookupUnbrokenItem(itemname))
	return true;

    return false;
}

ITEM *
MOB::giftItem(ITEM_NAMES itemname)
{
    ITEM		*item;

    item = ITEM::create(itemname);

    addItem(item);

    // Allow for stacking!
    item = lookupItem(itemname);

    return item;
}

bool
MOB::destroyItem(ITEM_NAMES itemname)
{
    ITEM		*item = lookupItem(itemname);
    if (!item)
	return false;
    removeItem(item);
    delete item;
    return true;
}

ITEM *
MOB::lookupWeapon() const
{
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->isWielded())
	    return myInventory(i);
    }
    return 0;
}

int
MOB::getDamageReduction(ELEMENT_NAMES element) const
{
    int		reduction = defn().damagereduction;
    ITEM	*armour = lookupArmour();
    if (armour)
    {
	reduction += armour->getDamageReduction();
    }

    if (element != ELEMENT_PHYSICAL)
	reduction = 0;

    ITEM *ring = lookupRing();
    if (ring)
    {
	RING_NAMES	 ringname = ring->asRing();
	if (ringname != RING_NONE)
	{
	    if (GAMEDEF::ringdef(ringname)->resist == element)
	    {
		reduction += GAMEDEF::ringdef(ringname)->resist_amt + ring->getBonus();
		// Identify.
		ring->markMagicClassKnown();
		ring->markBonusKnown();
	    }
	}
    }

    return reduction;
}

ITEM *
MOB::lookupArmour() const
{
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->isArmour() && myInventory(i)->isEquipped())
	    return myInventory(i);
    }
    return 0;
}

ITEM *
MOB::lookupWand() const
{
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->isRanged() && myInventory(i)->isEquipped())
	    return myInventory(i);
    }
    return 0;
}

ITEM *
MOB::lookupUnbrokenItem(ITEM_NAMES itemname) const
{
    for (auto && item : inventory())
    {
	if (item->isBroken())
	    continue;
	if (item->getDefinition() == itemname)
	    return item;
    }
    return 0;
}

ITEM *
MOB::lookupItem(ITEM_NAMES itemname) const
{
    int		i;

    for (i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->getDefinition() == itemname)
	    return myInventory(i);
    }
    return 0;
}

ITEM *
MOB::lookupItem(ITEM_NAMES itemname, MATERIAL_NAMES materialname) const
{
    int		i;

    for (i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->getDefinition() == itemname)
	{
	    if (myInventory(i)->material() == materialname)
		return myInventory(i);
	}
    }
    return 0;
}

ITEM *
MOB::lookupRing() const
{
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->isRing() && myInventory(i)->isEquipped())
	    return myInventory(i);
    }
    return 0;
}

RING_NAMES
MOB::lookupRingName() const
{
    ITEM		*ring = lookupRing();
    if (!ring)
	return RING_NONE;

    return ring->asRing();
}

void
MOB::stopRunning()
{
    if (hasItem(ITEM_QUICKBOOST))
	removeItem(lookupItem(ITEM_QUICKBOOST));
    myLastDirCount = 0;
}

bool
MOB::canMoveDir(int dx, int dy, bool checkmob, bool onlypassive) const
{
    POS		goal;

    goal = myPos.delta(dx, dy);

    return canMove(goal, checkmob, onlypassive);
}

bool
MOB::canMove(POS pos, bool checkmob, bool onlypassive) const
{
    if (!pos.valid())
	return false;

    if (!pos.isPassable())
    {
	if (pos.defn().isphaseable && defn().passwall)
	{
	    // Can move through it...
	}
	else if (pos.defn().isdiggable && defn().candig && !onlypassive)
	{
	    // Could dig through it.
	}
	else if (defn().breakdoors && pos.tile() == TILE_ICEWALL && !onlypassive)
        {
            // Can smash ice walls
        }
	else if ((defn().opendoors || defn().breakdoors) && pos.defn().isdoorway && !onlypassive)
	{
	    switch (pos.tile())
	    {
		case TILE_DOOR_OPEN:
		case TILE_DOOR_BROKEN:
		case TILE_DOOR_EMPTY:
		    // Should not occur as should be passable.
		    break;
		case TILE_DOOR_CLOSED:
		    // Anyone with hands will attempt this.
		    break;
		case TILE_DOOR_JAMMED_UNKNOWN:
		case TILE_DOOR_JAMMED:
		case TILE_DOOR_LOCKED_UNKNOWN:
		case TILE_DOOR_LOCKED:
		    // Monsters know not to try these, but the
		    // avatar is unwise.
		    if (isAvatar())
			break;
		    // No stopping some monsters.
		    if (defn().breakdoors)
			break;
		    // Do not attempt if a mob.
		    return false;
	    }
	}
	else
	{
	    // Failed...
	    return false;
	}
    }

    if (checkmob && pos.mob())
	return false;

    return true;
}

void
MOB::move(POS newpos, bool ignoreangle)
{
    // If we have swallowed something, move it too.
    if (!isSwallowed())
    {
	PTRLIST<MOB *>	moblist;
	int		i;

	pos().getAllMobs(moblist);
	for (i = 0; i < moblist.entries(); i++)
	{
	    if (moblist(i)->isSwallowed())
	    {
		moblist(i)->move(newpos, true);
	    }
	}
    }

    if (ignoreangle)
	newpos.setAngle(pos().angle());

    POS oldpos = pos();

    // Check if we are entering a new room...
    if (isAvatar() && (newpos.roomId() != pos().roomId()))
    {
	if (newpos.map())
	{
	    newpos.map()->expandActiveRooms(newpos.roomId(), 2);
	}
    }

    setPos(newpos);

    reportSquare(pos(), oldpos);
}

void
MOB::heardNoise(POS src, int loudness)
{
    if (hasItem(ITEM_ASLEEP))
    {
	if (loudness > rand_choice(4))
	{
	    destroyItem(ITEM_ASLEEP);
	}
    }
    else
    {
	// Do we attract monsters as well?
	if (!myTarget.valid())
	    myTarget = src;
    }
}

int
MOB::getFood() const
{
    return myFood;
}

void
MOB::gainFood(int food)
{
    FOODLEVEL_NAMES oldstatus = foodStatus();
    myFood += food;
    // We allow ourselves to be oversatiated!
    if (oldstatus != foodStatus())
    {
	formatAndReport("%S <be> now %o.", foodStatusDescr());
    }
}

void
MOB::eatFood(int food)
{
    FOODLEVEL_NAMES oldstatus = foodStatus();
    myFood -= food;
    if (myFood < 0)
    {
	myFood = 0;
    }
    if (oldstatus != foodStatus())
    {
	formatAndReport("%S <be> now %o.", foodStatusDescr());
    }
}

int
MOB::getMaxFood() const
{
    return 200;
}

const char *
MOB::healthStatusDescr() const
{
    HEALTHLEVEL_NAMES l = healthStatus();

    if (defn().isconstruct)
	return glb_healthleveldefs[l].construct;
    return glb_healthleveldefs[l].normal;
}

HEALTHLEVEL_NAMES
MOB::healthStatus() const
{
    // THe order is somewhat important to make sure 1 hp creatures
    // with max 1 hp aren't at death's gate.
    int		hp = getHP();
    int		maxhp = getMaxHP();

    if (hp <= 0)
	return HEALTHLEVEL_DEAD;
    if (hp >= maxhp)
	return HEALTHLEVEL_FULL;
    if (hp == 1)
	return HEALTHLEVEL_DEATHSGATE;

    // Rest is vaguely by percentage...
    float frac = (float)hp / (float)maxhp;

    if (frac > 0.75)
	return HEALTHLEVEL_SCRATCHED;
    if (frac > 0.5)
	return HEALTHLEVEL_BRUISED;
    if (frac > 0.3)
	return HEALTHLEVEL_BLOODIED;
    if (frac > 0.2)
	return HEALTHLEVEL_INJURED;
    if (frac > 0.1)
	return HEALTHLEVEL_SEVERELYINJURED;

    return HEALTHLEVEL_CRITICALLYINJURED;
}

const char *
MOB::foodStatusDescr() const
{
    FOODLEVEL_NAMES l = foodStatus();

    return glb_foodleveldefs[l].normal;
}

FOODLEVEL_NAMES
MOB::foodStatus() const
{
    // THe order is somewhat important to make sure 1 hp creatures
    // with max 1 hp aren't at death's gate.
    int		food = getFood();
    int		maxfood = getMaxFood();

    if (food <= 0)
	return FOODLEVEL_STARVING;
    if (food >= maxfood)
	return FOODLEVEL_SATIATED;
    if (food * 2 < maxfood)
	return FOODLEVEL_HUNGRY;
    return FOODLEVEL_FULL;
}

bool
MOB::aliveInWorld(WORLD_NAMES world) const
{
    // if we don't have a world stash, so aren't polymorphic,
    // this amounts to being alive in this world.
    if (!myWorldStash[world])
	return alive();
    // Likewise, if current world is chosen world we match current
    // status as we might not yet stashed ourselves as dead.
    if (world == pos().world())
	return alive();
    // Check if stashed self is alive.
    return myWorldStash[world]->alive();
}

int
MOB::getMaxHP() const
{
    return myMaxHP;
}

void
MOB::gainHP(int hp)
{
    int		maxhp;

    HEALTHLEVEL_NAMES oldstatus = healthStatus();

    maxhp = getMaxHP();

    if (hasItem(ITEM_PLAGUE))
    {
	// Do not heal as well when sick.
	if (hp > 0)
	    hp = (hp + 1) / 2;
    }

    myHP += hp;
    if (myHP > maxhp)
	myHP = maxhp;
    if (myHP < 0)
	myHP = 0;

    if (oldstatus != healthStatus())
	formatAndReport("%S <be> now %o.", healthStatusDescr());
}

int
MOB::getMaxMP() const
{
    return defn().max_mp;
}

void
MOB::gainMP(int mp)
{
    int		maxmp;

    maxmp = getMaxMP();

    myMP += mp;
    if (myMP > maxmp)
	myMP = maxmp;
    if (myMP < 0)
	myMP = 0;
}


bool
MOB::putToSleep(bool doresistcheck, int checkdc, int timer, bool silent)
{
    if (hasItem(ITEM_ASLEEP))
    {
        // Sleeping critters blissfully unware.
        ITEM *item = lookupItem(ITEM_ASLEEP);
        if (item->getTimer() > 0)
        {
            if (timer >= 0)
                item->addTimer(timer);
            else
                item->setTimer(timer);          // permanent
        }
        formatAndReport("%S <snore>.");
    }
    else if (!defn().cansleep)
    {
        if (!silent)
            formatAndReport("%S <be> unaffected.");
    }
    else if (!doresistcheck ||
            rand_choice(getLevel() + defn().depth) <= rand_choice(checkdc))
    {
        ITEM *sleep = giftItem(ITEM_ASLEEP);
        sleep->setTimer(timer);
    }
    else
    {
        formatAndReport("%S <resist>.");
    }
    return true;
}

void
MOB::postEvent(EVENTTYPE_NAMES type, u8 sym, ATTR_NAMES attr, const char *text) const
{
    if (pos().map())
	pos().map()->getDisplay()->queue().append(EVENT((MOB *)this, sym, attr, type, text));
}

void
MOB::doEmote(const char *text)
{
    postEvent( (EVENTTYPE_NAMES) (EVENTTYPE_SHOUT | EVENTTYPE_LONG),
		    ' ', ATTR_EMOTE,
		    text);
}

void
MOB::doShout(const char *text)
{
    postEvent( (EVENTTYPE_NAMES) (EVENTTYPE_SHOUT | EVENTTYPE_LONG),
		    ' ', ATTR_SHOUT,
		    text);
}

BUF
MOB::buildEffectDescr(EFFECT_NAMES effect)
{
    BUF		descr;
    EFFECT_DEF	*def = GAMEDEF::effectdef(effect);

    if (effect == EFFECT_NONE)
	return descr;
    switch (def->type)
    {
	case NUM_EFFECTCLASSS:
	case EFFECTCLASS_NONE:
	    break;
	case EFFECTCLASS_HEAL:
	    descr.sprintf("Heals %s.  ",
			    def->amount.format().buffer());
	    break;
	case EFFECTCLASS_GAINMANA:
	    descr.sprintf("Regains %s mana.  ",
			    def->amount.format().buffer());
	    break;
	case EFFECTCLASS_LOSEMANA:
	    descr.sprintf("Drains %d (%s) mana.  ",
			    def->amount.format().buffer());
	    break;
	case EFFECTCLASS_GAINFOOD:
	    descr.sprintf("Creates %s food.  ",
			    def->amount.format().buffer());
	    break;
	case EFFECTCLASS_LOSEFOOD:
	    descr.sprintf("Consumes %s food.  ",
			    def->amount.format().buffer());
	    break;
	case EFFECTCLASS_POISON:
	    descr.sprintf("Poisons for %s turns.  ",
			    def->duration.format().buffer());
	    break;
	case EFFECTCLASS_CURE:
	    descr.sprintf("Cures poison%s.  ",
			    "");
	    break;
	case EFFECTCLASS_DAMAGE:
	    descr.sprintf("Causes %s %s damage.  ",
			def->amount.format().buffer(),
			GAMEDEF::elementdef(def->element)->name);
	    break;
	case EFFECTCLASS_SCALED_DAMAGE:
	    descr.sprintf("Causes %s%% damage.  ",
			def->amount.format().buffer(),
			GAMEDEF::elementdef(def->element)->name);
	    break;
	case EFFECTCLASS_RESIST:
	    descr.sprintf("Grant resist %s for %s turns.  ",
			GAMEDEF::elementdef(def->element)->name,
			def->duration.format().buffer());
	    break;
	case EFFECTCLASS_VULNERABLE:
	    descr.sprintf("Grant vulnerable to %s for %s turns.  ",
			GAMEDEF::elementdef(def->element)->name,
			def->duration.format().buffer());
	    break;
	case EFFECTCLASS_GAINITEM:
	    descr.sprintf("Grants %s for %s turns.  ",
			    GAMEDEF::itemdef(def->itemflag)->name,
			    def->duration.format().buffer());
	    break;
    }

    if (def->next != EFFECT_NONE)
    {
	BUF furtherdescr = buildEffectDescr(def->next);

	BUF total;
	total.sprintf("%s%s", descr.buffer(), furtherdescr.buffer());
	descr = total;
    }

    return descr;
}

bool
MOB::applyEffect(MOB *src, EFFECT_NAMES effect, ATTACKSTYLE_NAMES attackstyle)
{
    if (effect == EFFECT_NONE)
	return false;

    EFFECT_DEF	*def = GAMEDEF::effectdef(effect);

    switch (def->type)
    {
	case NUM_EFFECTCLASSS:
	case EFFECTCLASS_NONE:
	    break;

	case EFFECTCLASS_HEAL:
	{
	    BUF		healstring;
	    int		heal = def->amount.roll();
	    
	    // heal = MIN(heal, getMaxHP()-getHP());

	    healstring.sprintf("%%S <heal> %d damage.", heal);
	    formatAndReport(healstring);
	    gainHP(heal);
	    break;
	}

	case EFFECTCLASS_GAINMANA:
	{
	    BUF		healstring;
	    int		heal = def->amount.roll();

	    healstring.sprintf("%%S <regain> %d mana.", heal);
	    formatAndReport(healstring);
	    gainMP(heal);
	    break;
	}

	case EFFECTCLASS_LOSEMANA:
	{
	    BUF		healstring;
	    int		heal = def->amount.roll();
	    healstring.sprintf("%%S <be> drained of %d mana.", heal);
	    formatAndReport(healstring);
	    gainMP(-heal);
	    break;
	}

	case EFFECTCLASS_GAINFOOD:
	{
	    BUF		healstring;
	    int		heal = def->amount.roll();

	    heal = MIN(heal, getMaxFood()-getFood());

	    healstring.sprintf("%%S <gain> %d food.", heal);
	    formatAndReport(healstring);
	    gainFood(heal);
	    break;
	}

	case EFFECTCLASS_LOSEFOOD:
	{
	    BUF		healstring;
	    int		heal = def->amount.roll();
	    heal = MIN(heal, getFood());

	    healstring.sprintf("%%S <be> drained of %d food.", heal);
	    formatAndReport(healstring);
	    eatFood(heal);
	    break;
	}

	case EFFECTCLASS_CURE:
	{
	    ITEM		*item = lookupItem(ITEM_POISON);

	    if (!item)
	    {
		formatAndReport("%S <feel> healthy.");
	    }
	    else
	    {
		removeItem(item);
		delete item;
	    }
	    break;
	}

	case EFFECTCLASS_DAMAGE:
	{
	    int damage = def->amount.roll();
	    if (src)
	    {
		src->formatAndReport("%S %v %O.", GAMEDEF::elementdef(def->element)->damageverb, this);
	    }
	    else
	    {
		formatAndReport("%S <be> %V.", GAMEDEF::elementdef(def->element)->damageverb, this);
	    }
	    if (applyDamage(src, damage, def->element, attackstyle))
		return true;
	    break;
	}

	case EFFECTCLASS_SCALED_DAMAGE:
	{
	    int damage = def->amount.roll();
            damage = (getHP() * damage) / 100;
            if (!damage) damage = 1;
	    if (src)
	    {
		src->formatAndReport("%S %v %O.", GAMEDEF::elementdef(def->element)->damageverb, this);
	    }
	    else
	    {
		formatAndReport("%S <be> %V.", GAMEDEF::elementdef(def->element)->damageverb, this);
	    }
	    if (applyDamage(src, damage, def->element, attackstyle))
		return true;
	    break;
	}

	case EFFECTCLASS_RESIST:
	case EFFECTCLASS_VULNERABLE:
	case EFFECTCLASS_GAINITEM:
	case EFFECTCLASS_POISON:
	{
	    ITEM_NAMES		 itemname;
	    // Probably should have all been GAINITEM to begin with!
	    if (def->type == EFFECTCLASS_RESIST)
		itemname = ITEM_RESIST;
	    else if (def->type == EFFECTCLASS_VULNERABLE)
		itemname = ITEM_VULNERABLE;
	    else if (def->type == EFFECTCLASS_GAINITEM)
		itemname = def->itemflag;
	    else if (def->type == EFFECTCLASS_POISON)
		itemname = ITEM_POISON;
	    else
	    {
		J_ASSERT(!"Unhandled effect class");
		break;
	    }

	    // If have the corresponding resistance item, skip.
	    if (hasItem(def->itemresist))
		break;

	    ITEM		*item = ITEM::create(itemname);

	    int		duration = def->duration.roll();
	    item->setTimer(duration);
	    item->setElement(def->element);

	    addItem(item);
	    break;
	}
    }

    // Chain the effect (still alive as we'd return if we died)
    if (def->next != EFFECT_NONE)
    {
	return applyEffect(src, def->next, attackstyle);
    }

    return false;
}

bool
MOB::isVulnerable(ELEMENT_NAMES element) const
{
    if (defn().vulnerability == element)
	return true;

    // Check any items...
    for (int i = 0; i < myInventory.entries(); i++)
    {
	ITEM	*item = myInventory(i);
	if (item->getDefinition() == ITEM_VULNERABLE)
	{
	    if (item->element() == element)
		return true;
	}
    }
    return false;
}

bool
MOB::applyAttack(ATTACK_NAMES attack, MOB *src, 
		 ATTACKSTYLE_NAMES attackstyle, bool silent)
{
    ATTACK_DEF	*def = GAMEDEF::attackdef(attack);

    int		damage = def->damage.roll();
    if (damage)
    {
	// Non zero damage, so apply it.
	if (applyDamage(src, damage, def->element, attackstyle, silent))
	    return true;		// is ded
    }

    // Apply the effect.
    if (applyEffect(src, def->effect, attackstyle))
	return true;			// is dead

    return false;
}

bool
MOB::applyDamage(MOB *src, int hits, ELEMENT_NAMES element, ATTACKSTYLE_NAMES attackstyle, bool silent)
{
    // Being hit isn't boring.
    if (!silent)
    {
	clearBoredom();
	stopWaiting("%S <be> hit, so <end> %r rest.");
    }

    // Being hit will wake you up.
    destroyItem(ITEM_ASLEEP);

    if (hasItem(ITEM_INVULNERABLE))
	return false;

    {
	ITEM *armour = lookupArmour();
	if (armour && armour->getDefinition() == ITEM_STEEL_CLOAK)
	{
	    formatAndReport("%S <ignore> the attack.");
	    return false;
	}
    }

    // Check if wearing 

    if (element == ELEMENT_LIGHT && hasItem(ITEM_BLIND))
    {
	return false;
    }

    if (src && src->isAvatar() && src != this)
    {
	if (!myAngryWithAvatar && getAI() == AI_PEACEFUL)
	{
	    formatAndReport("%S <grow> angry with %o!", src);
	    myAngryWithAvatar = true;
	}
	// Reset our AI target to the source so we don't pick
	// up the arrow that just hit us.
	myTarget = src->pos();
    }

    // Adjust damage down for armour.
    float		 multiplier = 1;
    int			 reduction = 0;

    reduction = getDamageReduction(element);

    // No armour if the attack came from within.
    // But magical elemental resistance from rings should handle this,
    // and get damage reduction already ignores non-physical elements
    // for armour...
    //if (attackstyle == ATTACKSTYLE_INTERNAL)
    //	  reduction = 0;
    
    // Check for resist/vulnerability
    if (element != ELEMENT_NONE)
    {
	if (defn().vulnerability == element)
	    multiplier += 1;
	if (defn().resistance == element)
	    multiplier -= 1;

	// Check any items...
	for (int i = 0; i < myInventory.entries(); i++)
	{
	    ITEM	*item = myInventory(i);
	    if (item->getDefinition() == ITEM_RESIST)
	    {
		if (item->element() == element)
		    multiplier -= 1;
	    }
	    if (item->getDefinition() == ITEM_VULNERABLE)
	    {
		if (item->element() == element)
		    multiplier += 1;
	    }
	}
	if (multiplier < 0)
	    multiplier = 0;
    }

    if (multiplier == 0)
    {
	formatAndReport("%S <ignore> the attack.");
	return false;
    }

    if (multiplier != 1.0f)
    {
	float		fhits = hits * multiplier;

	hits = int(fhits);
	fhits -= hits;

	// Add the round off error.
	if (rand_double() < fhits)
	    hits++;
    }

    if (hasItem(ITEM_PLAGUE))
    {
	// Take extra damage.
	hits += hits / 3;
    }

    // Add in reduction
    reduction = rand_range(0, reduction);
    hits -= reduction;
    if (hits < 0)
	hits = 0;

    if (hits >= getHP())
    {
	// If we die, but have an extra life...
	if (getExtraLives())
	{
	    formatAndReport("%S <fall> to the ground dead.");
	    formatAndReport("%S <glow> brightly and <return> to life!");
	    if (isAvatar())
	    {
		text_registervariable("MOB", getName());
		glbEngine->popupText(text_lookup("death", "Another Chance"));
	    }
	    // All flags are reset with our new life, both bonus an malus.
	    loseTempItems();

	    myExtraLives--;
	    gainHP(getMaxHP() - getHP());
	    return true;
	}
	kill(src);
	return true;
    }

    // If the attack element is light, they are now blind
    if (element == ELEMENT_LIGHT)
    {
	giftItem(ITEM_BLIND);
    }

    // They lived.  They get the chance to yell.
    if (src && src->isAvatar() && isFriends(src) && !isAvatar())
    {
	// This is a free action.
	actionYell(YELL_MURDERER);
	giftItem(ITEM_ENRAGED);
    }

    // Flash that they are hit.
    if (hits && !silent)
	pos().postEvent(EVENTTYPE_FOREBACK, ' ', ATTR_HEALTH);

    HEALTHLEVEL_NAMES oldstatus = healthStatus();
    myHP -= hits;
    if (oldstatus != healthStatus())
	formatAndReport("%S <be> now %o.", healthStatusDescr());

    return false;
}

void
MOB::doDeathDrop(bool allworlds)
{
    // Acquire any loot.
    int		gold = 0;
    if (defn().loot >= 0)
	gold = rand_choice(defn().loot+1);
    if (gold)
    {
	gainGold(gold);
    }

    // Drop any stuff we have.
    int i;
    for (i = 0; i < myInventory.entries(); i++)
    {
	myInventory(i)->setEquipped(false);
	myInventory(i)->setWielded(false);
	if (myInventory(i)->isFlag())
	    delete myInventory(i);
	else
	    myInventory(i)->move(pos());
    }
    myInventory.clear();

    if (allworlds)
    {
	WORLD_NAMES	curworld = pos().world();
	WORLD_NAMES	world;
	FOREACH_WORLD(world)
	{
	    if (world == curworld)
		continue;
	    if (!myWorldStash[world])
		continue;
	    MOB 	*mob = myWorldStash[world];
	    for (i = 0; i < mob->myInventory.entries(); i++)
	    {
		mob->myInventory(i)->setEquipped(false);
		mob->myInventory(i)->setWielded(false);
		if (mob->myInventory(i)->isFlag() ||
		    mob->myInventory(i)->isStartingItem())
		{
		    delete mob->myInventory(i);
		}
		else
		{
		    // This item was in the wrong world...
		    mob->myInventory(i)->transferToWorld(world, pos().map());
		    mob->myInventory(i)->move(pos());
		}
	    }
	    mob->myInventory.clear();
	}
    }
}

void
MOB::kill(MOB *src)
{
    // Ensure we are dead to alive()
    myHP = 0;
    myNumDeaths++;
    // Record for inverse monster memory.
    edefn().totalkillsever++;
    // Rather mean.
    myMP = 0;

    // Death!
    if (src)
	src->formatAndReport("%S <kill> %O!", this);
    else
	formatAndReport("%S <be> killed!");

    // If there is a source, and they are swallowed,
    // they are released.
    if (src && src->isSwallowed())
    {
	src->setSwallowed(false);
    }
    
    // If we are the avatar, special stuff happens.
    if (isAvatar())
    {
	// never update on this thread!
	// Swap out someone else to be our leader!
	// Only if everyone is gone do we fail

	// msg_update();
	// TODO: Pause something here?
    }
    else
    {
	doDeathDrop(true);
    }

    // Really should be assigned to the source, but most creatures
    // don't fight and you should get exp for things you kill
    // indirectly...
    // We don't want to get experience for killing ourselves though,
    // despite it being quite an experience.  As if you also gain a level
    // at the same time you'll then insta-revive.
    if (getAvatar() && !isAvatar())
    {
	// Assign exp based on target's level.
	// Because the same number of mobs is spread out we give a boost
	// to exp here.
	getAvatar()->gainExp( 2 * (10 + defn().depth + getLevel()), /*silent=*/ false );
	// getAvatar()->gainExp( 100 + defn().depth + getLevel(), /*silent=*/ false );
    }

    if (src && src->isAvatar())
    {
	// Award our score.

    }

    // Flash the screen
    pos().postEvent(EVENTTYPE_FOREBACK, ' ', ATTR_INVULNERABLE);

    // Note that avatar doesn't actually die...
    if (!isAvatar())
    {
	// Anyone who witnessed this can yell.
	if (src && src->isAvatar() && isFriends(src))
	{
	    MOBLIST		allmobs;

	    pos().map()->getAllMobs(allmobs);

	    for (int i = 0; i < allmobs.entries(); i++)
	    {
		if (allmobs(i)->pos().isFOV() && allmobs(i) != this &&
		    !allmobs(i)->isAvatar() &&
		    allmobs(i)->alive() &&
		    allmobs(i)->isFriends(src))
		{
		    allmobs(i)->actionYell(YELL_MURDERER);
		    allmobs(i)->giftItem(ITEM_ENRAGED);
		    break;
		}
	    }
	}

	// Don't actually delete, but die.
	MAP		*map = pos().map();

	{
	    if (rand_chance(defn().corpsechance))
	    {
		ITEM	*corpse = ITEM::create(ITEM_CORPSE);
		corpse->setMobType(getDefinition());

		corpse->move(pos());
	    }
	}

	myPos.removeMob(this);
	map->addDeadMob(this);
	clearAllPos();
	loseTempItems();
    }
    else
    {
	// Either the avatar, or someone in the party dies.
	// If it is the avatar it is the main player so we have more
	// work to do.
	bool	avatardeath = isAvatar();

	// Make sure we drop our blind attribute..
	loseTempItems();

	// End any meditation
	myMeditatePos = POS();

	// No matter what, the source sees it (we may be meditating)
	if (src && avatardeath)
	    src->mySawVictory = true;

	if (pos().isFOV() && avatardeath)
	{
	    MOBLIST		allmobs;

	    pos().map()->getAllMobs(allmobs);

	    for (int i = 0; i < allmobs.entries(); i++)
	    {
		if (allmobs(i)->pos().isFOV())
		    allmobs(i)->mySawVictory = true;
	    }
	}

	// Now activate the next party member if we have more than one.
	MAP *map = pos().map();
	POS  thispos = pos();

	{
	    if (rand_chance(defn().corpsechance))
	    {
		ITEM	*corpse = ITEM::create(ITEM_CORPSE);
		corpse->setMobType(getDefinition());

		corpse->move(thispos);
	    }
	}

	// Total party kill and no one in town.
	// We have to make the dead avatar active in the party
	// so that findAvatar() will locate a dead avatar
	// and we trigger the end conditions.
    }
}

VERB_PERSON
MOB::getPerson() const
{
    if (isAvatar())
	return VERB_YOU;

    return VERB_IT;
}

bool
MOB::isFriends(const MOB *other) const
{
    if (other == this)
	return true;

    if (hasItem(ITEM_ENRAGED) || other->hasItem(ITEM_ENRAGED))
	return false;
    
    if (isAvatar())
    {
	if (other->defn().isfriendly)
	    return true;
	else
	    return false;
    }

    // Friendly hate non friendly, vice versa.
    return defn().isfriendly == other->defn().isfriendly;
}

AI_NAMES
MOB::getAI() const
{
    return defn().ai;
}

void
MOB::reportSquare(POS t, POS oldpos)
{
    if (!isAvatar())
	return;

    if (glbConfig->myVerbose && oldpos.metaData() != t.metaData())
    {
	CHAMBER_NAMES	chamber = t.chamber();
	if (oldpos.depth() != t.depth())
	{
	    // Reported by map.
	}
	else if (chamber != CHAMBER_NONE)
	    formatAndReport("%S <enter> %o.",
			glb_chamberdefs[chamber].descr);
    }

    if (t.defn().describe || glbConfig->myVerbose)
    {
	formatAndReport("%o.", t.defn().legend);
    }

    bool	isblind = hasItem(ITEM_BLIND);

    if (t.mob() && t.mob() != this)
    {
	if (isblind)
	    formatAndReport("%S <feel> %O.", t.mob());
	else
	    formatAndReport("%S <see> %O.", t.mob());
    }
    if (t.item())
    {
	ITEMLIST	itemlist;

	t.allItems(itemlist);
	if (itemlist.entries())
	{
	    BUF		msg;
	    if (isblind)
		msg.strcpy("%S <feel> ");
	    else
		msg.strcpy("%S <see> ");
	    for (int i = 0; i < itemlist.entries(); i++)
	    {
		if (i)
		{
		    if (i == itemlist.entries()-1)
			msg.strcat(" and ");
		    else
			msg.strcat(", ");
		}

		msg.strcat(itemlist(i)->getArticleName());
	    }

	    msg.strcat(".");
	    formatAndReport(msg);
	}
    }
}

bool
MOB::shouldPush(MOB *victim) const
{
    // Global rank:
    return true;
}

void
MOB::meditateMove(POS t, bool decouple)
{
    POS oldpos = myMeditatePos;
    myMeditatePos = t;
    myDecoupledFromBody = decouple;
    // Don't spam because we have no cursor.
    // reportSquare(t, oldpos);
}

bool
MOB::actionBump(int dx, int dy)
{
    MOB		*mob;
    POS		 t;

    // Stand in place.
    if (!dx && !dy)
    {
	stopRunning();
	return true;
    }

    if (isMeditating())
    {
	stopRunning();
	// We are free of our body!
	t = myMeditatePos.delta(dx, dy);
	if (t.defn().ispassable)
	{
	    meditateMove(t);
	    return true;
	}
	// Wall slide...
	if (dx && dy && isAvatar() && ENABLE_WALLSLIDE)
	{
	    // Try to wall slide, cause we are pretty real time here
	    // and it is frustrating to navigate curvy passages otherwise.
	    t = myMeditatePos.delta(dx, 0);
	    if (!rand_choice(2) && t.defn().ispassable)
	    { meditateMove(t); return true; }

	    t = myMeditatePos.delta(0, dy);
	    if (t.defn().ispassable)
	    { meditateMove(t); return true; }

	    t = myMeditatePos.delta(dx, 0);
	    if (t.defn().ispassable)
	    { meditateMove(t); return true; }
	}
	else if ((dx || dy) && isAvatar() && ENABLE_WALLSLIDE)
	{
	    // If we have
	    // ..
	    // @#
	    // ..
	    // Moving right we want to slide to a diagonal.
	    int		sdx, sdy;

	    // This bit of code is too clever for its own good!
	    sdx = !dx;
	    sdy = !dy;

	    t = myMeditatePos.delta(dx+sdx, dy+sdy);
	    if (!rand_choice(2) && t.defn().ispassable)
	    { meditateMove(t); return true; }

	    t = myMeditatePos.delta(dx-sdx, dy-sdy);
	    if (t.defn().ispassable)
	    { meditateMove(t); return true; }

	    t = myMeditatePos.delta(dx+sdx, dy+sdy);
	    if (t.defn().ispassable)
	    { meditateMove(t); return true; }
	}
	return false;
    }

    t = pos().delta(dx, dy);

    // If we are swallowed, we must attack.
    if (isSwallowed())
	return actionMelee(dx, dy);
    
    mob = t.mob();
    if (mob)
    {
	// Either kill or chat!
	if (mob->isFriends(this))
	{
	    if (isAvatar())
		return actionChat(dx, dy);
	}
	else
	{
	    return actionMelee(dx, dy);
	}
    }

    // No mob, see if we can move that way.
    // We let actionWalk deal with unable to move notification.
    return actionWalk(dx, dy);
}

bool
MOB::actionMeditate()
{
    stopRunning();
    if (isMeditating())
    {
	formatAndReport("%S <stop> meditating.");
	myMeditatePos = POS();
	// we don't want the user to be able to use up a fast
	// turn by doing this!
	PHASE_NAMES		phase;

	phase = map()->getPhase();
	if (phase == PHASE_FAST || phase == PHASE_QUICK)
	{
	    mySkipNextTurn = true;
	}
    }
    else
    {
	if (pos().tile() != TILE_MEDITATIONSPOT)
	{
	    formatAndReport("This location is not tranquil enough to support meditation.");
	    return false;
	}
	formatAndReport("%S <close> %r eyes and meditate.");
    }
    return true;
}

void
MOB::searchOffset(int dx, int dy, bool silent)
{
    POS		square = pos().delta(dx, dy);

    // If the square isn't seen, we can't search it.
    if (!square.isFOV())
	return;

    if (square.isTrap())
    {
	TRAP_NAMES		trap = (TRAP_NAMES) rand_choice(NUM_TRAPS);

	formatAndReport("%S find %O and disarm it.", glb_trapdefs[trap].name);
	square.clearTrap();

	square.postEvent((EVENTTYPE_NAMES)(EVENTTYPE_ALL | EVENTTYPE_LONG), 
			glb_trapdefs[trap].sym,
			glb_trapdefs[trap].attr);
    }
    else if (square.tile() == TILE_SECRETDOOR)
    {
	formatAndReport("%S <reveal> a secret door.");
	square.setTile(TILE_DOOR_CLOSED);
	square.postEvent(EVENTTYPE_FOREBACK, '+', ATTR_SEARCH);
    }
    else
    {
	if (!silent)
	    square.postEvent(EVENTTYPE_FOREBACK, ' ', ATTR_SEARCH);
    }
}

bool
MOB::actionTeleport(bool force)
{
    // Ideally checks teleport control.
    // Force means you can't cancel, and it doesn't matter if you 
    // have teleport ability.
    if (!pos().rawRoom())
	return false;

    formatAndReport("%S <vanish> with a pop.");

    POS		np = pos().rawRoom()->getRandomPos(pos(), nullptr);
    // Avoid rotating..
    np.setAngle(pos().angle());
    
    // No longer swallowed!
    setSwallowed(false);

    move(np);

    formatAndReport("%S <pop> into existence.");
    return true;
}

bool
MOB::actionSearch(bool silent)
{
    stopRunning();
    mySearchPower++;
    if (!silent)
	formatAndReport("%S <search>.");

    // Clamp search amount.
    if (mySearchPower > 4)
	mySearchPower = 1;

    int		ir, r;

    r = mySearchPower;
    // Note the < r to avoid double counting corners.
    for (ir = -r; ir < r; ir++)
    {
	searchOffset(ir, -r, silent);
	searchOffset(r, ir, silent);
	searchOffset(-ir, r, silent);
	searchOffset(-r, -ir, silent);
    }

    return true;
}

bool
MOB::actionDropButOne(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    ITEM		*butone;

    butone = splitStack(item);
    J_ASSERT(butone != item);

    if (butone == item)
    {
	addItem(butone);
	return false;
    }

    // Swap the two meanings
    butone->setStackCount(item->getStackCount());
    item->setStackCount(1);

    formatAndReport("%S <drop> %O.", butone);

    // And drop
    butone->move(pos());

    return true;
}

bool
MOB::actionDrop(ITEM *item, int limitdrop)
{
    stopRunning();
    if (!item)
	return false;

    if (item->isWielded())
    {
	formatAndReport("%S cannot drop wielded items.");
	return true;
    }
    if (item->isEquipped() && !item->isRanged())
    {
	formatAndReport("%S cannot drop worn items.");
	return true;
    }
    if (item->isEquipped() && item->isRanged())
    {
	formatAndReport("%S cannot drop readied items.");
	return true;
    }

    // Drop any stuff we have.
    int i;
    bool	fail = true;
    for (i = 0; i < myInventory.entries(); i++)
    {
	// Ignore special case items..
	// We don't want to drop "blindness" :>
	if (myInventory(i)->isFlag())
	    continue;

	if (myInventory(i) == item)
	{
	    if (limitdrop && limitdrop < item->getStackCount())
	    {
		ITEM	*drop = splitStack(item, limitdrop);
		formatAndReport("%S <drop> %O.", drop);
		drop->move(pos());
	    }
	    else
	    {
		formatAndReport("%S <drop> %O.", myInventory(i));
		myInventory(i)->setEquipped(false);
		myInventory(i)->setWielded(false);
		myInventory(i)->move(pos());
		myInventory.set(i, 0);
	    }
	    // Quit as we found something.
	    fail = false;
	    break;
	}
    }
    myInventory.collapse();

    updateEquippedItems();

    if (fail)
	formatAndReport("%S <drop> nothing.");

    return true;
}

bool
MOB::actionQuaffTop()
{
    stopRunning();
    ITEM		*item = getTopBackpackItem();
    if (item)
	actionQuaff(item);
    else
	formatAndReport("%S <fiddle> with %r backpack.");
    return true;
}

bool
MOB::actionQuaff(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;
    if (!item->isPotion())
    {
	formatAndReport("%S cannot drink %O.", item);
	return false;
    }

    item = splitStack(item);

    doEmote("*quaff*");
    formatAndReport("%S <quaff> %O.", item);

    bool		interesting = false;

    interesting = item->potionEffect() != EFFECT_NONE;
    applyEffect(this, item->potionEffect(),
		    ATTACKSTYLE_INTERNAL);

    if (item->asPotion() == POTION_SLEEP)
        interesting |= putToSleep(false,/*dc*/ 0, 15);

    if (interesting)
    {
	item->markMagicClassKnown();
    }
    else
    {
	formatAndReport("Nothing happens.");
    }


    delete item;

    return true;
}

int
MOB::rollStrengthPoints() const
{
    int points = 0;
    // 4 / 25 is a good starter.
    for (int attempts = 5; attempts --> 0; )
	points += rand_chance(34 + getLevel());

    return points;
}

bool
MOB::actionRead(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if (hasItem(ITEM_BLIND))
    {
	formatAndReport("%S <be> blind so cannot read.");
	return true;
    }

    if (item->isSpellbook())
    {
        if (role() != ROLE_WIZARD)
        {
            formatAndReport("%S <lack> the training to learn magical spells.");
            return true;
        }

	doEmote("*studies*");
	formatAndReport("%S <read> %O.", item);

	// These are self named now.
	item->markMagicClassKnown();

	SPELL_NAMES spell = item->asSpell();
	if (isSpellMemorized(spell))
	{
	    formatAndReport("%S <refresh> %r memory.");
	}
	else if (numSpellsMemorized() >= maxSpellSlots())
	{
	    formatAndReport("%S must forget a spell first.");
	}
	else
	{
	    // Attempt to learn it.
	    // We build some spell points and see if it exceeds difficulty.
	    int		chance = learnChance(spell);
	    chance += 5 * item->getBonus();
	    if (rand_chance(chance))
	    {
		formatAndReport("%S <gain> understanding of %o.",
                        GAMEDEF::spelldef(spell)->name);
		memorizeSpell(spell);
		if (rand_chance(10 - item->getBonus()))
		{
		    item = splitStack(item);
		    item->fadeWriting();
		    addItem(item);
		}
	    }
	    else
	    {
		if (chance >= 50)
		    formatAndReport("%S almost <grasp> the runes.");
		else if (chance >= 40)
		    formatAndReport("%S <find> the runes challenging.");
		else if (chance >= 30)
		    formatAndReport("%S <be> confounded by the runes."); 
		else if (chance >= 20)
		    formatAndReport("%S cannot make any sense of the runes."); 
		else
		    formatAndReport("%S <find> the runes impenetrable."); 
	    }
	}
    }
    else if (item->isScroll())
    {
	doEmote("*read*");
	formatAndReport("%S <read> %O.", item);
	item = splitStack(item);

	// These are self named now.
	bool cansee = false;
	if (pos().isFOV() || isAvatar())
	{
	    cansee = true;
	}

	// Check if we screw up!
	SCROLL_NAMES	scroll = item->asScroll();
	if (role() == ROLE_FIGHTER)
	{
	    if (rand_chance(50))
	    {
		formatAndReport("%S <mispronounce> the magic runes!");
		scroll = (SCROLL_NAMES) (rand_choice(NUM_SCROLLS-1)+1);

		// This doesn't tell you the base type!
		cansee = false;
	    }
	}

	switch (scroll)
	{
	    case SCROLL_FIRE:
	    {
		EFFECT_OP	op(this, EFFECT_SCROLL_FIRE, true);

		if (cansee)
		    item->markMagicClassKnown();
		delete item;
		POS vpos = pos();
		vpos.reportFireball(2, "column of fire erupts");
		vpos.fireball(this, 2,
				'*', ATTR_FIRE,
				[&](POS p) -> bool
                                {
                                    p.burnSquare();
                                    return op(p);
                                });
		return true;
	    }

	    case SCROLL_IDENTIFY:
	    {
		if (isAvatar() && hasAnyItems())
		{
		    formatAndReport("%S <gain> understanding of %r possessions.");
		    for (auto && item : inventory())
		    {
			item->markMagicClassKnown();
			item->markBonusKnown();
		    }
		    if (cansee)
			item->markMagicClassKnown();
		}
		else
		    formatAndReport("Nothing happens.");
		break;
	    }

	    case SCROLL_TELEPORT:
	    {
		formatAndReport("%S <teleport>.");
		if (cansee)
		    item->markMagicClassKnown();
		actionTeleport(/*force*/ true);
		break;
	    }

	    case SCROLL_ENCHANT_WEAPON:
	    {
		ITEM *target = lookupWeapon();
		if (target)
		{
		    if (!target->enchant(1))
		    {
			target = splitStack(target);
			delete target;
		    }
		    if (cansee)
			item->markMagicClassKnown();
		}
		else
		    formatAndReport("Nothing happens.");
		break;
	    }
	    case SCROLL_ENCHANT_ARMOUR:
	    {
		ITEM *target = lookupArmour();
		if (target)
		{
		    if (!target->enchant(1))
		    {
			target = splitStack(target);
			delete target;
		    }
		    if (cansee)
			item->markMagicClassKnown();
		}
		else
		    formatAndReport("Nothing happens.");
		break;
	    }
	    case SCROLL_MAP:
	    {
		if (isAvatar())
		{
		    formatAndReport("A map forms in %r mind.");
		    pos().mapCurrentLevel();
		    if (cansee)
			item->markMagicClassKnown();
		}
		else
		    formatAndReport("Nothing happens.");
		break;
	    }

	    case SCROLL_SUMMON:
	    {
		// Attempt to summon.
		POS 		np = pos().randAdjacent();
		if (np.valid())
		{
		    if (cansee)
			item->markMagicClassKnown();
		    MOB *summon = createNPC(pos().depth() + /*mean!*/ 4,
					    pos().world());
		    if (summon)
		    {
			formatAndReport("%S <summon> %O to %r side.", summon);
			summon->move(np);
		    }
		    else
			formatAndReport("The air shudders."); // Shoudln't happen
		}
		else
		    formatAndReport("Nothing happens.");
		break;
	    }
	}

	delete item;
    }
    else
    {
	formatAndReport("%S <see> no writing on %O.", item);
	return false;
    }

    return true;
}

bool
MOB::actionThrowTop(int dx, int dy)
{
    ITEM *item = getTopBackpackItem();

    if (!item)
    {
	formatAndReport("%S <have> nothing to throw.");
	return true;
    }

    return actionThrow(item, dx, dy);
}

bool
MOB::actionThrow(ITEM *item, int dx, int dy)
{
    stopRunning();

    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("%S <do> not have enough room inside here.");
	return true;
    }

    if (!item)
	return false;

    // Can't throw what you wear
    if (item->isEquipped() && !item->isRanged())
    {
	formatAndReport("%S cannot throw what %S <be> wearing.");
	return true;
    }

    item = splitStack(item);

    formatAndReport("%S <throw> %O.", item);
    if (item->isPotion())
    {
	bool		interesting = false;

	POTION_OP		op(this, item->getDefinition(), &interesting);

	u8			sym;
	ATTR_NAMES		attr;
	item->getLook(sym, attr);

	doRangedAttack(5, 2, dx ,dy, '~', attr, 
		      "splash", 
		      "spray of liquid bursts",
		      true, false, op);

	if (interesting)
	{
	    item->markMagicClassKnown();
	}

	delete item;

	return true;
    }
    else if (item->defn().throwable 
	    && item->size() < size()	// Can't throw things bigger than you
	    ) 
    {
	// Aerodynamic, shot and counts as melee!
	ATTACK_OP	op(this, item, nullptr, item->getMeleeAttack());

	u8		symbol;
	ATTR_NAMES	attr;
	item->getLook(symbol, attr);

	POS epos = doRangedAttack(5, 1, dx, dy,
			symbol, attr,
			GAMEDEF::attackdef(item->getMeleeAttack())->verb, "",
			true, false, op);
	item->move(epos);

	return true;
    }
#if 0
    else if (item->getDefinition() == ITEM_PORTAL_STONE)
    {
	bool		ok = false;
	if (pos().depth() == 0)
	{
	    formatAndReport("%S <throw> %O, but it fails to activate.  Perhaps it must be farther from the village.", item);
	}
	else
	{
	    if (actionPortalFire(dx, dy, 0))
	    {
		// Made a portal!
		delete item;
		return true;
	    }
	}
	// Failed to make a portal, so go back to throwing the rock.
    }
#endif
    // Unwieldly!
    // Ideally we'd still run as an attack.
    int		range = 1;
    if (size() > item->size()) range++;
    if (item->size() == SIZE_TINY) range++;

    POS	epos = pos().traceBulletPos(range, dx, dy, true, true);
    item->move(epos);

    return true;
}

bool
MOB::actionApplyTool(ITEM *item, int dx, int dy, ITEM *target)
{
    stopRunning();

    // If swallowed, rather useless.
    if (isSwallowed() && (dx || dy))
    {
	formatAndReport("%S <do> not have enough room inside here.");
	return true;
    }

    if (!item)
	return false;

    if (item->isBroken())
    {
	formatAndReport("%S cannot use %O.", item);
	return true;
    }

    int			breakchance = 0;
    bool		didsomething = false;

    POS t = pos().delta(dx, dy);

    auto pickDoor = [self = this, &breakchance](POS t, ITEM *pick) -> bool
    {
	int	chance = pick->getBonus()*10 + 10;
	int	breakrate = 20;
	if (pick->getDefinition() == ITEM_LOCKPICK)
	{
	    chance += 50;
	    breakrate = 10;
	}
        if (self->role() == ROLE_THIEF)
        {
            chance += 50;
            breakrate /= 2;
        }
	bool	success = rand_chance(chance);
	bool	acted = false;

	if (t.defn().isdoorway)
	{
	    switch (t.tile())
	    {
		case TILE_DOOR_CLOSED:
		    if (success)
		    {
			self->formatAndReport("%S <pick> the door locked.");
			t.setTile(TILE_DOOR_LOCKED);
		    }
		    else
		    {
			self->formatAndReport("%S <fail> to lock the door.");
		    }
		    breakchance = breakrate;
		    acted = true;
		    break;
		case TILE_DOOR_OPEN:
		    self->formatAndReport("This is not the lock picking lawyer.  There is no point picking an open door.");
		    acted = true;
		    break;
		case TILE_DOOR_BROKEN:
		    self->formatAndReport("The door is broken beyond picking.");
		    acted = true;
		    break;
		case TILE_DOOR_EMPTY:
		    self->formatAndReport("There is no door to pick there.");
		    acted = true;
		    break;
		case TILE_DOOR_JAMMED_UNKNOWN:
		    if (self->isAvatar())
		    {
			self->formatAndReport("%S <discover> the door is jammed!");
			t.setTile(TILE_DOOR_JAMMED);
		    }
		    // FALL THROUGH
		case TILE_DOOR_JAMMED:
		    self->formatAndReport("%S cannot pick a jammed door.");
		    acted = true;
		    break;
		case TILE_DOOR_LOCKED_UNKNOWN:
		    if (self->isAvatar())
		    {
			self->formatAndReport("%S <discover> the door is locked!");
			t.setTile(TILE_DOOR_LOCKED);
		    }
		    // FALL THROUGH
		case TILE_DOOR_LOCKED:
		    if (success)
		    {
			self->formatAndReport("%S <pick> the door unlocked.");
			t.setTile(TILE_DOOR_CLOSED);
		    }
		    else
		    {
			self->formatAndReport("%S <fail> to unlock the door.");
		    }
		    breakchance = breakrate;
		    acted = true;
		    break;
	    }
	}
	return acted;
    };

    switch (item->getDefinition())
    {
	case ITEM_LOCKPICK:
	{
	    didsomething = pickDoor(t, item);
	    break;
	}
	case ITEM_KNOWLEDGE_ORB:
	{
	    didsomething = true;
	    formatAndReport("%S <peer> into the orb...");
	    if (isAvatar())
	    {
		formatAndReport("A map forms in %r mind.");
		pos().mapCurrentLevel();
		for (auto && item : inventory())
		{
		    item->markMagicClassKnown();
		    item->markBonusKnown();
		}
	    }
	    break;
	}
	case ITEM_MIDAS_WAND:
	{
            if (!target)
            {
                formatAndReport("%S <wave> the wand in the air.  Perhaps fortunately, the air does not become gold..");
                didsomething = true;
            }
	    else if (target == item)
	    {
		formatAndReport("%S cannot point the wand at itself.");
                didsomething = true;
	    }
	    else
	    {
		// Convert to gold!
		target->setEquipped(false);
		target->setWielded(false);
		int		count = glb_sizedefs[target->size()].weight * target->getStackCount();
		formatAndReport("%S <transform> %O into gold.", target);
		count = rand_range(count/2+1, count);
		target->setDefinition(ITEM_COIN);
		// Makes for gold gold coins!
		//target->setMaterial(MATERIAL_GOLD);
		target->setStackCount(count);
		didsomething = true;
	    }
	    break;
	}
	case ITEM_DAGGER:
	{
	    ITEMLIST	items;

	    t.getAllItems(items);
	    for (auto && corpse : items)
	    {
		if (corpse->getDefinition() == ITEM_CORPSE)
		{
		    // Butcher!
		    didsomething = true;
		    breakchance = 0;
		    ITEM		*meatballs = ITEM::create(ITEM_MEATCHUNK);
		    meatballs->setStackCount(glb_sizedefs[corpse->size()].weight);

		    formatAndReport("%S <butcher> %O into chunks of meat.", corpse);
		    t.removeItem(corpse);
		    delete corpse;
		    meatballs->move(t);
		    break;
		}
	    }

	    if (didsomething)
		break;

	    didsomething = pickDoor(t, item);

	    break;
	}
	case ITEM_PICKAXE:
	{
	    if (t.isPassable())
	    {
		if (t.item())
		{
		    formatAndReport("%S <swing> at %O.",
				    t.item());
		    t.item()->shatter();
		}
		else
		{
		    formatAndReport("%S <swing> %r %O wildly.",
				    item);
		}
		didsomething = true;
	    }
	    else if (t.defn().isdiggable)
	    {
		formatAndReport("%S <swing> at %O.",
			t.defn().legend);
		breakchance = 20;
		if (t.digSquare())
		{
		    formatAndReport("%S <dig> a hole.");
		}
		else
		{
		    formatAndReport("%S <fail> to break through.");
		}
		makeNoise(4);
		didsomething = true;
	    }
	    else if (t.defn().isdoorway)
	    {
		// Must be closed or we'd be passable...
		formatAndReport("%S <swing> at %O, breaking it.",
			t.defn().legend);
		breakchance = 10;
		makeNoise(3);
		didsomething = true;
		t.setTile(TILE_DOOR_BROKEN);
	    }
	    else
	    {
		formatAndReport("%S <swing> at %O to no effect.",
			t.defn().legend);
		makeNoise(4);
		didsomething = true;
	    }
	    break;
	}
	case ITEM_WRITINGSET:
	{
            if (!target)
            {
                formatAndReport("%S <draw> nine perpendicular red lines in the air.");
                didsomething = true;
            }
            else if (role() == ROLE_FIGHTER)
            {
                formatAndReport("%S <be> illiterate so can't write anything more interesting than X.");
                didsomething = true;
            }
            else if (role() == ROLE_THIEF)
            {
                formatAndReport("%S <lack> the training to create magical texts.");
                didsomething = true;
            }
            else 
            {
                switch (target->getDefinition())
                {
                    case ITEM_SPELLBOOK_BLANK:
                    {
                        formatAndReport("%S <channel> distant planes and <write> whatever flows through the connection.");
                        SPELL_NAMES spell = (SPELL_NAMES) (rand_choice(NUM_SPELLS-1)+1);
			target = splitStack(target);
                        target->setDefinition(ITEM::lookupSpell(spell));
			formatAndReport("%S <write> %O.", target);
			addItem(target);
                        didsomething = true;
                        breakchance = 20;
                        break;
                    }
                    case ITEM_SCROLL_BLANK:
                    {
                        formatAndReport("%S <blank> %r mind and fill the page with the visions that occur..");
                        SCROLL_NAMES scroll = (SCROLL_NAMES) (rand_choice(NUM_SCROLLS-1)+1);
			target = splitStack(target);
                        target->setDefinition(ITEM::lookupScroll(scroll));
			formatAndReport("%S <write> %O.", target);
			addItem(target);
                        didsomething = true;
                        breakchance = 20;
                        break;
                    }
                }
            }
	    break;
	}
    }

    breakchance -= item->getBonus()*5;

    if (didsomething && rand_chance(breakchance))
    {
	item = splitStack(item);
	formatAndReport("%r %o breaks.", item);
	item->setBroken(true);
	addItem(item);
    }

    if (!didsomething)
    {
        if (target)
            formatAndReport("%S cannot figure out how to use %O on that.", item);
        else
            formatAndReport("%S cannot figure out how to use %O there.", item);
    }

    return true;
}

bool
MOB::actionEatTop()
{
    ITEM		*item = getTopBackpackItem();
    if (item)
	actionEat(item);
    else
	formatAndReport("%S <fiddle> with %r backpack.");
    return true;
}

bool
MOB::actionEat(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if (!item->isFood())
    {
	formatAndReport("%S cannot eat %O.", item);
	return false;
    }

    if (getFood() >= getMaxFood())
    {
	formatAndReport("%S <be> too full to eat any more.");
	return false;
    }


    if (item->pos().valid())
    {
	// We are eating something off the ground.
	item = item->pos().splitStack(item);
	formatAndReport("%S <eat> %O off the ground.", item);
    }
    else
    {
	item = splitStack(item);

	formatAndReport("%S <eat> %O.", item);
    }


    // This is basically the timeout for healing with food.
    // But everyone in the party eats
    gainFood(item->getFoodVal());

    if (item->corpseEffect() != EFFECT_NONE)
    {
	applyEffect(this, item->corpseEffect(), ATTACKSTYLE_INTERNAL);
    }

    delete item;

    return true;
}

bool
MOB::actionBreakTop()
{
    ITEM		*item = getWieldedOrTopBackpackItem();
    if (item)
	actionBreak(item);
    else
	formatAndReport("%S <fiddle> with %r backpack.");
    return true;
}

bool
MOB::actionBreak(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if ((!item->isRanged() && !item->isWeapon()) || item->isBroken())
    {
	item = splitStack(item);

	formatAndReport("%S <destroy> %O.", item);
	delete item;
	return true;
    }

    formatAndReport("%S <break> %O.", item);

    doEmote("*snap*");

    item->setBroken(true);

    return true;
}

bool
MOB::actionWearTop()
{
    stopRunning();
    ITEM *item = getTopBackpackItem();

    if (item)
    {
	actionWear(item);
    }
    else
    {
	formatAndReport("%S <fiddle> with %r backpack.");
    }
    return true;
}

bool
MOB::actionWear(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if (item->isWielded())
    {
	formatAndReport("%S must <unwield> %O before wearing it.", item);
	return true;
    }

    if (item->isArmour())
    {
        // Check limitations of new armour...
	if (!isProficientWith(item))
	{
	    formatAndReport("%S would find %O too constraining.", item);
	    return true;
        }

	ITEM		*olditem = lookupArmour();
	if (olditem)
	{
	    formatAndReport("%S <take> off %O.", olditem);
	    olditem->setEquipped(false);
	    myInventory.removePtr(olditem);
	    myInventory.append(olditem);
	}

	// If they are the same, the user wanted to toggle!
	if (olditem != item)
	{
	    item = splitStack(item);
	    formatAndReport("%S <put> on %O.", item);
	    item->setEquipped(true);
	    addItem(item);
	}
	return true;
    }

    if (!item->isRing())
    {
	formatAndReport("%S cannot wear %O.", item);
	return false;
    }

    ITEM		*oldring = lookupRing();

    if (oldring)
    {
	formatAndReport("%S <take> off %O.", oldring);
	oldring->setEquipped(false);
	myInventory.removePtr(oldring);
	myInventory.append(oldring);
    }

    // If they are the same, the user wanted to toggle!
    if (oldring != item)
    {
	item = splitStack(item);
	formatAndReport("%S <put> on %O.", item);
	item->setEquipped(true);
	addItem(item);
    }

    return true;
}

bool
MOB::actionReadyRanged(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if (item->isRanged())
    {
	if (!isProficientWith(item))
	{
	    formatAndReport("%S <be> not trained with %O.", item);
	    return true;
        }
	if (item->isWielded())
	{
	    // This is a bit odd, but is due to our flag management :<
	    formatAndReport("%S must <unwield> %O before readying it.", item);
	    return true;
	}
	ITEM		*olditem = lookupWand();
	if (olditem)
	{
	    formatAndReport("%S <put> away %O.", olditem);
	    olditem->setEquipped(false);
	    myInventory.removePtr(olditem);
	    myInventory.append(olditem);
	}

	// If they are the same, the user wanted to toggle!
	if (olditem != item)
	{
	    item = splitStack(item);
	    formatAndReport("%S <ready> %O for firing.", item);
	    item->setEquipped(true);
	    addItem(item);
	}
	return true;
    }

    formatAndReport("%S cannot ready %O for ranged combat.", item);
    return false;
}

bool
MOB::actionWield(ITEM *item)
{
    stopRunning();
    if (!item)
	return false;

    if (item->isFlag())	// should be guarded earlier.
	return false;

    if (item->isEquipped()) // Can't wield that you are wearing.
    {
	formatAndReport("%S must <remove> %O before wielding.", item);
	return true;
    }

    // You can wield anything...
    ITEM		*olditem = lookupWeapon();

    if (olditem)
    {
	formatAndReport("%S <unwield> %O.", olditem);
	olditem->setWielded(false);
	// Move to top of our list.
	myInventory.removePtr(olditem);
	myInventory.append(olditem);
    }

    // If they are the same, the user wanted to toggle!
    if (olditem != item)
    {
	item = splitStack(item);
	formatAndReport("%S <wield> %O.", item);
	item->setWielded(true);
	// Check if you are proficient...
	if (!isProficientWith(item))
	{
	    formatAndReport("%S <be> not trained with %O.", item);
	}
	addItem(item);
    }
    return true;
}

const char *
getYellMessage(YELL_NAMES yell)
{
    const char *yellmsg = "I KNOW NOT WHAT I'M DOING!";

    switch (yell)
    {
	case YELL_KEEPOUT:
	{
	    const char *msg[] =
	    {		// KEEPOUT
		"Keep out!",
		"Away!",
		"Leave!",
		"No Farther!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_MURDERER:
	{
	    const char *msg[] =
	    {		// MURDERER
		"Killer!",
		"Murderer!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_INVADER:
	{
	    const char *msg[] =
	    {		// INVADER
		"Invader!",
		"ALARM!",
		"ALARM!",
		"Code Yellow!",	// For yummy gold.
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_RANGED:
	{
	    const char *msg[] =
	    {		// RANGED
		"Far Threat!",
		"Code Bow!",	// Kobolds not that imaginative after all
		"Incoming!",
		"Archers!",	
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_KILL:
	{
	    const char *msg[] =
	    {		// KILL
		"Kill it now!",
		"Code Gold!",
		"Kill!",
		"Eviscerate It!",
		"Kill!",
		"Kill!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_KILLCHASE:
	{
	    const char *msg[] =
	    {		// KILL
		"No Mercy!",
		"Code Blood!",
		"Hunt It Down!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_LOCATION:
	{
	    const char *msg[] =
	    {		// LOCATION
		"It's here!",
		"Foe Sighted!",
		"Over here!",
		"I see it!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_HEARDLOCATION:
	{
	    const char *msg[] =
	    {		// LOCATION_HEARD 
		"It's there!",
		"Foe Located!",
		"Over there!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_TAUNT:
	{
	    const char *msg[] =
	    {		// LOCATION_HEARD 
		"Coward!",
		"You Flea!",
		"Mendicant!",
		"Wimp!",
		"Fool!",
		"Urchin!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
	case YELL_VICTORY:
	{
	    const char *msg[] =
	    {		// LOCATION_HEARD 
		"Victory!",
		"The @ is dead!",
		"Huzzah!",
		"Hooray!",
		"Gold!",
		0
	    };
	    yellmsg = rand_string(msg);
	    break;
	}
    }
    return yellmsg;
}

bool
MOB::actionYell(YELL_NAMES yell)
{
    stopRunning();
    const char	*yellflavour[] =
    {
	"%S <yell>: \"%o\"",
	"%S <shout>: \"%o\"",
	"%S <curse>: \"%o\"",
	0
    };
    const char *yellmsg = getYellMessage(yell);

#if 1
    if (pos().isFOV())
    {
	// Send the shout out.
	msg_format(rand_string(yellflavour), this, yellmsg);
	doShout(yellmsg);
    }
#else
    MOBLIST	hearmobs;

    // Should use spatial search!
    for (int i = 0; i < hearmobs.entries(); i++)
    {
	if (hearmobs(i)->isAvatar())
	{
	    // Don't use line of sight, but the same logic we use.
	    // Thus can't use format and report!
	    msg_format(rand_string(yellflavour), this, yellmsg);
	    doShout(yellmsg);
	}
	if (hearmobs(i) == this)
	    continue;
	hearmobs(i)->myHeardYell[yell] = true;
	if (hearmobs(i)->pos().roomId() == pos().roomId())
	{
	    // Extra flag to avoid spurious re-shouts
	    hearmobs(i)->myHeardYellSameRoom[yell] = true;
	}
    }
#endif

    return true;
}

bool
MOB::actionRotate(int angle)
{
    myPos = myPos.rotate(angle);
    if (isMeditating())
	myMeditatePos = myMeditatePos.rotate(angle);
    // This is always a free action.
    return false;
}

bool
MOB::actionSuicide()
{
    const char *attempt_notes[] =
    {
	"You hold your breath until you fall unconscious.  Blearily, you wake up.",
	"You bash your head against the ground.  Sanity barely intervenes.",
	"It is claimed it is physically impossible to swallow your tongue.  You give it a good try, but fail.",
	"Your fingernails seem not quite enough to open your cartoid artery.",
	"Only after casting the sphere of death spell do you recall it isn't a ranged spell.  Thankfully you got some of the words wrong, and it doesn't hit you fully.",
	"You close your eyes and empty your mind.  You commend all but a spark to the void.",
	"You curse the gods for your situation.  They decide to give you a quick reminder of why they deserve respect.",
	0
    };

    const char *death_notes[] =
    {
	"Through intense concentration, you manage to stop the beating of your heart!  You die.",
	"You hold your breath until you fall unconscious.  You don't wake up.",
	"You bash your head against the ground.  Until you can't.",
	"It is claimed it is physically impossible to swallow your tongue.  You manage to prove them wrong.  You choke to death.",
	"It takes a while, but your fingernails can suffice to open your cartoid artery.  You die.",
	"Only after casting the sphere of death spell do you recall it isn't a ranged spell.",
	"You close your eyes and empty your mind.  You succeed.  You are dead.",
	"You curse the gods for your situation.  They hear, and are not impressed.  You are killed by divine wrath.",
	"You wrap your hands to opposite sides of your head.  A quick jerk, a snap, and your neck is broken.",
	0
    };

    if (getHP() < getMaxHP() / 2 || myHasTriedSuicide)
    {
	formatAndReport(rand_string(death_notes));

	gainHP(-getHP());
	return true;
    }
    myHasTriedSuicide = true;
    formatAndReport(rand_string(attempt_notes));
    gainHP(-getHP()+1);
    return true;
}

bool
MOB::actionClimb()
{
    stopRunning();
    TILE_NAMES		tile;

    tile = pos().tile();

    if (tile == TILE_DOWNSTAIRS || tile == TILE_MEDITATIONSPOT)
    {
	// Climbing is interesting.
	myFleeCount = 0;
	clearBoredom();

	if (isAvatar())
	{
	    if (pos().depth() == GAMEDEF::rules().bosslevel)
	    {
		if (hasItem(glb_worlddefs[pos().world()].macguffin))
		{
		    formatAndReport("%S <escape> the %o!", glb_worlddefs[pos().world()].name );
		    myHasWon = true;
		}
		else
		{
		    formatAndReport("%S <will> rather be damned than return empty handed!");
		}
	    }
	    else
	    {
		formatAndReport("%S <climb> down...");
		pos().map()->moveAvatarToDepth(pos().depth()+1);
	    }
	    // Don't use time so you have a chance
	    // to move on new level.
	    return false;
	}
	else
	{
	    if (pos().depth() < GAMEDEF::rules().bosslevel)
	    {
		formatAndReport("%S <climb> down.");
		if (pos().map()->moveMobToDepth(this, pos().depth()+1))
		    formatAndReport("%S <climb> down.");
		return true;
	    }
	    else
	    {
		formatAndReport("%S <recoil> from the bright sun.");
		return false;
	    }
	}
    }
    else if (tile == TILE_UPSTAIRS)
    {
	if (pos().depth() <= 1)
	{
	    if (isAvatar())
	    {
		if (hasItem(glb_worlddefs[pos().world()].macguffin))
		{
		    formatAndReport("%S <escape> the %o!", glb_worlddefs[pos().world()].name );
		    myHasWon = true;
		}
		else
		{
		    formatAndReport("%S <will> rather be damned than return empty handed!");
		}
	    }
	    else
	    {
		formatAndReport("%S <recoil> from the bright sun.");
	    }
	    return false;
	}
	if (isAvatar())
	{
	    // Not done yet
	    formatAndReport("%S <climb> up...");
	    pos().map()->moveAvatarToDepth(pos().depth()-1);
	    // Don't use time so you have a chance
	    // to move on new level.
	    return false;
	}
	else
	{
	    formatAndReport("%S <climb> up.");
	    if (pos().map()->moveMobToDepth(this, pos().depth()-1))
		formatAndReport("%S <climb> up.");
	    return true;
	}
    }

    formatAndReport("%S <see> nothing to climb here.");

    return true;
}

bool
MOB::actionChat(int dx, int dy)
{
    stopRunning();
    MOB		*victim;

    // This should never occur, but to avoid
    // embarassaments...
    if (!isAvatar())
	return false;
    
    victim = pos().delta(dx, dy).mob();
    if (!victim)
    {
	// Talk to self
	formatAndReport("%S <talk> to %O!", this);
	return false;
    }

    formatAndReport("%S <chat> with %O.", victim);

    return true;
}

bool
MOB::actionMelee(int dx, int dy)
{
    stopRunning();
    MOB		*victim;

    // If we are swallowed, attack the person on our square.
    if (isSwallowed())
	dx = dy = 0;

    POS		 t = pos().delta(dx, dy);

    victim = t.mob();
    if (!victim)
    {
	// Swing in air!
	formatAndReport("%S <swing> at empty air!");
	return false;
    }

    if (!victim->alive())
    {
	// The end GAme avatar, just silently ignore
	J_ASSERT(!isAvatar());
	return false;
    }

    // Shooting is interesting.
    clearBoredom();
 
    int		damage;
    bool	victimdead;

    damage = 0;
    if (evaluateMeleeWillHit(victim))
	damage = evaluateMeleeDamage();

    if (victim && victim->hasItem(ITEM_ASLEEP))
	damage *= 2;

    // attempt to kill victim
    if (damage)
    {
	formatAndReport("%S %v %O.", getMeleeVerb(), victim);
	ITEM *weapon = lookupWeapon();
	int		noise = 1;		// min noise to hit.
	if (weapon)
	    noise = MAX(noise, weapon->defn().noise);
	makeNoise(noise);
    }
    else
    {
	formatAndReport("%S <miss> %O.", victim);
	return true;
    }

    MOB_NAMES	victimtype = victim->getDefinition();

    victimdead = victim->applyDamage(this, damage,
				    getMeleeElement(),
				    ATTACKSTYLE_MELEE);
    
    // Vampires regenerate!
    if (defn().isvampire)
    {
	gainHP(damage);
    }

    // Grant our item...
    if (!victimdead)
    {
	if (attackdefn().effect != EFFECT_NONE)
	{
	    if (victim->applyEffect(this, attackdefn().effect,
				    ATTACKSTYLE_MELEE))
		return true;
	}

	// Swallow the victim
	// Seems silly to do this on a miss.
	// You also can't swallow people when you yourself are swallowed.
	if (!isSwallowed() && defn().swallows && damage)
	{
	    victim->setSwallowed(true);
	    victim->move(pos(), true);
	}

	// Steal something
	if (defn().isthief)
	{
	    ITEM		*item;
	    
	    // Get a random item from the victim
	    // Don't steal equipped items as that is too deadly for
	    // most item-levelling systems.
	    item = victim->getRandomItem(false);
	    if (item) 
	    {
		formatAndReport("%S <steal> %O.", item);
		doEmote("*yoink*");
		// Theft successful.
		myFleeCount += 30;
		victim->removeItem(item, true);
		item->setEquipped(false);
		item->setWielded(false);
		addItem(item);
	    }
	}
    }

    // Chance to break our weapon.
    {
	ITEM	*w;

	w = lookupWeapon();
	if (w)
	{
	    if (rand_chance(w->breakChance(victimtype)))
	    {
		formatAndReport("%r %o breaks.", w);
		doEmote("*snap*");
		w->setBroken(true);
	    }
	}
    }
    return true;
}

ATTACK_NAMES
MOB::getMeleeAttack() const
{
    ITEM	*w;

    w = lookupWeapon();
    if (!w)
	return defn().melee_attack;

    return w->getMeleeAttack();
}

int
MOB::getMeleeCanonicalDamage() const
{
    ITEM	*weap;

    weap = lookupWeapon();

    int		base = attackdefn().damage.buildDPDF().expectedValueRounded();
    base += weap->getBonus();
    return base;
}

const char *
MOB::getMeleeVerb() const
{
    return attackdefn().verb;
}

ELEMENT_NAMES
MOB::getMeleeElement() const
{
    ATTACK_NAMES	attack = getMeleeAttack();

    return GAMEDEF::attackdef(attack)->element;
}

const char *
MOB::getMeleeWeaponName() const
{
    ITEM	*w;

    w = lookupWeapon();
    if (!w)
	return attackdefn().noun;

    return w->defn().name;
}

int
MOB::evaluateMeleeDamage() const
{
    ATTACK_NAMES	attack = getMeleeAttack();

    return evaluateAttackDamage(attack, lookupWeapon(), nullptr);
}

bool
MOB::evaluateMeleeWillHit(MOB *victim) const
{
    ATTACK_NAMES	attack = getMeleeAttack();

    if (victim && victim->hasItem(ITEM_ASLEEP))
	return true;

    return evaluateWillHit(attack, lookupWeapon(), nullptr, victim);
}

bool
MOB::evaluateWillHit(ATTACK_NAMES attack, ITEM *weap, ITEM *ammo, MOB *victim) const
{
    // We can always hit inanimate objects?
    if (!victim)
	return true;

    ATTACK_DEF	*def = GAMEDEF::attackdef(attack);

    int		tohit = 10;

    tohit -= def->chancebonus;
    tohit += victim->defn().dodgebonus;
    ITEM *armour = victim->lookupArmour();
    if (armour)
    {
	tohit += armour->getArmourClass(def->damageclass);
	if (victim->pos().isFOV() || pos().isFOV())
	    armour->markBonusKnown();
    }

    // We have the difference so a factor of 20 is overwhelming.
    if (weap)
    {
	tohit -= weap->getBonus();
	if (victim->pos().isFOV() || pos().isFOV())
	    weap->markBonusKnown();
	if (!isProficientWith(weap))
	    tohit += 10;
    }
    if (ammo)
    {
	tohit -= ammo->getBonus();
	if (victim->pos().isFOV() || pos().isFOV())
	    ammo->markBonusKnown();
    }

    // We use our depth, not our level, so as not to overpower teh
    // avatar but ensure things can punch through plate eventually.
    tohit -= defn().depth/2;
    tohit -= getLevel()/3;	// Slow thac0...


    // I keep turning back to the d20 system.  The simple fact is
    // that 5% is right at the human threshold for noticing, so is
    // by far the most convenient to express our boni with.
    int		dieroll = rand_range(1, 20);

    if (dieroll != 1 &&
	(dieroll == 20 || (dieroll >= tohit)))
	return true;
    return false;
}

int
MOB::getRangedCanonicalDamage() const
{
    ITEM	*weap;

    weap = lookupWand();

    int		base = rangeattackdefn().damage.buildDPDF().expectedValueRounded();
    if (weap)
	base += weap->getBonus();

    return base;
}


int
MOB::evaluateAttackDamage(ATTACK_NAMES attack, ITEM *weap, ITEM *ammo) const
{
    ATTACK_DEF	*def = GAMEDEF::attackdef(attack);

    int		base = def->damage.roll();
    if (weap)
    {
	base += weap->getBonus();
	if (pos().isFOV())
	    weap->markBonusKnown();
    }
    if (ammo)
    {
	// Don't apply full bonus as you get a sick stack
	// between the ammo and the launcher.
	base += rand_choice(ammo->getBonus());
	if (pos().isFOV())
	    ammo->markBonusKnown();
    }

    return base;
}

int
MOB::evaluateDistribution(int amount, DISTRIBUTION_NAMES distribution)
{
    int		base = amount;

    switch (distribution)
    {
	case NUM_DISTRIBUTIONS:
	    break;
	case DISTRIBUTION_CONSTANT:
	    // damage is just damage
	    break;
	case DISTRIBUTION_UNIFORM:
	    // Uniform in double range gives same average.
	    base = rand_range(0, base * 2-1);
	    base++;
	    break;
	case DISTRIBUTION_BIMODAL:
	{
	    // Half the time we do a low-end distribution,
	    // half the time a high end.
	    if (rand_choice(2))
	    {
		// We don't double here, but add 50 percent
		// so our average matches.
		base += base / 2;
	    }
	    else
	    {
		base = base/2;
		if (!base)
		    base = 1;
	    }
		
	    //FALL THROUGH
	}
	case DISTRIBUTION_GAUSSIAN:
	{
	    // Sum of four uniforms.
	    int		total = 0;
	    total += rand_range(0, base * 2-1);
	    total += rand_range(0, base * 2-1);
	    total += rand_range(0, base * 2-1);
	    total += rand_range(0, base * 2-1);

	    total += 2;
	    total /= 4;

	    base = total + 1;
	    break;
	}
    }

    return base;
}

ATTACK_NAMES
MOB::getRangedAttack() const
{
    ITEM	*w;

    w = lookupWand();
    if (!w)
	return defn().range_attack;

    return w->getRangeAttack();
}

int
MOB::getRangedRange() const
{
    ITEM	*w;

    w = lookupWand();
    if (!w)
	return defn().range_range;

    return w->getRangeRange();
}

int
MOB::getRangedArea() const
{
    ITEM	*w;

    w = lookupWand();
    if (!w)
	return defn().range_area;

    return w->getRangeArea();
}

const char *
MOB::getRangedWeaponName() const
{
    ITEM	*w;

    w = lookupWand();
    if (!w)
	return rangeattackdefn().noun;

    return w->defn().name;
}

const char *
MOB::getRangedVerb() const
{
    return rangeattackdefn().verb;
}

const char *
MOB::getRangedFireball() const
{
    return rangeattackdefn().fireball;
}


ELEMENT_NAMES
MOB::getRangedElement() const
{
    return rangeattackdefn().element;
}

void
MOB::getRangedLook(u8 &symbol, ATTR_NAMES &attr) const
{
    ITEM	*w;

    w = lookupWand();
    if (!w)
    {
	symbol = defn().range_symbol;
	attr = defn().range_attr;
    }
    else
    {
	symbol = w->defn().range_symbol;
	attr = w->defn().range_attr;
    }
}

BUF
MOB::getRangedAnnounce() const
{
    BUF		result;
    ITEM *w = lookupWand();
    if (!w)
	result.reference(defn().range_announce);
    else
	result.reference(w->defn().range_announce);
	
    return result;
}

bool
MOB::roleListContains(ROLELIST_NAMES roles, ROLE_NAMES role)
{
    // QUestionable??
    if (role == ROLE_NONE)
	return true;

    while (roles != ROLELIST_NONE)
    {
	if (glb_rolelistdefs[roles].role == role)
	    return true;
	roles = glb_rolelistdefs[roles].next;
    }
    return false;
}

bool
MOB::isProficientWith(ITEM *item) const
{
    J_ASSERT(item);
    if (!item) return false;

    return roleListContains(item->roles(), role());
}

template <typename OP>
void
MOB::doConeAttack(int range, int dx, int dy,
	u8 symbol, ATTR_NAMES attr,
	const char *verb, const char *fireball,
	const OP &op)
{
    // Shooting is interesting.
    clearBoredom();

    // Apply damage to everyone in range.
    POS vpos = pos().delta(dx, dy);
    vpos.reportCone(dx, dy, range, fireball);
    vpos.cone(dx, dy, this, range, symbol, attr, op,
	    [](POS p) -> bool { return p.isPassable(); }); 
}

template <typename OP>
POS
MOB::doRangedAttack(int range, int area, int dx, int dy, 
		u8 symbol, ATTR_NAMES attr,
		const char *verb, const char *fireball,
		bool targetself, bool piercing, const OP &op)
{
    int		rangeleft;
    MOB		*victim;
    POS		vpos;

    // Check for friendly kill.
    victim = pos().traceBullet(range, dx, dy, &rangeleft);

    // Friendly fire is cool!

    // Shooting is interesting.
    clearBoredom();
 
    pos().displayBullet(range,
			dx, dy,
			symbol, attr,
			true);

    if (!victim)
    {
	// Shoot at air!
	// But, really, the fireball should explode!
	vpos = pos().traceBulletPos(range, dx, dy, true);

	// Apply damage to everyone in range.
	vpos.reportFireball(area, fireball);
	vpos.fireball(this, area, symbol, attr, op); 

	return vpos;
    }

    //  Attemp to kill victim
    // I'm not sure why this print out is here, this is a duplicate
    // as the effect will also trigger this?
#if 0
    if (targetself || (victim != this))
	formatAndReport("%S %v %O.", verb, victim);
#endif

    // NOTE: Bad idea to call a function on victim->pos() as
    // that likely will be deleted :>
    vpos = victim->pos();

    // Apply damage to everyone in range.
    vpos.reportFireball(area, fireball);
    bool	hit = vpos.fireball(this, area, symbol, attr, op); 
    
    // The following code will keep the flame going past the target
    // Piercing will continue even if it hits. Non piercing only continues
    // if not a true fireball
    while ( ( (!hit && area == 1) || piercing ) 
            && rangeleft 
            // && area == 1
            && (dx || dy))
    {
	// Ensure our dx/dy is copacetic.
	vpos.setAngle(pos().angle());
	int	newrangeleft = rangeleft;
	victim = vpos.traceBullet(rangeleft, dx, dy, &newrangeleft);

	vpos.displayBullet(rangeleft,
			    dx, dy,
			    symbol, attr,
			    true);

	hit = false;
	if (victim)
	{
	    formatAndReport("%S %v %O.", verb, victim);
	    vpos = victim->pos();
	    vpos.reportFireball(area, fireball);
	    hit = vpos.fireball(this, area, symbol, attr, op); 
	    rangeleft = newrangeleft;
	}
	else
	{
	    // Shoot at air!
	    // But, really, the fireball should explode!
	    vpos = vpos.traceBulletPos(rangeleft, dx, dy, true);

	    // Apply damage to everyone in range.
	    // Could hit secondarily, but this won't continue the
	    // flight.
	    vpos.reportFireball(area, fireball);
	    vpos.fireball(this, area, symbol, attr, op); 

	    rangeleft = 0;
	}
    }

    return vpos;
}

bool
MOB::actionFire(int dx, int dy)
{
    stopRunning();
    // Check for no ranged weapon.
    ITEM		*wand = lookupWand();

    if (!defn().range_valid && !wand)
    {
	formatAndReport("%S <lack> a ranged attack!");
	return false;
    }

    ITEM_NAMES		ammo = ITEM_NONE;

    // Everyone should require ammo...
    if (lookupWand())
	ammo = lookupWand()->defn().ammo;
    else
	ammo = defn().range_ammo;

    if (ammo != ITEM_NONE && !hasUnbrokenItem(ammo))
    {
	BUF		msg, ammoname;

	ammoname = gram_makeplural(ITEM::defn(ammo).name);
	msg.sprintf("%%S <be> out of %s!", ammoname.buffer());
	formatAndReport(msg);
	return false;
    }

    // No suicide.
    if (!dx && !dy)
    {
	formatAndReport("%S <decide> not to aim at %O.", this);
	return false;
    }
    
    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("%S <do> not have enough room inside here.");
	return false;
    }

    u8		symbol;
    ATTR_NAMES	attr;
    getRangedLook(symbol, attr);

    // Use up an arrow!
    ITEM	*arrow = nullptr;
    bool	 ranout = false;
    if (ammo != ITEM_NONE)
    {
	arrow = lookupUnbrokenItem(ammo);
	if (arrow)
	{
	    if (arrow->getStackCount() > 1)
	    {
		arrow = splitStack(arrow);
	    }
	    else
	    {
		removeItem(arrow, true);
		ranout = true;
	    }
	}
    }

    ATTACK_OP	op(this, wand, arrow, getRangedAttack());

    // This is not enforced, but is a hint for the AI.
    myRangeTimeout += defn().range_recharge;
    formatAndReport(getRangedAnnounce(), arrow);
    if (ranout)
    {
	formatAndReport("%S <be> out of %o.", gram_makeplural(ITEM::defn(ammo).name).buffer());
    }

    POS epos = doRangedAttack(getRangedRange(), getRangedArea(), dx, dy, 
		    symbol, attr,
		    getRangedVerb(), getRangedFireball(),
		    true, false, op);
    // Deposit the arrow with chance of breaking
    if (arrow)
    {
	if (rand_chance(arrow->breakChance(MOB_NONE)))
	    arrow->setBroken(true);
	arrow->move(epos);
    }
    return true;
}

bool
MOB::actionOpen(int dx, int dy)
{
    stopRunning();

    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("%S <wish> opening the jaws of this beast were so easy.");
	return false;
    }

    bool acted = false;
    POS t = pos().delta(dx, dy);
    if (t.tile() == TILE_ICEWALL && defn().breakdoors)
    {
        // Smash the wall
        if (isAvatar())
            formatAndReport("%S <smash> the ice wall.");
        else if (t.isFOV())
            msg_format("%S <smash> the ice wall.", this);
        if (t.metaData() < 0)
            t.setTile(TILE_DOOR_EMPTY);
        else
            t.setTile(TILE_FLOOR);
        return true;
    }
    if (t.defn().isdoorway)
    {
	if (defn().breakdoors)
	{
	    // Smash the door!
	    switch (t.tile())
	    {
		case TILE_DOOR_JAMMED_UNKNOWN:
		case TILE_DOOR_JAMMED:
		case TILE_DOOR_LOCKED_UNKNOWN:
		case TILE_DOOR_LOCKED:
		case TILE_DOOR_CLOSED:
		    if (isAvatar())
			formatAndReport("%S <smash> the door.");
		    else if (t.isFOV())
			msg_format("%S <smash> the door.", this);
		    t.setTile(TILE_DOOR_BROKEN);
		    acted = true;
		    break;
		case TILE_DOOR_OPEN:
		case TILE_DOOR_BROKEN:
		    formatAndReport("The door is already open.");
		    break;
		case TILE_DOOR_EMPTY:
		    formatAndReport("There is no door there.");
		    break;
	    }
	    return acted;
	}
	if (!defn().opendoors)
	{
	    formatAndReport("%S cannot open doors.");
	    return true;
	}

	switch (t.tile())
	{
	    case TILE_DOOR_CLOSED:
		if (isAvatar())
		    formatAndReport("%S <open> the door.");
		else if (t.isFOV())
		    msg_format("%S <open> the door.", this);
		t.setTile(TILE_DOOR_OPEN);
		acted = true;
		break;
	    case TILE_DOOR_OPEN:
	    case TILE_DOOR_BROKEN:
		formatAndReport("The door is already open.");
		break;
	    case TILE_DOOR_EMPTY:
		formatAndReport("There is no door there.");
		break;
	    case TILE_DOOR_JAMMED_UNKNOWN:
		if (isAvatar())
		{
		    formatAndReport("%S <discover> the door is jammed!");
		    acted = true;
		    t.setTile(TILE_DOOR_JAMMED);
		    break;
		}
		// FALL THROUGH
	    case TILE_DOOR_JAMMED:
		formatAndReport("%S cannot budge the jammed door.");
		acted = true;
		break;
	    case TILE_DOOR_LOCKED_UNKNOWN:
		if (isAvatar())
		{
		    formatAndReport("%S <discover> the door is locked!");
		    acted = true;
		    t.setTile(TILE_DOOR_LOCKED);
		    break;
		}
		// FALL THROUGH
	    case TILE_DOOR_LOCKED:
		formatAndReport("%S cannot budge the locked door.");
		acted = true;
		break;
	}
    }
    else
    {
	formatAndReport("%S <see> nothing to open there.");
    }

    return acted;
}

bool
MOB::actionKick(int dx, int dy)
{
    stopRunning();

    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("%S <do> not have room to move %r legs!");
	return false;
    }

    if (!dx && !dy)
    {
	formatAndReport("%S <find> kicking oneself is easier said than done.");
	return false;
    }

    clearBoredom();

    bool acted = false;
    POS t = pos().delta(dx, dy);

    // Monster check
    MOB	*victim = t.mob();
    if (victim && victim->alive())
    {
	bool hit = evaluateWillHit(ATTACK_KICK, nullptr, nullptr, victim);
	int damage = 0;
	if (hit)
	    damage = evaluateAttackDamage(ATTACK_KICK, nullptr, nullptr);
	if (damage)
	{
	    formatAndReport("%S <kick> %O.", victim);
	    makeNoise(1);
	}
	else
	{
	    formatAndReport("%S <miss> %O.", victim);
	    return true;
	}
	bool victimdead = victim->applyDamage(this, damage,
					ELEMENT_PHYSICAL,
					ATTACKSTYLE_MELEE);
	if (!victimdead && victim->size() < size())
	{
	    // Knockback...
	    victim->knockBack(dx, dy);
	}
	return true;
    }

    ITEM *kickee = t.item();
    if (kickee)
    {
	if (kickee->size() >= size())
	{
	    formatAndReport("%S <kick> %O.  THUMP!  ", kickee);
	    makeNoise(2);
	    return true;
	}
	// Kick...
	int		range = 1;
	if (kickee->size() == SIZE_TINY) range++;

	formatAndReport("%S <kick> %O.  ", kickee);
	makeNoise(1);
	POS	epos = kickee->pos().traceBulletPos(range, dx, dy, true, true);
	kickee->move(epos);
	return true;
    }

    switch (t.tile())
    {
        case TILE_DOOR_JAMMED_UNKNOWN:
        case TILE_DOOR_JAMMED:
        case TILE_DOOR_LOCKED_UNKNOWN:
        case TILE_DOOR_LOCKED:
        case TILE_DOOR_CLOSED:
        {
            int points = rollStrengthPoints() - 2;
            if (points >= 0)
            {
                // Break down door
                formatAndReport("%S <kick> open the door.");
                t.setTile(TILE_DOOR_BROKEN);
                makeNoise(2);
            }
            else
            {
                formatAndReport("%S <kick> the door.  WHAAM!");
                makeNoise(3 - points);
            }
            return true;
        }
        case TILE_DOOR_OPEN:
        case TILE_DOOR_BROKEN:
        case TILE_DOOR_EMPTY:
            break;

        case TILE_ICEWALL:
        {
            int points = rollStrengthPoints() - 2;
            if (points >= 0)
            {
                // Break down door
                formatAndReport("%S <kick> through the ice wall.");
                if (t.metaData() < 0)
                    t.setTile(TILE_DOOR_EMPTY);
                else
                    t.setTile(TILE_FLOOR);
                makeNoise(2);
            }
            else
            {
                formatAndReport("%S <kick> the ice wall.  WHAAM!");
                makeNoise(3 - points);
            }
            return true;
        }

    }

    if (t.isPassable())
    {
	// Kick open air.
	formatAndReport("%S <kick> the air.");
	return true;
    }

    // Kick a wall?
    formatAndReport("%S <kick> %O.", t.defn().legend);
    makeNoise(2);

    return true;
}


bool
MOB::actionClose(int dx, int dy)
{
    stopRunning();

    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("The jaws being closed is the problem!");
	return false;
    }

    bool acted = false;
    POS t = pos().delta(dx, dy);
    if (t.defn().isdoorway)
    {
	switch (t.tile())
	{
	    case TILE_DOOR_JAMMED_UNKNOWN:
	    case TILE_DOOR_JAMMED:
	    case TILE_DOOR_LOCKED_UNKNOWN:
	    case TILE_DOOR_LOCKED:
	    case TILE_DOOR_CLOSED:
		formatAndReport("The door is already closed.");
		acted = true;
		break;
	    case TILE_DOOR_BROKEN:
		formatAndReport("The broken door can no longer latch shut.");
		acted = true;
		break;
	    case TILE_DOOR_OPEN:
		if (t.mob())
		{
		    MOB *victim = t.mob();
		    // Attempt to push??
		    if (victim->size() == SIZE_TINY)
		    {
			if (victim->knockBack(dx, dy))
			{
			    // Victim could have died, so re-aquire.
			    POS epos = t.delta(dx, dy);
			    if (epos.mob())
				formatAndReport("%S <close> the door on %O.", epos.mob());
			    else
				formatAndReport("%S <close> the door.", epos.mob());
			    t.setTile(TILE_DOOR_CLOSED);
			    acted = true;
			    break;
			}
		    }
		    t.mob()->formatAndReport("%S <block> the door.");
		}
		else
		{
		    formatAndReport("%S <close> the door.");
		    t.setTile(TILE_DOOR_CLOSED);
		}
		acted = true;
		break;
	    case TILE_DOOR_EMPTY:
		formatAndReport("There is no door there.");
		break;
	}
    }
    else
    {
	formatAndReport("%S <see> nothing to close there.");
    }

    return acted;
}

void
MOB::triggerManaUse(SPELL_NAMES spell, int manacost)
{
}

int
MOB::learnChance(SPELL_NAMES spell) const
{
    int		hardness = GAMEDEF::spelldef(spell)->difficulty;

    int		level = getLevel() - 1;
    int		chance = 0;
    int		halflevel = 0;

    switch (hardness)
    {
	default:
	    J_ASSERT(!"Unhandled diffciulty");
	    // FALL THROUGH
	case 1:
	    chance = 50 + 5 * level;
	    break;
	case 2:
	    chance = 25 + 5 * level;
	    break;
	case 3:
	    chance = 5 + 5 * level;
	    break;
    }
    if (everKnewSpell(spell))
	chance += 25;
    return chance;
}

int
MOB::forgetChance(SPELL_NAMES spell) const
{
    int		hardness = GAMEDEF::spelldef(spell)->difficulty;

    int		level = getLevel() - 1;
    int		chance = 0;
    int		halflevel = 0;

    switch (hardness)
    {
	default:
	    J_ASSERT(!"Unhandled diffciulty");
	    // FALL THROUGH
	case 1:
	    chance = 50 - 5 * level;
	    break;
	case 2:
	    chance = 75 - 5 * level;
	    break;
	case 3:
	    chance = 100 - 5 * level;
	    break;
    }
    return chance;
}

void
MOB::memorizeSpell(SPELL_NAMES spell)
{
    if (myKnownSpells[spell])
    {
	// Relearn.
	formatAndReport("%S <relearn> %o.", GAMEDEF::spelldef(spell)->name);
	myKnownSpells[spell] = tolower(myKnownSpells[spell]);
    }
    else
    {
	// Find free slot...
	u8		minsymbol = 'a'-1;
	SPELL_NAMES	search;
	FOREACH_SPELL(search)
	{
	    if (myKnownSpells[search])
	    {
		u8 key = tolower(myKnownSpells[search]);
		minsymbol = MAX(key, minsymbol);
	    }
	}
	minsymbol++;
	myKnownSpells[spell] = minsymbol;
    }
}

bool
MOB::isSpellMemorized(SPELL_NAMES spell) const
{
    if (myKnownSpells[spell] >= 'a')
	return true;
    return false;
}

int
MOB::numSpellsMemorized() const
{
    SPELL_NAMES spell;
    int count = 0;
    FOREACH_SPELL(spell)
    {
	if (isSpellMemorized(spell))
	    count++;
    }
    return count;
}

int
MOB::maxSpellSlots() const
{
    return getLevel();
}

bool
MOB::everKnewSpell(SPELL_NAMES spell) const
{
    if (myKnownSpells[spell])
	return true;
    return false;
}

SPELL_NAMES
MOB::spellFromLetter(u8 letter) const
{
    if (!letter)
	return SPELL_NONE;

    SPELL_NAMES spell;
    FOREACH_SPELL(spell)
    {
	if (myKnownSpells[spell] == letter)
	    return spell;
    }
    return SPELL_NONE;
}

void
MOB::forgetSpell(SPELL_NAMES spell)
{
    if (myKnownSpells[spell])
    {
	// Forget!.
	formatAndReport("%o fades from %r mind.", GAMEDEF::spelldef(spell)->name);
	myKnownSpells[spell] = toupper(myKnownSpells[spell]);
    }
}

void
MOB::getKnownSpells(PTRLIST<SPELL_NAMES> &spells) const
{
    SPELL_NAMES spell;
    spells.clear();
    FOREACH_SPELL(spell)
    {
	if (isSpellMemorized(spell))
	{
	    spells.append(spell);
	}
    }
    // Sort by bound key
    spells.stableSort([self = this](SPELL_NAMES a, SPELL_NAMES b) -> bool
    {
	return self->spellLetter(a) < self->spellLetter(b);
    });
}

int
MOB::getGold() const
{
    return getCoin(MATERIAL_GOLD);
}

int
MOB::getCoin(MATERIAL_NAMES material) const
{
    ITEM		*gold;
    int			 ngold;

    gold = lookupItem(ITEM_COIN, material);
    
    ngold = 0;
    if (gold)
	ngold = gold->getStackCount();

    return ngold;
}

void
MOB::gainGold(int delta) 
{
    gainCoin(MATERIAL_GOLD, delta);
}

void
MOB::gainExp(int exp, bool silent)
{
    myExp += exp;

    int level = LEVEL::levelFromExp(myExp);
    while (level > myLevel)
	gainLevel(silent);
}

void
MOB::gainLevel(bool silent)
{
    myLevel++;
    if (!silent)
    {
	formatAndReport("%S <be> more experienced.");
	const char *msg = nullptr;
	static const char *wizard_msgs[] =
	{
	    "My studies bear fruit!",
	    "The orb draws closer...",
	    "Reason conquers all.",
	    "Chance leaves no choice.",
	    "To know is to understand.",
	    0
	};
	static const char *thief_msgs[] =
	{
	    "More skills, more gold!",
	    "Work is theft.",
	    "Theft is my work.",
	    "The Midas Wand belongs with me.",
	    0
	};
	static const char *fighter_msgs[] =
	{
	    "Power is all.",
	    "Knowledge without power is wasted.",
	    "Strength is needed for safety.",
	    "Best deterence is a bigger stick.",
	    "I am now a bigger stick!",
	    "Walking quietly is for the weak.",
	    0
	};
	switch (getDefinition())
	{
	    case MOB_AVATAR_WIZARD:
		msg = rand_string(wizard_msgs);
		break;
	    case MOB_AVATAR_FIGHTER:
		msg = rand_string(fighter_msgs);
		break;
	    case MOB_AVATAR_THIEF:
		msg = rand_string(thief_msgs);
		break;
	}

	if (msg)
	{
	    doShout(msg);
	}
    }

    // Increase max hp
    int         bonus = defn().hp_gain.roll();
    myMaxHP += bonus;

    // Full heal.  Will always report new health, which is good to point
    // out this feature...
    //gainHP(getMaxHP() - getHP());
    // Instead only gain your new hp
    gainHP(bonus);
}

void
MOB::grantExtraLife()
{
    myExtraLives++;
    formatAndReport("%S <glow> from within, gaining the ability to thwart death!");
    gainHP(getMaxHP() - getHP());
}

void
MOB::gainCoin(MATERIAL_NAMES material, int delta)
{
    ITEM		*gold;

    if (!delta)
	return;

    gold = lookupItem(ITEM_COIN, material);
    if (!gold && delta>0)
    {
	gold = ITEM::create(ITEM_COIN);
	gold->setMaterial(material);
	gold->setStackCount(0);
	addItem(gold);
    }
    if (!gold && delta<=0)
    {
	// Can't go negative?
	J_ASSERT(!"Negative gold?");
	return;
    }
    
    gold->setStackCount(gold->getStackCount() + delta);

    if (gold->getStackCount() <= 0)
    {
	// All gone, get rid of it so we don't have a No Coins entry.
	// (as cute as that is)
	removeItem(gold);
	delete gold;
    }
}

class SPELL_OP
{
public:
    SPELL_OP(MOB *src, SPELL_NAMES spell, bool hitsself, bool dx, bool dy) 
    { mySrc = src; mySpell = spell; myHitsSelf = hitsself;
      myDx = dx; myDy = dy; }

    bool operator()(POS p) const
    {
	MOB		*mob = p.mob();
	bool		 hit = false;
        EFFECT_NAMES     effect = GAMEDEF::spelldef(mySpell)->effect;

	if (!myHitsSelf && mySrc == mob)
	    return false;

        // Special effects...
        switch (mySpell)
        {
            case SPELL_COLOURSPRAY:
            {
                // Sadly these are not contiguous as we built adhocs
                // as building them!
                const static EFFECT_NAMES cspray[6] =
                { EFFECT_COLOURSPRAY_RED, 
                  EFFECT_COLOURSPRAY_ORANGE, 
                  EFFECT_COLOURSPRAY_YELLOW, 
                  EFFECT_COLOURSPRAY_GREEN, 
                  EFFECT_COLOURSPRAY_BLUE, 
                  EFFECT_COLOURSPRAY_VIOLET };
                effect = cspray[rand_choice(6)];
                break;
            }
            case SPELL_SLEEP:
            {
                if (mob && mySrc)
                    mob->putToSleep(true, mySrc->getLevel(), 15);
                return true;
            }
            case SPELL_FIREBALL:
            {
                p.burnSquare();
                break;
            }
            case SPELL_TSUNAMI:
            {
                p.soakSquare(POTION_WATER);
                break;
            }
        }

	if (mob)
	{
	    // Spells always hit?
	    hit = true;
	    bool died = mob->applyEffect(mySrc, effect, ATTACKSTYLE_RANGE);
	    // Double jeopardy!
	    if (!died)
	    {
		if (mySpell == SPELL_TSUNAMI)
		    mob->knockBack(myDx, myDy);
	    }
	}
	return hit;
    }

private:
    SPELL_NAMES	 	mySpell;
    bool		myHitsSelf;
    MOB			*mySrc;
    int			myDx, myDy;
};

bool
MOB::actionCast(SPELL_NAMES spell, int dx, int dy)
{
    stopRunning();

    if (!isSpellMemorized(spell))
    {
	formatAndReport("%S cannot cast what %S <do> not know.");
	return true;
    }

    // Pay the piper...
    int			manacost = 0, hpcost = 0;

    manacost = GAMEDEF::spelldef(spell)->mana;
    if (GAMEDEF::spelldef(spell)->reqfullmana)
    {
	manacost = getMaxMP();
    }

    // CHeck timeout
    int timeout = GAMEDEF::spelldef(spell)->timeout;
    if (timeout)
    {
	if (mySpellTimeout)
	{
	    formatAndReport("%S <be> not ready to cast %O.",
				GAMEDEF::spelldef(spell)->name);
	    return false;
	}
    }
    
    if (manacost > getMP())
    {
	// Let people suicide.
	hpcost = manacost - getMP();
	manacost = getMP();
    }

    if (hpcost)
    {
	// No blood magic!
#if 0
	formatAndReport("Paying with %r own life, %S <cast> %O.",
			    GAMEDEF::spelldef(spell)->name);
#else
	formatAndReport("%S <lack> the mana to cast %O.",
			    GAMEDEF::spelldef(spell)->name);
	return false;
#endif
    }
    else
    {
	// Less dramatic.
	formatAndReport("%S <cast> %O.",
			    GAMEDEF::spelldef(spell)->name);
    }

    makeNoise(1);

    if (hpcost)
    {
	if (applyDamage(this, hpcost, ELEMENT_NONE, ATTACKSTYLE_INTERNAL))
	    return true;
    }

    // Delay mana cost until after the spell so you can't
    // recharge your q with the q itself.
    triggerManaUse(spell, manacost);

    // See if the spell fades...
    if (rand_chance(forgetChance(spell)))
    {
	forgetSpell(spell);
    }

    // Generic spells:

    // Fire ball self or in a direction...
    bool targetself = GAMEDEF::spelldef(spell)->friendlyfire;
    SPELL_OP		op(this, spell, targetself, dx, dy);

    int radius = GAMEDEF::spelldef(spell)->radius;
    int range = GAMEDEF::spelldef(spell)->range;

    // Shapes of spells...
    // One's with no radius means we don't use simple callbacks as
    // they are more weird...
    if (!radius)
    {
    }
    else if (GAMEDEF::spelldef(spell)->blast)
    {
	POS		vpos = pos().delta(dx * radius, dy * radius);

	vpos.reportFireball(radius, GAMEDEF::spelldef(spell)->fireball);
	vpos.fireball(this, radius,
		    GAMEDEF::spelldef(spell)->symbol,
		    GAMEDEF::spelldef(spell)->attr,
		    op);
    }
    else if (GAMEDEF::spelldef(spell)->cone)
    {
	doConeAttack(radius,
		    dx, dy,
		    GAMEDEF::spelldef(spell)->symbol,
		    GAMEDEF::spelldef(spell)->attr,
		    GAMEDEF::spelldef(spell)->verb,
		    GAMEDEF::spelldef(spell)->fireball,
		    // targetself, GAMEDEF::spelldef(spell)->piercing,
		    op);
    }
    else
    {
	// Ray + fireball
	doRangedAttack(range, 
		    radius,
		    dx, dy,
		    GAMEDEF::spelldef(spell)->symbol,
		    GAMEDEF::spelldef(spell)->attr,
		    GAMEDEF::spelldef(spell)->verb,
		    GAMEDEF::spelldef(spell)->fireball,
		    targetself, GAMEDEF::spelldef(spell)->piercing,
		    op);
    }

    // We may have killed ourself.
    if (!alive())
	return true;

    // Special effects...
    switch (spell)
    {
	case SPELL_TELEPORT:
	{
	    POS		vpos = pos().delta(dx, dy);
	    if (vpos.mob())
	    {
		vpos.mob()->actionTeleport(true);
	    }
	    if (vpos.teleportItems())
	    {
		// Report?
	    }
	    break;
	}
        case SPELL_DISINTEGRATE:
        {
	    POS		vpos = pos().delta(dx, dy);
            if (vpos.defn().isdiggable)
            {
                formatAndReport("%S <disintegrate> %O.", vpos.defn().legend);
                vpos.digSquare();
            }
            else if (vpos.defn().isdoorway)
            {
		switch (vpos.tile())
		{
		    case TILE_DOOR_CLOSED:
		    case TILE_DOOR_JAMMED_UNKNOWN:
		    case TILE_DOOR_JAMMED:
		    case TILE_DOOR_LOCKED_UNKNOWN:
		    case TILE_DOOR_LOCKED:
		    case TILE_DOOR_OPEN:
		    case TILE_DOOR_BROKEN:
                        formatAndReport("%S <disintegrate> %O.", vpos.defn().legend);
			vpos.setTile(TILE_DOOR_EMPTY);
			break;
		    case TILE_DOOR_EMPTY:
			break;
		}
            }
            if (vpos.item())
            {
                ITEMLIST        items, topurge;

                vpos.getAllItems(items);
                for (auto && item : items)
                {
                    // Sovreign acid destroys most things, but
                    // potions become potions of acid...
                    if (item->isPotion() && item->asPotion() != POTION_ACID)
                    {
                        item->setDefinition(ITEM::lookupPotion(POTION_ACID));
                    }
                    else if (item->asRing() == RING_ACID)
                    {
                        // These ignore it naturally.
                    }
                    else
                        topurge.append(item);
                }

                BUF             list;
                list.strcat("%S <disintegrate> ");
                list.strcat(ITEM::buildList(topurge));
                list.strcat(".");
                for (auto && item : topurge)
                {
                    item->move(POS());
                    delete item;
                }
                if (topurge.entries())
                {
                    formatAndReport(list);
                }
            }
            break;
        }
        case SPELL_ICEWALL:
        {
	    POS		vpos = pos().delta(dx, dy);
	    if (vpos.mob())
		vpos.mob()->knockBack(dx, dy);
            // create an ice wall!
            switch (vpos.tile())
            {
                case TILE_DOOR_CLOSED:
                case TILE_DOOR_JAMMED_UNKNOWN:
                case TILE_DOOR_JAMMED:
                case TILE_DOOR_LOCKED_UNKNOWN:
                case TILE_DOOR_LOCKED:
                case TILE_DOOR_OPEN:
                case TILE_DOOR_BROKEN:
                    formatAndReport("The door shatters.");
                    // Fall through
                case TILE_FLOOR:
                case TILE_DOOR_EMPTY:
                    vpos.setTile(TILE_ICEWALL);
                    formatAndReport("%S <create> a wall of ice.");
                    break;
            }
            break;
        }
	case SPELL_FORCEBOLT:
	{
	    POS		vpos = pos().delta(dx, dy);
	    if (vpos.mob())
		vpos.mob()->knockBack(dx, dy);
	    vpos.shatterSquare();
	    break;
	}
    }


    gainMP(-manacost);
    mySpellTimeout += timeout;
    
    return true;
}

bool
MOB::actionForget(SPELL_NAMES spell)
{
    stopRunning();

    if (!isSpellMemorized(spell))
    {
	formatAndReport("%S cannot forget what %S <do> not know.");
	return true;
    }

    formatAndReport("%S <purge> %r memory.");
    forgetSpell(spell);
    
    return true;
}

bool
MOB::castDestroyWalls()
{
    formatAndReport("The walls around %S shake.");

    bool		interesting;
    interesting = pos().forceConnectNeighbours();

    if (interesting)
    {
	formatAndReport("New passages open up.");
    }
    else
    {
	formatAndReport("Nothing happens.");
    }

    return true;
}

bool
MOB::actionFleeBattleField()
{
    int		dx, dy;
    FORALL_4DIR(dx, dy)
    {
	POS	t = pos().delta(dx, dy);

	if (!t.valid() && !isAvatar())
	{
	    // Fall off the world and die!
	    formatAndReport("%S <flee> the battleground.");
	    actionDropAll();

	    MAP *map = pos().map();
	    pos().removeMob(this);
	    map->addDeadMob(this);
	    clearAllPos();
	    loseTempItems();
	    // Successfully walk!
	    return true;
	}
    }
    return false;
}

bool
MOB::knockBack(int dx, int dy)
{
    stopRunning();

    if (isSwallowed())
	return false;

    POS		 t;
    t = pos().delta(dx, dy);
    if (canMove(t, /*checkmob*/true, /*onlypassive*/true))
    {
	formatAndReport("%S <be> knocked back.");
	move(t);
	return true;
    }
    // no room just silently fails.
    return false;
}

bool
MOB::actionWalk(int dx, int dy)
{
    MOB		*victim;
    POS		 t;

    // If we are swallowed, we can't move.
    if (isSwallowed())
    {
	stopRunning();
	return false;
    }
    
    t = pos().delta(dx, dy);
    
    victim = t.mob();
    if (victim)
    {
	// If we are the avatar, screw being nice!
	if (!isFriends(victim))
	    return actionMelee(dx, dy);

	// formatAndReport("%S <bump> into %O.", victim);
	// Bump into the mob.

	// Nah, tell the map we want a delayed move.
	// Do not delay move the avatar, however!
	if (victim->isAvatar() || isAvatar())
	    return false;

	return pos().map()->requestDelayedMove(this, dx, dy);
    }

    // Check to see if we can move that way.
    if (canMove(t))
    {
	TILE_NAMES	tile;
	
	// Determine if it is a special move..
	tile = t.tile();
	if (t.defn().isdiggable &&
	    !t.defn().ispassable &&	
	    defn().candig)
	{
	    stopRunning();
	    return t.digSquare();
	}

	if (!canMove(t, /*checkmob*/true, /*onlypassive*/true))
	{
	    // Another special, likely door...
	    if (defn().opendoors || defn().breakdoors)
	    {
		stopRunning();
		return actionOpen(dx, dy);
	    }
	    stopRunning();
	    formatAndReport("%S <be> blocked by %O.", t.defn().legend);
	    if (isAvatar())
		msg_newturn();
	    // Bump into a wall.
	    return false;
	}

	// See if we are still running...
	int		dir = rand_dirtoangle(dx, dy);
	if (myLastDir != dir)
	{
	    stopRunning();
	    myLastDir = dir;
	}
	myLastDirCount++;
	if (myLastDirCount > 3 && isAvatar())
	{
	    if (!hasItem(ITEM_QUICKBOOST))
		addItem(ITEM::create(ITEM_QUICKBOOST));
	}

	POS		opos = pos();
	
	// Move!
	move(t);

	// If we are a breeder, chance to reproduce!
	if (defn().breeder)
	{
	    static  int		lastbreedtime = -10;

	    // Only breed every 7 phases at most so one can
	    // always kill off the breeders.
	    // Only breed while the player is in the dungeon
	    bool		 canbreed = false;

	    if (getAvatar() && getAvatar()->alive() &&
		getAvatar()->pos().roomId() == pos().roomId())
	    {
		canbreed = true;
	    }

	    // Hard cap
	    int		mobcount = pos().map()->getMobCount(getDefinition());
	    if (mobcount > 10)
		canbreed = false;

	    // Every new breeder increases the chance of breeding
	    // by 1 roll.  Thus we need at least the base * mobcount
	    // to just keep the breeding chance linear.
	    if (canbreed && 
		(lastbreedtime < map()->getTime() - 12) &&
		!rand_choice(3 + 3*mobcount))
	    {
		MOB		*child;
		
		formatAndReport("%S <reproduce>!");
		child = MOB::create(getDefinition(), false, map()->world());
		child->move(opos);
		lastbreedtime = map()->getTime();
	    }
	}
	
	// Make noise from armour.
	{
	    ITEM *armour = lookupArmour();
	    if (armour)
	    {
		makeNoise(armour->defn().noise);
	    }
	}
	
	// If we are the avatar and something is here, pick it up.
	// Disable as packrat just fills your backpack and we
	// want people addicted to transmuting
#if 0
	if (isAvatar() && t.item() && 
	    (t.item()->defn().itemclass != ITEMCLASS_FURNITURE))
	{
	    stopRunning();
	    actionPickup();
	}
#endif

	// Avatars set off traps because they are clumsy
	bool		hadtrap = false;
	if (isAvatar() && t.isTrap())
	{
	    hadtrap = true;
	    t.clearTrap();
	    TRAP_NAMES		trap = (TRAP_NAMES) rand_choice(NUM_TRAPS);

	    t.postEvent((EVENTTYPE_NAMES)(EVENTTYPE_ALL | EVENTTYPE_LONG), 
			    glb_trapdefs[trap].sym,
			    (ATTR_NAMES) glb_trapdefs[trap].attr);

	    stopRunning();
	    if (trap == TRAP_NONE)
	    {
		// Take this to mean we evade it...
		formatAndReport("%S <trigger> a trap but evade it!");
	    }
	    else
	    {
		formatAndReport("%S <trigger> %O!", glb_trapdefs[trap].name);

		if (applyAttack(glb_trapdefs[trap].attack, nullptr, ATTACKSTYLE_MELEE))
		    return true;		// is dead
	    }
	}

	// Apply searching

#if 0
	// Attempt to climb!
	if (alive() && isAvatar() && t.tile() == TILE_DOWNSTAIRS)
	{
	    msg_report("Press > to climb down.");
	}
	// Attempt to climb!
	if (alive() && isAvatar() && t.tile() == TILE_UPSTAIRS)
	{
	    msg_report("Press < to climb up.");
	}
#endif

	return true;
    }
    else
    {
	if (dx && dy && isAvatar() && ENABLE_WALLSLIDE)
	{
	    // Try to wall slide, cause we are pretty real time here
	    // and it is frustrating to navigate curvy passages otherwise.
	    if (!rand_choice(2) && canMoveDir(dx, 0, false))
		if (actionWalk(dx, 0))
		    return true;
	    if (canMoveDir(0, dy, false))
		if (actionWalk(0, dy))
		    return true;
	    if (canMoveDir(dx, 0, false))
		if (actionWalk(dx, 0))
		    return true;
	}
	else if ((dx || dy) && isAvatar() && ENABLE_WALLSLIDE)
	{
	    // If we have
	    // ..
	    // @#
	    // ..
	    // Moving right we want to slide to a diagonal.
	    int		sdx, sdy;

	    // This bit of code is too clever for its own good!
	    sdx = !dx;
	    sdy = !dy;

	    if (!rand_choice(2) && canMoveDir(dx+sdx, dy+sdy, false))
		if (actionWalk(dx+sdx, dy+sdy))
		    return true;
	    if (canMoveDir(dx-sdx, dy-sdy, false))
		if (actionWalk(dx-sdx, dy-sdy))
		    return true;
	    if (canMoveDir(dx+sdx, dy+sdy, false))
		if (actionWalk(dx+sdx, dy+sdy))
		    return true;
	    
	}

	stopRunning();
	formatAndReport("%S <be> blocked by %O.", t.defn().legend);
	if (isAvatar())
	    msg_newturn();
	// Bump into a wall.
	return false;
    }
}

bool
MOB::actionTransmute()
{
    stopRunning();
    ITEM		*item;

    item = pos().item();
    if (!item)
    {
	formatAndReport("%S <fail> to fuse the empty air.");
	return false;
    }

    if (item->pos() != pos())
    {
	formatAndReport("%S <be> too far away to fuse %O.", item);
	return false;
    }

    // Transmute the item.
    formatAndReport("%S <fuse> %O.", item);

    // Fail to transmute things with no base materials.
    if (item->material() == MATERIAL_NONE || item->getDefinition() == ITEM_COIN)
    {
	msg_format("%S <shake> violently, but remains intact.", item);
	return true;
    }

    // Create the coins
    msg_format("%S <dissolve> into its component parts.", item);

    item->move(POS());

    ITEM		*coin;
    coin = ITEM::create(ITEM_COIN);
    coin->setMaterial(item->material());
    coin->setStackCount(item->defn().mass * item->getStackCount());
    coin->silentMove(pos());
    actionPickup(coin);

    if (item->giltMaterial() != MATERIAL_NONE)
    {
	coin = ITEM::create(ITEM_COIN);
	coin->setMaterial(item->giltMaterial());
	coin->setStackCount(1 * item->getStackCount());
	coin->silentMove(pos());
	actionPickup(coin);
    }

    incTransmuteTaint();

    delete item;
    return true;
}

bool
MOB::actionTransmute(ITEM *source)
{
    stopRunning();

    // Make sure we have an equipped item to accept the power.
    ITEM *target = nullptr;

    if (role() != ROLE_WIZARD)
    {
        formatAndReport("%S <do> not know how to enhance items.");
        return true;
    }

    // By default donate to equipped.
    target = lookupWeapon();
    if (source->isRing())
    {
	target = lookupRing();
    }
    if (source->isArmour())
    {
	target = lookupArmour();
    }

    if (!target)
    {
	formatAndReport("%S <have> nothing equipped to absorb %O.", source);
	return true;
    }

    return actionTransmute(source, target);
}

bool
MOB::actionTransmute(ITEM *source, ITEM *target)
{
    stopRunning();

    if (role() != ROLE_WIZARD)
    {
        formatAndReport("%S <do> not know how to enhance items.");
        return true;
    }

    if (!target)
    {
	formatAndReport("%S cannot <fuse> %O without a target.", source);
	return true;
    }

    // Self destruction does work if it is a stack!
    if (source == target && source->getStackCount() < 2)
    {
	formatAndReport("%S <realize> that destroying %O to empower itself won't work.", target);
	return true;
    }

    // Only allow fusing of same type!
    if (source->getDefinition() != target->getDefinition())
    {
	formatAndReport("%S cannot <use> %O as a source it does not match %r target.", source);
	return true;
    }

    // Transmute the item.
    source = splitStack(source);
    formatAndReport("%S <use> %O as an enhancement source.", source);
    delete source;

    // Enchant the target, it may be stacked though!
    bool		splittarget = false;
    if (target->getStackCount() > 1)
    {
	splittarget = true;
	target = splitStack(target);
    }

    if (!target->enchant(1))
    {
	// Destroyed.
	if (!splittarget)
	    target = splitStack(target);
	delete target;
    }
    else
    {
	if (splittarget)
	    addItem(target);
    }

    return true;
}


bool
MOB::actionPickup()
{
    stopRunning();
    ITEM		*item;

    item = pos().item();

    return actionPickup(item);
}

bool
MOB::actionPickup(ITEM *item)
{
    stopRunning();
    if (!item)
    {
	formatAndReport("%S <grope> the ground foolishly.");
	return true;
    }

    if (item->pos() != pos())
    {
	formatAndReport("%S <be> too far away to pick up %O.", item);
	return false;
    }

    // I like this idea, but then we need code for eating large 
    // critters from the ground which is mendokosai.
    // But we now after much pain have that code.
    // No more carting around boulders!
    if (item->size() > size())
    {
	formatAndReport("%O is too big for %S to pick up.", item);
	return true;
    }

    // Pick up the item!
    formatAndReport("%S <pick> up %O.", item);

    // These should have been cleared when dropped, but to be safe...
    item->setEquipped(false);
    item->setWielded(false);

    item->move(POS());

    if (isAvatar())
	item->fixWorldState();

    if (item->defn().discardextra && hasItem(item->getDefinition()))
    {
	// Replace broken items...
	ITEM		*olditem = lookupItem(item->getDefinition());
	
	if (olditem->isBroken() && !item->isBroken())
	{
	    formatAndReport("%S <replaces> %O.", olditem);
	    olditem->setBroken(false);
	}

	formatAndReport("%S already <have> one of these, so %S <discard> it.");

	delete item;
    }
    else
	addItem(item);

    return true;
}

void
MOB::updateEquippedItems()
{
    ITEM		*item;

    // Non avatars auto equip.
    if (!isAvatar() && !isAvatarDef(getDefinition()))
    {
	// Mark everything unequipped
	for (int i = 0; i < myInventory.entries(); i++)
	{
	    item = myInventory(i);

	    if (!item->isRing())
	    {
		item->setEquipped(false);
		item->setWielded(false);
	    }
	}

	if (!defn().useitems)
	    return;

	// Equip our weapon and armour
	ITEM		*weap = 0, *arm = 0, *wand = 0;

	for (int i = 0; i < myInventory.entries(); i++)
	{
	    if (myInventory(i)->isWeapon() && !myInventory(i)->isBroken())
		weap = aiLikeMoreWeapon(myInventory(i), weap);
	}
	if (weap)
	    weap->setWielded(true);
	for (int i = 0; i < myInventory.entries(); i++)
	{
	    if (myInventory(i)->isRanged() && !myInventory(i)->isBroken())
		wand = aiLikeMoreWand(myInventory(i), wand);
	}
	if (wand)
	    wand->setEquipped(true);
	for (int i = 0; i < myInventory.entries(); i++)
	{
	    if (myInventory(i)->isArmour() && !myInventory(i)->isBroken())
		arm = aiLikeMoreArmour(myInventory(i), arm);
	}
	if (arm)
	    arm->setEquipped(true);
    }

    // Count total items
    int		total = 0;
    for (auto && item : inventory())
    {
	if (item->isFlag())
	    continue;
	total++;
	if (isAvatar() && !item->inventoryLetter())
	{
	    assignInventoryLetter(item);
	}
    }
    const int max_items = 26;
    if (total > max_items)
    {
	// Yay!  Chacne to use count down operator.
	// We want to run in reverse here.
	formatAndReport("%r backpack is out of room!");
	for (int i = myInventory.entries(); i --> 0; )
	{
	    item = myInventory(i);
	    if (item->isFlag())
		continue;

	    formatAndReport("%S <drop> %O.", item);
	    item->move(pos());
	    item->setEquipped(false);
	    item->setWielded(false);
	    myInventory.removeAt(i);

	    total--;
	    if (total <= max_items)
		break;
	}
    }
}

void
MOB::addItem(ITEM *item)
{
    int			 i;

    if (!item)
	return;
    
    // Alert the world to our acquirement.
    if (isAvatar() || pos().isFOV())
	if (item->defn().gaintxt)
	    formatAndReport(item->defn().gaintxt, item);

    // First, check to see if we can merge...
    for (i = 0; i < myInventory.entries(); i++)
    {
	if (item->canStackWith(myInventory(i)))
	{
	    myInventory(i)->combineItem(item);
	    delete item;
	    // Pop that inventory to the top.
	    ITEM *merge = myInventory(i);
	    if (isAvatar() && !merge->isFlag() && merge->inventoryLetter())
	    {
		BUF	buf;
		buf.sprintf("[%c] %s.", merge->inventoryLetter(), merge->getName().buffer());
		msg_report(buf);
	    }
	    myInventory.removePtr(merge);
	    myInventory.append(merge);
	    return;
	}
    }

    // Brand new item.
    if (isAvatar() && !item->isFlag())
    {
	// Check if we have a conflict... or unassigned
	if (!item->inventoryLetter() ||
	    itemFromLetter(item->inventoryLetter()))
	{
	    if (!assignInventoryLetter(item))
	    {
		formatAndReport("%S <lack> room to carry %O.", item);
		item->move(pos());
		return;
	    }
	}
	{
	    BUF	buf;
	    buf.sprintf("[%c] %s.", item->inventoryLetter(), item->getName().buffer());
	    msg_report(buf);
	}
    }
    myInventory.append(item);

    // If we didn't stack, we may need to update our best items.
    updateEquippedItems();
}

ITEM *
MOB::splitStack(ITEM *item, int newcount)
{
    // Must actually be an item in our pack!
    J_ASSERT(!item->pos().valid());
    if (item->getStackCount() > newcount)
    {
	ITEM	*result;

	item->decStackCount(newcount);
	result = item->createCopy();
	result->setStackCount(newcount);

	result->setEquipped(false);
	result->setWielded(false);

	return result;
    }
    else
    {
	item->setEquipped(false);
	item->setWielded(false);
	removeItem(item);
	return item;
    }
}

void
MOB::removeItem(ITEM *item, bool quiet)
{
    // Alert the world to our acquirement.
    if ((isAvatar() || pos().isFOV()) && !quiet)
	if (item->defn().losetxt)
	    formatAndReport(item->defn().losetxt, item);

    if (item)
    {
	item->setEquipped(false);
	item->setWielded(false);
    }
    myInventory.removePtr(item);

    updateEquippedItems();
}

void
MOB::loseAllItems()
{
    int		i;
    ITEM	*item;

    for (i = myInventory.entries(); i --> 0;)
    {
	item = myInventory(i);
	removeItem(item, true);
	delete item;
    }
}

void
MOB::loseTempItems()
{
    int		i;
    ITEM	*item;

    for (i = myInventory.entries(); i --> 0;)
    {
	item = myInventory(i);
	// All flags and timers are temp.
	if (item->getTimer() >= 0 || item->isFlag())
	{
	    removeItem(item, true);
	    delete item;
	}
    }
}

void
MOB::save(ostream &os) const
{
    int		val;
    u8		c;

    val = getDefinition();
    os.write((const char *) &val, sizeof(int));

    myName.save(os);

    myPos.save(os);
    myMeditatePos.save(os);
    myTarget.save(os);
    myHome.save(os);

    c = myWaiting;
    os.write((const char *) &c, sizeof(c));

    os.write((const char *) &myHP, sizeof(int));
    os.write((const char *) &myMaxHP, sizeof(int));
    os.write((const char *) &myMP, sizeof(int));
    os.write((const char *) &myFood, sizeof(int));
    os.write((const char *) &myAIState, sizeof(int));
    os.write((const char *) &myFleeCount, sizeof(int));
    os.write((const char *) &myBoredom, sizeof(int));
    os.write((const char *) &myYellHystersis, sizeof(int));
    os.write((const char *) &myNumDeaths, sizeof(int));
    os.write((const char *) &myUID, sizeof(int));
    os.write((const char *) &mySeed, sizeof(int));
    os.write((const char *) &myLevel, sizeof(int));
    os.write((const char *) &myExp, sizeof(int));
    os.write((const char *) &myExtraLives, sizeof(int));
    os.write((const char *) &mySearchPower, sizeof(int));
    os.write((const char *) &myCowardice, sizeof(int));
    os.write((const char *) &myKills, sizeof(int));
    os.write((const char *) &myRangeTimeout, sizeof(int));
    os.write((const char *) &mySpellTimeout, sizeof(int));

    os.write((const char *) &myLastDir, sizeof(int));
    os.write((const char *) &myLastDirCount, sizeof(int));

    val = myHasTriedSuicide;
    os.write((const char *) &val, sizeof(int));

    val = isSwallowed();
    os.write((const char *) &val, sizeof(int));

    val = myAngryWithAvatar;
    os.write((const char *) &val, sizeof(int));

    int			 numitem;
    int			 i;

    c = mySawMurder;
    os.write((const char *) &c, 1);
    c = mySawMeanMurder;
    os.write((const char *) &c, 1);
    c = mySawVictory;
    os.write((const char *) &c, 1);
    c = myAvatarHasRanged;
    os.write((const char *) &c, 1);

    YELL_NAMES		yell;
    FOREACH_YELL(yell)
    {
	c = myHeardYell[yell];
	os.write((const char *) &c, 1);
	c = myHeardYellSameRoom[yell];
	os.write((const char *) &c, 1);
    }

    SPELL_NAMES		spell;
    FOREACH_SPELL(spell)
    {
	c = myKnownSpells[spell];
	os.write((const char *) &c, 1);
    }

    numitem = myInventory.entries();
    os.write((const char *) &numitem, sizeof(int));

    for (i = 0; i < myInventory.entries(); i++)
    {
	myInventory(i)->save(os);
    }

    WORLD_NAMES		world;
    FOREACH_WORLD(world)
    {
	if (myWorldStash[world])
	{
	    c = 1;
	    os.write((const char *) &c, 1);
	    myWorldStash[world]->save(os);
	}
	else
	{
	    c = 0;
	    os.write((const char *) &c, 1);
	}
    }
}

MOB *
MOB::load(istream &is)
{
    int		 val, num, i;
    u8		 c;
    MOB		*mob;

    mob = new MOB();

    is.read((char *)&val, sizeof(int));
    mob->myDefinition = (MOB_NAMES) val;

    if (mob->myDefinition >= GAMEDEF::getNumMob())
    {
	mob->myDefinition = MOB_NONE;
    }

    mob->myName.load(is);

    mob->myPos.load(is);
    mob->myMeditatePos.load(is);
    mob->myTarget.load(is);
    mob->myHome.load(is);

    is.read((char *) &c, sizeof(c));
    mob->myWaiting = c ? true : false;
    
    is.read((char *)&mob->myHP, sizeof(int));
    is.read((char *)&mob->myMaxHP, sizeof(int));
    is.read((char *)&mob->myMP, sizeof(int));
    is.read((char *)&mob->myFood, sizeof(int));
    is.read((char *)&mob->myAIState, sizeof(int));
    is.read((char *)&mob->myFleeCount, sizeof(int));
    is.read((char *)&mob->myBoredom, sizeof(int));
    is.read((char *)&mob->myYellHystersis, sizeof(int));
    is.read((char *)&mob->myNumDeaths, sizeof(int));
    is.read((char *)&mob->myUID, sizeof(int));
    is.read((char *)&mob->mySeed, sizeof(int));
    is.read((char *)&mob->myLevel, sizeof(int));
    is.read((char *)&mob->myExp, sizeof(int));
    is.read((char *)&mob->myExtraLives, sizeof(int));
    is.read((char *)&mob->mySearchPower, sizeof(int));
    is.read((char *)&mob->myCowardice, sizeof(int));
    is.read((char *)&mob->myKills, sizeof(int));
    is.read((char *)&mob->myRangeTimeout, sizeof(int));
    is.read((char *)&mob->mySpellTimeout, sizeof(int));

    is.read((char *) &mob->myLastDir, sizeof(int));
    is.read((char *) &mob->myLastDirCount, sizeof(int));

    is.read((char *)&val, sizeof(int));
    mob->myHasTriedSuicide = val ? true : false;

    is.read((char *)&val, sizeof(int));
    mob->setSwallowed(val ? true : false);

    is.read((char *)&val, sizeof(int));
    mob->myAngryWithAvatar = (val ? true : false);

    is.read((char *)&c, 1);
    mob->mySawMurder = (c ? true : false);
    is.read((char *)&c, 1);
    mob->mySawMeanMurder = (c ? true : false);
    is.read((char *)&c, 1);
    mob->mySawVictory = (c ? true : false);
    is.read((char *)&c, 1);
    mob->myAvatarHasRanged = (c ? true : false);
    
    YELL_NAMES		yell;
    FOREACH_YELL(yell)
    {
	is.read((char *)&c, 1);
	mob->myHeardYell[yell] = (c ? true : false);
	is.read((char *)&c, 1);
	mob->myHeardYellSameRoom[yell] = (c ? true : false);
    }

    SPELL_NAMES		spell;
    FOREACH_SPELL(spell)
    {
	is.read((char *)&c, 1);
	mob->myKnownSpells[spell] = c;
    }

    is.read((char *)&num, sizeof(int));

    for (i = 0; i < num; i++)
    {
	mob->myInventory.append(ITEM::load(is));
    }

    WORLD_NAMES		world;
    FOREACH_WORLD(world)
    {
	is.read((char *)&c, 1);
	if (c)
	{
	    c = 1;
	    mob->myWorldStash[world] = MOB::load(is);
	}
	else
	{
	    c = 0;
	    mob->myWorldStash[world] = nullptr;
	}
    }

    return mob;
}

ITEM *
MOB::getItemFromId(int itemid) const
{
    for (int i = 0; i < myInventory.entries(); i++) 
    { 
	if (myInventory(i)->getUID() == itemid) 
	    return myInventory(i);
    } 
    return 0; 
}

bool
MOB::hasVisibleEnemies() const
{
    PTRLIST<MOB *> list;

    getVisibleEnemies(list);
    if (list.entries())
	return true;
    return false;
}

ITEM *
MOB::getWieldedOrTopBackpackItem() const
{
    ITEM *item = lookupWeapon();
    if (item)
	return item;
    return getTopBackpackItem();
}

ITEM *
MOB::getTopBackpackItem(int depth) const
{
    for (int n = myInventory.entries(); n --> 0; )
    {
	ITEM *drop = myInventory(n);
	if (drop->isFlag())
	    continue;
	if (drop->isEquipped())
	    continue;
	if (drop->isWielded())
	    continue;

	if (!depth)
	    return drop;
	depth--;
    }
    return 0;
}

bool
MOB::actionDropTop()
{
    stopRunning();

    ITEM		*drop = getTopBackpackItem();

    if (drop)
	return actionDrop(drop);

    formatAndReport("%S <have> nothing to drop.");
    return true;
}

bool
MOB::actionDropAll()
{
    stopRunning();

    ITEM		*drop = 0;
    int		         dropped = 0;

    // Cap the loop to backpack size in case something goes horrificaly wrong.
    int			maxdrop = inventory().entries();
    while (dropped < maxdrop)
    {
	ITEM		*drop = getTopBackpackItem();

	if (!drop)
	    break;

	// found a droppable.
	actionDrop(drop);
	dropped++;
    }

    if (!dropped && isAvatar())
	formatAndReport("%S <have> nothing to drop.");

    return true;
}

bool
MOB::actionDropSurplus()
{
    stopRunning();
    ITEM		*weap, *wand, *arm, *ring;

    weap = lookupWeapon();
    wand = lookupWand();
    arm = lookupArmour();
    ring = lookupRing();
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i) == weap)
	    continue;
	if (myInventory(i) == wand)
	    continue;
	if (myInventory(i) == arm)
	    continue;
	if (myInventory(i) == ring)
	    continue;
	if (myInventory(i)->isPotion())
	    continue;

	if (myInventory(i)->isFlag())
	    continue;

	return actionDrop(myInventory(i));
    }
    // Check if our wand/weap is redundant
    if (weap && weap->getStackCount() > 1)
    {
	return actionDropButOne(weap);
    }
    if (wand && wand->getStackCount() > 1)
    {
	return actionDropButOne(wand);
    }
    return false;
}

bool
MOB::actionBagShake()
{
    myInventory.shuffle();
    formatAndReport("%S <shake> %r backpack.");
    return true;
}

bool
MOB::actionBagSwapTop()
{
    // Find top non-held and non-flag items.
    ITEM		*top = getTopBackpackItem();
    ITEM		*nexttop = getTopBackpackItem(1);

    if (nexttop)
    {
	formatAndReport("%S <bury> %O farther into %r backpack.", top);
	myInventory.removePtr(nexttop);
	myInventory.append(nexttop);
    }
    else
    {
	formatAndReport("%S <fiddle> with %r backpack.");
    }

    return true;
}

bool
MOB::hasSurplusItems() const
{
    bool	hasrange = false;
    bool	hasmelee = false;

    for (int i = 0; i < myInventory.entries(); i++)
    {
	// We can always drink these.
	if (myInventory(i)->isPotion())
	    continue;
	if (myInventory(i)->isFlag())
	    continue;
	// Gold isn't surplus!
	if (myInventory(i)->getDefinition() == ITEM_COIN)
	    continue;
	// Stuff we wear is important!
	if (myInventory(i)->isEquipped())
	    continue;
	if (myInventory(i)->isWielded())
	    continue;
	return true;
    }

    return false;
}

int
MOB::numSurplusRange() const
{
    int		numrange = 0;

    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i)->isRanged())
	{
	    numrange += myInventory(i)->getStackCount();
	}
    }

    // Reserve one range for our own use.
    return numrange - 1;
}

void
MOB::getVisibleEnemies(PTRLIST<MOB *> &list) const
{
    // If we are blind, we can't see anyone.
    if (hasItem(ITEM_BLIND))
	return;

    // We only care about the non-avatar case now.
    if (isAvatar())
	return;

    // Check if we are in FOV.  If so, the avatar is visible.
    if (getAvatar())
    {
	// Ignore dead avatars.
	if (!getAvatar()->alive())
	{
	    return;
	}
	// Ignore none hostile
	if (isFriends(getAvatar()))
	    return;

	// We need the double check because if you are meditating
	// and put them in the fov, that doesn't mean that 
	// the avatar is now in fov.
	if (pos().isFOV() && getAvatar()->pos().isFOV())
	{
	    list.append(getAvatar());
	}
    }
}

void
MOB::formatAndReport(const char *msg) const
{
    if (isAvatar() || pos().isFOV())
    {
	msg_format(msg, this);
    }
}

void
MOB::formatAndReport(const char *msg, MOB *object) const
{
    if (isAvatar() || pos().isFOV())
    {
	msg_format(msg, this, object);
    }
}

void
MOB::formatAndReport(const char *msg, const char *verb, MOB *object) const
{
    if (isAvatar() || pos().isFOV())
    {
	msg_format(msg, this, verb, object);
    }
}

void
MOB::formatAndReport(const char *msg, ITEM *object) const
{
    if (isAvatar() || pos().isFOV())
    {
	msg_format(msg, this, object);
    }
}

void
MOB::formatAndReport(const char *msg, const char *object) const
{
    if (isAvatar() || pos().isFOV())
    {
	msg_format(msg, this, object);
    }
}

int
MOB::numberMeleeHostiles() const
{
    int		dx, dy;
    MOB		*mob;
    int		hostiles = 0;

    FORALL_8DIR(dx, dy)
    {
	mob = pos().delta(dx, dy).mob();

	if (mob && !mob->isFriends(this))
	    hostiles++;
    }
    return hostiles;
}

void
MOB::clearCollision()
{
    myCollisionSources.clear();
    myCollisionTarget = 0;
}

void
MOB::setCollisionTarget(MOB *target)
{
    if (myCollisionTarget == target)
	return;

    J_ASSERT(!target || !myCollisionTarget);

    myCollisionTarget = target;
    if (target)
	target->collisionSources().append(this);
}

void
MOB::makeNoise(int loudness) const
{
    // Maybe account for stealth?
    pos().makeNoise(loudness);
}

bool
MOB::actionPortalFire(int dx, int dy, int portal)
{
    stopRunning();
    // No suicide.
    if (!dx && !dy)
    {
	formatAndReport("%S <decide> not to aim at %O.", this);
	return false;
    }
    
    // If swallowed, rather useless.
    if (isSwallowed())
    {
	formatAndReport("%S do not have enough room inside here.");
	return false;
    }

    clearBoredom();

    // Always display now.
 
    u8		symbol = '*';
    ATTR_NAMES	attr;
    int		portalrange = 60;

    attr = ATTR_BLUE;
    if (portal)
	attr = ATTR_ORANGE;
    
    pos().displayBullet(portalrange,
			dx, dy,
			symbol, attr,
			false);

    POS		vpos;
    vpos = pos().traceBulletPos(portalrange, dx, dy, false, false);

    TILE_NAMES	wall_tile = (TILE_NAMES) vpos.roomDefn().wall_tile;
    
    if (vpos.tile() != wall_tile && vpos.tile() != TILE_PROTOPORTAL)
    {
	msg_format("The portal dissipates at %O.", 0, vpos.defn().legend);
	return false;
    }

    if (!vpos.roomDefn().allowportal)
    {
	formatAndReport("The walls of this area seem unable to hold portals.");
	return false;
    }


    // We want sloppy targeting for portals.
    // This means that given a portal location of # fired at from the south,
    // we want:
    //
    // 1#1
    // 2*2
    //
    // as potential portals.
    //
    // One fired from the south-west
    //  2
    // 1#2
    // *1

    POS		alt[5];

    if (dx && dy)
    {
	alt[0] = vpos;
	alt[1] = vpos.delta(-dx, 0);
	alt[2] = vpos.delta(0, -dy);
	alt[3] = vpos.delta(0, dy);
	alt[4] = vpos.delta(dx, 0);
    }
    else if (dx)
    {
	alt[0] = vpos;
	alt[1] = vpos.delta(0, 1);
	alt[2] = vpos.delta(0, -1);
	alt[3] = vpos.delta(-dx, 1);
	alt[4] = vpos.delta(-dx, -1);
    }
    else
    {
	alt[0] = vpos;
	alt[1] = vpos.delta(1, 0);
	alt[2] = vpos.delta(-1, 0);
	alt[3] = vpos.delta(1, -dy);
	alt[4] = vpos.delta(-1, -dy);
    }

    for (int i = 0; i < 5; i++)
    {
	if (buildPortalAtLocation(alt[i], portal))
	    return true;
    }
    msg_report("The wall proved too unstable to hold a portal.");

    return false;
}

bool
MOB::buildPortalAtLocation(POS vpos, int portal) const
{
    int		origangle;

    origangle = vpos.angle();

    TILE_NAMES	wall_tile = (TILE_NAMES) vpos.roomDefn().wall_tile;
    TILE_NAMES	tunnelwall_tile = (TILE_NAMES) vpos.roomDefn().tunnelwall_tile;
    TILE_NAMES	floor_tile = (TILE_NAMES) vpos.roomDefn().floor_tile;

    // We want all our portals in the same space.
    vpos.setAngle(0);

    // Check if it is a valid portal pos?
    if (!vpos.prepSquareForDestruction())
    {
	return false;
    }

    // Verify the portal is well formed, ie, three neighbours are now
    // walls and the other is a floor.
    int		dir, floordir = -1;
    TILE_NAMES	tile;

    for (dir = 0; dir < 4; dir++)
    {
	tile = vpos.delta4Direction(dir).tile();
	if (tile == floor_tile)
	{
	    if (floordir < 0)
		floordir = dir;
	    else
	    {
		// Uh oh.
		return false;

	    }
	}
	// Note invalid shouldn't occur here as we will have transformed
	// that into a wall in prep for destrction.
	else if (tile == wall_tile || tile == TILE_PROTOPORTAL ||
		 tile == tunnelwall_tile || tile == TILE_INVALID)
	{
	    // All good.
	}
	else
	{
	    // Uh oh.
	    return false;
	}
    }

    // Failed to find a proper portal.
    if (floordir < 0)
	return false;

    // In floordir+2 we will be placing the mirror of the opposite
    // portal.  It is important that square is not accessible.  We
    // merely make sure it isn't a floor
    // Still an issue of having ants dig it out.  Ideally we'd
    // have the virtual portal flagged with MAPFLAG_PORTAL but
    // we don't do that currently in buildPortal and doing so
    // would mean we'd have to clean it up properly.

    POS		virtualportal;

    virtualportal = vpos.delta4Direction((floordir+2)&3);
    virtualportal = virtualportal.delta4Direction((floordir+2)&3);

    // We now point to the square behind the proposed virtual portal
    // this should be wall or invalid
    tile = virtualportal.tile();
    if (tile != wall_tile && tile != tunnelwall_tile && tile != TILE_PROTOPORTAL && tile != TILE_INVALID)
	return false;
    // Try neighbours.
    tile = virtualportal.delta4Direction((floordir+1)&3).tile();
    if (tile != wall_tile && tile != tunnelwall_tile && tile != TILE_PROTOPORTAL && tile != TILE_INVALID)
	return false;
    tile = virtualportal.delta4Direction((floordir-1)&3).tile();
    if (tile != wall_tile && tile != tunnelwall_tile && tile != TILE_PROTOPORTAL && tile != TILE_INVALID)
	return false;

    // We now know we aren't an isolated island.  Yet.

    vpos.map()->buildUserPortal(vpos, portal, (floordir+2) & 3, origangle);

    return true;
}

