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

#include "mob.h"

#include "map.h"
#include "msg.h"
#include "text.h"
#include "item.h"
#include "config.h"

// 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(p.map(), myPotion) == POTION_SLEEP)
            {
                if (mob->putToSleep(true,/*dc*/ 20, 15))
                    if (myInterest) *myInterest = true;

            }
	    if (ITEM::potionEffect(p.map(), myPotion) != EFFECT_NONE)
	    {
		if (myInterest)
		    *myInterest = true;
	    }
	    mob->applyEffect(mySrc, ITEM::potionEffect(p.map(), myPotion),
		ATTACKSTYLE_RANGE);
	}
	if (p.soakSquare(ITEM::asPotion(p.map(), 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;
};

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

///
/// Attack styles, has to be same translation unit :<
///
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();

    // If swallowed, only apply to local pos.
    if (isSwallowed())
    {
	pos().reportCone(dx, dy, 1, fireball);
	op(pos());
	return;
    }

    // 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>
bool
MOB::doRayAttack(int range, int dx, int dy, 
		u8 symbol, ATTR_NAMES attr,
		const char *verb, const char *fireball,
		bool piercing, const OP &op)
{
    // Shooting is interesting.
    clearBoredom();

    // If swallowed, only apply to local pos.
    if (isSwallowed())
    {
	pos().reportRay(dx, dy, fireball);
	bool anyhit = op(pos());
	return anyhit;
    }

    // Apply damage to everyone in range.
    POS vpos = pos().delta(dx, dy);
    vpos.reportRay(dx, dy, fireball);
    bool	anyhit;
    if (piercing)
    {
	anyhit = vpos.ray(range, dx, dy, this, symbol, attr, op,
		POS::filterPassable(),
		POS::filterAll());
    }
    else
    {
	anyhit = vpos.ray(range, dx, dy, this, symbol, attr, op,
		POS::filterPassable(),
		POS::filterNoMob());
    }

    return anyhit;
}

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;

    if (isSwallowed())
    {
	// target the top mob...
	pos().displayBullet(1,
			    dx, dy,
			    symbol, attr,
			    true);

	victim = pos().mob();
	if (victim)
	{
	    formatAndReport("%S %v %O.", verb, victim);
	    vpos = victim->pos();
	    vpos.reportFireball(area, fireball);
	    vpos.fireball(this, 1, symbol, attr, op); 
	    if (!alive())
		return vpos;
	}

	// Continue if piercing
	if (!piercing)
	    return pos();
    }

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

    //  Attempt 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::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;
}

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

    MAP		*map = pos().map();

    // You always transform, even if the new form is the same...
    formatAndReport("%S <be> hidden by a bright violet light.");

    MOB_NAMES	newmobtype = chooseAnyNPCType();

    MOB *newmob = MOB::create(map, newmobtype, /*items*/false);

    // All flags stay with original mob, all inventory transfers (to
    // start at least :>)
    newmob->stealAllItems(this);

    // How much of the brain should transfer?
    newmob->myPos = myPos;
    newmob->setMap(map);
    newmob->skipNextTurn();

    clearAllPos();

    {
	// If we were already polyd, we destroy the intermediate
	// body here and just keep the original, which ideally
	// isn't a deep stack...
	// Note we are quite capable of keeping an full stack,
	// but that sort of becomes a stack of free lives...
	if (myOrigBody)
	{
	    newmob->myOrigBody = myOrigBody;
	    map->addDeadMob(this);
	}
	else
	{
	    // Put our body into storage.
	    newmob->myOrigBody.reset(this);
	}
    }
    newmob->pos().replaceMob(this, newmob);

    // Forms the shape of...
    newmob->giftItem(ITEM_POLYMORPH);

    // Drop whatever doesn't fit.
    newmob->updateEquippedPostPoly();

    newmob->formatAndReport("%S <emerge> from the light.");
    return true;
}

bool
MOB::actionUnpolymorph(MOB *src)
{
    if (!hasItem(ITEM_POLYMORPH))
	return false;

    if (!pos().valid())
	return false;

    MAP		*map = pos().map();

    formatAndReport("%S <be> covered with amber light.");
    // Not really needed but probably the spam is good.
    removeItem(lookupItem(ITEM_POLYMORPH));

    // If you have an orig body, restore.  If not.  Die.
    if (myOrigBody)
    {
	// Transfer all non-flags.
	MOB	*newmob = myOrigBody->copy();

	newmob->stealAllItems(this);
	newmob->myPos = pos();
	newmob->setMap(map);
	newmob->skipNextTurn();

	clearAllPos();

	newmob->pos().replaceMob(this, newmob);
	newmob->updateEquippedPostPoly();
	newmob->formatAndReport("%S <emerge> from the light.");
	return true;
    }
    else
    {
	// Die!
	kill(src);
	return true;
    }
    return true;
}

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::actionLayEgg()
{
    if (defn().eggtype == MOB_NONE)
    {
	formatAndReport("%S cannot lay eggs.");
	return false;
    }

    ITEM *egg = ITEM::create(map(), ITEM_EGG);
    egg->setMobType(defn().eggtype);
    formatAndReport("%S <lay> an egg.");
    egg->move(pos());
    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());
    if (isAvatar())
	butone->markDiscarded(true);

    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;
    MOB		*gainer = nullptr;
    if (isSwallowed())
    {
	gainer = pos().mob();
	if (gainer == this)
	{
	    // Odd?
	    formatAndReport("%S cannot drop items inside %O.", gainer);
	    return true;
	}
    }
    for (i = myInventory.entries(); i --> 0; )
    {
	// 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);
		if (isAvatar())
		    drop->markDiscarded(true);
		if (gainer)
		    gainer->addItem(drop);
		else
		    drop->move(pos());
	    }
	    else
	    {
		formatAndReport("%S <drop> %O.", myInventory(i));
		myInventory(i)->setEquipped(false);
		myInventory(i)->setWielded(false);
		if (isAvatar())
		    myInventory(i)->markDiscarded(true);
		ITEM *drop = myInventory(i);
		myInventory.removePtr(drop);
		if (gainer)
		    gainer->addItem(drop);
		else
		    drop->move(pos());
	    }
	    // Quit as we found something.
	    fail = false;
	    break;
	}
    }
    // This would be clever, but our various other loops
    // don't do null checks and we don't knwo where they all are.
    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)
    {
	if (visibleFromAvatar())
	    item->markMagicClassKnown();
    }
    else
    {
	formatAndReport("Nothing happens.");
    }


    delete item;

    return true;
}


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

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

    if (!defn().canread)
    {
	formatAndReport("%S <do> not know how to read.");
	return true;
    }

    if (item->isSpellbook())
    {
	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 (visibleFromAvatar())
	{
	    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(np.map(), pos().depth() + /*mean!*/ 4);
		    if (summon)
		    {
			formatAndReport("%S <summon> %O to %r side.", summon);
			summon->move(np);
		    }
		    else
			formatAndReport("The air shudders."); // Shouldn'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);

	if (item->getDefinition() == ITEM_EGG)
	{
	    epos.postEvent((EVENTTYPE_NAMES)(EVENTTYPE_SHOUT|EVENTTYPE_LONG), ' ', ATTR_YOLK, "*splat*");
	    if (epos.mob())
	    {
		epos.mob()->giftItem(ITEM_BLIND);
	    }
	    delete item;
	}
	else
	    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 (!item)
	return false;

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

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

    int			breakchance = 0;
    bool		didsomething = false;
    bool		swallowed = isSwallowed();
    MOB			*newowner = this;

    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:
		    self->doEmote("*pick*");
		    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:
		    self->doEmote("*pick*");
		    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(t.map(), 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)
	    {
		doEmote("*dig*");
		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)
	    {
		doEmote("*bash*");
		// Must be closed or we'd be passable...
		formatAndReport("%S <swing> at %O, breaking it.",
			t.defn().legend);
		// Don't break on doors.
		breakchance = 0;
		makeNoise(3);
		didsomething = true;
		t.setTile(TILE_DOOR_BROKEN);
	    }
	    else if (t.tile() == TILE_ALTAR)
	    {
		doEmote("*bash*");
		// Must be closed or we'd be passable...
		formatAndReport("%S <swing> at %O, breaking it.",
			t.defn().legend);
		// Don't break on doors.
		breakchance = 0;
		makeNoise(3);
		didsomething = true;
		formatAndReport("With the destruction of the altar the apocalypse is averted!");
		t.setTile(TILE_ALTAR_BROKEN);
		t.map()->avertApocalypse();
	    }
	    else
	    {
		formatAndReport("%S <swing> at %O to no effect.",
			t.defn().legend);
		makeNoise(4);
		didsomething = true;
	    }
	    break;
	}
	case ITEM_WRITINGSET:
	{
	    if (!defn().canread)
	    {
                formatAndReport("%S cannot write.");
		return true;
	    }
            if (!target)
            {
                formatAndReport("%S <draw> nine perpendicular red lines in the air.");
                didsomething = true;
            }
            else 
            {
                switch (target->getDefinition())
                {
                    case ITEM_SPELLBOOK_BLANK:
                    {
			doEmote("*write*");
                        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(map(),spell));
			formatAndReport("%S <write> %O.", target);
			addItem(target);
                        didsomething = true;
                        breakchance = 20;
                        break;
                    }
                    case ITEM_SCROLL_BLANK:
                    {
			doEmote("*write*");
                        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(map(), scroll));
			formatAndReport("%S <write> %O.", target);
			addItem(target);
                        didsomething = true;
                        breakchance = 20;
                        break;
                    }
                }
            }
	    break;
	}
    }
    
    if (item->isWand())
    {
	WAND_NAMES	wand = item->asWand();
	bool		interesting = false;

	bool		cansee = visibleFromAvatar() || t.visibleFromAvatar();

	formatAndReport("%S <zap> %O1.", item);
	breakchance = GAMEDEF::wanddef(wand)->breakchance;
	didsomething = true;

	MOB 	*victim = t.mob();
	if (swallowed)
	{
	    if (dx || dy)
		victim = pos().mob();
	    else
		victim = this;
	}

	switch (item->asWand())
	{
	    case WAND_NONE:
		// Not actually a wand...
		break;

	    case WAND_NOTHING:
		// Intentionally nothing.
		break;

	    case WAND_RESTORATION:
	    {
		// Only restore first possible stack..
		if (victim)
		    interesting |= victim->applyRestoration(this);
		if (!swallowed && !interesting)
		    interesting |= t.restoreSquare();
		break;
	    }

	    case WAND_TELEPORT:
	    {
		POS		vpos = pos().delta(dx, dy);
		if (victim)
		{
		    interesting = true;
		    victim->actionTeleport(true);
		}
		if (!swallowed &&
		    vpos.teleportItems())
		{
		    // Report?
		    interesting = true;
		}
		break;
	    }

	    case WAND_FIRE:
	    {
		interesting = true;
		EFFECT_OP	op(this, EFFECT_WAND_FIRE, true);

		{
		    // Send a ray.
		    doRayAttack(GAMEDEF::wanddef(WAND_FIRE)->range, dx, dy,
			    '*', ATTR_FIRE,
			    "burn", "ray of fire",
			    /*pierce*/true,
			    [&](POS p) -> bool
			    {
				p.burnSquare();
				return op(p);
			    });
		}

		break;
	    }
	    case WAND_DIG:
	    {
		interesting = true;
		EFFECT_OP	op(this, EFFECT_WAND_FIRE, true);

		if (!dx && !dy)
		{
		    // Target yourself.
		    formatAndReport("%r stomach rubmles.");
		    eatFood(50);
		    break;
		}


		if (swallowed)
		{
		    formatAndReport("%S <dig> a hole through %O.", pos().mob());
		    if (!canMove(t, /*mob*/true, /*onlypassive*/true))
		    {
			formatAndReport("There is no room to escape there!");
		    }
		    else
		    {
			formatAndReport("%S <escape> through the hole.");
			// don't let them eat right away!
			pos().mob()->skipNextTurn();
			setSwallowed(false);
			move(t);
		    }
		    // Don't do anyother tests of walls, etc.
		    break;
		}


		{
		    t.reportRay(dx, dy, "ray of digging");
		    t.ray(GAMEDEF::wanddef(WAND_DIG)->range, dx, dy,
			    this,
			    '*', ATTR_GREY,
			    [&](POS p) -> bool
			    {
				p.digSquare();
				return op(p);
			    },
			    POS::filterValid(),
			    // Should be come passable if dig worked...
			    POS::filterPassable());
		}

		break;
	    }
	    case WAND_OPEN:
	    {
		EFFECT_OP	op(this, EFFECT_WAND_FIRE, true);

		if (!dx && !dy)
		{
		    // Target yourself.
		    formatAndReport("%S <feel> more receptive to new ideas.");
		    interesting = true;
		    break;
		}

		if (swallowed)
		{
		    interesting = true;
		    formatAndReport("%S <see> light as the jaws open.");
		    POS	escape = pos().randAdjacent();
		    if (!escape.valid())
		    {
			formatAndReport("There is no room to escape!");
		    }
		    else
		    {
			formatAndReport("%S <be> spat out.");
			// don't let them eat right away!
			pos().mob()->skipNextTurn();
			setSwallowed(false);
			move(escape);
		    }
		    // Don't do anyother tests of walls, etc.
		    break;
		}


		switch (t.tile())
		{
		    case TILE_DOOR_CLOSED:
		    case TILE_DOOR_JAMMED_UNKNOWN:
		    case TILE_DOOR_JAMMED:
		    case TILE_DOOR_LOCKED_UNKNOWN:
		    case TILE_DOOR_LOCKED:
			formatAndReport("The door opens.");
			interesting = true;
			t.setTile(TILE_DOOR_OPEN);
			break;
		    case TILE_SECRETDOOR:
			formatAndReport("A secret door opens!");
			interesting = true;
			t.setTile(TILE_DOOR_OPEN);
			break;
		}

		break;
	    }

	    case WAND_POLY:
	    {
		POS		vpos = pos().delta(dx, dy);

		if (victim)
		{
		    bool	selfpoly = victim == this;
		    interesting = true;
		    victim->actionPolymorph(true);

		    if (selfpoly)
		    {
			newowner = nullptr;
			MOBLIST		allmobs;
			vpos.getAllMobs(allmobs);
			for (auto && testmob : allmobs)
			{
			    if (testmob->ownsSpecificItem(item))
			    {
				newowner = testmob;
				break;
			    }
			}
		    }
		}
		else if (!swallowed &&
			  vpos.polymorphItems())
		{
		    // Report?
		    interesting = true;
		}
		break;
	    }
	}
	if (interesting)
	{
	    if (cansee)
		item->markMagicClassKnown();
	}
	else
	{
	    formatAndReport("Nothing happens.");
	}
    }

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

    if (!newowner)
	return true;

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

    if (!didsomething)
    {
        if (target)
            newowner->formatAndReport("%S cannot figure out how to use %O on that.", item);
        else
            newowner->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 (item->defn().quest)
    {
	formatAndReport("%S <feel> it would be unwise to 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 (!defn().canequip)
    {
	formatAndReport("%S cannot equip anything.");
	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 (!defn().canequip)
    {
	formatAndReport("%S cannot equip anything.");
	return true;
    }

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

    if (!defn().canwield)
    {
	formatAndReport("%S cannot wield anything.");
	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_MURDERER:
	{
	    const char *msg[] =
	    {		// MURDERER
		"Killer!",
		"Murderer!",
		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 (!pos().map()->apocalypseActive())
		{
		    formatAndReport("%S <escape> the %o!", "Labyrinth" );
		    myHasWon = true;
		}
		else
		{
		    formatAndReport("%S <see> no point leaving while the apocalpytic countdown continues!");
		}
	    }
	    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 (!pos().map()->apocalypseActive())
		{
		    formatAndReport("%S <escape> the %o!", "Labyrinth" );
		    myHasWon = true;
		}
		else
		{
		    formatAndReport("%S <see> no point leaving while the apocalpytic countdown continues!");
		}
	    }
	    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.
	// Ensure the enemy is smaller than your self so worms don't 
	// swallow worms.
	if (!isSwallowed() && defn().swallows && damage && rand_chance(50)
	    && size() > victim->size())
	{
	    formatAndReport("%S <swallow> %O whole.", victim);
	    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;
}

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

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

    ITEM_NAMES		ammo = ITEM_NONE;

    // Everyone should require ammo...
    if (lookupRanged())
	ammo = lookupRanged()->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.");
		if (!acted)
		{
		    if (defn().useitems && hasUnbrokenItem(ITEM::lookupWand(map(), WAND_OPEN)))
		    {
			return actionApplyTool(lookupUnbrokenItem(ITEM::lookupWand(map(), WAND_OPEN)), dx, dy, nullptr);

		    }
		    if (defn().useitems && hasUnbrokenItem(ITEM_PICKAXE))
		    {
			return actionApplyTool(lookupUnbrokenItem(ITEM_PICKAXE), dx, dy, nullptr);

		    }
		    if (defn().kickdoors)
		    {
			return actionKick(dx, dy);
		    }
		}
		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.");
		if (!acted)
		{
		    if (defn().useitems && hasUnbrokenItem(ITEM::lookupWand(map(), WAND_OPEN)))
		    {
			return actionApplyTool(lookupUnbrokenItem(ITEM::lookupWand(map(), WAND_OPEN)), dx, dy, nullptr);

		    }
		    if (defn().useitems && hasUnbrokenItem(ITEM_LOCKPICK))
		    {
			return actionApplyTool(lookupUnbrokenItem(ITEM_LOCKPICK), dx, dy, nullptr);

		    }
		    if (defn().useitems && hasUnbrokenItem(ITEM_PICKAXE))
		    {
			return actionApplyTool(lookupUnbrokenItem(ITEM_PICKAXE), dx, dy, nullptr);

		    }
		    if (defn().kickdoors)
		    {
			return actionKick(dx, dy);
		    }
		}
		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);

    doEmote("*kick*");

    // 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_ALTAR:
	{
            int points = rollStrengthPoints() - 2;
            if (points >= 0)
            {
                // Break down altar
                formatAndReport("%S <kick> altar.  It crashes to pieces!");
		formatAndReport("With the destruction of the altar the apocalypse is averted!");
		t.setTile(TILE_ALTAR_BROKEN);
		pos().map()->avertApocalypse();
                makeNoise(2);
            }
            else
            {
                formatAndReport("%S <kick> the altar.  WHAAM!");
                makeNoise(3 - points);
            }
            return true;
	}

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

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)
    {
	// 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:
    bool	swallowed = isSwallowed();

    // 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)
    {
	// Blast affects only you if swallowed
	if (swallowed)
	{
	    pos().reportFireball(1, GAMEDEF::spelldef(spell)->fireball);
	    pos().fireball(this, 1,
			GAMEDEF::spelldef(spell)->symbol,
			GAMEDEF::spelldef(spell)->attr,
			op);
	}
	else
	{
	    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;

    // We may be no longer swallowed...
    swallowed = isSwallowed();

    // Special effects...
    switch (spell)
    {
	case SPELL_TELEPORT:
	{
	    POS		vpos = pos().delta(dx, dy);
	    MOB		*mob = vpos.mob();
	    if (swallowed)
	    {
		if (dx || dy)
		    mob = pos().mob();		// Your swallower!
		else
		    mob = this;
	    }
	    if (mob)
	    {
		mob->actionTeleport(true);
	    }
	    if (!swallowed &&
		vpos.teleportItems())
	    {
		// Report?
	    }
	    break;
	}
        case SPELL_DISINTEGRATE:
        {
	    POS		vpos = pos().delta(dx, dy);

	    if (swallowed && (dx || dy))
	    {
		formatAndReport("%S <disintegrate> a hole through %O.", pos().mob());
		if (!canMove(vpos, /*mob*/true, /*onlypassive*/true))
		{
		    formatAndReport("There is no room to escape there!");
		}
		else
		{
		    formatAndReport("%S <escape> through the hole.");
		    // don't let them eat right away!
		    pos().mob()->skipNextTurn();
		    setSwallowed(false);
		    move(vpos);
		}
		// Don't do anyother tests of walls, etc.
		break;
	    }

            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(vpos.map(), 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:
        {
	    // No ice if swallowed:
	    if (swallowed)
		break;
	    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:
	{
	    // Yep, you can push yourself around, Newton be damned!
	    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::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::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))
	{
	    stopRunning();
	    // Avatars smash altars.  Heathens!
	    if (isAvatar())
	    {
		if (t.tile() == TILE_ALTAR)
		    return actionKick(dx, dy);
	    }
	    // Another special, likely door...
	    if (defn().opendoors || defn().breakdoors)
	    {
		return actionOpen(dx, dy);
	    }
	    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(map(), 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(map(), getDefinition(), false);
		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(map(), 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(map(), ITEM_COIN);
	coin->setMaterial(item->giltMaterial());
	coin->setStackCount(1 * item->getStackCount());
	coin->silentMove(pos());
	actionPickup(coin);
    }

    delete item;
    return true;
}

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

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

    // 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 (!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 (isSwallowed())
    {
	formatAndReport("%S <find> only guts and viscera.");
	return false;
    }
    if (!item)
    {
	formatAndReport("%S <search> the ground foolishly.");
	return false;
    }

    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);
    if (isAvatar())
    {
	// Obviously you like this!
	item->markDiscarded(false);
    }

    item->move(POS());

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

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, *ranged, *arm, *ring;

    weap = lookupWeapon();
    ranged = lookupRanged();
    arm = lookupArmour();
    ring = lookupRing();
    for (int i = 0; i < myInventory.entries(); i++)
    {
	if (myInventory(i) == weap)
	    continue;
	if (myInventory(i) == ranged)
	    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 ranged/weap is redundant
    if (weap && weap->getStackCount() > 1)
    {
	return actionDropButOne(weap);
    }
    if (ranged && ranged->getStackCount() > 1)
    {
	return actionDropButOne(ranged);
    }
    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::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;
}


