/*
* File: mon-abil.cc
* Summary: Monster abilities.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "mon-abil.h"
#include "externs.h"
#include "arena.h"
#include "beam.h"
#include "colour.h"
#include "coordit.h"
#include "directn.h"
#include "fprop.h"
#include "ghost.h"
#include "misc.h"
#include "mon-act.h"
#include "mon-behv.h"
#include "mon-cast.h"
#include "mon-iter.h"
#include "mon-place.h"
#include "terrain.h"
#include "mgen_data.h"
#include "coord.h"
#include "mon-speak.h"
#include "mon-stuff.h"
#include "random.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "env.h"
#include "areas.h"
#include "view.h"
#include "shout.h"
#include "viewchar.h"
#include <algorithm>
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,
0,
target,
thing->foe,
MG_FORCE_PLACE));
if (slime_idx == -1)
return (false);
monsters *new_slime = &env.mons[slime_idx];
if (!new_slime)
return (false);
// 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;
new_slime->props = thing->props;
// XXX copy summoner info
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);
if (crawl_state.arena)
arena_split_monster(thing, new_slime);
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);
// We want to find out if merge_to will move next time it has a turn
// (assuming for the sake of argument the next delay is 10). The
// purpose of subtracting energy from merge_to is to make it lose a
// turn after the merge takes place, if it's already going to lose
// a turn we don't need to do anything.
merge_to->speed_increment += entry->speed;
bool can_move = merge_to->has_action_energy();
merge_to->speed_increment -= entry->speed;
if(can_move)
{
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());
}
flash_view_delay(LIGHTGREEN, 150);
}
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)
|| thing->paralysed());
}
// See if there are any appropriate adjacent slime creatures for 'thing'
// to merge with. If so, carry out the merge.
//
// A slime creature will merge if there is an adjacent slime, merging
// onto that slime would reduce the distance to the original slime's
// target, and there are no empty squares that would also reduce the
// distance to the target.
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();
int target_distance = grid_distance(thing->target, thing->pos());
monsters * merge_target = NULL;
// Check for adjacent slime creatures.
for (int i = 0; i < 8; ++i)
{
coord_def target = origin + Compass[compass_idx[i]];
// If this square won't reduce the distance to our target, don't
// look for a potential merge, and don't allow this square to
// prevent a merge if empty.
if (grid_distance(thing->target, target) >= target_distance)
continue;
// Don't merge if there is an open square that reduces distance
// to target, even if we found a possible slime to merge with.
if (!actor_at(target)
&& mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target)))
{
return false;
}
// Is there a slime creature on this square we can consider
// merging with?
monsters *other_thing = monster_at(target);
if (!merge_target
&& other_thing
&& other_thing->type == MONS_SLIME_CREATURE
&& other_thing->attitude == thing->attitude
&& other_thing->is_summoned() == thing->is_summoned()
&& !other_thing->is_shapeshifter()
&& !_disabled_slime(other_thing))
{
// We can potentially merge if doing so won't take us over
// the merge cap.
int new_blob_count = other_thing->number + thing->number;
if (new_blob_count <= max_slime_merge)
merge_target = other_thing;
}
}
// We found a merge target and didn't find an open square that
// would reduce distance to target, so we can actually merge.
if (merge_target)
return (_do_merge(thing, merge_target));
// No adjacent slime creatures we could merge with.
return (false);
}
static bool _slime_can_spawn(const coord_def target)
{
return (mons_class_can_pass(MONS_SLIME_CREATURE, env.grid(target))
&& !actor_at(target));
}
// 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 || thing->number <= 1
|| coinflip() // Don't make splitting quite so reliable. (jpeg)
|| _disabled_slime(thing))
{
return (false);
}
const coord_def origin = thing->pos();
const actor* foe = thing->get_foe();
const bool has_foe = (foe != NULL && thing->can_see(foe));
const coord_def foe_pos = (has_foe ? foe->position : coord_def(0,0));
const int old_dist = (has_foe ? distance(origin, foe_pos) : 0);
if (has_foe && old_dist > 1)
{
// If we're not already adjacent to the foe, check whether we can
// move any closer. If so, do that rather than splitting.
for (radius_iterator ri(origin, 1, true, false, true); ri; ++ri)
if (_slime_can_spawn(*ri) && distance(*ri, foe_pos) < old_dist)
return (false);
}
int compass_idx[] = {0, 1, 2, 3, 4, 5, 6, 7};
std::random_shuffle(compass_idx, compass_idx + 8);
// Anywhere we can place an offspring?
for (int i = 0; i < 8; ++i)
{
coord_def target = origin + Compass[compass_idx[i]];
// Don't split if this increases the distance to the target.
if (has_foe && distance(target, foe_pos) > old_dist)
continue;
if (_slime_can_spawn(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
|| thing->is_shapeshifter()
|| thing->type != MONS_SLIME_CREATURE)
{
return (false);
}
if (_slime_split(thing))
return (true);
return (_slime_merge(thing));
}
// Returns true if you resist the siren's call.
static bool _siren_movement_effect(const monsters *monster)
{
bool do_resist = (you.attribute[ATTR_HELD] || you.check_res_magic(70)
|| you.cannot_act() || you.asleep());
if (!do_resist)
{
coord_def dir(coord_def(0,0));
if (monster->pos().x < you.pos().x)
dir.x = -1;
else if (monster->pos().x > you.pos().x)
dir.x = 1;
if (monster->pos().y < you.pos().y)
dir.y = -1;
else if (monster->pos().y > you.pos().y)
dir.y = 1;
const coord_def newpos = you.pos() + dir;
if (!in_bounds(newpos) || is_feat_dangerous(grd(newpos))
|| !you.can_pass_through_feat(grd(newpos)))
{
do_resist = true;
}
else
{
bool swapping = false;
monsters *mon = monster_at(newpos);
if (mon)
{
coord_def swapdest;
if (mon->wont_attack()
&& !mons_is_stationary(mon)
&& !mons_is_projectile(mon->type)
&& !mon->cannot_act()
&& !mon->asleep()
&& swap_check(mon, swapdest, true))
{
swapping = true;
}
else if (!mon->submerged())
do_resist = true;
}
if (!do_resist)
{
const coord_def oldpos = you.pos();
mprf("The pull of her song draws you forwards.");
if (swapping)
{
if (monster_at(oldpos))
{
mprf("Something prevents you from swapping places "
"with %s.",
mon->name(DESC_NOCAP_THE).c_str());
return (do_resist);
}
int swap_mon = mgrd(newpos);
// Pick the monster up.
mgrd(newpos) = NON_MONSTER;
mon->moveto(oldpos);
// Plunk it down.
mgrd(mon->pos()) = swap_mon;
mprf("You swap places with %s.",
mon->name(DESC_NOCAP_THE).c_str());
}
move_player_to_grid(newpos, true, true, true);
if (swapping)
mon->apply_location_effects(newpos);
}
}
}
return (do_resist);
}
static 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), mons, 5, 0, foe->pos(), mons->foe));
return (true);
}
return (false);
}
static 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, mons->mindex(), SPTYP_DIVINATION,
random2(15), random2(150),
"an orange crystal statue");
return (true);
}
return (false);
}
static bool _orc_battle_cry(monsters *chief)
{
const actor *foe = chief->get_foe();
int affected = 0;
if (foe
&& (foe != &you || !chief->friendly())
&& !silenced(chief->pos())
&& chief->can_see(foe)
&& coinflip())
{
const int boss_index = chief->mindex();
const int level = chief->hit_dice > 12? 2 : 1;
std::vector<monsters*> seen_affected;
for (monster_iterator mi(chief); mi; ++mi)
{
if (*mi != chief
&& mons_species(mi->type) == MONS_ORC
&& mons_aligned(boss_index, mi->mindex())
&& mi->hit_dice < chief->hit_dice
&& !mi->berserk()
&& !mi->has_ench(ENCH_MIGHT)
&& !mi->cannot_move()
&& !mi->confused())
{
mon_enchant ench = mi->get_ench(ENCH_BATTLE_FRENZY);
if (ench.ench == ENCH_NONE || ench.degree < level)
{
const int dur =
random_range(12, 20) * speed_to_duration(mi->speed);
if (ench.ench != ENCH_NONE)
{
ench.degree = level;
ench.duration = std::max(ench.duration, dur);
mi->update_ench(ench);
}
else
{
mi->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level,
KC_OTHER, dur));
}
affected++;
if (you.can_see(*mi))
seen_affected.push_back(*mi);
if (mi->asleep())
behaviour_event(*mi, 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(LOS_RADIUS, chief->pos(), chief->mindex());
// Disabling detailed frenzy announcement because it's so spammy.
const msg_channel_type channel =
chief->friendly() ? 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!",
chief->friendly() ? "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 (mon->friendly() != targ->friendly())
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);
}
static bool _moth_incite_monsters(const monsters *mon)
{
if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos()))
return false;
int goaded = 0;
circle_def c(mon->pos(), 3, C_SQUARE);
for (monster_iterator mi(&c); mi; ++mi)
{
if (*mi == mon || !mi->needs_berserk())
continue;
if (is_sanctuary(mi->pos()))
continue;
// Cannot goad other moths of wrath!
if (mi->type == MONS_MOTH_OF_WRATH)
continue;
if (_make_monster_angry(mon, *mi) && !one_chance_in(3 * ++goaded))
return (true);
}
return (false);
}
static inline void _mons_cast_abil(monsters *monster, bolt &pbolt,
spell_type spell_cast)
{
mons_cast(monster, pbolt, spell_cast, true, true);
}
//---------------------------------------------------------------
//
// mon_special_ability
//
//---------------------------------------------------------------
bool mon_special_ability(monsters *monster, bolt & beem)
{
bool used = false;
const monster_type mclass = (mons_genus( monster->type ) == MONS_DRACONIAN)
? draco_subspecies( monster )
: static_cast<monster_type>( monster->type );
// Slime creatures can split while out of sight.
if ((!mons_near(monster) || monster->asleep() || monster->submerged())
&& monster->type != MONS_SLIME_CREATURE)
{
return (false);
}
const msg_channel_type spl = (monster->friendly() ? MSGCH_FRIEND_SPELL
: MSGCH_MONSTER_SPELL);
spell_type spell = SPELL_NO_SPELL;
circle_def c;
switch (mclass)
{
case MONS_UGLY_THING:
case MONS_VERY_UGLY_THING:
// A (very) ugly thing's proximity to you if you're glowing, or
// to others of its kind, can mutate it into a different (very)
// ugly thing.
used = ugly_thing_mutate(monster, true);
break;
case MONS_SLIME_CREATURE:
// Slime creatures may split or merge depending on the
// situation.
used = slime_split_merge(monster);
if (!monster->alive())
return (true);
break;
case MONS_ORC_KNIGHT:
case MONS_ORC_WARLORD:
case MONS_SAINT_ROKA:
if (is_sanctuary(monster->pos()))
break;
used = _orc_battle_cry(monster);
break;
case MONS_ORANGE_STATUE:
if (player_or_mon_in_sanct(monster))
break;
used = _orange_statue_effects(monster);
break;
case MONS_SILVER_STATUE:
if (player_or_mon_in_sanct(monster))
break;
used = _silver_statue_effects(monster);
break;
case MONS_BALL_LIGHTNING:
if (is_sanctuary(monster->pos()))
break;
if (monster->attitude == ATT_HOSTILE
&& distance(you.pos(), monster->pos()) <= 5)
{
monster->hit_points = -1;
used = true;
break;
}
c = circle_def(monster->pos(), 4, C_CIRCLE);
for (monster_iterator targ(&c); targ; ++targ)
{
if (mons_atts_aligned(monster->attitude, targ->attitude))
continue;
if (monster->can_see(*targ) && !feat_is_solid(grd(targ->pos())))
{
monster->hit_points = -1;
used = true;
break;
}
}
break;
case MONS_LAVA_SNAKE:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (coinflip())
break;
// Setup tracer.
beem.name = "glob of lava";
beem.aux_source = "glob of lava";
beem.range = 6;
beem.damage = dice_def(3, 10);
beem.hit = 20;
beem.colour = RED;
beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
beem.flavour = BEAM_LAVA;
beem.beam_source = monster->mindex();
beem.thrower = KILL_MON;
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
simple_monster_message(monster, " spits lava!");
beem.fire();
used = true;
}
break;
case MONS_ELECTRIC_EEL:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (coinflip())
break;
// Setup tracer.
beem.name = "bolt of electricity";
beem.aux_source = "bolt of electricity";
beem.range = 8;
beem.damage = dice_def( 3, 6 );
beem.hit = 50;
beem.colour = LIGHTCYAN;
beem.type = dchar_glyph(DCHAR_FIRED_ZAP);
beem.flavour = BEAM_ELECTRICITY;
beem.beam_source = monster->mindex();
beem.thrower = KILL_MON;
beem.is_beam = true;
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
simple_monster_message(monster,
" shoots out a bolt of electricity!");
beem.fire();
used = true;
}
break;
case MONS_ACID_BLOB:
case MONS_OKLOB_PLANT:
case MONS_YELLOW_DRACONIAN:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (player_or_mon_in_sanct(monster))
break;
if (one_chance_in(3))
{
spell = SPELL_ACID_SPLASH;
setup_mons_cast(monster, beem, spell);
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
_mons_cast_abil(monster, beem, spell);
used = true;
}
}
break;
case MONS_MOTH_OF_WRATH:
if (one_chance_in(3))
used = _moth_incite_monsters(monster);
break;
case MONS_SNORG:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (monster->foe == MHITNOT
|| monster->foe == MHITYOU && monster->friendly())
{
break;
}
// There's a 5% chance of Snorg spontaneously going berserk that
// increases to 20% once he is wounded.
if (monster->hit_points == monster->max_hit_points && !one_chance_in(4))
break;
if (one_chance_in(5))
monster->go_berserk(true);
break;
case MONS_PIT_FIEND:
if (one_chance_in(3))
break;
// deliberate fall through
case MONS_FIEND:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (player_or_mon_in_sanct(monster))
break;
// Friendly fiends won't use torment, preferring hellfire
// (right now there is no way a monster can predict how
// badly they'll damage the player with torment) -- GDL
// Well, I guess you could allow it if the player is torment
// resistant, but there's a very good reason torment resistant
// players can't cast Torment themselves, and allowing your
// allies to cast it would just introduce harmless Torment
// through the backdoor. Thus, shouldn't happen. (jpeg)
if (one_chance_in(4))
{
spell_type spell_cast = SPELL_NO_SPELL;
switch (random2(4))
{
case 0:
if (!monster->friendly())
{
make_mons_stop_fleeing(monster);
spell_cast = SPELL_SYMBOL_OF_TORMENT;
_mons_cast_abil(monster, beem, spell_cast);
used = true;
break;
}
// deliberate fallthrough -- see above
case 1:
case 2:
case 3:
spell_cast = SPELL_HELLFIRE;
setup_mons_cast(monster, beem, spell_cast);
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
_mons_cast_abil(monster, beem, spell_cast);
used = true;
}
break;
}
}
break;
case MONS_IMP:
case MONS_PHANTOM:
case MONS_INSUBSTANTIAL_WISP:
case MONS_BLINK_FROG:
case MONS_KILLER_KLOWN:
case MONS_PRINCE_RIBBIT:
case MONS_MARA:
case MONS_MARA_FAKE:
case MONS_GOLDEN_EYE:
if (one_chance_in(7) || monster->caught() && one_chance_in(3))
used = monster_blink(monster);
break;
case MONS_MANTICORE:
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
// The fewer spikes the manticore has left, the less
// likely it will use them.
if (random2(16) >= static_cast<int>(monster->number))
break;
// Do the throwing right here, since the beam is so
// easy to set up and doesn't involve inventory.
// Set up the beam.
beem.name = "volley of spikes";
beem.aux_source = "volley of spikes";
beem.range = 6;
beem.hit = 14;
beem.damage = dice_def( 2, 10 );
beem.beam_source = monster->mindex();
beem.type = dchar_glyph(DCHAR_FIRED_MISSILE);
beem.colour = LIGHTGREY;
beem.flavour = BEAM_MISSILE;
beem.thrower = KILL_MON;
beem.is_beam = false;
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
simple_monster_message(monster, " flicks its tail!");
beem.fire();
used = true;
// Decrement # of volleys left.
monster->number--;
}
break;
case MONS_PLAYER_GHOST:
{
const ghost_demon &ghost = *(monster->ghost);
if (ghost.species < SP_RED_DRACONIAN
|| ghost.species == SP_GREY_DRACONIAN
|| ghost.species >= SP_BASE_DRACONIAN
|| ghost.xl < 7
|| one_chance_in(ghost.xl - 5))
{
break;
}
}
// Intentional fallthrough
case MONS_WHITE_DRACONIAN:
case MONS_RED_DRACONIAN:
spell = SPELL_DRACONIAN_BREATH;
// Intentional fallthrough
case MONS_ICE_DRAGON:
if (spell == SPELL_NO_SPELL)
spell = SPELL_COLD_BREATH;
// Intentional fallthrough
// Dragon breath weapons:
case MONS_DRAGON:
case MONS_HELL_HOUND:
case MONS_LINDWURM:
case MONS_FIRE_DRAKE:
case MONS_XTAHUA:
if (spell == SPELL_NO_SPELL)
spell = SPELL_FIRE_BREATH;
if (monster->has_ench(ENCH_CONFUSION))
break;
if (!you.visible_to(monster))
break;
if (monster->type != MONS_HELL_HOUND && x_chance_in_y(3, 13)
|| one_chance_in(10))
{
setup_mons_cast(monster, beem, spell);
// Fire tracer.
fire_tracer(monster, beem);
// Good idea?
if (mons_should_fire(beem))
{
make_mons_stop_fleeing(monster);
_mons_cast_abil(monster, beem, spell);
used = true;
}
}
break;
case MONS_MERMAID:
case MONS_SIREN:
{
// Don't behold observer in the arena.
if (crawl_state.arena)
break;
// Don't behold player already half down or up the stairs.
if (!you.delay_queue.empty())
{
delay_queue_item delay = you.delay_queue.front();
if (delay.type == DELAY_ASCENDING_STAIRS
|| delay.type == DELAY_DESCENDING_STAIRS)
{
dprf("Taking stairs, don't mesmerise.");
break;
}
}
// Won't sing if either of you silenced, or it's friendly,
// confused, fleeing, or leaving the level.
if (monster->has_ench(ENCH_CONFUSION)
|| mons_is_fleeing(monster)
|| monster->pacified()
|| monster->friendly()
|| !player_can_hear(monster->pos()))
{
break;
}
// Don't even try on berserkers. Mermaids know their limits.
if (you.berserk())
break;
// Reduce probability because of spamminess.
if (you.species == SP_MERFOLK && !one_chance_in(4))
break;
// A wounded invisible mermaid is less likely to give away her position.
if (monster->invisible()
&& monster->hit_points <= monster->max_hit_points / 2
&& !one_chance_in(3))
{
break;
}
bool already_mesmerised = you.beheld_by(monster);
if (one_chance_in(5)
|| monster->foe == MHITYOU && !already_mesmerised && coinflip())
{
noisy(LOS_RADIUS, monster->pos(), monster->mindex(), true);
bool did_resist = false;
if (you.can_see(monster))
{
simple_monster_message(monster,
make_stringf(" chants %s song.",
already_mesmerised ? "her luring" : "a haunting").c_str(),
spl);
if (monster->type == MONS_SIREN)
{
if (_siren_movement_effect(monster))
{
canned_msg(MSG_YOU_RESIST); // flavour only
did_resist = true;
}
}
}
else
{
// If you're already mesmerised by an invisible mermaid she
// can still prolong the enchantment; otherwise you "resist".
if (already_mesmerised)
mpr("You hear a luring song.", MSGCH_SOUND);
else
{
if (one_chance_in(4)) // reduce spamminess
{
if (coinflip())
mpr("You hear a haunting song.", MSGCH_SOUND);
else
mpr("You hear an eerie melody.", MSGCH_SOUND);
canned_msg(MSG_YOU_RESIST); // flavour only
}
break;
}
}
// Once mesmerised by a particular monster, you cannot resist
// anymore.
if (!already_mesmerised
&& (you.species == SP_MERFOLK || you.check_res_magic(100)))
{
if (!did_resist)
canned_msg(MSG_YOU_RESIST);
break;
}
you.add_beholder(monster);
used = true;
}
break;
}
default:
break;
}
if (used)
monster->lose_energy(EUT_SPECIAL);
return (used);
}
// Combines code using in Confusing Eye, Giant Eye and Eye of Draining to
// reduce clutter.
bool _eyeball_will_use_ability (monsters *monster)
{
return (coinflip()
&& !mons_is_wandering(monster)
&& !mons_is_fleeing(monster)
&& !monster->pacified()
&& !player_or_mon_in_sanct(monster));
}
//---------------------------------------------------------------
//
// mon_nearby_ability
//
// Gives monsters a chance to use a special ability when they're
// next to the player.
//
//---------------------------------------------------------------
void mon_nearby_ability(monsters *monster)
{
actor *foe = monster->get_foe();
if (!foe
|| !monster->can_see(foe)
|| monster->asleep()
|| monster->submerged())
{
return;
}
maybe_mons_speaks(monster);
if (monster_can_submerge(monster, grd(monster->pos()))
&& !monster->caught() // No submerging while caught.
&& !you.beheld_by(monster) // No submerging if player entranced.
&& !mons_is_lurking(monster) // Handled elsewhere.
&& monster->wants_submerge())
{
monsterentry* entry = get_monster_data(monster->type);
monster->add_ench(ENCH_SUBMERGED);
monster->speed_increment -= ENERGY_SUBMERGE(entry);
return;
}
switch (monster->type)
{
case MONS_SPATIAL_VORTEX:
case MONS_KILLER_KLOWN:
// Choose random colour.
monster->colour = random_colour();
break;
case MONS_GOLDEN_EYE:
if (_eyeball_will_use_ability(monster))
{
const bool can_see = you.can_see(monster);
if (can_see && you.can_see(foe))
mprf("%s blinks at %s.",
monster->name(DESC_CAP_THE).c_str(),
foe->name(DESC_NOCAP_THE).c_str());
int confuse_power = 2 + random2(3);
if (foe->atype() == ACT_PLAYER && !can_see)
mpr("You feel you are being watched by something.");
if (foe->check_res_magic((monster->hit_dice * 5) * confuse_power))
{
if (foe->atype() == ACT_PLAYER)
canned_msg(MSG_YOU_RESIST);
else if (foe->atype() == ACT_MONSTER)
{
const monsters *foe_mons = dynamic_cast<const monsters*>(foe);
simple_monster_message(foe_mons, mons_resist_string(foe_mons));
}
break;
}
foe->confuse(monster, 2 + random2(3));
}
break;
case MONS_GIANT_EYEBALL:
if (_eyeball_will_use_ability(monster))
{
const bool can_see = you.can_see(monster);
if (can_see && you.can_see(foe))
mprf("%s stares at %s.",
monster->name(DESC_CAP_THE).c_str(),
foe->name(DESC_NOCAP_THE).c_str());
if (foe->atype() == ACT_PLAYER && !can_see)
mpr("You feel you are being watched by something.");
// Subtly different from old paralysis behaviour, but
// it'll do.
foe->paralyse(monster, 2 + random2(3));
}
break;
case MONS_EYE_OF_DRAINING:
if (_eyeball_will_use_ability(monster) && foe->atype() == ACT_PLAYER)
{
if (you.can_see(monster))
simple_monster_message(monster, " stares at you.");
else
mpr("You feel you are being watched by something.");
dec_mp(5 + random2avg(13, 3));
monster->heal(10, true); // heh heh {dlb}
}
break;
case MONS_AIR_ELEMENTAL:
if (one_chance_in(5))
monster->add_ench(ENCH_SUBMERGED);
break;
case MONS_PANDEMONIUM_DEMON:
if (monster->ghost->cycle_colours)
monster->colour = random_colour();
break;
default:
break;
}
}
// When giant spores move maybe place a ballistomycete on the they move
// off of.
void ballisto_on_move(monsters * monster, const coord_def & position)
{
if (monster->type == MONS_GIANT_SPORE)
{
// The number field is used as a cooldown timer for this behavior.
if (monster->number <= 0)
{
if (one_chance_in(4))
{
beh_type attitude = SAME_ATTITUDE(monster);
if (!crawl_state.arena && attitude == BEH_FRIENDLY)
{
attitude = BEH_GOOD_NEUTRAL;
}
int rc = create_monster(mgen_data(MONS_BALLISTOMYCETE,
attitude,
monster,
0,
0,
position,
MHITNOT,
MG_FORCE_PLACE));
if (rc != -1 && you.can_see(&env.mons[rc]))
mprf("A ballistomycete grows in the wake of the spore.");
monster->number = 40;
}
}
else
{
monster->number--;
}
}
}
// If 'monster' is a ballistomycete or spore activate some number of
// ballistomycetes on the level.
void activate_ballistomycetes( monsters * monster, const coord_def & origin)
{
if (!monster || monster->type != MONS_BALLISTOMYCETE
&& monster->type != MONS_GIANT_SPORE)
{
return;
}
bool found_others = false;
std::vector<monsters *> candidates;
for (monster_iterator mi; mi; ++mi)
{
if (mi->mindex() != monster->mindex()
&& mi->alive()
&& mi->type == MONS_BALLISTOMYCETE)
{
candidates.push_back(*mi);
}
}
if (candidates.empty())
return;
// If a spore or inactive ballisto died we will only activate one
// other ballisto. If it was an active ballisto we will distribute
// its count to others on the level.
int activation_count = 1;
if (monster ->type == MONS_BALLISTOMYCETE)
{
activation_count += monster->number;
}
std::random_shuffle(candidates.begin(), candidates.end());
int index = 0;
for (int i=0; i<activation_count; ++i)
{
index = i % candidates.size();
monsters * spawner = candidates[index];
spawner->number++;
found_others = true;
// Change color and start the spore production timer if we
// are moving from 0 to 1.
if (spawner->number == 1)
{
spawner->colour = LIGHTRED;
// Reset the spore production timer.
spawner->del_ench(ENCH_SPORE_PRODUCTION, false);
spawner->add_ench(ENCH_SPORE_PRODUCTION);
}
}
if (you.see_cell(origin) && found_others)
mprf("You feel the ballistomycetes will spawn a replacement spore.");
}