-/* NetHack 3.6 spell.c $NHDT-Date: 1447653429 2015/11/16 05:57:09 $ $NHDT-Branch: master $:$NHDT-Revision: 1.72 $ */
+/* NetHack 3.6 spell.c $NHDT-Date: 1546565814 2019/01/04 01:36:54 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.88 $ */
/* Copyright (c) M. Stephenson 1988 */
/* NetHack may be freely redistributed. See license for details. */
/* JNetHack Copyright */
/* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
-/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016 */
+/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2022 */
/* JNetHack may be freely redistributed. See license for details. */
#include "hack.h"
STATIC_DCL void NDECL(cast_protection);
STATIC_DCL void FDECL(spell_backfire, (int));
STATIC_DCL const char *FDECL(spelltypemnemonic, (int));
+STATIC_DCL boolean FDECL(spell_aim_step, (genericptr_t, int, int));
/* The roles[] table lists the role-specific values for tuning
* percent_success().
cursed_book(bp)
struct obj *bp;
{
+ boolean was_in_use;
int lev = objects[bp->otyp].oc_level;
int dmg = 0;
*/
pline("\82±\82Ì\96{\82Í\90Ú\90G\8c^\82Ì\93Å\82Å\95¢\82í\82ê\82Ä\82¢\82é\81I");
if (uarmg) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
erode_obj(uarmg, "gloves", ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
#else
erode_obj(uarmg, "\8f¬\8eè", ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
break;
}
/* temp disable in_use; death should not destroy the book */
+ was_in_use = bp->in_use;
bp->in_use = FALSE;
losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3));
-#if 0 /*JP*/
+#if 0 /*JP:T*/
losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook",
KILLED_BY_AN);
#else
losehp(rnd(Poison_resistance ? 6 : 10), "\90Ú\90G\93Å\82Ì\96\82\96@\8f\91\82Å",
KILLED_BY_AN);
#endif
- bp->in_use = TRUE;
+ bp->in_use = was_in_use;
break;
case 6:
if (Antimagic) {
useup(spellbook);
gone = TRUE;
} else {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
You("find yourself reading the %s line over and over again.",
spellbook == context.spbook.book ? "next" : "first");
#else
return;
}
+/* 'book' has just become cursed; if we're reading it and realize it is
+ now cursed, interrupt */
+void
+book_cursed(book)
+struct obj *book;
+{
+ if (occupation == learn && context.spbook.book == book
+ && book->cursed && book->bknown && multi >= 0)
+ stop_occupation();
+}
+
STATIC_PTR int
learn(VOID_ARGS)
{
context.spbook.book = 0; /* no longer studying */
context.spbook.o_id = 0;
nomul(context.spbook.delay); /* remaining delay is uninterrupted */
+/*JP
multi_reason = "reading a book";
+*/
+ multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
nomovemsg = 0;
context.spbook.delay = 0;
return 0;
}
- if (context
- .spbook.delay) { /* not if (context.spbook.delay++), so at end
- delay == 0 */
+ if (context.spbook.delay) {
+ /* not if (context.spbook.delay++), so at end delay == 0 */
context.spbook.delay++;
return 1; /* still busy */
}
You("\82·\82Å\82É%s\82ð\8fn\92m\82µ\82Ä\82¢\82é\81D", splname);
costly = FALSE;
} else { /* spellknow(i) <= KEEN/10 */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
Your("knowledge of %s is %s.", splname,
spellknow(i) ? "keener" : "restored");
#else
boolean too_hard = FALSE;
/* attempting to read dull book may make hero fall asleep */
- if (!confused && booktype != SPE_BLANK_PAPER
+ if (!confused && !Sleep_resistance
/*JP
&& !strcmp(OBJ_DESCR(objects[booktype]), "dull")) {
*/
/* handle the sequence: start reading, get interrupted, have
context.spbook.book become erased somehow, resume reading it */
&& booktype != SPE_BLANK_PAPER) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
You("continue your efforts to %s.",
(booktype == SPE_NOVEL) ? "read the novel" : "memorize the spell");
#else
return 1;
}
- /* 3.6.0 tribute */
+ /* 3.6 tribute */
if (booktype == SPE_NOVEL) {
/* Obtain current Terry Pratchett book title */
const char *tribtitle = noveltitle(&spellbook->novelidx);
spellbook->o_id)) {
u.uconduct.literate++;
check_unpaid(spellbook);
+ makeknown(booktype);
if (!u.uevent.read_tribute) {
/* give bonus of 20 xp and 4*20+0 pts */
more_experienced(20, 0);
too_hard = TRUE;
} else {
/* uncursed - chance to fail */
- int read_ability =
- ACURR(A_INT) + 4 + u.ulevel / 2
- - 2 * objects[booktype].oc_level
- + ((ublindf && ublindf->otyp == LENSES) ? 2 : 0);
+ int read_ability = ACURR(A_INT) + 4 + u.ulevel / 2
+ - 2 * objects[booktype].oc_level
+ + ((ublindf && ublindf->otyp == LENSES) ? 2 : 0);
+
/* only wizards know if a spell is too difficult */
if (Role_if(PM_WIZARD) && read_ability < 20 && !confused) {
char qbuf[QBUFSZ];
+
+#if 0 /*JP:T*/
Sprintf(qbuf,
-#if 0 /*JP*/
- "This spellbook is %sdifficult to comprehend. Continue?",
+ "This spellbook is %sdifficult to comprehend. Continue?",
(read_ability < 12 ? "very " : ""));
#else
- "\82±\82Ì\96\82\96@\8f\91\82ð\97\9d\89ð\82·\82é\82Ì\82Í%s\8d¢\93ï\82¾\81D\91±\82¯\82Ü\82·\82©\81H",
+ Sprintf(qbuf,
+ "\82±\82Ì\96\82\96@\8f\91\82ð\97\9d\89ð\82·\82é\82Ì\82Í%s\8d¢\93ï\82¾\81D\91±\82¯\82Ü\82·\82©\81H",
(read_ability < 12 ? "\82Æ\82Ä\82à" : ""));
#endif
if (yn(qbuf) != 'y') {
boolean gone = cursed_book(spellbook);
nomul(context.spbook.delay); /* study time */
+/*JP
multi_reason = "reading a book";
+*/
+ multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
nomovemsg = 0;
context.spbook.delay = 0;
if (gone || !rn2(3)) {
spellbook->in_use = FALSE;
}
nomul(context.spbook.delay);
+/*JP
multi_reason = "reading a book";
+*/
+ multi_reason = "\96{\82ð\93Ç\82ñ\82Å\82¢\82é\8e\9e\82É";
nomovemsg = 0;
context.spbook.delay = 0;
return 1;
}
spellbook->in_use = FALSE;
-#if 0 /*JP*/
+#if 0 /*JP:T*/
You("begin to %s the runes.",
spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" : "memorize");
#else
/*JP
You("are too impaired to cast a spell.");
*/
- You("\96\82\96@\82ð\8f¥\82¦\82ç\82ê\82È\82¢\81D");
+ You("\82\82ç\82\82ç\82µ\82Ä\82¢\82Ä\96\82\96@\82ð\8eg\82¦\82È\82¢\81D");
+ return TRUE;
+ } else if (!can_chant(&youmonst)) {
+/*JP
+ You("are unable to chant the incantation.");
+*/
+ You("\8eô\95¶\82ð\8f¥\82¦\82é\82±\82Æ\82ª\82Å\82«\82È\82¢\81D");
return TRUE;
} else if (!freehand()) {
/* Note: !freehand() occurs when weapon and shield (or two-handed
: "\82Ë\82Î\82Ë\82Î")
: (u.uinwater
/*JP
- ? "water"
+ ? hliquid("water")
*/
- ? "\90\85"
+ ? hliquid("\90\85")
: (rmtyp == CLOUD)
/*JP
? "cloud"
? "\89_"
: IS_TREE(rmtyp)
/*JP
- ? "vegitation"
+ ? "vegetation"
*/
? "\96Ø"
: IS_STWALL(rmtyp)
: "air");
*/
: "\8bó\8bC");
-#if 0 /*JP*/
+#if 0 /*JP:T*/
pline_The("%s around you begins to shimmer with %s haze.",
atmosphere, an(hgolden));
#else
break;
case 7:
case 8:
- make_stunned(old_conf + 2L * duration / 3L, FALSE); /* 20% */
- make_confused(old_stun + duration / 3L, FALSE);
+ make_stunned(old_stun + 2L * duration / 3L, FALSE); /* 20% */
+ make_confused(old_conf + duration / 3L, FALSE);
break;
case 9:
make_stunned(old_stun + duration, FALSE); /* 10% */
boolean atme;
{
int energy, damage, chance, n, intell;
- int skill, role_skill;
+ int otyp, skill, role_skill, res = 0;
boolean confused = (Confusion != 0);
boolean physical_damage = FALSE;
struct obj *pseudo;
*/
Your("\82±\82Ì\96\82\96@\82É\8aÖ\82·\82é\92m\8e¯\82ª\8f\99\81X\82É\94\96\82ê\82Ä\82«\82½\81D");
}
+ /*
+ * Note: dotele() also calculates energy use and checks nutrition
+ * and strength requirements; it any of these change, update it too.
+ */
energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */
if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) {
return 1;
}
- if (u.uhave.amulet) {
+ /* if the cast attempt is already going to fail due to insufficient
+ energy (ie, u.uen < energy), the Amulet's drain effect won't kick
+ in and no turn will be consumed; however, when it does kick in,
+ the attempt may fail due to lack of energy after the draining, in
+ which case a turn will be used up in addition to the energy loss */
+ if (u.uhave.amulet && u.uen >= energy) {
/*JP
You_feel("the amulet draining your energy away.");
*/
pline("\96\82\8f\9c\82¯\82ª\82 \82È\82½\82Ì\83G\83l\83\8b\83M\81[\82ð\8bz\82¢\82Æ\82Á\82Ä\82¢\82é\82æ\82¤\82È\8bC\82ª\82µ\82½\81D");
- energy += rnd(2 * energy);
+ /* this used to be 'energy += rnd(2 * energy)' (without 'res'),
+ so if amulet-induced cost was more than u.uen, nothing
+ (except the "don't have enough energy" message) happened
+ and player could just try again (and again and again...);
+ now we drain some energy immediately, which has a
+ side-effect of not increasing the hunger aspect of casting */
+ u.uen -= rnd(2 * energy);
+ if (u.uen < 0)
+ u.uen = 0;
+ context.botl = 1;
+ res = 1; /* time is going to elapse even if spell doesn't get cast */
}
+
if (energy > u.uen) {
/*JP
You("don't have enough energy to cast that spell.");
*/
pline("\96\82\96@\82ð\8f¥\82¦\82é\82¾\82¯\82Ì\8f\\95ª\82È\83G\83l\83\8b\83M\81[\82ª\82È\82¢\81D");
- return 0;
+ return res;
} else {
if (spellid(spell) != SPE_DETECT_FOOD) {
int hungr = energy * 2;
* Find the skill the hero has in a spell type category.
* See spell_skilltype for categories.
*/
- skill = spell_skilltype(pseudo->otyp);
+ otyp = pseudo->otyp;
+ skill = spell_skilltype(otyp);
role_skill = P_SKILL(skill);
- switch (pseudo->otyp) {
+ switch (otyp) {
/*
* At first spells act as expected. As the hero increases in skill
* with the appropriate spell type, some spells increase in their
}
} else {
explode(u.dx, u.dy,
- pseudo->otyp - SPE_MAGIC_MISSILE + 10,
+ otyp - SPE_MAGIC_MISSILE + 10,
spell_damage_bonus(u.ulevel / 2 + 1), 0,
- (pseudo->otyp == SPE_CONE_OF_COLD)
+ (otyp == SPE_CONE_OF_COLD)
? EXPL_FROSTY
: EXPL_FIERY);
}
}
}
break;
- } /* else fall through... */
+ } /* else */
+ /*FALLTHRU*/
/* these spells are all duplicates of wand effects */
case SPE_FORCE_BOLT:
physical_damage = TRUE;
- /* fall through */
+ /*FALLTHRU*/
case SPE_SLEEP:
case SPE_MAGIC_MISSILE:
case SPE_KNOCK:
case SPE_EXTRA_HEALING:
case SPE_DRAIN_LIFE:
case SPE_STONE_TO_FLESH:
- if (!(objects[pseudo->otyp].oc_dir == NODIR)) {
+ if (objects[otyp].oc_dir != NODIR) {
+ if (otyp == SPE_HEALING || otyp == SPE_EXTRA_HEALING) {
+ /* healing and extra healing are actually potion effects,
+ but they've been extended to take a direction like wands */
+ if (role_skill >= P_SKILLED)
+ pseudo->blessed = 1;
+ }
if (atme) {
u.dx = u.dy = u.dz = 0;
} else if (!getdir((char *) 0)) {
/* high skill yields effect equivalent to blessed scroll */
if (role_skill >= P_SKILLED)
pseudo->blessed = 1;
- /* fall through */
+ /*FALLTHRU*/
case SPE_CHARM_MONSTER:
case SPE_MAGIC_MAPPING:
case SPE_CREATE_MONSTER:
/* high skill yields effect equivalent to blessed potion */
if (role_skill >= P_SKILLED)
pseudo->blessed = 1;
- /* fall through */
+ /*FALLTHRU*/
case SPE_INVISIBILITY:
(void) peffects(pseudo);
break;
+ /* end of potion-like spells */
case SPE_CURE_BLINDNESS:
healup(0, 0, FALSE, TRUE);
(void) make_familiar((struct obj *) 0, u.ux, u.uy, FALSE);
break;
case SPE_CLAIRVOYANCE:
- if (!BClairvoyant)
- do_vicinity_map();
+ if (!BClairvoyant) {
+ if (role_skill >= P_SKILLED)
+ pseudo->blessed = 1; /* detect monsters as well as map */
+ do_vicinity_map(pseudo);
/* at present, only one thing blocks clairvoyance */
- else if (uarmh && uarmh->otyp == CORNUTHAUM)
+ } else if (uarmh && uarmh->otyp == CORNUTHAUM)
/*JP
You("sense a pointy hat on top of your %s.", body_part(HEAD));
*/
return 1;
}
+/*ARGSUSED*/
+STATIC_OVL boolean
+spell_aim_step(arg, x, y)
+genericptr_t arg UNUSED;
+int x, y;
+{
+ if (!isok(x,y))
+ return FALSE;
+ if (!ZAP_POS(levl[x][y].typ)
+ && !(IS_DOOR(levl[x][y].typ) && (levl[x][y].doormask & D_ISOPEN)))
+ return FALSE;
+ return TRUE;
+}
+
/* Choose location where spell takes effect. */
STATIC_OVL int
throwspell()
{
- coord cc;
+ coord cc, uc;
struct monst *mtmp;
if (u.uinwater) {
/*JP
- pline("You're joking! In this weather?");
+ pline("You're joking! In this weather?");
*/
pline("\90\85\92\86\82Å\89½\82ð\82µ\82æ\82¤\82Á\82Ä\82¢\82¤\82ñ\82¾\82¢\81H");
return 0;
return 0;
}
+ uc.x = u.ux;
+ uc.y = u.uy;
+
+ walk_path(&uc, &cc, spell_aim_step, (genericptr_t) 0);
+
u.dx = cc.x;
u.dy = cc.y;
return 1;
}
+/* add/hide/remove/unhide teleport-away on behalf of dotelecmd() to give
+ more control to behavior of ^T when used in wizard mode */
+int
+tport_spell(what)
+int what;
+{
+ static struct tport_hideaway {
+ struct spell savespell;
+ int tport_indx;
+ } save_tport;
+ int i;
+/* also defined in teleport.c */
+#define NOOP_SPELL 0
+#define HIDE_SPELL 1
+#define ADD_SPELL 2
+#define UNHIDESPELL 3
+#define REMOVESPELL 4
+
+ for (i = 0; i < MAXSPELL; i++)
+ if (spellid(i) == SPE_TELEPORT_AWAY || spellid(i) == NO_SPELL)
+ break;
+ if (i == MAXSPELL) {
+ impossible("tport_spell: spellbook full");
+ /* wizard mode ^T is not able to honor player's menu choice */
+ } else if (spellid(i) == NO_SPELL) {
+ if (what == HIDE_SPELL || what == REMOVESPELL) {
+ save_tport.tport_indx = MAXSPELL;
+ } else if (what == UNHIDESPELL) {
+ /*assert( save_tport.savespell.sp_id == SPE_TELEPORT_AWAY );*/
+ spl_book[save_tport.tport_indx] = save_tport.savespell;
+ save_tport.tport_indx = MAXSPELL; /* burn bridge... */
+ } else if (what == ADD_SPELL) {
+ save_tport.savespell = spl_book[i];
+ save_tport.tport_indx = i;
+ spl_book[i].sp_id = SPE_TELEPORT_AWAY;
+ spl_book[i].sp_lev = objects[SPE_TELEPORT_AWAY].oc_level;
+ spl_book[i].sp_know = KEEN;
+ return REMOVESPELL; /* operation needed to reverse */
+ }
+ } else { /* spellid(i) == SPE_TELEPORT_AWAY */
+ if (what == ADD_SPELL || what == UNHIDESPELL) {
+ save_tport.tport_indx = MAXSPELL;
+ } else if (what == REMOVESPELL) {
+ /*assert( i == save_tport.tport_indx );*/
+ spl_book[i] = save_tport.savespell;
+ save_tport.tport_indx = MAXSPELL;
+ } else if (what == HIDE_SPELL) {
+ save_tport.savespell = spl_book[i];
+ save_tport.tport_indx = i;
+ spl_book[i].sp_id = NO_SPELL;
+ return UNHIDESPELL; /* operation needed to reverse */
+ }
+ }
+ return NOOP_SPELL;
+}
+
/* forget a random selection of known spells due to amnesia;
they used to be lost entirely, as if never learned, but now we
just set the memory retention to zero so that they can't be cast */
* are learned, they get inserted into sorted order rather than be
* appended to the end of the list?
*/
-static const char *spl_sortchoices[] = {
+enum spl_sort_types {
+ SORTBY_LETTER = 0,
+ SORTBY_ALPHA,
+ SORTBY_LVL_LO,
+ SORTBY_LVL_HI,
+ SORTBY_SKL_AL,
+ SORTBY_SKL_LO,
+ SORTBY_SKL_HI,
+ SORTBY_CURRENT,
+ SORTRETAINORDER,
+
+ NUM_SPELL_SORTBY
+};
+
+static const char *spl_sortchoices[NUM_SPELL_SORTBY] = {
/*JP
"by casting letter",
*/
"\91I\91ð\82·\82é\95¶\8e\9a\8f\87",
-#define SORTBY_LETTER 0
/*JP
"alphabetically",
*/
"\95¶\8e\9a\83R\81[\83h\8f\87",
-#define SORTBY_ALPHA 1
/*JP
"by level, low to high",
*/
"\83\8c\83x\83\8b\8f¸\8f\87",
-#define SORTBY_LVL_LO 2
/*JP
"by level, high to low",
*/
"\83\8c\83x\83\8b\8d~\8f\87",
-#define SORTBY_LVL_HI 3
/*JP
"by skill group, alphabetized within each group",
*/
"\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\95¶\8e\9a\83R\81[\83h\8f\87",
-#define SORTBY_SKL_AL 4
/*JP
"by skill group, low to high level within group",
*/
"\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\83\8c\83x\83\8b\8f¸\8f\87",
-#define SORTBY_SKL_LO 5
/*JP
"by skill group, high to low level within group",
*/
"\83X\83L\83\8b\83O\83\8b\81[\83v\96\88\82É\83\8c\83x\83\8b\8d~\8f\87",
-#define SORTBY_SKL_HI 6
/*JP
"maintain current ordering",
*/
"\8c»\8dÝ\82Ì\8f\87\8f\98\82ð\88Û\8e\9d",
-#define SORTBY_CURRENT 7
/* a menu choice rather than a sort choice */
/*JP
"reassign casting letters to retain current order",
*/
"\8c»\8dÝ\82Ì\8f\87\8f\98\82ð\95Ï\82¦\82¸\82É\91I\91ð\82·\82é\95¶\8e\9a\82ð\8dÄ\8a\84\82è\93\96\82Ä",
-#define SORTRETAINORDER 8
};
static int spl_sortmode = 0; /* index into spl_sortchoices[] */
static int *spl_orderindx = 0; /* array of spl_book[] indices */
* given string and are of the form "a - ".
*/
if (!iflags.menu_tab_sep) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
Sprintf(buf, "%-20s Level %-12s Fail Retention", " Name",
"Category");
#else
- Sprintf(buf, "%-20s Level %-12s \90¬\8c÷\97¦", " Name", "\95ª\97Þ");
+ Sprintf(buf, "%-20s Level %-12s \90¬\8c÷\97¦", " \96¼\91O", "\95ª\97Þ");
#endif
fmt = "%-20s %2d %-12s %3d%% %9s";
} else {
if (turnsleft < 1L) {
/* spell has expired; hero can't successfully cast it anymore */
+/*JP
Strcpy(outbuf, "(gone)");
+*/
+ Strcpy(outbuf, "(\96Y\8bp)");
} else if (turnsleft >= (long) KEEN) {
/* full retention, first turn or immediately after reading book */
Strcpy(outbuf, "100%");