make rank() static again
[NetHack.git] / src / pline.c
blob13ec79924c4cc07ff92bb39ec498d16170d384fd
1 /* NetHack 3.7 pline.c $NHDT-Date: 1719819280 2024/07/01 07:34:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.130 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2018. */
4 /* NetHack may be freely redistributed. See license for details. */
6 #include "hack.h"
8 #define BIGBUFSZ (5 * BUFSZ) /* big enough to format a 4*BUFSZ string (from
9 * config file parsing) with modest decoration;
10 * result will then be truncated to BUFSZ-1 */
12 staticfn void putmesg(const char *);
13 staticfn char *You_buf(int);
14 staticfn void execplinehandler(const char *);
15 #ifdef USER_SOUNDS
16 extern void maybe_play_sound(const char *);
17 #endif
18 #ifdef DUMPLOG_CORE
20 /* keep the most recent DUMPLOG_MSG_COUNT messages */
21 void
22 dumplogmsg(const char *line)
25 * TODO:
26 * This essentially duplicates message history, which is
27 * currently implemented in an interface-specific manner.
28 * The core should take responsibility for that and have
29 * this share it.
31 unsigned indx = gs.saved_pline_index; /* next slot to use */
32 char *oldest = gs.saved_plines[indx]; /* current content of that slot */
34 if (!strncmp(line, "Unknown command", 15))
35 return;
36 if (oldest && strlen(oldest) >= strlen(line)) {
37 /* this buffer will gradually shrink until the 'else' is needed;
38 there's no pressing need to track allocation size instead */
39 Strcpy(oldest, line);
40 } else {
41 if (oldest)
42 free((genericptr_t) oldest);
43 gs.saved_plines[indx] = dupstr(line);
45 gs.saved_pline_index = (indx + 1) % DUMPLOG_MSG_COUNT;
48 /* called during save (unlike the interface-specific message history,
49 this data isn't saved and restored); end-of-game releases saved_plines[]
50 while writing its contents to the final dump log */
51 void
52 dumplogfreemessages(void)
54 unsigned i;
56 for (i = 0; i < DUMPLOG_MSG_COUNT; ++i)
57 if (gs.saved_plines[i])
58 free((genericptr_t) gs.saved_plines[i]), gs.saved_plines[i] = 0;
59 gs.saved_pline_index = 0;
61 #endif
63 /* keeps windowprocs usage out of pline() */
64 staticfn void
65 putmesg(const char *line)
67 int attr = ATR_NONE;
69 if ((gp.pline_flags & URGENT_MESSAGE) != 0
70 && (windowprocs.wincap2 & WC2_URGENT_MESG) != 0)
71 attr |= ATR_URGENT;
72 if ((gp.pline_flags & SUPPRESS_HISTORY) != 0
73 && (windowprocs.wincap2 & WC2_SUPPRESS_HIST) != 0)
74 attr |= ATR_NOHISTORY;
75 putstr(WIN_MESSAGE, attr, line);
76 SoundSpeak(line);
79 /* set the direction where next message happens */
80 void
81 set_msg_dir(int dir)
83 dtoxy(&a11y.msg_loc, dir);
84 a11y.msg_loc.x += u.ux;
85 a11y.msg_loc.y += u.uy;
88 /* set the coordinate where next message happens */
89 void
90 set_msg_xy(coordxy x, coordxy y)
92 a11y.msg_loc.x = x;
93 a11y.msg_loc.y = y;
96 staticfn void vpline(const char *, va_list);
98 DISABLE_WARNING_FORMAT_NONLITERAL
100 void
101 pline(const char *line, ...)
103 va_list the_args;
105 va_start(the_args, line);
106 vpline(line, the_args);
107 va_end(the_args);
110 void
111 pline_dir(int dir, const char *line, ...)
113 va_list the_args;
115 set_msg_dir(dir);
117 va_start(the_args, line);
118 vpline(line, the_args);
119 va_end(the_args);
122 void
123 pline_xy(coordxy x, coordxy y, const char *line, ...)
125 va_list the_args;
127 set_msg_xy(x, y);
129 va_start(the_args, line);
130 vpline(line, the_args);
131 va_end(the_args);
134 void
135 pline_mon(struct monst *mtmp, const char *line, ...)
137 va_list the_args;
139 set_msg_xy(mtmp->mx, mtmp->my);
141 va_start(the_args, line);
142 vpline(line, the_args);
143 va_end(the_args);
146 staticfn void
147 vpline(const char *line, va_list the_args)
149 static int in_pline = 0;
150 char pbuf[BIGBUFSZ]; /* will get chopped down to BUFSZ-1 if longer */
151 int ln;
152 int msgtyp;
153 boolean no_repeat;
154 coord a11y_mesgxy;
156 a11y_mesgxy = a11y.msg_loc; /* save a11y.msg_loc before reseting it */
157 /* always reset a11y.msg_loc whether we end up using it or not */
158 a11y.msg_loc.x = a11y.msg_loc.y = 0;
160 if (!line || !*line)
161 return;
162 #ifdef HANGUPHANDLING
163 if (program_state.done_hup)
164 return;
165 #endif
166 if (program_state.wizkit_wishing)
167 return;
169 /* when accessiblemsg is set and a11y.msg_loc is nonzero, use the latter
170 to insert a location prefix in front of current message */
171 if (a11y.accessiblemsg && isok(a11y_mesgxy.x, a11y_mesgxy.y)) {
172 char *tmp, *dirstr, dirstrbuf[QBUFSZ];
174 dirstr = coord_desc(a11y_mesgxy.x, a11y_mesgxy.y, dirstrbuf,
175 ((iflags.getpos_coords == GPCOORDS_NONE)
176 ? GPCOORDS_COMFULL : iflags.getpos_coords));
177 tmp = (char *) alloc(strlen(line) + sizeof ": " + strlen(dirstr));
178 Strcpy(tmp, dirstr);
179 Strcat(tmp, ": ");
180 Strcat(tmp, line);
181 vpline(tmp, the_args);
182 free((genericptr_t) tmp);
183 return;
186 if (!strchr(line, '%')) {
187 /* format does not specify any substitutions; use it as-is */
188 ln = (int) strlen(line);
189 } else if (line[0] == '%' && line[1] == 's' && !line[2]) {
190 /* "%s" => single string; skip format and use its first argument;
191 unlike with the format, it is irrelevant whether the argument
192 contains any percent signs */
193 line = va_arg(the_args, const char *); /*VA_NEXT(line,const char *);*/
194 ln = (int) strlen(line);
195 } else {
196 /* perform printf() formatting */
197 ln = vsnprintf(pbuf, sizeof pbuf, line, the_args);
198 line = pbuf;
199 /* note: 'ln' is number of characters attempted, not necessarily
200 strlen(line); that matters for the overflow check; if we avoid
201 the extremely-too-long panic then 'ln' will be actual length */
203 if (ln > (int) sizeof pbuf - 1) /* extremely too long */
204 panic("pline attempting to print %d characters!", ln);
206 if (ln > BUFSZ - 1) {
207 /* too long but modestly so; allow but truncate, preserving final
208 3 chars: "___ extremely long text" -> "___ extremely l...ext"
209 (this may be suboptimal if overflow is less than 3) */
210 if (line != pbuf) /* no '%' was present or format was just "%s" */
211 (void) strncpy(pbuf, line, BUFSZ - 1); /* caveat: unterminated */
212 pbuf[BUFSZ - 1 - 6] = pbuf[BUFSZ - 1 - 5] = pbuf[BUFSZ - 1 - 4] = '.';
213 /* avoid strncpy; buffers could overlap if excess is small */
214 pbuf[BUFSZ - 1 - 3] = line[ln - 3];
215 pbuf[BUFSZ - 1 - 2] = line[ln - 2];
216 pbuf[BUFSZ - 1 - 1] = line[ln - 1];
217 pbuf[BUFSZ - 1] = '\0';
218 line = pbuf;
220 msgtyp = MSGTYP_NORMAL;
222 #ifdef DUMPLOG_CORE
223 /* We hook here early to have options-agnostic output.
224 * Unfortunately, that means Norep() isn't honored (general issue) and
225 * that short lines aren't combined into one longer one (tty behavior).
227 if ((gp.pline_flags & SUPPRESS_HISTORY) == 0)
228 dumplogmsg(line);
229 #endif
230 /* use raw_print() if we're called too early (or perhaps too late
231 during shutdown) or if we're being called recursively (probably
232 via debugpline() in the interface code) */
233 if (in_pline++ || !iflags.window_inited) {
234 /* [we should probably be using raw_printf("\n%s", line) here] */
235 raw_print(line);
236 iflags.last_msg = PLNMSG_UNKNOWN;
237 goto pline_done;
240 no_repeat = (gp.pline_flags & PLINE_NOREPEAT) ? TRUE : FALSE;
241 if ((gp.pline_flags & OVERRIDE_MSGTYPE) == 0) {
242 msgtyp = msgtype_type(line, no_repeat);
243 #ifdef USER_SOUNDS
244 if (msgtyp == MSGTYP_NORMAL || msgtyp == MSGTYP_NOSHOW)
245 maybe_play_sound(line);
246 #endif
247 if ((gp.pline_flags & URGENT_MESSAGE) == 0
248 && (msgtyp == MSGTYP_NOSHOW
249 || (msgtyp == MSGTYP_NOREP && !strcmp(line, gp.prevmsg))))
250 /* FIXME: we need a way to tell our caller that this message
251 * was suppressed so that caller doesn't set iflags.last_msg
252 * for something that hasn't been shown, otherwise a subsequent
253 * message which uses alternate wording based on that would be
254 * doing so out of context and probably end up seeming silly.
255 * (Not an issue for no-repeat but matters for no-show.)
257 goto pline_done;
260 if (gv.vision_full_recalc) {
261 int tmp_in_pline = in_pline;
263 in_pline = 0;
264 vision_recalc(0);
265 in_pline = tmp_in_pline;
267 if (u.ux)
268 flush_screen((gp.pline_flags & NO_CURS_ON_U) ? 0 : 1); /* %% */
270 putmesg(line);
272 execplinehandler(line);
274 /* this gets cleared after every pline message */
275 iflags.last_msg = PLNMSG_UNKNOWN;
276 (void) strncpy(gp.prevmsg, line, BUFSZ), gp.prevmsg[BUFSZ - 1] = '\0';
277 if (msgtyp == MSGTYP_STOP)
278 display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */
279 pline_done:
280 #ifdef SND_SPEECH
281 /* clear the SPEECH flag so caller never has to */
282 gp.pline_flags &= ~PLINE_SPEECH;
283 #endif
284 --in_pline;
287 RESTORE_WARNING_FORMAT_NONLITERAL
289 /* pline() variant which can override MSGTYPE handling or suppress
290 message history (tty interface uses pline() to issue prompts and
291 they shouldn't be blockable via MSGTYPE=hide) */
292 void
293 custompline(unsigned pflags, const char *line, ...)
295 va_list the_args;
297 va_start(the_args, line);
298 gp.pline_flags = pflags;
299 vpline(line, the_args);
300 gp.pline_flags = 0;
301 va_end(the_args);
304 /* if player has dismissed --More-- with ESC to suppress further messages
305 until next input request, tell the interface that it should override that
306 and re-enable them; equivalent to custompline(URGENT_MESSAGE, line, ...)
307 but slightly simpler to use */
308 void
309 urgent_pline(const char *line, ...)
311 va_list the_args;
313 va_start(the_args, line);
314 gp.pline_flags = URGENT_MESSAGE;
315 vpline(line, the_args);
316 gp.pline_flags = 0;
317 va_end(the_args);
320 void
321 Norep(const char *line, ...)
323 va_list the_args;
325 va_start(the_args, line);
326 gp.pline_flags = PLINE_NOREPEAT;
327 vpline(line, the_args);
328 gp.pline_flags = 0;
329 va_end(the_args);
332 staticfn char *
333 You_buf(int siz)
335 if (siz > gy.you_buf_siz) {
336 if (gy.you_buf)
337 free((genericptr_t) gy.you_buf);
338 gy.you_buf_siz = siz + 10;
339 gy.you_buf = (char *) alloc((unsigned) gy.you_buf_siz);
341 return gy.you_buf;
344 void
345 free_youbuf(void)
347 if (gy.you_buf)
348 free((genericptr_t) gy.you_buf), gy.you_buf = (char *) 0;
349 gy.you_buf_siz = 0;
352 /* `prefix' must be a string literal, not a pointer */
353 #define YouPrefix(pointer, prefix, text) \
354 Strcpy((pointer = You_buf((int) (strlen(text) + sizeof prefix))), prefix)
356 #define YouMessage(pointer, prefix, text) \
357 strcat((YouPrefix(pointer, prefix, text), pointer), text)
359 void
360 You(const char *line, ...)
362 va_list the_args;
363 char *tmp;
365 va_start(the_args, line);
366 vpline(YouMessage(tmp, "You ", line), the_args);
367 va_end(the_args);
370 void
371 Your(const char *line, ...)
373 va_list the_args;
374 char *tmp;
376 va_start(the_args, line);
377 vpline(YouMessage(tmp, "Your ", line), the_args);
378 va_end(the_args);
381 void
382 You_feel(const char *line, ...)
384 va_list the_args;
385 char *tmp;
387 va_start(the_args, line);
388 if (Unaware)
389 YouPrefix(tmp, "You dream that you feel ", line);
390 else
391 YouPrefix(tmp, "You feel ", line);
392 vpline(strcat(tmp, line), the_args);
393 va_end(the_args);
396 void
397 You_cant(const char *line, ...)
399 va_list the_args;
400 char *tmp;
402 va_start(the_args, line);
403 vpline(YouMessage(tmp, "You can't ", line), the_args);
404 va_end(the_args);
407 void
408 pline_The(const char *line, ...)
410 va_list the_args;
411 char *tmp;
413 va_start(the_args, line);
414 vpline(YouMessage(tmp, "The ", line), the_args);
415 va_end(the_args);
418 void
419 There(const char *line, ...)
421 va_list the_args;
422 char *tmp;
424 va_start(the_args, line);
425 vpline(YouMessage(tmp, "There ", line), the_args);
426 va_end(the_args);
429 void
430 You_hear(const char *line, ...)
432 va_list the_args;
433 char *tmp;
435 if ((Deaf && !Unaware) || !flags.acoustics)
436 return;
437 va_start(the_args, line);
438 if (Underwater)
439 YouPrefix(tmp, "You barely hear ", line);
440 else if (Unaware)
441 YouPrefix(tmp, "You dream that you hear ", line);
442 else
443 YouPrefix(tmp, "You hear ", line); /* Deaf-aware */
444 vpline(strcat(tmp, line), the_args);
445 va_end(the_args);
448 void
449 You_see(const char *line, ...)
451 va_list the_args;
452 char *tmp;
454 va_start(the_args, line);
455 if (Unaware)
456 YouPrefix(tmp, "You dream that you see ", line);
457 else if (Blind) /* caller should have caught this... */
458 YouPrefix(tmp, "You sense ", line);
459 else
460 YouPrefix(tmp, "You see ", line);
461 vpline(strcat(tmp, line), the_args);
462 va_end(the_args);
465 /* Print a message inside double-quotes.
466 * The caller is responsible for checking deafness.
467 * Gods can speak directly to you in spite of deafness.
469 void
470 verbalize(const char *line, ...)
472 va_list the_args;
473 char *tmp;
475 va_start(the_args, line);
476 gp.pline_flags |= PLINE_VERBALIZE;
477 tmp = You_buf((int) strlen(line) + sizeof "\"\"");
478 Strcpy(tmp, "\"");
479 Strcat(tmp, line);
480 Strcat(tmp, "\"");
481 vpline(tmp, the_args);
482 gp.pline_flags &= ~PLINE_VERBALIZE;
483 va_end(the_args);
486 #ifdef CHRONICLE
488 void
489 gamelog_add(long glflags, long gltime, const char *str)
491 struct gamelog_line *tmp;
492 struct gamelog_line *lst = gg.gamelog;
494 tmp = (struct gamelog_line *) alloc(sizeof (struct gamelog_line));
495 tmp->turn = gltime;
496 tmp->flags = glflags;
497 tmp->text = dupstr(str);
498 tmp->next = NULL;
499 while (lst && lst->next)
500 lst = lst->next;
501 if (!lst)
502 gg.gamelog = tmp;
503 else
504 lst->next = tmp;
507 void
508 livelog_printf(long ll_type, const char *line, ...)
510 char gamelogbuf[BUFSZ * 2];
511 va_list the_args;
513 va_start(the_args, line);
514 (void) vsnprintf(gamelogbuf, sizeof gamelogbuf, line, the_args);
515 va_end(the_args);
517 gamelog_add(ll_type, svm.moves, gamelogbuf);
518 strNsubst(gamelogbuf, "\t", "_", 0);
519 livelog_add(ll_type, gamelogbuf);
522 #else
524 void
525 gamelog_add(
526 long glflags UNUSED, long gltime UNUSED, const char *msg UNUSED)
528 ; /* nothing here */
531 void
532 livelog_printf(
533 long ll_type UNUSED, const char *line UNUSED, ...)
535 ; /* nothing here */
538 #endif /* !CHRONICLE */
540 staticfn void vraw_printf(const char *, va_list);
542 void
543 raw_printf(const char *line, ...)
545 va_list the_args;
547 va_start(the_args, line);
548 vraw_printf(line, the_args);
549 va_end(the_args);
550 if (!program_state.beyond_savefile_load)
551 ge.early_raw_messages++;
554 DISABLE_WARNING_FORMAT_NONLITERAL
556 staticfn void
557 vraw_printf(const char *line, va_list the_args)
559 char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
561 if (strchr(line, '%')) {
562 (void) vsnprintf(pbuf, sizeof(pbuf), line, the_args);
563 line = pbuf;
565 if ((int) strlen(line) > BUFSZ - 1) {
566 if (line != pbuf)
567 line = strncpy(pbuf, line, BUFSZ - 1);
568 /* unlike pline, we don't futz around to keep last few chars */
569 pbuf[BUFSZ - 1] = '\0'; /* terminate strncpy or truncate vsprintf */
571 raw_print(line);
572 execplinehandler(line);
573 if (!program_state.beyond_savefile_load)
574 ge.early_raw_messages++;
577 void
578 impossible(const char *s, ...)
580 va_list the_args;
581 char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
582 char pbuf2[BUFSZ];
584 va_start(the_args, s);
585 if (program_state.in_impossible)
586 panic("impossible called impossible");
588 program_state.in_impossible = 1;
589 (void) vsnprintf(pbuf, sizeof pbuf, s, the_args);
590 va_end(the_args);
591 pbuf[BUFSZ - 1] = '\0'; /* sanity */
592 paniclog("impossible", pbuf);
593 if (iflags.debug_fuzzer == fuzzer_impossible_panic)
594 panic("%s", pbuf);
596 gp.pline_flags = URGENT_MESSAGE;
597 pline("%s", pbuf);
598 gp.pline_flags = 0;
600 if (program_state.in_sanity_check) {
601 /* skip rest of multi-line feedback */
602 program_state.in_impossible = 0;
603 return;
606 Strcpy(pbuf2, "Program in disorder!");
607 if (program_state.something_worth_saving)
608 Strcat(pbuf2, " (Saving and reloading may fix this problem.)");
609 pline("%s", pbuf2);
610 pline("Please report these messages to %s.", DEVTEAM_EMAIL);
611 if (sysopt.support) {
612 pline("Alternatively, contact local support: %s", sysopt.support);
615 #ifdef CRASHREPORT
616 if (sysopt.crashreporturl) {
617 boolean report = ('y' == yn_function("Report now?", ynchars,
618 'n', FALSE));
620 raw_print(""); /* prove to the user the character was accepted */
621 if (report) {
622 submit_web_report(1, "Impossible", pbuf);
625 #endif
627 program_state.in_impossible = 0;
630 RESTORE_WARNING_FORMAT_NONLITERAL
632 static boolean use_pline_handler = TRUE;
634 staticfn void
635 execplinehandler(const char *line)
637 #if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__))
638 int f;
639 #endif
640 const char *args[3];
642 if (!use_pline_handler || !sysopt.msghandler)
643 return;
645 #if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__))
646 f = fork();
647 if (f == 0) { /* child */
648 args[0] = sysopt.msghandler;
649 args[1] = line;
650 args[2] = NULL;
651 (void) setgid(getgid());
652 (void) setuid(getuid());
653 (void) execv(args[0], (char *const *) args);
654 perror((char *) 0);
655 (void) fprintf(stderr, "Exec to message handler %s failed.\n", sysopt.msghandler);
656 nh_terminate(EXIT_FAILURE);
657 } else if (f > 0) {
658 int status;
660 waitpid(f, &status, 0);
661 } else if (f == -1) {
662 perror((char *) 0);
663 use_pline_handler = FALSE;
664 pline("%s", "Fork to message handler failed.");
666 #elif defined(WIN32)
668 intptr_t ret;
669 args[0] = sysopt.msghandler;
670 args[1] = line;
671 args[2] = NULL;
672 ret = _spawnv(_P_NOWAIT, sysopt.msghandler, args);
673 nhUse(ret); /* -Wunused-but-set-variable */
675 #else
676 use_pline_handler = FALSE;
677 nhUse(args);
678 nhUse(line);
679 #endif
683 * varargs handling for files.c
685 staticfn void vconfig_error_add(const char *, va_list);
687 DISABLE_WARNING_FORMAT_NONLITERAL
689 void
690 config_error_add(const char *str, ...)
692 va_list the_args;
694 va_start(the_args, str);
695 vconfig_error_add(str, the_args);
696 va_end(the_args);
699 staticfn void
700 vconfig_error_add(const char *str, va_list the_args)
701 { /* start of vconf...() or of nested block in USE_OLDARG's conf...() */
702 int vlen = 0;
703 char buf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
705 vlen = vsnprintf(buf, sizeof buf, str, the_args);
706 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG)
707 if (vlen >= (int) sizeof buf)
708 panic("%s: truncation of buffer at %zu of %d bytes",
709 "config_error_add", sizeof buf, vlen);
710 #else
711 nhUse(vlen);
712 #endif
713 buf[BUFSZ - 1] = '\0';
714 config_erradd(buf);
717 RESTORE_WARNING_FORMAT_NONLITERAL
719 /* nhassert_failed is called when an nhassert's condition is false */
720 void
721 nhassert_failed(const char *expression, const char *filepath, int line)
723 const char *filename, *p;
725 /* Attempt to get filename from path.
726 TODO: we really need a port provided function to return a filename
727 from a path. */
728 filename = filepath;
729 if ((p = strrchr(filename, '/')) != 0)
730 filename = p + 1;
731 if ((p = strrchr(filename, '\\')) != 0)
732 filename = p + 1;
733 #ifdef VMS
734 /* usually "device:[directory]name"
735 but might be "device:[root.][directory]name"
736 and either "[directory]" or "[root.]" or both can be delimited
737 by <> rather than by []; find the last of ']', '>', and ':' */
738 if ((p = strrchr(filename, ']')) != 0)
739 filename = p + 1;
740 if ((p = strrchr(filename, '>')) != 0)
741 filename = p + 1;
742 if ((p = strrchr(filename, ':')) != 0)
743 filename = p + 1;
744 #endif
746 impossible("nhassert(%s) failed in file '%s' at line %d",
747 expression, filename, line);
750 /*pline.c*/