effect only if compiled with -DDGAMELAUNCH.
Simple messaging: interacts with dgamelaunch's messaging facility allowing viewers to send messages to the player.
Milestones: Writes a milestones.txt file (in xlogfile format) for things like the player killing uniques, reaching the end of a dungeon branch, etc. (similar to notes). milestones.txt is used for game announcements by an IRC bot.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1095 c06c8d41-db1a-0410-9941-cceddc491573
TV3ZC6WOZKSQQJQN26JIVKCHK6UK7WMDBYZDUYRWEAZ4JB4YVNAAC
BIZDHHK5LIO57S5AKHEPJFLWV5DAFKZIKYBGOUNGICSWTX7DCXKAC
6L4EP4ZRWWYLT55PD5KTTJON5J2JB5VV5MWNHF5VPZQZ5BKEYZ4QC
DODCHP2S4I6VZKQAVXX6D76OPNFI2YWZ4XH3HZTMAJZXA2RJ3XRQC
TAVHZJPVNJBZR7CUURAOYNDZPNVQ3AGHTXDEP5K4JGYETBLQJDRQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
YIIILIV4ZPRIPWWT4GL7YWSJCUVH6RJJLXV4XIHY6SF3H7Y3EAYAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
GBUB77EAYHOFY6GQ5IY3ZSBC7FSQFZZKYNBD5QMCQFIKFLYLWHOQC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
43ZTEB57FU7KE5EVMYWZONNVJBZCGF3JEAJZIY25LC4LGE65PG5QC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
ZK5H5YBD3R7H3KKNKSJYE65TUEPPWIWUJLU567FKSZYQYIW6ZVFAC
74LQ7JXVLAFSHLI7LCBKFX47CNTYSKGUQSXNX5FCIUIGCC2JTR3QC
NNG27Y5ZQAZX6UD7F7M4F6KEZBEDFXPEEC3LFUSX4ESKT7K6UJQAC
CAHE52HL2ZGRJPBYZ3DS4BVKUD2XC7N3SG25TGG7JGHGJDST4P3QC
RISMOCQM6BKK4XSIRKYLOBB2UPDYJNDAL6OGIIR5GGNZQAK5YSZAC
7AMQN7MITMXBNVDAK5VOXTQ4TZIAOD6ZLOFJG7GQMBTY23Y2BKSAC
RIMJO42HB75SAZH44KZ2UH2QULOPIJLMEN4CR2CYNR2EKEKLFHSQC
3WRAJZ5ZLOSIZHFBUH5552QC4F3GAK7AXF6VIQFVN6VY7PUO6HPQC
void Note::save( FILE* fp ) const
{
if (br != -1)
{
std::string branch = place_name(packed_place, true, false).c_str();
if (branch.find("The ") == 0)
branch[0] = tolower(branch[0]);
if (dep == 1)
mark_milestone("enter", "entered " + branch + ".");
else if (dep == dungeon_branch_depth(br))
{
char branch_finale[500];
std::string level = place_name(packed_place, true, true);
if (level.find("Level ") == 0)
level[0] = tolower(level[0]);
snprintf(branch_finale, sizeof branch_finale,
"reached %s.", level.c_str());
mark_milestone("branch-finale", branch_finale);
}
}
}
#endif
}
void Note::save( FILE* fp ) const {
static void check_kill_milestone(const monsters *mons, int killer, int i)
{
if (mons->type == MONS_PLAYER_GHOST)
{
std::string milestone = milestone_kill_verb(killer) + "the ghost of ";
milestone += ghost_description(*mons, true);
milestone += ".";
mark_milestone("ghost", milestone);
}
else if (mons_is_unique(mons->type))
{
mark_milestone("unique",
milestone_kill_verb(killer)
+ mons->name(DESC_NOCAP_THE, true)
+ ".");
}
}
#endif // MILESTONES
static void milestone_check(const item_def &item)
{
if (item.base_type == OBJ_MISCELLANY
&& item.sub_type == MISC_RUNE_OF_ZOT)
{
mark_milestone("rune", milestone_rune(item));
}
else if (item.base_type == OBJ_ORBS && item.sub_type == ORB_ZOT)
{
mark_milestone("orb", "found the Orb of Zot!");
}
}
#endif // MILESTONES
fields->add_field("ktyp", ::kill_method_name(kill_method_type(death_type)));
fields->add_field("killer", death_source_desc());
fields->add_field("kaux", "%s", auxkilldata.c_str());
if (piety > 0)
fields->add_field("piety", "%d", piety);
if (penance > 0)
fields->add_field("pen", "%d", penance);
fields->add_field("nrune", "%d", num_runes);
fields->add_field("nrune", "%d", num_runes);
}
void scorefile_entry::set_score_fields() const
{
fields.reset(new xlog_fields);
if (!fields.get())
return;
set_base_xlog_fields();
fields->add_field("sc", "%ld", points);
fields->add_field("ktyp", ::kill_method_name(kill_method_type(death_type)));
fields->add_field("killer", death_source_desc());
fields->add_field("kaux", "%s", auxkilldata.c_str());
if (piety > 0)
fields->add_field("piety", "%d", piety);
if (penance > 0)
fields->add_field("pen", "%d", penance);
fields->add_field("end", "%s", make_date_string(death_time).c_str());
if (you.inv[d].base_type == OBJ_MISCELLANY
&& you.inv[d].sub_type == MISC_RUNE_OF_ZOT)
{
if (rune_array[ you.inv[d].plus ] == 0)
num_diff_runes++;
if (you.inv[d].base_type == OBJ_MISCELLANY
&& you.inv[d].sub_type == MISC_RUNE_OF_ZOT)
{
if (rune_array[ you.inv[d].plus ] == 0)
num_diff_runes++;
// Bonus for exploring different areas, not for collecting a
// huge stack of demonic runes in Pandemonium (gold value
// is enough for those). -- bwr
if (num_diff_runes >= 3)
points += ((num_diff_runes + 2) * (num_diff_runes + 2) * 1000);
}
// Bonus for exploring different areas, not for collecting a
// huge stack of demonic runes in Pandemonium (gold value
// is enough for those). -- bwr
if (calc_item_values && num_diff_runes >= 3)
points += ((num_diff_runes + 2) * (num_diff_runes + 2) * 1000);
///////////////////////////////////////////////////////////////////////////////
// Milestones
#ifdef MILESTONES
void mark_milestone(const std::string &type, const std::string &milestone)
{
const std::string milestone_file = Options.save_dir + "milestones.txt";
if (FILE *fp = lk_open("a", milestone_file))
{
const scorefile_entry se(0, 0, KILL_MISC, NULL);
se.set_base_xlog_fields();
xlog_fields xl = *se.fields;
xl.add_field("time", "%s", make_date_string(se.death_time).c_str());
xl.add_field("type", "%s", type.c_str());
xl.add_field("milestone", "%s", milestone.c_str());
fprintf(fp, "%s\n", xl.xlog_line().c_str());
lk_close(fp, "a", milestone_file);
}
}
#endif // MILESTONES
char *home; // only used by MULTIUSER systems
bool board_with_nail; // Easter Egg silliness
char *home; // only used by MULTIUSER systems
bool board_with_nail; // Easter Egg silliness
#ifdef SIMPLE_MESSAGING
std::string messagefile; // File containing messages from other users.
bool have_messages; // There are messages waiting to be read.
unsigned message_check_tick;
#endif
return (random2(attack) >= random2avg(defence, 2));
return (attack == AUTOMATIC_HIT
|| random2(attack) >= random2avg(defence, 2));
}
static std::string beam_zapper(const bolt &beam)
{
const int bsrc = beam_source(beam);
if (bsrc == MHITYOU)
return ("self");
else if (bsrc == MHITNOT)
return ("");
else
return ptr_monam( &menv[bsrc], DESC_PLAIN );
#ifdef MILESTONES
// Not entirely accurate - the player could die before reaching the Abyss.
if (grd[you.x_pos][you.y_pos] == DNGN_ENTER_ABYSS)
mark_milestone("abyss.enter", "entered the Abyss!");
else if (grd[you.x_pos][you.y_pos] == DNGN_EXIT_ABYSS)
mark_milestone("abyss.exit", "escaped from the Abyss!");
#endif
}
#ifdef SIMPLE_MESSAGING
static struct stat mfilestat;
static void show_message_line(std::string line)
{
const std::string::size_type sender_pos = line.find(":");
if (sender_pos == std::string::npos)
mpr(line.c_str());
else
{
std::string sender = line.substr(0, sender_pos);
line = line.substr(sender_pos + 1);
trim_string(line);
// XXX: Eventually fix mpr so it can do a different colour for
// the sender.
mprf("%s: %s", sender.c_str(), line.c_str());
}
static void read_each_message()
{
bool say_got_msg = true;
FILE *mf = fopen(SysEnv.messagefile.c_str(), "r+");
if (!mf)
{
mprf(MSGCH_WARN, "Couldn't read %s: %s", SysEnv.messagefile.c_str(),
strerror(errno));
goto kill_messaging;
}
// Read messages, code borrowed from the SIMPLEMAIL patch.
char line[120];
if (!lock_file_handle(mf, F_RDLCK))
{
mprf(MSGCH_WARN, "Failed to lock %s: %s", SysEnv.messagefile.c_str(),
strerror(errno));
goto kill_messaging;
}
while (fgets(line, sizeof line, mf))
{
unlock_file_handle(mf);
const int len = strlen(line);
if (len)
{
if (line[len - 1] == '\n')
line[len - 1] = 0;
if (say_got_msg)
{
mprf(MSGCH_PROMPT, "Your messages:");
say_got_msg = false;
}
show_message_line(line);
}
if (!lock_file_handle(mf, F_RDLCK))
{
mprf(MSGCH_WARN, "Failed to lock %s: %s",
SysEnv.messagefile.c_str(),
strerror(errno));
goto kill_messaging;
}
}
if (!lock_file_handle(mf, F_WRLCK))
{
mprf(MSGCH_WARN, "Unable to write lock %s: %s",
SysEnv.messagefile.c_str(),
strerror(errno));
}
if (!ftruncate(fileno(mf), 0))
mfilestat.st_mtime = 0;
unlock_file_handle(mf);
fclose(mf);
SysEnv.have_messages = false;
return;
kill_messaging:
if (mf)
fclose(mf);
SysEnv.have_messages = false;
Options.messaging = false;
}
static void read_messages()
{
read_each_message();
update_message_status();
}
static void announce_messages()
{
// XXX: We could do a NetHack-like mail daemon here at some point.
mprf("Beep! Your pager goes off! Use _ to check your messages.");
}
static void check_messages()
{
if (!Options.messaging
|| SysEnv.have_messages
|| SysEnv.messagefile.empty()
|| kbhit()
|| (SysEnv.message_check_tick++ % MESSAGE_CHECK_INTERVAL))
{
return;
}
const bool had_messages = SysEnv.have_messages;
struct stat st;
if (stat(SysEnv.messagefile.c_str(), &st))
{
mfilestat.st_mtime = 0;
return;
}
if (st.st_mtime > mfilestat.st_mtime)
{
if (st.st_size)
SysEnv.have_messages = true;
mfilestat.st_mtime = st.st_mtime;
}
if (SysEnv.have_messages && !had_messages)
{
announce_messages();
update_message_status();
}
}
#endif
// How often we check for messages. This is not once per turn, but once
// per player-input. Message checks are not performed if the keyboard
// buffer is full, so messages should not interrupt macros.
#define MESSAGE_CHECK_INTERVAL 1
// Record game milestones in an xlogfile.
#define MILESTONES
// ===========================================================================
// Misc
// ===========================================================================
#if HAS_NAMESPACES
using namespace std;
#if defined(SIMPLE_MESSAGING) && !defined(USE_FILE_LOCKING)
# error Must define USE_FILE_LOCKING for SIMPLE_MESSAGING