/*
* File: spl-cast.cc
* Summary: Spell casting and miscast functions.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include <sstream>
#include <iomanip>
#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 << "<darkgrey>";
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 << "</darkgrey>";
return desc.str();
}
static std::string _spell_extra_description(spell_type spell, bool grey = false)
{
std::ostringstream desc;
if (grey)
desc << "<darkgrey>";
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 << "</darkgrey>";
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<MenuEntry*> 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 <w>Z</w> 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<int> 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<<i)))
{
if (already)
desc += "/";
desc += spelltype_long_name(1 << i);
already = true;
}
}
return (desc);
}