1 /* NetHack 3.7 topl.c $NHDT-Date: 1717967339 2024/06/09 21:08:59 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.89 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2009. */
4 /* NetHack may be freely redistributed. See license for details. */
13 static void redotoplin(const char *);
14 static void topl_putsym(char);
15 static void removetopl(int);
16 static void msghistory_snapshot(boolean
);
17 static void free_msghistory_snapshot(boolean
);
20 tty_doprev_message(void)
22 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
26 if ((iflags
.prevmsg_window
!= 's')
27 && !ttyDisplay
->inread
) { /* not single */
28 if (iflags
.prevmsg_window
== 'f') { /* full */
29 prevmsg_win
= create_nhwindow(NHW_MENU
);
30 putstr(prevmsg_win
, 0, "Message History");
31 putstr(prevmsg_win
, 0, "");
32 cw
->maxcol
= cw
->maxrow
;
35 if (cw
->data
[i
] && strcmp(cw
->data
[i
], ""))
36 putstr(prevmsg_win
, 0, cw
->data
[i
]);
37 i
= (i
+ 1) % cw
->rows
;
38 } while (i
!= cw
->maxcol
);
39 putstr(prevmsg_win
, 0, gt
.toplines
);
40 display_nhwindow(prevmsg_win
, TRUE
);
41 destroy_nhwindow(prevmsg_win
);
42 } else if (iflags
.prevmsg_window
== 'c') { /* combination */
45 if (cw
->maxcol
== cw
->maxrow
) {
46 ttyDisplay
->dismiss_more
= C('p'); /* ^P ok at --More-- */
47 redotoplin(gt
.toplines
);
50 cw
->maxcol
= cw
->rows
- 1;
51 if (!cw
->data
[cw
->maxcol
])
52 cw
->maxcol
= cw
->maxrow
;
53 } else if (cw
->maxcol
== (cw
->maxrow
- 1)) {
54 ttyDisplay
->dismiss_more
= C('p'); /* ^P ok at --More-- */
55 redotoplin(cw
->data
[cw
->maxcol
]);
58 cw
->maxcol
= cw
->rows
- 1;
59 if (!cw
->data
[cw
->maxcol
])
60 cw
->maxcol
= cw
->maxrow
;
62 prevmsg_win
= create_nhwindow(NHW_MENU
);
63 putstr(prevmsg_win
, 0, "Message History");
64 putstr(prevmsg_win
, 0, "");
65 cw
->maxcol
= cw
->maxrow
;
68 if (cw
->data
[i
] && strcmp(cw
->data
[i
], ""))
69 putstr(prevmsg_win
, 0, cw
->data
[i
]);
70 i
= (i
+ 1) % cw
->rows
;
71 } while (i
!= cw
->maxcol
);
72 putstr(prevmsg_win
, 0, gt
.toplines
);
73 display_nhwindow(prevmsg_win
, TRUE
);
74 destroy_nhwindow(prevmsg_win
);
77 } while (morc
== C('p'));
78 ttyDisplay
->dismiss_more
= 0;
79 } else { /* reversed */
81 prevmsg_win
= create_nhwindow(NHW_MENU
);
82 putstr(prevmsg_win
, 0, "Message History");
83 putstr(prevmsg_win
, 0, "");
84 putstr(prevmsg_win
, 0, gt
.toplines
);
85 cw
->maxcol
= cw
->maxrow
- 1;
87 cw
->maxcol
= cw
->rows
- 1;
89 putstr(prevmsg_win
, 0, cw
->data
[cw
->maxcol
]);
92 cw
->maxcol
= cw
->rows
- 1;
93 if (!cw
->data
[cw
->maxcol
])
94 cw
->maxcol
= cw
->maxrow
;
95 } while (cw
->maxcol
!= cw
->maxrow
);
97 display_nhwindow(prevmsg_win
, TRUE
);
98 destroy_nhwindow(prevmsg_win
);
99 cw
->maxcol
= cw
->maxrow
;
100 ttyDisplay
->dismiss_more
= 0;
102 } else if (iflags
.prevmsg_window
== 's') { /* single */
103 ttyDisplay
->dismiss_more
= C('p'); /* <ctrl/P> allowed at --More-- */
106 if (cw
->maxcol
== cw
->maxrow
)
107 redotoplin(gt
.toplines
);
108 else if (cw
->data
[cw
->maxcol
])
109 redotoplin(cw
->data
[cw
->maxcol
]);
112 cw
->maxcol
= cw
->rows
- 1;
113 if (!cw
->data
[cw
->maxcol
])
114 cw
->maxcol
= cw
->maxrow
;
115 } while (morc
== C('p'));
116 ttyDisplay
->dismiss_more
= 0;
122 redotoplin(const char *str
)
124 int otoplin
= ttyDisplay
->toplin
;
127 if (!ttyDisplay
->topl_utf8
) {
128 if (ttyDisplay
->mixed
&& (*str
& 0x80)) {
129 /* kludge for the / command, the only time we ever want a */
130 /* graphics character on the top line */
131 g_putch((int) *str
++);
134 end_glyphout(); /* in case message printed during graphics output */
138 ttyDisplay
->toplin
= TOPLINE_NEED_MORE
;
139 if (ttyDisplay
->cury
&& otoplin
!= TOPLINE_SPECIAL_PROMPT
)
143 /* for use by tty_putstr() */
145 show_topl(const char *str
)
147 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
149 /* show if either STOP isn't set or current message specifies NOSTOP */
150 if ((cw
->flags
& (WIN_STOP
| WIN_NOSTOP
)) != WIN_STOP
) {
151 /* NOSTOP cancels persistent STOP and is a one-shot operation;
152 force both to be cleared (no-op for either bit that isn't set) */
153 cw
->flags
&= ~(WIN_STOP
| WIN_NOSTOP
);
155 if (ttyDisplay
->cury
&& ttyDisplay
->toplin
== TOPLINE_NON_EMPTY
)
156 tty_clear_nhwindow(WIN_MESSAGE
);
158 cw
->curx
= cw
->cury
= 0;
163 if (ttyDisplay
->cury
&& ttyDisplay
->toplin
!= TOPLINE_SPECIAL_PROMPT
)
164 ttyDisplay
->toplin
= TOPLINE_NON_EMPTY
;
168 /* used by update_topl(); also by tty_putstr() */
172 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
173 int idx
= cw
->maxrow
;
174 unsigned len
= strlen(gt
.toplines
) + 1;
176 if ((cw
->flags
& WIN_LOCKHISTORY
) || !*gt
.toplines
)
179 if (len
> (unsigned) cw
->datlen
[idx
]) {
182 len
+= (8 - (len
& 7)); /* pad up to next multiple of 8 */
183 cw
->data
[idx
] = (char *) alloc(len
);
184 cw
->datlen
[idx
] = (short) len
;
186 Strcpy(cw
->data
[idx
], gt
.toplines
);
187 if (!program_state
.in_checkpoint
) {
189 cw
->maxcol
= cw
->maxrow
= (idx
+ 1) % cw
->rows
;
194 addtopl(const char *s
)
196 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
198 tty_curs(BASE_WINDOW
, cw
->curx
+ 1, cw
->cury
);
201 ttyDisplay
->toplin
= TOPLINE_NEED_MORE
;
207 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
209 if (iflags
.debug_fuzzer
)
212 /* avoid recursion -- only happens from interrupts */
213 if (ttyDisplay
->inmore
)
216 ttyDisplay
->inmore
++;
218 if (ttyDisplay
->toplin
) {
219 tty_curs(BASE_WINDOW
, cw
->curx
+ 1, cw
->cury
);
220 if (cw
->curx
>= CO
- 8)
230 xwaitforspace("\033 ");
232 if (morc
== '\033') {
233 if (!(cw
->flags
& WIN_NOSTOP
))
234 cw
->flags
|= WIN_STOP
;
237 if (ttyDisplay
->toplin
&& cw
->cury
) {
238 docorner(1, cw
->cury
+ 1, 0);
239 cw
->curx
= cw
->cury
= 0;
241 } else if (morc
== '\033') {
242 cw
->curx
= cw
->cury
= 0;
246 ttyDisplay
->toplin
= TOPLINE_EMPTY
;
247 ttyDisplay
->inmore
= 0;
251 update_topl(const char *bp
)
256 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
257 boolean skip
= (cw
->flags
& (WIN_STOP
| WIN_NOSTOP
)) == WIN_STOP
;
259 /* If there is room on the line, print message on same line */
260 /* But messages like "You die..." deserve their own line */
262 if ((ttyDisplay
->toplin
== TOPLINE_NEED_MORE
|| skip
)
264 && n0
+ (int) strlen(gt
.toplines
) + 3 < CO
- 8 /* room for --More-- */
265 && (notdied
= strncmp(bp
, "You die", 7)) != 0) {
266 Strcat(gt
.toplines
, " ");
267 Strcat(gt
.toplines
, bp
);
273 if (ttyDisplay
->toplin
== TOPLINE_NEED_MORE
) {
275 } else if (cw
->cury
) { /* for toplin==TOPLINE_NON_EMPTY && cury > 1 */
276 docorner(1, cw
->cury
+ 1, 0); /* reset cury = 0 if redraw screen */
277 cw
->curx
= cw
->cury
= 0; /* from home--cls() & docorner(1,n,0) */
281 (void) strncpy(gt
.toplines
, bp
, TBUFSZ
);
282 gt
.toplines
[TBUFSZ
- 1] = 0;
284 for (tl
= gt
.toplines
; n0
>= CO
; ) {
286 for (tl
+= CO
- 1; tl
!= otl
; --tl
)
290 /* Eek! A huge token. Try splitting after it. */
291 tl
= strchr(otl
, ' ');
293 break; /* No choice but to spit it out whole. */
298 if (!notdied
) /* double negative => "You die"; avoid suppressing mesg */
299 cw
->flags
&= ~WIN_STOP
, skip
= FALSE
;
301 redotoplin(gt
.toplines
);
307 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
309 if (cw
== (struct WinDesc
*) 0)
310 panic("Putsym window MESSAGE nonexistent");
314 if (ttyDisplay
->curx
== 0 && ttyDisplay
->cury
> 0)
315 tty_curs(BASE_WINDOW
, CO
, (int) ttyDisplay
->cury
- 1);
317 nhassert(ttyDisplay
->curx
> 0);
319 cw
->curx
= ttyDisplay
->curx
;
323 ttyDisplay
->curx
= 0;
325 cw
->cury
= ttyDisplay
->cury
;
331 if (ttyDisplay
->curx
== CO
- 1)
332 topl_putsym('\n'); /* 1 <= curx < CO; avoid CO */
338 cw
->curx
= ttyDisplay
->curx
;
347 putsyms(const char *str
)
356 /* assume addtopl() has been done, so ttyDisplay->toplin is already set */
361 extern char erase_char
; /* from xxxtty.c; don't need kill_char */
363 /* returns a single keystroke; also sets 'yn_number' */
371 * Generic yes/no function. 'def' is the default (returned by space
372 * or return; 'esc' returns 'q', or 'n', or the default, depending on
373 * what's in the expected-response string. The 'query' string is
374 * printed before the user is asked about the string.
376 * If resp is NULL, any single character is accepted and returned.
377 * If not-NULL, only characters in it are allowed (exceptions: the
378 * quitchars are always allowed, and if it contains '#' then digits
379 * are allowed). If it includes an <esc>, anything beyond that won't
380 * be shown in the prompt to the user but will be acceptable as input.
384 boolean digit_ok
, allow_num
, preserve_case
= FALSE
;
385 struct WinDesc
*cw
= wins
[WIN_MESSAGE
];
390 if (ttyDisplay
->toplin
== TOPLINE_NEED_MORE
391 && (cw
->flags
& (WIN_STOP
| WIN_NOSTOP
)) != WIN_STOP
)
393 cw
->flags
&= ~(WIN_STOP
| WIN_NOSTOP
);
394 ttyDisplay
->toplin
= TOPLINE_SPECIAL_PROMPT
;
395 ttyDisplay
->inread
++;
397 char *rb
, respbuf
[QBUFSZ
];
399 allow_num
= (strchr(resp
, '#') != 0);
400 Strcpy(respbuf
, resp
);
401 /* normally we force lowercase, but if any uppercase letters
402 are present in the allowed response, preserve case;
403 check this before stripping the hidden choices */
404 for (rb
= respbuf
; *rb
; ++rb
)
405 if ('A' <= *rb
&& *rb
<= 'Z') {
406 preserve_case
= TRUE
;
409 /* any acceptable responses that follow <esc> aren't displayed */
410 if ((rb
= strchr(respbuf
, '\033')) != 0)
412 (void) strncpy(prompt
, query
, QBUFSZ
- 1);
413 prompt
[QBUFSZ
- 1] = '\0';
414 Sprintf(eos(prompt
), " [%s]", respbuf
);
416 Sprintf(eos(prompt
), " (%c)", def
);
417 /* not pline("%s ", prompt);
418 trailing space is wanted here in case of reprompt */
420 custompline(OVERRIDE_MSGTYPE
| SUPPRESS_HISTORY
, "%s", prompt
);
422 /* no restriction on allowed response, so always preserve case */
423 /* preserve_case = TRUE; -- moot since we're jumping to the end */
424 Sprintf(prompt
, "%s ", query
);
425 custompline(OVERRIDE_MSGTYPE
| SUPPRESS_HISTORY
, "%s", prompt
);
430 do { /* loop until we get valid input */
434 if (q
== '\020') { /* ctrl-P */
435 if (iflags
.prevmsg_window
!= 's') {
436 int sav
= ttyDisplay
->inread
;
437 ttyDisplay
->inread
= 0;
438 (void) tty_doprev_message();
439 ttyDisplay
->inread
= sav
;
440 tty_clear_nhwindow(WIN_MESSAGE
);
441 cw
->maxcol
= cw
->maxrow
;
445 (void) tty_doprev_message(); /* need two initially */
446 (void) tty_doprev_message();
449 q
= '\0'; /* force another loop iteration */
452 /* BUG[?]: this probably ought to check whether the
453 character which has just been read is an acceptable
454 response; if so, skip the reprompt and use it. */
455 tty_clear_nhwindow(WIN_MESSAGE
);
456 cw
->maxcol
= cw
->maxrow
;
459 q
= '\0'; /* force another loop iteration */
462 digit_ok
= allow_num
&& digit(q
);
464 if (strchr(resp
, 'q'))
466 else if (strchr(resp
, 'n'))
471 } else if (strchr(quitchars
, q
)) {
475 if (!strchr(resp
, q
) && !digit_ok
) {
478 } else if (q
== '#' || digit_ok
) {
479 char z
, digit_string
[2];
483 addtopl("#"), n_len
++;
484 digit_string
[1] = '\0';
487 addtopl(digit_string
), n_len
++;
491 do { /* loop until we get a non-digit */
496 long dgt
= (long) (z
- '0');
498 /* value = (10 * value) + (z - '0'); */
499 value
= AppendLongDigit(value
, dgt
);
501 break; /* overflow: try again */
503 addtopl(digit_string
), n_len
++;
504 } else if (z
== 'y' || strchr(quitchars
, z
)) {
506 value
= -1; /* abort */
507 z
= '\n'; /* break */
508 } else if (z
== erase_char
|| z
== '\b') {
514 removetopl(1), n_len
--;
517 value
= -1; /* abort */
525 q
= 'n'; /* 0 => "no" */
526 else { /* remove number from top line, then try again */
527 removetopl(n_len
), n_len
= 0;
535 Sprintf(rtmp
, "#%ld", yn_number
);
537 (void) key2txt(q
, rtmp
);
538 /* addtopl(rtmp); -- rewrite gt.toplines instead */
539 Sprintf(gt
.toplines
, "%s%s", prompt
, rtmp
);
541 dumplogmsg(gt
.toplines
);
543 ttyDisplay
->inread
--;
544 ttyDisplay
->toplin
= TOPLINE_NON_EMPTY
;
545 if (ttyDisplay
->intr
)
547 if (wins
[WIN_MESSAGE
]->cury
)
548 tty_clear_nhwindow(WIN_MESSAGE
);
553 /* shared by tty_getmsghistory() and tty_putmsghistory() */
554 static char **snapshot_mesgs
= 0;
556 /* collect currently available message history data into a sequential array;
557 optionally, purge that data from the active circular buffer set as we go */
560 boolean purge
) /* clear message history buffer as we copy it */
563 int i
, inidx
, outidx
;
566 /* paranoia (too early or too late panic save attempt?) */
567 if (WIN_MESSAGE
== WIN_ERR
|| !wins
[WIN_MESSAGE
])
569 cw
= wins
[WIN_MESSAGE
];
571 /* flush gt.toplines[], moving most recent message to history */
574 /* for a passive snapshot, we just copy pointers, so can't allow further
575 history updating to take place because that could clobber them */
577 cw
->flags
|= WIN_LOCKHISTORY
;
579 snapshot_mesgs
= (char **) alloc((cw
->rows
+ 1) * sizeof(char *));
582 for (i
= 0; i
< cw
->rows
; ++i
) {
583 snapshot_mesgs
[i
] = (char *) 0;
584 mesg
= cw
->data
[inidx
];
586 snapshot_mesgs
[outidx
++] = mesg
;
588 /* we're taking this pointer away; subsequent history
589 updates will eventually allocate a new one to replace it */
590 cw
->data
[inidx
] = (char *) 0;
591 cw
->datlen
[inidx
] = 0;
594 inidx
= (inidx
+ 1) % cw
->rows
;
596 snapshot_mesgs
[cw
->rows
] = (char *) 0; /* sentinel */
598 /* for a destructive snapshot, history is now completely empty */
600 cw
->maxcol
= cw
->maxrow
= 0;
603 /* release memory allocated to message history snapshot */
605 free_msghistory_snapshot(
606 boolean purged
) /* True: took history's pointers,
607 * False: just cloned them */
609 if (snapshot_mesgs
) {
610 /* snapshot pointers are no longer in use */
614 for (i
= 0; snapshot_mesgs
[i
]; ++i
)
615 free((genericptr_t
) snapshot_mesgs
[i
]);
618 free((genericptr_t
) snapshot_mesgs
), snapshot_mesgs
= (char **) 0;
620 /* history can resume being updated at will now... */
622 wins
[WIN_MESSAGE
]->flags
&= ~WIN_LOCKHISTORY
;
627 * This is called by the core save routines.
628 * Each time we are called, we return one string from the
629 * message history starting with the oldest message first.
630 * When none are left, we return a final null string.
632 * History is collected at the time of the first call.
633 * Any new messages issued after that point will not be
634 * included among the output of the subsequent calls.
637 tty_getmsghistory(boolean init
)
644 msghistory_snapshot(FALSE
);
648 if (snapshot_mesgs
) {
649 nextmesg
= snapshot_mesgs
[nxtidx
++];
651 result
= (char *) nextmesg
;
653 free_msghistory_snapshot(FALSE
);
660 * This is called by the core savefile restore routines.
661 * Each time we are called, we stuff the string into our message
662 * history recall buffer. The core will send the oldest message
663 * first (actually it sends them in the order they exist in the
664 * save file, but that is supposed to be the oldest first).
665 * These messages get pushed behind any which have been issued
666 * since this session with the program has been started, since
667 * they come from a previous session and logically precede
668 * anything (like "Restoring save file...") that's happened now.
670 * Called with a null pointer to finish up restoration.
672 * It's also called by the quest pager code when a block message
673 * has a one-line summary specified. We put that line directly
674 * into message history for ^P recall without having displayed it.
677 tty_putmsghistory(const char *msg
, boolean restoring_msghist
)
679 static boolean initd
= FALSE
;
682 if (restoring_msghist
&& !initd
) {
683 /* we're restoring history from the previous session, but new
684 messages have already been issued this session ("Restoring...",
685 for instance); collect current history (ie, those new messages),
686 and also clear it out so that nothing will be present when the
687 restored ones are being put into place */
688 msghistory_snapshot(TRUE
);
691 /* this suffices; there's no need to scrub saved_pline[] pointers */
692 gs
.saved_pline_index
= 0;
697 /* Caller is asking us to remember a top line that needed more.
698 Should we call more? This can happen when the player has set
699 iflags.force_invmenu and they attempt to shoot with nothing in
701 if (ttyDisplay
&& ttyDisplay
->toplin
== TOPLINE_NEED_MORE
)
702 ttyDisplay
->toplin
= TOPLINE_NON_EMPTY
;
704 /* move most recent message to history, make this become most recent */
706 Strcpy(gt
.toplines
, msg
);
708 dumplogmsg(gt
.toplines
);
710 } else if (snapshot_mesgs
) {
711 nhassert(ttyDisplay
== NULL
||
712 ttyDisplay
->toplin
!= TOPLINE_NEED_MORE
);
714 /* done putting arbitrary messages in; put the snapshot ones back */
715 for (idx
= 0; snapshot_mesgs
[idx
]; ++idx
) {
717 Strcpy(gt
.toplines
, snapshot_mesgs
[idx
]);
719 dumplogmsg(gt
.toplines
);
722 /* now release the snapshot */
723 free_msghistory_snapshot(TRUE
);
724 initd
= FALSE
; /* reset */
728 #endif /* TTY_GRAPHICS */