]> granicus.if.org Git - nethack/commitdiff
fix #K3564 - obj sanity failure: N globs for N>1
authorPatR <rankin@nethack.org>
Wed, 30 Mar 2022 21:11:29 +0000 (14:11 -0700)
committerPatR <rankin@nethack.org>
Wed, 30 Mar 2022 21:11:29 +0000 (14:11 -0700)
Using #name and picking an item on the floor to be assigned a type
name allowed any of the four types of globs to be named.  After
that, wishing for those by the assigned name bypassed the code that
forced the quantity to stay at 1.  Asking for "3 foo" could then
produce "3 small globs of gray ooze" which fails obj_sanity() and
issues an impossible warning (which the fuzzer escalates to panic).

The "getobj refactor" patch changed the return value of call_ok().
When it gets used to check whether an object on the floor could have
a type name assigned (rather than as a getobj() callback), the test
that should have rejected the naming attempt accepted it instead.

Update the wishing code to handle globs differently:  you can still
specify the relative size via small, medium, large, or very large,
but now you can specify a count either instead or in addition.  A
count of more than 1 is used to multiply the created glob's weight,
although it's less likely to be honored as-is when the size is bigger
than small.  Quantity is always forced to 1, at a different place in
readobjnam() than previously.

doc/fixes3-7-0.txt
src/do_name.c
src/objnam.c

index df5de95419d1cfd52ac4acd0ef60db11073a8285..bba43c658c0630796cdb9d4417e83a20c9b7320c 100644 (file)
@@ -1000,6 +1000,9 @@ if an invisible hero managed to convert an unaligned altar to an aligned one
        with color enabled, altar wasn't immediately redrawn with new color
 repair some regressions to (a)pply introduced by "getobj refactor" patch
 getobj too: allow attempting to (E)ngrave with any item in inventory
+getobj refactor also allowed non-nameable items on floor to be assigned names
+exploting the bug to assign type name to glob on floor allowed wishing for
+       "N assigned-glob-name" to create a glob with quantity N instead of 1
 fix ^X feedback when held typo: "unseen createure" -> "unseen creature"
 if a <foo> corpse was set to revive as a <foo> zombie and corpse was partly
        eaten at revival time and monster <foo> is defined as providing more
index 6409fe60cdda2dd32c960531d3bc4fe65121c8fb..b1f0d9ea5abcde82c8b3ffc1074183f5499fb619 100644 (file)
@@ -1478,7 +1478,7 @@ docallcmd(void)
             if (!obj->dknown) {
                 You("would never recognize another one.");
 #if 0
-            } else if (!call_ok(obj)) {
+            } else if (call_ok(obj) == GETOBJ_EXCLUDE) {
                 You("know those as well as you ever will.");
 #endif
             } else {
@@ -1645,7 +1645,7 @@ namefloorobj(void)
         pline("%s %s to call you \"%s.\"",
               The(buf), use_plural ? "decide" : "decides",
               unames[rn2_on_display_rng(SIZE(unames))]);
-    } else if (!call_ok(obj)) {
+    } else if (call_ok(obj) == GETOBJ_EXCLUDE) {
         pline("%s %s can't be assigned a type name.",
               use_plural ? "Those" : "That", buf);
     } else if (!obj->dknown) {
index 8a639516d76cb02a377f4e21e74859579d1dee43..88411bde407900c168f82da0b94ff53a49d1ae64 100644 (file)
@@ -3908,6 +3908,10 @@ readobjnam_postparse1(struct _readobjnam_data *d)
         /* if we didn't recognize monster type, pick a valid one at random */
         if (d->mntmp == NON_PM)
             d->mntmp = rn1(PM_BLACK_PUDDING - PM_GRAY_OOZE, PM_GRAY_OOZE);
+        /* normally this would be done when makesingular() changes the value
+           but canonical form here is already singular so that won't happen */
+        if (d->cnt < 2 && strstri(d->bp, "globs"))
+            d->cnt = 2; /* affects otmp->owt but not otmp->quan for globs */
         /* construct canonical spelling in case name_to_mon() recognized a
            variant (grey ooze) or player used inverted syntax (<foo> glob);
            if player has given a valid monster type but not valid glob type,
@@ -3915,7 +3919,6 @@ readobjnam_postparse1(struct _readobjnam_data *d)
         Sprintf(d->globbuf, "glob of %s", mons[d->mntmp].pmnames[NEUTRAL]);
         d->bp = d->globbuf;
         d->mntmp = NON_PM; /* not useful for "glob of <foo>" object lookup */
-        d->cnt = 0; /* globs don't stack */
         d->oclass = FOOD_CLASS;
         d->actualn = d->bp, d->dn = 0;
         return 1; /*goto srch;*/
@@ -4569,12 +4572,42 @@ readobjnam(char *bp, struct obj *no_wish)
         obj_extract_self(d.otmp); /* now release it for caller's use */
     }
 
-    /* if player specified a reasonable count, maybe honor it */
-    if (d.cnt > 0 && objects[d.typ].oc_merge
-        && (wizard || d.cnt < rnd(6) || (d.cnt <= 7 && Is_candle(d.otmp))
-            || (d.cnt <= 20 && ((d.oclass == WEAPON_CLASS && is_ammo(d.otmp))
-                              || d.typ == ROCK || is_missile(d.otmp)))))
-        d.otmp->quan = (long) d.cnt;
+    /* if player specified a reasonable count, maybe honor it;
+       quantity for gold is handled elsewhere and d.cnt is 0 for it here */
+    if (d.otmp->globby) {
+        /* for globs, calculate weight based on gsize, then multiply by cnt;
+           asking for 2 globs or for 2 small globs produces 1 small glob
+           weighing 40au instead of normal 20au; asking for 5 medium globs
+           might produce 1 very large glob weighing 600au */
+        d.otmp->quan = 1L; /* always 1 for globs */
+        d.otmp->owt = weight(d.otmp);
+        /* gsize 0: unspecified => small;
+           1: small (1..5) => keep default owt for 1, yielding 20;
+           2: medium (6..15) => use weight for 6, yielding 120;
+           3: large (16..25) => 320; 4: very large (26+) => 520 */
+        if (d.gsize > 1)
+            d.otmp->owt += (unsigned) (100 + (d.gsize - 2) * 200);
+        if (d.cnt > 1) {
+            if ((d.cnt > 6 - d.gsize) && !wizard)
+                d.cnt = rn1(5, 2); /* 2..6 */
+            d.otmp->owt *= (unsigned) d.cnt;
+        }
+        /* note: the owt assignment below will not change glob's weight */
+        d.cnt = 0;
+    } else if (d.cnt > 0) {
+        if (objects[d.typ].oc_merge
+            && (wizard /* quantity isn't restricted when debugging */
+                /* note: in normal play, explicitly asking for 1 might
+                   fail the 'cnt < rnd(6)' test and could produce more
+                   than 1 if mksobj() creates the item that way */
+                || d.cnt < rnd(6)
+                || (d.cnt <= 7 && Is_candle(d.otmp))
+                || (d.cnt <= 20
+                    && (d.typ == ROCK || is_missile(d.otmp)
+                        /* WEAPON_CLASS test is to exclude gems */
+                        || (d.oclass == WEAPON_CLASS && is_ammo(d.otmp))))))
+            d.otmp->quan = (long) d.cnt;
+    }
 
     if (d.spesgn == 0) {
         /* spe not specifed; retain the randomly assigned value */
@@ -4873,10 +4906,6 @@ readobjnam(char *bp, struct obj *no_wish)
     d.otmp->owt = weight(d.otmp);
     if (d.very && d.otmp->otyp == HEAVY_IRON_BALL)
         d.otmp->owt += IRON_BALL_W_INCR;
-    else if (d.gsize > 1 && d.otmp->globby)
-        /* 0: unspecified => small; 1: small => keep default owt of 20;
-           2: medium => 120; 3: large => 320; 4: very large => 520 */
-        d.otmp->owt += 100 + (d.gsize - 2) * 200;
 
     return d.otmp;
 }