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