diff options
Diffstat (limited to 'src/cmd5.cc')
-rw-r--r-- | src/cmd5.cc | 2155 |
1 files changed, 2155 insertions, 0 deletions
diff --git a/src/cmd5.cc b/src/cmd5.cc new file mode 100644 index 00000000..a93759b0 --- /dev/null +++ b/src/cmd5.cc @@ -0,0 +1,2155 @@ +/* + * 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 "game.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 <boost/noncopyable.hpp> +#include <boost/optional.hpp> +#include <cassert> + +/* 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. + */ + +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() +{ + /* 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() +{ + auto const &race_info = game->edit_data.race_info; + + 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) + { + dec_stat(tmp, randint(6) + 6, (rand_int(3) == 0)); + power -= 1; + } + tmp++; + } + + /* Deformities are discriminated against! */ + 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(race_info.size()); + 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[0]) ? "n" : ""), + race_info[new_race].title.c_str()); + } + else + { + msg_format("You turn into a %s %s!", effect_msg, + race_info[new_race].title.c_str()); + } + + 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) + { + 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) +{ + auto const &r_info = game->edit_data.r_info; + + 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 + { + auto 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 <monster type>". + 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<monster_power const *> extract_monster_powers(monster_race const *r_ptr, bool great) +{ + std::vector<monster_power const *> 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<int, int> 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) + { + set_fast(randint(20 + (plev) ) + plev, 10); + } + else + { + 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; + + 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: + { + 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_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<void(monster_power const *power)> 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) +{ + auto const &r_info = game->edit_data.r_info; + + 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) +{ + auto const &r_info = game->edit_data.r_info; + + 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<int> 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); +} |