/* * File: spl-mis.cc * Summary: Spell miscast class. * Written by: Matthew Cline */ #include "AppHdr.h" #include "spl-mis.h" #include "externs.h" #include #include "colour.h" #include "cloud.h" #include "directn.h" #include "effects.h" #include "env.h" #include "it_use2.h" #include "kills.h" #include "misc.h" #include "mon-place.h" #include "mgen_data.h" #include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "player.h" #include "religion.h" #include "spells1.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "terrain.h" #include "transform.h" #include "view.h" #include "shout.h" #include "viewchar.h" #include "xom.h" // This determines how likely it is that more powerful wild magic // effects will occur. Set to 100 for the old probabilities (although // the individual effects have been made much nastier since then). #define WILD_MAGIC_NASTINESS 150 #define MAX_RECURSE 100 MiscastEffect::MiscastEffect(actor* _target, int _source, spell_type _spell, int _pow, int _fail, std::string _cause, nothing_happens_when_type _nothing_happens, int _lethality_margin, std::string _hand_str, bool _can_plural) : target(_target), source(_source), cause(_cause), spell(_spell), school(SPTYP_NONE), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES), kt(KILL_NONE), nothing_happens_when(_nothing_happens), lethality_margin(_lethality_margin), hand_str(_hand_str), can_plural_hand(_can_plural) { ASSERT(is_valid_spell(_spell)); unsigned int schools = get_spell_disciplines(_spell); ASSERT(schools != SPTYP_NONE); ASSERT(!(schools & SPTYP_HOLY)); UNUSED(schools); init(); do_miscast(); } MiscastEffect::MiscastEffect(actor* _target, int _source, spschool_flag_type _school, int _level, std::string _cause, nothing_happens_when_type _nothing_happens, int _lethality_margin, std::string _hand_str, bool _can_plural) : target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL), school(_school), pow(-1), fail(-1), level(_level), kc(KC_NCATEGORIES), kt(KILL_NONE), nothing_happens_when(_nothing_happens), lethality_margin(_lethality_margin), hand_str(_hand_str), can_plural_hand(_can_plural) { ASSERT(!_cause.empty()); ASSERT(count_bits(_school) == 1); ASSERT(_school < SPTYP_HOLY || _school == SPTYP_RANDOM); ASSERT(level >= 0 && level <= 3); init(); do_miscast(); } MiscastEffect::MiscastEffect(actor* _target, int _source, spschool_flag_type _school, int _pow, int _fail, std::string _cause, nothing_happens_when_type _nothing_happens, int _lethality_margin, std::string _hand_str, bool _can_plural) : target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL), school(_school), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES), kt(KILL_NONE), nothing_happens_when(_nothing_happens), lethality_margin(_lethality_margin), hand_str(_hand_str), can_plural_hand(_can_plural) { ASSERT(!_cause.empty()); ASSERT(count_bits(_school) == 1); ASSERT(_school < SPTYP_HOLY || _school == SPTYP_RANDOM); init(); do_miscast(); } MiscastEffect::~MiscastEffect() { ASSERT(recursion_depth == 0); } void MiscastEffect::init() { ASSERT(spell != SPELL_NO_SPELL && school == SPTYP_NONE || spell == SPELL_NO_SPELL && school != SPTYP_NONE); ASSERT(pow != -1 && fail != -1 && level == -1 || pow == -1 && fail == -1 && level >= 0 && level <= 3); ASSERT(target != NULL); ASSERT(target->alive()); ASSERT(lethality_margin == 0 || target->atype() == ACT_PLAYER); recursion_depth = 0; source_known = target_known = false; act_source = NULL; const bool death_curse = (cause.find("death curse") != std::string::npos); if (target->atype() == ACT_MONSTER) target_known = you.can_see(target); else target_known = true; kill_source = source; if (source == WIELD_MISCAST || source == MELEE_MISCAST) { if (target->atype() == ACT_MONSTER) kill_source = target->mindex(); else kill_source = NON_MONSTER; } if (kill_source == NON_MONSTER) { kc = KC_YOU; kt = KILL_YOU_MISSILE; act_source = &you; source_known = true; } else if (!invalid_monster_index(kill_source)) { monsters* mon_source = &menv[kill_source]; ASSERT(mon_source->type != -1); act_source = mon_source; if (death_curse && target->atype() == ACT_MONSTER && target_as_monster()->confused_by_you()) { kt = KILL_YOU_CONF; } else if (!death_curse && mon_source->confused_by_you() && !mon_source->friendly()) { kt = KILL_YOU_CONF; } else kt = KILL_MON_MISSILE; if (mon_source->friendly()) kc = KC_FRIENDLY; else kc = KC_OTHER; source_known = you.can_see(mon_source); if (target_known && death_curse) source_known = true; } else { ASSERT(source == ZOT_TRAP_MISCAST || source == MISC_KNOWN_MISCAST || source == MISC_UNKNOWN_MISCAST || (source < 0 && -source < NUM_GODS)); act_source = target; kc = KC_OTHER; kt = KILL_MISCAST; if (source == ZOT_TRAP_MISCAST) { source_known = target_known; if (target->atype() == ACT_MONSTER && target_as_monster()->confused_by_you()) { kt = KILL_YOU_CONF; } } else if (source == MISC_KNOWN_MISCAST) source_known = true; else if (source == MISC_UNKNOWN_MISCAST) source_known = false; else source_known = true; } ASSERT(kc != KC_NCATEGORIES && kt != KILL_NONE); if (death_curse && !invalid_monster_index(kill_source)) { if (starts_with(cause, "a ")) cause.replace(cause.begin(), cause.begin() + 1, "an indirect"); else if (starts_with(cause, "an ")) cause.replace(cause.begin(), cause.begin() + 2, "an indirect"); else cause = replace_all(cause, "death curse", "indirect death curse"); } // source_known = false for MELEE_MISCAST so that melee miscasts // won't give a "nothing happens" message. if (source == MELEE_MISCAST) source_known = false; if (hand_str.empty()) target->hand_name(true, &can_plural_hand); // Explosion stuff. beam.is_explosion = true; // [ds] Don't attribute the beam's cause to the actor, because the // death message will name the actor anyway. if (cause.empty()) cause = get_default_cause(false); beam.aux_source = cause; beam.beam_source = kill_source; beam.thrower = kt; } std::string MiscastEffect::get_default_cause(bool attribute_to_user) const { // This is only for true miscasts, which means both a spell and that // the source of the miscast is the same as the target of the miscast. ASSERT(source >= 0 && source <= NON_MONSTER); ASSERT(spell != SPELL_NO_SPELL); ASSERT(school == SPTYP_NONE); if (source == NON_MONSTER) { ASSERT(target->atype() == ACT_PLAYER); std::string str = "miscasting "; str += spell_title(spell); return str; } ASSERT(act_source->atype() == ACT_MONSTER); ASSERT(act_source == target); if (attribute_to_user) { return (std::string(you.can_see(act_source)? act_source->name(DESC_NOCAP_A) : "something") + " miscasting " + spell_title(spell)); } else { return std::string("miscast of ") + spell_title(spell); } } bool MiscastEffect::neither_end_silenced() { return (!silenced(you.pos()) && !silenced(target->pos())); } void MiscastEffect::do_miscast() { ASSERT(recursion_depth >= 0 && recursion_depth < MAX_RECURSE); if (recursion_depth == 0) did_msg = false; unwind_var unwind_depth(recursion_depth); recursion_depth++; // Repeated calls to do_miscast() on a single object instance have // killed a target which was alive when the object was created. if (!target->alive()) { dprf("Miscast target '%s' already dead", target->name(DESC_PLAIN, true).c_str()); return; } spschool_flag_type sp_type; int severity; if (spell != SPELL_NO_SPELL) { std::vector school_list; for (int i = 0; i < SPTYP_LAST_EXPONENT; i++) if (spell_typematch(spell, 1 << i)) school_list.push_back(i); unsigned int _school = school_list[random2(school_list.size())]; sp_type = static_cast(1 << _school); } else { sp_type = school; if (sp_type == SPTYP_RANDOM) { int exp = (random2(SPTYP_LAST_EXPONENT)); sp_type = (spschool_flag_type) (1 << exp); } } if (level != -1) severity = level; else { severity = (pow * fail * (10 + pow) / 7 * WILD_MAGIC_NASTINESS) / 100; #if DEBUG_DIAGNOSTICS || DEBUG_MISCAST mprf(MSGCH_DIAGNOSTICS, "'%s' miscast power: %d", spell != SPELL_NO_SPELL ? spell_title(spell) : spelltype_short_name(sp_type), severity); #endif if (random2(40) > severity && random2(40) > severity) { if (target->atype() == ACT_PLAYER) canned_msg(MSG_NOTHING_HAPPENS); return; } severity /= 100; severity = random2(severity); if (severity > 3) severity = 3; else if (severity < 0) severity = 0; } #if DEBUG_DIAGNOSTICS || DEBUG_MISCAST mprf(MSGCH_DIAGNOSTICS, "Sptype: %s, severity: %d", spelltype_short_name(sp_type), severity ); #endif beam.ex_size = 1; beam.name = ""; beam.damage = dice_def(0, 0); beam.flavour = BEAM_NONE; beam.msg_generated = false; beam.in_explosion_phase = false; // Do this here since multiple miscasts (wizmode testing) might move // the target around. beam.source = target->pos(); beam.target = target->pos(); beam.use_target_as_pos = true; all_msg = you_msg = mon_msg = mon_msg_seen = mon_msg_unseen = ""; msg_ch = MSGCH_PLAIN; sound_loudness = 0; switch (sp_type) { case SPTYP_CONJURATION: _conjuration(severity); break; case SPTYP_ENCHANTMENT: _enchantment(severity); break; case SPTYP_TRANSLOCATION: _translocation(severity); break; case SPTYP_SUMMONING: _summoning(severity); break; case SPTYP_NECROMANCY: _necromancy(severity); break; case SPTYP_TRANSMUTATION: _transmutation(severity); break; case SPTYP_FIRE: _fire(severity); break; case SPTYP_ICE: _ice(severity); break; case SPTYP_EARTH: _earth(severity); break; case SPTYP_AIR: _air(severity); break; case SPTYP_POISON: _poison(severity); break; case SPTYP_DIVINATION: // Divination miscasts have nothing in common between the player // and monsters. if (target->atype() == ACT_PLAYER) _divination_you(severity); else _divination_mon(severity); break; default: DEBUGSTR("Invalid miscast spell discipline."); } if (target->atype() == ACT_PLAYER) xom_is_stimulated(severity * 50); } void MiscastEffect::do_msg(bool suppress_nothing_happens) { ASSERT(!did_msg); if (target->atype() == ACT_MONSTER && !mons_near(target_as_monster())) return; did_msg = true; std::string msg; if (!all_msg.empty()) msg = all_msg; else if (target->atype() == ACT_PLAYER) msg = you_msg; else if (!mon_msg.empty()) { msg = mon_msg; // Monster might be unseen with hands that can't be seen. ASSERT(msg.find("@hand") == std::string::npos); } else { if (you.can_see(target)) msg = mon_msg_seen; else { msg = mon_msg_unseen; // Can't see the hands of invisible monsters. ASSERT(msg.find("@hand") == std::string::npos); } } if (msg.empty()) { if (!suppress_nothing_happens && (nothing_happens_when == NH_ALWAYS || (nothing_happens_when == NH_DEFAULT && source_known && target_known))) { canned_msg(MSG_NOTHING_HAPPENS); } return; } bool plural; if (hand_str.empty()) { msg = replace_all(msg, "@hand@", target->hand_name(false, &plural)); msg = replace_all(msg, "@hands@", target->hand_name(true)); } else { plural = can_plural_hand; msg = replace_all(msg, "@hand@", hand_str); if (can_plural_hand) msg = replace_all(msg, "@hands@", pluralise(hand_str)); else msg = replace_all(msg, "@hands@", hand_str); } if (plural) msg = replace_all(msg, "@hand_conj@", ""); else msg = replace_all(msg, "@hand_conj@", "s"); if (target->atype() == ACT_MONSTER) msg = do_mon_str_replacements(msg, target_as_monster(), S_SILENT); mpr(msg.c_str(), msg_ch); if (msg_ch == MSGCH_SOUND) { // Those monsters of normal or greater intelligence will realize that they // were the source of the sound. int src = target->atype() == ACT_PLAYER ? you.mindex() : mons_intel(target_as_monster()) >= I_NORMAL ? target->mindex() : -1; noisy(sound_loudness, target->pos(), src); } } bool MiscastEffect::_ouch(int dam, beam_type flavour) { // Delay do_msg() until after avoid_lethal(). if (target->atype() == ACT_MONSTER) { monsters* mon_target = target_as_monster(); do_msg(true); bolt beem; beem.flavour = flavour; dam = mons_adjust_flavoured(mon_target, beem, dam, true); mon_target->hurt(NULL, dam, BEAM_MISSILE, false); if (!mon_target->alive()) monster_die(mon_target, kt, kill_source); } else { dam = check_your_resists(dam, flavour); if (avoid_lethal(dam)) return (false); do_msg(true); kill_method_type method; if (source == NON_MONSTER && spell != SPELL_NO_SPELL) method = KILLED_BY_WILD_MAGIC; else if (source == ZOT_TRAP_MISCAST) method = KILLED_BY_TRAP; else if (source < 0 && -source < NUM_GODS) { god_type god = static_cast(-source); if (god == GOD_XOM && you.penance[GOD_XOM] == 0) method = KILLED_BY_XOM; else method = KILLED_BY_DIVINE_WRATH; } else if (!invalid_monster_index(source)) method = KILLED_BY_MONSTER; else method = KILLED_BY_SOMETHING; bool see_source = act_source && you.can_see(act_source); ouch(dam, kill_source, method, cause.c_str(), see_source); } return (true); } bool MiscastEffect::_explosion() { ASSERT(!beam.name.empty()); ASSERT(beam.damage.num != 0 && beam.damage.size != 0); ASSERT(beam.flavour != BEAM_NONE); int max_dam = beam.damage.num * beam.damage.size; max_dam = check_your_resists(max_dam, beam.flavour); if (avoid_lethal(max_dam)) return (false); do_msg(true); beam.explode(); return (true); } bool MiscastEffect::_big_cloud(cloud_type cl_type, int power, int size, int spread_rate) { if (avoid_lethal( 2 * max_cloud_damage(cl_type, power) )) return (false); do_msg(true); big_cloud(cl_type, kc, kt, target->pos(), power, size, spread_rate); return (true); } bool MiscastEffect::_lose_stat(unsigned char which_stat, unsigned char stat_loss) { if (lethality_margin <= 0) return lose_stat(which_stat, stat_loss, false, cause); if (which_stat == STAT_RANDOM) { const int might = you.duration[DUR_MIGHT] ? 5 : 0; const int brilliant = you.duration[DUR_BRILLIANCE] ? 5 : 0; const int agile = you.duration[DUR_AGILITY] ? 5 : 0; std::vector stat_types; if ((you.strength - might - stat_loss) > 0) stat_types.push_back(STAT_STRENGTH); if ((you.intel - brilliant - stat_loss) > 0) stat_types.push_back(STAT_INTELLIGENCE); if ((you.dex - agile - stat_loss) > 0) stat_types.push_back(STAT_DEXTERITY); if (stat_types.size() == 0) { if (avoid_lethal(you.hp)) return (false); else return lose_stat(which_stat, stat_loss, false, cause); } which_stat = stat_types[random2(stat_types.size())]; } int val; switch (which_stat) { case STAT_STRENGTH: val = you.strength; break; case STAT_INTELLIGENCE: val = you.intel; break; case STAT_DEXTERITY: val = you.dex; break; default: DEBUGSTR("Invalid stat type."); return (false); } if ((val - stat_loss) <= 0) { if (avoid_lethal(you.hp)) return (false); } return lose_stat(which_stat, stat_loss, false, cause); } void MiscastEffect::_potion_effect(int pot_eff, int pot_pow) { if (target->atype() == ACT_PLAYER) { potion_effect(static_cast(pot_eff), pot_pow, false, false); return; } monsters* mon_target = target_as_monster(); switch (pot_eff) { case POT_LEVITATION: // There's no levitation enchantment for monsters, and, // anyway, it's not nearly as inconvenient for monsters as // for the player, so backlight them instead. mon_target->add_ench(mon_enchant(ENCH_CORONA, pot_pow, kc)); break; case POT_BERSERK_RAGE: if (target->can_go_berserk()) { target->go_berserk(false); break; } // Intentional fallthrough if that didn't work. case POT_SLOWING: target->slow_down(act_source, pot_pow); break; case POT_PARALYSIS: target->paralyse(act_source, pot_pow); break; case POT_CONFUSION: target->confuse(act_source, pot_pow); break; default: ASSERT(false); } } void MiscastEffect::send_abyss() { if (you.level_type == LEVEL_ABYSS) { you_msg = "The world appears momentarily distorted."; mon_msg_seen = "@The_monster@ wobbles for a moment."; mon_msg_unseen = "An empty piece of space momentarily distorts."; do_msg(); return; } target->banish(cause); } bool MiscastEffect::avoid_lethal(int dam) { if (lethality_margin <= 0 || (you.hp - dam) > lethality_margin) return (false); if (recursion_depth == MAX_RECURSE) { #if DEBUG_DIAGNOSTICS || DEBUG_MISCAST mpr("Couldn't avoid lethal miscast: too much recursion.", MSGCH_ERROR); #endif return (false); } if (did_msg) { #if DEBUG_DIAGNOSTICS || DEBUG_MISCAST mpr("Couldn't avoid lethal miscast: already printed message for this " "miscast.", MSGCH_ERROR); #endif return (false); } #if DEBUG_DIAGNOSTICS || DEBUG_MISCAST mpr("Avoided lethal miscast.", MSGCH_DIAGNOSTICS); #endif do_miscast(); return (true); } bool MiscastEffect::_create_monster(monster_type what, int abj_deg, bool alert) { const god_type god = (crawl_state.is_god_acting()) ? crawl_state.which_god_acting() : GOD_NO_GOD; if (cause.empty()) cause = get_default_cause(true); mgen_data data = mgen_data::hostile_at(what, cause, alert, abj_deg, 0, target->pos(), 0, god); // hostile_at() assumes the monster is hostile to the player, // but should be hostile to the target monster unless the miscast // is a result of either divine wrath or a Zot trap. if (target->atype() == ACT_MONSTER && you.penance[god] == 0 && source != ZOT_TRAP_MISCAST) { monsters* mon_target = target_as_monster(); switch (mon_target->temp_attitude()) { case ATT_FRIENDLY: data.behaviour = BEH_HOSTILE; break; case ATT_HOSTILE: data.behaviour = BEH_FRIENDLY; break; case ATT_GOOD_NEUTRAL: case ATT_NEUTRAL: case ATT_STRICT_NEUTRAL: data.behaviour = BEH_NEUTRAL; break; } if (alert) data.foe = mon_target->mindex(); // No permanent allies from miscasts. if (data.behaviour == BEH_FRIENDLY && abj_deg == 0) data.abjuration_duration = 6; } // If data.abjuration_duration == 0, then data.summon_type will // simply be ignored. if (data.abjuration_duration != 0) { if (you.penance[god] > 0) data.summon_type = MON_SUMM_WRATH; else if (source == ZOT_TRAP_MISCAST) data.summon_type = MON_SUMM_ZOT; else data.summon_type = MON_SUMM_MISCAST; } return (create_monster(data) != -1); } static bool _has_hair(actor* target) { // Don't bother for monsters. if (target->atype() == ACT_MONSTER) return (false); return (!transform_changed_physiology() && you.species != SP_GHOUL && you.species != SP_KENKU && !player_genus(GENPC_DRACONIAN)); } static std::string _hair_str(actor* target, bool &plural) { ASSERT(target->atype() == ACT_PLAYER); if (you.species == SP_MUMMY) { plural = true; return "bandages"; } else { plural = false; return "hair"; } } void MiscastEffect::_conjuration(int severity) { int num; switch (severity) { case 0: // just a harmless message num = 10 + (_has_hair(target) ? 1 : 0); switch (random2(num)) { case 0: you_msg = "Sparks fly from your @hands@!"; mon_msg_seen = "Sparks fly from @the_monster@'s @hands@!"; break; case 1: you_msg = "The air around you crackles with energy!"; mon_msg_seen = "The air around @the_monster@ crackles " "with energy!"; break; case 2: you_msg = "Wisps of smoke drift from your @hands@."; mon_msg_seen = "Wisps of smoke drift from @the_monster@'s " "@hands@."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "You are momentarily dazzled by a flash of light!"; mon_msg_seen = "@The_monster@ emits a flash of light!"; break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "Your skin tingles."; mon_msg_seen = "@The_monster@ twitches."; break; case 7: you_msg = "Your skin glows momentarily."; mon_msg_seen = "@The_monster@ glows momentarily."; // A small glow isn't going to make it past invisibility. break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (you.can_smell()) all_msg = "You smell something strange."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 10: { // Player only (for now). bool plural; std::string hair = _hair_str(target, plural); you_msg = make_stringf("Your %s stand%s on end.", hair.c_str(), plural ? "" : "s"); } } do_msg(); break; case 1: // a bit less harmless stuff switch (random2(2)) { case 0: you_msg = "Smoke pours from your @hands@!"; mon_msg_seen = "Smoke pours from @the_monster@'s @hands@!"; mon_msg_unseen = "Smoke appears from out of nowhere!"; _big_cloud(CLOUD_GREY_SMOKE, 20, 7 + random2(7)); break; case 1: you_msg = "A wave of violent energy washes through your body!"; mon_msg_seen = "@The_monster@ lurches violently!"; _ouch(6 + random2avg(7, 2)); break; } break; case 2: // rather less harmless stuff switch (random2(2)) { case 0: you_msg = "Energy rips through your body!"; mon_msg_seen = "@The_monster@ jerks violently!"; _ouch(9 + random2avg(17, 2)); break; case 1: you_msg = "You are caught in a violent explosion!"; mon_msg_seen = "@The_monster@ is caught in a violent explosion!"; mon_msg_unseen = "A violent explosion happens from out of thin " "air!"; beam.flavour = BEAM_MISSILE; beam.damage = dice_def(3, 12); beam.name = "explosion"; beam.colour = random_colour(); _explosion(); break; } break; case 3: // considerably less harmless stuff switch (random2(2)) { case 0: you_msg = "You are blasted with magical energy!"; mon_msg_seen = "@The_monster@ is blasted with magical energy!"; // No message for invis monsters? _ouch(12 + random2avg(29, 2)); break; case 1: all_msg = "There is a sudden explosion of magical energy!"; beam.flavour = BEAM_MISSILE; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 20); beam.name = "explosion"; beam.colour = random_colour(); beam.ex_size = coinflip() ? 1 : 2; _explosion(); break; } } } static void _your_hands_glow(actor* target, std::string& you_msg, std::string& mon_msg_seen, bool pluralise) { you_msg = "Your @hands@ "; mon_msg_seen = "@The_monster@'s @hands@ "; // No message for invisible monsters. if (pluralise) { you_msg += "glow"; mon_msg_seen += "glow"; } else { you_msg += "glows"; mon_msg_seen += "glows"; } you_msg += " momentarily."; mon_msg_seen += " momentarily."; } void MiscastEffect::_enchantment(int severity) { switch (severity) { case 0: // harmless messages only switch (random2(10)) { case 0: _your_hands_glow(target, you_msg, mon_msg_seen, can_plural_hand); break; case 1: you_msg = "The air around you crackles with energy!"; mon_msg_seen = "The air around @the_monster@ crackles with" " energy!"; break; case 2: you_msg = "Multicoloured lights dance before your eyes!"; mon_msg_seen = "Multicoloured lights dance around @the_monster@!"; mon_msg_unseen = "Multicoloured lights dance in the air!"; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "Waves of light ripple over your body."; mon_msg_seen = "Waves of light ripple over @the_monster@'s body."; break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "Your skin tingles."; mon_msg_seen = "@The_monster@ twitches."; break; case 7: you_msg = "Your skin glows momentarily."; mon_msg_seen = "@The_monster@'s body glows momentarily."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { all_msg = "You hear something strange."; msg_ch = MSGCH_SOUND; sound_loudness = 2; return; } else if (target->atype() == ACT_PLAYER) you_msg = "Your skull vibrates slightly."; break; } do_msg(); break; case 1: // slightly annoying switch (random2(crawl_state.arena ? 1 : 2)) { case 0: _potion_effect(POT_LEVITATION, 20); break; case 1: // XXX: Something else for monsters? random_uselessness(); break; } break; case 2: // much more annoying switch (random2(7)) { case 0: case 1: case 2: if (target->atype() == ACT_PLAYER) { mpr("You sense a malignant aura."); curse_an_item(false); break; } // Intentional fall-through for monsters. case 3: case 4: case 5: _potion_effect(POT_SLOWING, 10); break; case 6: _potion_effect(POT_BERSERK_RAGE, 10); break; } break; case 3: // potentially lethal // Only use first two cases for monsters. switch (random2(target->atype() == ACT_PLAYER ? 4 : 2)) { case 0: _potion_effect(POT_PARALYSIS, 10); break; case 1: _potion_effect(POT_CONFUSION, 10); break; case 2: mpr("You feel saturated with unharnessed energies!"); you.magic_contamination += random2avg(19, 3); break; case 3: do curse_an_item(false); while (!one_chance_in(3)); mpr("You sense an overwhelmingly malignant aura!"); break; } break; } } void MiscastEffect::_translocation(int severity) { switch (severity) { case 0: // harmless messages only switch (random2(10)) { case 0: you_msg = "Space warps around you."; mon_msg_seen = "Space warps around @the_monster@."; break; case 1: you_msg = "The air around you crackles with energy!"; mon_msg_seen = "The air around @the_monster@ crackles with " "energy!"; break; case 2: you_msg = "You feel a wrenching sensation."; mon_msg_seen = "@The_monster@ jerks violently for a moment."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "You spin around."; mon_msg_seen = "@The_monster@ spins around."; break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "Your skin tingles."; mon_msg_seen = "@The_monster@ twitches."; break; case 7: you_msg = "The world appears momentarily distorted!"; mon_msg_seen = "@The_monster@ appears momentarily distorted!"; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: you_msg = "You feel uncomfortable."; mon_msg_seen = "@The_monster@ scowls."; break; } do_msg(); break; case 1: // mostly harmless switch (random2(6)) { case 0: case 1: case 2: you_msg = "You are caught in a localised field of spatial " "distortion."; mon_msg_seen = "@The_monster@ is caught in a localised field of " "spatial distortion."; mon_msg_unseen = "A piece of empty space twists and distorts."; _ouch(4 + random2avg(9, 2)); break; case 3: case 4: you_msg = "Space bends around you!"; mon_msg_seen = "Space bends around @the_monster@!"; mon_msg_unseen = "A piece of empty space twists and distorts."; if (_ouch(4 + random2avg(7, 2)) && target->alive()) target->blink(false); break; case 5: if (_create_monster(MONS_SPATIAL_VORTEX, 3)) all_msg = "Space twists in upon itself!"; do_msg(); break; } break; case 2: // less harmless switch (random2(7)) { case 0: case 1: case 2: you_msg = "You are caught in a strong localised spatial " "distortion."; mon_msg_seen = "@The_monster@ is caught in a strong localised " "spatial distortion."; mon_msg_unseen = "A piece of empty space twists and writhes."; _ouch(9 + random2avg(23, 2)); break; case 3: case 4: you_msg = "Space warps around you!"; mon_msg_seen = "Space warps around @the_monster@!"; mon_msg_unseen = "A piece of empty space twists and writhes."; if (_ouch(5 + random2avg(9, 2)) && target->alive()) { if (one_chance_in(3)) target->teleport(true); else target->blink(false); _potion_effect(POT_CONFUSION, 40); } break; case 5: { bool success = false; for (int i = 1 + random2(3); i >= 0; --i) { if (_create_monster(MONS_SPATIAL_VORTEX, 3)) success = true; } if (success) all_msg = "Space twists in upon itself!"; break; } case 6: send_abyss(); break; } break; case 3: // much less harmless // Don't use the last case for monsters. switch (random2(target->atype() == ACT_PLAYER ? 4 : 3)) { case 0: you_msg = "You are caught in an extremely strong localised " "spatial distortion!"; mon_msg_seen = "@The_monster@ is caught in an extremely strong " "localised spatial distortion!"; mon_msg_unseen = "A rift temporarily opens in the fabric of space!"; _ouch(15 + random2avg(29, 2)); break; case 1: you_msg = "Space warps crazily around you!"; mon_msg_seen = "Space warps crazily around @the_monster@!"; mon_msg_unseen = "A rift temporarily opens in the fabric of space!"; if (_ouch(9 + random2avg(17, 2)) && target->alive()) { target->teleport(true); _potion_effect(POT_CONFUSION, 60); } break; case 2: send_abyss(); break; case 3: mpr("You feel saturated with unharnessed energies!"); you.magic_contamination += random2avg(19, 3); break; } break; } } void MiscastEffect::_summoning(int severity) { switch (severity) { case 0: // harmless messages only switch (random2(10)) { case 0: you_msg = "Shadowy shapes form in the air around you, " "then vanish."; mon_msg_seen = "Shadowy shapes form in the air around " "@the_monster@, then vanish."; break; case 1: if (neither_end_silenced()) { all_msg = "You hear strange voices."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (target->atype() == ACT_PLAYER) you_msg = "You feel momentarily dizzy."; break; case 2: you_msg = "Your head hurts."; // Monster messages needed. break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "Your brain hurts!"; // Monster messages needed. break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "The world appears momentarily distorted."; mon_msg_seen = "@The_monster@ appears momentarily distorted."; break; case 7: you_msg = "Space warps around you."; mon_msg_seen = "Space warps around @the_monster@."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { you_msg = "Distant voices call out to you!"; mon_msg_seen = "Distant voices call out to @the_monster@!"; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (target->atype() == ACT_PLAYER) you_msg = "You feel watched."; break; } do_msg(); break; case 1: // a little bad switch (random2(6)) { case 0: // identical to translocation case 1: case 2: you_msg = "You are caught in a localised spatial " "distortion."; mon_msg_seen = "@The_monster@ is caught in a localised spatial " "distortion."; mon_msg_unseen = "An empty piece of space distorts and twists."; _ouch(5 + random2avg(9, 2)); break; case 3: if (_create_monster(MONS_SPATIAL_VORTEX, 3)) all_msg = "Space twists in upon itself!"; do_msg(); break; case 4: case 5: if (_create_monster(summon_any_demon(DEMON_LESSER), 5, true)) all_msg = "Something appears in a flash of light!"; do_msg(); break; } break; case 2: // more bad switch (random2(6)) { case 0: { bool success = false; for (int i = 1 + random2(3); i >= 0; --i) { if (_create_monster(MONS_SPATIAL_VORTEX, 3)) success = true; } if (success) all_msg = "Space twists in upon itself!"; do_msg(); break; } case 1: case 2: if (_create_monster(summon_any_demon(DEMON_COMMON), 5, true)) all_msg = "Something forms from out of thin air!"; do_msg(); break; case 3: case 4: case 5: { bool success = false; for (int i = 1 + random2(2); i >= 0; --i) { if (_create_monster(summon_any_demon(DEMON_LESSER), 5, true)) success = true; } if (success && neither_end_silenced()) { you_msg = "A chorus of chattering voices calls out to" " you!"; mon_msg = "A chorus of chattering voices calls out!"; msg_ch = MSGCH_SOUND; sound_loudness = 3; } do_msg(); break; } } break; case 3: // more bad switch (random2(4)) { case 0: if (_create_monster(MONS_ABOMINATION_SMALL, 0, true)) all_msg = "Something forms from out of thin air."; do_msg(); break; case 1: if (_create_monster(summon_any_demon(DEMON_GREATER), 0, true)) all_msg = "You sense a hostile presence."; do_msg(); break; case 2: { bool success = false; for (int i = 1 + random2(2); i >= 0; --i) { if (_create_monster(summon_any_demon(DEMON_COMMON), 3, true)) success = true; } if (success) { you_msg = "Something turns its malign attention towards " "you..."; mon_msg = "You sense a malign presence."; } do_msg(); break; } case 3: send_abyss(); break; } break; } } void MiscastEffect::_divination_you(int severity) { switch (severity) { case 0: // just a harmless message switch (random2(10)) { case 0: mpr("Weird images run through your mind."); break; case 1: if (!silenced(you.pos())) { mpr("You hear strange voices.", MSGCH_SOUND); noisy(2, you.pos()); } else mpr("Your nose twitches."); break; case 2: mpr("Your head hurts."); break; case 3: mpr("You feel a strange surge of energy!"); break; case 4: mpr("Your brain hurts!"); break; case 5: mpr("Strange energies run through your body."); break; case 6: mpr("Everything looks hazy for a moment."); break; case 7: mpr("You seem to have forgotten something, but you can't remember what it was!"); break; case 8: canned_msg(MSG_NOTHING_HAPPENS); break; case 9: mpr("You feel uncomfortable."); break; } break; case 1: // more annoying things switch (random2(2)) { case 0: mpr("You feel slightly disoriented."); forget_map(10 + random2(10)); break; case 1: potion_effect(POT_CONFUSION, 10); break; } break; case 2: // even more annoying things switch (random2(2)) { case 0: if (you.is_undead) mpr("You suddenly recall your previous life!"); else if (_lose_stat(STAT_INTELLIGENCE, 1 + random2(3))) { mpr("You have damaged your brain!"); } else if (!did_msg) mpr("You have a terrible headache."); break; case 1: mpr("You feel lost."); forget_map(40 + random2(40)); break; } potion_effect(POT_CONFUSION, 1); // common to all cases here {dlb} break; case 3: // nasty switch (random2(3)) { case 0: if (!forget_spell()) mpr("You get a splitting headache."); break; case 1: mpr("You feel completely lost."); forget_map(100); break; case 2: if (you.is_undead) mpr("You suddenly recall your previous life."); else if (_lose_stat(STAT_INTELLIGENCE, 3 + random2(3))) { mpr("You have damaged your brain!"); } else if (!did_msg) mpr("You have a terrible headache."); break; } potion_effect(POT_CONFUSION, 1); // common to all cases here {dlb} break; } } // XXX: Monster divination miscasts. void MiscastEffect::_divination_mon(int severity) { switch (severity) { case 0: // just a harmless message mon_msg_seen = "@The_monster@ looks momentarily confused."; break; case 1: // more annoying things switch (random2(2)) { case 0: mon_msg_seen = "@The_monster@ looks slightly disoriented."; break; case 1: target->confuse( act_source, 1 + random2(3 + act_source->get_experience_level())); break; } break; case 2: // even more annoying things mon_msg_seen = "@The_monster@ shudders."; target->confuse( act_source, 5 + random2(3 + act_source->get_experience_level())); break; case 3: // nasty mon_msg_seen = "@The_monster@ reels."; if (one_chance_in(7)) target_as_monster()->forget_random_spell(); target->confuse( act_source, 8 + random2(3 + act_source->get_experience_level())); break; } } void MiscastEffect::_necromancy(int severity) { if (target->atype() == ACT_PLAYER && you.religion == GOD_KIKUBAAQUDGHA && !player_under_penance() && you.piety >= piety_breakpoint(1) && x_chance_in_y(you.piety, 150)) { canned_msg(MSG_NOTHING_HAPPENS); return; } switch (severity) { case 0: switch (random2(10)) { case 0: if (you.can_smell()) all_msg = "You smell decay."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 1: if (neither_end_silenced()) { all_msg = "You hear strange and distant voices."; msg_ch = MSGCH_SOUND; sound_loudness = 3; } else if (target->atype() == ACT_PLAYER) you_msg = "You feel homesick."; break; case 2: you_msg = "Pain shoots through your body."; // Monster messages needed. break; case 3: you_msg = "Your bones ache."; // Monster messages needed. break; case 4: you_msg = "The world around you seems to dim momentarily."; mon_msg_seen = "@The_monster@ seems to dim momentarily."; break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "You shiver with cold."; mon_msg_seen = "@The_monster@ shivers with cold."; break; case 7: you_msg = "You sense a malignant aura."; // Monster messages needed. break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: you_msg = "You feel very uncomfortable."; mon_msg_seen = "@The_monster@ scowls horribly."; break; } do_msg(); break; case 1: // a bit nasty switch (random2(3)) { case 0: if (target->res_torment()) { you_msg = "You feel weird for a moment."; mon_msg_seen = "@The_monster@ has an odd expression for a " "moment."; } else { you_msg = "Pain shoots through your body!"; mon_msg_seen = "@The_monster@ convulses with pain!"; _ouch(5 + random2avg(15, 2)); } break; case 1: you_msg = "You feel horribly lethargic."; mon_msg_seen = "@The_monster@ looks incredibly listless."; _potion_effect(POT_SLOWING, 15); break; case 2: if (!target->res_rotting()) { if (you.can_smell()) { // identical to a harmless message all_msg = "You smell decay."; } if (target->atype() == ACT_PLAYER) you.rotting++; else target_as_monster()->add_ench(mon_enchant(ENCH_ROT, 1, kc)); } else if (you.species == SP_MUMMY) { // Monster messages needed. you_msg = "Your bandages flutter."; } break; } if (!did_msg) do_msg(); break; case 2: // much nastier switch (random2(3)) { case 0: { bool success = false; for (int i = random2(3); i >= 0; --i) { if (_create_monster(MONS_SHADOW, 2, true)) success = true; } if (success) { you_msg = "Flickering shadows surround you."; mon_msg_seen = "Flickering shadows surround @the_monster@."; mon_msg_unseen = "Shadows flicker in the thin air."; } do_msg(); break; } case 1: you_msg = "You are engulfed in negative energy!"; mon_msg_seen = "@The_monster@ is engulfed in negative energy!"; if (lethality_margin == 0 || you.experience > 0 || !avoid_lethal(you.hp)) { if (one_chance_in(3)) { do_msg(); target->drain_exp(act_source); break; } } // If draining failed, just flow through... case 2: if (target->res_torment()) { you_msg = "You feel weird for a moment."; mon_msg_seen = "@The_monster@ has an odd expression for a " "moment."; } else { you_msg = "You convulse helplessly as pain tears through " "your body!"; mon_msg_seen = "@The_monster@ convulses helplessly with pain!"; _ouch(15 + random2avg(23, 2)); } if (!did_msg) do_msg(); break; } break; case 3: // even nastier // Don't use last case for monsters. switch (random2(target->atype() == ACT_PLAYER ? 6 : 5)) { case 0: if (target->holiness() == MH_UNDEAD) { you_msg = "Something just walked over your grave. No, " "really!"; mon_msg_seen = "@The_monster@ seems frightened for a moment."; do_msg(); } else torment_monsters(target->pos(), 0, TORMENT_GENERIC); break; case 1: target->rot(act_source, random2avg(7, 2) + 1); break; case 2: if (_create_monster(MONS_SOUL_EATER, 4, true)) { you_msg = "Something reaches out for you..."; mon_msg_seen = "Something reaches out for @the_monster@..."; mon_msg_unseen = "Something reaches out from thin air..."; } do_msg(); break; case 3: if (_create_monster(MONS_REAPER, 4, true)) { you_msg = "Death has come for you..."; mon_msg_seen = "Death has come for @the_monster@..."; mon_msg_unseen = "Death appears from thin air..."; } do_msg(); break; case 4: you_msg = "You are engulfed in negative energy!"; mon_msg_seen = "@The_monster@ is engulfed in negative energy!"; if (lethality_margin == 0 || you.experience > 0 || !avoid_lethal(you.hp)) { do_msg(); target->drain_exp(act_source); break; } // If draining failed, just flow through if it's the player... if (target->atype() == ACT_MONSTER) break; case 5: _lose_stat(STAT_RANDOM, 1 + random2avg(7, 2)); break; } break; } } void MiscastEffect::_transmutation(int severity) { int num; switch (severity) { case 0: // just a harmless message num = 10 + (_has_hair(target) ? 1 : 0); switch (random2(num)) { case 0: _your_hands_glow(target, you_msg, mon_msg_seen, can_plural_hand); break; case 1: you_msg = "The air around you crackles with energy!"; mon_msg_seen = "The air around @the_monster@ crackles with " "energy!"; mon_msg_unseen = "The thin air crackles with energy!"; break; case 2: you_msg = "Multicoloured lights dance before your eyes!"; mon_msg_seen = "Multicoloured lights dance around @the_monster@!"; mon_msg_unseen = "Multicoloured lights dance in the air!"; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "Waves of light ripple over your body."; mon_msg_seen = "Waves of light ripple over @the_monster@'s body."; mon_msg_unseen = "Waves of light ripple in the air."; break; case 5: you_msg = "Strange energies run through your body."; mon_msg_seen = "@The_monster@ glows " + weird_glowing_colour() + " for a moment."; break; case 6: you_msg = "Your skin tingles."; mon_msg_seen = "@The_monster@ twitches."; break; case 7: you_msg = "Your skin glows momentarily."; mon_msg_seen = "@The_monster@'s body glows momentarily."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (you.can_smell()) all_msg = "You smell something strange."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 10: { // Player only (for now). bool plural; std::string hair = _hair_str(target, plural); you_msg = make_stringf("Your %s momentarily turn%s into snakes!", hair.c_str(), plural ? "" : "s"); } } do_msg(); break; case 1: // slightly annoying switch (random2(crawl_state.arena ? 1 : 2)) { case 0: you_msg = "Your body is twisted painfully."; mon_msg_seen = "@The_monster@'s body twists unnaturally."; _ouch(1 + random2avg(11, 2)); break; case 1: random_uselessness(); break; } break; case 2: // much more annoying // Last case for players only. switch (random2(target->atype() == ACT_PLAYER ? 4 : 3)) { case 0: you_msg = "Your body is twisted very painfully!"; mon_msg_seen = "@The_monster@'s body twists and writhes."; _ouch(3 + random2avg(23, 2)); break; case 1: _potion_effect(POT_PARALYSIS, 10); break; case 2: _potion_effect(POT_CONFUSION, 10); break; case 3: mpr("You feel saturated with unharnessed energies!"); you.magic_contamination += random2avg(19, 3); break; } break; case 3: // even nastier if (target->atype() == ACT_MONSTER) target->mutate(); // Polymorph the monster, if possible. switch (random2(3)) { case 0: you_msg = "Your body is flooded with distortional energies!"; mon_msg = "@The_monster@'s body is flooded with distortional " "energies!"; if (_ouch(3 + random2avg(18, 2)) && target->alive()) { if (target->atype() == ACT_PLAYER) you.magic_contamination += random2avg(35, 3); } break; case 1: // HACK: Avoid lethality before deleting mutation, since // afterwards a message would already have been given. if (lethality_margin > 0 && (you.hp - lethality_margin) <= 27 && avoid_lethal(you.hp)) { return; } if (target->atype() == ACT_PLAYER) { you_msg = "You feel very strange."; delete_mutation(RANDOM_MUTATION, true, false, false, false, lethality_margin > 0); } _ouch(5 + random2avg(23, 2)); break; case 2: // HACK: Avoid lethality before giving mutation, since // afterwards a message would already have been given. if (lethality_margin > 0 && (you.hp - lethality_margin) <= 27 && avoid_lethal(you.hp)) { return; } if (target->atype() == ACT_PLAYER) { you_msg = "Your body is distorted in a weirdly horrible way!"; // We don't need messages when the mutation fails, // because we give our own (which is justified anyway as // you take damage). give_bad_mutation(false, false, lethality_margin > 0); if (coinflip()) give_bad_mutation(false, false, lethality_margin > 0); } _ouch(5 + random2avg(23, 2)); break; } break; } } void MiscastEffect::_fire(int severity) { switch (severity) { case 0: // just a harmless message switch (random2(10)) { case 0: you_msg = "Sparks fly from your @hands@!"; mon_msg_seen = "Sparks fly from @the_monster@'s @hands@!"; break; case 1: you_msg = "The air around you burns with energy!"; mon_msg_seen = "The air around @the_monster@ burns with energy!"; break; case 2: you_msg = "Wisps of smoke drift from your @hands@."; mon_msg_seen = "Wisps of smoke drift from @the_monster@'s @hands@."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: if (you.can_smell()) all_msg = "You smell smoke."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 5: you_msg = "Heat runs through your body."; // Monster messages needed. break; case 6: you_msg = "You feel uncomfortably hot."; // Monster messages needed. break; case 7: you_msg = "Lukewarm flames ripple over your body."; mon_msg_seen = "Dim flames ripple over @the_monster@'s body."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { all_msg = "You hear a sizzling sound."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (target->atype() == ACT_PLAYER) you_msg = "You feel like you have heartburn."; break; } do_msg(); break; case 1: // a bit less harmless stuff switch (random2(2)) { case 0: you_msg = "Smoke pours from your @hands@!"; mon_msg_seen = "Smoke pours from @the_monster@'s @hands@!"; mon_msg_unseen = "Smoke appears out of nowhere!"; _big_cloud(random_smoke_type(), 20, 7 + random2(7)); break; case 1: you_msg = "Flames sear your flesh."; mon_msg_seen = "Flames sear @the_monster@."; if (target->res_fire() < 0) { if (!_ouch(2 + random2avg(13, 2))) return; } else do_msg(); if (target->alive()) target->expose_to_element(BEAM_FIRE, 3); break; } break; case 2: // rather less harmless stuff switch (random2(2)) { case 0: you_msg = "You are blasted with fire."; mon_msg_seen = "@The_monster@ is blasted with fire."; mon_msg_unseen = "A flame briefly burns in thin air."; if (_ouch(5 + random2avg(29, 2), BEAM_FIRE) && target->alive()) target->expose_to_element(BEAM_FIRE, 5); break; case 1: you_msg = "You are caught in a fiery explosion!"; mon_msg_seen = "@The_monster@ is caught in a fiery explosion!"; mon_msg_unseen = "Fire explodes from out of thin air!"; beam.flavour = BEAM_FIRE; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 14); beam.name = "explosion"; beam.colour = RED; _explosion(); break; } break; case 3: // considerably less harmless stuff switch (random2(3)) { case 0: you_msg = "You are blasted with searing flames!"; mon_msg_seen = "@The_monster@ is blasted with searing flames!"; mon_msg_unseen = "A large flame burns hotly for a moment in the " "thin air."; if (_ouch(9 + random2avg(33, 2), BEAM_FIRE) && target->alive()) target->expose_to_element(BEAM_FIRE, 10); break; case 1: all_msg = "There is a sudden and violent explosion of flames!"; beam.flavour = BEAM_FIRE; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 20); beam.name = "fireball"; beam.colour = RED; beam.ex_size = coinflip() ? 1 : 2; _explosion(); break; case 2: { you_msg = "You are covered in liquid flames!"; mon_msg_seen = "@The_monster@ is covered in liquid flames!"; do_msg(); if (target->atype() == ACT_PLAYER) napalm_player(random2avg(7,3) + 1); else { monsters* mon_target = target_as_monster(); mon_target->add_ench(mon_enchant(ENCH_STICKY_FLAME, std::min(4, 1 + random2(mon_target->hit_dice) / 2), kc)); } break; } } break; } } void MiscastEffect::_ice(int severity) { const dungeon_feature_type feat = grd(target->pos()); const bool frostable_feat = (feat == DNGN_FLOOR || feat_altar_god(feat) != GOD_NO_GOD || feat_is_staircase(feat) || feat_is_water(feat)); const std::string feat_name = (feat == DNGN_FLOOR ? "the " : "") + feature_description(target->pos(), false, DESC_NOCAP_THE); int num; switch (severity) { case 0: // just a harmless message num = 10 + (frostable_feat ? 1 : 0); switch (random2(num)) { case 0: you_msg = "You shiver with cold."; mon_msg_seen = "@The_monster@ shivers with cold."; break; case 1: you_msg = "A chill runs through your body."; // Monster messages needed. break; case 2: you_msg = "Wisps of condensation drift from your @hands@."; mon_msg_seen = "Wisps of condensation drift from @the_monster@'s " "@hands@."; mon_msg_unseen = "Wisps of condensation drift in the air."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "Your @hands@ feel@hand_conj@ numb with cold."; // Monster messages needed. break; case 5: you_msg = "A chill runs through your body."; // Monster messages needed. break; case 6: you_msg = "You feel uncomfortably cold."; // Monster messages needed. break; case 7: you_msg = "Frost covers your body."; mon_msg_seen = "Frost covers @the_monster@'s body."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { all_msg = "You hear a crackling sound."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (target->atype() == ACT_PLAYER) you_msg = "A snowflake lands on your nose."; break; case 10: if (feat_is_water(feat)) all_msg = "A thin layer of ice forms on " + feat_name; else all_msg = "Frost spreads across " + feat_name; break; } do_msg(); break; case 1: // a bit less harmless stuff switch (random2(2)) { case 0: mpr("You feel extremely cold."); // Monster messages needed. break; case 1: you_msg = "You are covered in a thin layer of ice."; mon_msg_seen = "@The_monster@ is covered in a thin layer of ice."; if (target->res_cold() < 0) { if (!_ouch(4 + random2avg(5, 2))) return; } else do_msg(); if (target->alive()) target->expose_to_element(BEAM_COLD, 2); break; } break; case 2: // rather less harmless stuff switch (random2(2)) { case 0: you_msg = "Heat is drained from your body."; // Monster messages needed. if (_ouch(5 + random2(6) + random2(7), BEAM_COLD) && target->alive()) target->expose_to_element(BEAM_COLD, 4); break; case 1: you_msg = "You are caught in an explosion of ice and frost!"; mon_msg_seen = "@The_monster@ is caught in an explosion of " "ice and frost!"; mon_msg_unseen = "Ice and frost explode from out of thin air!"; beam.flavour = BEAM_COLD; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 11); beam.name = "explosion"; beam.colour = WHITE; _explosion(); break; } break; case 3: // less harmless stuff switch (random2(2)) { case 0: you_msg = "You are blasted with ice!"; mon_msg_seen = "@The_monster@ is blasted with ice!"; if (_ouch(9 + random2avg(23, 2), BEAM_ICE) && target->alive()) target->expose_to_element(BEAM_COLD, 9); break; case 1: you_msg = "Freezing gasses pour from your @hands@!"; mon_msg_seen = "Freezing gasses pour from @the_monster@'s " "@hands@!"; _big_cloud(CLOUD_COLD, 20, 8 + random2(4)); break; } break; } } static bool _on_floor(actor* target) { return (!target->airborne() && grd(target->pos()) == DNGN_FLOOR); } void MiscastEffect::_earth(int severity) { int num; switch (severity) { case 0: // just a harmless message case 1: num = 11 + (_on_floor(target) ? 2 : 0); switch (random2(num)) { case 0: you_msg = "You feel earthy."; // Monster messages needed. break; case 1: you_msg = "You are showered with tiny particles of grit."; mon_msg_seen = "@The_monster@ is showered with tiny particles " "of grit."; mon_msg_unseen = "Tiny particles of grit hang in the air for a " "moment."; break; case 2: you_msg = "Sand pours from your @hands@."; mon_msg_seen = "Sand pours from @the_monster@'s @hands@."; mon_msg_unseen = "Sand pours from out of thin air."; break; case 3: you_msg = "You feel a surge of energy from the ground."; // Monster messages needed. break; case 4: if (neither_end_silenced()) { all_msg = "You hear a distant rumble."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (target->atype() == ACT_PLAYER) you_msg = "You sympathise with the stones."; break; case 5: you_msg = "You feel gritty."; // Monster messages needed. break; case 6: you_msg = "You feel momentarily lethargic."; mon_msg_seen = "@The_monster@ looks momentarily listless."; break; case 7: you_msg = "Motes of dust swirl before your eyes."; mon_msg_seen = "Motes of dust swirl around @the_monster@."; mon_msg_unseen = "Motes of dust swirl around in the air."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: { bool pluralised = true; std::string feet = you.foot_name(true, &pluralised); std::ostringstream str; str << "Your " << feet << (pluralised ? " feel" : " feels") << " warm."; you_msg = str.str(); // Monster messages needed. break; } case 10: if (target->cannot_move()) { you_msg = "You briefly vibrate."; mon_msg_seen = "@The_monster@ briefly vibrates."; } else { you_msg = "You momentarily stiffen."; mon_msg_seen = "@The_monster@ momentarily stiffens."; } break; case 11: all_msg = "The floor vibrates."; break; case 12: all_msg = "The floor shifts beneath you alarmingly!"; break; } do_msg(); break; case 2: // slightly less harmless stuff switch (random2(1)) { case 0: switch (random2(3)) { case 0: you_msg = "You are hit by flying rocks!"; mon_msg_seen = "@The_monster@ is hit by flying rocks!"; mon_msg_unseen = "Flying rocks appear out of thin air!"; break; case 1: you_msg = "You are blasted with sand!"; mon_msg_seen = "@The_monster@ is blasted with sand!"; mon_msg_unseen = "A miniature sandstorm briefly appears!"; break; case 2: you_msg = "Rocks fall onto you out of nowhere!"; mon_msg_seen = "Rocks fall onto @the_monster@ out of " "nowhere!"; mon_msg_unseen = "Rocks fall out of nowhere!"; break; } _ouch(random2avg(13, 2) + 10 - random2(1 + target->armour_class())); break; } break; case 3: // less harmless stuff switch (random2(1)) { case 0: you_msg = "You are caught in an explosion of flying " "shrapnel!"; mon_msg_seen = "@The_monster@ is caught in an explosion of " "flying shrapnel!"; mon_msg_unseen = "Flying shrapnel explodes from thin air!"; beam.flavour = BEAM_FRAG; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 15); beam.name = "explosion"; beam.colour = CYAN; if (one_chance_in(5)) beam.colour = BROWN; if (one_chance_in(5)) beam.colour = LIGHTCYAN; _explosion(); break; } break; } } void MiscastEffect::_air(int severity) { int num; switch (severity) { case 0: // just a harmless message num = 10 + (_has_hair(target) ? 1 : 0); switch (random2(num)) { case 0: you_msg = "Ouch! You gave yourself an electric shock."; // Monster messages needed. break; case 1: you_msg = "You feel momentarily weightless."; mon_msg_seen = "@The_monster@ bobs in the air for a moment."; break; case 2: you_msg = "Wisps of vapour drift from your @hands@."; mon_msg_seen = "Wisps of vapour drift from @the_monster@'s " "@hands@."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "You feel electric!"; // Monster messages needed. break; case 5: { bool pluralised = true; if (!hand_str.empty()) pluralised = can_plural_hand; else target->hand_name(true, &pluralised); if (pluralised) { you_msg = "Sparks of electricity dance between your " "@hands@."; mon_msg_seen = "Sparks of electricity dance between " "@the_monster@'s @hands@."; } else { you_msg = "Sparks of electricity dance over your " "@hand@."; mon_msg_seen = "Sparks of electricity dance over " "@the_monster@'s @hand@."; } break; } case 6: you_msg = "You are blasted with air!"; mon_msg_seen = "@The_monster@ is blasted with air!"; break; case 7: if (neither_end_silenced()) { all_msg = "You hear a whooshing sound."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (you.can_smell()) all_msg = "You smell ozone."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { all_msg = "You hear a crackling sound."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (you.can_smell()) all_msg = "You smell something musty."; else if (you.species == SP_MUMMY) you_msg = "Your bandages flutter."; break; case 10: { // Player only (for now). bool plural; std::string hair = _hair_str(target, plural); you_msg = make_stringf("Your %s stand%s on end.", hair.c_str(), plural ? "" : "s"); break; } } do_msg(); break; case 1: // a bit less harmless stuff switch (random2(2)) { case 0: you_msg = "There is a short, sharp shower of sparks."; mon_msg_seen = "@The_monster@ is briefly showered in sparks."; break; case 1: if (silenced(you.pos())) { you_msg = "The wind whips around you!"; mon_msg_seen = "The wind whips around @the_monster@!"; mon_msg_unseen = "The wind whips!"; } else { you_msg = "The wind howls around you!"; mon_msg_seen = "The wind howls around @the_monster@!"; mon_msg_unseen = "The wind howls!"; } break; } do_msg(); break; case 2: // rather less harmless stuff switch (random2(2)) { case 0: you_msg = "Electricity courses through your body."; // Monster messages needed. _ouch(4 + random2avg(9, 2), BEAM_ELECTRICITY); break; case 1: you_msg = "Noxious gasses pour from your @hands@!"; mon_msg_seen = "Noxious gasses pour from @the_monster@'s " "@hands@!"; mon_msg_unseen = "Noxious gasses appear from out of thin air!"; _big_cloud(CLOUD_STINK, 20, 9 + random2(4)); break; } break; case 3: // musch less harmless stuff switch (random2(2)) { case 0: you_msg = "You are caught in an explosion of electrical " "discharges!"; mon_msg_seen = "@The_monster@ is caught in an explosion of " "electrical discharges!"; mon_msg_unseen = "Electrical discharges explode from out of " "thin air!"; beam.flavour = BEAM_ELECTRICITY; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 8); beam.name = "explosion"; beam.colour = LIGHTBLUE; beam.ex_size = one_chance_in(4) ? 1 : 2; _explosion(); break; case 1: you_msg = "Venomous gasses pour from your @hands@!"; mon_msg_seen = "Venomous gasses pour from @the_monster@'s " "@hands@!"; mon_msg_unseen = "Venomous gasses pour forth from the thin air!"; _big_cloud(CLOUD_POISON, 20, 8 + random2(5)); break; } } } void MiscastEffect::_poison(int severity) { switch (severity) { case 0: // just a harmless message switch (random2(10)) { case 0: you_msg = "You feel mildly nauseous."; // Monster messages needed. break; case 1: you_msg = "You feel slightly ill."; // Monster messages needed. break; case 2: you_msg = "Wisps of poison gas drift from your @hands@."; mon_msg_seen = "Wisps of poison gas drift from @the_monster@'s " "@hands@."; break; case 3: you_msg = "You feel a strange surge of energy!"; // Monster messages needed. break; case 4: you_msg = "You feel faint for a moment."; // Monster messages needed. break; case 5: you_msg = "You feel sick."; // Monster messages needed. break; case 6: you_msg = "You feel odd."; // Monster messages needed. break; case 7: you_msg = "You feel weak for a moment."; // Monster messages needed. break; case 8: // Set nothing; canned_msg(MSG_NOTHING_HAPPENS) will be taken // care of elsewhere. break; case 9: if (neither_end_silenced()) { all_msg = "You hear a slurping sound."; msg_ch = MSGCH_SOUND; sound_loudness = 2; } else if (you.species != SP_MUMMY) you_msg = "You taste almonds."; break; } do_msg(); break; case 1: // a bit less harmless stuff switch (random2(2)) { case 0: if (target->res_poison() <= 0) { you_msg = "You feel sick."; // Monster messages needed. target->poison(act_source, 2 + random2(3)); } do_msg(); break; case 1: you_msg = "Noxious gasses pour from your @hands@!"; mon_msg_seen = "Noxious gasses pour from @the_monster@'s " "@hands@!"; mon_msg_unseen = "Noxious gasses pour forth from the thin air!"; place_cloud(CLOUD_STINK, target->pos(), 2 + random2(4), kc, kt ); break; } break; case 2: // rather less harmless stuff // Don't use last case for monsters. switch (random2(target->atype() == ACT_PLAYER ? 3 : 2)) { case 0: if (target->res_poison() <= 0) { you_msg = "You feel very sick."; // Monster messages needed. target->poison(act_source, 3 + random2avg(9, 2)); } do_msg(); break; case 1: you_msg = "Noxious gasses pour from your @hands@!"; mon_msg_seen = "Noxious gasses pour from @the_monster@'s " "@hands@!"; mon_msg_unseen = "Noxious gasses pour forth from the thin air!"; _big_cloud(CLOUD_STINK, 20, 8 + random2(5)); break; case 2: if (player_res_poison()) canned_msg(MSG_NOTHING_HAPPENS); else _lose_stat(STAT_RANDOM, 1); break; } break; case 3: // less harmless stuff // Don't use last case for monsters. switch (random2(target->atype() == ACT_PLAYER ? 3 : 2)) { case 0: if (target->res_poison() <= 0) { you_msg = "You feel incredibly sick."; // Monster messages needed. target->poison(act_source, 10 + random2avg(19, 2)); } do_msg(); break; case 1: you_msg = "Venomous gasses pour from your @hands@!"; mon_msg_seen = "Venomous gasses pour from @the_monster@'s " "@hands@!"; mon_msg_unseen = "Venomous gasses pour forth from the thin air!"; _big_cloud(CLOUD_POISON, 20, 7 + random2(7)); break; case 2: if (player_res_poison()) canned_msg(MSG_NOTHING_HAPPENS); else _lose_stat(STAT_RANDOM, 1 + random2avg(5, 2)); break; } break; } }