diff --git a/doc/fixes35.0 b/doc/fixes35.0 index d566d4e85..3668be89f 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -176,6 +176,7 @@ some intelligent pets will avoid cannibalism keep track of which monsters were cloned from other monsters number_pad:3 run-time option to use inverted phone keypad layout for movement number_pad:-1 to swap function of y and z keys; z to move NW, y to zap wands +display spell retention information in the spell menu Platform- and/or Interface-Specific New Features diff --git a/src/spell.c b/src/spell.c index e85abb6b7..b161c78c9 100644 --- a/src/spell.c +++ b/src/spell.c @@ -1,4 +1,4 @@ -/* 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. */ @@ -8,8 +8,16 @@ #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)]) @@ -24,6 +32,7 @@ STATIC_PTR int NDECL(learn); 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)); @@ -358,14 +367,15 @@ learn() 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 */ @@ -383,7 +393,7 @@ learn() } 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); @@ -584,22 +594,24 @@ getspell(spell_no) 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", @@ -1086,7 +1098,8 @@ int *spell_no; { winid tmpwin; int i, n, how; - char buf[BUFSZ]; + char buf[BUFSZ], retentionbuf[24]; + const char *fmt; menu_item *selected; anything any; @@ -1095,26 +1108,27 @@ int *spell_no; 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, @@ -1278,28 +1292,70 @@ int spell; 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; }