]> granicus.if.org Git - nethack/commitdiff
divine gift of spell knowledge
authorPatR <rankin@nethack.org>
Wed, 9 Mar 2022 15:06:37 +0000 (07:06 -0800)
committerPatR <rankin@nethack.org>
Wed, 9 Mar 2022 15:06:37 +0000 (07:06 -0800)
Remove the conduct-specific aspect of receiving spells as prayer boon.
Anyone now has a 25% chance of having the spell directly implanted
into their head, not just characters who have maintained illiterate
conduct.  It can now also restore a forgotten spell or refresh one
that is nearly forgotten.  It still tries to choose a spell which
isn't already known (new: or was known but has been forgotten) but if
it picks one that is known and doesn't need refreshing, a redundant
book will be given, same as the behavior in earlier versions.

The chance for receiving a blank spellbook is higher when that item
is undiscovered.  When given as a prayer reward, make it become
discovered even if hero doesn't read it so that it will be less likely
to be given again.  There's a 1% chance for that auto-discovery to
happen with other bestowed books.  Unlike blank boots, having the book
be discovered doesn't lessen their chance of being repeat gifts.

Minor bug fix:  for a spell implanted from scratch, the book remains
unknown.  That's ok; it's actually more interesting than discovering
a book you haven't seen yet.  But after acquiring and reading the book
you could get "you know <spell> quite well already" and the book would
stay undiscovered even though you were just told what spell it's for.

include/extern.h
include/spell.h
src/apply.c
src/pray.c
src/spell.c
src/teleport.c

index 8a4535b7dbf2de69160bc404072da574d57e9788..62a8d029802b753dd8331a3f6f5b49de1d9fe35a 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 extern.h        $NHDT-Date: 1646255373 2022/03/02 21:09:33 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1064 $ */
+/* NetHack 3.7 extern.h        $NHDT-Date: 1646838387 2022/03/09 15:06:27 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1068 $ */
 /* Copyright (c) Steve Creps, 1988.                              */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -2543,9 +2543,9 @@ extern int tport_spell(int);
 extern void losespells(void);
 extern int dovspell(void);
 extern void initialspell(struct obj *);
-extern boolean known_spell(short);
+extern int known_spell(short);
 extern int spell_idx(short);
-extern boolean force_learn_spell(short);
+extern char force_learn_spell(short);
 extern int num_spells(void);
 
 /* ### steal.c ### */
index 095e6605dca5e81ecb3a501163742ab4398b9b96..c3179b36d9dd1e48bbd809440e975a6348875aca 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 spell.h $NHDT-Date: 1596498560 2020/08/03 23:49:20 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.11 $ */
+/* NetHack 3.7 spell.h $NHDT-Date: 1646838388 2022/03/09 15:06:28 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.14 $ */
 /* Copyright 1986, M. Stephenson                                 */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -17,6 +17,13 @@ struct spell {
     int sp_know;  /* knowlege of spell */
 };
 
+enum spellknowledge {
+    spe_Forgotten  = -1, /* known but no longer castable */
+    spe_Unknown    =  0, /* not yet known */
+    spe_Fresh      =  1, /* castable if various casting criteria are met */
+    spe_GoingStale =  2  /* still castable but nearly forgotten */
+};
+
 /* levels of memory destruction with a scroll of amnesia */
 #define ALL_MAP 0x1
 #define ALL_SPELLS 0x2
index 9e8ef6bf1f673b9a20dfb45f7456d1eb5003c882..086f9b9d857fe97706c1cf4d98863bd4eb7f794c 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 apply.c $NHDT-Date: 1629242800 2021/08/17 23:26:40 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.347 $ */
+/* NetHack 3.7 apply.c $NHDT-Date: 1646838388 2022/03/09 15:06:28 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.369 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /*-Copyright (c) Robert Patrick Rankin, 2012. */
 /* NetHack may be freely redistributed.  See license for details. */
@@ -1756,7 +1756,7 @@ jump(int magic) /* 0=Physical, otherwise skill level */
     coord cc;
 
     /* attempt "jumping" spell if hero has no innate jumping ability */
-    if (!magic && !Jumping && known_spell(SPE_JUMPING))
+    if (!magic && !Jumping && known_spell(SPE_JUMPING) >= spe_Fresh)
         return spelleffects(SPE_JUMPING, FALSE);
 
     if (!magic && (nolimbs(g.youmonst.data) || slithy(g.youmonst.data))) {
@@ -1767,6 +1767,7 @@ jump(int magic) /* 0=Physical, otherwise skill level */
     } else if (!magic && !Jumping) {
         You_cant("jump very far.");
         return ECMD_OK;
+
     /* if steed is immobile, can't do physical jump but can do spell one */
     } else if (!magic && u.usteed && stucksteed(FALSE)) {
         /* stucksteed gave "<steed> won't move" message */
index 65d45eaecba4ddc8f62474c0a81f3be074e6b310..c5ae676df8fb33ada4eff7c5243846e602f6e79e 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 pray.c  $NHDT-Date: 1621208529 2021/05/16 23:42:09 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.147 $ */
+/* NetHack 3.7 pray.c  $NHDT-Date: 1646838389 2022/03/09 15:06:29 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.163 $ */
 /* Copyright (c) Benson I. Margulies, Mike Stephenson, Steve Linhart, 1989. */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -11,6 +11,7 @@ static void fix_worst_trouble(int);
 static void angrygods(aligntyp);
 static void at_your_feet(const char *);
 static void gcrownu(void);
+static void give_spell(void);
 static void pleased(aligntyp);
 static void godvoice(aligntyp, const char *);
 static void god_zaps_you(aligntyp);
@@ -838,7 +839,7 @@ gcrownu(void)
 
         /* when getting a new book for known spell, enhance
            currently wielded weapon rather than the book */
-        if (known_spell(class_gift) && ok_wep(uwep))
+        if (known_spell(class_gift) != spe_Unknown && ok_wep(uwep))
             obj = uwep; /* to be blessed,&c */
     }
 
@@ -938,6 +939,86 @@ gcrownu(void)
     return;
 }
 
+static void
+give_spell(void)
+{
+    struct obj *otmp;
+    char spe_let;
+    int spe_knowledge, trycnt = u.ulevel + 1;
+
+    /* not yet known spells and forgotten spells are given preference over
+       usable ones; also, try to grant spell that hero could gain skill in
+       (even though being restricted doesn't prevent learning and casting) */
+    otmp = mkobj(SPBOOK_no_NOVEL, TRUE);
+    while (--trycnt > 0) {
+        if (otmp->otyp != SPE_BLANK_PAPER) {
+            if (known_spell(otmp->otyp) <= spe_Unknown
+                && !P_RESTRICTED(spell_skilltype(otmp->otyp)))
+                break; /* forgotten or not yet known */
+        } else {
+            /* blank paper is acceptable if not discovered yet or
+               if hero has a magic marker to write something on it
+               (doesn't matter if marker is out of charges); it will
+               become discovered (below) without needing to be read */
+            if (!objects[SPE_BLANK_PAPER].oc_name_known
+                || carrying(MAGIC_MARKER))
+                break;
+        }
+        otmp->otyp = rnd_class(g.bases[SPBOOK_CLASS], SPE_BLANK_PAPER);
+    }
+    /*
+     * 25% chance of learning the spell directly instead of
+     * receiving the book for it, unless it's already well known.
+     * The chance is not influenced by whether hero is illiterate.
+     */
+    if (otmp->otyp != SPE_BLANK_PAPER && !rn2(4)
+        && (spe_knowledge = known_spell(otmp->otyp)) != spe_Fresh) {
+        /* force_learn_spell() should only return '\0' if the book
+           is blank paper or the spell is known and has retention
+           of spe_Fresh, so no 'else' case is needed here */
+        if ((spe_let = force_learn_spell(otmp->otyp)) != '\0') {
+            /* for spellbook class, OBJ_NAME() yields the name of
+               the spell rather than "spellbook of <spell-name>" */
+            const char *spe_name = OBJ_NAME(objects[otmp->otyp]);
+
+            if (spe_knowledge == spe_Unknown) /* prior to learning */
+                /* appending "spell 'a'" seems slightly silly but
+                   is similar to "added to your repertoire, as 'a'"
+                   and without any spellbook on hand a novice player
+                   might not recognize that 'spe_name' is a spell */
+                pline("Divine knowledge of %s fills your mind!  Spell '%c'.",
+                      spe_name, spe_let);
+            else
+                Your("knowledge of spell '%c' - %s is %s.",
+                     spe_let, spe_name,
+                     (spe_knowledge == spe_Forgotten) ? "restored"
+                                                      : "refreshed");
+        }
+        obfree(otmp, (struct obj *) 0); /* discard the book */
+    } else {
+        otmp->dknown = 1; /* not bknown */
+        /* discovering blank paper will make it less likely to
+           be given again; small chance to arbitrarily discover
+           some other book type without having to read it first */
+        if (otmp->otyp == SPE_BLANK_PAPER || !rn2(100))
+            makeknown(otmp->otyp);
+        bless(otmp);
+        /* note: Hallucination case can't happen because we only get
+           called for a boon and boons are only bestowed if all troubles
+           (including hallucination) have been cured/repaired; might
+           apply in variants that offer "always high" as a play option
+           and classify hallucinating as not trouble or not fixable */
+        at_your_feet(Hallucination ? "A thesarus"
+                     : Blind ? "A spellbook"
+                       /* "An orange spellbook" or "A spellbook of knock"
+                          depending on discoveries */
+                       : upstart(ansimpleoname(otmp)));
+        place_object(otmp, u.ux, u.uy);
+        newsym(u.ux, u.uy);
+    }
+    return;
+}
+
 static void
 pleased(aligntyp g_align)
 {
@@ -1204,39 +1285,9 @@ pleased(aligntyp g_align)
                 break;
             }
             /*FALLTHRU*/
-        case 6: {
-            struct obj *otmp;
-            int trycnt = u.ulevel + 1;
-
-            /* not yet known spells given preference over already known ones;
-               also, try to grant a spell for which there is a skill slot */
-            otmp = mkobj(SPBOOK_no_NOVEL, TRUE);
-            while (--trycnt > 0) {
-                if (otmp->otyp != SPE_BLANK_PAPER) {
-                    if (!known_spell(otmp->otyp)
-                        && !P_RESTRICTED(spell_skilltype(otmp->otyp)))
-                        break; /* usable, but not yet known */
-                } else {
-                    if ((!objects[SPE_BLANK_PAPER].oc_name_known
-                         || carrying(MAGIC_MARKER)) && u.uconduct.literate)
-                        break;
-                }
-                otmp->otyp = rnd_class(g.bases[SPBOOK_CLASS], SPE_BLANK_PAPER);
-            }
-            if (!u.uconduct.literate && (otmp->otyp != SPE_BLANK_PAPER)
-                && !known_spell(otmp->otyp)) {
-                if (force_learn_spell(otmp->otyp))
-                    pline("Divine knowledge of %s fills your mind!",
-                          OBJ_NAME(objects[otmp->otyp]));
-                obfree(otmp, (struct obj *) 0);
-            } else {
-                bless(otmp);
-                at_your_feet("A spellbook");
-                place_object(otmp, u.ux, u.uy);
-                newsym(u.ux, u.uy);
-            }
+        case 6:
+            give_spell();
             break;
-        }
         default:
             impossible("Confused deity!");
             break;
index ad4aee72503dca9b1146cf7f4d0713a9d6485e60..60f4c5b155157c1f4062c30a3b71a074d5bb37a3 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 spell.c $NHDT-Date: 1638499998 2021/12/03 02:53:18 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.120 $ */
+/* NetHack 3.7 spell.c $NHDT-Date: 1646838390 2022/03/09 15:06:30 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.131 $ */
 /*      Copyright (c) M. Stephenson 1988                          */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -538,7 +538,11 @@ study_book(register struct obj* spellbook)
             if (spellid(i) == booktype || spellid(i) == NO_SPELL)
                 break;
         if (spellid(i) == booktype && spellknow(i) > KEEN / 10) {
-            You("know \"%s\" quite well already.", OBJ_NAME(objects[booktype]));
+            You("know \"%s\" quite well already.",
+                OBJ_NAME(objects[booktype]));
+            /* hero has just been told what spell this book is for; it may
+               have been undiscovered if spell was learned via divine gift */
+            makeknown(booktype);
             if (yn("Refresh your memory anyway?") == 'n')
                 return 0;
         }
@@ -1668,7 +1672,7 @@ dospellmenu(
 {
     winid tmpwin;
     int i, n, how, splnum;
-    char buf[BUFSZ], retentionbuf[24];
+    char buf[BUFSZ], retentionbuf[24], sep;
     const char *fmt;
     menu_item *selected;
     anything any;
@@ -1685,13 +1689,18 @@ dospellmenu(
      * given string and are of the form "a - ".
      */
     if (!iflags.menu_tab_sep) {
-        Sprintf(buf, "%-20s     Level %-12s Fail Retention", "    Name",
-                "Category");
+        Sprintf(buf, "%-20s     Level %-12s Fail Retention",
+                "    Name", "Category");
         fmt = "%-20s  %2d   %-12s %3d%% %9s";
+        sep = ' ';
     } else {
         Sprintf(buf, "Name\tLevel\tCategory\tFail\tRetention");
         fmt = "%s\t%-d\t%s\t%-d%%\t%s";
+        sep = '\t';
     }
+    if (wizard)
+        Sprintf(eos(buf), "%c%6s", sep, "turns");
+
     add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0,
              iflags.menu_headings, buf, MENU_ITEMFLAGS_NONE);
     for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) {
@@ -1700,6 +1709,8 @@ dospellmenu(
                 spelltypemnemonic(spell_skilltype(spellid(splnum))),
                 100 - percent_success(splnum),
                 spellretention(splnum, retentionbuf));
+        if (wizard)
+            Sprintf(eos(buf), "%c%6d", sep, spellknow(i));
 
         any.a_int = splnum + 1; /* must be non-zero */
         add_menu(tmpwin, &nul_glyphinfo, &any, spellet(splnum), 0,
@@ -1898,10 +1909,10 @@ spellretention(int idx, char * outbuf)
          * KEEN is a multiple of 100; KEEN/100 loses no precision.
          */
         percent = (turnsleft - 1L) / ((long) KEEN / 100L) + 1L;
-        accuracy =
-            (skill == P_EXPERT) ? 2L : (skill == P_SKILLED)
-                                           ? 5L
-                                           : (skill == P_BASIC) ? 10L : 25L;
+        accuracy = (skill == P_EXPERT) ? 2L
+                   : (skill == P_SKILLED) ? 5L
+                     : (skill == P_BASIC) ? 10L
+                       : 25L;
         /* round up to the high end of this range */
         percent = accuracy * ((percent - 1L) / accuracy + 1L);
         Sprintf(outbuf, "%ld%%-%ld%%", percent - accuracy + 1L, percent);
@@ -1932,16 +1943,20 @@ initialspell(struct obj* obj)
     return;
 }
 
-/* return TRUE if hero knows spell otyp, FALSE otherwise */
-boolean
+/* returns one of spe_Unknown, spe_Fresh, spe_GoingStale, spe_Forgotten */
+int
 known_spell(short otyp)
 {
-    int i;
+    int i, k;
 
     for (i = 0; (i < MAXSPELL) && (spellid(i) != NO_SPELL); i++)
-        if (spellid(i) == otyp)
-            return TRUE;
-    return FALSE;
+        if (spellid(i) == otyp) {
+            k = spellknow(i);
+            return (k > KEEN / 10) ? spe_Fresh
+                   : (k > 0) ? spe_GoingStale
+                     : spe_Forgotten;
+        }
+    return spe_Unknown;
 }
 
 /* return index for spell otyp, or UNKNOWN_SPELL if not found */
@@ -1956,27 +1971,30 @@ spell_idx(short otyp)
     return UNKNOWN_SPELL;
 }
 
-/* forcibly learn spell otyp, if possible */
-boolean
+/* learn or refresh spell otyp, if feasible; return casting letter or '\0' */
+char
 force_learn_spell(short otyp)
 {
     int i;
 
-    if (known_spell(otyp))
-        return FALSE;
+    if (otyp == SPE_BLANK_PAPER || otyp == SPE_BOOK_OF_THE_DEAD
+        || known_spell(otyp) == spe_Fresh)
+        return '\0';
 
     for (i = 0; i < MAXSPELL; i++)
-        if (spellid(i) == NO_SPELL)
+        if (spellid(i) == NO_SPELL || spellid(i) == otyp)
             break;
-    if (i == MAXSPELL)
+    if (i == MAXSPELL) {
         impossible("Too many spells memorized");
-    else {
-        g.spl_book[i].sp_id = otyp;
-        g.spl_book[i].sp_lev = objects[otyp].oc_level;
-        incrnknow(i, 1);
-        return TRUE;
+        return '\0';
     }
-    return FALSE;
+    /* for a going-stale or forgotten spell the sp_id and sp_lev assignments
+       are redundant but harmless; for an unknown spell, they're essential */
+    g.spl_book[i].sp_id = otyp;
+    g.spl_book[i].sp_lev = objects[otyp].oc_level;
+    incrnknow(i, 0); /* set spl_book[i].sp_know to KEEN; unlike when learning
+                      * a spell by reading its book, we don't need to add 1 */
+    return spellet(i);
 }
 
 /* number of spells hero knows */
index 37bbcdef9a82e45a5de8d685ca74cf95ee6926c9..158bd9778c71b9288ac21d98545ff4c3019d3dbb 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.7 teleport.c      $NHDT-Date: 1605305493 2020/11/13 22:11:33 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.134 $ */
+/* NetHack 3.7 teleport.c      $NHDT-Date: 1646838392 2022/03/09 15:06:32 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.163 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /*-Copyright (c) Robert Patrick Rankin, 2011. */
 /* NetHack may be freely redistributed.  See license for details. */
@@ -713,16 +713,15 @@ dotele(
         if (!Teleportation || (u.ulevel < (Role_if(PM_WIZARD) ? 8 : 12)
                                && !can_teleport(g.youmonst.data))) {
             /* Try to use teleport away spell. */
-            boolean knownsp = known_spell(SPE_TELEPORT_AWAY);
+            int knownsp = known_spell(SPE_TELEPORT_AWAY);
 
             /* casting isn't inhibited by being Stunned (...it ought to be) */
-            castit = (knownsp && !Confusion);
+            castit = (knownsp >= spe_Fresh && !Confusion);
             if (!castit && !break_the_rules) {
-                You("%s.",
-                    !Teleportation ? (knownsp
-                                        ? "can't cast that spell"
-                                        : "don't know that spell")
-                                   : "are not able to teleport at will");
+                You("%s.", (!Teleportation ? ((knownsp != spe_Unknown)
+                                              ? "can't cast that spell"
+                                              : "don't know that spell")
+                            : "are not able to teleport at will"));
                 return 0;
             }
         }