diff --git a/doc/fixes36.3 b/doc/fixes36.3 index 77aac6895..d85086092 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, @@ -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 @@ -26,6 +27,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/src/invent.c b/src/invent.c index 4a65624ac..17e96279c 100644 --- a/src/invent.c +++ b/src/invent.c @@ -3569,11 +3569,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) @@ -3582,6 +3578,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 9d45b9e42..eb949d9a9 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) @@ -2755,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) @@ -2792,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) */ @@ -2806,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 30ddb87bb..f2ebbcfb0 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. */ @@ -905,7 +905,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) */ @@ -1203,26 +1203,27 @@ 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 || g.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)", 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 */ @@ -1236,7 +1237,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; @@ -4202,11 +4204,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*/ diff --git a/src/shk.c b/src/shk.c index dd974d912..6618e9d6b 100644 --- a/src/shk.c +++ b/src/shk.c @@ -4788,91 +4788,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; @@ -4900,7 +4917,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*/ diff --git a/win/curses/cursinit.c b/win/curses/cursinit.c index 05e943313..deda96ab8 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 760b88a87..7f32a2de8 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 82a8714b9..dfa86fcb9 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,18 +384,41 @@ 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 */ + + /* 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 >= border + (messageh - 1)) { + scroll_window(MESSAGE_WIN); + 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 */ } + /* 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; - winy += messageh - 1; - + counting = TRUE; #ifdef PDCURSES if (countwin) curses_destroy_win(countwin), countwin = NULL; @@ -404,10 +427,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 b66639c4b..8cd873a9d 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; }