Allow dropping just picked up items

When using a menu to drop or put in items into a container,
allow putting in the item (or items) you picked up previously,
by selecting the 'P' entry from the item class menu

Inspired by the itemcat patch by Stanislav Traykov.

Invalidates saves and bones.
This commit is contained in:
Pasi Kallinen
2021-09-17 20:52:54 +03:00
parent e43ec0cef1
commit b30061b5ad
14 changed files with 183 additions and 30 deletions

View File

@@ -729,6 +729,8 @@ drop all objects known to be uncursed.
drop all objects known to be cursed.
.PL DX
drop all objects of unknown B/U/C status.
.PL DP
drop objects picked up last.
.PL Da
drop all objects, without asking for confirmation.
.PL Di
@@ -826,6 +828,8 @@ list all items known to be uncursed;
list all items known to be cursed;
.PL IX
list all items whose bless/curse status is unknown;
.PL IP
list items picked up last;
.PL I$
count your money.
.PE

View File

@@ -829,6 +829,7 @@ the bless\-ed/\-un\-curs\-ed/\-curs\-ed groups may be typed.\\
{\tt DU} --- drop all objects known to be uncursed.\\
{\tt DC} --- drop all objects known to be cursed.\\
{\tt DX} --- drop all objects of unknown B/U/C status.\\
{\tt DP} --- drop objects picked up last.\\
{\tt Da} --- drop all objects, without asking for confirmation.\\
{\tt Di} --- examine your inventory before dropping anything.\\
{\tt Du} --- drop only unpaid objects (when in a shop).\\
@@ -903,6 +904,7 @@ for potions.\\
{\tt IU} --- list all items known to be uncursed;\\
{\tt IC} --- list all items known to be cursed;\\
{\tt IX} --- list all items whose bless/curse status is unknown;\\
{\tt IP} --- list items picked up last;\\
{\tt I\$} --- count your money.
%.ei
%.ed

View File

@@ -1158,6 +1158,7 @@ new bigroom variant, a boulder maze
vomiting on an altar provokes the deities wrath
branch stairs have a different glyph, show up in yellow color in tty
duration of confusion when drinking booze depends upon hunger state
using 'D' allows dropping items picked up previously
Platform- and/or Interface-Specific New Features

View File

@@ -1094,6 +1094,8 @@ struct instance_globals {
boolean class_filter;
boolean bucx_filter;
boolean shop_filter;
boolean picked_filter;
boolean loot_reset_justpicked;
/* pline.c */
unsigned pline_flags;

View File

@@ -1076,7 +1076,7 @@ extern void free_pickinv_cache(void);
extern int count_unpaid(struct obj *);
extern int count_buc(struct obj *, int, boolean(*)(struct obj *));
extern void tally_BUCX(struct obj *, boolean, int *, int *, int *, int *,
int *);
int *, int *);
extern long count_contents(struct obj *, boolean, boolean, boolean, boolean);
extern void carry_obj_effects(struct obj *);
extern const char *currency(long);
@@ -1950,6 +1950,9 @@ extern boolean allow_category(struct obj *);
extern boolean is_worn_by_type(struct obj *);
extern int ck_bag(struct obj *);
extern void removed_from_icebox(struct obj *);
extern void reset_justpicked(struct obj *);
extern int count_justpicked(struct obj *);
extern struct obj *find_justpicked(struct obj *);
extern int pickup(int);
extern int pickup_object(struct obj *, long, boolean);
extern int query_category(const char *, struct obj *, int, menu_item **, int);

View File

@@ -366,6 +366,7 @@ typedef struct sortloot_item Loot;
#define BUC_CURSED 0x0200
#define BUC_UNCURSED 0x0400
#define BUC_UNKNOWN 0x0800
#define JUSTPICKED 0x1000
#define BUC_ALLBKNOWN (BUC_BLESSED | BUC_CURSED | BUC_UNCURSED)
#define BUCX_TYPES (BUC_ALLBKNOWN | BUC_UNKNOWN)
#define ALL_TYPES_SELECTED -2

View File

@@ -117,7 +117,8 @@ struct obj {
Bitfield(cknown, 1); /* for containers (including statues): the contents
* are known; also applicable to tins */
Bitfield(lknown, 1); /* locked/unlocked status is known */
/* 4 free bits */
Bitfield(pickup_prev, 1); /* was picked up previously */
/* 3 free bits */
int corpsenm; /* type of corpse is mons[corpsenm] */
#define leashmon corpsenm /* gets m_id of attached pet */

View File

@@ -17,7 +17,7 @@
* Incrementing EDITLEVEL can be used to force invalidation of old bones
* and save files.
*/
#define EDITLEVEL 40
#define EDITLEVEL 41
/*
* Development status possibilities.

View File

@@ -57,6 +57,7 @@ moveloop_preamble(boolean resuming)
if (!resuming) { /* new game */
g.context.rndencode = rnd(9000);
set_wear((struct obj *) 0); /* for side-effects of starting gear */
reset_justpicked(g.invent);
(void) pickup(1); /* autopickup at initial location */
/* only matters if someday a character is able to start with
clairvoyance (wizard with cornuthaum perhaps?); without this,

View File

@@ -553,6 +553,8 @@ const struct instance_globals g_init = {
UNDEFINED_VALUE, /* class_filter */
UNDEFINED_VALUE, /* bucx_filter */
UNDEFINED_VALUE, /* shop_filter */
UNDEFINED_VALUE, /* picked_filter */
UNDEFINED_VALUE, /* loot_reset_justpicked */
/* pline.c */
0, /* pline_flags */

View File

@@ -12,6 +12,7 @@ static void polymorph_sink(void);
static boolean teleport_sink(void);
static void dosinkring(struct obj *);
static int drop(struct obj *);
static int menudrop_split(struct obj *, int);
static boolean engulfer_digests_food(struct obj *);
static int wipeoff(void);
static int menu_drop(int);
@@ -825,15 +826,32 @@ doddrop(void)
return result;
}
static int
menudrop_split(struct obj *otmp, int cnt)
{
if (cnt && cnt < otmp->quan) {
if (welded(otmp)) {
; /* don't split */
} else if (otmp->otyp == LOADSTONE && otmp->cursed) {
/* same kludge as getobj(), for canletgo()'s use */
otmp->corpsenm = (int) cnt; /* don't split */
} else {
otmp = splitobj(otmp, cnt);
}
}
return drop(otmp);
}
/* Drop things from the hero's inventory, using a menu. */
static int
menu_drop(int retry)
{
int n, i, n_dropped = 0;
long cnt;
struct obj *otmp, *otmp2;
menu_item *pick_list;
boolean all_categories = TRUE, drop_everything = FALSE, autopick = FALSE;
boolean drop_justpicked = FALSE;
long justpicked_quan = 0;
if (retry) {
all_categories = (retry == -2);
@@ -842,7 +860,7 @@ menu_drop(int retry)
n = query_category("Drop what type of items?", g.invent,
(UNPAID_TYPES | ALL_TYPES | CHOOSE_ALL
| BUC_BLESSED | BUC_CURSED | BUC_UNCURSED
| BUC_UNKNOWN | INCLUDE_VENOM),
| BUC_UNKNOWN | JUSTPICKED | INCLUDE_VENOM),
&pick_list, PICK_ANY);
if (!n)
goto drop_done;
@@ -851,6 +869,10 @@ menu_drop(int retry)
all_categories = TRUE;
} else if (pick_list[i].item.a_int == 'A') {
drop_everything = autopick = TRUE;
} else if (pick_list[i].item.a_int == 'P') {
justpicked_quan = max(0, pick_list[i].count);
drop_justpicked = TRUE;
add_valid_menu_class(pick_list[i].item.a_int);
} else {
add_valid_menu_class(pick_list[i].item.a_int);
drop_everything = FALSE;
@@ -898,6 +920,11 @@ menu_drop(int retry)
/* we might not have dropped everything (worn armor, welded weapon,
cursed loadstones), so reset any remaining inventory to normal */
bypass_objlist(g.invent, FALSE);
} else if (drop_justpicked && count_justpicked(g.invent) == 1) {
/* drop the just picked item automatically, if only one stack */
otmp = find_justpicked(g.invent);
if (otmp)
n_dropped += menudrop_split(otmp, justpicked_quan);
} else {
/* should coordinate with perm invent, maybe not show worn items */
n = query_objlist("What would you like to drop?", &g.invent,
@@ -926,18 +953,7 @@ menu_drop(int retry)
if (!otmp2 || !otmp2->bypass)
continue;
/* found next selected invent item */
cnt = pick_list[i].count;
if (cnt < otmp->quan) {
if (welded(otmp)) {
; /* don't split */
} else if (otmp->otyp == LOADSTONE && otmp->cursed) {
/* same kludge as getobj(), for canletgo()'s use */
otmp->corpsenm = (int) cnt; /* don't split */
} else {
otmp = splitobj(otmp, cnt);
}
}
n_dropped += drop(otmp);
n_dropped += menudrop_split(otmp, pick_list[i].count);
}
bypass_objlist(g.invent, FALSE); /* reset g.invent to normal */
free((genericptr_t) pick_list);

View File

@@ -719,6 +719,9 @@ merged(struct obj **potmp, struct obj **pobj)
otmp = *potmp = oname(otmp, ONAME(obj));
obj_extract_self(obj);
if (obj->pickup_prev && otmp->where == OBJ_INVENT)
otmp->pickup_prev = 1;
/* really should merge the timeouts */
if (obj->lamplit)
obj_merge_light_sources(obj, otmp);
@@ -877,6 +880,11 @@ addinv_core0(struct obj *obj, struct obj *other_obj,
obj_was_thrown = obj->was_thrown;
obj->was_thrown = 0; /* not meaningful for invent */
if (g.loot_reset_justpicked) {
g.loot_reset_justpicked = FALSE;
reset_justpicked(g.invent);
}
addinv_core1(obj);
/* for addinv_before(); if something has been removed and is now being
@@ -937,6 +945,7 @@ addinv_core0(struct obj *obj, struct obj *other_obj,
&& (throwing_weapon(obj) || is_ammo(obj)))
setuqwep(obj);
added:
obj->pickup_prev = 1;
addinv_core2(obj);
carry_obj_effects(obj); /* carrying affects the obj */
if (update_perm_invent)
@@ -1164,6 +1173,7 @@ void
freeinv(struct obj *obj)
{
extract_nobj(obj, &g.invent);
obj->pickup_prev = 0;
freeinv_core(obj);
update_inventory();
}
@@ -1869,7 +1879,7 @@ ggetobj(const char *word, int (*fn)(OBJ_P), int mx,
boolean takeoff, ident, allflag, m_seen;
int itemcount;
int oletct, iletct, unpaid, oc_of_sym;
char sym, *ip, olets[MAXOCLASSES + 5], ilets[MAXOCLASSES + 10];
char sym, *ip, olets[MAXOCLASSES + 6], ilets[MAXOCLASSES + 11];
char extra_removeables[3 + 1]; /* uwep,uswapwep,uquiver */
char buf[BUFSZ] = DUMMY, qbuf[QBUFSZ];
@@ -1908,6 +1918,8 @@ ggetobj(const char *word, int (*fn)(OBJ_P), int mx,
ilets[iletct++] = 'C';
if (count_buc(g.invent, BUC_UNKNOWN, ofilter))
ilets[iletct++] = 'X';
if (count_justpicked(g.invent))
ilets[iletct++] = 'P';
ilets[iletct++] = 'a';
}
ilets[iletct++] = 'i';
@@ -1988,8 +2000,8 @@ ggetobj(const char *word, int (*fn)(OBJ_P), int mx,
} else if (sym == 'u') {
add_valid_menu_class('u');
ckfn = ckunpaid;
} else if (index("BUCX", sym)) {
add_valid_menu_class(sym); /* 'B','U','C',or 'X' */
} else if (index("BUCXP", sym)) {
add_valid_menu_class(sym); /* 'B','U','C','X', or 'P' */
ckfn = ckvalidcat;
} else if (sym == 'm') {
m_seen = TRUE;
@@ -2916,7 +2928,7 @@ count_buc(struct obj *list, int type, boolean (*filterfunc)(OBJ_P))
rather than looking for a specific type */
void
tally_BUCX(struct obj *list, boolean by_nexthere,
int *bcp, int *ucp, int *ccp, int *xcp, int *ocp)
int *bcp, int *ucp, int *ccp, int *xcp, int *ocp, int *jcp)
{
/* Future extensions:
* Skip current_container when list is invent, uchain when
@@ -2937,6 +2949,8 @@ tally_BUCX(struct obj *list, boolean by_nexthere,
++(*ucp);
continue;
}
if (list->pickup_prev)
++(*jcp);
/* ordinary items */
if (!list->bknown)
++(*xcp);
@@ -3109,6 +3123,9 @@ this_type_only(struct obj *obj)
case 'X':
res = !obj->bknown;
break;
case 'P':
res = obj->pickup_prev;
break;
default:
break; /* use 'res' as-is */
}
@@ -3124,7 +3141,7 @@ dotypeinv(void)
int n, i = 0;
char *extra_types, types[BUFSZ];
int class_count, oclass, unpaid_count, itemcount;
int bcnt, ccnt, ucnt, xcnt, ocnt;
int bcnt, ccnt, ucnt, xcnt, ocnt, jcnt;
boolean billx = *u.ushops && doinvbill(0);
menu_item *pick_list;
boolean traditional = TRUE;
@@ -3135,7 +3152,7 @@ dotypeinv(void)
return 0;
}
unpaid_count = count_unpaid(g.invent);
tally_BUCX(g.invent, FALSE, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt);
tally_BUCX(g.invent, FALSE, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt, &jcnt);
if (flags.menu_style != MENU_TRADITIONAL) {
if (flags.menu_style == MENU_FULL
@@ -3152,6 +3169,8 @@ dotypeinv(void)
i |= BUC_CURSED;
if (xcnt)
i |= BUC_UNKNOWN;
if (jcnt)
i |= JUSTPICKED;
i |= INCLUDE_VENOM;
n = query_category(prompt, g.invent, i, &pick_list, PICK_ONE);
if (!n)
@@ -3180,6 +3199,8 @@ dotypeinv(void)
types[class_count++] = 'C';
if (xcnt)
types[class_count++] = 'X';
if (jcnt)
types[class_count++] = 'P';
types[class_count] = '\0';
/* add everything not already included; user won't see these */
extra_types = eos(types);
@@ -3236,7 +3257,7 @@ dotypeinv(void)
return 0;
}
if (traditional) {
if (index("BUCX", c))
if (index("BUCXP", c))
oclass = c; /* not a class but understood by this_type_only() */
else
oclass = def_char_to_objclass(c); /* change to object class */
@@ -3260,6 +3281,9 @@ dotypeinv(void)
case 'X':
after = " whose blessed/uncursed/cursed status is unknown";
break; /* better phrasing is desirable */
case 'P':
after = " you just picked up";
break;
default:
/* 'c' is an object class, because we've already handled
all the non-class letters which were put into 'types[]';

View File

@@ -400,6 +400,7 @@ splitobj(struct obj *obj, long num)
otmp->quan = num;
otmp->owt = weight(otmp); /* -= obj->owt ? */
otmp->lua_ref_cnt = 0;
otmp->pickup_prev = 0;
g.context.objsplit.parent_oid = obj->o_id;
g.context.objsplit.child_oid = otmp->o_id;
@@ -765,6 +766,7 @@ mksobj(int otyp, boolean init, boolean artif)
unknow_object(otmp); /* set up dknown and known: non-0 for some things */
otmp->corpsenm = NON_PM;
otmp->lua_ref_cnt = 0;
otmp->pickup_prev = 0;
if (init) {
switch (let) {

View File

@@ -46,6 +46,7 @@ static boolean able_to_loot(int, int, boolean);
static boolean reverse_loot(void);
static boolean mon_beside(int, int);
static int do_loot_cont(struct obj **, int, int);
static int doloot_core(void);
static void tipcontainer(struct obj *);
/* define for query_objlist() and autopickup() */
@@ -133,7 +134,7 @@ query_classes(char oclasses[], boolean *one_at_a_time, boolean *everything,
boolean not_everything, filtered;
char qbuf[QBUFSZ];
boolean m_seen;
int itemcount, bcnt, ucnt, ccnt, xcnt, ocnt;
int itemcount, bcnt, ucnt, ccnt, xcnt, ocnt, jcnt;
oclasses[oclassct = 0] = '\0';
*one_at_a_time = *everything = m_seen = FALSE;
@@ -159,7 +160,7 @@ query_classes(char oclasses[], boolean *one_at_a_time, boolean *everything,
if (count_unpaid(objs))
ilets[iletct++] = 'u';
tally_BUCX(objs, here, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt);
tally_BUCX(objs, here, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt, &jcnt);
if (bcnt)
ilets[iletct++] = 'B';
if (ucnt)
@@ -168,6 +169,8 @@ query_classes(char oclasses[], boolean *one_at_a_time, boolean *everything,
ilets[iletct++] = 'C';
if (xcnt)
ilets[iletct++] = 'X';
if (jcnt)
ilets[iletct++] = 'P';
ilets[iletct] = '\0';
if (iletct > 1) {
@@ -203,8 +206,8 @@ query_classes(char oclasses[], boolean *one_at_a_time, boolean *everything,
goto ask_again;
} else if (sym == 'm') {
m_seen = TRUE;
} else if (index("uBUCX", sym)) {
add_valid_menu_class(sym); /* 'u' or 'B','U','C',or 'X' */
} else if (index("uBUCXP", sym)) {
add_valid_menu_class(sym); /* 'u' or 'B','U','C','X','P' */
filtered = TRUE;
} else {
oc_of_sym = def_char_to_objclass(sym);
@@ -407,6 +410,7 @@ add_valid_menu_class(int c)
if (c == 0) { /* reset */
vmc_count = 0;
g.class_filter = g.bucx_filter = g.shop_filter = FALSE;
g.picked_filter = FALSE;
} else if (!menu_class_present(c)) {
g.valid_menu_classes[vmc_count++] = (char) c;
/* categorize the new class */
@@ -417,6 +421,9 @@ add_valid_menu_class(int c)
case 'X':
g.bucx_filter = TRUE;
break;
case 'P':
g.picked_filter = TRUE;
break;
case 'u':
g.shop_filter = TRUE;
break;
@@ -457,6 +464,8 @@ allow_category(struct obj *obj)
? (index(g.valid_menu_classes, COIN_CLASS) ? TRUE : FALSE)
: g.shop_filter /* coins are never unpaid, but check anyway */
? (obj->unpaid ? TRUE : FALSE)
: g.picked_filter
? obj->pickup_prev
: g.bucx_filter
? (index(g.valid_menu_classes, flags.goldX ? 'X' : 'U')
? TRUE : FALSE)
@@ -500,6 +509,8 @@ allow_category(struct obj *obj)
if (!index(g.valid_menu_classes, bucx))
return FALSE;
}
if (g.picked_filter && !obj->pickup_prev)
return FALSE;
/* obj didn't fail any of the filter checks, so accept */
return TRUE;
}
@@ -524,6 +535,41 @@ is_worn_by_type(struct obj *otmp)
return (is_worn(otmp) && allow_category(otmp)) ? TRUE : FALSE;
}
/* reset last-picked-up flags */
void
reset_justpicked(struct obj *olist)
{
struct obj *otmp;
for (otmp = olist; otmp; otmp = otmp->nobj)
otmp->pickup_prev = 0;
}
int
count_justpicked(struct obj *olist)
{
struct obj *otmp;
int cnt = 0;
for (otmp = olist; otmp; otmp = otmp->nobj)
if (otmp->pickup_prev)
cnt++;
return cnt;
}
struct obj *
find_justpicked(struct obj *olist)
{
struct obj *otmp;
for (otmp = olist; otmp; otmp = otmp->nobj)
if (otmp->pickup_prev)
return otmp;
return (struct obj *) 0;
}
/*
* Have the hero pick things from the ground
* or a monster's inventory if swallowed.
@@ -601,6 +647,7 @@ pickup(int what) /* should be a long */
nomul(0);
}
reset_justpicked(g.invent);
add_valid_menu_class(0); /* reset */
if (!u.uswallow) {
objchain_p = &g.level.objects[u.ux][u.uy];
@@ -1083,6 +1130,7 @@ query_category(const char *qstr, /* query string */
do_buc_unknown = FALSE;
int num_buc_types = 0;
unsigned itemflags = MENU_ITEMFLAGS_NONE;
int num_justpicked = 0;
*pick_list = (menu_item *) 0;
if (!olist)
@@ -1107,6 +1155,9 @@ query_category(const char *qstr, /* query string */
do_buc_unknown = TRUE;
num_buc_types++;
}
if (qflags & JUSTPICKED) {
num_justpicked = count_justpicked(olist);
}
ccount = count_categories(olist, qflags);
/* no point in actually showing a menu for a single category */
@@ -1246,6 +1297,19 @@ query_category(const char *qstr, /* query string */
add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE,
"Items of unknown Bless/Curse status", itemflags);
}
if (num_justpicked) {
char tmpbuf[BUFSZ];
if (num_justpicked == 1)
Sprintf(tmpbuf, "%s", doname(find_justpicked(olist)));
else
Sprintf(tmpbuf, "Items you just picked up");
invlet = 'P';
any = cg.zeroany;
any.a_int = 'P';
add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE,
tmpbuf, itemflags);
}
end_menu(win, qstr);
n = select_menu(win, how, pick_list);
query_done:
@@ -1834,9 +1898,21 @@ do_loot_cont(struct obj **cobjp,
return use_container(cobjp, 0, (boolean) (cindex < ccount));
}
/* loot a container on the floor or loot saddle from mon. */
/* #loot extended command */
int
doloot(void)
{
int res;
g.loot_reset_justpicked = TRUE;
res = doloot_core();
g.loot_reset_justpicked = FALSE;
return res;
}
/* loot a container on the floor or loot saddle from mon. */
static int
doloot_core(void)
{
struct obj *cobj, *nobj;
register int c = -1;
@@ -2901,6 +2977,7 @@ menu_loot(int retry, boolean put_in)
int n, i, n_looted = 0;
boolean all_categories = TRUE, loot_everything = FALSE, autopick = FALSE;
char buf[BUFSZ];
boolean loot_justpicked = FALSE;
const char *action = put_in ? "Put in" : "Take out";
struct obj *otmp, *otmp2;
menu_item *pick_list;
@@ -2912,7 +2989,8 @@ menu_loot(int retry, boolean put_in)
} else if (flags.menu_style == MENU_FULL) {
all_categories = FALSE;
Sprintf(buf, "%s what type of objects?", action);
mflags = (ALL_TYPES | UNPAID_TYPES | BUCX_TYPES | CHOOSE_ALL);
mflags = (ALL_TYPES | UNPAID_TYPES | BUCX_TYPES | CHOOSE_ALL
| JUSTPICKED );
n = query_category(buf, put_in ? g.invent : g.current_container->cobj,
mflags, &pick_list, PICK_ANY);
if (!n)
@@ -2920,6 +2998,10 @@ menu_loot(int retry, boolean put_in)
for (i = 0; i < n; i++) {
if (pick_list[i].item.a_int == 'A') {
loot_everything = autopick = TRUE;
} else if (put_in && pick_list[i].item.a_int == 'P') {
loot_justpicked = TRUE;
count = max(0, pick_list[i].count);
add_valid_menu_class(pick_list[i].item.a_int);
} else if (pick_list[i].item.a_int == ALL_TYPES_SELECTED) {
all_categories = TRUE;
} else {
@@ -2958,10 +3040,22 @@ menu_loot(int retry, boolean put_in)
n_looted += res;
}
}
} else if (put_in && loot_justpicked && count_justpicked(g.invent) == 1) {
otmp = find_justpicked(g.invent);
if (otmp) {
n_looted = 1;
if (count > 0 && count < otmp->quan) {
otmp = splitobj(otmp, count);
}
(void) in_container(otmp);
/* return value doesn't matter, even if container blew up */
}
} else {
mflags = INVORDER_SORT | INCLUDE_VENOM;
if (put_in && flags.invlet_constant)
mflags |= USE_INVLET;
if (put_in && loot_justpicked)
mflags |= JUSTPICKED;
if (!put_in)
g.current_container->cknown = 1;
Sprintf(buf, "%s what?", action);