1 /* NetHack 3.7	insight.c	$NHDT-Date: 1619640466 2021/04/28 20:07:46 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.35 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 /*
6  * Enlightenment and Conduct+Achievements and Vanquished+Extinct+Geno'd
7  * and stethoscope/probing feedback.
8  *
9  * Most code used to reside in cmd.c, presumeably because ^X was originally
10  * a wizard mode command and the majority of those are in that file.
11  * Some came from end.c where it is used during end of game disclosure.
12  * And some came from priest.c that had once been in pline.c.
13  */
14 
15 #include "hack.h"
16 
17 static void enlght_out_attr(int, const char *);
18 static void enlght_out(const char *);
19 static void enlght_line(const char *, const char *, const char *,
20                         const char *);
21 static char *enlght_combatinc(const char *, int, int, char *);
22 static void enlght_halfdmg(int, int);
23 static boolean walking_on_water(void);
24 static boolean cause_known(int);
25 static char *attrval(int, int, char *);
26 static void background_enlightenment(int, int);
27 static void basics_enlightenment(int, int);
28 static void characteristics_enlightenment(int, int);
29 static void one_characteristic(int, int, int);
30 static void status_enlightenment(int, int);
31 static void weapon_insight(int);
32 static void attributes_enlightenment(int, int);
33 static void show_achievements(int);
34 static int QSORTCALLBACK vanqsort_cmp(const genericptr, const genericptr);
35 static int set_vanq_order(void);
36 static int num_extinct(void);
37 
38 extern const char *hu_stat[];  /* hunger status from eat.c */
39 extern const char *enc_stat[]; /* encumbrance status from botl.c */
40 
41 static const char You_[] = "You ", are[] = "are ", were[] = "were ",
42                   have[] = "have ", had[] = "had ", can[] = "can ",
43                   could[] = "could ";
44 static const char have_been[] = "have been ", have_never[] = "have never ",
45                   never[] = "never ";
46 
47 /* for livelogging: */
48 struct ll_achieve_msg {
49     unsigned long llflag;
50     const char *msg;
51 };
52 /* ordered per 'enum achievements' in you.h */
53 /* take care to keep them in sync! */
54 static struct ll_achieve_msg achieve_msg [] = {
55     { 0, "" }, /* actual achievements are numbered from 1 */
56     { LL_ACHIEVE, "acquired the Bell of Opening" },
57     { LL_ACHIEVE, "entered Gehennom" },
58     { LL_ACHIEVE, "acquired the Candelabrum of Invocation" },
59     { LL_ACHIEVE, "acquired the Book of the Dead" },
60     { LL_ACHIEVE, "performed the invocation" },
61     { LL_ACHIEVE, "acquired The Amulet of Yendor" },
62     { LL_ACHIEVE, "entered the Planes" },
63     { LL_ACHIEVE, "entered the Astral Plane" },
64     { LL_ACHIEVE, "ascended" },
65     { LL_ACHIEVE, "acquired the Mines' End luckstone" },
66     { LL_ACHIEVE, "completed Sokoban" },
67     { LL_ACHIEVE|LL_UMONST, "killed Medusa" },
68      /* these two are not logged */
69     { 0, "hero was always blind" },
70     { 0, "hero never wore armor" },
71      /* */
72     { LL_MINORAC, "entered the Gnomish Mines" },
73     { LL_ACHIEVE, "reached Minetown" }, /* probably minor, but dnh logs it */
74     { LL_MINORAC, "entered a shop" },
75     { LL_MINORAC, "entered a temple" },
76     { LL_ACHIEVE, "consulted the Oracle" }, /* minor, but rare enough */
77     { LL_ACHIEVE, "read a Discworld novel" }, /* ditto */
78     { LL_ACHIEVE, "entered Sokoban" }, /* Keep as major for turn comparison w/completed soko */
79     { LL_ACHIEVE, "entered the Big Room" },
80     /* The following 8 are for advancing through the ranks
81        messages differ by role so are created on the fly */
82     { LL_MINORAC, "" },
83     { LL_MINORAC, "" },
84     { LL_MINORAC, "" },
85     { LL_MINORAC, "" },
86     { LL_ACHIEVE, "" },
87     { LL_ACHIEVE, "" },
88     { LL_ACHIEVE, "" },
89     { LL_ACHIEVE, "" },
90     { 0, "" } /* keep this one at the end */
91 };
92 
93 #define enl_msg(prefix, present, past, suffix, ps) \
94     enlght_line(prefix, final ? past : present, suffix, ps)
95 #define you_are(attr, ps) enl_msg(You_, are, were, attr, ps)
96 #define you_have(attr, ps) enl_msg(You_, have, had, attr, ps)
97 #define you_can(attr, ps) enl_msg(You_, can, could, attr, ps)
98 #define you_have_been(goodthing) enl_msg(You_, have_been, were, goodthing, "")
99 #define you_have_never(badthing) \
100     enl_msg(You_, have_never, never, badthing, "")
101 #define you_have_X(something) \
102     enl_msg(You_, have, (const char *) "", something, "")
103 
104 static void
enlght_out_attr(int attr,const char * buf)105 enlght_out_attr(int attr, const char *buf)
106 {
107     if (g.en_via_menu) {
108         anything any;
109 
110         any = cg.zeroany;
111         add_menu(g.en_win, &nul_glyphinfo, &any, 0, 0, attr, buf,
112                  MENU_ITEMFLAGS_NONE);
113     } else
114         putstr(g.en_win, attr, buf);
115 }
116 
117 static void
enlght_out(const char * buf)118 enlght_out(const char *buf)
119 {
120     enlght_out_attr(ATR_NONE, buf);
121 }
122 
123 static void
enlght_line(const char * start,const char * middle,const char * end,const char * ps)124 enlght_line(const char *start, const char *middle, const char *end,
125             const char *ps)
126 {
127     char buf[BUFSZ];
128 
129     Sprintf(buf, " %s%s%s%s.", start, middle, end, ps);
130     enlght_out(buf);
131 }
132 
133 /* format increased chance to hit or damage or defense (Protection) */
134 static char *
enlght_combatinc(const char * inctyp,int incamt,int final,char * outbuf)135 enlght_combatinc(const char *inctyp, int incamt, int final, char *outbuf)
136 {
137     const char *modif, *bonus;
138     boolean invrt;
139     int absamt;
140 
141     absamt = abs(incamt);
142     /* Protection amount is typically larger than damage or to-hit;
143        reduce magnitude by a third in order to stretch modifier ranges
144        (small:1..5, moderate:6..10, large:11..19, huge:20+) */
145     if (!strcmp(inctyp, "defense"))
146         absamt = (absamt * 2) / 3;
147 
148     if (absamt <= 3)
149         modif = "small";
150     else if (absamt <= 6)
151         modif = "moderate";
152     else if (absamt <= 12)
153         modif = "large";
154     else
155         modif = "huge";
156 
157     modif = !incamt ? "no" : an(modif); /* ("no" case shouldn't happen) */
158     bonus = (incamt >= 0) ? "bonus" : "penalty";
159     /* "bonus <foo>" (to hit) vs "<bar> bonus" (damage, defense) */
160     invrt = strcmp(inctyp, "to hit") ? TRUE : FALSE;
161 
162     Sprintf(outbuf, "%s %s %s", modif, invrt ? inctyp : bonus,
163             invrt ? bonus : inctyp);
164     if (final || wizard)
165         Sprintf(eos(outbuf), " (%s%d)", (incamt > 0) ? "+" : "", incamt);
166 
167     return outbuf;
168 }
169 
170 /* report half physical or half spell damage */
171 static void
enlght_halfdmg(int category,int final)172 enlght_halfdmg(int category, int final)
173 {
174     const char *category_name;
175     char buf[BUFSZ];
176 
177     switch (category) {
178     case HALF_PHDAM:
179         category_name = "physical";
180         break;
181     case HALF_SPDAM:
182         category_name = "spell";
183         break;
184     default:
185         category_name = "unknown";
186         break;
187     }
188     Sprintf(buf, " %s %s damage", (final || wizard) ? "half" : "reduced",
189             category_name);
190     enl_msg(You_, "take", "took", buf, from_what(category));
191 }
192 
193 /* is hero actively using water walking capability on water (or lava)? */
194 static boolean
walking_on_water(void)195 walking_on_water(void)
196 {
197     if (u.uinwater || Levitation || Flying)
198         return FALSE;
199     return (boolean) (Wwalking
200                       && (is_pool(u.ux, u.uy) || is_lava(u.ux, u.uy)));
201 }
202 
203 /* describe u.utraptype; used by status_enlightenment() and self_lookat() */
204 char *
trap_predicament(char * outbuf,int final,boolean wizxtra)205 trap_predicament(char *outbuf, int final, boolean wizxtra)
206 {
207     struct trap *t;
208 
209     /* caller has verified u.utrap */
210     *outbuf = '\0';
211     switch (u.utraptype) {
212     case TT_BURIEDBALL:
213         Strcpy(outbuf, "tethered to something buried");
214         break;
215     case TT_LAVA:
216         Sprintf(outbuf, "sinking into %s", final ? "lava" : hliquid("lava"));
217         break;
218     case TT_INFLOOR:
219         Sprintf(outbuf, "stuck in %s", the(surface(u.ux, u.uy)));
220         break;
221     default: /* TT_BEARTRAP, TT_PIT, or TT_WEB */
222         Strcpy(outbuf, "trapped");
223         if ((t = t_at(u.ux, u.uy)) != 0) /* should never be null */
224             Sprintf(eos(outbuf), " in %s", an(trapname(t->ttyp, FALSE)));
225         break;
226     }
227     if (wizxtra) { /* give extra information for wizard mode enlightenment */
228         /* curly braces: u.utrap is an escape attempt counter rather than a
229            turn timer so use different ornamentation than usual parentheses */
230         Sprintf(eos(outbuf), " {%u}", u.utrap);
231     }
232     return outbuf;
233 }
234 
235 /* check whether hero is wearing something that player definitely knows
236    confers the target property; item must have been seen and its type
237    discovered but it doesn't necessarily have to be fully identified */
238 static boolean
cause_known(int propindx)239 cause_known(int propindx) /* index of a property which can be conveyed by worn item */
240 {
241     register struct obj *o;
242     long mask = W_ARMOR | W_AMUL | W_RING | W_TOOL;
243 
244     /* simpler than from_what()/what_gives(); we don't attempt to
245        handle artifacts and we deliberately ignore wielded items */
246     for (o = g.invent; o; o = o->nobj) {
247         if (!(o->owornmask & mask))
248             continue;
249         if ((int) objects[o->otyp].oc_oprop == propindx
250             && objects[o->otyp].oc_name_known && o->dknown)
251             return TRUE;
252     }
253     return FALSE;
254 }
255 
256 /* format a characteristic value, accommodating Strength's strangeness */
257 static char *
attrval(int attrindx,int attrvalue,char resultbuf[])258 attrval(int attrindx, int attrvalue,
259         char resultbuf[]) /* should be at least [7] to hold "18/100\0" */
260 {
261     if (attrindx != A_STR || attrvalue <= 18)
262         Sprintf(resultbuf, "%d", attrvalue);
263     else if (attrvalue > STR18(100)) /* 19 to 25 */
264         Sprintf(resultbuf, "%d", attrvalue - 100);
265     else /* simplify "18/ **" to be "18/100" */
266         Sprintf(resultbuf, "18/%02d", attrvalue - 18);
267     return resultbuf;
268 }
269 
270 void
enlightenment(int mode,int final)271 enlightenment(int mode,  /* BASICENLIGHTENMENT | MAGICENLIGHTENMENT (| both) */
272               int final) /* ENL_GAMEINPROGRESS:0, ENL_GAMEOVERALIVE,
273                             ENL_GAMEOVERDEAD */
274 {
275     char buf[BUFSZ], tmpbuf[BUFSZ];
276 
277     g.en_win = create_nhwindow(NHW_MENU);
278     g.en_via_menu = !final;
279     if (g.en_via_menu)
280         start_menu(g.en_win, MENU_BEHAVE_STANDARD);
281 
282     Strcpy(tmpbuf, g.plname);
283     *tmpbuf = highc(*tmpbuf); /* same adjustment as bottom line */
284     /* as in background_enlightenment, when poly'd we need to use the saved
285        gender in u.mfemale rather than the current you-as-monster gender */
286     Snprintf(buf, sizeof(buf), "%s the %s's attributes:", tmpbuf,
287             ((Upolyd ? u.mfemale : flags.female) && g.urole.name.f)
288                 ? g.urole.name.f
289                 : g.urole.name.m);
290 
291     /* title */
292     enlght_out_attr(ATR_HEADING, buf); /* "Conan the Archeologist's attributes:" */
293     /* background and characteristics; ^X or end-of-game disclosure */
294     if (mode & BASICENLIGHTENMENT) {
295         /* role, race, alignment, deities, dungeon level, time, experience */
296         background_enlightenment(mode, final);
297         /* hit points, energy points, armor class, gold */
298         basics_enlightenment(mode, final);
299         /* strength, dexterity, &c */
300         characteristics_enlightenment(mode, final);
301     }
302     /* expanded status line information, including things which aren't
303        included there due to space considerations;
304        shown for both basic and magic enlightenment */
305     status_enlightenment(mode, final);
306     /* remaining attributes; shown for potion,&c or wizard mode and
307        explore mode ^X or end of game disclosure */
308     if (mode & MAGICENLIGHTENMENT) {
309         /* intrinsics and other traditional enlightenment feedback */
310         attributes_enlightenment(mode, final);
311     }
312     /* reminder to player and/or information for dumplog */
313     if ((mode & BASICENLIGHTENMENT) != 0 && (wizard || discover || final)) {
314         enlght_out(""); /* separator */
315         enlght_out("Miscellaneous:");
316         if (wizard || discover) {
317         Sprintf(buf, "running in %s mode", wizard ? "debug" : "explore");
318         you_are(buf, "");
319     }
320 
321         if (!flags.bones) {
322             you_have_X("disabled loading of bones levels");
323         } else if (!u.uroleplay.numbones) {
324             you_have_never("encountered a bones level");
325         } else {
326             Sprintf(buf, "encountered %ld bones level%s",
327                     u.uroleplay.numbones, plur(u.uroleplay.numbones));
328             you_have_X(buf);
329         }
330     }
331 
332     if (!g.en_via_menu) {
333         display_nhwindow(g.en_win, TRUE);
334     } else {
335         menu_item *selected = 0;
336 
337         end_menu(g.en_win, (char *) 0);
338         if (select_menu(g.en_win, PICK_NONE, &selected) > 0)
339             free((genericptr_t) selected);
340         g.en_via_menu = FALSE;
341     }
342     destroy_nhwindow(g.en_win);
343     g.en_win = WIN_ERR;
344 }
345 
346 /*ARGSUSED*/
347 /* display role, race, alignment and such to en_win */
348 static void
background_enlightenment(int unused_mode UNUSED,int final)349 background_enlightenment(int unused_mode UNUSED, int final)
350 {
351     const char *role_titl, *rank_titl;
352     int innategend, difgend, difalgn;
353     char buf[BUFSZ], tmpbuf[BUFSZ];
354 
355     /* note that if poly'd, we need to use u.mfemale instead of flags.female
356        to access hero's saved gender-as-human/elf/&c rather than current */
357     innategend = (Upolyd ? u.mfemale : flags.female) ? 1 : 0;
358     role_titl = (innategend && g.urole.name.f) ? g.urole.name.f
359                                                : g.urole.name.m;
360     rank_titl = rank_of(u.ulevel, Role_switch, innategend);
361 
362     enlght_out(""); /* separator after title */
363     enlght_out_attr(ATR_SUBHEAD, "Background:");
364 
365     /* if polymorphed, report current shape before underlying role;
366        will be repeated as first status: "you are transformed" and also
367        among various attributes: "you are in beast form" (after being
368        told about lycanthropy) or "you are polymorphed into <a foo>"
369        (with countdown timer appended for wizard mode); we really want
370        the player to know he's not a samurai at the moment... */
371     if (Upolyd) {
372         char anbuf[20]; /* includes trailing space; [4] suffices */
373         struct permonst *uasmon = g.youmonst.data;
374         boolean altphrasing = vampshifted(&g.youmonst);
375 
376         tmpbuf[0] = '\0';
377         /* here we always use current gender, not saved role gender */
378         if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon))
379             Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj);
380         if (altphrasing)
381             Sprintf(eos(tmpbuf), "%s in ",
382                     pmname(&mons[g.youmonst.cham],
383                            flags.female ? FEMALE : MALE));
384         Snprintf(buf, sizeof(buf), "%s%s%s%s form",
385                  !final ? "currently " : "",
386                 altphrasing ? just_an(anbuf, tmpbuf) : "in ",
387                  tmpbuf, pmname(uasmon, flags.female ? FEMALE : MALE));
388         you_are(buf, "");
389     }
390 
391     /* report role; omit gender if it's redundant (eg, "female priestess") */
392     tmpbuf[0] = '\0';
393     if (!g.urole.name.f
394         && ((g.urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE)
395             || innategend != flags.initgend))
396         Sprintf(tmpbuf, "%s ", genders[innategend].adj);
397     buf[0] = '\0';
398     if (Upolyd)
399         Strcpy(buf, "actually "); /* "You are actually a ..." */
400     if (!strcmpi(rank_titl, role_titl)) {
401         /* omit role when rank title matches it */
402         Sprintf(eos(buf), "%s, level %d %s%s", an(rank_titl), u.ulevel,
403                 tmpbuf, g.urace.noun);
404     } else {
405         Sprintf(eos(buf), "%s, a level %d %s%s %s", an(rank_titl), u.ulevel,
406                 tmpbuf, g.urace.adj, role_titl);
407     }
408     you_are(buf, "");
409 
410     /* report alignment (bypass you_are() in order to omit ending period);
411        adverb is used to distinguish between temporary change (helm of opp.
412        alignment), permanent change (one-time conversion), and original */
413     Sprintf(buf, " %s%s%s, %son a mission for %s",
414             You_, !final ? are : were,
415             align_str(u.ualign.type),
416             /* helm of opposite alignment (might hide conversion) */
417             (u.ualign.type != u.ualignbase[A_CURRENT])
418                /* what's the past tense of "currently"? if we used "formerly"
419                   it would sound like a reference to the original alignment */
420                ? (!final ? "currently " : "temporarily ")
421                /* permanent conversion */
422                : (u.ualign.type != u.ualignbase[A_ORIGINAL])
423                   /* and what's the past tense of "now"? certainly not "then"
424                      in a context like this...; "belatedly" == weren't that
425                      way sooner (in other words, didn't start that way) */
426                   ? (!final ? "now " : "belatedly ")
427                   /* atheist (ignored in very early game) */
428                   : (!u.uconduct.gnostic && g.moves > 1000L)
429                      ? "nominally "
430                      /* lastly, normal case */
431                      : "",
432             u_gname());
433     enlght_out(buf);
434     /* show the rest of this game's pantheon (finishes previous sentence)
435        [appending "also Moloch" at the end would allow for straightforward
436        trailing "and" on all three aligned entries but looks too verbose] */
437     Sprintf(buf, " who %s opposed by", !final ? "is" : "was");
438     if (u.ualign.type != A_LAWFUL)
439         Sprintf(eos(buf), " %s (%s) and", align_gname(A_LAWFUL),
440                 align_str(A_LAWFUL));
441     if (u.ualign.type != A_NEUTRAL)
442         Sprintf(eos(buf), " %s (%s)%s", align_gname(A_NEUTRAL),
443                 align_str(A_NEUTRAL),
444                 (u.ualign.type != A_CHAOTIC) ? " and" : "");
445     if (u.ualign.type != A_CHAOTIC)
446         Sprintf(eos(buf), " %s (%s)", align_gname(A_CHAOTIC),
447                 align_str(A_CHAOTIC));
448     Strcat(buf, "."); /* terminate sentence */
449     enlght_out(buf);
450 
451     /* show original alignment,gender,race,role if any have been changed;
452        giving separate message for temporary alignment change bypasses need
453        for tricky phrasing otherwise necessitated by possibility of having
454        helm of opposite alignment mask a permanent alignment conversion */
455     difgend = (innategend != flags.initgend);
456     difalgn = (((u.ualign.type != u.ualignbase[A_CURRENT]) ? 1 : 0)
457                + ((u.ualignbase[A_CURRENT] != u.ualignbase[A_ORIGINAL])
458                   ? 2 : 0));
459     if (difalgn & 1) { /* have temporary alignment so report permanent one */
460         Sprintf(buf, "actually %s", align_str(u.ualignbase[A_CURRENT]));
461         you_are(buf, "");
462         difalgn &= ~1; /* suppress helm from "started out <foo>" message */
463     }
464     if (difgend || difalgn) { /* sex change or perm align change or both */
465         Sprintf(buf, " You started out %s%s%s.",
466                 difgend ? genders[flags.initgend].adj : "",
467                 (difgend && difalgn) ? " and " : "",
468                 difalgn ? align_str(u.ualignbase[A_ORIGINAL]) : "");
469         enlght_out(buf);
470     }
471 
472     /* sexual orientation */
473     buf[0] = '\0';
474     Sprintf(buf, "%s", orientations[flags.orientation].technical);
475     you_are(buf, "");
476 
477     /* As of 3.6.2: dungeon level, so that ^X really has all status info as
478        claimed by the comment below; this reveals more information than
479        the basic status display, but that's one of the purposes of ^X;
480        similar information is revealed by #overview; the "You died in
481        <location>" given by really_done() is more rudimentary than this */
482     *buf = *tmpbuf = '\0';
483     if (In_endgame(&u.uz)) {
484         int egdepth = observable_depth(&u.uz);
485 
486         (void) endgamelevelname(tmpbuf, egdepth);
487         Snprintf(buf, sizeof(buf), "in the endgame, on the %s%s",
488                 !strncmp(tmpbuf, "Plane", 5) ? "Elemental " : "", tmpbuf);
489     } else if (Is_knox(&u.uz)) {
490         /* this gives away the fact that the knox branch is only 1 level */
491         Sprintf(buf, "on the %s level", g.dungeons[u.uz.dnum].dname);
492         /* TODO? maybe phrase it differently when actually inside the fort,
493            if we're able to determine that (not trivial) */
494     } else {
495         char dgnbuf[QBUFSZ];
496 
497         Strcpy(dgnbuf, g.dungeons[u.uz.dnum].dname);
498         if (!strncmpi(dgnbuf, "The ", 4))
499             *dgnbuf = lowc(*dgnbuf);
500         Sprintf(tmpbuf, "level %d",
501                 In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
502         /* TODO? maybe extend this bit to include various other automatic
503            annotations from the dungeon overview code */
504         if (Is_bigroom(&u.uz) && !Blind)
505             Strcat(tmpbuf, ", a very big room");
506         Snprintf(buf, sizeof(buf), "in %s, on %s", dgnbuf, tmpbuf);
507     }
508     you_are(buf, "");
509 
510     /* this is shown even if the 'time' option is off */
511     if (g.moves == 1L) {
512         you_have("just started your adventure", "");
513     } else {
514         /* 'turns' grates on the nerves in this context... */
515         Sprintf(buf, "the dungeon %ld turn%s ago", g.moves, plur(g.moves));
516         /* same phrasing for current and final: "entered" is unconditional */
517         enlght_line(You_, "entered ", buf, "");
518     }
519 
520     /* for gameover, these have been obtained in really_done() so that they
521        won't vary if user leaves a disclosure prompt or --More-- unanswered
522        long enough for the dynamic value to change between then and now */
523     if (final ? iflags.at_midnight : midnight()) {
524         enl_msg("It ", "is ", "was ", "the midnight hour", "");
525     } else if (final ? iflags.at_night : night()) {
526         enl_msg("It ", "is ", "was ", "nighttime", "");
527     }
528     /* other environmental factors */
529     if (flags.moonphase == FULL_MOON || flags.moonphase == NEW_MOON) {
530         /* [This had "tonight" but has been changed to "in effect".
531            There is a similar issue to Friday the 13th--it's the value
532            at the start of the current session but that session might
533            have dragged on for an arbitrary amount of time.  We want to
534            report the values that currently affect play--or affected
535            play when game ended--rather than actual outside situation.] */
536         Sprintf(buf, "a %s moon in effect%s",
537                 (flags.moonphase == FULL_MOON) ? "full"
538                 : (flags.moonphase == NEW_MOON) ? "new"
539                   /* showing these would probably just lead to confusion
540                      since they have no effect on game play... */
541                   : (flags.moonphase < FULL_MOON) ? "first quarter"
542                     : "last quarter",
543                 /* we don't have access to 'how' here--aside from survived
544                    vs died--so settle for general platitude */
545                 final ? " when your adventure ended" : "");
546         enl_msg("There ", "is ", "was ", buf, "");
547     }
548     if (flags.friday13) {
549         /* let player know that friday13 penalty is/was in effect;
550            we don't say "it is/was Friday the 13th" because that was at
551            the start of the session and it might be past midnight (or
552            days later if the game has been paused without save/restore),
553            so phrase this similar to the start up message */
554         Sprintf(buf, " Bad things %s on Friday the 13th.",
555                 !final ? "can happen"
556                 : (final == ENL_GAMEOVERALIVE) ? "could have happened"
557                   /* there's no may to tell whether -1 Luck made a
558                      difference but hero has died... */
559                   : "happened");
560         enlght_out(buf);
561     }
562 
563     if (!Upolyd) {
564         int ulvl = (int) u.ulevel;
565         /* [flags.showexp currently does not matter; should it?] */
566 
567         /* experience level is already shown above */
568         Sprintf(buf, "%-1ld experience point%s", u.uexp, plur(u.uexp));
569         /* TODO?
570          *  Remove wizard-mode restriction since patient players can
571          *  determine the numbers needed without resorting to spoilers
572          *  (even before this started being disclosed for 'final';
573          *  just enable 'showexp' and look at normal status lines
574          *  after drinking gain level potions or eating wraith corpses
575          *  or being level-drained by vampires).
576          */
577         if (ulvl < 30 && (final || wizard)) {
578             long nxtlvl = newuexp(ulvl), delta = nxtlvl - u.uexp;
579 
580             Sprintf(eos(buf), ", %ld %s%sneeded %s level %d",
581                     delta, (u.uexp > 0) ? "more " : "",
582                     /* present tense=="needed", past tense=="were needed" */
583                     !final ? "" : (delta == 1L) ? "was " : "were ",
584                     /* "for": grammatically iffy but less likely to wrap */
585                     (ulvl < 18) ? "to attain" : "for", (ulvl + 1));
586         }
587         you_have(buf, "");
588     }
589 #ifdef SCORE_ON_BOTL
590     if (flags.showscore) {
591         /* describes what's shown on status line, which is an approximation;
592            only show it here if player has the 'showscore' option enabled */
593         Sprintf(buf, "%ld%s", botl_score(),
594                 !final ? "" : " before end-of-game adjustments");
595         enl_msg("Your score ", "is ", "was ", buf, "");
596     }
597 #endif
598 }
599 
600 /* hit points, energy points, armor class -- essential information which
601    doesn't fit very well in other categories */
602 /*ARGSUSED*/
603 static void
basics_enlightenment(int mode UNUSED,int final)604 basics_enlightenment(int mode UNUSED, int final)
605 {
606     static char Power[] = "energy points (spell power)";
607     char buf[BUFSZ];
608     int pw = u.uen, hp = (Upolyd ? u.mh : u.uhp),
609         pwmax = u.uenmax, hpmax = (Upolyd ? u.mhmax : u.uhpmax);
610 
611     enlght_out(""); /* separator after background */
612     enlght_out_attr(ATR_SUBHEAD, "Basics:");
613 
614     if (hp < 0)
615         hp = 0;
616     /* "1 out of 1" rather than "all" if max is only 1; should never happen */
617     if (hp == hpmax && hpmax > 1)
618         Sprintf(buf, "all %d hit points", hpmax);
619     else
620         Sprintf(buf, "%d out of %d hit point%s", hp, hpmax, plur(hpmax));
621     you_have(buf, "");
622 
623     /* low max energy is feasible, so handle couple of extra special cases */
624     if (pwmax == 0 || (pw == pwmax && pwmax == 2)) /* both: not "all 2" */
625         Sprintf(buf, "%s %s", !pwmax ? "no" : "both", Power);
626     else if (pw == pwmax && pwmax > 2)
627         Sprintf(buf, "all %d %s", pwmax, Power);
628     else
629         Sprintf(buf, "%d out of %d %s", pw, pwmax, Power);
630     you_have(buf, "");
631 
632     if (Upolyd) {
633         switch (mons[u.umonnum].mlevel) {
634         case 0:
635             /* status line currently being explained shows "HD:0" */
636             Strcpy(buf, "0 hit dice (actually 1/2)");
637             break;
638         case 1:
639             Strcpy(buf, "1 hit die");
640             break;
641         default:
642             Sprintf(buf, "%d hit dice", mons[u.umonnum].mlevel);
643             break;
644         }
645         you_have(buf, "");
646     }
647 
648     find_ac(); /* enforces AC_MAX cap */
649     Sprintf(buf, "%d", u.uac);
650     if (abs(u.uac) == AC_MAX)
651         Sprintf(eos(buf), ", the %s possible",
652                 (u.uac < 0) ? "best" : "worst");
653     enl_msg("Your armor class ", "is ", "was ", buf, "");
654 
655     /* gold; similar to doprgold(#seegold) but without shop billing info;
656        same amount as shown on status line which ignores container contents */
657     {
658         static const char Your_wallet[] = "Your wallet ";
659         long umoney = money_cnt(g.invent);
660 
661         if (!umoney) {
662             enl_msg(Your_wallet, "is ", "was ", "empty", "");
663         } else {
664             Sprintf(buf, "%ld %s", umoney, currency(umoney));
665             enl_msg(Your_wallet, "contains ", "contained ", buf, "");
666         }
667     }
668 
669     if (flags.pickup) {
670         char ocl[MAXOCLASSES + 1];
671 
672         Strcpy(buf, "on");
673         if (costly_spot(u.ux, u.uy)) {
674             /* being in a shop inhibits autopickup, even 'pickup_thrown' */
675             Strcat(buf, ", but temporarily disabled while inside the shop");
676         } else {
677             oc_to_str(flags.pickup_types, ocl);
678             Sprintf(eos(buf), " for %s%s%s", *ocl ? "'" : "",
679                     *ocl ? ocl : "all types", *ocl ? "'" : "");
680             if (flags.pickup_thrown && *ocl)
681                 Strcat(buf, " plus thrown"); /* show when not 'all types' */
682             if (g.apelist)
683                 Strcat(buf, ", with exceptions");
684         }
685     } else
686         Strcpy(buf, "off");
687     enl_msg("Autopickup ", "is ", "was ", buf, "");
688 }
689 
690 /* characteristics: expanded version of bottom line strength, dexterity, &c */
691 static void
characteristics_enlightenment(int mode,int final)692 characteristics_enlightenment(int mode, int final)
693 {
694     char buf[BUFSZ];
695 
696     enlght_out("");
697     Sprintf(buf, "%s Characteristics:", !final ? "Current" : "Final");
698     enlght_out_attr(ATR_SUBHEAD, buf);
699 
700     /* bottom line order */
701     one_characteristic(mode, final, A_STR); /* strength */
702     one_characteristic(mode, final, A_DEX); /* dexterity */
703     one_characteristic(mode, final, A_CON); /* constitution */
704     one_characteristic(mode, final, A_INT); /* intelligence */
705     one_characteristic(mode, final, A_WIS); /* wisdom */
706     one_characteristic(mode, final, A_CHA); /* charisma */
707 }
708 
709 /* display one attribute value for characteristics_enlightenment() */
710 static void
one_characteristic(int mode,int final,int attrindx)711 one_characteristic(int mode, int final, int attrindx)
712 {
713     extern const char *const attrname[]; /* attrib.c */
714     boolean hide_innate_value = FALSE, interesting_alimit;
715     int acurrent, abase, apeak, alimit;
716     const char *paren_pfx;
717     char subjbuf[BUFSZ], valubuf[BUFSZ], valstring[32];
718 
719     /* being polymorphed or wearing certain cursed items prevents
720        hero from reliably tracking changes to characteristics so
721        we don't show base & peak values then; when the items aren't
722        cursed, hero could take them off to check underlying values
723        and we show those in such case so that player doesn't need
724        to actually resort to doing that */
725     if (Upolyd) {
726         hide_innate_value = TRUE;
727     } else if (Fixed_abil) {
728         if (stuck_ring(uleft, RIN_SUSTAIN_ABILITY)
729             || stuck_ring(uright, RIN_SUSTAIN_ABILITY))
730             hide_innate_value = TRUE;
731     }
732     switch (attrindx) {
733     case A_STR:
734         if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER && uarmg->cursed)
735             hide_innate_value = TRUE;
736         break;
737     case A_DEX:
738         break;
739     case A_CON:
740         if (uwep && uwep->oartifact == ART_OGRESMASHER && uwep->cursed)
741             hide_innate_value = TRUE;
742         break;
743     case A_INT:
744         if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
745             hide_innate_value = TRUE;
746         break;
747     case A_WIS:
748         if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
749             hide_innate_value = TRUE;
750         break;
751     case A_CHA:
752         break;
753     default:
754         return; /* impossible */
755     };
756     /* note: final disclosure includes MAGICENLIGHTENTMENT */
757     if ((mode & MAGICENLIGHTENMENT) && !Upolyd)
758         hide_innate_value = FALSE;
759 
760     acurrent = ACURR(attrindx);
761     (void) attrval(attrindx, acurrent, valubuf); /* Sprintf(valubuf,"%d",) */
762     Sprintf(subjbuf, "Your %s ", attrname[attrindx]);
763 
764     if (!hide_innate_value) {
765         /* show abase, amax, and/or attrmax if acurr doesn't match abase
766            (a magic bonus or penalty is in effect) or abase doesn't match
767            amax (some points have been lost to poison or exercise abuse
768            and are restorable) or attrmax is different from normal human
769            (while game is in progress; trying to reduce dependency on
770            spoilers to keep track of such stuff) or attrmax was different
771            from abase (at end of game; this attribute wasn't maxed out) */
772         abase = ABASE(attrindx);
773         apeak = AMAX(attrindx);
774         alimit = ATTRMAX(attrindx);
775         /* criterium for whether the limit is interesting varies */
776         interesting_alimit =
777             final ? TRUE /* was originally `(abase != alimit)' */
778                   : (alimit != (attrindx != A_STR ? 18 : STR18(100)));
779         paren_pfx = final ? " (" : " (current; ";
780         if (acurrent != abase) {
781             Sprintf(eos(valubuf), "%sbase:%s", paren_pfx,
782                     attrval(attrindx, abase, valstring));
783             paren_pfx = ", ";
784         }
785         if (abase != apeak) {
786             Sprintf(eos(valubuf), "%speak:%s", paren_pfx,
787                     attrval(attrindx, apeak, valstring));
788             paren_pfx = ", ";
789         }
790         if (interesting_alimit) {
791             Sprintf(eos(valubuf), "%s%slimit:%s", paren_pfx,
792                     /* more verbose if exceeding 'limit' due to magic bonus */
793                     (acurrent > alimit) ? "innate " : "",
794                     attrval(attrindx, alimit, valstring));
795             /* paren_pfx = ", "; */
796         }
797         if (acurrent != abase || abase != apeak || interesting_alimit)
798             Strcat(valubuf, ")");
799     }
800     enl_msg(subjbuf, "is ", "was ", valubuf, "");
801 }
802 
803 /* status: selected obvious capabilities, assorted troubles */
804 static void
status_enlightenment(int mode,int final)805 status_enlightenment(int mode, int final)
806 {
807     boolean magic = (mode & MAGICENLIGHTENMENT) ? TRUE : FALSE;
808     int cap;
809     char buf[BUFSZ], youtoo[BUFSZ], heldmon[BUFSZ];
810     boolean Riding = (u.usteed
811                       /* if hero dies while dismounting, u.usteed will still
812                          be set; we want to ignore steed in that situation */
813                       && !(final == ENL_GAMEOVERDEAD
814                            && !strcmp(g.killer.name, "riding accident")));
815     const char *steedname = (!Riding ? (char *) 0
816                       : x_monnam(u.usteed,
817                                  u.usteed->mtame ? ARTICLE_YOUR : ARTICLE_THE,
818                                  (char *) 0,
819                                  (SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION),
820                                  FALSE));
821 
822     /*\
823      * Status (many are abbreviated on bottom line; others are or
824      *     should be discernible to the hero hence to the player)
825     \*/
826     enlght_out(""); /* separator after title or characteristics */
827     enlght_out_attr(ATR_SUBHEAD, final ? "Final Status:" : "Current Status:");
828 
829     Strcpy(youtoo, You_);
830     /* not a traditional status but inherently obvious to player; more
831        detail given below (attributes section) for magic enlightenment */
832     if (Upolyd) {
833         Strcpy(buf, "transformed");
834         if (ugenocided())
835             Sprintf(eos(buf), " and %s %s inside",
836                     final ? "felt" : "feel", udeadinside());
837         you_are(buf, "");
838     }
839     /* not a trouble, but we want to display riding status before maybe
840        reporting steed as trapped or hero stuck to cursed saddle */
841     if (Riding) {
842         Sprintf(buf, "riding %s", steedname);
843         you_are(buf, "");
844         Sprintf(eos(youtoo), "and %s ", steedname);
845     }
846     /* other movement situations that hero should always know */
847     if (Levitation) {
848         if (Lev_at_will && magic)
849             you_are("levitating, at will", "");
850         else
851             enl_msg(youtoo, are, were, "levitating", from_what(LEVITATION));
852     } else if (Flying) { /* can only fly when not levitating */
853         enl_msg(youtoo, are, were, "flying", from_what(FLYING));
854     }
855     if (Underwater) {
856         you_are("underwater", "");
857     } else if (u.uinwater) {
858         you_are(Swimming ? "swimming" : "in water", from_what(SWIMMING));
859     } else if (walking_on_water()) {
860         /* show active Wwalking here, potential Wwalking elsewhere */
861         Sprintf(buf, "walking on %s",
862                 is_pool(u.ux, u.uy) ? "water"
863                 : is_lava(u.ux, u.uy) ? "lava"
864                   : surface(u.ux, u.uy)); /* catchall; shouldn't happen */
865         you_are(buf, from_what(WWALKING));
866     }
867     if (Upolyd && (u.uundetected || U_AP_TYPE != M_AP_NOTHING))
868         youhiding(TRUE, final);
869 
870     /* internal troubles, mostly in the order that prayer ranks them */
871     if (Stoned) {
872         if (final && (Stoned & I_SPECIAL))
873             enlght_out(" You turned into stone.");
874         else
875             you_are("turning to stone", "");
876     }
877     if (Slimed) {
878         if (final && (Slimed & I_SPECIAL))
879             enlght_out(" You turned into slime.");
880         else
881             you_are("turning into slime", "");
882     }
883     if (Strangled) {
884         if (u.uburied) {
885             you_are("buried", "");
886         } else {
887             if (final && (Strangled & I_SPECIAL)) {
888                 enlght_out(" You died from strangulation.");
889             } else {
890                 Strcpy(buf, "being strangled");
891                 if (wizard)
892                     Sprintf(eos(buf), " (%ld)", (Strangled & TIMEOUT));
893                 you_are(buf, from_what(STRANGLED));
894             }
895         }
896     }
897     if (Sick) {
898         /* the two types of sickness are lumped together; hero can be
899            afflicted by both but there is only one timeout; botl status
900            puts TermIll before FoodPois and death due to timeout reports
901            terminal illness if both are in effect, so do the same here */
902         if (final && (Sick & I_SPECIAL)) {
903             Sprintf(buf, " %sdied from %s.", You_, /* has trailing space */
904                     (u.usick_type & SICK_NONVOMITABLE)
905                     ? "terminal illness" : "food poisoning");
906             enlght_out(buf);
907         } else {
908             /* unlike death due to sickness, report the two cases separately
909                because it is possible to cure one without curing the other */
910             if (u.usick_type & SICK_NONVOMITABLE)
911                 you_are("terminally sick from illness", "");
912             if (u.usick_type & SICK_VOMITABLE)
913                 you_are("terminally sick from food poisoning", "");
914         }
915     }
916     if (Withering)
917         you_are("withering away", "");
918     if (Vomiting)
919         you_are("nauseated", "");
920     if (Stunned)
921         you_are("stunned", "");
922     if (Confusion)
923         you_are("confused", "");
924     if (Hallucination)
925         you_are("hallucinating", from_what(HALLUC));
926     if (Blind) {
927         /* from_what() (currently wizard-mode only) checks !haseyes()
928            before u.uroleplay.blind, so we should too */
929         Sprintf(buf, "%s blind",
930                 !haseyes(g.youmonst.data) ? "innately"
931                 : u.uroleplay.blind ? "permanently"
932                   /* better phrasing desperately wanted... */
933                   : Blindfolded_only ? "deliberately"
934                     : "temporarily");
935         if (wizard && (Blinded & TIMEOUT) != 0L
936             && !u.uroleplay.blind && haseyes(g.youmonst.data))
937             Sprintf(eos(buf), " (%ld)", (Blinded & TIMEOUT));
938         /* !haseyes: avoid "you are innately blind innately" */
939         you_are(buf, !haseyes(g.youmonst.data) ? "" : from_what(BLINDED));
940     }
941     if (Deaf)
942         you_are("deaf", from_what(DEAF));
943 
944     /* external troubles, more or less */
945     if (Punished) {
946         if (uball) {
947             Sprintf(buf, "chained to %s", ansimpleoname(uball));
948         } else {
949             impossible("Punished without uball?");
950             Strcpy(buf, "punished");
951         }
952         you_are(buf, "");
953     }
954     if (u.utrap) {
955         char predicament[BUFSZ];
956         boolean anchored = (u.utraptype == TT_BURIEDBALL);
957 
958         (void) trap_predicament(predicament, final, wizard);
959         if (u.usteed) { /* not `Riding' here */
960             Sprintf(buf, "%s%s ", anchored ? "you and " : "", steedname);
961             *buf = highc(*buf);
962             enl_msg(buf, (anchored ? "are " : "is "),
963                     (anchored ? "were " : "was "), predicament, "");
964         } else
965             you_are(predicament, "");
966     } /* (u.utrap) */
967     heldmon[0] = '\0'; /* lint suppression */
968     if (u.ustuck) { /* includes u.uswallow */
969         Strcpy(heldmon, a_monnam(u.ustuck));
970         if (!strcmp(heldmon, "it")
971             && (!has_mgivenname(u.ustuck)
972                 || strcmp(MGIVENNAME(u.ustuck), "it") != 0))
973             Strcpy(heldmon, "an unseen creature");
974     }
975     if (u.uswallow) { /* implies u.ustuck is non-Null */
976         Sprintf(buf, "%s by %s",
977                 is_animal(u.ustuck->data) ? "swallowed" : "engulfed",
978                 heldmon);
979         if (dmgtype(u.ustuck->data, AD_DGST)) {
980             /* if final, death via digestion can be deduced by u.uswallow
981                still being True and u.uswldtim having been decremented to 0 */
982             if (final && !u.uswldtim)
983                 Strcat(buf, " and got totally digested");
984             else
985                 Sprintf(eos(buf), " and %s being digested",
986                         final ? "were" : "are");
987         }
988         if (wizard)
989             Sprintf(eos(buf), " (%u)", u.uswldtim);
990         you_are(buf, "");
991     } else if (u.ustuck) {
992         boolean ustick = (Upolyd && sticks(g.youmonst.data));
993         int dx = u.ustuck->mx - u.ux, dy = u.ustuck->my - u.uy;
994 
995         Sprintf(buf, "%s %s (%s)", ustick ? "holding" : "held by",
996                 heldmon, dxdy_to_dist_descr(dx, dy, TRUE));
997         you_are(buf, "");
998     }
999     if (Riding) {
1000         struct obj *saddle = which_armor(u.usteed, W_SADDLE);
1001 
1002         if (saddle && saddle->cursed) {
1003             Sprintf(buf, "stuck to %s %s", s_suffix(steedname),
1004                     simpleonames(saddle));
1005             you_are(buf, "");
1006         }
1007     }
1008     if (Wounded_legs) {
1009         /* when mounted, Wounded_legs applies to steed rather than to
1010            hero; we only report steed's wounded legs in wizard mode */
1011         if (u.usteed) { /* not `Riding' here */
1012             if (wizard && steedname) {
1013                 Strcpy(buf, steedname);
1014                 *buf = highc(*buf);
1015                 enl_msg(buf, " has", " had", " wounded legs", "");
1016             }
1017         } else {
1018             long wl = (EWounded_legs & BOTH_SIDES);
1019             const char *bp = body_part(LEG), *article = "a ";
1020 
1021             if (wl == BOTH_SIDES)
1022                 bp = makeplural(bp), article = "";
1023             Sprintf(buf, "%swounded %s", article, bp);
1024             you_have(buf, "");
1025         }
1026     }
1027     if (Glib) {
1028         Sprintf(buf, "slippery %s", fingers_or_gloves(TRUE));
1029         if (wizard)
1030             Sprintf(eos(buf), " (%ld)", (Glib & TIMEOUT));
1031         you_have(buf, "");
1032     }
1033     if (Fumbling) {
1034         if (magic || cause_known(FUMBLING))
1035             enl_msg(You_, "fumble", "fumbled", "", from_what(FUMBLING));
1036     }
1037     if (Sleepy) {
1038         if (magic || cause_known(SLEEPY)) {
1039             Strcpy(buf, from_what(SLEEPY));
1040             if (wizard)
1041                 Sprintf(eos(buf), " (%ld)", (HSleepy & TIMEOUT));
1042             enl_msg("You ", "fall", "fell", " asleep uncontrollably", buf);
1043         }
1044     }
1045     /* hunger/nutrition */
1046     if (Hunger) {
1047         if (magic || cause_known(HUNGER))
1048             enl_msg(You_, "hunger", "hungered", " rapidly",
1049                     from_what(HUNGER));
1050     }
1051     Strcpy(buf, hu_stat[u.uhs]); /* hunger status; omitted if "normal" */
1052     mungspaces(buf);             /* strip trailing spaces */
1053     /* status line doesn't show hunger when state is "not hungry", we do;
1054        needed for wizard mode's reveal of u.uhunger but add it for everyone */
1055     if (!*buf)
1056         Strcpy(buf, "not hungry");
1057     if (*buf) { /* (since "not hungry" was added, this will always be True) */
1058         *buf = lowc(*buf); /* override capitalization */
1059         if (!strcmp(buf, "weak"))
1060             Strcat(buf, " from severe hunger");
1061         else if (!strncmp(buf, "faint", 5)) /* fainting, fainted */
1062             Strcat(buf, " due to starvation");
1063         if (wizard)
1064             Sprintf(eos(buf), " <%d>", u.uhunger);
1065         you_are(buf, "");
1066     }
1067     /* encumbrance */
1068     if ((cap = near_capacity()) > UNENCUMBERED) {
1069         const char *adj = "?_?"; /* (should always get overridden) */
1070 
1071         Strcpy(buf, enc_stat[cap]);
1072         *buf = lowc(*buf);
1073         switch (cap) {
1074         case SLT_ENCUMBER:
1075             adj = "slightly";
1076             break; /* burdened */
1077         case MOD_ENCUMBER:
1078             adj = "moderately";
1079             break; /* stressed */
1080         case HVY_ENCUMBER:
1081             adj = "very";
1082             break; /* strained */
1083         case EXT_ENCUMBER:
1084             adj = "extremely";
1085             break; /* overtaxed */
1086         case OVERLOADED:
1087             adj = "not possible";
1088             break;
1089         }
1090         if (wizard)
1091             Sprintf(eos(buf), " <%d>", inv_weight());
1092         Sprintf(eos(buf), "; movement %s %s%s", !final ? "is" : "was", adj,
1093                 (cap < OVERLOADED) ? " slowed" : "");
1094         you_are(buf, "");
1095     } else {
1096         /* last resort entry, guarantees Status section is non-empty
1097            (no longer needed for that purpose since weapon status added;
1098            still useful though) */
1099         Strcpy(buf, "unencumbered");
1100         if (wizard)
1101             Sprintf(eos(buf), " <%d>", inv_weight());
1102         you_are(buf, "");
1103     }
1104     /* current weapon(s) and corresponding skill level(s) */
1105     weapon_insight(final);
1106     /* unlike ring of increase accuracy's effect, the monk's suit penalty
1107        is too blatant to be restricted to magical enlightenment */
1108     if (iflags.tux_penalty && !Upolyd) {
1109         (void) enlght_combatinc("to hit", -CUMBERSOME_ARMOR_PENALTY, final,
1110                                 buf);
1111         /* if from_what() ever gets extended from wizard mode to normal
1112            play, it could be adapted to handle this */
1113         Sprintf(eos(buf), " due to your %s", suit_simple_name(uarm));
1114         you_have(buf, "");
1115     }
1116     /* report 'nudity' */
1117     if (!uarm && !uarmu && !uarmc && !uarms && !uarmg && !uarmf && !uarmh) {
1118         if (u.uroleplay.nudist)
1119             enl_msg(You_, "do", "did", " not wear any armor", "");
1120         else
1121             you_are("not wearing any armor", "");
1122     }
1123 
1124     if (!final) {
1125         if (u.ulastprayed < 0)
1126             you_have_never("prayed");
1127         else {
1128             Sprintf(buf, " on turn %ld", u.ulastprayed);
1129             enlght_line(You_, "prayed", buf, "");
1130         }
1131     }
1132 }
1133 
1134 /* extracted from status_enlightenment() to reduce clutter there */
1135 static void
weapon_insight(int final)1136 weapon_insight(int final)
1137 {
1138     char buf[BUFSZ];
1139     int wtype;
1140 
1141     /* report being weaponless; distinguish whether gloves are worn
1142        [perhaps mention silver ring(s) when not wearning gloves?] */
1143     if (!uwep) {
1144         you_are(uarmg ? "empty handed" /* gloves imply hands */
1145                       : humanoid(g.youmonst.data)
1146                          /* hands but no weapon and no gloves */
1147                          ? "bare handed"
1148                          /* alternate phrasing for paws or lack of hands */
1149                          : "not wielding anything",
1150                 "");
1151 
1152     /* two-weaponing implies hands and
1153        a weapon or wep-tool (not other odd stuff) in each hand */
1154     } else if (u.twoweap) {
1155         you_are("wielding two weapons at once", "");
1156 
1157     /* report most weapons by their skill class (so a katana will be
1158        described as a long sword, for instance; mattock, hook, and aklys
1159        are exceptions), or wielded non-weapon item by its object class */
1160     } else {
1161         const char *what = weapon_descr(uwep);
1162 
1163         /* [what about other silver items?] */
1164         if (uwep->otyp == SHIELD_OF_REFLECTION)
1165             what = shield_simple_name(uwep); /* silver|smooth shield */
1166         else if (is_wet_towel(uwep))
1167             what = /* (uwep->spe < 3) ? "moist towel" : */ "wet towel";
1168 
1169         if (!strcmpi(what, "armor") || !strcmpi(what, "food")
1170             || !strcmpi(what, "venom"))
1171             Sprintf(buf, "wielding some %s", what);
1172         else
1173             /* [maybe include known blessed?] */
1174             Sprintf(buf, "wielding %s",
1175                     (uwep->quan == 1L) ? an(what) : makeplural(what));
1176         you_are(buf, "");
1177     }
1178 
1179     /*
1180      * Skill with current weapon.  Might help players who've never
1181      * noticed #enhance or decided that it was pointless.
1182      */
1183     if ((wtype = weapon_type(uwep)) != P_NONE && (!uwep || !is_ammo(uwep))) {
1184         char sklvlbuf[20];
1185         int sklvl = P_SKILL(wtype);
1186         boolean hav = (sklvl != P_UNSKILLED && sklvl != P_SKILLED);
1187 
1188         if (sklvl == P_ISRESTRICTED)
1189             Strcpy(sklvlbuf, "no");
1190         else
1191             (void) lcase(skill_level_name(sklvl, sklvlbuf));
1192         /* "you have no/basic/expert/master/grand-master skill with <skill>"
1193            or "you are unskilled/skilled in <skill>" */
1194         Sprintf(buf, "%s %s %s", sklvlbuf,
1195                 hav ? "skill with" : "in", skill_name(wtype));
1196         if (!u.twoweap) {
1197             if (can_advance(wtype, FALSE))
1198                 Sprintf(eos(buf), " and %s that",
1199                         !final ? "can enhance" : "could have enhanced");
1200             if (hav)
1201                 you_have(buf, "");
1202             else
1203                 you_are(buf, "");
1204 
1205         } else { /* two-weapon */
1206             static const char also_[] = "also ";
1207             char pfx[QBUFSZ], sfx[QBUFSZ],
1208                 sknambuf2[20], sklvlbuf2[20], twobuf[20];
1209             const char *also = "", *also2 = "", *also3 = (char *) 0,
1210                        *verb_present, *verb_past;
1211             int wtype2 = weapon_type(uswapwep),
1212                 sklvl2 = P_SKILL(wtype2),
1213                 twoskl = P_SKILL(P_TWO_WEAPON_COMBAT);
1214             boolean a1, a2, ab,
1215                     hav2 = (sklvl2 != P_UNSKILLED && sklvl2 != P_SKILLED);
1216 
1217             /* normally hero must have access to two-weapon skill in
1218                order to initiate u.twoweap, but not if polymorphed into
1219                a form which has multiple weapon attacks, so we need to
1220                avoid getting bitten by unexpected skill value */
1221             if (twoskl == P_ISRESTRICTED) {
1222                 twoskl = P_UNSKILLED;
1223                 /* restricted is the same as unskilled as far as bonus
1224                    or penalty goes, and it isn't ordinarily seen so
1225                    skill_level_name() returns "Unknown" for it */
1226                 Strcpy(twobuf, "restricted");
1227             } else {
1228                 (void) lcase(skill_level_name(twoskl, twobuf));
1229             }
1230 
1231             /* keep buf[] from above in case skill levels match */
1232             pfx[0] = sfx[0] = '\0';
1233             if (twoskl < sklvl) {
1234                 /* twoskil won't be restricted so sklvl is at least basic */
1235                 Sprintf(pfx, "Your skill in %s ", skill_name(wtype));
1236                 Sprintf(sfx, " limited by being %s with two weapons", twobuf);
1237                 also = also_;
1238             } else if (twoskl > sklvl) {
1239                 /* sklvl might be restricted */
1240                 Strcpy(pfx, "Your two weapon skill ");
1241                 Strcpy(sfx, " limited by ");
1242                 if (sklvl > P_ISRESTRICTED)
1243                     Sprintf(eos(sfx), "being %s", sklvlbuf);
1244                 else
1245                     Sprintf(eos(sfx), "having no skill");
1246                 Sprintf(eos(sfx), " with %s", skill_name(wtype));
1247                 also2 = also_;
1248             } else {
1249                 Strcat(buf, " and two weapons");
1250                 also3 = also_;
1251             }
1252             if (*pfx)
1253                 enl_msg(pfx, "is", "was", sfx, "");
1254             else if (hav)
1255                 you_have(buf, "");
1256             else
1257                 you_are(buf, "");
1258 
1259             /* skip comparison between secondary and two-weapons if it is
1260                identical to the comparison between primary and twoweap */
1261             if (wtype2 != wtype) {
1262                 Strcpy(sknambuf2, skill_name(wtype2));
1263                 (void) lcase(skill_level_name(sklvl2, sklvlbuf2));
1264                 verb_present = "is", verb_past = "was";
1265                 pfx[0] = sfx[0] = buf[0] = '\0';
1266                 if (twoskl < sklvl2) {
1267                     /* twoskil is at least unskilled, sklvl2 at least basic */
1268                     Sprintf(pfx, "Your skill in %s ", sknambuf2);
1269                     Sprintf(sfx, " %slimited by being %s with two weapons",
1270                             also, twobuf);
1271                 } else if (twoskl > sklvl2) {
1272                     /* sklvl2 might be restricted */
1273                     Strcpy(pfx, "Your two weapon skill ");
1274                     Sprintf(sfx, " %slimited by ", also2);
1275                     if (sklvl2 > P_ISRESTRICTED)
1276                         Sprintf(eos(sfx), "being %s with", sklvlbuf2);
1277                     else
1278                         Strcat(eos(sfx), "having no skill");
1279                     Sprintf(eos(sfx), " with %s", sknambuf2);
1280                 } else {
1281                     /* equal; two-weapon is at least unskilled, so sklvl2 is
1282                        too; "you [also] have basic/expert/master/grand-master
1283                        skill with <skill>" or "you [also] are unskilled/
1284                        skilled in <skill> */
1285                     Sprintf(buf, "%s %s %s", sklvlbuf2,
1286                             hav2 ? "skill with" : "in", sknambuf2);
1287                     Strcat(buf, " and two weapons");
1288                     if (also3) {
1289                         Strcpy(pfx, "You also ");
1290                         Snprintf(sfx, sizeof(sfx), " %s", buf), buf[0] = '\0';
1291                         verb_present = hav2 ? "have" : "are";
1292                         verb_past = hav2 ? "had" : "were";
1293                     }
1294                 }
1295                 if (*pfx)
1296                     enl_msg(pfx, verb_present, verb_past, sfx, "");
1297                 else if (hav2)
1298                     you_have(buf, "");
1299                 else
1300                     you_are(buf, "");
1301             } /* wtype2 != wtype */
1302 
1303             /* if training and available skill credits already allow
1304                #enhance for any of primary, secondary, or two-weapon,
1305                tell the player; avoid attempting figure out whether
1306                spending skill credits enhancing one might make either
1307                or both of the others become ineligible for enhancement */
1308             a1 = can_advance(wtype, FALSE);
1309             a2 = (wtype2 != wtype) ? can_advance(wtype2, FALSE) : FALSE;
1310             ab = can_advance(P_TWO_WEAPON_COMBAT, FALSE);
1311             if (a1 || a2 || ab) {
1312                 static const char also_wik_[] = " and also with ";
1313 
1314                 /* for just one, the conditionals yield
1315                    1) "skill with <that one>"; for more than one:
1316                    2) "skills with <primary> and also with <secondary>" or
1317                    3) "skills with <primary> and also with two-weapons" or
1318                    4) "skills with <secondary> and also with two-weapons" or
1319                    5) "skills with <primary>, <secondary>, and two-weapons"
1320                    (no 'also's or extra 'with's for case 5); when primary
1321                    and secondary use the same skill, only cases 1 and 3 are
1322                    possible because 'a2' gets forced to False above */
1323                 Sprintf(sfx, " skill%s with %s%s%s%s%s",
1324                         ((int) a1 + (int) a2 + (int) ab > 1) ? "s" : "",
1325                         a1 ? skill_name(wtype) : "",
1326                         ((a1 && a2 && ab) ? ", "
1327                          : (a1 && (a2 || ab)) ? also_wik_ : ""),
1328                         a2 ? skill_name(wtype2) : "",
1329                         ((a1 && a2 && ab) ? ", and "
1330                          : (a2 && ab) ? also_wik_ : ""),
1331                         ab ? "two weapons" : "");
1332                 enl_msg(You_, "can enhance", "could have enhanced", sfx, "");
1333             }
1334         } /* two-weapon */
1335     } /* skill applies */
1336 }
1337 
1338 /* attributes: intrinsics and the like, other non-obvious capabilities */
1339 static void
attributes_enlightenment(int unused_mode UNUSED,int final)1340 attributes_enlightenment(int unused_mode UNUSED, int final)
1341 {
1342     static NEARDATA const char if_surroundings_permitted[] =
1343         " if surroundings permitted";
1344     int ltmp, armpro;
1345     char buf[BUFSZ];
1346 
1347     /*\
1348      *  Attributes
1349     \*/
1350     enlght_out("");
1351     enlght_out_attr(ATR_SUBHEAD, final ? "Final Attributes:" : "Current Attributes:");
1352 
1353     if (u.uevent.uhand_of_elbereth) {
1354         static const char *const hofe_titles[3] = { "the Hand of Elbereth",
1355                                                     "the Envoy of Balance",
1356                                                     "the Glory of Arioch" };
1357         you_are(hofe_titles[u.uevent.uhand_of_elbereth - 1], "");
1358     }
1359 
1360     Sprintf(buf, "%s", piousness(TRUE, "aligned"));
1361     if (u.ualign.record >= 0)
1362         you_are(buf, "");
1363     else
1364         you_have(buf, "");
1365 
1366     if (wizard) {
1367         Sprintf(buf, " %d", u.ualign.record);
1368         enl_msg("Your alignment ", "is", "was", buf, "");
1369     }
1370 
1371     /*** Resistances to troubles ***/
1372     if (Invulnerable)
1373         you_are("invulnerable", from_what(INVULNERABLE));
1374     if (Antimagic)
1375         you_are("magic-protected", from_what(ANTIMAGIC));
1376     if (Fire_resistance)
1377         you_are("fire resistant", from_what(FIRE_RES));
1378     if (Cold_resistance)
1379         you_are("cold resistant", from_what(COLD_RES));
1380     if (Sleep_resistance)
1381         you_are("sleep resistant", from_what(SLEEP_RES));
1382     if (Disint_resistance)
1383         you_are("disintegration-resistant", from_what(DISINT_RES));
1384     if (Shock_resistance)
1385         you_are("shock resistant", from_what(SHOCK_RES));
1386     if (Poison_resistance)
1387         you_are("poison resistant", from_what(POISON_RES));
1388     if (Acid_resistance)
1389         you_are("acid resistant", from_what(ACID_RES));
1390     if (Drain_resistance)
1391         you_are("level-drain resistant", from_what(DRAIN_RES));
1392     if (Sick_resistance)
1393         you_are("immune to sickness", from_what(SICK_RES));
1394     if (Stone_resistance)
1395         you_are("petrification resistant", from_what(STONE_RES));
1396     if (Halluc_resistance)
1397         enl_msg(You_, "resist", "resisted", " hallucinations",
1398                 from_what(HALLUC_RES));
1399     if (u.uedibility)
1400         you_can("recognize detrimental food", "");
1401 
1402     /*** Vision and senses ***/
1403     if (!Blind && (Blinded || !haseyes(g.youmonst.data)))
1404         you_can("see", from_what(-BLINDED)); /* Eyes of the Overworld */
1405     if (See_invisible) {
1406         if (!Blind)
1407             enl_msg(You_, "see", "saw", " invisible", from_what(SEE_INVIS));
1408         else
1409             enl_msg(You_, "will see", "would have seen",
1410                     " invisible when not blind", from_what(SEE_INVIS));
1411     }
1412     if (Blind_telepat)
1413         you_are("telepathic", from_what(TELEPAT));
1414     if (Warning)
1415         you_are("warned", from_what(WARNING));
1416     if (Warn_of_mon && g.context.warntype.obj) {
1417         Sprintf(buf, "aware of the presence of %s",
1418                 (g.context.warntype.obj & M2_ORC) ? "orcs"
1419                 : (g.context.warntype.obj & M2_ELF) ? "elves"
1420                 : (g.context.warntype.obj & M2_DEMON) ? "demons" : something);
1421         you_are(buf, from_what(WARN_OF_MON));
1422     }
1423     if (Warn_of_mon && g.context.warntype.obj_mlet) {
1424         /* Like in pager.c, this will have weird results if anything is ever
1425          * added that warns of something strange like "eye or sphere". */
1426         Sprintf(buf, "aware of the presence of %s",
1427                 makeplural(def_monsyms[g.context.warntype.obj_mlet].explain));
1428         you_are(buf, from_what(WARN_OF_MON));
1429     }
1430     if (Warn_of_mon && g.context.warntype.polyd) {
1431         Sprintf(buf, "aware of the presence of %s",
1432                 ((g.context.warntype.polyd & (M2_HUMAN | M2_ELF))
1433                  == (M2_HUMAN | M2_ELF))
1434                     ? "humans and elves"
1435                     : (g.context.warntype.polyd & M2_HUMAN)
1436                           ? "humans"
1437                           : (g.context.warntype.polyd & M2_ELF)
1438                                 ? "elves"
1439                                 : (g.context.warntype.polyd & M2_ORC)
1440                                       ? "orcs"
1441                                       : (g.context.warntype.polyd & M2_DEMON)
1442                                             ? "demons"
1443                                             : "certain monsters");
1444         you_are(buf, "");
1445     }
1446     if (Warn_of_mon && g.context.warntype.speciesidx >= LOW_PM) {
1447         Sprintf(buf, "aware of the presence of %s",
1448              makeplural(mons[g.context.warntype.speciesidx].pmnames[NEUTRAL]));
1449         you_are(buf, from_what(WARN_OF_MON));
1450     }
1451     if (Undead_warning)
1452         you_are("warned of undead", from_what(WARN_UNDEAD));
1453     if (Searching)
1454         you_have("automatic searching", from_what(SEARCHING));
1455     if (Clairvoyant)
1456         you_are("clairvoyant", from_what(CLAIRVOYANT));
1457     else if ((HClairvoyant || EClairvoyant) && BClairvoyant) {
1458         Strcpy(buf, from_what(-CLAIRVOYANT));
1459         if (!strncmp(buf, " because of ", 12))
1460             /* overwrite substring */
1461             memcpy(buf, " if not for ", 12);
1462         enl_msg(You_, "could be", "could have been", " clairvoyant", buf);
1463     }
1464     if (Infravision)
1465         you_have("infravision", from_what(INFRAVISION));
1466     if (Detect_monsters)
1467         you_are("sensing the presence of monsters", "");
1468     if (u.umconf)
1469         you_are("going to confuse monsters", "");
1470 
1471     /*** Appearance and behavior ***/
1472     if (Adornment) {
1473         int adorn = 0;
1474 
1475         if (uleft && uleft->otyp == RIN_ADORNMENT)
1476             adorn += uleft->spe;
1477         if (uright && uright->otyp == RIN_ADORNMENT)
1478             adorn += uright->spe;
1479         /* the sum might be 0 (+0 ring or two which negate each other);
1480            that yields "you are charismatic" (which isn't pointless
1481            because it potentially impacts seduction attacks) */
1482         Sprintf(buf, "%scharismatic",
1483                 (adorn > 0) ? "more " : (adorn < 0) ? "less " : "");
1484         you_are(buf, from_what(ADORNED));
1485     }
1486     if (Invisible)
1487         you_are("invisible", from_what(INVIS));
1488     else if (Invis)
1489         you_are("invisible to others", from_what(INVIS));
1490     /* ordinarily "visible" is redundant; this is a special case for
1491        the situation when invisibility would be an expected attribute */
1492     else if ((HInvis || EInvis) && BInvis)
1493         you_are("visible", from_what(-INVIS));
1494     if (Displaced)
1495         you_are("displaced", from_what(DISPLACED));
1496     if (Stealth)
1497         you_are("stealthy", from_what(STEALTH));
1498     if (Aggravate_monster)
1499         enl_msg("You aggravate", "", "d", " monsters",
1500                 from_what(AGGRAVATE_MONSTER));
1501     if (Conflict)
1502         enl_msg("You cause", "", "d", " conflict", from_what(CONFLICT));
1503 
1504     /*** Transportation ***/
1505     if (Jumping)
1506         you_can("jump", from_what(JUMPING));
1507     if (Teleportation)
1508         you_can("teleport", from_what(TELEPORT));
1509     if (Teleport_control)
1510         you_have("teleport control", from_what(TELEPORT_CONTROL));
1511     /* actively levitating handled earlier as a status condition */
1512     if (BLevitation) { /* levitation is blocked */
1513         long save_BLev = BLevitation;
1514 
1515         BLevitation = 0L;
1516         if (Levitation) {
1517             /* either trapped in the floor or inside solid rock
1518                (or both if chained to buried iron ball and have
1519                moved one step into solid rock somehow) */
1520             boolean trapped = (save_BLev & I_SPECIAL) != 0L,
1521                     terrain = (save_BLev & FROMOUTSIDE) != 0L;
1522 
1523             Sprintf(buf, "%s%s%s",
1524                     trapped ? " if not trapped" : "",
1525                     (trapped && terrain) ? " and" : "",
1526                     terrain ? if_surroundings_permitted : "");
1527             enl_msg(You_, "would levitate", "would have levitated", buf, "");
1528         }
1529         BLevitation = save_BLev;
1530     }
1531     /* actively flying handled earlier as a status condition */
1532     if (BFlying) { /* flight is blocked */
1533         long save_BFly = BFlying;
1534 
1535         BFlying = 0L;
1536         if (Flying) {
1537             enl_msg(You_, "would fly", "would have flown",
1538                     /* wording quibble: for past tense, "hadn't been"
1539                        would sound better than "weren't" (and
1540                        "had permitted" better than "permitted"), but
1541                        "weren't" and "permitted" are adequate so the
1542                        extra complexity to handle that isn't worth it */
1543                     Levitation
1544                        ? " if you weren't levitating"
1545                        : (save_BFly == I_SPECIAL)
1546                           /* this is an oversimpliction; being trapped
1547                              might also be blocking levitation so flight
1548                              would still be blocked after escaping trap */
1549                           ? " if you weren't trapped"
1550                           : (save_BFly == FROMOUTSIDE)
1551                              ? if_surroundings_permitted
1552                              /* two or more of levitation, surroundings,
1553                                 and being trapped in the floor */
1554                              : " if circumstances permitted",
1555                     "");
1556         }
1557         BFlying = save_BFly;
1558     }
1559     /* actively walking on water handled earlier as a status condition */
1560     if (Wwalking && !walking_on_water())
1561         you_can("walk on water", from_what(WWALKING));
1562     /* actively swimming (in water but not under it) handled earlier */
1563     if (Swimming && (Underwater || !u.uinwater))
1564         you_can("swim", from_what(SWIMMING));
1565     if (Breathless)
1566         you_can("survive without air", from_what(MAGICAL_BREATHING));
1567     else if (Amphibious)
1568         you_can("breathe water", from_what(MAGICAL_BREATHING));
1569     if (Passes_walls)
1570         you_can("walk through walls", from_what(PASSES_WALLS));
1571 
1572     /*** Physical attributes ***/
1573     if (Regeneration)
1574         enl_msg("You regenerate", "", "d", "", from_what(REGENERATION));
1575     if (Slow_digestion)
1576         you_have("slower digestion", from_what(SLOW_DIGESTION));
1577     if (u.uhitinc) {
1578         (void) enlght_combatinc("to hit", u.uhitinc, final, buf);
1579         if (iflags.tux_penalty && !Upolyd)
1580             Sprintf(eos(buf), " %s your suit's penalty",
1581                     (u.uhitinc < 0) ? "increasing"
1582                     : (u.uhitinc < 4 * CUMBERSOME_ARMOR_PENALTY / 5)
1583                       ? "partly offsetting"
1584                       : (u.uhitinc < CUMBERSOME_ARMOR_PENALTY)
1585                         ? "nearly offseting"
1586                         : "overcoming");
1587         you_have(buf, "");
1588     }
1589     if (u.udaminc)
1590         you_have(enlght_combatinc("damage", u.udaminc, final, buf), "");
1591     if (u.uspellprot || Protection) {
1592         int prot = 0;
1593 
1594         if (uleft && uleft->otyp == RIN_PROTECTION)
1595             prot += uleft->spe;
1596         if (uright && uright->otyp == RIN_PROTECTION)
1597             prot += uright->spe;
1598         if (uamul && uamul->otyp == AMULET_OF_GUARDING)
1599             prot += 2;
1600         if (HProtection & INTRINSIC)
1601             prot += u.ublessed;
1602         prot += u.uspellprot;
1603         if (prot)
1604             you_have(enlght_combatinc("defense", prot, final, buf), "");
1605     }
1606     if ((armpro = magic_negation(&g.youmonst)) > 0) {
1607         /* magic cancellation factor, conferred by worn armor */
1608         static const char *const mc_types[] = {
1609             "" /*ordinary*/, "warded", "guarded", "protected",
1610         };
1611         /* sanity check */
1612         if (armpro >= SIZE(mc_types))
1613             armpro = SIZE(mc_types) - 1;
1614         you_are(mc_types[armpro], "");
1615     }
1616     if (Half_physical_damage)
1617         enlght_halfdmg(HALF_PHDAM, final);
1618     if (Half_spell_damage)
1619         enlght_halfdmg(HALF_SPDAM, final);
1620     if (Half_gas_damage)
1621         enl_msg(You_, "take", "took", " reduced poison gas damage", "");
1622     /* polymorph and other shape change */
1623     if (Protection_from_shape_changers)
1624         you_are("protected from shape changers",
1625                 from_what(PROT_FROM_SHAPE_CHANGERS));
1626     if (Unchanging) {
1627         const char *what = 0;
1628 
1629         if (!Upolyd) /* Upolyd handled below after current form */
1630             you_can("not change from your current form",
1631                     from_what(UNCHANGING));
1632         /* blocked shape changes */
1633         if (Polymorph)
1634             what = !final ? "polymorph" : "have polymorphed";
1635         else if (u.ulycn >= LOW_PM)
1636             what = !final ? "change shape" : "have changed shape";
1637         if (what) {
1638             Sprintf(buf, "would %s periodically", what);
1639             /* omit from_what(UNCHANGING); too verbose */
1640             enl_msg(You_, buf, buf, " if not locked into your current form",
1641                     "");
1642         }
1643     } else if (Polymorph) {
1644         you_are("polymorphing periodically", from_what(POLYMORPH));
1645     }
1646     if (Polymorph_control)
1647         you_have("polymorph control", from_what(POLYMORPH_CONTROL));
1648     if (Upolyd && u.umonnum != u.ulycn
1649         /* if we've died from turning into slime, we're polymorphed
1650            right now but don't want to list it as a temporary attribute
1651            [we need a more reliable way to detect this situation] */
1652         && !(final == ENL_GAMEOVERDEAD
1653              && u.umonnum == PM_GREEN_SLIME && !Unchanging)) {
1654         /* foreign shape (except were-form which is handled below) */
1655         if (!vampshifted(&g.youmonst))
1656             Sprintf(buf, "polymorphed into %s",
1657                     an(pmname(g.youmonst.data,
1658                               flags.female ? FEMALE : MALE)));
1659         else
1660             Sprintf(buf, "polymorphed into %s in %s form",
1661                     an(pmname(&mons[g.youmonst.cham],
1662                               flags.female ? FEMALE : MALE)),
1663                     pmname(g.youmonst.data, flags.female ? FEMALE : MALE));
1664         if (wizard)
1665             Sprintf(eos(buf), " (%d)", u.mtimedone);
1666         you_are(buf, "");
1667     }
1668     if (lays_eggs(g.youmonst.data) && flags.female) /* Upolyd */
1669         you_can("lay eggs", "");
1670     if (u.ulycn >= LOW_PM) {
1671         /* "you are a werecreature [in beast form]" */
1672         Strcpy(buf, an(pmname(&mons[u.ulycn],
1673                flags.female ? FEMALE : MALE)));
1674         if (u.umonnum == u.ulycn) {
1675             Strcat(buf, " in beast form");
1676             if (wizard)
1677                 Sprintf(eos(buf), " (%d)", u.mtimedone);
1678         }
1679         you_are(buf, "");
1680     }
1681     if (Unchanging && Upolyd) /* !Upolyd handled above */
1682         you_can("not change from your current form", from_what(UNCHANGING));
1683     for (ltmp = 1; ltmp < NUM_MATERIAL_TYPES; ++ltmp) {
1684         if (Hate_material(ltmp)) {
1685             Sprintf(buf, "harmed by %s", materialnm[ltmp]);
1686             you_are(buf, "");
1687         }
1688     }
1689     /* movement and non-armor-based protection */
1690     if (Fast)
1691         you_are(Very_fast ? "very fast" : "fast", from_what(FAST));
1692     if (Reflecting)
1693         /* mirror reflection is not guaranteed so don't display it here */
1694         you_have("reflection", from_what(REFLECTING));
1695     if (Free_action)
1696         you_have("free action", from_what(FREE_ACTION));
1697     if (Fixed_abil)
1698         you_have("fixed abilities", from_what(FIXED_ABIL));
1699     if (Lifesaved)
1700         enl_msg("Your life ", "will be", "would have been", " saved", "");
1701 
1702     /*** Miscellany ***/
1703     if (Luck) {
1704         ltmp = abs((int) Luck);
1705         Sprintf(buf, "%s%slucky",
1706                 ltmp >= 10 ? "extremely " : ltmp >= 5 ? "very " : "",
1707                 Luck < 0 ? "un" : "");
1708         if (wizard)
1709             Sprintf(eos(buf), " (%d)", Luck);
1710         you_are(buf, "");
1711     } else if (wizard)
1712         enl_msg("Your luck ", "is", "was", " zero", "");
1713     if (u.moreluck > 0)
1714         you_have("extra luck", "");
1715     else if (u.moreluck < 0)
1716         you_have("reduced luck", "");
1717     if (carrying(LUCKSTONE) || stone_luck(TRUE)) {
1718         ltmp = stone_luck(FALSE);
1719         if (ltmp <= 0)
1720             enl_msg("Bad luck ", "does", "did", " not time out for you", "");
1721         if (ltmp >= 0)
1722             enl_msg("Good luck ", "does", "did", " not time out for you", "");
1723     }
1724 
1725     if (u.ugangr) {
1726         Sprintf(buf, " %sangry with you",
1727                 u.ugangr > 6 ? "extremely " : u.ugangr > 3 ? "very " : "");
1728         if (wizard)
1729             Sprintf(eos(buf), " (%d)", u.ugangr);
1730         enl_msg(u_gname(), " is", " was", buf, "");
1731     } else {
1732         /*
1733          * We need to suppress this when the game is over, because death
1734          * can change the value calculated by can_pray(), potentially
1735          * resulting in a false claim that you could have prayed safely.
1736          */
1737         if (!final) {
1738 #if 0
1739             /* "can [not] safely pray" vs "could [not] have safely prayed" */
1740             Sprintf(buf, "%s%ssafely pray%s", can_pray(FALSE) ? "" : "not ",
1741                     final ? "have " : "", final ? "ed" : "");
1742 #else
1743             Sprintf(buf, "%ssafely pray", can_pray(FALSE) ? "" : "not ");
1744 #endif
1745             if (wizard)
1746                 Sprintf(eos(buf), " (%d)", u.ublesscnt);
1747             you_can(buf, "");
1748         }
1749     }
1750 
1751 #ifdef DEBUG
1752     /* named fruit debugging (doesn't really belong here...); to enable,
1753        include 'fruit' in DEBUGFILES list (even though it isn't a file...) */
1754     if (wizard && explicitdebug("fruit")) {
1755         struct fruit *f;
1756 
1757         reorder_fruit(TRUE); /* sort by fruit index, from low to high;
1758                               * this modifies the g.ffruit chain, so could
1759                               * possibly mask or even introduce a problem,
1760                               * but it does useful sanity checking */
1761         for (f = g.ffruit; f; f = f->nextf) {
1762             Sprintf(buf, "Fruit #%d ", f->fid);
1763             enl_msg(buf, "is ", "was ", f->fname, "");
1764         }
1765         enl_msg("The current fruit ", "is ", "was ", g.pl_fruit, "");
1766         Sprintf(buf, "%d", flags.made_fruit);
1767         enl_msg("The made fruit flag ", "is ", "was ", buf, "");
1768     }
1769 #endif
1770 
1771     {
1772         const char *p;
1773 
1774         buf[0] = '\0';
1775         if (final < 2) { /* still in progress, or quit/escaped/ascended */
1776             p = "survived after being killed ";
1777             switch (u.umortality) {
1778             case 0:
1779                 p = !final ? (char *) 0 : "survived";
1780                 break;
1781             case 1:
1782                 Strcpy(buf, "once");
1783                 break;
1784             case 2:
1785                 Strcpy(buf, "twice");
1786                 break;
1787             case 3:
1788                 Strcpy(buf, "thrice");
1789                 break;
1790             default:
1791                 Sprintf(buf, "%d times", u.umortality);
1792                 break;
1793             }
1794         } else { /* game ended in character's death */
1795             p = "are dead";
1796             switch (u.umortality) {
1797             case 0:
1798                 impossible("dead without dying?");
1799             case 1:
1800                 break; /* just "are dead" */
1801             default:
1802                 Sprintf(buf, " (%d%s time!)", u.umortality,
1803                         ordin(u.umortality));
1804                 break;
1805             }
1806         }
1807         if (p)
1808             enl_msg(You_, "have been killed ", p, buf, "");
1809     }
1810 }
1811 
1812 /* ^X command */
1813 int
doattributes(void)1814 doattributes(void)
1815 {
1816     int mode = BASICENLIGHTENMENT;
1817 
1818     /* show more--as if final disclosure--for wizard and explore modes */
1819     if (wizard || discover)
1820         mode |= MAGICENLIGHTENMENT;
1821 
1822     enlightenment(mode, ENL_GAMEINPROGRESS);
1823     return 0;
1824 }
1825 
1826 void
youhiding(boolean via_enlghtmt,int msgflag)1827 youhiding(boolean via_enlghtmt, /* englightment line vs topl message */
1828           int msgflag)          /* for variant message phrasing */
1829 {
1830     char *bp, buf[BUFSZ];
1831 
1832     Strcpy(buf, "hiding");
1833     if (U_AP_TYPE != M_AP_NOTHING) {
1834         /* mimic; hero is only able to mimic a strange object or gold
1835            or hallucinatory alternative to gold, so we skip the details
1836            for the hypothetical furniture and monster cases */
1837         bp = eos(strcpy(buf, "mimicking"));
1838         if (U_AP_TYPE == M_AP_OBJECT) {
1839             Sprintf(bp, " %s", an(simple_typename(g.youmonst.mappearance)));
1840         } else if (U_AP_TYPE == M_AP_FURNITURE) {
1841             Strcpy(bp, " something");
1842         } else if (U_AP_TYPE == M_AP_MONSTER) {
1843             Strcpy(bp, " someone");
1844         } else {
1845             ; /* something unexpected; leave 'buf' as-is */
1846         }
1847     } else if (u.uundetected) {
1848         bp = eos(buf); /* points past "hiding" */
1849         if (g.youmonst.data->mlet == S_EEL) {
1850             if (is_pool(u.ux, u.uy))
1851                 Sprintf(bp, " in the %s", waterbody_name(u.ux, u.uy));
1852         } else if (hides_under(g.youmonst.data)) {
1853             struct obj *o = g.level.objects[u.ux][u.uy];
1854 
1855             if (o)
1856                 Sprintf(bp, " underneath %s", ansimpleoname(o));
1857             else if (concealed_spot(u.ux, u.uy))
1858                 Sprintf(bp, " under %s", explain_terrain(u.ux, u.uy));
1859         } else if (is_clinger(g.youmonst.data) || Flying) {
1860             /* Flying: 'lurker above' hides on ceiling but doesn't cling */
1861             Sprintf(bp, " on the %s", ceiling(u.ux, u.uy));
1862         } else {
1863             /* on floor; is_hider() but otherwise not special: 'trapper' */
1864             if (u.utrap && u.utraptype == TT_PIT) {
1865                 struct trap *t = t_at(u.ux, u.uy);
1866 
1867                 Sprintf(bp, " in a %spit",
1868                         (t && t->ttyp == SPIKED_PIT) ? "spiked " : "");
1869             } else
1870                 Sprintf(bp, " on the %s", surface(u.ux, u.uy));
1871         }
1872     } else {
1873         ; /* shouldn't happen; will result in generic "you are hiding" */
1874     }
1875 
1876     if (via_enlghtmt) {
1877         int final = msgflag; /* 'final' is used by you_are() macro */
1878 
1879         you_are(buf, "");
1880     } else {
1881         /* for dohide(), when player uses '#monster' command */
1882         You("are %s %s.", msgflag ? "already" : "now", buf);
1883     }
1884 }
1885 
1886 /* #conduct command [KMH]; shares enlightenment's tense handling */
1887 int
doconduct(void)1888 doconduct(void)
1889 {
1890     show_conduct(0);
1891     return 0;
1892 }
1893 
1894 /* display conducts; for doconduct(), also disclose() and dump_everything() */
1895 void
show_conduct(int final)1896 show_conduct(int final)
1897 {
1898     char buf[BUFSZ];
1899     int ngenocided;
1900 
1901     /* Create the conduct window */
1902     g.en_win = create_nhwindow(NHW_MENU);
1903     putstr(g.en_win, ATR_HEADING, "Voluntary challenges:");
1904 
1905     if (u.uroleplay.blind)
1906         you_have_been("blind from birth");
1907     if (u.uroleplay.deaf)
1908         you_have_been("deaf from birth");
1909     if (u.uroleplay.hallu)
1910         you_have_been("hallucinating for your entire life");
1911     if (u.uroleplay.nudist)
1912         you_have_been("faithfully nudist");
1913 
1914     if (!u.uconduct.food)
1915         enl_msg(You_, "have gone", "went", " without food", "");
1916         /* but beverages are okay */
1917     else if (!u.uconduct.unvegan)
1918         you_have_X("followed a strict vegan diet");
1919     else if (!u.uconduct.unvegetarian)
1920         you_have_been("vegetarian");
1921 
1922     if (!u.uconduct.gnostic)
1923         you_have_been("an atheist");
1924 
1925     if (!u.uconduct.weaphit) {
1926         you_have_never("hit with a wielded weapon");
1927     } else if (wizard) {
1928         Sprintf(buf, "used a wielded weapon %ld time%s", u.uconduct.weaphit,
1929                 plur(u.uconduct.weaphit));
1930         you_have_X(buf);
1931     }
1932     if (!u.uconduct.killer)
1933         you_have_been("a pacifist");
1934 
1935     if (!u.uconduct.literate) {
1936         you_have_been("illiterate");
1937     } else {
1938         Sprintf(buf, "read items or engraved %ld time%s", u.uconduct.literate,
1939                 plur(u.uconduct.literate));
1940         you_have_X(buf);
1941     }
1942 
1943     ngenocided = num_genocides();
1944     if (ngenocided == 0) {
1945         you_have_never("genocided any monsters");
1946     } else {
1947         Sprintf(buf, "genocided %d type%s of monster%s", ngenocided,
1948                 plur(ngenocided), plur(ngenocided));
1949         you_have_X(buf);
1950     }
1951 
1952     if (!u.uconduct.polypiles) {
1953         you_have_never("polymorphed an object");
1954     } else {
1955         Sprintf(buf, "polymorphed %ld item%s", u.uconduct.polypiles,
1956                 plur(u.uconduct.polypiles));
1957         you_have_X(buf);
1958     }
1959 
1960     if (!u.uconduct.polyselfs) {
1961         you_have_never("changed form");
1962     } else if (wizard) {
1963         Sprintf(buf, "changed form %ld time%s", u.uconduct.polyselfs,
1964                 plur(u.uconduct.polyselfs));
1965         you_have_X(buf);
1966     }
1967 
1968     if (!u.uconduct.artitouch) {
1969         you_have_never("touched an artifact");
1970     }
1971 
1972     if (!u.uconduct.wishes) {
1973         you_have_X("used no wishes");
1974     } else {
1975         Sprintf(buf, "used %ld wish%s", u.uconduct.wishes,
1976                 (u.uconduct.wishes > 1L) ? "es" : "");
1977         if (u.uconduct.wisharti) {
1978             /* if wisharti == wishes
1979              *  1 wish (for an artifact)
1980              *  2 wishes (both for artifacts)
1981              *  N wishes (all for artifacts)
1982              * else (N is at least 2 in order to get here; M < N)
1983              *  N wishes (1 for an artifact)
1984              *  N wishes (M for artifacts)
1985              */
1986             if (u.uconduct.wisharti == u.uconduct.wishes)
1987                 Sprintf(eos(buf), " (%s",
1988                         (u.uconduct.wisharti > 2L) ? "all "
1989                           : (u.uconduct.wisharti == 2L) ? "both " : "");
1990             else
1991                 Sprintf(eos(buf), " (%ld ", u.uconduct.wisharti);
1992 
1993             Sprintf(eos(buf), "for %s)",
1994                     (u.uconduct.wisharti == 1L) ? "an artifact"
1995                                                 : "artifacts");
1996         }
1997         you_have_X(buf);
1998 
1999         if (!u.uconduct.wisharti)
2000             enl_msg(You_, "have not wished", "did not wish",
2001                     " for any artifacts", "");
2002     }
2003 
2004     if (!u.uconduct.pets) {
2005        you_have_never("owned a pet");
2006     }
2007 
2008     if (!u.uconduct.scares) {
2009         you_have_never("scared an enemy with impunity");
2010     }
2011     else if (wizard) {
2012         Sprintf(buf, "scared enemies with impunity %ld time%s", u.uconduct.scares,
2013                 plur(u.uconduct.scares));
2014         you_have_X(buf);
2015     }
2016 
2017     if (!u.uconduct.uncelibate) {
2018         you_have_been("celibate");
2019     }
2020 
2021     if (!u.uconduct.conflicting) {
2022         you_have_never("generated conflict");
2023     }
2024 
2025     /* only report Sokoban conduct if the Sokoban branch has been entered */
2026     if (sokoban_in_play()) {
2027         const char *presentverb = "have violated", *pastverb = "violated";
2028 
2029         Strcpy(buf, " the special Sokoban rules ");
2030         switch (u.uconduct.sokocheat) {
2031         case 0L:
2032             presentverb = "have not violated";
2033             pastverb = "did not violate";
2034             Strcpy(buf, " any of the special Sokoban rules");
2035             break;
2036         case 1L:
2037             Strcat(buf, "once");
2038             break;
2039         case 2L:
2040             Strcat(buf, "twice");
2041             break;
2042         case 3L:
2043             Strcat(buf, "thrice");
2044             break;
2045         default:
2046             Sprintf(eos(buf), "%ld times", u.uconduct.sokocheat);
2047             break;
2048         }
2049         enl_msg(You_, presentverb, pastverb, buf, "");
2050     }
2051 
2052     show_achievements(final);
2053 
2054     /* Pop up the window and wait for a key */
2055     display_nhwindow(g.en_win, TRUE);
2056     destroy_nhwindow(g.en_win);
2057     g.en_win = WIN_ERR;
2058 }
2059 
2060 /*
2061  *      Achievements (see 'enum achievements' in you.h).
2062  */
2063 
2064 static void
show_achievements(int final)2065 show_achievements(int final) /* used "behind the curtain" by enl_foo() macros */
2066 {
2067     int i, achidx, absidx, acnt;
2068     char title[QBUFSZ], buf[QBUFSZ];
2069     winid awin = WIN_ERR;
2070 
2071     /* unfortunately we can't show the achievements (at least not all of
2072        them) while the game is in progress because it would give away the
2073        ID of luckstone (at Mine's End) and of real Amulet of Yendor */
2074     if (!final && !wizard)
2075         return;
2076 
2077     /* first, figure whether any achievements have been accomplished
2078        so that we don't show the header for them if the resulting list
2079        below it would be empty */
2080     if ((acnt = count_achievements()) == 0)
2081         return;
2082 
2083     if (g.en_win != WIN_ERR) {
2084         awin = g.en_win; /* end of game disclosure window */
2085         putstr(awin, 0, "");
2086     } else {
2087         awin = create_nhwindow(NHW_MENU);
2088     }
2089     Sprintf(title, "Achievement%s:", plur(acnt));
2090     putstr(awin, ATR_HEADING, title);
2091 
2092     /* display achievements in the order in which they were recorded;
2093        lone exception is to defer the Amulet if we just ascended;
2094        it warrants alternate wording when given away during ascension,
2095        but the Amulet achievement is always attained before entering
2096        endgame and the alternate wording looks strange if shown before
2097        "reached endgame" and "reached Astral" */
2098     if (remove_achievement(ACH_UWIN)) { /* UWIN == Ascended! */
2099         /* for ascension, force it to be last and Amulet next to last
2100            by taking them out and then adding them back */
2101         if (remove_achievement(ACH_AMUL)) /* should always be True here */
2102             record_achievement(ACH_AMUL);
2103         record_achievement(ACH_UWIN);
2104     }
2105     for (i = 0; i < acnt; ++i) {
2106         achidx = u.uachieved[i];
2107         absidx = abs(achidx);
2108 
2109         switch (absidx) {
2110         case ACH_BLND:
2111             enl_msg(You_, "are exploring", "explored",
2112                     " without being able to see", "");
2113             break;
2114         case ACH_NUDE:
2115             enl_msg(You_, "have gone", "went", " without any armor", "");
2116             break;
2117         case ACH_MINE:
2118             you_have_X("entered the Gnomish Mines");
2119             break;
2120         case ACH_TOWN:
2121             you_have_X("entered Minetown");
2122             break;
2123         case ACH_SHOP:
2124             you_have_X("entered a shop");
2125             break;
2126         case ACH_TMPL:
2127             you_have_X("entered a temple");
2128             break;
2129         case ACH_ORCL:
2130             you_have_X("consulted the Oracle of Delphi");
2131             break;
2132         case ACH_NOVL:
2133             you_have_X("read from a Discworld novel");
2134             break;
2135         case ACH_SOKO:
2136             you_have_X("entered Sokoban");
2137             break;
2138         case ACH_SOKO_PRIZE: /* hard to reach guaranteed bag or amulet */
2139             you_have_X("completed Sokoban");
2140             break;
2141         case ACH_MINE_PRIZE: /* hidden guaranteed luckstone */
2142             you_have_X("completed the Gnomish Mines");
2143             break;
2144         case ACH_BGRM:
2145             you_have_X("entered the Big Room");
2146             break;
2147         case ACH_MEDU:
2148             you_have_X("defeated Medusa");
2149             break;
2150         case ACH_BELL:
2151             /* alternate phrasing for present vs past and also for
2152                possessing the item vs once held it */
2153             enl_msg(You_,
2154                     u.uhave.bell ? "have" : "have handled",
2155                     u.uhave.bell ? "had" : "handled",
2156                     " the Bell of Opening", "");
2157             break;
2158         case ACH_HELL:
2159             enl_msg(You_, "have ", "", "entered Gehennom", "");
2160             break;
2161         case ACH_CNDL:
2162             enl_msg(You_,
2163                     u.uhave.menorah ? "have" : "have handled",
2164                     u.uhave.menorah ? "had" : "handled",
2165                     " the Candelabrum of Invocation", "");
2166             break;
2167         case ACH_BOOK:
2168             enl_msg(You_,
2169                     u.uhave.book ? "have" : "have handled",
2170                     u.uhave.book ? "had" : "handled",
2171                     " the Book of the Dead", "");
2172             break;
2173         case ACH_INVK:
2174             you_have_X("gained access to Moloch's Sanctum");
2175             break;
2176         case ACH_AMUL:
2177             /* alternate wording for ascended (always past tense) since
2178                hero had it until #offer forced it to be relinquished */
2179             enl_msg(You_,
2180                     u.uhave.amulet ? "have" : "have obtained",
2181                     u.uevent.ascended ? "delivered"
2182                      : u.uhave.amulet ? "had" : "had obtained",
2183                     " the Amulet of Yendor", "");
2184             break;
2185 
2186         /* reaching Astral makes feedback about reaching the Planes
2187            be redundant and ascending makes both be redundant, but
2188            we display all that apply */
2189         case ACH_ENDG:
2190             you_have_X("reached the Elemental Planes");
2191             break;
2192         case ACH_ASTR:
2193             you_have_X("reached the Astral Plane");
2194             break;
2195         case ACH_UWIN:
2196             /* the ultimate achievement... */
2197             enlght_out(" You ascended!");
2198             break;
2199 
2200         /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */
2201         case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4:
2202         case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8:
2203             Sprintf(buf, "attained the rank of %s",
2204                     rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
2205                             Role_switch, (achidx < 0) ? TRUE : FALSE));
2206             you_have_X(buf);
2207             break;
2208 
2209         default:
2210             Sprintf(buf, " [Unexpected achievement #%d.]", achidx);
2211             enlght_out(buf);
2212             break;
2213         } /* switch */
2214     } /* for */
2215 
2216     if (awin != g.en_win) {
2217         display_nhwindow(awin, TRUE);
2218         destroy_nhwindow(awin);
2219     }
2220 }
2221 
2222 /* record an achievement (add at end of list unless already present) */
2223 void
record_achievement(schar achidx)2224 record_achievement(schar achidx)
2225 {
2226     int i, absidx;
2227 
2228     absidx = abs(achidx);
2229     /* valid achievements range from 1 to N_ACH-1; however, ranks can be
2230        stored as the complement (ie, negative) to track gender */
2231     if ((achidx < 1 && (absidx < ACH_RNK1 || absidx > ACH_RNK8))
2232         || achidx >= N_ACH) {
2233         impossible("Achievement #%d is out of range.", achidx);
2234         return;
2235     }
2236 
2237     /* the list has an extra slot so there is always at least one 0 at
2238        its end (more than one unless all N_ACH-1 possible achievements
2239        have been recorded); find first empty slot or achievement #achidx;
2240        an attempt to duplicate an achievement can happen if any of Bell,
2241        Candelabrum, Book, or Amulet is dropped then picked up again */
2242     for (i = 0; u.uachieved[i]; ++i)
2243         if (abs(u.uachieved[i]) == abs(achidx))
2244             return; /* already recorded, don't duplicate it */
2245     u.uachieved[i] = achidx;
2246     if (g.program_state.gameover)
2247         return; /* don't livelog achievements recorded at end of game */
2248     if (absidx >= ACH_RNK1 && absidx <= ACH_RNK8) {
2249         livelog_printf(achieve_msg[absidx].llflag, "attained the rank of %s",
2250                        rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
2251                                Role_switch, (achidx < 0) ? TRUE : FALSE));
2252     }
2253     else
2254         livelog_write_string(achieve_msg[absidx].llflag, achieve_msg[absidx].msg);
2255     return;
2256 }
2257 
2258 /* discard a recorded achievement; return True if removed, False otherwise */
2259 boolean
remove_achievement(schar achidx)2260 remove_achievement(schar achidx)
2261 {
2262     int i;
2263 
2264     for (i = 0; u.uachieved[i]; ++i)
2265         if (abs(u.uachieved[i]) == abs(achidx))
2266             break; /* stop when found */
2267     if (!u.uachieved[i]) /* not found */
2268         return FALSE;
2269     /* list is 0 terminated so any beyond the removed one move up a slot */
2270     do {
2271         u.uachieved[i] = u.uachieved[i + 1];
2272     } while (u.uachieved[++i]);
2273     return TRUE;
2274 }
2275 
2276 /* used to decide whether there are any achievements to display */
2277 int
count_achievements(void)2278 count_achievements(void)
2279 {
2280     int i, acnt = 0;
2281 
2282     for (i = 0; u.uachieved[i]; ++i)
2283         ++acnt;
2284     return acnt;
2285 }
2286 
2287 /* convert a rank index to an achievement number; encode it when female
2288    in order to subsequently report gender-specific ranks accurately */
2289 schar
achieve_rank(int rank)2290 achieve_rank(int rank) /* 1..8 */
2291 {
2292     schar achidx = (schar) ((rank - 1) + ACH_RNK1);
2293 
2294     if (flags.female)
2295         achidx = -achidx;
2296     return achidx;
2297 }
2298 
2299 /* return True if sokoban branch has been entered, False otherwise */
2300 boolean
sokoban_in_play(void)2301 sokoban_in_play(void)
2302 {
2303     int achidx;
2304 
2305     /* TODO? move this to dungeon.c and test furthest level reached of the
2306        sokoban branch instead of relying on the entered-sokoban achievement */
2307 
2308     for (achidx = 0; u.uachieved[achidx]; ++achidx)
2309         if (u.uachieved[achidx] == ACH_SOKO)
2310             return TRUE;
2311     return FALSE;
2312 }
2313 
2314 /*
2315  *      Vanquished monsters.
2316  */
2317 
2318 static const char *vanqorders[NUM_VANQ_ORDER_MODES] = {
2319     "traditional: by monster level, by internal monster index",
2320     "by monster toughness, by internal monster index",
2321     "alphabetically, first unique monsters, then others",
2322     "alphabetically, unique monsters and others intermixed",
2323     "by monster class, high to low level within class",
2324     "by monster class, low to high level within class",
2325     "by count, high to low, by internal index within tied count",
2326     "by count, low to high, by internal index within tied count",
2327 };
2328 
2329 static int QSORTCALLBACK
vanqsort_cmp(const genericptr vptr1,const genericptr vptr2)2330 vanqsort_cmp(const genericptr vptr1, const genericptr vptr2)
2331 {
2332     int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2,
2333         mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res;
2334     const char *name1, *name2, *punct;
2335     schar mcls1, mcls2;
2336 
2337     switch (g.vanq_sortmode) {
2338     default:
2339     case VANQ_MLVL_MNDX:
2340         /* sort by monster level */
2341         mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
2342         res = mlev2 - mlev1; /* mlevel high to low */
2343         break;
2344     case VANQ_MSTR_MNDX:
2345         /* sort by monster toughness */
2346         mstr1 = mons[indx1].difficulty, mstr2 = mons[indx2].difficulty;
2347         res = mstr2 - mstr1; /* monstr high to low */
2348         break;
2349     case VANQ_ALPHA_SEP:
2350         uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_CLERIC);
2351         uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_CLERIC);
2352         if (uniq1 ^ uniq2) { /* one or other uniq, but not both */
2353             res = uniq2 - uniq1;
2354             break;
2355         } /* else both unique or neither unique */
2356         /*FALLTHRU*/
2357     case VANQ_ALPHA_MIX:
2358         name1 = mons[indx1].pmnames[NEUTRAL],
2359                 name2 = mons[indx2].pmnames[NEUTRAL];
2360         res = strcmpi(name1, name2); /* caseblind alhpa, low to high */
2361         break;
2362     case VANQ_MCLS_HTOL:
2363     case VANQ_MCLS_LTOH:
2364         /* mons[].mlet is a small integer, 1..N, of type plain char;
2365            if 'char' happens to be unsigned, (mlet1 - mlet2) would yield
2366            an inappropriate result when mlet2 is greater than mlet1,
2367            so force our copies (mcls1, mcls2) to be signed */
2368         mcls1 = (schar) mons[indx1].mlet, mcls2 = (schar) mons[indx2].mlet;
2369         /* S_ANT through S_ZRUTY correspond to lowercase monster classes,
2370            S_ANGEL through S_ZOMBIE correspond to uppercase, and various
2371            punctuation characters are used for classes beyond those */
2372         if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) {
2373             /* force a specific order to the punctuation classes that's
2374                different from the internal order;
2375                internal order is ok if neither or just one is punctuation
2376                since letters have lower values so come out before punct */
2377             static const char punctclasses[] = {
2378                 S_GOLEM, S_DEMON, S_HUMAN, '\0'
2379             };
2380 
2381             if ((punct = index(punctclasses, mcls1)) != 0)
2382                 mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
2383             if ((punct = index(punctclasses, mcls2)) != 0)
2384                 mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
2385         }
2386         res = mcls1 - mcls2; /* class */
2387         if (res == 0) {
2388             mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
2389             res = mlev1 - mlev2; /* mlevel low to high */
2390             if (g.vanq_sortmode == VANQ_MCLS_HTOL)
2391                 res = -res; /* mlevel high to low */
2392         }
2393         break;
2394     case VANQ_COUNT_H_L:
2395     case VANQ_COUNT_L_H:
2396         died1 = g.mvitals[indx1].died, died2 = g.mvitals[indx2].died;
2397         res = died2 - died1; /* dead count high to low */
2398         if (g.vanq_sortmode == VANQ_COUNT_L_H)
2399             res = -res; /* dead count low to high */
2400         break;
2401     }
2402     /* tiebreaker: internal mons[] index */
2403     if (res == 0)
2404         res = indx1 - indx2; /* mndx low to high */
2405     return res;
2406 }
2407 
2408 /* returns -1 if cancelled via ESC */
2409 static int
set_vanq_order(void)2410 set_vanq_order(void)
2411 {
2412     winid tmpwin;
2413     menu_item *selected;
2414     anything any;
2415     int i, n, choice;
2416 
2417     tmpwin = create_nhwindow(NHW_MENU);
2418     start_menu(tmpwin, MENU_BEHAVE_STANDARD);
2419     any = cg.zeroany; /* zero out all bits */
2420     for (i = 0; i < SIZE(vanqorders); i++) {
2421         if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */
2422             continue;
2423         any.a_int = i + 1;
2424         add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
2425                  vanqorders[i],
2426                  (i == g.vanq_sortmode)
2427                     ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
2428     }
2429     end_menu(tmpwin, "Sort order for vanquished monster counts");
2430 
2431     n = select_menu(tmpwin, PICK_ONE, &selected);
2432     destroy_nhwindow(tmpwin);
2433     if (n > 0) {
2434         choice = selected[0].item.a_int - 1;
2435         /* skip preselected entry if we have more than one item chosen */
2436         if (n > 1 && choice == g.vanq_sortmode)
2437             choice = selected[1].item.a_int - 1;
2438         free((genericptr_t) selected);
2439         g.vanq_sortmode = choice;
2440     }
2441     return (n < 0) ? -1 : g.vanq_sortmode;
2442 }
2443 
2444 /* #vanquished command */
2445 int
dovanquished(void)2446 dovanquished(void)
2447 {
2448     list_vanquished('a', FALSE);
2449     return 0;
2450 }
2451 
2452 DISABLE_WARNING_FORMAT_NONLITERAL
2453 
2454 /* #wizborn extended command */
2455 int
doborn(void)2456 doborn(void)
2457 {
2458     static const char fmt[] = "%4i %4i %c %-30s";
2459     int i;
2460     winid datawin = create_nhwindow(NHW_TEXT);
2461     char buf[BUFSZ];
2462     int nborn = 0, ndied = 0;
2463 
2464     putstr(datawin, 0, "died born");
2465     for (i = LOW_PM; i < NUMMONS; i++)
2466         if (g.mvitals[i].born || g.mvitals[i].died
2467             || (g.mvitals[i].mvflags & G_GONE)) {
2468             Sprintf(buf, fmt,
2469                     g.mvitals[i].died, g.mvitals[i].born,
2470                     ((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT) ? 'E' :
2471                     ((g.mvitals[i].mvflags & G_GONE) == G_GENOD) ? 'G' : ' ',
2472                     mons[i].pmnames[NEUTRAL]);
2473             putstr(datawin, 0, buf);
2474             nborn += g.mvitals[i].born;
2475             ndied += g.mvitals[i].died;
2476         }
2477 
2478     putstr(datawin, 0, "");
2479     Sprintf(buf, fmt, ndied, nborn, ' ', "");
2480 
2481     display_nhwindow(datawin, FALSE);
2482     destroy_nhwindow(datawin);
2483 
2484     return 0;
2485 }
2486 
2487 RESTORE_WARNING_FORMAT_NONLITERAL
2488 
2489 /* high priests aren't unique but are flagged as such to simplify something */
2490 #define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \
2491                                && mndx != PM_HIGH_CLERIC)
2492 
2493 #define done_stopprint g.program_state.stopprint
2494 
2495 void
list_vanquished(char defquery,boolean ask)2496 list_vanquished(char defquery, boolean ask)
2497 {
2498     register int i;
2499     int pfx, nkilled;
2500     unsigned ntypes, ni;
2501     long total_killed = 0L;
2502     winid klwin;
2503     short mindx[NUMMONS];
2504     char c, buf[BUFSZ], buftoo[BUFSZ];
2505     boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
2506 
2507     dumping = (defquery == 'd');
2508     if (dumping)
2509         defquery = 'y';
2510 
2511     /* get totals first */
2512     ntypes = 0;
2513     for (i = LOW_PM; i < NUMMONS; i++) {
2514         if ((nkilled = (int) g.mvitals[i].died) == 0)
2515             continue;
2516         mindx[ntypes++] = i;
2517         total_killed += (long) nkilled;
2518     }
2519 
2520     /* vanquished creatures list;
2521      * includes all dead monsters, not just those killed by the player
2522      */
2523     if (ntypes != 0) {
2524         char mlet, prev_mlet = 0; /* used as small integer, not character */
2525         boolean class_header, uniq_header, was_uniq = FALSE;
2526 
2527         c = ask ? yn_function(
2528                             "Do you want an account of creatures vanquished?",
2529                               ynaqchars, defquery)
2530                 : defquery;
2531         if (c == 'q')
2532             done_stopprint++;
2533         if (c == 'y' || c == 'a') {
2534             if (c == 'a') { /* ask player to choose sort order */
2535                 /* choose value for vanq_sortmode via menu; ESC cancels list
2536                    of vanquished monsters but does not set 'done_stopprint' */
2537                 if (set_vanq_order() < 0)
2538                     return;
2539             }
2540             uniq_header = (g.vanq_sortmode == VANQ_ALPHA_SEP);
2541             class_header = (g.vanq_sortmode == VANQ_MCLS_LTOH
2542                             || g.vanq_sortmode == VANQ_MCLS_HTOL);
2543 
2544             klwin = create_nhwindow(NHW_MENU);
2545             putstr(klwin, ATR_HEADING, "Vanquished creatures:");
2546             if (!dumping)
2547                 putstr(klwin, 0, "");
2548 
2549             qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp);
2550             for (ni = 0; ni < ntypes; ni++) {
2551                 i = mindx[ni];
2552                 nkilled = g.mvitals[i].died;
2553                 mlet = mons[i].mlet;
2554                 if (class_header && mlet != prev_mlet) {
2555                     Strcpy(buf, def_monsyms[(int) mlet].explain);
2556                     putstr(klwin, ask ? 0 : iflags.menu_headings,
2557                            upstart(buf));
2558                     prev_mlet = mlet;
2559                 }
2560                 if (UniqCritterIndx(i)) {
2561                     Sprintf(buf, "%s%s",
2562                             !type_is_pname(&mons[i]) ? "the " : "",
2563                             mons[i].pmnames[NEUTRAL]);
2564                     if (nkilled > 1) {
2565                         switch (nkilled) {
2566                         case 2:
2567                             Sprintf(eos(buf), " (twice)");
2568                             break;
2569                         case 3:
2570                             Sprintf(eos(buf), " (thrice)");
2571                             break;
2572                         default:
2573                             Sprintf(eos(buf), " (%d times)", nkilled);
2574                             break;
2575                         }
2576                     }
2577                     was_uniq = TRUE;
2578                 } else {
2579                     if (uniq_header && was_uniq) {
2580                         putstr(klwin, 0, "");
2581                         was_uniq = FALSE;
2582                     }
2583                     /* trolls or undead might have come back,
2584                        but we don't keep track of that */
2585                     if (nkilled == 1)
2586                         Strcpy(buf, an(mons[i].pmnames[NEUTRAL]));
2587                     else
2588                         Sprintf(buf, "%3d %s", nkilled,
2589                                 makeplural(mons[i].pmnames[NEUTRAL]));
2590                 }
2591                 /* number of leading spaces to match 3 digit prefix */
2592                 pfx = !strncmpi(buf, "the ", 3) ? 0
2593                       : !strncmpi(buf, "an ", 3) ? 1
2594                         : !strncmpi(buf, "a ", 2) ? 2
2595                           : !digit(buf[2]) ? 4 : 0;
2596                 if (class_header)
2597                     ++pfx;
2598                 Snprintf(buftoo, sizeof(buftoo), "%*s%s", pfx, "", buf);
2599                 putstr(klwin, 0, buftoo);
2600             }
2601             if (Hallucination && ntypes > 10)
2602                 putstr(klwin, 0, "and a partridge in a pear tree");
2603             if (ntypes > 1) {
2604                 if (!dumping)
2605                     putstr(klwin, 0, "");
2606                 Sprintf(buf, "%ld creatures vanquished.", total_killed);
2607                 putstr(klwin, ATR_PREFORM, buf);
2608             }
2609             display_nhwindow(klwin, TRUE);
2610             destroy_nhwindow(klwin);
2611         }
2612     } else if (defquery == 'a') {
2613         /* #dovanquished rather than final disclosure, so pline() is ok */
2614         pline("No creatures have been vanquished.");
2615 #if defined(DUMPLOG) || defined(DUMPHTML)
2616     } else if (dumping) {
2617         putstr(0, 0, "No creatures were vanquished."); /* not pline() */
2618 #endif
2619     }
2620 }
2621 
2622 /* number of monster species which have been genocided */
2623 int
num_genocides(void)2624 num_genocides(void)
2625 {
2626     int i, n = 0;
2627 
2628     for (i = LOW_PM; i < NUMMONS; ++i) {
2629         if (g.mvitals[i].mvflags & G_GENOD) {
2630             ++n;
2631             if (UniqCritterIndx(i))
2632                 impossible("unique creature '%d: %s' genocided?",
2633                            i, mons[i].pmnames[NEUTRAL]);
2634         }
2635     }
2636     return n;
2637 }
2638 
2639 static int
num_extinct(void)2640 num_extinct(void)
2641 {
2642     int i, n = 0;
2643 
2644     for (i = LOW_PM; i < NUMMONS; ++i) {
2645         if (UniqCritterIndx(i))
2646             continue;
2647         if ((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT)
2648             ++n;
2649     }
2650     return n;
2651 }
2652 
2653 void
list_genocided(char defquery,boolean ask)2654 list_genocided(char defquery, boolean ask)
2655 {
2656     register int i;
2657     int ngenocided, nextinct;
2658     char c;
2659     winid klwin;
2660     char buf[BUFSZ];
2661     boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
2662 
2663     dumping = (defquery == 'd');
2664     if (dumping)
2665         defquery = 'y';
2666 
2667     ngenocided = num_genocides();
2668     nextinct = num_extinct();
2669 
2670     /* genocided or extinct species list */
2671     if (ngenocided != 0 || nextinct != 0) {
2672         Sprintf(buf, "Do you want a list of %sspecies%s%s?",
2673                 (nextinct && !ngenocided) ? "extinct " : "",
2674                 (ngenocided) ? " genocided" : "",
2675                 (nextinct && ngenocided) ? " and extinct" : "");
2676         c = ask ? yn_function(buf, ynqchars, defquery) : defquery;
2677         if (c == 'q')
2678             done_stopprint++;
2679         if (c == 'y') {
2680             klwin = create_nhwindow(NHW_MENU);
2681             Sprintf(buf, "%s%s species:",
2682                     (ngenocided) ? "Genocided" : "Extinct",
2683                     (nextinct && ngenocided) ? " or extinct" : "");
2684             putstr(klwin, ATR_SUBHEAD, buf);
2685             if (!dumping)
2686                 putstr(klwin, 0, "");
2687 
2688             for (i = LOW_PM; i < NUMMONS; i++) {
2689                 /* uniques can't be genocided but can become extinct;
2690                    however, they're never reported as extinct, so skip them */
2691                 if (UniqCritterIndx(i))
2692                     continue;
2693                 if (g.mvitals[i].mvflags & G_GONE) {
2694                     Sprintf(buf, " %s", makeplural(mons[i].pmnames[NEUTRAL]));
2695                     /*
2696                      * "Extinct" is unfortunate terminology.  A species
2697                      * is marked extinct when its birth limit is reached,
2698                      * but there might be members of the species still
2699                      * alive, contradicting the meaning of the word.
2700                      */
2701                     if ((g.mvitals[i].mvflags & G_GONE) == G_EXTINCT)
2702                         Strcat(buf, " (extinct)");
2703                     putstr(klwin, 0, buf);
2704                 }
2705             }
2706             if (!dumping)
2707                 putstr(klwin, 0, "");
2708             if (ngenocided > 0) {
2709                 Sprintf(buf, "%d species genocided.", ngenocided);
2710                 putstr(klwin, ATR_PREFORM, buf);
2711             }
2712             if (nextinct > 0) {
2713                 Sprintf(buf, "%d species extinct.", nextinct);
2714                 putstr(klwin, ATR_PREFORM, buf);
2715             }
2716 
2717             display_nhwindow(klwin, TRUE);
2718             destroy_nhwindow(klwin);
2719         }
2720 #if defined (DUMPLOG) || defined (DUMPHTML)
2721     } else if (dumping) {
2722         putstr(0, 0, "No species were genocided or became extinct.");
2723 #endif
2724     }
2725 }
2726 
2727 /*
2728  * align_str(), piousness(), mstatusline() and ustatusline() once resided
2729  * in pline.c, then got moved to priest.c just to be out of there.  They
2730  * fit better here.
2731  */
2732 
2733 const char *
align_str(aligntyp alignment)2734 align_str(aligntyp alignment)
2735 {
2736     switch ((int) alignment) {
2737     case A_CHAOTIC:
2738         return "chaotic";
2739     case A_NEUTRAL:
2740         return "neutral";
2741     case A_LAWFUL:
2742         return "lawful";
2743     case A_NONE:
2744         return "unaligned";
2745     }
2746     return "unknown";
2747 }
2748 
2749 /* used for self-probing */
2750 char *
piousness(boolean showneg,const char * suffix)2751 piousness(boolean showneg, const char *suffix)
2752 {
2753     static char buf[32]; /* bigger than "insufficiently neutral" */
2754     const char *pio;
2755 
2756     /* note: piousness 20 matches MIN_QUEST_ALIGN (quest.h) */
2757     if (u.ualign.record >= 20)
2758         pio = "piously";
2759     else if (u.ualign.record > 13)
2760         pio = "devoutly";
2761     else if (u.ualign.record > 8)
2762         pio = "fervently";
2763     else if (u.ualign.record > 3)
2764         pio = "stridently";
2765     else if (u.ualign.record == 3)
2766         pio = "";
2767     else if (u.ualign.record > 0)
2768         pio = "haltingly";
2769     else if (u.ualign.record == 0)
2770         pio = "nominally";
2771     else if (!showneg)
2772         pio = "insufficiently";
2773     else if (u.ualign.record >= -3)
2774         pio = "strayed";
2775     else if (u.ualign.record >= -8)
2776         pio = "sinned";
2777     else
2778         pio = "transgressed";
2779 
2780     Sprintf(buf, "%s", pio);
2781     if (suffix && (!showneg || u.ualign.record >= 0)) {
2782         if (u.ualign.record != 3)
2783             Strcat(buf, " ");
2784         Strcat(buf, suffix);
2785     }
2786     return buf;
2787 }
2788 
2789 /* stethoscope or probing applied to monster -- one-line feedback */
2790 void
mstatusline(struct monst * mtmp)2791 mstatusline(struct monst *mtmp)
2792 {
2793     aligntyp alignment = mon_aligntyp(mtmp);
2794     char info[BUFSZ], monnambuf[BUFSZ];
2795 
2796     info[0] = 0;
2797     if (mtmp->mtame) {
2798         Strcat(info, ", tame");
2799         if (wizard) {
2800             Sprintf(eos(info), " (%d", mtmp->mtame);
2801             if (!mtmp->isminion)
2802                 Sprintf(eos(info), "; hungry %ld; apport %d",
2803                         EDOG(mtmp)->hungrytime, EDOG(mtmp)->apport);
2804             Strcat(info, ")");
2805         }
2806     } else if (mtmp->mpeaceful)
2807         Strcat(info, ", peaceful");
2808 
2809     if (mtmp->data == &mons[PM_LONG_WORM]) {
2810         int segndx, nsegs = count_wsegs(mtmp);
2811 
2812         /* the worm code internals don't consider the head of be one of
2813            the worm's segments, but we count it as such when presenting
2814            worm feedback to the player */
2815         if (!nsegs) {
2816             Strcat(info, ", single segment");
2817         } else {
2818             ++nsegs; /* include head in the segment count */
2819             segndx = wseg_at(mtmp, g.bhitpos.x, g.bhitpos.y);
2820             Sprintf(eos(info), ", %d%s of %d segments",
2821                     segndx, ordin(segndx), nsegs);
2822         }
2823     }
2824     if (mtmp->cham >= LOW_PM && mtmp->data != &mons[mtmp->cham])
2825         /* don't reveal the innate form (chameleon, vampire, &c),
2826            just expose the fact that this current form isn't it */
2827         Strcat(info, ", shapechanger");
2828     /* pets eating mimic corpses mimic while eating, so this comes first */
2829     if (mtmp->meating)
2830         Strcat(info, ", eating");
2831     /* a stethoscope exposes mimic before getting here so this
2832        won't be relevant for it, but wand of probing doesn't */
2833     if (mtmp->mundetected || mtmp->m_ap_type)
2834         mhidden_description(mtmp, TRUE, eos(info));
2835     if (mtmp->mcan)
2836         Strcat(info, ", cancelled");
2837     if (mtmp->mconf)
2838         Strcat(info, ", confused");
2839     if (mtmp->mblinded || !mtmp->mcansee)
2840         Strcat(info, ", blind");
2841     if (mtmp->mstun)
2842         Strcat(info, ", stunned");
2843     if (mtmp->msleeping)
2844         Strcat(info, ", asleep");
2845 #if 0 /* unfortunately mfrozen covers temporary sleep and being busy \
2846          (donning armor, for instance) as well as paralysis */
2847     else if (mtmp->mfrozen)
2848         Strcat(info, ", paralyzed");
2849 #else
2850     else if (mtmp->mfrozen || !mtmp->mcanmove)
2851         Strcat(info, ", can't move");
2852 #endif
2853     /* [arbitrary reason why it isn't moving] */
2854     else if ((mtmp->mstrategy & STRAT_WAITMASK) != 0)
2855         Strcat(info, ", meditating");
2856     if (mtmp->mwither)
2857         Strcat(info, ", withering away");
2858     if (mtmp->mflee)
2859         Strcat(info, ", scared");
2860     if (mtmp->mtrapped)
2861         Strcat(info, ", trapped");
2862     if (mtmp->mspeed)
2863         Strcat(info, (mtmp->mspeed == MFAST) ? ", fast"
2864                       : (mtmp->mspeed == MSLOW) ? ", slow"
2865                          : ", [? speed]");
2866     if (mtmp->minvis)
2867         Strcat(info, ", invisible");
2868     if (mtmp == u.ustuck)
2869         Strcat(info, sticks(g.youmonst.data) ? ", held by you"
2870                       : !u.uswallow ? ", holding you"
2871                          : attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_DGST)
2872                             ? ", digesting you"
2873                             : is_animal(u.ustuck->data) ? ", swallowing you"
2874                                : ", engulfing you");
2875     if (mtmp == u.usteed)
2876         Strcat(info, ", carrying you");
2877 
2878     /* avoid "Status of the invisible newt ..., invisible" */
2879     /* and unlike a normal mon_nam, use "saddled" even if it has a name */
2880     Strcpy(monnambuf, x_monnam(mtmp, ARTICLE_THE, (char *) 0,
2881                                (SUPPRESS_IT | SUPPRESS_INVISIBLE), FALSE));
2882 
2883     pline("Status of %s (%s):  Level %d  HP %d(%d)  AC %d%s.", monnambuf,
2884           align_str(alignment), mtmp->m_lev, mtmp->mhp, mtmp->mhpmax,
2885           find_mac(mtmp), info);
2886 }
2887 
2888 /* stethoscope or probing applied to hero -- one-line feedback */
2889 void
ustatusline(void)2890 ustatusline(void)
2891 {
2892     char info[BUFSZ];
2893 
2894     info[0] = '\0';
2895     if (Sick) {
2896         Strcat(info, ", dying from");
2897         if (u.usick_type & SICK_VOMITABLE)
2898             Strcat(info, " food poisoning");
2899         if (u.usick_type & SICK_NONVOMITABLE) {
2900             if (u.usick_type & SICK_VOMITABLE)
2901                 Strcat(info, " and");
2902             Strcat(info, " illness");
2903         }
2904     }
2905     if (Stoned)
2906         Strcat(info, ", solidifying");
2907     if (Slimed)
2908         Strcat(info, ", becoming slimy");
2909     if (Strangled)
2910         Strcat(info, ", being strangled");
2911     if (Vomiting)
2912         Strcat(info, ", nauseated"); /* !"nauseous" */
2913     if (Confusion)
2914         Strcat(info, ", confused");
2915     if (Blind) {
2916         Strcat(info, ", blind");
2917         if (u.ucreamed) {
2918             if ((long) u.ucreamed < Blinded || Blindfolded
2919                 || !haseyes(g.youmonst.data))
2920                 Strcat(info, ", cover");
2921             Strcat(info, "ed by sticky goop");
2922         } /* note: "goop" == "glop"; variation is intentional */
2923     }
2924     if (Stunned)
2925         Strcat(info, ", stunned");
2926     if (!u.usteed && Wounded_legs) {
2927         const char *what = body_part(LEG);
2928         if ((Wounded_legs & BOTH_SIDES) == BOTH_SIDES)
2929             what = makeplural(what);
2930         Sprintf(eos(info), ", injured %s", what);
2931     }
2932     if (Glib)
2933         Sprintf(eos(info), ", slippery %s", makeplural(body_part(HAND)));
2934     if (u.utrap)
2935         Strcat(info, ", trapped");
2936     if (Fast)
2937         Strcat(info, Very_fast ? ", very fast" : ", fast");
2938     if (u.uundetected)
2939         Strcat(info, ", concealed");
2940     if (Invis)
2941         Strcat(info, ", invisible");
2942     if (u.ustuck) {
2943         if (sticks(g.youmonst.data))
2944             Strcat(info, ", holding ");
2945         else
2946             Strcat(info, ", held by ");
2947         Strcat(info, mon_nam(u.ustuck));
2948     }
2949 
2950     pline("Status of %s (%s):  Level %d  HP %d(%d)  AC %d%s.", g.plname,
2951           piousness(FALSE, align_str(u.ualign.type)),
2952           Upolyd ? mons[u.umonnum].mlevel : u.ulevel, Upolyd ? u.mh : u.uhp,
2953           Upolyd ? u.mhmax : u.uhpmax, u.uac, info);
2954 }
2955 
2956 /*insight.c*/
2957