I plan to use monster_info to get monster data out to the player safely via Lua.
2BGXBH5OMEFAX2XTS2OWIRODUGW6V2FJR4QRJIQTV4C2WIJI22BQC
CI7MCWE7NO6WUAPIZNOFVXN37TKVQWVZATJZZMAV3OW2DH3HCCBAC
JESCFNNBKC7BW3XYLJLAF6RLMP5VCXIUUXQUGEZBF4FYPNMEGNJAC
ETJXWTOUQEK2BECTSNQOQN2GQO4U5GMSCEIOVNW4KY7J3CLEUWCAC
JQK4F4RTKVQWIF5YKFPH7IHF7UREBOY2NOFQ7J3O44KYPCNHNL5AC
JR2RAQ523LOWNDYJNK6AZVKI6WVMI622PIV72XWOVZYPXPUKSQWAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
SKWBAGSAB625IIN4UP3NCPRX2H3KCPC2LULHS2A7JVRLO3EUBJDAC
7X4LFGBYTBK5LKZH7HPYBUHHMHWFAT3NPKAMJHK54YL2ZO5ZIK2AC
NO2HRD7RZS42S55UG4FQ5EFQDY6WYWKGIXHUKYLWF36HHE3VZ7WAC
IQFLSXLOKMSMM65BL7XOEI5ZP55WKZ7BFBOIA44AMTPNJ7DAQXBQC
QSRRV725GCNDOMIDS36NYAA65MOLDMAZSENVSZ3PNOLBFSGHH7BQC
WEFNLSQ7HRLNHIKFH46FIBBZNAORSXGOVIJWZGVVEHL36MICAUAAC
VZSAQXCHOUUAREGQBTIJVXOXVIUNACE74SHTBONNOXINDFK4FDCAC
TE3QYFTQB2N6RHYG6VULGNUZ7EN6MZ5IDITC7KWD75UHPG7A2YAAC
ZNMT5CZHP2FC4HTLNA7KYEDGFBXSCUE5QHJOALVPE6RDPHSEDXRQC
HAM54HXIO2245W6REO4RZDY2QMIH476AWWJSMYAMSYYNEBBJSHWAC
YXWZYOBUFR4EHBSKXCFGZNK7NIM7IPE2GMZYIXEJTUPDDC3BIEDAC
6XSOB55JCEWQK7OANHLEIPOIUGH32ZEB35A7XLBKSFJ5YH5GCZ4AC
N6J4ANT3WFBQYTFDPZFVF2PWS2S7CK4GHHX4UV3JW4VWAKETZR5AC
F2YWDQUXULLS2WKLVMNEJKEGSGG2MPEREVI2RKH7ROS2YYKKMXHAC
HT2Z5ZWY5G2QVHXCKFDQT62FRNGPRRD3HHJ2UULBWRTMAJICUDGAC
S34LKQDIQJLIWVIPASOJBBZ6ZCXDHP5KPS7TRBZJSCDRVNCLK6UAC
EXZ3JJJIBWKHQ7XSJPC6P3LOKAALMJHUX3CGIL6FO5KPNTFMLE4QC
7NRMOLVOLPSF4KW23GZOO4X6BMGSMBE3IDMO4WOKBZXPYCLRZIXQC
GJBUM2B6VECYVSAWUQAG64MDJ4MUHTACSTDDVPEKRDBGBX4HPQLQC
5WVUTEZLEZEML54CKPR6GACQBYY3EMVNXMLJOREN6SSEUZGC47AQC
4ZSVHDY23VI2FMLU5Z2CJ35SAPDQRREM6RABZ4GEILWI7NRULQNAC
5B5DP5S6A6LQMKZYVLQAEMHQZWFWYDHPCKQGRNSCNNYIBQYZ6BIQC
KVPP3CYPEFADQCL5Y56ELSUBA47SFNAJNCSVCSOK2GF67DHVMBCQC
HM6NOS7BN5665KWIFGBXOHZIDMRSVCESN72GMGI6NVBFZGCF3L6QC
TR6KNC3N4EXHAFJWYCQJ6QJOQRVVE5VJAMENA4DUZWE5XDGQQF4QC
UVGFF3QPZKZNMWFDDANQRBTVOROTA4TZFXGYDPV4GQXZKHZY2EXAC
U6OTXM3JN7SGPVIGQ5F6NR2I7J5V7KOWFQ7AVNNRQQDNLBEDMYFQC
BGEMR37KHW34PY6TU4WR7YBXFARYB6UEXAEXORTK7RKFXCBXN5UAC
KQLAGI6OMH5UB2UUG6BPJH46DV2B3THBQVSZIZHGROCON57GTLUQC
GQF26J3LYUS35FFHOV4N26QHSJIA5GXMNBXVVKDGX7YMFBFJK4LQC
ZR4ALQ5F2ETOD3GXMBYXSN6P75DPDGI3C275A7W5XHYI5TGPHNUQC
APWL24FMDRVA6NXFFCGNP3Z3FJJ6WFSRWO4ESHEOVT7Z5JGXPUQQC
UL7XFKMUX3WIU4O2LZANK4ECJ654UZPDBFGNXUEYZYOLKBYBCG6AC
MQ62OAMLGJVRW2QIL4PAZRAU6PC52ZVGY2FCOBIY6IWGQIHMU5CAC
HKQTMQVLLOBG2VO47TUGSTQALA3D2YLMEVADXXYNR4RGGKD3F2ZAC
RZP5Z6JM74GVX2M6L2JOFRJ5VT2XGFHBNRDESVHSKF66PQKIW32QC
// Monster info used by the pane; precomputes some data
// to help with sorting and rendering.
class monster_pane_info
{
public:
static bool less_than(const monster_pane_info& m1,
const monster_pane_info& m2, bool zombified = true);
static bool less_than_wrapper(const monster_pane_info& m1,
const monster_pane_info& m2);
}
monster_pane_info::monster_pane_info(const monsters *m)
: m_mon(m), m_attitude(ATT_HOSTILE), m_difficulty(0),
m_brands(0), m_fullname(true)
{
// XXX: this doesn't take into account ENCH_NEUTRAL, but that's probably
// a bug for mons_attitude, not this.
// XXX: also, mons_attitude_type should be sorted hostile/neutral/friendly;
// will break saves a little bit though.
m_attitude = mons_attitude(m);
int mtype = m->type;
if (mtype == MONS_RAKSHASA_FAKE)
mtype = MONS_RAKSHASA;
// Currently, difficulty is defined as "average hp".
m_difficulty = mons_difficulty(mtype);
// [ds] XXX: Kill the magic numbers.
if (mons_looks_stabbable(m)) m_brands |= 1;
if (mons_looks_distracted(m)) m_brands |= 2;
if (m->has_ench(ENCH_BERSERK)) m_brands |= 4;
}
// Needed because gcc 4.3 sort does not like comparison functions that take
// more than 2 arguments.
bool monster_pane_info::less_than_wrapper(const monster_pane_info& m1,
const monster_pane_info& m2)
{
return monster_pane_info::less_than(m1, m2, true);
}
// Sort monsters by (in that order): attitude, difficulty, type, brand
bool monster_pane_info::less_than(const monster_pane_info& m1,
const monster_pane_info& m2, bool zombified)
{
if (m1.m_attitude < m2.m_attitude)
return (true);
else if (m1.m_attitude > m2.m_attitude)
return (false);
int m1type = m1.m_mon->type;
int m2type = m2.m_mon->type;
// Don't differentiate real rakshasas from fake ones.
if (m1type == MONS_RAKSHASA_FAKE)
m1type = MONS_RAKSHASA;
if (m2type == MONS_RAKSHASA_FAKE)
m2type = MONS_RAKSHASA;
// Force plain but different coloured draconians to be treated like the
// same sub-type.
if (!zombified && m1type >= MONS_DRACONIAN
&& m1type <= MONS_PALE_DRACONIAN
&& m2type >= MONS_DRACONIAN
&& m2type <= MONS_PALE_DRACONIAN)
{
return (false);
}
// By descending difficulty
if (m1.m_difficulty > m2.m_difficulty)
return (true);
else if (m1.m_difficulty < m2.m_difficulty)
return (false);
// Force mimics of different types to be treated like the same one.
if (mons_is_mimic(m1type) && mons_is_mimic(m2type))
return (false);
if (m1type < m2type)
return (true);
else if (m1type > m2type)
return (false);
// Never distinguish between dancing weapons.
// The above checks guarantee that *both* monsters are of this type.
if (m1type == MONS_DANCING_WEAPON)
return (false);
if (zombified)
{
// Because of the type checks above, if one of the two is zombified, so
// is the other, and of the same type.
if (mons_is_zombified(m1.m_mon)
&& m1.m_mon->base_monster < m2.m_mon->base_monster)
{
return (true);
}
// Both monsters are hydras or hydra zombies, sort by number of heads.
if (m1.m_mon->has_hydra_multi_attack()
&& m1.m_mon->number > m2.m_mon->number)
{
return (true);
}
}
if (m1.m_fullname && m2.m_fullname || m1type == MONS_PLAYER_GHOST)
return (m1.m_mon->name(DESC_PLAIN) < m1.m_mon->name(DESC_PLAIN));
#if 0 // for now, sort brands together.
// By descending brands, so no brands sorts to the end
if (m1.m_brands > m2.m_brands)
return (true);
else if (m1.m_brands < m2.m_brands)
return (false);
#endif
return (false);
}
static std::string _verbose_info(const monsters* m)
{
if (mons_is_caught(m))
return (" (caught)");
if (mons_behaviour_perceptible(m))
{
if (mons_is_petrified(m))
return(" (petrified)");
if (mons_is_paralysed(m))
return(" (paralysed)");
if (mons_is_petrifying(m))
return(" (petrifying)");
if (mons_is_confused(m))
return(" (confused)");
if (mons_is_fleeing(m))
return(" (fleeing)");
if (mons_is_sleeping(m))
{
if (mons_holiness(m) == MH_UNDEAD
|| mons_holiness(m) == MH_NONLIVING
|| mons_holiness(m) == MH_PLANT)
{
return(" (dormant)");
}
else
return(" (sleeping)");
}
if (mons_is_wandering(m) && !mons_is_batty(m)
&& !(m->attitude == ATT_STRICT_NEUTRAL))
{
// Labeling strictly neutral monsters as fellow slimes is more important.
return(" (wandering)");
}
if (m->foe == MHITNOT && !mons_is_batty(m) && !mons_neutral(m)
&& !mons_friendly(m))
{
return (" (unaware)");
}
}
if (m->has_ench(ENCH_STICKY_FLAME))
return (" (burning)");
if (m->has_ench(ENCH_ROT))
return (" (rotting)");
if (m->has_ench(ENCH_INVIS))
return (" (invisible)");
return ("");
if (count == 1)
{
if (mons_is_mimic(m_mon->type))
out << mons_type_name(m_mon->type, DESC_PLAIN);
else
out << m_mon->full_name(DESC_PLAIN);
}
else
{
// Don't pluralise uniques, ever. Multiple copies of the same unique
// are unlikely in the dungeon currently, but quite common in the
// arena. This prevens "4 Gra", etc. {due}
if (mons_is_unique(m_mon->type))
{
out << count << " "
<< m_mon->name(DESC_PLAIN);
}
// Don't differentiate between dancing weapons, mimics, (very)
// ugly things or draconians of different types.
else if (m_fullname
&& m_mon->type != MONS_DANCING_WEAPON
&& mons_genus(m_mon->type) != MONS_DRACONIAN
&& m_mon->type != MONS_UGLY_THING
&& m_mon->type != MONS_VERY_UGLY_THING
&& !mons_is_mimic(m_mon->type)
&& m_mon->mname.empty())
{
out << count << " "
<< pluralise(m_mon->name(DESC_PLAIN));
}
else if (m_mon->type >= MONS_DRACONIAN
&& m_mon->type <= MONS_PALE_DRACONIAN)
{
out << count << " "
<< pluralise(mons_type_name(MONS_DRACONIAN, DESC_PLAIN));
}
else
{
out << count << " "
<< pluralise(mons_type_name(m_mon->type, DESC_PLAIN));
}
}
#if DEBUG_DIAGNOSTICS
out << " av" << m_difficulty << " "
<< m_mon->hit_points << "/" << m_mon->max_hit_points;
#endif
if (count == 1)
{
if (m_mon->has_ench(ENCH_BERSERK))
out << " (berserk)";
else if (Options.verbose_monster_pane)
out << _verbose_info(m_mon);
else if (mons_looks_stabbable(m_mon))
out << " (resting)";
else if (mons_looks_distracted(m_mon))
out << " (distracted)";
else if (m_mon->has_ench(ENCH_INVIS))
out << " (invisible)";
}
// Friendliness
switch (m_attitude)
{
case ATT_FRIENDLY:
//out << " (friendly)";
desc_color = GREEN;
break;
case ATT_GOOD_NEUTRAL:
case ATT_NEUTRAL:
//out << " (neutral)";
desc_color = BROWN;
break;
case ATT_STRICT_NEUTRAL:
out << " (fellow slime)";
desc_color = BROWN;
break;
case ATT_HOSTILE:
// out << " (hostile)";
desc_color = LIGHTGREY;
break;
}
// Evilness of attacking
switch (m_attitude)
{
case ATT_NEUTRAL:
case ATT_HOSTILE:
if (count == 1 && you.religion == GOD_SHINING_ONE
&& !tso_unchivalric_attack_safe_monster(m_mon)
&& is_unchivalric_attack(&you, m_mon))
{
desc_color = Options.evil_colour;
}
break;
default:
break;
}
desc = out.str();
}
void get_monster_pane_info(std::vector<monster_pane_info>& mons)
{
std::vector<monsters*> visible = get_nearby_monsters();
for (unsigned int i = 0; i < visible.size(); i++)
{
if (Options.target_zero_exp
|| !mons_class_flag( visible[i]->type, M_NO_EXP_GAIN )
|| visible[i]->type == MONS_KRAKEN_TENTACLE)
{
mons.push_back(monster_pane_info(visible[i]));
}
}
std::sort(mons.begin(), mons.end(), monster_pane_info::less_than_wrapper);
}
std::vector<monster_pane_info> mons;
get_monster_pane_info(mons);
std::sort(mons.begin(), mons.end(), monster_pane_info::less_than_wrapper);
std::vector<monster_info> mons;
get_monster_info(mons);
std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper);
#ifndef MON_INFO_H
#define MON_INFO_H
// Monster info used by the pane; precomputes some data
// to help with sorting and rendering.
class monster_info
{
public:
static bool less_than(const monster_info& m1,
const monster_info& m2, bool zombified = true);
static bool less_than_wrapper(const monster_info& m1,
const monster_info& m2);
monster_info(const monsters* m);
void to_string(int count, std::string& desc, int& desc_color) const;
const monsters* m_mon;
mon_attitude_type m_attitude;
int m_difficulty;
int m_brands;
bool m_fullname;
};
void get_monster_info(std::vector<monster_info>& mons);
#endif
#include "AppHdr.h"
#include "mon-info.h"
#include "fight.h"
#include "misc.h"
#include "mon-util.h"
#include "monster.h"
#include "religion.h"
#include <sstream>
enum monster_info_brands
{
MB_STABBABLE,
MB_DISTRACTED,
MB_BERSERK
};
monster_info::monster_info(const monsters *m)
: m_mon(m), m_attitude(ATT_HOSTILE), m_difficulty(0),
m_brands(0), m_fullname(true)
{
// XXX: this doesn't take into account ENCH_NEUTRAL, but that's probably
// a bug for mons_attitude, not this.
// XXX: also, mons_attitude_type should be sorted hostile/neutral/friendly;
// will break saves a little bit though.
m_attitude = mons_attitude(m);
int mtype = m->type;
if (mtype == MONS_RAKSHASA_FAKE)
mtype = MONS_RAKSHASA;
// Currently, difficulty is defined as "average hp".
m_difficulty = mons_difficulty(mtype);
if (mons_looks_stabbable(m)) m_brands |= (1 << MB_STABBABLE);
if (mons_looks_distracted(m)) m_brands |= (1 << MB_DISTRACTED);
if (m->has_ench(ENCH_BERSERK)) m_brands |= (1 << MB_BERSERK);
}
// Needed because gcc 4.3 sort does not like comparison functions that take
// more than 2 arguments.
bool monster_info::less_than_wrapper(const monster_info& m1,
const monster_info& m2)
{
return monster_info::less_than(m1, m2, true);
}
// Sort monsters by (in that order): attitude, difficulty, type, brand
bool monster_info::less_than(const monster_info& m1,
const monster_info& m2, bool zombified)
{
if (m1.m_attitude < m2.m_attitude)
return (true);
else if (m1.m_attitude > m2.m_attitude)
return (false);
int m1type = m1.m_mon->type;
int m2type = m2.m_mon->type;
// Don't differentiate real rakshasas from fake ones.
if (m1type == MONS_RAKSHASA_FAKE)
m1type = MONS_RAKSHASA;
if (m2type == MONS_RAKSHASA_FAKE)
m2type = MONS_RAKSHASA;
// Force plain but different coloured draconians to be treated like the
// same sub-type.
if (!zombified && m1type >= MONS_DRACONIAN
&& m1type <= MONS_PALE_DRACONIAN
&& m2type >= MONS_DRACONIAN
&& m2type <= MONS_PALE_DRACONIAN)
{
return (false);
}
// By descending difficulty
if (m1.m_difficulty > m2.m_difficulty)
return (true);
else if (m1.m_difficulty < m2.m_difficulty)
return (false);
// Force mimics of different types to be treated like the same one.
if (mons_is_mimic(m1type) && mons_is_mimic(m2type))
return (false);
if (m1type < m2type)
return (true);
else if (m1type > m2type)
return (false);
// Never distinguish between dancing weapons.
// The above checks guarantee that *both* monsters are of this type.
if (m1type == MONS_DANCING_WEAPON)
return (false);
if (zombified)
{
// Because of the type checks above, if one of the two is zombified, so
// is the other, and of the same type.
if (mons_is_zombified(m1.m_mon)
&& m1.m_mon->base_monster < m2.m_mon->base_monster)
{
return (true);
}
// Both monsters are hydras or hydra zombies, sort by number of heads.
if (m1.m_mon->has_hydra_multi_attack()
&& m1.m_mon->number > m2.m_mon->number)
{
return (true);
}
}
if (m1.m_fullname && m2.m_fullname || m1type == MONS_PLAYER_GHOST)
return (m1.m_mon->name(DESC_PLAIN) < m1.m_mon->name(DESC_PLAIN));
#if 0 // for now, sort brands together.
// By descending brands, so no brands sorts to the end
if (m1.m_brands > m2.m_brands)
return (true);
else if (m1.m_brands < m2.m_brands)
return (false);
#endif
return (false);
}
static std::string _verbose_info(const monsters* m)
{
if (mons_is_caught(m))
return (" (caught)");
if (mons_behaviour_perceptible(m))
{
if (mons_is_petrified(m))
return(" (petrified)");
if (mons_is_paralysed(m))
return(" (paralysed)");
if (mons_is_petrifying(m))
return(" (petrifying)");
if (mons_is_confused(m))
return(" (confused)");
if (mons_is_fleeing(m))
return(" (fleeing)");
if (mons_is_sleeping(m))
{
if (mons_holiness(m) == MH_UNDEAD
|| mons_holiness(m) == MH_NONLIVING
|| mons_holiness(m) == MH_PLANT)
{
return(" (dormant)");
}
else
return(" (sleeping)");
}
if (mons_is_wandering(m) && !mons_is_batty(m)
&& !(m->attitude == ATT_STRICT_NEUTRAL))
{
// Labeling strictly neutral monsters as fellow slimes is more important.
return(" (wandering)");
}
if (m->foe == MHITNOT && !mons_is_batty(m) && !mons_neutral(m)
&& !mons_friendly(m))
{
return (" (unaware)");
}
}
if (m->has_ench(ENCH_STICKY_FLAME))
return (" (burning)");
if (m->has_ench(ENCH_ROT))
return (" (rotting)");
if (m->has_ench(ENCH_INVIS))
return (" (invisible)");
return ("");
}
void monster_info::to_string(int count, std::string& desc,
int& desc_color) const
{
std::ostringstream out;
if (count == 1)
{
if (mons_is_mimic(m_mon->type))
out << mons_type_name(m_mon->type, DESC_PLAIN);
else
out << m_mon->full_name(DESC_PLAIN);
}
else
{
// Don't pluralise uniques, ever. Multiple copies of the same unique
// are unlikely in the dungeon currently, but quite common in the
// arena. This prevens "4 Gra", etc. {due}
if (mons_is_unique(m_mon->type))
{
out << count << " "
<< m_mon->name(DESC_PLAIN);
}
// Don't differentiate between dancing weapons, mimics, (very)
// ugly things or draconians of different types.
else if (m_fullname
&& m_mon->type != MONS_DANCING_WEAPON
&& mons_genus(m_mon->type) != MONS_DRACONIAN
&& m_mon->type != MONS_UGLY_THING
&& m_mon->type != MONS_VERY_UGLY_THING
&& !mons_is_mimic(m_mon->type)
&& m_mon->mname.empty())
{
out << count << " "
<< pluralise(m_mon->name(DESC_PLAIN));
}
else if (m_mon->type >= MONS_DRACONIAN
&& m_mon->type <= MONS_PALE_DRACONIAN)
{
out << count << " "
<< pluralise(mons_type_name(MONS_DRACONIAN, DESC_PLAIN));
}
else
{
out << count << " "
<< pluralise(mons_type_name(m_mon->type, DESC_PLAIN));
}
}
#if DEBUG_DIAGNOSTICS
out << " av" << m_difficulty << " "
<< m_mon->hit_points << "/" << m_mon->max_hit_points;
#endif
if (count == 1)
{
if (m_mon->has_ench(ENCH_BERSERK))
out << " (berserk)";
else if (Options.verbose_monster_pane)
out << _verbose_info(m_mon);
else if (mons_looks_stabbable(m_mon))
out << " (resting)";
else if (mons_looks_distracted(m_mon))
out << " (distracted)";
else if (m_mon->has_ench(ENCH_INVIS))
out << " (invisible)";
}
// Friendliness
switch (m_attitude)
{
case ATT_FRIENDLY:
//out << " (friendly)";
desc_color = GREEN;
break;
case ATT_GOOD_NEUTRAL:
case ATT_NEUTRAL:
//out << " (neutral)";
desc_color = BROWN;
break;
case ATT_STRICT_NEUTRAL:
out << " (fellow slime)";
desc_color = BROWN;
break;
case ATT_HOSTILE:
// out << " (hostile)";
desc_color = LIGHTGREY;
break;
}
// Evilness of attacking
switch (m_attitude)
{
case ATT_NEUTRAL:
case ATT_HOSTILE:
if (count == 1 && you.religion == GOD_SHINING_ONE
&& !tso_unchivalric_attack_safe_monster(m_mon)
&& is_unchivalric_attack(&you, m_mon))
{
desc_color = Options.evil_colour;
}
break;
default:
break;
}
desc = out.str();
}
void get_monster_info(std::vector<monster_info>& mons)
{
std::vector<monsters*> visible = get_nearby_monsters();
for (unsigned int i = 0; i < visible.size(); i++)
{
if (Options.target_zero_exp
|| !mons_class_flag( visible[i]->type, M_NO_EXP_GAIN )
|| visible[i]->type == MONS_KRAKEN_TENTACLE)
{
mons.push_back(monster_info(visible[i]));
}
}
std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper);
}
// Get monsters via the monster_pane_info, sorted by difficulty.
std::vector<monster_pane_info> mons;
get_monster_pane_info(mons);
std::sort(mons.begin(), mons.end(), monster_pane_info::less_than_wrapper);
// Get monsters via the monster_info, sorted by difficulty.
std::vector<monster_info> mons;
get_monster_info(mons);
std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper);