From: PatR Date: Wed, 9 Mar 2022 15:06:37 +0000 (-0800) Subject: divine gift of spell knowledge X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e3490743e071f18bda26c21f43a4c57240a7d79a;p=nethack divine gift of spell knowledge 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 quite well already" and the book would stay undiscovered even though you were just told what spell it's for. --- diff --git a/include/extern.h b/include/extern.h index 8a4535b7d..62a8d0298 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 ### */ diff --git a/include/spell.h b/include/spell.h index 095e6605d..c3179b36d 100644 --- a/include/spell.h +++ b/include/spell.h @@ -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 diff --git a/src/apply.c b/src/apply.c index 9e8ef6bf1..086f9b9d8 100644 --- a/src/apply.c +++ b/src/apply.c @@ -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 " won't move" message */ diff --git a/src/pray.c b/src/pray.c index 65d45eaec..c5ae676df 100644 --- a/src/pray.c +++ b/src/pray.c @@ -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 " */ + 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; diff --git a/src/spell.c b/src/spell.c index ad4aee725..60f4c5b15 100644 --- a/src/spell.c +++ b/src/spell.c @@ -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 */ diff --git a/src/teleport.c b/src/teleport.c index 37bbcdef9..158bd9778 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -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; } }