/*      SCCS Id: @(#)pickup.c   3.1     93/04/11        */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed.  See license for details. */

/*
 *      Contains code for picking objects up, and container use.
 */

#include        "hack.h"

static void FDECL(unsplitobj, (struct obj *,struct obj *,long));
static void FDECL(simple_look, (struct obj *,BOOLEAN_P));
static boolean FDECL(query_classes, (char *,boolean *,boolean *,
			     const char *,struct obj *,BOOLEAN_P,BOOLEAN_P));
static void FDECL(check_here, (BOOLEAN_P));
static int FDECL(pickup_object, (struct obj *,struct obj *));
static boolean FDECL(mbag_explodes, (struct obj *,int));
STATIC_PTR int FDECL(in_container,(struct obj *));
STATIC_PTR int FDECL(ck_bag,(struct obj *));
STATIC_PTR int FDECL(out_container,(struct obj *));

/*
 *  How much the weight of the given container will change when the given
 *  object is removed from it.  This calculation must match the one used
 *  by weight() in mkobj.c.
 */
#define DELTA_CWT(cont,obj)             \
    ((cont)->cursed ? (obj)->owt * 2 :  \
		      1 + ((obj)->owt / ((cont)->blessed ? 4 : 2)))

static const char moderateloadmsg[] = "You have a little trouble lifting";
static const char nearloadmsg[] = "You have much trouble lifting";

static void
unsplitobj(obj_block, obj_chip, resplit)
register struct obj *obj_block, *obj_chip;
long resplit;   /* non-zero => shift the quantities */
{
	if (obj_block->nobj != obj_chip) {
		impossible("can't unsplit objects");
	} else if (resplit) { /* 1st object should be reduced to 'resplit' */
		obj_chip->quan += (obj_block->quan - resplit);
		obj_block->quan = resplit;
		obj_block->owt = weight(obj_block);
		obj_chip->owt = weight(obj_chip);
	} else {  /* 2nd obj should be merged back into 1st, then destroyed */
		obj_block->nobj = obj_chip->nobj;
		obj_block->nexthere = obj_chip->nexthere;
		obj_block->quan += obj_chip->quan;
		obj_block->owt = weight(obj_block);
		/* no need to worry about 'unsplitbill'; unsplit only occurs
		   when unable to pick something up, hence we're not dealing
		   with billable objects here (I hope!)
		 */
		dealloc_obj(obj_chip);
	}
}

/* much simpler version of the look-here code; used by query_classes() */
static void
simple_look(otmp, here)
struct obj *otmp;       /* list of objects */
boolean here;           /* flag for type of obj list linkage */
{
	/* Neither of the first two cases is expected to happen, since
	 * we're only called after multiple classes of objects have been
	 * detected, hence multiple objects must be present.
	 */
	if (!otmp) {
	    impossible("simple_look(NULL)");
	} else if (!(here ? otmp->nexthere : otmp->nobj)) {
	    pline("%s", doname(otmp));
	} else {
	    winid tmpwin = create_nhwindow(NHW_MENU);
	    putstr(tmpwin, 0, "");
	    do {
		putstr(tmpwin, 0, doname(otmp));
		otmp = here ? otmp->nexthere : otmp->nobj;
	    } while (otmp);
	    display_nhwindow(tmpwin, TRUE);
	    destroy_nhwindow(tmpwin);
	}
}

int
collect_obj_classes(ilets, otmp, here, incl_gold)
char ilets[];
register struct obj *otmp;
boolean here, incl_gold;
{
	register int iletct = 0;
	register char c, last_c = '\0';

	if (incl_gold)
		ilets[iletct++] = def_oc_syms[GOLD_CLASS];
	ilets[iletct] = '\0'; /* terminate ilets so that index() will work */
	while (otmp) {
		c = def_oc_syms[(int)otmp->oclass];
		if (c != last_c && !index(ilets, (last_c = c)))
			ilets[iletct++] = c,  ilets[iletct] = '\0';
		otmp = here ? otmp->nexthere : otmp->nobj;
	}

	return iletct;
}

static boolean
query_classes(oclasses, one_at_a_time, everything, action, objs, here, incl_gold)
char oclasses[];
boolean *one_at_a_time, *everything;
const char *action;
struct obj *objs;
boolean here, incl_gold;
{
	char ilets[20], inbuf[BUFSZ];
	int iletct, oclassct;
	char qbuf[QBUFSZ];

	oclasses[oclassct = 0] = '\0';
	*one_at_a_time = *everything = FALSE;
	iletct = collect_obj_classes(ilets, objs, here, incl_gold);
	if (iletct == 0) {
		return FALSE;
	} else if (iletct == 1) {
		oclasses[0] = def_char_to_objclass(ilets[0]);
		oclasses[1] = '\0';
	} else  {       /* more than one choice available */
		const char *where = 0;
		register char sym, oc_of_sym, *p;
		/* additional choices */
		ilets[iletct++] = ' ';
		ilets[iletct++] = 'a';
		ilets[iletct++] = 'A';
		ilets[iletct++] = (objs == invent ? 'i' : ':');
		ilets[iletct] = '\0';
ask_again:
		oclasses[oclassct = 0] = '\0';
		*one_at_a_time = *everything = FALSE;
		Sprintf(qbuf,"What kinds of thing do you want to %s? [%s]",
			action, ilets);
		getlin(qbuf,inbuf);
		if (*inbuf == '\033') return FALSE;

		for (p = inbuf; (sym = *p++); ) {
		    /* new A function (selective all) added by GAN 01/09/87 */
		    if (sym == ' ') continue;
		    else if (sym == 'A') *one_at_a_time = TRUE;
		    else if (sym == 'a') *everything = TRUE;
		    else if (sym == ':') {
			simple_look(objs, here);  /* dumb if objs==invent */
			goto ask_again;
		    } else if (sym == 'i') {
			(void) display_inventory(NULL, FALSE);
			goto ask_again;
		    } else {
			oc_of_sym = def_char_to_objclass(sym);
			if (index(ilets,sym)) {
			    oclasses[oclassct++] = oc_of_sym;
			    oclasses[oclassct] = '\0';
			} else {
			    if (!where)
				where = !strcmp(action,"pick up")  ? "here" :
					!strcmp(action,"take out") ?
							    "inside" : "";
			    if (*where)
				pline("There are no %c's %s.", sym, where);
			    else
				You("have no %c's.", sym);
			}
		    }
		}
		if (!oclassct && !*everything) *one_at_a_time = TRUE;
	}
	return TRUE;
}

/* look at the objects at our location, unless there are too many of them */
static void
check_here(picked_some)
boolean picked_some;
{
	register struct obj *obj;
	register int ct = 0;

	/* count the objects here */
	for (obj = level.objects[u.ux][u.uy]; obj; obj = obj->nexthere) {
	    if (obj != uchain)
		ct++;
	}

	/* If there are objects here, take a look. */
	if (ct) {
	    if (flags.run) nomul(0);
	    flush_screen(1);
	    if (ct < 5) {
		(void) dolook();
	    } else {
		read_engr_at(u.ux,u.uy);
		pline("There are several %sobjects here.",
		      picked_some ? "more " : "");
	    }
	} else {
	    read_engr_at(u.ux,u.uy);
	}
}

void
pickup(all)
int all;        /* all >= 0 => yes/no; < 0 => -count */
{
	register struct obj *obj;
	struct obj *obj2, *objx;
	boolean all_of_a_type, selective;
	char oclasses[20];
	long count;
	int pick, pick_count = 0;

	count = (all < 0) ? (-1L * all) : 0L;
	if (count) all = 0;

	if (all && (flags.nopick || !OBJ_AT(u.ux, u.uy) ||
			(is_pool(u.ux, u.uy) && !Underwater))) {
		read_engr_at(u.ux, u.uy);
		return;
	}

	if (Levitation && !Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz)) {
		if ((multi && !flags.run) || (all && !flags.pickup))
			read_engr_at(u.ux,u.uy);
		return;
	}

	/* multi && !flags.run means they are in the middle of some other
	 * action, or possibly paralyzed, sleeping, etc.... and they just
	 * teleported onto the object.  They shouldn't pick it up.
	 */
	if ((multi && !flags.run) || (all && !flags.pickup)) {
		check_here(FALSE);
		return;
	}

	oclasses[0] = '\0';     /* types to consider (empty for all) */
	all_of_a_type = TRUE;   /* take all of considered types */
	selective = FALSE;      /* ask for each item */

	if (all) {
		if (flags.pickup) Strcpy(oclasses, flags.pickup_types);
	} else {
		/* check for more than one object */
		register int ct = 0;

		for (obj = level.objects[u.ux][u.uy]; obj; obj = obj->nexthere)
		    ct++;

		if (ct >= 2) {
		    pline("There are several objects here.");
		    count = 0;

		    /* added by GAN 10/24/86 to allow selective picking up */
		    if (!query_classes(oclasses, &selective, &all_of_a_type,
			  "pick up", level.objects[u.ux][u.uy], TRUE, FALSE))
			return;
		}
	}

	for (obj = level.objects[u.ux][u.uy]; obj; obj = obj2) {
		obj2 = obj->nexthere;   /* perhaps obj will be picked up */
		objx = 0;
		if (flags.run) nomul(0);

		if (!selective && oclasses[0] && !index(oclasses,obj->oclass))
			continue;

		if (!all_of_a_type) {
		    char qbuf[QBUFSZ];
		    Sprintf(qbuf, "Pick up %s?", doname(obj));
		    switch ((obj->quan < 2L) ? ynaq(qbuf) : ynNaq(qbuf)) {
		    case 'q': return;
		    case 'n': continue;
		    case 'a':
			all_of_a_type = TRUE;
			if (selective) {
			    selective = FALSE;
			    oclasses[0] = obj->oclass;
			    oclasses[1] = '\0';
			}
			break;
		    case '#':   /* count was entered */
			if (!yn_number) continue; /* 0 count => No */
			else count = yn_number;
			/* fall thru */
		    default:    /* 'y' */
			break;
		    }
		}

		if (count) {
		    /* Pickup a specific number of items; split the object
		       unless count corresponds to full quantity.  1 special
		       case:  cursed loadstones will remain as merged unit.
		     */
		    if (count < obj->quan &&
			    (!obj->cursed || !(obj->otyp == LOADSTONE ||
			    obj->otyp == STONE_OF_ROTTING))) {
			objx = splitobj(obj, count);
			if (!objx || objx->nexthere != obj2)
			    impossible("bad object split in pickup");
		    }
		    count = 0;  /* reset */
		}
		if ((pick = pickup_object(obj, objx)) < 0) break;
		pick_count += pick;
	}

	/*
	 *  Re-map what is at the hero's location after the pickup so the
	 *  map is correct.
	 */
	newsym(u.ux,u.uy);

	/* see whether there's anything else here, after auto-pickup is done */
	if (all && flags.pickup) check_here(pick_count > 0);
}

/*
 * Pick up an object from the ground or out of a container and add it to
 * the inventory.  Returns -1 if pickup() should break out of its loop,
 * 0 if nothing picked up, 1 if otherwise.
 */
static int
pickup_object(obj, objx)
struct obj *obj, *objx;
{
	int wt, nearload;
	long pickquan;

	/* in case of auto-pickup, where we haven't had a chance
	   to look at it yet; affects docall(SCR_SCARE_MONSTER) */
	if (!Blind) obj->dknown = 1;

#ifdef INVISIBLE_OBJECTS
	if (obj->oinvis && !See_invisible) obj->dknown = 1;
#endif

	if (obj == uchain) {    /* do not pick up attached chain */
	    return 0;
	} else if (obj->oartifact && !touch_artifact(obj,&youmonst)) {
	    return 0;
	} else if (obj->otyp == GOLD_PIECE) {
	    /*
	     *  Special consideration for gold pieces...
	     */
	    long iw = (long)max_capacity() - ((u.ugold + 50L) / 100L);
	    long gold_capacity = ((-iw) * 100L) - 50L + 99L - u.ugold;

	    if (gold_capacity <= 0L) {
		if (objx) unsplitobj(obj, objx, 0L);
       pline("There %s %ld gold piece%s here, but you cannot carry any more.",
			(obj->quan == 1L) ? "is" : "are",
			obj->quan, plur(obj->quan));
		return 0;
	    } else if (gold_capacity < obj->quan) {
		if (objx) unsplitobj(obj, objx, 0L);
		You("can only carry %s of the %ld gold pieces lying here.",
		    gold_capacity == 1L ? "one" : "some", obj->quan);
		pline("%s %ld gold piece%s.",
		    nearloadmsg, gold_capacity, plur(gold_capacity));
		u.ugold += gold_capacity;
		obj->quan -= gold_capacity;
		costly_gold(obj->ox, obj->oy, gold_capacity);
	    } else {
		u.ugold += obj->quan;
		if ((nearload = near_capacity()) != 0)
		    pline("%s %ld gold piece%s.",
			  nearload < MOD_ENCUMBER ?
			  moderateloadmsg : nearloadmsg,
			  obj->quan, plur(obj->quan));
		else
		    prinv(NULL, obj, 0L);
		costly_gold(obj->ox, obj->oy, obj->quan);
		delobj(obj);
	    }
	    flags.botl = 1;
	    if (flags.run) nomul(0);
	    return 1;
	} else if (obj->otyp == CORPSE) {

	    if ( (obj->corpsenm == PM_COCKATRICE || obj->corpsenm == PM_CHICKATRICE ||
		  obj->corpsenm == PM_BASILISK || obj->corpsenm == PM_ASPHYNX) && !uarmg
#ifdef POLYSELF
		&& !resists_ston(uasmon)
#endif
	    ) {
#ifdef POLYSELF
		if (poly_when_stoned(uasmon) && polymon(PM_STONE_GOLEM))
		    display_nhwindow(WIN_MESSAGE, FALSE);
		else
#endif
		{
		  pline("Touching the corpse is a fatal mistake.");
		    You("turn to stone.");
		    You("die...");
		    killer_format = KILLED_BY_AN;
		    killer = "petrifying corpse";
		    done(STONING);
		    return -1;
		}
	    } else if (is_rider(&mons[obj->corpsenm])) {
		pline("At your touch, the corpse suddenly moves...");
		revive_corpse(obj, 1, FALSE);
		exercise(A_WIS, FALSE);
		return -1;
	    }
	} else  if (obj->otyp == SCR_SCARE_MONSTER) {
	    if (obj->blessed) obj->blessed = 0;
	    else if (!obj->spe && !obj->cursed) obj->spe = 1;
	    else {
		pline("The scroll%s turn%s to dust as you pick %s up.",
			plur(obj->quan), (obj->quan == 1L) ? "s" : "",
			(obj->quan == 1L) ? "it" : "them");
		if (!(objects[SCR_SCARE_MONSTER].oc_name_known) &&
				    !(objects[SCR_SCARE_MONSTER].oc_uname))
		    docall(obj);
		useupf(obj);
		return 1;       /* tried to pick something up and failed, but
				   don't want to terminate pickup loop yet   */
	    }
	}

	wt = max_capacity() + (int)obj->owt;
	if (obj->otyp == LOADSTONE)
	    goto lift_some;     /* pick it up even if too heavy */
#ifdef POLYSELF
	if (obj->otyp == BOULDER && throws_rocks(uasmon)) {
	    goto lift_some;
	}
#endif
	if (wt > 0) {
	    if (obj->quan > 1L) {
		/* see how many we can lift */
		long qq, savequan = obj->quan;
		int iw = max_capacity();
		/*  This is correct only because containers */
		/*  don't merge.        -dean               */
		for (qq = 1; qq < savequan; qq++) {
			obj->quan = qq;
			if (iw + weight(obj) > 0)
				break;
		}
		obj->quan = savequan;
		qq--;
		/* we can carry qq of them */
		if (qq) {
		    if (objx) {         /* temporarily unsplit */
			savequan = obj->quan;
			obj->quan += objx->quan;
		    }
		    You("can only carry %s of the %s lying here.",
			(qq == 1L) ? "one" : "some", doname(obj));
		    if (objx) {         /* re-do the prior split */
			obj->quan = savequan;
			unsplitobj(obj, objx, qq);
		    } else {            /* split into two groups */
			objx = splitobj(obj, qq);
			if (objx->otyp == SCR_SCARE_MONSTER) objx->spe = 0;
		    }
		    goto lift_some;
		}
	    }
	    if (objx) unsplitobj(obj, objx, 0L);
	    pline("There %s %s here, but %s.",
		    (obj->quan == 1L) ? "is" : "are", doname(obj),
		    !invent ? (obj->quan == 1L ?
				"it is too heavy for you to lift" :
				"they are too heavy for you to lift") :
			"you cannot carry any more");
	    if (obj->otyp == SCR_SCARE_MONSTER) obj->spe = 0;
	    return -1;
	}

lift_some:
	if (inv_cnt() >= 52) {
	    if (objx) unsplitobj(obj, objx, 0L);
	    if (obj->otyp == SCR_SCARE_MONSTER) obj->spe = 0;
	    Your("knapsack cannot accommodate any more items.");
	    return -1;
	}

	if (obj->otyp != LOADSTONE &&
	    (wt*2 / weight_cap()) + 1 > SLT_ENCUMBER) {
	    /* as in near_capacity() */
		char qbuf[QBUFSZ];
		char ch;

		Sprintf(qbuf, "%s %s.  Continue?", nearloadmsg, doname(obj));
		switch (ch = ynq(qbuf)) {
			case 'n':
			case 'q':
				if (objx) unsplitobj(obj, objx, 0L);
				if (obj->otyp == SCR_SCARE_MONSTER)
					obj->spe = 0;
				return (ch == 'q') ? -1 : 0;
			default:  break;        /* 'y' */
		}
	}

	pickquan = obj->quan;   /* save number picked up */
	obj = pick_obj(obj);

	if (uwep && uwep == obj) mrg_to_wielded = TRUE;
	nearload = near_capacity();
	prinv(nearload == SLT_ENCUMBER ? moderateloadmsg : NULL,
		obj, pickquan);
	mrg_to_wielded = FALSE;
	return 1;
}

/* Gold never reaches this routine. */
struct obj *
pick_obj(otmp)
register struct obj *otmp;
{
	freeobj(otmp);
	if (*u.ushops && costly_spot(u.ux, u.uy) &&
	    otmp != uball)     /* don't charge for this - kd, 1/17/90 */
	   /* sets obj->unpaid if necessary */
	    addtobill(otmp, TRUE, FALSE, FALSE);
	if(Invisible) newsym(u.ux,u.uy);
	return(addinv(otmp));    /* might merge it with other objects */
}

/*
 * prints a message if encumbrance changed since the last check and
 * returns the new encumbrance value (from near_capacity()).
 */
int
encumber_msg()
{
    static int oldcap = UNENCUMBERED;
    int newcap = near_capacity();

    if(oldcap < newcap) {
	switch(newcap) {
	case 1: Your("movements are slowed slightly because of your load.");
		break;
	case 2: You("rebalance your load.  Movement is difficult.");
		break;
	case 3: You("stagger under your heavy load.  Movement is very hard.");
		break;
	default: You("can barely move a handspan with this load!");
		break;
	}
	flags.botl = 1;
    } else if(oldcap > newcap) {
	switch(newcap) {
	case 0: Your("movements are now unencumbered.");
		break;
	case 1: Your("movements are only slowed slightly by your load.");
		break;
	case 2: You("rebalance your load.  Movement is still difficult.");
		break;
	case 3: You("stagger under your load.  Movement is still very hard.");
		break;
	}
	flags.botl = 1;
    }

    oldcap = newcap;
    return (newcap);
}

int
doloot()        /* loot a container on the floor. */
{
	register struct obj *cobj, *nobj;
	register int c;
	int timepassed = 0;

	if (Levitation && !Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz)) {
		You("cannot reach the floor.");
		return(0);
	}
	if(is_pool(u.ux, u.uy)) {
		You("cannot loot things that are deep in the water.");
		return(0);
	}
	if (levl[u.ux][u.uy].typ == GRAVE) {
		You("need to dig up a grave in order to properly loot it...");
		return(0);
	}
#ifdef POLYSELF
	if(nolimbs(uasmon)) {
		You("cannot loot things without limbs.");
		return(0);
	}
#endif

	for(cobj = level.objects[u.ux][u.uy]; cobj; cobj = nobj) {
		nobj = cobj->nexthere;

		if(Is_container(cobj)) {
		    char qbuf[QBUFSZ];

		    Sprintf(qbuf, "There is %s here, loot it?", doname(cobj));
		    c = ynq(qbuf);
		    if(c == 'q') return (timepassed);
		    if(c == 'n') continue;

		    if(cobj->olocked) {
			pline("Hmmm, it seems to be locked.");
			continue;
		    }
		    if(cobj->otyp == BAG_OF_TRICKS) {
			You("carefully open the bag...");
			pline("It develops a huge set of teeth and bites you!");
			c = rnd(10);
			if(Half_physical_damage) c = (c+1) / 2;
			losehp(c, "carnivorous bag", KILLED_BY_AN);
			makeknown(BAG_OF_TRICKS);
			timepassed = 1;
			continue;
		    }

		    You("carefully open %s...", the(xname(cobj)));
		    if (cobj->otrapped && chest_trap(cobj, FINGER, FALSE)) {
			timepassed = 1;
			continue;       /* explosion destroyed cobj */
		    }
		    if(multi < 0) return (1); /* a paralysis trap */

		    timepassed |= use_container(cobj, 0);
		}
	}
	return (timepassed);
}

/*
 * Decide whether an object being placed into a magic bag will cause
 * it to explode.  If the object is a bag itself, check recursively.
 */
static boolean
mbag_explodes(obj, depthin)
    struct obj *obj;
    int depthin;
{
    /* odds: 1/1, 2/2, 3/4, 4/8, 5/16, 6/32, 7/64, 8/128, 9/128, 10/128,... */
    if ((Is_mbag(obj) || (obj->otyp == WAN_CANCELLATION && obj->spe > 0)) &&
	(rn2(1 << (depthin > 7 ? 7 : depthin)) <= depthin))
	return TRUE;
    else if (Has_contents(obj)) {
	struct obj *otmp;

	for (otmp = obj->cobj; otmp; otmp = otmp->nobj)
	    if (mbag_explodes(otmp, depthin+1)) return TRUE;
    }
    return FALSE;
}

/* A variable set in use_container(), to be used by the callback routines */
/* chk_bg(), in_container(), and out_container() from askchain().         */
static NEARDATA struct obj *current_container;
#define Icebox (current_container->otyp == ICE_BOX)

STATIC_PTR int
in_container(obj)
register struct obj *obj;
{
	register struct obj *gold;
	boolean is_gold = (obj->otyp == GOLD_PIECE);
	boolean floor_container = !carried(current_container);
	char buf[BUFSZ];

	if (!current_container) {
		impossible("<in> no current_container?");
		return 0;
	} else if (obj == uball || obj == uchain) {
		You("must be kidding.");
		return 0;
	} else if (obj == current_container) {
		pline("That would be an interesting topological exercise.");
		return 0;
	} else if (obj->owornmask & (W_ARMOR | W_RING | W_AMUL | W_TOOL)) {
		Norep("You cannot %s something you are wearing.",
			Icebox ? "refrigerate" : "stash");
		return 0;
	} else if ((obj->otyp == LOADSTONE || obj->otyp == STONE_OF_ROTTING)
		&& obj->cursed) {
		obj->bknown = 1;
	      pline("The stone%s won't leave your person.", plur(obj->quan));
		return 0;
	} else if (obj->otyp == AMULET_OF_YENDOR ||
		   obj->otyp == CANDELABRUM_OF_INVOCATION ||
		   obj->otyp == BELL_OF_OPENING ||
		   obj->otyp == SPE_BOOK_OF_THE_DEAD) {
	/* Prohibit Amulets in containers; if you allow it, monsters can't
	 * steal them.  It also becomes a pain to check to see if someone
	 * has the Amulet.  Ditto for the Candelabrum, the Bell and the Book.
	 */
	    pline("%s cannot be confined in such trappings.", The(xname(obj)));
	    return 0;
	}
#ifdef WALKIES
	else if (obj->otyp == LEASH && obj->leashmon != 0) {
		pline("%s is attached to your pet.", The(xname(obj)));
		return 0;
	}
#endif
	else if (obj == uwep) {
		if (welded(obj)) {
			weldmsg(obj, FALSE);
			return 0;
		}
		setuwep((struct obj *) 0);
		if (uwep) return 0;     /* unwielded, died, rewielded */
	}

	/* boxes can't fit into any container */
	if (obj->otyp == ICE_BOX || Is_box(obj)) {
		/*
		 *  xname() uses a static result array.  Save obj's name
		 *  before current_container's name is computed.  Don't
		 *  use the result of strcpy() within You() --- the order
		 *  of evaluation of the parameters is undefined.
		 */
		Strcpy(buf, the(xname(obj)));
		You("cannot fit %s into %s.", buf,
		    the(xname(current_container)));
		return 0;
	}

	freeinv(obj);

	if (is_gold) {  /* look for other gold within the container */
		for (gold = current_container->cobj; gold; gold = gold->nobj)
			if (gold->otyp == GOLD_PIECE) break;
	} else
		gold = 0;

	if (gold) {
		gold->quan += obj->quan;
	} else {
		obj->nobj = current_container->cobj;
		current_container->cobj = obj;
	}

	current_container->owt = weight(current_container);

	Strcpy(buf, the(xname(current_container)));
	You("put %s into %s.", doname(obj), buf);

	if (floor_container && costly_spot(u.ux, u.uy)) {
		sellobj_state(TRUE);
		sellobj(obj, u.ux, u.uy);
		sellobj_state(FALSE);
	}
	(void) snuff_candle(obj); /* must follow the "put" msg */
	if (Icebox && obj->otyp != OIL_LAMP && obj->otyp != BRASS_LANTERN
			&& !Is_candle(obj))
		obj->age = monstermoves - obj->age; /* actual age */

	else if (Is_mbag(current_container) && mbag_explodes(obj, 0)) {
		You("are blasted by a magical explosion!");

		/* the !floor_container case is taken care of */
		if(*u.ushops && costly_spot(u.ux, u.uy) && floor_container) {
		    register struct monst *shkp;

		    if ((shkp = shop_keeper(*u.ushops)) != 0)
			(void)stolen_value(current_container, u.ux, u.uy,
					   (boolean)shkp->mpeaceful, FALSE);
		}
		delete_contents(current_container);
		if (!floor_container)
			useup(current_container);
		else if (obj_here(current_container, u.ux, u.uy))
			useupf(current_container);
		else
			panic("in_container:  bag not found.");

		losehp(d(6,6),"magical explosion", KILLED_BY_AN);
		current_container = 0;  /* baggone = TRUE; */
	}

	if (is_gold) {
		if (gold) dealloc_obj(obj);
		bot();  /* update character's gold piece count immediately */
	}

	return(current_container ? 1 : -1);
}

STATIC_PTR int
ck_bag(obj)
struct obj *obj;
{
	return current_container && obj != current_container;
}

STATIC_PTR int
out_container(obj)
register struct obj *obj;
{
	register struct obj *otmp, *ootmp;
	register struct monst *shkp;
	boolean is_gold = (obj->otyp == GOLD_PIECE);
	int loadlev;
	int want_quan;
	long quan;

	want_quan=obj->quan;

try_again:
	if (!current_container) {
		impossible("<out> no current_container?");
		return -1;
	} else if (is_gold) {
		obj->owt = weight(obj);
	} else if (inv_cnt() >= 52) {
		You("have no room to hold anything else.");
		return -1;      /* skips gold too; oh well */
	}

	if(obj->oartifact && !touch_artifact(obj,&youmonst)) return 0;

	if(obj->otyp != LOADSTONE && max_capacity() + (int)obj->owt -
	   (carried(current_container) ?
	    (current_container->otyp == BAG_OF_HOLDING ?
	     (int)DELTA_CWT(current_container,obj) : (int)obj->owt) : 0) > 0) {
		char buf[BUFSZ];

		if (want_quan > 1) {
		   want_quan--;
		   goto try_again;
		}
		Strcpy(buf, doname(obj));
		pline("There %s %s in %s, but %s.",
			obj->quan==1 ? "is" : "are",
			buf, the(xname(current_container)),
			invent ? "you cannot carry any more"
			: "it is too heavy for you to carry");
		/* "too heavy for you to lift" is not right if you're carrying
		   the container... */
		return(0);
	}
	/* Remove the object from the list. */
	if (want_quan == obj->quan) {
	  if (obj == current_container->cobj)
		current_container->cobj = obj->nobj;
	  else {
		for(otmp = current_container->cobj; otmp->nobj != obj;
							    otmp = otmp->nobj)
			if(!otmp->nobj) panic("out_container");
		otmp->nobj = obj->nobj;
	  }
	} else {
	   obj->quan -= want_quan;
	}
	current_container->owt = weight(current_container);

	if (Icebox && obj->otyp != OIL_LAMP && obj->otyp != BRASS_LANTERN
			&& !Is_candle(obj))
		obj->age = monstermoves - obj->age;
	/* simulated point of time */
	if (obj->otyp == PICK_AXE && !obj->unpaid && *u.ushops && (shkp = shop_keeper(*u.ushops))) {
	   verbalize("You sneaky cad! Get out of here with that pick-axe!");
	}
	if(!obj->unpaid && !carried(current_container) &&
	     costly_spot(current_container->ox, current_container->oy)) {

		obj->ox = current_container->ox;
		obj->oy = current_container->oy;
		addtobill(obj, FALSE, FALSE, FALSE);
	}

	quan = want_quan;
	ootmp = addinv(obj);
	loadlev = near_capacity();
	prinv(loadlev ?
	      (loadlev < MOD_ENCUMBER ?
	       "You have a little trouble removing" :
	       "You have much trouble removing") : NULL,
	      ootmp, quan);

	if (obj->quan == 0 && is_gold) {
		dealloc_obj(obj);
		bot();  /* update character's gold piece count immediately */
	}
	return 1;
}

/* replacement "take things out of container" routine -- by [Tom] */
int
list_contents_for_removal(list, num_objects)
	struct obj *list;
	int num_objects;
{
	register struct obj *box, *obj;
	char buf[BUFSZ];
	int onum = 0;
	char oletter;
	int page = 0;
	int used = 0;
	int done = 0;
	int wanted_object = 0;
	int last_on_page = 0;
	int first_on_page = 0;
	char bag_command;
	winid tmpwin;
	box = list;
	do {
	  tmpwin = create_nhwindow(NHW_MENU);
	  start_menu(tmpwin);
	  Sprintf(buf, "What do you want to take out of %s?", the(xname(box)));
	  add_menu(tmpwin, 0, 0, buf);
	  if (page > num_objects / 20) page = 0;
	  if (page < 0) page = (num_objects/20);
	  if (num_objects > 20) {
	    Sprintf(buf, "([ or ] for more, ESC exits)");
	    add_menu(tmpwin, 0, 0, buf);
	  } else {
	    Sprintf(buf, "(ESC exits)");
	  }
	  add_menu(tmpwin, '[', 0, "");	  /* needed to allow 'more' */
	  add_menu(tmpwin, ']', 0, "");	  /* needed to allow 'more' */
	  obj = box->cobj;
	  first_on_page = page * 20 + 1;
	  last_on_page = (page + 1) * 20 + 1;
	  if (last_on_page > num_objects) last_on_page = num_objects;
	  onum = 0;
	  for (obj = box->cobj; obj; obj = obj->nobj) {
		onum++;
		if (onum == last_on_page+1) break;
		if (onum >= first_on_page) {
		  oletter = onum - first_on_page + 65 + 32;
		  Sprintf(buf, "%c - %s",oletter,doname(obj));
		  add_menu(tmpwin, oletter, 0, buf);
		}
	  }
	  end_menu(tmpwin, '\033', "\033", "");
	  bag_command = select_menu(tmpwin);
	  destroy_nhwindow(tmpwin);
	  if (bag_command == 27) done = 1;
	  if (bag_command == '[') page--;
	  if (bag_command == ']') page++;
	  if ((bag_command >= 'a') && (bag_command <= oletter)) {
		wanted_object = bag_command - 64 - 32 + page*20;
		used = 1;
		for (obj = box->cobj, onum = 1; onum < wanted_object; onum++, obj=obj->nobj);
		if (obj->quan > 1) {
		}
		if (out_container(obj) == 1) num_objects--;
		if (oletter == 'a') {
		   if (page == 0) done = 1; /* last object */
		   else page--;
		}
	  }
	} while (!done);
	return (used);
}

/* for getobj: allow counts, allow all types, expect food */
static NEARDATA const char frozen_food[] =
	{ ALLOW_COUNT, ALL_CLASSES, FOOD_CLASS, 0 };

int
use_container(obj, held)
register struct obj *obj;
register int held;
{
	register int cnt = 0;
	register struct obj *curr, *prev, *otmp;
	boolean one_by_one, allflag;
	char select[MAXOCLASSES+1];
	char qbuf[QBUFSZ];
	int used = 0, lcnt = 0;
	long loss = 0L;
	register struct monst *shkp;

	current_container = obj;        /* for use by in/out_container */
	if (current_container->olocked) {
		pline("%s seems to be locked.", The(xname(current_container)));
		if (held) You("must put it down to unlock.");
		return 0;
	}
	/* Count the number of contained objects. Sometimes toss objects if */
	/* a cursed magic bag.                                              */
	prev = (struct obj *) 0;
	for (curr = obj->cobj; curr; curr = otmp) {
	    otmp = curr->nobj;
	    if (Is_mbag(obj) && obj->cursed && !rn2(13)) {
		if (curr->known)
		    pline("%s to have vanished!", The(aobjnam(curr,"seem")));
		else
		    You("%s %s disappear.", Blind ? "notice" : "see",
							doname(curr));
		if (prev)
		    prev->nobj = otmp;
		else
		    obj->cobj = otmp;

		if(*u.ushops && (shkp = shop_keeper(*u.ushops))) {
		    if(held) {
			if(curr->unpaid)
			    loss += stolen_value(curr, u.ux, u.uy,
					     (boolean)shkp->mpeaceful, TRUE);
			lcnt++;
		    } else if(costly_spot(u.ux, u.uy)) {
			loss += stolen_value(curr, u.ux, u.uy,
					     (boolean)shkp->mpeaceful, TRUE);
			lcnt++;
		    }
		}
		/* obfree() will free all contained objects */
		obfree(curr, (struct obj *) 0);
	    } else {
		prev = curr;
		cnt++;
	    }
	}

	if (cnt && loss)
	    You("owe %ld zorkmids for lost item%s.",
		loss, lcnt > 1 ? "s" : "");

	current_container->owt = weight(current_container);

	if(!cnt)
	    pline("%s %s is empty.", (held) ? "Your" : "The", xname(obj));
	else {
	    Sprintf(qbuf, "Do you want to take something out of %s?",
			the(xname(obj)));
ask_again:
	    switch (yn_function(qbuf, ":ynq", 'n')) {
	    case ':':
		container_contents(current_container, FALSE, FALSE);
		goto ask_again;
	    case 'y':
		if (query_classes(select, &one_by_one, &allflag, "take out",
				   current_container->cobj, FALSE, FALSE)) {
		    if (askchain((struct obj **)&current_container->cobj,
				 (one_by_one ? (char *)0 : select), allflag,
				 out_container, (int (*)())0, 0, "nodot"))
			used = 1;
		}
		/*FALLTHRU*/
	    case 'n':
		break;
	    case 'q':
	    default:
		return 0;
	    }
	}

	if (!invent && (u.ugold == 0 || Icebox)) return used;
	if (yn_function("Do you wish to put something in?", ynqchars, 'n')
	    != 'y') return used;
	if (Icebox && current_container->dknown) {
		otmp = getobj(frozen_food, "put in");
		if(!otmp || !in_container(otmp))
			flags.move = multi = 0;
	} else {
		if (query_classes(select, &one_by_one, &allflag, "put in",
					   invent, FALSE, (u.ugold != 0L))) {
		    struct obj *u_gold = (struct obj *)0;
		    if (u.ugold && (one_by_one || (allflag && !*select)
				    || index(select, GOLD_CLASS))) {
			/* make gold object & insert at head of inventory */
			u_gold = mkgoldobj(u.ugold);    /*(removes gold too)*/
			u.ugold = u_gold->quan;         /* put the gold back */
			u_gold->nobj = invent;
			invent = u_gold;
		    }
		    used = (askchain((struct obj **)&invent,
				(one_by_one ? (char *)0 : select), allflag,
				in_container, ck_bag, 0, "nodot") > 0);
		    if (u_gold && invent && invent->otyp == GOLD_PIECE) {
			/* didn't stash [all of] it */
			u_gold = invent;
			invent = u_gold->nobj;
			dealloc_obj(u_gold);
		    }
		}
	}

	return used;
}

/*pickup.c*/
