OSDN Git Service

update year to 2022
[jnethack/source.git] / src / spell.c
index 752c26d..9aa580d 100644 (file)
@@ -1,10 +1,10 @@
-/* NetHack 3.6 spell.c $NHDT-Date: 1447653429 2015/11/16 05:57:09 $  $NHDT-Branch: master $:$NHDT-Revision: 1.72 $ */
+/* NetHack 3.6 spell.c $NHDT-Date: 1546565814 2019/01/04 01:36:54 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.88 $ */
 /*      Copyright (c) M. Stephenson 1988                          */
 /* NetHack may be freely redistributed.  See license for details. */
 
 /* JNetHack Copyright */
 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000  */
-/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016            */
+/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2022            */
 /* JNetHack may be freely redistributed.  See license for details. */
 
 #include "hack.h"
@@ -48,6 +48,7 @@ STATIC_DCL int NDECL(throwspell);
 STATIC_DCL void NDECL(cast_protection);
 STATIC_DCL void FDECL(spell_backfire, (int));
 STATIC_DCL const char *FDECL(spelltypemnemonic, (int));
+STATIC_DCL boolean FDECL(spell_aim_step, (genericptr_t, int, int));
 
 /* The roles[] table lists the role-specific values for tuning
  * percent_success().
@@ -127,6 +128,7 @@ STATIC_OVL boolean
 cursed_book(bp)
 struct obj *bp;
 {
+    boolean was_in_use;
     int lev = objects[bp->otyp].oc_level;
     int dmg = 0;
 
@@ -164,7 +166,7 @@ struct obj *bp;
 */
         pline("\82±\82Ì\96{\82Í\90Ú\90G\8c^\82Ì\93Å\82Å\95¢\82í\82ê\82Ä\82¢\82é\81I");
         if (uarmg) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             erode_obj(uarmg, "gloves", ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
 #else
             erode_obj(uarmg, "\8f¬\8eè", ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
@@ -172,16 +174,17 @@ struct obj *bp;
             break;
         }
         /* temp disable in_use; death should not destroy the book */
+        was_in_use = bp->in_use;
         bp->in_use = FALSE;
         losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3));
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook",
                KILLED_BY_AN);
 #else
         losehp(rnd(Poison_resistance ? 6 : 10), "\90Ú\90G\93Å\82Ì\96\82\96@\8f\91\82Å",
                KILLED_BY_AN);
 #endif
-        bp->in_use = TRUE;
+        bp->in_use = was_in_use;
         break;
     case 6:
         if (Antimagic) {
@@ -235,7 +238,7 @@ struct obj *spellbook;
         useup(spellbook);
         gone = TRUE;
     } else {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         You("find yourself reading the %s line over and over again.",
             spellbook == context.spbook.book ? "next" : "first");
 #else
@@ -406,6 +409,17 @@ struct obj *book2;
     return;
 }
 
+/* 'book' has just become cursed; if we're reading it and realize it is
+   now cursed, interrupt */
+void
+book_cursed(book)
+struct obj *book;
+{
+    if (occupation == learn && context.spbook.book == book
+        && book->cursed && book->bknown && multi >= 0)
+        stop_occupation();
+}
+
 STATIC_PTR int
 learn(VOID_ARGS)
 {
@@ -423,14 +437,16 @@ learn(VOID_ARGS)
         context.spbook.book = 0; /* no longer studying */
         context.spbook.o_id = 0;
         nomul(context.spbook.delay); /* remaining delay is uninterrupted */
+/*JP
         multi_reason = "reading a book";
+*/
+        multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
         nomovemsg = 0;
         context.spbook.delay = 0;
         return 0;
     }
-    if (context
-            .spbook.delay) { /* not if (context.spbook.delay++), so at end
-                                delay == 0 */
+    if (context.spbook.delay) {
+        /* not if (context.spbook.delay++), so at end delay == 0 */
         context.spbook.delay++;
         return 1; /* still busy */
     }
@@ -473,7 +489,7 @@ learn(VOID_ARGS)
             You("\82·\82Å\82É%s\82ð\8fn\92m\82µ\82Ä\82¢\82é\81D", splname);
             costly = FALSE;
         } else { /* spellknow(i) <= KEEN/10 */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             Your("knowledge of %s is %s.", splname,
                  spellknow(i) ? "keener" : "restored");
 #else
@@ -537,7 +553,7 @@ register struct obj *spellbook;
     boolean too_hard = FALSE;
 
     /* attempting to read dull book may make hero fall asleep */
-    if (!confused && booktype != SPE_BLANK_PAPER
+    if (!confused && !Sleep_resistance
 /*JP
         && !strcmp(OBJ_DESCR(objects[booktype]), "dull")) {
 */
@@ -568,7 +584,7 @@ register struct obj *spellbook;
         /* handle the sequence: start reading, get interrupted, have
            context.spbook.book become erased somehow, resume reading it */
         && booktype != SPE_BLANK_PAPER) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         You("continue your efforts to %s.",
             (booktype == SPE_NOVEL) ? "read the novel" : "memorize the spell");
 #else
@@ -586,7 +602,7 @@ register struct obj *spellbook;
             return 1;
         }
 
-        /* 3.6.0 tribute */
+        /* 3.6 tribute */
         if (booktype == SPE_NOVEL) {
             /* Obtain current Terry Pratchett book title */
             const char *tribtitle = noveltitle(&spellbook->novelidx);
@@ -595,6 +611,7 @@ register struct obj *spellbook;
                              spellbook->o_id)) {
                 u.uconduct.literate++;
                 check_unpaid(spellbook);
+                makeknown(booktype);
                 if (!u.uevent.read_tribute) {
                     /* give bonus of 20 xp and 4*20+0 pts */
                     more_experienced(20, 0);
@@ -636,19 +653,21 @@ register struct obj *spellbook;
                 too_hard = TRUE;
             } else {
                 /* uncursed - chance to fail */
-                int read_ability =
-                    ACURR(A_INT) + 4 + u.ulevel / 2
-                    - 2 * objects[booktype].oc_level
-                    + ((ublindf && ublindf->otyp == LENSES) ? 2 : 0);
+                int read_ability = ACURR(A_INT) + 4 + u.ulevel / 2
+                                   - 2 * objects[booktype].oc_level
+                             + ((ublindf && ublindf->otyp == LENSES) ? 2 : 0);
+
                 /* only wizards know if a spell is too difficult */
                 if (Role_if(PM_WIZARD) && read_ability < 20 && !confused) {
                     char qbuf[QBUFSZ];
+
+#if 0 /*JP:T*/
                     Sprintf(qbuf,
-#if 0 /*JP*/
-                     "This spellbook is %sdifficult to comprehend. Continue?",
+                    "This spellbook is %sdifficult to comprehend.  Continue?",
                             (read_ability < 12 ? "very " : ""));
 #else
-                     "\82±\82Ì\96\82\96@\8f\91\82ð\97\9d\89ð\82·\82é\82Ì\82Í%s\8d¢\93ï\82¾\81D\91±\82¯\82Ü\82·\82©\81H",
+                    Sprintf(qbuf,
+                    "\82±\82Ì\96\82\96@\8f\91\82ð\97\9d\89ð\82·\82é\82Ì\82Í%s\8d¢\93ï\82¾\81D\91±\82¯\82Ü\82·\82©\81H",
                             (read_ability < 12 ? "\82Æ\82Ä\82à" : ""));
 #endif
                     if (yn(qbuf) != 'y') {
@@ -667,7 +686,10 @@ register struct obj *spellbook;
             boolean gone = cursed_book(spellbook);
 
             nomul(context.spbook.delay); /* study time */
+/*JP
             multi_reason = "reading a book";
+*/
+            multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
             nomovemsg = 0;
             context.spbook.delay = 0;
             if (gone || !rn2(3)) {
@@ -688,14 +710,17 @@ register struct obj *spellbook;
                 spellbook->in_use = FALSE;
             }
             nomul(context.spbook.delay);
+/*JP
             multi_reason = "reading a book";
+*/
+            multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
             nomovemsg = 0;
             context.spbook.delay = 0;
             return 1;
         }
         spellbook->in_use = FALSE;
 
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         You("begin to %s the runes.",
             spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" : "memorize");
 #else
@@ -767,7 +792,13 @@ rejectcasting()
 /*JP
         You("are too impaired to cast a spell.");
 */
-        You("\96\82\96@\82ð\8f¥\82¦\82ç\82ê\82È\82¢\81D");
+        You("\82­\82ç\82­\82ç\82µ\82Ä\82¢\82Ä\96\82\96@\82ð\8eg\82¦\82È\82¢\81D");
+        return TRUE;
+    } else if (!can_chant(&youmonst)) {
+/*JP
+        You("are unable to chant the incantation.");
+*/
+        You("\8eô\95\82ð\8f¥\82¦\82é\82±\82Æ\82ª\82Å\82«\82È\82¢\81D");
         return TRUE;
     } else if (!freehand()) {
         /* Note: !freehand() occurs when weapon and shield (or two-handed
@@ -990,9 +1021,9 @@ cast_protection()
                                          : "\82Ë\82Î\82Ë\82Î")
                                 : (u.uinwater
 /*JP
-                                   ? "water"
+                                   ? hliquid("water")
 */
-                                   ? "\90\85"
+                                   ? hliquid("\90\85")
                                    : (rmtyp == CLOUD)
 /*JP
                                       ? "cloud"
@@ -1000,7 +1031,7 @@ cast_protection()
                                       ? "\89_"
                                       : IS_TREE(rmtyp)
 /*JP
-                                         ? "vegitation"
+                                         ? "vegetation"
 */
                                          ? "\96Ø"
                                          : IS_STWALL(rmtyp)
@@ -1012,7 +1043,7 @@ cast_protection()
                                             : "air");
 */
                                             : "\8bó\8bC");
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 pline_The("%s around you begins to shimmer with %s haze.",
                           atmosphere, an(hgolden));
 #else
@@ -1066,8 +1097,8 @@ int spell;
         break;
     case 7:
     case 8:
-        make_stunned(old_conf + 2L * duration / 3L, FALSE); /* 20% */
-        make_confused(old_stun + duration / 3L, FALSE);
+        make_stunned(old_stun + 2L * duration / 3L, FALSE); /* 20% */
+        make_confused(old_conf + duration / 3L, FALSE);
         break;
     case 9:
         make_stunned(old_stun + duration, FALSE); /* 10% */
@@ -1082,7 +1113,7 @@ int spell;
 boolean atme;
 {
     int energy, damage, chance, n, intell;
-    int skill, role_skill;
+    int otyp, skill, role_skill, res = 0;
     boolean confused = (Confusion != 0);
     boolean physical_damage = FALSE;
     struct obj *pseudo;
@@ -1137,6 +1168,10 @@ boolean atme;
 */
         Your("\82±\82Ì\96\82\96@\82É\8aÖ\82·\82é\92m\8e¯\82ª\8f\99\81X\82É\94\96\82ê\82Ä\82«\82½\81D");
     }
+    /*
+     *  Note: dotele() also calculates energy use and checks nutrition
+     *  and strength requirements; it any of these change, update it too.
+     */
     energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */
 
     if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) {
@@ -1159,19 +1194,35 @@ boolean atme;
         return 1;
     }
 
-    if (u.uhave.amulet) {
+    /* if the cast attempt is already going to fail due to insufficient
+       energy (ie, u.uen < energy), the Amulet's drain effect won't kick
+       in and no turn will be consumed; however, when it does kick in,
+       the attempt may fail due to lack of energy after the draining, in
+       which case a turn will be used up in addition to the energy loss */
+    if (u.uhave.amulet && u.uen >= energy) {
 /*JP
         You_feel("the amulet draining your energy away.");
 */
         pline("\96\82\8f\9c\82¯\82ª\82 \82È\82½\82Ì\83G\83l\83\8b\83M\81[\82ð\8bz\82¢\82Æ\82Á\82Ä\82¢\82é\82æ\82¤\82È\8bC\82ª\82µ\82½\81D");
-        energy += rnd(2 * energy);
+        /* this used to be 'energy += rnd(2 * energy)' (without 'res'),
+           so if amulet-induced cost was more than u.uen, nothing
+           (except the "don't have enough energy" message) happened
+           and player could just try again (and again and again...);
+           now we drain some energy immediately, which has a
+           side-effect of not increasing the hunger aspect of casting */
+        u.uen -= rnd(2 * energy);
+        if (u.uen < 0)
+            u.uen = 0;
+        context.botl = 1;
+        res = 1; /* time is going to elapse even if spell doesn't get cast */
     }
+
     if (energy > u.uen) {
 /*JP
         You("don't have enough energy to cast that spell.");
 */
         pline("\96\82\96@\82ð\8f¥\82¦\82é\82¾\82¯\82Ì\8f\\95ª\82È\83G\83l\83\8b\83M\81[\82ª\82È\82¢\81D");
-        return 0;
+        return res;
     } else {
         if (spellid(spell) != SPE_DETECT_FOOD) {
             int hungr = energy * 2;
@@ -1245,10 +1296,11 @@ boolean atme;
      * Find the skill the hero has in a spell type category.
      * See spell_skilltype for categories.
      */
-    skill = spell_skilltype(pseudo->otyp);
+    otyp = pseudo->otyp;
+    skill = spell_skilltype(otyp);
     role_skill = P_SKILL(skill);
 
-    switch (pseudo->otyp) {
+    switch (otyp) {
     /*
      * At first spells act as expected.  As the hero increases in skill
      * with the appropriate spell type, some spells increase in their
@@ -1277,9 +1329,9 @@ boolean atme;
                         }
                     } else {
                         explode(u.dx, u.dy,
-                                pseudo->otyp - SPE_MAGIC_MISSILE + 10,
+                                otyp - SPE_MAGIC_MISSILE + 10,
                                 spell_damage_bonus(u.ulevel / 2 + 1), 0,
-                                (pseudo->otyp == SPE_CONE_OF_COLD)
+                                (otyp == SPE_CONE_OF_COLD)
                                    ? EXPL_FROSTY
                                    : EXPL_FIERY);
                     }
@@ -1294,12 +1346,13 @@ boolean atme;
                 }
             }
             break;
-        } /* else fall through... */
+        } /* else */
+        /*FALLTHRU*/
 
     /* these spells are all duplicates of wand effects */
     case SPE_FORCE_BOLT:
         physical_damage = TRUE;
-    /* fall through */
+    /*FALLTHRU*/
     case SPE_SLEEP:
     case SPE_MAGIC_MISSILE:
     case SPE_KNOCK:
@@ -1317,7 +1370,13 @@ boolean atme;
     case SPE_EXTRA_HEALING:
     case SPE_DRAIN_LIFE:
     case SPE_STONE_TO_FLESH:
-        if (!(objects[pseudo->otyp].oc_dir == NODIR)) {
+        if (objects[otyp].oc_dir != NODIR) {
+            if (otyp == SPE_HEALING || otyp == SPE_EXTRA_HEALING) {
+                /* healing and extra healing are actually potion effects,
+                   but they've been extended to take a direction like wands */
+                if (role_skill >= P_SKILLED)
+                    pseudo->blessed = 1;
+            }
             if (atme) {
                 u.dx = u.dy = u.dz = 0;
             } else if (!getdir((char *) 0)) {
@@ -1367,7 +1426,7 @@ boolean atme;
         /* high skill yields effect equivalent to blessed scroll */
         if (role_skill >= P_SKILLED)
             pseudo->blessed = 1;
-    /* fall through */
+    /*FALLTHRU*/
     case SPE_CHARM_MONSTER:
     case SPE_MAGIC_MAPPING:
     case SPE_CREATE_MONSTER:
@@ -1383,10 +1442,11 @@ boolean atme;
         /* high skill yields effect equivalent to blessed potion */
         if (role_skill >= P_SKILLED)
             pseudo->blessed = 1;
-    /* fall through */
+    /*FALLTHRU*/
     case SPE_INVISIBILITY:
         (void) peffects(pseudo);
         break;
+    /* end of potion-like spells */
 
     case SPE_CURE_BLINDNESS:
         healup(0, 0, FALSE, TRUE);
@@ -1408,10 +1468,12 @@ boolean atme;
         (void) make_familiar((struct obj *) 0, u.ux, u.uy, FALSE);
         break;
     case SPE_CLAIRVOYANCE:
-        if (!BClairvoyant)
-            do_vicinity_map();
+        if (!BClairvoyant) {
+            if (role_skill >= P_SKILLED)
+                pseudo->blessed = 1; /* detect monsters as well as map */
+            do_vicinity_map(pseudo);
         /* at present, only one thing blocks clairvoyance */
-        else if (uarmh && uarmh->otyp == CORNUTHAUM)
+        else if (uarmh && uarmh->otyp == CORNUTHAUM)
 /*JP
             You("sense a pointy hat on top of your %s.", body_part(HEAD));
 */
@@ -1437,16 +1499,30 @@ boolean atme;
     return 1;
 }
 
+/*ARGSUSED*/
+STATIC_OVL boolean
+spell_aim_step(arg, x, y)
+genericptr_t arg UNUSED;
+int x, y;
+{
+    if (!isok(x,y))
+        return FALSE;
+    if (!ZAP_POS(levl[x][y].typ)
+        && !(IS_DOOR(levl[x][y].typ) && (levl[x][y].doormask & D_ISOPEN)))
+        return FALSE;
+    return TRUE;
+}
+
 /* Choose location where spell takes effect. */
 STATIC_OVL int
 throwspell()
 {
-    coord cc;
+    coord cc, uc;
     struct monst *mtmp;
 
     if (u.uinwater) {
 /*JP
-        pline("You're joking! In this weather?");
+        pline("You're joking!  In this weather?");
 */
         pline("\90\85\92\86\82Å\89½\82ð\82µ\82æ\82¤\82Á\82Ä\82¢\82¤\82ñ\82¾\82¢\81H");
         return 0;
@@ -1495,11 +1571,72 @@ throwspell()
         return 0;
     }
 
+    uc.x = u.ux;
+    uc.y = u.uy;
+
+    walk_path(&uc, &cc, spell_aim_step, (genericptr_t) 0);
+
     u.dx = cc.x;
     u.dy = cc.y;
     return 1;
 }
 
+/* add/hide/remove/unhide teleport-away on behalf of dotelecmd() to give
+   more control to behavior of ^T when used in wizard mode */
+int
+tport_spell(what)
+int what;
+{
+    static struct tport_hideaway {
+        struct spell savespell;
+        int tport_indx;
+    } save_tport;
+    int i;
+/* also defined in teleport.c */
+#define NOOP_SPELL  0
+#define HIDE_SPELL  1
+#define ADD_SPELL   2
+#define UNHIDESPELL 3
+#define REMOVESPELL 4
+
+    for (i = 0; i < MAXSPELL; i++)
+        if (spellid(i) == SPE_TELEPORT_AWAY || spellid(i) == NO_SPELL)
+            break;
+    if (i == MAXSPELL) {
+        impossible("tport_spell: spellbook full");
+        /* wizard mode ^T is not able to honor player's menu choice */
+    } else if (spellid(i) == NO_SPELL) {
+        if (what == HIDE_SPELL || what == REMOVESPELL) {
+            save_tport.tport_indx = MAXSPELL;
+        } else if (what == UNHIDESPELL) {
+            /*assert( save_tport.savespell.sp_id == SPE_TELEPORT_AWAY );*/
+            spl_book[save_tport.tport_indx] = save_tport.savespell;
+            save_tport.tport_indx = MAXSPELL; /* burn bridge... */
+        } else if (what == ADD_SPELL) {
+            save_tport.savespell = spl_book[i];
+            save_tport.tport_indx = i;
+            spl_book[i].sp_id = SPE_TELEPORT_AWAY;
+            spl_book[i].sp_lev = objects[SPE_TELEPORT_AWAY].oc_level;
+            spl_book[i].sp_know = KEEN;
+            return REMOVESPELL; /* operation needed to reverse */
+        }
+    } else { /* spellid(i) == SPE_TELEPORT_AWAY */
+        if (what == ADD_SPELL || what == UNHIDESPELL) {
+            save_tport.tport_indx = MAXSPELL;
+        } else if (what == REMOVESPELL) {
+            /*assert( i == save_tport.tport_indx );*/
+            spl_book[i] = save_tport.savespell;
+            save_tport.tport_indx = MAXSPELL;
+        } else if (what == HIDE_SPELL) {
+            save_tport.savespell = spl_book[i];
+            save_tport.tport_indx = i;
+            spl_book[i].sp_id = NO_SPELL;
+            return UNHIDESPELL; /* operation needed to reverse */
+        }
+    }
+    return NOOP_SPELL;
+}
+
 /* forget a random selection of known spells due to amnesia;
    they used to be lost entirely, as if never learned, but now we
    just set the memory retention to zero so that they can't be cast */
@@ -1582,53 +1719,58 @@ losespells()
  *      are learned, they get inserted into sorted order rather than be
  *      appended to the end of the list?
  */
-static const char *spl_sortchoices[] = {
+enum spl_sort_types {
+    SORTBY_LETTER = 0,
+    SORTBY_ALPHA,
+    SORTBY_LVL_LO,
+    SORTBY_LVL_HI,
+    SORTBY_SKL_AL,
+    SORTBY_SKL_LO,
+    SORTBY_SKL_HI,
+    SORTBY_CURRENT,
+    SORTRETAINORDER,
+
+    NUM_SPELL_SORTBY
+};
+
+static const char *spl_sortchoices[NUM_SPELL_SORTBY] = {
 /*JP
     "by casting letter",
 */
     "\91I\91ð\82·\82é\95\8e\9a\8f\87",
-#define SORTBY_LETTER 0
 /*JP
     "alphabetically",
 */
     "\95\8e\9a\83R\81[\83h\8f\87",
-#define SORTBY_ALPHA 1
 /*JP
     "by level, low to high",
 */
     "\83\8c\83x\83\8b\8f¸\8f\87",
-#define SORTBY_LVL_LO 2
 /*JP
     "by level, high to low",
 */
     "\83\8c\83x\83\8b\8d~\8f\87",
-#define SORTBY_LVL_HI 3
 /*JP
     "by skill group, alphabetized within each group",
 */
     "\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\95\8e\9a\83R\81[\83h\8f\87",
-#define SORTBY_SKL_AL 4
 /*JP
     "by skill group, low to high level within group",
 */
     "\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\83\8c\83x\83\8b\8f¸\8f\87",
-#define SORTBY_SKL_LO 5
 /*JP
     "by skill group, high to low level within group",
 */
     "\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\83\8c\83x\83\8b\8d~\8f\87",
-#define SORTBY_SKL_HI 6
 /*JP
     "maintain current ordering",
 */
     "\8c»\8dÝ\82Ì\8f\87\8f\98\82ð\88Û\8e\9d",
-#define SORTBY_CURRENT 7
     /* a menu choice rather than a sort choice */
 /*JP
     "reassign casting letters to retain current order",
 */
     "\8c»\8dÝ\82Ì\8f\87\8f\98\82ð\95Ï\82¦\82¸\82É\91I\91ð\82·\82é\95\8e\9a\82ð\8dÄ\8a\84\82è\93\96\82Ä",
-#define SORTRETAINORDER 8
 };
 static int spl_sortmode = 0;   /* index into spl_sortchoices[] */
 static int *spl_orderindx = 0; /* array of spl_book[] indices */
@@ -1854,11 +1996,11 @@ int *spell_no;
      * given string and are of the form "a - ".
      */
     if (!iflags.menu_tab_sep) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         Sprintf(buf, "%-20s     Level %-12s Fail Retention", "    Name",
                 "Category");
 #else
-        Sprintf(buf, "%-20s     Level  %-12s \90¬\8c÷\97¦", "    Name", "\95ª\97Þ");
+        Sprintf(buf, "%-20s     Level  %-12s \90¬\8c÷\97¦", "    \96¼\91O", "\95ª\97Þ");
 #endif
         fmt = "%-20s  %2d   %-12s %3d%% %9s";
     } else {
@@ -2048,7 +2190,10 @@ char *outbuf;
 
     if (turnsleft < 1L) {
         /* spell has expired; hero can't successfully cast it anymore */
+/*JP
         Strcpy(outbuf, "(gone)");
+*/
+        Strcpy(outbuf, "(\96Y\8bp)");
     } else if (turnsleft >= (long) KEEN) {
         /* full retention, first turn or immediately after reading book */
         Strcpy(outbuf, "100%");