static boolean dospellmenu(const char *prompt, int splaction, /* SPELLMENU_CAST, SPELLMENU_VIEW, or spl_book[] index */ int *spell_no) { int i, n, how, count = 0; struct nh_menuitem items[MAXSPELL + 1]; const int *selected; set_menuitem(&items[count++], 0, MI_HEADING, "Name\tLevel\tCategory\tFail\tMemory", 0, FALSE); for (i = 0; i < MAXSPELL; i++) { if (spellid(i) == NO_SPELL) continue; const char *buf = SPELL_IS_FROM_SPELLBOOK(i) ? msgprintf("%s\t%-d%s\t%s\t%-d%%\t%-d%%", spellname(i), spellev(i), spellknow(i) ? " " : "*", spelltypemnemonic(spell_skilltype(spellid(i))), 100 - percent_success(i), (spellknow(i) * 100 + (KEEN - 1)) / KEEN) : msgprintf("%s\t--\t%s\t?\t--", spellname(i), (spellid(i) == SPID_PRAY || spellid(i) == SPID_TURN) ? "divine" : "ability"); set_menuitem(&items[count++], i + 1, MI_NORMAL, buf, spelllet_from_no(i), FALSE); } how = PICK_ONE; if (splaction >= 0) how = PICK_LETTER; /* We're swapping spells. */ n = display_menu(&(struct nh_menulist){.items = items, .icount = count},
/* * Add SPID_* values to the player's spellbook, and remove those which are no * longer necessary. * * SPID_PRAY always exists; * SPID_TURN exists according to the player's role. * * The other three change dynamically: * * SPID_RLOC: on intrinsic/extrinsic change * (requires Teleportation + xlvl 12, or 8 as a wizard; and/or * Teleportation from polyform specifically) * SPID_JUMP: on intrinsic/extrinsic change (requires Jumping) * SPID_MONS: on polyform change * * Thus, we call this function: * - At character creation * - When levelling up * - When polymorphing * - When changing equipment * - When gaining/losing intrinsic teleportitis * This assumes that there's no source of Jumping or Teleportation on a * timeout; at present there isn't. */ void update_supernatural_abilities(void) { int i, j; /* Look through our spellbook for abilities we no longer have, and delete them. */ for (i = 0; i < MAXSPELL; i++) { if (!SPELL_IS_FROM_SPELLBOOK(i) && spellid(i) && !supernatural_ability_available(spellid(i))) { spl_book[i].sp_id = 0; spl_book[i].sp_know = 0; spl_book[i].sp_lev = 0; } } /* For each ability we have, look for it in our spellbook. If we can't find it there, assign a letter to it. We default to letters near the end of the alphabet, specific to each spell. We might have to use a different letter, though, if the one we want is already taken. */ for (j = -1; j >= -SPID_COUNT; j--) { if (!supernatural_ability_available(j)) continue; int best = -1; for (i = 0; i < MAXSPELL; i++) { /* Later letters are better than earlier ones, apart from the last SPID_COUNT which are worth avoiding if possible. Two exceptions trump everything: the letter where the spell already is; and the preferred letter. */ if (spellid(i) == j) goto next_spid; /* multi-level continue */ if (spellid(i) == NO_SPELL && (i < MAXSPELL - SPID_COUNT || best == -1 || spelllet_from_no(i) == SPID_PREFERRED_LETTER[-1-j])) best = i; } if (best == -1) panic("too many spells + supernatural abilities"); spl_book[best].sp_id = j; spl_book[best].sp_lev = 0; spl_book[best].sp_know = 0; next_spid: ; } }
void menuspellprint(int i) { int x,y; getyx(Menuw,y,x); if (y >= ScreenLength - 2) { wrefresh(Menuw); morewait(); wclear(Menuw); touchwin(Menuw); } wprintw(Menuw,spellid(i)); wprintw(Menuw,"(%d)\n",Spells[i].powerdrain); }
static boolean getargspell(const struct nh_cmd_arg *arg, int *spell_no) { if (arg->argtype & CMD_ARG_SPELL) { char slet = arg->spelllet; int sno = spellno_from_let(slet); if (sno >= 0 && spellid(sno) != NO_SPELL) { *spell_no = sno; return TRUE; } } return getspell(spell_no); }
static const char * spellname(int spell) { int sid = spellid(spell); if (SPELL_IS_FROM_SPELLBOOK(spell)) return OBJ_NAME(objects[sid]); else if (sid == SPID_PRAY) return "pray for help"; else if (sid == SPID_TURN) return "turn undead"; else if (sid == SPID_RLOC) return "teleportitis"; else if (sid == SPID_JUMP) return "supernatural jump"; /* distinguish from the spell */ else if (sid == SPID_MONS) { struct polyform_ability pa; if (has_polyform_ability(youmonst.data, &pa)) return pa.description; panic("SPID_MONS is in spellbook, but #monster is invalid"); } panic("Unknown spell number %d", sid); }
int spelleffects(int spell, boolean atme, const struct nh_cmd_arg *arg) { int energy, damage, chance, n, intell; int skill, role_skill; boolean confused = (Confusion != 0); struct obj *pseudo; boolean dummy; coord cc; schar dx = 0, dy = 0, dz = 0; if (!SPELL_IS_FROM_SPELLBOOK(spell)) { /* At the moment, we implement this via calling the code for the shortcut command. Eventually, it would make sense to invert this (and make the shortcut commands wrappers for spelleffects). */ switch (spellid(spell)) { case SPID_PRAY: return dopray(arg); case SPID_TURN: return doturn(arg); case SPID_RLOC: return dotele(arg); case SPID_JUMP: return dojump(arg); case SPID_MONS: return domonability(arg); default: impossible("Unknown spell number %d?", spellid(spell)); return 0; } } /* * Find the skill the hero has in a spell type category. * See spell_skilltype for categories. */ skill = spell_skilltype(spellid(spell)); role_skill = P_SKILL(skill); /* Get the direction or target, if applicable. We want to do this *before* determining spell success, both for interface consistency and to cut down on needless mksobj calls. */ switch (spellid(spell)) { /* These spells ask the user to target a specific space. */ case SPE_CONE_OF_COLD: case SPE_FIREBALL: /* If Skilled or better, get a specific space. */ if (role_skill >= P_SKILLED) { if (throwspell(&dx, &dy, arg)) { dz = 0; break; } else { /* Decided not to target anything. Abort the spell. */ pline("Spell canceled."); return 0; } } /* If not Skilled, fall through. */ /* These spells ask the user to target a direction. */ case SPE_FORCE_BOLT: case SPE_SLEEP: case SPE_MAGIC_MISSILE: case SPE_KNOCK: case SPE_SLOW_MONSTER: case SPE_WIZARD_LOCK: case SPE_DIG: case SPE_TURN_UNDEAD: case SPE_POLYMORPH: case SPE_TELEPORT_AWAY: case SPE_CANCELLATION: case SPE_FINGER_OF_DEATH: case SPE_HEALING: case SPE_EXTRA_HEALING: case SPE_DRAIN_LIFE: case SPE_STONE_TO_FLESH: if (atme) dx = dy = dz = 0; else if (!getargdir(arg, NULL, &dx, &dy, &dz)) { /* getdir cancelled, abort */ pline("Spell canceled."); return 0; } break; case SPE_JUMPING: if(!get_jump_coords(arg, &cc, max(role_skill, 1))) { /* No jumping after all, I guess. */ pline("Spell canceled."); return 0; } break; /* The rest of the spells don't have targeting. */ default: break; } /* Spell casting no longer affects knowledge of the spell. A decrement of spell knowledge is done every turn. */ if (spellknow(spell) <= 0) { pline("Your knowledge of this spell is twisted."); pline("It invokes nightmarish images in your mind..."); spell_backfire(spell); return 0; } else if (spellknow(spell) <= 200) { /* 1% */ pline("You strain to recall the spell."); } else if (spellknow(spell) <= 1000) { /* 5% */ pline("Your knowledge of this spell is growing faint."); } energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */ if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) { pline("You are too hungry to cast that spell."); return 0; } else if (ACURR(A_STR) < 4) { pline("You lack the strength to cast spells."); return 0; } else if (check_capacity ("Your concentration falters while carrying so much stuff.")) { return 1; } else if (!freehand()) { pline("Your arms are not free to cast!"); return 0; } if (Uhave_amulet) { pline("You feel the amulet draining your energy away."); energy += rnd(2 * energy); } if (energy > u.uen) { pline("You don't have enough energy to cast that spell."); return 0; } else { if (spellid(spell) != SPE_DETECT_FOOD) { int hungr = energy * 2; /* * If hero is a wizard, their current intelligence * (bonuses + temporary + current) * affects hunger reduction in casting a spell. * 1. int = 17-18 no reduction * 2. int = 16 1/4 hungr * 3. int = 15 1/2 hungr * 4. int = 1-14 normal reduction * The reason for this is: * a) Intelligence affects the amount of exertion * in thinking. * b) Wizards have spent their life at magic and * understand quite well how to cast spells. */ intell = acurr(A_INT); if (!Role_if(PM_WIZARD)) intell = 10; if (intell >= 17) hungr = 0; else if (intell == 16) hungr /= 4; else if (intell == 15) hungr /= 2; /* don't put player (quite) into fainting from casting a spell, particularly since they might not even be hungry at the beginning; however, this is low enough that they must eat before casting anything else except detect food */ if (hungr > u.uhunger - 3) hungr = u.uhunger - 3; morehungry(hungr); } } chance = percent_success(spell); if (confused || (rnd(100) > chance)) { pline("You fail to cast the spell correctly."); u.uen -= energy / 2; return 1; } u.uen -= energy; /* pseudo is a temporary "false" object containing the spell stats */ pseudo = mktemp_sobj(level, spellid(spell)); pseudo->blessed = pseudo->cursed = 0; pseudo->quan = 20L; /* do not let useup get it */ switch (pseudo->otyp) { /* * At first spells act as expected. As the hero increases in skill * with the appropriate spell type, some spells increase in their * effects, e.g. more damage, further distance, and so on, without * additional cost to the spellcaster. */ case SPE_CONE_OF_COLD: case SPE_FIREBALL: if (role_skill >= P_SKILLED) { cc.x = dx; cc.y = dy; n = rnd(8) + 1; while (n--) { if (!dx && !dy && !dz) { if ((damage = zapyourself(pseudo, TRUE)) != 0) losehp(damage, msgprintf( "zapped %sself with an exploding spell", uhim())); } else { explode(dx, dy, pseudo->otyp - SPE_MAGIC_MISSILE + 10, u.ulevel / 2 + 1 + spell_damage_bonus(), 0, (pseudo->otyp == SPE_CONE_OF_COLD) ? EXPL_FROSTY : EXPL_FIERY, NULL, 0); } dx = cc.x + rnd(3) - 2; dy = cc.y + rnd(3) - 2; if (!isok(dx, dy) || !cansee(dx, dy) || IS_STWALL(level->locations[dx][dy].typ) || Engulfed) { /* Spell is reflected back to center */ dx = cc.x; dy = cc.y; } } break; } /* else fall through... */ /* these spells are all duplicates of wand effects */ case SPE_FORCE_BOLT: case SPE_SLEEP: case SPE_MAGIC_MISSILE: case SPE_KNOCK: case SPE_SLOW_MONSTER: case SPE_WIZARD_LOCK: case SPE_DIG: case SPE_TURN_UNDEAD: case SPE_POLYMORPH: case SPE_TELEPORT_AWAY: case SPE_CANCELLATION: case SPE_FINGER_OF_DEATH: case SPE_LIGHT: case SPE_DETECT_UNSEEN: case SPE_HEALING: case SPE_EXTRA_HEALING: case SPE_DRAIN_LIFE: case SPE_STONE_TO_FLESH: if (objects[pseudo->otyp].oc_dir != NODIR) { if (!dx && !dy && !dz) { if ((damage = zapyourself(pseudo, TRUE)) != 0) { losehp(damage, msgprintf("zapped %sself with a spell", uhim())); } } else weffects(pseudo, dx, dy, dz); } else weffects(pseudo, 0, 0, 0); update_inventory(); /* spell may modify inventory */ break; /* these are all duplicates of scroll effects */ case SPE_REMOVE_CURSE: case SPE_CONFUSE_MONSTER: case SPE_DETECT_FOOD: case SPE_CAUSE_FEAR: /* high skill yields effect equivalent to blessed scroll */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; /* fall through */ case SPE_CHARM_MONSTER: case SPE_MAGIC_MAPPING: case SPE_CREATE_MONSTER: case SPE_IDENTIFY: seffects(pseudo, &dummy); break; /* these are all duplicates of potion effects */ case SPE_HASTE_SELF: case SPE_DETECT_TREASURE: case SPE_DETECT_MONSTERS: case SPE_LEVITATION: case SPE_RESTORE_ABILITY: /* high skill yields effect equivalent to blessed potion */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; /* fall through */ case SPE_INVISIBILITY: peffects(pseudo); break; case SPE_CURE_BLINDNESS: healup(0, 0, FALSE, TRUE); break; case SPE_CURE_SICKNESS: if (Sick) pline("You are no longer ill."); if (Slimed) { pline("The slime disappears!"); Slimed = 0; } healup(0, 0, TRUE, FALSE); break; case SPE_CREATE_FAMILIAR: make_familiar(NULL, u.ux, u.uy, FALSE); break; case SPE_CLAIRVOYANCE: if (!BClairvoyant) do_vicinity_map(); /* at present, only one thing blocks clairvoyance */ else if (uarmh && uarmh->otyp == CORNUTHAUM) pline("You sense a pointy hat on top of your %s.", body_part(HEAD)); break; case SPE_PROTECTION: cast_protection(); break; case SPE_JUMPING: jump_to_coords(&cc); break; default: impossible("Unknown spell %d attempted.", spell); obfree(pseudo, NULL); return 0; } /* gain skill for successful cast */ use_skill(skill, spellev(spell)); obfree(pseudo, NULL); /* now, get rid of it */ return 1; }
/* Handles one turn of book reading. Returns 1 if unfinished, 0 if finshed. */ static int learn(void) { int i; short booktype; boolean costly = TRUE; boolean already_known = FALSE; int first_unknown = MAXSPELL; int known_spells = 0; const char *splname; /* JDS: lenses give 50% faster reading; 33% smaller read time */ if (u.uoccupation_progress[tos_book] && ublindf && ublindf->otyp == LENSES && rn2(2)) u.uoccupation_progress[tos_book]++; if (Confusion) { /* became confused while learning */ confused_book(u.utracked[tos_book]); u.utracked[tos_book] = 0; /* no longer studying */ helpless(-u.uoccupation_progress[tos_book], hr_busy, "absorbed in a spellbook", "You're finally able to put the book down."); u.uoccupation_progress[tos_book] = 0; return 0; } booktype = u.utracked[tos_book]->otyp; if (booktype == SPE_BOOK_OF_THE_DEAD) { deadbook(u.utracked[tos_book], FALSE); u.utracked[tos_book] = 0; u.uoccupation_progress[tos_book] = 0; return 0; } /* The book might get cursed while we're reading it. In this case, immediately stop reading it, cancel progress, and apply a few turns of helplessness. (3.4.3 applies negative spellbook effects but lets you memorize the spell anyway; this makes no sense. It destroys the spellbook on the "contact poison" result, which makes even less sense.) */ if (u.utracked[tos_book]->cursed) { pline("This book isn't making sense any more."); helpless(rn1(5,5), hr_busy, "making sense of a spellbook", "You give up trying to make sense of the spellbook."); u.uoccupation_progress[tos_book] = 0; u.utracked[tos_book] = 0; return 0; } if (++u.uoccupation_progress[tos_book] < 0) return 1; /* still busy */ if (ACURR(A_WIS) < 12) exercise(A_WIS, TRUE); /* you're studying. */ splname = msgprintf(objects[booktype].oc_name_known ? "\"%s\"" : "the \"%s\" spell", OBJ_NAME(objects[booktype])); for (i = 0; i < MAXSPELL; i++) { if (spellid(i) == booktype) { already_known = TRUE; if (u.utracked[tos_book]->spestudied > MAX_SPELL_STUDY) { pline("This spellbook is too faint to be read any more."); u.utracked[tos_book]->otyp = booktype = SPE_BLANK_PAPER; } else if (spellknow(i) <= 1000) { pline("Your knowledge of %s is keener.", splname); incrnknow(i); u.utracked[tos_book]->spestudied++; if (ACURR(A_WIS) < 12) exercise(A_WIS, TRUE); /* extra study */ } else { /* 1000 < spellknow(i) <= MAX_SPELL_STUDY */ pline("You know %s quite well already.", splname); if (yn("Do you want to read the book anyway?") == 'y') { pline("You refresh your knowledge of %s.", splname); incrnknow(i); u.utracked[tos_book]->spestudied++; } else costly = FALSE; } /* make book become known even when spell is already known, in case amnesia made you forget the book */ makeknown((int)booktype); break; } else if (spellid(i) == NO_SPELL && (i < first_unknown || i == spellno_from_let(objects[booktype].oc_defletter))) first_unknown = i; else known_spells++; } if (first_unknown == MAXSPELL && !already_known) panic("Too many spells memorized!"); if (!already_known) { spl_book[first_unknown].sp_id = booktype; spl_book[first_unknown].sp_lev = objects[booktype].oc_level; incrnknow(first_unknown); u.utracked[tos_book]->spestudied++; pline(known_spells > 0 ? "You add %s to your repertoire." : "You learn %s.", splname); makeknown((int)booktype); } if (costly) check_unpaid(u.utracked[tos_book]); u.utracked[tos_book] = 0; return 0; }