summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/mstuff2.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/mstuff2.cc')
-rw-r--r--crawl-ref/source/mstuff2.cc3225
1 files changed, 0 insertions, 3225 deletions
diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc
index a86faa226f..acaa231a67 100644
--- a/crawl-ref/source/mstuff2.cc
+++ b/crawl-ref/source/mstuff2.cc
@@ -48,3229 +48,4 @@
#include "traps.h"
#include "view.h"
-static int _monster_abjuration(const monsters *caster, bool actual);
-static bool _mons_abjured(monsters *monster, bool nearby)
-{
- if (nearby && _monster_abjuration(monster, false) > 0
- && coinflip())
- {
- _monster_abjuration(monster, true);
- return (true);
- }
-
- return (false);
-}
-
-static monster_type _pick_random_wraith()
-{
- static monster_type wraiths[] =
- {
- MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
- MONS_SPECTRAL_WARRIOR, MONS_PHANTOM, MONS_HUNGRY_GHOST,
- MONS_FLAYED_GHOST
- };
-
- return (RANDOM_ELEMENT(wraiths));
-}
-
-static monster_type _pick_horrible_thing()
-{
- return (one_chance_in(4) ? MONS_TENTACLED_MONSTROSITY
- : MONS_ABOMINATION_LARGE);
-}
-
-static monster_type _pick_undead_summon()
-{
- static monster_type undead[] =
- {
- MONS_NECROPHAGE, MONS_GHOUL, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST,
- MONS_ZOMBIE_SMALL, MONS_SKELETON_SMALL, MONS_SIMULACRUM_SMALL,
- MONS_FLYING_SKULL, MONS_FLAMING_CORPSE, MONS_MUMMY, MONS_VAMPIRE,
- MONS_WIGHT, MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH,
- MONS_SPECTRAL_WARRIOR, MONS_ZOMBIE_LARGE, MONS_SKELETON_LARGE,
- MONS_SIMULACRUM_LARGE, MONS_SHADOW
- };
-
- return (RANDOM_ELEMENT(undead));
-}
-
-static void _do_high_level_summon(monsters *monster, bool monsterNearby,
- spell_type spell_cast,
- monster_type (*mpicker)(), int nsummons,
- god_type god, coord_def *target = NULL)
-{
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- const int duration = std::min(2 + monster->hit_dice / 5, 6);
-
- for (int i = 0; i < nsummons; ++i)
- {
- monster_type which_mons = mpicker();
-
- if (which_mons == MONS_NO_MONSTER)
- continue;
-
- create_monster(
- mgen_data(which_mons, SAME_ATTITUDE(monster),
- duration, spell_cast, target ? *target : monster->pos(),
- monster->foe, 0, god));
- }
-}
-
-static bool _los_free_spell(spell_type spell_cast)
-{
- return (spell_cast == SPELL_HELLFIRE_BURST
- || spell_cast == SPELL_BRAIN_FEED
- || spell_cast == SPELL_SMITING
- || spell_cast == SPELL_HAUNT
- || spell_cast == SPELL_FIRE_STORM
- || spell_cast == SPELL_AIRSTRIKE);
-}
-
-// Returns true if a message referring to the player's legs makes sense.
-static bool _legs_msg_applicable()
-{
- return (you.species != SP_NAGA
- && (you.species != SP_MERFOLK || !player_is_swimming()));
-}
-
-void mons_cast_haunt(monsters *monster)
-{
- coord_def fpos;
-
- switch (monster->foe)
- {
- case MHITNOT:
- return;
-
- case MHITYOU:
- fpos = you.pos();
- break;
-
- default:
- fpos = menv[monster->foe].pos();
- }
-
- _do_high_level_summon(monster, mons_near(monster), SPELL_HAUNT,
- _pick_random_wraith, random_range(3, 6), GOD_NO_GOD, &fpos);
-}
-
-void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
- bool do_noise)
-{
- // Always do setup. It might be done already, but it doesn't hurt
- // to do it again (cheap).
- setup_mons_cast(monster, pbolt, spell_cast);
-
- // single calculation permissible {dlb}
- bool monsterNearby = mons_near(monster);
-
- int sumcount = 0;
- int sumcount2;
- int duration = 0;
-
-#if DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "Mon #%d casts %s (#%d)",
- monster_index(monster), spell_title(spell_cast), spell_cast);
-#endif
-
- if (spell_cast == SPELL_CANTRIP)
- do_noise = false; // Spell itself does the messaging.
-
- if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast))
- {
- if (monster->foe == MHITYOU || monster->foe == MHITNOT)
- {
- if (monsterNearby)
- {
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
- direct_effect(monster, spell_cast, pbolt, &you);
- }
- return;
- }
-
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
- direct_effect(monster, spell_cast, pbolt, monster->get_foe());
- return;
- }
-
-#ifdef DEBUG
- const unsigned int flags = get_spell_flags(spell_cast);
-
- ASSERT(!(flags & (SPFLAG_TESTING | SPFLAG_MAPPING)));
-
- // Targeted spells need a valid target.
- ASSERT(!(flags & SPFLAG_TARGETTING_MASK) || in_bounds(pbolt.target));
-#endif
-
- if (do_noise)
- mons_cast_noise(monster, pbolt, spell_cast);
-
- // If the monster's a priest, assume summons come from priestly
- // abilities, in which case they'll have the same god. If the
- // monster is neither a priest nor a wizard, assume summons come
- // from intrinsic abilities, in which case they'll also have the
- // same god.
- const bool priest = mons_class_flag(monster->type, M_PRIEST);
- const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
- god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD;
-
- switch (spell_cast)
- {
- default:
- break;
-
- case SPELL_MAJOR_HEALING:
- if (heal_monster(monster, 50 + random2avg(monster->hit_dice * 10, 2),
- false))
- {
- simple_monster_message(monster, " is healed.");
- }
- return;
-
- case SPELL_BERSERKER_RAGE:
- monster->go_berserk(true);
- return;
-
- case SPELL_SUMMON_SMALL_MAMMALS:
- case SPELL_VAMPIRE_SUMMON:
- if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS)
- sumcount2 = 1 + random2(4);
- else
- sumcount2 = 3 + random2(3) + monster->hit_dice / 5;
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- const monster_type rats[] = { MONS_ORANGE_RAT, MONS_GREEN_RAT,
- MONS_GREY_RAT, MONS_RAT };
- const monster_type mon = (one_chance_in(3) ? MONS_GIANT_BAT
- : RANDOM_ELEMENT(rats));
- create_monster(
- mgen_data(mon, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(RANDOM_MONSTER, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_WATER_ELEMENTALS:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1);
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(MONS_WATER_ELEMENTAL, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_KRAKEN_TENTACLES:
- {
- int kraken_index = monster_index(monster);
- if (invalid_monster_index(duration))
- {
- mpr("Error! Kraken is not a part of the current environment!",
- MSGCH_ERROR);
- return;
- }
- sumcount2 = std::max(random2(9), random2(9)); // up to eight tentacles
- if (sumcount2 == 0)
- return;
-
- for (sumcount = 0; sumcount < MAX_MONSTERS; ++sumcount)
- if (menv[sumcount].type == MONS_KRAKEN_TENTACLE
- && (int)menv[sumcount].number == kraken_index)
- {
- // Reduce by tentacles already placed.
- sumcount2--;
- }
-
- for (sumcount = sumcount2; sumcount > 0; --sumcount)
- {
- // Tentacles aren't really summoned (controlled by spell_cast
- // being passed to summon_type), so I'm not sure what the
- // abjuration value (3) is doing there. (jpeg)
- if (create_monster(
- mgen_data(MONS_KRAKEN_TENTACLE, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god,
- MONS_NO_MONSTER, kraken_index, monster->colour,
- you.your_level, PROX_CLOSE_TO_PLAYER,
- you.level_type)) == -1)
- {
- sumcount2--;
- }
- }
- if (sumcount2 == 1)
- mpr("A tentacle rises from the water!");
- else if (sumcount2 > 1)
- mpr("Tentacles burst out of the water!");
- return;
- }
- case SPELL_FAKE_RAKSHASA_SUMMON:
- sumcount2 = (coinflip() ? 2 : 3);
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(MONS_RAKSHASA_FAKE, SAME_ATTITUDE(monster),
- 3, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SUMMON_DEMON: // class 2-4 demons
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_COMMON),
- SAME_ATTITUDE(monster), duration, spell_cast,
- monster->pos(), monster->foe, 0, god));
- }
- return;
-
- case SPELL_SUMMON_UGLY_THING:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- const int chance = std::max(6 - (monster->hit_dice / 6), 1);
- monster_type mon = (one_chance_in(chance) ? MONS_VERY_UGLY_THING
- : MONS_UGLY_THING);
-
- create_monster(
- mgen_data(mon, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_ANIMATE_DEAD:
- // see special handling in monstuff::handle_spell() {dlb}
- animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster),
- monster->foe, god);
- return;
-
- case SPELL_CALL_IMP: // class 5 demons
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_LESSER),
- SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_SCORPIONS:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(MONS_SCORPION, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_UFETUBUS:
- sumcount2 = 2 + random2(2) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
-
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(MONS_UFETUBUS, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_BEAST: // Geryon
- create_monster(
- mgen_data(MONS_BEAST, SAME_ATTITUDE(monster),
- 4, spell_cast, monster->pos(), monster->foe, 0, god));
- return;
-
- case SPELL_SUMMON_ICE_BEAST:
- create_monster(
- mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(monster),
- 5, spell_cast, monster->pos(), monster->foe, 0, god));
- return;
-
- case SPELL_SUMMON_MUSHROOMS: // Summon swarms of icky crawling fungi.
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 4 + 1);
-
- duration = std::min(2 + monster->hit_dice / 5, 6);
- for (int i = 0; i < sumcount2; ++i)
- {
- create_monster(
- mgen_data(MONS_WANDERING_MUSHROOM, SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe, 0,
- god));
- }
- return;
-
- case SPELL_SUMMON_HORRIBLE_THINGS:
- _do_high_level_summon(monster, monsterNearby, spell_cast,
- _pick_horrible_thing, random_range(3, 5), god);
- return;
-
- case SPELL_CONJURE_BALL_LIGHTNING:
- {
- const int n = 2 + random2(monster->hit_dice / 4);
- for (int i = 0; i < n; ++i)
- {
- create_monster(
- mgen_data(MONS_BALL_LIGHTNING, SAME_ATTITUDE(monster),
- 2, spell_cast, monster->pos(), monster->foe, 0, god));
- }
- return;
- }
-
- case SPELL_SUMMON_UNDEAD: // Summon undead around player.
- _do_high_level_summon(monster, monsterNearby, spell_cast,
- _pick_undead_summon,
- 2 + random2(2)
- + random2(monster->hit_dice / 4 + 1), god);
- return;
-
- case SPELL_SYMBOL_OF_TORMENT:
- if (!monsterNearby || mons_friendly(monster))
- return;
-
- torment(monster_index(monster), monster->pos());
- return;
-
- case SPELL_SUMMON_GREATER_DEMON:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(monster->hit_dice / 10 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
- for (sumcount = 0; sumcount < sumcount2; ++sumcount)
- {
- create_monster(
- mgen_data(summon_any_demon(DEMON_GREATER),
- SAME_ATTITUDE(monster),
- duration, spell_cast, monster->pos(), monster->foe,
- 0, god));
- }
- return;
-
- // Journey -- Added in Summon Lizards and Draconian
- case SPELL_SUMMON_DRAKES:
- if (_mons_abjured(monster, monsterNearby))
- return;
-
- sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1);
-
- duration = std::min(2 + monster->hit_dice / 10, 6);
-
- {
- std::vector<monster_type> monsters;
-
- for (sumcount = 0; sumcount < sumcount2; sumcount++)
- {
- monster_type mon = summon_any_dragon(DRAGON_LIZARD);
-
- if (mon == MONS_DRAGON)
- {
- monsters.clear();
- monsters.push_back(summon_any_dragon(DRAGON_DRAGON));
- break;
- }
-
- monsters.push_back(mon);
- }
-
- for (int i = 0, size = monsters.size(); i < size; ++i)
- {
- create_monster(
- mgen_data(monsters[i], SAME_ATTITUDE(monster),
- duration, spell_cast,
- monster->pos(), monster->foe, 0, god));
- }
- }
- return;
-
- // TODO: Outsource the cantrip messages and allow specification of
- // special cantrip spells per monster, like for speech, both as
- // "self buffs" and "player enchantments".
- case SPELL_CANTRIP:
- {
- // Monster spell of uselessness, just prints a message.
- // This spell exists so that some monsters with really strong
- // spells (ie orc priest) can be toned down a bit. -- bwr
- //
- // XXX: Needs expansion, and perhaps different priest/mage flavours.
-
- // Don't give any message if the monster isn't nearby.
- // (Otherwise you could get them from halfway across the level.)
- if (!mons_near(monster))
- return;
-
- const bool friendly = mons_friendly(monster);
- const bool buff_only = !friendly && is_sanctuary(you.pos());
- const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT
- : MSGCH_MONSTER_ENCHANT;
-
- if (monster->type == MONS_GASTRONOK)
- {
- bool has_mon_foe = !invalid_monster_index(monster->foe);
- std::string slugform = "";
- if (buff_only || crawl_state.arena && !has_mon_foe
- || friendly && !has_mon_foe || coinflip())
- {
- slugform = getSpeakString("gastronok_self_buff");
- if (!slugform.empty())
- {
- slugform = replace_all(slugform, "@The_monster@",
- monster->name(DESC_CAP_THE));
- mpr(slugform.c_str(), channel);
- }
- }
- else if (!friendly && !has_mon_foe)
- {
- mons_cast_noise(monster, pbolt, spell_cast);
-
- // "Enchant" the player.
- slugform = getSpeakString("gastronok_debuff");
- if (!slugform.empty()
- && (slugform.find("legs") == std::string::npos
- || _legs_msg_applicable()))
- {
- mpr(slugform.c_str());
- }
- }
- else
- {
- // "Enchant" another monster.
- const monsters* foe
- = dynamic_cast<const monsters*>(monster->get_foe());
- slugform = getSpeakString("gastronok_other_buff");
- if (!slugform.empty())
- {
- slugform = replace_all(slugform, "@The_monster@",
- foe->name(DESC_CAP_THE));
- mpr(slugform.c_str(), MSGCH_MONSTER_ENCHANT);
- }
- }
- }
- else
- {
- // Messages about the monster influencing itself.
- const char* buff_msgs[] = { " glows brightly for a moment.",
- " looks stronger.",
- " becomes somewhat translucent.",
- "'s eyes start to glow." };
-
- // Messages about the monster influencing you.
- const char* other_msgs[] = {
- "You feel troubled.",
- "You feel a wave of unholy energy pass over you."
- };
-
- if (buff_only || crawl_state.arena || x_chance_in_y(2,3))
- {
- simple_monster_message(monster, RANDOM_ELEMENT(buff_msgs),
- channel);
- }
- else if (friendly)
- {
- simple_monster_message(monster, " shimmers for a moment.",
- channel);
- }
- else // "Enchant" the player.
- {
- mons_cast_noise(monster, pbolt, spell_cast);
- mpr(RANDOM_ELEMENT(other_msgs));
- }
- }
- return;
- }
- case SPELL_BLINK_OTHER:
- {
- // Allow the caster to comment on moving the foe.
- std::string msg = getSpeakString(monster->name(DESC_PLAIN)
- + " blink_other");
- if (!msg.empty() && msg != "__NONE")
- {
- mons_speaks_msg(monster, msg, MSGCH_TALK,
- silenced(you.pos()) || silenced(monster->pos()));
- }
- break;
- }
- case SPELL_TOMB_OF_DOROKLOHE:
- {
- sumcount = 0;
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- // we can blink away the crowd, but only our allies
- if (mgrd(*ai) != NON_MONSTER
- && monster_at(*ai)->attitude != monster->attitude)
- sumcount++;
- if (grd(*ai) != DNGN_FLOOR && grd(*ai) > DNGN_MAX_NONREACH
- && !feat_is_trap(grd(*ai)))
- sumcount++;
- }
- if (abs(you.pos().x-monster->pos().x)<=1 &&
- abs(you.pos().y-monster->pos().y)<=1)
- sumcount++;
- if (sumcount)
- {
- monster->blink();
- return;
- }
-
- sumcount = 0;
- for (adjacent_iterator ai(monster->pos()); ai; ++ai)
- {
- if (mgrd(*ai) != NON_MONSTER && monster_at(*ai) != monster)
- {
- monster_at(*ai)->blink();
- if (mgrd(*ai) != NON_MONSTER)
- {
- monster_at(*ai)->teleport(true);
- if (mgrd(*ai) != NON_MONSTER)
- continue;
- }
- }
- if (grd(*ai) == DNGN_FLOOR || feat_is_trap(grd(*ai)))
- {
- grd(*ai) = DNGN_ROCK_WALL;
- sumcount++;
- }
- }
- if (sumcount)
- mpr("Walls emerge from the floor!");
- monster->number = 1; // mark Khufu as entombed
- return;
- }
- }
-
- // If a monster just came into view and immediately cast a spell,
- // we need to refresh the screen before drawing the beam.
- viewwindow(true, false);
- if (spell_is_direct_explosion(spell_cast))
- {
- const actor *foe = monster->get_foe();
- const bool need_more = foe && (foe == &you || see_cell(foe->pos()));
- pbolt.in_explosion_phase = false;
- pbolt.explode(need_more);
- }
- else
- pbolt.fire();
-}
-
-void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast)
-{
- bool force_silent = false;
-
- spell_type real_spell = spell_cast;
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- {
- int type = monster->type;
- if (mons_genus(type) == MONS_DRACONIAN)
- type = draco_subspecies(monster);
-
- switch (type)
- {
- case MONS_MOTTLED_DRACONIAN:
- real_spell = SPELL_STICKY_FLAME_SPLASH;
- break;
-
- case MONS_YELLOW_DRACONIAN:
- real_spell = SPELL_ACID_SPLASH;
- break;
-
- case MONS_PLAYER_GHOST:
- // Draining breath is silent.
- force_silent = true;
- break;
-
- default:
- break;
- }
- }
- else if (monster->type == MONS_SHADOW_DRAGON)
- // Draining breath is silent.
- force_silent = true;
-
- const bool unseen = !you.can_see(monster);
- const bool silent = silenced(monster->pos()) || force_silent;
- const bool no_silent = mons_class_flag(monster->type, M_SPELL_NO_SILENT);
-
- if (unseen && silent)
- return;
-
- const unsigned int flags = get_spell_flags(real_spell);
-
- const bool priest = mons_class_flag(monster->type, M_PRIEST);
- const bool wizard = mons_class_flag(monster->type, M_ACTUAL_SPELLS);
- const bool innate = !(priest || wizard || no_silent)
- || (flags & SPFLAG_INNATE);
-
- int noise;
- if (silent
- || (innate
- && !mons_class_flag(monster->type, M_NOISY_SPELLS)
- && !(flags & SPFLAG_NOISY)
- && mons_genus(monster->type) != MONS_DRAGON))
- {
- noise = 0;
- }
- else
- {
- if (mons_genus(monster->type) == MONS_DRAGON)
- noise = get_shout_noise_level(S_ROAR);
- else
- noise = spell_noise(real_spell);
- }
-
- const std::string cast_str = " cast";
-
- const std::string spell_name = spell_title(real_spell);
- const mon_body_shape shape = get_mon_shape(monster);
-
- std::vector<std::string> key_list;
-
- // First try the spells name.
- if (shape <= MON_SHAPE_NAGA)
- {
- if (!innate && (priest || wizard))
- key_list.push_back(spell_name + cast_str + " real");
- if (mons_intel(monster) >= I_NORMAL)
- key_list.push_back(spell_name + cast_str + " gestures");
- }
- key_list.push_back(spell_name + cast_str);
-
- const unsigned int num_spell_keys = key_list.size();
-
- // Next the monster type name, then species name, then genus name.
- key_list.push_back(mons_type_name(monster->type, DESC_PLAIN) + cast_str);
- key_list.push_back(mons_type_name(mons_species(monster->type), DESC_PLAIN)
- + cast_str);
- key_list.push_back(mons_type_name(mons_genus(monster->type), DESC_PLAIN)
- + cast_str);
-
- // Last, generic wizard, priest or demon.
- if (wizard)
- key_list.push_back("wizard" + cast_str);
- else if (priest)
- key_list.push_back("priest" + cast_str);
- else if (mons_is_demon(monster->type))
- key_list.push_back("demon" + cast_str);
-
- const bool visible_beam = pbolt.type != 0 && pbolt.type != ' '
- && pbolt.name[0] != '0'
- && !pbolt.is_enchantment();
-
- const bool targeted = (flags & SPFLAG_TARGETTING_MASK)
- && (pbolt.target != monster->pos() || visible_beam);
-
- if (targeted)
- {
- // For targeted spells, try with the targeted suffix first.
- for (unsigned int i = key_list.size() - 1; i >= num_spell_keys; i--)
- {
- std::string str = key_list[i] + " targeted";
- key_list.insert(key_list.begin() + i, str);
- }
-
- // Generic beam messages.
- if (visible_beam)
- {
- key_list.push_back(pbolt.get_short_name() + " beam " + cast_str);
- key_list.push_back("beam catchall cast");
- }
- }
-
- std::string prefix;
- if (silent)
- prefix = "silent ";
- else if (unseen)
- prefix = "unseen ";
-
- std::string msg;
- for (unsigned int i = 0; i < key_list.size(); i++)
- {
- const std::string key = key_list[i];
-
- msg = getSpeakString(prefix + key);
- if (msg == "__NONE")
- {
- msg = "";
- break;
- }
- else if (msg == "__NEXT")
- {
- msg = "";
- if (i < num_spell_keys)
- i = num_spell_keys - 1;
- else if (ends_with(key, " targeted"))
- i++;
- continue;
- }
- else if (!msg.empty())
- break;
-
- // If we got no message and we're using the silent prefix, then
- // try again without the prefix.
- if (prefix != "silent")
- continue;
-
- msg = getSpeakString(key);
- if (msg == "__NONE")
- {
- msg = "";
- break;
- }
- else if (msg == "__NEXT")
- {
- msg = "";
- if (i < num_spell_keys)
- i = num_spell_keys - 1;
- else if (ends_with(key, " targeted"))
- i++;
- continue;
- }
- else if (!msg.empty())
- break;
- }
-
- if (msg.empty())
- {
- if (silent)
- return;
-
- noisy(noise, monster->pos(), monster->mindex());
- return;
- }
-
- /////////////////////
- // We have a message.
- /////////////////////
-
- const bool gestured = msg.find("Gesture") != std::string::npos
- || msg.find(" gesture") != std::string::npos
- || msg.find("Point") != std::string::npos
- || msg.find(" point") != std::string::npos;
-
- bolt tracer = pbolt;
- if (targeted)
- {
- // For a targeted but rangeless spell make the range positive so that
- // fire_tracer() will fill out path_taken.
- if (pbolt.range == 0 && pbolt.target != monster->pos())
- tracer.range = ENV_SHOW_DIAMETER;
-
- fire_tracer(monster, tracer);
- }
-
- std::string targ_prep = "at";
- std::string target = "nothing";
-
- if (!targeted)
- target = "NO TARGET";
- else if (pbolt.target == you.pos())
- target = "you";
- else if (pbolt.target == monster->pos())
- target = monster->pronoun(PRONOUN_REFLEXIVE);
- // Monsters should only use targeted spells while foe == MHITNOT
- // if they're targetting themselves.
- else if (monster->foe == MHITNOT && !monster->confused())
- target = "NONEXISTENT FOE";
- else if (!invalid_monster_index(monster->foe)
- && menv[monster->foe].type == MONS_NO_MONSTER)
- {
- target = "DEAD FOE";
- }
- else if (in_bounds(pbolt.target) && see_cell(pbolt.target))
- {
- if (const monsters* mtarg = monster_at(pbolt.target))
- {
- if (you.can_see(mtarg))
- target = mtarg->name(DESC_NOCAP_THE);
- }
- }
-
- // Monster might be aiming past the real target, or maybe some fuzz has
- // been applied because the target is invisible.
- if (target == "nothing" && targeted)
- {
- if (pbolt.aimed_at_spot)
- {
- int count = 0;
- for (adjacent_iterator ai(pbolt.target); ai; ++ai)
- {
- const actor* act = actor_at(*ai);
- if (act && act != monster && you.can_see(act))
- {
- targ_prep = "next to";
-
- if (act->atype() == ACT_PLAYER || one_chance_in(++count))
- target = act->name(DESC_NOCAP_THE);
-
- if (act->atype() == ACT_PLAYER)
- break;
- }
- }
- }
-
- const bool visible_path = visible_beam || gestured;
- bool mons_targ_aligned = false;
-
- const std::vector<coord_def> &path = tracer.path_taken;
- for (unsigned int i = 0; i < path.size(); i++)
- {
- const coord_def pos = path[i];
-
- if (pos == monster->pos())
- continue;
-
- const monsters *m = monster_at(pos);
- if (pos == you.pos())
- {
- // Be egotistical and assume that the monster is aiming at
- // the player, rather than the player being in the path of
- // a beam aimed at an ally.
- if (!mons_wont_attack(monster))
- {
- targ_prep = "at";
- target = "you";
- break;
- }
- // If the ally is confused or aiming at an invisible enemy,
- // with the player in the path, act like it's targeted at
- // the player if there isn't any visible target earlier
- // in the path.
- else if (target == "nothing")
- {
- targ_prep = "at";
- target = "you";
- mons_targ_aligned = true;
- }
- }
- else if (visible_path && m && you.can_see(m))
- {
- bool is_aligned = mons_aligned(m->mindex(), monster->mindex());
- std::string name = m->name(DESC_NOCAP_THE);
-
- if (target == "nothing")
- {
- mons_targ_aligned = is_aligned;
- target = name;
- }
- // If the first target was aligned with the beam source then
- // the first subsequent non-aligned monster in the path will
- // take it's place.
- else if (mons_targ_aligned && !is_aligned)
- {
- mons_targ_aligned = false;
- target = name;
- }
- targ_prep = "at";
- }
- else if (visible_path && target == "nothing")
- {
- int count = 0;
- for (adjacent_iterator ai(pbolt.target); ai; ++ai)
- {
- const actor* act = monster_at(*ai);
- if (act && act != monster && you.can_see(act))
- {
- targ_prep = "past";
- if (act->atype() == ACT_PLAYER
- || one_chance_in(++count))
- {
- target = act->name(DESC_NOCAP_THE);
- }
-
- if (act->atype() == ACT_PLAYER)
- break;
- }
- }
- }
- } // for (unsigned int i = 0; i < path.size(); i++)
- } // if (target == "nothing" && targeted)
-
- const actor* foe = monster->get_foe();
-
- // If we still can't find what appears to be the target, and the
- // monster isn't just throwing the spell in a random direction,
- // we should be able to tell what the monster was aiming for if
- // we can see the monster's foe and the beam (or the beam path
- // implied by gesturing). But only if the beam didn't actually hit
- // anything (but if it did hit something, why didn't that monster
- // show up in the beam's path?)
- if (targeted
- && target == "nothing"
- && (tracer.foe_info.count + tracer.friend_info.count) == 0
- && foe != NULL
- && you.can_see(foe)
- && !monster->confused()
- && (visible_beam || gestured))
- {
- target = foe->name(DESC_NOCAP_THE);
- targ_prep = (pbolt.aimed_at_spot ? "next to" : "past");
- }
-
- // If the monster gestures to create an invisible beam then
- // assume that anything close to the beam is the intended target.
- // Also, if the monster gestures to create a visible beam but it
- // misses still say that the monster gestured "at" the target,
- // rather than "past".
- if (gestured || target == "nothing")
- targ_prep = "at";
-
- msg = replace_all(msg, "@at@", targ_prep);
- msg = replace_all(msg, "@target@", target);
-
- std::string beam_name;
- if (!targeted)
- beam_name = "NON TARGETED BEAM";
- else if (pbolt.name.empty())
- beam_name = "INVALID BEAM";
- else if (!tracer.seen)
- beam_name = "UNSEEN BEAM";
- else
- beam_name = pbolt.get_short_name();
-
- msg = replace_all(msg, "@beam@", beam_name);
-
- const msg_channel_type chan =
- (unseen ? MSGCH_SOUND :
- mons_friendly_real(monster) ? MSGCH_FRIEND_SPELL
- : MSGCH_MONSTER_SPELL);
-
- if (silent)
- mons_speaks_msg(monster, msg, chan, true);
- else if (noisy(noise, monster->pos(), monster->mindex()) || !unseen)
- {
- // noisy() returns true if the player heard the noise.
- mons_speaks_msg(monster, msg, chan);
- }
-}
-
-// Set up bolt structure for monster spell casting.
-void setup_mons_cast(monsters *monster, bolt &pbolt,
- spell_type spell_cast)
-{
- // always set these -- used by things other than fire_beam()
-
- // [ds] Used to be 12 * MHD and later buggily forced to -1 downstairs.
- // Setting this to a more realistic number now that that bug is
- // squashed.
- pbolt.ench_power = 4 * monster->hit_dice;
-
- if (spell_cast == SPELL_TELEPORT_SELF)
- pbolt.ench_power = 2000;
-
- pbolt.beam_source = monster_index(monster);
-
- // Convenience for the hapless innocent who assumes that this
- // damn function does all possible setup. [ds]
- if (pbolt.target.origin())
- pbolt.target = monster->target;
-
- // Set bolt type and range.
- if (_los_free_spell(spell_cast))
- {
- pbolt.range = 0;
- switch (spell_cast)
- {
- case SPELL_BRAIN_FEED:
- pbolt.type = DMNBM_BRAIN_FEED;
- return;
- case SPELL_SMITING:
- case SPELL_AIRSTRIKE:
- pbolt.type = DMNBM_SMITING;
- return;
- default:
- // Other spells get normal setup:
- break;
- }
- }
-
- // The below are no-ops since they don't involve direct_effect,
- // fire_tracer, or beam.
- switch (spell_cast)
- {
- case SPELL_SUMMON_SMALL_MAMMALS:
- case SPELL_MAJOR_HEALING:
- case SPELL_VAMPIRE_SUMMON:
- case SPELL_SHADOW_CREATURES: // summon anything appropriate for level
- case SPELL_FAKE_RAKSHASA_SUMMON:
- case SPELL_SUMMON_DEMON:
- case SPELL_SUMMON_UGLY_THING:
- case SPELL_ANIMATE_DEAD:
- case SPELL_CALL_IMP:
- case SPELL_SUMMON_SCORPIONS:
- case SPELL_SUMMON_UFETUBUS:
- case SPELL_SUMMON_BEAST: // Geryon
- case SPELL_SUMMON_UNDEAD: // summon undead around player
- case SPELL_SUMMON_ICE_BEAST:
- case SPELL_SUMMON_MUSHROOMS:
- case SPELL_CONJURE_BALL_LIGHTNING:
- case SPELL_SUMMON_DRAKES:
- case SPELL_SUMMON_HORRIBLE_THINGS:
- case SPELL_HAUNT:
- case SPELL_SYMBOL_OF_TORMENT:
- case SPELL_SUMMON_GREATER_DEMON:
- case SPELL_CANTRIP:
- case SPELL_BERSERKER_RAGE:
- case SPELL_WATER_ELEMENTALS:
- case SPELL_KRAKEN_TENTACLES:
- case SPELL_BLINK:
- case SPELL_CONTROLLED_BLINK:
- case SPELL_TOMB_OF_DOROKLOHE:
- return;
- default:
- break;
- }
-
- // Need to correct this for power of spellcaster
- int power = 12 * monster->hit_dice;
-
- bolt theBeam = mons_spells(monster, spell_cast, power);
-
- pbolt.colour = theBeam.colour;
- pbolt.range = theBeam.range;
- pbolt.hit = theBeam.hit;
- pbolt.damage = theBeam.damage;
-
- if (theBeam.ench_power != -1)
- pbolt.ench_power = theBeam.ench_power;
-
- pbolt.type = theBeam.type;
- pbolt.flavour = theBeam.flavour;
- pbolt.thrower = theBeam.thrower;
- pbolt.name = theBeam.name;
- pbolt.short_name = theBeam.short_name;
- pbolt.is_beam = theBeam.is_beam;
- pbolt.source = monster->pos();
- pbolt.is_tracer = false;
- pbolt.is_explosion = theBeam.is_explosion;
- pbolt.ex_size = theBeam.ex_size;
-
- pbolt.foe_ratio = theBeam.foe_ratio;
-
- if (!pbolt.is_enchantment())
- pbolt.aux_source = pbolt.name;
- else
- pbolt.aux_source.clear();
-
- if (spell_cast == SPELL_HASTE
- || spell_cast == SPELL_INVISIBILITY
- || spell_cast == SPELL_MINOR_HEALING
- || spell_cast == SPELL_TELEPORT_SELF)
- {
- pbolt.target = monster->pos();
- }
- else if (spell_cast == SPELL_PORKALATOR && one_chance_in(3))
- {
- int target = -1;
- int count = 0;
- monster_type hog_type = MONS_HOG;
- for (int i = 0; i < MAX_MONSTERS; i++)
- {
- monsters *targ = &menv[i];
-
- if (!monster->can_see(targ))
- continue;
-
- hog_type = MONS_HOG;
- if (targ->holiness() == MH_DEMONIC)
- hog_type = MONS_HELL_HOG;
- else if (targ->holiness() != MH_NATURAL)
- continue;
-
- if (targ->type != hog_type
- && mons_atts_aligned(monster->attitude, targ->attitude)
- && mons_power(hog_type) + random2(4) >= mons_power(targ->type)
- && (!mons_class_flag(targ->type, M_SPELLCASTER) || coinflip())
- && one_chance_in(++count))
- {
- target = i;
- }
- }
-
- if (target != -1)
- {
- monsters *targ = &menv[target];
- pbolt.target = targ->pos();
-#if DEBUG_DIAGNOSTICS
- mprf("Porkalator: targetting %s instead",
- targ->name(DESC_PLAIN).c_str());
-#endif
- monster_polymorph(targ, hog_type);
- }
- // else target remains as specified
- }
-}
-
-bool monster_random_space(const monsters *monster, coord_def& target,
- bool forbid_sanctuary)
-{
- int tries = 0;
- while (tries++ < 1000)
- {
- target = random_in_bounds();
-
- // Don't land on top of another monster.
- if (actor_at(target))
- continue;
-
- if (is_sanctuary(target) && forbid_sanctuary)
- continue;
-
- if (monster_habitable_grid(monster, grd(target)))
- return (true);
- }
-
- return (false);
-}
-
-bool monster_random_space(monster_type mon, coord_def& target,
- bool forbid_sanctuary)
-{
- monsters dummy;
- dummy.type = mon;
-
- return monster_random_space(&dummy, target, forbid_sanctuary);
-}
-
-void monster_teleport(monsters *monster, bool instan, bool silent)
-{
- if (!instan)
- {
- if (monster->del_ench(ENCH_TP))
- {
- if (!silent)
- simple_monster_message(monster, " seems more stable.");
- }
- else
- {
- if (!silent)
- simple_monster_message(monster, " looks slightly unstable.");
-
- monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER,
- random_range(20, 30)) );
- }
-
- return;
- }
-
- bool was_seen = you.can_see(monster) && !mons_is_lurking(monster);
-
- if (!silent)
- simple_monster_message(monster, " disappears!");
-
- const coord_def oldplace = monster->pos();
-
- // Pick the monster up.
- mgrd(oldplace) = NON_MONSTER;
-
- coord_def newpos;
- if (monster_random_space(monster, newpos, !mons_wont_attack(monster)))
- monster->moveto(newpos);
-
- mgrd(monster->pos()) = monster_index(monster);
-
- // Mimics change form/colour when teleported.
- if (mons_is_mimic(monster->type))
- {
- monster_type old_type = monster->type;
- monster->type = static_cast<monster_type>(
- MONS_GOLD_MIMIC + random2(5));
- monster->colour = get_mimic_colour(monster);
-
- // If it's changed form, you won't recognise it.
- // This assumes that a non-gold mimic turning into another item of
- // the same description is really, really unlikely.
- if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC)
- was_seen = false;
- }
-
- const bool now_visible = mons_near(monster);
- if (!silent && now_visible)
- {
- if (was_seen)
- simple_monster_message(monster, " reappears nearby!");
- else
- {
- // Even if it doesn't interrupt an activity (the player isn't
- // delayed, the monster isn't hostile) we still want to give
- // a message.
- activity_interrupt_data ai(monster, "thin air");
- if (!interrupt_activity(AI_SEE_MONSTER, ai))
- simple_monster_message(monster, " appears out of thin air!");
- }
- }
-
- if (monster->visible_to(&you) && now_visible)
- handle_seen_interrupt(monster);
-
- // Leave a purple cloud.
- place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
- monster->kill_alignment());
-
- monster->check_redraw(oldplace);
- monster->apply_location_effects(oldplace);
-
- mons_relocated(monster);
-
- // Teleporting mimics change form - if they reappear out of LOS, they are
- // no longer known.
- if (mons_is_mimic(monster->type))
- {
- if (now_visible)
- monster->flags |= MF_KNOWN_MIMIC;
- else
- monster->flags &= ~MF_KNOWN_MIMIC;
- }
-}
-
-void setup_generic_throw(struct monsters *monster, struct bolt &pbolt)
-{
- // FIXME we should use a sensible range here
- pbolt.range = LOS_RADIUS;
- pbolt.beam_source = monster_index(monster);
-
- pbolt.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- pbolt.flavour = BEAM_MISSILE;
- pbolt.thrower = KILL_MON_MISSILE;
- pbolt.aux_source.clear();
- pbolt.is_beam = false;
-}
-
-bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used)
-{
- std::string ammo_name;
-
- bool returning = false;
-
- int baseHit = 0, baseDam = 0; // from thrown or ammo
- int ammoHitBonus = 0, ammoDamBonus = 0; // from thrown or ammo
- int lnchHitBonus = 0, lnchDamBonus = 0; // special add from launcher
- int exHitBonus = 0, exDamBonus = 0; // 'extra' bonus from skill/dex/str
- int lnchBaseDam = 0;
-
- int hitMult = 0;
- int damMult = 0;
- int diceMult = 100;
-
- // Some initial convenience & initializations.
- int wepClass = mitm[hand_used].base_type;
- int wepType = mitm[hand_used].sub_type;
-
- int weapon = monster->inv[MSLOT_WEAPON];
- int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0;
-
- mon_inv_type slot = get_mon_equip_slot(monster, mitm[hand_used]);
- ASSERT(slot != NUM_MONSTER_SLOTS);
-
- const bool skilled = mons_class_flag(monster->type, M_FIGHTER);
-
- monster->lose_energy(EUT_MISSILE);
- const int throw_energy = monster->action_energy(EUT_MISSILE);
-
- // Dropping item copy, since the launched item might be different.
- item_def item = mitm[hand_used];
- item.quantity = 1;
- if (mons_friendly(monster))
- item.flags |= ISFLAG_DROPPED_BY_ALLY;
-
- // FIXME we should actually determine a sensible range here
- pbolt.range = LOS_RADIUS;
-
- if (setup_missile_beam(monster, pbolt, item, ammo_name, returning))
- return (false);
-
- pbolt.aimed_at_spot = returning;
-
- const launch_retval projected =
- is_launched(monster, monster->mslot_item(MSLOT_WEAPON),
- mitm[hand_used]);
-
- // extract launcher bonuses due to magic
- if (projected == LRET_LAUNCHED)
- {
- lnchHitBonus = mitm[weapon].plus;
- lnchDamBonus = mitm[weapon].plus2;
- lnchBaseDam = property(mitm[weapon], PWPN_DAMAGE);
- }
-
- // extract weapon/ammo bonuses due to magic
- ammoHitBonus = item.plus;
- ammoDamBonus = item.plus2;
-
- // Archers get a boost from their melee attack.
- if (mons_class_flag(monster->type, M_ARCHER))
- {
- const mon_attack_def attk = mons_attack_spec(monster, 0);
- if (attk.type == AT_SHOOT)
- ammoDamBonus += random2avg(attk.damage, 2);
- }
-
- if (projected == LRET_THROWN)
- {
- // Darts are easy.
- if (wepClass == OBJ_MISSILES && wepType == MI_DART)
- {
- baseHit = 11;
- hitMult = 40;
- damMult = 25;
- }
- else
- {
- baseHit = 6;
- hitMult = 30;
- damMult = 25;
- }
-
- baseDam = property(item, PWPN_DAMAGE);
-
- if (wepClass == OBJ_MISSILES) // throw missile
- {
- // ammo damage needs adjusting here - OBJ_MISSILES
- // don't get separate tohit/damage bonuses!
- ammoDamBonus = ammoHitBonus;
-
- // [dshaligram] Thrown stones/darts do only half the damage of
- // launched stones/darts. This matches 4.0 behaviour.
- if (wepType == MI_DART || wepType == MI_STONE
- || wepType == MI_SLING_BULLET)
- {
- baseDam = div_rand_round(baseDam, 2);
- }
- }
-
- // give monster "skill" bonuses based on HD
- exHitBonus = (hitMult * monster->hit_dice) / 10 + 1;
- exDamBonus = (damMult * monster->hit_dice) / 10 + 1;
- }
-
- // Monsters no longer gain unfair advantages with weapons of
- // fire/ice and incorrect ammo. They now have the same restrictions
- // as players.
-
- int bow_brand = SPWPN_NORMAL;
- const int ammo_brand = get_ammo_brand(item);
-
- if (projected == LRET_LAUNCHED)
- {
- bow_brand = get_weapon_brand(mitm[monster->inv[MSLOT_WEAPON]]);
-
- switch (lnchType)
- {
- case WPN_BLOWGUN:
- baseHit = 12;
- hitMult = 60;
- damMult = 0;
- lnchDamBonus = 0;
- break;
- case WPN_BOW:
- case WPN_LONGBOW:
- baseHit = 0;
- hitMult = 60;
- damMult = 35;
- // monsters get half the launcher damage bonus,
- // which is about as fair as I can figure it.
- lnchDamBonus = (lnchDamBonus + 1) / 2;
- break;
- case WPN_CROSSBOW:
- baseHit = 4;
- hitMult = 70;
- damMult = 30;
- break;
- case WPN_HAND_CROSSBOW:
- baseHit = 2;
- hitMult = 50;
- damMult = 20;
- break;
- case WPN_SLING:
- baseHit = 10;
- hitMult = 40;
- damMult = 20;
- // monsters get half the launcher damage bonus,
- // which is about as fair as I can figure it.
- lnchDamBonus /= 2;
- break;
- }
-
- // Launcher is now more important than ammo for base damage.
- baseDam = property(item, PWPN_DAMAGE);
- if (lnchBaseDam)
- baseDam = lnchBaseDam + random2(1 + baseDam);
-
- // missiles don't have pluses2; use hit bonus
- ammoDamBonus = ammoHitBonus;
-
- exHitBonus = (hitMult * monster->hit_dice) / 10 + 1;
- exDamBonus = (damMult * monster->hit_dice) / 10 + 1;
-
- if (!baseDam && elemental_missile_beam(bow_brand, ammo_brand))
- baseDam = 4;
-
- // [dshaligram] This is a horrible hack - we force beam.cc to
- // consider this beam "needle-like".
- if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE)
- pbolt.ench_power = AUTOMATIC_HIT;
-
- // elven bow w/ elven arrow, also orcish
- if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]])
- == get_equip_race(mitm[monster->inv[MSLOT_MISSILE]]))
- {
- baseHit++;
- baseDam++;
-
- if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]]) == ISFLAG_ELVEN)
- pbolt.hit++;
- }
-
- // POISON brand launchers poison ammo
- if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_NORMAL)
- set_item_ego_type(item, OBJ_MISSILES, SPMSL_POISONED);
-
- // Vorpal brand increases damage dice size.
- if (bow_brand == SPWPN_VORPAL)
- diceMult = diceMult * 130 / 100;
-
- // As do steel ammo.
- if (ammo_brand == SPMSL_STEEL)
- diceMult = diceMult * 150 / 100;
-
- // Note: we already have throw_energy taken off. -- bwr
- int speed_delta = 0;
- if (lnchType == WPN_CROSSBOW)
- {
- if (bow_brand == SPWPN_SPEED)
- {
- // Speed crossbows take 50% less time to use than
- // ordinary crossbows.
- speed_delta = div_rand_round(throw_energy * 2, 5);
- }
- else
- {
- // Ordinary crossbows take 20% more time to use
- // than ordinary bows.
- speed_delta = -div_rand_round(throw_energy, 5);
- }
- }
- else if (bow_brand == SPWPN_SPEED)
- {
- // Speed bows take 50% less time to use than
- // ordinary bows.
- speed_delta = div_rand_round(throw_energy, 2);
- }
-
- monster->speed_increment += speed_delta;
- }
-
- // Chaos overides flame and frost
- if (pbolt.flavour != BEAM_MISSILE)
- {
- baseHit += 2;
- exDamBonus += 6;
- }
-
- // monster intelligence bonus
- if (mons_intel(monster) == I_HIGH)
- exHitBonus += 10;
-
- // Now, if a monster is, for some reason, throwing something really
- // stupid, it will have baseHit of 0 and damage of 0. Ah well.
- std::string msg = monster->name(DESC_CAP_THE);
- msg += ((projected == LRET_LAUNCHED) ? " shoots " : " throws ");
-
- if (!pbolt.name.empty() && projected == LRET_LAUNCHED)
- msg += article_a(pbolt.name);
- else
- {
- // build shoot message
- msg += item.name(DESC_NOCAP_A);
-
- // build beam name
- pbolt.name = item.name(DESC_PLAIN, false, false, false);
- }
- msg += ".";
-
- if (monster->observable())
- {
- mpr(msg.c_str());
-
- if (projected == LRET_LAUNCHED
- && item_type_known(mitm[monster->inv[MSLOT_WEAPON]])
- || projected == LRET_THROWN
- && mitm[hand_used].base_type == OBJ_MISSILES)
- {
- set_ident_flags(mitm[hand_used], ISFLAG_KNOW_TYPE);
- }
- }
-
- // [dshaligram] When changing bolt names here, you must edit
- // hiscores.cc (scorefile_entry::terse_missile_cause()) to match.
- char throw_buff[ITEMNAME_SIZE];
- if (projected == LRET_LAUNCHED)
- {
- snprintf(throw_buff, sizeof(throw_buff), "Shot with a%s %s by %s",
- (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(),
- monster->name(DESC_NOCAP_A).c_str());
- }
- else
- {
- snprintf(throw_buff, sizeof(throw_buff), "Hit by a%s %s thrown by %s",
- (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(),
- monster->name(DESC_NOCAP_A).c_str());
- }
-
- pbolt.aux_source = throw_buff;
-
- // Add everything up.
- pbolt.hit = baseHit + random2avg(exHitBonus, 2) + ammoHitBonus;
- pbolt.damage =
- dice_def(1, baseDam + random2avg(exDamBonus, 2) + ammoDamBonus);
-
- if (projected == LRET_LAUNCHED)
- {
- pbolt.damage.size += lnchDamBonus;
- pbolt.hit += lnchHitBonus;
- }
- pbolt.damage.size = diceMult * pbolt.damage.size / 100;
-
- if (monster->has_ench(ENCH_BATTLE_FRENZY))
- {
- const mon_enchant ench = monster->get_ench(ENCH_BATTLE_FRENZY);
-
-#ifdef DEBUG_DIAGNOSTICS
- const dice_def orig_damage = pbolt.damage;
-#endif
-
- pbolt.damage.size = pbolt.damage.size * (115 + ench.degree * 15) / 100;
-
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "%s frenzy damage: %dd%d -> %dd%d",
- monster->name(DESC_PLAIN).c_str(),
- orig_damage.num, orig_damage.size,
- pbolt.damage.num, pbolt.damage.size);
-#endif
- }
-
- // Skilled archers get better to-hit and damage.
- if (skilled)
- {
- pbolt.hit = pbolt.hit * 120 / 100;
- pbolt.damage.size = pbolt.damage.size * 120 / 100;
- }
-
- scale_dice(pbolt.damage);
-
- // decrease inventory
- bool really_returns;
- if (returning && !one_chance_in(mons_power(monster->type) + 3))
- really_returns = true;
- else
- really_returns = false;
-
- pbolt.drop_item = !really_returns;
-
- // Redraw the screen before firing, in case the monster just
- // came into view and the screen hasn't been updated yet.
- viewwindow(true, false);
- pbolt.fire();
-
- // The item can be destroyed before returning.
- if (really_returns && thrown_object_destroyed(&item, pbolt.target, true))
- {
- really_returns = false;
- }
-
- if (really_returns)
- {
- // Fire beam in reverse.
- pbolt.setup_retrace();
- viewwindow(true, false);
- pbolt.fire();
- msg::stream << "The weapon returns "
- << (you.can_see(monster)?
- ("to " + monster->name(DESC_NOCAP_THE))
- : "whence it came from")
- << "!" << std::endl;
-
- // Player saw the item return.
- if (!is_artefact(item))
- {
- // Since this only happens for non-artefacts, also mark properties
- // as known.
- set_ident_flags(mitm[hand_used],
- ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES);
- }
- }
- else if (dec_mitm_item_quantity(hand_used, 1))
- monster->inv[returning ? slot : MSLOT_MISSILE] = NON_ITEM;
-
- if (pbolt.special_explosion != NULL)
- delete pbolt.special_explosion;
-
- return (true);
-}
-
-static void _scale_draconian_breath(bolt& beam, int drac_type)
-{
- int scaling = 100;
- switch (drac_type)
- {
- case MONS_RED_DRACONIAN:
- beam.name = "searing blast";
- beam.aux_source = "blast of searing breath";
- scaling = 65;
- break;
-
- case MONS_WHITE_DRACONIAN:
- beam.name = "chilling blast";
- beam.aux_source = "blast of chilling breath";
- beam.short_name = "frost";
- scaling = 65;
- break;
-
- case MONS_PLAYER_GHOST: // draconians only
- beam.name = "blast of negative energy";
- beam.aux_source = "blast of draining breath";
- beam.flavour = BEAM_NEG;
- beam.colour = DARKGREY;
- scaling = 65;
- break;
- }
- beam.damage.size = scaling * beam.damage.size / 100;
-}
-
-static spell_type _draco_type_to_breath(int drac_type)
-{
- switch (drac_type)
- {
- case MONS_BLACK_DRACONIAN: return SPELL_LIGHTNING_BOLT;
- case MONS_MOTTLED_DRACONIAN: return SPELL_STICKY_FLAME_SPLASH;
- case MONS_YELLOW_DRACONIAN: return SPELL_ACID_SPLASH;
- case MONS_GREEN_DRACONIAN: return SPELL_POISONOUS_CLOUD;
- case MONS_PURPLE_DRACONIAN: return SPELL_ISKENDERUNS_MYSTIC_BLAST;
- case MONS_RED_DRACONIAN: return SPELL_FIRE_BREATH;
- case MONS_WHITE_DRACONIAN: return SPELL_COLD_BREATH;
- case MONS_PALE_DRACONIAN: return SPELL_STEAM_BALL;
-
- // Handled later.
- case MONS_PLAYER_GHOST: return SPELL_DRACONIAN_BREATH;
-
- default:
- DEBUGSTR("Invalid monster using draconian breath spell");
- break;
- }
-
- return (SPELL_DRACONIAN_BREATH);
-}
-
-
-bolt mons_spells( monsters *mons, spell_type spell_cast, int power )
-{
- ASSERT(power > 0);
-
- bolt beam;
-
- // Initialise to some bogus values so we can catch problems.
- beam.name = "****";
- beam.colour = 1000;
- beam.hit = -1;
- beam.damage = dice_def( 1, 0 );
- beam.ench_power = -1;
- beam.type = 0;
- beam.flavour = BEAM_NONE;
- beam.thrower = KILL_MISC;
- beam.is_beam = false;
- beam.is_explosion = false;
-
- // Sandblast is different, and gets range updated later
- if (spell_cast != SPELL_SANDBLAST)
- beam.range = spell_range(spell_cast, power, true, false);
-
- const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN)
- ? draco_subspecies(mons) : mons->type;
-
- spell_type real_spell = spell_cast;
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- real_spell = _draco_type_to_breath(drac_type);
-
- beam.type = dchar_glyph(DCHAR_FIRED_ZAP); // default
- beam.thrower = KILL_MON_MISSILE;
-
- // FIXME: this should use the zap_data[] struct from beam.cc!
- switch (real_spell)
- {
- case SPELL_MAGIC_DART:
- beam.colour = LIGHTMAGENTA;
- beam.name = "magic dart";
- beam.damage = dice_def( 3, 4 + (power / 100) );
- beam.hit = AUTOMATIC_HIT;
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_THROW_FLAME:
- beam.colour = RED;
- beam.name = "puff of flame";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 25 + power / 40;
- beam.flavour = BEAM_FIRE;
- break;
-
- case SPELL_THROW_FROST:
- beam.colour = WHITE;
- beam.name = "puff of frost";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 25 + power / 40;
- beam.flavour = BEAM_COLD;
- break;
-
- case SPELL_SANDBLAST:
- beam.colour = BROWN;
- beam.name = "rocky blast";
- beam.damage = dice_def( 3, 5 + (power / 40) );
- beam.hit = 20 + power / 40;
- beam.flavour = BEAM_FRAG;
- beam.range = 2; // spell_range() is wrong here
- break;
-
- case SPELL_DISPEL_UNDEAD:
- beam.flavour = BEAM_DISPEL_UNDEAD;
- beam.damage = dice_def( 3, std::min(6 + power / 10, 40) );
- beam.is_beam = true;
- break;
-
- case SPELL_PARALYSE:
- beam.flavour = BEAM_PARALYSIS;
- beam.is_beam = true;
- break;
-
- case SPELL_SLOW:
- beam.flavour = BEAM_SLOW;
- beam.is_beam = true;
- break;
-
- case SPELL_HASTE: // (self)
- beam.flavour = BEAM_HASTE;
- break;
-
- case SPELL_BACKLIGHT:
- beam.flavour = BEAM_BACKLIGHT;
- beam.is_beam = true;
- break;
-
- case SPELL_CONFUSE:
- beam.flavour = BEAM_CONFUSION;
- beam.is_beam = true;
- break;
-
- case SPELL_SLEEP:
- beam.flavour = BEAM_SLEEP;
- beam.is_beam = true;
- break;
-
- case SPELL_POLYMORPH_OTHER:
- beam.flavour = BEAM_POLYMORPH;
- beam.is_beam = true;
- // Be careful with this one.
- // Having allies mutate you is infuriating.
- beam.foe_ratio = 1000;
- break;
-
- case SPELL_VENOM_BOLT:
- beam.name = "bolt of poison";
- beam.damage = dice_def( 3, 6 + power / 13 );
- beam.colour = LIGHTGREEN;
- beam.flavour = BEAM_POISON;
- beam.hit = 19 + power / 20;
- beam.is_beam = true;
- break;
-
- case SPELL_POISON_ARROW:
- beam.name = "poison arrow";
- beam.damage = dice_def( 3, 7 + power / 12 );
- beam.colour = LIGHTGREEN;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_POISON_ARROW;
- beam.hit = 20 + power / 25;
- break;
-
- case SPELL_BOLT_OF_MAGMA:
- beam.name = "bolt of magma";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = RED;
- beam.flavour = BEAM_LAVA;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_FIRE:
- beam.name = "bolt of fire";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = RED;
- beam.flavour = BEAM_FIRE;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_FLING_ICICLE:
- beam.name = "shard of ice";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_ICE;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_COLD:
- beam.name = "bolt of cold";
- beam.damage = dice_def( 3, 8 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_COLD;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- break;
-
- case SPELL_FREEZING_CLOUD:
- beam.name = "freezing blast";
- beam.damage = dice_def( 2, 9 + power / 11 );
- beam.colour = WHITE;
- beam.flavour = BEAM_COLD;
- beam.hit = 17 + power / 25;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_SHOCK:
- beam.name = "zap";
- beam.damage = dice_def( 1, 8 + (power / 20) );
- beam.colour = LIGHTCYAN;
- beam.flavour = BEAM_ELECTRICITY;
- beam.hit = 17 + power / 20;
- beam.is_beam = true;
- break;
-
- case SPELL_LIGHTNING_BOLT:
- beam.name = "bolt of lightning";
- beam.damage = dice_def( 3, 10 + power / 17 );
- beam.colour = LIGHTCYAN;
- beam.flavour = BEAM_ELECTRICITY;
- beam.hit = 16 + power / 40;
- beam.is_beam = true;
- break;
-
- case SPELL_INVISIBILITY:
- beam.flavour = BEAM_INVISIBILITY;
- break;
-
- case SPELL_FIREBALL:
- beam.colour = RED;
- beam.name = "fireball";
- beam.damage = dice_def( 3, 7 + power / 10 );
- beam.hit = 40;
- beam.flavour = BEAM_FIRE;
- beam.foe_ratio = 60;
- beam.is_explosion = true;
- break;
-
- case SPELL_FIRE_STORM:
- setup_fire_storm(mons, power / 2, beam);
- beam.foe_ratio = random_range(40, 55);
- break;
-
- case SPELL_ICE_STORM:
- beam.name = "great blast of cold";
- beam.colour = BLUE;
- beam.damage = calc_dice( 10, 18 + power / 2 );
- beam.hit = 20 + power / 10; // 50: 25 100: 30
- beam.ench_power = power; // used for radius
- beam.flavour = BEAM_ICE; // half resisted
- beam.is_explosion = true;
- beam.foe_ratio = random_range(40, 55);
- break;
-
- case SPELL_HELLFIRE_BURST:
- beam.aux_source = "burst of hellfire";
- beam.name = "burst of hellfire";
- beam.ex_size = 1;
- beam.flavour = BEAM_HELLFIRE;
- beam.is_explosion = true;
- beam.colour = RED;
- beam.aux_source.clear();
- beam.is_tracer = false;
- beam.hit = 20;
- beam.damage = mons_foe_is_mons(mons) ? dice_def(5, 7)
- : dice_def(3, 20);
- break;
-
- case SPELL_MINOR_HEALING:
- beam.flavour = BEAM_HEALING;
- beam.hit = 25 + (power / 5);
- break;
-
- case SPELL_TELEPORT_SELF:
- beam.flavour = BEAM_TELEPORT;
- break;
-
- case SPELL_TELEPORT_OTHER:
- beam.flavour = BEAM_TELEPORT;
- beam.is_beam = true;
- break;
-
- case SPELL_LEHUDIBS_CRYSTAL_SPEAR: // was splinters
- beam.name = "crystal spear";
- beam.damage = dice_def( 3, 16 + power / 10 );
- beam.colour = WHITE;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE;
- beam.hit = 22 + power / 20;
- break;
-
- case SPELL_DIG:
- beam.flavour = BEAM_DIGGING;
- beam.is_beam = true;
- break;
-
- case SPELL_BOLT_OF_DRAINING: // negative energy
- beam.name = "bolt of negative energy";
- beam.damage = dice_def( 3, 6 + power / 13 );
- beam.colour = DARKGREY;
- beam.flavour = BEAM_NEG;
- beam.hit = 16 + power / 35;
- beam.is_beam = true;
- break;
-
- case SPELL_ISKENDERUNS_MYSTIC_BLAST: // mystic blast
- beam.colour = LIGHTMAGENTA;
- beam.name = "orb of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 7 + (power / 14) );
- beam.hit = 20 + (power / 20);
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_STEAM_BALL:
- beam.colour = LIGHTGREY;
- beam.name = "ball of steam";
- beam.damage = dice_def( 3, 7 + (power / 15) );
- beam.hit = 20 + power / 20;
- beam.flavour = BEAM_STEAM;
- break;
-
- case SPELL_PAIN:
- beam.flavour = BEAM_PAIN;
- beam.damage = dice_def( 1, 7 + (power / 20) );
- beam.ench_power = std::max(50, 8 * mons->hit_dice);
- beam.is_beam = true;
- break;
-
- case SPELL_STICKY_FLAME_SPLASH:
- case SPELL_STICKY_FLAME:
- beam.colour = RED;
- beam.name = "sticky flame";
- beam.damage = dice_def( 3, 3 + power / 50 );
- beam.hit = 18 + power / 15;
- beam.flavour = BEAM_FIRE;
- break;
-
- case SPELL_POISONOUS_CLOUD:
- beam.name = "blast of poison";
- beam.damage = dice_def( 3, 3 + power / 25 );
- beam.colour = LIGHTGREEN;
- beam.flavour = BEAM_POISON;
- beam.hit = 18 + power / 25;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_ENERGY_BOLT: // eye of devastation
- beam.colour = YELLOW;
- beam.name = "bolt of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 20 );
- beam.hit = 15 + power / 30;
- beam.flavour = BEAM_NUKE; // a magical missile which destroys walls
- beam.is_beam = true;
- break;
-
- case SPELL_STING: // sting
- beam.colour = GREEN;
- beam.name = "sting";
- beam.damage = dice_def( 1, 6 + power / 25 );
- beam.hit = 60;
- beam.flavour = BEAM_POISON;
- break;
-
- case SPELL_IRON_SHOT:
- beam.colour = LIGHTCYAN;
- beam.name = "iron shot";
- beam.damage = dice_def( 3, 8 + (power / 9) );
- beam.hit = 20 + (power / 25);
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
- break;
-
- case SPELL_STONE_ARROW:
- beam.colour = LIGHTGREY;
- beam.name = "stone arrow";
- beam.damage = dice_def( 3, 5 + (power / 10) );
- beam.hit = 14 + power / 35;
- beam.type = dchar_glyph(DCHAR_FIRED_MISSILE);
- beam.flavour = BEAM_MMISSILE; // similarly unresisted thing
- break;
-
- case SPELL_POISON_SPLASH:
- beam.colour = GREEN;
- beam.name = "splash of poison";
- beam.damage = dice_def( 1, 4 + power / 10 );
- beam.hit = 16 + power / 20;
- beam.flavour = BEAM_POISON;
- break;
-
- case SPELL_ACID_SPLASH:
- beam.colour = YELLOW;
- beam.name = "splash of acid";
- beam.damage = dice_def( 3, 7 );
- beam.hit = 20 + (3 * mons->hit_dice);
- beam.flavour = BEAM_ACID;
- break;
-
- case SPELL_DISINTEGRATE:
- beam.flavour = BEAM_DISINTEGRATION;
- beam.ench_power = 50;
- beam.damage = dice_def( 1, 30 + (power / 10) );
- beam.is_beam = true;
- break;
-
- case SPELL_MEPHITIC_CLOUD: // swamp drake, player ghost
- beam.name = "foul vapour";
- beam.damage = dice_def(1,0);
- beam.colour = GREEN;
- // Well, it works, even if the name isn't quite intuitive.
- beam.flavour = BEAM_POTION_STINKING_CLOUD;
- beam.hit = 14 + power / 30;
- beam.ench_power = power; // probably meaningless
- beam.is_explosion = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_MIASMA: // death drake
- beam.name = "foul vapour";
- beam.damage = dice_def( 3, 5 + power / 24 );
- beam.colour = DARKGREY;
- beam.flavour = BEAM_MIASMA;
- beam.hit = 17 + power / 20;
- beam.is_beam = true;
- beam.is_big_cloud = true;
- break;
-
- case SPELL_QUICKSILVER_BOLT: // Quicksilver dragon
- beam.colour = random_colour();
- beam.name = "bolt of energy";
- beam.short_name = "energy";
- beam.damage = dice_def( 3, 25 );
- beam.hit = 16 + power / 25;
- beam.flavour = BEAM_MMISSILE;
- break;
-
- case SPELL_HELLFIRE: // fiend's hellfire
- beam.name = "blast of hellfire";
- beam.aux_source = "blast of hellfire";
- beam.colour = RED;
- beam.damage = dice_def( 3, 25 );
- beam.hit = 24;
- beam.flavour = BEAM_HELLFIRE;
- beam.is_beam = true;
- beam.is_explosion = true;
- break;
-
- case SPELL_METAL_SPLINTERS:
- beam.name = "spray of metal splinters";
- beam.short_name = "metal splinters";
- beam.damage = dice_def( 3, 20 + power / 20 );
- beam.colour = CYAN;
- beam.flavour = BEAM_FRAG;
- beam.hit = 19 + power / 30;
- beam.is_beam = true;
- break;
-
- case SPELL_BANISHMENT:
- beam.flavour = BEAM_BANISH;
- beam.is_beam = true;
- break;
-
- case SPELL_BLINK_OTHER:
- beam.flavour = BEAM_BLINK;
- beam.is_beam = true;
- break;
-
- case SPELL_FIRE_BREATH:
- beam.name = "blast of flame";
- beam.aux_source = "blast of fiery breath";
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.colour = RED;
- beam.hit = 30;
- beam.flavour = BEAM_FIRE;
- beam.is_beam = true;
- break;
-
- case SPELL_COLD_BREATH:
- beam.name = "blast of cold";
- beam.aux_source = "blast of icy breath";
- beam.short_name = "frost";
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.colour = WHITE;
- beam.hit = 30;
- beam.flavour = BEAM_COLD;
- beam.is_beam = true;
- break;
-
- case SPELL_DRACONIAN_BREATH:
- beam.damage = dice_def( 3, (mons->hit_dice * 2) );
- beam.hit = 30;
- beam.is_beam = true;
- break;
-
- case SPELL_PORKALATOR:
- beam.name = "porkalator";
- beam.type = 0;
- beam.flavour = BEAM_PORKALATOR;
- beam.thrower = KILL_MON_MISSILE;
- beam.is_beam = true;
- break;
-
- default:
- if (!is_valid_spell(real_spell))
- DEBUGSTR("Invalid spell #%d cast by %s", (int) real_spell,
- mons->name(DESC_PLAIN, true).c_str());
-
- DEBUGSTR("Unknown monster spell '%s' cast by %s",
- spell_title(real_spell),
- mons->name(DESC_PLAIN, true).c_str());
-
- return (beam);
- }
-
- if (beam.is_enchantment())
- {
- beam.type = dchar_glyph(DCHAR_SPACE);
- beam.name = "0";
- }
-
- if (spell_cast == SPELL_DRACONIAN_BREATH)
- _scale_draconian_breath(beam, drac_type);
-
- // Accuracy is lowered by one quarter if the dragon is attacking
- // a target that is wielding a weapon of dragon slaying (which
- // makes the dragon/draconian avoid looking at the foe).
- // FIXME: This effect is not yet implemented for player draconians
- // or characters in dragon form breathing at monsters wielding a
- // weapon with this brand.
- if (is_dragonkind(mons))
- {
- if (actor *foe = mons->get_foe())
- {
- if (const item_def *weapon = foe->weapon())
- {
- if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING)
- {
- beam.hit *= 3;
- beam.hit /= 4;
- }
- }
- }
- }
-
- return (beam);
-}
-
-static int _monster_abjure_square(const coord_def &pos,
- int pow, int actual,
- int wont_attack)
-{
- monsters *target = monster_at(pos);
- if (target == NULL)
- return (0);
-
- if (!target->alive()
- || ((bool)wont_attack == mons_wont_attack_real(target)))
- {
- return (0);
- }
-
- int duration;
-
- if (!target->is_summoned(&duration))
- return (0);
-
- pow = std::max(20, fuzz_value(pow, 40, 25));
-
- if (!actual)
- return (pow > 40 || pow >= duration);
-
- // TSO and Trog's abjuration protection.
- bool shielded = false;
- if (you.religion == GOD_SHINING_ONE)
- {
- pow = pow * (30 - target->hit_dice) / 30;
- if (pow < duration)
- {
- simple_god_message(" protects your fellow warrior from evil "
- "magic!");
- shielded = true;
- }
- }
- else if (you.religion == GOD_TROG)
- {
- pow = pow * 4 / 5;
- if (pow < duration)
- {
- simple_god_message(" shields your ally from puny magic!");
- shielded = true;
- }
- }
- else if (is_sanctuary(target->pos()))
- {
- pow = 0;
- mpr("Zin's power protects your fellow warrior from evil magic!",
- MSGCH_GOD);
- shielded = true;
- }
-
-#ifdef DEBUG_DIAGNOSTICS
- mprf(MSGCH_DIAGNOSTICS, "Abj: dur: %d, pow: %d, ndur: %d",
- duration, pow, duration - pow);
-#endif
-
- mon_enchant abj = target->get_ench(ENCH_ABJ);
- if (!target->lose_ench_duration(abj, pow))
- {
- if (!shielded)
- simple_monster_message(target, " shudders.");
- return (1);
- }
-
- return (0);
-}
-
-static int _apply_radius_around_square( const coord_def &c, int radius,
- int (*fn)(const coord_def &, int, int, int),
- int pow, int par1, int par2)
-{
- int res = 0;
- for (int yi = -radius; yi <= radius; ++yi)
- {
- const coord_def c1(c.x - radius, c.y + yi);
- const coord_def c2(c.x + radius, c.y + yi);
- if (in_bounds(c1))
- res += fn(c1, pow, par1, par2);
- if (in_bounds(c2))
- res += fn(c2, pow, par1, par2);
- }
-
- for (int xi = -radius + 1; xi < radius; ++xi)
- {
- const coord_def c1(c.x + xi, c.y - radius);
- const coord_def c2(c.x + xi, c.y + radius);
- if (in_bounds(c1))
- res += fn(c1, pow, par1, par2);
- if (in_bounds(c2))
- res += fn(c2, pow, par1, par2);
- }
- return (res);
-}
-
-static int _monster_abjuration(const monsters *caster, bool actual)
-{
- const bool wont_attack = mons_wont_attack_real(caster);
- int maffected = 0;
-
- if (actual)
- mpr("Send 'em back where they came from!");
-
- int pow = std::min(caster->hit_dice * 90, 2500);
-
- // Abjure radius.
- for (int rad = 1; rad < 5 && pow >= 30; ++rad)
- {
- int number_hit =
- _apply_radius_around_square(caster->pos(), rad,
- _monster_abjure_square,
- pow, actual, wont_attack);
-
- maffected += number_hit;
-
- // Each affected monster drops power.
- //
- // We could further tune this by the actual amount of abjuration
- // damage done to each summon, but the player will probably never
- // notice. :-)
- while (number_hit-- > 0)
- pow = pow * 90 / 100;
-
- pow /= 2;
- }
- return (maffected);
-}
-
-bool silver_statue_effects(monsters *mons)
-{
- actor *foe = mons->get_foe();
- if (foe && mons->can_see(foe) && !one_chance_in(3))
- {
- const std::string msg =
- "'s eyes glow " + weird_glowing_colour() + '.';
- simple_monster_message(mons, msg.c_str(), MSGCH_WARN);
-
- create_monster(
- mgen_data(
- summon_any_demon((coinflip() ? DEMON_COMMON
- : DEMON_LESSER)),
- SAME_ATTITUDE(mons), 5, 0, foe->pos(), mons->foe));
- return (true);
- }
- return (false);
-}
-
-bool orange_statue_effects(monsters *mons)
-{
- actor *foe = mons->get_foe();
- if (foe && mons->can_see(foe) && !one_chance_in(3))
- {
- if (you.can_see(foe))
- {
- if (foe == &you)
- mprf(MSGCH_WARN, "A hostile presence attacks your mind!");
- else if (you.can_see(mons))
- mprf(MSGCH_WARN, "%s fixes %s piercing gaze on %s.",
- mons->name(DESC_CAP_THE).c_str(),
- mons->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
- foe->name(DESC_NOCAP_THE).c_str());
- }
-
- MiscastEffect(foe, monster_index(mons), SPTYP_DIVINATION,
- random2(15), random2(150),
- "an orange crystal statue");
- return (true);
- }
-
- return (false);
-}
-
-bool ugly_thing_mutate(monsters *ugly, bool proximity)
-{
- bool success = false;
-
- std::string src = "";
-
- unsigned char mon_colour = BLACK;
-
- if (!proximity)
- success = true;
- else if (one_chance_in(8))
- {
- int you_mutate_chance = 0;
- int mon_mutate_chance = 0;
-
- for (adjacent_iterator ri(ugly->pos()); ri; ++ri)
- {
- if (you.pos() == *ri)
- you_mutate_chance = get_contamination_level();
- else
- {
- monsters *ugly_near = monster_at(*ri);
-
- if (!ugly_near
- || ugly_near->type != MONS_UGLY_THING
- && ugly_near->type != MONS_VERY_UGLY_THING)
- {
- continue;
- }
-
- for (int i = 0; i < 2; ++i)
- {
- if (coinflip())
- {
- mon_mutate_chance++;
-
- if (coinflip())
- {
- const int ugly_colour =
- make_low_colour(ugly->colour);
- const int ugly_near_colour =
- make_low_colour(ugly_near->colour);
-
- if (ugly_colour != ugly_near_colour)
- mon_colour = ugly_near_colour;
- }
- }
-
- if (ugly_near->type != MONS_VERY_UGLY_THING)
- break;
- }
- }
- }
-
- you_mutate_chance = std::min(16, you_mutate_chance);
- mon_mutate_chance = std::min(16, mon_mutate_chance);
-
- if (!one_chance_in(you_mutate_chance + mon_mutate_chance + 1))
- {
- const bool proximity_you =
- (you_mutate_chance > mon_mutate_chance) ? true :
- (you_mutate_chance == mon_mutate_chance) ? coinflip()
- : false;
-
- src = proximity_you ? " from you" : " from its kin";
-
- success = true;
- }
- }
-
- if (success)
- {
- simple_monster_message(ugly,
- make_stringf(" basks in the mutagenic energy%s and changes!",
- src.c_str()).c_str());
-
- ugly->uglything_mutate(mon_colour);
-
- return (true);
- }
-
- return (false);
-}
-
-// Inflict any enchantments the parent slime has on its offspring,
-// leaving durations unchanged, I guess. -cao
-static void _split_ench_durations(monsters *initial_slime, monsters *split_off)
-{
- mon_enchant_list::iterator i;
-
- for (i = initial_slime->enchantments.begin();
- i != initial_slime->enchantments.end(); ++i)
- {
- split_off->add_ench(i->second);
- }
-
-}
-
-// What to do about any enchantments these two slimes may have? For
-// now, we are averaging the durations. -cao
-static void _merge_ench_durations(monsters *initial_slime, monsters *merge_to)
-{
- mon_enchant_list::iterator i;
-
- int initial_count = initial_slime->number;
- int merge_to_count = merge_to->number;
- int total_count = initial_count + merge_to_count;
-
- for (i = initial_slime->enchantments.begin();
- i != initial_slime->enchantments.end(); ++i)
- {
- // Does the other slime have this enchantment as well?
- mon_enchant temp = merge_to->get_ench(i->first);
- // If not, use duration 0 for their part of the average.
- int duration = temp.ench == ENCH_NONE ? 0 : temp.duration;
-
- i->second.duration = (i->second.duration * initial_count
- + duration * merge_to_count)/total_count;
-
- if (!i->second.duration)
- i->second.duration = 1;
-
- merge_to->add_ench(i->second);
- }
-
- for (i = merge_to->enchantments.begin();
- i != merge_to->enchantments.end(); ++i)
- {
- if (initial_slime->enchantments.find(i->first)
- != initial_slime->enchantments.end()
- && i->second.duration > 1)
- {
- i->second.duration = (merge_to_count * i->second.duration)
- / total_count;
-
- merge_to->update_ench(i->second);
- }
- }
-}
-
-// Calculate slime creature hp based on how many are merged.
-static void _stats_from_blob_count(monsters *slime, float hp_per_blob)
-{
- slime->max_hit_points = (int)(slime->number * hp_per_blob);
- slime->hit_points = slime->max_hit_points;
-}
-
-// Create a new slime creature at 'target', and split 'thing''s hp and
-// merge count with the new monster.
-static bool _do_split(monsters *thing, coord_def & target)
-{
- // Create a new slime.
- int slime_idx = create_monster(mgen_data(MONS_SLIME_CREATURE,
- thing->behaviour,
- 0,
- 0,
- target,
- thing->foe,
- MG_FORCE_PLACE));
-
- if (slime_idx == -1)
- return (false);
-
- monsters *new_slime = &env.mons[slime_idx];
-
- // Inflict the new slime with any enchantments on the parent.
- _split_ench_durations(thing, new_slime);
- new_slime->attitude = thing->attitude;
- new_slime->flags = thing->flags;
-
- if (!new_slime)
- return (false);
-
- if (you.can_see(thing))
- mprf("%s splits.", thing->name(DESC_CAP_A).c_str());
-
- int split_off = thing->number / 2;
- float hp_per_blob = thing->max_hit_points / float(thing->number);
-
- thing->number -= split_off;
- new_slime->number = split_off;
-
- new_slime->hit_dice = thing->hit_dice;
-
- _stats_from_blob_count(thing, hp_per_blob);
- _stats_from_blob_count(new_slime, hp_per_blob);
-
- return (true);
-}
-
-// Actually merge two slime creature, pooling their hp, etc.
-// initial_slime is the one that gets killed off by this process.
-static bool _do_merge(monsters *initial_slime, monsters *merge_to)
-{
- // Combine enchantment durations.
- _merge_ench_durations(initial_slime, merge_to);
-
- merge_to->number += initial_slime->number;
- merge_to->max_hit_points += initial_slime->max_hit_points;
- merge_to->hit_points += initial_slime->max_hit_points;
-
- // Merge monster flags (mostly so that MF_CREATED_NEUTRAL, etc. are
- // passed on if the merged slime subsequently splits. Hopefully
- // this won't do anything weird.
- merge_to->flags |= initial_slime->flags;
-
- // Merging costs the combined slime some energy.
- monsterentry* entry = get_monster_data(merge_to->type);
- merge_to->speed_increment -= entry->energy_usage.move;
-
- // This is dumb. With that said, the idea is that if 2 slimes merge
- // you can gain a space by moving away the turn after (maybe this is
- // too nice but there will probably be a lot of complaints about the
- // damage on higher level slimes). So we subtracted some energy
- // above, but if merge_to hasn't moved yet this turn, that will just
- // cancel its turn in this round of world_reacts(). So we are going
- // to see if merge_to has gone already by checking its mindex (this
- // works because handle_monsters just iterates over env.mons in
- // ascending order).
- if (initial_slime->mindex() < merge_to->mindex())
- merge_to->speed_increment -= entry->energy_usage.move;
-
- // Overwrite the state of the slime getting merged into, because it
- // might have been resting or something.
- merge_to->behaviour = initial_slime->behaviour;
- merge_to->foe = initial_slime->foe;
-
- behaviour_event(merge_to, ME_EVAL);
-
- // Messaging.
- if (you.can_see(merge_to))
- {
- if (you.can_see(initial_slime))
- {
- mprf("Two slime creatures merge to form %s.",
- merge_to->name(DESC_NOCAP_A).c_str());
- }
- else
- {
- mprf("A slime creature suddenly becomes %s.",
- merge_to->name(DESC_NOCAP_A).c_str());
- }
-
- you.flash_colour = LIGHTGREEN;
- viewwindow(true, false);
-
- int flash_delay = 150;
- // Scale delay to match change in arena_delay.
- if (crawl_state.arena)
- {
- flash_delay *= Options.arena_delay;
- flash_delay /= 600;
- }
-
- delay(flash_delay);
- }
- else if (you.can_see(initial_slime))
- mpr("A slime creature suddenly disappears!");
-
- // Have to 'kill' the slime doing the merging.
- monster_die(initial_slime, KILL_MISC, NON_MONSTER, true);
-
- return (true);
-}
-
-// Slime creatures can split but not merge under these conditions.
-static bool _unoccupied_slime(monsters *thing)
-{
- return (thing->asleep()
- || mons_is_wandering(thing)
- || thing->foe == MHITNOT);
-}
-
-// Slime creatures cannot split or merge under these conditions.
-static bool _disabled_slime(monsters *thing)
-{
- return (!thing
- || mons_is_fleeing(thing)
- || mons_is_confused(thing)
- || mons_is_paralysed(thing));
-}
-
-// See if there are any appropriate adjacent slime creatures for 'thing'
-// to merge with. If so, carry out the merge.
-static bool _slime_merge(monsters *thing)
-{
- if (!thing || _disabled_slime(thing) || _unoccupied_slime(thing))
- return (false);
-
- int max_slime_merge = 5;
- int compass_idx[8] = {0, 1, 2, 3, 4, 5, 6, 7};
- std::random_shuffle(compass_idx, compass_idx + 8);
- coord_def origin = thing->pos();
-
- // Check for adjacent slime creatures.
- for (int i = 0; i < 8; ++i)
- {
- coord_def target = origin + Compass[compass_idx[i]];
- monsters *other_thing = monster_at(target);
-
- // We found an adjacent monster. Is it another slime creature
- // we can consider merging with?
- if (other_thing
- && other_thing->mons_species() == MONS_SLIME_CREATURE
- && other_thing->attitude == thing->attitude
- && other_thing->is_summoned() == thing->is_summoned()
- && !mons_is_shapeshifter(other_thing)
- && !_disabled_slime(other_thing))
- {
- // We can actually merge if doing so won't take us over the
- // merge cap and the 'movement' would bring us closer to our
- // target.
- int new_blob_count = other_thing->number + thing->number;
- if (new_blob_count <= max_slime_merge
- && grid_distance(thing->target, thing->pos()) >
- grid_distance(thing->target, target))
- {
- return (_do_merge(thing, other_thing));
- }
- }
- }
-
- // No adjacent slime creatures we could merge with.
- return (false);
-}
-
-// See if slime creature 'thing' can split, and carry out the split if
-// we can find a square to place the new slime creature on.
-static bool _slime_split(monsters *thing)
-{
- if (!thing
- || !_unoccupied_slime(thing)
- || _disabled_slime(thing)
- || thing->number <= 1)
- {
- return (false);
- }
-
- int compass_idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
- std::random_shuffle(compass_idx, compass_idx + 8);
- coord_def origin = thing->pos();
-
- // Anywhere we can place an offspring?
- for (int i = 0; i < 8; ++i)
- {
- coord_def target = origin + Compass[compass_idx[i]];
-
- if (mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target))
- && !actor_at(target))
- {
- // This can fail if placing a new monster fails. That
- // probably means we have too many monsters on the level,
- // so just return in that case.
- return (_do_split(thing, target));
- }
- }
-
- // No free squares.
- return (false);
-}
-
-// See if a given slime creature can split or merge.
-bool slime_split_merge(monsters *thing)
-{
- // No merging/splitting shapeshifters.
- if (!thing
- || mons_is_shapeshifter(thing)
- || thing->mons_species() != MONS_SLIME_CREATURE)
- {
- return (false);
- }
-
- if (_slime_split(thing))
- return (true);
-
- return (_slime_merge(thing));
-}
-
-bool orc_battle_cry(monsters *chief)
-{
- const actor *foe = chief->get_foe();
- int affected = 0;
-
- if (foe
- && (foe != &you || !mons_friendly(chief))
- && !silenced(chief->pos())
- && chief->can_see(foe)
- && coinflip())
- {
- const int boss_index = monster_index(chief);
- const int level = chief->hit_dice > 12? 2 : 1;
- std::vector<monsters*> seen_affected;
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- monsters *mon = &menv[i];
- if (mon != chief
- && mon->alive()
- && mons_species(mon->type) == MONS_ORC
- && mons_aligned(boss_index, i)
- && mon->hit_dice < chief->hit_dice
- && !mon->has_ench(ENCH_BERSERK)
- && !mon->has_ench(ENCH_MIGHT)
- && !mon->cannot_move()
- && !mon->confused()
- && chief->can_see(mon))
- {
- mon_enchant ench = mon->get_ench(ENCH_BATTLE_FRENZY);
- if (ench.ench == ENCH_NONE || ench.degree < level)
- {
- const int dur =
- random_range(12, 20) * speed_to_duration(mon->speed);
-
- if (ench.ench != ENCH_NONE)
- {
- ench.degree = level;
- ench.duration = std::max(ench.duration, dur);
- mon->update_ench(ench);
- }
- else
- {
- mon->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level,
- KC_OTHER, dur));
- }
-
- affected++;
- if (you.can_see(mon))
- seen_affected.push_back(mon);
-
- if (mon->asleep())
- behaviour_event(mon, ME_DISTURB, MHITNOT, chief->pos());
- }
- }
- }
-
- if (affected)
- {
- if (you.can_see(chief) && player_can_hear(chief->pos()))
- {
- mprf(MSGCH_SOUND, "%s roars a battle-cry!",
- chief->name(DESC_CAP_THE).c_str());
- }
-
- // The yell happens whether you happen to see it or not.
- noisy(15, chief->pos(), chief->mindex());
-
- // Disabling detailed frenzy announcement because it's so spammy.
- const msg_channel_type channel =
- mons_friendly_real(chief) ? MSGCH_MONSTER_ENCHANT
- : MSGCH_FRIEND_ENCHANT;
-
- if (!seen_affected.empty())
- {
- std::string who;
- if (seen_affected.size() == 1)
- {
- who = seen_affected[0]->name(DESC_CAP_THE);
- mprf(channel, "%s goes into a battle-frenzy!", who.c_str());
- }
- else
- {
- int type = seen_affected[0]->type;
- for (unsigned int i = 0; i < seen_affected.size(); i++)
- {
- if (seen_affected[i]->type != type)
- {
- // just mention plain orcs
- type = MONS_ORC;
- break;
- }
- }
- who = get_monster_data(type)->name;
-
- mprf(channel, "%s %s go into a battle-frenzy!",
- mons_friendly(chief) ? "Your" : "The",
- pluralise(who).c_str());
- }
- }
- }
- }
- // Orc battle cry doesn't cost the monster an action.
- return (false);
-}
-
-static bool _make_monster_angry(const monsters *mon, monsters *targ)
-{
- if (mons_friendly_real(mon) != mons_friendly_real(targ))
- return (false);
-
- // targ is guaranteed to have a foe (needs_berserk checks this).
- // Now targ needs to be closer to *its* foe than mon is (otherwise
- // mon might be in the way).
-
- coord_def victim;
- if (targ->foe == MHITYOU)
- victim = you.pos();
- else if (targ->foe != MHITNOT)
- {
- const monsters *vmons = &menv[targ->foe];
- if (!vmons->alive())
- return (false);
- victim = vmons->pos();
- }
- else
- {
- // Should be impossible. needs_berserk should find this case.
- ASSERT(false);
- return (false);
- }
-
- // If mon may be blocking targ from its victim, don't try.
- if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos()))
- return (false);
-
- if (you.can_see(mon))
- {
- mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(),
- targ->name(DESC_NOCAP_THE).c_str());
- }
-
- targ->go_berserk(false);
-
- return (true);
-}
-
-bool moth_incite_monsters(const monsters *mon)
-{
- if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
- return false;
-
- int goaded = 0;
- for (int i = 0; i < MAX_MONSTERS; ++i)
- {
- monsters *targ = &menv[i];
- if (targ == mon || !targ->alive() || !targ->needs_berserk())
- continue;
-
- if (mon->pos().distance_from(targ->pos()) > 3)
- continue;
-
- if (is_sanctuary(targ->pos()))
- continue;
-
- // Cannot goad other moths of wrath!
- if (targ->type == MONS_MOTH_OF_WRATH)
- continue;
-
- if (_make_monster_angry(mon, targ) && !one_chance_in(3 * ++goaded))
- return (true);
- }
-
- return (false);
-}
-
-void mons_clear_trapping_net(monsters *mon)
-{
- if (!mons_is_caught(mon))
- return;
-
- const int net = get_trapping_net(mon->pos());
- if (net != NON_ITEM)
- remove_item_stationary(mitm[net]);
-
- mon->del_ench(ENCH_HELD, true);
-}
-
-bool mons_clonable(const monsters* mon, bool needs_adjacent)
-{
- // No uniques or ghost demon monsters. Also, figuring out the name
- // for the clone of a named monster isn't worth it.
- if (mons_is_unique(mon->type)
- || mons_is_ghost_demon(mon->type)
- || mon->is_named())
- {
- return (false);
- }
-
- if (needs_adjacent)
- {
- // Is there space for the clone?
- bool square_found = false;
- for (int i = 0; i < 8; i++)
- {
- const coord_def p = mon->pos() + Compass[i];
-
- if (in_bounds(p)
- && !actor_at(p)
- && monster_habitable_grid(mon, grd(p)))
- {
- square_found = true;
- break;
- }
- }
- if (!square_found)
- return (false);
- }
-
- // Is the monster carrying an artefact?
- for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
- {
- const int index = mon->inv[i];
-
- if (index == NON_ITEM)
- continue;
-
- if (is_artefact(mitm[index]))
- return (false);
- }
-
- return (true);
-}
-
-int clone_mons(const monsters* orig, bool quiet, bool* obvious,
- coord_def pos)
-{
- // Is there an open slot in menv?
- int midx = NON_MONSTER;
- for (int i = 0; i < MAX_MONSTERS; i++)
- if (menv[i].type == MONS_NO_MONSTER)
- {
- midx = i;
- break;
- }
-
- if (midx == NON_MONSTER)
- return (NON_MONSTER);
-
- if (!in_bounds(pos))
- {
- // Find an adjacent square.
- int squares = 0;
- for (int i = 0; i < 8; i++)
- {
- const coord_def p = orig->pos() + Compass[i];
-
- if (in_bounds(p)
- && !actor_at(p)
- && monster_habitable_grid(orig, grd(p)))
- {
- if (one_chance_in(++squares))
- pos = p;
- }
- }
-
- if (squares == 0)
- return (NON_MONSTER);
- }
-
- ASSERT( !actor_at(pos) );
-
- monsters &mon(menv[midx]);
-
- mon = *orig;
- mon.position = pos;
- mgrd(pos) = midx;
-
- // Duplicate objects, or unequip them if they can't be duplicated.
- for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
- {
- const int old_index = orig->inv[i];
-
- if (old_index == NON_ITEM)
- continue;
-
- const int new_index = get_item_slot(0);
- if (new_index == NON_ITEM)
- {
- mon.unequip(mitm[old_index], i, 0, true);
- mon.inv[i] = NON_ITEM;
- continue;
- }
-
- mon.inv[i] = new_index;
- mitm[new_index] = mitm[old_index];
- mitm[new_index].set_holding_monster(midx);
- }
-
- bool _obvious;
- if (obvious == NULL)
- obvious = &_obvious;
- *obvious = false;
-
- if (you.can_see(orig) && you.can_see(&mon))
- {
- if (!quiet)
- simple_monster_message(orig, " is duplicated!");
- *obvious = true;
- }
-
- mark_interesting_monst(&mon, mon.behaviour);
- if (you.can_see(&mon))
- {
- handle_seen_interrupt(&mon);
- viewwindow(true, false);
- }
-
- if (crawl_state.arena)
- arena_placed_monster(&mon);
-
- return (midx);
-}
-
-std::string summoned_poof_msg(const monsters* monster, bool plural)
-{
- int summon_type = 0;
- bool valid_mon = false;
- if (monster != NULL && !invalid_monster(monster))
- {
- (void) monster->is_summoned(NULL, &summon_type);
- valid_mon = true;
- }
-
- std::string msg = "disappear%s in a puff of smoke";
- bool no_chaos = false;
-
- switch (summon_type)
- {
- case SPELL_SHADOW_CREATURES:
- msg = "dissolve%s into shadows";
- no_chaos = true;
- break;
-
- case MON_SUMM_CHAOS:
- msg = "degenerate%s into a cloud of primal chaos";
- break;
-
- case MON_SUMM_WRATH:
- case MON_SUMM_AID:
- if (valid_mon && is_good_god(monster->god))
- {
- msg = "dissolve%s into sparkling lights";
- no_chaos = true;
- }
- break;
- }
-
- if (valid_mon)
- {
- if (monster->god == GOD_XOM && !no_chaos && one_chance_in(10)
- || monster->type == MONS_CHAOS_SPAWN)
- {
- msg = "degenerate%s into a cloud of primal chaos";
- }
-
- if (mons_is_holy(monster) && summon_type != SPELL_SHADOW_CREATURES
- && summon_type != MON_SUMM_CHAOS)
- {
- msg = "dissolve%s into sparkling lights";
- }
- }
-
- // Conjugate.
- msg = make_stringf(msg.c_str(), plural ? "" : "s");
-
- return (msg);
-}
-
-std::string summoned_poof_msg(const int midx, const item_def &item)
-{
- if (midx == NON_MONSTER)
- return summoned_poof_msg(static_cast<const monsters*>(NULL), item);
- else
- return summoned_poof_msg(&menv[midx], item);
-}
-
-std::string summoned_poof_msg(const monsters* monster, const item_def &item)
-{
- ASSERT(item.flags & ISFLAG_SUMMONED);
-
- return summoned_poof_msg(monster, item.quantity > 1);
-}