EXYVMVO2EMMZW4LRYSHHHNRFLQ7P6YHNM7SLUO2X2KUMV3WQHQZQC
EC2RWFR6OZNMDBBOERNPI5XH6FWAUCJUWU22HK7ROQL2U7JVAN3AC
6AOBHPEVZFUDSTZ6FR6PRQ6H2V766R2PJRXS4UMYI6SAELGAV5EAC
NT5SNG44MU2JRBQA55TR27YDMA6SMKAP2ANDCROS3T2IL7ATLFUAC
NRZI542DZ2F4R5O34V5DPGMJWKRJBOBSZPQRXTVCSDAPCM3KO3KQC
EHV4CAJV2NVOHTFHFZP6XFW56X7G4PQKBWEEPXQPYQ57SOCYJYRAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
TAHSTXR7ROOMDFUSBUU4ZAIEWQLAS5CIRCTARLD4Q2BGNLSL7E5QC
AUJG42P2TOWAVVU6HBT3D7USOSZCRPQS7FEUGV57HVNULEFDPTSQC
T5QIFBTUF5UQT66HK6U7FU7DTSFLK5MSTDFMJOSR2XPX7B32DGEQC
FRJZHFXLXDE7MANBAGFHRWIH77HC2I4JRGQ5TAVYZS676DU72QSAC
XDACRDVLDEUFUBN4L7ES5WBD3YSLBHMRZ4Q5PXIUMOK44D3TLWSAC
SSQP7MS6LZYY73QEF66EYNNQJJSB6TVLLWXLWL7JJAYBLXCEY2XAC
5FA5IEAXTMXYS2VUBVDKBKHPKAIOY4GN5SXYJORBYWQIGHVW3FFQC
45OFFQNRRS46LXSPQ3SPNP2OXMBECE22A6NWQPITDEOXLRKYSQKAC
ROWW5YUHXITC4QBBSYRVPE6HJ2XMFX3K3M2LHPX4WRDKMEFJVTOQC
PU6VE6DFO4AJPL4RLKMMAIEGYDRNMTH4N4KSERK6B5QU5ZP53ZUAC
OZ2NHOTPZXWCZ7YUMY7AHFAQOCZYL7UI2CAMIYY4LG7LB6F2NUIAC
ZNMT5CZHP2FC4HTLNA7KYEDGFBXSCUE5QHJOALVPE6RDPHSEDXRQC
7K2MOEL4VXTJG2XV575MS3YHL7VWWYBROAV5RCWUBFSF25XXV4XAC
QEEJFAETO6B2J4IWDIDCJ5UNIFNNHHG22IWF2CUJRTJJBNE47CWQC
547JREUJXTZNYVGHNNAET5F5O5JYYGNTDQB6ABZNT7YX5EY64OHAC
EJRKMYKMOYRQXTWGFTMADEWIGWLMWHMUDA73AUT7HO5OBK2GUQEAC
AL7EYY4HB7JNEFGDB6NVVHCVVUYYKUJKC4UFH4T7XUT3P5NT4NAAC
453NICOL4ZKH7YQAWQFKSZZRDJ53XECMTQC7VEMXWV2N4IEAL5VAC
6Y2GZNY5I5YY34QKEPSSDCGAVVLFMOFI4QI34KBNQDRC76JM5AFAC
G7DNYFW745Q567EF3TPR2FCQ4ATPN236ON7X5TLYC7TEPZW3BAFAC
LPV66WRRLBAXH3Z6BYUK36N33H6VFGXMLRGYAE2WGHGN6DTZMVBAC
R4V76NHTUCQ25LBZKMS34INQEXVTJ5TT3QZSPGG6S2WR2FYKK5DQC
HVEU33HIUHCYFINJMVSSOIRUPZGGVC7NMWUN2EADG2W373JP3WOAC
CBPDARXV7QY5JAOCPIYTGKZJRTGYJF4GF4JAJP5V2GW4AI5M3SSAC
TQXNC2YQVJZXBPSLDEANMM2EDGHTH4DSN74OJIRMUF7D46SESHZAC
354AVBXVMW7MMEPBTMB3Q7JHJTEWGSKVPRR7PEYZGEBWQV33TFSAC
HIPFIMUOA7DFOFV3DQ55YZJVGNU2GNDYFUCB4MRPUR5DTYDO5YMAC
XP3TQISCLEST3ZNTF6OZ6FYMHIIPMVTNVBIIG47LA5PS7J234SQAC
HC35ORPWMUNGV4G2TVNPID4ZDRNTWXE4U37LYT4QGSEQR2EXQJNAC
7PVZC6EFCEFBDQLSBR7OREADW43UF2I3HKJZSQBART5SOIJTYECQC
IPXXB4VRVZWOU5DKQ5ZTD37LS3QNK2R6APNZUO672YEEJT6OFAYQC
GWGKGHFGBLVPDSSDWYFORHZHMWOR3SFC5PJNF732V7FEKWWJKPOAC
OYYZVCE3QHBVJM6IEKK5HTJRG5YOVQNCBRMSJENTHOI2WPJLNCFAC
RS2Q66UPKG2NLNBE6UMX273UEVNGTNLVEITB2PCRVHZWTS2WXTAQC
Q3B3UVMYEVC4YJUPYVSNTR4DJH4E6J4JJDHZNT5LNOCHCPPMEMXAC
7IG33VVCNNO7RQ5LNZSNDYADT5AZYME2BNZFY33D3CA2Z7A5AZSAC
PDOFPXD2X6VI23AHKCGQ5RVDBG74CNP2E3YOHKXLOARHHBXEK3HQC
PL6I2CMSTHY5ZHWVMIQE5YTM5S5VPKBNZM6QJVHZSSKOJGIJ5W4AC
LDBTCT5WIPLJPZWXS2RUQ26QKISCUUTLO77M464WOE6VSYSNPKYAC
TVRUXTWYN7ZEPAC7WCNNNUUOSNTAT7IHSGQJI45LP4OJPIT6DT2QC
RS24ZF3Y47QA2534EHQWZ35O2CI4JUOIVHUPRANCCNLVINSCYFXQC
25BU4E7ND5KWNWCNIEOQPI5ITHMRNVQUQNJURBFSEP5FVASYDVQQC
void cycle_exclude_radius(const coord_def &p);
void del_exclude(const coord_def &p);
void set_exclude(const coord_def &p, int radius = LOS_RADIUS,
bool autoexcl = false, bool vaultexcl = false);
void maybe_remove_autoexclusion(const coord_def &p);
std::string get_exclusion_desc();
void clear_excludes();
struct travel_exclude
{
coord_def pos; // exclusion centre
int radius; // exclusion radius
los_def los; // los from exclusion centre
bool uptodate; // Is los up to date?
bool autoex; // Was set automatically.
monster_type mon; // Monster around which exclusion is centered.
bool vault; // Is this exclusion set by a vault?
travel_exclude(const coord_def &p, int r = LOS_RADIUS,
bool autoex = false, monster_type mons = MONS_NO_MONSTER,
bool vault = false);
int radius_sq() const;
void set_los();
bool in_bounds(const coord_def& p) const;
bool affects(const coord_def& p) const;
};
}
opacity_type _feat_opacity(dungeon_feature_type feat)
{
return (feat_is_opaque(feat) ? OPC_OPAQUE : OPC_CLEAR);
}
// A cell is considered clear unless the player knows it's
// opaque.
struct opacity_excl : opacity_func
{
CLONE(opacity_excl)
opacity_type operator()(const coord_def& p) const
{
if (!is_terrain_seen(p))
return OPC_CLEAR;
else if (!is_terrain_changed(p))
return _feat_opacity(env.grid(p));
else if (env.map(p).object < NUM_REAL_FEATURES)
return _feat_opacity((dungeon_feature_type) env.map(p).object);
else
{
// If you have seen monsters, items or clouds there,
// it must have been passable.
return OPC_CLEAR;
}
}
};
static opacity_excl opc_excl;
// Note: bounds_radius gives a circle with square radius r*r+1;
// this doesn't work well for radius 0, but then we want to
// skip LOS calculation in that case anyway since it doesn't
// currently short-cut for small bounds. So radius 0 is special-cased.
travel_exclude::travel_exclude(const coord_def &p, int r,
bool autoexcl, monster_type mons, bool vaultexcl)
: pos(p), radius(r),
los(los_def(p, opc_excl, bounds_radius(r))),
uptodate(false), autoex(autoex), mon(mons), vault(vaultexcl)
{
set_los();
}
void travel_exclude::set_los()
{
uptodate = true;
if (radius > 0)
{
// Radius might have been changed, and this is cheap.
los.set_bounds(bounds_radius(radius));
los.update();
}
}
bool travel_exclude::affects(const coord_def& p) const
{
if (!uptodate)
mprf(MSGCH_ERROR, "exclusion not up-to-date: e (%d,%d) p (%d,%d)",
pos.x, pos.y, p.x, p.y);
if (radius == 0)
return (p == pos);
return (los.see_cell(p));
}
bool travel_exclude::in_bounds(const coord_def &p) const
{
return (radius == 0 && p == pos
|| los.in_bounds(p));
}
void init_exclusion_los()
{
for (unsigned int i = 0; i < curr_excludes.size(); i++)
curr_excludes[i].set_los();
}
void _mark_excludes_non_updated(const coord_def &p)
{
for (exclvec::iterator it = curr_excludes.begin();
it != curr_excludes.end(); ++it)
{
it->uptodate = it->uptodate && it->in_bounds(p);
}
}
void _update_exclusion_los(bool all=false)
{
for (exclvec::iterator it = curr_excludes.begin();
it != curr_excludes.end(); ++it)
{
if (all || !it->uptodate)
it->set_los();
}
}
/*
* Update exclusions' LOS to reflect changes within their range.
* "changed" is a list of coordinates that have been changed.
* Only exclusions that might have one of the changed points
* in view are updated.
*/
void update_exclusion_los(std::vector<coord_def> changed)
{
if (changed.empty())
return;
for (unsigned int i = 0; i < changed.size(); ++i)
_mark_excludes_non_updated(changed[i]);
_update_exclusion_los();
}
static bool _is_excluded(const coord_def &p,
const exclvec &exc)
{
for (unsigned int i = 0; i < exc.size(); ++i)
if (exc[i].affects(p))
return (true);
return (false);
}
bool is_excluded(const coord_def &p)
{
return _is_excluded(p, curr_excludes);
}
static travel_exclude *_find_exclude_root(const coord_def &p)
{
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
if (curr_excludes[i].pos == p)
return (&curr_excludes[i]);
return (NULL);
}
bool is_exclude_root(const coord_def &p)
{
return (_find_exclude_root(p));
}
#ifdef USE_TILE
// update Gmap for squares surrounding exclude centre
static void _tile_exclude_gmap_update(const coord_def &p)
{
for (int x = -8; x <= 8; x++)
for (int y = -8; y <= 8; y++)
{
int px = p.x+x, py = p.y+y;
if (in_bounds(coord_def(px,py)))
{
tiles.update_minimap(px, py);
}
}
}
#endif
static void _exclude_update()
{
if (can_travel_interlevel())
{
LevelInfo &li = travel_cache.get_level_info(level_id::current());
li.update();
}
set_level_exclusion_annotation(get_exclusion_desc());
}
static void _exclude_update(const coord_def &p)
{
#ifdef USE_TILE
_tile_exclude_gmap_update(p);
#endif
_exclude_update();
}
void clear_excludes()
{
// Sanity checks
if (!player_in_mappable_area())
return;
#ifdef USE_TILE
for (int i = curr_excludes.size()-1; i >= 0; i--)
_tile_exclude_gmap_update(curr_excludes[i].pos);
#endif
curr_excludes.clear();
clear_level_exclusion_annotation();
_exclude_update();
}
// Cycles the radius of an exclusion, including "off" state.
void cycle_exclude_radius(const coord_def &p)
{
// XXX: scanning through curr_excludes twice
if (travel_exclude *exc = _find_exclude_root(p))
{
if (exc->radius == LOS_RADIUS)
set_exclude(p, 0);
else
{
ASSERT(exc->radius == 0);
del_exclude(p);
}
}
else
set_exclude(p, LOS_RADIUS);
}
// Remove a possible exclude.
void del_exclude(const coord_def &p)
{
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
if (curr_excludes[i].pos == p)
{
curr_excludes.erase(curr_excludes.begin() + i);
break;
}
_exclude_update(p);
}
// Set or update an exclude.
void set_exclude(const coord_def &p, int radius, bool autoexcl, bool vaultexcl)
{
// Sanity checks; excludes can be set in Pan and regular dungeon
// levels only.
if (!player_in_mappable_area())
return;
if (!in_bounds(p))
return;
if (travel_exclude *exc = _find_exclude_root(p))
{
exc->radius = radius;
exc->set_los();
}
else
{
monster_type montype = MONS_NO_MONSTER;
const monsters *m = monster_at(p);
if (m && you.can_see(m))
montype = m->type;
curr_excludes.push_back(travel_exclude(p, radius, autoexcl,
montype, vaultexcl));
}
_exclude_update(p);
}
// If a grid that was placed automatically no longer contains the original
// monster (or it is invisible), remove the exclusion.
void maybe_remove_autoexclusion(const coord_def &p)
{
if (travel_exclude *exc = _find_exclude_root(p))
{
const monsters *m = monster_at(p);
if (exc->autoex && (!m || !you.can_see(m) || m->type != exc->mon))
del_exclude(p);
}
}
// Lists all exclusions on the current level.
std::string get_exclusion_desc()
{
std::vector<std::string> monsters;
int count_other = 0;
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
{
if (!invalid_monster_type(curr_excludes[i].mon))
monsters.push_back(mons_type_name(curr_excludes[i].mon, DESC_PLAIN));
else
count_other++;
}
if (count_other > 0)
{
snprintf(info, INFO_SIZE, "%d %sexclusion%s",
count_other, monsters.empty() ? "" : "more ",
count_other > 1 ? "s" : "");
monsters.push_back(info);
}
else if (monsters.empty())
return "";
std::string desc = "";
if (monsters.size() > 1 || count_other == 0)
{
snprintf(info, INFO_SIZE, "exclusion%s: ",
monsters.size() > 1 ? "s" : "");
desc += info;
}
return (desc + comma_separated_line(monsters.begin(), monsters.end(),
", and ", ", "));
void init_exclusion_los();
void update_exclusion_los(std::vector<coord_def> changed);
bool is_exclude_root(const coord_def &p);
void cycle_exclude_radius(const coord_def &p);
void del_exclude(const coord_def &p);
void set_exclude(const coord_def &p, int radius = LOS_RADIUS,
bool autoexcl = false, bool vaultexcl = false);
void maybe_remove_autoexclusion(const coord_def &p);
std::string get_exclusion_desc();
void clear_excludes();
struct travel_exclude
{
coord_def pos; // exclusion centre
int radius; // exclusion radius
los_def los; // los from exclusion centre
bool uptodate; // Is los up to date?
bool autoex; // Was set automatically.
monster_type mon; // Monster around which exclusion is centered.
bool vault; // Is this exclusion set by a vault?
travel_exclude(const coord_def &p, int r = LOS_RADIUS,
bool autoex = false, monster_type mons = MONS_NO_MONSTER,
bool vault = false);
int radius_sq() const;
void set_los();
bool in_bounds(const coord_def& p) const;
bool affects(const coord_def& p) const;
};
typedef std::vector<travel_exclude> exclvec;
extern exclvec curr_excludes; // in travel.cc
bool is_excluded(const coord_def &p, const exclvec &exc = curr_excludes);
}
}
opacity_type _feat_opacity(dungeon_feature_type feat)
{
return (feat_is_opaque(feat) ? OPC_OPAQUE : OPC_CLEAR);
}
// A cell is considered clear unless the player knows it's
// opaque.
struct opacity_excl : opacity_func
{
CLONE(opacity_excl)
opacity_type operator()(const coord_def& p) const
{
if (!is_terrain_seen(p))
return OPC_CLEAR;
else if (!is_terrain_changed(p))
return _feat_opacity(env.grid(p));
else if (env.map(p).object < NUM_REAL_FEATURES)
return _feat_opacity((dungeon_feature_type) env.map(p).object);
else
{
// If you have seen monsters, items or clouds there,
// it must have been passable.
return OPC_CLEAR;
}
}
};
static opacity_excl opc_excl;
// Note: bounds_radius gives a circle with square radius r*r+1;
// this doesn't work well for radius 0, but then we want to
// skip LOS calculation in that case anyway since it doesn't
// currently short-cut for small bounds. So radius 0 is special-cased.
travel_exclude::travel_exclude(const coord_def &p, int r,
bool autoexcl, monster_type mons, bool vaultexcl)
: pos(p), radius(r),
los(los_def(p, opc_excl, bounds_radius(r))),
uptodate(false), autoex(autoex), mon(mons), vault(vaultexcl)
{
set_los();
}
void travel_exclude::set_los()
{
uptodate = true;
if (radius > 0)
{
// Radius might have been changed, and this is cheap.
los.set_bounds(bounds_radius(radius));
los.update();
}
}
bool travel_exclude::affects(const coord_def& p) const
{
if (!uptodate)
mprf(MSGCH_ERROR, "exclusion not up-to-date: e (%d,%d) p (%d,%d)",
pos.x, pos.y, p.x, p.y);
if (radius == 0)
return (p == pos);
return (los.see_cell(p));
}
bool travel_exclude::in_bounds(const coord_def &p) const
{
return (radius == 0 && p == pos
|| los.in_bounds(p));
}
void _mark_excludes_non_updated(const coord_def &p)
{
for (exclvec::iterator it = curr_excludes.begin();
it != curr_excludes.end(); ++it)
{
it->uptodate = it->uptodate && it->in_bounds(p);
void init_exclusion_los()
{
_update_exclusion_los(true);
}
/*
* Update exclusions' LOS to reflect changes within their range.
* "changed" is a list of coordinates that have been changed.
* Only exclusions that might have one of the changed points
* in view are updated.
*/
void update_exclusion_los(std::vector<coord_def> changed)
{
if (changed.empty())
return;
for (unsigned int i = 0; i < changed.size(); ++i)
_mark_excludes_non_updated(changed[i]);
_update_exclusion_los();
}
bool is_excluded(const coord_def &p, const exclvec &exc)
{
for (unsigned int i = 0; i < exc.size(); ++i)
if (exc[i].affects(p))
return (true);
return (false);
}
static travel_exclude *_find_exclude_root(const coord_def &p)
{
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
if (curr_excludes[i].pos == p)
return (&curr_excludes[i]);
return (NULL);
}
bool is_exclude_root(const coord_def &p)
{
return (_find_exclude_root(p));
}
#ifdef USE_TILE
// update Gmap for squares surrounding exclude centre
static void _tile_exclude_gmap_update(const coord_def &p)
{
for (int x = -8; x <= 8; x++)
for (int y = -8; y <= 8; y++)
{
int px = p.x+x, py = p.y+y;
if (in_bounds(coord_def(px,py)))
{
tiles.update_minimap(px, py);
}
}
}
#endif
static void _exclude_update()
{
if (can_travel_interlevel())
{
LevelInfo &li = travel_cache.get_level_info(level_id::current());
li.update();
}
set_level_exclusion_annotation(get_exclusion_desc());
}
static void _exclude_update(const coord_def &p)
{
#ifdef USE_TILE
_tile_exclude_gmap_update(p);
#endif
_exclude_update();
}
void clear_excludes()
{
// Sanity checks
if (!player_in_mappable_area())
return;
#ifdef USE_TILE
for (int i = curr_excludes.size()-1; i >= 0; i--)
_tile_exclude_gmap_update(curr_excludes[i].pos);
#endif
curr_excludes.clear();
clear_level_exclusion_annotation();
_exclude_update();
}
// Cycles the radius of an exclusion, including "off" state.
void cycle_exclude_radius(const coord_def &p)
{
// XXX: scanning through curr_excludes twice
if (travel_exclude *exc = _find_exclude_root(p))
{
if (exc->radius == LOS_RADIUS)
set_exclude(p, 0);
else
{
ASSERT(exc->radius == 0);
del_exclude(p);
}
}
else
set_exclude(p, LOS_RADIUS);
}
// Remove a possible exclude.
void del_exclude(const coord_def &p)
{
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
if (curr_excludes[i].pos == p)
{
curr_excludes.erase(curr_excludes.begin() + i);
break;
}
_exclude_update(p);
}
// Set or update an exclude.
void set_exclude(const coord_def &p, int radius, bool autoexcl, bool vaultexcl)
{
// Sanity checks; excludes can be set in Pan and regular dungeon
// levels only.
if (!player_in_mappable_area())
return;
if (!in_bounds(p))
return;
if (travel_exclude *exc = _find_exclude_root(p))
{
exc->radius = radius;
exc->set_los();
}
else
{
monster_type montype = MONS_NO_MONSTER;
const monsters *m = monster_at(p);
if (m && you.can_see(m))
montype = m->type;
curr_excludes.push_back(travel_exclude(p, radius, autoexcl,
montype, vaultexcl));
}
_exclude_update(p);
}
// If a grid that was placed automatically no longer contains the original
// monster (or it is invisible), remove the exclusion.
void maybe_remove_autoexclusion(const coord_def &p)
{
if (travel_exclude *exc = _find_exclude_root(p))
{
const monsters *m = monster_at(p);
if (exc->autoex && (!m || !you.can_see(m) || m->type != exc->mon))
del_exclude(p);
}
}
// Lists all exclusions on the current level.
std::string get_exclusion_desc()
{
std::vector<std::string> monsters;
int count_other = 0;
for (unsigned int i = 0; i < curr_excludes.size(); ++i)
{
if (!invalid_monster_type(curr_excludes[i].mon))
monsters.push_back(mons_type_name(curr_excludes[i].mon, DESC_PLAIN));
else
count_other++;
}
if (count_other > 0)
{
snprintf(info, INFO_SIZE, "%d %sexclusion%s",
count_other, monsters.empty() ? "" : "more ",
count_other > 1 ? "s" : "");
monsters.push_back(info);
}
else if (monsters.empty())
return "";
std::string desc = "";
if (monsters.size() > 1 || count_other == 0)
{
snprintf(info, INFO_SIZE, "exclusion%s: ",
monsters.size() > 1 ? "s" : "");
desc += info;
}
return (desc + comma_separated_line(monsters.begin(), monsters.end(),
", and ", ", "));
}