/*
* File: teleport.cc
* Summary: Functions related to teleportation and blinking.
*/
#include "AppHdr.h"
#include "teleport.h"
#include "cloud.h"
#include "coord.h"
#include "coordit.h"
#include "env.h"
#include "fprop.h"
#include "los.h"
#include "monster.h"
#include "mon-stuff.h"
#include "player.h"
#include "random.h"
#include "random-weight.h"
#include "state.h"
#include "terrain.h"
bool player::blink_to(const coord_def& dest, bool quiet)
{
// We rely on the non-generalized move_player_to_cell.
ASSERT(this == &you);
if (dest == pos())
return (false);
if (!quiet)
mpr("You blink.");
const coord_def origin = pos();
if (!move_player_to_grid(dest, false, true, true))
return (false);
place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), KC_YOU);
return (true);
}
bool monsters::blink_to(const coord_def& dest, bool quiet)
{
if (dest == pos())
return (false);
if (!quiet)
simple_monster_message(this, " blinks!");
if (!(flags & MF_WAS_IN_VIEW))
seen_context = "thin air";
const coord_def oldplace = pos();
if (!move_to_pos(dest))
return (false);
// Leave a purple cloud.
place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3),
kill_alignment());
check_redraw(oldplace);
apply_location_effects(oldplace);
mons_relocated(this);
return (true);
}
typedef std::pair<coord_def, int> coord_weight;
// Try to find a "safe" place for moved close or far from the target.
// keep_los indicates that the destination should be in view of the target.
//
// XXX: Check the result against in_bounds(), not coord_def::origin(),
// beceause of a memory problem described below.
static coord_def random_space_weighted(actor* moved, actor* target,
bool close, bool keep_los = true,
bool allow_sanct = true)
{
std::vector<coord_weight> dests;
const coord_def tpos = target->pos();
const los_def* mlos = &moved->get_los_no_trans();
const los_def* tlos = &target->get_los_no_trans();
for (radius_iterator ri(mlos); ri; ++ri)
{
if (!moved->is_habitable(*ri) || actor_at(*ri)
|| keep_los && !tlos->see_cell(*ri)
|| !allow_sanct && is_sanctuary(*ri))
{
continue;
}
int weight;
int dist = (tpos - *ri).rdist();
if (close)
weight = (LOS_RADIUS - dist) * (LOS_RADIUS - dist);
else
weight = dist;
if (weight < 0)
weight = 0;
dests.push_back(coord_weight(*ri, weight));
}
coord_def* choice = random_choose_weighted(dests);
return (choice ? *choice : coord_def(0, 0));
}
// Blink the victim closer to the monster at target.
void blink_other_close(actor* victim, const coord_def &target)
{
actor* caster = actor_at(target);
if (!caster)
return;
if (is_sanctuary(you.pos()))
return;
coord_def dest = random_space_weighted(victim, caster, true);
if (!in_bounds(dest))
return;
bool success = victim->blink_to(dest);
ASSERT(success);
#ifndef DEBUG
UNUSED(success);
#endif
}
// Blink the monster away from its foe.
void blink_away(monsters* mon)
{
actor* foe = mon->get_foe();
if (!foe || !mon->can_see(foe))
return;
coord_def dest = random_space_weighted(mon, foe, false, false);
if (dest.origin())
return;
bool success = mon->blink_to(dest);
ASSERT(success);
#ifndef DEBUG
UNUSED(success);
#endif
}
// Blink the monster within range but at distance to its foe.
void blink_range(monsters* mon)
{
actor* foe = mon->get_foe();
if (!foe || !mon->can_see(foe))
return;
coord_def dest = random_space_weighted(mon, foe, false, true);
if (dest.origin())
return;
bool success = mon->blink_to(dest);
ASSERT(success);
#ifndef DEBUG
UNUSED(success);
#endif
}
// Blink the monster close to its foe.
void blink_close(monsters* mon)
{
actor* foe = mon->get_foe();
if (!foe || !mon->can_see(foe))
return;
coord_def dest = random_space_weighted(mon, foe, true);
if (dest.origin())
return;
bool success = mon->blink_to(dest);
ASSERT(success);
#ifndef DEBUG
UNUSED(success);
#endif
}
bool random_near_space(const coord_def& origin, coord_def& target,
bool allow_adjacent, bool restrict_los,
bool forbid_dangerous, bool forbid_sanctuary)
{
// This might involve ray tracing (via num_feats_between()), so
// cache results to avoid duplicating ray traces.
#define RNS_OFFSET 6
#define RNS_WIDTH (2*RNS_OFFSET + 1)
FixedArray<bool, RNS_WIDTH, RNS_WIDTH> tried;
const coord_def tried_o = coord_def(RNS_OFFSET, RNS_OFFSET);
tried.init(false);
// Is the monster on the other side of a transparent wall?
const bool trans_wall_block = you.trans_wall_blocking(origin);
const bool origin_is_player = (you.pos() == origin);
int min_walls_between = 0;
// Skip ray tracing if possible.
if (trans_wall_block && !crawl_state.arena)
{
// XXX: you.pos() is invalid in the arena.
min_walls_between = num_feats_between(origin, you.pos(),
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL);
}
for (int tries = 0; tries < 150; tries++)
{
coord_def p = coord_def(random2(RNS_WIDTH), random2(RNS_WIDTH));
if (tried(p))
continue;
else
tried(p) = true;
target = origin + (p - tried_o);
// Origin is not 'near'.
if (target == origin)
continue;
if (!in_bounds(target)
|| restrict_los && !you.see_cell(target)
|| grd(target) < DNGN_SHALLOW_WATER
|| actor_at(target)
|| !allow_adjacent && distance(origin, target) <= 2
|| forbid_sanctuary && is_sanctuary(target))
{
continue;
}
// Don't pick grids that contain a dangerous cloud.
if (forbid_dangerous)
{
const int cloud = env.cgrid(target);
if (cloud != EMPTY_CLOUD
&& is_damaging_cloud(env.cloud[cloud].type, true))
{
continue;
}
}
if (!trans_wall_block && !origin_is_player)
return (true);
// If the monster is on a visible square which is on the other
// side of one or more translucent walls from the player, then it
// can only blink through translucent walls if the end point
// is either not visible to the player, or there are at least
// as many translucent walls between the player and the end
// point as between the player and the start point. However,
// monsters can still blink through translucent walls to get
// away from the player, since in the absence of translucent
// walls monsters can blink to places which are not in either
// the monster's nor the player's LOS.
if (!origin_is_player && !you.see_cell(target))
return (true);
// Player can't randomly pass through translucent walls.
if (origin_is_player)
{
if (you.see_cell_no_trans(target))
return (true);
continue;
}
int walls_passed = num_feats_between(target, origin,
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL,
true, true);
if (walls_passed == 0)
return (true);
// Player can't randomly pass through translucent walls.
if (origin_is_player)
continue;
int walls_between = 0;
if (!crawl_state.arena)
{
walls_between = num_feats_between(target, you.pos(),
DNGN_CLEAR_ROCK_WALL,
DNGN_CLEAR_PERMAROCK_WALL);
}
if (walls_between >= min_walls_between)
return (true);
}
return (false);
}