]> granicus.if.org Git - nethack/commitdiff
Split scroll effects into separate functions
authorPasi Kallinen <paxed@alt.org>
Wed, 7 Jul 2021 09:02:59 +0000 (12:02 +0300)
committerPasi Kallinen <paxed@alt.org>
Wed, 7 Jul 2021 09:02:59 +0000 (12:02 +0300)
src/read.c

index 7e1ebdf4f41a517304166166033b601d23c720f4..518537163e2f5b00ccb66b2b040da2dd1a01d5f8 100644 (file)
@@ -24,6 +24,31 @@ static int maybe_tame(struct monst *, struct obj *);
 static boolean get_valid_stinking_cloud_pos(int, int);
 static boolean is_valid_stinking_cloud_pos(int, int, boolean);
 static void display_stinking_cloud_positions(int);
+static void seffect_enchant_armor(struct obj **);
+static void seffect_destroy_armor(struct obj **);
+static void seffect_confuse_monster(struct obj **);
+static void seffect_scare_monster(struct obj **);
+static void seffect_remove_curse(struct obj **);
+static void seffect_create_monster(struct obj **);
+static void seffect_enchant_weapon(struct obj **);
+static void seffect_taming(struct obj **);
+static void seffect_genocide(struct obj **);
+static void seffect_light(struct obj **);
+static void seffect_charging(struct obj **);
+static void seffect_amnesia(struct obj **);
+static void seffect_fire(struct obj **);
+static void seffect_earth(struct obj **);
+static void seffect_punishment(struct obj **);
+static void seffect_stinking_cloud(struct obj **);
+static void seffect_blank_paper(struct obj **);
+static void seffect_teleportation(struct obj **);
+static void seffect_gold_detection(struct obj **);
+static void seffect_food_detection(struct obj **);
+static void seffect_identify(struct obj **);
+static void seffect_magic_mapping(struct obj **);
+#ifdef MAIL_STRUCTURES
+static void seffect_mail(struct obj **);
+#endif /* MAIL_STRUCTURES */
 static void set_lit(int, int, genericptr);
 static void do_class_genocide(void);
 static boolean create_particular_parse(char *,
@@ -932,574 +957,843 @@ display_stinking_cloud_positions(int state)
     }
 }
 
-/* scroll effects; return 1 if we use up the scroll and possibly make it
-   become discovered, 0 if caller should take care of those side-effects */
-int
-seffects(struct obj *sobj) /* sobj - scroll or fake spellbook for spell */
+static void
+seffect_enchant_armor(struct obj **sobjp)
 {
-    int cval, otyp = sobj->otyp;
-    boolean confused = (Confusion != 0), sblessed = sobj->blessed,
-            scursed = sobj->cursed, already_known, old_erodeproof,
-            new_erodeproof;
-    struct obj *otmp;
-
-    if (objects[otyp].oc_magic)
-        exercise(A_WIS, TRUE);                       /* just for trying */
-    already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
-                     || objects[otyp].oc_name_known);
+    struct obj *sobj = *sobjp;
+    register schar s;
+    boolean special_armor;
+    boolean same_color;
+    struct obj *otmp = some_armor(&g.youmonst);
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    boolean old_erodeproof, new_erodeproof;
+
+    if (!otmp) {
+        strange_feeling(sobj, !Blind
+                        ? "Your skin glows then fades."
+                        : "Your skin feels warm for a moment.");
+        *sobjp = 0; /* useup() in strange_feeling() */
+        exercise(A_CON, !scursed);
+        exercise(A_STR, !scursed);
+        return;
+    }
+    if (confused) {
+        old_erodeproof = (otmp->oerodeproof != 0);
+        new_erodeproof = !scursed;
+        otmp->oerodeproof = 0; /* for messages */
+        if (Blind) {
+            otmp->rknown = FALSE;
+            pline("%s warm for a moment.", Yobjnam2(otmp, "feel"));
+        } else {
+            otmp->rknown = TRUE;
+            pline("%s covered by a %s %s %s!", Yobjnam2(otmp, "are"),
+                  scursed ? "mottled" : "shimmering",
+                  hcolor(scursed ? NH_BLACK : NH_GOLDEN),
+                  scursed ? "glow"
+                  : (is_shield(otmp) ? "layer" : "shield"));
+        }
+        if (new_erodeproof && (otmp->oeroded || otmp->oeroded2)) {
+            otmp->oeroded = otmp->oeroded2 = 0;
+            pline("%s as good as new!",
+                  Yobjnam2(otmp, Blind ? "feel" : "look"));
+        }
+        if (old_erodeproof && !new_erodeproof) {
+            /* restore old_erodeproof before shop charges */
+            otmp->oerodeproof = 1;
+            costly_alteration(otmp, COST_DEGRD);
+        }
+        otmp->oerodeproof = new_erodeproof ? 1 : 0;
+        return;
+    }
+    /* elven armor vibrates warningly when enchanted beyond a limit */
+    special_armor = is_elven_armor(otmp)
+        || (Role_if(PM_WIZARD) && otmp->otyp == CORNUTHAUM);
+    if (scursed)
+        same_color = (otmp->otyp == BLACK_DRAGON_SCALE_MAIL
+                      || otmp->otyp == BLACK_DRAGON_SCALES);
+    else
+        same_color = (otmp->otyp == SILVER_DRAGON_SCALE_MAIL
+                      || otmp->otyp == SILVER_DRAGON_SCALES
+                      || otmp->otyp == SHIELD_OF_REFLECTION);
+    if (Blind)
+        same_color = FALSE;
+
+    /* KMH -- catch underflow */
+    s = scursed ? -otmp->spe : otmp->spe;
+    if (s > (special_armor ? 5 : 3) && rn2(s)) {
+        otmp->in_use = TRUE;
+        pline("%s violently %s%s%s for a while, then %s.", Yname2(otmp),
+              otense(otmp, Blind ? "vibrate" : "glow"),
+              (!Blind && !same_color) ? " " : "",
+              (Blind || same_color) ? "" : hcolor(scursed ? NH_BLACK
+                                                  : NH_SILVER),
+              otense(otmp, "evaporate"));
+        remove_worn_item(otmp, FALSE);
+        useup(otmp);
+        return;
+    }
+    s = scursed ? -1
+        : (otmp->spe >= 9)
+        ? (rn2(otmp->spe) == 0)
+        : sblessed
+        ? rnd(3 - otmp->spe / 3)
+        : 1;
+    if (s >= 0 && Is_dragon_scales(otmp)) {
+        /* dragon scales get turned into dragon scale mail */
+        pline("%s merges and hardens!", Yname2(otmp));
+        setworn((struct obj *) 0, W_ARM);
+        /* assumes same order */
+        otmp->otyp += GRAY_DRAGON_SCALE_MAIL - GRAY_DRAGON_SCALES;
+        if (sblessed) {
+            otmp->spe++;
+            cap_spe(otmp);
+            if (!otmp->blessed)
+                bless(otmp);
+        } else if (otmp->cursed)
+            uncurse(otmp);
+        otmp->known = 1;
+        setworn(otmp, W_ARM);
+        if (otmp->unpaid)
+            alter_cost(otmp, 0L); /* shop bill */
+        return;
+    }
+    pline("%s %s%s%s%s for a %s.", Yname2(otmp),
+          (s == 0) ? "violently " : "",
+          otense(otmp, Blind ? "vibrate" : "glow"),
+          (!Blind && !same_color) ? " " : "",
+          (Blind || same_color)
+          ? "" : hcolor(scursed ? NH_BLACK : NH_SILVER),
+          (s * s > 1) ? "while" : "moment");
+    /* [this cost handling will need updating if shop pricing is
+       ever changed to care about curse/bless status of armor] */
+    if (s < 0)
+        costly_alteration(otmp, COST_DECHNT);
+    if (scursed && !otmp->cursed)
+        curse(otmp);
+    else if (sblessed && !otmp->blessed)
+        bless(otmp);
+    else if (!scursed && otmp->cursed)
+        uncurse(otmp);
+    if (s) {
+        int oldspe = otmp->spe;
+        /* despite being schar, it shouldn't be possible for spe to wrap
+           here because it has been capped at 99 and s is quite small;
+           however, might need to change s if it takes spe past 99 */
+        otmp->spe += s;
+        cap_spe(otmp); /* make sure that it doesn't exceed SPE_LIM */
+        s = otmp->spe - oldspe; /* cap_spe() might have throttled 's' */
+        if (s) /* skip if it got changed to 0 */
+            adj_abon(otmp, s); /* adjust armor bonus for Dex or Int+Wis */
+        g.known = otmp->known;
+        /* update shop bill to reflect new higher price */
+        if (s > 0 && otmp->unpaid)
+            alter_cost(otmp, 0L);
+    }
 
-    switch (otyp) {
-#ifdef MAIL_STRUCTURES
-    case SCR_MAIL: {
-        boolean odd = (sobj->o_id % 2) == 1;
+    if ((otmp->spe > (special_armor ? 5 : 3))
+        && (special_armor || !rn2(7)))
+        pline("%s %s.", Yobjnam2(otmp, "suddenly vibrate"),
+              Blind ? "again" : "unexpectedly");
+}
 
-        g.known = TRUE;
-        switch (sobj->spe) {
-        case 2:
-            /* "stamped scroll" created via magic marker--without a stamp */
-            pline("This scroll is marked \"%s\".",
-                  odd ? "Postage Due" : "Return to Sender");
-            break;
-        case 1:
-            /* scroll of mail obtained from bones file or from wishing;
-               note to the puzzled: the game Larn actually sends you junk
-               mail if you win! */
-            pline("This seems to be %s.",
-                  odd ? "a chain letter threatening your luck"
-                  : "junk mail addressed to the finder of the Eye of Larn");
-            break;
-        default:
-#ifdef MAIL
-            readmail(sobj);
-#else
-            /* unreachable since with MAIL undefined, sobj->spe won't be 0;
-               as a precaution, be prepared to give arbitrary feedback;
-               caller has already reported that it disappears upon reading */
-            pline("That was a scroll of mail?");
-#endif
-            break;
-        }
-        break;
-    }
-#endif
-    case SCR_ENCHANT_ARMOR: {
-        register schar s;
-        boolean special_armor;
-        boolean same_color;
+static void
+seffect_destroy_armor(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    struct obj *otmp = some_armor(&g.youmonst);
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    boolean old_erodeproof, new_erodeproof;
 
-        otmp = some_armor(&g.youmonst);
+    if (confused) {
         if (!otmp) {
-            strange_feeling(sobj, !Blind
-                                      ? "Your skin glows then fades."
-                                      : "Your skin feels warm for a moment.");
-            sobj = 0; /* useup() in strange_feeling() */
-            exercise(A_CON, !scursed);
-            exercise(A_STR, !scursed);
-            break;
-        }
-        if (confused) {
-            old_erodeproof = (otmp->oerodeproof != 0);
-            new_erodeproof = !scursed;
-            otmp->oerodeproof = 0; /* for messages */
-            if (Blind) {
-                otmp->rknown = FALSE;
-                pline("%s warm for a moment.", Yobjnam2(otmp, "feel"));
-            } else {
-                otmp->rknown = TRUE;
-                pline("%s covered by a %s %s %s!", Yobjnam2(otmp, "are"),
-                      scursed ? "mottled" : "shimmering",
-                      hcolor(scursed ? NH_BLACK : NH_GOLDEN),
-                      scursed ? "glow"
-                              : (is_shield(otmp) ? "layer" : "shield"));
-            }
-            if (new_erodeproof && (otmp->oeroded || otmp->oeroded2)) {
-                otmp->oeroded = otmp->oeroded2 = 0;
-                pline("%s as good as new!",
-                      Yobjnam2(otmp, Blind ? "feel" : "look"));
-            }
-            if (old_erodeproof && !new_erodeproof) {
-                /* restore old_erodeproof before shop charges */
-                otmp->oerodeproof = 1;
-                costly_alteration(otmp, COST_DEGRD);
-            }
-            otmp->oerodeproof = new_erodeproof ? 1 : 0;
-            break;
-        }
-        /* elven armor vibrates warningly when enchanted beyond a limit */
-        special_armor = is_elven_armor(otmp)
-                        || (Role_if(PM_WIZARD) && otmp->otyp == CORNUTHAUM);
-        if (scursed)
-            same_color = (otmp->otyp == BLACK_DRAGON_SCALE_MAIL
-                          || otmp->otyp == BLACK_DRAGON_SCALES);
-        else
-            same_color = (otmp->otyp == SILVER_DRAGON_SCALE_MAIL
-                          || otmp->otyp == SILVER_DRAGON_SCALES
-                          || otmp->otyp == SHIELD_OF_REFLECTION);
-        if (Blind)
-            same_color = FALSE;
-
-        /* KMH -- catch underflow */
-        s = scursed ? -otmp->spe : otmp->spe;
-        if (s > (special_armor ? 5 : 3) && rn2(s)) {
-            otmp->in_use = TRUE;
-            pline("%s violently %s%s%s for a while, then %s.", Yname2(otmp),
-                  otense(otmp, Blind ? "vibrate" : "glow"),
-                  (!Blind && !same_color) ? " " : "",
-                  (Blind || same_color) ? "" : hcolor(scursed ? NH_BLACK
-                                                              : NH_SILVER),
-                  otense(otmp, "evaporate"));
-            remove_worn_item(otmp, FALSE);
-            useup(otmp);
-            break;
-        }
-        s = scursed ? -1
-                    : (otmp->spe >= 9)
-                       ? (rn2(otmp->spe) == 0)
-                       : sblessed
-                          ? rnd(3 - otmp->spe / 3)
-                          : 1;
-        if (s >= 0 && Is_dragon_scales(otmp)) {
-            /* dragon scales get turned into dragon scale mail */
-            pline("%s merges and hardens!", Yname2(otmp));
-            setworn((struct obj *) 0, W_ARM);
-            /* assumes same order */
-            otmp->otyp += GRAY_DRAGON_SCALE_MAIL - GRAY_DRAGON_SCALES;
-            if (sblessed) {
-                otmp->spe++;
-                cap_spe(otmp);
-                if (!otmp->blessed)
-                    bless(otmp);
-            } else if (otmp->cursed)
-                uncurse(otmp);
-            otmp->known = 1;
-            setworn(otmp, W_ARM);
-            if (otmp->unpaid)
-                alter_cost(otmp, 0L); /* shop bill */
-            break;
+            strange_feeling(sobj, "Your bones itch.");
+            *sobjp = 0; /* useup() in strange_feeling() */
+            exercise(A_STR, FALSE);
+            exercise(A_CON, FALSE);
+            return;
         }
-        pline("%s %s%s%s%s for a %s.", Yname2(otmp),
-              (s == 0) ? "violently " : "",
-              otense(otmp, Blind ? "vibrate" : "glow"),
-              (!Blind && !same_color) ? " " : "",
-              (Blind || same_color)
-                 ? "" : hcolor(scursed ? NH_BLACK : NH_SILVER),
-              (s * s > 1) ? "while" : "moment");
-        /* [this cost handling will need updating if shop pricing is
-           ever changed to care about curse/bless status of armor] */
-        if (s < 0)
-            costly_alteration(otmp, COST_DECHNT);
-        if (scursed && !otmp->cursed)
-            curse(otmp);
-        else if (sblessed && !otmp->blessed)
-            bless(otmp);
-        else if (!scursed && otmp->cursed)
-            uncurse(otmp);
-        if (s) {
-            int oldspe = otmp->spe;
-            /* despite being schar, it shouldn't be possible for spe to wrap
-               here because it has been capped at 99 and s is quite small;
-               however, might need to change s if it takes spe past 99 */
-            otmp->spe += s;
-            cap_spe(otmp); /* make sure that it doesn't exceed SPE_LIM */
-            s = otmp->spe - oldspe; /* cap_spe() might have throttled 's' */
-            if (s) /* skip if it got changed to 0 */
-                adj_abon(otmp, s); /* adjust armor bonus for Dex or Int+Wis */
-            g.known = otmp->known;
-            /* update shop bill to reflect new higher price */
-            if (s > 0 && otmp->unpaid)
-                alter_cost(otmp, 0L);
+        old_erodeproof = (otmp->oerodeproof != 0);
+        new_erodeproof = scursed;
+        otmp->oerodeproof = 0; /* for messages */
+        p_glow2(otmp, NH_PURPLE);
+        if (old_erodeproof && !new_erodeproof) {
+            /* restore old_erodeproof before shop charges */
+            otmp->oerodeproof = 1;
+            costly_alteration(otmp, COST_DEGRD);
         }
-
-        if ((otmp->spe > (special_armor ? 5 : 3))
-            && (special_armor || !rn2(7)))
-            pline("%s %s.", Yobjnam2(otmp, "suddenly vibrate"),
-                  Blind ? "again" : "unexpectedly");
-        break;
+        otmp->oerodeproof = new_erodeproof ? 1 : 0;
+        return;
     }
-    case SCR_DESTROY_ARMOR: {
-        otmp = some_armor(&g.youmonst);
-        if (confused) {
-            if (!otmp) {
-                strange_feeling(sobj, "Your bones itch.");
-                sobj = 0; /* useup() in strange_feeling() */
-                exercise(A_STR, FALSE);
-                exercise(A_CON, FALSE);
-                break;
-            }
-            old_erodeproof = (otmp->oerodeproof != 0);
-            new_erodeproof = scursed;
-            otmp->oerodeproof = 0; /* for messages */
-            p_glow2(otmp, NH_PURPLE);
-            if (old_erodeproof && !new_erodeproof) {
-                /* restore old_erodeproof before shop charges */
-                otmp->oerodeproof = 1;
-                costly_alteration(otmp, COST_DEGRD);
-            }
-            otmp->oerodeproof = new_erodeproof ? 1 : 0;
-            break;
-        }
-        if (!scursed || !otmp || !otmp->cursed) {
-            if (!destroy_arm(otmp)) {
-                strange_feeling(sobj, "Your skin itches.");
-                sobj = 0; /* useup() in strange_feeling() */
-                exercise(A_STR, FALSE);
-                exercise(A_CON, FALSE);
-                break;
-            } else
-                g.known = TRUE;
-        } else { /* armor and scroll both cursed */
-            pline("%s.", Yobjnam2(otmp, "vibrate"));
-            if (otmp->spe >= -6) {
-                otmp->spe += -1;
-                adj_abon(otmp, -1);
-            }
-            make_stunned((HStun & TIMEOUT) + (long) rn1(10, 10), TRUE);
+    if (!scursed || !otmp || !otmp->cursed) {
+        if (!destroy_arm(otmp)) {
+            strange_feeling(sobj, "Your skin itches.");
+            *sobjp = 0; /* useup() in strange_feeling() */
+            exercise(A_STR, FALSE);
+            exercise(A_CON, FALSE);
+            return;
+        } else
+            g.known = TRUE;
+    } else { /* armor and scroll both cursed */
+        pline("%s.", Yobjnam2(otmp, "vibrate"));
+        if (otmp->spe >= -6) {
+            otmp->spe += -1;
+            adj_abon(otmp, -1);
         }
-    } break;
-    case SCR_CONFUSE_MONSTER:
-    case SPE_CONFUSE_MONSTER:
-        if (g.youmonst.data->mlet != S_HUMAN || scursed) {
-            if (!HConfusion)
-                You_feel("confused.");
+        make_stunned((HStun & TIMEOUT) + (long) rn1(10, 10), TRUE);
+    }
+}
+
+static void
+seffect_confuse_monster(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    if (g.youmonst.data->mlet != S_HUMAN || scursed) {
+        if (!HConfusion)
+            You_feel("confused.");
+        make_confused(HConfusion + rnd(100), FALSE);
+    } else if (confused) {
+        if (!sblessed) {
+            Your("%s begin to %s%s.", makeplural(body_part(HAND)),
+                 Blind ? "tingle" : "glow ",
+                 Blind ? "" : hcolor(NH_PURPLE));
             make_confused(HConfusion + rnd(100), FALSE);
-        } else if (confused) {
-            if (!sblessed) {
-                Your("%s begin to %s%s.", makeplural(body_part(HAND)),
-                     Blind ? "tingle" : "glow ",
-                     Blind ? "" : hcolor(NH_PURPLE));
-                make_confused(HConfusion + rnd(100), FALSE);
-            } else {
-                pline("A %s%s surrounds your %s.",
-                      Blind ? "" : hcolor(NH_RED),
-                      Blind ? "faint buzz" : " glow", body_part(HEAD));
-                make_confused(0L, TRUE);
-            }
         } else {
-            if (!sblessed) {
-                Your("%s%s %s%s.", makeplural(body_part(HAND)),
-                     Blind ? "" : " begin to glow",
-                     Blind ? (const char *) "tingle" : hcolor(NH_RED),
-                     u.umconf ? " even more" : "");
+            pline("A %s%s surrounds your %s.",
+                  Blind ? "" : hcolor(NH_RED),
+                  Blind ? "faint buzz" : " glow", body_part(HEAD));
+            make_confused(0L, TRUE);
+        }
+    } else {
+        if (!sblessed) {
+            Your("%s%s %s%s.", makeplural(body_part(HAND)),
+                 Blind ? "" : " begin to glow",
+                 Blind ? (const char *) "tingle" : hcolor(NH_RED),
+                 u.umconf ? " even more" : "");
+            u.umconf++;
+        } else {
+            if (Blind)
+                Your("%s tingle %s sharply.", makeplural(body_part(HAND)),
+                     u.umconf ? "even more" : "very");
+            else
+                Your("%s glow a%s brilliant %s.",
+                     makeplural(body_part(HAND)),
+                     u.umconf ? "n even more" : "", hcolor(NH_RED));
+            /* after a while, repeated uses become less effective */
+            if (u.umconf >= 40)
                 u.umconf++;
-            } else {
-                if (Blind)
-                    Your("%s tingle %s sharply.", makeplural(body_part(HAND)),
-                         u.umconf ? "even more" : "very");
-                else
-                    Your("%s glow a%s brilliant %s.",
-                         makeplural(body_part(HAND)),
-                         u.umconf ? "n even more" : "", hcolor(NH_RED));
-                /* after a while, repeated uses become less effective */
-                if (u.umconf >= 40)
-                    u.umconf++;
-                else
-                    u.umconf += rn1(8, 2);
-            }
+            else
+                u.umconf += rn1(8, 2);
         }
-        break;
-    case SCR_SCARE_MONSTER:
-    case SPE_CAUSE_FEAR: {
-        register int ct = 0;
-        register struct monst *mtmp;
+    }
+}
 
-        for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
-            if (DEADMONSTER(mtmp))
-                continue;
-            if (cansee(mtmp->mx, mtmp->my)) {
-                if (confused || scursed) {
-                    mtmp->mflee = mtmp->mfrozen = mtmp->msleeping = 0;
-                    mtmp->mcanmove = 1;
-                } else if (!resist(mtmp, sobj->oclass, 0, NOTELL))
-                    monflee(mtmp, 0, FALSE, FALSE);
-                if (!mtmp->mtame)
-                    ct++; /* pets don't laugh at you */
-            }
+static void
+seffect_scare_monster(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    register int ct = 0;
+    register struct monst *mtmp;
+
+    for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
+        if (DEADMONSTER(mtmp))
+            continue;
+        if (cansee(mtmp->mx, mtmp->my)) {
+            if (confused || scursed) {
+                mtmp->mflee = mtmp->mfrozen = mtmp->msleeping = 0;
+                mtmp->mcanmove = 1;
+            } else if (!resist(mtmp, sobj->oclass, 0, NOTELL))
+                monflee(mtmp, 0, FALSE, FALSE);
+            if (!mtmp->mtame)
+                ct++; /* pets don't laugh at you */
         }
-        if (otyp == SCR_SCARE_MONSTER || !ct)
-            You_hear("%s %s.", (confused || scursed) ? "sad wailing"
-                                                     : "maniacal laughter",
-                     !ct ? "in the distance" : "close by");
-        break;
     }
-    case SCR_BLANK_PAPER:
-        if (Blind)
-            You("don't remember there being any magic words on this scroll.");
-        else
-            pline("This scroll seems to be blank.");
-        g.known = TRUE;
-        break;
-    case SCR_REMOVE_CURSE:
-    case SPE_REMOVE_CURSE: {
-        register struct obj *obj;
-
-        You_feel(!Hallucination
-                     ? (!confused ? "like someone is helping you."
-                                  : "like you need some help.")
-                     : (!confused ? "in touch with the Universal Oneness."
-                                  : "the power of the Force against you!"));
+    if (otyp == SCR_SCARE_MONSTER || !ct)
+        You_hear("%s %s.", (confused || scursed) ? "sad wailing"
+                 : "maniacal laughter",
+                 !ct ? "in the distance" : "close by");
+}
 
-        if (scursed) {
-            pline_The("scroll disintegrates.");
-        } else {
-            for (obj = g.invent; obj; obj = obj->nobj) {
-                long wornmask;
+static void
+seffect_remove_curse(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    register struct obj *obj;
+
+    You_feel(!Hallucination
+             ? (!confused ? "like someone is helping you."
+                : "like you need some help.")
+             : (!confused ? "in touch with the Universal Oneness."
+                : "the power of the Force against you!"));
+
+    if (scursed) {
+        pline_The("scroll disintegrates.");
+    } else {
+        for (obj = g.invent; obj; obj = obj->nobj) {
+            long wornmask;
 
-                /* gold isn't subject to cursing and blessing */
-                if (obj->oclass == COIN_CLASS)
-                    continue;
-                /* hide current scroll from itself so that perm_invent won't
-                   show known blessed scroll losing bknown when confused */
-                if (obj == sobj && obj->quan == 1L)
-                    continue;
-                wornmask = (obj->owornmask & ~(W_BALL | W_ART | W_ARTI));
-                if (wornmask && !sblessed) {
-                    /* handle a couple of special cases; we don't
-                       allow auxiliary weapon slots to be used to
-                       artificially increase number of worn items */
-                    if (obj == uswapwep) {
-                        if (!u.twoweap)
+            /* gold isn't subject to cursing and blessing */
+            if (obj->oclass == COIN_CLASS)
+                continue;
+            /* hide current scroll from itself so that perm_invent won't
+               show known blessed scroll losing bknown when confused */
+            if (obj == sobj && obj->quan == 1L)
+                continue;
+            wornmask = (obj->owornmask & ~(W_BALL | W_ART | W_ARTI));
+            if (wornmask && !sblessed) {
+                /* handle a couple of special cases; we don't
+                   allow auxiliary weapon slots to be used to
+                   artificially increase number of worn items */
+                if (obj == uswapwep) {
+                    if (!u.twoweap)
+                        wornmask = 0L;
+                } else if (obj == uquiver) {
+                    if (obj->oclass == WEAPON_CLASS) {
+                        /* mergeable weapon test covers ammo,
+                           missiles, spears, daggers & knives */
+                        if (!objects[obj->otyp].oc_merge)
                             wornmask = 0L;
-                    } else if (obj == uquiver) {
-                        if (obj->oclass == WEAPON_CLASS) {
-                            /* mergeable weapon test covers ammo,
-                               missiles, spears, daggers & knives */
-                            if (!objects[obj->otyp].oc_merge)
-                                wornmask = 0L;
-                        } else if (obj->oclass == GEM_CLASS) {
-                            /* possibly ought to check whether
-                               alternate weapon is a sling... */
-                            if (!uslinging())
-                                wornmask = 0L;
-                        } else {
-                            /* weptools don't merge and aren't
-                               reasonable quivered weapons */
+                    } else if (obj->oclass == GEM_CLASS) {
+                        /* possibly ought to check whether
+                           alternate weapon is a sling... */
+                        if (!uslinging())
                             wornmask = 0L;
-                        }
-                    }
-                }
-                if (sblessed || wornmask || obj->otyp == LOADSTONE
-                    || (obj->otyp == LEASH && obj->leashmon)) {
-                    /* water price varies by curse/bless status */
-                    boolean shop_h2o = (obj->unpaid && obj->otyp == POT_WATER);
-
-                    if (confused) {
-                        blessorcurse(obj, 2);
-                        /* lose knowledge of this object's curse/bless
-                           state (even if it didn't actually change) */
-                        obj->bknown = 0;
-                        /* blessorcurse() only affects uncursed items
-                           so no need to worry about price of water
-                           going down (hence no costly_alteration) */
-                        if (shop_h2o && (obj->cursed || obj->blessed))
-                            alter_cost(obj, 0L); /* price goes up */
-                    } else if (obj->cursed) {
-                        if (shop_h2o)
-                            costly_alteration(obj, COST_UNCURS);
-                        uncurse(obj);
-                        /* if the object was known to be cursed and is now
-                           known not to be, make the scroll known; it's
-                           trivial to identify anyway by comparing inventory
-                           before and after */
-                        if (obj->bknown && otyp == SCR_REMOVE_CURSE)
-                            learnscrolltyp(SCR_REMOVE_CURSE);
+                    } else {
+                        /* weptools don't merge and aren't
+                           reasonable quivered weapons */
+                        wornmask = 0L;
                     }
                 }
             }
-            /* if riding, treat steed's saddle as if part of hero's invent */
-            if (u.usteed && (obj = which_armor(u.usteed, W_SADDLE)) != 0) {
+            if (sblessed || wornmask || obj->otyp == LOADSTONE
+                || (obj->otyp == LEASH && obj->leashmon)) {
+                /* water price varies by curse/bless status */
+                boolean shop_h2o = (obj->unpaid && obj->otyp == POT_WATER);
+
                 if (confused) {
                     blessorcurse(obj, 2);
-                    obj->bknown = 0; /* skip set_bknown() */
+                    /* lose knowledge of this object's curse/bless
+                       state (even if it didn't actually change) */
+                    obj->bknown = 0;
+                    /* blessorcurse() only affects uncursed items
+                       so no need to worry about price of water
+                       going down (hence no costly_alteration) */
+                    if (shop_h2o && (obj->cursed || obj->blessed))
+                        alter_cost(obj, 0L); /* price goes up */
                 } else if (obj->cursed) {
+                    if (shop_h2o)
+                        costly_alteration(obj, COST_UNCURS);
                     uncurse(obj);
-                    /* like rndcurse(sit.c), effect on regular inventory
-                       doesn't show things glowing but saddle does */
-                    if (!Blind) {
-                        pline("%s %s.", Yobjnam2(obj, "glow"),
-                              hcolor("amber"));
-                        obj->bknown = Hallucination ? 0 : 1;
-                    } else {
-                        obj->bknown = 0; /* skip set_bknown() */
-                    }
+                    /* if the object was known to be cursed and is now
+                       known not to be, make the scroll known; it's
+                       trivial to identify anyway by comparing inventory
+                       before and after */
+                    if (obj->bknown && otyp == SCR_REMOVE_CURSE)
+                        learnscrolltyp(SCR_REMOVE_CURSE);
                 }
             }
         }
-        if (Punished && !confused)
-            unpunish();
-        if (u.utrap && u.utraptype == TT_BURIEDBALL) {
-            buried_ball_to_freedom();
-            pline_The("clasp on your %s vanishes.", body_part(LEG));
+        /* if riding, treat steed's saddle as if part of hero's invent */
+        if (u.usteed && (obj = which_armor(u.usteed, W_SADDLE)) != 0) {
+            if (confused) {
+                blessorcurse(obj, 2);
+                obj->bknown = 0; /* skip set_bknown() */
+            } else if (obj->cursed) {
+                uncurse(obj);
+                /* like rndcurse(sit.c), effect on regular inventory
+                   doesn't show things glowing but saddle does */
+                if (!Blind) {
+                    pline("%s %s.", Yobjnam2(obj, "glow"),
+                              hcolor("amber"));
+                    obj->bknown = Hallucination ? 0 : 1;
+                } else {
+                    obj->bknown = 0; /* skip set_bknown() */
+                }
+            }
         }
-        update_inventory();
-        break;
     }
-    case SCR_CREATE_MONSTER:
-    case SPE_CREATE_MONSTER:
-        if (create_critters(1 + ((confused || scursed) ? 12 : 0)
-                                + ((sblessed || rn2(73)) ? 0 : rnd(4)),
-                            confused ? &mons[PM_ACID_BLOB]
-                                     : (struct permonst *) 0,
-                            FALSE))
-            g.known = TRUE;
-        /* no need to flush monsters; we ask for identification only if the
-         * monsters are not visible
-         */
-        break;
-    case SCR_ENCHANT_WEAPON:
-        /* [What about twoweapon mode?  Proofing/repairing/enchanting both
-           would be too powerful, but shouldn't we choose randomly between
-           primary and secondary instead of always acting on primary?] */
-        if (confused && uwep
-            && erosion_matters(uwep) && uwep->oclass != ARMOR_CLASS) {
-            old_erodeproof = (uwep->oerodeproof != 0);
-            new_erodeproof = !scursed;
-            uwep->oerodeproof = 0; /* for messages */
-            if (Blind) {
-                uwep->rknown = FALSE;
-                Your("weapon feels warm for a moment.");
-            } else {
-                uwep->rknown = TRUE;
-                pline("%s covered by a %s %s %s!", Yobjnam2(uwep, "are"),
-                      scursed ? "mottled" : "shimmering",
-                      hcolor(scursed ? NH_PURPLE : NH_GOLDEN),
-                      scursed ? "glow" : "shield");
+    if (Punished && !confused)
+        unpunish();
+    if (u.utrap && u.utraptype == TT_BURIEDBALL) {
+        buried_ball_to_freedom();
+        pline_The("clasp on your %s vanishes.", body_part(LEG));
+    }
+    update_inventory();
+}
+
+static void
+seffect_create_monster(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    if (create_critters(1 + ((confused || scursed) ? 12 : 0)
+                        + ((sblessed || rn2(73)) ? 0 : rnd(4)),
+                        confused ? &mons[PM_ACID_BLOB]
+                        : (struct permonst *) 0,
+                        FALSE))
+        g.known = TRUE;
+    /* no need to flush monsters; we ask for identification only if the
+     * monsters are not visible
+     */
+}
+
+static void
+seffect_enchant_weapon(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    boolean old_erodeproof, new_erodeproof;
+
+    /* [What about twoweapon mode?  Proofing/repairing/enchanting both
+       would be too powerful, but shouldn't we choose randomly between
+       primary and secondary instead of always acting on primary?] */
+    if (confused && uwep
+        && erosion_matters(uwep) && uwep->oclass != ARMOR_CLASS) {
+        old_erodeproof = (uwep->oerodeproof != 0);
+        new_erodeproof = !scursed;
+        uwep->oerodeproof = 0; /* for messages */
+        if (Blind) {
+            uwep->rknown = FALSE;
+            Your("weapon feels warm for a moment.");
+        } else {
+            uwep->rknown = TRUE;
+            pline("%s covered by a %s %s %s!", Yobjnam2(uwep, "are"),
+                  scursed ? "mottled" : "shimmering",
+                  hcolor(scursed ? NH_PURPLE : NH_GOLDEN),
+                  scursed ? "glow" : "shield");
+        }
+        if (new_erodeproof && (uwep->oeroded || uwep->oeroded2)) {
+            uwep->oeroded = uwep->oeroded2 = 0;
+            pline("%s as good as new!",
+                  Yobjnam2(uwep, Blind ? "feel" : "look"));
+        }
+        if (old_erodeproof && !new_erodeproof) {
+            /* restore old_erodeproof before shop charges */
+            uwep->oerodeproof = 1;
+            costly_alteration(uwep, COST_DEGRD);
+        }
+        uwep->oerodeproof = new_erodeproof ? 1 : 0;
+        return;
+    }
+    if (!chwepon(sobj, scursed ? -1
+                 : !uwep ? 1
+                 : (uwep->spe >= 9) ? !rn2(uwep->spe)
+                 : sblessed ? rnd(3 - uwep->spe / 3)
+                 : 1))
+        *sobjp = 0; /* nothing enchanted: strange_feeling -> useup */
+    if (uwep)
+        cap_spe(uwep);
+}
+
+static void
+seffect_taming(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean confused = (Confusion != 0);
+    int candidates, res, results, vis_results;
+
+    if (u.uswallow) {
+        candidates = 1;
+        results = vis_results = maybe_tame(u.ustuck, sobj);
+    } else {
+        int i, j, bd = confused ? 5 : 1;
+        struct monst *mtmp;
+
+        /* note: maybe_tame() can return either positive or
+           negative values, but not both for the same scroll */
+        candidates = results = vis_results = 0;
+        for (i = -bd; i <= bd; i++)
+            for (j = -bd; j <= bd; j++) {
+                if (!isok(u.ux + i, u.uy + j))
+                    continue;
+                if ((mtmp = m_at(u.ux + i, u.uy + j)) != 0
+                    || (!i && !j && (mtmp = u.usteed) != 0)) {
+                    ++candidates;
+                    res = maybe_tame(mtmp, sobj);
+                    results += res;
+                    if (canspotmon(mtmp))
+                        vis_results += res;
+                }
             }
-            if (new_erodeproof && (uwep->oeroded || uwep->oeroded2)) {
-                uwep->oeroded = uwep->oeroded2 = 0;
-                pline("%s as good as new!",
-                      Yobjnam2(uwep, Blind ? "feel" : "look"));
+    }
+    if (!results) {
+        pline("Nothing interesting %s.",
+              !candidates ? "happens" : "seems to happen");
+    } else {
+        pline_The("neighborhood %s %sfriendlier.",
+                  vis_results ? "is" : "seems",
+                  (results < 0) ? "un" : "");
+        if (vis_results > 0)
+            g.known = TRUE;
+    }
+}
+
+static void
+seffect_genocide(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
+                             || objects[otyp].oc_name_known);
+
+    if (!already_known)
+        You("have found a scroll of genocide!");
+    g.known = TRUE;
+    if (sblessed)
+        do_class_genocide();
+    else
+        do_genocide((!scursed) | (2 * !!Confusion));
+}
+
+static void
+seffect_light(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    if (!confused) {
+        if (!Blind)
+            g.known = TRUE;
+        litroom(!scursed, sobj);
+        if (!scursed) {
+            if (lightdamage(sobj, TRUE, 5))
+                g.known = TRUE;
+        }
+    } else {
+        int pm = scursed ? PM_BLACK_LIGHT : PM_YELLOW_LIGHT;
+
+        if ((g.mvitals[pm].mvflags & G_GONE)) {
+            pline("Tiny lights sparkle in the air momentarily.");
+        } else {
+            /* surround with cancelled tame lights which won't explode */
+            boolean sawlights = FALSE;
+            int numlights = rn1(2,3) + (sblessed * 2);
+            int i;
+
+            for (i = 0; i < numlights; ++i) {
+                struct monst * mon = makemon(&mons[pm], u.ux, u.uy,
+                                             MM_EDOG | NO_MINVENT);
+                initedog(mon);
+                mon->msleeping = 0;
+                mon->mcan = TRUE;
+                if (canspotmon(mon))
+                    sawlights = TRUE;
+                newsym(mon->mx, mon->my);
             }
-            if (old_erodeproof && !new_erodeproof) {
-                /* restore old_erodeproof before shop charges */
-                uwep->oerodeproof = 1;
-                costly_alteration(uwep, COST_DEGRD);
+            if (sawlights) {
+                pline("Lights appear all around you!");
+                g.known = TRUE;
             }
-            uwep->oerodeproof = new_erodeproof ? 1 : 0;
-            break;
         }
-        if (!chwepon(sobj, scursed ? -1
-                             : !uwep ? 1
-                               : (uwep->spe >= 9) ? !rn2(uwep->spe)
-                                 : sblessed ? rnd(3 - uwep->spe / 3)
-                                   : 1))
-            sobj = 0; /* nothing enchanted: strange_feeling -> useup */
-        if (uwep)
-            cap_spe(uwep);
-        break;
-    case SCR_TAMING:
-    case SPE_CHARM_MONSTER: {
-        int candidates, res, results, vis_results;
+    }
+}
 
-        if (u.uswallow) {
-            candidates = 1;
-            results = vis_results = maybe_tame(u.ustuck, sobj);
+static void
+seffect_charging(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
+                             || objects[otyp].oc_name_known);
+    struct obj *otmp;
+
+    if (confused) {
+        if (scursed) {
+            You_feel("discharged.");
+            u.uen = 0;
         } else {
-            int i, j, bd = confused ? 5 : 1;
-            struct monst *mtmp;
-
-            /* note: maybe_tame() can return either positive or
-               negative values, but not both for the same scroll */
-            candidates = results = vis_results = 0;
-            for (i = -bd; i <= bd; i++)
-                for (j = -bd; j <= bd; j++) {
-                    if (!isok(u.ux + i, u.uy + j))
-                        continue;
-                    if ((mtmp = m_at(u.ux + i, u.uy + j)) != 0
-                        || (!i && !j && (mtmp = u.usteed) != 0)) {
-                        ++candidates;
-                        res = maybe_tame(mtmp, sobj);
-                        results += res;
-                        if (canspotmon(mtmp))
-                            vis_results += res;
-                    }
-                }
+            You_feel("charged up!");
+            u.uen += d(sblessed ? 6 : 4, 4);
+            if (u.uen > u.uenmax) /* if current energy is already at   */
+                u.uenmax = u.uen; /* or near maximum, increase maximum */
+            else
+                u.uen = u.uenmax; /* otherwise restore current to max  */
+        }
+        g.context.botl = 1;
+        return;
+    }
+    /* known = TRUE; -- handled inline here */
+    if (!already_known) {
+        pline("This is a charging scroll.");
+        learnscroll(sobj);
+    }
+    /* use it up now to prevent it from showing in the
+       getobj picklist because the "disappears" message
+       was already delivered */
+    useup(sobj);
+    sobjp = 0; /* it's gone */
+    otmp = getobj("charge", charge_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
+    if (otmp)
+        recharge(otmp, scursed ? -1 : sblessed ? 1 : 0);
+}
+
+static void
+seffect_amnesia(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+
+    g.known = TRUE;
+    forget((!sblessed ? ALL_SPELLS : 0));
+    if (Hallucination) /* Ommmmmm! */
+        Your("mind releases itself from mundane concerns.");
+    else if (!strncmpi(g.plname, "Maud", 4))
+        pline(
+              "As your mind turns inward on itself, you forget everything else.");
+    else if (rn2(2))
+        pline("Who was that Maud person anyway?");
+    else
+        pline("Thinking of Maud you forget everything else.");
+    exercise(A_WIS, FALSE);
+}
+
+static void
+seffect_fire(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean sblessed = sobj->blessed;
+    boolean confused = (Confusion != 0);
+    boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
+                             || objects[otyp].oc_name_known);
+    coord cc;
+    int dam, cval;
+
+    cc.x = u.ux;
+    cc.y = u.uy;
+    cval = bcsign(sobj);
+    dam = (2 * (rn1(3, 3) + 2 * cval) + 1) / 3;
+    useup(sobj);
+    sobjp = 0; /* it's gone */
+    if (!already_known)
+        (void) learnscrolltyp(SCR_FIRE);
+    if (confused) {
+        if (Underwater) {
+            pline("A little %s around you vaporizes.", hliquid("water"));
         }
-        if (!results) {
-            pline("Nothing interesting %s.",
-                  !candidates ? "happens" : "seems to happen");
+        else if (Fire_resistance) {
+            shieldeff(u.ux, u.uy);
+            monstseesu(M_SEEN_FIRE);
+            if (!Blind)
+                pline("Oh, look, what a pretty fire in your %s.",
+                      makeplural(body_part(HAND)));
+            else
+                You_feel("a pleasant warmth in your %s.",
+                         makeplural(body_part(HAND)));
         } else {
-            pline_The("neighborhood %s %sfriendlier.",
-                      vis_results ? "is" : "seems",
-                      (results < 0) ? "un" : "");
-            if (vis_results > 0)
-                g.known = TRUE;
+            pline_The("scroll catches fire and you burn your %s.",
+                      makeplural(body_part(HAND)));
+            losehp(1, "scroll of fire", KILLED_BY_AN);
         }
-        break;
+        return;
     }
-    case SCR_GENOCIDE:
-        if (!already_known)
-            You("have found a scroll of genocide!");
-        g.known = TRUE;
-        if (sblessed)
-            do_class_genocide();
-        else
-            do_genocide((!scursed) | (2 * !!Confusion));
-        break;
-    case SCR_LIGHT:
-        if (!confused) {
-            if (!Blind)
-                g.known = TRUE;
-            litroom(!scursed, sobj);
-            if (!scursed) {
-                if (lightdamage(sobj, TRUE, 5))
-                    g.known = TRUE;
+    if (Underwater) {
+        pline_The("%s around you vaporizes violently!", hliquid("water"));
+    } else {
+        if (sblessed) {
+            if (!already_known)
+                pline("This is a scroll of fire!");
+            dam *= 5;
+            pline("Where do you want to center the explosion?");
+            getpos_sethilite(display_stinking_cloud_positions,
+                             get_valid_stinking_cloud_pos);
+            (void) getpos(&cc, TRUE, "the desired position");
+            if (!is_valid_stinking_cloud_pos(cc.x, cc.y, FALSE)) {
+                /* try to reach too far, get burned */
+                cc.x = u.ux;
+                cc.y = u.uy;
             }
-        } else {
-            int pm = scursed ? PM_BLACK_LIGHT : PM_YELLOW_LIGHT;
+        }
+        if (cc.x == u.ux && cc.y == u.uy) {
+            pline_The("scroll erupts in a tower of flame!");
+            iflags.last_msg = PLNMSG_TOWER_OF_FLAME; /* for explode() */
+            burn_away_slime();
+        }
+    }
+    explode(cc.x, cc.y, 11, dam, SCROLL_CLASS, EXPL_FIERY);
+}
 
-            if ((g.mvitals[pm].mvflags & G_GONE)) {
-                pline("Tiny lights sparkle in the air momentarily.");
-            } else {
-                /* surround with cancelled tame lights which won't explode */
-                boolean sawlights = FALSE;
-                int numlights = rn1(2,3) + (sblessed * 2);
-                int i;
-
-                for (i = 0; i < numlights; ++i) {
-                    struct monst * mon = makemon(&mons[pm], u.ux, u.uy,
-                                                 MM_EDOG | NO_MINVENT);
-                    initedog(mon);
-                    mon->msleeping = 0;
-                    mon->mcan = TRUE;
-                    if (canspotmon(mon))
-                        sawlights = TRUE;
-                    newsym(mon->mx, mon->my);
-                }
-                if (sawlights) {
-                    pline("Lights appear all around you!");
-                    g.known = TRUE;
+static void
+seffect_earth(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    /* TODO: handle steeds */
+    if (!Is_rogue_level(&u.uz) && has_ceiling(&u.uz)
+        && (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) {
+        register int x, y;
+        int nboulders = 0;
+
+        /* Identify the scroll */
+        if (u.uswallow)
+            You_hear("rumbling.");
+        else
+            pline_The("%s rumbles %s you!", ceiling(u.ux, u.uy),
+                      sblessed ? "around" : "above");
+        g.known = 1;
+        sokoban_guilt();
+
+        /* Loop through the surrounding squares */
+        if (!scursed)
+            for (x = u.ux - 1; x <= u.ux + 1; x++) {
+                for (y = u.uy - 1; y <= u.uy + 1; y++) {
+                    /* Is this a suitable spot? */
+                    if (isok(x, y) && !closed_door(x, y)
+                        && !IS_ROCK(levl[x][y].typ)
+                        && !IS_AIR(levl[x][y].typ)
+                        && (x != u.ux || y != u.uy)) {
+                        nboulders +=
+                            drop_boulder_on_monster(x, y, confused, TRUE);
+                    }
                 }
             }
-        }
-        break;
-    case SCR_TELEPORTATION:
-        if (confused || scursed) {
-            level_tele();
-            /* gives "materialize on different/same level!" message, must
-               be a teleport scroll */
-            g.known = TRUE;
-        } else {
-            scrolltele(sobj);
-            /* this will call learnscroll() as appropriate, and has results
-               which maybe shouldn't result in the scroll becoming known;
-               either way, no need to set g.known here */
-        }
-        break;
-    case SCR_GOLD_DETECTION:
-        if ((confused || scursed) ? trap_detect(sobj) : gold_detect(sobj))
-            sobj = 0; /* failure: strange_feeling() -> useup() */
-        break;
-    case SCR_FOOD_DETECTION:
-    case SPE_DETECT_FOOD:
-        if (food_detect(sobj))
-            sobj = 0; /* nothing detected: strange_feeling -> useup */
-        break;
-    case SCR_IDENTIFY:
+        /* Attack the player */
+        if (!sblessed) {
+            drop_boulder_on_player(confused, !scursed, TRUE, FALSE);
+        } else if (!nboulders)
+            pline("But nothing else happens.");
+    }
+}
+
+static void
+seffect_punishment(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean sblessed = sobj->blessed;
+    boolean confused = (Confusion != 0);
+
+    g.known = TRUE;
+    if (confused || sblessed) {
+        You_feel("guilty.");
+        return;
+    }
+    punish(sobj);
+}
+
+static void
+seffect_stinking_cloud(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
+                             || objects[otyp].oc_name_known);
+    coord cc;
+
+    if (!already_known)
+        You("have found a scroll of stinking cloud!");
+    g.known = TRUE;
+    pline("Where do you want to center the %scloud?",
+          already_known ? "stinking " : "");
+    cc.x = u.ux;
+    cc.y = u.uy;
+    getpos_sethilite(display_stinking_cloud_positions,
+                     get_valid_stinking_cloud_pos);
+    if (getpos(&cc, TRUE, "the desired position") < 0) {
+        pline1(Never_mind);
+        return;
+    }
+    if (!is_valid_stinking_cloud_pos(cc.x, cc.y, TRUE))
+        return;
+    (void) create_gas_cloud(cc.x, cc.y, 3 + bcsign(sobj),
+                            8 + 4 * bcsign(sobj));
+}
+
+static void
+seffect_blank_paper(struct obj **sobjp UNUSED)
+{
+    if (Blind)
+        You("don't remember there being any magic words on this scroll.");
+    else
+        pline("This scroll seems to be blank.");
+    g.known = TRUE;
+}
+
+static void
+seffect_teleportation(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    if (confused || scursed) {
+        level_tele();
+        /* gives "materialize on different/same level!" message, must
+           be a teleport scroll */
+        g.known = TRUE;
+    } else {
+        scrolltele(sobj);
+        /* this will call learnscroll() as appropriate, and has results
+           which maybe shouldn't result in the scroll becoming known;
+           either way, no need to set g.known here */
+    }
+}
+
+static void
+seffect_gold_detection(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+
+    if ((confused || scursed) ? trap_detect(sobj) : gold_detect(sobj))
+        sobjp = 0; /* failure: strange_feeling() -> useup() */
+}
+
+static void
+seffect_food_detection(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+
+    if (food_detect(sobj))
+        sobjp = 0; /* nothing detected: strange_feeling -> useup */
+}
+
+static void
+seffect_identify(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    int otyp = sobj->otyp;
+    boolean is_scroll = (sobj->oclass == SCROLL_CLASS);
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */
+                             || objects[otyp].oc_name_known);
+
+    if (is_scroll) { /* scroll of identify */
         /* known = TRUE; -- handled inline here */
         /* use up the scroll first, before learnscrolltyp() -> makeknown()
            performs perm_invent update; also simplifies empty invent check */
         useup(sobj);
-        sobj = 0; /* it's gone */
+        sobjp = 0; /* it's gone */
         /* scroll just identifies itself for any scroll read while confused
            or for cursed scroll read without knowing identify yet */
         if (confused || (scursed && !already_known))
@@ -1509,56 +1803,37 @@ seffects(struct obj *sobj) /* sobj - scroll or fake spellbook for spell */
         if (!already_known)
             (void) learnscrolltyp(SCR_IDENTIFY);
         if (confused || (scursed && !already_known))
-            break;
-        /*FALLTHRU*/
-    case SPE_IDENTIFY:
-        if (g.invent) {
-            cval = 1;
-            if (sblessed || (!scursed && !rn2(5))) {
-                cval = rn2(5);
-                /* note: if cval==0, identify all items */
-                if (cval == 1 && sblessed && Luck > 0)
-                    ++cval;
-            }
-            identify_pack(cval, !already_known);
-        } else {
-            /* spell cast with inventory empty or scroll read when it's
-               the only item leaving empty inventory after being used up */
-            pline("You're not carrying anything%s to be identified.",
-                  (otyp == SCR_IDENTIFY) ? " else" : "");
-        }
-        break;
-    case SCR_CHARGING:
-        if (confused) {
-            if (scursed) {
-                You_feel("discharged.");
-                u.uen = 0;
-            } else {
-                You_feel("charged up!");
-                u.uen += d(sblessed ? 6 : 4, 4);
-                if (u.uen > u.uenmax) /* if current energy is already at   */
-                    u.uenmax = u.uen; /* or near maximum, increase maximum */
-                else
-                    u.uen = u.uenmax; /* otherwise restore current to max  */
-            }
-            g.context.botl = 1;
-            break;
-        }
-        /* known = TRUE; -- handled inline here */
-        if (!already_known) {
-            pline("This is a charging scroll.");
-            learnscroll(sobj);
+            return;
+    }
+
+    if (g.invent) {
+        int cval = 1;
+        if (sblessed || (!scursed && !rn2(5))) {
+            cval = rn2(5);
+            /* note: if cval==0, identify all items */
+            if (cval == 1 && sblessed && Luck > 0)
+                ++cval;
         }
-        /* use it up now to prevent it from showing in the
-           getobj picklist because the "disappears" message
-           was already delivered */
-        useup(sobj);
-        sobj = 0; /* it's gone */
-        otmp = getobj("charge", charge_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
-        if (otmp)
-            recharge(otmp, scursed ? -1 : sblessed ? 1 : 0);
-        break;
-    case SCR_MAGIC_MAPPING:
+        identify_pack(cval, !already_known);
+    } else {
+        /* spell cast with inventory empty or scroll read when it's
+           the only item leaving empty inventory after being used up */
+        pline("You're not carrying anything%s to be identified.",
+              (is_scroll) ? " else" : "");
+    }
+}
+
+static void
+seffect_magic_mapping(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean is_scroll = (sobj->oclass == SCROLL_CLASS);
+    boolean sblessed = sobj->blessed;
+    boolean scursed = sobj->cursed;
+    boolean confused = (Confusion != 0);
+    int cval;
+
+    if (is_scroll) {
         if (g.level.flags.nommap) {
             Your("mind is filled with crazy lines!");
             if (Hallucination)
@@ -1566,7 +1841,7 @@ seffects(struct obj *sobj) /* sobj - scroll or fake spellbook for spell */
             else
                 Your("%s spins in bewilderment.", body_part(HEAD));
             make_confused(HConfusion + rnd(30), FALSE);
-            break;
+            return;
         }
         if (sblessed) {
             register int x, y;
@@ -1578,163 +1853,151 @@ seffects(struct obj *sobj) /* sobj - scroll or fake spellbook for spell */
             /* do_mapping() already reveals secret passages */
         }
         g.known = TRUE;
-        /*FALLTHRU*/
+    }
+
+    if (g.level.flags.nommap) {
+        Your("%s spins as %s blocks the spell!", body_part(HEAD),
+             something);
+        make_confused(HConfusion + rnd(30), FALSE);
+        return;
+    }
+    pline("A map coalesces in your mind!");
+    cval = (scursed && !confused);
+    if (cval)
+        HConfusion = 1; /* to screw up map */
+    do_mapping();
+    if (cval) {
+        HConfusion = 0; /* restore */
+        pline("Unfortunately, you can't grasp the details.");
+    }
+}
+
+#ifdef MAIL_STRUCTURES
+static void
+seffect_mail(struct obj **sobjp)
+{
+    struct obj *sobj = *sobjp;
+    boolean odd = (sobj->o_id % 2) == 1;
+
+    g.known = TRUE;
+    switch (sobj->spe) {
+    case 2:
+        /* "stamped scroll" created via magic marker--without a stamp */
+        pline("This scroll is marked \"%s\".",
+              odd ? "Postage Due" : "Return to Sender");
+        break;
+    case 1:
+        /* scroll of mail obtained from bones file or from wishing;
+           note to the puzzled: the game Larn actually sends you junk
+           mail if you win! */
+        pline("This seems to be %s.",
+              odd ? "a chain letter threatening your luck"
+              : "junk mail addressed to the finder of the Eye of Larn");
+        break;
+    default:
+#ifdef MAIL
+        readmail(sobj);
+#else
+        /* unreachable since with MAIL undefined, sobj->spe won't be 0;
+           as a precaution, be prepared to give arbitrary feedback;
+           caller has already reported that it disappears upon reading */
+        pline("That was a scroll of mail?");
+#endif
+        break;
+    }
+}
+#endif /* MAIL_STRUCTURES */
+
+/* scroll effects; return 1 if we use up the scroll and possibly make it
+   become discovered, 0 if caller should take care of those side-effects */
+int
+seffects(struct obj *sobj) /* sobj - scroll or fake spellbook for spell */
+{
+    int otyp = sobj->otyp;
+
+    if (objects[otyp].oc_magic)
+        exercise(A_WIS, TRUE);                       /* just for trying */
+
+    switch (otyp) {
+#ifdef MAIL_STRUCTURES
+    case SCR_MAIL:
+        seffect_mail(&sobj);
+        break;
+#endif
+    case SCR_ENCHANT_ARMOR:
+        seffect_enchant_armor(&sobj);
+        break;
+    case SCR_DESTROY_ARMOR:
+        seffect_destroy_armor(&sobj);
+        break;
+    case SCR_CONFUSE_MONSTER:
+    case SPE_CONFUSE_MONSTER:
+        seffect_confuse_monster(&sobj);
+        break;
+    case SCR_SCARE_MONSTER:
+    case SPE_CAUSE_FEAR:
+        seffect_scare_monster(&sobj);
+        break;
+    case SCR_BLANK_PAPER:
+        seffect_blank_paper(&sobj);
+        break;
+    case SCR_REMOVE_CURSE:
+    case SPE_REMOVE_CURSE:
+        seffect_remove_curse(&sobj);
+        break;
+    case SCR_CREATE_MONSTER:
+    case SPE_CREATE_MONSTER:
+        seffect_create_monster(&sobj);
+        break;
+    case SCR_ENCHANT_WEAPON:
+        seffect_enchant_weapon(&sobj);
+        break;
+    case SCR_TAMING:
+    case SPE_CHARM_MONSTER:
+        seffect_taming(&sobj);
+        break;
+    case SCR_GENOCIDE:
+        seffect_genocide(&sobj);
+        break;
+    case SCR_LIGHT:
+        seffect_light(&sobj);
+        break;
+    case SCR_TELEPORTATION:
+        seffect_teleportation(&sobj);
+        break;
+    case SCR_GOLD_DETECTION:
+        seffect_gold_detection(&sobj);
+        break;
+    case SCR_FOOD_DETECTION:
+    case SPE_DETECT_FOOD:
+        seffect_food_detection(&sobj);
+        break;
+    case SCR_IDENTIFY:
+    case SPE_IDENTIFY:
+        seffect_identify(&sobj);
+        break;
+    case SCR_CHARGING:
+        seffect_charging(&sobj);
+        break;
+    case SCR_MAGIC_MAPPING:
     case SPE_MAGIC_MAPPING:
-        if (g.level.flags.nommap) {
-            Your("%s spins as %s blocks the spell!", body_part(HEAD),
-                 something);
-            make_confused(HConfusion + rnd(30), FALSE);
-            break;
-        }
-        pline("A map coalesces in your mind!");
-        cval = (scursed && !confused);
-        if (cval)
-            HConfusion = 1; /* to screw up map */
-        do_mapping();
-        if (cval) {
-            HConfusion = 0; /* restore */
-            pline("Unfortunately, you can't grasp the details.");
-        }
+        seffect_magic_mapping(&sobj);
         break;
     case SCR_AMNESIA:
-        g.known = TRUE;
-        forget((!sblessed ? ALL_SPELLS : 0));
-        if (Hallucination) /* Ommmmmm! */
-            Your("mind releases itself from mundane concerns.");
-        else if (!strncmpi(g.plname, "Maud", 4))
-            pline(
-          "As your mind turns inward on itself, you forget everything else.");
-        else if (rn2(2))
-            pline("Who was that Maud person anyway?");
-        else
-            pline("Thinking of Maud you forget everything else.");
-        exercise(A_WIS, FALSE);
+        seffect_amnesia(&sobj);
         break;
-    case SCR_FIRE: {
-        coord cc;
-        int dam;
-
-        cc.x = u.ux;
-        cc.y = u.uy;
-        cval = bcsign(sobj);
-        dam = (2 * (rn1(3, 3) + 2 * cval) + 1) / 3;
-        useup(sobj);
-        sobj = 0; /* it's gone */
-        if (!already_known)
-            (void) learnscrolltyp(SCR_FIRE);
-        if (confused) {
-            if (Underwater) {
-                pline("A little %s around you vaporizes.", hliquid("water"));
-            }
-            else if (Fire_resistance) {
-                shieldeff(u.ux, u.uy);
-                monstseesu(M_SEEN_FIRE);
-                if (!Blind)
-                    pline("Oh, look, what a pretty fire in your %s.",
-                          makeplural(body_part(HAND)));
-                else
-                    You_feel("a pleasant warmth in your %s.",
-                             makeplural(body_part(HAND)));
-            } else {
-                pline_The("scroll catches fire and you burn your %s.",
-                          makeplural(body_part(HAND)));
-                losehp(1, "scroll of fire", KILLED_BY_AN);
-            }
-            break;
-        }
-        if (Underwater) {
-            pline_The("%s around you vaporizes violently!", hliquid("water"));
-        } else {
-            if (sblessed) {
-                if (!already_known)
-                    pline("This is a scroll of fire!");
-                dam *= 5;
-                pline("Where do you want to center the explosion?");
-                getpos_sethilite(display_stinking_cloud_positions,
-                                 get_valid_stinking_cloud_pos);
-                (void) getpos(&cc, TRUE, "the desired position");
-                if (!is_valid_stinking_cloud_pos(cc.x, cc.y, FALSE)) {
-                    /* try to reach too far, get burned */
-                    cc.x = u.ux;
-                    cc.y = u.uy;
-                }
-            }
-            if (cc.x == u.ux && cc.y == u.uy) {
-                pline_The("scroll erupts in a tower of flame!");
-                iflags.last_msg = PLNMSG_TOWER_OF_FLAME; /* for explode() */
-                burn_away_slime();
-            }
-        }
-        explode(cc.x, cc.y, 11, dam, SCROLL_CLASS, EXPL_FIERY);
+    case SCR_FIRE:
+        seffect_fire(&sobj);
         break;
-    }
     case SCR_EARTH:
-        /* TODO: handle steeds */
-        if (!Is_rogue_level(&u.uz) && has_ceiling(&u.uz)
-            && (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) {
-            register int x, y;
-            int nboulders = 0;
-
-            /* Identify the scroll */
-            if (u.uswallow)
-                You_hear("rumbling.");
-            else
-                pline_The("%s rumbles %s you!", ceiling(u.ux, u.uy),
-                          sblessed ? "around" : "above");
-            g.known = 1;
-            sokoban_guilt();
-
-            /* Loop through the surrounding squares */
-            if (!scursed)
-                for (x = u.ux - 1; x <= u.ux + 1; x++) {
-                    for (y = u.uy - 1; y <= u.uy + 1; y++) {
-                        /* Is this a suitable spot? */
-                        if (isok(x, y) && !closed_door(x, y)
-                            && !IS_ROCK(levl[x][y].typ)
-                            && !IS_AIR(levl[x][y].typ)
-                            && (x != u.ux || y != u.uy)) {
-                            nboulders +=
-                                drop_boulder_on_monster(x, y, confused, TRUE);
-                        }
-                    }
-                }
-            /* Attack the player */
-            if (!sblessed) {
-                drop_boulder_on_player(confused, !scursed, TRUE, FALSE);
-            } else if (!nboulders)
-                pline("But nothing else happens.");
-        }
+        seffect_earth(&sobj);
         break;
     case SCR_PUNISHMENT:
-        g.known = TRUE;
-        if (confused || sblessed) {
-            You_feel("guilty.");
-            break;
-        }
-        punish(sobj);
+        seffect_punishment(&sobj);
         break;
-    case SCR_STINKING_CLOUD: {
-        coord cc;
-
-        if (!already_known)
-            You("have found a scroll of stinking cloud!");
-        g.known = TRUE;
-        pline("Where do you want to center the %scloud?",
-              already_known ? "stinking " : "");
-        cc.x = u.ux;
-        cc.y = u.uy;
-        getpos_sethilite(display_stinking_cloud_positions,
-                         get_valid_stinking_cloud_pos);
-        if (getpos(&cc, TRUE, "the desired position") < 0) {
-            pline1(Never_mind);
-            break;
-        }
-        if (!is_valid_stinking_cloud_pos(cc.x, cc.y, TRUE))
-            break;
-        (void) create_gas_cloud(cc.x, cc.y, 3 + bcsign(sobj),
-                                8 + 4 * bcsign(sobj));
+    case SCR_STINKING_CLOUD:
+        seffect_stinking_cloud(&sobj);
         break;
-    }
     default:
         impossible("What weird effect is this? (%u)", otyp);
     }