This is for consistency with the other resistance insight messages, which use spaces instead of hyphens.
3149 lines
119 KiB
C
3149 lines
119 KiB
C
/* NetHack 3.7 insight.c $NHDT-Date: 1650875487 2022/04/25 08:31:27 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.60 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/*
|
|
* Enlightenment and Conduct+Achievements and Vanquished+Extinct+Geno'd
|
|
* and stethoscope/probing feedback.
|
|
*
|
|
* Most code used to reside in cmd.c, presumeably because ^X was originally
|
|
* a wizard mode command and the majority of those are in that file.
|
|
* Some came from end.c where it is used during end of game disclosure.
|
|
* And some came from priest.c that had once been in pline.c.
|
|
*/
|
|
|
|
#include "hack.h"
|
|
|
|
static void enlght_out(const char *);
|
|
static void enlght_line(const char *, const char *, const char *,
|
|
const char *);
|
|
static char *enlght_combatinc(const char *, int, int, char *);
|
|
static void enlght_halfdmg(int, int);
|
|
static boolean walking_on_water(void);
|
|
static boolean cause_known(int);
|
|
static char *attrval(int, int, char *);
|
|
static char *fmt_elapsed_time(char *, int);
|
|
static void background_enlightenment(int, int);
|
|
static void basics_enlightenment(int, int);
|
|
static void characteristics_enlightenment(int, int);
|
|
static void one_characteristic(int, int, int);
|
|
static void status_enlightenment(int, int);
|
|
static void weapon_insight(int);
|
|
static void attributes_enlightenment(int, int);
|
|
static void show_achievements(int);
|
|
static int QSORTCALLBACK vanqsort_cmp(const genericptr, const genericptr);
|
|
static int set_vanq_order(void);
|
|
static int num_extinct(void);
|
|
|
|
extern const char *const hu_stat[]; /* hunger status from eat.c */
|
|
extern const char *const enc_stat[]; /* encumbrance status from botl.c */
|
|
|
|
static const char You_[] = "You ", are[] = "are ", were[] = "were ",
|
|
have[] = "have ", had[] = "had ", can[] = "can ",
|
|
could[] = "could ";
|
|
static const char have_been[] = "have been ", have_never[] = "have never ",
|
|
never[] = "never ";
|
|
|
|
/* for livelogging: */
|
|
struct ll_achieve_msg {
|
|
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 Elemental Planes" },
|
|
{ LL_ACHIEVE, "entered the Astral Plane" },
|
|
{ LL_ACHIEVE, "ascended" },
|
|
/* if the type of item isn't discovered yet, disclosing the event
|
|
via #chronicle would be a spoiler (particularly for gray stone);
|
|
the ID'd name for the type of item will be appended to the next
|
|
two messages, for display via livelog and/or dumplog */
|
|
{ LL_ACHIEVE | LL_SPOILER, "acquired the Mines' End" }, /* " luckstone" */
|
|
{ LL_ACHIEVE | LL_SPOILER, "acquired the Sokoban" }, /* " <item>" */
|
|
{ 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 | LL_DUMP, "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_MINORAC | LL_DUMP, "read a Discworld novel" }, /* even more so */
|
|
{ LL_ACHIEVE, "entered Sokoban" }, /* keep as major for turn comparison
|
|
* with completed sokoban */
|
|
{ LL_ACHIEVE, "entered the Bigroom" },
|
|
/* The following 8 are for advancing through the ranks
|
|
and messages differ by role so are created on the fly;
|
|
rank 0 (Xp 1 and 2) isn't an achievement */
|
|
{ LL_MINORAC | LL_DUMP, "" }, /* Xp 3 */
|
|
{ LL_MINORAC | LL_DUMP, "" }, /* Xp 6 */
|
|
{ LL_MINORAC | LL_DUMP, "" }, /* Xp 10 */
|
|
{ LL_ACHIEVE, "" }, /* Xp 14, so able to attempt the quest */
|
|
{ LL_ACHIEVE, "" }, /* Xp 18 */
|
|
{ LL_ACHIEVE, "" }, /* Xp 22 */
|
|
{ LL_ACHIEVE, "" }, /* Xp 26 */
|
|
{ LL_ACHIEVE, "" }, /* Xp 30 */
|
|
{ LL_MINORAC, "learned castle drawbridge's tune" }, /* achievement #31 */
|
|
{ 0, "" } /* keep this one at the end */
|
|
};
|
|
|
|
/* macros to simplify output of enlightenment messages; also used by
|
|
conduct and achievements */
|
|
#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)
|
|
#define you_have(attr, ps) enl_msg(You_, have, had, attr, ps)
|
|
#define you_can(attr, ps) enl_msg(You_, can, could, attr, ps)
|
|
#define you_have_been(goodthing) enl_msg(You_, have_been, were, goodthing, "")
|
|
#define you_have_never(badthing) \
|
|
enl_msg(You_, have_never, never, badthing, "")
|
|
#define you_have_X(something) \
|
|
enl_msg(You_, have, (const char *) "", something, "")
|
|
|
|
static void
|
|
enlght_out(const char *buf)
|
|
{
|
|
int clr = 0;
|
|
|
|
if (g.en_via_menu) {
|
|
anything any;
|
|
|
|
any = cg.zeroany;
|
|
add_menu(g.en_win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf,
|
|
MENU_ITEMFLAGS_NONE);
|
|
} else
|
|
putstr(g.en_win, 0, buf);
|
|
}
|
|
|
|
static void
|
|
enlght_line(const char *start, const char *middle, const char *end,
|
|
const char *ps)
|
|
{
|
|
char buf[BUFSZ];
|
|
|
|
Sprintf(buf, " %s%s%s%s.", start, middle, end, ps);
|
|
enlght_out(buf);
|
|
}
|
|
|
|
/* format increased chance to hit or damage or defense (Protection) */
|
|
static char *
|
|
enlght_combatinc(const char *inctyp, int incamt, int final, char *outbuf)
|
|
{
|
|
const char *modif, *bonus;
|
|
boolean invrt;
|
|
int absamt;
|
|
|
|
absamt = abs(incamt);
|
|
/* Protection amount is typically larger than damage or to-hit;
|
|
reduce magnitude by a third in order to stretch modifier ranges
|
|
(small:1..5, moderate:6..10, large:11..19, huge:20+) */
|
|
if (!strcmp(inctyp, "defense"))
|
|
absamt = (absamt * 2) / 3;
|
|
|
|
if (absamt <= 3)
|
|
modif = "small";
|
|
else if (absamt <= 6)
|
|
modif = "moderate";
|
|
else if (absamt <= 12)
|
|
modif = "large";
|
|
else
|
|
modif = "huge";
|
|
|
|
modif = !incamt ? "no" : an(modif); /* ("no" case shouldn't happen) */
|
|
bonus = (incamt >= 0) ? "bonus" : "penalty";
|
|
/* "bonus <foo>" (to hit) vs "<bar> bonus" (damage, defense) */
|
|
invrt = strcmp(inctyp, "to hit") ? TRUE : FALSE;
|
|
|
|
Sprintf(outbuf, "%s %s %s", modif, invrt ? inctyp : bonus,
|
|
invrt ? bonus : inctyp);
|
|
if (final || wizard)
|
|
Sprintf(eos(outbuf), " (%s%d)", (incamt > 0) ? "+" : "", incamt);
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
/* report half physical or half spell damage */
|
|
static void
|
|
enlght_halfdmg(int category, int final)
|
|
{
|
|
const char *category_name;
|
|
char buf[BUFSZ];
|
|
|
|
switch (category) {
|
|
case HALF_PHDAM:
|
|
category_name = "physical";
|
|
break;
|
|
case HALF_SPDAM:
|
|
category_name = "spell";
|
|
break;
|
|
default:
|
|
category_name = "unknown";
|
|
break;
|
|
}
|
|
Sprintf(buf, " %s %s damage", (final || wizard) ? "half" : "reduced",
|
|
category_name);
|
|
enl_msg(You_, "take", "took", buf, from_what(category));
|
|
}
|
|
|
|
/* is hero actively using water walking capability on water (or lava)? */
|
|
static boolean
|
|
walking_on_water(void)
|
|
{
|
|
if (u.uinwater || Levitation || Flying)
|
|
return FALSE;
|
|
return (boolean) (Wwalking && is_pool_or_lava(u.ux, u.uy));
|
|
}
|
|
|
|
/* describe u.utraptype; used by status_enlightenment() and self_lookat() */
|
|
char *
|
|
trap_predicament(char *outbuf, int final, boolean wizxtra)
|
|
{
|
|
struct trap *t;
|
|
|
|
/* caller has verified u.utrap */
|
|
*outbuf = '\0';
|
|
switch (u.utraptype) {
|
|
case TT_BURIEDBALL:
|
|
Strcpy(outbuf, "tethered to something buried");
|
|
break;
|
|
case TT_LAVA:
|
|
Sprintf(outbuf, "sinking into %s", final ? "lava" : hliquid("lava"));
|
|
break;
|
|
case TT_INFLOOR:
|
|
Sprintf(outbuf, "stuck in %s", the(surface(u.ux, u.uy)));
|
|
break;
|
|
default: /* TT_BEARTRAP, TT_PIT, or TT_WEB */
|
|
Strcpy(outbuf, "trapped");
|
|
if ((t = t_at(u.ux, u.uy)) != 0) /* should never be null */
|
|
Sprintf(eos(outbuf), " in %s", an(trapname(t->ttyp, FALSE)));
|
|
break;
|
|
}
|
|
if (wizxtra) { /* give extra information for wizard mode enlightenment */
|
|
/* curly braces: u.utrap is an escape attempt counter rather than a
|
|
turn timer so use different ornamentation than usual parentheses */
|
|
Sprintf(eos(outbuf), " {%u}", u.utrap);
|
|
}
|
|
return outbuf;
|
|
}
|
|
|
|
/* check whether hero is wearing something that player definitely knows
|
|
confers the target property; item must have been seen and its type
|
|
discovered but it doesn't necessarily have to be fully identified */
|
|
static boolean
|
|
cause_known(
|
|
int propindx) /* index of a property which can be conveyed by worn item */
|
|
{
|
|
register struct obj *o;
|
|
long mask = W_ARMOR | W_AMUL | W_RING | W_TOOL;
|
|
|
|
/* simpler than from_what()/what_gives(); we don't attempt to
|
|
handle artifacts and we deliberately ignore wielded items */
|
|
for (o = g.invent; o; o = o->nobj) {
|
|
if (!(o->owornmask & mask))
|
|
continue;
|
|
if ((int) objects[o->otyp].oc_oprop == propindx
|
|
&& objects[o->otyp].oc_name_known && o->dknown)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* format a characteristic value, accommodating Strength's strangeness */
|
|
static char *
|
|
attrval(int attrindx, int attrvalue,
|
|
char resultbuf[]) /* should be at least [7] to hold "18/100\0" */
|
|
{
|
|
if (attrindx != A_STR || attrvalue <= 18)
|
|
Sprintf(resultbuf, "%d", attrvalue);
|
|
else if (attrvalue > STR18(100)) /* 19 to 25 */
|
|
Sprintf(resultbuf, "%d", attrvalue - 100);
|
|
else /* simplify "18/ **" to be "18/100" */
|
|
Sprintf(resultbuf, "18/%02d", attrvalue - 18);
|
|
return resultbuf;
|
|
}
|
|
|
|
/* format urealtime.realtime as
|
|
" D days, H hours, M minutes and S seconds"
|
|
with any fields having a value of 0 omitted:
|
|
0-00:00:20 => " 20 seconds"
|
|
0-00:15:05 => " 15 minutes and 5 seconds"
|
|
0-00:16:00 => " 16 minutes"
|
|
0-01:15:10 => " 1 hour, 15 minutes and 10 seconds"
|
|
0-02:00:01 => " 2 hours and 1 second"
|
|
3-00:25:40 => " 3 days, 25 minutes and 40 seconds"
|
|
(note: for a list of more than two entries, nethack usually includes the
|
|
[style-wise] optional comma before "and" but in this instance it does not)
|
|
*/
|
|
static char *
|
|
fmt_elapsed_time(char *outbuf, int final)
|
|
{
|
|
int fieldcnt;
|
|
long edays, ehours, eminutes, eseconds;
|
|
/* for a game that's over, reallydone() has updated urealtime.realtime
|
|
to its final value before calling us during end of game disclosure;
|
|
for a game that's still in progress, it holds the amount of elapsed
|
|
game time from previous sessions up through most recent save/restore
|
|
(or up through latest level change when 'checkpoint' is On);
|
|
'.start_timing' has a non-zero value even if '.realtime' is 0 */
|
|
long etim = urealtime.realtime;
|
|
|
|
if (!final)
|
|
etim += timet_delta(getnow(), urealtime.start_timing);
|
|
/* we could use localtime() to convert the value into a 'struct tm'
|
|
to get date and time fields but this is simple and straightforward */
|
|
eseconds = etim % 60L, etim /= 60L;
|
|
eminutes = etim % 60L, etim /= 60L;
|
|
ehours = etim % 24L;
|
|
edays = etim / 24L;
|
|
fieldcnt = !!edays + !!ehours + !!eminutes + !!eseconds;
|
|
|
|
Strcpy(outbuf, fieldcnt ? "" : " none"); /* 'none' should never happen */
|
|
if (edays) {
|
|
Sprintf(eos(outbuf), " %ld day%s", edays, plur(edays));
|
|
if (fieldcnt > 1) /* hours and/or minutes and/or seconds to follow */
|
|
Strcat(outbuf, (fieldcnt == 2) ? " and" : ",");
|
|
--fieldcnt; /* edays has been processed */
|
|
}
|
|
if (ehours) {
|
|
Sprintf(eos(outbuf), " %ld hour%s", ehours, plur(ehours));
|
|
if (fieldcnt > 1) /* minutes and/or seconds to follow */
|
|
Strcat(outbuf, (fieldcnt == 2) ? " and" : ",");
|
|
--fieldcnt; /* ehours has been processed */
|
|
}
|
|
if (eminutes) {
|
|
Sprintf(eos(outbuf), " %ld minute%s", eminutes, plur(eminutes));
|
|
if (fieldcnt > 1) /* seconds to follow */
|
|
Strcat(outbuf, " and");
|
|
/* eminutes has been processed but no need to decrement fieldcnt */
|
|
}
|
|
if (eseconds)
|
|
Sprintf(eos(outbuf), " %ld second%s", eseconds, plur(eseconds));
|
|
return outbuf;
|
|
}
|
|
|
|
void
|
|
enlightenment(
|
|
int mode, /* BASICENLIGHTENMENT | MAGICENLIGHTENMENT (| both) */
|
|
int final) /* ENL_GAMEINPROGRESS:0, ENL_GAMEOVERALIVE, ENL_GAMEOVERDEAD */
|
|
{
|
|
char buf[BUFSZ], tmpbuf[BUFSZ];
|
|
|
|
g.en_win = create_nhwindow(NHW_MENU);
|
|
g.en_via_menu = !final;
|
|
if (g.en_via_menu)
|
|
start_menu(g.en_win, MENU_BEHAVE_STANDARD);
|
|
|
|
Strcpy(tmpbuf, g.plname);
|
|
*tmpbuf = highc(*tmpbuf); /* same adjustment as bottom line */
|
|
/* as in background_enlightenment, when poly'd we need to use the saved
|
|
gender in u.mfemale rather than the current you-as-monster gender */
|
|
Snprintf(buf, sizeof(buf), "%s the %s's attributes:", tmpbuf,
|
|
((Upolyd ? u.mfemale : flags.female) && g.urole.name.f)
|
|
? g.urole.name.f
|
|
: g.urole.name.m);
|
|
|
|
/* title */
|
|
enlght_out(buf); /* "Conan the Archeologist's attributes:" */
|
|
/* background and characteristics; ^X or end-of-game disclosure */
|
|
if (mode & BASICENLIGHTENMENT) {
|
|
/* role, race, alignment, deities, dungeon level, time, experience */
|
|
background_enlightenment(mode, final);
|
|
/* hit points, energy points, armor class, gold */
|
|
basics_enlightenment(mode, final);
|
|
/* strength, dexterity, &c */
|
|
characteristics_enlightenment(mode, final);
|
|
}
|
|
/* expanded status line information, including things which aren't
|
|
included there due to space considerations;
|
|
shown for both basic and magic enlightenment */
|
|
status_enlightenment(mode, final);
|
|
/* remaining attributes; shown for potion,&c or wizard mode and
|
|
explore mode ^X or end of game disclosure */
|
|
if (mode & MAGICENLIGHTENMENT) {
|
|
/* intrinsics and other traditional enlightenment feedback */
|
|
attributes_enlightenment(mode, final);
|
|
}
|
|
|
|
enlght_out(""); /* separator */
|
|
enlght_out("Miscellaneous:");
|
|
/* reminder to player and/or information for dumplog */
|
|
if ((mode & BASICENLIGHTENMENT) != 0 && (wizard || discover || final)) {
|
|
if (wizard || discover) {
|
|
Sprintf(buf, "running in %s mode", wizard ? "debug" : "explore");
|
|
you_are(buf, "");
|
|
}
|
|
|
|
if (!flags.bones) {
|
|
you_have_X("disabled loading of bones levels");
|
|
} else if (!u.uroleplay.numbones) {
|
|
you_have_never("encountered a bones level");
|
|
} else {
|
|
Sprintf(buf, "encountered %ld bones level%s",
|
|
u.uroleplay.numbones, plur(u.uroleplay.numbones));
|
|
you_have_X(buf);
|
|
}
|
|
}
|
|
(void) fmt_elapsed_time(buf, final);
|
|
enl_msg("Total elapsed playing time ", "is", "was", buf, "");
|
|
|
|
if (!g.en_via_menu) {
|
|
display_nhwindow(g.en_win, TRUE);
|
|
} else {
|
|
menu_item *selected = 0;
|
|
|
|
end_menu(g.en_win, (char *) 0);
|
|
if (select_menu(g.en_win, PICK_NONE, &selected) > 0)
|
|
free((genericptr_t) selected);
|
|
g.en_via_menu = FALSE;
|
|
}
|
|
destroy_nhwindow(g.en_win);
|
|
g.en_win = WIN_ERR;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
/* display role, race, alignment and such to en_win */
|
|
static void
|
|
background_enlightenment(int unused_mode UNUSED, int final)
|
|
{
|
|
const char *role_titl, *rank_titl;
|
|
int innategend, difgend, difalgn;
|
|
char buf[BUFSZ], tmpbuf[BUFSZ];
|
|
|
|
/* note that if poly'd, we need to use u.mfemale instead of flags.female
|
|
to access hero's saved gender-as-human/elf/&c rather than current */
|
|
innategend = (Upolyd ? u.mfemale : flags.female) ? 1 : 0;
|
|
role_titl = (innategend && g.urole.name.f) ? g.urole.name.f
|
|
: g.urole.name.m;
|
|
rank_titl = rank_of(u.ulevel, Role_switch, innategend);
|
|
|
|
enlght_out(""); /* separator after title */
|
|
enlght_out("Background:");
|
|
|
|
/* if polymorphed, report current shape before underlying role;
|
|
will be repeated as first status: "you are transformed" and also
|
|
among various attributes: "you are in beast form" (after being
|
|
told about lycanthropy) or "you are polymorphed into <a foo>"
|
|
(with countdown timer appended for wizard mode); we really want
|
|
the player to know he's not a samurai at the moment... */
|
|
if (Upolyd) {
|
|
char anbuf[20]; /* includes trailing space; [4] suffices */
|
|
struct permonst *uasmon = g.youmonst.data;
|
|
boolean altphrasing = vampshifted(&g.youmonst);
|
|
|
|
tmpbuf[0] = '\0';
|
|
/* here we always use current gender, not saved role gender */
|
|
if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon))
|
|
Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj);
|
|
if (altphrasing)
|
|
Sprintf(eos(tmpbuf), "%s in ",
|
|
pmname(&mons[g.youmonst.cham],
|
|
flags.female ? FEMALE : MALE));
|
|
Snprintf(buf, sizeof(buf), "%s%s%s%s form",
|
|
!final ? "currently " : "",
|
|
altphrasing ? just_an(anbuf, tmpbuf) : "in ",
|
|
tmpbuf, pmname(uasmon, flags.female ? FEMALE : MALE));
|
|
you_are(buf, "");
|
|
}
|
|
|
|
/* report role; omit gender if it's redundant (eg, "female priestess") */
|
|
tmpbuf[0] = '\0';
|
|
if (!g.urole.name.f
|
|
&& ((g.urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE)
|
|
|| innategend != flags.initgend))
|
|
Sprintf(tmpbuf, "%s ", genders[innategend].adj);
|
|
buf[0] = '\0';
|
|
if (Upolyd)
|
|
Strcpy(buf, "actually "); /* "You are actually a ..." */
|
|
if (!strcmpi(rank_titl, role_titl)) {
|
|
/* omit role when rank title matches it */
|
|
Sprintf(eos(buf), "%s, level %d %s%s", an(rank_titl), u.ulevel,
|
|
tmpbuf, g.urace.noun);
|
|
} else {
|
|
Sprintf(eos(buf), "%s, a level %d %s%s %s", an(rank_titl), u.ulevel,
|
|
tmpbuf, g.urace.adj, role_titl);
|
|
}
|
|
you_are(buf, "");
|
|
|
|
/* report alignment (bypass you_are() in order to omit ending period);
|
|
adverb is used to distinguish between temporary change (helm of opp.
|
|
alignment), permanent change (one-time conversion), and original */
|
|
Sprintf(buf, " %s%s%s, %son a mission for %s",
|
|
You_, !final ? are : were,
|
|
align_str(u.ualign.type),
|
|
/* helm of opposite alignment (might hide conversion) */
|
|
(u.ualign.type != u.ualignbase[A_CURRENT])
|
|
/* what's the past tense of "currently"? if we used "formerly"
|
|
it would sound like a reference to the original alignment */
|
|
? (!final ? "currently " : "temporarily ")
|
|
/* permanent conversion */
|
|
: (u.ualign.type != u.ualignbase[A_ORIGINAL])
|
|
/* and what's the past tense of "now"? certainly not "then"
|
|
in a context like this...; "belatedly" == weren't that
|
|
way sooner (in other words, didn't start that way) */
|
|
? (!final ? "now " : "belatedly ")
|
|
/* atheist (ignored in very early game) */
|
|
: (!u.uconduct.gnostic && g.moves > 1000L)
|
|
? "nominally "
|
|
/* lastly, normal case */
|
|
: "",
|
|
u_gname());
|
|
enlght_out(buf);
|
|
/* show the rest of this game's pantheon (finishes previous sentence)
|
|
[appending "also Moloch" at the end would allow for straightforward
|
|
trailing "and" on all three aligned entries but looks too verbose] */
|
|
Sprintf(buf, " who %s opposed by", !final ? "is" : "was");
|
|
if (u.ualign.type != A_LAWFUL)
|
|
Sprintf(eos(buf), " %s (%s) and", align_gname(A_LAWFUL),
|
|
align_str(A_LAWFUL));
|
|
if (u.ualign.type != A_NEUTRAL)
|
|
Sprintf(eos(buf), " %s (%s)%s", align_gname(A_NEUTRAL),
|
|
align_str(A_NEUTRAL),
|
|
(u.ualign.type != A_CHAOTIC) ? " and" : "");
|
|
if (u.ualign.type != A_CHAOTIC)
|
|
Sprintf(eos(buf), " %s (%s)", align_gname(A_CHAOTIC),
|
|
align_str(A_CHAOTIC));
|
|
Strcat(buf, "."); /* terminate sentence */
|
|
enlght_out(buf);
|
|
|
|
/* show original alignment,gender,race,role if any have been changed;
|
|
giving separate message for temporary alignment change bypasses need
|
|
for tricky phrasing otherwise necessitated by possibility of having
|
|
helm of opposite alignment mask a permanent alignment conversion */
|
|
difgend = (innategend != flags.initgend);
|
|
difalgn = (((u.ualign.type != u.ualignbase[A_CURRENT]) ? 1 : 0)
|
|
+ ((u.ualignbase[A_CURRENT] != u.ualignbase[A_ORIGINAL])
|
|
? 2 : 0));
|
|
if (difalgn & 1) { /* have temporary alignment so report permanent one */
|
|
Sprintf(buf, "actually %s", align_str(u.ualignbase[A_CURRENT]));
|
|
you_are(buf, "");
|
|
difalgn &= ~1; /* suppress helm from "started out <foo>" message */
|
|
}
|
|
if (difgend || difalgn) { /* sex change or perm align change or both */
|
|
Sprintf(buf, " You started out %s%s%s.",
|
|
difgend ? genders[flags.initgend].adj : "",
|
|
(difgend && difalgn) ? " and " : "",
|
|
difalgn ? align_str(u.ualignbase[A_ORIGINAL]) : "");
|
|
enlght_out(buf);
|
|
}
|
|
|
|
/* As of 3.6.2: dungeon level, so that ^X really has all status info as
|
|
claimed by the comment below; this reveals more information than
|
|
the basic status display, but that's one of the purposes of ^X;
|
|
similar information is revealed by #overview; the "You died in
|
|
<location>" given by really_done() is more rudimentary than this */
|
|
*buf = *tmpbuf = '\0';
|
|
if (In_endgame(&u.uz)) {
|
|
int egdepth = observable_depth(&u.uz);
|
|
|
|
(void) endgamelevelname(tmpbuf, egdepth);
|
|
Snprintf(buf, sizeof(buf), "in the endgame, on the %s%s",
|
|
!strncmp(tmpbuf, "Plane", 5) ? "Elemental " : "", tmpbuf);
|
|
} else if (Is_knox(&u.uz)) {
|
|
/* this gives away the fact that the knox branch is only 1 level */
|
|
Sprintf(buf, "on the %s level", g.dungeons[u.uz.dnum].dname);
|
|
/* TODO? maybe phrase it differently when actually inside the fort,
|
|
if we're able to determine that (not trivial) */
|
|
} else {
|
|
char dgnbuf[QBUFSZ];
|
|
|
|
Strcpy(dgnbuf, g.dungeons[u.uz.dnum].dname);
|
|
if (!strncmpi(dgnbuf, "The ", 4))
|
|
*dgnbuf = lowc(*dgnbuf);
|
|
Sprintf(tmpbuf, "level %d",
|
|
In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
|
|
/* TODO? maybe extend this bit to include various other automatic
|
|
annotations from the dungeon overview code */
|
|
if (Is_rogue_level(&u.uz))
|
|
Strcat(tmpbuf, ", a primitive area");
|
|
else if (Is_bigroom(&u.uz) && !Blind)
|
|
Strcat(tmpbuf, ", a very big room");
|
|
Snprintf(buf, sizeof(buf), "in %s, on %s", dgnbuf, tmpbuf);
|
|
}
|
|
you_are(buf, "");
|
|
|
|
/* this is shown even if the 'time' option is off */
|
|
if (g.moves == 1L) {
|
|
you_have("just started your adventure", "");
|
|
} else {
|
|
/* 'turns' grates on the nerves in this context... */
|
|
Sprintf(buf, "the dungeon %ld turn%s ago", g.moves, plur(g.moves));
|
|
/* same phrasing for current and final: "entered" is unconditional */
|
|
enlght_line(You_, "entered ", buf, "");
|
|
}
|
|
|
|
/* for gameover, these have been obtained in really_done() so that they
|
|
won't vary if user leaves a disclosure prompt or --More-- unanswered
|
|
long enough for the dynamic value to change between then and now */
|
|
if (final ? iflags.at_midnight : midnight()) {
|
|
enl_msg("It ", "is ", "was ", "the midnight hour", "");
|
|
} else if (final ? iflags.at_night : night()) {
|
|
enl_msg("It ", "is ", "was ", "nighttime", "");
|
|
}
|
|
/* other environmental factors */
|
|
if (flags.moonphase == FULL_MOON || flags.moonphase == NEW_MOON) {
|
|
/* [This had "tonight" but has been changed to "in effect".
|
|
There is a similar issue to Friday the 13th--it's the value
|
|
at the start of the current session but that session might
|
|
have dragged on for an arbitrary amount of time. We want to
|
|
report the values that currently affect play--or affected
|
|
play when game ended--rather than actual outside situation.] */
|
|
Sprintf(buf, "a %s moon in effect%s",
|
|
(flags.moonphase == FULL_MOON) ? "full"
|
|
: (flags.moonphase == NEW_MOON) ? "new"
|
|
/* showing these would probably just lead to confusion
|
|
since they have no effect on game play... */
|
|
: (flags.moonphase < FULL_MOON) ? "first quarter"
|
|
: "last quarter",
|
|
/* we don't have access to 'how' here--aside from survived
|
|
vs died--so settle for general platitude */
|
|
final ? " when your adventure ended" : "");
|
|
enl_msg("There ", "is ", "was ", buf, "");
|
|
}
|
|
if (flags.friday13) {
|
|
/* let player know that friday13 penalty is/was in effect;
|
|
we don't say "it is/was Friday the 13th" because that was at
|
|
the start of the session and it might be past midnight (or
|
|
days later if the game has been paused without save/restore),
|
|
so phrase this similar to the start up message */
|
|
Sprintf(buf, " Bad things %s on Friday the 13th.",
|
|
!final ? "can happen"
|
|
: (final == ENL_GAMEOVERALIVE) ? "could have happened"
|
|
/* there's no may to tell whether -1 Luck made a
|
|
difference but hero has died... */
|
|
: "happened");
|
|
enlght_out(buf);
|
|
}
|
|
|
|
if (!Upolyd) {
|
|
int ulvl = (int) u.ulevel;
|
|
/* [flags.showexp currently does not matter; should it?] */
|
|
|
|
/* experience level is already shown above */
|
|
Sprintf(buf, "%-1ld experience point%s", u.uexp, plur(u.uexp));
|
|
/* TODO?
|
|
* Remove wizard-mode restriction since patient players can
|
|
* determine the numbers needed without resorting to spoilers
|
|
* (even before this started being disclosed for 'final';
|
|
* just enable 'showexp' and look at normal status lines
|
|
* after drinking gain level potions or eating wraith corpses
|
|
* or being level-drained by vampires).
|
|
*/
|
|
if (ulvl < 30 && (final || wizard)) {
|
|
long nxtlvl = newuexp(ulvl), delta = nxtlvl - u.uexp;
|
|
|
|
Sprintf(eos(buf), ", %ld %s%sneeded %s level %d",
|
|
delta, (u.uexp > 0) ? "more " : "",
|
|
/* present tense=="needed", past tense=="were needed" */
|
|
!final ? "" : (delta == 1L) ? "was " : "were ",
|
|
/* "for": grammatically iffy but less likely to wrap */
|
|
(ulvl < 18) ? "to attain" : "for", (ulvl + 1));
|
|
}
|
|
you_have(buf, "");
|
|
}
|
|
#ifdef SCORE_ON_BOTL
|
|
if (flags.showscore) {
|
|
/* describes what's shown on status line, which is an approximation;
|
|
only show it here if player has the 'showscore' option enabled */
|
|
Sprintf(buf, "%ld%s", botl_score(),
|
|
!final ? "" : " before end-of-game adjustments");
|
|
enl_msg("Your score ", "is ", "was ", buf, "");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* hit points, energy points, armor class -- essential information which
|
|
doesn't fit very well in other categories */
|
|
/*ARGSUSED*/
|
|
static void
|
|
basics_enlightenment(int mode UNUSED, int final)
|
|
{
|
|
static char Power[] = "energy points (spell power)";
|
|
char buf[BUFSZ];
|
|
int pw = u.uen, hp = (Upolyd ? u.mh : u.uhp),
|
|
pwmax = u.uenmax, hpmax = (Upolyd ? u.mhmax : u.uhpmax);
|
|
|
|
enlght_out(""); /* separator after background */
|
|
enlght_out("Basics:");
|
|
|
|
if (hp < 0)
|
|
hp = 0;
|
|
/* "1 out of 1" rather than "all" if max is only 1; should never happen */
|
|
if (hp == hpmax && hpmax > 1)
|
|
Sprintf(buf, "all %d hit points", hpmax);
|
|
else
|
|
Sprintf(buf, "%d out of %d hit point%s", hp, hpmax, plur(hpmax));
|
|
you_have(buf, "");
|
|
|
|
/* low max energy is feasible, so handle couple of extra special cases */
|
|
if (pwmax == 0 || (pw == pwmax && pwmax == 2)) /* both: not "all 2" */
|
|
Sprintf(buf, "%s %s", !pwmax ? "no" : "both", Power);
|
|
else if (pw == pwmax && pwmax > 2)
|
|
Sprintf(buf, "all %d %s", pwmax, Power);
|
|
else
|
|
Sprintf(buf, "%d out of %d %s", pw, pwmax, Power);
|
|
you_have(buf, "");
|
|
|
|
if (Upolyd) {
|
|
switch (mons[u.umonnum].mlevel) {
|
|
case 0:
|
|
/* status line currently being explained shows "HD:0" */
|
|
Strcpy(buf, "0 hit dice (actually 1/2)");
|
|
break;
|
|
case 1:
|
|
Strcpy(buf, "1 hit die");
|
|
break;
|
|
default:
|
|
Sprintf(buf, "%d hit dice", mons[u.umonnum].mlevel);
|
|
break;
|
|
}
|
|
you_have(buf, "");
|
|
}
|
|
|
|
find_ac(); /* enforces AC_MAX cap */
|
|
Sprintf(buf, "%d", u.uac);
|
|
if (abs(u.uac) == AC_MAX)
|
|
Sprintf(eos(buf), ", the %s possible",
|
|
(u.uac < 0) ? "best" : "worst");
|
|
enl_msg("Your armor class ", "is ", "was ", buf, "");
|
|
|
|
/* gold; similar to doprgold(#seegold) but without shop billing info;
|
|
same amount as shown on status line which ignores container contents */
|
|
{
|
|
static const char Your_wallet[] = "Your wallet ";
|
|
long umoney = money_cnt(g.invent);
|
|
|
|
if (!umoney) {
|
|
enl_msg(Your_wallet, "is ", "was ", "empty", "");
|
|
} else {
|
|
Sprintf(buf, "%ld %s", umoney, currency(umoney));
|
|
enl_msg(Your_wallet, "contains ", "contained ", buf, "");
|
|
}
|
|
}
|
|
|
|
if (flags.pickup) {
|
|
char ocl[MAXOCLASSES + 1];
|
|
|
|
Strcpy(buf, "on");
|
|
if (costly_spot(u.ux, u.uy)) {
|
|
/* being in a shop inhibits autopickup, even 'pickup_thrown' */
|
|
Strcat(buf, ", but temporarily disabled while inside the shop");
|
|
} else {
|
|
oc_to_str(flags.pickup_types, ocl);
|
|
Sprintf(eos(buf), " for %s%s%s", *ocl ? "'" : "",
|
|
*ocl ? ocl : "all types", *ocl ? "'" : "");
|
|
if (flags.pickup_thrown && *ocl)
|
|
Strcat(buf, " plus thrown"); /* show when not 'all types' */
|
|
if (g.apelist)
|
|
Strcat(buf, ", with exceptions");
|
|
}
|
|
} else
|
|
Strcpy(buf, "off");
|
|
enl_msg("Autopickup ", "is ", "was ", buf, "");
|
|
}
|
|
|
|
/* characteristics: expanded version of bottom line strength, dexterity, &c */
|
|
static void
|
|
characteristics_enlightenment(int mode, int final)
|
|
{
|
|
char buf[BUFSZ];
|
|
|
|
enlght_out("");
|
|
Sprintf(buf, "%s Characteristics:", !final ? "Current" : "Final");
|
|
enlght_out(buf);
|
|
|
|
/* bottom line order */
|
|
one_characteristic(mode, final, A_STR); /* strength */
|
|
one_characteristic(mode, final, A_DEX); /* dexterity */
|
|
one_characteristic(mode, final, A_CON); /* constitution */
|
|
one_characteristic(mode, final, A_INT); /* intelligence */
|
|
one_characteristic(mode, final, A_WIS); /* wisdom */
|
|
one_characteristic(mode, final, A_CHA); /* charisma */
|
|
}
|
|
|
|
/* display one attribute value for characteristics_enlightenment() */
|
|
static void
|
|
one_characteristic(int mode, int final, int attrindx)
|
|
{
|
|
extern const char *const attrname[]; /* attrib.c */
|
|
boolean hide_innate_value = FALSE, interesting_alimit;
|
|
int acurrent, abase, apeak, alimit;
|
|
const char *paren_pfx;
|
|
char subjbuf[BUFSZ], valubuf[BUFSZ], valstring[32];
|
|
|
|
/* being polymorphed or wearing certain cursed items prevents
|
|
hero from reliably tracking changes to characteristics so
|
|
we don't show base & peak values then; when the items aren't
|
|
cursed, hero could take them off to check underlying values
|
|
and we show those in such case so that player doesn't need
|
|
to actually resort to doing that */
|
|
if (Upolyd) {
|
|
hide_innate_value = TRUE;
|
|
} else if (Fixed_abil) {
|
|
if (stuck_ring(uleft, RIN_SUSTAIN_ABILITY)
|
|
|| stuck_ring(uright, RIN_SUSTAIN_ABILITY))
|
|
hide_innate_value = TRUE;
|
|
}
|
|
switch (attrindx) {
|
|
case A_STR:
|
|
if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER && uarmg->cursed)
|
|
hide_innate_value = TRUE;
|
|
break;
|
|
case A_DEX:
|
|
break;
|
|
case A_CON:
|
|
if (u_wield_art(ART_OGRESMASHER) && uwep->cursed)
|
|
hide_innate_value = TRUE;
|
|
break;
|
|
case A_INT:
|
|
if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
|
|
hide_innate_value = TRUE;
|
|
break;
|
|
case A_WIS:
|
|
if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
|
|
hide_innate_value = TRUE;
|
|
break;
|
|
case A_CHA:
|
|
break;
|
|
default:
|
|
return; /* impossible */
|
|
};
|
|
/* note: final disclosure includes MAGICENLIGHTENTMENT */
|
|
if ((mode & MAGICENLIGHTENMENT) && !Upolyd)
|
|
hide_innate_value = FALSE;
|
|
|
|
acurrent = ACURR(attrindx);
|
|
(void) attrval(attrindx, acurrent, valubuf); /* Sprintf(valubuf,"%d",) */
|
|
Sprintf(subjbuf, "Your %s ", attrname[attrindx]);
|
|
|
|
if (!hide_innate_value) {
|
|
/* show abase, amax, and/or attrmax if acurr doesn't match abase
|
|
(a magic bonus or penalty is in effect) or abase doesn't match
|
|
amax (some points have been lost to poison or exercise abuse
|
|
and are restorable) or attrmax is different from normal human
|
|
(while game is in progress; trying to reduce dependency on
|
|
spoilers to keep track of such stuff) or attrmax was different
|
|
from abase (at end of game; this attribute wasn't maxed out) */
|
|
abase = ABASE(attrindx);
|
|
apeak = AMAX(attrindx);
|
|
alimit = ATTRMAX(attrindx);
|
|
/* criterium for whether the limit is interesting varies */
|
|
interesting_alimit =
|
|
final ? TRUE /* was originally `(abase != alimit)' */
|
|
: (alimit != (attrindx != A_STR ? 18 : STR18(100)));
|
|
paren_pfx = final ? " (" : " (current; ";
|
|
if (acurrent != abase) {
|
|
Sprintf(eos(valubuf), "%sbase:%s", paren_pfx,
|
|
attrval(attrindx, abase, valstring));
|
|
paren_pfx = ", ";
|
|
}
|
|
if (abase != apeak) {
|
|
Sprintf(eos(valubuf), "%speak:%s", paren_pfx,
|
|
attrval(attrindx, apeak, valstring));
|
|
paren_pfx = ", ";
|
|
}
|
|
if (interesting_alimit) {
|
|
Sprintf(eos(valubuf), "%s%slimit:%s", paren_pfx,
|
|
/* more verbose if exceeding 'limit' due to magic bonus */
|
|
(acurrent > alimit) ? "innate " : "",
|
|
attrval(attrindx, alimit, valstring));
|
|
/* paren_pfx = ", "; */
|
|
}
|
|
if (acurrent != abase || abase != apeak || interesting_alimit)
|
|
Strcat(valubuf, ")");
|
|
}
|
|
enl_msg(subjbuf, "is ", "was ", valubuf, "");
|
|
}
|
|
|
|
/* status: selected obvious capabilities, assorted troubles */
|
|
static void
|
|
status_enlightenment(int mode, int final)
|
|
{
|
|
boolean magic = (mode & MAGICENLIGHTENMENT) ? TRUE : FALSE;
|
|
int cap;
|
|
char buf[BUFSZ], youtoo[BUFSZ], heldmon[BUFSZ];
|
|
boolean Riding = (u.usteed
|
|
/* if hero dies while dismounting, u.usteed will still
|
|
be set; we want to ignore steed in that situation */
|
|
&& !(final == ENL_GAMEOVERDEAD
|
|
&& !strcmp(g.killer.name, "riding accident")));
|
|
const char *steedname = (!Riding ? (char *) 0
|
|
: x_monnam(u.usteed,
|
|
u.usteed->mtame ? ARTICLE_YOUR : ARTICLE_THE,
|
|
(char *) 0,
|
|
(SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION),
|
|
FALSE));
|
|
|
|
/*\
|
|
* Status (many are abbreviated on bottom line; others are or
|
|
* should be discernible to the hero hence to the player)
|
|
\*/
|
|
enlght_out(""); /* separator after title or characteristics */
|
|
enlght_out(final ? "Final Status:" : "Current Status:");
|
|
|
|
Strcpy(youtoo, You_);
|
|
/* not a traditional status but inherently obvious to player; more
|
|
detail given below (attributes section) for magic enlightenment */
|
|
if (Upolyd) {
|
|
Strcpy(buf, "transformed");
|
|
if (ugenocided())
|
|
Sprintf(eos(buf), " and %s %s inside",
|
|
final ? "felt" : "feel", udeadinside());
|
|
you_are(buf, "");
|
|
}
|
|
/* not a trouble, but we want to display riding status before maybe
|
|
reporting steed as trapped or hero stuck to cursed saddle */
|
|
if (Riding) {
|
|
Sprintf(buf, "riding %s", steedname);
|
|
you_are(buf, "");
|
|
Sprintf(eos(youtoo), "and %s ", steedname);
|
|
}
|
|
/* other movement situations that hero should always know */
|
|
if (Levitation) {
|
|
if (Lev_at_will && magic)
|
|
you_are("levitating, at will", "");
|
|
else
|
|
enl_msg(youtoo, are, were, "levitating", from_what(LEVITATION));
|
|
} else if (Flying) { /* can only fly when not levitating */
|
|
enl_msg(youtoo, are, were, "flying", from_what(FLYING));
|
|
}
|
|
if (Underwater) {
|
|
you_are("underwater", "");
|
|
} else if (u.uinwater) {
|
|
you_are(Swimming ? "swimming" : "in water", from_what(SWIMMING));
|
|
} else if (walking_on_water()) {
|
|
/* show active Wwalking here, potential Wwalking elsewhere */
|
|
Sprintf(buf, "walking on %s",
|
|
is_pool(u.ux, u.uy) ? "water"
|
|
: is_lava(u.ux, u.uy) ? "lava"
|
|
: surface(u.ux, u.uy)); /* catchall; shouldn't happen */
|
|
you_are(buf, from_what(WWALKING));
|
|
}
|
|
if (Upolyd && (u.uundetected || U_AP_TYPE != M_AP_NOTHING))
|
|
youhiding(TRUE, final);
|
|
|
|
/* internal troubles, mostly in the order that prayer ranks them */
|
|
if (Stoned) {
|
|
if (final && (Stoned & I_SPECIAL))
|
|
enlght_out(" You turned into stone.");
|
|
else
|
|
you_are("turning to stone", "");
|
|
}
|
|
if (Slimed) {
|
|
if (final && (Slimed & I_SPECIAL))
|
|
enlght_out(" You turned into slime.");
|
|
else
|
|
you_are("turning into slime", "");
|
|
}
|
|
if (Strangled) {
|
|
if (u.uburied) {
|
|
you_are("buried", "");
|
|
} else {
|
|
if (final && (Strangled & I_SPECIAL)) {
|
|
enlght_out(" You died from strangulation.");
|
|
} else {
|
|
Strcpy(buf, "being strangled");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%ld)", (Strangled & TIMEOUT));
|
|
you_are(buf, from_what(STRANGLED));
|
|
}
|
|
}
|
|
}
|
|
if (Sick) {
|
|
/* the two types of sickness are lumped together; hero can be
|
|
afflicted by both but there is only one timeout; botl status
|
|
puts TermIll before FoodPois and death due to timeout reports
|
|
terminal illness if both are in effect, so do the same here */
|
|
if (final && (Sick & I_SPECIAL)) {
|
|
Sprintf(buf, " %sdied from %s.", You_, /* has trailing space */
|
|
(u.usick_type & SICK_NONVOMITABLE)
|
|
? "terminal illness" : "food poisoning");
|
|
enlght_out(buf);
|
|
} else {
|
|
/* unlike death due to sickness, report the two cases separately
|
|
because it is possible to cure one without curing the other */
|
|
if (u.usick_type & SICK_NONVOMITABLE)
|
|
you_are("terminally sick from illness", "");
|
|
if (u.usick_type & SICK_VOMITABLE)
|
|
you_are("terminally sick from food poisoning", "");
|
|
}
|
|
}
|
|
if (Vomiting)
|
|
you_are("nauseated", "");
|
|
if (Stunned)
|
|
you_are("stunned", "");
|
|
if (Confusion)
|
|
you_are("confused", "");
|
|
if (Hallucination)
|
|
you_are("hallucinating", "");
|
|
if (Blind) {
|
|
/* from_what() (currently wizard-mode only) checks !haseyes()
|
|
before u.uroleplay.blind, so we should too */
|
|
Sprintf(buf, "%s blind",
|
|
!haseyes(g.youmonst.data) ? "innately"
|
|
: u.uroleplay.blind ? "permanently"
|
|
/* better phrasing desperately wanted... */
|
|
: Blindfolded_only ? "deliberately"
|
|
: "temporarily");
|
|
if (wizard && (Blinded & TIMEOUT) != 0L
|
|
&& !u.uroleplay.blind && haseyes(g.youmonst.data))
|
|
Sprintf(eos(buf), " (%ld)", (Blinded & TIMEOUT));
|
|
/* !haseyes: avoid "you are innately blind innately" */
|
|
you_are(buf, !haseyes(g.youmonst.data) ? "" : from_what(BLINDED));
|
|
}
|
|
if (Deaf)
|
|
you_are("deaf", from_what(DEAF));
|
|
|
|
/* external troubles, more or less */
|
|
if (Punished) {
|
|
if (uball) {
|
|
Sprintf(buf, "chained to %s", ansimpleoname(uball));
|
|
} else {
|
|
impossible("Punished without uball?");
|
|
Strcpy(buf, "punished");
|
|
}
|
|
you_are(buf, "");
|
|
}
|
|
if (u.utrap) {
|
|
char predicament[BUFSZ];
|
|
boolean anchored = (u.utraptype == TT_BURIEDBALL);
|
|
|
|
(void) trap_predicament(predicament, final, wizard);
|
|
if (u.usteed) { /* not `Riding' here */
|
|
Sprintf(buf, "%s%s ", anchored ? "you and " : "", steedname);
|
|
*buf = highc(*buf);
|
|
enl_msg(buf, (anchored ? "are " : "is "),
|
|
(anchored ? "were " : "was "), predicament, "");
|
|
} else
|
|
you_are(predicament, "");
|
|
} /* (u.utrap) */
|
|
heldmon[0] = '\0'; /* lint suppression */
|
|
if (u.ustuck) { /* includes u.uswallow */
|
|
Strcpy(heldmon, a_monnam(u.ustuck));
|
|
if (!strcmp(heldmon, "it")
|
|
&& (!has_mgivenname(u.ustuck)
|
|
|| strcmp(MGIVENNAME(u.ustuck), "it") != 0))
|
|
Strcpy(heldmon, "an unseen creature");
|
|
}
|
|
if (u.uswallow) { /* implies u.ustuck is non-Null */
|
|
Snprintf(buf, sizeof buf, "%s by %s",
|
|
digests(u.ustuck->data) ? "swallowed" : "engulfed",
|
|
heldmon);
|
|
if (dmgtype(u.ustuck->data, AD_DGST)) {
|
|
/* if final, death via digestion can be deduced by u.uswallow
|
|
still being True and u.uswldtim having been decremented to 0 */
|
|
if (final && !u.uswldtim)
|
|
Strcat(buf, " and got totally digested");
|
|
else
|
|
Sprintf(eos(buf), " and %s being digested",
|
|
final ? "were" : "are");
|
|
}
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%u)", u.uswldtim);
|
|
you_are(buf, "");
|
|
} else if (u.ustuck) {
|
|
boolean ustick = (Upolyd && sticks(g.youmonst.data));
|
|
int dx = u.ustuck->mx - u.ux, dy = u.ustuck->my - u.uy;
|
|
|
|
Snprintf(buf, sizeof buf, "%s %s (%s)",
|
|
ustick ? "holding" : "held by",
|
|
heldmon, dxdy_to_dist_descr(dx, dy, TRUE));
|
|
you_are(buf, "");
|
|
}
|
|
if (Riding) {
|
|
struct obj *saddle = which_armor(u.usteed, W_SADDLE);
|
|
|
|
if (saddle && saddle->cursed) {
|
|
Sprintf(buf, "stuck to %s %s", s_suffix(steedname),
|
|
simpleonames(saddle));
|
|
you_are(buf, "");
|
|
}
|
|
}
|
|
if (Wounded_legs) {
|
|
/* EWounded_legs is used to track left/right/both rather than some
|
|
form of extrinsic impairment; HWounded_legs is used for timeout;
|
|
both apply to steed instead of hero when mounted */
|
|
long whichleg = (EWounded_legs & BOTH_SIDES);
|
|
const char *bp = u.usteed ? mbodypart(u.usteed, LEG) : body_part(LEG),
|
|
*article = "a ", /* precedes "wounded", so never "an " */
|
|
*leftright = "";
|
|
|
|
if (whichleg == BOTH_SIDES)
|
|
bp = makeplural(bp), article = "";
|
|
else
|
|
leftright = (whichleg == LEFT_SIDE) ? "left " : "right ";
|
|
Sprintf(buf, "%swounded %s%s", article, leftright, bp);
|
|
|
|
/* when mounted, Wounded_legs applies to steed rather than to
|
|
hero; we only report steed's wounded legs in wizard mode */
|
|
if (u.usteed) { /* not `Riding' here */
|
|
if (wizard && steedname) {
|
|
char steednambuf[BUFSZ];
|
|
|
|
Strcpy(steednambuf, steedname);
|
|
*steednambuf = highc(*steednambuf);
|
|
enl_msg(steednambuf, " has ", " had ", buf, "");
|
|
}
|
|
} else {
|
|
you_have(buf, "");
|
|
}
|
|
}
|
|
if (Glib) {
|
|
Sprintf(buf, "slippery %s", fingers_or_gloves(TRUE));
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%ld)", (Glib & TIMEOUT));
|
|
you_have(buf, "");
|
|
}
|
|
if (Fumbling) {
|
|
if (magic || cause_known(FUMBLING))
|
|
enl_msg(You_, "fumble", "fumbled", "", from_what(FUMBLING));
|
|
}
|
|
if (Sleepy) {
|
|
if (magic || cause_known(SLEEPY)) {
|
|
Strcpy(buf, from_what(SLEEPY));
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%ld)", (HSleepy & TIMEOUT));
|
|
enl_msg("You ", "fall", "fell", " asleep uncontrollably", buf);
|
|
}
|
|
}
|
|
/* hunger/nutrition */
|
|
if (Hunger) {
|
|
if (magic || cause_known(HUNGER))
|
|
enl_msg(You_, "hunger", "hungered", " rapidly",
|
|
from_what(HUNGER));
|
|
}
|
|
Strcpy(buf, hu_stat[u.uhs]); /* hunger status; omitted if "normal" */
|
|
mungspaces(buf); /* strip trailing spaces */
|
|
/* status line doesn't show hunger when state is "not hungry", we do;
|
|
needed for wizard mode's reveal of u.uhunger but add it for everyone */
|
|
if (!*buf)
|
|
Strcpy(buf, "not hungry");
|
|
if (*buf) { /* (since "not hungry" was added, this will always be True) */
|
|
*buf = lowc(*buf); /* override capitalization */
|
|
if (!strcmp(buf, "weak"))
|
|
Strcat(buf, " from severe hunger");
|
|
else if (!strncmp(buf, "faint", 5)) /* fainting, fainted */
|
|
Strcat(buf, " due to starvation");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " <%d>", u.uhunger);
|
|
you_are(buf, "");
|
|
}
|
|
/* encumbrance */
|
|
if ((cap = near_capacity()) > UNENCUMBERED) {
|
|
const char *adj = "?_?"; /* (should always get overridden) */
|
|
|
|
Strcpy(buf, enc_stat[cap]);
|
|
*buf = lowc(*buf);
|
|
switch (cap) {
|
|
case SLT_ENCUMBER:
|
|
adj = "slightly";
|
|
break; /* burdened */
|
|
case MOD_ENCUMBER:
|
|
adj = "moderately";
|
|
break; /* stressed */
|
|
case HVY_ENCUMBER:
|
|
adj = "very";
|
|
break; /* strained */
|
|
case EXT_ENCUMBER:
|
|
adj = "extremely";
|
|
break; /* overtaxed */
|
|
case OVERLOADED:
|
|
adj = "not possible";
|
|
break;
|
|
}
|
|
if (wizard)
|
|
Sprintf(eos(buf), " <%d>", inv_weight());
|
|
Sprintf(eos(buf), "; movement %s %s%s", !final ? "is" : "was", adj,
|
|
(cap < OVERLOADED) ? " slowed" : "");
|
|
you_are(buf, "");
|
|
} else {
|
|
/* last resort entry, guarantees Status section is non-empty
|
|
(no longer needed for that purpose since weapon status added;
|
|
still useful though) */
|
|
Strcpy(buf, "unencumbered");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " <%d>", inv_weight());
|
|
you_are(buf, "");
|
|
}
|
|
/* current weapon(s) and corresponding skill level(s) */
|
|
weapon_insight(final);
|
|
/* unlike ring of increase accuracy's effect, the monk's suit penalty
|
|
is too blatant to be restricted to magical enlightenment */
|
|
if (iflags.tux_penalty && !Upolyd) {
|
|
(void) enlght_combatinc("to hit", -g.urole.spelarmr, final, buf);
|
|
/* if from_what() ever gets extended from wizard mode to normal
|
|
play, it could be adapted to handle this */
|
|
Sprintf(eos(buf), " due to your %s", suit_simple_name(uarm));
|
|
you_have(buf, "");
|
|
}
|
|
/* report 'nudity' */
|
|
if (!uarm && !uarmu && !uarmc && !uarms && !uarmg && !uarmf && !uarmh) {
|
|
if (u.uroleplay.nudist)
|
|
enl_msg(You_, "do", "did", " not wear any armor", "");
|
|
else
|
|
you_are("not wearing any armor", "");
|
|
}
|
|
}
|
|
|
|
/* extracted from status_enlightenment() to reduce clutter there */
|
|
static void
|
|
weapon_insight(int final)
|
|
{
|
|
char buf[BUFSZ];
|
|
int wtype;
|
|
|
|
/* report being weaponless; distinguish whether gloves are worn
|
|
[perhaps mention silver ring(s) when not wearning gloves?] */
|
|
if (!uwep) {
|
|
you_are(empty_handed(), "");
|
|
|
|
/* two-weaponing implies hands and
|
|
a weapon or wep-tool (not other odd stuff) in each hand */
|
|
} else if (u.twoweap) {
|
|
you_are("wielding two weapons at once", "");
|
|
|
|
/* report most weapons by their skill class (so a katana will be
|
|
described as a long sword, for instance; mattock, hook, and aklys
|
|
are exceptions), or wielded non-weapon item by its object class */
|
|
} else {
|
|
const char *what = weapon_descr(uwep);
|
|
|
|
/* [what about other silver items?] */
|
|
if (uwep->otyp == SHIELD_OF_REFLECTION)
|
|
what = shield_simple_name(uwep); /* silver|smooth shield */
|
|
else if (is_wet_towel(uwep))
|
|
what = /* (uwep->spe < 3) ? "moist towel" : */ "wet towel";
|
|
|
|
if (!strcmpi(what, "armor") || !strcmpi(what, "food")
|
|
|| !strcmpi(what, "venom"))
|
|
Sprintf(buf, "wielding some %s", what);
|
|
else
|
|
/* [maybe include known blessed?] */
|
|
Sprintf(buf, "wielding %s",
|
|
(uwep->quan == 1L) ? an(what) : makeplural(what));
|
|
you_are(buf, "");
|
|
}
|
|
|
|
/*
|
|
* Skill with current weapon. Might help players who've never
|
|
* noticed #enhance or decided that it was pointless.
|
|
*/
|
|
if ((wtype = weapon_type(uwep)) != P_NONE && (!uwep || !is_ammo(uwep))) {
|
|
char sklvlbuf[20];
|
|
int sklvl = P_SKILL(wtype);
|
|
boolean hav = (sklvl != P_UNSKILLED && sklvl != P_SKILLED);
|
|
|
|
if (sklvl == P_ISRESTRICTED)
|
|
Strcpy(sklvlbuf, "no");
|
|
else
|
|
(void) lcase(skill_level_name(wtype, sklvlbuf));
|
|
/* "you have no/basic/expert/master/grand-master skill with <skill>"
|
|
or "you are unskilled/skilled in <skill>" */
|
|
Sprintf(buf, "%s %s %s", sklvlbuf,
|
|
hav ? "skill with" : "in", skill_name(wtype));
|
|
|
|
if (!u.twoweap) {
|
|
if (can_advance(wtype, FALSE))
|
|
Sprintf(eos(buf), " and %s that",
|
|
!final ? "can enhance" : "could have enhanced");
|
|
if (hav)
|
|
you_have(buf, "");
|
|
else
|
|
you_are(buf, "");
|
|
|
|
} else { /* two-weapon */
|
|
static const char also_[] = "also ";
|
|
char pfx[QBUFSZ], sfx[QBUFSZ],
|
|
sknambuf2[20], sklvlbuf2[20], twobuf[20];
|
|
const char *also = "", *also2 = "", *also3 = (char *) 0,
|
|
*verb_present, *verb_past;
|
|
int wtype2 = weapon_type(uswapwep),
|
|
sklvl2 = P_SKILL(wtype2),
|
|
twoskl = P_SKILL(P_TWO_WEAPON_COMBAT);
|
|
boolean a1, a2, ab,
|
|
hav2 = (sklvl2 != P_UNSKILLED && sklvl2 != P_SKILLED);
|
|
|
|
/* normally hero must have access to two-weapon skill in
|
|
order to initiate u.twoweap, but not if polymorphed into
|
|
a form which has multiple weapon attacks, so we need to
|
|
avoid getting bitten by unexpected skill value */
|
|
if (twoskl == P_ISRESTRICTED) {
|
|
twoskl = P_UNSKILLED;
|
|
/* restricted is the same as unskilled as far as bonus
|
|
or penalty goes, and it isn't ordinarily seen so
|
|
skill_level_name() returns "Unknown" for it */
|
|
Strcpy(twobuf, "restricted");
|
|
} else {
|
|
(void) lcase(skill_level_name(P_TWO_WEAPON_COMBAT, twobuf));
|
|
}
|
|
|
|
/* keep buf[] from above in case skill levels match */
|
|
pfx[0] = sfx[0] = '\0';
|
|
if (twoskl < sklvl) {
|
|
/* twoskil won't be restricted so sklvl is at least basic */
|
|
Sprintf(pfx, "Your skill in %s ", skill_name(wtype));
|
|
Sprintf(sfx, " limited by being %s with two weapons", twobuf);
|
|
also = also_;
|
|
} else if (twoskl > sklvl) {
|
|
/* sklvl might be restricted */
|
|
Strcpy(pfx, "Your two weapon skill ");
|
|
Strcpy(sfx, " limited by ");
|
|
if (sklvl > P_ISRESTRICTED)
|
|
Sprintf(eos(sfx), "being %s", sklvlbuf);
|
|
else
|
|
Sprintf(eos(sfx), "having no skill");
|
|
Sprintf(eos(sfx), " with %s", skill_name(wtype));
|
|
also2 = also_;
|
|
} else {
|
|
Strcat(buf, " and two weapons");
|
|
also3 = also_;
|
|
}
|
|
if (*pfx)
|
|
enl_msg(pfx, "is", "was", sfx, "");
|
|
else if (hav)
|
|
you_have(buf, "");
|
|
else
|
|
you_are(buf, "");
|
|
|
|
/* skip comparison between secondary and two-weapons if it is
|
|
identical to the comparison between primary and twoweap */
|
|
if (wtype2 != wtype) {
|
|
Strcpy(sknambuf2, skill_name(wtype2));
|
|
(void) lcase(skill_level_name(wtype2, sklvlbuf2));
|
|
verb_present = "is", verb_past = "was";
|
|
pfx[0] = sfx[0] = buf[0] = '\0';
|
|
if (twoskl < sklvl2) {
|
|
/* twoskil is at least unskilled, sklvl2 at least basic */
|
|
Sprintf(pfx, "Your skill in %s ", sknambuf2);
|
|
Sprintf(sfx, " %slimited by being %s with two weapons",
|
|
also, twobuf);
|
|
} else if (twoskl > sklvl2) {
|
|
/* sklvl2 might be restricted */
|
|
Strcpy(pfx, "Your two weapon skill ");
|
|
Sprintf(sfx, " %slimited by ", also2);
|
|
if (sklvl2 > P_ISRESTRICTED)
|
|
Sprintf(eos(sfx), "being %s", sklvlbuf2);
|
|
else
|
|
Strcat(eos(sfx), "having no skill");
|
|
Sprintf(eos(sfx), " with %s", sknambuf2);
|
|
} else {
|
|
/* equal; two-weapon is at least unskilled, so sklvl2 is
|
|
too; "you [also] have basic/expert/master/grand-master
|
|
skill with <skill>" or "you [also] are unskilled/
|
|
skilled in <skill> */
|
|
Sprintf(buf, "%s %s %s", sklvlbuf2,
|
|
hav2 ? "skill with" : "in", sknambuf2);
|
|
Strcat(buf, " and two weapons");
|
|
if (also3) {
|
|
Strcpy(pfx, "You also ");
|
|
Snprintf(sfx, sizeof(sfx), " %s", buf), buf[0] = '\0';
|
|
verb_present = hav2 ? "have" : "are";
|
|
verb_past = hav2 ? "had" : "were";
|
|
}
|
|
}
|
|
if (*pfx)
|
|
enl_msg(pfx, verb_present, verb_past, sfx, "");
|
|
else if (hav2)
|
|
you_have(buf, "");
|
|
else
|
|
you_are(buf, "");
|
|
} /* wtype2 != wtype */
|
|
|
|
/* if training and available skill credits already allow
|
|
#enhance for any of primary, secondary, or two-weapon,
|
|
tell the player; avoid attempting figure out whether
|
|
spending skill credits enhancing one might make either
|
|
or both of the others become ineligible for enhancement */
|
|
a1 = can_advance(wtype, FALSE);
|
|
a2 = (wtype2 != wtype) ? can_advance(wtype2, FALSE) : FALSE;
|
|
ab = can_advance(P_TWO_WEAPON_COMBAT, FALSE);
|
|
if (a1 || a2 || ab) {
|
|
static const char also_wik_[] = " and also with ";
|
|
|
|
/* for just one, the conditionals yield
|
|
1) "skill with <that one>"; for more than one:
|
|
2) "skills with <primary> and also with <secondary>" or
|
|
3) "skills with <primary> and also with two-weapons" or
|
|
4) "skills with <secondary> and also with two-weapons" or
|
|
5) "skills with <primary>, <secondary>, and two-weapons"
|
|
(no 'also's or extra 'with's for case 5); when primary
|
|
and secondary use the same skill, only cases 1 and 3 are
|
|
possible because 'a2' gets forced to False above */
|
|
Sprintf(sfx, " skill%s with %s%s%s%s%s",
|
|
((int) a1 + (int) a2 + (int) ab > 1) ? "s" : "",
|
|
a1 ? skill_name(wtype) : "",
|
|
((a1 && a2 && ab) ? ", "
|
|
: (a1 && (a2 || ab)) ? also_wik_ : ""),
|
|
a2 ? skill_name(wtype2) : "",
|
|
((a1 && a2 && ab) ? ", and "
|
|
: (a2 && ab) ? also_wik_ : ""),
|
|
ab ? "two weapons" : "");
|
|
enl_msg(You_, "can enhance", "could have enhanced", sfx, "");
|
|
}
|
|
} /* two-weapon */
|
|
} /* skill applies */
|
|
}
|
|
|
|
/* attributes: intrinsics and the like, other non-obvious capabilities */
|
|
static void
|
|
attributes_enlightenment(int unused_mode UNUSED, int final)
|
|
{
|
|
static NEARDATA const char
|
|
if_surroundings_permitted[] = " if surroundings permitted";
|
|
int ltmp, armpro;
|
|
char buf[BUFSZ];
|
|
|
|
/*\
|
|
* Attributes
|
|
\*/
|
|
enlght_out("");
|
|
enlght_out(final ? "Final Attributes:" : "Current Attributes:");
|
|
|
|
if (u.uevent.uhand_of_elbereth) {
|
|
static const char *const hofe_titles[3] = { "the Hand of Elbereth",
|
|
"the Envoy of Balance",
|
|
"the Glory of Arioch" };
|
|
you_are(hofe_titles[u.uevent.uhand_of_elbereth - 1], "");
|
|
}
|
|
|
|
Sprintf(buf, "%s", piousness(TRUE, "aligned"));
|
|
if (u.ualign.record >= 0)
|
|
you_are(buf, "");
|
|
else
|
|
you_have(buf, "");
|
|
|
|
if (wizard) {
|
|
Sprintf(buf, " %d", u.ualign.record);
|
|
enl_msg("Your alignment ", "is", "was", buf, "");
|
|
}
|
|
|
|
/*** Resistances to troubles ***/
|
|
if (Invulnerable)
|
|
you_are("invulnerable", from_what(INVULNERABLE));
|
|
if (Antimagic)
|
|
you_are("magic-protected", from_what(ANTIMAGIC));
|
|
if (Fire_resistance)
|
|
you_are("fire resistant", from_what(FIRE_RES));
|
|
if (u_adtyp_resistance_obj(AD_FIRE))
|
|
enl_msg("Your items ", "are", "were", " protected from fire",
|
|
item_what(AD_FIRE));
|
|
if (Cold_resistance)
|
|
you_are("cold resistant", from_what(COLD_RES));
|
|
if (u_adtyp_resistance_obj(AD_COLD))
|
|
enl_msg("Your items ", "are", "were", " protected from cold",
|
|
item_what(AD_COLD));
|
|
if (Sleep_resistance)
|
|
you_are("sleep resistant", from_what(SLEEP_RES));
|
|
if (Disint_resistance)
|
|
you_are("disintegration resistant", from_what(DISINT_RES));
|
|
if (u_adtyp_resistance_obj(AD_DISN))
|
|
enl_msg("Your items ", "are", "were",
|
|
" protected from disintegration", item_what(AD_DISN));
|
|
if (Shock_resistance)
|
|
you_are("shock resistant", from_what(SHOCK_RES));
|
|
if (u_adtyp_resistance_obj(AD_ELEC))
|
|
enl_msg("Your items ", "are", "were",
|
|
" protected from electric shocks", item_what(AD_ELEC));
|
|
if (Poison_resistance)
|
|
you_are("poison resistant", from_what(POISON_RES));
|
|
if (Acid_resistance) {
|
|
Sprintf(buf, "%.20s%.30s",
|
|
temp_resist(ACID_RES) ? "temporarily " : "",
|
|
"acid resistant");
|
|
you_are(buf, from_what(ACID_RES));
|
|
}
|
|
if (u_adtyp_resistance_obj(AD_ACID))
|
|
enl_msg("Your items ", "are", "were", " protected from acid",
|
|
item_what(AD_ACID));
|
|
if (Drain_resistance)
|
|
you_are("level-drain resistant", from_what(DRAIN_RES));
|
|
if (Sick_resistance)
|
|
you_are("immune to sickness", from_what(SICK_RES));
|
|
if (Stone_resistance) {
|
|
Sprintf(buf, "%.20s%.30s",
|
|
temp_resist(STONE_RES) ? "temporarily " : "",
|
|
"petrification resistant");
|
|
you_are(buf, from_what(STONE_RES));
|
|
}
|
|
if (Halluc_resistance)
|
|
enl_msg(You_, "resist", "resisted", " hallucinations",
|
|
from_what(HALLUC_RES));
|
|
if (u.uedibility)
|
|
you_can("recognize detrimental food", "");
|
|
|
|
/*** Vision and senses ***/
|
|
if (!Blind && (Blinded || !haseyes(g.youmonst.data)))
|
|
you_can("see", from_what(-BLINDED)); /* Eyes of the Overworld */
|
|
if (See_invisible) {
|
|
if (!Blind)
|
|
enl_msg(You_, "see", "saw", " invisible", from_what(SEE_INVIS));
|
|
else
|
|
enl_msg(You_, "will see", "would have seen",
|
|
" invisible when not blind", from_what(SEE_INVIS));
|
|
}
|
|
if (Blind_telepat)
|
|
you_are("telepathic", from_what(TELEPAT));
|
|
if (Warning)
|
|
you_are("warned", from_what(WARNING));
|
|
if (Warn_of_mon && g.context.warntype.obj) {
|
|
Sprintf(buf, "aware of the presence of %s",
|
|
(g.context.warntype.obj & M2_ORC) ? "orcs"
|
|
: (g.context.warntype.obj & M2_ELF) ? "elves"
|
|
: (g.context.warntype.obj & M2_DEMON) ? "demons" : something);
|
|
you_are(buf, from_what(WARN_OF_MON));
|
|
}
|
|
if (Warn_of_mon && g.context.warntype.polyd) {
|
|
Sprintf(buf, "aware of the presence of %s",
|
|
((g.context.warntype.polyd & (M2_HUMAN | M2_ELF))
|
|
== (M2_HUMAN | M2_ELF))
|
|
? "humans and elves"
|
|
: (g.context.warntype.polyd & M2_HUMAN)
|
|
? "humans"
|
|
: (g.context.warntype.polyd & M2_ELF)
|
|
? "elves"
|
|
: (g.context.warntype.polyd & M2_ORC)
|
|
? "orcs"
|
|
: (g.context.warntype.polyd & M2_DEMON)
|
|
? "demons"
|
|
: "certain monsters");
|
|
you_are(buf, "");
|
|
}
|
|
if (Warn_of_mon && g.context.warntype.speciesidx >= LOW_PM) {
|
|
Sprintf(buf, "aware of the presence of %s",
|
|
makeplural(mons[g.context.warntype.speciesidx].pmnames[NEUTRAL]));
|
|
you_are(buf, from_what(WARN_OF_MON));
|
|
}
|
|
if (Undead_warning)
|
|
you_are("warned of undead", from_what(WARN_UNDEAD));
|
|
if (Searching)
|
|
you_have("automatic searching", from_what(SEARCHING));
|
|
if (Clairvoyant) {
|
|
you_are("clairvoyant", from_what(CLAIRVOYANT));
|
|
} else if ((HClairvoyant || EClairvoyant) && BClairvoyant) {
|
|
Strcpy(buf, from_what(-CLAIRVOYANT));
|
|
(void) strsubst(buf, " because of ", " if not for ");
|
|
enl_msg(You_, "could be", "could have been", " clairvoyant", buf);
|
|
}
|
|
if (Infravision)
|
|
you_have("infravision", from_what(INFRAVISION));
|
|
if (Detect_monsters) {
|
|
Strcpy(buf, "sensing the presence of monsters");
|
|
if (wizard) {
|
|
long detectmon_timeout = (HDetect_monsters & TIMEOUT);
|
|
|
|
if (detectmon_timeout)
|
|
Sprintf(eos(buf), " (%ld)", detectmon_timeout);
|
|
}
|
|
you_are(buf, "");
|
|
}
|
|
if (u.umconf) { /* 'u.umconf' is a counter rather than a timeout */
|
|
Strcpy(buf, " monsters when hitting them");
|
|
if (wizard && !final) {
|
|
if (u.umconf == 1)
|
|
Strcat(buf, " (next hit only)");
|
|
else /* u.umconf > 1 */
|
|
Sprintf(eos(buf), " (next %u hits)", u.umconf);
|
|
}
|
|
enl_msg(You_, "will confuse", "would have confused", buf, "");
|
|
}
|
|
|
|
/*** Appearance and behavior ***/
|
|
if (Adornment) {
|
|
int adorn = 0;
|
|
|
|
if (uleft && uleft->otyp == RIN_ADORNMENT)
|
|
adorn += uleft->spe;
|
|
if (uright && uright->otyp == RIN_ADORNMENT)
|
|
adorn += uright->spe;
|
|
/* the sum might be 0 (+0 ring or two which negate each other);
|
|
that yields "you are charismatic" (which isn't pointless
|
|
because it potentially impacts seduction attacks) */
|
|
Sprintf(buf, "%scharismatic",
|
|
(adorn > 0) ? "more " : (adorn < 0) ? "less " : "");
|
|
you_are(buf, from_what(ADORNED));
|
|
}
|
|
if (Invisible)
|
|
you_are("invisible", from_what(INVIS));
|
|
else if (Invis)
|
|
you_are("invisible to others", from_what(INVIS));
|
|
/* ordinarily "visible" is redundant; this is a special case for
|
|
the situation when invisibility would be an expected attribute */
|
|
else if ((HInvis || EInvis) && BInvis)
|
|
you_are("visible", from_what(-INVIS));
|
|
if (Displaced)
|
|
you_are("displaced", from_what(DISPLACED));
|
|
if (Stealth)
|
|
you_are("stealthy", from_what(STEALTH));
|
|
if (Aggravate_monster)
|
|
enl_msg("You aggravate", "", "d", " monsters",
|
|
from_what(AGGRAVATE_MONSTER));
|
|
if (Conflict)
|
|
enl_msg("You cause", "", "d", " conflict", from_what(CONFLICT));
|
|
|
|
/*** Transportation ***/
|
|
if (Jumping)
|
|
you_can("jump", from_what(JUMPING));
|
|
if (Teleportation)
|
|
you_can("teleport", from_what(TELEPORT));
|
|
if (Teleport_control)
|
|
you_have("teleport control", from_what(TELEPORT_CONTROL));
|
|
/* actively levitating handled earlier as a status condition */
|
|
if (BLevitation) { /* levitation is blocked */
|
|
long save_BLev = BLevitation;
|
|
|
|
BLevitation = 0L;
|
|
if (Levitation) {
|
|
/* either trapped in the floor or inside solid rock
|
|
(or both if chained to buried iron ball and have
|
|
moved one step into solid rock somehow) */
|
|
boolean trapped = (save_BLev & I_SPECIAL) != 0L,
|
|
terrain = (save_BLev & FROMOUTSIDE) != 0L;
|
|
|
|
Sprintf(buf, "%s%s%s",
|
|
trapped ? " if not trapped" : "",
|
|
(trapped && terrain) ? " and" : "",
|
|
terrain ? if_surroundings_permitted : "");
|
|
enl_msg(You_, "would levitate", "would have levitated", buf, "");
|
|
}
|
|
BLevitation = save_BLev;
|
|
}
|
|
/* actively flying handled earlier as a status condition */
|
|
if (BFlying) { /* flight is blocked */
|
|
long save_BFly = BFlying;
|
|
|
|
BFlying = 0L;
|
|
if (Flying) {
|
|
enl_msg(You_, "would fly", "would have flown",
|
|
/* wording quibble: for past tense, "hadn't been"
|
|
would sound better than "weren't" (and
|
|
"had permitted" better than "permitted"), but
|
|
"weren't" and "permitted" are adequate so the
|
|
extra complexity to handle that isn't worth it */
|
|
Levitation
|
|
? " if you weren't levitating"
|
|
: (save_BFly == I_SPECIAL)
|
|
/* this is an oversimpliction; being trapped
|
|
might also be blocking levitation so flight
|
|
would still be blocked after escaping trap */
|
|
? " if you weren't trapped"
|
|
: (save_BFly == FROMOUTSIDE)
|
|
? if_surroundings_permitted
|
|
/* two or more of levitation, surroundings,
|
|
and being trapped in the floor */
|
|
: " if circumstances permitted",
|
|
"");
|
|
}
|
|
BFlying = save_BFly;
|
|
}
|
|
/* including this might bring attention to the fact that ceiling
|
|
clinging has inconsistencies... */
|
|
if (is_clinger(g.youmonst.data)) {
|
|
boolean has_lid = has_ceiling(&u.uz);
|
|
|
|
if (has_lid && !u.uinwater) {
|
|
you_can("cling to the ceiling", "");
|
|
} else {
|
|
Sprintf(buf, " to the ceiling if %s%s%s",
|
|
!has_lid ? "there was one" : "",
|
|
(!has_lid && u.uinwater) ? " and " : "",
|
|
u.uinwater ? (Underwater ? "you weren't underwater"
|
|
: "you weren't in the water") : "");
|
|
/* past tense is applicable for death while Unchanging */
|
|
enl_msg(You_, "could cling", "could have clung", buf, "");
|
|
}
|
|
}
|
|
/* actively walking on water handled earlier as a status condition */
|
|
if (Wwalking && !walking_on_water())
|
|
you_can("walk on water", from_what(WWALKING));
|
|
/* actively swimming (in water but not under it) handled earlier */
|
|
if (Swimming && (Underwater || !u.uinwater))
|
|
you_can("swim", from_what(SWIMMING));
|
|
if (Breathless)
|
|
you_can("survive without air", from_what(MAGICAL_BREATHING));
|
|
else if (Amphibious)
|
|
you_can("breathe water", from_what(MAGICAL_BREATHING));
|
|
if (Passes_walls)
|
|
you_can("walk through walls", from_what(PASSES_WALLS));
|
|
|
|
/*** Physical attributes ***/
|
|
if (Regeneration)
|
|
enl_msg("You regenerate", "", "d", "", from_what(REGENERATION));
|
|
if (Slow_digestion)
|
|
you_have("slower digestion", from_what(SLOW_DIGESTION));
|
|
if (u.uhitinc) {
|
|
(void) enlght_combatinc("to hit", u.uhitinc, final, buf);
|
|
if (iflags.tux_penalty && !Upolyd)
|
|
Sprintf(eos(buf), " %s your suit's penalty",
|
|
(u.uhitinc < 0) ? "increasing"
|
|
: (u.uhitinc < 4 * g.urole.spelarmr / 5)
|
|
? "partly offsetting"
|
|
: (u.uhitinc < g.urole.spelarmr) ? "nearly offseting"
|
|
: "overcoming");
|
|
you_have(buf, "");
|
|
}
|
|
if (u.udaminc)
|
|
you_have(enlght_combatinc("damage", u.udaminc, final, buf), "");
|
|
if (u.uspellprot || Protection) {
|
|
int prot = 0;
|
|
|
|
if (uleft && uleft->otyp == RIN_PROTECTION)
|
|
prot += uleft->spe;
|
|
if (uright && uright->otyp == RIN_PROTECTION)
|
|
prot += uright->spe;
|
|
if (uamul && uamul->otyp == AMULET_OF_GUARDING)
|
|
prot += 2;
|
|
if (HProtection & INTRINSIC)
|
|
prot += u.ublessed;
|
|
prot += u.uspellprot;
|
|
if (prot)
|
|
you_have(enlght_combatinc("defense", prot, final, buf), "");
|
|
}
|
|
if ((armpro = magic_negation(&g.youmonst)) > 0) {
|
|
/* magic cancellation factor, conferred by worn armor */
|
|
static const char *const mc_types[] = {
|
|
"" /*ordinary*/, "warded", "guarded", "protected",
|
|
};
|
|
/* sanity check */
|
|
if (armpro >= SIZE(mc_types))
|
|
armpro = SIZE(mc_types) - 1;
|
|
you_are(mc_types[armpro], "");
|
|
}
|
|
if (Half_physical_damage)
|
|
enlght_halfdmg(HALF_PHDAM, final);
|
|
if (Half_spell_damage)
|
|
enlght_halfdmg(HALF_SPDAM, final);
|
|
if (Half_gas_damage)
|
|
enl_msg(You_, "take", "took", " reduced poison gas damage", "");
|
|
/* polymorph and other shape change */
|
|
if (Protection_from_shape_changers)
|
|
you_are("protected from shape changers",
|
|
from_what(PROT_FROM_SHAPE_CHANGERS));
|
|
if (Unchanging) {
|
|
const char *what = 0;
|
|
|
|
if (!Upolyd) /* Upolyd handled below after current form */
|
|
you_can("not change from your current form",
|
|
from_what(UNCHANGING));
|
|
/* blocked shape changes */
|
|
if (Polymorph)
|
|
what = !final ? "polymorph" : "have polymorphed";
|
|
else if (u.ulycn >= LOW_PM)
|
|
what = !final ? "change shape" : "have changed shape";
|
|
if (what) {
|
|
Sprintf(buf, "would %s periodically", what);
|
|
/* omit from_what(UNCHANGING); too verbose */
|
|
enl_msg(You_, buf, buf, " if not locked into your current form",
|
|
"");
|
|
}
|
|
} else if (Polymorph) {
|
|
you_are("polymorphing periodically", from_what(POLYMORPH));
|
|
}
|
|
if (Polymorph_control)
|
|
you_have("polymorph control", from_what(POLYMORPH_CONTROL));
|
|
if (Upolyd && u.umonnum != u.ulycn
|
|
/* if we've died from turning into slime, we're polymorphed
|
|
right now but don't want to list it as a temporary attribute
|
|
[we need a more reliable way to detect this situation] */
|
|
&& !(final == ENL_GAMEOVERDEAD
|
|
&& u.umonnum == PM_GREEN_SLIME && !Unchanging)) {
|
|
/* foreign shape (except were-form which is handled below) */
|
|
if (!vampshifted(&g.youmonst))
|
|
Sprintf(buf, "polymorphed into %s",
|
|
an(pmname(g.youmonst.data,
|
|
flags.female ? FEMALE : MALE)));
|
|
else
|
|
Sprintf(buf, "polymorphed into %s in %s form",
|
|
an(pmname(&mons[g.youmonst.cham],
|
|
flags.female ? FEMALE : MALE)),
|
|
pmname(g.youmonst.data, flags.female ? FEMALE : MALE));
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%d)", u.mtimedone);
|
|
you_are(buf, "");
|
|
}
|
|
if (lays_eggs(g.youmonst.data) && flags.female) /* Upolyd */
|
|
you_can("lay eggs", "");
|
|
if (u.ulycn >= LOW_PM) {
|
|
/* "you are a werecreature [in beast form]" */
|
|
Strcpy(buf, an(pmname(&mons[u.ulycn],
|
|
flags.female ? FEMALE : MALE)));
|
|
if (u.umonnum == u.ulycn) {
|
|
Strcat(buf, " in beast form");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%d)", u.mtimedone);
|
|
}
|
|
you_are(buf, "");
|
|
}
|
|
if (Unchanging && Upolyd) /* !Upolyd handled above */
|
|
you_can("not change from your current form", from_what(UNCHANGING));
|
|
if (Hate_silver)
|
|
you_are("harmed by silver", "");
|
|
/* movement and non-armor-based protection */
|
|
if (Fast)
|
|
you_are(Very_fast ? "very fast" : "fast", from_what(FAST));
|
|
if (Reflecting)
|
|
you_have("reflection", from_what(REFLECTING));
|
|
if (Free_action)
|
|
you_have("free action", from_what(FREE_ACTION));
|
|
if (Fixed_abil)
|
|
you_have("fixed abilities", from_what(FIXED_ABIL));
|
|
if (Lifesaved)
|
|
enl_msg("Your life ", "will be", "would have been", " saved", "");
|
|
|
|
/*** Miscellany ***/
|
|
if (Luck) {
|
|
ltmp = abs((int) Luck);
|
|
Sprintf(buf, "%s%slucky",
|
|
ltmp >= 10 ? "extremely " : ltmp >= 5 ? "very " : "",
|
|
Luck < 0 ? "un" : "");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%d)", Luck);
|
|
you_are(buf, "");
|
|
} else if (wizard)
|
|
enl_msg("Your luck ", "is", "was", " zero", "");
|
|
if (u.moreluck > 0)
|
|
you_have("extra luck", "");
|
|
else if (u.moreluck < 0)
|
|
you_have("reduced luck", "");
|
|
if (carrying(LUCKSTONE) || stone_luck(TRUE)) {
|
|
ltmp = stone_luck(FALSE);
|
|
if (ltmp <= 0)
|
|
enl_msg("Bad luck ", "does", "did", " not time out for you", "");
|
|
if (ltmp >= 0)
|
|
enl_msg("Good luck ", "does", "did", " not time out for you", "");
|
|
}
|
|
|
|
if (u.ugangr) {
|
|
Sprintf(buf, " %sangry with you",
|
|
u.ugangr > 6 ? "extremely " : u.ugangr > 3 ? "very " : "");
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%d)", u.ugangr);
|
|
enl_msg(u_gname(), " is", " was", buf, "");
|
|
} else {
|
|
/*
|
|
* We need to suppress this when the game is over, because death
|
|
* can change the value calculated by can_pray(), potentially
|
|
* resulting in a false claim that you could have prayed safely.
|
|
*/
|
|
if (!final) {
|
|
#if 0
|
|
/* "can [not] safely pray" vs "could [not] have safely prayed" */
|
|
Sprintf(buf, "%s%ssafely pray%s", can_pray(FALSE) ? "" : "not ",
|
|
final ? "have " : "", final ? "ed" : "");
|
|
#else
|
|
Sprintf(buf, "%ssafely pray", can_pray(FALSE) ? "" : "not ");
|
|
#endif
|
|
if (wizard)
|
|
Sprintf(eos(buf), " (%d)", u.ublesscnt);
|
|
you_can(buf, "");
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* named fruit debugging (doesn't really belong here...); to enable,
|
|
include 'fruit' in DEBUGFILES list (even though it isn't a file...) */
|
|
if (wizard && explicitdebug("fruit")) {
|
|
struct fruit *f;
|
|
|
|
reorder_fruit(TRUE); /* sort by fruit index, from low to high;
|
|
* this modifies the g.ffruit chain, so could
|
|
* possibly mask or even introduce a problem,
|
|
* but it does useful sanity checking */
|
|
for (f = g.ffruit; f; f = f->nextf) {
|
|
Sprintf(buf, "Fruit #%d ", f->fid);
|
|
enl_msg(buf, "is ", "was ", f->fname, "");
|
|
}
|
|
enl_msg("The current fruit ", "is ", "was ", g.pl_fruit, "");
|
|
Sprintf(buf, "%d", flags.made_fruit);
|
|
enl_msg("The made fruit flag ", "is ", "was ", buf, "");
|
|
}
|
|
#endif
|
|
|
|
{
|
|
const char *p;
|
|
|
|
buf[0] = '\0';
|
|
if (final < 2) { /* still in progress, or quit/escaped/ascended */
|
|
p = "survived after being killed ";
|
|
switch (u.umortality) {
|
|
case 0:
|
|
p = !final ? (char *) 0 : "survived";
|
|
break;
|
|
case 1:
|
|
Strcpy(buf, "once");
|
|
break;
|
|
case 2:
|
|
Strcpy(buf, "twice");
|
|
break;
|
|
case 3:
|
|
Strcpy(buf, "thrice");
|
|
break;
|
|
default:
|
|
Sprintf(buf, "%d times", u.umortality);
|
|
break;
|
|
}
|
|
} else { /* game ended in character's death */
|
|
p = "are dead";
|
|
switch (u.umortality) {
|
|
case 0:
|
|
impossible("dead without dying?");
|
|
case 1:
|
|
break; /* just "are dead" */
|
|
default:
|
|
Sprintf(buf, " (%d%s time!)", u.umortality,
|
|
ordin(u.umortality));
|
|
break;
|
|
}
|
|
}
|
|
if (p)
|
|
enl_msg(You_, "have been killed ", p, buf, "");
|
|
}
|
|
}
|
|
|
|
/* ^X command */
|
|
int
|
|
doattributes(void)
|
|
{
|
|
int mode = BASICENLIGHTENMENT;
|
|
|
|
/* show more--as if final disclosure--for wizard and explore modes */
|
|
if (wizard || discover)
|
|
mode |= MAGICENLIGHTENMENT;
|
|
|
|
enlightenment(mode, ENL_GAMEINPROGRESS);
|
|
return ECMD_OK;
|
|
}
|
|
|
|
void
|
|
youhiding(boolean via_enlghtmt, /* englightment line vs topl message */
|
|
int msgflag) /* for variant message phrasing */
|
|
{
|
|
char *bp, buf[BUFSZ];
|
|
|
|
Strcpy(buf, "hiding");
|
|
if (U_AP_TYPE != M_AP_NOTHING) {
|
|
/* mimic; hero is only able to mimic a strange object or gold
|
|
or hallucinatory alternative to gold, so we skip the details
|
|
for the hypothetical furniture and monster cases */
|
|
bp = eos(strcpy(buf, "mimicking"));
|
|
if (U_AP_TYPE == M_AP_OBJECT) {
|
|
Sprintf(bp, " %s", an(simple_typename(g.youmonst.mappearance)));
|
|
} else if (U_AP_TYPE == M_AP_FURNITURE) {
|
|
Strcpy(bp, " something");
|
|
} else if (U_AP_TYPE == M_AP_MONSTER) {
|
|
Strcpy(bp, " someone");
|
|
} else {
|
|
; /* something unexpected; leave 'buf' as-is */
|
|
}
|
|
} else if (u.uundetected) {
|
|
bp = eos(buf); /* points past "hiding" */
|
|
if (g.youmonst.data->mlet == S_EEL) {
|
|
if (is_pool(u.ux, u.uy))
|
|
Sprintf(bp, " in the %s", waterbody_name(u.ux, u.uy));
|
|
} else if (hides_under(g.youmonst.data)) {
|
|
struct obj *o = g.level.objects[u.ux][u.uy];
|
|
|
|
if (o)
|
|
Sprintf(bp, " underneath %s", ansimpleoname(o));
|
|
} else if (is_clinger(g.youmonst.data) || Flying) {
|
|
/* Flying: 'lurker above' hides on ceiling but doesn't cling */
|
|
Sprintf(bp, " on the %s", ceiling(u.ux, u.uy));
|
|
} else {
|
|
/* on floor; is_hider() but otherwise not special: 'trapper' */
|
|
if (u.utrap && u.utraptype == TT_PIT) {
|
|
struct trap *t = t_at(u.ux, u.uy);
|
|
|
|
Sprintf(bp, " in a %spit",
|
|
(t && t->ttyp == SPIKED_PIT) ? "spiked " : "");
|
|
} else
|
|
Sprintf(bp, " on the %s", surface(u.ux, u.uy));
|
|
}
|
|
} else {
|
|
; /* shouldn't happen; will result in generic "you are hiding" */
|
|
}
|
|
|
|
if (via_enlghtmt) {
|
|
int final = msgflag; /* 'final' is used by you_are() macro */
|
|
|
|
you_are(buf, "");
|
|
} else {
|
|
/* for dohide(), when player uses '#monster' command */
|
|
You("are %s %s.", msgflag ? "already" : "now", buf);
|
|
}
|
|
}
|
|
|
|
/* #conduct command [KMH]; shares enlightenment's tense handling */
|
|
int
|
|
doconduct(void)
|
|
{
|
|
show_conduct(ENL_GAMEINPROGRESS);
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* display conducts; for doconduct(), also disclose() and dump_everything() */
|
|
void
|
|
show_conduct(int final)
|
|
{
|
|
char buf[BUFSZ];
|
|
int ngenocided;
|
|
|
|
/* Create the conduct window */
|
|
g.en_win = create_nhwindow(NHW_MENU);
|
|
putstr(g.en_win, 0, "Voluntary challenges:");
|
|
|
|
if (u.uroleplay.blind)
|
|
you_have_been("blind from birth");
|
|
if (u.uroleplay.nudist)
|
|
you_have_been("faithfully nudist");
|
|
|
|
if (!u.uconduct.food)
|
|
enl_msg(You_, "have gone", "went", " without food", "");
|
|
/* but beverages are okay */
|
|
else if (!u.uconduct.unvegan)
|
|
you_have_X("followed a strict vegan diet");
|
|
else if (!u.uconduct.unvegetarian)
|
|
you_have_been("vegetarian");
|
|
|
|
if (!u.uconduct.gnostic)
|
|
you_have_been("an atheist");
|
|
|
|
if (!u.uconduct.weaphit) {
|
|
you_have_never("hit with a wielded weapon");
|
|
} else if (wizard) {
|
|
Sprintf(buf, "hit with a wielded weapon %ld time%s",
|
|
u.uconduct.weaphit, plur(u.uconduct.weaphit));
|
|
you_have_X(buf);
|
|
}
|
|
if (!u.uconduct.killer)
|
|
you_have_been("a pacifist");
|
|
|
|
if (!u.uconduct.literate) {
|
|
you_have_been("illiterate");
|
|
} else if (wizard) {
|
|
Sprintf(buf, "read items or engraved %ld time%s", u.uconduct.literate,
|
|
plur(u.uconduct.literate));
|
|
you_have_X(buf);
|
|
}
|
|
|
|
ngenocided = num_genocides();
|
|
if (ngenocided == 0) {
|
|
you_have_never("genocided any monsters");
|
|
} else {
|
|
Sprintf(buf, "genocided %d type%s of monster%s", ngenocided,
|
|
plur(ngenocided), plur(ngenocided));
|
|
you_have_X(buf);
|
|
}
|
|
|
|
if (!u.uconduct.polypiles) {
|
|
you_have_never("polymorphed an object");
|
|
} else if (wizard) {
|
|
Sprintf(buf, "polymorphed %ld item%s", u.uconduct.polypiles,
|
|
plur(u.uconduct.polypiles));
|
|
you_have_X(buf);
|
|
}
|
|
|
|
if (!u.uconduct.polyselfs) {
|
|
you_have_never("changed form");
|
|
} else if (wizard) {
|
|
Sprintf(buf, "changed form %ld time%s", u.uconduct.polyselfs,
|
|
plur(u.uconduct.polyselfs));
|
|
you_have_X(buf);
|
|
}
|
|
|
|
if (!u.uconduct.wishes) {
|
|
you_have_X("used no wishes");
|
|
} else {
|
|
Sprintf(buf, "used %ld wish%s", u.uconduct.wishes,
|
|
(u.uconduct.wishes > 1L) ? "es" : "");
|
|
if (u.uconduct.wisharti) {
|
|
/* if wisharti == wishes
|
|
* 1 wish (for an artifact)
|
|
* 2 wishes (both for artifacts)
|
|
* N wishes (all for artifacts)
|
|
* else (N is at least 2 in order to get here; M < N)
|
|
* N wishes (1 for an artifact)
|
|
* N wishes (M for artifacts)
|
|
*/
|
|
if (u.uconduct.wisharti == u.uconduct.wishes)
|
|
Sprintf(eos(buf), " (%s",
|
|
(u.uconduct.wisharti > 2L) ? "all "
|
|
: (u.uconduct.wisharti == 2L) ? "both " : "");
|
|
else
|
|
Sprintf(eos(buf), " (%ld ", u.uconduct.wisharti);
|
|
|
|
Sprintf(eos(buf), "for %s)",
|
|
(u.uconduct.wisharti == 1L) ? "an artifact"
|
|
: "artifacts");
|
|
}
|
|
you_have_X(buf);
|
|
|
|
if (!u.uconduct.wisharti)
|
|
enl_msg(You_, "have not wished", "did not wish",
|
|
" for any artifacts", "");
|
|
}
|
|
|
|
/* only report Sokoban conduct if the Sokoban branch has been entered */
|
|
if (sokoban_in_play()) {
|
|
const char *presentverb = "have violated", *pastverb = "violated";
|
|
|
|
Strcpy(buf, " the special Sokoban rules ");
|
|
switch (u.uconduct.sokocheat) {
|
|
case 0L:
|
|
presentverb = "have not violated";
|
|
pastverb = "did not violate";
|
|
Strcpy(buf, " any of the special Sokoban rules");
|
|
break;
|
|
case 1L:
|
|
Strcat(buf, "once");
|
|
break;
|
|
case 2L:
|
|
Strcat(buf, "twice");
|
|
break;
|
|
case 3L:
|
|
Strcat(buf, "thrice");
|
|
break;
|
|
default:
|
|
Sprintf(eos(buf), "%ld times", u.uconduct.sokocheat);
|
|
break;
|
|
}
|
|
enl_msg(You_, presentverb, pastverb, buf, "");
|
|
}
|
|
|
|
show_achievements(final);
|
|
|
|
/* Pop up the window and wait for a key */
|
|
display_nhwindow(g.en_win, TRUE);
|
|
destroy_nhwindow(g.en_win);
|
|
g.en_win = WIN_ERR;
|
|
}
|
|
|
|
/*
|
|
* Achievements (see 'enum achievements' in you.h).
|
|
*/
|
|
|
|
static void
|
|
show_achievements(
|
|
int final) /* 'final' is used "behind the curtain" by enl_foo() macros */
|
|
{
|
|
int i, achidx, absidx, acnt;
|
|
char title[QBUFSZ], buf[QBUFSZ];
|
|
winid awin = WIN_ERR;
|
|
|
|
/* unfortunately we can't show the achievements (at least not all of
|
|
them) while the game is in progress because it would give away the
|
|
ID of luckstone (at Mine's End) and of real Amulet of Yendor */
|
|
if (!final && !wizard)
|
|
return;
|
|
|
|
/* first, figure whether any achievements have been accomplished
|
|
so that we don't show the header for them if the resulting list
|
|
below it would be empty */
|
|
if ((acnt = count_achievements()) == 0)
|
|
return;
|
|
|
|
if (g.en_win != WIN_ERR) {
|
|
awin = g.en_win; /* end of game disclosure window */
|
|
putstr(awin, 0, "");
|
|
} else {
|
|
awin = create_nhwindow(NHW_MENU);
|
|
}
|
|
Sprintf(title, "Achievement%s:", plur(acnt));
|
|
putstr(awin, 0, title);
|
|
|
|
/* display achievements in the order in which they were recorded;
|
|
lone exception is to defer the Amulet if we just ascended;
|
|
it warrants alternate wording when given away during ascension,
|
|
but the Amulet achievement is always attained before entering
|
|
endgame and the alternate wording looks strange if shown before
|
|
"reached endgame" and "reached Astral" */
|
|
if (remove_achievement(ACH_UWIN)) { /* UWIN == Ascended! */
|
|
/* for ascension, force it to be last and Amulet next to last
|
|
by taking them out and then adding them back */
|
|
if (remove_achievement(ACH_AMUL)) /* should always be True here */
|
|
record_achievement(ACH_AMUL);
|
|
record_achievement(ACH_UWIN);
|
|
}
|
|
for (i = 0; i < acnt; ++i) {
|
|
achidx = u.uachieved[i];
|
|
absidx = abs(achidx);
|
|
|
|
switch (absidx) {
|
|
case ACH_BLND:
|
|
enl_msg(You_, "are exploring", "explored",
|
|
" without being able to see", "");
|
|
break;
|
|
case ACH_NUDE:
|
|
enl_msg(You_, "have gone", "went", " without any armor", "");
|
|
break;
|
|
case ACH_MINE:
|
|
you_have_X("entered the Gnomish Mines");
|
|
break;
|
|
case ACH_TOWN:
|
|
you_have_X("entered Minetown");
|
|
break;
|
|
case ACH_SHOP:
|
|
you_have_X("entered a shop");
|
|
break;
|
|
case ACH_TMPL:
|
|
you_have_X("entered a temple");
|
|
break;
|
|
case ACH_ORCL:
|
|
you_have_X("consulted the Oracle of Delphi");
|
|
break;
|
|
case ACH_NOVL:
|
|
you_have_X("read from a Discworld novel");
|
|
break;
|
|
case ACH_SOKO:
|
|
you_have_X("entered Sokoban");
|
|
break;
|
|
case ACH_SOKO_PRIZE: /* hard to reach guaranteed bag or amulet */
|
|
you_have_X("completed Sokoban");
|
|
break;
|
|
case ACH_MINE_PRIZE: /* hidden guaranteed luckstone */
|
|
you_have_X("completed the Gnomish Mines");
|
|
break;
|
|
case ACH_BGRM:
|
|
you_have_X("entered the Big Room");
|
|
break;
|
|
case ACH_MEDU:
|
|
you_have_X("defeated Medusa");
|
|
break;
|
|
case ACH_TUNE:
|
|
you_have_X(
|
|
"learned the tune to open and close the Castle's drawbridge");
|
|
break;
|
|
case ACH_BELL:
|
|
/* alternate phrasing for present vs past and also for
|
|
possessing the item vs once held it */
|
|
enl_msg(You_,
|
|
u.uhave.bell ? "have" : "have handled",
|
|
u.uhave.bell ? "had" : "handled",
|
|
" the Bell of Opening", "");
|
|
break;
|
|
case ACH_HELL:
|
|
enl_msg(You_, "have ", "", "entered Gehennom", "");
|
|
break;
|
|
case ACH_CNDL:
|
|
enl_msg(You_,
|
|
u.uhave.menorah ? "have" : "have handled",
|
|
u.uhave.menorah ? "had" : "handled",
|
|
" the Candelabrum of Invocation", "");
|
|
break;
|
|
case ACH_BOOK:
|
|
enl_msg(You_,
|
|
u.uhave.book ? "have" : "have handled",
|
|
u.uhave.book ? "had" : "handled",
|
|
" the Book of the Dead", "");
|
|
break;
|
|
case ACH_INVK:
|
|
you_have_X("gained access to Moloch's Sanctum");
|
|
break;
|
|
case ACH_AMUL:
|
|
/* alternate wording for ascended (always past tense) since
|
|
hero had it until #offer forced it to be relinquished */
|
|
enl_msg(You_,
|
|
u.uhave.amulet ? "have" : "have obtained",
|
|
u.uevent.ascended ? "delivered"
|
|
: u.uhave.amulet ? "had" : "had obtained",
|
|
" the Amulet of Yendor", "");
|
|
break;
|
|
|
|
/* reaching Astral makes feedback about reaching the Planes
|
|
be redundant and ascending makes both be redundant, but
|
|
we display all that apply */
|
|
case ACH_ENDG:
|
|
you_have_X("reached the Elemental Planes");
|
|
break;
|
|
case ACH_ASTR:
|
|
you_have_X("reached the Astral Plane");
|
|
break;
|
|
case ACH_UWIN:
|
|
/* the ultimate achievement... */
|
|
enlght_out(" You ascended!");
|
|
break;
|
|
|
|
/* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */
|
|
case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4:
|
|
case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8:
|
|
Sprintf(buf, "attained the rank of %s",
|
|
rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
|
|
Role_switch, (achidx < 0) ? TRUE : FALSE));
|
|
you_have_X(buf);
|
|
break;
|
|
|
|
default:
|
|
Sprintf(buf, " [Unexpected achievement #%d.]", achidx);
|
|
enlght_out(buf);
|
|
break;
|
|
} /* switch */
|
|
} /* for */
|
|
|
|
if (awin != g.en_win) {
|
|
display_nhwindow(awin, TRUE);
|
|
destroy_nhwindow(awin);
|
|
}
|
|
}
|
|
|
|
/* record an achievement (add at end of list unless already present) */
|
|
void
|
|
record_achievement(schar achidx)
|
|
{
|
|
int i, absidx;
|
|
|
|
absidx = abs(achidx);
|
|
/* valid achievements range from 1 to N_ACH-1; however, ranks can be
|
|
stored as the complement (ie, negative) to track gender */
|
|
if ((achidx < 1 && (absidx < ACH_RNK1 || absidx > ACH_RNK8))
|
|
|| achidx >= N_ACH) {
|
|
impossible("Achievement #%d is out of range.", achidx);
|
|
return;
|
|
}
|
|
|
|
/* the list has an extra slot so there is always at least one 0 at
|
|
its end (more than one unless all N_ACH-1 possible achievements
|
|
have been recorded); find first empty slot or achievement #achidx;
|
|
an attempt to duplicate an achievement can happen if any of Bell,
|
|
Candelabrum, Book, or Amulet is dropped then picked up again */
|
|
for (i = 0; u.uachieved[i]; ++i)
|
|
if (abs(u.uachieved[i]) == absidx)
|
|
return; /* already recorded, don't duplicate it */
|
|
u.uachieved[i] = achidx;
|
|
|
|
/* avoid livelog for achievements recorded during final disclosure:
|
|
nudist and blind-from-birth; also ascension which is suppressed
|
|
by this gets logged separately in really_done() */
|
|
if (g.program_state.gameover)
|
|
return;
|
|
|
|
if (absidx >= ACH_RNK1 && absidx <= ACH_RNK8) {
|
|
livelog_printf(achieve_msg[absidx].llflag,
|
|
"attained the rank of %s (level %d)",
|
|
rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
|
|
Role_switch, (achidx < 0) ? TRUE : FALSE),
|
|
u.ulevel);
|
|
} else if (achidx == ACH_SOKO_PRIZE
|
|
|| achidx == ACH_MINE_PRIZE) {
|
|
/* need to supply extra information for these two */
|
|
short otyp = ((achidx == ACH_SOKO_PRIZE)
|
|
? g.context.achieveo.soko_prize_otyp
|
|
: g.context.achieveo.mines_prize_otyp);
|
|
|
|
/* note: OBJ_NAME() works here because both "bag of holding" and
|
|
"amulet of reflection" are fully named in their objects[] entry
|
|
but that's not true in the general case */
|
|
livelog_printf(achieve_msg[achidx].llflag, "%s %s",
|
|
achieve_msg[achidx].msg, OBJ_NAME(objects[otyp]));
|
|
} else {
|
|
livelog_printf(achieve_msg[absidx].llflag, "%s",
|
|
achieve_msg[absidx].msg);
|
|
}
|
|
}
|
|
|
|
/* discard a recorded achievement; return True if removed, False otherwise */
|
|
boolean
|
|
remove_achievement(schar achidx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; u.uachieved[i]; ++i)
|
|
if (abs(u.uachieved[i]) == abs(achidx))
|
|
break; /* stop when found */
|
|
if (!u.uachieved[i]) /* not found */
|
|
return FALSE;
|
|
/* list is 0 terminated so any beyond the removed one move up a slot */
|
|
do {
|
|
u.uachieved[i] = u.uachieved[i + 1];
|
|
} while (u.uachieved[++i]);
|
|
return TRUE;
|
|
}
|
|
|
|
/* used to decide whether there are any achievements to display */
|
|
int
|
|
count_achievements(void)
|
|
{
|
|
int i, acnt = 0;
|
|
|
|
for (i = 0; u.uachieved[i]; ++i)
|
|
++acnt;
|
|
return acnt;
|
|
}
|
|
|
|
/* convert a rank index to an achievement number; encode it when female
|
|
in order to subsequently report gender-specific ranks accurately */
|
|
schar
|
|
achieve_rank(int rank) /* 1..8 */
|
|
{
|
|
schar achidx = (schar) ((rank - 1) + ACH_RNK1);
|
|
|
|
if (flags.female)
|
|
achidx = -achidx;
|
|
return achidx;
|
|
}
|
|
|
|
/* return True if sokoban branch has been entered, False otherwise */
|
|
boolean
|
|
sokoban_in_play(void)
|
|
{
|
|
int achidx;
|
|
|
|
/* TODO? move this to dungeon.c and test furthest level reached of the
|
|
sokoban branch instead of relying on the entered-sokoban achievement */
|
|
|
|
for (achidx = 0; u.uachieved[achidx]; ++achidx)
|
|
if (u.uachieved[achidx] == ACH_SOKO)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/* #chronicle command */
|
|
int
|
|
do_gamelog(void)
|
|
{
|
|
#ifdef CHRONICLE
|
|
if (g.gamelog) {
|
|
show_gamelog(ENL_GAMEINPROGRESS);
|
|
} else {
|
|
pline("No chronicled events.");
|
|
}
|
|
#else
|
|
pline("Chronicle was turned off during compile-time.");
|
|
#endif /* !CHRONICLE */
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* 'major' events for dumplog; inclusion or exclusion here may need tuning */
|
|
#define LL_majors (0L \
|
|
| LL_WISH \
|
|
| LL_ACHIEVE \
|
|
| LL_UMONST \
|
|
| LL_DIVINEGIFT \
|
|
| LL_LIFESAVE \
|
|
| LL_ARTIFACT \
|
|
| LL_GENOCIDE \
|
|
| LL_DUMP) /* explicitly for dumplog */
|
|
#define majorevent(llmsg) (((llmsg)->flags & LL_majors) != 0)
|
|
#define spoilerevent(llmsg) (((llmsg)->flags & LL_SPOILER) != 0)
|
|
|
|
/* #chronicle details */
|
|
void
|
|
show_gamelog(int final)
|
|
{
|
|
#ifdef CHRONICLE
|
|
struct gamelog_line *llmsg;
|
|
winid win;
|
|
char buf[BUFSZ];
|
|
int eventcnt = 0;
|
|
|
|
win = create_nhwindow(NHW_TEXT);
|
|
Sprintf(buf, "%s events:", final ? "Major" : "Logged");
|
|
putstr(win, 0, buf);
|
|
for (llmsg = g.gamelog; llmsg; llmsg = llmsg->next) {
|
|
if (final && !majorevent(llmsg))
|
|
continue;
|
|
if (!final && !wizard && spoilerevent(llmsg))
|
|
continue;
|
|
if (!eventcnt++)
|
|
putstr(win, 0, " Turn");
|
|
Sprintf(buf, "%5ld: %s", llmsg->turn, llmsg->text);
|
|
putstr(win, 0, buf);
|
|
}
|
|
/* since start of game is logged as a major event, 'eventcnt' should
|
|
never end up as 0; for 'final', end of game is a major event too */
|
|
if (!eventcnt)
|
|
putstr(win, 0, " none");
|
|
|
|
display_nhwindow(win, TRUE);
|
|
destroy_nhwindow(win);
|
|
#else
|
|
nhUse(final);
|
|
#endif /* !CHRONICLE */
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Vanquished monsters.
|
|
*/
|
|
|
|
static const char *vanqorders[NUM_VANQ_ORDER_MODES] = {
|
|
"traditional: by monster level, by internal monster index",
|
|
"by monster toughness, by internal monster index",
|
|
"alphabetically, first unique monsters, then others",
|
|
"alphabetically, unique monsters and others intermixed",
|
|
"by monster class, high to low level within class",
|
|
"by monster class, low to high level within class",
|
|
"by count, high to low, by internal index within tied count",
|
|
"by count, low to high, by internal index within tied count",
|
|
};
|
|
|
|
static int QSORTCALLBACK
|
|
vanqsort_cmp(const genericptr vptr1, const genericptr vptr2)
|
|
{
|
|
int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2,
|
|
mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res;
|
|
const char *name1, *name2, *punct;
|
|
schar mcls1, mcls2;
|
|
|
|
switch (g.vanq_sortmode) {
|
|
default:
|
|
case VANQ_MLVL_MNDX:
|
|
/* sort by monster level */
|
|
mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
|
|
res = mlev2 - mlev1; /* mlevel high to low */
|
|
break;
|
|
case VANQ_MSTR_MNDX:
|
|
/* sort by monster toughness */
|
|
mstr1 = mons[indx1].difficulty, mstr2 = mons[indx2].difficulty;
|
|
res = mstr2 - mstr1; /* monstr high to low */
|
|
break;
|
|
case VANQ_ALPHA_SEP:
|
|
uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_CLERIC);
|
|
uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_CLERIC);
|
|
if (uniq1 ^ uniq2) { /* one or other uniq, but not both */
|
|
res = uniq2 - uniq1;
|
|
break;
|
|
} /* else both unique or neither unique */
|
|
/*FALLTHRU*/
|
|
case VANQ_ALPHA_MIX:
|
|
name1 = mons[indx1].pmnames[NEUTRAL],
|
|
name2 = mons[indx2].pmnames[NEUTRAL];
|
|
res = strcmpi(name1, name2); /* caseblind alhpa, low to high */
|
|
break;
|
|
case VANQ_MCLS_HTOL:
|
|
case VANQ_MCLS_LTOH:
|
|
/* mons[].mlet is a small integer, 1..N, of type plain char;
|
|
if 'char' happens to be unsigned, (mlet1 - mlet2) would yield
|
|
an inappropriate result when mlet2 is greater than mlet1,
|
|
so force our copies (mcls1, mcls2) to be signed */
|
|
mcls1 = (schar) mons[indx1].mlet, mcls2 = (schar) mons[indx2].mlet;
|
|
/* S_ANT through S_ZRUTY correspond to lowercase monster classes,
|
|
S_ANGEL through S_ZOMBIE correspond to uppercase, and various
|
|
punctuation characters are used for classes beyond those */
|
|
if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) {
|
|
/* force a specific order to the punctuation classes that's
|
|
different from the internal order;
|
|
internal order is ok if neither or just one is punctuation
|
|
since letters have lower values so come out before punct */
|
|
static const char punctclasses[] = {
|
|
S_LIZARD, S_EEL, S_GOLEM, S_GHOST, S_DEMON, S_HUMAN, '\0'
|
|
};
|
|
|
|
if ((punct = index(punctclasses, mcls1)) != 0)
|
|
mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
|
|
if ((punct = index(punctclasses, mcls2)) != 0)
|
|
mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
|
|
}
|
|
res = mcls1 - mcls2; /* class */
|
|
if (res == 0) {
|
|
mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
|
|
res = mlev1 - mlev2; /* mlevel low to high */
|
|
if (g.vanq_sortmode == VANQ_MCLS_HTOL)
|
|
res = -res; /* mlevel high to low */
|
|
}
|
|
break;
|
|
case VANQ_COUNT_H_L:
|
|
case VANQ_COUNT_L_H:
|
|
died1 = g.mvitals[indx1].died, died2 = g.mvitals[indx2].died;
|
|
res = died2 - died1; /* dead count high to low */
|
|
if (g.vanq_sortmode == VANQ_COUNT_L_H)
|
|
res = -res; /* dead count low to high */
|
|
break;
|
|
}
|
|
/* tiebreaker: internal mons[] index */
|
|
if (res == 0)
|
|
res = indx1 - indx2; /* mndx low to high */
|
|
return res;
|
|
}
|
|
|
|
/* returns -1 if cancelled via ESC */
|
|
static int
|
|
set_vanq_order(void)
|
|
{
|
|
winid tmpwin;
|
|
menu_item *selected;
|
|
anything any;
|
|
int i, n, choice,
|
|
clr = 0;
|
|
|
|
tmpwin = create_nhwindow(NHW_MENU);
|
|
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
|
|
any = cg.zeroany; /* zero out all bits */
|
|
for (i = 0; i < SIZE(vanqorders); i++) {
|
|
if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */
|
|
continue;
|
|
any.a_int = i + 1;
|
|
add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr,
|
|
vanqorders[i],
|
|
(i == g.vanq_sortmode)
|
|
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
}
|
|
end_menu(tmpwin, "Sort order for vanquished monster counts");
|
|
|
|
n = select_menu(tmpwin, PICK_ONE, &selected);
|
|
destroy_nhwindow(tmpwin);
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int - 1;
|
|
/* skip preselected entry if we have more than one item chosen */
|
|
if (n > 1 && choice == g.vanq_sortmode)
|
|
choice = selected[1].item.a_int - 1;
|
|
free((genericptr_t) selected);
|
|
g.vanq_sortmode = choice;
|
|
}
|
|
return (n < 0) ? -1 : g.vanq_sortmode;
|
|
}
|
|
|
|
/* #vanquished command */
|
|
int
|
|
dovanquished(void)
|
|
{
|
|
list_vanquished('a', FALSE);
|
|
return ECMD_OK;
|
|
}
|
|
|
|
DISABLE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* #wizborn extended command */
|
|
int
|
|
doborn(void)
|
|
{
|
|
static const char fmt[] = "%4i %4i %c %-30s";
|
|
int i;
|
|
winid datawin = create_nhwindow(NHW_TEXT);
|
|
char buf[BUFSZ];
|
|
int nborn = 0, ndied = 0;
|
|
|
|
putstr(datawin, 0, "died born");
|
|
for (i = LOW_PM; i < NUMMONS; i++)
|
|
if (g.mvitals[i].born || g.mvitals[i].died
|
|
|| (g.mvitals[i].mvflags & G_GONE)) {
|
|
Sprintf(buf, fmt,
|
|
g.mvitals[i].died, g.mvitals[i].born,
|
|
((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT) ? 'E' :
|
|
((g.mvitals[i].mvflags & G_GONE) == G_GENOD) ? 'G' : ' ',
|
|
mons[i].pmnames[NEUTRAL]);
|
|
putstr(datawin, 0, buf);
|
|
nborn += g.mvitals[i].born;
|
|
ndied += g.mvitals[i].died;
|
|
}
|
|
|
|
putstr(datawin, 0, "");
|
|
Sprintf(buf, fmt, ndied, nborn, ' ', "");
|
|
|
|
display_nhwindow(datawin, FALSE);
|
|
destroy_nhwindow(datawin);
|
|
|
|
return ECMD_OK;
|
|
}
|
|
|
|
RESTORE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* high priests aren't unique but are flagged as such to simplify something */
|
|
#define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \
|
|
&& mndx != PM_HIGH_CLERIC)
|
|
|
|
#define done_stopprint g.program_state.stopprint
|
|
|
|
void
|
|
list_vanquished(char defquery, boolean ask)
|
|
{
|
|
register int i;
|
|
int pfx, nkilled;
|
|
unsigned ntypes, ni;
|
|
long total_killed = 0L;
|
|
winid klwin;
|
|
short mindx[NUMMONS];
|
|
char c, buf[BUFSZ], buftoo[BUFSZ];
|
|
boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
|
|
|
|
dumping = (defquery == 'd');
|
|
if (dumping)
|
|
defquery = 'y';
|
|
|
|
/* get totals first */
|
|
ntypes = 0;
|
|
for (i = LOW_PM; i < NUMMONS; i++) {
|
|
if ((nkilled = (int) g.mvitals[i].died) == 0)
|
|
continue;
|
|
mindx[ntypes++] = i;
|
|
total_killed += (long) nkilled;
|
|
}
|
|
|
|
/* vanquished creatures list;
|
|
* includes all dead monsters, not just those killed by the player
|
|
*/
|
|
if (ntypes != 0) {
|
|
char mlet, prev_mlet = 0; /* used as small integer, not character */
|
|
boolean class_header, uniq_header, was_uniq = FALSE;
|
|
|
|
c = ask ? yn_function(
|
|
"Do you want an account of creatures vanquished?",
|
|
ynaqchars, defquery, TRUE)
|
|
: defquery;
|
|
if (c == 'q')
|
|
done_stopprint++;
|
|
if (c == 'y' || c == 'a') {
|
|
if (c == 'a') { /* ask player to choose sort order */
|
|
/* choose value for vanq_sortmode via menu; ESC cancels list
|
|
of vanquished monsters but does not set 'done_stopprint' */
|
|
if (set_vanq_order() < 0)
|
|
return;
|
|
}
|
|
uniq_header = (g.vanq_sortmode == VANQ_ALPHA_SEP);
|
|
class_header = (g.vanq_sortmode == VANQ_MCLS_LTOH
|
|
|| g.vanq_sortmode == VANQ_MCLS_HTOL);
|
|
|
|
klwin = create_nhwindow(NHW_MENU);
|
|
putstr(klwin, 0, "Vanquished creatures:");
|
|
if (!dumping)
|
|
putstr(klwin, 0, "");
|
|
|
|
qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp);
|
|
for (ni = 0; ni < ntypes; ni++) {
|
|
i = mindx[ni];
|
|
nkilled = g.mvitals[i].died;
|
|
mlet = mons[i].mlet;
|
|
if (class_header && mlet != prev_mlet) {
|
|
Strcpy(buf, def_monsyms[(int) mlet].explain);
|
|
putstr(klwin, ask ? 0 : iflags.menu_headings,
|
|
upstart(buf));
|
|
prev_mlet = mlet;
|
|
}
|
|
if (UniqCritterIndx(i)) {
|
|
Sprintf(buf, "%s%s",
|
|
!type_is_pname(&mons[i]) ? "the " : "",
|
|
mons[i].pmnames[NEUTRAL]);
|
|
if (nkilled > 1) {
|
|
switch (nkilled) {
|
|
case 2:
|
|
Sprintf(eos(buf), " (twice)");
|
|
break;
|
|
case 3:
|
|
Sprintf(eos(buf), " (thrice)");
|
|
break;
|
|
default:
|
|
Sprintf(eos(buf), " (%d times)", nkilled);
|
|
break;
|
|
}
|
|
}
|
|
was_uniq = TRUE;
|
|
} else {
|
|
if (uniq_header && was_uniq) {
|
|
putstr(klwin, 0, "");
|
|
was_uniq = FALSE;
|
|
}
|
|
/* trolls or undead might have come back,
|
|
but we don't keep track of that */
|
|
if (nkilled == 1)
|
|
Strcpy(buf, an(mons[i].pmnames[NEUTRAL]));
|
|
else
|
|
Sprintf(buf, "%3d %s", nkilled,
|
|
makeplural(mons[i].pmnames[NEUTRAL]));
|
|
}
|
|
/* number of leading spaces to match 3 digit prefix */
|
|
pfx = !strncmpi(buf, "the ", 4) ? 0
|
|
: !strncmpi(buf, "an ", 3) ? 1
|
|
: !strncmpi(buf, "a ", 2) ? 2
|
|
: !digit(buf[2]) ? 4 : 0;
|
|
if (class_header)
|
|
++pfx;
|
|
Snprintf(buftoo, sizeof(buftoo), "%*s%s", pfx, "", buf);
|
|
putstr(klwin, 0, buftoo);
|
|
}
|
|
/*
|
|
* if (Hallucination)
|
|
* putstr(klwin, 0, "and a partridge in a pear tree");
|
|
*/
|
|
if (ntypes > 1) {
|
|
if (!dumping)
|
|
putstr(klwin, 0, "");
|
|
Sprintf(buf, "%ld creatures vanquished.", total_killed);
|
|
putstr(klwin, 0, buf);
|
|
}
|
|
display_nhwindow(klwin, TRUE);
|
|
destroy_nhwindow(klwin);
|
|
}
|
|
} else if (defquery == 'a') {
|
|
/* #dovanquished rather than final disclosure, so pline() is ok */
|
|
pline("No creatures have been vanquished.");
|
|
#ifdef DUMPLOG
|
|
} else if (dumping) {
|
|
putstr(0, 0, "No creatures were vanquished."); /* not pline() */
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* number of monster species which have been genocided */
|
|
int
|
|
num_genocides(void)
|
|
{
|
|
int i, n = 0;
|
|
|
|
for (i = LOW_PM; i < NUMMONS; ++i) {
|
|
if (g.mvitals[i].mvflags & G_GENOD) {
|
|
++n;
|
|
if (UniqCritterIndx(i))
|
|
impossible("unique creature '%d: %s' genocided?",
|
|
i, mons[i].pmnames[NEUTRAL]);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
num_extinct(void)
|
|
{
|
|
int i, n = 0;
|
|
|
|
for (i = LOW_PM; i < NUMMONS; ++i) {
|
|
if (UniqCritterIndx(i))
|
|
continue;
|
|
if ((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT)
|
|
++n;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void
|
|
list_genocided(char defquery, boolean ask)
|
|
{
|
|
register int i;
|
|
int ngenocided, nextinct;
|
|
char c;
|
|
winid klwin;
|
|
char buf[BUFSZ];
|
|
boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
|
|
|
|
dumping = (defquery == 'd');
|
|
if (dumping)
|
|
defquery = 'y';
|
|
|
|
ngenocided = num_genocides();
|
|
nextinct = num_extinct();
|
|
|
|
/* genocided or extinct species list */
|
|
if (ngenocided != 0 || nextinct != 0) {
|
|
Sprintf(buf, "Do you want a list of %sspecies%s%s?",
|
|
(nextinct && !ngenocided) ? "extinct " : "",
|
|
(ngenocided) ? " genocided" : "",
|
|
(nextinct && ngenocided) ? " and extinct" : "");
|
|
c = ask ? yn_function(buf, ynqchars, defquery, TRUE) : defquery;
|
|
if (c == 'q')
|
|
done_stopprint++;
|
|
if (c == 'y') {
|
|
klwin = create_nhwindow(NHW_MENU);
|
|
Sprintf(buf, "%s%s species:",
|
|
(ngenocided) ? "Genocided" : "Extinct",
|
|
(nextinct && ngenocided) ? " or extinct" : "");
|
|
putstr(klwin, 0, buf);
|
|
if (!dumping)
|
|
putstr(klwin, 0, "");
|
|
|
|
for (i = LOW_PM; i < NUMMONS; i++) {
|
|
/* uniques can't be genocided but can become extinct;
|
|
however, they're never reported as extinct, so skip them */
|
|
if (UniqCritterIndx(i))
|
|
continue;
|
|
if (g.mvitals[i].mvflags & G_GONE) {
|
|
Sprintf(buf, " %s", makeplural(mons[i].pmnames[NEUTRAL]));
|
|
/*
|
|
* "Extinct" is unfortunate terminology. A species
|
|
* is marked extinct when its birth limit is reached,
|
|
* but there might be members of the species still
|
|
* alive, contradicting the meaning of the word.
|
|
*/
|
|
if ((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT)
|
|
Strcat(buf, " (extinct)");
|
|
putstr(klwin, 0, buf);
|
|
}
|
|
}
|
|
if (!dumping)
|
|
putstr(klwin, 0, "");
|
|
if (ngenocided > 0) {
|
|
Sprintf(buf, "%d species genocided.", ngenocided);
|
|
putstr(klwin, 0, buf);
|
|
}
|
|
if (nextinct > 0) {
|
|
Sprintf(buf, "%d species extinct.", nextinct);
|
|
putstr(klwin, 0, buf);
|
|
}
|
|
|
|
display_nhwindow(klwin, TRUE);
|
|
destroy_nhwindow(klwin);
|
|
}
|
|
#ifdef DUMPLOG
|
|
} else if (dumping) {
|
|
putstr(0, 0, "No species were genocided or became extinct.");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* align_str(), piousness(), mstatusline() and ustatusline() once resided
|
|
* in pline.c, then got moved to priest.c just to be out of there. They
|
|
* fit better here.
|
|
*/
|
|
|
|
const char *
|
|
align_str(aligntyp alignment)
|
|
{
|
|
switch ((int) alignment) {
|
|
case A_CHAOTIC:
|
|
return "chaotic";
|
|
case A_NEUTRAL:
|
|
return "neutral";
|
|
case A_LAWFUL:
|
|
return "lawful";
|
|
case A_NONE:
|
|
return "unaligned";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/* used for self-probing */
|
|
char *
|
|
piousness(boolean showneg, const char *suffix)
|
|
{
|
|
static char buf[32]; /* bigger than "insufficiently neutral" */
|
|
const char *pio;
|
|
|
|
/* note: piousness 20 matches MIN_QUEST_ALIGN (quest.h) */
|
|
if (u.ualign.record >= 20)
|
|
pio = "piously";
|
|
else if (u.ualign.record > 13)
|
|
pio = "devoutly";
|
|
else if (u.ualign.record > 8)
|
|
pio = "fervently";
|
|
else if (u.ualign.record > 3)
|
|
pio = "stridently";
|
|
else if (u.ualign.record == 3)
|
|
pio = "";
|
|
else if (u.ualign.record > 0)
|
|
pio = "haltingly";
|
|
else if (u.ualign.record == 0)
|
|
pio = "nominally";
|
|
else if (!showneg)
|
|
pio = "insufficiently";
|
|
else if (u.ualign.record >= -3)
|
|
pio = "strayed";
|
|
else if (u.ualign.record >= -8)
|
|
pio = "sinned";
|
|
else
|
|
pio = "transgressed";
|
|
|
|
Sprintf(buf, "%s", pio);
|
|
if (suffix && (!showneg || u.ualign.record >= 0)) {
|
|
if (u.ualign.record != 3)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, suffix);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/* stethoscope or probing applied to monster -- one-line feedback */
|
|
void
|
|
mstatusline(struct monst *mtmp)
|
|
{
|
|
aligntyp alignment = mon_aligntyp(mtmp);
|
|
char info[BUFSZ], monnambuf[BUFSZ];
|
|
|
|
info[0] = 0;
|
|
if (mtmp->mtame) {
|
|
Strcat(info, ", tame");
|
|
if (wizard) {
|
|
Sprintf(eos(info), " (%d", mtmp->mtame);
|
|
if (!mtmp->isminion)
|
|
Sprintf(eos(info), "; hungry %ld; apport %d",
|
|
EDOG(mtmp)->hungrytime, EDOG(mtmp)->apport);
|
|
Strcat(info, ")");
|
|
}
|
|
} else if (mtmp->mpeaceful)
|
|
Strcat(info, ", peaceful");
|
|
|
|
if (mtmp->data == &mons[PM_LONG_WORM]) {
|
|
int segndx, nsegs = count_wsegs(mtmp);
|
|
|
|
/* the worm code internals don't consider the head of be one of
|
|
the worm's segments, but we count it as such when presenting
|
|
worm feedback to the player */
|
|
if (!nsegs) {
|
|
Strcat(info, ", single segment");
|
|
} else {
|
|
++nsegs; /* include head in the segment count */
|
|
segndx = wseg_at(mtmp, g.bhitpos.x, g.bhitpos.y);
|
|
Sprintf(eos(info), ", %d%s of %d segments",
|
|
segndx, ordin(segndx), nsegs);
|
|
}
|
|
}
|
|
if (mtmp->cham >= LOW_PM && mtmp->data != &mons[mtmp->cham])
|
|
/* don't reveal the innate form (chameleon, vampire, &c),
|
|
just expose the fact that this current form isn't it */
|
|
Strcat(info, ", shapechanger");
|
|
/* pets eating mimic corpses mimic while eating, so this comes first */
|
|
if (mtmp->meating)
|
|
Strcat(info, ", eating");
|
|
/* a stethoscope exposes mimic before getting here so this
|
|
won't be relevant for it, but wand of probing doesn't */
|
|
if (mtmp->mundetected || mtmp->m_ap_type)
|
|
mhidden_description(mtmp, TRUE, eos(info));
|
|
if (mtmp->mcan)
|
|
Strcat(info, ", cancelled");
|
|
if (mtmp->mconf)
|
|
Strcat(info, ", confused");
|
|
if (mtmp->mblinded || !mtmp->mcansee)
|
|
Strcat(info, ", blind");
|
|
if (mtmp->mstun)
|
|
Strcat(info, ", stunned");
|
|
if (mtmp->msleeping)
|
|
Strcat(info, ", asleep");
|
|
#if 0 /* unfortunately mfrozen covers temporary sleep and being busy
|
|
* (donning armor, for instance) as well as paralysis */
|
|
else if (mtmp->mfrozen)
|
|
Strcat(info, ", paralyzed");
|
|
#else
|
|
else if (mtmp->mfrozen || !mtmp->mcanmove)
|
|
Strcat(info, ", can't move");
|
|
#endif
|
|
/* [arbitrary reason why it isn't moving] */
|
|
else if ((mtmp->mstrategy & STRAT_WAITMASK) != 0)
|
|
Strcat(info, ", meditating");
|
|
if (mtmp->mflee)
|
|
Strcat(info, ", scared");
|
|
if (mtmp->mtrapped)
|
|
Strcat(info, ", trapped");
|
|
if (mtmp->mspeed)
|
|
Strcat(info, (mtmp->mspeed == MFAST) ? ", fast"
|
|
: (mtmp->mspeed == MSLOW) ? ", slow"
|
|
: ", [? speed]");
|
|
if (mtmp->minvis)
|
|
Strcat(info, ", invisible");
|
|
if (mtmp == u.ustuck) {
|
|
struct permonst *pm = u.ustuck->data;
|
|
|
|
/* being swallowed/engulfed takes priority over sticks(youmonst);
|
|
this used to have that backwards and checked sticks() first */
|
|
Strcat(info, u.uswallow ? (digests(pm)
|
|
? ", digesting you"
|
|
/* note: the "swallowing you" case won't
|
|
happen because all animal engulfers
|
|
either digest their victims (purple
|
|
worm) or enfold them (trappers and
|
|
lurkers above) */
|
|
: (is_animal(pm) && !enfolds(pm))
|
|
? ", swallowing you"
|
|
: ", engulfing you")
|
|
/* !u.uswallow; if both youmonst and ustuck are holders,
|
|
youmonst wins */
|
|
: (!sticks(g.youmonst.data) ? ", holding you"
|
|
: ", held by you"));
|
|
}
|
|
if (mtmp == u.usteed) {
|
|
Strcat(info, ", carrying you");
|
|
if (Wounded_legs) {
|
|
/* EWounded_legs is used to track left/right/both rather than
|
|
some form of extrinsic impairment; HWounded_legs is used for
|
|
timeout; both apply to steed instead of hero when mounted */
|
|
long legs = (EWounded_legs & BOTH_SIDES);
|
|
const char *what = mbodypart(mtmp, LEG);
|
|
|
|
if (legs == BOTH_SIDES)
|
|
what = makeplural(what);
|
|
Sprintf(eos(info), ", injured %s", what);
|
|
}
|
|
}
|
|
if (mtmp->mleashed)
|
|
Strcat(info, ", leashed");
|
|
|
|
/* avoid "Status of the invisible newt ..., invisible" */
|
|
/* and unlike a normal mon_nam, use "saddled" even if it has a name */
|
|
Strcpy(monnambuf, x_monnam(mtmp, ARTICLE_THE, (char *) 0,
|
|
(SUPPRESS_IT | SUPPRESS_INVISIBLE), FALSE));
|
|
|
|
pline("Status of %s (%s): Level %d HP %d(%d) AC %d%s.", monnambuf,
|
|
align_str(alignment), mtmp->m_lev, mtmp->mhp, mtmp->mhpmax,
|
|
find_mac(mtmp), info);
|
|
}
|
|
|
|
/* stethoscope or probing applied to hero -- one-line feedback */
|
|
void
|
|
ustatusline(void)
|
|
{
|
|
char info[BUFSZ];
|
|
|
|
info[0] = '\0';
|
|
if (Sick) {
|
|
Strcat(info, ", dying from");
|
|
if (u.usick_type & SICK_VOMITABLE)
|
|
Strcat(info, " food poisoning");
|
|
if (u.usick_type & SICK_NONVOMITABLE) {
|
|
if (u.usick_type & SICK_VOMITABLE)
|
|
Strcat(info, " and");
|
|
Strcat(info, " illness");
|
|
}
|
|
}
|
|
if (Stoned)
|
|
Strcat(info, ", solidifying");
|
|
if (Slimed)
|
|
Strcat(info, ", becoming slimy");
|
|
if (Strangled)
|
|
Strcat(info, ", being strangled");
|
|
if (Vomiting)
|
|
Strcat(info, ", nauseated"); /* !"nauseous" */
|
|
if (Confusion)
|
|
Strcat(info, ", confused");
|
|
if (Blind) {
|
|
Strcat(info, ", blind");
|
|
if (u.ucreamed) {
|
|
if ((long) u.ucreamed < Blinded || Blindfolded
|
|
|| !haseyes(g.youmonst.data))
|
|
Strcat(info, ", cover");
|
|
Strcat(info, "ed by sticky goop");
|
|
} /* note: "goop" == "glop"; variation is intentional */
|
|
}
|
|
if (Stunned)
|
|
Strcat(info, ", stunned");
|
|
if (Wounded_legs && !u.usteed) {
|
|
/* EWounded_legs is used to track left/right/both rather than some
|
|
form of extrinsic impairment; HWounded_legs is used for timeout;
|
|
both apply to steed instead of hero when mounted */
|
|
long legs = (EWounded_legs & BOTH_SIDES);
|
|
const char *what = body_part(LEG);
|
|
|
|
if (legs == BOTH_SIDES)
|
|
what = makeplural(what);
|
|
/* when it's just one leg, ^X reports which, left or right;
|
|
ustatusline() doesn't, in order to keep the output a bit shorter */
|
|
Sprintf(eos(info), ", injured %s", what);
|
|
}
|
|
if (Glib)
|
|
Sprintf(eos(info), ", slippery %s", fingers_or_gloves(TRUE));
|
|
if (u.utrap)
|
|
Strcat(info, ", trapped");
|
|
if (Fast)
|
|
Strcat(info, Very_fast ? ", very fast" : ", fast");
|
|
if (u.uundetected)
|
|
Strcat(info, ", concealed");
|
|
if (Invis)
|
|
Strcat(info, ", invisible");
|
|
if (u.ustuck) {
|
|
if (sticks(g.youmonst.data))
|
|
Strcat(info, ", holding ");
|
|
else
|
|
Strcat(info, ", held by ");
|
|
Strcat(info, mon_nam(u.ustuck));
|
|
}
|
|
|
|
pline("Status of %s (%s): Level %d HP %d(%d) AC %d%s.", g.plname,
|
|
piousness(FALSE, align_str(u.ualign.type)),
|
|
Upolyd ? mons[u.umonnum].mlevel : u.ulevel, Upolyd ? u.mh : u.uhp,
|
|
Upolyd ? u.mhmax : u.uhpmax, u.uac, info);
|
|
}
|
|
|
|
/*insight.c*/
|