From 255d969da2427ef32827be1d2533228226d0bcef Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 17 May 2019 13:28:41 -0700 Subject: [PATCH 1/5] obj sanity checking specific to globs Verify that objects with the globby bit set are actually glob objects, that their quantity is 1, and that their weight at least superficially makes sense. --- src/mkobj.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/mkobj.c b/src/mkobj.c index 00bbdebe9..2428aec49 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mkobj.c $NHDT-Date: 1557526914 2019/05/10 22:21:54 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.144 $ */ +/* NetHack 3.6 mkobj.c $NHDT-Date: 1558124913 2019/05/17 20:28:33 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.147 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -18,6 +18,7 @@ STATIC_DCL const char *FDECL(where_name, (struct obj *)); STATIC_DCL void FDECL(insane_object, (struct obj *, const char *, const char *, struct monst *)); STATIC_DCL void FDECL(check_contained, (struct obj *, const char *)); +STATIC_DCL void FDECL(check_glob, (struct obj *, const char *)); STATIC_DCL void FDECL(sanity_check_worn, (struct obj *)); struct icp { @@ -2352,6 +2353,8 @@ const char *mesg; } break; } + if (obj->globby) + check_glob(obj, mesg); } } } @@ -2366,7 +2369,8 @@ const char *mesg; struct obj *obj, *mwep; for (mon = monlist; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) continue; + if (DEADMONSTER(mon)) + continue; mwep = MON_WEP(mon); if (mwep) { if (!mcarried(mwep)) @@ -2379,6 +2383,8 @@ const char *mesg; insane_object(obj, mfmt1, mesg, mon); if (obj->ocarry != mon) insane_object(obj, mfmt2, mesg, mon); + if (obj->globby) + check_glob(obj, mesg); check_contained(obj, mesg); } } @@ -2429,7 +2435,8 @@ struct monst *mon; impossible(altfmt, mesg, fmt_ptr((genericptr_t) obj), where_name(obj), objnm, fmt_ptr((genericptr_t) mon), monnm); } else { - impossible(fmt, mesg, fmt_ptr((genericptr_t) obj), where_name(obj), objnm); + impossible(fmt, mesg, fmt_ptr((genericptr_t) obj), where_name(obj), + objnm); } } @@ -2461,6 +2468,8 @@ const char *mesg; fmt_ptr((genericptr_t) obj), fmt_ptr((genericptr_t) obj->ocontainer), fmt_ptr((genericptr_t) container)); + if (obj->globby) + check_glob(obj, mesg); if (Has_contents(obj)) { /* catch most likely indirect cycle; we won't notice if @@ -2472,14 +2481,36 @@ const char *mesg; and "nested contained..." to "nested nested contained..." */ Strcpy(nestedmesg, "nested "); copynchars(eos(nestedmesg), mesg, (int) sizeof nestedmesg - - (int) strlen(nestedmesg) - - 1); + - (int) strlen(nestedmesg) - 1); /* recursively check contents */ check_contained(obj, nestedmesg); } } } +/* called when 'obj->globby' is set so we don't recheck it here */ +STATIC_OVL void +check_glob(obj, mesg) +struct obj *obj; +const char *mesg; +{ +#define LOWEST_GLOB GLOB_OF_GRAY_OOZE +#define HIGHEST_GLOB GLOB_OF_BLACK_PUDDING + if (obj->quan != 1L || obj->owt == 0 + || obj->otyp < LOWEST_GLOB || obj->otyp > HIGHEST_GLOB + /* a partially eaten glob could have any non-zero weight but an + intact one should weigh an exact multiple of base weight (20) */ + || ((obj->owt % objects[obj->otyp].oc_weight) != 0 && !obj->oeaten)) { + char mesgbuf[BUFSZ], globbuf[QBUFSZ]; + + Sprintf(globbuf, " glob %d,quan=%ld,owt=%u ", + obj->otyp, obj->quan, obj->owt); + mesg = strsubst(strcpy(mesgbuf, mesg), " obj ", globbuf); + insane_object(obj, ofmt0, mesg, + (obj->where == OBJ_MINVENT) ? obj->ocarry : 0); + } +} + /* check an object in hero's or monster's inventory which has worn mask set */ STATIC_OVL void sanity_check_worn(obj) From 345c1b719f84df46b13ddad3af091ed618b5f7c9 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 17 May 2019 13:38:34 -0700 Subject: [PATCH 2/5] unpaid glob formatting Make unpaid (shop owned, that is) globs show same weight information as for-sale globs. And don't treat required arguments to globwt() as if they were optional. --- src/objnam.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 72670149b..a684f163c 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1551138256 2019/02/25 23:44:16 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.235 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1558125504 2019/05/17 20:38:24 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.239 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -910,7 +910,7 @@ unsigned doname_flags; vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0; boolean known, dknown, cknown, bknown, lknown; int omndx = obj->corpsenm; - char prefix[PREFIX]; + char prefix[PREFIX], globbuf[QBUFSZ]; char tmpbuf[PREFIX + 1]; /* for when we have to add something at the start of prefix instead of the end (Strcat is used on the end) */ @@ -1208,19 +1208,20 @@ unsigned doname_flags; } } /* treat 'restoring' like suppress_price because shopkeeper and - bill might not be available yet while restore is in progress */ + bill might not be available yet while restore is in progress + (objects won't normally be formatted during that time, but if + 'perm_invent' is enabled then they might be) */ if (iflags.suppress_price || restoring) { ; /* don't attempt to obtain any stop pricing, even if 'with_price' */ } else if (is_unpaid(obj)) { /* in inventory or in container in invent */ long quotedprice = unpaid_cost(obj, TRUE); - Sprintf(eos(bp), " (%s, %ld %s)", + Sprintf(eos(bp), " (%s, %s%ld %s)", obj->unpaid ? "unpaid" : "contents", - quotedprice, currency(quotedprice)); + globwt(obj, globbuf), quotedprice, currency(quotedprice)); } else if (with_price) { /* on floor or in container on floor */ int nochrg = 0; long price = get_cost_of_shop_item(obj, &nochrg); - char globbuf[BUFSZ]; if (price > 0L) Sprintf(eos(bp), " (%s, %s%ld %s)", @@ -4207,11 +4208,11 @@ globwt(otmp, buf) struct obj *otmp; char *buf; { - if (otmp && buf && otmp->globby && otmp->quan == 1L) { - Sprintf(buf, "%d aum, ", (int) otmp->owt); - return buf; + *buf = '\0'; + if (otmp->globby) { + Sprintf(buf, "%u aum, ", otmp->owt); } - return ""; + return buf; } /*objnam.c*/ From f218e3f15e2d5525e817fb07e6a8b4773f296073 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 18 May 2019 02:25:48 -0700 Subject: [PATCH 3/5] fix #H8753 - curses message window anomalies Autodescribe feedback and multi-digit count prompts are always shown on the last line of the message window and are suppressed from message history (both ^P and DUMPLOG). When the message window is using all available lines, the last one was being overwritten (until the count or the feedback was completed or dismissed, then last line returned). Adopt the suggestion that it be scrolled up a line instead of being overwritten. [I haven't been able to reproduce the reported problem where shorter overlaid text left some of longer underlying text visible but that should now become moot.] Bonus fix: while testing, I noticed that if your screen only has room for a one-line message window and you used ESC to cancel 'pick a spot with cursor' prompting before moving the cursor, the prompt was left intact on the message line. tty erases it in that situation, but the clear_nhwindow(WIN_MESSAGE) was a no-op for curses because it usually doesn't erase old messages. This changes the curses behavior when the core asks it to erase the message window: now it forces one blank line of fake autodesribe feedback (causing the prompt or other most recent message to scroll off top), then removes that fake feedback (leaving a blank message line). For multi-line message window, the old messages scroll up by one line sooner than they would when waiting for the next real message but are otherwise unaffected. --- doc/fixes36.3 | 8 +++++++- win/curses/cursinit.c | 3 +++ win/curses/cursmain.c | 6 ++++++ win/curses/cursmesg.c | 34 ++++++++++++++++++++++------------ win/curses/curswins.c | 6 ++++++ 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/doc/fixes36.3 b/doc/fixes36.3 index 77aac6895..f935c3bdc 100644 --- a/doc/fixes36.3 +++ b/doc/fixes36.3 @@ -1,4 +1,4 @@ -$NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.3 $ $NHDT-Date: 1558045586 2019/05/16 22:26:26 $ +$NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.5 $ $NHDT-Date: 1558171542 2019/05/18 09:25:42 $ This fixes36.3 file is here to capture information about updates in the 3.6.x lineage following the release of 3.6.2 in May 2019. Please note, however, @@ -26,6 +26,12 @@ Platform- and/or Interface-Specific Fixes or Features curses: very tall menus tried to use selector characters a-z, A-Z, and 0-9, but 0-9 should be reserved for counts and if the display was tall enough for more than 62 entries, arbitrary ASCII punctuation got used +curses: when all available lines in the message window are in use, + autodescribe feedback for 'pick a position with cursor' overwrote + the last line (usually the 'pick a position' prompt/hint), sometimes + leaving part of longer underlying line's text visible +curses: if message window is only one line, cancelling some prompts with ESC + left the prompts visible on the message line instead of erasing them Windows: some startup error messages were not being delivered successfully diff --git a/win/curses/cursinit.c b/win/curses/cursinit.c index 19a12c6bc..728f1e8ea 100644 --- a/win/curses/cursinit.c +++ b/win/curses/cursinit.c @@ -260,6 +260,9 @@ curses_create_main_windows() if (curses_get_nhwin(STATUS_WIN)) { curses_del_nhwin(STATUS_WIN); + /* 'count window' overlays last line of mesg win; + asking it to display a Null string removes it */ + curses_count_window((char *) 0); curses_del_nhwin(MESSAGE_WIN); curses_del_nhwin(MAP_WIN); curses_del_nhwin(INV_WIN); diff --git a/win/curses/cursmain.c b/win/curses/cursmain.c index c179610b6..96f984213 100644 --- a/win/curses/cursmain.c +++ b/win/curses/cursmain.c @@ -301,6 +301,11 @@ curses_clear_nhwindow(winid wid) { if (wid != NHW_MESSAGE) { curses_clear_nhwin(wid); + } else { + /* scroll the message window one line if it's full */ + curses_count_window(""); + /* remove 'countwin', leaving last message line blank */ + curses_count_window((char *) 0); } } @@ -585,6 +590,7 @@ wait_synch() -- Wait until all pending output is complete (*flush*() for void curses_wait_synch() { + /* [do we need 'if (counting) curses_count_window((char *)0);' here?] */ } /* diff --git a/win/curses/cursmesg.c b/win/curses/cursmesg.c index 656dce57d..f9a8468d6 100644 --- a/win/curses/cursmesg.c +++ b/win/curses/cursmesg.c @@ -164,7 +164,7 @@ curses_got_input(void) /* misleadingly named; represents number of lines delivered since player was sure to have had a chance to read them; if player - has just given input then there aren't any such lines right; + has just given input then there aren't any such lines right now; that includes responding to More>> even though it stays same turn */ turn_lines = 0; } @@ -375,8 +375,8 @@ void curses_count_window(const char *count_text) { static WINDOW *countwin = NULL; - int startx, starty, winx, winy; - int messageh, messagew; + int winx, winy; + int messageh, messagew, border; if (!count_text) { if (countwin) @@ -384,17 +384,28 @@ curses_count_window(const char *count_text) counting = FALSE; return; } - counting = TRUE; + /* position of message window, not current position within message window + (so <0,0> for align_message:Top but will vary for other alignings) */ curses_get_window_xy(MESSAGE_WIN, &winx, &winy); + /* size of message window, with space for borders already subtracted */ curses_get_window_size(MESSAGE_WIN, &messageh, &messagew); - if (curses_window_has_border(MESSAGE_WIN)) { - winx++; - winy++; - } + /* decide where to put the one-line counting window */ + border = curses_window_has_border(MESSAGE_WIN) ? 1 : 0; + winx += border; /* first writeable message column */ + winy += border + (messageh - 1); /* last writable message line */ - winy += messageh - 1; + /* if most recent message (probably prompt leading to this instance of + counting window) is going to be covered up, scroll mesgs up a line */ + if (!counting && my + 1 >= border + messageh) { + scroll_window(MESSAGE_WIN); + /* last position within the message window */ + my = border + (messageh - 1) - 1; + mx = border; + /* wmove(curses_get_nhwin(MESSAGE_WIN), my, mx); -- not needed */ + } + counting = TRUE; #ifdef PDCURSES if (countwin) @@ -404,10 +415,9 @@ curses_count_window(const char *count_text) but not for dolook's autodescribe when it refers to a named monster */ if (!countwin) countwin = newwin(1, messagew, winy, winx); - startx = 0; - starty = 0; + werase(countwin); - mvwprintw(countwin, starty, startx, "%s", count_text); + mvwprintw(countwin, 0, 0, "%s", count_text); wrefresh(countwin); } diff --git a/win/curses/curswins.c b/win/curses/curswins.c index ae28140b9..5c9997633 100644 --- a/win/curses/curswins.c +++ b/win/curses/curswins.c @@ -352,6 +352,8 @@ curses_del_wid(winid wid) void curs_destroy_all_wins() { + curses_count_window((char *) 0); /* clean up orphan */ + while (nhwids) curses_del_wid(nhwids->nhwid); } @@ -483,6 +485,10 @@ curses_puts(winid wid, int attr, const char *text) } if (wid == MESSAGE_WIN) { + /* if a no-history message is being shown, remove it */ + if (counting) + curses_count_window((char *) 0); + curses_message_win_puts(text, FALSE); return; } From a19e64e470f21ef30b96122dd2d40c26ef9b1527 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 18 May 2019 08:12:43 -0700 Subject: [PATCH 4/5] curses followup Some prompts were being overwritten by the message that followed. And clear_nhwindow(WIN_MESSAGE) gets called for just about every keystroke so try to reduce the overhead I unwittingly added. The "scroll up one line earlier than the next message" mentioned in the prior commit is much more obvious that I realized and prompt erasure might need to be redone. --- win/curses/cursmesg.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/win/curses/cursmesg.c b/win/curses/cursmesg.c index f9a8468d6..a32b6824b 100644 --- a/win/curses/cursmesg.c +++ b/win/curses/cursmesg.c @@ -398,15 +398,27 @@ curses_count_window(const char *count_text) /* if most recent message (probably prompt leading to this instance of counting window) is going to be covered up, scroll mesgs up a line */ - if (!counting && my + 1 >= border + messageh) { + if (!counting && my >= border + (messageh - 1)) { scroll_window(MESSAGE_WIN); - /* last position within the message window */ - my = border + (messageh - 1) - 1; - mx = border; + if (messageh > 1) { + /* handling for next message will behave as if we're currently + positioned at the end of next to last line of message window */ + my = border + (messageh - 1) - 1; + mx = border + (messagew - 1); /* (0 + 80 - 1) or (1 + 78 - 1) */ + } else { + /* for a one-line window, use beginning of only line instead */ + my = mx = border; /* 0 or 1 */ + } /* wmove(curses_get_nhwin(MESSAGE_WIN), my, mx); -- not needed */ } - counting = TRUE; + /* in case we're being called from clear_nhwindow(MESSAGE_WIN) + which gets called for every command keystroke; it sends an + empty string to get the scroll-up-one-line effect above and + we want to avoid the curses overhead for the operations below... */ + if (!*count_text) + return; + counting = TRUE; #ifdef PDCURSES if (countwin) curses_destroy_win(countwin), countwin = NULL; From 670fc9ca3493dfbc369a1b6da9a1b918563558d7 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sat, 18 May 2019 16:24:48 -0400 Subject: [PATCH 5/5] further improve additional glob interaction scenarios within a shop Scenarios: 1. shop_owned glob merging into shop_owned glob 2. player_owned glob merging into shop_owned glob 3. shop_owned glob merging into player_owned glob 4. player_owned glob merging into player_owned glob --- doc/fixes36.3 | 1 + src/invent.c | 12 ++-- src/mkobj.c | 15 ++--- src/objnam.c | 5 +- src/shk.c | 152 ++++++++++++++++++++++++++++++++------------------ 5 files changed, 117 insertions(+), 68 deletions(-) diff --git a/doc/fixes36.3 b/doc/fixes36.3 index f935c3bdc..d85086092 100644 --- a/doc/fixes36.3 +++ b/doc/fixes36.3 @@ -19,6 +19,7 @@ glob pricing did not consider weight properly Fixes to Post-3.6.2 Problems that Were Exposed Via git Repository ------------------------------------------------------------------ +glob shop interaction improved to handle more of the expected scenarios Platform- and/or Interface-Specific Fixes or Features diff --git a/src/invent.c b/src/invent.c index 13c6eed67..51c505375 100644 --- a/src/invent.c +++ b/src/invent.c @@ -3581,11 +3581,7 @@ register struct obj *otmp, *obj; if (obj->oclass == COIN_CLASS) return TRUE; - if (obj->unpaid != otmp->unpaid || obj->spe != otmp->spe - || obj->cursed != otmp->cursed || obj->blessed != otmp->blessed - || obj->no_charge != otmp->no_charge || obj->obroken != otmp->obroken - || obj->otrapped != otmp->otrapped || obj->lamplit != otmp->lamplit - || obj->bypass != otmp->bypass) + if (obj->bypass != otmp->bypass) return FALSE; if (obj->globby) @@ -3594,6 +3590,12 @@ register struct obj *otmp, *obj; * or don't inhibit their merger. */ + if (obj->unpaid != otmp->unpaid || obj->spe != otmp->spe + || obj->cursed != otmp->cursed || obj->blessed != otmp->blessed + || obj->no_charge != otmp->no_charge || obj->obroken != otmp->obroken + || obj->otrapped != otmp->otrapped || obj->lamplit != otmp->lamplit) + return FALSE; + if (obj->oclass == FOOD_CLASS && (obj->oeaten != otmp->oeaten || obj->orotten != otmp->orotten)) return FALSE; diff --git a/src/mkobj.c b/src/mkobj.c index 2428aec49..94370c96d 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -2786,10 +2786,7 @@ struct obj **obj1, **obj2; otmp1 = *obj1; otmp2 = *obj2; if (otmp1 && otmp2 && otmp1 != otmp2) { - if (otmp1->unpaid || otmp2->unpaid) - globby_bill_fixup(otmp1, otmp2); - else if (costly_spot(otmp1->ox, otmp1->oy)) - globby_donation(otmp1, otmp2); + globby_bill_fixup(otmp1, otmp2); if (otmp1->bknown != otmp2->bknown) otmp1->bknown = otmp2->bknown = 0; if (otmp1->rknown != otmp2->rknown) @@ -2823,7 +2820,10 @@ struct obj **obj1, **obj2; } /* - * Causes the heavier object to absorb the lighter object; + * Causes the heavier object to absorb the lighter object in + * most cases, but if one object is OBJ_FREE and the other is + * on the floor, the floor object goes first. + * * wrapper for obj_absorb so that floor_effects works more * cleanly (since we don't know which we want to stay around) */ @@ -2837,8 +2837,9 @@ struct obj **obj1, **obj2; otmp1 = *obj1; otmp2 = *obj2; if (otmp1 && otmp2 && otmp1 != otmp2) { - if (otmp1->owt > otmp2->owt - || (otmp1->owt == otmp2->owt && rn2(2))) { + if (!(otmp2->where == OBJ_FLOOR && otmp1->where == OBJ_FREE) && + (otmp1->owt > otmp2->owt + || (otmp1->owt == otmp2->owt && rn2(2)))) { return obj_absorb(obj1, obj2); } return obj_absorb(obj2, obj1); diff --git a/src/objnam.c b/src/objnam.c index a684f163c..e8bb71c14 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1228,7 +1228,7 @@ unsigned doname_flags; nochrg ? "contents" : "for sale", globwt(obj, globbuf), price, currency(price)); else if (nochrg > 0) - Strcat(bp, " (no charge)"); + Sprintf(eos(bp), " (%sno charge)", globwt(obj, globbuf)); } if (!strncmp(prefix, "a ", 2)) { /* save current prefix, without "a "; might be empty */ @@ -1242,7 +1242,8 @@ unsigned doname_flags; /* show weight for items (debug tourist info) * aum is stolen from Crawl's "Arbitrary Unit of Measure" */ if (wizard && iflags.wizweight) { - Sprintf(eos(bp), " (%d aum)", obj->owt); + if (!obj->globby) /* aum already apparent for globs */ + Sprintf(eos(bp), " (%d aum)", obj->owt); } bp = strprepend(bp, prefix); return bp; diff --git a/src/shk.c b/src/shk.c index 18a2599e0..470253570 100644 --- a/src/shk.c +++ b/src/shk.c @@ -4801,91 +4801,108 @@ sasc_bug(struct obj *op, unsigned x) #endif /* - * When one glob is absorbed by another glob, the two become - * indistinguishable and the remaining glob grows in mass, - * the product of both. + * The caller is about to make obj_absorbed go away. * - * The original billed item is lost to the absorption and the - * original billed amount for the object being absorbed gets - * added to the cost owing for the absorber, and the separate - * cost for the absorbed object goes away. + * There's no way for you (or a shopkeeper) to prevent globs + * from merging with each other on the floor due to the + * inherent nature of globs so it irretrievably becomes part + * of the floor glob mass. When one glob is absorbed by another + * glob, the two become indistinguishable and the remaining + * glob object grows in mass, the product of both. + * + * billing admin, player compensation, shopkeeper compensation + * all need to be considered. + * + * Any original billed item is lost to the absorption so the + * original billed amount for the object being absorbed must + * get added to the cost owing for the absorber, and the + * separate cost for the object being absorbed goes away. + * + * There are four scenarios to deal with: + * 1. shop_owned glob merging into shop_owned glob + * 2. player_owned glob merging into shop_owned glob + * 3. shop_owned glob merging into player_owned glob + * 4. player_owned glob merging into player_owned glob */ void globby_bill_fixup(obj_absorber, obj_absorbed) struct obj *obj_absorber, *obj_absorbed; { + int x, y; struct bill_x *bp, *bp_absorber = (struct bill_x *) 0; struct monst *shkp = 0; + struct eshk *eshkp; + long amount, per_unit_cost = set_cost(obj_absorbed, shkp); + boolean floor_absorber = (obj_absorber->where == OBJ_FLOOR); if (!obj_absorber->globby) impossible("globby_bill_fixup called for non-globby object"); + if (floor_absorber) { + x = obj_absorber->ox, y = obj_absorber->oy; + } if (obj_absorber->unpaid) { /* look for a shopkeeper who owns this object */ for (shkp = next_shkp(fmon, TRUE); shkp; shkp = next_shkp(shkp->nmon, TRUE)) if (onbill(obj_absorber, shkp, TRUE)) break; + } else if (obj_absorbed->unpaid) { + if (obj_absorbed->where == OBJ_FREE + && floor_absorber && costly_spot(x, y)) { + shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); + } } /* sanity check, in case obj is on bill but not marked 'unpaid' */ if (!shkp) shkp = shop_keeper(*u.ushops); + if (!shkp) + return; + bp_absorber = onbill(obj_absorber, shkp, FALSE); + bp = onbill(obj_absorbed, shkp, FALSE); + eshkp = ESHK(shkp); - if ((bp_absorber = onbill(obj_absorber, shkp, FALSE)) != 0) { - bp = onbill(obj_absorbed, shkp, FALSE); - if (bp) { - bp_absorber->price += bp->price; - ESHK(shkp)->billct--; + /************************************************************** + * Scenario 1. Shop-owned glob absorbing into shop-owned glob + **************************************************************/ + if (bp && (!obj_absorber->no_charge + || billable(&shkp, obj_absorber, eshkp->shoproom, FALSE))) { + /* the glob being absorbed has a billing record */ + amount = bp->price; + eshkp->billct--; #ifdef DUMB - { - /* DRS/NS 2.2.6 messes up -- Peter Kendell */ - int indx = ESHK(shkp)->billct; + { + /* DRS/NS 2.2.6 messes up -- Peter Kendell */ + int indx = eshkp->billct; - *bp = ESHK(shkp)->bill_p[indx]; - } -#else - *bp = ESHK(shkp)->bill_p[ESHK(shkp)->billct]; -#endif - clear_unpaid_obj(shkp, obj_absorbed); - } else { - /* should never happen */ - bp_absorber->price += get_cost(obj_absorbed, shkp) - * get_pricing_units(obj_absorbed); + *bp = eshkp->bill_p[indx]; } +#else + *bp = eshkp->bill_p[eshkp->billct]; +#endif + clear_unpaid_obj(shkp, obj_absorbed); + + if (bp_absorber) { + /* the absorber has a billing record */ + bp_absorber->price += amount; + } else { + /* the absorber has no billing record */ + ; + } + return; } -} - -/* - * The caller is about to make obj_absorbed go away. - * - * There's no way for you (or a shopkeeper) to prevent globs - * from merging with each other on the floor due to the - * inherent nature of globs so it irretrievably becomes part - * of the floor glob mass. - * - * globby_donation() needs to handle whether/how to - * compensate you for that. - * - * unpaid globs don't end up here. - */ -void -globby_donation(obj_absorber, obj_absorbed) -struct obj *obj_absorber, *obj_absorbed; -{ - if (obj_absorber->where == OBJ_FLOOR - && costly_spot(obj_absorber->ox, obj_absorber->oy)) { - int x = obj_absorber->ox, y = obj_absorber->oy; - struct monst *shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); - struct eshk *eshkp = ESHK(shkp); - long amount, per_unit_cost = get_cost(obj_absorbed, shkp); - + /************************************************************** + * Scenario 2. Player-owned glob absorbing into shop-owned glob + **************************************************************/ + if (!bp_absorber && !bp && !obj_absorber->no_charge) { + /* there are no billing records */ + amount = get_pricing_units(obj_absorbed) * per_unit_cost; if (saleable(shkp, obj_absorbed)) { - amount = get_pricing_units(obj_absorbed) * per_unit_cost; if (eshkp->debit >= amount) { if (eshkp->loan) { /* you carry shop's gold */ - if (eshkp->loan >= amount) + if (eshkp->loan >= amount) eshkp->loan -= amount; - else + else eshkp->loan = 0L; } eshkp->debit -= amount; @@ -4913,7 +4930,34 @@ struct obj *obj_absorber, *obj_absorbed; eshkp->credit, currency(eshkp->credit)); } } + return; + } else if (bp_absorber) { + /* absorber has a billing record */ + bp_absorber->price += per_unit_cost * get_pricing_units(obj_absorbed); + return; } + /************************************************************** + * Scenario 3. shop_owned glob merging into player_owned glob + **************************************************************/ + if (bp && + (obj_absorber->no_charge + || (floor_absorber && !costly_spot(x, y)))) { + amount = bp->price; + bill_dummy_object(obj_absorbed); + verbalize( + "You owe me %ld %s for my %s that %s with your%s", + amount, currency(amount), obj_typename(obj_absorbed->otyp), + ANGRY(shkp) ? "had the audacity to mix" : + "just mixed", + ANGRY(shkp) ? " stinking batch!" : + "s."); + return; + } + /************************************************************** + * Scenario 4. player_owned glob merging into player_owned glob + **************************************************************/ + + return; } /*shk.c*/