/* * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke * * This software may be copied and distributed for educational, research, and * not for profit purposes provided that this copyright and statement are * included in all such copies. */ #include "cmd5.hpp" #include "birth.hpp" #include "cave.hpp" #include "cave_type.hpp" #include "corrupt.hpp" #include "dungeon_flag.hpp" #include "lua_bind.hpp" #include "monster2.hpp" #include "monster_race.hpp" #include "monster_race_flag.hpp" #include "monster_spell_flag.hpp" #include "object1.hpp" #include "object2.hpp" #include "object_flag.hpp" #include "object_type.hpp" #include "player_class.hpp" #include "player_race.hpp" #include "player_race_mod.hpp" #include "player_type.hpp" #include "school_book.hpp" #include "skills.hpp" #include "spell_type.hpp" #include "spells1.hpp" #include "spells2.hpp" #include "spells4.hpp" #include "spells5.hpp" #include "stats.hpp" #include "tables.hpp" #include "util.hpp" #include "util.h" #include "variable.h" #include "variable.hpp" #include "wizard2.hpp" #include "xtra1.hpp" #include "xtra2.hpp" #include "z-rand.hpp" #include #include #include /* Maximum number of tries for teleporting */ #define MAX_TRIES 300 static object_filter_t const &is_school_book() { using namespace object_filter; static auto instance = Or( TVal(TV_BOOK), TVal(TV_DAEMON_BOOK), TVal(TV_INSTRUMENT)); return instance; } /* Does it contains a schooled spell ? */ static object_filter_t const &hook_school_spellable() { using namespace object_filter; static auto has_pval2 = [=](object_type const *o_ptr) -> bool { return (o_ptr->pval2 != -1); }; static auto instance = Or( is_school_book(), And( HasFlags(TR_SPELL_CONTAIN), has_pval2)); return instance; } /* Is it a browsable for spells? */ static object_filter_t const &item_tester_hook_browsable() { using namespace object_filter; static auto instance = Or( hook_school_spellable(), TVal(TV_BOOK)); return instance; } /* * Are we using a mage staff */ bool_ is_magestaff() { int i; i = 0; while (p_ptr->body_parts[i] == INVEN_WIELD) { object_type *o_ptr = &p_ptr->inventory[INVEN_WIELD + i]; /* Wielding a mage staff */ if ((o_ptr->k_idx) && (o_ptr->tval == TV_MSTAFF)) return (TRUE); /* Next slot */ i++; /* Paranoia */ if (i >= (INVEN_TOTAL - INVEN_WIELD)) break; } /* Not wielding a mage staff */ return (FALSE); } static int print_book(s16b sval, s32b spell_idx, object_type *obj) { int y = 2; int i; random_book_setup(sval, spell_idx); school_book *school_book = school_books_at(sval); /* Parse all spells */ i = 0; for (auto spell_idx : school_book->spell_idxs) { byte color = TERM_L_DARK; bool_ is_ok; char label[8]; is_ok = is_ok_spell(spell_idx, obj->pval); if (is_ok) { color = (get_mana(spell_idx) > get_power(spell_idx)) ? TERM_ORANGE : TERM_L_GREEN; } sprintf(label, "%c) ", 'a' + i); y = print_spell(label, color, y, spell_idx); i++; } prt(format(" %-20s%-16s Level Cost Fail Info", "Name", "School"), 1, 0); return y; } static void browse_school_spell(int book, int spell_idx, object_type *o_ptr) { int i; int num = 0; int ask; char choice; char out_val[160]; /* Show choices */ window_stuff(); num = school_book_length(book); /* Build a prompt (accept all spells) */ strnfmt(out_val, 78, "(Spells %c-%c, ESC=exit) cast which spell? ", I2A(0), I2A(num - 1)); /* Save the screen */ character_icky = TRUE; Term_save(); /* Display a list of spells */ print_book(book, spell_idx, o_ptr); /* Get a spell from the user */ while (get_com(out_val, &choice)) { /* Display a list of spells */ print_book(book, spell_idx, o_ptr); /* Note verify */ ask = (isupper(choice)); /* Lowercase */ if (ask) choice = tolower(choice); /* Extract request */ i = (islower(choice) ? A2I(choice) : -1); /* Totally Illegal */ if ((i < 0) || (i >= num)) { bell(); continue; } /* Restore the screen */ Term_load(); /* Display a list of spells */ auto where = print_book(book, spell_idx, o_ptr); print_spell_desc(spell_x(book, spell_idx, i), where); } /* Restore the screen */ Term_load(); character_icky = FALSE; /* Show choices */ window_stuff(); } /* * Peruse the spells/prayers in a book * * Note that *all* spells in the book are listed * * Note that browsing is allowed while confused or blind, * and in the dark, primarily to allow browsing in stores. */ extern void do_cmd_browse_aux(object_type *o_ptr) { auto const flags = object_flags(o_ptr); if (is_school_book()(o_ptr)) { browse_school_spell(o_ptr->sval, o_ptr->pval, o_ptr); } else if ((flags & TR_SPELL_CONTAIN) && (o_ptr->pval2 != -1)) { browse_school_spell(255, o_ptr->pval2, o_ptr); } } void do_cmd_browse(void) { /* Get an item */ int item; if (!get_item(&item, "Browse which book? ", "You have no books that you can read.", (USE_INVEN | USE_EQUIP | USE_FLOOR), item_tester_hook_browsable())) { return; } /* Get the item */ object_type *o_ptr = get_object(item); do_cmd_browse_aux(o_ptr); } static void do_poly_wounds() { /* Changed to always provide at least _some_ healing */ s16b wounds = p_ptr->cut; s16b hit_p = (p_ptr->mhp - p_ptr->chp); s16b change = damroll(p_ptr->lev, 5); bool_ Nasty_effect = (randint(5) == 1); if (!(wounds || hit_p || Nasty_effect)) return; msg_print("Your wounds are polymorphed into less serious ones."); hp_player(change); if (Nasty_effect) { msg_print("A new wound was created!"); take_hit(change / 2, "a polymorphed wound"); set_cut(change); } else { set_cut((p_ptr->cut) - (change / 2)); } } void do_poly_self(void) { int power = p_ptr->lev; int poly_power; msg_print("You feel a change coming over you..."); if ((power > rand_int(20)) && (rand_int(3) == 0)) { char effect_msg[80] = ""; int new_race, expfact, goalexpfact; /* Some form of racial polymorph... */ power -= 10; if ((power > rand_int(30)) && (rand_int(5) == 0)) { int tmp = 0; /* Harmful deformity */ power -= 15; while (tmp < 6) { if ( rand_int(2) == 0) { (void)dec_stat(tmp, randint(6) + 6, (rand_int(3) == 0)); power -= 1; } tmp++; } /* Deformities are discriminated against! */ (void)dec_stat(A_CHR, randint(6), TRUE); if (effect_msg[0]) { char tmp_msg[10]; strnfmt(tmp_msg, 10, "%s", effect_msg); strnfmt(effect_msg, 80, "deformed %s", tmp_msg); } else { strcpy(effect_msg, "deformed"); } } while ((power > rand_int(20)) && (rand_int(10) == 0)) { /* Polymorph into a less corrupted form */ power -= 10; lose_corruption(); } /* * I'm not sure 'power' is always positive, with *so* many minuses. * Also, passing zero / negative numbers to randint/rand_int can * cause a zero divide exception, IIRC, not to speak of its absurdity * -- pelpel */ poly_power = (power > 1) ? power : 1; /* * Restrict the race choices by exp penalty so weak polymorph * always means weak race */ goalexpfact = 100 + 3 * rand_int(poly_power); /* Roll until an appropriate selection is made */ while (1) { new_race = rand_int(max_rp_idx); expfact = race_info[new_race].ps.exp; if ((new_race != p_ptr->prace) && (expfact <= goalexpfact)) break; } if (effect_msg[0]) { msg_format("You turn into a%s %s!", ((is_a_vowel(*race_info[new_race].title)) ? "n" : ""), race_info[new_race].title); } else { msg_format("You turn into a %s %s!", effect_msg, race_info[new_race].title); } p_ptr->prace = new_race; rp_ptr = &race_info[p_ptr->prace]; /* Experience factor */ p_ptr->expfact = rp_ptr->ps.exp + rmp_ptr->ps.exp + cp_ptr->ps.exp; /* Level up if necessary */ check_experience(); p_ptr->max_plv = p_ptr->lev; p_ptr->redraw |= (PR_FRAME); p_ptr->update |= (PU_BONUS); handle_stuff(); lite_spot(p_ptr->py, p_ptr->px); } if ((power > rand_int(30)) && (rand_int(6) == 0)) { int tmp = 0; /* Abomination! */ power -= 20; msg_print("Your internal organs are rearranged!"); while (tmp < 6) { (void)dec_stat(tmp, randint(6) + 6, (rand_int(3) == 0)); tmp++; } if (rand_int(6) == 0) { msg_print("You find living difficult in your present form!"); take_hit(damroll(randint(10), p_ptr->lev), "a lethal corruption"); power -= 10; } } if ((power > rand_int(20)) && (rand_int(4) == 0)) { power -= 10; do_cmd_rerate(); } while ((power > rand_int(15)) && (rand_int(3) == 0)) { power -= 7; gain_random_corruption(); } if (power > rand_int(5)) { power -= 5; do_poly_wounds(); } /* Note: earlier deductions may have left power < 0 already. */ while (power > 0) { corrupt_player(); power--; } } /* * Fetch an item (teleport it right underneath the caster) */ void fetch(int dir, int wgt, bool_ require_los) { /* Check to see if an object is already there */ if (!cave[p_ptr->py][p_ptr->px].o_idxs.empty()) { msg_print("You can't fetch when you're already standing on something."); return; } /* Use a target */ cave_type *c_ptr = nullptr; if ((dir == 5) && target_okay()) { int tx = target_col; int ty = target_row; if (distance(p_ptr->py, p_ptr->px, ty, tx) > MAX_RANGE) { msg_print("You can't fetch something that far away!"); return; } c_ptr = &cave[ty][tx]; if (c_ptr->o_idxs.empty()) { msg_print("There is no object at this place."); return; } if (require_los && (!player_has_los_bold(ty, tx))) { msg_print("You have no direct line of sight to that location."); return; } } else { /* Use a direction */ int ty = p_ptr->py; /* Where to drop the item */ int tx = p_ptr->px; while (1) { ty += ddy[dir]; tx += ddx[dir]; c_ptr = &cave[ty][tx]; if ((distance(p_ptr->py, p_ptr->px, ty, tx) > MAX_RANGE) || !cave_floor_bold(ty, tx)) return; if (!c_ptr->o_idxs.empty()) break; } } assert(c_ptr != nullptr); assert(!c_ptr->o_idxs.empty()); /* Pick object from the list */ auto o_idx = c_ptr->o_idxs.front(); object_type *o_ptr = &o_list[o_idx]; if (o_ptr->weight > wgt) { /* Too heavy to 'fetch' */ msg_print("The object is too heavy."); return; } /* Move the object between the lists */ c_ptr->o_idxs.erase(c_ptr->o_idxs.begin()); // Remove cave[p_ptr->py][p_ptr->px].o_idxs.push_back(o_idx); // Add /* Update object's location */ o_ptr->iy = p_ptr->py; o_ptr->ix = p_ptr->px; /* Feedback */ char o_name[80]; object_desc(o_name, o_ptr, TRUE, 0); msg_format("%^s flies through the air to your feet.", o_name); note_spot(p_ptr->py, p_ptr->px); p_ptr->redraw |= PR_MAP; } /* * Return the symbiote's name or description. */ std::string symbiote_name(bool capitalize) { object_type *o_ptr = &p_ptr->inventory[INVEN_CARRY]; std::string buf; buf.reserve(32); // Fallback; shouldn't ever be necessary if (!o_ptr->k_idx) { buf += "A non-existent symbiote"; } else { monster_race *r_ptr = &r_info[o_ptr->pval]; std::size_t i = 0; if (r_ptr->flags & RF_UNIQUE) { // Unique monster; no preceding "your" and ignore name buf += r_ptr->name; } else if ((i = o_ptr->inscription.find("#named ")) != std::string::npos) { // We've named it; extract the name */ buf += o_ptr->inscription.substr(i); } else { // No special cases; just return "Your ". buf += "your "; buf += r_ptr->name; } } // Capitalize? if (capitalize) { buf[0] = toupper(buf[0]); } // Done return buf; } /* * Find monster power */ monster_power const *lookup_monster_power(std::size_t idx) { for (auto const &p: monster_powers) { if (p.monster_spell_index == idx) { return &p; } } return nullptr; } /* * Extract powers */ std::vector extract_monster_powers(monster_race const *r_ptr, bool great) { std::vector powers; powers.reserve(MONSTER_POWERS_MAX); for (std::size_t i = 0; i < monster_spell_flag_set::nbits; i++) { if (r_ptr->spells.bit(i)) { if (auto power = lookup_monster_power(i)) { if (power->great && (!great)) { continue; } powers.push_back(power); } } } return powers; } /** * Calculate mana required for a given monster power. */ static int calc_monster_spell_mana(monster_power const *mp_ptr) { int mana = mp_ptr->mana / 10; if (mana > p_ptr->msp) mana = p_ptr->msp; if (!mana) mana = 1; return mana; } /** * Choose a monster power */ static std::tuple choose_monster_power(monster_race const *r_ptr, bool great, bool symbiosis) { /* Extract available monster powers */ auto powers = extract_monster_powers(r_ptr, great); int const num = powers.size(); // Avoid signed/unsigned warnings if (!num) { msg_print("You have no powers you can use."); return std::make_tuple(0, num); } /* Get the last label */ int label = (num <= 26) ? I2A(num - 1) : I2D(num - 1 - 26); /* Build a prompt (accept all spells) */ /* Mega Hack -- if no_cost is false, we're actually a Possessor -dsb */ char out_val[160]; strnfmt(out_val, 78, "(Powers a-%c, ESC=exit) Use which power of your %s? ", label, (symbiosis ? "symbiote" : "body")); /* Save the screen */ character_icky = TRUE; Term_save(); /* Get a spell from the user */ monster_power const *power = nullptr; bool_ flag = FALSE; // Nothing chosen yet while (!flag) { /* Show the list */ { byte y = 1, x = 0; int ctr = 0; char dummy[80]; strcpy(dummy, ""); prt ("", y++, x); while (ctr < num) { monster_power const *mp_ptr = powers[ctr]; label = (ctr < 26) ? I2A(ctr) : I2D(ctr - 26); byte color = TERM_L_GREEN; if (!symbiosis) { int mana = calc_monster_spell_mana(mp_ptr); strnfmt(dummy, 80, " %c) %2d %s", label, mana, mp_ptr->name); // Gray out if player doesn't have enough mana to cast. if (mana > p_ptr->csp) { color = TERM_L_DARK; } } else { strnfmt(dummy, 80, " %c) %s", label, mp_ptr->name); } if (ctr < 17) { c_prt(color, dummy, y + ctr, x); } else { c_prt(color, dummy, y + ctr - 17, x + 40); } ctr++; } if (ctr < 17) { prt ("", y + ctr, x); } else { prt ("", y + 17, x); } } char choice; if (!get_com(out_val, &choice)) { flag = FALSE; break; } if (choice == '\r' && num == 1) { choice = 'a'; } int i; int ask; if (isalpha(choice)) { /* Note verify */ ask = (isupper(choice)); /* Lowercase */ if (ask) choice = tolower(choice); /* Extract request */ i = (islower(choice) ? A2I(choice) : -1); } else { /* Can't uppercase digits XXX XXX XXX */ ask = FALSE; i = choice - '0' + 26; } /* Totally Illegal */ if ((i < 0) || (i >= num)) { bell(); continue; } /* Save the spell */ power = powers[i]; /* Make sure it's actually possible for the player to cast */ if (!symbiosis) { if (p_ptr->csp < calc_monster_spell_mana(power)) { bell(); continue; } } /* Verify it */ if (ask) { char tmp_val[160]; /* Prompt */ strnfmt(tmp_val, 78, "Use %s? ", power->name); /* Belay that order */ if (!get_check(tmp_val)) continue; } /* Stop the loop */ flag = TRUE; } /* Restore the screen */ Term_load(); character_icky = FALSE; /* Abort if needed */ if (!flag || (power == nullptr)) { return std::make_tuple(-1, num); } return std::make_tuple(power->monster_spell_index, num); } /* * Apply the effect of a monster power */ static void apply_monster_power(monster_race const *r_ptr, std::size_t monster_spell_idx) { assert(monster_spell_idx < monster_spell_flag_set::nbits); /* Shorthand */ int const x = p_ptr->px; int const y = p_ptr->py; int const plev = p_ptr->lev; int const rlev = ((r_ptr->level >= 1) ? r_ptr->level : 1); /* 'Powerful' monsters have wider radii */ int rad = (r_ptr->flags & RF_POWERFUL) ? 1 + (p_ptr->lev / 15) : 1 + (p_ptr->lev / 20); /* Analyse power */ switch (monster_spell_idx) { case SF_SHRIEK_IDX: { aggravate_monsters( -1); break; } case SF_MULTIPLY_IDX: { do_cmd_wiz_named_friendly(p_ptr->body_monster, FALSE); break; } case SF_S_ANIMAL_IDX: { summon_specific_friendly(y, x, rlev, SUMMON_ANIMAL, TRUE); break; } case SF_ROCKET_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_ROCKET, dir, p_ptr->lev * 12, 1 + (p_ptr->lev / 20)); break; } case SF_ARROW_1_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ARROW, dir, damroll(1, 6)); break; } case SF_ARROW_2_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ARROW, dir, damroll(3, 6)); break; } case SF_ARROW_3_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ARROW, dir, damroll(5, 6)); break; } case SF_ARROW_4_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ARROW, dir, damroll(7, 6)); break; } case SF_BR_ACID_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_ACID, dir, p_ptr->lev * 5, rad); break; } case SF_BR_ELEC_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_ELEC, dir, p_ptr->lev * 5, rad); break; } case SF_BR_FIRE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_FIRE, dir, p_ptr->lev * 5, rad); break; } case SF_BR_COLD_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_COLD, dir, p_ptr->lev * 5, rad); break; } case SF_BR_POIS_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_POIS, dir, p_ptr->lev * 5, rad); break; } case SF_BR_NETH_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_NETHER, dir, p_ptr->lev * 5, rad); break; } case SF_BR_LITE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_LITE, dir, p_ptr->lev * 8, rad); break; } case SF_BR_DARK_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_DARK, dir, p_ptr->lev * 8, rad); break; } case SF_BR_CONF_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_CONFUSION, dir, p_ptr->lev * 8, rad); break; } case SF_BR_SOUN_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_SOUND, dir, p_ptr->lev * 8, rad); break; } case SF_BR_CHAO_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_CHAOS, dir, p_ptr->lev * 7, rad); break; } case SF_BR_DISE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_DISENCHANT, dir, p_ptr->lev * 7, rad); break; } case SF_BR_NEXU_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_NEXUS, dir, p_ptr->lev * 5, rad); break; } case SF_BR_TIME_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_TIME, dir, p_ptr->lev * 3, rad); break; } case SF_BR_INER_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_INERTIA, dir, p_ptr->lev * 4, rad); break; } case SF_BR_GRAV_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_GRAVITY, dir, p_ptr->lev * 4, rad); break; } case SF_BR_SHAR_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_SHARDS, dir, p_ptr->lev * 8, rad); break; } case SF_BR_PLAS_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_PLASMA, dir, p_ptr->lev * 3, rad); break; } case SF_BR_WALL_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_FORCE, dir, p_ptr->lev * 4, rad); break; } case SF_BR_MANA_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_MANA, dir, p_ptr->lev * 5, rad); break; } case SF_BA_NUKE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_NUKE, dir, p_ptr->lev * 8, 1 + (p_ptr->lev / 20)); break; } case SF_BR_NUKE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_NUKE, dir, p_ptr->lev * 8, 1 + (p_ptr->lev / 20)); break; } case SF_BA_CHAO_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_CHAOS, dir, p_ptr->lev * 4, 2); break; } case SF_BR_DISI_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_DISINTEGRATE, dir, p_ptr->lev * 5, 1 + (p_ptr->lev / 20)); break; } case SF_BA_ACID_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_ACID, dir, randint(p_ptr->lev * 6) + 20, 2); break; } case SF_BA_ELEC_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_ELEC, dir, randint(p_ptr->lev * 3) + 20, 2); break; } case SF_BA_FIRE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_FIRE, dir, randint(p_ptr->lev * 7) + 20, 2); break; } case SF_BA_COLD_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_COLD, dir, randint(p_ptr->lev * 3) + 20, 2); break; } case SF_BA_POIS_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_POIS, dir, damroll(12, 2), 2); break; } case SF_BA_NETH_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_NETHER, dir, randint(p_ptr->lev * 4) + 20, 2); break; } case SF_BA_WATE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_WATER, dir, randint(p_ptr->lev * 4) + 20, 2); break; } case SF_BA_MANA_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_MANA, dir, randint(p_ptr->lev * 3) + 20, 2); break; } case SF_BA_DARK_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_ball(GF_DARK, dir, randint(p_ptr->lev * 3) + 20, 2); break; } case SF_CAUSE_1_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(3, 8)); break; } case SF_CAUSE_2_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(8, 8)); break; } case SF_CAUSE_3_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(10, 15)); break; } case SF_CAUSE_4_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(15, 15)); break; } case SF_BO_ACID_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ACID, dir, damroll(7, 8) + (p_ptr->lev / 3)); break; } case SF_BO_ELEC_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ELEC, dir, damroll(4, 8) + (p_ptr->lev / 3)); break; } case SF_BO_FIRE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_FIRE, dir, damroll(9, 8) + (p_ptr->lev / 3)); break; } case SF_BO_COLD_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_COLD, dir, damroll(6, 8) + (p_ptr->lev / 3)); break; } case SF_BO_POIS_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_POIS, dir, damroll(7, 8) + (p_ptr->lev / 3)); break; } case SF_BO_NETH_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_NETHER, dir, damroll(5, 5) + (p_ptr->lev / 3)); break; } case SF_BO_WATE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_WATER, dir, damroll(10, 10) + (p_ptr->lev / 3)); break; } case SF_BO_MANA_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(3, 8) + (p_ptr->lev / 3)); break; } case SF_BO_PLAS_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_PLASMA, dir, damroll(8, 8) + (p_ptr->lev / 3)); break; } case SF_BO_ICEE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_ICE, dir, damroll(6, 6) + (p_ptr->lev / 3)); break; } case SF_MISSILE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MISSILE, dir, damroll(2, 6) + (p_ptr->lev / 3)); break; } case SF_SCARE_IDX: { int dir; if (!get_aim_dir(&dir)) break; fear_monster(dir, plev); break; } case SF_BLIND_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_CONFUSION, dir, damroll(1, 8) + (p_ptr->lev / 3)); break; } case SF_CONF_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_CONFUSION, dir, damroll(7, 8) + (p_ptr->lev / 3)); break; } case SF_SLOW_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_OLD_SLOW, dir, damroll(6, 8) + (p_ptr->lev / 3)); break; } case SF_HOLD_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_OLD_SLEEP, dir, damroll(5, 8) + (p_ptr->lev / 3)); break; } case SF_HASTE_IDX: { if (!p_ptr->fast) { (void)set_fast(randint(20 + (plev) ) + plev, 10); } else { (void)set_fast(p_ptr->fast + randint(5), 10); } break; } case SF_HAND_DOOM_IDX: { int dir; if (!get_aim_dir(&dir)) break; fire_bolt(GF_MANA, dir, damroll(10, 8) + (p_ptr->lev)); break; } case SF_HEAL_IDX: { hp_player(damroll(8, 5)); break; } case SF_S_ANIMALS_IDX: { for (int k = 0; k < 4; k++) { summon_specific_friendly(y, x, rlev, SUMMON_ANIMAL, TRUE); } break; } case SF_BLINK_IDX: { if (dungeon_flags & DF_NO_TELEPORT) { msg_print("No teleport on special levels..."); break; } teleport_player(10); break; } case SF_TPORT_IDX: { if (dungeon_flags & DF_NO_TELEPORT) { msg_print("No teleport on special levels..."); break; } teleport_player(plev * 5); break; } case SF_TELE_TO_IDX: { int ii, ij; if (dungeon_flags & DF_NO_TELEPORT) { msg_print("No teleport on special levels..."); break; } msg_print("You go between."); if (!tgt_pt(&ii, &ij)) break; p_ptr->energy -= 60 - plev; if (!cave_empty_bold(ij, ii) || (cave[ij][ii].info & CAVE_ICKY) || (distance(ij, ii, p_ptr->py, p_ptr->px) > plev * 20 + 2)) { msg_print("You fail to show the destination correctly!"); p_ptr->energy -= 100; teleport_player(10); } else teleport_player_to(ij, ii); break; } case SF_TELE_AWAY_IDX: { if (dungeon_flags & DF_NO_TELEPORT) { msg_print("No teleport on special levels..."); break; } int dir; if (!get_aim_dir(&dir)) break; (void)fire_beam(GF_AWAY_ALL, dir, plev); break; } case SF_TELE_LEVEL_IDX: { if (dungeon_flags & DF_NO_TELEPORT) { msg_print("No teleport on special levels..."); break; } teleport_player_level(); break; } case SF_DARKNESS_IDX: { (void)project( -1, 3, p_ptr->py, p_ptr->px, 0, GF_DARK_WEAK, PROJECT_GRID | PROJECT_KILL); /* Unlite the room */ unlite_room(p_ptr->py, p_ptr->px); break; } case SF_TRAPS_IDX: { trap_creation(); break; } case SF_S_THUNDERLORD_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_THUNDERLORD, TRUE); } break; } case SF_S_KIN_IDX: { /* Big hack */ summon_kin_type = r_ptr->d_char; for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, SUMMON_KIN, TRUE); } break; } case SF_S_HI_DEMON_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_HI_DEMON, TRUE); } break; } case SF_S_MONSTER_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, 0, TRUE); } break; } case SF_S_MONSTERS_IDX: { for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, 0, TRUE); } break; } case SF_S_ANT_IDX: { for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, SUMMON_ANT, TRUE); } break; } case SF_S_SPIDER_IDX: { for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, SUMMON_SPIDER, TRUE); } break; } case SF_S_HOUND_IDX: { for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, SUMMON_HOUND, TRUE); } break; } case SF_S_HYDRA_IDX: { for (int k = 0; k < 6; k++) { summon_specific_friendly(y, x, rlev, SUMMON_HYDRA, TRUE); } break; } case SF_S_ANGEL_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_ANGEL, TRUE); } break; } case SF_S_DEMON_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_DEMON, TRUE); } break; } case SF_S_UNDEAD_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_UNDEAD, TRUE); } break; } case SF_S_DRAGON_IDX: { for (int k = 0; k < 1; k++) { summon_specific_friendly(y, x, rlev, SUMMON_DRAGON, TRUE); } break; } case SF_S_HI_UNDEAD_IDX: { for (int k = 0; k < 8; k++) { summon_specific_friendly(y, x, rlev, SUMMON_HI_UNDEAD_NO_UNIQUES, TRUE); } break; } case SF_S_HI_DRAGON_IDX: { for (int k = 0; k < 8; k++) { summon_specific_friendly(y, x, rlev, SUMMON_HI_DRAGON_NO_UNIQUES, TRUE); } break; } case SF_S_WRAITH_IDX: { for (int k = 0; k < 8; k++) { summon_specific_friendly(y, x, rlev, SUMMON_WRAITH, TRUE); } break; } } } /* * Use a monster power and call the given callback. */ static int use_monster_power_aux(monster_race const *r_ptr, bool great, bool symbiosis, std::function f) { int power; int num; std::tie(power, num) = choose_monster_power(r_ptr, great, symbiosis); // Early exit? if (power == 0) { // No powers available return 0; } else if (power < 0) { // Canceled by user energy_use = 0; return -1; } // Apply the effect apply_monster_power(r_ptr, power); // Post-processing f(&monster_powers[power]); /* Redraw mana */ p_ptr->redraw |= (PR_FRAME); /* Window stuff */ p_ptr->window |= (PW_PLAYER); return (num); } /** * Use a power of the monster in symbiosis */ int use_symbiotic_power(int r_idx, bool great) { monster_race const *r_ptr = &r_info[r_idx]; return use_monster_power_aux(r_ptr, great, true, [](monster_power const *) { // Don't need to do anything post-cast. }); } /** * Use a power of a possessed body. */ void use_monster_power(int r_idx, bool great) { monster_race const *r_ptr = &r_info[r_idx]; use_monster_power_aux(r_ptr, great, false, [r_ptr](monster_power const *power) { // Sometimes give a free cast. int chance = (power->mana + r_ptr->level); int pchance = adj_str_wgt[p_ptr->stat_ind[A_WIS]] / 2 + get_skill(SKILL_POSSESSION); if (rand_int(chance) >= pchance) { p_ptr->csp -= calc_monster_spell_mana(power); } }); } /* * Schooled magic */ /* * Find a spell in any books/objects */ static int hack_force_spell = -1; static s32b hack_force_spell_pval = -1; boost::optional get_item_hook_find_spell(object_filter_t const &) { char buf[80]; strcpy(buf, "Manathrust"); if (!get_string("Spell name? ", buf, 79)) { return boost::none; } int const spell = find_spell(buf); if (spell == -1) { return boost::none; } for (int i = 0; i < INVEN_TOTAL; i++) { object_type *o_ptr = &p_ptr->inventory[i]; /* Extract object flags */ auto const flags = object_flags(o_ptr); /* Must we wield it to cast from it? */ if ((wield_slot(o_ptr) != -1) && (i < INVEN_WIELD) && (flags & TR_WIELD_CAST)) { continue; } /* Is it a non-book? */ if (!is_school_book()(o_ptr)) { /* Does it contain the appropriate spell? */ if ((flags & TR_SPELL_CONTAIN) && (o_ptr->pval2 == spell)) { hack_force_spell = spell; hack_force_spell_pval = o_ptr->pval; return i; } } /* A random book ? */ else if (school_book_contains_spell(o_ptr->sval, spell)) { hack_force_spell = spell; hack_force_spell_pval = o_ptr->pval; return i; } } return boost::none; } /* * Is the spell castable? */ bool_ is_ok_spell(s32b spell_idx, s32b pval) { spell_type *spell = spell_at(spell_idx); // Calculate availability based on caster's skill level. s32b level; bool_ na; get_level_school(spell, 50, 0, &level, &na); if (na || (level == 0)) { return FALSE; } // Are we permitted to cast based on item pval? Only music // spells have non-zero minimum PVAL. if (pval < spell_type_minimum_pval(spell)) { return FALSE; } // OK, we're permitted to cast it. return TRUE; } /* * Get a spell from a book */ s32b get_school_spell(cptr do_what, s16b force_book) { int i, item; s32b spell = -1; int num = 0; s32b where = 1; int ask; bool_ flag; char out_val[160]; object_type *o_ptr, forge; int tmp; int sval, pval; hack_force_spell = -1; hack_force_spell_pval = -1; /* Ok do we need to ask for a book ? */ if (!force_book) { char buf2[40]; char buf3[40]; sprintf(buf2, "You have no book to %s from", do_what); sprintf(buf3, "%s from which book?", do_what); if (!get_item(&item, buf3, buf2, USE_INVEN | USE_EQUIP, hook_school_spellable(), get_item_hook_find_spell)) { return -1; } /* Get the item */ o_ptr = get_object(item); auto const f = object_flags(o_ptr); /* If it can be wielded, it must */ if ((wield_slot(o_ptr) != -1) && (item < INVEN_WIELD) && (f & TR_WIELD_CAST)) { msg_format("You cannot %s from that object; it must be wielded first.", do_what); return -1; } } else { o_ptr = &forge; o_ptr->tval = TV_BOOK; o_ptr->sval = force_book; o_ptr->pval = 0; } if (repeat_pull(&tmp)) { return tmp; } /* Nothing chosen yet */ flag = FALSE; /* Show choices */ window_stuff(); /* No spell to cast by default */ spell = -1; /* Is it a random book, or something else ? */ if (is_school_book()(o_ptr)) { sval = o_ptr->sval; pval = o_ptr->pval; } else { sval = 255; pval = o_ptr->pval2; } /* Save the screen */ character_icky = TRUE; Term_save(); /* Go */ if (hack_force_spell == -1) { num = school_book_length(sval); /* Build a prompt (accept all spells) */ strnfmt(out_val, 78, "(Spells %c-%c, Descs %c-%c, ESC=exit) %^s which spell? ", I2A(0), I2A(num - 1), I2A(0) - 'a' + 'A', I2A(num - 1) - 'a' + 'A', do_what); /* Get a spell from the user */ while (!flag) { char choice; /* Restore and save screen; this prevents subprompt from leaving garbage when going around the loop multiple times. */ Term_load(); Term_save(); /* Display a list of spells */ where = print_book(sval, pval, o_ptr); /* Input */ if (!get_com(out_val, &choice)) { flag = FALSE; break; } /* Note verify */ ask = (isupper(choice)); /* Lowercase */ if (ask) choice = tolower(choice); /* Extract request */ i = (islower(choice) ? A2I(choice) : -1); /* Totally Illegal */ if ((i < 0) || (i >= num)) { bell(); continue; } /* Verify it */ if (ask) { /* Display a list of spells */ where = print_book(sval, pval, o_ptr); print_spell_desc(spell_x(sval, pval, i), where); } else { bool_ ok; /* Save the spell index */ spell = spell_x(sval, pval, i); /* Do we need to do some pre test */ ok = is_ok_spell(spell, o_ptr->pval); /* Require "okay" spells */ if (!ok) { bell(); msg_format("You may not %s that spell.", do_what); spell = -1; continue; } /* Stop the loop */ flag = TRUE; } } } else { bool_ ok; /* Require "okay" spells */ ok = is_ok_spell(hack_force_spell, hack_force_spell_pval); if (ok) { flag = TRUE; spell = hack_force_spell; } else { bell(); msg_format("You may not %s that spell.", do_what); spell = -1; } } /* Restore the screen */ Term_load(); character_icky = FALSE; /* Show choices */ window_stuff(); /* Abort if needed */ if (!flag) return -1; tmp = spell; repeat_push(tmp); return spell; } void cast_school_spell() { int spell; /* No magic */ if (p_ptr->antimagic) { msg_print("Your anti-magic field disrupts any magic attempts."); return; } /* No magic */ if (p_ptr->anti_magic) { msg_print("Your anti-magic shell disrupts any magic attempts."); return; } spell = get_school_spell("cast", 0); /* Actualy cast the choice */ if (spell != -1) { lua_cast_school_spell(spell, FALSE); } } /* Can it contains a schooled spell ? */ static bool hook_school_can_spellable(object_type const *o_ptr) { auto const f = object_flags(o_ptr); return ((f & TR_SPELL_CONTAIN) && (o_ptr->pval2 == -1)); } /* * Copy a spell from a bok to an object */ void do_cmd_copy_spell() { int spell = get_school_spell("copy", 0); int item; if (spell == -1) return; /* Spells that cannot be randomly created cannot be copied */ if (spell_type_random_type(spell_at(spell)) <= 0) { msg_print("This spell cannot be copied."); return; } if (!get_item(&item, "Copy to which object? ", "You have no object to copy to.", (USE_INVEN | USE_EQUIP), hook_school_can_spellable)) return; object_type *o_ptr = get_object(item); msg_print("You copy the spell!"); o_ptr->pval2 = spell; inven_item_describe(item); }