/* * File: spl-cast.cc * Summary: Spell casting and miscast functions. * Written by: Linley Henzell */ #include "AppHdr.h" #include #include #include "spl-cast.h" #include "externs.h" #include "options.h" #include "beam.h" #include "coord.h" #include "coordit.h" #include "describe.h" #include "directn.h" #include "effects.h" #include "env.h" #include "map_knowledge.h" #include "food.h" #include "format.h" #include "godabil.h" #include "goditem.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemname.h" #include "itemprop.h" #include "macro.h" #include "menu.h" #include "misc.h" #include "message.h" #include "mon-cast.h" #include "mon-place.h" #include "mon-project.h" #include "mon-stuff.h" #include "mutation.h" #include "ouch.h" #include "player.h" #include "religion.h" #include "skills.h" #include "skills2.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spells4.h" #include "spl-book.h" #include "spl-mis.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "transform.h" #include "tutorial.h" #include "view.h" #include "shout.h" static bool _surge_identify_boosters(spell_type spell) { const unsigned int typeflags = get_spell_disciplines(spell); if ( (typeflags & SPTYP_FIRE) || (typeflags & SPTYP_ICE) ) { // Must not be wielding an unIDed staff. // Note that robes of the Archmagi identify on wearing, // so that's less of an issue. const item_def* wpn = you.weapon(); if (wpn == NULL || wpn->base_type != OBJ_STAVES || item_ident(*wpn, ISFLAG_KNOW_PROPERTIES)) { int num_unknown = 0; for (int i = EQ_LEFT_RING; i <= EQ_RIGHT_RING; ++i) { if (player_wearing_slot(i) && !item_ident(you.inv[you.equip[i]], ISFLAG_KNOW_PROPERTIES)) { ++num_unknown; } } // We can also identify cases with two unknown rings, both // of fire (or both of ice)...let's skip it. if (num_unknown == 1) { for (int i = EQ_LEFT_RING; i <= EQ_RIGHT_RING; ++i) if (player_wearing_slot(i)) { item_def& ring = you.inv[you.equip[i]]; if (!item_ident(ring, ISFLAG_KNOW_PROPERTIES) && (ring.sub_type == RING_FIRE || ring.sub_type == RING_ICE)) { set_ident_type( ring.base_type, ring.sub_type, ID_KNOWN_TYPE ); set_ident_flags(ring, ISFLAG_KNOW_PROPERTIES); mprf("You are wearing: %s", ring.name(DESC_INVENTORY_EQUIP).c_str()); } } return (true); } } } return (false); } static void _surge_power(spell_type spell) { int enhanced = 0; _surge_identify_boosters(spell); enhanced += spell_enhancement(get_spell_disciplines(spell)); if (enhanced) // one way or the other {dlb} { mprf("You feel a%s %s", (enhanced < -2) ? "n extraordinarily" : (enhanced == -2) ? "n extremely" : (enhanced == 2) ? " strong" : (enhanced > 2) ? " huge" : "", (enhanced < 0) ? "numb sensation." : "surge of power!"); } } static std::string _spell_base_description(spell_type spell, bool grey = false) { std::ostringstream desc; if (grey) desc << ""; desc << std::left; // spell name desc << std::setw(30) << spell_title(spell); // spell schools desc << spell_schools_string(spell); const int so_far = desc.str().length() - (grey ? 10 : 0); if (so_far < 60) desc << std::string(60 - so_far, ' '); // spell fail rate, level desc << std::setw(12) << failure_rate_to_string(spell_fail(spell)) << spell_difficulty(spell); if (grey) desc << ""; return desc.str(); } static std::string _spell_extra_description(spell_type spell, bool grey = false) { std::ostringstream desc; if (grey) desc << ""; desc << std::left; // spell name desc << std::setw(30) << spell_title(spell); // spell power, spell range, hunger level, level const std::string rangestring = spell_range_string(spell); desc << std::setw(14) << spell_power_string(spell) << std::setw(16 + tagged_string_tag_length(rangestring)) << rangestring << std::setw(12) << spell_hunger_string(spell) << spell_difficulty(spell); if (grey) desc << ""; return desc.str(); } static bool _spell_no_hostile_in_range(spell_type spell, int minRange) { if (minRange < 0) return (false); bool bonus = 0; switch (spell) { // These don't target monsters. case SPELL_APPORTATION: case SPELL_PROJECTED_NOISE: case SPELL_CONJURE_FLAME: case SPELL_DIG: case SPELL_PASSWALL: // Airstrike has LOS_RANGE and can go through glass walls. case SPELL_AIRSTRIKE: // These bounce and may be aimed elsewhere to bounce at monsters // outside range (I guess). case SPELL_SHOCK: case SPELL_LIGHTNING_BOLT: return (false); case SPELL_EVAPORATE: case SPELL_MEPHITIC_CLOUD: case SPELL_FIREBALL: case SPELL_FREEZING_CLOUD: case SPELL_POISONOUS_CLOUD: // Increase range by one due to cloud radius. bonus = 1; break; default: break; } // The healing spells. if (testbits(get_spell_flags(spell), SPFLAG_HELPFUL)) return (false); const int range = calc_spell_range(spell); if (range < 0) return (false); if (range + bonus < minRange) return (true); return (false); } int list_spells(bool toggle_with_I, bool viewing, int minRange, spell_selector selector) { if (toggle_with_I && get_spell_by_letter('I') != SPELL_NO_SPELL) toggle_with_I = false; #ifdef USE_TILE const bool text_only = false; #else const bool text_only = true; #endif ToggleableMenu spell_menu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALWAYS_SHOW_MORE | MF_ALLOW_FORMATTING, text_only); #ifdef USE_TILE { // [enne] - Hack. Make title an item so that it's aligned. ToggleableMenuEntry* me = new ToggleableMenuEntry( " Your Spells Type " " Success Level", " Your Spells Power " "Range Hunger Level", MEL_ITEM); me->colour = BLUE; spell_menu.add_entry(me); } #else spell_menu.set_title( new ToggleableMenuEntry( " Your Spells Type " " Success Level", " Your Spells Power " "Range Hunger Level", MEL_TITLE)); #endif spell_menu.set_highlighter(NULL); spell_menu.set_tag("spell"); spell_menu.add_toggle_key('!'); std::string more_str = "Press '!' "; if (toggle_with_I) { spell_menu.add_toggle_key('I'); more_str += "or 'I' "; } if (!viewing) spell_menu.menu_action = Menu::ACT_EXECUTE; more_str += "to toggle spell view."; spell_menu.set_more(formatted_string(more_str)); bool grey = false; // Needs to be greyed out? for (int i = 0; i < 52; ++i) { const char letter = index_to_letter(i); const spell_type spell = get_spell_by_letter(letter); // If an equipped artefact prevents teleportation, the following spells // cannot be cast. if ((spell == SPELL_BLINK || spell == SPELL_CONTROLLED_BLINK || spell == SPELL_TELEPORT_SELF) && scan_artefacts(ARTP_PREVENT_TELEPORTATION, false)) { grey = true; } else if (!viewing) { if (spell_mana(spell) > you.magic_points || _spell_no_hostile_in_range(spell, minRange)) { grey = true; } else grey = false; } if (is_valid_spell(spell) && selector && !(*selector)(spell, grey)) continue; if (spell != SPELL_NO_SPELL) { ToggleableMenuEntry* me = new ToggleableMenuEntry(_spell_base_description(spell, grey), _spell_extra_description(spell, grey), MEL_ITEM, 1, letter); #ifdef USE_TILE me->add_tile(tile_def(tileidx_spell(spell), TEX_GUI)); #endif spell_menu.add_entry(me); } } while (true) { std::vector sel = spell_menu.show(); if (!crawl_state.doing_prev_cmd_again) redraw_screen(); if (sel.empty()) return 0; ASSERT(sel.size() == 1); ASSERT(sel[0]->hotkeys.size() == 1); if (spell_menu.menu_action == Menu::ACT_EXAMINE) { describe_spell(get_spell_by_letter(sel[0]->hotkeys[0])); redraw_screen(); } else return sel[0]->hotkeys[0]; } } static int _apply_spellcasting_success_boosts(spell_type spell, int chance) { int wizardry = player_mag_abil(false); int fail_reduce = 100; int wiz_factor = 87; if (you.religion == GOD_VEHUMET && !player_under_penance() && you.piety >= piety_breakpoint(1) && vehumet_supports_spell(spell)) { // [dshaligram] Fail rate multiplier used to be .5, scaled // back to 67%. fail_reduce = fail_reduce * 67 / 100; } // [dshaligram] Apply wizardry factor here, rather than mixed into the // pre-scaling spell power. while (wizardry-- > 0) { fail_reduce = fail_reduce * wiz_factor / 100; wiz_factor += (100 - wiz_factor) / 3; } // Apply Brilliance factor here. if (you.duration[DUR_BRILLIANCE]) fail_reduce = fail_reduce * 67 / 100; // Draconians get a boost to dragon-form. if (spell == SPELL_DRAGON_FORM && player_genus(GENPC_DRACONIAN)) fail_reduce = fail_reduce * 70 / 100; // Hard cap on fail rate reduction. if (fail_reduce < 50) fail_reduce = 50; return (chance * fail_reduce / 100); } int spell_fail(spell_type spell) { int chance = 60; int chance2 = 0, armour = 0; // Don't cap power for failure rate purposes. chance -= 6 * calc_spell_power(spell, false, true, false); chance -= (you.intel * 2); //chance -= (you.intel - 10) * abs(you.intel - 10); //chance += spell_difficulty(spell) * spell_difficulty(spell) * 3; //spell_difficulty(spell); if (you.equip[EQ_BODY_ARMOUR] != -1) { int ev_penalty = abs(property( you.inv[you.equip[EQ_BODY_ARMOUR]], PARM_EVASION )); // The minus 15 is to make the -1 light armours not so bad armour += (20 * ev_penalty) - 15; //jmf: armour skill now reduces failure due to armour //bwr: this was far too good, an armour skill of 5 was // completely negating plate mail. Plate mail should // hardly be completely negated, it should still be // an important consideration for even high level characters. // Truth is, even a much worse penalty than the above can // easily be overcome by gaining spell skills... and a lot // faster than any reasonable rate of bonus here. int lim_str = (you.strength > 30) ? 30 : (you.strength < 10) ? 10 : you.strength; armour -= ((you.skills[SK_ARMOUR] * lim_str) / 15); int race_arm = get_equip_race( you.inv[you.equip[EQ_BODY_ARMOUR]] ); int racial_type = 0; if (player_genus(GENPC_DWARVEN)) racial_type = ISFLAG_DWARVEN; else if (player_genus(GENPC_ELVEN)) racial_type = ISFLAG_ELVEN; else if (you.species == SP_HILL_ORC) racial_type = ISFLAG_ORCISH; // Elven armour gives everyone some benefit to spellcasting, // Dwarven armour hinders everyone. switch (race_arm) { case ISFLAG_ELVEN: armour -= 20; break; case ISFLAG_DWARVEN: armour += 10; break; default: break; } // Armour of the same racial type reduces penalty. if (racial_type && race_arm == racial_type) armour -= 10; if (armour > 0) chance += armour; } if (you.weapon() && you.weapon()->base_type == OBJ_WEAPONS) { int wpn_penalty = (3 * (property(*you.weapon(), PWPN_SPEED) - 12))/2; if (wpn_penalty > 0) chance += wpn_penalty; } if (you.shield()) { switch (you.shield()->sub_type) { case ARM_BUCKLER: chance += 5; break; case ARM_SHIELD: chance += 15; break; case ARM_LARGE_SHIELD: // *BCR* Large chars now get a lower penalty for large shields if (player_genus(GENPC_OGRE) || you.species == SP_TROLL || player_genus(GENPC_DRACONIAN)) { chance += 20; } else chance += 30; break; } } switch (spell_difficulty(spell)) { case 1: chance += 3; break; case 2: chance += 15; break; case 3: chance += 35; break; case 4: chance += 70; break; case 5: chance += 100; break; case 6: chance += 150; break; case 7: chance += 200; break; case 8: chance += 260; break; case 9: chance += 330; break; case 10: chance += 420; break; case 11: chance += 500; break; case 12: chance += 600; break; default: chance += 750; break; } chance2 = chance; const int chance_breaks[][2] = { {45, 45}, {42, 43}, {38, 41}, {35, 40}, {32, 38}, {28, 36}, {22, 34}, {16, 32}, {10, 30}, {2, 28}, {-7, 26}, {-12, 24}, {-18, 22}, {-24, 20}, {-30, 18}, {-38, 16}, {-46, 14}, {-60, 12}, {-80, 10}, {-100, 8}, {-120, 6}, {-140, 4}, {-160, 2}, {-180, 0} }; for ( unsigned int i = 0; i < ARRAYSZ(chance_breaks); ++i ) if ( chance < chance_breaks[i][0] ) chance2 = chance_breaks[i][1]; if (you.duration[DUR_TRANSFORMATION] > 0) { switch (you.attribute[ATTR_TRANSFORMATION]) { case TRAN_BLADE_HANDS: chance2 += 20; break; case TRAN_SPIDER: case TRAN_BAT: chance2 += 10; break; } } // Apply the effects of Vehumet and items of wizardry. chance2 = _apply_spellcasting_success_boosts(spell, chance2); if (chance2 > 100) chance2 = 100; return (chance2); } // end spell_fail() int calc_spell_power(spell_type spell, bool apply_intel, bool fail_rate_check, bool cap_power) { // When checking failure rates, wizardry is handled after the various // stepping calulations. int power = (you.skills[SK_SPELLCASTING] / 2) + (fail_rate_check? 0 : player_mag_abil(false)); int enhanced = 0; unsigned int disciplines = get_spell_disciplines( spell ); //jmf: evil evil evil -- exclude HOLY bit disciplines &= (~SPTYP_HOLY); int skillcount = count_bits( disciplines ); if (skillcount) { for (int ndx = 0; ndx <= SPTYP_LAST_EXPONENT; ndx++) { unsigned int bit = (1 << ndx); if (disciplines & bit) power += (you.skills[spell_type2skill(bit)] * 2) / skillcount; } } if (apply_intel) power = (power * you.intel) / 10; // [dshaligram] Enhancers don't affect fail rates any more, only spell // power. Note that this does not affect Vehumet's boost in castability. if (!fail_rate_check) enhanced = spell_enhancement( disciplines ); if (enhanced > 0) { for (int i = 0; i < enhanced; i++) { power *= 15; power /= 10; } } else if (enhanced < 0) { for (int i = enhanced; i < 0; i++) power /= 2; } power = stepdown_value( power, 50, 50, 150, 200 ); const int cap = spell_power_cap(spell); if (cap > 0 && cap_power) power = std::min(power, cap); return (power); } int spell_enhancement( unsigned int typeflags ) { int enhanced = 0; if (typeflags & SPTYP_CONJURATION) enhanced += player_spec_conj(); if (typeflags & SPTYP_ENCHANTMENT) enhanced += player_spec_ench(); if (typeflags & SPTYP_SUMMONING) enhanced += player_spec_summ(); if (typeflags & SPTYP_POISON) enhanced += player_spec_poison(); if (typeflags & SPTYP_NECROMANCY) enhanced += player_spec_death() - player_spec_holy(); if (typeflags & SPTYP_FIRE) enhanced += player_spec_fire() - player_spec_cold(); if (typeflags & SPTYP_ICE) enhanced += player_spec_cold() - player_spec_fire(); if (typeflags & SPTYP_EARTH) enhanced += player_spec_earth() - player_spec_air(); if (typeflags & SPTYP_AIR) enhanced += player_spec_air() - player_spec_earth(); if (you.attribute[ATTR_SHADOWS]) enhanced -= 2; // These are used in an exponential way, so we'll limit them a bit. -- bwr if (enhanced > 3) enhanced = 3; else if (enhanced < -3) enhanced = -3; return (enhanced); } // end spell_enhancement() void inspect_spells() { if (!you.spell_no) { mpr("You don't know any spells."); return; } // Maybe we should honour auto_list here, but if you want the // description, you probably want the listing, too. list_spells(true, true); } static int _get_dist_to_nearest_monster() { int minRange = LOS_RADIUS + 1; for (radius_iterator ri(&you.get_los_no_trans(), true); ri; ++ri) { const monsters *mon = monster_at(*ri); if (mon == NULL) continue; if (!mon->visible_to(&you) || mons_is_unknown_mimic(mon)) { continue; } // Plants/fungi don't count. if (mons_class_flag(mon->type, M_NO_EXP_GAIN)) continue; if (mon->wont_attack()) continue; int dist = grid_distance(you.pos(), *ri); if (dist < minRange) minRange = dist; } return (minRange); } // Returns false if spell failed, and true otherwise. bool cast_a_spell(bool check_range, spell_type spell) { if (!you.spell_no) { mpr("You don't know any spells."); crawl_state.zero_turns_taken(); return (false); } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (false); } if (silenced(you.pos())) { mpr("You cannot cast spells when silenced!"); crawl_state.zero_turns_taken(); more(); return (false); } const int minRange = _get_dist_to_nearest_monster(); if (spell == SPELL_NO_SPELL) { int keyin = 0; while (true) { if (keyin == 0) { mpr("Cast which spell? (? or * to list) ", MSGCH_PROMPT); keyin = get_ch(); } if (keyin == '?' || keyin == '*') { keyin = list_spells(true, false, minRange); if (!keyin) keyin = ESCAPE; if (!crawl_state.doing_prev_cmd_again) redraw_screen(); if (isalpha(keyin) || keyin == ESCAPE) break; else mesclr(); keyin = 0; } else break; } if (keyin == ESCAPE) { canned_msg( MSG_OK ); return (false); } if (!isalpha(keyin)) { mpr("You don't know that spell."); crawl_state.zero_turns_taken(); return (false); } spell = get_spell_by_letter( keyin ); } if (spell == SPELL_NO_SPELL) { mpr("You don't know that spell."); crawl_state.zero_turns_taken(); return (false); } if (spell_mana(spell) > you.magic_points) { mpr("You don't have enough magic to cast that spell."); return (false); } if (check_range && _spell_no_hostile_in_range(spell, minRange)) { // Abort if there are no hostiles within range, but flash the range // markers for about half a second. mpr("There are no visible monsters within range! (Use Z to " "cast anyway.)"); if (Options.darken_beyond_range) { crawl_state.darken_range = calc_spell_range(spell); viewwindow(false, false); delay(500); crawl_state.darken_range = -1; viewwindow(false, false); } return (false); } if (you.is_undead != US_UNDEAD && you.species != SP_VAMPIRE && (you.hunger_state == HS_STARVING || you.hunger <= spell_hunger( spell ))) { mpr("You don't have the energy to cast that spell."); return (false); } const bool staff_energy = player_energy(); if (you.confused()) random_uselessness(); else { const spret_type cast_result = your_spells(spell); if (cast_result == SPRET_ABORT) { crawl_state.zero_turns_taken(); return (false); } exercise_spell( spell, true, cast_result == SPRET_SUCCESS ); did_god_conduct( DID_SPELL_CASTING, 1 + random2(5) ); } dec_mp( spell_mana(spell) ); if (!staff_energy && you.is_undead != US_UNDEAD) { const int spellh = calc_hunger( spell_hunger(spell) ); if (spellh > 0) { make_hungry(spellh, true); learned_something_new(TUT_SPELL_HUNGER); } } you.turn_is_over = true; alert_nearby_monsters(); return (true); } // end cast_a_spell() // "Utility" spells for the sake of simplicity are currently ones with // enchantments, translocations, or divinations. static bool _spell_is_utility_spell(spell_type spell_id) { return (spell_typematch(spell_id, SPTYP_ENCHANTMENT | SPTYP_TRANSLOCATION)); } bool maybe_identify_staff(item_def &item, spell_type spell) { if (item_type_known(item)) return (true); int relevant_skill = 0; const bool chance = (spell != SPELL_NO_SPELL); switch (item.sub_type) { case STAFF_ENERGY: if (!chance) // The staff of energy only autoIDs by chance. return (false); // intentional fall-through case STAFF_WIZARDRY: relevant_skill = you.skills[SK_SPELLCASTING]; break; case STAFF_FIRE: if (!chance || spell_typematch(spell, SPTYP_FIRE)) relevant_skill = you.skills[SK_FIRE_MAGIC]; else if (spell_typematch(spell, SPTYP_ICE)) relevant_skill = you.skills[SK_ICE_MAGIC]; break; case STAFF_COLD: if (!chance || spell_typematch(spell, SPTYP_ICE)) relevant_skill = you.skills[SK_ICE_MAGIC]; else if (spell_typematch(spell, SPTYP_FIRE)) relevant_skill = you.skills[SK_FIRE_MAGIC]; break; case STAFF_AIR: if (!chance || spell_typematch(spell, SPTYP_AIR)) relevant_skill = you.skills[SK_AIR_MAGIC]; else if (spell_typematch(spell, SPTYP_EARTH)) relevant_skill = you.skills[SK_EARTH_MAGIC]; break; case STAFF_EARTH: if (!chance || spell_typematch(spell, SPTYP_EARTH)) relevant_skill = you.skills[SK_EARTH_MAGIC]; else if (spell_typematch(spell, SPTYP_AIR)) relevant_skill = you.skills[SK_AIR_MAGIC]; break; case STAFF_POISON: if (!chance || spell_typematch(spell, SPTYP_POISON)) relevant_skill = you.skills[SK_POISON_MAGIC]; break; case STAFF_DEATH: if (!chance || spell_typematch(spell, SPTYP_NECROMANCY)) relevant_skill = you.skills[SK_NECROMANCY]; break; case STAFF_CONJURATION: if (!chance || spell_typematch(spell, SPTYP_CONJURATION)) relevant_skill = you.skills[SK_CONJURATIONS]; break; case STAFF_ENCHANTMENT: if (!chance || spell_typematch(spell, SPTYP_ENCHANTMENT)) relevant_skill = you.skills[SK_ENCHANTMENTS]; break; case STAFF_SUMMONING: if (!chance || spell_typematch(spell, SPTYP_SUMMONING)) relevant_skill = you.skills[SK_SUMMONINGS]; break; } bool id_staff = false; if (chance) { if (you.skills[SK_SPELLCASTING] > relevant_skill) relevant_skill = you.skills[SK_SPELLCASTING]; if (x_chance_in_y(relevant_skill, 100)) id_staff = true; } else if (relevant_skill >= 4) id_staff = true; if (id_staff) { item_def& wpn = *you.weapon(); set_ident_type(wpn, ID_KNOWN_TYPE); set_ident_flags( wpn, ISFLAG_IDENT_MASK); mprf("You are wielding %s.", wpn.name(DESC_NOCAP_A).c_str()); more(); you.wield_change = true; } return (id_staff); } static void _spellcasting_side_effects(spell_type spell, bool idonly = false) { if (you.weapon() && item_is_staff(*you.weapon())) maybe_identify_staff(*you.weapon(), spell); if (idonly) return; // If you are casting while a god is acting, then don't do conducts. // (Presumably Xom is forcing you to cast a spell.) if (!_spell_is_utility_spell(spell) && !crawl_state.is_god_acting()) did_god_conduct(DID_SPELL_NONUTILITY, 10 + spell_difficulty(spell)); if (is_holy_spell(spell) && !crawl_state.is_god_acting()) did_god_conduct(DID_HOLY, 10 + spell_difficulty(spell)); if (is_unholy_spell(spell) && !crawl_state.is_god_acting()) did_god_conduct(DID_UNHOLY, 10 + spell_difficulty(spell)); if (is_unclean_spell(spell) && !crawl_state.is_god_acting()) did_god_conduct(DID_UNCLEAN, 10 + spell_difficulty(spell)); if (is_chaotic_spell(spell) && !crawl_state.is_god_acting()) did_god_conduct(DID_CHAOS, 10 + spell_difficulty(spell)); // Linley says: Condensation Shield needs some disadvantages to keep // it from being a no-brainer... this isn't much, but its a start. - bwr if (spell_typematch(spell, SPTYP_FIRE)) expose_player_to_element(BEAM_FIRE, 0); if (spell_typematch(spell, SPTYP_NECROMANCY) && !crawl_state.is_god_acting()) { did_god_conduct(DID_NECROMANCY, 10 + spell_difficulty(spell)); if (spell == SPELL_NECROMUTATION && is_good_god(you.religion)) excommunication(); } alert_nearby_monsters(); } static bool _vampire_cannot_cast(spell_type spell) { if (you.species != SP_VAMPIRE) return (false); if (you.hunger_state > HS_SATIATED) return (false); // Satiated or less switch (spell) { case SPELL_ALTER_SELF: case SPELL_BERSERKER_RAGE: case SPELL_BLADE_HANDS: case SPELL_CURE_POISON: case SPELL_DRAGON_FORM: case SPELL_ICE_FORM: case SPELL_RESIST_POISON: case SPELL_SPIDER_FORM: case SPELL_STATUE_FORM: case SPELL_STONESKIN: case SPELL_TAME_BEASTS: return (true); default: return (false); } } static bool _spell_is_uncastable(spell_type spell) { if (you.undead_or_demonic() && is_holy_spell(spell)) { mpr("You can't use this type of magic!"); return (true); } // Normally undead can't memorise these spells, so this check is // to catch those in Lich form. As such, we allow the Lich form // to be extended here. - bwr if (spell != SPELL_NECROMUTATION && you_cannot_memorise(spell)) { mpr( "You cannot cast that spell in your current form!" ); return (true); } if (spell == SPELL_SYMBOL_OF_TORMENT && player_res_torment(true, false)) { mpr("To torment others, one must first know what torment means. "); return (true); } if (_vampire_cannot_cast(spell)) { mpr("Your current blood level is not sufficient to cast that spell."); return (true); } return (false); } #ifdef WIZARD static void _try_monster_cast(spell_type spell, int powc, dist &spd, bolt &beam) { if (monster_at(you.pos())) { mpr("Couldn't try casting monster spell because you're " "on top of a monster."); return; } monsters* mon = get_free_monster(); if (!mon) { mpr("Couldn't try casting monster spell because there is " "no empty monster slot."); return; } mpr("Invalid player spell, attempting to cast it as monster spell."); mon->mname = "Dummy Monster"; mon->type = MONS_HUMAN; mon->behaviour = BEH_SEEK; mon->attitude = ATT_FRIENDLY; mon->flags = (MF_NO_REWARD | MF_JUST_SUMMONED | MF_SEEN | MF_WAS_IN_VIEW | MF_HARD_RESET); mon->hit_points = you.hp; mon->hit_dice = you.experience_level; mon->set_position(you.pos()); mon->target = spd.target; if (!spd.isTarget) mon->foe = MHITNOT; else if (!monster_at(spd.target)) { if (spd.isMe) mon->foe = MHITYOU; else mon->foe = MHITNOT; } else mon->foe = mgrd(spd.target); mgrd(you.pos()) = mon->mindex(); mons_cast(mon, beam, spell); mon->reset(); } #endif // WIZARD beam_type _spell_to_beam_type(spell_type spell) { switch (spell) { case SPELL_FREEZE: return BEAM_COLD; default: break; } return BEAM_NONE; } int _setup_evaporate_cast() { int rc = prompt_invent_item("Throw which potion?", MT_INVLIST, OBJ_POTIONS); if (prompt_failed(rc)) { rc = -1; } else if (you.inv[rc].base_type != OBJ_POTIONS) { mpr("This spell works only on potions!"); rc = -1; } else { mprf(MSGCH_PROMPT, "Where do you want to aim %s?", you.inv[rc].name(DESC_NOCAP_YOUR).c_str()); } return rc; } static bool _can_cast_detect() { if (player_in_mappable_area()) return true; mpr("You feel momentarily disoriented."); return false; } // Returns SPRET_SUCCESS if spell is successfully cast for purposes of // exercising, SPRET_FAIL otherwise, or SPRET_ABORT if the player canceled // the casting. // Not all of these are actually real spells; invocations, decks, rods or misc. // effects might also land us here. // Others are currently unused or unimplemented. spret_type your_spells(spell_type spell, int powc, bool allow_fail) { ASSERT(!crawl_state.arena); const bool wiz_cast = (crawl_state.prev_cmd == CMD_WIZARD && !allow_fail); dist spd; bolt beam; beam.origin_spell = spell; // [dshaligram] Any action that depends on the spellcasting attempt to have // succeeded must be performed after the switch(). if (!wiz_cast && _spell_is_uncastable(spell)) return (SPRET_ABORT); if ((spell == SPELL_BLINK || spell == SPELL_CONTROLLED_BLINK || spell == SPELL_TELEPORT_SELF) && scan_artefacts(ARTP_PREVENT_TELEPORTATION, false) && !yesno("You cannot teleport right now. Cast anyway?", true, 'n')) { return (SPRET_ABORT); } const unsigned int flags = get_spell_flags(spell); ASSERT(wiz_cast || !(flags & SPFLAG_TESTING)); if ((flags & SPFLAG_TESTING) && !wiz_cast) { mprf(MSGCH_ERROR, "Spell %s is a testing spell, but you didn't use " "the &Z wizard command; please file a bug report.", spell_title(spell)); return (SPRET_ABORT); } int potion = -1; // XXX: This handles only some of the cases where spells need // targetting. There are others that do their own that will be // missed by this (and thus will not properly ESC without cost // because of it). Hopefully, those will eventually be fixed. - bwr if ((flags & SPFLAG_TARGETTING_MASK) && spell != SPELL_PORTAL_PROJECTILE) { targ_mode_type targ = (testbits(flags, SPFLAG_HELPFUL) ? TARG_FRIEND : TARG_HOSTILE); if (testbits(flags, SPFLAG_NEUTRAL)) targ = TARG_ANY; targetting_type dir = (testbits(flags, SPFLAG_TARG_OBJ) ? DIR_TARGET_OBJECT : testbits(flags, SPFLAG_TARGET) ? DIR_TARGET : testbits(flags, SPFLAG_GRID) ? DIR_TARGET : testbits(flags, SPFLAG_DIR) ? DIR_DIR : DIR_NONE); const char *prompt = get_spell_target_prompt(spell); if (spell == SPELL_EVAPORATE) { potion = _setup_evaporate_cast(); if (potion == -1) return (SPRET_ABORT); } else if (dir == DIR_DIR) mpr(prompt ? prompt : "Which direction? ", MSGCH_PROMPT); const bool needs_path = (!testbits(flags, SPFLAG_GRID) && !testbits(flags, SPFLAG_TARGET)); const bool dont_cancel_me = testbits(flags, SPFLAG_AREA); const int range = calc_spell_range(spell, powc, false); if (!spell_direction(spd, beam, dir, targ, range, needs_path, true, dont_cancel_me, prompt, testbits(flags, SPFLAG_NOT_SELF))) { return (SPRET_ABORT); } if (spd.isMe && spell == SPELL_MEPHITIC_CLOUD) { if (i_feel_safe(false, false, true, 1) && !yesno("Really target yourself?", false, 'n')) { return (SPRET_ABORT); } } beam.range = calc_spell_range(spell, powc, true); if (testbits(flags, SPFLAG_NOT_SELF) && spd.isMe) { if (spell == SPELL_TELEPORT_OTHER || spell == SPELL_POLYMORPH_OTHER || spell == SPELL_BANISHMENT) { mpr("Sorry, this spell works on others only."); } else canned_msg(MSG_UNTHINKING_ACT); return (SPRET_ABORT); } } // Enhancers only matter for calc_spell_power() and spell_fail(). // Not sure about this: is it flavour or misleading? (jpeg) if (powc == 0 || allow_fail) _surge_power(spell); // Added this so that the passed in powc can have meaning. - bwr // Remember that most holy spells don't yet use powc! if (powc == 0) powc = calc_spell_power(spell, true); const god_type god = (crawl_state.is_god_acting()) ? crawl_state.which_god_acting() : GOD_NO_GOD; const bool normal_cast = crawl_state.prev_cmd == CMD_CAST_SPELL && god == GOD_NO_GOD; const int loudness = spell_noise(spell); const bool sound_at_caster = !(flags & SPFLAG_TARGETTING_MASK); // Make some noise if it's actually the player casting. // NOTE: zappy() sets up noise for beams. if (god == GOD_NO_GOD) noisy(sound_at_caster ? loudness : 1, you.pos()); if (allow_fail) { int spfl = random2avg(100, 3); if (you.religion != GOD_SIF_MUNA && you.penance[GOD_SIF_MUNA] && one_chance_in(20)) { god_speaks(GOD_SIF_MUNA, "You feel a surge of divine spite."); // This will cause failure and increase the miscast effect. spfl = -you.penance[GOD_SIF_MUNA]; // Reduced penance reduction here because casting spells // is a player controllable act. - bwr if (one_chance_in(12)) dec_penance(GOD_SIF_MUNA, 1); } else if (spell_typematch(spell, SPTYP_NECROMANCY) && you.religion != GOD_KIKUBAAQUDGHA && you.penance[GOD_KIKUBAAQUDGHA] && one_chance_in(20)) { // And you thought you'd Necromutate your way out of penance... simple_god_message(" does not allow the disloyal to dabble in " "death!", GOD_KIKUBAAQUDGHA); // The spell still goes through, but you get a miscast anyway. MiscastEffect(&you, -god, SPTYP_NECROMANCY, (you.experience_level / 2) + (spell_mana(spell) * 2), random2avg(88, 3), "the malice of Kikubaaqudgha"); } const int spfail_chance = spell_fail(spell); // Divination mappings backfire in Labyrinths and the Abyss. if (testbits(env.level_flags, LFLAG_NO_MAGIC_MAP) && testbits(flags, SPFLAG_MAPPING)) { mprf(MSGCH_WARN, "The warped magic of this place twists your " "spell in on itself!"); spfl = spfail_chance / 2 - 1; } if (spfl < spfail_chance) { _spellcasting_side_effects(spell, true); mpr("You miscast the spell."); flush_input_buffer(FLUSH_ON_FAILURE); learned_something_new(TUT_SPELL_MISCAST); if (you.religion == GOD_SIF_MUNA && !player_under_penance() && you.piety >= 100 && x_chance_in_y(you.piety + 1, 150)) { canned_msg(MSG_NOTHING_HAPPENS); return (SPRET_FAIL); } // All spell failures give a bit of magical radiation. // Failure is a function of power squared multiplied by how // badly you missed the spell. High power spells can be // quite nasty: 9 * 9 * 90 / 500 = 15 points of // contamination! int nastiness = spell_mana(spell) * spell_mana(spell) * (spfail_chance - spfl) + 250; const int cont_points = div_rand_round(nastiness, 500); // miscasts are uncontrolled contaminate_player(cont_points); MiscastEffect(&you, NON_MONSTER, spell, spell_mana(spell), spfail_chance - spfl); return (SPRET_FAIL); } } dprf("Spell #%d, power=%d", spell, powc); switch (spell) { // spells using burn_freeze() case SPELL_FREEZE: if (!burn_freeze(powc, _spell_to_beam_type(spell), monster_at(you.pos() + spd.delta))) { return (SPRET_ABORT); } break; // direct beams/bolts case SPELL_MAGIC_DART: if (!zapping(ZAP_MAGIC_DARTS, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_STRIKING: if (!zapping(ZAP_STRIKING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_THROW_FLAME: if (!zapping(ZAP_FLAME, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_THROW_FROST: if (!zapping(ZAP_FROST, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PAIN: if (!zapping(ZAP_PAIN, powc, beam, true)) return (SPRET_ABORT); // Deep Dwarves' damage reduction always blocks at least 1 hp. if (you.species != SP_DEEP_DWARF) dec_hp(1, false); break; case SPELL_FLAME_TONGUE: if (!zapping(ZAP_FLAME_TONGUE, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_SANDBLAST: if (!cast_sandblast(powc, beam)) return (SPRET_ABORT); break; case SPELL_BONE_SHARDS: if (!cast_bone_shards(powc, beam)) return (SPRET_ABORT); break; case SPELL_SHOCK: if (!zapping(ZAP_ELECTRICITY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_STING: if (!zapping(ZAP_STING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_VAMPIRIC_DRAINING: vampiric_drain(powc, spd); break; case SPELL_BOLT_OF_FIRE: if (!zapping(ZAP_FIRE, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_BOLT_OF_COLD: if (!zapping(ZAP_COLD, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PRIMAL_WAVE: if (!zapping(ZAP_PRIMAL_WAVE, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_STONE_ARROW: if (!zapping(ZAP_STONE_ARROW, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_POISON_ARROW: if (!zapping(ZAP_POISON_ARROW, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_IRON_SHOT: if (!zapping(ZAP_IRON_SHOT, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_LIGHTNING_BOLT: if (!zapping(ZAP_LIGHTNING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_BOLT_OF_MAGMA: if (!zapping(ZAP_MAGMA, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_VENOM_BOLT: if (!zapping(ZAP_VENOM_BOLT, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_BOLT_OF_DRAINING: if (!zapping(ZAP_NEGATIVE_ENERGY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_LEHUDIBS_CRYSTAL_SPEAR: if (!zapping(ZAP_CRYSTAL_SPEAR, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_BOLT_OF_INACCURACY: if (!zapping(ZAP_BEAM_OF_ENERGY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_STICKY_FLAME: if (!zapping(ZAP_STICKY_FLAME, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_DISPEL_UNDEAD: if (!zapping(ZAP_DISPEL_UNDEAD, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_ISKENDERUNS_MYSTIC_BLAST: if (!zapping(ZAP_MYSTIC_BLAST, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_AGONY: if (!zapping(ZAP_AGONY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_DISINTEGRATE: if (!zapping(ZAP_DISINTEGRATION, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_THROW_ICICLE: if (!zapping(ZAP_THROW_ICICLE, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_CIGOTUVIS_DEGENERATION: if (!zapping(ZAP_DEGENERATION, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PORKALATOR: // Wizard mode only. if (!zapping(ZAP_PORKALATOR, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_HELLFIRE: // Should only be available from Staff of Dispater and Sceptre // of Asmodeus. if (!zapping(ZAP_HELLFIRE, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_IOOD: if (!player_tracer(ZAP_IOOD, powc, beam)) return (SPRET_ABORT); if (!cast_iood(&you, powc, &beam)) return (SPRET_ABORT); break; // Clouds and explosions. case SPELL_MEPHITIC_CLOUD: if (!stinking_cloud(powc, beam)) return (SPRET_ABORT); break; case SPELL_EVAPORATE: if (!cast_evaporate(powc, beam, potion)) return SPRET_ABORT; break; case SPELL_POISONOUS_CLOUD: cast_big_c(powc, CLOUD_POISON, KC_YOU, beam); break; case SPELL_FREEZING_CLOUD: cast_big_c(powc, CLOUD_COLD, KC_YOU, beam); break; case SPELL_FIRE_STORM: cast_fire_storm(powc, beam); break; case SPELL_HELLFIRE_BURST: if (!cast_hellfire_burst(powc, beam)) return (SPRET_ABORT); break; case SPELL_ICE_STORM: if (!zapping(ZAP_ICE_STORM, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_FIREBALL: if (!fireball(powc, beam)) return (SPRET_ABORT); break; case SPELL_DELAYED_FIREBALL: if (normal_cast) crawl_state.cant_cmd_repeat("You can't repeat delayed fireball."); // This spell has two main advantages over Fireball: // // (1) The release is instantaneous, so monsters will not // get an action before the player... this allows the // player to hit monsters with a double fireball (this // is why we only allow one delayed fireball at a time, // if you want to allow for more, then the release should // take at least some amount of time). // // The casting of this spell still costs a turn. So // casting Delayed Fireball and immediately releasing // the fireball is only slightly different from casting // a regular Fireball (monsters act in the middle instead // of at the end). This is why we allow for the spell // level discount so that Fireball is free with this spell // (so that it only costs 7 levels instead of 13 to have // both). // // (2) When the fireball is released, it is guaranteed to // go off... the spell only fails at this point. This can // be a large advantage for characters who have difficulty // casting Fireball in their standard equipment. However, // the power level for the actual fireball is determined at // release, so if you do swap out your enhancers you'll // get a less powerful ball when it's released. - bwr // if (!you.attribute[ATTR_DELAYED_FIREBALL]) { // Okay, this message is weak but functional. - bwr mpr("You feel magically charged."); you.attribute[ATTR_DELAYED_FIREBALL] = 1; } else canned_msg(MSG_NOTHING_HAPPENS); break; // LOS spells case SPELL_SMITING: if (!cast_smiting(powc, beam.target)) return (SPRET_ABORT); break; case SPELL_AIRSTRIKE: airstrike(powc, spd); break; case SPELL_FRAGMENTATION: if (!cast_fragmentation(powc, spd)) return (SPRET_ABORT); break; case SPELL_PORTAL_PROJECTILE: if (!cast_portal_projectile(powc)) return SPRET_ABORT; break; // other effects case SPELL_DISCHARGE: cast_discharge(powc); break; case SPELL_CHAIN_LIGHTNING: cast_chain_lightning(powc, &you); break; case SPELL_DISPERSAL: cast_dispersal(powc); break; case SPELL_SHATTER: cast_shatter(powc); break; case SPELL_SYMBOL_OF_TORMENT: torment(TORMENT_SPELL, you.pos()); break; case SPELL_OZOCUBUS_REFRIGERATION: cast_refrigeration(powc); break; case SPELL_IGNITE_POISON: cast_ignite_poison(powc); break; // Summoning spells, and other spells that create new monsters. // If a god is making you cast one of these spells, any monsters // produced will count as god gifts. case SPELL_SUMMON_BUTTERFLIES: cast_summon_butterflies(powc, god); break; case SPELL_SUMMON_SMALL_MAMMALS: cast_summon_small_mammals(powc, god); break; case SPELL_STICKS_TO_SNAKES: cast_sticks_to_snakes(powc, god); break; case SPELL_SUMMON_SCORPIONS: cast_summon_scorpions(powc, god); break; case SPELL_SUMMON_SWARM: cast_summon_swarm(powc, god); break; case SPELL_CALL_CANINE_FAMILIAR: cast_call_canine_familiar(powc, god); break; case SPELL_SUMMON_ELEMENTAL: if (!cast_summon_elemental(powc, god)) return (SPRET_ABORT); break; case SPELL_SUMMON_ICE_BEAST: cast_summon_ice_beast(powc, god); break; case SPELL_SUMMON_UGLY_THING: cast_summon_ugly_thing(powc, god); break; case SPELL_SUMMON_DRAGON: cast_summon_dragon(powc, god); break; case SPELL_TUKIMAS_DANCE: // Temporarily turns a wielded weapon into a dancing weapon. if (normal_cast) crawl_state.cant_cmd_repeat("You can't repeat Tukima's Dance."); cast_tukimas_dance(powc, god); break; case SPELL_CONJURE_BALL_LIGHTNING: cast_conjure_ball_lightning(powc, god); break; case SPELL_CALL_IMP: cast_call_imp(powc, god); break; case SPELL_SUMMON_DEMON: cast_summon_demon(powc, god); break; case SPELL_DEMONIC_HORDE: cast_demonic_horde(powc, god); break; case SPELL_SUMMON_GREATER_DEMON: cast_summon_greater_demon(powc, god); break; case SPELL_SHADOW_CREATURES: cast_shadow_creatures(god); break; case SPELL_SUMMON_HORRIBLE_THINGS: cast_summon_horrible_things(powc, god); break; case SPELL_ANIMATE_SKELETON: mpr("You attempt to give life to the dead..."); if (animate_remains(you.pos(), CORPSE_SKELETON, BEH_FRIENDLY, MHITYOU, &you, "", god) < 0) { mpr("There is no skeleton here to animate!"); } break; case SPELL_ANIMATE_DEAD: mpr("You call on the dead to walk for you..."); animate_dead(&you, powc + 1, BEH_FRIENDLY, MHITYOU, &you, "", god); break; case SPELL_SIMULACRUM: cast_simulacrum(powc, god); break; case SPELL_TWISTED_RESURRECTION: cast_twisted_resurrection(powc, god); break; case SPELL_HAUNT: cast_haunt(powc, beam.target, god); break; case SPELL_DEATH_CHANNEL: cast_death_channel(powc, god); break; // Enchantments. case SPELL_CONFUSING_TOUCH: cast_confusing_touch(powc); break; case SPELL_CORONA: if (!zapping(ZAP_CORONA, powc + 10, beam, true)) return (SPRET_ABORT); break; case SPELL_CAUSE_FEAR: mass_enchantment(ENCH_FEAR, powc, MHITYOU); break; case SPELL_SLOW: if (!zapping(ZAP_SLOWING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_CONFUSE: if (!zapping(ZAP_CONFUSION, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_ENSLAVEMENT: if (!zapping(ZAP_ENSLAVEMENT, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_TAME_BEASTS: cast_tame_beasts(powc); break; case SPELL_HIBERNATION: { const int sleep_power = stepdown_value(powc * 9 / 10, 5, 35, 45, 50); dprf("Sleep power stepdown: %d -> %d", powc, sleep_power); if (!zapping(ZAP_HIBERNATION, sleep_power, beam, true)) return (SPRET_ABORT); break; } case SPELL_SLEEP: if (!zapping(ZAP_SLEEP, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PARALYSE: if (!zapping(ZAP_PARALYSIS, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PETRIFY: if (!zapping(ZAP_PETRIFY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_POLYMORPH_OTHER: if (!zapping(ZAP_POLYMORPH_OTHER, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_TELEPORT_OTHER: if (!zapping(ZAP_TELEPORTATION, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_INTOXICATE: cast_intoxicate(powc); break; case SPELL_MASS_CONFUSION: mass_enchantment(ENCH_CONFUSION, powc, MHITYOU); break; case SPELL_ENGLACIATION: cast_mass_sleep(powc); break; case SPELL_CONTROL_UNDEAD: mass_enchantment(ENCH_CHARM, powc, MHITYOU); break; case SPELL_ABJURATION: abjuration(powc); break; case SPELL_BANISHMENT: if (beam.target == you.pos()) { mpr("You cannot banish yourself!"); break; } if (!zapping(ZAP_BANISHMENT, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_OLGREBS_TOXIC_RADIANCE: cast_toxic_radiance(); break; // beneficial enchantments case SPELL_HASTE: if (!zapping(ZAP_HASTING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_INVISIBILITY: if (!zapping(ZAP_INVISIBILITY, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_MINOR_HEALING: if (cast_healing(5) < 0) return (SPRET_ABORT); break; case SPELL_MAJOR_HEALING: if (cast_healing(25) < 0) return (SPRET_ABORT); break; // Self-enchantments. (Spells that can only affect the player.) // Resistances. case SPELL_INSULATION: cast_insulation(powc); break; case SPELL_RESIST_POISON: cast_resist_poison(powc); break; case SPELL_SEE_INVISIBLE: cast_see_invisible(powc); break; case SPELL_CONTROL_TELEPORT: cast_teleport_control(powc); break; // Healing. case SPELL_CURE_POISON: cast_cure_poison(powc); break; // Weapon brands. case SPELL_SURE_BLADE: cast_sure_blade(powc); break; case SPELL_TUKIMAS_VORPAL_BLADE: if (!brand_weapon(SPWPN_VORPAL, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_FIRE_BRAND: if (!brand_weapon(SPWPN_FLAMING, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_FREEZING_AURA: if (!brand_weapon(SPWPN_FREEZING, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_MAXWELLS_SILVER_HAMMER: if (!brand_weapon(SPWPN_DUMMY_CRUSHING, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_POISON_WEAPON: if (!brand_weapon(SPWPN_VENOM, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_EXCRUCIATING_WOUNDS: if (!brand_weapon(SPWPN_PAIN, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_LETHAL_INFUSION: if (!brand_weapon(SPWPN_DRAINING, powc)) canned_msg(MSG_SPELL_FIZZLES); break; case SPELL_WARP_BRAND: if (!brand_weapon(SPWPN_DISTORTION, powc)) canned_msg(MSG_SPELL_FIZZLES); break; // Transformations. case SPELL_BLADE_HANDS: if (!transform(powc, TRAN_BLADE_HANDS)) return (SPRET_ABORT); break; case SPELL_SPIDER_FORM: if (!transform(powc, TRAN_SPIDER)) return (SPRET_ABORT); break; case SPELL_STATUE_FORM: if (!transform(powc, TRAN_STATUE)) return (SPRET_ABORT); break; case SPELL_ICE_FORM: if (!transform(powc, TRAN_ICE_BEAST)) return (SPRET_ABORT); break; case SPELL_DRAGON_FORM: if (!transform(powc, TRAN_DRAGON)) return (SPRET_ABORT); break; case SPELL_NECROMUTATION: if (!transform(powc, TRAN_LICH)) return (SPRET_ABORT); break; case SPELL_ALTER_SELF: if (normal_cast) crawl_state.cant_cmd_repeat("You can't repeat Alter Self."); if (!enough_hp(you.hp_max / 2, true)) { mpr("Your body is in too poor a condition for this spell " "to function."); return (SPRET_FAIL); } mpr("Your body is suffused with transfigurative energy!"); set_hp(1 + random2(you.hp), false); if (mutate(RANDOM_MUTATION, false)) did_god_conduct(DID_DELIBERATE_MUTATING, 2 + random2(3)); else mpr("Odd... you don't feel any different."); break; // General enhancement. case SPELL_BERSERKER_RAGE: if (!berserk_check_wielded_weapon()) return (SPRET_ABORT); cast_berserk(); break; case SPELL_REGENERATION: cast_regen(powc); break; case SPELL_REPEL_MISSILES: missile_prot(powc); break; case SPELL_DEFLECT_MISSILES: deflection(powc); break; case SPELL_SWIFTNESS: cast_swiftness(powc); break; case SPELL_LEVITATION: potion_effect(POT_LEVITATION, powc); break; case SPELL_FLY: cast_fly(powc); break; case SPELL_STONESKIN: cast_stoneskin(powc); break; case SPELL_STONEMAIL: stone_scales(powc); break; case SPELL_CONDENSATION_SHIELD: cast_condensation_shield(powc); break; case SPELL_OZOCUBUS_ARMOUR: ice_armour(powc, false); break; case SPELL_PHASE_SHIFT: cast_phase_shift(powc); break; case SPELL_SILENCE: cast_silence(powc); break; // other case SPELL_SELECTIVE_AMNESIA: crawl_state.cant_cmd_repeat("You can't repeat selective amnesia."); // Sif Muna power calls with true if (!cast_selective_amnesia(false)) return (SPRET_ABORT); break; case SPELL_EXTENSION: extension(powc); break; case SPELL_BORGNJORS_REVIVIFICATION: cast_revivification(powc); break; case SPELL_SUBLIMATION_OF_BLOOD: cast_sublimation_of_blood(powc); break; case SPELL_DEATHS_DOOR: cast_deaths_door(powc); break; case SPELL_RING_OF_FLAMES: cast_ring_of_flames(powc); break; // Escape spells. case SPELL_BLINK: random_blink(god != GOD_XOM); break; case SPELL_TELEPORT_SELF: you_teleport(); break; case SPELL_CONTROLLED_BLINK: if (blink(powc, true) == -1) return (SPRET_ABORT); break; // Utility spells. case SPELL_DETECT_SECRET_DOORS: if (_can_cast_detect()) cast_detect_secret_doors(powc); break; case SPELL_DETECT_TRAPS: if (_can_cast_detect()) mprf("You detect %s", (detect_traps(powc) > 0) ? "traps!" : "nothing."); break; case SPELL_DETECT_ITEMS: if (_can_cast_detect()) mprf("You detect %s", (detect_items(powc) > 0) ? "items!" : "nothing."); break; case SPELL_DETECT_CREATURES: { if (!_can_cast_detect()) break; const int prev_detected = count_detected_mons(); const int num_creatures = detect_creatures(powc); if (!num_creatures) mpr("You detect nothing."); else if (num_creatures == prev_detected) { // This is not strictly true. You could have cast // Detect Creatures with a big enough fuzz that the detected // glyph is still on the map when the original one has been // killed. Then another one is spawned, so the number is // the same as before. There's no way we can check this however. mpr("You detect no further creatures."); } else mpr("You detect creatures!"); break; } case SPELL_PROJECTED_NOISE: project_noise(); break; case SPELL_CONJURE_FLAME: if (!conjure_flame(powc, beam.target)) return (SPRET_ABORT); break; case SPELL_DIG: if (!zapping(ZAP_DIGGING, powc, beam, true)) return (SPRET_ABORT); break; case SPELL_PASSWALL: if (!cast_passwall(spd.delta, powc)) return (SPRET_ABORT); break; case SPELL_APPORTATION: if (!cast_apportation(powc, beam.target)) return (SPRET_ABORT); break; case SPELL_RECALL: recall(0); break; case SPELL_PORTAL: if (normal_cast) crawl_state.cant_cmd_repeat("You can't repeat create portal."); if (portal() == -1) return (SPRET_ABORT); break; case SPELL_CORPSE_ROT: corpse_rot(); break; case SPELL_FULSOME_DISTILLATION: cast_fulsome_distillation(powc); break; case SPELL_DEBUGGING_RAY: if (!zapping(ZAP_DEBUGGING_RAY, powc, beam, true)) return (SPRET_ABORT); break; default: #ifdef WIZARD if (you.wizard && !allow_fail && is_valid_spell(spell) && (flags & SPFLAG_MONSTER)) { _try_monster_cast(spell, powc, spd, beam); return (SPRET_SUCCESS); } #endif if (is_valid_spell(spell)) mprf(MSGCH_ERROR, "Spell '%s' is not a player castable spell.", spell_title(spell)); else mpr("Invalid spell!", MSGCH_ERROR); return (SPRET_ABORT); break; } // end switch _spellcasting_side_effects(spell); return (SPRET_SUCCESS); } void exercise_spell(spell_type spell, bool spc, bool success) { // (!success) reduces skill increase for miscast spells int skill; int exer = 0; int exer_norm = 0; int workout = 0; // This is used as a reference level to normalise spell skill training // (for Sif Muna piety). Normalised skill training is worked out as: // norm = actual_amount_trained * species_aptitude / ref_skill. This was // set at 50 in stone_soup 0.1.1 (which is bad). const int ref_skill = 80; unsigned int disciplines = get_spell_disciplines(spell); //jmf: evil evil evil -- exclude HOLY bit disciplines &= (~SPTYP_HOLY); int skillcount = count_bits( disciplines ); if (!success) skillcount += 4 + random2(10); const int diff = spell_difficulty(spell); // Fill all disciplines into a vector, then shuffle the vector, and // exercise skills in that random order. That way, small xp pools // don't always train exclusively the first skill. std::vector disc; for (int ndx = 0; ndx <= SPTYP_LAST_EXPONENT; ndx++) { if (!spell_typematch( spell, 1 << ndx )) continue; disc.push_back(ndx); } std::random_shuffle(disc.begin(), disc.end()); for (unsigned int k = 0; k < disc.size(); ++k) { int ndx = disc[k]; skill = spell_type2skill( 1 << ndx ); workout = (random2(1 + diff) / skillcount); if (!one_chance_in(5)) workout++; // most recently, this was an automatic add {dlb} const int exercise_amount = exercise( skill, workout ); exer += exercise_amount; exer_norm += exercise_amount * species_skills(skill, you.species) / ref_skill; } /* ****************************************************************** Other recent formulae for the above: * workout = random2(spell_difficulty(spell_ex) * (10 + (spell_difficulty(spell_ex) * 2 )) / 10 / spellsy + 1); * workout = spell_difficulty(spell_ex) * (15 + spell_difficulty(spell_ex)) / 15 / spellsy; spellcasting had also been generally exercised at the same time ****************************************************************** */ if (spc) { const int exercise_amount = exercise(SK_SPELLCASTING, one_chance_in(3) ? 1 : random2(1 + random2(diff))); exer += exercise_amount; exer_norm += exercise_amount * species_skills(SK_SPELLCASTING, you.species) / ref_skill; } if (exer_norm) did_god_conduct( DID_SPELL_PRACTISE, exer_norm ); } const char* failure_rate_to_string( int fail ) { return (fail == 100) ? "Useless" : // 0% success chance (fail > 77) ? "Terrible" : // 0-5% (fail > 71) ? "Cruddy" : // 5-10% (fail > 64) ? "Bad" : // 10-20% (fail > 59) ? "Very Poor" : // 20-30% (fail > 50) ? "Poor" : // 30-50% (fail > 40) ? "Fair" : // 50-70% (fail > 35) ? "Good" : // 70-80% (fail > 28) ? "Very Good" : // 80-90% (fail > 22) ? "Great" : // 90-95% (fail > 0) ? "Excellent" // 95-100% : "Perfect"; // 100% } static unsigned int _breakpoint_rank(int val, const int breakpoints[], unsigned int num_breakpoints) { unsigned int result = 0; while (result < num_breakpoints && val >= breakpoints[result]) ++result; return result; } const char* spell_hunger_string(spell_type spell) { if (you.is_undead == US_UNDEAD) return ("N/A"); const int hunger = spell_hunger(spell); // Spell hunger is "Fruit" if casting the spell five times costs at // most one "Fruit". const char* hunger_descriptions[] = { "None", "Sultana", "Strawberry", "Choko", "Honeycomb", "Ration" }; const int breakpoints[] = { 1, 15, 41, 121, 401 }; return (hunger_descriptions[_breakpoint_rank(hunger, breakpoints, ARRAYSZ(breakpoints))]); } int spell_power_colour(spell_type spell) { const int powercap = spell_power_cap(spell); if (powercap == 0) return DARKGREY; const int power = calc_spell_power(spell, true); if (power >= powercap) return WHITE; if (power * 3 < powercap) return RED; if (power * 3 < powercap * 2) return YELLOW; return GREEN; } static int _power_to_barcount( int power ) { if (power == -1) return -1; const int breakpoints[] = { 5, 10, 15, 25, 35, 50, 75, 100, 150 }; return (_breakpoint_rank(power, breakpoints, ARRAYSZ(breakpoints)) + 1); } int spell_power_bars( spell_type spell ) { const int cap = spell_power_cap(spell); if (cap == 0) return -1; const int power = std::min(calc_spell_power(spell, true), cap); return _power_to_barcount(power); } std::string spell_power_string(spell_type spell) { const int numbars = spell_power_bars(spell); const int capbars = _power_to_barcount(spell_power_cap(spell)); ASSERT(numbars <= capbars); if (numbars < 0) return "N/A"; else return std::string(numbars, '#') + std::string(capbars - numbars, '.'); } int calc_spell_range(spell_type spell, int power, bool real_cast) { if (power == 0) power = calc_spell_power(spell, true); const int range = spell_range(spell, power, real_cast); return (range); } std::string spell_range_string(spell_type spell) { const int cap = spell_power_cap(spell); const int range = calc_spell_range(spell); const int maxrange = spell_range(spell, cap, false); if (range < 0) return "N/A"; else { return std::string("@") + std::string(range - 1, '-') + std::string(">") + std::string(maxrange - range, '.'); } } std::string spell_schools_string(spell_type spell) { std::string desc; bool already = false; for (int i = 0; i <= SPTYP_LAST_EXPONENT; ++i) { if (spell_typematch(spell, (1<