-/* SCCS Id: @(#)spell.c 3.5 2006/02/03 */
+/* SCCS Id: @(#)spell.c 3.5 2006/02/10 */
/* Copyright (c) M. Stephenson 1988 */
/* NetHack may be freely redistributed. See license for details. */
#define SPELLMENU_CAST (-2)
#define SPELLMENU_VIEW (-1)
+/* spell retention period, in turns; at 10% of this value, player becomes
+ eligible to reread the spellbook and regain 100% retention (the threshold
+ used to be 1000 turns, which was 10% of the original 10000 turn retention
+ period but didn't get adjusted when that period got doubled to 20000) */
#define KEEN 20000
-#define incrnknow(spell) spl_book[spell].sp_know = KEEN
+/* x: need to add 1 when used for reading a spellbook rather than for hero
+ initialization; spell memory is decremented at the end of each turn,
+ including the turn on which the spellbook is read; without the extra
+ increment, the hero used to get cheated out of 1 turn of retention */
+#define incrnknow(spell,x) (spl_book[spell].sp_know = KEEN + (x))
#define spellev(spell) spl_book[spell].sp_lev
#define spellname(spell) OBJ_NAME(objects[spellid(spell)])
STATIC_DCL boolean FDECL(getspell, (int *));
STATIC_DCL boolean FDECL(dospellmenu, (const char *,int,int *));
STATIC_DCL int FDECL(percent_success, (int));
+STATIC_DCL char *FDECL(spellretention, (int,char *));
STATIC_DCL int NDECL(throwspell);
STATIC_DCL void NDECL(cast_protection);
STATIC_DCL void FDECL(spell_backfire, (int));
book->otyp = booktype = SPE_BLANK_PAPER;
/* reset spestudied as if polymorph had taken place */
book->spestudied = rn2(book->spestudied);
- } else if (spellknow(i) <= 1000) {
- Your("knowledge of %s is keener.", splname);
- incrnknow(i);
- book->spestudied++;
- exercise(A_WIS,TRUE); /* extra study */
- } else { /* 1000 < spellknow(i) <= KEEN */
+ } else if (spellknow(i) > KEEN / 10) {
You("know %s quite well already.", splname);
costly = FALSE;
+ } else { /* spellknow(i) <= KEEN/10 */
+ Your("knowledge of %s is %s.", splname,
+ spellknow(i) ? "keener" : "restored");
+ incrnknow(i, 1);
+ book->spestudied++;
+ exercise(A_WIS,TRUE); /* extra study */
}
/* make book become known even when spell is already
known, in case amnesia made you forget the book */
} else {
spl_book[i].sp_id = booktype;
spl_book[i].sp_lev = objects[booktype].oc_level;
- incrnknow(i);
+ incrnknow(i, 1);
book->spestudied++;
You(i > 0 ? "add %s to your repertoire." : "learn %s.",
splname);
if (nspells == 1) Strcpy(lets, "a");
else if (nspells < 27) Sprintf(lets, "a-%c", 'a' + nspells - 1);
else if (nspells == 27) Sprintf(lets, "a-zA");
+ /* this assumes that there are at most 52 spells... */
else Sprintf(lets, "a-zA-%c", 'A' + nspells - 27);
- for(;;) {
- Sprintf(qbuf, "Cast which spell? [%s ?]", lets);
- if ((ilet = yn_function(qbuf, (char *)0, '\0')) == '?')
- break;
-
+ for (;;) {
+ Sprintf(qbuf, "Cast which spell? [%s *?]", lets);
+ ilet = yn_function(qbuf, (char *)0, '\0');
+ if (ilet == '*' || ilet == '?')
+ break; /* use menu mode */
if (index(quitchars, ilet))
return FALSE;
idx = spell_let_to_idx(ilet);
- if (idx >= 0 && idx < nspells) {
- *spell_no = idx;
- return TRUE;
- } else
+ if (idx < 0 || idx >= nspells) {
You("don't know that spell.");
+ continue; /* ask again */
+ }
+ *spell_no = idx;
+ return TRUE;
}
}
return dospellmenu("Choose which spell to cast",
{
winid tmpwin;
int i, n, how;
- char buf[BUFSZ];
+ char buf[BUFSZ], retentionbuf[24];
+ const char *fmt;
menu_item *selected;
anything any;
any.a_void = 0; /* zero out all bits */
/*
- * The correct spacing of the columns depends on the
- * following that (1) the font is monospaced and (2)
- * that selection letters are pre-pended to the given
- * string and are of the form "a - ".
- *
- * To do it right would require that we implement columns
- * in the window-ports (say via a tab character).
+ * The correct spacing of the columns when not using
+ * tab separation depends on the following:
+ * (1) that the font is monospaced, and
+ * (2) that selection letters are pre-pended to the
+ * given string and are of the form "a - ".
*/
- if (!iflags.menu_tab_sep)
- Sprintf(buf, "%-20s Level %-12s Fail", " Name", "Category");
- else
- Sprintf(buf, "Name\tLevel\tCategory\tFail");
+ if (!iflags.menu_tab_sep) {
+ Sprintf(buf, "%-20s Level %-12s Fail Retention",
+ " Name", "Category");
+ fmt = "%-20s %2d %-12s %3d%% %9s";
+ } else {
+ Sprintf(buf, "Name\tLevel\tCategory\tFail\tRetention");
+ fmt = "%s\t%-d\t%s\t%-d%%\t%s";
+ }
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_BOLD, buf, MENU_UNSELECTED);
for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) {
- Sprintf(buf, iflags.menu_tab_sep ?
- "%s\t%-d%s\t%s\t%-d%%" : "%-20s %2d%s %-12s %3d%%",
+ Sprintf(buf, fmt,
spellname(i), spellev(i),
- spellknow(i) ? " " : "*",
spelltypemnemonic(spell_skilltype(spellid(i))),
- 100 - percent_success(i));
+ 100 - percent_success(i),
+ spellretention(i, retentionbuf));
any.a_int = i+1; /* must be non-zero */
add_menu(tmpwin, NO_GLYPH, &any,
return chance;
}
+STATIC_OVL char *
+spellretention(idx, outbuf)
+int idx;
+char *outbuf;
+{
+ long turnsleft, percent, accuracy;
+ int skill;
+
+ skill = P_SKILL(spell_skilltype(spellid(idx)));
+ skill = max(skill, P_UNSKILLED); /* restricted same as unskilled */
+ turnsleft = spellknow(idx);
+ *outbuf = '\0'; /* lint suppression */
+
+ if (turnsleft < 1L) {
+ /* spell has expired; hero can't successfully cast it anymore */
+ Strcpy(outbuf, "(gone)");
+ } else if (turnsleft >= (long)KEEN) {
+ /* full retention, first turn or immediately after reading book */
+ Strcpy(outbuf, "100%");
+ } else {
+ /*
+ * Retention is displayed as a range of percentages of
+ * amount of time left until memory of the spell expires;
+ * the precision of the range depends upon hero's skill
+ * in this spell.
+ * expert: 2% intervals; 1-2, 3-4, ..., 99-100;
+ * skilled: 5% intervals; 1-5, 6-10, ..., 95-100;
+ * basic: 10% intervals; 1-10, 11-20, ..., 91-100;
+ * unskilled: 25% intervals; 1-25, 26-50, 51-75, 76-100.
+ *
+ * At the low end of each range, a value of N% really means
+ * (N-1)%+1 through N%; so 1% is "greater than 0, at most 200".
+ * 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;
+ /* round up to the high end of this range */
+ percent = accuracy * ((percent - 1L) / accuracy + 1L);
+ Sprintf(outbuf, "%ld%%-%ld%%", percent - accuracy + 1L, percent);
+ }
+ return outbuf;
+}
/* Learn a spell during creation of the initial inventory */
void
initialspell(obj)
struct obj *obj;
{
- int i;
+ int i, otyp = obj->otyp;
- for (i = 0; i < MAXSPELL; i++) {
- if (spellid(i) == obj->otyp) {
- pline("Error: Spell %s already known.",
- OBJ_NAME(objects[obj->otyp]));
- return;
- }
- if (spellid(i) == NO_SPELL) {
- spl_book[i].sp_id = obj->otyp;
- spl_book[i].sp_lev = objects[obj->otyp].oc_level;
- incrnknow(i);
- return;
- }
+ for (i = 0; i < MAXSPELL; i++)
+ if (spellid(i) == NO_SPELL || spellid(i) == otyp) break;
+
+ if (i == MAXSPELL) {
+ impossible("Too many spells memorized!");
+ } else if (spellid(i) != NO_SPELL) {
+ /* initial inventory shouldn't contain duplicate spellbooks */
+ impossible("Spell %s already known.", OBJ_NAME(objects[otyp]));
+ } else {
+ spl_book[i].sp_id = otyp;
+ spl_book[i].sp_lev = objects[otyp].oc_level;
+ incrnknow(i, 0);
}
- impossible("Too many spells memorized!");
return;
}