diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index dcb9a5e5f..aa995b359 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -5,7 +5,7 @@ .ds vr "NetHack 3.4 .ds f0 "\*(vr .ds f1 -.ds f2 "March 14, 2003 +.ds f2 "April 2, 2003 .mt A Guide to the Mazes of Menace (Guidebook for NetHack) @@ -707,6 +707,8 @@ Ride (or stop riding) a monster. Rub a lamp or a stone. .lp #sit Sit down. +.lp #tip +Tip over a container to pour out its contents. .lp #turn Turn undead. .lp #twoweapon diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index f4cadd8c4..eb49989cb 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -27,7 +27,7 @@ \begin{document} % % input file: guidebook.mn -% $Revision: 1.61 $ $Date: 2003/02/13 04:55:28 $ +% $Revision: 1.62 $ $Date: 2003/03/14 13:50:58 $ % %.ds h0 " %.ds h1 %.ds h2 \% @@ -40,7 +40,7 @@ %.au \author{Eric S. Raymond\\ (Extensively edited and expanded for 3.4)} -\date{March 14, 2003} +\date{April 2, 2003} \maketitle @@ -941,6 +941,9 @@ Rub a lamp or a stone. \item[\tb{\#sit}] Sit down. %.lp +\item[\tb{\#tip}] +Tip over a container to pour out its contents. +%.lp \item[\tb{\#turn}] Turn undead. %.lp diff --git a/doc/fixes35.0 b/doc/fixes35.0 index df20675cc..3994c9062 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -37,6 +37,7 @@ win32gui: better handling of "more" prompt for messages that would have scrolled General New Features -------------------- burying a punishment ball no longer ends your punishment +#tip command--pay a modest gratuity Platform- and/or Interface-Specific New Features diff --git a/include/extern.h b/include/extern.h index cfc172a67..2de637de3 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1477,6 +1477,7 @@ E int NDECL(encumber_msg); E int NDECL(doloot); E int FDECL(use_container, (struct obj *,int)); E int FDECL(loot_mon, (struct monst *,int *,boolean *)); +E int NDECL(dotip); /* ### pline.c ### */ diff --git a/src/cmd.c b/src/cmd.c index 3384d758c..38f6b7ba0 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)cmd.c 3.4 2003/02/06 */ +/* SCCS Id: @(#)cmd.c 3.4 2003/04/02 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -77,6 +77,7 @@ extern int NDECL(dodrink); /**/ extern int NDECL(dodip); /**/ extern int NDECL(dosacrifice); /**/ extern int NDECL(dopray); /**/ +extern int NDECL(dotip); /**/ extern int NDECL(doturn); /**/ extern int NDECL(doredraw); /**/ extern int NDECL(doread); /**/ @@ -1486,6 +1487,7 @@ struct ext_func_tab extcmdlist[] = { #endif {"rub", "rub a lamp or a stone", dorub, FALSE}, {"sit", "sit down", dosit, FALSE}, + {"tip", "empty a container", dotip, FALSE}, {"turn", "turn undead", doturn, TRUE}, {"twoweapon", "toggle two-weapon combat", dotwoweapon, FALSE}, {"untrap", "untrap something", dountrap, FALSE}, diff --git a/src/invent.c b/src/invent.c index 0ac4fbb45..64eee6991 100644 --- a/src/invent.c +++ b/src/invent.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)invent.c 3.4 2003/01/24 */ +/* SCCS Id: @(#)invent.c 3.4 2003/04/02 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -890,6 +890,7 @@ register const char *let,*word; (otmp->dknown && objects[OIL_LAMP].oc_name_known)))) || (!strcmp(word, "untrap with") && (otmp->oclass == TOOL_CLASS && otyp != CAN_OF_GREASE)) + || (!strcmp(word, "tip") && !Is_container(otmp)) || (!strcmp(word, "charge") && !is_chargeable(otmp)) ) foo--; diff --git a/src/pickup.c b/src/pickup.c index 1a626bad5..43ed52e32 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -35,8 +35,9 @@ STATIC_DCL long FDECL(mbag_item_gone, (int,struct obj *)); STATIC_DCL int FDECL(menu_loot, (int, struct obj *, BOOLEAN_P)); STATIC_DCL int FDECL(in_or_out_menu, (const char *,struct obj *, BOOLEAN_P, BOOLEAN_P)); STATIC_DCL int FDECL(container_at, (int, int, BOOLEAN_P)); -STATIC_DCL boolean FDECL(able_to_loot, (int, int)); +STATIC_DCL boolean FDECL(able_to_loot, (int,int,BOOLEAN_P)); STATIC_DCL boolean FDECL(mon_beside, (int, int)); +STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* define for query_objlist() and autopickup() */ #define FOLLOW(curr, flags) \ @@ -1374,9 +1375,12 @@ boolean countem; } STATIC_OVL boolean -able_to_loot(x, y) +able_to_loot(x, y, looting) int x, y; +boolean looting; /* loot vs tip */ { + const char *verb = looting ? "loot" : "tip"; + if (!can_reach_floor()) { #ifdef STEED if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) @@ -1385,13 +1389,15 @@ int x, y; #endif You("cannot reach the %s.", surface(x, y)); return FALSE; - } else if (is_pool(x, y) || is_lava(x, y)) { - /* at present, can't loot in water even when Underwater */ - You("cannot loot things that are deep in the %s.", - is_lava(x, y) ? "lava" : "water"); + } else if ((is_pool(x, y) && (looting || !Underwater)) || + is_lava(x, y)) { + /* at present, can't loot in water even when Underwater; + can tip underwater, but not when over--or stuck in--lava */ + You("cannot %s things that are deep in the %s.", + verb, is_lava(x, y) ? "lava" : "water"); return FALSE; } else if (nolimbs(youmonst.data)) { - pline("Without limbs, you cannot loot anything."); + pline("Without limbs, you cannot %s anything.", verb); return FALSE; } return TRUE; @@ -1441,7 +1447,7 @@ lootcont: if (container_at(cc.x, cc.y, FALSE)) { boolean any = FALSE; - if (!able_to_loot(cc.x, cc.y)) return 0; + if (!able_to_loot(cc.x, cc.y, TRUE)) return 0; for (cobj = level.objects[cc.x][cc.y]; cobj; cobj = nobj) { nobj = cobj->nexthere; @@ -2297,4 +2303,149 @@ boolean outokay, inokay; return n; } +static const char tippables[] = { ALL_CLASSES, TOOL_CLASS, 0 }; + +/* #tip command -- empty container contents onto floor */ +int +dotip() +{ + struct obj *cobj, *nobj; + coord cc; + int boxes; + char c, buf[BUFSZ]; + const char *spillage = 0; + + /* + * doesn't require free hands; + * limbs are needed to tip floor containers + */ + + /* at present, can only tip things at current spot, not adjacent ones */ + cc.x = u.ux, cc.y = u.uy; + + /* check floor container(s) first; at most one will be accessed */ + if ((boxes = container_at(cc.x, cc.y, TRUE)) > 0) { + if (flags.verbose) + pline("There %s here.", + (boxes > 1) ? "are containers" : "is a container"); + Sprintf(buf, "You can't tip %s while carrying so much.", + !flags.verbose ? "a container" : (boxes > 1) ? "one" : "it"); + if (!check_capacity(buf) && able_to_loot(cc.x, cc.y, FALSE)) { + for (cobj = level.objects[cc.x][cc.y]; cobj; cobj = nobj) { + nobj = cobj->nexthere; + if (!Is_container(cobj)) continue; + + Sprintf(buf, "There is %s here, tip it?", doname(cobj)); + c = ynq(buf); + if (c == 'q') return 0; + if (c == 'n') continue; + + tipcontainer(cobj); + return 1; + } /* next cobj */ + } + } + + /* either no floor container(s) or couldn't tip one or didn't tip any */ + cobj = getobj(tippables, "tip"); + if (!cobj) return 0; + + /* normal case */ + if (Is_container(cobj)) { + tipcontainer(cobj); + return 1; + } + /* assorted other cases */ + if (Is_candle(cobj) && cobj->lamplit) { + /* note "wax" even for tallow candles to avoid giving away info */ + spillage = "wax"; + } else if ((cobj->otyp == POT_OIL && cobj->lamplit) || + (cobj->otyp == OIL_LAMP && cobj->age != 0L) || + (cobj->otyp == MAGIC_LAMP && cobj->spe != 0)) { + spillage = "oil"; + /* todo: reduce potion's remaining burn timer or oil lamp's fuel */ + } else if (cobj->otyp == CAN_OF_GREASE && cobj->spe > 0) { + /* charged consumed below */ + spillage = "grease"; + } else if (cobj->otyp == FOOD_RATION || + cobj->otyp == CRAM_RATION || + cobj->otyp == LEMBAS_WAFER) { + spillage = "crumbs"; + } else if (cobj->oclass == VENOM_CLASS) { + spillage = "venom"; + } + if (spillage) { + buf[0] = '\0'; + if (is_pool(u.ux, u.uy)) + Sprintf(buf, " and gradually %s", vtense(spillage, "dissipate")); + else if (is_lava(u.ux, u.uy)) + Sprintf(buf, " and immediately %s away", vtense(spillage, "burn")); + pline("Some %s %s onto the %s%s.", + spillage, vtense(spillage, "spill"), + surface(u.ux, u.uy), buf); + /* shop usage message comes after the spill message */ + if (cobj->otyp == CAN_OF_GREASE && cobj->spe > 0) { + check_unpaid(cobj); + cobj->spe--; /* doesn't affect cobj->owt */ + } + /* something [useless] happened */ + return 1; + } + /* anything not covered yet */ + if (cobj->oclass == POTION_CLASS) /* can't pour potions... */ + pline_The("%s %s securely sealed.", xname(cobj), otense(cobj, "are")); + else + pline(nothing_happens); + return 0; +} + +STATIC_OVL void +tipcontainer(box) +struct obj *box; /* or bag */ +{ + if (box->olocked) { + pline("It's locked."); + } else if (box->otyp == BAG_OF_TRICKS && box->spe > 0) { + /* apply (not loot) this bag; uses up one charge */ + bagotricks(box); + } else if (!Has_contents(box)) { + pline("It's empty."); + } else { + struct obj *otmp, *nobj; + boolean verbose = FALSE, + highdrop = !can_reach_floor(), + altarizing = IS_ALTAR(levl[u.ux][u.uy].typ), + cursed_mbag = (Is_mbag(box) && box->cursed); + int held = carried(box); + long loss = 0L; + + pline("%s out%c", + box->cobj->nobj ? "Objects spill" : "An object spills", + !(highdrop || altarizing) ? ':' : '.'); + for (otmp = box->cobj; otmp; otmp = nobj) { + nobj = otmp->nobj; + obj_extract_self(otmp); + if (cursed_mbag && !rn2(13)) { + loss += mbag_item_gone(held, otmp); + /* abbreviated drop format is no longer appropriate */ + verbose = TRUE; + } else if (highdrop) { + /* might break or fall down stairs; handles altars itself */ + hitfloor(otmp); + } else { + if (altarizing) + doaltarobj(otmp); + else if (verbose) + pline("%s %s to the %s.", Doname2(otmp), + otense(otmp, "drop"), surface(u.ux, u.uy)); + else + pline("%s%c", doname(otmp), nobj ? ',' : '.'); + dropy(otmp); + } + } + if (loss) + You("owe %ld %s for lost merchandise.", loss, currency(loss)); + } +} + /*pickup.c*/