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. */
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 *);
16 extern void maybe_play_sound(const char *);
20 /* keep the most recent DUMPLOG_MSG_COUNT messages */
22 dumplogmsg(const char *line
)
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
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))
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 */
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 */
52 dumplogfreemessages(void)
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;
63 /* keeps windowprocs usage out of pline() */
65 putmesg(const char *line
)
69 if ((gp
.pline_flags
& URGENT_MESSAGE
) != 0
70 && (windowprocs
.wincap2
& WC2_URGENT_MESG
) != 0)
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
);
79 /* set the direction where next message happens */
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 */
90 set_msg_xy(coordxy x
, coordxy y
)
96 staticfn
void vpline(const char *, va_list);
98 DISABLE_WARNING_FORMAT_NONLITERAL
101 pline(const char *line
, ...)
105 va_start(the_args
, line
);
106 vpline(line
, the_args
);
111 pline_dir(int dir
, const char *line
, ...)
117 va_start(the_args
, line
);
118 vpline(line
, the_args
);
123 pline_xy(coordxy x
, coordxy y
, const char *line
, ...)
129 va_start(the_args
, line
);
130 vpline(line
, the_args
);
135 pline_mon(struct monst
*mtmp
, const char *line
, ...)
139 set_msg_xy(mtmp
->mx
, mtmp
->my
);
141 va_start(the_args
, line
);
142 vpline(line
, the_args
);
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 */
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;
162 #ifdef HANGUPHANDLING
163 if (program_state
.done_hup
)
166 if (program_state
.wizkit_wishing
)
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
));
181 vpline(tmp
, the_args
);
182 free((genericptr_t
) tmp
);
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
);
196 /* perform printf() formatting */
197 ln
= vsnprintf(pbuf
, sizeof pbuf
, line
, the_args
);
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';
220 msgtyp
= MSGTYP_NORMAL
;
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)
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] */
236 iflags
.last_msg
= PLNMSG_UNKNOWN
;
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
);
244 if (msgtyp
== MSGTYP_NORMAL
|| msgtyp
== MSGTYP_NOSHOW
)
245 maybe_play_sound(line
);
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.)
260 if (gv
.vision_full_recalc
) {
261 int tmp_in_pline
= in_pline
;
265 in_pline
= tmp_in_pline
;
268 flush_screen((gp
.pline_flags
& NO_CURS_ON_U
) ? 0 : 1); /* %% */
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-- */
281 /* clear the SPEECH flag so caller never has to */
282 gp
.pline_flags
&= ~PLINE_SPEECH
;
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) */
293 custompline(unsigned pflags
, const char *line
, ...)
297 va_start(the_args
, line
);
298 gp
.pline_flags
= pflags
;
299 vpline(line
, 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 */
309 urgent_pline(const char *line
, ...)
313 va_start(the_args
, line
);
314 gp
.pline_flags
= URGENT_MESSAGE
;
315 vpline(line
, the_args
);
321 Norep(const char *line
, ...)
325 va_start(the_args
, line
);
326 gp
.pline_flags
= PLINE_NOREPEAT
;
327 vpline(line
, the_args
);
335 if (siz
> gy
.you_buf_siz
) {
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
);
348 free((genericptr_t
) gy
.you_buf
), gy
.you_buf
= (char *) 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)
360 You(const char *line
, ...)
365 va_start(the_args
, line
);
366 vpline(YouMessage(tmp
, "You ", line
), the_args
);
371 Your(const char *line
, ...)
376 va_start(the_args
, line
);
377 vpline(YouMessage(tmp
, "Your ", line
), the_args
);
382 You_feel(const char *line
, ...)
387 va_start(the_args
, line
);
389 YouPrefix(tmp
, "You dream that you feel ", line
);
391 YouPrefix(tmp
, "You feel ", line
);
392 vpline(strcat(tmp
, line
), the_args
);
397 You_cant(const char *line
, ...)
402 va_start(the_args
, line
);
403 vpline(YouMessage(tmp
, "You can't ", line
), the_args
);
408 pline_The(const char *line
, ...)
413 va_start(the_args
, line
);
414 vpline(YouMessage(tmp
, "The ", line
), the_args
);
419 There(const char *line
, ...)
424 va_start(the_args
, line
);
425 vpline(YouMessage(tmp
, "There ", line
), the_args
);
430 You_hear(const char *line
, ...)
435 if ((Deaf
&& !Unaware
) || !flags
.acoustics
)
437 va_start(the_args
, line
);
439 YouPrefix(tmp
, "You barely hear ", line
);
441 YouPrefix(tmp
, "You dream that you hear ", line
);
443 YouPrefix(tmp
, "You hear ", line
); /* Deaf-aware */
444 vpline(strcat(tmp
, line
), the_args
);
449 You_see(const char *line
, ...)
454 va_start(the_args
, line
);
456 YouPrefix(tmp
, "You dream that you see ", line
);
457 else if (Blind
) /* caller should have caught this... */
458 YouPrefix(tmp
, "You sense ", line
);
460 YouPrefix(tmp
, "You see ", line
);
461 vpline(strcat(tmp
, line
), 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.
470 verbalize(const char *line
, ...)
475 va_start(the_args
, line
);
476 gp
.pline_flags
|= PLINE_VERBALIZE
;
477 tmp
= You_buf((int) strlen(line
) + sizeof "\"\"");
481 vpline(tmp
, the_args
);
482 gp
.pline_flags
&= ~PLINE_VERBALIZE
;
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
));
496 tmp
->flags
= glflags
;
497 tmp
->text
= dupstr(str
);
499 while (lst
&& lst
->next
)
508 livelog_printf(long ll_type
, const char *line
, ...)
510 char gamelogbuf
[BUFSZ
* 2];
513 va_start(the_args
, line
);
514 (void) vsnprintf(gamelogbuf
, sizeof gamelogbuf
, line
, the_args
);
517 gamelog_add(ll_type
, svm
.moves
, gamelogbuf
);
518 strNsubst(gamelogbuf
, "\t", "_", 0);
519 livelog_add(ll_type
, gamelogbuf
);
526 long glflags UNUSED
, long gltime UNUSED
, const char *msg UNUSED
)
533 long ll_type UNUSED
, const char *line UNUSED
, ...)
538 #endif /* !CHRONICLE */
540 staticfn
void vraw_printf(const char *, va_list);
543 raw_printf(const char *line
, ...)
547 va_start(the_args
, line
);
548 vraw_printf(line
, the_args
);
550 if (!program_state
.beyond_savefile_load
)
551 ge
.early_raw_messages
++;
554 DISABLE_WARNING_FORMAT_NONLITERAL
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
);
565 if ((int) strlen(line
) > BUFSZ
- 1) {
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 */
572 execplinehandler(line
);
573 if (!program_state
.beyond_savefile_load
)
574 ge
.early_raw_messages
++;
578 impossible(const char *s
, ...)
581 char pbuf
[BIGBUFSZ
]; /* will be chopped down to BUFSZ-1 if longer */
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
);
591 pbuf
[BUFSZ
- 1] = '\0'; /* sanity */
592 paniclog("impossible", pbuf
);
593 if (iflags
.debug_fuzzer
== fuzzer_impossible_panic
)
596 gp
.pline_flags
= URGENT_MESSAGE
;
600 if (program_state
.in_sanity_check
) {
601 /* skip rest of multi-line feedback */
602 program_state
.in_impossible
= 0;
606 Strcpy(pbuf2
, "Program in disorder!");
607 if (program_state
.something_worth_saving
)
608 Strcat(pbuf2
, " (Saving and reloading may fix this problem.)");
610 pline("Please report these messages to %s.", DEVTEAM_EMAIL
);
611 if (sysopt
.support
) {
612 pline("Alternatively, contact local support: %s", sysopt
.support
);
616 if (sysopt
.crashreporturl
) {
617 boolean report
= ('y' == yn_function("Report now?", ynchars
,
620 raw_print(""); /* prove to the user the character was accepted */
622 submit_web_report(1, "Impossible", pbuf
);
627 program_state
.in_impossible
= 0;
630 RESTORE_WARNING_FORMAT_NONLITERAL
632 static boolean use_pline_handler
= TRUE
;
635 execplinehandler(const char *line
)
637 #if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__))
642 if (!use_pline_handler
|| !sysopt
.msghandler
)
645 #if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__))
647 if (f
== 0) { /* child */
648 args
[0] = sysopt
.msghandler
;
651 (void) setgid(getgid());
652 (void) setuid(getuid());
653 (void) execv(args
[0], (char *const *) args
);
655 (void) fprintf(stderr
, "Exec to message handler %s failed.\n", sysopt
.msghandler
);
656 nh_terminate(EXIT_FAILURE
);
660 waitpid(f
, &status
, 0);
661 } else if (f
== -1) {
663 use_pline_handler
= FALSE
;
664 pline("%s", "Fork to message handler failed.");
669 args
[0] = sysopt
.msghandler
;
672 ret
= _spawnv(_P_NOWAIT
, sysopt
.msghandler
, args
);
673 nhUse(ret
); /* -Wunused-but-set-variable */
676 use_pline_handler
= FALSE
;
683 * varargs handling for files.c
685 staticfn
void vconfig_error_add(const char *, va_list);
687 DISABLE_WARNING_FORMAT_NONLITERAL
690 config_error_add(const char *str
, ...)
694 va_start(the_args
, str
);
695 vconfig_error_add(str
, the_args
);
700 vconfig_error_add(const char *str
, va_list the_args
)
701 { /* start of vconf...() or of nested block in USE_OLDARG's conf...() */
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
);
713 buf
[BUFSZ
- 1] = '\0';
717 RESTORE_WARNING_FORMAT_NONLITERAL
719 /* nhassert_failed is called when an nhassert's condition is false */
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
729 if ((p
= strrchr(filename
, '/')) != 0)
731 if ((p
= strrchr(filename
, '\\')) != 0)
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)
740 if ((p
= strrchr(filename
, '>')) != 0)
742 if ((p
= strrchr(filename
, ':')) != 0)
746 impossible("nhassert(%s) failed in file '%s' at line %d",
747 expression
, filename
, line
);