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; } }