reading a blessed scroll of light has a chance to improve bless/curse state
of wielded Sunsword or worn gold dragon scales/mail similar to dipping
those into holy water; cursed scroll has chance to worsen the state
+added a chronicle of major events, and optional live logging of those
Platform- and/or Interface-Specific New Features
/*
* Section 2: Some global parameters and filenames.
*
- * LOGFILE, XLOGFILE, NEWS and PANICLOG refer to files in
- * the playground directory. Commenting out LOGFILE, XLOGFILE,
- * NEWS or PANICLOG removes that feature from the game.
+ * LOGFILE, XLOGFILE, LIVELOGFILE, NEWS and PANICLOG refer to
+ * files in the playground directory. Commenting out LOGFILE,
+ * XLOGFILE, NEWS or PANICLOG removes that feature from the game.
*
* Building with debugging features enabled is now unconditional;
* the old WIZARD setting for that has been eliminated.
whole thing, then type a new end for the text. */
/* #define EDIT_GETLIN */
+#ifndef NO_CHRONICLE
+/* CHRONICLE - enable #chronicle command, a log of major game events.
+ The logged messages will also appear in DUMPLOG. */
+#define CHRONICLE
+#ifdef CHRONICLE
+/* LIVELOG - log CHRONICLE events into LIVELOGFILE as they happen. */
+/* #define LIVELOG */
+#ifdef LIVELOG
+#define LIVELOGFILE "livelog" /* in-game events recorded, live */
+#endif /* LIVELOG */
+#endif /* CHRONICLE */
+#else
+#undef LIVELOG
+#endif /* NO_CHRONICLE */
+
/* #define DUMPLOG */ /* End-of-game dump logs */
#ifdef DUMPLOG
char name[BUFSZ]; /* actual killer name */
};
+/* game events log */
+struct gamelog_line {
+ long turn; /* turn when this happened */
+ long flags; /* LL_foo flags */
+ char *text;
+ struct gamelog_line *next;
+};
+
enum movemodes {
MV_ANY = -1,
MV_WALK,
/* work buffer for You(), &c and verbalize() */
char *you_buf;
int you_buf_siz;
+ struct gamelog_line *gamelog;
/* polyself.c */
int sex_change_ok; /* controls whether taking on new form or becoming new
extern int doextlist(void);
extern int extcmd_via_menu(void);
extern int enter_explore_mode(void);
+extern int do_gamelog(void);
extern boolean bind_key(uchar, const char *);
extern void dokeylist(void);
extern int xytod(schar, schar);
extern boolean read_tribute(const char *, const char *, int, char *, int,
unsigned);
extern boolean Death_quote(char *, int);
+extern void livelog_add(unsigned int ll_type, const char *);
/* ### fountain.c ### */
extern void pline_The(const char *, ...) PRINTF_F(1, 2);
extern void There(const char *, ...) PRINTF_F(1, 2);
extern void verbalize(const char *, ...) PRINTF_F(1, 2);
+extern void gamelog_add(unsigned int, long, const char *);
+extern void livelog_printf(unsigned int, const char *, ...) PRINTF_F(2, 3);
extern void raw_printf(const char *, ...) PRINTF_F(1, 2);
extern void impossible(const char *, ...) PRINTF_F(1, 2);
extern void config_error_add(const char *, ...) PRINTF_F(1, 2);
#define unctrl(c) ((c) <= C('z') ? (0x60 | (c)) : (c))
#define unmeta(c) (0x7f & (c))
+/* Game log message type flags */
+#define LL_NONE 0x0000 /* No message is livelogged */
+#define LL_WISH 0x0001 /* Report stuff people type at the wish prompt */
+#define LL_ACHIEVE 0x0002 /* Achievements bitfield + invocation, planes */
+#define LL_UMONST 0x0004 /* Kill, Bribe or otherwise dispatch unique monsters */
+#define LL_DIVINEGIFT 0x0008 /* Sacrifice gifts, crowning */
+#define LL_LIFESAVE 0x0010 /* Use up amulet of lifesaving */
+#define LL_CONDUCT 0x0020 /* Break conduct - not reported early-game */
+#define LL_ARTIFACT 0x0040 /* Excalibur, Sting, Orcrist, plus sac gifts and artwishes */
+#define LL_GENOCIDE 0x0080 /* Logging of genocides */
+#define LL_KILLEDPET 0x0100 /* Killed a tame monster */
+#define LL_ALIGNMENT 0x0200 /* changed alignment temporarily or permanently */
+#define LL_DUMP_ASC 0x0400 /* Log URL for dumplog if ascended */
+#define LL_DUMP_ALL 0x0800 /* Log dumplog url for all games */
+#define LL_MINORAC 0x1000 /* Log 'minor' achievements - can be spammy */
+#define LL_DEBUG 0x8000 /* For debugging messages and other spam */
+
#endif /* GLOBAL_H */
* Incrementing EDITLEVEL can be used to force invalidation of old bones
* and save files.
*/
-#define EDITLEVEL 47
+#define EDITLEVEL 48
/*
* Development status possibilities.
int check_save_uid; /* restoring savefile checks UID? */
int check_plname; /* use plname for checking wizards/explorers/shellers */
int bones_pools;
+ unsigned int livelog; /* LL_foo events to livelog */
/* record file */
int persmax;
g.context.botl = TRUE; /* status line needs updating */
if (reason == 0) {
/* conversion via altar */
+ livelog_printf(LL_ALIGNMENT, "permanently converted to %s",
+ aligns[1 - newalign].adj);
u.ualignbase[A_CURRENT] = (aligntyp) newalign;
/* worn helm of opposite alignment might block change */
if (!uarmh || uarmh->otyp != HELM_OF_OPPOSITE_ALIGNMENT)
(u.ualign.type != oldalign) ? "sudden " : "");
} else {
/* putting on or taking off a helm of opposite alignment */
+ if (reason == 1) {
+ /* don't livelog taking it back off */
+ livelog_printf(LL_ALIGNMENT, "used a helm to turn %s",
+ aligns[1 - newalign].adj);
+ }
u.ualign.type = (aligntyp) newalign;
if (reason == 1)
Your("mind oscillates %s.", Hallucination ? "wildly" : "briefly");
return ECMD_OK;
}
+int
+do_gamelog(void)
+{
+#ifdef CHRONICLE
+ struct gamelog_line *tmp = g.gamelog;
+ winid win;
+ char buf[BUFSZ];
+
+ if (!tmp) {
+ pline("No chronicled events.");
+ return ECMD_OK;
+ }
+
+ win = create_nhwindow(NHW_TEXT);
+ putstr(win, 0, "Major events:");
+ putstr(win, 0, "");
+ putstr(win, 0, " Turn");
+ while (tmp) {
+ Sprintf(buf, "%5li: %s", tmp->turn, tmp->text);
+ putstr(win, 0, buf);
+ tmp = tmp->next;
+ }
+ display_nhwindow(win, TRUE);
+ destroy_nhwindow(win);
+#else
+ pline("Chronicle was turned off during compile-time.");
+#endif /* !CHRONICLE */
+ return ECMD_OK;
+}
+
/* #wizwish command - wish for something */
static int
wiz_wish(void) /* Unlimited wishes for debug mode by Paul Polderman */
docast, IFBURIED, NULL },
{ M('c'), "chat", "talk to someone",
dotalk, IFBURIED | AUTOCOMPLETE, NULL },
+ { '\0', "chronicle", "show journal of major events",
+ do_gamelog, IFBURIED | GENERALCMD, NULL },
{ 'c', "close", "close a door",
doclose, 0, NULL },
{ M('C'), "conduct", "list voluntary challenges you have maintained",
#endif
(char *) 0, /* you_buf */
0, /* you_buf_siz */
+ NULL, /* gamelog */
/* polyself.c */
0, /* sex_change_ok */
if (obj->oclass != COIN_CLASS) {
/* KMH, conduct */
- u.uconduct.gnostic++;
+ if (!u.uconduct.gnostic++)
+ livelog_printf(LL_CONDUCT,
+ "eschewed atheism, by dropping %s on an altar",
+ doname(obj));
} else {
/* coins don't have bless/curse status */
obj->blessed = obj->cursed = 0;
}
mklev();
new = TRUE; /* made the level */
+ livelog_printf(LL_DEBUG, "entered new level %d, %s",
+ dunlev(&u.uz), g.dungeons[u.uz.dnum].dname);
familiar = bones_include_name(g.plname);
} else {
alter_cost(obj, 0L);
if (g.via_naming) {
/* violate illiteracy conduct since successfully wrote arti-name */
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT | LL_ARTIFACT,
+ "became literate by naming %s",
+ bare_artifactname(obj));
+ else
+ livelog_printf(LL_ARTIFACT,
+ "chose %s to be named \"%s\"",
+ ansimpleoname(obj), bare_artifactname(obj));
}
}
if (carried(obj))
/* attack hits mon */
if (hmode == HMON_APPLIED)
- u.uconduct.weaphit++;
+ if (!u.uconduct.weaphit++)
+ livelog_printf(LL_CONDUCT, "hit with a wielded weapon for the first time");
if (hmon(mon, obj, hmode, dieroll)) { /* mon still alive */
if (mon->wormno)
cutworm(mon, g.bhitpos.x, g.bhitpos.y, chopper);
void
eating_conducts(struct permonst *pd)
{
- u.uconduct.food++;
- if (!vegan(pd))
- u.uconduct.unvegan++;
- if (!vegetarian(pd))
+ int ll_conduct = 0;
+
+ if (!u.uconduct.food++) {
+ livelog_printf(LL_CONDUCT, "ate for the first time - %s",
+ pd->pmnames[NEUTRAL]);
+ ll_conduct++;
+ }
+ if (!vegan(pd)) {
+ if (!u.uconduct.unvegan++ && !ll_conduct) {
+ livelog_printf(LL_CONDUCT,
+ "consumed animal products (%s) for the first time",
+ pd->pmnames[NEUTRAL]);
+ ll_conduct++;
+ }
+ }
+ if (!vegetarian(pd)) {
+ if (!u.uconduct.unvegetarian && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "tasted meat (%s) for the first time",
+ pd->pmnames[NEUTRAL]);
violated_vegetarian();
+ }
}
/* handle side-effects of mind flayer's tentacle attack */
if (g.youmonst.data->mlet != S_MIMIC && !Unchanging) {
char buf[BUFSZ];
- u.uconduct.polyselfs++; /* you're changing form */
+ if (!u.uconduct.polyselfs++) /* you're changing form */
+ livelog_printf(LL_CONDUCT,
+ "changed form for the first time by mimicking %s",
+ Hallucination ? "an orange" : "a pile of gold");
You_cant("resist the temptation to mimic %s.",
Hallucination ? "an orange" : "a pile of gold");
/* A pile of gold can't ride. */
* Same order as with non-spinach above:
* conduct update, side-effects, shop handling, and nutrition.
*/
- u.uconduct.food++; /* don't need vegetarian checks for spinach */
+ /* don't need vegetarian checks for spinach */
+ if (!u.uconduct.food++)
+ livelog_printf(LL_CONDUCT, "ate for the first time (spinach)");
if (!tin->cursed)
pline("This makes you feel like %s!",
/* "Swee'pea" is a character from the Popeye cartoons */
{
int retcode = 0, tp = 0, mnum = otmp->corpsenm;
long rotted = 0L;
+ int ll_conduct = 0;
boolean stoneable = (flesh_petrifies(&mons[mnum]) && !Stone_resistance
&& !poly_when_stoned(g.youmonst.data)),
slimeable = (mnum == PM_GREEN_SLIME && !Slimed && !Unchanging
/* KMH, conduct */
if (!vegan(&mons[mnum]))
- u.uconduct.unvegan++;
- if (!vegetarian(&mons[mnum]))
+ if (!u.uconduct.unvegan++) {
+ livelog_printf(LL_CONDUCT,
+ "consumed animal products for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
+ ll_conduct++;
+ }
+ if (!vegetarian(&mons[mnum])) {
+ if (!u.uconduct.unvegetarian && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "tasted meat for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
violated_vegetarian();
-
+ }
if (!nonrotting_corpse(mnum)) {
long age = peek_at_iced_corpse_age(otmp);
case FORTUNE_COOKIE:
outrumor(bcsign(otmp), BY_COOKIE);
if (!Blind)
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by reading the fortune inside a cookie");
break;
case LUMP_OF_ROYAL_JELLY:
/* This stuff seems to be VERY healthy! */
int basenutrit; /* nutrition of full item */
boolean dont_start = FALSE, nodelicious = FALSE,
already_partly_eaten;
+ int ll_conduct = 0;
if (Strangled) {
pline("If you can't breathe air, how can you consume solids?");
g.context.victual.nmod = basenutrit;
g.context.victual.eating = TRUE; /* needed for lesshungry() */
+ if (!u.uconduct.food++) {
+ ll_conduct++;
+ livelog_printf(LL_CONDUCT, "ate for the first time (%s)",
+ food_xname(otmp, FALSE));
+ }
material = objects[otmp->otyp].oc_material;
if (material == LEATHER || material == BONE
|| material == DRAGON_HIDE) {
- u.uconduct.unvegan++;
+ if (!u.uconduct.unvegan++ && !ll_conduct) {
+ livelog_printf(LL_CONDUCT, "consumed animal products for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
+ ll_conduct++;
+ }
+ if (!u.uconduct.unvegetarian && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "tasted meat for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
violated_vegetarian();
} else if (material == WAX)
- u.uconduct.unvegan++;
- u.uconduct.food++;
+ if (!u.uconduct.unvegan++ && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "consumed animal products for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
if (otmp->cursed) {
(void) rottenfood(otmp);
}
/* KMH, conduct */
- u.uconduct.food++;
+ if (!u.uconduct.food++) {
+ livelog_printf(LL_CONDUCT, "ate for the first time - %s", food_xname(otmp, FALSE));
+ ll_conduct++;
+ }
already_partly_eaten = otmp->oeaten ? TRUE : FALSE;
g.context.victual.piece = otmp = touchfood(otmp);
*/
switch (objects[otmp->otyp].oc_material) {
case FLESH:
- u.uconduct.unvegan++;
+ if (!u.uconduct.unvegan++ && !ll_conduct) {
+ livelog_printf(LL_CONDUCT, "consumed animal products for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
+ ll_conduct++;
+ }
if (otmp->otyp != EGG) {
+ if (!u.uconduct.unvegetarian && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "tasted meat for the first time, by eating %s",
+ an(food_xname(otmp, FALSE)));
+
violated_vegetarian();
}
break;
if (otmp->otyp == PANCAKE || otmp->otyp == FORTUNE_COOKIE /*eggs*/
|| otmp->otyp == CREAM_PIE || otmp->otyp == CANDY_BAR /*milk*/
|| otmp->otyp == LUMP_OF_ROYAL_JELLY)
- u.uconduct.unvegan++;
+ if (!u.uconduct.unvegan++ && !ll_conduct)
+ livelog_printf(LL_CONDUCT, "consumed animal products (%s) for the first time",
+ food_xname(otmp, FALSE));
break;
}
dump_plines();
putstr(0, 0, "");
+ (void) do_gamelog();
+ putstr(0, 0, "");
putstr(0, 0, "Inventory:");
(void) display_inventory((char *) 0, TRUE);
container_contents(g.invent, TRUE, TRUE, FALSE);
if (how == GENOCIDED) {
pline("Unfortunately you are still genocided...");
} else {
+ char killbuf[BUFSZ];
+ formatkiller(killbuf, BUFSZ, how, FALSE);
+ livelog_printf(LL_LIFESAVE, "averted death (%s)", killbuf);
survive = TRUE;
}
}
/* A single `x' is the traditional signature of an illiterate person */
if (len != 1 || (!index(ebuf, 'x') && !index(ebuf, 'X')))
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by engraving \"%s\"", ebuf);
/* Mix up engraving if surface or state of mind is unsound.
Note: this won't add or remove any spaces. */
n = 10;
}
sysopt.tt_oname_maxrank = n;
+ } else if (src == set_in_sysconf && match_varname(buf, "LIVELOG", 7)) {
+ n = strtol(bufp,NULL,0);
+ if (n < 0 || n > 0xFFFF) {
+ raw_printf("Illegal value in LIVELOG (must be between 0 and 0xFFFF).");
+ return 0;
+ }
+ sysopt.livelog = n;
/* SYSCF PANICTRACE options */
} else if (in_sysconf && match_varname(buf, "PANICTRACE_LIBC", 15)) {
/* ---------- END TRIBUTE ----------- */
+#if defined LIVELOG
+#define LLOG_SEP '\t' /* livelog field separator */
+
+/* Locks the live log file and writes 'buffer'
+ * IF the ll_type matches sysopt.livelog mask
+ * lltype is included in LL entry for post-process filtering also
+ */
+void
+livelog_add(unsigned int ll_type, const char *str)
+{
+ FILE* livelogfile;
+
+ if (!(ll_type & sysopt.livelog))
+ return;
+ if (lock_file(LIVELOGFILE, SCOREPREFIX, 10)) {
+ if (!(livelogfile = fopen_datafile(LIVELOGFILE, "a", SCOREPREFIX))) {
+ pline("Cannot open live log file!");
+ unlock_file(LIVELOGFILE);
+ return;
+ }
+ fprintf(livelogfile,
+ "lltype=%d%cname=%s%crole=%s%crace=%s%cgender=%s%c"
+ "align=%s%cturns=%ld%cstarttime=%ld%ccurtime=%ld%c"
+ "message=%s\n",
+ (ll_type & sysopt.livelog), LLOG_SEP,
+ g.plname, LLOG_SEP,
+ g.urole.filecode, LLOG_SEP,
+ g.urace.filecode, LLOG_SEP,
+ genders[flags.female].filecode, LLOG_SEP,
+ aligns[1-u.ualign.type].filecode, LLOG_SEP,
+ g.moves, LLOG_SEP,
+ (long)ubirthday, LLOG_SEP,
+ (long)time(NULL),
+ LLOG_SEP, str);
+ (void) fclose(livelogfile);
+ unlock_file(LIVELOGFILE);
+ }
+}
+#undef LLOG_SEP
+
+#else
+void
+livelog_add(unsigned int ll_type UNUSED, const char *str UNUSED)
+{
+ /* nothing here */
+}
+#endif /* !LIVELOG */
+
/*files.c*/
obj->spe--;
obj->oerodeproof = FALSE;
exercise(A_WIS, FALSE);
+ livelog_printf(LL_ARTIFACT, "was denied Excalibur! The Lady of the Lake has deemed %s unworthy", uhim());
} else {
/* The lady of the lake acts! - Eric Backus */
/* Be *REAL* nice */
obj->oeroded = obj->oeroded2 = 0;
obj->oerodeproof = TRUE;
exercise(A_WIS, TRUE);
+ livelog_printf(LL_ARTIFACT, "was given Excalibur");
}
update_inventory();
levl[u.ux][u.uy].typ = ROOM, levl[u.ux][u.uy].flags = 0;
}
/* Okay, you've chewed through something */
- u.uconduct.food++;
+ if (!u.uconduct.food++)
+ livelog_printf(LL_CONDUCT, "ate for the first time, by chewing through %s",
+ boulder
+ ? "a boulder"
+ : IS_TREE(lev->typ)
+ ? "a tree"
+ : IS_ROCK(lev->typ)
+ ? "rock"
+ : (lev->typ == IRONBARS)
+ ? "iron bars"
+ : "a door");
u.uhunger += rnd(20);
if (boulder) {
killed() so we duplicate some of the latter here */
int tmp, mndx;
- u.uconduct.killer++;
+ if (!u.uconduct.killer++)
+ livelog_printf(LL_CONDUCT, "killed for the first time");
mndx = monsndx(mtmp->data);
tmp = experience(mtmp, (int) g.mvitals[mndx].died);
more_experienced(tmp, 0);
static const char have_been[] = "have been ", have_never[] = "have never ",
never[] = "never ";
+/* for livelogging: */
+struct ll_achieve_msg {
+ unsigned long llflag;
+ const char *msg;
+};
+/* ordered per 'enum achievements' in you.h */
+/* take care to keep them in sync! */
+static struct ll_achieve_msg achieve_msg [] = {
+ { 0, "" }, /* actual achievements are numbered from 1 */
+ { LL_ACHIEVE, "acquired the Bell of Opening" },
+ { LL_ACHIEVE, "entered Gehennom" },
+ { LL_ACHIEVE, "acquired the Candelabrum of Invocation" },
+ { LL_ACHIEVE, "acquired the Book of the Dead" },
+ { LL_ACHIEVE, "performed the invocation" },
+ { LL_ACHIEVE, "acquired The Amulet of Yendor" },
+ { LL_ACHIEVE, "entered the Planes" },
+ { LL_ACHIEVE, "entered the Astral Plane" },
+ { LL_ACHIEVE, "ascended" },
+ { LL_ACHIEVE, "acquired the Mines' End luckstone" },
+ { LL_ACHIEVE, "completed Sokoban" },
+ { LL_ACHIEVE|LL_UMONST, "killed Medusa" },
+ /* these two are not logged */
+ { 0, "hero was always blond, no, blind" },
+ { 0, "hero never wore armor" },
+ /* */
+ { LL_MINORAC, "entered the Gnomish Mines" },
+ { LL_ACHIEVE, "reached Mine Town" }, /* probably minor, but dnh logs it */
+ { LL_MINORAC, "entered a shop" },
+ { LL_MINORAC, "entered a temple" },
+ { LL_ACHIEVE, "consulted the Oracle" }, /* minor, but rare enough */
+ { LL_ACHIEVE, "read a Discworld novel" }, /* ditto */
+ { LL_ACHIEVE, "entered Sokoban" }, /* Keep as major for turn comparison w/completed soko */
+ { LL_ACHIEVE, "entered the Bigroom" },
+ /* The following 8 are for advancing through the ranks
+ messages differ by role so are created on the fly */
+ { LL_MINORAC, "" },
+ { LL_MINORAC, "" },
+ { LL_MINORAC, "" },
+ { LL_MINORAC, "" },
+ { LL_ACHIEVE, "" },
+ { LL_ACHIEVE, "" },
+ { LL_ACHIEVE, "" },
+ { LL_ACHIEVE, "" },
+ { 0, "" } /* keep this one at the end */
+};
+
+
#define enl_msg(prefix, present, past, suffix, ps) \
enlght_line(prefix, final ? past : present, suffix, ps)
#define you_are(attr, ps) enl_msg(You_, are, were, attr, ps)
if (abs(u.uachieved[i]) == abs(achidx))
return; /* already recorded, don't duplicate it */
u.uachieved[i] = achidx;
- return;
+
+ if (g.program_state.gameover)
+ return; /* don't livelog achievements recorded at end of game */
+ if (absidx >= ACH_RNK1 && absidx <= ACH_RNK8) {
+ livelog_printf(achieve_msg[absidx].llflag, "attained the rank of %s",
+ rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
+ Role_switch, (achidx < 0) ? TRUE : FALSE));
+ } else
+ livelog_printf(achieve_msg[absidx].llflag, "%s", achieve_msg[absidx].msg);
}
/* discard a recorded achievement; return True if removed, False otherwise */
if (!Deaf && ((offer = bribe(mtmp)) >= demand)) {
pline("%s vanishes, laughing about cowardly mortals.",
Amonnam(mtmp));
+ livelog_printf(LL_UMONST, "bribed %s with %ld %s for safe passage",
+ Amonnam(mtmp), offer, currency(offer));
} else if (offer > 0L
&& (long) rnd(5 * ACURR(A_CHA)) > (demand - offer)) {
pline("%s scowls at you menacingly, then vanishes.",
Amonnam(mtmp));
+ livelog_printf(LL_UMONST, "bribed %s with %ld %s for safe passage",
+ Amonnam(mtmp), offer, currency(offer));
} else {
pline("%s gets angry...", Amonnam(mtmp));
mtmp->mpeaceful = 0;
(Is_rogue_level(&u.uz) \
|| (g.level.flags.graveyard && is_undead(mdat) && rn2(3)))
+/* A specific combination of x_monnam flags for livelogging. The livelog
+ * shouldn't show that you killed a hallucinatory monster and not what it
+ * actually is. */
+#define livelog_mon_nam(mtmp) \
+ x_monnam(mtmp, ARTICLE_THE, (char *) 0, \
+ (SUPPRESS_IT | SUPPRESS_HALLUCINATION), FALSE)
+
+
#if 0
/* part of the original warning code which was replaced in 3.3.1 */
const char *warnings[] = {
#endif
if (mtmp->data == &mons[PM_MEDUSA])
record_achievement(ACH_MEDU);
+ else if (unique_corpstat(mtmp->data)) {
+ switch (g.mvitals[tmp].died) {
+ case 1:
+ livelog_printf(LL_UMONST, "%s %s",
+ nonliving(mtmp->data) ? "destroyed" : "killed",
+ livelog_mon_nam(mtmp));
+ break;
+ case 5:
+ case 10:
+ case 50:
+ case 100:
+ case 150:
+ case 200:
+ case 250:
+ livelog_printf(LL_UMONST, "%s %s (%d times)",
+ nonliving(mtmp->data) ? "destroyed" : "killed",
+ livelog_mon_nam(mtmp), g.mvitals[tmp].died);
+ break;
+ default:
+ /* don't spam the log every time */
+ break;
+ }
+ }
+
if (glyph_is_invisible(levl[mtmp->mx][mtmp->my].glyph))
unmap_object(mtmp->mx, mtmp->my);
m_detach(mtmp, mptr);
mtmp->mhp = 0; /* caller will usually have already done this */
if (!noconduct) /* KMH, conduct */
- u.uconduct.killer++;
+ if (!u.uconduct.killer++)
+ livelog_printf(LL_CONDUCT, "killed for the first time");
if (!nomsg) {
boolean namedpet = has_mgivenname(mtmp) && !Hallucination;
You_hear("the rumble of distant thunder...");
else
You_hear("the studio audience applaud!");
+ if (!unique_corpstat(mdat)) {
+ boolean mname = has_mgivenname(mtmp);
+
+ livelog_printf(LL_KILLEDPET, "murdered %s%s%s faithful %s",
+ mname ? MGIVENNAME(mtmp) : "",
+ mname ? ", " : "",
+ uhis(), pmname(mdat, Mgender(mtmp)));
+ }
} else if (mtmp->mpeaceful)
adjalign(-5);
if (obj->otyp == BAG_OF_HOLDING) /* one bag of holding into another */
do_boh_explosion(obj, (obj->where == OBJ_FLOOR));
obfree(obj, (struct obj *) 0);
+ livelog_printf(LL_ACHIEVE, "just blew up %s bag of holding", uhis());
/* if carried, shop goods will be flagged 'unpaid' and obfree() will
handle bill issues, but if on floor, we need to put them on bill
before deleting them (non-shop items will be flagged 'no_charge') */
va_end(the_args);
}
+#ifdef CHRONICLE
+
+void
+gamelog_add(unsigned int glflags, long gltime, const char *str)
+{
+ struct gamelog_line *tmp;
+ struct gamelog_line *lst = g.gamelog;
+
+ tmp = (struct gamelog_line *)alloc(sizeof(struct gamelog_line));
+ tmp->turn = gltime;
+ tmp->flags = glflags;
+ tmp->text = strdup(str);
+ tmp->next = NULL;
+ while (lst && lst->next)
+ lst = lst->next;
+ if (!lst)
+ g.gamelog = tmp;
+ else
+ lst->next = tmp;
+}
+
+void
+livelog_printf(unsigned int ll_type, const char *line, ...)
+{
+ char gamelogbuf[BUFSZ * 2];
+ va_list the_args;
+
+ va_start(the_args, line);
+ vsnprintf(gamelogbuf, sizeof gamelogbuf, line, the_args);
+ va_end(the_args);
+
+ gamelog_add(ll_type, g.moves, gamelogbuf);
+ strNsubst(gamelogbuf, "\t", "_", 0);
+ livelog_add(ll_type, gamelogbuf);
+}
+
+#else
+void
+gamelog_add(unsigned int glflags UNUSED, long gltime UNUSED, const char *msg UNUSED)
+{
+ /* nothing here */
+}
+void
+livelog_printf(unsigned int ll_type UNUSED, const char *line UNUSED, ...)
+{
+ /* nothing here */
+}
+#endif /* !CHRONICLE */
+
static void vraw_printf(const char *, va_list);
void
}
/* KMH, conduct */
- u.uconduct.polyselfs++;
+ if (!u.uconduct.polyselfs++)
+ livelog_printf(LL_CONDUCT,
+ "changed form for the first time, becoming %s",
+ an(pmname(&mons[mntmp], flags.female ? FEMALE : MALE)));
/* exercise used to be at the very end but only Wis was affected
there since the polymorph was always in effect by then */
short save_otyp = obj->otyp;
/* KMH, conduct */
- u.uconduct.polypiles++;
+ if (!u.uconduct.polypiles++)
+ livelog_printf(LL_CONDUCT, "polymorphed %s first item", uhis());
obj = poly_obj(obj, STRANGE_OBJECT);
case A_LAWFUL:
u.uevent.uhand_of_elbereth = 1;
verbalize("I crown thee... The Hand of Elbereth!");
+ livelog_printf(LL_DIVINEGIFT,
+ "was crowned \"The Hand of Elbereth\" by %s", u_gname());
break;
case A_NEUTRAL:
u.uevent.uhand_of_elbereth = 2;
already_exists =
exist_artifact(LONG_SWORD, artiname(ART_VORPAL_BLADE));
verbalize("Thou shalt be my Envoy of Balance!");
+ livelog_printf(LL_DIVINEGIFT, "became %s Envoy of Balance",
+ s_suffix(u_gname()));
break;
case A_CHAOTIC:
u.uevent.uhand_of_elbereth = 3;
((already_exists && !in_hand)
|| class_gift != STRANGE_OBJECT) ? "take lives"
: "steal souls");
+ livelog_printf(LL_DIVINEGIFT, "was chosen to %s for the Glory of %s",
+ ((already_exists && !in_hand)
+ || class_gift != STRANGE_OBJECT) ? "take lives"
+ : "steal souls",
+ u_gname());
break;
}
struct monst *mtmp;
/* KMH, conduct */
- u.uconduct.gnostic++;
+ if (!u.uconduct.gnostic++)
+ livelog_printf(LL_CONDUCT,
+ "rejected atheism by offering %s on an altar of %s",
+ corpse_xname(otmp, (const char *) 0, CXN_ARTICLE),
+ a_gname());
/* you're handling this corpse, even if it was killed upon the altar
*/
u.ugifts++;
u.ublesscnt = rnz(300 + (50 * nartifacts));
exercise(A_WIS, TRUE);
+ livelog_printf (LL_DIVINEGIFT|LL_ARTIFACT,
+ "had %s bestowed upon %s by %s",
+ artiname(otmp->oartifact),
+ uhim(), align_gname(u.ualign.type));
/* make sure we can use this weapon */
unrestrict_weapon_skill(weapon_type(otmp));
if (!Hallucination && !Blind) {
if (ParanoidPray && yn("Are you sure you want to pray?") != 'y')
return ECMD_OK;
- u.uconduct.gnostic++;
+ if (!u.uconduct.gnostic++)
+ /* breaking conduct should probably occur in can_pray() at
+ * "You begin praying to %s", as demons who find praying repugnant
+ * should not break conduct. Also we can add more detail to the
+ * livelog message as p_aligntyp will be known.
+ */
+ livelog_printf(LL_CONDUCT, "rejected atheism with a prayer");
/* set up p_type and p_alignment */
if (!can_pray(TRUE))
You("don't know how to turn undead!");
return ECMD_OK;
}
- u.uconduct.gnostic++;
+ if (!u.uconduct.gnostic++)
+ livelog_printf(LL_CONDUCT, "rejected atheism by turning undead");
+
Gname = halu_gname(u.ualign.type);
/* [What about needing free hands (does #turn involve any gesturing)?] */
boolean strayed = (u.ualign.record < 0);
/* KMH, conduct */
- u.uconduct.gnostic++;
+ if (!u.uconduct.gnostic++)
+ livelog_printf(LL_CONDUCT,
+ "rejected atheism by consulting with %s",
+ mon_nam(priest));
if (priest->mflee || (!priest->ispriest && coaligned && strayed)) {
pline("%s doesn't want anything to do with you!", Monnam(priest));
You("break up the cookie and throw away the pieces.");
outrumor(bcsign(scroll), BY_COOKIE);
if (!Blind)
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by reading a fortune cookie");
useup(scroll);
return ECMD_TIME;
} else if (otyp == T_SHIRT || otyp == ALCHEMY_SMOCK
hawaiian_design(scroll, buf));
return ECMD_TIME;
}
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by reading %s",
+ (scroll->otyp == T_SHIRT) ? "a T-shirt" : "an apron");
+
/* populate 'buf[]' */
mesg = (otyp == T_SHIRT) ? tshirt_text(scroll, buf)
: apron_text(scroll, buf);
pline("%s on the %s. It reads: %s.",
!Blind ? "There is writing" : "You feel lettering",
simpleonames(scroll), cap_text);
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by reading %s",
+ otyp == DUNCE_CAP ? "a dunce cap" : "a cornuthaum");
+
/* yet another note: despite the fact that player will recognize
the object type, don't make it become a discovery for hero */
if (!objects[otyp].oc_name_known && !objects[otyp].oc_uname)
(!((int) scroll->o_id % 3)),
(((int) scroll->o_id * 7) % 10),
(flags.verbose || Blind) ? "." : "");
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading a credit card");
+
return ECMD_TIME;
} else if (otyp == CAN_OF_GREASE) {
pline("This %s has no label.", singular(scroll, xname));
if (flags.verbose)
pline("It reads:");
pline("\"Magic Marker(TM) Red Ink Marker Pen. Water Soluble.\"");
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading a magic marker");
+
return ECMD_TIME;
} else if (scroll->oclass == COIN_CLASS) {
if (Blind)
else if (flags.verbose)
You("read:");
pline("\"1 Zorkmid. 857 GUE. In Frobs We Trust.\"");
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading a coin's engravings");
+
return ECMD_TIME;
} else if (scroll->oartifact == ART_ORB_OF_FATE) {
if (Blind)
else
pline("It is signed:");
pline("\"Odin.\"");
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading the divine signature of Odin");
+
return ECMD_TIME;
} else if (otyp == CANDY_BAR) {
const char *wrapper = candy_wrapper_text(scroll);
return ECMD_OK;
}
pline("The wrapper reads: \"%s\".", wrapper);
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading a candy bar wrapper");
+
return ECMD_TIME;
} else if (scroll->oclass != SCROLL_CLASS
&& scroll->oclass != SPBOOK_CLASS) {
/* Novel conduct is handled in read_tribute so exclude it too */
if (otyp != SPE_BOOK_OF_THE_DEAD && otyp != SPE_NOVEL
&& otyp != SPE_BLANK_PAPER && otyp != SCR_BLANK_PAPER)
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT, "became literate by reading %s",
+ scroll->oclass == SPBOOK_CLASS ? "a book" :
+ scroll->oclass == SCROLL_CLASS ? "a scroll" : "something");
if (scroll->oclass == SPBOOK_CLASS) {
return study_book(scroll) ? ECMD_TIME : ECMD_OK;
do_class_genocide(void)
{
int i, j, immunecnt, gonecnt, goodcnt, class, feel_dead = 0;
+ int ll_done = 0;
char buf[BUFSZ] = DUMMY;
boolean gameover = FALSE; /* true iff killed self */
/* This check must be first since player monsters might
* have G_GENOD or !G_GENO.
*/
+ if (!ll_done++) {
+ if (!num_genocides())
+ livelog_printf(LL_CONDUCT | LL_GENOCIDE,
+ "performed %s first genocide (class %c)",
+ uhis(), def_monsyms[class].sym);
+ else
+ livelog_printf(LL_GENOCIDE, "genocided class %c", def_monsyms[class].sym);
+ }
+
g.mvitals[i].mvflags |= (G_GENOD | G_NOCORPSE);
kill_genocided_monsters();
update_inventory(); /* eggs & tins */
which = !type_is_pname(ptr) ? "the " : "";
}
if (how & REALLY) {
+ if (!num_genocides())
+ livelog_printf(LL_CONDUCT | LL_GENOCIDE,
+ "performed %s first genocide (%s)", uhis(), makeplural(buf));
+ else
+ livelog_printf(LL_GENOCIDE, "genocided %s", makeplural(buf));
+
/* setting no-corpse affects wishing and random tin generation */
g.mvitals[mndx].mvflags |= (G_GENOD | G_NOCORPSE);
pline("Wiped out %s%s.", which,
static boolean restgamestate(NHFILE *, unsigned int *, unsigned int *);
static void restlevelstate(unsigned int, unsigned int);
static int restlevelfile(xchar);
+static void restore_gamelog(NHFILE *);
static void restore_msghistory(NHFILE *);
static void reset_oattached_mids(boolean);
static void rest_levl(NHFILE *, boolean);
restnames(nhfp);
restore_waterlevel(nhfp);
restore_msghistory(nhfp);
+ restore_gamelog(nhfp);
/* must come after all mons & objs are restored */
relink_timers(FALSE);
relink_light_sources(FALSE);
return;
}
+static void
+restore_gamelog(NHFILE* nhfp)
+{
+ int slen = 0;
+ char msg[BUFSZ*2];
+ struct gamelog_line tmp;
+
+ while (1) {
+ if (nhfp->structlevel)
+ mread(nhfp->fd, (genericptr_t)&slen, sizeof(slen));
+ if (slen == -1)
+ break;
+ if (slen > ((BUFSZ*2) - 1))
+ panic("restore_gamelog: msg too big (%d)", slen);
+ if (nhfp->structlevel) {
+ mread(nhfp->fd, (genericptr_t) msg, slen);
+ mread(nhfp->fd, (genericptr_t) &tmp, sizeof(tmp));
+ msg[slen] = '\0';
+ gamelog_add(tmp.flags, tmp.turn, msg);
+ }
+ }
+}
+
static void
restore_msghistory(NHFILE* nhfp)
{
static void savemon(NHFILE *,struct monst *);
static void savemonchn(NHFILE *,struct monst *);
static void savetrapchn(NHFILE *,struct trap *);
+static void save_gamelog(NHFILE *);
static void savegamestate(NHFILE *);
static void save_msghistory(NHFILE *);
return res;
}
+static void
+save_gamelog(NHFILE* nhfp)
+{
+ struct gamelog_line *tmp = g.gamelog, *tmp2;
+ int slen;
+
+ while (tmp) {
+ tmp2 = tmp->next;
+ if (perform_bwrite(nhfp)) {
+ if (nhfp->structlevel) {
+ slen = strlen(tmp->text);
+ bwrite(nhfp->fd, (genericptr_t) &slen, sizeof(slen));
+ bwrite(nhfp->fd, (genericptr_t) tmp->text, slen);
+ bwrite(nhfp->fd, (genericptr_t) tmp, sizeof(struct gamelog_line));
+ }
+ }
+ if (release_data(nhfp)) {
+ free((genericptr_t) tmp->text);
+ free((genericptr_t) tmp);
+ }
+ tmp = tmp2;
+ }
+ if (perform_bwrite(nhfp)) {
+ if (nhfp->structlevel) {
+ slen = -1;
+ bwrite(nhfp->fd, (genericptr_t) &slen, sizeof(slen));
+ }
+ }
+ if (release_data(nhfp))
+ g.gamelog = NULL;
+}
+
static void
savegamestate(NHFILE* nhfp)
{
savenames(nhfp);
save_waterlevel(nhfp);
save_msghistory(nhfp);
+ save_gamelog(nhfp);
if (nhfp->structlevel)
bflush(nhfp->fd);
g.program_state.saving--;
/* by this point, we know an actual robbery has taken place */
eshkp->robbed += total;
You("stole %ld %s worth of merchandise.", total, currency(total));
+ livelog_printf(LL_ACHIEVE, "stole %ld %s worth of merchandise from %s %s",
+ total, currency(total), s_suffix(shkname(shkp)),
+ shtypes[eshkp->shoptype - SHOPBASE].name);
+
if (!Role_if(PM_ROGUE)) /* stealing is unlawful */
adjalign(-sgn(u.ualign.type));
if (read_tribute("books", tribtitle, 0, (char *) 0, 0,
spellbook->o_id)) {
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by reading %s", tribtitle);
+
check_unpaid(spellbook);
makeknown(booktype);
if (!u.uevent.read_tribute) {
sysopt.genericusers = (char *) 0;
sysopt.maxplayers = 0; /* XXX eventually replace MAX_NR_OF_PLAYERS */
sysopt.bones_pools = 0;
+ sysopt.livelog = LL_NONE;
/* record file */
sysopt.persmax = max(PERSMAX, 1);
if (mon->wormno && *mhit)
cutworm(mon, g.bhitpos.x, g.bhitpos.y, slice_or_chop);
}
+ if (u.uconduct.weaphit && !oldweaphit)
+ livelog_printf(LL_CONDUCT,
+ "hit with a wielded weapon for the first time");
+
}
return malive;
}
}
/* KMH, conduct */
- u.uconduct.literate++;
+ if (!u.uconduct.literate++)
+ livelog_printf(LL_CONDUCT,
+ "became literate by writing %s", an(typeword));
new_obj = mksobj(i, FALSE, FALSE);
new_obj->bknown = (paper->bknown && pen->bknown);
break;
}
/* KMH, conduct */
- u.uconduct.polypiles++;
+ if (!u.uconduct.polypiles++)
+ livelog_printf(LL_CONDUCT, "polymorphed %s first object", uhis());
+
/* any saved lock context will be dangerously obsolete */
if (Is_box(obj))
(void) boxlock(obj, otmp);
{
char buf[BUFSZ] = DUMMY;
char promptbuf[BUFSZ];
+ char bufcpy[BUFSZ];
struct obj *otmp, nothing;
int tries = 0;
+ int prev_artwish = u.uconduct.wisharti;
promptbuf[0] = '\0';
nothing = cg.zeroobj; /* lint suppression; only its address matters */
* otmp == &zeroobj. That includes an artifact which has been denied.
* Wishing for "nothing" requires a separate value to remain distinct.
*/
+ strcpy(bufcpy, buf);
otmp = readobjnam(buf, ¬hing);
if (!otmp) {
pline("Nothing fitting that description exists in the game.");
}
/* KMH, conduct */
- u.uconduct.wishes++;
+ if (!u.uconduct.wishes++)
+ livelog_printf(LL_CONDUCT | LL_WISH | (prev_artwish < u.uconduct.wisharti ? LL_ARTIFACT : 0),
+ "made %s first wish - \"%s\"", uhis(), bufcpy);
+ else if (!prev_artwish && u.uconduct.wisharti) /* arti conduct handled in readobjnam() above */
+ livelog_printf(LL_CONDUCT | LL_WISH | LL_ARTIFACT, "made %s first artifact wish - \"%s\"",
+ uhis(), bufcpy);
+ else
+ livelog_printf(LL_WISH | (prev_artwish < u.uconduct.wisharti ? LL_ARTIFACT : 0),
+ "wished for \"%s\"", bufcpy);
if (otmp != &cg.zeroobj) {
const char
# set up the game files
( $(MAKE) dofiles )
# set up some additional files
- touch $(VARDIR)/perm $(VARDIR)/record $(VARDIR)/logfile $(VARDIR)/xlogfile
- -( cd $(VARDIR) ; $(CHOWN) $(GAMEUID) perm record logfile xlogfile ; \
- $(CHGRP) $(GAMEGRP) perm record logfile xlogfile ; \
- chmod $(VARFILEPERM) perm record logfile xlogfile )
+ touch $(VARDIR)/perm $(VARDIR)/record $(VARDIR)/logfile $(VARDIR)/xlogfile \
+ $(VARDIR)/livelog
+ -( cd $(VARDIR) ; $(CHOWN) $(GAMEUID) perm record logfile xlogfile livelog ; \
+ $(CHGRP) $(GAMEGRP) perm record logfile xlogfile livelog ; \
+ chmod $(VARFILEPERM) perm record logfile xlogfile livelog )
true; $(POSTINSTALL)
# and a reminder
@echo You may also want to reinstall the man pages via the doc Makefile.
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "mkdir -p \"${NH_INSTALL_DIR}\"/save\ncd \"${NH_DAT_DIR}\"\ncp nhdat license symbols \"${NH_INSTALL_DIR}\"\ncp \"${NH_SRC_DIR}\"/nethack \"${NH_INSTALL_DIR}\"\ncp \"${NH_UTIL_DIR}\"/recover \"${NH_INSTALL_DIR}\"\ntouch \"${NH_INSTALL_DIR}\"/perm\ntouch \"${NH_INSTALL_DIR}\"/record\ntouch \"${NH_INSTALL_DIR}\"/logfile\ntouch \"${NH_INSTALL_DIR}\"/xlogfile\ncd \"${NH_UNIX_DIR}\"\nsh hints/macosx.sh editsysconf sysconf \"${NH_INSTALL_DIR}\"/sysconf\n";
+ shellScript = "mkdir -p \"${NH_INSTALL_DIR}\"/save\ncd \"${NH_DAT_DIR}\"\ncp nhdat license symbols \"${NH_INSTALL_DIR}\"\ncp \"${NH_SRC_DIR}\"/nethack \"${NH_INSTALL_DIR}\"\ncp \"${NH_UTIL_DIR}\"/recover \"${NH_INSTALL_DIR}\"\ntouch \"${NH_INSTALL_DIR}\"/perm\ntouch \"${NH_INSTALL_DIR}\"/record\ntouch \"${NH_INSTALL_DIR}\"/logfile\ntouch \"${NH_INSTALL_DIR}\"/xlogfile\ntouch \"${NH_INSTALL_DIR}\"/livelog\ncd \"${NH_UNIX_DIR}\"\nsh hints/macosx.sh editsysconf sysconf \"${NH_INSTALL_DIR}\"/sysconf\n";
};
3192867121A3A2D500325BEB /* Copy nethack */ = {
isa = PBXShellScriptBuildPhase;
touch $(WASM_DATA_DIR)/record
touch $(WASM_DATA_DIR)/logfile
touch $(WASM_DATA_DIR)/xlogfile
+ touch $(WASM_DATA_DIR)/livelog
cp ../sys/libnh/sysconf $(WASM_DATA_DIR)/sysconf
$(WASM_DATA_DIR)/nhdat:
#NHCFLAGS+=-DMSGHANDLER
#NHCFLAGS+=-DTTY_TILES_ESCCODES
#NHCFLAGS+=-DTTY_SOUND_ESCCODES
+#NHCFLAGS+=-DNO_CHRONICLE
+#NHCFLAGS+=-DLIVELOG
CFLAGS+= $(WINCFLAGS) #WINCFLAGS set from multiw-2.370
CFLAGS+= $(NHCFLAGS)
#NHCFLAGS+=-DMSGHANDLER
#NHCFLAGS+=-DTTY_TILES_ESCCODES
#NHCFLAGS+=-DTTY_SOUND_ESCCODES
+#NHCFLAGS+=-DNO_CHRONICLE
+#NHCFLAGS+=-DLIVELOG
CFLAGS+= $(WINCFLAGS) #WINCFLAGS set from multiw-2.370
CFLAGS+= $(NHCFLAGS)
sys/unix/hints/macosx.sh editsysconf sys/unix/sysconf $(PKGROOT_UGLN)/sysconf
cd dat; install -p $(DATNODLB) ../$(PKGROOT_UGLN)
# XXX these files should be somewhere else for good Mac form
- touch $(PKGROOT_UGLN)/perm $(PKGROOT_UGLN)/record $(PKGROOT_UGLN)/logfile $(PKGROOT_UGLN)/xlogfile
+ touch $(PKGROOT_UGLN)/perm $(PKGROOT_UGLN)/record $(PKGROOT_UGLN)/logfile $(PKGROOT_UGLN)/xlogfile $(PKGROOT_UGLN)/livelog
mkdir $(PKGROOT_UGLN)/save
# XXX what about a news file?
echo chmod $(VARFILEPERM) $(HACKDIR)/record >> PKGSCRIPTS/postinstall
echo chmod $(VARFILEPERM) $(HACKDIR)/logfile >> PKGSCRIPTS/postinstall
echo chmod $(VARFILEPERM) $(HACKDIR)/xlogfile >> PKGSCRIPTS/postinstall
+ echo chmod $(VARFILEPERM) $(HACKDIR)/livelog >> PKGSCRIPTS/postinstall
echo chmod $(VARFILEPERM) $(HACKDIR)/sysconf >> PKGSCRIPTS/postinstall
echo chmod $(GAMEPERM) $(SHELLDIR)/nethack >> PKGSCRIPTS/postinstall
echo chmod $(EXEPERM) $(SHELLDIR)/recover >> PKGSCRIPTS/postinstall
# Maximum number of score file entries to use for random statue names
#MAX_STATUENAME_RANK=10
+# Use "Live logging" for important events (achievements, wishes, etc)
+# Only available if NetHack was compiled with LIVELOG.
+# Only really meaningful for public servers.
+# See the log in-game with #chronicle -command.
+# Bitmask for kinds of things you want to log - combine the following values
+# as desired.
+# 0x0000 - No live logging (default)
+# 0x0001 - Wishes
+# 0x0002 - Significant achievements (complete sokoban, perform invocation, etc)
+# 0x0004 - Kill, destroy or bribe a unique monster.
+# 0x0008 - Significant religious events (sacrifice gifts, crowning)
+# 0x0010 - Life-saving
+# 0x0020 - Break conduct - see also LLC_TURNS below.
+# 0x0040 - Artifact obtained (#name Sting, dip for Excalibur)
+# 0x0080 - Genocides
+# 0x0100 - Murder of tame pet
+# 0x0200 - Changed alignment temporarily or permanently
+# 0x0400 - Log URL for dumplog if ascended
+# 0x0800 - Log dumplog url for all games
+# 0x1000 - Log 'minor' achievements - can be spammy
+# 0x8000 - Livelog debug msgs (currently only 'enter new level')
+#LIVELOG=0x1FFF
+
# Show debugging information originating from these source files.
# Use '*' for all, or list source files separated by spaces.
# Only available if game has been compiled with DEBUG, and can be