]> granicus.if.org Git - nethack/commitdiff
Refactor getobj() to use callbacks on candidate objects
authorcopperwater <aosdict@gmail.com>
Thu, 7 Jan 2021 16:05:59 +0000 (11:05 -0500)
committercopperwater <aosdict@gmail.com>
Thu, 7 Jan 2021 16:06:58 +0000 (11:06 -0500)
This replaces the arcane system previously used by getobj where the
caller would pass in a "string" whose characters were object class
numbers, with the first up to four characters being special constants
that effectively acted as flags and had to be in a certain order.
Because there are many places where getobj must behave more granularly
than just object class filtering, this was supplemented by over a
hundred lines enumerating all these special cases and "ugly checks", as
well as other ugly code spread around in getobj callers that formatted
the "string".

Now, getobj callers pass in a callback which will return one of five
possible values for any given object in the player's inventory. The
logic of determining the eligibility of a given object is handled in the
caller, which greatly simplifies the code and makes it clearer to read.
Particularly since there's no real need to cram everything into one if
statement.

This is related to pull request #77 by FIQ; it's largely a
reimplementation of its callbacks system, without doing a bigger than
necessary refactor of getobj or adding the ability to select a
floor/trap/dungeon feature with getobj. Differences in implementation
are mostly minor:
- using enum constants for returns instead of magic numbers
- 5 possible return values for callbacks instead of 3, due to trying to
  make it behave exactly as it did previously. PR #77 would sometimes
  outright exclude objects because it lacked semantics for invalid
  objects that should be selectable anyway, or give slightly different
  messages.
- passing a bitmask of flags to getobj rather than booleans (easier to
  add more flags later - such as FIQ's "allow floor features" flag, if
  that becomes desirable)
- renaming some of getobj's variables to clearer versions
- naming all callbacks consistently with "_ok"
- generally more comments explaining things

The callbacks use the same logic from getobj_obj_exclude,
getobj_obj_exclude_too and getobj_obj_acceptable_unlisted (and in a few
cases, from special cases still within getobj). In a number of them, I
added comments suggesting possible further refinements to what is and
isn't eligible (e.g. should a bullwhip really be presented as a
candidate for readying a thrown weapon?)

This also removed ALLOW_COUNT and ALLOW_NONE, relics of the old system,
and moved ALLOW_ALL's definition into detect.c which is the only place
it's used now (unrelated to getobj). The ALLOW_ALL functionality still
exists as the GETOBJ_PROMPT flag, because its main use is to force
getobj to prompt for input even if nothing is valid.

I did not refactor ggetobj() as part of this change.

20 files changed:
include/extern.h
include/hack.h
include/objclass.h
src/apply.c
src/artifact.c
src/detect.c
src/do.c
src/do_name.c
src/do_wear.c
src/dothrow.c
src/eat.c
src/engrave.c
src/invent.c
src/pickup.c
src/potion.c
src/read.c
src/trap.c
src/wield.c
src/write.c
src/zap.c

index a24ca64f3081d7f8da0940a1c4a6bef5f04e036f..e1f96be64c6111a116b18f65be6f39b6c909a8d7 100644 (file)
@@ -1011,7 +1011,8 @@ E boolean NDECL(wearing_armor);
 E boolean FDECL(is_worn, (struct obj *));
 E struct obj *FDECL(g_at, (int, int));
 E boolean FDECL(splittable, (struct obj *));
-E struct obj *FDECL(getobj, (const char *, const char *));
+E int FDECL(any_obj_ok, (struct obj *));
+E struct obj *FDECL(getobj, (const char *, int (*)(OBJ_P), unsigned int));
 E int FDECL(ggetobj, (const char *, int (*)(OBJ_P), int,
                       BOOLEAN_P, unsigned *));
 E int FDECL(askchain, (struct obj **, const char *, int, int (*)(OBJ_P),
@@ -2124,7 +2125,7 @@ E char *FDECL(apron_text, (struct obj *, char *));
 E const char *FDECL(candy_wrapper_text, (struct obj *));
 E void FDECL(assign_candy_wrapper, (struct obj *));
 E int NDECL(doread);
-E boolean FDECL(is_chargeable, (struct obj *));
+E int FDECL(charge_ok, (struct obj *));
 E void FDECL(recharge, (struct obj *, int));
 E int FDECL(seffects, (struct obj *));
 E void FDECL(drop_boulder_on_player,
index ca8b855c4fe00b8278f0e0e4c1dd567a0db75d28..b245d9a6e26c0ea4023aeb45668e55143d119a70 100644 (file)
@@ -478,6 +478,40 @@ enum bodypart_types {
 #define MON_POLE_DIST 5 /* How far monsters can use pole-weapons */
 #define PET_MISSILE_RANGE2 36 /* Square of distance within which pets shoot */
 
+/* flags passed to getobj() to control how it responds to player input */
+#define GETOBJ_NOFLAGS  0x0
+#define GETOBJ_ALLOWCNT 0x1 /* is a count allowed with this command? */
+#define GETOBJ_PROMPT   0x2 /* should it force a prompt for input? (prevents it
+                               exiting early with "You don't have anything to
+                               foo" if nothing in inventory is valid) */
+
+/* values returned from getobj() callback functions */
+enum getobj_callback_returns {
+    /* generally invalid - can't be used for this purpose. will give a "silly
+     * thing" message if the player tries to pick it, unless a more specific
+     * failure message is in getobj itself - e.g. "You cannot foo gold". */
+    GETOBJ_EXCLUDE = -2,
+    /* invalid because it is an inaccessible or unwanted piece of gear, but
+     * psuedo-valid for the purposes of allowing the player to select it and
+     * getobj to return it if there is a prompt instead of getting "silly
+     * thing", in order for the getobj caller to present a specific failure
+     * message. Other than that, the only thing this does differently from
+     * GETOBJ_EXCLUDE is that it inserts an "else" in "You don't have anything
+     * else to foo". */
+    GETOBJ_EXCLUDE_INACCESS = -1,
+    /* invalid for purposes of not showing a prompt if nothing is valid but
+     * psuedo-valid for selecting - identical to GETOBJ_EXCLUDE_INACCESS but
+     * without the "else" in "You don't have anything else to foo". */
+    GETOBJ_EXCLUDE_SELECTABLE = 0,
+    /* valid - invlet not presented in the summary or the ? menu as a
+     * recommendation, but is selectable if the player enters it anyway. Used
+     * for objects that are actually valid but unimportantly so, such as shirts
+     * for reading. */
+    GETOBJ_DOWNPLAY = 1,
+    /* valid - will be shown in summary and ? menu */
+    GETOBJ_SUGGEST  = 2,
+};
+
 /*
  * option setting restrictions
  */
index cdfc9b6256de56a0a0666ce7af8cff525bb92408..7339d41bb58b9001521e1b05b63018af72b5c3dd 100644 (file)
@@ -166,10 +166,6 @@ enum obj_class_types {
 /* for mkobj() use ONLY! odd '-SPBOOK_CLASS' is in case of unsigned enums */
 #define SPBOOK_no_NOVEL (0 - (int) SPBOOK_CLASS)
 
-#define ALLOW_COUNT (MAXOCLASSES + 1) /* Can be used in the object class    */
-#define ALL_CLASSES (MAXOCLASSES + 2) /* input to getobj().                 */
-#define ALLOW_NONE  (MAXOCLASSES + 3)
-
 #define BURNING_OIL (MAXOCLASSES + 1) /* Can be used as input to explode.   */
 #define MON_EXPLODE (MAXOCLASSES + 2) /* Exploding monster (e.g. gas spore) */
 
index fdc179e527b2ec8cd193b3b75e3cd485f0a5ecf5..0d00e32366862fd8a85ddb23cba3a2c8f610d462 100644 (file)
@@ -18,25 +18,28 @@ static void FDECL(use_candelabrum, (struct obj *));
 static void FDECL(use_candle, (struct obj **));
 static void FDECL(use_lamp, (struct obj *));
 static void FDECL(light_cocktail, (struct obj **));
+static int FDECL(rub_ok, (struct obj *));
 static void FDECL(display_jump_positions, (int));
 static void FDECL(use_tinning_kit, (struct obj *));
 static void FDECL(use_figurine, (struct obj **));
+static int FDECL(grease_ok, (struct obj *));
 static void FDECL(use_grease, (struct obj *));
 static void FDECL(use_trap, (struct obj *));
+static int FDECL(touchstone_ok, (struct obj *));
 static void FDECL(use_stone, (struct obj *));
 static int NDECL(set_trap); /* occupation callback */
 static int FDECL(use_whip, (struct obj *));
 static void FDECL(display_polearm_positions, (int));
 static int FDECL(use_pole, (struct obj *));
 static int FDECL(use_cream_pie, (struct obj *));
+static int FDECL(jelly_ok, (struct obj *));
 static int FDECL(use_royal_jelly, (struct obj *));
 static int FDECL(use_grapple, (struct obj *));
 static int FDECL(do_break_wand, (struct obj *));
+static int FDECL(apply_ok, (struct obj *));
 static int FDECL(flip_through_book, (struct obj *));
 static boolean FDECL(figurine_location_checks, (struct obj *,
                                                     coord *, BOOLEAN_P));
-static void FDECL(add_class, (char *, CHAR_P));
-static void FDECL(setapplyclasses, (char *));
 static boolean FDECL(check_jump, (genericptr_t, int, int));
 static boolean FDECL(is_valid_jump_pos, (int, int, int, BOOLEAN_P));
 static boolean FDECL(get_valid_jump_position, (int, int));
@@ -1552,9 +1555,22 @@ struct obj **optr;
     *optr = obj;
 }
 
-static NEARDATA const char
-    cuddly[] = { TOOL_CLASS, GEM_CLASS, 0 },
-    cuddlier[] = { TOOL_CLASS, GEM_CLASS, FOOD_CLASS, 0 };
+/* getobj callback for object to be rubbed - not selecting a secondary object to
+ * rub on a gray stone or rub jelly on */
+static int
+rub_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP
+        || obj->otyp == BRASS_LANTERN || is_graystone(obj)
+        || obj->otyp == LUMP_OF_ROYAL_JELLY)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
 
 int
 dorub()
@@ -1565,7 +1581,7 @@ dorub()
         You("aren't able to rub anything without hands.");
         return 0;
     }
-    obj = getobj(carrying(LUMP_OF_ROYAL_JELLY) ? cuddlier : cuddly, "rub");
+    obj = getobj("rub", rub_ok, GETOBJ_NOFLAGS);
     if (!obj) {
         /* pline1(Never_mind); -- handled by getobj() */
         return 0;
@@ -2331,7 +2347,24 @@ struct obj **optr;
     *optr = 0;
 }
 
-static NEARDATA const char lubricables[] = { ALL_CLASSES, ALLOW_NONE, 0 };
+/* getobj callback for object to apply grease to */
+static int
+grease_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_SUGGEST;
+
+    if (obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    if (inaccessible_equipment(obj, (const char *) 0, FALSE))
+        return GETOBJ_EXCLUDE_INACCESS;
+
+    /* Possible extension: don't suggest greasing objects which are already
+     * greased. */
+    return GETOBJ_SUGGEST;
+}
 
 static void
 use_grease(obj)
@@ -2357,7 +2390,7 @@ struct obj *obj;
             dropx(obj);
             return;
         }
-        otmp = getobj(lubricables, "grease");
+        otmp = getobj("grease", grease_ok, GETOBJ_PROMPT);
         if (!otmp)
             return;
         if (inaccessible_equipment(otmp, "grease", FALSE))
@@ -2386,31 +2419,52 @@ struct obj *obj;
     update_inventory();
 }
 
+/* getobj callback for object to rub on a known touchstone */
+static int
+touchstone_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* Gold being suggested as a rub target is questionable - it fits the
+     * real-world historic use of touchstones, but doesn't do anything
+     * significant in the game. */
+    if (obj->oclass == COIN_CLASS)
+        return GETOBJ_SUGGEST;
+
+    /* don't suggest identified gems */
+    if (obj->oclass == GEM_CLASS
+        && !(obj->dknown && objects[obj->otyp].oc_name_known))
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
+
+
 /* touchstones - by Ken Arnold */
 static void
 use_stone(tstone)
 struct obj *tstone;
 {
     static const char scritch[] = "\"scritch, scritch\"";
-    static const char allowall[3] = { COIN_CLASS, ALL_CLASSES, 0 };
-    static const char coins_gems[3] = { COIN_CLASS, GEM_CLASS, 0 };
     struct obj *obj;
     boolean do_scratch;
-    const char *streak_color, *choices;
+    const char *streak_color;
     char stonebuf[QBUFSZ];
     int oclass;
+    boolean known;
 
     /* in case it was acquired while blinded */
     if (!Blind)
         tstone->dknown = 1;
+    known = (tstone->otyp == TOUCHSTONE && tstone->dknown
+              && objects[TOUCHSTONE].oc_name_known);
+    Sprintf(stonebuf, "rub on the stone%s", plur(tstone->quan));
     /* when the touchstone is fully known, don't bother listing extra
        junk as likely candidates for rubbing */
-    choices = (tstone->otyp == TOUCHSTONE && tstone->dknown
-               && objects[TOUCHSTONE].oc_name_known)
-                  ? coins_gems
-                  : allowall;
-    Sprintf(stonebuf, "rub on the stone%s", plur(tstone->quan));
-    if ((obj = getobj(choices, stonebuf)) == 0)
+    if ((obj = getobj(stonebuf, known ? touchstone_ok : any_obj_ok,
+                      GETOBJ_PROMPT)) == 0)
         return;
 
     if (obj == tstone && obj->quan == 1L) {
@@ -3180,11 +3234,21 @@ struct obj *obj;
     return 0;
 }
 
+/* getobj callback for object to rub royal jelly on */
+static int
+jelly_ok(obj)
+struct obj *obj;
+{
+    if (obj && obj->otyp == EGG)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
+
 static int
 use_royal_jelly(obj)
 struct obj *obj;
 {
-    static const char allowall[2] = { ALL_CLASSES, 0 };
     int oldcorpsenm;
     unsigned was_timed;
     struct obj *eobj;
@@ -3196,7 +3260,7 @@ struct obj *obj;
     freeinv(obj);
 
     /* right now you can rub one royal jelly on an entire stack of eggs */
-    eobj = getobj(allowall, "rub the royal jelly on");
+    eobj = getobj("rub the royal jelly on", jelly_ok, GETOBJ_PROMPT);
     if (!eobj) {
         addinv(obj); /* put the unused lump back; if it came from
                       * a split, it should merge back */
@@ -3624,64 +3688,53 @@ discard_broken_wand:
     return 1;
 }
 
-static void
-add_class(cl, class)
-char *cl;
-char class;
-{
-    char tmp[2];
-
-    tmp[0] = class;
-    tmp[1] = '\0';
-    Strcat(cl, tmp);
-}
-
-static const char tools[] = { TOOL_CLASS, WEAPON_CLASS, WAND_CLASS, 0 };
-
-/* augment tools[] if various items are carried */
-static void
-setapplyclasses(class_list)
-char class_list[];
+/* getobj callback for object to apply - this is more complex than most other
+ * callbacks because there are a lot of appliables */
+static int
+apply_ok(obj)
+struct obj *obj;
 {
-    register struct obj *otmp;
-    int otyp;
-    boolean knowoil, knowtouchstone;
-    boolean addpotions, addstones, addfood, addspellbooks;
-
-    knowoil = objects[POT_OIL].oc_name_known;
-    knowtouchstone = objects[TOUCHSTONE].oc_name_known;
-    addpotions = addstones = addfood = addspellbooks = FALSE;
-    for (otmp = g.invent; otmp; otmp = otmp->nobj) {
-        otyp = otmp->otyp;
-        if (otyp == POT_OIL
-            || (otmp->oclass == POTION_CLASS
-                && (!otmp->dknown
-                    || (!knowoil && !objects[otyp].oc_name_known))))
-            addpotions = TRUE;
-        if (otyp == TOUCHSTONE
-            || (is_graystone(otmp)
-                && (!otmp->dknown
-                    || (!knowtouchstone && !objects[otyp].oc_name_known))))
-            addstones = TRUE;
-        if (otyp == CREAM_PIE || otyp == EUCALYPTUS_LEAF
-            || otyp == LUMP_OF_ROYAL_JELLY)
-            addfood = TRUE;
-        if (otmp->oclass == SPBOOK_CLASS)
-            addspellbooks = TRUE;
-    }
-
-    class_list[0] = '\0';
-    if (addpotions || addstones)
-        add_class(class_list, ALL_CLASSES);
-    Strcat(class_list, tools);
-    if (addpotions)
-        add_class(class_list, POTION_CLASS);
-    if (addstones)
-        add_class(class_list, GEM_CLASS);
-    if (addfood)
-        add_class(class_list, FOOD_CLASS);
-    if (addspellbooks)
-        add_class(class_list, SPBOOK_CLASS);
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* all tools, all wands (breaking), all spellbooks (flipping through -
+     * including blank/novel/Book of the Dead) */
+    if (obj->oclass == TOOL_CLASS || obj->oclass == WAND_CLASS
+        || obj->oclass == SPBOOK_CLASS)
+        return GETOBJ_SUGGEST;
+
+    /* certain weapons */
+    if (obj->oclass == WEAPON_CLASS
+        && (is_pick(obj) || is_axe(obj) || is_pole(obj)
+            || obj->otyp == BULLWHIP))
+        return GETOBJ_SUGGEST;
+
+    /* only applicable potion is oil, and it will only be offered as a choice
+     * when already discovered */
+    if (obj->otyp == POT_OIL && obj->dknown
+        && objects[obj->otyp].oc_name_known)
+        return GETOBJ_SUGGEST;
+
+    /* certain foods */
+    if (obj->otyp == CREAM_PIE || obj->otyp == EUCALYPTUS_LEAF
+        || obj->otyp == LUMP_OF_ROYAL_JELLY)
+        return GETOBJ_SUGGEST;
+
+    if (is_graystone(obj)) {
+        /* The only case where we don't suggest a gray stone is if we KNOW it
+         * isn't a touchstone. */
+        if (!obj->dknown)
+            return GETOBJ_SUGGEST;
+
+        if (obj->otyp != TOUCHSTONE
+            && (objects[TOUCHSTONE].oc_name_known
+                || objects[obj->otyp].oc_name_known))
+            return GETOBJ_EXCLUDE;
+
+        return GETOBJ_SUGGEST;
+    }
+
+    return GETOBJ_EXCLUDE;
 }
 
 /* the 'a' command */
@@ -3690,7 +3743,6 @@ doapply()
 {
     struct obj *obj;
     register int res = 1;
-    char class_list[MAXOCLASSES + 2];
 
     if (nohands(g.youmonst.data)) {
         You("aren't able to use or apply tools in your current form.");
@@ -3699,8 +3751,7 @@ doapply()
     if (check_capacity((char *) 0))
         return 0;
 
-    setapplyclasses(class_list); /* tools[] */
-    obj = getobj(class_list, "use or apply");
+    obj = getobj("use or apply", apply_ok, GETOBJ_NOFLAGS);
     if (!obj)
         return 0;
 
index b9bca5126bd94b6bbc19bcf4f022f029e465ab7d..e2433c8c1555edd4c7ce55e1161b94d1b59f5159 100644 (file)
@@ -20,6 +20,7 @@
 static boolean FDECL(bane_applies, (const struct artifact *,
                                         struct monst *));
 static int FDECL(spec_applies, (const struct artifact *, struct monst *));
+static int FDECL(invoke_ok, (struct obj *));
 static int FDECL(arti_invoke, (struct obj *));
 static boolean FDECL(Mb_hit, (struct monst * magr, struct monst *mdef,
                                 struct obj *, int *, int, BOOLEAN_P, char *));
@@ -1411,9 +1412,28 @@ int dieroll; /* needed for Magicbane and vorpal blades */
     return FALSE;
 }
 
-static NEARDATA const char recharge_type[] = { ALLOW_COUNT, ALL_CLASSES, 0 };
-static NEARDATA const char invoke_types[] = { ALL_CLASSES, 0 };
-/* #invoke: an "ugly check" filters out most objects */
+/* getobj callback for object to be invoked */
+static int
+invoke_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* artifacts and other special items */
+    if (obj->oartifact || objects[obj->otyp].oc_unique
+        || (obj->otyp == FAKE_AMULET_OF_YENDOR && !obj->known))
+        return GETOBJ_SUGGEST;
+
+    /* synonym for apply, though actually invoking it will do different things
+     * depending if it's a regular crystal ball, an artifact one that has an
+     * invoke power, and a (theoretical) artifact one that doesn't have an
+     * invoke power */
+    if (obj->otyp == CRYSTAL_BALL)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
 
 /* the #invoke command */
 int
@@ -1421,7 +1441,7 @@ doinvoke()
 {
     struct obj *obj;
 
-    obj = getobj(invoke_types, "invoke");
+    obj = getobj("invoke", invoke_ok, GETOBJ_PROMPT);
     if (!obj)
         return 0;
     if (!retouch_object(&obj, FALSE))
@@ -1516,7 +1536,8 @@ struct obj *obj;
             break;
         }
         case CHARGE_OBJ: {
-            struct obj *otmp = getobj(recharge_type, "charge");
+            struct obj *otmp = getobj("charge", charge_ok,
+                                      GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
             boolean b_effect;
 
             if (!otmp) {
index 4c4419417d3ab508fcf6c13a622d9750c94fb197..8dcf00b8859f534f785464d80529ba893787d972 100644 (file)
@@ -29,6 +29,10 @@ static int FDECL(mfind0, (struct monst *, BOOLEAN_P));
 static int FDECL(reveal_terrain_getglyph, (int, int, int,
                                                unsigned, int, int));
 
+/* wildcard class for clear_stale_map - this used to be used as a getobj() input
+ * but it's no longer used for that function */
+#define ALL_CLASSES (MAXOCLASSES + 1)
+
 /* bring hero out from underwater or underground or being engulfed;
    return True iff any change occurred */
 static boolean
index 241d1d5dfd7961edfded596ef19b91eb142041f7..bc3af874f5cd351823854b9a05ffd4b7dbc3c11f 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -19,18 +19,15 @@ static NHFILE *NDECL(currentlevel_rewrite);
 static void NDECL(final_level);
 /* static boolean FDECL(badspot, (XCHAR_P,XCHAR_P)); */
 
-static NEARDATA const char drop_types[] = { ALLOW_COUNT, COIN_CLASS,
-                                            ALL_CLASSES, 0 };
-
 /* 'd' command: drop one inventory item */
 int
 dodrop()
 {
-    int result, i = (g.invent) ? 0 : (SIZE(drop_types) - 1);
+    int result;
 
     if (*u.ushops)
         sellobj_state(SELL_DELIBERATE);
-    result = drop(getobj(&drop_types[i], "drop"));
+    result = drop(getobj("drop", any_obj_ok, GETOBJ_PROMPT));
     if (*u.ushops)
         sellobj_state(SELL_NORMAL);
     if (result)
index 4958c600b4cf5c6ce1377ec2358a75a8c5e606a1..f0374b931dcbc4afa84c41386589f288338df49b 100644 (file)
@@ -21,6 +21,8 @@ static void FDECL(auto_describe, (int, int));
 static void NDECL(do_mgivenname);
 static boolean FDECL(alreadynamed, (struct monst *, char *, char *));
 static void FDECL(do_oname, (struct obj *));
+static int FDECL(name_ok, (struct obj *));
+static int FDECL(call_ok, (struct obj *));
 static char *FDECL(docall_xname, (struct obj *));
 static void NDECL(namefloorobj);
 
@@ -1334,18 +1336,53 @@ const char *name;
     return obj;
 }
 
-static NEARDATA const char callable[] = {
-    SCROLL_CLASS, POTION_CLASS, WAND_CLASS,  RING_CLASS, AMULET_CLASS,
-    GEM_CLASS,    SPBOOK_CLASS, ARMOR_CLASS, TOOL_CLASS, VENOM_CLASS, 0
-};
-
 boolean
 objtyp_is_callable(i)
 int i;
 {
-    return (boolean) (objects[i].oc_uname
-                      || (OBJ_DESCR(objects[i])
-                          && index(callable, objects[i].oc_class)));
+    if (objects[i].oc_uname)
+        return TRUE;
+
+    switch(objects[i].oc_class) {
+    case SCROLL_CLASS:
+    case POTION_CLASS:
+    case WAND_CLASS:
+    case RING_CLASS:
+    case AMULET_CLASS:
+    case GEM_CLASS:
+    case SPBOOK_CLASS:
+    case ARMOR_CLASS:
+    case TOOL_CLASS:
+    case VENOM_CLASS:
+        if (OBJ_DESCR(objects[i]))
+            return TRUE;
+        break;
+    default:
+        break;
+    }
+    return FALSE;
+}
+
+/* getobj callback for object to name (specific item) - anything but gold */
+static int
+name_ok(obj)
+struct obj *obj;
+{
+    if (!obj || obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    return GETOBJ_SUGGEST;
+}
+
+/* getobj callback for object to call (name its type) */
+static int
+call_ok(obj)
+struct obj *obj;
+{
+    if (!obj || !objtyp_is_callable(obj->otyp))
+        return GETOBJ_EXCLUDE;
+
+    return GETOBJ_SUGGEST;
 }
 
 /* C and #name commands - player can name monster or object or type of obj */
@@ -1356,7 +1393,7 @@ docallcmd()
     winid win;
     anything any;
     menu_item *pick_list = 0;
-    char ch, allowall[2];
+    char ch;
     /* if player wants a,b,c instead of i,o when looting, do that here too */
     boolean abc = flags.lootabc;
 
@@ -1406,14 +1443,12 @@ docallcmd()
         do_mgivenname();
         break;
     case 'i': /* name an individual object in inventory */
-        allowall[0] = ALL_CLASSES;
-        allowall[1] = '\0';
-        obj = getobj(allowall, "name");
+        obj = getobj("name", name_ok, GETOBJ_PROMPT);
         if (obj)
             do_oname(obj);
         break;
     case 'o': /* name a type of object in inventory */
-        obj = getobj(callable, "call");
+        obj = getobj("call", call_ok, GETOBJ_NOFLAGS);
         if (obj) {
             /* behave as if examining it in inventory;
                this might set dknown if it was picked up
@@ -1423,7 +1458,7 @@ docallcmd()
             if (!obj->dknown) {
                 You("would never recognize another one.");
 #if 0
-            } else if (!objtyp_is_callable(obj->otyp)) {
+            } else if (!call_ok(obj)) {
                 You("know those as well as you ever will.");
 #endif
             } else {
@@ -1591,7 +1626,7 @@ namefloorobj()
         pline("%s %s to call you \"%s.\"",
               The(buf), use_plural ? "decide" : "decides",
               unames[rn2_on_display_rng(SIZE(unames))]);
-    } else if (!objtyp_is_callable(obj->otyp)) {
+    } else if (!call_ok(obj)) {
         pline("%s %s can't be assigned a type name.",
               use_plural ? "Those" : "That", buf);
     } else if (!obj->dknown) {
index 044b363d6ae41d97f36eb1f30347c2535dfd9e37..ebdf6102ce9992f5df3d3a4c745945a59f0dcd8b 100644 (file)
@@ -42,6 +42,11 @@ static int FDECL(armor_or_accessory_off, (struct obj *));
 static int FDECL(accessory_or_armor_on, (struct obj *));
 static void FDECL(already_wearing, (const char *));
 static void FDECL(already_wearing2, (const char *, const char *));
+static int FDECL(equip_ok, (struct obj *, BOOLEAN_P, BOOLEAN_P));
+static int FDECL(puton_ok, (struct obj *));
+static int FDECL(remove_ok, (struct obj *));
+static int FDECL(wear_ok, (struct obj *));
+static int FDECL(takeoff_ok, (struct obj *));
 
 /* plural "fingers" or optionally "gloves" */
 const char *
@@ -1446,14 +1451,6 @@ struct obj *stolenobj; /* no message if stolenobj is already being doffing */
     return result;
 }
 
-/* both 'clothes' and 'accessories' now include both armor and accessories;
-   TOOL_CLASS is for eyewear, FOOD_CLASS is for MEAT_RING */
-static NEARDATA const char clothes[] = {
-    ARMOR_CLASS, RING_CLASS, AMULET_CLASS, TOOL_CLASS, FOOD_CLASS, 0
-};
-static NEARDATA const char accessories[] = {
-    RING_CLASS, AMULET_CLASS, TOOL_CLASS, FOOD_CLASS, ARMOR_CLASS, 0
-};
 static NEARDATA int Narmorpieces, Naccessories;
 
 /* assign values to Narmorpieces and Naccessories */
@@ -1575,7 +1572,7 @@ dotakeoff()
         return 0;
     }
     if (Narmorpieces != 1 || ParanoidRemove)
-        otmp = getobj(clothes, "take off");
+        otmp = getobj("take off", takeoff_ok, GETOBJ_NOFLAGS);
     if (!otmp)
         return 0;
 
@@ -1594,7 +1591,7 @@ doremring()
         return 0;
     }
     if (Naccessories != 1 || ParanoidRemove)
-        otmp = getobj(accessories, "remove");
+        otmp = getobj("remove", remove_ok, GETOBJ_NOFLAGS);
     if (!otmp)
         return 0;
 
@@ -2159,7 +2156,7 @@ dowear()
         You("are already wearing a full complement of armor.");
         return 0;
     }
-    otmp = getobj(clothes, "wear");
+    otmp = getobj("wear", wear_ok, GETOBJ_NOFLAGS);
     return otmp ? accessory_or_armor_on(otmp) : 0;
 }
 
@@ -2178,7 +2175,7 @@ doputon()
              (ublindf->otyp == LENSES) ? "some lenses" : "a blindfold");
         return 0;
     }
-    otmp = getobj(accessories, "put on");
+    otmp = getobj("put on", puton_ok, GETOBJ_NOFLAGS);
     return otmp ? accessory_or_armor_on(otmp) : 0;
 }
 
@@ -2934,4 +2931,84 @@ boolean only_if_known_cursed; /* ignore covering unless known to be cursed */
     return FALSE;
 }
 
+/* not a getobj callback - unifies code among the other four getobj callbacks */
+static int
+equip_ok(obj, removing, accessory)
+struct obj *obj;
+boolean removing;
+boolean accessory;
+{
+    boolean is_worn;
+    long dummymask = 0;
+
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* ignore for putting on if already worn, or removing if not already worn */
+    is_worn = ((obj->owornmask & (W_ARMOR | W_ACCESSORY)) != 0);
+    if (removing ^ is_worn)
+        return GETOBJ_EXCLUDE_INACCESS;
+
+    /* exclude most object classes outright */
+    if (obj->oclass != ARMOR_CLASS && obj->oclass != RING_CLASS
+        && obj->oclass != AMULET_CLASS) {
+        /* ... except for a few wearable exceptions outside these classes */
+        if (obj->otyp != MEAT_RING && obj->otyp != BLINDFOLD
+            && obj->otyp != TOWEL && obj->otyp != LENSES)
+            return GETOBJ_EXCLUDE;
+    }
+
+    /* armor with 'P' or 'R' or accessory with 'W' or 'T' */
+    if (accessory ^ (obj->oclass != ARMOR_CLASS))
+        return GETOBJ_DOWNPLAY;
+
+    /* armor we can't wear, e.g. from polyform */
+    if (obj->oclass == ARMOR_CLASS && !removing &&
+        !canwearobj(obj, &dummymask, FALSE))
+        return GETOBJ_DOWNPLAY;
+
+    /* Possible extension: downplay items (both accessories and armor) which
+     * can't be worn because the slot is filled with something else. */
+
+    /* removing inaccessible equipment */
+    if (removing && inaccessible_equipment(obj, (const char *) 0,
+                                           (obj->oclass == RING_CLASS)))
+        return GETOBJ_EXCLUDE_INACCESS;
+
+    /* all good to go */
+    return GETOBJ_SUGGEST;
+}
+
+/* getobj callback for P command */
+static int
+puton_ok(obj)
+struct obj *obj;
+{
+    return equip_ok(obj, FALSE, TRUE);
+}
+
+/* getobj callback for R command */
+static int
+remove_ok(obj)
+struct obj *obj;
+{
+    return equip_ok(obj, TRUE, TRUE);
+}
+
+/* getobj callback for W command */
+static int
+wear_ok(obj)
+struct obj *obj;
+{
+    return equip_ok(obj, FALSE, FALSE);
+}
+
+/* getobj callback for T command */
+static int
+takeoff_ok(obj)
+struct obj *obj;
+{
+    return equip_ok(obj, TRUE, FALSE);
+}
+
 /*do_wear.c*/
index 8318bafb3ba3a9ca2befb4820a5bf0ec69fafc0d..ec325bb36cac68cc4c9ae6e5c3610c85cb91a78f 100644 (file)
@@ -9,6 +9,7 @@
 
 static int FDECL(throw_obj, (struct obj *, int));
 static boolean FDECL(ok_to_throw, (int *));
+static int FDECL(throw_ok, (struct obj *));
 static void NDECL(autoquiver);
 static int FDECL(gem_accept, (struct monst *, struct obj *));
 static void FDECL(tmiss, (struct obj *, struct monst *, BOOLEAN_P));
@@ -20,13 +21,6 @@ static boolean FDECL(toss_up, (struct obj *, BOOLEAN_P));
 static void FDECL(sho_obj_return_to_u, (struct obj * obj));
 static boolean FDECL(mhurtle_step, (genericptr_t, int, int));
 
-static NEARDATA const char toss_objs[] = { ALLOW_COUNT, COIN_CLASS,
-                                           ALL_CLASSES, WEAPON_CLASS, 0 };
-/* different default choices when wielding a sling (gold must be included) */
-static NEARDATA const char t_bullets[] = {
-    ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, GEM_CLASS, 0
-};
-
 /* g.thrownobj (decl.c) tracks an object until it lands */
 
 int
@@ -275,6 +269,36 @@ int *shotlimit_p; /* (see dothrow()) */
     return TRUE;
 }
 
+/* getobj callback for object to be thrown */
+static int
+throw_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->quan == 1 && (obj == uwep || (obj == uswapwep && u.twoweap)))
+        return GETOBJ_DOWNPLAY;
+    /* Possible extension: return GETOBJ_SUGGEST for uwep if it will return when
+     * thrown. */
+
+    if (obj->oclass == COIN_CLASS)
+        return GETOBJ_SUGGEST;
+
+    if (!uslinging() && obj->oclass == WEAPON_CLASS)
+        return GETOBJ_SUGGEST;
+    /* Possible extension: exclude weapons that make no sense to throw, such as
+     * whips, bows, slings, rubber hoses. */
+
+    if (uslinging() && obj->oclass == GEM_CLASS)
+        return GETOBJ_SUGGEST;
+
+    if (throws_rocks(g.youmonst.data) && obj->otyp == BOULDER)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
+
 /* t command - throw */
 int
 dothrow()
@@ -296,7 +320,7 @@ dothrow()
     if (!ok_to_throw(&shotlimit))
         return 0;
 
-    obj = getobj(uslinging() ? t_bullets : toss_objs, "throw");
+    obj = getobj("throw", throw_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
     /* it is also possible to throw food */
     /* (or jewels, or iron balls... ) */
 
@@ -408,7 +432,7 @@ dofire()
                use direction of previous throw as getobj()'s choice here */
             g.in_doagain = 0;
             /* choose something from inventory, then usually quiver it */
-            obj = getobj(uslinging() ? t_bullets : toss_objs, "throw");
+            obj = getobj("throw", throw_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
             /* Q command doesn't allow gold in quiver */
             if (obj && !obj->owornmask && obj->oclass != COIN_CLASS)
                 setuqwep(obj); /* demi-autoquiver */
index 13628642b626d13c63f4f3779636d328bd0ac530..72254a9934aacee31319dbc5fdf3ae4a11f388f7 100644 (file)
--- a/src/eat.c
+++ b/src/eat.c
@@ -30,6 +30,7 @@ static void FDECL(fprefx, (struct obj *));
 static void FDECL(fpostfx, (struct obj *));
 static int NDECL(bite);
 static int FDECL(edibility_prompts, (struct obj *));
+static int FDECL(tinopen_ok, (struct obj *));
 static int FDECL(rottenfood, (struct obj *));
 static void NDECL(eatspecial);
 static int FDECL(bounded_increase, (int, int, int));
@@ -38,6 +39,9 @@ static void FDECL(eataccessory, (struct obj *));
 static const char *FDECL(foodword, (struct obj *));
 static int FDECL(tin_variety, (struct obj *, BOOLEAN_P));
 static boolean FDECL(maybe_cannibal, (int, BOOLEAN_P));
+static int FDECL(eat_ok, (struct obj *));
+static int FDECL(offer_ok, (struct obj *));
+static int FDECL(tin_ok, (struct obj *));
 
 /* also used to see if you're allowed to eat cats and dogs */
 #define CANNIBAL_ALLOWED() (Role_if(PM_CAVE_DWELLER) || Race_if(PM_ORC))
@@ -55,18 +59,6 @@ static boolean FDECL(maybe_cannibal, (int, BOOLEAN_P));
 #define nonrotting_food(otyp) \
     ((otyp) == LEMBAS_WAFER || (otyp) == CRAM_RATION)
 
-static NEARDATA const char comestibles[] = { FOOD_CLASS, 0 };
-static NEARDATA const char offerfodder[] = { FOOD_CLASS, AMULET_CLASS,
-                                                 0 };
-
-/* Gold must come first for getobj(). */
-static NEARDATA const char allobj[] = {
-    COIN_CLASS,   WEAPON_CLASS, ARMOR_CLASS,  POTION_CLASS,
-    SCROLL_CLASS, WAND_CLASS,   RING_CLASS,   AMULET_CLASS,
-    FOOD_CLASS,   TOOL_CLASS,   GEM_CLASS,    ROCK_CLASS,
-    BALL_CLASS,   CHAIN_CLASS,  SPBOOK_CLASS, 0
-};
-
 /* see hunger states in hack.h - texts used on bottom line */
 const char *hu_stat[] = { "Satiated", "        ", "Hungry  ", "Weak    ",
                           "Fainting", "Fainted ", "Starved " };
@@ -102,7 +94,6 @@ register struct obj *obj;
         && !Has_contents(obj))
         return TRUE;
 
-    /* return (boolean) !!index(comestibles, obj->oclass); */
     return (boolean) (obj->oclass == FOOD_CLASS);
 }
 
@@ -2763,6 +2754,18 @@ doeat()
     return 1;
 }
 
+/* getobj callback for object to be opened with a tin opener */
+static int
+tinopen_ok(obj)
+struct obj *obj;
+{
+    if (obj && obj->otyp == TIN)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
+
+
 int
 use_tin_opener(obj)
 struct obj *obj;
@@ -2788,7 +2791,7 @@ struct obj *obj;
         res = 1;
     }
 
-    otmp = getobj(comestibles, "open");
+    otmp = getobj("open", tinopen_ok, GETOBJ_NOFLAGS);
     if (!otmp)
         return res;
 
@@ -3163,6 +3166,60 @@ boolean incr;
     }
 }
 
+/* getobj callback for object to eat - effectively just wraps is_edible() */
+static int
+eat_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    if (is_edible(obj))
+        return GETOBJ_SUGGEST;
+
+    /* make sure to exclude, not downplay, gold (if not is_edible) in order to
+     * produce the "You cannot eat gold" message in getobj */
+    if (obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    return GETOBJ_EXCLUDE_SELECTABLE;
+}
+
+/* getobj callback for object to be offered (corpses and things that look like
+ * the Amulet only */
+static int
+offer_ok(obj)
+struct obj *obj;
+{
+    if (!obj || (obj->oclass != FOOD_CLASS && obj->oclass != AMULET_CLASS))
+        return GETOBJ_EXCLUDE;
+
+    if (obj->otyp != CORPSE && obj->otyp != AMULET_OF_YENDOR
+        && obj->otyp != FAKE_AMULET_OF_YENDOR)
+        return GETOBJ_EXCLUDE_SELECTABLE;
+
+    /* suppress corpses on astral, amulets elsewhere
+     * (!astral && amulet) || (astral && !amulet) */
+    if (Is_astralevel(&u.uz) ^ (obj->oclass == AMULET_CLASS))
+        return GETOBJ_DOWNPLAY;
+
+    return GETOBJ_SUGGEST;
+}
+
+/* getobj callback for object to be tinned */
+static int
+tin_ok(obj)
+struct obj *obj;
+{
+    if (!obj || obj->oclass != FOOD_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->otyp != CORPSE || !tinnable(obj))
+        return GETOBJ_EXCLUDE_SELECTABLE;
+
+    return GETOBJ_SUGGEST;
+}
+
 /* Returns an object representing food.
  * Object may be either on floor or in inventory.
  */
@@ -3278,11 +3335,18 @@ int corpsecheck; /* 0, no check, 1, corpses, 2, tinnable corpses */
     }
 
  skipfloor:
-    /* We cannot use ALL_CLASSES since that causes getobj() to skip its
-     * "ugly checks" and we need to check for inedible items.
-     */
-    otmp = getobj(feeding ? allobj : offering ? offerfodder : comestibles,
-                  verb);
+    /* We cannot use GETOBJ_PROMPT since we don't want a prompt in the case
+     * where nothing edible is being carried. */
+    if (feeding)
+        otmp = getobj("eat", eat_ok, GETOBJ_NOFLAGS);
+    else if (offering)
+        otmp = getobj("sacrifice", offer_ok, GETOBJ_NOFLAGS);
+    else if (corpsecheck == 2)
+        otmp = getobj(verb, tin_ok, GETOBJ_NOFLAGS);
+    else {
+        impossible("floorfood: unknown request (%s)", verb);
+        return (struct obj *) 0;
+    }
     if (corpsecheck && otmp && !(offering && otmp->oclass == AMULET_CLASS))
         if (otmp->otyp != CORPSE || (corpsecheck == 2 && !tinnable(otmp))) {
             You_cant("%s that!", verb);
index c35311d23eec55f12d6ad61aaea3c76233597030..fb3a163c692ee144e4b269128698c07399f1c0e9 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "hack.h"
 
+static int FDECL(stylus_ok, (struct obj *));
 static const char *NDECL(blengr);
 
 char *
@@ -435,10 +436,28 @@ freehand()
             || (!bimanual(uwep) && (!uarms || !uarms->cursed)));
 }
 
-static NEARDATA const char styluses[] = { ALL_CLASSES, ALLOW_NONE,
-                                          TOOL_CLASS,  WEAPON_CLASS,
-                                          WAND_CLASS,  GEM_CLASS,
-                                          RING_CLASS,  0 };
+/* getobj callback for an object to engrave with */
+static int
+stylus_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_SUGGEST;
+
+    /* Potential extension: exclude weapons that don't make any sense (such as
+     * bullwhips) and downplay rings and gems that wouldn't be good to write
+     * with (such as glass and non-gem rings) */
+    if (obj->oclass == WEAPON_CLASS || obj->oclass == WAND_CLASS
+        || obj->oclass == GEM_CLASS || obj->oclass == RING_CLASS)
+        return GETOBJ_SUGGEST;
+
+    /* Only markers and towels are recommended tools. */
+    if (obj->oclass == TOOL_CLASS
+        && (obj->otyp == TOWEL || obj->otyp == MAGIC_MARKER))
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
 
 /* Mohs' Hardness Scale:
  *  1 - Talc             6 - Orthoclase
@@ -545,7 +564,7 @@ doengrave()
      * Edited by GAN 10/20/86 so as not to change weapon wielded.
      */
 
-    otmp = getobj(styluses, "write with");
+    otmp = getobj("write with", stylus_ok, GETOBJ_PROMPT);
     if (!otmp) /* otmp == cg.zeroobj if fingers */
         return 0;
 
index e8d8273cc833e2c41219e7d28a48ce1b51587de8..17e49514d7504a7eb6650630f73a25c533d55dff 100644 (file)
@@ -26,7 +26,6 @@ static boolean FDECL(worn_wield_only, (struct obj *));
 static boolean FDECL(only_here, (struct obj *));
 static void FDECL(compactify, (char *));
 static boolean FDECL(taking_off, (const char *));
-static boolean FDECL(putting_on, (const char *));
 static int FDECL(ckvalidcat, (struct obj *));
 static int FDECL(ckunpaid, (struct obj *));
 static char *FDECL(safeq_xprname, (struct obj *));
@@ -39,6 +38,7 @@ static void NDECL(dounpaid);
 static struct obj *FDECL(find_unpaid, (struct obj *, struct obj **));
 static void FDECL(menu_identify, (int));
 static boolean FDECL(tool_in_use, (struct obj *));
+static int FDECL(adjust_ok, (struct obj *));
 static char FDECL(obj_to_let, (struct obj *));
 
 /* wizards can wish for venom, which will become an invisible inventory
@@ -1455,142 +1455,6 @@ const char *action;
     return !strcmp(action, "take off") || !strcmp(action, "remove");
 }
 
-/* match the prompt for either 'W' or 'P' command */
-static boolean
-putting_on(action)
-const char *action;
-{
-    return !strcmp(action, "wear") || !strcmp(action, "put on");
-}
-
-/* helper for getobj(), exclude obj if it cannot be used to do word */
-static boolean
-getobj_obj_exclude(word, otmp)
-const char *word;
-struct obj *otmp;
-{
-    return ((taking_off(word) /* exclude if not worn */
-            && !(otmp->owornmask & (W_ARMOR | W_ACCESSORY)))
-        || (putting_on(word) /* exclude if already worn */
-            && (otmp->owornmask & (W_ARMOR | W_ACCESSORY)))
-#if 0 /* 3.4.1 -- include currently wielded weapon among 'wield' choices */
-        || (!strcmp(word, "wield")
-            && (otmp->owornmask & W_WEP))
-#endif
-        || (!strcmp(word, "ready")    /* exclude when wielded... */
-            && ((otmp == uwep || (otmp == uswapwep && u.twoweap))
-                && otmp->quan == 1L)) /* ...unless more than one */
-        || ((!strcmp(word, "dip") || !strcmp(word, "grease"))
-            && inaccessible_equipment(otmp, (const char *) 0, FALSE)));
-}
-
-/* helper for getobj(), exclude obj if it cannot be used to do word */
-static boolean
-getobj_obj_exclude_too(word, otmp)
-const char *word;
-struct obj *otmp;
-{
-    short otyp = otmp->otyp;
-
-    return ((putting_on(word)
-             && ((otmp->oclass == FOOD_CLASS && otyp != MEAT_RING)
-                 || (otmp->oclass == TOOL_CLASS && otyp != BLINDFOLD
-                     && otyp != TOWEL && otyp != LENSES)))
-            || (!strcmp(word, "wield")
-                && (otmp->oclass == TOOL_CLASS && !is_weptool(otmp)))
-            || (!strcmp(word, "eat") && !is_edible(otmp))
-            || (!strcmp(word, "sacrifice")
-                && (otyp != CORPSE && otyp != AMULET_OF_YENDOR
-                    && otyp != FAKE_AMULET_OF_YENDOR))
-        || (!strcmp(word, "write with")
-            && (otmp->oclass == TOOL_CLASS
-                && otyp != MAGIC_MARKER && otyp != TOWEL))
-            || (!strcmp(word, "tin")
-                && (otyp != CORPSE || !tinnable(otmp)))
-        || (!strcmp(word, "rub")
-            && ((otmp->oclass == TOOL_CLASS && otyp != OIL_LAMP
-                 && otyp != MAGIC_LAMP && otyp != BRASS_LANTERN)
-                || (otmp->oclass == GEM_CLASS && !is_graystone(otmp))
-                || (otmp->oclass == FOOD_CLASS
-                    && otmp->otyp != LUMP_OF_ROYAL_JELLY)))
-            || (!strcmp(word, "use or apply")
-                /* Picks, axes, pole-weapons, bullwhips */
-                && ((otmp->oclass == WEAPON_CLASS
-                     && !is_pick(otmp) && !is_axe(otmp)
-                     && !is_pole(otmp) && otyp != BULLWHIP)
-                    || (otmp->oclass == POTION_CLASS
-                        /* only applicable potion is oil, and it will only
-                           be offered as a choice when already discovered */
-                        && (otyp != POT_OIL || !otmp->dknown
-                            || !objects[POT_OIL].oc_name_known))
-                    || (otmp->oclass == FOOD_CLASS
-                        && otyp != CREAM_PIE && otyp != EUCALYPTUS_LEAF
-                        && otyp != LUMP_OF_ROYAL_JELLY)
-                    || (otmp->oclass == GEM_CLASS && !is_graystone(otmp))))
-            || (!strcmp(word, "rub the royal jelly on") && otmp->otyp != EGG)
-            || (!strcmp(word, "invoke")
-                && !otmp->oartifact
-                && !objects[otyp].oc_unique
-                && (otyp != FAKE_AMULET_OF_YENDOR || otmp->known)
-                && otyp != CRYSTAL_BALL /* synonym for apply */
-                /* note: presenting the possibility of invoking non-artifact
-                   mirrors and/or lamps is simply a cruel deception... */
-                && otyp != MIRROR
-                && otyp != MAGIC_LAMP
-                && (otyp != OIL_LAMP /* don't list known oil lamp */
-                    || (otmp->dknown && objects[OIL_LAMP].oc_name_known)))
-            || (!strcmp(word, "untrap with")
-                && ((otmp->oclass == TOOL_CLASS && otyp != CAN_OF_GREASE)
-                    || (otmp->oclass == POTION_CLASS
-                        /* only applicable potion is oil, and it will only
-                           be offered as a choice when already discovered */
-                        && (otyp != POT_OIL || !otmp->dknown
-                            || !objects[POT_OIL].oc_name_known))))
-            || (!strcmp(word, "tip") && !Is_container(otmp)
-                /* include horn of plenty if sufficiently discovered */
-                && (otmp->otyp != HORN_OF_PLENTY || !otmp->dknown
-                    || !objects[HORN_OF_PLENTY].oc_name_known))
-            || (!strcmp(word, "charge") && !is_chargeable(otmp))
-            || (!strcmp(word, "open") && otyp != TIN)
-            || (!strcmp(word, "call") && !objtyp_is_callable(otyp)));
-}
-
-/* helper for getobj(), obj is acceptable but not listed */
-static boolean
-getobj_obj_acceptable_unlisted(word, otmp, let)
-const char *word;
-struct obj *otmp;
-char let;
-{
-    long dummymask;
-    short otyp = otmp->otyp;
-
-    return (/* ugly check for unworn armor that can't be worn */
-            (putting_on(word) && let == ARMOR_CLASS
-             && !canwearobj(otmp, &dummymask, FALSE))
-            /* or armor with 'P' or 'R' or accessory with 'W' or 'T' */
-            || ((putting_on(word) || taking_off(word))
-                && ((let == ARMOR_CLASS) ^ (otmp->oclass == ARMOR_CLASS)))
-            /* or unsuitable items rubbed on known touchstone */
-            || (!strncmp(word, "rub on the stone", 16)
-                && let == GEM_CLASS && otmp->dknown
-                && objects[otyp].oc_name_known)
-            /* suppress corpses on astral, amulets elsewhere */
-            || (!strcmp(word, "sacrifice")
-                /* (!astral && amulet) || (astral && !amulet) */
-                && (!Is_astralevel(&u.uz) ^ (otmp->oclass != AMULET_CLASS)))
-            /* suppress container being stashed into */
-            || (!strcmp(word, "stash") && !ck_bag(otmp))
-            /* worn armor (shirt, suit) covered by worn armor (suit, cloak)
-               or accessory (ring) covered by cursed worn armor (gloves) */
-            || (taking_off(word)
-                && inaccessible_equipment(otmp, (const char *) 0,
-                                      (boolean) (otmp->oclass == RING_CLASS)))
-            || (!strcmp(word, "write on")
-                && (!(otyp == SCR_BLANK_PAPER || otyp == SPE_BLANK_PAPER)
-                    || !otmp->dknown || !objects[otyp].oc_name_known)));
-}
-
 void
 mime_action(word)
 const char *word;
@@ -1614,68 +1478,57 @@ const char *word;
         suf ? suf : "");
 }
 
+/* getobj callback that allows any object - but not hands. */
+int
+any_obj_ok(obj)
+struct obj *obj;
+{
+    if (obj)
+        return GETOBJ_SUGGEST;
+    return GETOBJ_EXCLUDE;
+}
+
 /*
  * getobj returns:
  *      struct obj *xxx:        object to do something with.
  *      (struct obj *) 0        error return: no object.
  *      &cg.zeroobj                explicitly no object (as in w-).
-!!!! test if gold can be used in unusual ways (eaten etc.)
-!!!! may be able to remove "usegold"
+ * The obj_ok callback should not have side effects (apart from
+ * abnormal-behavior things like impossible calls); it can be called multiple
+ * times on the same object during the execution of this function.
+ * Callbacks' argument is either a valid object pointer or a null pointer, which
+ * represents the validity of doing that action on HANDS_SYM. getobj won't call
+ * it with &cg.zeroobj, so its behavior can be undefined in that case.
  */
 struct obj *
-getobj(let, word)
-register const char *let, *word;
+getobj(word, obj_ok, ctrlflags)
+register const char *word;
+int FDECL((*obj_ok), (OBJ_P)); /* callback */
+unsigned int ctrlflags;
 {
     register struct obj *otmp;
     register char ilet = 0;
     char buf[BUFSZ], qbuf[QBUFSZ];
-    char lets[BUFSZ], altlets[BUFSZ], *ap;
-    register int foo = 0;
-    register char *bp = buf;
-    xchar allowcnt = 0; /* 0, 1 or 2 */
-    boolean usegold = FALSE; /* can't use gold because its illegal */
-    boolean allowall = FALSE;
-    boolean allownone = FALSE;
-    boolean useboulder = FALSE;
-    xchar foox = 0;
+    char lets[BUFSZ], altlets[BUFSZ];
+    register int suggested = 0;
+    register char *bp = buf, *ap = altlets;
+    boolean allowcnt = (ctrlflags & GETOBJ_ALLOWCNT),
+            forceprompt = (ctrlflags & GETOBJ_PROMPT),
+            allownone = FALSE;
+    xchar inaccess = 0; /* counts GETOBJ_EXCLUDE_INACCESS items for a message
+                           tweak */
     long cnt;
     boolean cntgiven = FALSE;
     boolean msggiven = FALSE;
     boolean oneloop = FALSE;
     Loot *sortedinvent, *srtinv;
 
-    if (*let == ALLOW_COUNT)
-        let++, allowcnt = 1;
-    if (*let == COIN_CLASS)
-        let++, usegold = TRUE;
-
-    /* Equivalent of an "ugly check" for gold */
-    if (usegold && !strcmp(word, "eat")
-        && (!metallivorous(g.youmonst.data)
-            || g.youmonst.data == &mons[PM_RUST_MONSTER]))
-        usegold = FALSE;
-
-    if (*let == ALL_CLASSES)
-        let++, allowall = TRUE;
-    if (*let == ALLOW_NONE)
-        let++, allownone = TRUE;
-    /* "ugly check" for reading fortune cookies, part 1.
-     * The normal 'ugly check' keeps the object on the inventory list.
-     * We don't want to do that for shirts/cookies, so the check for
-     * them is handled a bit differently (and also requires that we set
-     * allowall in the caller).
-     */
-    if (allowall && !strcmp(word, "read"))
-        allowall = FALSE;
-
-    /* another ugly check: show boulders (not statues) */
-    if (*let == WEAPON_CLASS && !strcmp(word, "throw")
-        && throws_rocks(g.youmonst.data))
-        useboulder = TRUE;
-
-    if (allownone)
-        *bp++ = HANDS_SYM, *bp++ = ' '; /* '-' */
-    ap = altlets;
+    /* is "hands"/"self" a valid thing to do this action on? */
+    if ((*obj_ok)((struct obj *) 0) == GETOBJ_SUGGEST) {
+       allownone = TRUE;
+        *bp++ = HANDS_SYM;
+        *bp++ = ' '; /* put a space after the '-' in the prompt */
+    }
 
     if (!flags.invlet_constant)
         reassign();
@@ -1686,61 +1539,55 @@ register const char *let, *word;
                             (boolean FDECL((*), (OBJ_P))) 0);
 
     for (srtinv = sortedinvent; (otmp = srtinv->obj) != 0; ++srtinv) {
-        if (&bp[foo] == &buf[sizeof buf - 1]
+        if (&bp[suggested] == &buf[sizeof buf - 1]
             || ap == &altlets[sizeof altlets - 1]) {
-            /* we must have a huge number of NOINVSYM items somehow */
+            /* we must have a huge number of noinvsym items somehow */
             impossible("getobj: inventory overflow");
             break;
         }
 
-        if (!*let || index(let, otmp->oclass)
-            || (usegold && otmp->invlet == GOLD_SYM)
-            || (useboulder && otmp->otyp == BOULDER)) {
-            bp[foo++] = otmp->invlet;
-
-            /* remove inappropriate things */
-            if (getobj_obj_exclude(word, otmp)) {
-                foo--;
-                foox++;
-
+        bp[suggested++] = otmp->invlet;
+        switch ((*obj_ok)(otmp)) {
+        case GETOBJ_EXCLUDE_INACCESS:
+            /* remove inaccessible things */
+            suggested--;
+            inaccess++;
+            break;
+        case GETOBJ_EXCLUDE:
+        case GETOBJ_EXCLUDE_SELECTABLE:
             /* remove more inappropriate things, but unlike the first it won't
                trigger an "else" in "you don't have anything else to ___" */
-            } else if (getobj_obj_exclude_too(word, otmp)
-                       || (!strcmp(word, "adjust")
-                           && otmp->oclass == COIN_CLASS && !usegold)) {
-                foo--;
-
+            suggested--;
+            break;
+        case GETOBJ_DOWNPLAY:
             /* acceptable but not listed as likely candidates in the prompt
-               or in the inventory subset if player responds with '?' */
-            } else if (getobj_obj_acceptable_unlisted(word, otmp, *let)) {
-                foo--;
-                allowall = TRUE;
-                *ap++ = otmp->invlet;
-            }
-        } else {
-            /* "ugly check" for reading fortune cookies, part 2 */
-            if ((!strcmp(word, "read") && is_readable(otmp)))
-                allowall = usegold = TRUE;
+               or in the inventory subset if player responds with '?' - thus,
+               don't add it to lets with bp, but add it to altlets with ap */
+            suggested--;
+            forceprompt = TRUE;
+            *ap++ = otmp->invlet;
+            break;
+        case GETOBJ_SUGGEST:
+            break; /* adding otmp->invlet is all that's needed */
+        default:
+            impossible("bad return from getobj callback");
         }
     }
     unsortloot(&sortedinvent);
 
-    bp[foo] = '\0';
-    if (foo == 0 && bp > buf && bp[-1] == ' ')
+    bp[suggested] = '\0';
+    /* If no objects were suggested but we added '- ' at the beginning for
+     * hands, destroy the trailing space */
+    if (suggested == 0 && bp > buf && bp[-1] == ' ')
         *--bp = '\0';
     Strcpy(lets, bp); /* necessary since we destroy buf */
-    if (foo > 5)      /* compactify string */
+    if (suggested > 5)      /* compactify string */
         compactify(bp);
     *ap = '\0';
 
-    if (!foo && !allowall && !allownone) {
-        You("don't have anything %sto %s.", foox ? "else " : "", word);
+    if (suggested == 0 && !forceprompt && !allownone) {
+        You("don't have anything %sto %s.", inaccess ? "else " : "", word);
         return (struct obj *) 0;
-    } else if (!strcmp(word, "write on")) { /* ugly check for magic marker */
-        /* we wanted all scrolls and books in altlets[], but that came with
-           'allowall' which we don't want since it prevents "silly thing"
-           result if anything other than scroll or spellbook is chosen */
-        allowall = FALSE;
     }
     for (;;) {
         cnt = 0;
@@ -1751,7 +1598,7 @@ register const char *let, *word;
         else if (iflags.force_invmenu) {
             /* don't overwrite a possible quitchars */
             if (!oneloop)
-                ilet = *let ? '?' : '*';
+                ilet = forceprompt ? '*' : '?';
             if (!msggiven)
                 putmsghistory(qbuf, FALSE);
             msggiven = TRUE;
@@ -1840,7 +1687,7 @@ register const char *let, *word;
             /* guard against the [hypothetical] chace of having more
                than one invent slot of gold and picking the non-'$' one */
             || (otmp && otmp->oclass == COIN_CLASS)) {
-            if (!usegold) {
+            if (obj_ok(otmp) <= GETOBJ_EXCLUDE) {
                 You("cannot %s gold.", word);
                 return (struct obj *) 0;
             }
@@ -1887,8 +1734,7 @@ register const char *let, *word;
         }
         break;
     }
-    if (!allowall && let && !index(let, otmp->oclass)
-        && !(usegold && otmp->oclass == COIN_CLASS)) {
+    if (obj_ok(otmp) == GETOBJ_EXCLUDE) {
         silly_thing(word, otmp);
         return (struct obj *) 0;
     }
@@ -4111,6 +3957,42 @@ reassign()
     g.lastinvnr = i;
 }
 
+/* getobj callback for item to #adjust */
+int
+adjust_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* gold should never end up in a letter slot, nor should two '$' slots
+     * occur, but if they ever do, allow #adjust to handle them (in the
+     * past, things like this have happened, usually due to bknown being
+     * erroneously set on one stack, clear on another; object merger isn't
+     * fooled by that anymore) */
+    if (obj->oclass == COIN_CLASS) {
+        int goldstacks = 0;
+        struct obj *otmp;
+        if (obj->invlet != GOLD_SYM)
+            return GETOBJ_SUGGEST;
+        for (otmp = g.invent; otmp; otmp = otmp->nobj) {
+            if (otmp->oclass == COIN_CLASS) {
+                goldstacks++;
+            }
+        }
+
+        if (goldstacks > 1) {
+            impossible("getobj: multiple gold stacks in inventory");
+            return GETOBJ_SUGGEST;
+        }
+        /* assuming this impossible case doesn't happen, gold should be
+         * outright ignored as far as #adjust is concerned */
+        return GETOBJ_EXCLUDE;
+    }
+
+    return GETOBJ_SUGGEST;
+}
+
 /* #adjust command
  *
  *      User specifies a 'from' slot for inventory stack to move,
@@ -4156,14 +4038,13 @@ int
 doorganize() /* inventory organizer by Del Lamb */
 {
     struct obj *obj, *otmp, *splitting, *bumped;
-    int ix, cur, trycnt, goldstacks;
+    int ix, cur, trycnt;
     char let;
 #define GOLD_INDX   0
 #define GOLD_OFFSET 1
 #define OVRFLW_INDX (GOLD_OFFSET + 52) /* past gold and 2*26 letters */
     char lets[1 + 52 + 1 + 1]; /* room for '$a-zA-Z#\0' */
     char qbuf[QBUFSZ];
-    char allowall[4]; /* { ALLOW_COUNT, ALL_CLASSES, 0, 0 } */
     char *objname, *otmpname;
     const char *adj_type;
     boolean ever_mind = FALSE, collect;
@@ -4179,24 +4060,8 @@ doorganize() /* inventory organizer by Del Lamb */
     if (!flags.invlet_constant)
         reassign();
     /* get object the user wants to organize (the 'from' slot) */
-    allowall[0] = ALLOW_COUNT;
-    allowall[1] = ALL_CLASSES;
-    allowall[2] = '\0';
-    for (goldstacks = 0, otmp = g.invent; otmp; otmp = otmp->nobj) {
-        /* gold should never end up in a letter slot, nor should two '$'
-           slots occur, but if they ever do, allow #adjust to handle them
-           (in the past, things like this have happened, usually due to
-           bknown being erroneously set on one stack, clear on another;
-           object merger isn't fooled by that anymore) */
-        if (otmp->oclass == COIN_CLASS
-            && (otmp->invlet != GOLD_SYM || ++goldstacks > 1)) {
-            allowall[1] = COIN_CLASS;
-            allowall[2] = ALL_CLASSES;
-            allowall[3] = '\0';
-            break;
-        }
-    }
-    if (!(obj = getobj(allowall, "adjust")))
+    obj = getobj("adjust", adjust_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
+    if (!obj)
         return 0;
 
     /* figure out whether user gave a split count to getobj() */
index 4b5e715cf9ca503a79cb4c0aa848b9a6286bd8ea..bbfec3db7d619beb7ff4d0ff44074715cce29ed1 100644 (file)
@@ -37,9 +37,11 @@ static long FDECL(boh_loss, (struct obj *, int));
 static int FDECL(in_container, (struct obj *));
 static int FDECL(out_container, (struct obj *));
 static long FDECL(mbag_item_gone, (int, struct obj *, BOOLEAN_P));
+static int FDECL(stash_ok, (struct obj *));
 static void FDECL(explain_container_prompt, (BOOLEAN_P));
 static int FDECL(traditional_loot, (BOOLEAN_P));
 static int FDECL(menu_loot, (int, BOOLEAN_P));
+static int FDECL(tip_ok, (struct obj *));
 static char FDECL(in_or_out_menu, (const char *, struct obj *, BOOLEAN_P,
                                        BOOLEAN_P, BOOLEAN_P, BOOLEAN_P));
 static boolean FDECL(able_to_loot, (int, int, BOOLEAN_P));
@@ -2667,7 +2669,22 @@ u_handsy()
     return TRUE;
 }
 
-static const char stashable[] = { ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, 0 };
+/* getobj callback for object to be stashed into a container */
+int
+stash_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    /* downplay the container being stashed into */
+    if (!ck_bag(obj))
+        return GETOBJ_EXCLUDE_SELECTABLE;
+    /* Possible extension: downplay things too big to fit into containers (in
+     * which case extract in_container()'s logic.) */
+
+    return GETOBJ_SUGGEST;
+}
 
 int
 use_container(objp, held, more_containers)
@@ -2863,7 +2880,8 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */
         add_valid_menu_class(0);
     } else if (stash_one) {
         /* put one item into container */
-        if ((otmp = getobj(stashable, "stash")) != 0) {
+        if ((otmp = getobj("stash", stash_ok,
+                           GETOBJ_PROMPT | GETOBJ_ALLOWCNT)) != 0) {
             if (in_container(otmp)) {
                 used = 1;
             } else {
@@ -3122,7 +3140,26 @@ boolean outokay, inokay, alreadyused, more_containers;
     return (n == 0 && more_containers) ? 'n' : 'q'; /* next or quit */
 }
 
-static const char tippables[] = { ALL_CLASSES, TOOL_CLASS, 0 };
+/* getobj callback for object to tip */
+static int
+tip_ok(obj)
+struct obj *obj;
+{
+    if (!obj || obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    if (Is_container(obj)) {
+        return GETOBJ_SUGGEST;
+    }
+
+    /* include horn of plenty if sufficiently discovered */
+    if (obj->otyp == HORN_OF_PLENTY && obj->dknown &&
+        objects[obj->otyp].oc_name_known)
+        return GETOBJ_SUGGEST;
+
+    /* allow trying anything else in inventory */
+    return GETOBJ_DOWNPLAY;
+}
 
 /* #tip command -- empty container contents onto floor */
 int
@@ -3222,7 +3259,7 @@ dotip()
     }
 
     /* either no floor container(s) or couldn't tip one or didn't tip any */
-    cobj = getobj(tippables, "tip");
+    cobj = getobj("tip", tip_ok, GETOBJ_PROMPT);
     if (!cobj)
         return 0;
 
index 4d3265c0da845b982d90468eb4f8147b6af87864..d46d4da329b04388f672715fa2761feed68715a3 100644 (file)
@@ -5,14 +5,14 @@
 
 #include "hack.h"
 
-static NEARDATA const char beverages[] = { POTION_CLASS, 0 };
-
 static long FDECL(itimeout, (long));
 static long FDECL(itimeout_incr, (long, int));
 static void NDECL(ghost_from_bottle);
+static int FDECL(drink_ok, (struct obj *));
 static boolean
 FDECL(H2Opotion_dip, (struct obj *, struct obj *, BOOLEAN_P, const char *));
 static short FDECL(mixtype, (struct obj *, struct obj *));
+static int FDECL(dip_ok, (struct obj *));
 
 /* force `val' to be within valid range for intrinsic timeout value */
 static long
@@ -481,6 +481,18 @@ ghost_from_bottle()
     g.nomovemsg = "You regain your composure.";
 }
 
+/* getobj callback for object to drink from, which also does double duty as the
+ * callback for dipping into (both just allow potions). */
+static int
+drink_ok(obj)
+struct obj *obj;
+{
+    if (obj && obj->oclass == POTION_CLASS)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_EXCLUDE;
+}
+
 /* "Quaffing is like drinking, except you spill more." - Terry Pratchett */
 int
 dodrink()
@@ -517,7 +529,7 @@ dodrink()
         }
     }
 
-    otmp = getobj(beverages, "drink");
+    otmp = getobj("drink", drink_ok, GETOBJ_NOFLAGS);
     if (!otmp)
         return 0;
 
@@ -1888,6 +1900,22 @@ register struct obj *o1, *o2;
     return STRANGE_OBJECT;
 }
 
+/* getobj callback for object to be dipped (not the thing being dipped into,
+ * that uses drink_ok) */
+static int
+dip_ok(obj)
+struct obj *obj;
+{
+    /* dipping hands and gold isn't currently implemented */
+    if (!obj || obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    if (inaccessible_equipment(obj, (const char *) 0, FALSE))
+        return GETOBJ_EXCLUDE_INACCESS;
+
+    return GETOBJ_SUGGEST;
+}
+
 /* #dip command */
 int
 dodip()
@@ -1896,14 +1924,11 @@ dodip()
     register struct obj *potion, *obj;
     struct obj *singlepotion;
     uchar here;
-    char allowall[2];
     short mixture;
     char qbuf[QBUFSZ], obuf[QBUFSZ];
     const char *shortestname; /* last resort obj name for prompt */
 
-    allowall[0] = ALL_CLASSES;
-    allowall[1] = '\0';
-    if (!(obj = getobj(allowall, "dip")))
+    if (!(obj = getobj("dip", dip_ok, GETOBJ_PROMPT)))
         return 0;
     if (inaccessible_equipment(obj, "dip", FALSE))
         return 0;
@@ -1957,7 +1982,7 @@ dodip()
 
     /* "What do you want to dip <the object> into? [xyz or ?*] " */
     Sprintf(qbuf, "dip %s into", flags.verbose ? obuf : shortestname);
-    potion = getobj(beverages, qbuf);
+    potion = getobj(qbuf, drink_ok, GETOBJ_NOFLAGS);
     if (!potion)
         return 0;
     if (potion == obj && potion->quan == 1L) {
index 130259ee3d51600c4bcb9cc733610b9b8f7d2f8b..ba6fa214f13e3935d6e97274ad0a389f643fafe2 100644 (file)
     ((mndx) == g.urace.malenum \
      || (g.urace.femalenum != NON_PM && (mndx) == g.urace.femalenum))
 
-static NEARDATA const char readable[] = { ALL_CLASSES, SCROLL_CLASS,
-                                          SPBOOK_CLASS, 0 };
-static const char all_count[] = { ALLOW_COUNT, ALL_CLASSES, 0 };
-
 static boolean FDECL(learnscrolltyp, (SHORT_P));
 static void FDECL(cap_spe, (struct obj *));
 static char *FDECL(erode_obj_text, (struct obj *, char *));
+static int FDECL(read_ok, (struct obj *));
 static void FDECL(stripspe, (struct obj *));
 static void FDECL(p_glow1, (struct obj *));
 static void FDECL(p_glow2, (struct obj *, const char *));
@@ -228,6 +225,20 @@ struct obj *obj;
     return;
 }
 
+/* getobj callback for object to read */
+static int
+read_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
+
 /* the 'r' command; read a scroll or spell book or various other things */
 int
 doread()
@@ -258,7 +269,7 @@ doread()
     if (check_capacity((char *) 0))
         return 0;
 
-    scroll = getobj(readable, "read");
+    scroll = getobj("read", read_ok, GETOBJ_PROMPT);
     if (!scroll)
         return 0;
     otyp = scroll->otyp;
@@ -534,33 +545,38 @@ register const char *color;
           Blind ? "" : " ", Blind ? "" : hcolor(color));
 }
 
-/* Is the object chargeable?  For purposes of inventory display; it is
-   possible to be able to charge things for which this returns FALSE. */
-boolean
-is_chargeable(obj)
+/* getobj callback for object to charge */
+int
+charge_ok(obj)
 struct obj *obj;
 {
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
     if (obj->oclass == WAND_CLASS)
-        return TRUE;
-    /* known && !oc_name_known is possible after amnesia/mind flayer */
-    if (obj->oclass == RING_CLASS)
-        return (boolean) (objects[obj->otyp].oc_charged
-                          && (obj->known
-                              || (obj->dknown
-                                  && objects[obj->otyp].oc_name_known)));
+        return GETOBJ_SUGGEST;
+
+    if (obj->oclass == RING_CLASS && objects[obj->otyp].oc_charged
+        && obj->dknown && objects[obj->otyp].oc_name_known)
+        return GETOBJ_SUGGEST;
+
     if (is_weptool(obj)) /* specific check before general tools */
-        return FALSE;
+        return GETOBJ_EXCLUDE;
+
     if (obj->oclass == TOOL_CLASS) {
+        /* suggest tools that aren't oc_charged but can still be recharged */
         if (obj->otyp == BRASS_LANTERN
             || (obj->otyp == OIL_LAMP)
             /* only list magic lamps if they are not identified yet */
             || (obj->otyp == MAGIC_LAMP
                 && !objects[MAGIC_LAMP].oc_name_known)) {
-            return TRUE;
+            return GETOBJ_SUGGEST;
         }
-        return (boolean) objects[obj->otyp].oc_charged;
+        return objects[obj->otyp].oc_charged ? GETOBJ_SUGGEST : GETOBJ_EXCLUDE;
     }
-    return FALSE; /* why are weapons/armor considered charged anyway? */
+    /* why are weapons/armor considered charged anyway?
+     * make them selectable even so for "feeling of loss" message */
+    return GETOBJ_EXCLUDE_SELECTABLE;
 }
 
 /* recharge an object; curse_bless is -1 if the recharging implement
@@ -1541,7 +1557,7 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */
            was already delivered */
         useup(sobj);
         sobj = 0; /* it's gone */
-        otmp = getobj(all_count, "charge");
+        otmp = getobj("charge", charge_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
         if (otmp)
             recharge(otmp, scursed ? -1 : sblessed ? 1 : 0);
         break;
index d922049db2ea1fae6c19d87e1445a6156bdf6079..00d4140adb15bc5875d430189cc59c483b424bf8 100644 (file)
@@ -49,6 +49,7 @@ static int FDECL(try_disarm, (struct trap *, BOOLEAN_P));
 static void FDECL(reward_untrap, (struct trap *, struct monst *));
 static int FDECL(disarm_holdingtrap, (struct trap *));
 static int FDECL(disarm_landmine, (struct trap *));
+static int FDECL(unsqueak_ok, (struct obj *));
 static int FDECL(disarm_squeaky_board, (struct trap *));
 static int FDECL(disarm_shooting_trap, (struct trap *, int));
 static void FDECL(clear_conjoined_pits, (struct trap *));
@@ -4574,9 +4575,29 @@ struct trap *ttmp;
     return 1;
 }
 
-/* getobj will filter down to cans of grease and known potions of oil */
-static NEARDATA const char oil[] = { ALL_CLASSES, TOOL_CLASS, POTION_CLASS,
-                                     0 };
+/* getobj callback for object to disarm a squeaky board with */
+static int
+unsqueak_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->otyp == CAN_OF_GREASE)
+        return GETOBJ_SUGGEST;
+
+    if (obj->otyp == POT_OIL && obj->dknown &&
+        objects[POT_OIL].oc_name_known)
+        return GETOBJ_SUGGEST;
+
+    /* downplay all other potions, including unidentified oil
+     * Potential extension: if oil is known, skip this and exclude all other
+     * potions. */
+    if (obj->oclass == POTION_CLASS)
+        return GETOBJ_DOWNPLAY;
+
+    return GETOBJ_EXCLUDE;
+}
 
 /* it may not make much sense to use grease on floor boards, but so what? */
 static int
@@ -4587,7 +4608,7 @@ struct trap *ttmp;
     boolean bad_tool;
     int fails;
 
-    obj = getobj(oil, "untrap with");
+    obj = getobj("untrap with", unsqueak_ok, GETOBJ_PROMPT);
     if (!obj)
         return 0;
 
index b5f2dbac8ee60ebb2988fe370cf450f63263d770..a296975babdaa04f5bb1148e7a44a44d73e51bf7 100644 (file)
@@ -54,6 +54,8 @@
 
 static boolean FDECL(cant_wield_corpse, (struct obj *));
 static int FDECL(ready_weapon, (struct obj *));
+static int FDECL(ready_ok, (struct obj *));
+static int FDECL(wield_ok, (struct obj *));
 
 /* used by will_weld() */
 /* probably should be renamed */
@@ -267,18 +269,51 @@ register struct obj *obj;
     return;
 }
 
-/*** Commands to change particular slot(s) ***/
+/* getobj callback for object to ready for throwing/shooting */
+static int
+ready_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_SUGGEST;
+
+    /* exclude when wielded... */
+    if ((obj == uwep || (obj == uswapwep && u.twoweap))
+        && obj->quan == 1) /* ...unless more than one */
+        return GETOBJ_EXCLUDE_INACCESS;
+
+    if (obj->oclass == WEAPON_CLASS || obj->oclass == COIN_CLASS)
+        return GETOBJ_SUGGEST;
+    /* Possible extension: exclude weapons that make no sense to throw, such as
+     * whips, bows, slings, rubber hoses. */
+
+    /* Include gems/stones as likely candidates if either primary
+       or secondary weapon is a sling. */
+    if (obj->oclass == GEM_CLASS
+        && (uslinging()
+            || (uswapwep && objects[uswapwep->otyp].oc_skill == P_SLING)))
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
 
-static NEARDATA const char wield_objs[] = {
-    ALLOW_COUNT, ALL_CLASSES, ALLOW_NONE, WEAPON_CLASS, TOOL_CLASS, 0
-};
-static NEARDATA const char ready_objs[] = {
-    ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, ALLOW_NONE, WEAPON_CLASS, 0
-};
-static NEARDATA const char w_bullets[] = { /* (different from dothrow.c) */
-    ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, ALLOW_NONE,
-    GEM_CLASS, WEAPON_CLASS, 0
-};
+/* getobj callback for object to wield */
+static int
+wield_ok(obj)
+struct obj *obj;
+{
+    if (!obj)
+        return GETOBJ_SUGGEST;
+
+    if (obj->oclass == COIN_CLASS)
+        return GETOBJ_EXCLUDE;
+
+    if (obj->oclass == WEAPON_CLASS
+        || (obj->oclass == TOOL_CLASS && is_weptool(obj)))
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
 
 int
 dowield()
@@ -297,7 +332,7 @@ dowield()
 
     /* Prompt for a new weapon */
     clear_splitobjs();
-    if (!(wep = getobj(wield_objs, "wield"))) {
+    if (!(wep = getobj("wield", wield_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT))) {
         /* Cancelled */
         return 0;
     } else if (wep == uwep) {
@@ -438,7 +473,6 @@ dowieldquiver()
 {
     char qbuf[QBUFSZ];
     struct obj *newquiver;
-    const char *quivee_types;
     int res;
     boolean finish_splitting = FALSE,
             was_uwep = FALSE, was_twoweap = u.twoweap;
@@ -446,18 +480,11 @@ dowieldquiver()
     /* Since the quiver isn't in your hands, don't check cantwield(), */
     /* will_weld(), touch_petrifies(), etc. */
     g.multi = 0;
-    /* forget last splitobj() before calling getobj() with ALLOW_COUNT */
+    /* forget last splitobj() before calling getobj() with GETOBJ_ALLOWCNT */
     clear_splitobjs();
 
-    /* Prompt for a new quiver: "What do you want to ready?"
-       (Include gems/stones as likely candidates if either primary
-       or secondary weapon is a sling.) */
-    quivee_types = (uslinging()
-                    || (uswapwep
-                        && objects[uswapwep->otyp].oc_skill == P_SLING))
-                   ? w_bullets
-                   : ready_objs;
-    newquiver = getobj(quivee_types, "ready");
+    /* Prompt for a new quiver: "What do you want to ready?" */
+    newquiver = getobj("ready", ready_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
 
     if (!newquiver) {
         /* Cancelled */
index 3267138861d8c0a1edd77741311102f2155b1290..fb0d28a6ad1b5c35504e15dcad43aadf2eaeb252 100644 (file)
@@ -5,6 +5,7 @@
 
 static int FDECL(cost, (struct obj *));
 static boolean FDECL(label_known, (int, struct obj *));
+static int FDECL(write_ok, (struct obj *));
 static char *FDECL(new_book_description, (int, char *));
 
 /*
@@ -87,7 +88,19 @@ struct obj *objlist;
     return FALSE;
 }
 
-static NEARDATA const char write_on[] = { SCROLL_CLASS, SPBOOK_CLASS, 0 };
+/* getobj callback for object to write on */
+static int
+write_ok(obj)
+struct obj *obj;
+{
+    if (!obj || (obj->oclass != SCROLL_CLASS && obj->oclass != SPBOOK_CLASS))
+        return GETOBJ_EXCLUDE;
+
+    if (obj->otyp == SCR_BLANK_PAPER || obj->otyp == SPE_BLANK_PAPER)
+        return GETOBJ_SUGGEST;
+
+    return GETOBJ_DOWNPLAY;
+}
 
 /* write -- applying a magic marker */
 int
@@ -115,7 +128,7 @@ register struct obj *pen;
     }
 
     /* get paper to write on */
-    paper = getobj(write_on, "write on");
+    paper = getobj("write on", write_ok, GETOBJ_NOFLAGS);
     if (!paper)
         return 0;
     /* can't write on a novel (unless/until it's been converted into a blank
index 04ebb818167d2a3ee13d858016033410d9edc7f7..de51db503e5aa733c1bb2f9e2f554d5087b3b93a 100644 (file)
--- a/src/zap.c
+++ b/src/zap.c
@@ -23,6 +23,7 @@ static void FDECL(skiprange, (int, int *, int *));
 static int FDECL(zap_hit, (int, int));
 static void FDECL(disintegrate_mon, (struct monst *, int, const char *));
 static void FDECL(backfire, (struct obj *));
+static int FDECL(zap_ok, (struct obj *));
 static void FDECL(boxlock_invent, (struct obj *));
 static int FDECL(spell_hit_bonus, (int));
 static void FDECL(destroy_one_item, (struct obj *, int, int));
@@ -2302,7 +2303,15 @@ struct obj *otmp;
     useupall(otmp);
 }
 
-static NEARDATA const char zap_syms[] = { WAND_CLASS, 0 };
+/* getobj callback for object to zap */
+static int
+zap_ok(obj)
+struct obj *obj;
+{
+    if (obj && obj->oclass == WAND_CLASS)
+        return GETOBJ_SUGGEST;
+    return GETOBJ_EXCLUDE;
+}
 
 /* 'z' command (or 'y' if numbed_pad==-1) */
 int
@@ -2317,7 +2326,7 @@ dozap()
     }
     if (check_capacity((char *) 0))
         return 0;
-    obj = getobj(zap_syms, "zap");
+    obj = getobj("zap", zap_ok, GETOBJ_NOFLAGS);
     if (!obj)
         return 0;