#include "AppHdr.h"
#include "mstuff2.h"
#include <string>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include "externs.h"
#include "arena.h"
#include "artefact.h"
#include "beam.h"
#include "cloud.h"
#include "colour.h"
#include "database.h"
#include "debug.h"
#include "delay.h"
#include "effects.h"
#include "item_use.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "kills.h"
#include "los.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monspeak.h"
#include "monstuff.h"
#include "mon-util.h"
#include "player.h"
#include "religion.h"
#include "spells1.h"
#include "spells3.h"
#include "spl-cast.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#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);
}
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)
{
setup_mons_cast(monster, pbolt, spell_cast);
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;
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)));
ASSERT(!(flags & SPFLAG_TARGETTING_MASK) || in_bounds(pbolt.target));
#endif
if (do_noise)
mons_cast_noise(monster, pbolt, spell_cast);
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: 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)); if (sumcount2 == 0)
return;
for (sumcount = 0; sumcount < MAX_MONSTERS; ++sumcount)
if (menv[sumcount].type == MONS_KRAKEN_TENTACLE
&& (int)menv[sumcount].number == kraken_index)
{
sumcount2--;
}
for (sumcount = sumcount2; sumcount > 0; --sumcount)
{
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: 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:
animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster),
monster->foe, god);
return;
case SPELL_CALL_IMP: 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: 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: 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: _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;
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;
case SPELL_CANTRIP:
{
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);
slugform = getSpeakString("gastronok_debuff");
if (!slugform.empty()
&& (slugform.find("legs") == std::string::npos
|| _legs_msg_applicable()))
{
mpr(slugform.c_str());
}
}
else
{
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
{
const char* buff_msgs[] = { " glows brightly for a moment.",
" looks stronger.",
" becomes somewhat translucent.",
"'s eyes start to glow." };
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 {
mons_cast_noise(monster, pbolt, spell_cast);
mpr(RANDOM_ELEMENT(other_msgs));
}
}
return;
}
case SPELL_BLINK_OTHER:
{
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)
{
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; return;
}
}
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:
force_silent = true;
break;
default:
break;
}
}
else if (monster->type == MONS_SHADOW_DRAGON)
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;
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();
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);
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 (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);
}
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 (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;
}
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)
{
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);
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);
}
}
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())
{
if (!mons_wont_attack(monster))
{
targ_prep = "at";
target = "you";
break;
}
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;
}
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;
}
}
}
} }
const actor* foe = monster->get_foe();
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 (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)
{
mons_speaks_msg(monster, msg, chan);
}
}
void setup_mons_cast(monsters *monster, bolt &pbolt,
spell_type spell_cast)
{
pbolt.ench_power = 4 * monster->hit_dice;
if (spell_cast == SPELL_TELEPORT_SELF)
pbolt.ench_power = 2000;
pbolt.beam_source = monster_index(monster);
if (pbolt.target.origin())
pbolt.target = monster->target;
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:
break;
}
}
switch (spell_cast)
{
case SPELL_SUMMON_SMALL_MAMMALS:
case SPELL_MAJOR_HEALING:
case SPELL_VAMPIRE_SUMMON:
case SPELL_SHADOW_CREATURES: 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: case SPELL_SUMMON_UNDEAD: 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;
}
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);
}
}
}
bool monster_random_space(const monsters *monster, coord_def& target,
bool forbid_sanctuary)
{
int tries = 0;
while (tries++ < 1000)
{
target = random_in_bounds();
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();
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);
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 (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
{
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);
place_cloud(CLOUD_PURP_SMOKE, oldplace, 1 + random2(3),
monster->kill_alignment());
monster->check_redraw(oldplace);
monster->apply_location_effects(oldplace);
mons_relocated(monster);
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)
{
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; int ammoHitBonus = 0, ammoDamBonus = 0; int lnchHitBonus = 0, lnchDamBonus = 0; int exHitBonus = 0, exDamBonus = 0; int lnchBaseDam = 0;
int hitMult = 0;
int damMult = 0;
int diceMult = 100;
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);
item_def item = mitm[hand_used];
item.quantity = 1;
if (mons_friendly(monster))
item.flags |= ISFLAG_DROPPED_BY_ALLY;
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]);
if (projected == LRET_LAUNCHED)
{
lnchHitBonus = mitm[weapon].plus;
lnchDamBonus = mitm[weapon].plus2;
lnchBaseDam = property(mitm[weapon], PWPN_DAMAGE);
}
ammoHitBonus = item.plus;
ammoDamBonus = item.plus2;
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)
{
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) {
ammoDamBonus = ammoHitBonus;
if (wepType == MI_DART || wepType == MI_STONE
|| wepType == MI_SLING_BULLET)
{
baseDam = div_rand_round(baseDam, 2);
}
}
exHitBonus = (hitMult * monster->hit_dice) / 10 + 1;
exDamBonus = (damMult * monster->hit_dice) / 10 + 1;
}
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;
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;
lnchDamBonus /= 2;
break;
}
baseDam = property(item, PWPN_DAMAGE);
if (lnchBaseDam)
baseDam = lnchBaseDam + random2(1 + baseDam);
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;
if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE)
pbolt.ench_power = AUTOMATIC_HIT;
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++;
}
if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_NORMAL)
set_item_ego_type(item, OBJ_MISSILES, SPMSL_POISONED);
if (bow_brand == SPWPN_VORPAL)
diceMult = diceMult * 130 / 100;
if (ammo_brand == SPMSL_STEEL)
diceMult = diceMult * 150 / 100;
int speed_delta = 0;
if (lnchType == WPN_CROSSBOW)
{
if (bow_brand == SPWPN_SPEED)
{
speed_delta = div_rand_round(throw_energy * 2, 5);
}
else
{
speed_delta = -div_rand_round(throw_energy, 5);
}
}
else if (bow_brand == SPWPN_SPEED)
{
speed_delta = div_rand_round(throw_energy, 2);
}
monster->speed_increment += speed_delta;
}
if (pbolt.flavour != BEAM_MISSILE)
{
baseHit += 2;
exDamBonus += 6;
}
if (mons_intel(monster) == I_HIGH)
exHitBonus += 10;
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
{
msg += item.name(DESC_NOCAP_A);
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);
}
}
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;
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
}
if (skilled)
{
pbolt.hit = pbolt.hit * 120 / 100;
pbolt.damage.size = pbolt.damage.size * 120 / 100;
}
scale_dice(pbolt.damage);
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;
viewwindow(true, false);
pbolt.fire();
if (really_returns && thrown_object_destroyed(&item, pbolt.target, true))
{
really_returns = false;
}
if (really_returns)
{
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;
if (!is_artefact(item))
{
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: 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;
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;
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;
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); beam.thrower = KILL_MON_MISSILE;
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; 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: 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;
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; beam.ench_power = power; beam.flavour = BEAM_ICE; 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: 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: 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: 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: 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; beam.is_beam = true;
break;
case SPELL_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; 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; 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: beam.name = "foul vapour";
beam.damage = dice_def(1,0);
beam.colour = GREEN;
beam.flavour = BEAM_POTION_STINKING_CLOUD;
beam.hit = 14 + power / 30;
beam.ench_power = power; beam.is_explosion = true;
beam.is_big_cloud = true;
break;
case SPELL_MIASMA: 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: 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: 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);
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);
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);
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;
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);
}
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);
}
}
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)
{
mon_enchant temp = merge_to->get_ench(i->first);
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);
}
}
}
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;
}
static bool _do_split(monsters *thing, coord_def & target)
{
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];
_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);
}
static bool _do_merge(monsters *initial_slime, monsters *merge_to)
{
_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_to->flags |= initial_slime->flags;
monsterentry* entry = get_monster_data(merge_to->type);
merge_to->speed_increment -= entry->energy_usage.move;
if (initial_slime->mindex() < merge_to->mindex())
merge_to->speed_increment -= entry->energy_usage.move;
merge_to->behaviour = initial_slime->behaviour;
merge_to->foe = initial_slime->foe;
behaviour_event(merge_to, ME_EVAL);
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;
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!");
monster_die(initial_slime, KILL_MISC, NON_MONSTER, true);
return (true);
}
static bool _unoccupied_slime(monsters *thing)
{
return (thing->asleep()
|| mons_is_wandering(thing)
|| thing->foe == MHITNOT);
}
static bool _disabled_slime(monsters *thing)
{
return (!thing
|| mons_is_fleeing(thing)
|| mons_is_confused(thing)
|| mons_is_paralysed(thing));
}
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();
for (int i = 0; i < 8; ++i)
{
coord_def target = origin + Compass[compass_idx[i]];
monsters *other_thing = monster_at(target);
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))
{
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));
}
}
}
return (false);
}
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();
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))
{
return (_do_split(thing, target));
}
}
return (false);
}
bool slime_split_merge(monsters *thing)
{
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());
}
noisy(15, chief->pos(), chief->mindex());
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)
{
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());
}
}
}
}
return (false);
}
static bool _make_monster_angry(const monsters *mon, monsters *targ)
{
if (mons_friendly_real(mon) != mons_friendly_real(targ))
return (false);
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
{
ASSERT(false);
return (false);
}
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;
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)
{
if (mons_is_unique(mon->type)
|| mons_is_ghost_demon(mon->type)
|| mon->is_named())
{
return (false);
}
if (needs_adjacent)
{
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);
}
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)
{
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))
{
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;
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";
}
}
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);
}