/** * @file * @brief Spell casting and miscast functions. **/ #include "AppHdr.h" #include #include #include "spl-cast.h" #include "externs.h" #include "options.h" #include "areas.h" #include "art-enum.h" #include "beam.h" #include "cloud.h" #include "colour.h" #include "describe.h" #include "directn.h" #include "effects.h" #include "env.h" #include "exercise.h" #include "food.h" #include "format.h" #include "godabil.h" #include "godconduct.h" #include "goditem.h" #include "hints.h" #include "libutil.h" #include "macro.h" #include "menu.h" #include "misc.h" #include "message.h" #include "mon-behv.h" #include "mon-cast.h" #include "mon-place.h" #include "mon-project.h" #include "mon-util.h" #include "mutation.h" #include "ouch.h" #include "output.h" #include "player.h" #include "prompt.h" #include "religion.h" #include "shout.h" #include "skills.h" #include "spl-book.h" #include "spl-clouds.h" #include "spl-damage.h" #include "spl-goditem.h" #include "spl-miscast.h" #include "spl-monench.h" #include "spl-other.h" #include "spl-selfench.h" #include "spl-summoning.h" #include "spl-transloc.h" #include "spl-wpnench.h" #include "spl-zap.h" #include "state.h" #include "stepdown.h" #include "strings.h" #include "target.h" #include "terrain.h" #ifdef USE_TILE #include "tilepick.h" #endif #include "transform.h" #include "unicode.h" #include "view.h" static int _spell_enhancement(unsigned int typeflags); static void _surge_power(spell_type spell) { int enhanced = 0; enhanced += _spell_enhancement(get_spell_disciplines(spell)); if (enhanced) // one way or the other {dlb} { const string modifier = (enhanced < -2) ? "extraordinarily" : (enhanced == -2) ? "extremely" : (enhanced == 2) ? "strong" : (enhanced > 2) ? "huge" : ""; mprf("You feel %s %s", !modifier.length() ? "a" : article_a(modifier).c_str(), (enhanced < 0) ? "numb sensation." : "surge of power!"); } } static string _spell_base_description(spell_type spell, bool viewing) { ostringstream desc; int highlight = spell_highlight_by_utility(spell, COL_UNKNOWN, !viewing); desc << "<" << colour_to_str(highlight) << ">" << left; // spell name desc << chop_string(spell_title(spell), 30); // spell schools desc << spell_schools_string(spell); const int so_far = strwidth(desc.str()) - (strwidth(colour_to_str(highlight))+2); if (so_far < 60) desc << string(60 - so_far, ' '); desc << ""; // spell fail rate, level highlight = failure_rate_colour(spell); desc << "<" << colour_to_str(highlight) << ">"; char* failure = failure_rate_to_string(spell_fail(spell)); desc << chop_string(failure, 12); free(failure); desc << ""; desc << spell_difficulty(spell); return desc.str(); } static string _spell_extra_description(spell_type spell, bool viewing) { ostringstream desc; int highlight = spell_highlight_by_utility(spell, COL_UNKNOWN, !viewing); desc << "<" << colour_to_str(highlight) << ">" << left; // spell name desc << chop_string(spell_title(spell), 30); // spell power, spell range, hunger level, level const string rangestring = spell_range_string(spell); desc << chop_string(spell_power_string(spell), 14) << chop_string(rangestring, 16 + tagged_string_tag_length(rangestring)) << chop_string(spell_hunger_string(spell), 12) << spell_difficulty(spell); desc << ""; return desc.str(); } // selector is a boolean function that filters spells according // to certain criteria. Currently used for Tiles to distinguish // spells targeted on player vs. spells targeted on monsters. int list_spells(bool toggle_with_I, bool viewing, bool allow_preselect, const string &title, spell_selector selector) { if (toggle_with_I && get_spell_by_letter('I') != SPELL_NO_SPELL) toggle_with_I = false; #ifdef USE_TILE_LOCAL 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); string titlestring = make_stringf("%-25.25s", title.c_str()); #ifdef USE_TILE_LOCAL { // [enne] - Hack. Make title an item so that it's aligned. ToggleableMenuEntry* me = new ToggleableMenuEntry( " " + titlestring + " Type " " Failure Level", " " + titlestring + " Power " "Range " + "Hunger" + " Level", MEL_ITEM); me->colour = BLUE; spell_menu.add_entry(me); } #else spell_menu.set_title( new ToggleableMenuEntry( " " + titlestring + " Type " " Failure Level", " " + titlestring + " Power " "Range " + "Hunger" + " Level", MEL_TITLE)); #endif spell_menu.set_highlighter(NULL); spell_menu.set_tag("spell"); spell_menu.add_toggle_key('!'); 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::parse_string(more_str)); // If there's only a single spell in the offered spell list, // taking the selector function into account, preselect that one. bool preselect_first = false; if (allow_preselect) { int count = 0; if (you.spell_no == 1) count = 1; else if (selector) { for (int i = 0; i < 52; ++i) { const char letter = index_to_letter(i); const spell_type spell = get_spell_by_letter(letter); if (!is_valid_spell(spell) || !(*selector)(spell)) continue; // Break out early if we've got > 1 spells. if (++count > 1) break; } } // Preselect the first spell if it's only spell applicable. preselect_first = (count == 1); } if (allow_preselect || preselect_first && you.last_cast_spell != SPELL_NO_SPELL) { spell_menu.set_flags(spell_menu.get_flags() | MF_PRESELECTED); } for (int i = 0; i < 52; ++i) { const char letter = index_to_letter(i); const spell_type spell = get_spell_by_letter(letter); if (!is_valid_spell(spell)) continue; if (selector && !(*selector)(spell)) continue; bool preselect = (preselect_first || allow_preselect && you.last_cast_spell == spell); ToggleableMenuEntry* me = new ToggleableMenuEntry(_spell_base_description(spell, viewing), _spell_extra_description(spell, viewing), MEL_ITEM, 1, letter, preselect); #ifdef USE_TILE me->add_tile(tile_def(tileidx_spell(spell), TEX_GUI)); #endif spell_menu.add_entry(me); } while (true) { 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) { const int wizardry = player_wizardry(); int fail_reduce = 100; if (you_worship(GOD_VEHUMET) && !player_under_penance() && you.piety >= piety_breakpoint(2) && vehumet_supports_spell(spell)) { // [dshaligram] Fail rate multiplier used to be .5, scaled // back to 67%. fail_reduce = fail_reduce * 2 / 3; } if (wizardry > 0) fail_reduce = fail_reduce * 6 / (7 + wizardry); if (you.duration[DUR_BRILLIANCE]) fail_reduce = fail_reduce / 2; // 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; // Don't cap power for failure rate purposes. chance -= 6 * calc_spell_power(spell, false, true, false); chance -= (you.intel() * 2); const int armour_shield_penalty = player_armour_shield_spell_penalty(); dprf("Armour+Shield spell failure penalty: %d", armour_shield_penalty); chance += armour_shield_penalty; 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; } #if TAG_MAJOR_VERSION == 34 // Only apply this penalty to Dj because other species lose nutrition // rather than gaining contamination when casting spells. // Also, this penalty gives fairly precise information about contam // level, and only Dj already has such information (on the contam bar). // Other species would have to check their failure rates all the time // when at yellow glow. if (you.species == SP_DJINNI) { int64_t contam = you.magic_contamination; // Just +25 on the edge of yellow glow, +200 in the middle of yellow, // forget casting when in orange. chance += contam * contam * contam / 5000000000LL; } #endif int 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.form) { case TRAN_BLADE_HANDS: chance2 += 20; break; case TRAN_SPIDER: case TRAN_BAT: chance2 += 10; break; default: break; } } chance2 += 7 * player_mutation_level(MUT_WILD_MAGIC); chance2 -= 7 * player_mutation_level(MUT_PLACID_MAGIC); if (player_equip_unrand(UNRAND_HIGH_COUNCIL)) chance2 += 7; chance2 += you.duration[DUR_MAGIC_SAPPED] / BASELINE_DELAY; // Apply the effects of Vehumet and items of wizardry. chance2 = _apply_spellcasting_success_boosts(spell, chance2); if (chance2 > 100) chance2 = 100; return chance2; } int calc_spell_power(spell_type spell, bool apply_intel, bool fail_rate_check, bool cap_power, bool rod) { int power = 0; if (rod) power = 5 + you.skill(SK_EVOCATIONS, 3); else { int enhanced = 0; unsigned int disciplines = get_spell_disciplines(spell); 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.skill(spell_type2skill(bit), 200); } power /= skillcount; } power += you.skill(SK_SPELLCASTING, 50); // Brilliance boosts spell power a bit (equivalent to three // spell school levels). if (!fail_rate_check && you.duration[DUR_BRILLIANCE]) power += 600; 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; } // Wild magic boosts spell power but decreases success rate. if (!fail_rate_check) { power *= (10 + 5 * player_mutation_level(MUT_WILD_MAGIC)); power /= (10 + 5 * player_mutation_level(MUT_PLACID_MAGIC)); } // Augmentation boosts spell power at high HP. if (!fail_rate_check) { power *= 10 + 4 * augmentation_amount(); power /= 10; } power = stepdown_value(power / 100, 50, 50, 150, 200); } const int cap = spell_power_cap(spell); if (cap > 0 && cap_power) power = min(power, cap); return power; } static int _spell_enhancement(unsigned int typeflags) { int enhanced = 0; if (typeflags & SPTYP_CONJURATION) enhanced += player_spec_conj(); if (typeflags & SPTYP_HEXES) enhanced += player_spec_hex(); if (typeflags & SPTYP_CHARMS) enhanced += player_spec_charm(); if (typeflags & SPTYP_SUMMONING) enhanced += player_spec_summ(); if (typeflags & SPTYP_POISON) enhanced += player_spec_poison(); if (typeflags & SPTYP_NECROMANCY) enhanced += player_spec_death(); 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; if (you.form == TRAN_SHADOW) enhanced -= 2; enhanced += you.archmagi(); enhanced += player_equip_unrand(UNRAND_MAJIN); #if TAG_MAJOR_VERSION == 34 if (you.species == SP_LAVA_ORC && temperature_effect(LORC_LAVA_BOOST) && (typeflags & SPTYP_FIRE) && (typeflags & SPTYP_EARTH)) { enhanced++; } #endif // 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; } void inspect_spells() { if (!you.spell_no) { canned_msg(MSG_NO_SPELLS); return; } list_spells(true, true); } static bool _can_cast() { if (you.form == TRAN_BAT || you.form == TRAN_PIG || you.form == TRAN_PORCUPINE || you.form == TRAN_WISP || you.form == TRAN_FUNGUS) { canned_msg(MSG_PRESENT_FORM); return false; } if (you.duration[DUR_WATER_HOLD] && !you.res_water_drowning()) { mpr("You cannot cast spells while unable to breathe!"); return false; } if (you.stat_zero[STAT_INT]) { mpr("You lack the mental capacity to cast spells."); return false; } // Randart weapons. if (you.no_cast()) { mpr("Something interferes with your magic!"); return false; } if (!you.spell_no) { canned_msg(MSG_NO_SPELLS); return false; } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return false; } if (you.confused()) { mpr("You're too confused to cast spells."); return false; } if (silenced(you.pos())) { mpr("You cannot cast spells when silenced!"); more(); return false; } return true; } void do_cast_spell_cmd(bool force) { if (!cast_a_spell(!force)) flush_input_buffer(FLUSH_ON_FAILURE); } /** * Cast a spell. * * Handles general preconditions & costs. * * @param check_range If true, abort if no targets are in range. (z vs Z) * @param spell The type of spell to be cast. * @return Whether the spell was successfully cast. **/ bool cast_a_spell(bool check_range, spell_type spell) { if (!_can_cast()) { crawl_state.zero_turns_taken(); return false; } if (crawl_state.game_is_hints()) Hints.hints_spell_counter++; if (spell == SPELL_NO_SPELL) { int keyin = 0; while (true) { #ifdef TOUCH_UI keyin = list_spells(true, false); if (!keyin) keyin = ESCAPE; if (!crawl_state.doing_prev_cmd_again) redraw_screen(); if (isaalpha(keyin) || key_is_escape(keyin)) break; else clear_messages(); keyin = 0; #else if (keyin == 0) { if (you.spell_no == 1) { // Set last_cast_spell to the current only spell. for (int i = 0; i < 52; ++i) { const char letter = index_to_letter(i); const spell_type spl = get_spell_by_letter(letter); if (!is_valid_spell(spl)) continue; you.last_cast_spell = spl; break; } } if (you.last_cast_spell == SPELL_NO_SPELL) mprf(MSGCH_PROMPT, "Cast which spell? (? or * to list) "); else { mprf(MSGCH_PROMPT, "Casting: %s", spell_title(you.last_cast_spell)); mprf(MSGCH_PROMPT, "Confirm with . or Enter, or press ? or * to list all spells."); } keyin = get_ch(); } if (keyin == '?' || keyin == '*') { keyin = list_spells(true, false); if (!keyin) keyin = ESCAPE; if (!crawl_state.doing_prev_cmd_again) redraw_screen(); if (isaalpha(keyin) || key_is_escape(keyin)) break; else clear_messages(); keyin = 0; } else break; #endif } if (key_is_escape(keyin)) { canned_msg(MSG_OK); crawl_state.zero_turns_taken(); return false; } else if (keyin == '.' || keyin == CK_ENTER) spell = you.last_cast_spell; else if (!isaalpha(keyin)) { mpr("You don't know that spell."); crawl_state.zero_turns_taken(); return false; } else 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; } const int cost = spell_mana(spell); if (!enough_mp(cost, true)) { mpr("You don't have enough magic to cast that spell."); crawl_state.zero_turns_taken(); return false; } if (check_range && spell_no_hostile_in_range(spell)) { // Abort if there are no hostiles within range, but flash the range // markers for a short while. mpr("You can't see any susceptible monsters within range! " "(Use Z to cast anyway.)"); if (Options.darken_beyond_range) { targetter_smite range(&you, calc_spell_range(spell), 0, 0, true); range_view_annotator show_range(&range); delay(50); } crawl_state.zero_turns_taken(); return false; } if (!you.is_undead && !you_foodless() && (you.hunger_state == HS_STARVING || you.hunger <= spell_hunger(spell))) { canned_msg(MSG_NO_ENERGY); crawl_state.zero_turns_taken(); return false; } // This needs more work: there are spells which are hated but allowed if // they don't have a certain effect. You may use Poison Arrow on those // immune, use Mephitic Cloud to shield yourself from other clouds. // There are also spells which god_hates_spell() doesn't recognize. // // I'm disabling this code for now except for excommunication, please // re-enable if you can fix it. if (/*god_hates_spell*/god_loathes_spell(spell, you.religion)) { // None currently dock just piety, right? if (!yesno(god_loathes_spell(spell, you.religion) ? "Casting this spell will cause instant excommunication!" " Really cast?" : "Casting this spell will put you into penance. Really cast?", true, 'n')) { canned_msg(MSG_OK); crawl_state.zero_turns_taken(); return false; } } const bool staff_energy = player_energy(); you.last_cast_spell = spell; // Silently take MP before the spell. dec_mp(cost, true); const spret_type cast_result = your_spells(spell, 0, true); if (cast_result == SPRET_ABORT) { crawl_state.zero_turns_taken(); // Return the MP since the spell is aborted. inc_mp(cost, true); return false; } // XXX: the message order here might not be the best zin_recite_interrupt(); if (cast_result == SPRET_SUCCESS) { practise(EX_DID_CAST, spell); did_god_conduct(DID_SPELL_CASTING, 1 + random2(5)); count_action(CACT_CAST, spell); } else practise(EX_DID_MISCAST, spell); #if TAG_MAJOR_VERSION == 34 // Nasty special cases. if (you.species == SP_DJINNI && cast_result == SPRET_SUCCESS && (spell == SPELL_BORGNJORS_REVIVIFICATION || spell == SPELL_SUBLIMATION_OF_BLOOD && you.hp == you.hp_max)) { // These spells have replenished essence to full. inc_mp(cost, true); } else // Redraw MP #endif flush_mp(); if (!staff_energy && you.is_undead != US_UNDEAD) { const int spellh = spell_hunger(spell); if (calc_hunger(spellh) > 0) { make_hungry(spellh, true, true); learned_something_new(HINT_SPELL_HUNGER); } } you.turn_is_over = true; alert_nearby_monsters(); return true; } /** * Handles divine response to spellcasting. * * @param spell The type of spell just cast. */ static void _spellcasting_god_conduct(spell_type spell) { // If you are casting while a god is acting, then don't do conducts. // (Presumably Xom is forcing you to cast a spell.) if (crawl_state.is_god_acting()) return; const int conduct_level = 10 + spell_difficulty(spell); if (is_unholy_spell(spell) || you.spellcasting_unholy()) did_god_conduct(DID_UNHOLY, conduct_level); if (is_unclean_spell(spell)) did_god_conduct(DID_UNCLEAN, conduct_level); if (is_chaotic_spell(spell)) did_god_conduct(DID_CHAOS, conduct_level); if (is_corpse_violating_spell(spell)) did_god_conduct(DID_CORPSE_VIOLATION, conduct_level); if (spell_typematch(spell, SPTYP_NECROMANCY)) { did_god_conduct(DID_NECROMANCY, conduct_level); if (spell == SPELL_NECROMUTATION && is_good_god(you.religion)) excommunication(); } if (spell == SPELL_STATUE_FORM && you_worship(GOD_YREDELEMNUL)) excommunication(); } /** * Handles side effects of successfully casting a spell. * * Spell noise, magic 'sap' effects, and god conducts. * * @param spell The type of spell just cast. * @param god Which god is casting the spell; NO_GOD if it's you. * @param real_spell An actual spellcast, vs. spell-like effects (rods?) */ static void _spellcasting_side_effects(spell_type spell, god_type god, bool real_spell) { _spellcasting_god_conduct(spell); if (god == GOD_NO_GOD) { if (you.duration[DUR_SAP_MAGIC] && real_spell) { mprf(MSGCH_WARN, "Your control over your magic is sapped."); you.increase_duration(DUR_MAGIC_SAPPED, spell_difficulty(spell), 100); } // Make some noise if it's actually the player casting. noisy(spell_noise(spell), you.pos()); } 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_BEASTLY_APPENDAGE: case SPELL_BLADE_HANDS: case SPELL_DRAGON_FORM: case SPELL_ICE_FORM: case SPELL_SPIDER_FORM: case SPELL_STATUE_FORM: case SPELL_STONESKIN: return true; case SPELL_CURE_POISON: // You can become poisoned at any state but bloodless; allow curing // it under the same conditions. return you.hunger_state <= HS_STARVING; default: return false; } } #if TAG_MAJOR_VERSION == 34 static bool _too_hot_to_cast(spell_type spell) { if (you.species != SP_LAVA_ORC) return false; // Lava orcs can never benefit from casting stoneskin. if (spell == SPELL_STONESKIN) return true; // Lava orcs have no restrictions if their skin is // non-molten. if (temperature_effect(LORC_STONESKIN)) return false; // If it is, though, they lose out on these spells: switch (spell) { case SPELL_STATUE_FORM: // Stony self is too melty // Too hot for these ice spells: case SPELL_ICE_FORM: case SPELL_OZOCUBUS_ARMOUR: case SPELL_CONDENSATION_SHIELD: return true; default: return false; } } #endif bool is_prevented_teleport(spell_type spell) { return (spell == SPELL_BLINK || spell == SPELL_CONTROLLED_BLINK || spell == SPELL_TELEPORT_SELF) && you.no_tele(false, false, spell != SPELL_TELEPORT_SELF); } bool spell_is_uncastable(spell_type spell, string &msg) { // 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)) { msg = "You cannot cast that spell in your current form!"; return true; } if (_vampire_cannot_cast(spell)) { msg = "Your current blood level is not sufficient to cast that spell."; return true; } #if TAG_MAJOR_VERSION == 34 if (_too_hot_to_cast(spell)) { if (spell == SPELL_STONESKIN && temperature_effect(LORC_STONESKIN)) msg = "Your skin is already made of stone."; else if (spell == SPELL_STONESKIN && !temperature_effect(LORC_STONESKIN)) msg = "Your skin is already made of molten stone."; else msg = "Your temperature is too high to benefit from that spell."; return true; } #endif 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; } monster* 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->set_hit_dice(you.experience_level); mon->set_position(you.pos()); mon->target = spd.target; mon->mid = MID_PLAYER; 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 static void _maybe_cancel_repeat(spell_type spell) { switch (spell) { case SPELL_DELAYED_FIREBALL: crawl_state.cant_cmd_repeat(make_stringf("You can't repeat %s.", spell_title(spell))); break; default: break; } } static spret_type _do_cast(spell_type spell, int powc, const dist& spd, bolt& beam, god_type god, int potion, bool fail); static bool _spellcasting_aborted(spell_type spell, bool wiz_cast) { string msg; if (!wiz_cast && spell_is_uncastable(spell, msg)) { mprf("%s", msg.c_str()); return true; } if (is_prevented_teleport(spell)) { if (you.species == SP_FORMICID) mpr("You cannot teleport."); else mpr("You cannot teleport right now."); return true; } if (spell == SPELL_MALIGN_GATEWAY && !can_cast_malign_gateway()) { mpr("The dungeon can only cope with one malign gateway at a time!"); return true; } if (spell == SPELL_TORNADO && (you.duration[DUR_TORNADO] || you.duration[DUR_TORNADO_COOLDOWN])) { mpr("You need to wait for the winds to calm down."); return true; } if (spell == SPELL_SUMMON_FOREST && you.duration[DUR_FORESTED]) { mpr("This spell is already ongoing."); return true; } return false; } static targetter* _spell_targetter(spell_type spell, int pow, int range) { switch (spell) { case SPELL_FIREBALL: return new targetter_beam(&you, range, ZAP_FIREBALL, pow, 1, 1); case SPELL_HELLFIRE: return new targetter_beam(&you, range, ZAP_HELLFIRE, pow, 1, 1); case SPELL_MEPHITIC_CLOUD: return new targetter_beam(&you, range, ZAP_BREATHE_MEPHITIC, pow, pow >= 100 ? 1 : 0, 1); case SPELL_ISKENDERUNS_MYSTIC_BLAST: return new targetter_imb(&you, pow, range); case SPELL_FIRE_STORM: return new targetter_smite(&you, range, 2, pow > 76 ? 3 : 2); case SPELL_FREEZING_CLOUD: case SPELL_POISONOUS_CLOUD: case SPELL_HOLY_BREATH: return new targetter_cloud(&you, range); case SPELL_THUNDERBOLT: return new targetter_thunderbolt(&you, range, (you.props.exists("thunderbolt_last") && you.props["thunderbolt_last"].get_int() + 1 == you.num_turns) ? you.props["thunderbolt_aim"].get_coord() : coord_def()); case SPELL_LRD: return new targetter_fragment(&you, pow, range); case SPELL_FULMINANT_PRISM: return new targetter_smite(&you, range, 0, 2); case SPELL_DAZZLING_SPRAY: return new targetter_spray(&you, range, ZAP_DAZZLING_SPRAY); case SPELL_EXPLOSIVE_BOLT: return new targetter_explosive_bolt(&you, pow, range); case SPELL_GLACIATE: return new targetter_cone(&you, range); case SPELL_CLOUD_CONE: return new targetter_shotgun(&you, range); case SPELL_MAGIC_DART: case SPELL_FORCE_LANCE: case SPELL_SHOCK: case SPELL_LIGHTNING_BOLT: case SPELL_FLAME_TONGUE: case SPELL_THROW_FLAME: case SPELL_BOLT_OF_FIRE: case SPELL_THROW_FROST: case SPELL_THROW_ICICLE: case SPELL_BOLT_OF_COLD: case SPELL_STING: case SPELL_VENOM_BOLT: case SPELL_POISON_ARROW: case SPELL_BOLT_OF_MAGMA: case SPELL_IRON_SHOT: case SPELL_STONE_ARROW: case SPELL_LEHUDIBS_CRYSTAL_SPEAR: case SPELL_CORONA: case SPELL_SLOW: case SPELL_CONFUSE: case SPELL_ENSLAVEMENT: case SPELL_INNER_FLAME: case SPELL_PAIN: case SPELL_AGONY: case SPELL_BOLT_OF_DRAINING: case SPELL_HASTE: case SPELL_PETRIFY: case SPELL_POLYMORPH: case SPELL_DISPEL_UNDEAD: case SPELL_CRYSTAL_BOLT: case SPELL_TUKIMAS_DANCE: return new targetter_beam(&you, range, spell_to_zap(spell), pow, 0, 0); case SPELL_RANDOM_BOLT: return new targetter_beam(&you, range, ZAP_CRYSTAL_BOLT, pow, 0, 0); default: return 0; } } static double _chance_miscast_prot() { double miscast_prot = 0; if (you_worship(GOD_SIF_MUNA) && !player_under_penance() && you.piety >= piety_breakpoint(3)) { miscast_prot = (double) you.piety/piety_breakpoint(5); } return min(1.0, miscast_prot); } /** * Handles damage from corrupted magic effects. * * Currently only from the Majin-Bo. * * @param spell The type of spell that was just cast. **/ static void _spellcasting_corruption(spell_type spell) { // never kill the player (directly) int hp_cost = min(you.spell_hp_cost() * spell_mana(spell), you.hp - 1); const char * source = NULL; if (player_equip_unrand(UNRAND_MAJIN)) source = "the Majin-Bo"; // for debugging ouch(hp_cost, NON_MONSTER, KILLED_BY_SOMETHING, source); } /** * Targets and fires player-cast spells & spell-like effects. * * 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. * * @param spell The type of spell being cast. * @param powc Spellpower. * @param allow_fail Whether spell-fail chance applies. * @return SPRET_SUCCESS if spell is successfully cast for purposes of * exercising, SPRET_FAIL otherwise, or SPRET_ABORT if the player cancelled * the casting. **/ spret_type your_spells(spell_type spell, int powc, bool allow_fail) { ASSERT(!crawl_state.game_is_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 (_spellcasting_aborted(spell, wiz_cast)) return SPRET_ABORT; const unsigned int flags = get_spell_flags(spell); ASSERT(wiz_cast || !(flags & SPFLAG_TESTING)); int potion = -1; if (!powc) powc = calc_spell_power(spell, true); // XXX: This handles only some of the cases where spells need // targeting. 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_TARGETING_MASK) { targ_mode_type targ = (testbits(flags, SPFLAG_HELPFUL) ? TARG_FRIEND : TARG_HOSTILE); if (testbits(flags, SPFLAG_NEUTRAL)) targ = TARG_ANY; if (spell == SPELL_DISPEL_UNDEAD) targ = TARG_HOSTILE_UNDEAD; targeting_type dir = (testbits(flags, SPFLAG_TARG_OBJ) ? DIR_MOVABLE_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 (dir == DIR_DIR) mprf(MSGCH_PROMPT, "%s", prompt ? prompt : "Which direction?"); const bool needs_path = (!testbits(flags, SPFLAG_GRID) && !testbits(flags, SPFLAG_TARGET)); const bool dont_cancel_me = (testbits(flags, SPFLAG_HELPFUL) || testbits(flags, SPFLAG_ALLOW_SELF)); const int range = calc_spell_range(spell, powc); targetter *hitfunc = _spell_targetter(spell, powc, range); string title = "Aiming: "; title += spell_title(spell); title += ""; if (!spell_direction(spd, beam, dir, targ, range, needs_path, true, dont_cancel_me, prompt, title.c_str(), testbits(flags, SPFLAG_NOT_SELF), hitfunc)) { if (hitfunc) delete hitfunc; return SPRET_ABORT; } if (hitfunc) delete hitfunc; beam.range = range; if (testbits(flags, SPFLAG_NOT_SELF) && spd.isMe()) { if (spell == SPELL_TELEPORT_OTHER) mpr("Sorry, this spell works on others only."); else canned_msg(MSG_UNTHINKING_ACT); return SPRET_ABORT; } if (spell == SPELL_HASTE && spd.isMe() && you.stasis(false)) { if (you.species == SP_FORMICID) mpr("You cannot haste."); else mpr("You cannot haste right now."); return SPRET_ABORT; } } // Enhancers only matter for calc_spell_power() and spell_fail(). // Not sure about this: is it flavour or misleading? (jpeg) if (allow_fail) _surge_power(spell); const god_type god = (crawl_state.is_god_acting()) ? crawl_state.which_god_acting() : GOD_NO_GOD; int fail = 0; bool antimagic = false; // lost time but no other penalty if (allow_fail && you.duration[DUR_ANTIMAGIC] && x_chance_in_y(you.duration[DUR_ANTIMAGIC] / 3, you.hp_max)) { mpr("You fail to access your magic."); fail = antimagic = true; } else if (allow_fail) { int spfl = random2avg(100, 3); if (!you_worship(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]; } else if (spell_typematch(spell, SPTYP_NECROMANCY) && !you_worship(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_KIKUBAAQUDGHA, SPTYP_NECROMANCY, (you.experience_level / 2) + (spell_difficulty(spell) * 2), random2avg(88, 3), "the malice of Kikubaaqudgha"); } else if (vehumet_supports_spell(spell) && !you_worship(GOD_VEHUMET) && you.penance[GOD_VEHUMET] && one_chance_in(20)) { // And you thought you'd Fire Storm your way out of penance... simple_god_message(" does not allow the disloyal to dabble in " "destruction!", GOD_VEHUMET); // The spell still goes through, but you get a miscast anyway. MiscastEffect(&you, -GOD_VEHUMET, SPTYP_CONJURATION, (you.experience_level / 2) + (spell_difficulty(spell) * 2), random2avg(88, 3), "the malice of Vehumet"); } const int spfail_chance = spell_fail(spell); if (spfl < spfail_chance) fail = spfail_chance - spfl; } dprf("Spell #%d, power=%d", spell, powc); if (crawl_state.prev_cmd == CMD_CAST_SPELL && god == GOD_NO_GOD) _maybe_cancel_repeat(spell); // Have to set aim first, in case the spellcast kills its first target if (you.props.exists("battlesphere") && allow_fail) aim_battlesphere(&you, spell, powc, beam); const bool old_target = actor_at(beam.target); spret_type cast_result = _do_cast(spell, powc, spd, beam, god, potion, fail); if (cast_result != SPRET_ABORT && you.spell_hp_cost() && allow_fail) _spellcasting_corruption(spell); switch (cast_result) { case SPRET_SUCCESS: { if (you.props.exists("battlesphere") && allow_fail) trigger_battlesphere(&you, beam); actor* victim = actor_at(beam.target); if (you_worship(GOD_DITHMENOS) && allow_fail && !god_hates_spell(spell, GOD_DITHMENOS, !allow_fail) && (flags & SPFLAG_TARGETING_MASK) && !(flags & SPFLAG_NEUTRAL) && (beam.is_enchantment() || battlesphere_can_mirror(spell)) && (!old_target || (victim && !victim->is_player()))) { dithmenos_shadow_spell(&beam, spell); } _spellcasting_side_effects(spell, god, allow_fail); return SPRET_SUCCESS; } case SPRET_FAIL: { if (antimagic) return SPRET_FAIL; mprf("You miscast %s.", spell_title(spell)); flush_input_buffer(FLUSH_ON_FAILURE); learned_something_new(HINT_SPELL_MISCAST); if (decimal_chance(_chance_miscast_prot())) { simple_god_message(" protects you from the effects of your miscast!"); 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_difficulty(spell) * spell_difficulty(spell) * fail + 250; const int cont_points = 2 * nastiness; // miscasts are uncontrolled contaminate_player(cont_points, true); MiscastEffect(&you, NON_MONSTER, spell, spell_difficulty(spell), fail); return SPRET_FAIL; } case SPRET_ABORT: return SPRET_ABORT; case SPRET_NONE: #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 mprf(MSGCH_ERROR, "Invalid spell!"); return SPRET_ABORT; } return SPRET_SUCCESS; } /** * Handles special-cased aftereffects of spellcasting. * * Currently handles damage from casting Pain, since that occurs before the * spellcast, whether or not it's successful, as long as it's not aborted. * * @param spell The type of spell that was just cast. **/ static void _spell_zap_effect(spell_type spell) { // Casting pain costs 1 hp. // Deep Dwarves' damage reduction always blocks at least 1 hp. if (spell == SPELL_PAIN && (you.species != SP_DEEP_DWARF && !player_res_torment())) { dec_hp(1, false); } } // Returns SPRET_SUCCESS, SPRET_ABORT, SPRET_FAIL // or SPRET_NONE (not a player spell). static spret_type _do_cast(spell_type spell, int powc, const dist& spd, bolt& beam, god_type god, int potion, bool fail) { // First handle the zaps. zap_type zap = spell_to_zap(spell); if (zap != NUM_ZAPS) { spret_type ret = zapping(zap, spell_zap_power(spell, powc), beam, true, NULL, fail); if (ret == SPRET_SUCCESS) _spell_zap_effect(spell); return ret; } const coord_def target = spd.isTarget ? beam.target : you.pos() + spd.delta; switch (spell) { case SPELL_FREEZE: return cast_freeze(powc, monster_at(target), fail); case SPELL_SANDBLAST: return cast_sandblast(powc, beam, fail); case SPELL_VAMPIRIC_DRAINING: return vampiric_drain(powc, monster_at(target), fail); case SPELL_IOOD: return cast_iood(&you, powc, &beam, 0, 0, MHITNOT, fail); // Clouds and explosions. case SPELL_MEPHITIC_CLOUD: return stinking_cloud(powc, beam, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_EVAPORATE: case SPELL_CIGOTUVIS_DEGENERATION: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_POISONOUS_CLOUD: return cast_big_c(powc, CLOUD_POISON, &you, beam, fail); case SPELL_HOLY_BREATH: return cast_big_c(powc, CLOUD_HOLY_FLAMES, &you, beam, fail); case SPELL_FREEZING_CLOUD: return cast_big_c(powc, CLOUD_COLD, &you, beam, fail); case SPELL_FIRE_STORM: return cast_fire_storm(powc, beam, fail); // Demonspawn ability, no failure. case SPELL_HELLFIRE_BURST: return cast_hellfire_burst(powc, beam) ? SPRET_SUCCESS : SPRET_ABORT; case SPELL_DELAYED_FIREBALL: return cast_delayed_fireball(fail); // LOS spells // Beogh ability, no failure. case SPELL_SMITING: return cast_smiting(powc, monster_at(target)) ? SPRET_SUCCESS : SPRET_ABORT; case SPELL_AIRSTRIKE: return cast_airstrike(powc, spd, fail); case SPELL_LRD: return cast_fragmentation(powc, &you, spd.target, fail); // other effects case SPELL_DISCHARGE: return cast_discharge(powc, fail); case SPELL_CHAIN_LIGHTNING: return cast_chain_spell(SPELL_CHAIN_LIGHTNING, powc, &you, fail); case SPELL_DISPERSAL: return cast_dispersal(powc, fail); case SPELL_SHATTER: return cast_shatter(powc, fail); case SPELL_LEDAS_LIQUEFACTION: return cast_liquefaction(powc, fail); case SPELL_OZOCUBUS_REFRIGERATION: return cast_los_attack_spell(spell, powc, &you, true, true, fail); case SPELL_OLGREBS_TOXIC_RADIANCE: return cast_toxic_radiance(&you, powc, fail); case SPELL_IGNITE_POISON: return cast_ignite_poison(&you, powc, fail); case SPELL_TORNADO: return cast_tornado(powc, fail); case SPELL_THUNDERBOLT: return cast_thunderbolt(&you, powc, target, fail); case SPELL_DAZZLING_SPRAY: return cast_dazzling_spray(powc, target, fail); case SPELL_CHAIN_OF_CHAOS: return cast_chain_spell(SPELL_CHAIN_OF_CHAOS, powc, &you, fail); case SPELL_CLOUD_CONE: return cast_cloud_cone(&you, powc, target, fail); // 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: return cast_summon_butterflies(powc, god, fail); case SPELL_SUMMON_SMALL_MAMMAL: return cast_summon_small_mammal(powc, god, fail); case SPELL_STICKS_TO_SNAKES: return cast_sticks_to_snakes(powc, god, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_SUMMON_SCORPIONS: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_SUMMON_SWARM: return cast_summon_swarm(powc, god, fail); case SPELL_CALL_CANINE_FAMILIAR: return cast_call_canine_familiar(powc, god, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_SUMMON_ELEMENTAL: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_SUMMON_ICE_BEAST: return cast_summon_ice_beast(powc, god, fail); case SPELL_MONSTROUS_MENAGERIE: return cast_monstrous_menagerie(&you, powc, god, fail); case SPELL_SUMMON_DRAGON: return cast_summon_dragon(&you, powc, god, fail); case SPELL_DRAGON_CALL: return cast_dragon_call(powc, fail); case SPELL_SUMMON_HYDRA: return cast_summon_hydra(&you, powc, god, fail); case SPELL_SUMMON_MANA_VIPER: return cast_summon_mana_viper(powc, god, fail); case SPELL_CONJURE_BALL_LIGHTNING: return cast_conjure_ball_lightning(powc, god, fail); case SPELL_SUMMON_LIGHTNING_SPIRE: return cast_summon_lightning_spire(powc, beam.target, god, fail); case SPELL_SUMMON_GUARDIAN_GOLEM: return cast_summon_guardian_golem(powc, god, fail); case SPELL_CALL_IMP: return cast_call_imp(powc, god, fail); case SPELL_SUMMON_DEMON: return cast_summon_demon(powc, god, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_DEMONIC_HORDE: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_SUMMON_GREATER_DEMON: return cast_summon_greater_demon(powc, god, fail); case SPELL_SHADOW_CREATURES: return cast_shadow_creatures(spell, god, level_id::current(), fail); case SPELL_SUMMON_HORRIBLE_THINGS: return cast_summon_horrible_things(powc, god, fail); case SPELL_MALIGN_GATEWAY: return cast_malign_gateway(&you, powc, god, fail); case SPELL_SUMMON_FOREST: return cast_summon_forest(&you, powc, god, fail); case SPELL_ANIMATE_SKELETON: return cast_animate_skeleton(god, fail); case SPELL_ANIMATE_DEAD: return cast_animate_dead(powc, god, fail); case SPELL_SIMULACRUM: return cast_simulacrum(powc, god, fail); case SPELL_TWISTED_RESURRECTION: return cast_twisted_resurrection(powc, god, fail); case SPELL_HAUNT: return cast_haunt(powc, beam.target, god, fail); case SPELL_DEATH_CHANNEL: return cast_death_channel(powc, god, fail); case SPELL_SPELLFORGED_SERVITOR: return cast_spellforged_servitor(powc, god, fail); case SPELL_FORCEFUL_DISMISSAL: return cast_forceful_dismissal(powc, fail); case SPELL_SPECTRAL_WEAPON: return cast_spectral_weapon(&you, powc, god, fail); case SPELL_BATTLESPHERE: return cast_battlesphere(&you, powc, god, fail); // Enchantments. case SPELL_CONFUSING_TOUCH: return cast_confusing_touch(powc, fail); case SPELL_CAUSE_FEAR: return mass_enchantment(ENCH_FEAR, powc, fail); case SPELL_INTOXICATE: return cast_intoxicate(powc, fail); case SPELL_MASS_CONFUSION: return mass_enchantment(ENCH_CONFUSION, powc, fail); case SPELL_DISCORD: return mass_enchantment(ENCH_INSANE, powc, fail); case SPELL_ENGLACIATION: return cast_englaciation(powc, fail); case SPELL_CONTROL_UNDEAD: return mass_enchantment(ENCH_CHARM, powc, fail); case SPELL_ABJURATION: return cast_abjuration(powc, beam.target, fail); case SPELL_AURA_OF_ABJURATION: return cast_aura_of_abjuration(powc, fail); case SPELL_WEAVE_SHADOWS: { level_id place(BRANCH_DUNGEON, min(27, max(1, div_rand_round(powc, 3)))); return cast_shadow_creatures(spell, god, place, fail); } // XXX: I don't think any call to healing goes through here. --rla case SPELL_MINOR_HEALING: return cast_healing(5, 5); case SPELL_MAJOR_HEALING: return cast_healing(25, 25); // Self-enchantments. (Spells that can only affect the player.) // Resistances. #if TAG_MAJOR_VERSION == 34 case SPELL_INSULATION: case SPELL_SEE_INVISIBLE: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_CONTROL_TELEPORT: return cast_teleport_control(powc, fail); // Healing. case SPELL_CURE_POISON: return cast_cure_poison(powc, fail); // Weapon brands. case SPELL_SURE_BLADE: return cast_sure_blade(powc, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_FIRE_BRAND: case SPELL_FREEZING_AURA: case SPELL_POISON_WEAPON: case SPELL_LETHAL_INFUSION: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_EXCRUCIATING_WOUNDS: return brand_weapon(SPWPN_PAIN, powc, fail); case SPELL_WARP_BRAND: return brand_weapon(SPWPN_DISTORTION, powc, fail); // Transformations. case SPELL_BEASTLY_APPENDAGE: return cast_transform(powc, TRAN_APPENDAGE, fail); case SPELL_BLADE_HANDS: return cast_transform(powc, TRAN_BLADE_HANDS, fail); case SPELL_SPIDER_FORM: return cast_transform(powc, TRAN_SPIDER, fail); case SPELL_STATUE_FORM: return cast_transform(powc, TRAN_STATUE, fail); case SPELL_ICE_FORM: return cast_transform(powc, TRAN_ICE_BEAST, fail); case SPELL_DRAGON_FORM: return cast_transform(powc, TRAN_DRAGON, fail); case SPELL_NECROMUTATION: return cast_transform(powc, TRAN_LICH, fail); // General enhancement. case SPELL_REGENERATION: return cast_regen(powc, fail); case SPELL_REPEL_MISSILES: return missile_prot(powc, fail); case SPELL_DEFLECT_MISSILES: return deflection(powc, fail); case SPELL_SWIFTNESS: return cast_swiftness(powc, fail); case SPELL_FLY: return cast_fly(powc, fail); case SPELL_STONESKIN: return cast_stoneskin(powc, fail); case SPELL_CONDENSATION_SHIELD: return cast_condensation_shield(powc, fail); case SPELL_OZOCUBUS_ARMOUR: return ice_armour(powc, fail); case SPELL_PHASE_SHIFT: return cast_phase_shift(powc, fail); case SPELL_SILENCE: return cast_silence(powc, fail); case SPELL_INFUSION: return cast_infusion(powc, fail); case SPELL_SONG_OF_SLAYING: return cast_song_of_slaying(powc, fail); #if TAG_MAJOR_VERSION == 34 case SPELL_SONG_OF_SHIELDING: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_PORTAL_PROJECTILE: return cast_portal_projectile(powc, fail); // other case SPELL_BORGNJORS_REVIVIFICATION: return cast_revivification(powc, fail); case SPELL_SUBLIMATION_OF_BLOOD: return cast_sublimation_of_blood(powc, fail); case SPELL_DEATHS_DOOR: return cast_deaths_door(powc, fail); case SPELL_RING_OF_FLAMES: return cast_ring_of_flames(powc, fail); // Escape spells. case SPELL_BLINK: return cast_blink(god != GOD_XOM, fail); case SPELL_TELEPORT_SELF: return cast_teleport_self(fail); case SPELL_CONTROLLED_BLINK: return cast_controlled_blink(powc, fail); case SPELL_CONJURE_FLAME: return conjure_flame(powc, beam.target, fail); case SPELL_PASSWALL: return cast_passwall(spd.delta, powc, fail); case SPELL_APPORTATION: return cast_apportation(powc, beam, fail); case SPELL_RECALL: return cast_recall(fail); case SPELL_DISJUNCTION: return cast_disjunction(powc, fail); case SPELL_CORPSE_ROT: return cast_corpse_rot(fail); #if TAG_MAJOR_VERSION == 34 case SPELL_FULSOME_DISTILLATION: mpr("Sorry, this spell is gone!"); return SPRET_ABORT; #endif case SPELL_GOLUBRIAS_PASSAGE: return cast_golubrias_passage(beam.target, fail); case SPELL_DARKNESS: return cast_darkness(powc, fail); case SPELL_SHROUD_OF_GOLUBRIA: return cast_shroud_of_golubria(powc, fail); case SPELL_FULMINANT_PRISM: return cast_fulminating_prism(powc, beam.target, fail); case SPELL_SEARING_RAY: return cast_searing_ray(powc, beam, fail); case SPELL_MELEE: // Rod of striking mpr("This rod is automatically evoked when it strikes in combat."); return SPRET_ABORT; case SPELL_GLACIATE: return cast_glaciate(&you, powc, target, fail); case SPELL_RANDOM_BOLT: return cast_random_bolt(powc, beam, fail); default: return SPRET_NONE; } return SPRET_SUCCESS; } // _tetrahedral_number: returns the nth tetrahedral number. // This is the number of triples of nonnegative integers with sum < n. // Called only by get_true_fail_rate. static int _tetrahedral_number(int n) { return n * (n+1) * (n+2) / 6; } // get_true_fail_rate: Takes the raw failure to-beat number // and converts it to the actual chance of failure: // the probability that random2avg(100,3) < raw_fail. // Should probably use more constants, though I doubt the spell // success algorithms will really change *that* much. // Called only by failure_rate_to_int and get_miscast_chance. static double _get_true_fail_rate(int raw_fail) { //Need random2(101) + random2(101) + random2(100) to be less than 3*raw_fail. //Fun with tetrahedral numbers! int target = raw_fail * 3; if (target <= 100) return (double) _tetrahedral_number(target)/1020100; if (target <= 200) { //PIE: the negative term takes the maximum of 100 (or 99) into //consideration. Note that only one term can exceed it in this case, //which is why this works. return (double) (_tetrahedral_number(target) - 2*_tetrahedral_number(target - 101) - _tetrahedral_number(target - 100)) / 1020100; } //The random2avg distribution is symmetric, so the last interval is //essentially the same as the first interval. return (double) (1020100 - _tetrahedral_number(300 - target)) / 1020100; } /** * Compute the chance of getting a miscast effect of a given severity or higher. * @param spell The spell to be checked. * @param severity Check the chance of getting a miscast this severe or higher. * @return The chance of this kind of miscast. */ double get_miscast_chance(spell_type spell, int severity) { int raw_fail = spell_fail(spell); int level = spell_difficulty(spell); if (severity <= 0) return _get_true_fail_rate(raw_fail); double C = 70000.0 / (150 * level * (10 + level)); double chance = 0.0; int k = severity + 1; while ((C * k) <= raw_fail) { chance += _get_true_fail_rate((int) (raw_fail + 1 - (C * k))) * severity / (k * (k - 1)); k++; } return chance; } static double _get_miscast_chance_with_miscast_prot(spell_type spell) { double raw_chance = get_miscast_chance(spell); double miscast_prot = _chance_miscast_prot(); double chance = raw_chance * (1 - miscast_prot); return chance; } // Chooses a colour for the failure rate display for a spell. The colour is // based on the chance of getting a severity >= 2 miscast. int failure_rate_colour(spell_type spell) { double chance = _get_miscast_chance_with_miscast_prot(spell); return (chance < 0.001) ? LIGHTGREY : (chance < 0.005) ? YELLOW : (chance < 0.025) ? LIGHTRED : RED; } //Converts the raw failure rate into a number to be displayed. int failure_rate_to_int(int fail) { if (fail <= 0) return 0; else if (fail >= 100) return (fail + 100)/2; else return max(1, (int) (100 * _get_true_fail_rate(fail))); } //Note that this char[] is allocated on the heap, so anything calling //this function will also need to call free()! char* failure_rate_to_string(int fail) { char *buffer = (char *)malloc(9); sprintf(buffer, "%d%%", failure_rate_to_int(fail)); return buffer; } string spell_hunger_string(spell_type spell, bool rod) { return hunger_cost_string(spell_hunger(spell, rod)); } string spell_noise_string(spell_type spell) { const int casting_noise = spell_noise(spell); int effect_noise; switch (spell) //When the noise can vary, we're most interested in the worst case. { // Clouds case SPELL_POISONOUS_CLOUD: case SPELL_FREEZING_CLOUD: case SPELL_CONJURE_FLAME: effect_noise = 2; break; case SPELL_AIRSTRIKE: effect_noise = 4; break; case SPELL_IOOD: effect_noise = 7; break; case SPELL_SONG_OF_SLAYING: case SPELL_MALIGN_GATEWAY: effect_noise = 10; break; case SPELL_EXCRUCIATING_WOUNDS: // Small explosions. case SPELL_MEPHITIC_CLOUD: case SPELL_FIREBALL: case SPELL_DELAYED_FIREBALL: case SPELL_HELLFIRE_BURST: case SPELL_TORNADO: effect_noise = 15; break; // Medium explosions case SPELL_LRD: //LRD most often do small and medium explosions effect_noise = 20; //and sometimes big ones with green crystal break; // Big explosions case SPELL_FIRE_STORM: //The storms make medium or big explosions case SPELL_GLACIATE: case SPELL_LIGHTNING_BOLT: case SPELL_CHAIN_LIGHTNING: case SPELL_CONJURE_BALL_LIGHTNING: effect_noise = 25; break; case SPELL_SHATTER: effect_noise = 30; break; default: effect_noise = 0; } const int noise = max(casting_noise, effect_noise); const char* noise_descriptions[] = { "Silent", "Almost silent", "Quiet", "A bit loud", "Loud", "Very loud", "Extremely loud", "Deafening" }; const int breakpoints[] = { 1, 2, 4, 8, 15, 20, 30 }; COMPILE_CHECK(ARRAYSZ(noise_descriptions) == 1 + ARRAYSZ(breakpoints)); const char* desc = noise_descriptions[breakpoint_rank(noise, breakpoints, ARRAYSZ(breakpoints))]; #ifdef WIZARD if (you.wizard) return make_stringf("%s (%d)", desc, noise); else #endif return desc; } static int _power_to_barcount(int power) { if (power == -1) return -1; const int breakpoints[] = { 10, 15, 25, 35, 50, 75, 100, 150, 200 }; return breakpoint_rank(power, breakpoints, ARRAYSZ(breakpoints)) + 1; } static int _spell_power_bars(spell_type spell, bool rod) { const int cap = spell_power_cap(spell); if (cap == 0) return -1; const int power = min(calc_spell_power(spell, true, false, false, rod), cap); return _power_to_barcount(power); } #ifdef WIZARD static string _wizard_spell_power_numeric_string(spell_type spell, bool rod) { const int cap = spell_power_cap(spell); if (cap == 0) return "N/A"; const int power = min(calc_spell_power(spell, true, false, false, rod), cap); return make_stringf("%d (%d)", power, cap); } #endif string spell_power_string(spell_type spell, bool rod) { #ifdef WIZARD if (you.wizard) return _wizard_spell_power_numeric_string(spell, rod); #endif const int numbars = _spell_power_bars(spell, rod); const int capbars = _power_to_barcount(spell_power_cap(spell)); ASSERT(numbars <= capbars); if (numbars < 0) return "N/A"; else return string(numbars, '#') + string(capbars - numbars, '.'); } int calc_spell_range(spell_type spell, int power, bool rod) { if (power == 0) power = calc_spell_power(spell, true, false, false, rod); const int range = spell_range(spell, power); return range; } string spell_range_string(spell_type spell, bool rod) { const int cap = spell_power_cap(spell); const int range = calc_spell_range(spell, 0, rod); const int maxrange = spell_range(spell, cap); if (range < 0) return "N/A"; else { return string("@") + string(range - 1, '-') + string(">") + string(maxrange - range, '.'); } } string spell_schools_string(spell_type spell) { string desc; bool already = false; for (int i = 0; i <= SPTYP_LAST_EXPONENT; ++i) { if (spell_typematch(spell, (1< &skills) { unsigned int disciplines = get_spell_disciplines(spell); for (int i = 0; i <= SPTYP_LAST_EXPONENT; ++i) { const unsigned int bit = (1 << i); if (disciplines & bit) skills.insert(spell_type2skill(bit)); } }