1 /* NetHack 3.6 wintty.c $NHDT-Date: 1575245194 2019/12/02 00:06:34 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.227 $ */
2 /* Copyright (c) David Cohrs, 1991 */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* JNetHack Copyright */
6 /* (c) Issei Numata 1994-2000 */
7 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2019 */
8 /* JNetHack may be freely redistributed. See license for details. */
11 * Neither a standard out nor character-based control codes should be
12 * part of the "tty look" windowing implementation.
16 /* It's still not clear I've caught all the cases for H2344. #undef this
17 * to back out the changes. */
26 #define MICRO /* The Mac is a MICRO only for this file, not in general! */
28 extern void msmsg(const char *, ...);
38 #ifdef CLIPPING /* might want SIGWINCH */
39 #if defined(BSD) || defined(ULTRIX) || defined(AIX_31) || defined(_BULL_SOURCE)
44 #ifdef TTY_TILES_ESCCODES
45 extern short glyph2tile[];
46 #define TILE_ANSI_COMMAND 'z'
47 #define AVTC_GLYPH_START 0
48 #define AVTC_GLYPH_END 1
49 #define AVTC_SELECT_WINDOW 2
50 #define AVTC_INLINE_SYNC 3
53 #ifdef HANGUP_HANDLING
55 * NetHack's core switches to a dummy windowing interface when it
56 * detects SIGHUP, but that's no help for any interface routine which
57 * is already in progress at the time, and there have been reports of
58 * runaway disconnected processes which use up all available CPU time.
59 * HUPSKIP() and HUPSKIP_RETURN(x) are used to try to cut them off so
60 * that they return to the core instead attempting more terminal I/O.
64 if (program_state.done_hup) { \
69 /* morc=ESC - in case we bypass xwaitforspace() which sets that */
70 #define HUPSKIP_RESULT(RES) \
72 if (program_state.done_hup) \
75 #else /* !HANGUP_HANDLING */
76 #define HUPSKIP() /*empty*/
77 #define HUPSKIP_RESULT(RES) /*empty*/
78 #endif /* ?HANGUP_HANDLING */
80 extern char mapped_menu_cmds[]; /* from options.c */
82 /* Interface definition, for windows.c */
83 struct window_procs tty_procs = {
87 | WC_TILED_MAP | WC_ASCII_MAP
92 | WC_COLOR | WC_HILITE_PET | WC_INVERSE | WC_EIGHT_BIT_IN),
94 #if defined(SELECTSAVED)
97 #if defined(STATUS_HILITES)
98 | WC2_HILITE_STATUS | WC2_HITPOINTBAR | WC2_FLUSH_STATUS
101 | WC2_DARKGRAY | WC2_SUPPRESS_HIST | WC2_STATUSLINES),
103 {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
105 {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},
107 tty_init_nhwindows, tty_player_selection, tty_askname, tty_get_nh_event,
108 tty_exit_nhwindows, tty_suspend_nhwindows, tty_resume_nhwindows,
109 tty_create_nhwindow, tty_clear_nhwindow, tty_display_nhwindow,
110 tty_destroy_nhwindow, tty_curs, tty_putstr, genl_putmixed,
111 tty_display_file, tty_start_menu, tty_add_menu, tty_end_menu,
112 tty_select_menu, tty_message_menu, tty_update_inventory, tty_mark_synch,
118 tty_update_positionbar,
120 tty_print_glyph, tty_raw_print, tty_raw_print_bold, tty_nhgetch,
121 tty_nh_poskey, tty_nhbell, tty_doprev_message, tty_yn_function,
122 tty_getlin, tty_get_ext_cmd, tty_number_pad, tty_delay_output,
123 #ifdef CHANGE_COLOR /* the Mac uses a palette device */
126 tty_change_background, set_tty_font_name,
128 tty_get_color_string,
131 /* other defs that really should go away (they're tty specific) */
132 tty_start_screen, tty_end_screen, genl_outrip,
133 tty_preference_update,
134 tty_getmsghistory, tty_putmsghistory,
136 genl_status_finish, tty_status_enablefield,
137 #ifdef STATUS_HILITES
142 genl_can_suspend_yes,
146 struct WinDesc *wins[MAXWIN];
147 struct DisplayDesc *ttyDisplay; /* the tty display descriptor */
149 extern void FDECL(cmov, (int, int)); /* from termcap.c */
150 extern void FDECL(nocmov, (int, int)); /* from termcap.c */
151 #if defined(UNIX) || defined(VMS)
152 static char obuf[BUFSIZ]; /* BUFSIZ is defined in stdio.h */
155 static char winpanicstr[] = "Bad window id %d";
156 char defmorestr[] = "--More--";
159 #if defined(USE_TILES) && defined(MSDOS)
160 boolean clipping = FALSE; /* clipping on? */
161 int clipx = 0, clipxmax = 0;
163 static boolean clipping = FALSE; /* clipping on? */
164 static int clipx = 0, clipxmax = 0;
166 static int clipy = 0, clipymax = 0;
167 #endif /* CLIPPING */
169 #if defined(USE_TILES) && defined(MSDOS)
170 extern void FDECL(adjust_cursor_flags, (struct WinDesc *));
173 #if defined(ASCIIGRAPH) && !defined(NO_TERMS)
174 boolean GFlag = FALSE;
175 boolean HE_resets_AS; /* see termcap.c */
178 #if defined(MICRO) || defined(WIN32CON)
179 static const char to_continue[] = "to continue";
180 #define getret() getreturn(to_continue)
182 STATIC_DCL void NDECL(getret);
184 STATIC_DCL void FDECL(bail, (const char *)); /* __attribute__((noreturn)) */
185 STATIC_DCL void NDECL(new_status_window);
186 STATIC_DCL void FDECL(erase_menu_or_text, (winid, struct WinDesc *,
188 STATIC_DCL void FDECL(free_window_info, (struct WinDesc *, BOOLEAN_P));
189 STATIC_DCL void FDECL(dmore, (struct WinDesc *, const char *));
190 STATIC_DCL void FDECL(set_item_state, (winid, int, tty_menu_item *));
191 STATIC_DCL void FDECL(set_all_on_page, (winid, tty_menu_item *,
193 STATIC_DCL void FDECL(unset_all_on_page, (winid, tty_menu_item *,
195 STATIC_DCL void FDECL(invert_all_on_page, (winid, tty_menu_item *,
196 tty_menu_item *, CHAR_P));
197 STATIC_DCL void FDECL(invert_all, (winid, tty_menu_item *,
198 tty_menu_item *, CHAR_P));
199 STATIC_DCL void FDECL(toggle_menu_attr, (BOOLEAN_P, int, int));
200 STATIC_DCL void FDECL(process_menu_window, (winid, struct WinDesc *));
201 STATIC_DCL void FDECL(process_text_window, (winid, struct WinDesc *));
202 STATIC_DCL tty_menu_item *FDECL(reverse, (tty_menu_item *));
203 STATIC_DCL const char *FDECL(compress_str, (const char *));
204 STATIC_DCL void FDECL(tty_putsym, (winid, int, int, CHAR_P));
205 STATIC_DCL void FDECL(setup_rolemenu, (winid, BOOLEAN_P, int, int, int));
206 STATIC_DCL void FDECL(setup_racemenu, (winid, BOOLEAN_P, int, int, int));
207 STATIC_DCL void FDECL(setup_gendmenu, (winid, BOOLEAN_P, int, int, int));
208 STATIC_DCL void FDECL(setup_algnmenu, (winid, BOOLEAN_P, int, int, int));
209 STATIC_DCL boolean NDECL(reset_role_filtering);
210 #ifdef STATUS_HILITES
211 STATIC_DCL boolean FDECL(check_fields, (BOOLEAN_P, int *));
212 STATIC_DCL void NDECL(render_status);
213 STATIC_DCL void FDECL(tty_putstatusfield, (const char *, int, int));
214 STATIC_DCL boolean NDECL(check_windowdata);
215 STATIC_DCL void NDECL(set_condition_length);
216 STATIC_DCL int FDECL(make_things_fit, (BOOLEAN_P));
217 STATIC_DCL void FDECL(shrink_enc, (int));
218 STATIC_DCL void FDECL(shrink_dlvl, (int));
219 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
220 STATIC_DCL void NDECL(status_sanity_check);
221 #endif /* NH_DEVEL_STATUS */
225 * A string containing all the default commands -- to add to a list
226 * of acceptable inputs.
228 static const char default_menu_cmds[] = {
229 MENU_FIRST_PAGE, MENU_LAST_PAGE, MENU_NEXT_PAGE,
230 MENU_PREVIOUS_PAGE, MENU_SELECT_ALL, MENU_UNSELECT_ALL,
231 MENU_INVERT_ALL, MENU_SELECT_PAGE, MENU_UNSELECT_PAGE,
232 MENU_INVERT_PAGE, MENU_SEARCH, 0 /* null terminator */
235 #ifdef TTY_TILES_ESCCODES
236 static int vt_tile_current_window = -2;
239 print_vt_code(i, c, d)
243 if (iflags.vt_tiledata) {
245 if (i == AVTC_SELECT_WINDOW) {
246 if (c == vt_tile_current_window)
248 vt_tile_current_window = c;
251 printf("\033[1;%d;%d;%d%c", i, c, d, TILE_ANSI_COMMAND);
253 printf("\033[1;%d;%d%c", i, c, TILE_ANSI_COMMAND);
255 printf("\033[1;%d%c", i, TILE_ANSI_COMMAND);
260 # define print_vt_code(i, c, d) ;
261 #endif /* !TTY_TILES_ESCCODES */
262 #define print_vt_code1(i) print_vt_code((i), -1, -1)
263 #define print_vt_code2(i,c) print_vt_code((i), (c), -1)
264 #define print_vt_code3(i,c,d) print_vt_code((i), (c), (d))
267 /* clean up and quit */
273 tty_exit_nhwindows(mesg);
274 nh_terminate(EXIT_SUCCESS);
278 #if defined(SIGWINCH) && defined(CLIPPING)
279 STATIC_DCL void FDECL(winch_handler, (int));
282 * This really ought to just set a flag like the hangup handler does,
283 * then check the flag at "safe" times, in case the signal arrives
284 * while something fragile is executing. Better to have a brief period
285 * where display updates don't fit the new size than for tty internals
286 * to become corrupted.
288 * 'winch_seen' has been "notyet" for a long time....
292 winch_handler(sig_unused) /* signal handler is called with at least 1 arg */
293 int sig_unused UNUSED;
295 int oldLI = LI, oldCO = CO, i;
296 register struct WinDesc *cw;
300 #define WINCH_MESSAGE "(SIGWINCH)"
302 (void) write(fileno(wc_tracelogf), WINCH_MESSAGE,
303 strlen(WINCH_MESSAGE));
308 /* For long running events such as multi-page menus and
309 * display_file(), we note the signal's occurance and
310 * hope the code there decides to handle the situation
311 * and reset the flag. There will be race conditions
312 * when handling this - code handlers so it doesn't matter.
317 if ((oldLI != LI || oldCO != CO) && ttyDisplay) {
318 ttyDisplay->rows = LI;
319 ttyDisplay->cols = CO;
321 cw = wins[BASE_WINDOW];
322 cw->rows = ttyDisplay->rows;
323 cw->cols = ttyDisplay->cols;
325 if (iflags.window_inited) {
326 cw = wins[WIN_MESSAGE];
327 cw->curx = cw->cury = 0;
331 i = ttyDisplay->toplin;
332 ttyDisplay->toplin = 0;
335 ttyDisplay->toplin = i;
340 for (i = WIN_INVEN; i < MAXWIN; i++)
341 if (wins[i] && wins[i]->active) {
343 addtopl("Press Return to continue: ");
346 (void) fflush(stdout);
355 /* destroy and recreate status window; extracted from winch_handler()
356 and augmented for use by tty_preference_update() */
360 if (WIN_STATUS != WIN_ERR) {
361 /* if it's shrinking, clear it before destroying so that
362 dropped portion won't show anything that's now becoming stale */
363 if (wins[WIN_STATUS]->maxrow > iflags.wc2_statuslines)
364 tty_clear_nhwindow(WIN_STATUS);
366 tty_destroy_nhwindow(WIN_STATUS), WIN_STATUS = WIN_ERR;
368 /* frees some status tracking data */
369 genl_status_finish();
370 /* creates status window and allocates tracking data */
372 tty_clear_nhwindow(WIN_STATUS); /* does some init, sets context.botlx */
373 #ifdef STATUS_HILITES
374 status_initialize(REASSESS_ONLY);
379 if (LI < 1 + ROWNO + iflags.wc2_statuslines) {
381 tty_cliparound(u.ux, u.uy);
392 tty_init_nhwindows(argcp, argv)
398 /* options aren't processed yet so wc2_statuslines might be 0;
399 make sure that it has a reasonable value during tty setup */
400 iflags.wc2_statuslines = (iflags.wc2_statuslines < 3) ? 2 : 3;
402 * Remember tty modes, to be restored on exit.
404 * gettty() must be called before tty_startup()
405 * due to ordering of LI/CO settings
406 * tty_startup() must be called before initoptions()
407 * due to ordering of graphics settings
409 #if defined(UNIX) || defined(VMS)
410 setbuf(stdout, obuf);
414 /* to port dependant tty setup */
415 tty_startup(&wid, &hgt);
416 setftty(); /* calls start_screen */
418 /* set up tty descriptor */
419 ttyDisplay = (struct DisplayDesc *) alloc(sizeof (struct DisplayDesc));
420 ttyDisplay->toplin = 0;
421 ttyDisplay->rows = hgt;
422 ttyDisplay->cols = wid;
423 ttyDisplay->curx = ttyDisplay->cury = 0;
424 ttyDisplay->inmore = ttyDisplay->inread = ttyDisplay->intr = 0;
425 ttyDisplay->dismiss_more = 0;
427 ttyDisplay->color = NO_COLOR;
429 ttyDisplay->attrs = 0;
431 /* set up the default windows */
432 BASE_WINDOW = tty_create_nhwindow(NHW_BASE);
433 wins[BASE_WINDOW]->active = 1;
435 ttyDisplay->lastwin = WIN_ERR;
437 #if defined(SIGWINCH) && defined(CLIPPING) && !defined(NO_SIGNAL)
438 (void) signal(SIGWINCH, (SIG_RET_TYPE) winch_handler);
441 tty_clear_nhwindow(BASE_WINDOW);
443 tty_putstr(BASE_WINDOW, 0, "");
444 for (i = 1; i <= 4; ++i)
445 tty_putstr(BASE_WINDOW, 0, copyright_banner_line(i));
446 tty_putstr(BASE_WINDOW, 0, "");
447 #if 0 /*JP*//*JPTB reversed*/
448 tty_putstr(BASE_WINDOW, 0, JA_COPYRIGHT_BANNER_A);
449 tty_putstr(BASE_WINDOW, 0, JA_COPYRIGHT_BANNER_B);
450 tty_putstr(BASE_WINDOW, 0, JA_COPYRIGHT_BANNER_C);
451 tty_putstr(BASE_WINDOW, 0, JA_COPYRIGHT_BANNER_D);
452 tty_putstr(BASE_WINDOW, 0, "");
454 tty_display_nhwindow(BASE_WINDOW, FALSE);
456 /* 'statuslines' defaults to SET_IN_FILE, allowed but invisible;
457 make it dynamically settable if feasible, otherwise visible */
458 if (tty_procs.wincap2 & WC2_STATUSLINES)
459 set_wc2_option_mod_status(WC2_STATUSLINES,
461 (LI < 1 + ROWNO + 2) ? DISP_IN_GAME :
467 tty_preference_update(pref)
470 if (!strcmp(pref, "statuslines") && iflags.window_inited) {
475 nttty_preference_update(pref);
477 genl_preference_update(pref);
482 /* try to reduce clutter in the code below... */
483 #define ROLE flags.initrole
484 #define RACE flags.initrace
485 #define GEND flags.initgend
486 #define ALGN flags.initalign
489 tty_player_selection()
491 int i, k, n, choice, nextpick;
492 boolean getconfirmation, picksomething;
494 char pbuf[QBUFSZ], plbuf[QBUFSZ];
497 menu_item *selected = 0;
499 /* Used to avoid "Is this ok?" if player has already specified all
500 * four facets of role.
501 * Note that rigid_role_checks might force any unspecified facets to
502 * have a specific value, but that will still require confirmation;
503 * player can specify the forced ones if avoiding that is demanded.
505 picksomething = (ROLE == ROLE_NONE || RACE == ROLE_NONE
506 || GEND == ROLE_NONE || ALGN == ROLE_NONE);
508 * choose randomly without asking for all unspecified facets.
510 if (flags.randomall && picksomething) {
511 if (ROLE == ROLE_NONE)
513 if (RACE == ROLE_NONE)
515 if (GEND == ROLE_NONE)
517 if (ALGN == ROLE_NONE)
521 /* prevent unnecessary prompting if role forces race (samurai) or gender
522 (valkyrie) or alignment (rogue), or race forces alignment (orc), &c */
525 /* Should we randomly pick for the player? */
526 if (ROLE == ROLE_NONE || RACE == ROLE_NONE || GEND == ROLE_NONE
527 || ALGN == ROLE_NONE) {
529 char *prompt = build_plselection_prompt(pbuf, QBUFSZ,
530 ROLE, RACE, GEND, ALGN);
532 /* this prompt string ends in "[ynaq]?":
533 y - game picks role,&c then asks player to confirm;
534 n - player manually chooses via menu selections;
535 a - like 'y', but skips confirmation and starts game;
538 tty_putstr(BASE_WINDOW, 0, "");
539 echoline = wins[BASE_WINDOW]->cury;
540 tty_putstr(BASE_WINDOW, 0, prompt);
542 pick4u = lowc(readchar());
543 if (index(quitchars, pick4u))
545 } while (!index(ynaqchars, pick4u));
546 if ((int) strlen(prompt) + 1 < CO) {
547 /* Echo choice and move back down line */
548 tty_putsym(BASE_WINDOW, (int) strlen(prompt) + 1, echoline,
550 tty_putstr(BASE_WINDOW, 0, "");
552 /* Otherwise it's hard to tell where to echo, and things are
553 * wrapping a bit messily anyway, so (try to) make sure the next
554 * question shows up well and doesn't get wrapped at the
555 * bottom of the window.
557 tty_clear_nhwindow(BASE_WINDOW);
559 if (pick4u != 'y' && pick4u != 'a' && pick4u != 'n')
566 if (nextpick == RS_ROLE) {
568 /* Select a role, if necessary;
569 we'll try to be compatible with pre-selected
570 race/gender/alignment, but may not succeed. */
572 /* Process the choice */
573 if (pick4u == 'y' || pick4u == 'a' || ROLE == ROLE_RANDOM) {
574 /* Pick a random role */
575 k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
577 tty_putstr(BASE_WINDOW, 0, "Incompatible role!");
581 /* Prompt for a role */
582 tty_clear_nhwindow(BASE_WINDOW);
583 role_selection_prolog(RS_ROLE, BASE_WINDOW);
584 win = create_nhwindow(NHW_MENU);
586 /* populate the menu with role choices */
587 setup_rolemenu(win, TRUE, RACE, GEND, ALGN);
588 /* add miscellaneous menu entries */
589 role_menu_extra(ROLE_RANDOM, win, TRUE);
590 any = zeroany; /* separator, not a choice */
591 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "",
593 role_menu_extra(RS_RACE, win, FALSE);
594 role_menu_extra(RS_GENDER, win, FALSE);
595 role_menu_extra(RS_ALGNMNT, win, FALSE);
597 role_menu_extra(RS_filter, win, FALSE);
598 role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
600 Strcpy(pbuf, "Pick a role or profession");
602 Strcpy(pbuf, "
\90E
\8bÆ
\82ð
\91I
\82ñ
\82Å
\82
\82¾
\82³
\82¢");
604 n = select_menu(win, PICK_ONE, &selected);
606 * PICK_ONE with preselected choice behaves strangely:
607 * n == -1 -- <escape>, so use quit choice;
608 * n == 0 -- explicitly chose preselected entry,
609 * toggling it off, so use it;
610 * n == 1 -- implicitly chose preselected entry
611 * with <space> or <return>;
612 * n == 2 -- explicitly chose a different entry, so
613 * both it and preselected one are in list.
616 choice = selected[0].item.a_int;
617 if (n > 1 && choice == ROLE_RANDOM)
618 choice = selected[1].item.a_int;
620 choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
622 free((genericptr_t) selected), selected = 0;
623 destroy_nhwindow(win);
625 if (choice == ROLE_NONE) {
626 goto give_up; /* Selected quit */
627 } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
628 ALGN = k = ROLE_NONE;
629 nextpick = RS_ALGNMNT;
630 } else if (choice == RS_menu_arg(RS_GENDER)) {
631 GEND = k = ROLE_NONE;
632 nextpick = RS_GENDER;
633 } else if (choice == RS_menu_arg(RS_RACE)) {
634 RACE = k = ROLE_NONE;
636 } else if (choice == RS_menu_arg(RS_filter)) {
637 ROLE = k = ROLE_NONE;
638 (void) reset_role_filtering();
640 } else if (choice == ROLE_RANDOM) {
641 k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
652 if (nextpick == RS_RACE) {
653 nextpick = (ROLE < 0) ? RS_ROLE : RS_GENDER;
654 /* Select a race, if necessary;
655 force compatibility with role, try for compatibility
656 with pre-selected gender/alignment. */
657 if (RACE < 0 || !validrace(ROLE, RACE)) {
658 /* no race yet, or pre-selected race not valid */
659 if (pick4u == 'y' || pick4u == 'a' || RACE == ROLE_RANDOM) {
660 k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
662 tty_putstr(BASE_WINDOW, 0, "Incompatible race!");
665 } else { /* pick4u == 'n' */
666 /* Count the number of valid races */
667 n = 0; /* number valid */
668 k = 0; /* valid race */
669 for (i = 0; races[i].noun; i++)
670 if (ok_race(ROLE, i, GEND, ALGN)) {
675 for (i = 0; races[i].noun; i++)
676 if (validrace(ROLE, i)) {
681 /* Permit the user to pick, if there is more than one */
683 tty_clear_nhwindow(BASE_WINDOW);
684 role_selection_prolog(RS_RACE, BASE_WINDOW);
685 win = create_nhwindow(NHW_MENU);
687 any = zeroany; /* zero out all bits */
688 /* populate the menu with role choices */
689 setup_racemenu(win, TRUE, ROLE, GEND, ALGN);
690 /* add miscellaneous menu entries */
691 role_menu_extra(ROLE_RANDOM, win, TRUE);
692 any.a_int = 0; /* separator, not a choice */
693 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "",
695 role_menu_extra(RS_ROLE, win, FALSE);
696 role_menu_extra(RS_GENDER, win, FALSE);
697 role_menu_extra(RS_ALGNMNT, win, FALSE);
699 role_menu_extra(RS_filter, win, FALSE);
700 role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
702 Strcpy(pbuf, "Pick a race or species");
704 Strcpy(pbuf, "
\8eí
\91°
\82ð
\91I
\82ñ
\82Å
\82
\82¾
\82³
\82¢");
706 n = select_menu(win, PICK_ONE, &selected);
708 choice = selected[0].item.a_int;
709 if (n > 1 && choice == ROLE_RANDOM)
710 choice = selected[1].item.a_int;
712 choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
714 free((genericptr_t) selected), selected = 0;
715 destroy_nhwindow(win);
717 if (choice == ROLE_NONE) {
718 goto give_up; /* Selected quit */
719 } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
720 ALGN = k = ROLE_NONE;
721 nextpick = RS_ALGNMNT;
722 } else if (choice == RS_menu_arg(RS_GENDER)) {
723 GEND = k = ROLE_NONE;
724 nextpick = RS_GENDER;
725 } else if (choice == RS_menu_arg(RS_ROLE)) {
726 ROLE = k = ROLE_NONE;
728 } else if (choice == RS_menu_arg(RS_filter)) {
729 RACE = k = ROLE_NONE;
730 if (reset_role_filtering())
734 } else if (choice == ROLE_RANDOM) {
735 k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
747 if (nextpick == RS_GENDER) {
748 nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE
750 /* Select a gender, if necessary;
751 force compatibility with role/race, try for compatibility
752 with pre-selected alignment. */
753 if (GEND < 0 || !validgend(ROLE, RACE, GEND)) {
754 /* no gender yet, or pre-selected gender not valid */
755 if (pick4u == 'y' || pick4u == 'a' || GEND == ROLE_RANDOM) {
756 k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
758 tty_putstr(BASE_WINDOW, 0, "Incompatible gender!");
759 k = randgend(ROLE, RACE);
761 } else { /* pick4u == 'n' */
762 /* Count the number of valid genders */
763 n = 0; /* number valid */
764 k = 0; /* valid gender */
765 for (i = 0; i < ROLE_GENDERS; i++)
766 if (ok_gend(ROLE, RACE, i, ALGN)) {
771 for (i = 0; i < ROLE_GENDERS; i++)
772 if (validgend(ROLE, RACE, i)) {
777 /* Permit the user to pick, if there is more than one */
779 tty_clear_nhwindow(BASE_WINDOW);
780 role_selection_prolog(RS_GENDER, BASE_WINDOW);
781 win = create_nhwindow(NHW_MENU);
783 any = zeroany; /* zero out all bits */
784 /* populate the menu with gender choices */
785 setup_gendmenu(win, TRUE, ROLE, RACE, ALGN);
786 /* add miscellaneous menu entries */
787 role_menu_extra(ROLE_RANDOM, win, TRUE);
788 any.a_int = 0; /* separator, not a choice */
789 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "",
791 role_menu_extra(RS_ROLE, win, FALSE);
792 role_menu_extra(RS_RACE, win, FALSE);
793 role_menu_extra(RS_ALGNMNT, win, FALSE);
795 role_menu_extra(RS_filter, win, FALSE);
796 role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
798 Strcpy(pbuf, "Pick a gender or sex");
800 Strcpy(pbuf, "
\90«
\95Ê
\82ð
\91I
\82ñ
\82Å
\82
\82¾
\82³
\82¢");
802 n = select_menu(win, PICK_ONE, &selected);
804 choice = selected[0].item.a_int;
805 if (n > 1 && choice == ROLE_RANDOM)
806 choice = selected[1].item.a_int;
808 choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
810 free((genericptr_t) selected), selected = 0;
811 destroy_nhwindow(win);
813 if (choice == ROLE_NONE) {
814 goto give_up; /* Selected quit */
815 } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
816 ALGN = k = ROLE_NONE;
817 nextpick = RS_ALGNMNT;
818 } else if (choice == RS_menu_arg(RS_RACE)) {
819 RACE = k = ROLE_NONE;
821 } else if (choice == RS_menu_arg(RS_ROLE)) {
822 ROLE = k = ROLE_NONE;
824 } else if (choice == RS_menu_arg(RS_filter)) {
825 GEND = k = ROLE_NONE;
826 if (reset_role_filtering())
829 nextpick = RS_GENDER;
830 } else if (choice == ROLE_RANDOM) {
831 k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
833 k = randgend(ROLE, RACE);
840 } /* needed gender */
841 } /* picking gender */
843 if (nextpick == RS_ALGNMNT) {
844 nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE : RS_GENDER;
845 /* Select an alignment, if necessary;
846 force compatibility with role/race/gender. */
847 if (ALGN < 0 || !validalign(ROLE, RACE, ALGN)) {
848 /* no alignment yet, or pre-selected alignment not valid */
849 if (pick4u == 'y' || pick4u == 'a' || ALGN == ROLE_RANDOM) {
850 k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
852 tty_putstr(BASE_WINDOW, 0, "Incompatible alignment!");
853 k = randalign(ROLE, RACE);
855 } else { /* pick4u == 'n' */
856 /* Count the number of valid alignments */
857 n = 0; /* number valid */
858 k = 0; /* valid alignment */
859 for (i = 0; i < ROLE_ALIGNS; i++)
860 if (ok_align(ROLE, RACE, GEND, i)) {
865 for (i = 0; i < ROLE_ALIGNS; i++)
866 if (validalign(ROLE, RACE, i)) {
871 /* Permit the user to pick, if there is more than one */
873 tty_clear_nhwindow(BASE_WINDOW);
874 role_selection_prolog(RS_ALGNMNT, BASE_WINDOW);
875 win = create_nhwindow(NHW_MENU);
877 any = zeroany; /* zero out all bits */
878 setup_algnmenu(win, TRUE, ROLE, RACE, GEND);
879 role_menu_extra(ROLE_RANDOM, win, TRUE);
880 any.a_int = 0; /* separator, not a choice */
881 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "",
883 role_menu_extra(RS_ROLE, win, FALSE);
884 role_menu_extra(RS_RACE, win, FALSE);
885 role_menu_extra(RS_GENDER, win, FALSE);
887 role_menu_extra(RS_filter, win, FALSE);
888 role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
890 Strcpy(pbuf, "Pick an alignment or creed");
892 Strcpy(pbuf, "
\91®
\90«
\82ð
\91I
\82ñ
\82Å
\82
\82¾
\82³
\82¢");
894 n = select_menu(win, PICK_ONE, &selected);
896 choice = selected[0].item.a_int;
897 if (n > 1 && choice == ROLE_RANDOM)
898 choice = selected[1].item.a_int;
900 choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
902 free((genericptr_t) selected), selected = 0;
903 destroy_nhwindow(win);
905 if (choice == ROLE_NONE) {
906 goto give_up; /* Selected quit */
907 } else if (choice == RS_menu_arg(RS_GENDER)) {
908 GEND = k = ROLE_NONE;
909 nextpick = RS_GENDER;
910 } else if (choice == RS_menu_arg(RS_RACE)) {
911 RACE = k = ROLE_NONE;
913 } else if (choice == RS_menu_arg(RS_ROLE)) {
914 ROLE = k = ROLE_NONE;
916 } else if (choice == RS_menu_arg(RS_filter)) {
917 ALGN = k = ROLE_NONE;
918 if (reset_role_filtering())
921 nextpick = RS_ALGNMNT;
922 } else if (choice == ROLE_RANDOM) {
923 k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
925 k = randalign(ROLE, RACE);
932 } /* needed alignment */
933 } /* picking alignment */
935 } while (ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0);
938 * Role, race, &c have now been determined;
939 * ask for confirmation and maybe go back to choose all over again.
941 * Uses ynaq for familiarity, although 'a' is usually a
942 * superset of 'y' but here is an alternate form of 'n'.
944 * title: Is this ok? [ynaq]
946 * text: $name, $alignment $gender $race $role
948 * menu: y + yes; play
950 * maybe: a - no; rename hero
954 getconfirmation = (picksomething && pick4u != 'a' && !flags.randomall);
955 while (getconfirmation) {
956 tty_clear_nhwindow(BASE_WINDOW);
957 role_selection_prolog(ROLE_NONE, BASE_WINDOW);
958 win = create_nhwindow(NHW_MENU);
960 any = zeroany; /* zero out all bits */
962 if (!roles[ROLE].name.f
963 && (roles[ROLE].allow & ROLE_GENDMASK)
964 == (ROLE_MALE | ROLE_FEMALE))
966 Sprintf(plbuf, " %s", genders[GEND].adj);
968 Sprintf(plbuf, "%s
\82Ì", genders[GEND].adj);
970 *plbuf = '\0'; /* omit redundant gender */
972 Sprintf(pbuf, "%s, %s%s %s %s", plname, aligns[ALGN].adj, plbuf,
974 (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f
975 : roles[ROLE].name.m);
977 Sprintf(pbuf, "%s, %s%s%s%s", plname, aligns[ALGN].adj, plbuf,
979 (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f
980 : roles[ROLE].name.m);
982 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, pbuf,
984 /* blank separator */
986 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
987 /* [ynaq] menu choices */
990 add_menu(win, NO_GLYPH, &any, 'y', 0, ATR_NONE, "Yes; start game",
992 add_menu(win, NO_GLYPH, &any, 'y', 0, ATR_NONE, "
\82Í
\82¢;
\83Q
\81[
\83\80\82ð
\8en
\82ß
\82é",
995 add_menu(win, NO_GLYPH, &any, 'n', 0, ATR_NONE,
997 "No; choose role again", MENU_UNSELECTED);
999 "
\82¢
\82¢
\82¦;
\90E
\8bÆ
\82ð
\91I
\82Ñ
\92¼
\82·", MENU_UNSELECTED);
1000 if (iflags.renameallowed) {
1002 add_menu(win, NO_GLYPH, &any, 'a', 0, ATR_NONE,
1004 "Not yet; choose another name", MENU_UNSELECTED);
1006 "
\82Ü
\82¾;
\96¼
\91O
\82ð
\95Ï
\82¦
\82é", MENU_UNSELECTED);
1010 add_menu(win, NO_GLYPH, &any, 'q', 0, ATR_NONE, "Quit",
1012 add_menu(win, NO_GLYPH, &any, 'q', 0, ATR_NONE, "
\94²
\82¯
\82é",
1015 Sprintf(pbuf, "Is this ok? [yn%sq]", iflags.renameallowed ? "a" : "");
1017 Sprintf(pbuf, "
\82±
\82ê
\82Å
\82æ
\82¢
\81H [yn%sq]", iflags.renameallowed ? "a" : "");
1018 end_menu(win, pbuf);
1019 n = select_menu(win, PICK_ONE, &selected);
1020 /* [pick-one menus with a preselected entry behave oddly...] */
1021 choice = (n > 0) ? selected[n - 1].item.a_int : (n == 0) ? 1 : -1;
1023 free((genericptr_t) selected), selected = 0;
1024 destroy_nhwindow(win);
1027 default: /* 'q' or ESC */
1028 goto give_up; /* quit */
1032 * TODO: what, if anything, should be done if the name is
1033 * changed to or from "wizard" after port-specific startup
1034 * code has set flags.debug based on the original name?
1036 int saveROLE, saveRACE, saveGEND, saveALGN;
1038 iflags.renameinprogress = TRUE;
1039 /* plnamesuffix() can change any or all of ROLE, RACE,
1040 GEND, ALGN; we'll override that and honor only the name */
1041 saveROLE = ROLE, saveRACE = RACE, saveGEND = GEND,
1044 plnamesuffix(); /* calls askname() when plname[] is empty */
1045 ROLE = saveROLE, RACE = saveRACE, GEND = saveGEND,
1047 break; /* getconfirmation is still True */
1050 /* start fresh, but bypass "shall I pick everything for you?"
1051 step; any partial role selection via config file, command
1052 line, or name suffix is discarded this time */
1054 ROLE = RACE = GEND = ALGN = ROLE_NONE;
1057 case 1: /* 'y' or Space or Return/Enter */
1058 /* success; drop out through end of function */
1059 getconfirmation = FALSE;
1065 tty_display_nhwindow(BASE_WINDOW, FALSE);
1071 free((genericptr_t) selected); /* [obsolete] */
1078 reset_role_filtering()
1083 menu_item *selected = 0;
1085 win = create_nhwindow(NHW_MENU);
1089 /* no extra blank line preceding this entry; end_menu supplies one */
1090 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
1091 "Unacceptable roles", MENU_UNSELECTED);
1092 setup_rolemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
1094 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
1095 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
1096 "Unacceptable races", MENU_UNSELECTED);
1097 setup_racemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
1099 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
1100 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
1101 "Unacceptable genders", MENU_UNSELECTED);
1102 setup_gendmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
1104 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
1105 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
1106 "Unacceptable alignments", MENU_UNSELECTED);
1107 setup_algnmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
1109 end_menu(win, "Pick all that apply");
1110 n = select_menu(win, PICK_ANY, &selected);
1114 for (i = 0; i < n; i++)
1115 setrolefilter(selected[i].item.a_string);
1117 ROLE = RACE = GEND = ALGN = ROLE_NONE;
1120 free((genericptr_t) selected), selected = 0;
1121 destroy_nhwindow(win);
1122 return (n > 0) ? TRUE : FALSE;
1130 /* add entries a-Archeologist, b-Barbarian, &c to menu being built in 'win' */
1132 setup_rolemenu(win, filtering, race, gend, algn)
1134 boolean filtering; /* True => exclude filtered roles; False => filter reset */
1135 int race, gend, algn; /* all ROLE_NONE for !filtering case */
1140 char thisch, lastch = '\0', rolenamebuf[50];
1142 any = zeroany; /* zero out all bits */
1143 for (i = 0; roles[i].name.m; i++) {
1144 role_ok = ok_role(i, race, gend, algn);
1145 if (filtering && !role_ok)
1150 any.a_string = roles[i].name.m;
1152 thisch = lowc(*roles[i].name.m);
1154 thisch = lowc(*roles[i].filecode);
1155 if (thisch == lastch)
1156 thisch = highc(thisch);
1157 Strcpy(rolenamebuf, roles[i].name.m);
1158 if (roles[i].name.f) {
1159 /* role has distinct name for female (C,P) */
1161 /* female already chosen; replace male name */
1162 Strcpy(rolenamebuf, roles[i].name.f);
1163 } else if (gend < 0) {
1164 /* not chosen yet; append slash+female name */
1165 Strcat(rolenamebuf, "/");
1166 Strcat(rolenamebuf, roles[i].name.f);
1169 /* !filtering implies reset_role_filtering() where we want to
1170 mark this role as preseleted if current filter excludes it */
1171 add_menu(win, NO_GLYPH, &any, thisch, 0, ATR_NONE, an(rolenamebuf),
1172 (!filtering && !role_ok) ? MENU_SELECTED : MENU_UNSELECTED);
1178 setup_racemenu(win, filtering, role, gend, algn)
1181 int role, gend, algn;
1189 for (i = 0; races[i].noun; i++) {
1190 race_ok = ok_race(role, i, gend, algn);
1191 if (filtering && !race_ok)
1196 any.a_string = races[i].noun;
1198 this_ch = *races[i].noun;
1200 this_ch = lowc(*races[i].filecode);
1201 /* filtering: picking race, so choose by first letter, with
1202 capital letter as unseen accelerator;
1203 !filtering: resetting filter rather than picking, choose by
1204 capital letter since lowercase role letters will be present */
1205 add_menu(win, NO_GLYPH, &any,
1206 filtering ? this_ch : highc(this_ch),
1207 filtering ? highc(this_ch) : 0,
1208 ATR_NONE, races[i].noun,
1209 (!filtering && !race_ok) ? MENU_SELECTED : MENU_UNSELECTED);
1214 setup_gendmenu(win, filtering, role, race, algn)
1217 int role, race, algn;
1225 for (i = 0; i < ROLE_GENDERS; i++) {
1226 gend_ok = ok_gend(role, race, i, algn);
1227 if (filtering && !gend_ok)
1232 any.a_string = genders[i].adj;
1234 this_ch = *genders[i].adj;
1236 this_ch = lowc(*genders[i].filecode);
1237 /* (see setup_racemenu for explanation of selector letters
1238 and setup_rolemenu for preselection) */
1239 add_menu(win, NO_GLYPH, &any,
1240 filtering ? this_ch : highc(this_ch),
1241 filtering ? highc(this_ch) : 0,
1242 ATR_NONE, genders[i].adj,
1243 (!filtering && !gend_ok) ? MENU_SELECTED : MENU_UNSELECTED);
1248 setup_algnmenu(win, filtering, role, race, gend)
1251 int role, race, gend;
1259 for (i = 0; i < ROLE_ALIGNS; i++) {
1260 algn_ok = ok_align(role, race, gend, i);
1261 if (filtering && !algn_ok)
1267 any.a_string = aligns[i].adj;
1269 any.a_string = aligns[i].noun;
1271 this_ch = *aligns[i].adj;
1273 this_ch = lowc(*aligns[i].filecode);
1274 /* (see setup_racemenu for explanation of selector letters
1275 and setup_rolemenu for preselection) */
1277 add_menu(win, NO_GLYPH, &any,
1278 filtering ? this_ch : highc(this_ch),
1279 filtering ? highc(this_ch) : 0,
1280 ATR_NONE, aligns[i].adj,
1281 (!filtering && !algn_ok) ? MENU_SELECTED : MENU_UNSELECTED);
1283 add_menu(win, NO_GLYPH, &any,
1284 filtering ? this_ch : highc(this_ch),
1285 filtering ? highc(this_ch) : 0,
1286 ATR_NONE, aligns[i].noun,
1287 (!filtering && !algn_ok) ? MENU_SELECTED : MENU_UNSELECTED);
1293 * plname is filled either by an option (-u Player or -uPlayer) or
1294 * explicitly (by being the wizard) or by askname.
1295 * It may still contain a suffix denoting the role, etc.
1296 * Always called after init_nhwindows() and before display_gamewindows().
1302 static const char who_are_you[] = "Who are you? ";
1304 static const char who_are_you[] = "
\82 \82È
\82½
\82Í
\92N
\81H ";
1305 register int c, ct, tryct = 0;
1307 char ptmpname[PL_NSIZ];
1311 if (iflags.wc2_selectsaved && !iflags.renameinprogress)
1312 switch (restore_menu(BASE_WINDOW)) {
1314 bail("Until next time then..."); /* quit */
1317 break; /* no game chosen; start new game */
1319 return; /* plname[] has been set */
1321 #endif /* SELECTSAVED */
1323 tty_putstr(BASE_WINDOW, 0, "");
1327 bail("Giving up after 10 tries.\n");
1328 tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury - 1);
1330 tty_putstr(BASE_WINDOW, 0, "Enter a name for your character...");
1332 tty_putstr(BASE_WINDOW, 0, "
\82 \82È
\82½
\82Ì
\83L
\83\83\83\89\83N
\83^
\82Ì
\96¼
\91O
\82Í
\81H");
1334 /* erase previous prompt (in case of ESC after partial response) */
1335 tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury), cl_end();
1337 tty_putstr(BASE_WINDOW, 0, who_are_you);
1338 tty_curs(BASE_WINDOW, (int) (sizeof who_are_you),
1339 wins[BASE_WINDOW]->cury - 1);
1341 while ((c = tty_nhgetch()) != '\n') {
1349 } /* continue outer loop */
1350 #if defined(WIN32CON)
1352 bail("^C abort.\n");
1354 /* some people get confused when their erase char is not ^H */
1355 if (c == '\b' || c == '\177') {
1364 #if defined(MICRO) || defined(WIN32CON)
1365 #if defined(WIN32CON) || defined(MSDOS)
1366 backsp(); /* \b is visible on NT */
1367 (void) putchar(' ');
1373 (void) putchar('\b');
1374 (void) putchar(' ');
1375 (void) putchar('\b');
1379 if(is_kanji2(ptmpname, ct))
1384 #if defined(UNIX) || defined(VMS)
1386 if (c != '-' && c != '@')
1387 if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z')
1388 /* reject leading digit but allow digits elsewhere
1389 (avoids ambiguity when character name gets
1390 appended to uid to construct save file name) */
1391 && !(c >= '0' && c <= '9' && ct > 0))
1395 if (ct < (int) (sizeof plname) - 1) {
1398 if (iflags.grmode) {
1405 wins[BASE_WINDOW]->curx++;
1434 /* move to next line to simulate echo of user's <return> */
1435 tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury + 1);
1437 Strcpy(plname, str2ic(ptmpname));
1440 /* since we let user pick an arbitrary name now, he/she can pick
1441 another one during role selection */
1442 iflags.renameallowed = TRUE;
1451 #if !defined(MICRO) && !defined(WIN32CON)
1463 xputs(iflags.cbreak ? "space" : "return");
1464 xputs(" to continue: ");
1472 tty_suspend_nhwindows(str)
1475 settty(str); /* calls end_screen, perhaps raw_print */
1477 tty_raw_print(""); /* calls fflush(stdout) */
1481 tty_resume_nhwindows()
1484 setftty(); /* calls start_screen */
1489 tty_exit_nhwindows(str)
1494 tty_suspend_nhwindows(str);
1496 * Disable windows to avoid calls to window routines.
1498 free_pickinv_cache(); /* reset its state as well as tear down window */
1499 for (i = 0; i < MAXWIN; i++) {
1500 if (i == BASE_WINDOW)
1501 continue; /* handle wins[BASE_WINDOW] last */
1503 #ifdef FREE_ALL_MEMORY
1504 free_window_info(wins[i], TRUE);
1505 free((genericptr_t) wins[i]);
1507 wins[i] = (struct WinDesc *) 0;
1510 WIN_MAP = WIN_MESSAGE = WIN_INVEN = WIN_ERR; /* these are all gone now */
1511 WIN_STATUS = WIN_ERR;
1512 #ifdef FREE_ALL_MEMORY
1513 if (BASE_WINDOW != WIN_ERR && wins[BASE_WINDOW]) {
1514 free_window_info(wins[BASE_WINDOW], TRUE);
1515 free((genericptr_t) wins[BASE_WINDOW]);
1516 wins[BASE_WINDOW] = (struct WinDesc *) 0;
1517 BASE_WINDOW = WIN_ERR;
1519 free((genericptr_t) ttyDisplay);
1520 ttyDisplay = (struct DisplayDesc *) 0;
1523 #ifndef NO_TERMS /*(until this gets added to the window interface)*/
1524 tty_shutdown(); /* cleanup termcap/terminfo/whatever */
1529 iflags.window_inited = 0;
1533 tty_create_nhwindow(type)
1536 struct WinDesc *newwin;
1540 for (newid = 0; newid < MAXWIN; ++newid)
1541 if (wins[newid] == 0)
1543 if (newid == MAXWIN) {
1544 panic("No window slots!");
1549 newwin = (struct WinDesc *) alloc(sizeof (struct WinDesc));
1550 wins[newid] = newwin;
1552 newwin->type = type;
1554 newwin->active = FALSE;
1555 newwin->curx = newwin->cury = 0;
1556 newwin->morestr = 0;
1557 newwin->mlist = (tty_menu_item *) 0;
1558 newwin->plist = (tty_menu_item **) 0;
1559 newwin->npages = newwin->plist_size = newwin->nitems = newwin->how = 0;
1562 /* base window, used for absolute movement on the screen */
1563 newwin->offx = newwin->offy = 0;
1564 newwin->rows = ttyDisplay->rows;
1565 newwin->cols = ttyDisplay->cols;
1566 newwin->maxrow = newwin->maxcol = 0;
1569 /* message window, 1 line long, very wide, top of screen */
1570 newwin->offx = newwin->offy = 0;
1572 if (iflags.msg_history < 20)
1573 iflags.msg_history = 20;
1574 else if (iflags.msg_history > 60)
1575 iflags.msg_history = 60;
1576 newwin->maxrow = newwin->rows = iflags.msg_history;
1577 newwin->maxcol = newwin->cols = 0;
1580 /* status window, 2 or 3 lines long, full width, bottom of screen */
1581 if (iflags.wc2_statuslines < 2
1583 || (LI < 1 + ROWNO + 3)
1585 || iflags.wc2_statuslines > 3)
1586 iflags.wc2_statuslines = 2;
1588 rowoffset = ttyDisplay->rows - iflags.wc2_statuslines;
1589 #if defined(USE_TILES) && defined(MSDOS)
1590 if (iflags.grmode) {
1591 newwin->offy = rowoffset;
1594 newwin->offy = min(rowoffset, ROWNO + 1);
1595 newwin->rows = newwin->maxrow = iflags.wc2_statuslines;
1596 newwin->cols = newwin->maxcol = ttyDisplay->cols;
1599 /* map window, ROWNO lines long, full width, below message window */
1602 newwin->rows = ROWNO;
1603 newwin->cols = COLNO;
1604 newwin->maxrow = 0; /* no buffering done -- let gbuf do it */
1609 /* inventory/menu window, variable length, full width, top of screen;
1610 help window, the same, different semantics for display, etc */
1611 newwin->offx = newwin->offy = 0;
1613 newwin->cols = ttyDisplay->cols;
1614 newwin->maxrow = newwin->maxcol = 0;
1617 panic("Tried to create window type %d\n", (int) type);
1622 if (newwin->maxrow) {
1623 newwin->data = (char **) alloc(
1624 (unsigned) (newwin->maxrow * sizeof (char *)));
1625 newwin->datlen = (short *) alloc(
1626 (unsigned) (newwin->maxrow * sizeof (short)));
1627 for (i = 0; i < newwin->maxrow; i++) {
1628 if (newwin->maxcol) { /* WIN_STATUS */
1629 newwin->data[i] = (char *) alloc((unsigned) newwin->maxcol);
1630 newwin->datlen[i] = (short) newwin->maxcol;
1632 newwin->data[i] = (char *) 0;
1633 newwin->datlen[i] = 0;
1636 if (newwin->type == NHW_MESSAGE)
1639 newwin->data = (char **) 0;
1640 newwin->datlen = (short *) 0;
1647 erase_menu_or_text(window, cw, clear)
1652 if (cw->offx == 0) {
1654 tty_curs(window, 1, 0);
1662 docorner((int) cw->offx, cw->maxrow + 1);
1667 free_window_info(cw, free_data)
1674 if (cw == wins[WIN_MESSAGE] && cw->rows > cw->maxrow)
1675 cw->maxrow = cw->rows; /* topl data */
1676 for (i = 0; i < cw->maxrow; i++)
1678 free((genericptr_t) cw->data[i]);
1679 cw->data[i] = (char *) 0;
1684 free((genericptr_t) cw->data);
1685 cw->data = (char **) 0;
1687 free((genericptr_t) cw->datlen);
1688 cw->datlen = (short *) 0;
1692 cw->maxrow = cw->maxcol = 0;
1694 tty_menu_item *temp;
1696 while ((temp = cw->mlist) != 0) {
1697 cw->mlist = temp->next;
1699 free((genericptr_t) temp->str);
1700 free((genericptr_t) temp);
1704 free((genericptr_t) cw->plist);
1707 cw->plist_size = cw->npages = cw->nitems = cw->how = 0;
1709 free((genericptr_t) cw->morestr);
1715 tty_clear_nhwindow(window)
1719 register struct WinDesc *cw = 0;
1722 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
1723 panic(winpanicstr, window);
1724 ttyDisplay->lastwin = window;
1726 print_vt_code2(AVTC_SELECT_WINDOW, window);
1730 if (ttyDisplay->toplin) {
1734 docorner(1, cw->cury + 1);
1735 ttyDisplay->toplin = 0;
1741 for (i = 0; i < m; ++i) {
1742 tty_curs(window, 1, i);
1745 for (j = 0; j < n - 1; ++j)
1746 cw->data[i][j] = ' ';
1747 cw->data[i][n - 1] = '\0';
1748 /*finalx[i][NOW] = finalx[i][BEFORE] = 0;*/
1753 /* cheap -- clear the whole thing and tell nethack to redraw botl */
1758 /*for (i = 0; i < cw->maxrow; ++i) */
1759 /* finalx[i][NOW] = finalx[i][BEFORE] = 0;*/
1764 erase_menu_or_text(window, cw, TRUE);
1765 free_window_info(cw, FALSE);
1768 cw->curx = cw->cury = 0;
1772 toggle_menu_curr(window, curr, lineno, in_view, counting, count)
1774 tty_menu_item *curr;
1776 boolean in_view, counting;
1779 if (curr->selected) {
1780 if (counting && count > 0) {
1781 curr->count = count;
1783 set_item_state(window, lineno, curr);
1785 } else { /* change state */
1786 curr->selected = FALSE;
1789 set_item_state(window, lineno, curr);
1792 } else { /* !selected */
1793 if (counting && count > 0) {
1794 curr->count = count;
1795 curr->selected = TRUE;
1797 set_item_state(window, lineno, curr);
1799 } else if (!counting) {
1800 curr->selected = TRUE;
1802 set_item_state(window, lineno, curr);
1805 /* do nothing counting&&count==0 */
1812 register struct WinDesc *cw;
1813 const char *s; /* valid responses */
1815 const char *prompt = cw->morestr ? cw->morestr : defmorestr;
1816 int offset = (cw->type == NHW_TEXT) ? 1 : 2;
1819 tty_curs(BASE_WINDOW, (int) ttyDisplay->curx + offset,
1820 (int) ttyDisplay->cury);
1825 ttyDisplay->curx += strlen(prompt);
1827 jputchar('\0'); /* reset */
1845 set_item_state(window, lineno, item)
1848 tty_menu_item *item;
1850 char ch = item->selected ? (item->count == -1L ? '+' : '#') : '-';
1853 tty_curs(window, 4, lineno);
1854 term_start_attr(item->attr);
1858 (void) jputchar(ch);
1860 term_end_attr(item->attr);
1864 set_all_on_page(window, page_start, page_end)
1866 tty_menu_item *page_start, *page_end;
1868 tty_menu_item *curr;
1871 for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next)
1872 if (curr->identifier.a_void && !curr->selected) {
1873 curr->selected = TRUE;
1874 set_item_state(window, n, curr);
1879 unset_all_on_page(window, page_start, page_end)
1881 tty_menu_item *page_start, *page_end;
1883 tty_menu_item *curr;
1886 for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next)
1887 if (curr->identifier.a_void && curr->selected) {
1888 curr->selected = FALSE;
1890 set_item_state(window, n, curr);
1895 invert_all_on_page(window, page_start, page_end, acc)
1897 tty_menu_item *page_start, *page_end;
1898 char acc; /* group accelerator, 0 => all */
1900 tty_menu_item *curr;
1903 for (n = 0, curr = page_start; curr != page_end; n++, curr = curr->next)
1904 if (curr->identifier.a_void && (acc == 0 || curr->gselector == acc)) {
1905 if (curr->selected) {
1906 curr->selected = FALSE;
1909 curr->selected = TRUE;
1910 set_item_state(window, n, curr);
1915 * Invert all entries that match the give group accelerator (or all if zero).
1918 invert_all(window, page_start, page_end, acc)
1920 tty_menu_item *page_start, *page_end;
1921 char acc; /* group accelerator, 0 => all */
1923 tty_menu_item *curr;
1924 boolean on_curr_page;
1925 struct WinDesc *cw = wins[window];
1927 invert_all_on_page(window, page_start, page_end, acc);
1929 /* invert the rest */
1930 for (on_curr_page = FALSE, curr = cw->mlist; curr; curr = curr->next) {
1931 if (curr == page_start)
1932 on_curr_page = TRUE;
1933 else if (curr == page_end)
1934 on_curr_page = FALSE;
1936 if (!on_curr_page && curr->identifier.a_void
1937 && (acc == 0 || curr->gselector == acc)) {
1938 if (curr->selected) {
1939 curr->selected = FALSE;
1942 curr->selected = TRUE;
1947 /* support menucolor in addition to caller-supplied attribute */
1949 toggle_menu_attr(on, color, attr)
1954 term_start_attr(attr);
1956 if (color != NO_COLOR)
1957 term_start_color(color);
1961 if (color != NO_COLOR)
1964 term_end_attr(attr);
1973 process_menu_window(window, cw)
1977 tty_menu_item *page_start, *page_end, *curr;
1979 int n, attr_n, curr_page, page_lines, resp_len;
1980 boolean finished, counting, reset_count;
1981 char *cp, *rp, resp[QBUFSZ], gacc[QBUFSZ], *msave, *morestr, really_morc;
1982 #define MENU_EXPLICIT_CHOICE 0x7f /* pseudo menu manipulation char */
1984 curr_page = page_lines = 0;
1985 page_start = page_end = 0;
1986 msave = cw->morestr; /* save the morestr */
1987 cw->morestr = morestr = (char *) alloc((unsigned) QBUFSZ);
1993 /* collect group accelerators; for PICK_NONE, they're ignored;
1994 for PICK_ONE, only those which match exactly one entry will be
1995 accepted; for PICK_ANY, those which match any entry are okay */
1997 if (cw->how != PICK_NONE) {
1999 #define GSELIDX(c) (c & 127) /* guard against `signed char' */
2001 for (i = 0; i < SIZE(gcnt); i++)
2003 for (n = 0, curr = cw->mlist; curr; curr = curr->next)
2004 if (curr->gselector && curr->gselector != curr->selector) {
2006 ++gcnt[GSELIDX(curr->gselector)];
2009 if (n > 0) /* at least one group accelerator found */
2010 for (rp = gacc, curr = cw->mlist; curr; curr = curr->next)
2011 if (curr->gselector && curr->gselector != curr->selector
2012 && !index(gacc, curr->gselector)
2013 && (cw->how == PICK_ANY
2014 || gcnt[GSELIDX(curr->gselector)] == 1)) {
2015 *rp++ = curr->gselector;
2016 *rp = '\0'; /* re-terminate for index() */
2019 resp_len = 0; /* lint suppression */
2021 /* loop until finished */
2031 /* new page to be displayed */
2032 if (curr_page < 0 || (cw->npages > 0 && curr_page >= cw->npages))
2033 panic("bad menu screen page #%d", curr_page);
2036 if (!cw->offx) { /* if not corner, do clearscreen */
2038 tty_curs(window, 1, 0);
2045 if (cw->npages > 0) {
2046 /* collect accelerators */
2047 page_start = cw->plist[curr_page];
2048 page_end = cw->plist[curr_page + 1];
2049 for (page_lines = 0, curr = page_start; curr != page_end;
2050 page_lines++, curr = curr->next) {
2051 int attr, color = NO_COLOR;
2054 *rp++ = curr->selector;
2056 tty_curs(window, 1, page_lines);
2061 (void) putchar(' ');
2063 (void) jputchar(' ');
2067 if (!iflags.use_menu_color
2068 || !get_menu_coloring(curr->str, &color, &attr))
2071 /* which character to start attribute highlighting;
2072 whole line for headers and such, after the selector
2073 character and space and selection indicator for menu
2074 lines (including fake ones that simulate grayed-out
2075 entries, so we don't rely on curr->identifier here) */
2076 attr_n = 0; /* whole line */
2077 if (curr->str[0] && curr->str[1] == ' '
2078 && curr->str[2] && index("-+#", curr->str[2])
2079 && curr->str[3] == ' ')
2080 /* [0]=letter, [1]==space, [2]=[-+#], [3]=space */
2081 attr_n = 4; /* [4:N]=entry description */
2084 * Don't use xputs() because (1) under unix it calls
2085 * tputstr() which will interpret a '*' as some kind
2086 * of padding information and (2) it calls xputc to
2087 * actually output the character. We're faster doing
2090 for (n = 0, cp = curr->str;
2093 (int) ++ttyDisplay->curx < (int) ttyDisplay->cols;
2095 (int) ttyDisplay->curx < (int) ttyDisplay->cols;
2099 if (n == attr_n && (color != NO_COLOR
2100 || attr != ATR_NONE))
2101 toggle_menu_attr(TRUE, color, attr);
2103 && curr->identifier.a_void != 0
2104 && curr->selected) {
2105 if (curr->count == -1L)
2107 (void) putchar('+'); /* all selected */
2109 (void) jputchar('+'); /* all selected */
2113 (void) putchar('#'); /* count selected */
2115 (void) jputchar('#'); /* count selected */
2119 (void) putchar(*cp);
2121 (void) jputchar(*cp);
2124 if (n > attr_n && (color != NO_COLOR || attr != ATR_NONE))
2125 toggle_menu_attr(FALSE, color, attr);
2126 } /* if npages > 0 */
2133 /* remember how many explicit menu choices there are */
2134 resp_len = (int) strlen(resp);
2136 /* corner window - clear extra lines from last page */
2138 for (n = page_lines + 1; n < cw->maxrow; n++) {
2139 tty_curs(window, 1, n);
2144 /* set extra chars.. */
2145 Strcat(resp, default_menu_cmds);
2146 Strcat(resp, " "); /* next page or end */
2147 Strcat(resp, "0123456789\033\n\r"); /* counts, quit */
2148 Strcat(resp, gacc); /* group accelerators */
2149 Strcat(resp, mapped_menu_cmds);
2153 Sprintf(cw->morestr, "(%d of %d)", curr_page + 1,
2156 Sprintf(cw->morestr, "(%d/%d)", curr_page + 1,
2160 Strcpy(cw->morestr, msave);
2162 Strcpy(cw->morestr, defmorestr);
2164 tty_curs(window, 1, page_lines);
2168 /* just put the cursor back... */
2169 tty_curs(window, (int) strlen(cw->morestr) + 2, page_lines);
2170 xwaitforspace(resp);
2173 really_morc = morc; /* (only used with MENU_EXPLICIT_CHOICE */
2174 if ((rp = index(resp, morc)) != 0 && rp < resp + resp_len)
2175 /* explicit menu selection; don't override it if it also
2176 happens to match a mapped menu command (such as ':' to
2177 look inside a container vs ':' to search) */
2178 morc = MENU_EXPLICIT_CHOICE;
2180 morc = map_menu_cmd(morc);
2184 /* special case: '0' is also the default ball class */
2185 if (!counting && index(gacc, morc))
2187 /* fall through to count the zero */
2198 count = (count * 10L) + (long) (morc - '0');
2200 * It is debatable whether we should allow 0 to
2201 * start a count. There is no difference if the
2202 * item is selected. If not selected, then
2205 * count starting zero: "zero b's"
2206 * ignore starting zero: "select b"
2208 * At present I don't know which is better.
2210 if (count != 0L) { /* ignore leading zeros */
2212 reset_count = FALSE;
2215 case '\033': /* cancel - from counting or loop */
2217 /* deselect everything */
2218 for (curr = cw->mlist; curr; curr = curr->next) {
2219 curr->selected = FALSE;
2222 cw->flags |= WIN_CANCELLED;
2225 /* else only stop count */
2227 case '\0': /* finished (commit) */
2230 /* only finished if we are actually picking something */
2231 if (cw->how != PICK_NONE) {
2235 /* else fall through */
2237 case MENU_NEXT_PAGE:
2238 if (cw->npages > 0 && curr_page != cw->npages - 1) {
2241 } else if (morc == ' ') {
2242 /* ' ' finishes menus here, but stop '>' doing the same. */
2246 case MENU_PREVIOUS_PAGE:
2247 if (cw->npages > 0 && curr_page != 0) {
2252 case MENU_FIRST_PAGE:
2253 if (cw->npages > 0 && curr_page != 0) {
2258 case MENU_LAST_PAGE:
2259 if (cw->npages > 0 && curr_page != cw->npages - 1) {
2261 curr_page = cw->npages - 1;
2264 case MENU_SELECT_PAGE:
2265 if (cw->how == PICK_ANY)
2266 set_all_on_page(window, page_start, page_end);
2268 case MENU_UNSELECT_PAGE:
2269 unset_all_on_page(window, page_start, page_end);
2271 case MENU_INVERT_PAGE:
2272 if (cw->how == PICK_ANY)
2273 invert_all_on_page(window, page_start, page_end, 0);
2275 case MENU_SELECT_ALL:
2276 if (cw->how == PICK_ANY) {
2277 set_all_on_page(window, page_start, page_end);
2279 for (curr = cw->mlist; curr; curr = curr->next)
2280 if (curr->identifier.a_void && !curr->selected)
2281 curr->selected = TRUE;
2284 case MENU_UNSELECT_ALL:
2285 unset_all_on_page(window, page_start, page_end);
2286 /* unset the rest */
2287 for (curr = cw->mlist; curr; curr = curr->next)
2288 if (curr->identifier.a_void && curr->selected) {
2289 curr->selected = FALSE;
2293 case MENU_INVERT_ALL:
2294 if (cw->how == PICK_ANY)
2295 invert_all(window, page_start, page_end, 0);
2298 if (cw->how == PICK_NONE) {
2302 char searchbuf[BUFSZ + 2], tmpbuf[BUFSZ] = DUMMY;
2303 boolean on_curr_page = FALSE;
2306 tty_getlin("Search for:", tmpbuf);
2307 if (!tmpbuf[0] || tmpbuf[0] == '\033')
2309 Sprintf(searchbuf, "*%s*", tmpbuf);
2311 for (curr = cw->mlist; curr; curr = curr->next) {
2314 if (curr == page_start)
2315 on_curr_page = TRUE;
2316 else if (curr == page_end)
2317 on_curr_page = FALSE;
2318 if (curr->identifier.a_void
2319 && pmatchi(searchbuf, curr->str)) {
2320 toggle_menu_curr(window, curr, lineno, on_curr_page,
2322 if (cw->how == PICK_ONE) {
2330 case MENU_EXPLICIT_CHOICE:
2334 if (cw->how == PICK_NONE || !index(resp, morc)) {
2335 /* unacceptable input received */
2338 } else if (index(gacc, morc)) {
2340 /* group accelerator; for the PICK_ONE case, we know that
2341 it matches exactly one item in order to be in gacc[] */
2342 invert_all(window, page_start, page_end, morc);
2343 if (cw->how == PICK_ONE)
2347 /* find, toggle, and possibly update */
2348 for (n = 0, curr = page_start; curr != page_end;
2349 n++, curr = curr->next)
2350 if (morc == curr->selector) {
2351 toggle_menu_curr(window, curr, n, TRUE, counting, count);
2352 if (cw->how == PICK_ONE)
2354 break; /* from `for' loop */
2360 cw->morestr = msave;
2361 free((genericptr_t) morestr);
2365 process_text_window(window, cw)
2373 for (n = 0, i = 0; i < cw->maxrow; i++) {
2375 if (!cw->offx && (n + cw->offy == ttyDisplay->rows - 1)) {
2376 tty_curs(window, 1, n);
2378 dmore(cw, quitchars);
2379 if (morc == '\033') {
2380 cw->flags |= WIN_CANCELLED;
2384 tty_curs(window, 1, 0);
2390 tty_curs(window, 1, n++);
2398 attr = cw->data[i][0] - 1;
2401 (void) putchar(' ');
2403 (void) jputchar(' ');
2407 term_start_attr(attr);
2408 for (cp = &cw->data[i][1], linestart = TRUE;
2410 *cp && (int) ++ttyDisplay->curx < (int) ttyDisplay->cols;
2413 *cp && (int) ttyDisplay->curx < (int) ttyDisplay->cols;
2414 cp++, ttyDisplay->curx++
2417 /* message recall for msg_window:full/combination/reverse
2418 might have output from '/' in it (see redotoplin()) */
2420 if (linestart && (*cp & 0x80) != 0) {
2425 (void) putchar(*cp);
2428 (void) jputchar(*cp);
2431 term_end_attr(attr);
2434 if (i == cw->maxrow) {
2436 if (cw->type == NHW_TEXT) {
2437 tty_curs(BASE_WINDOW, 1, (int) ttyDisplay->cury + 1);
2441 tty_curs(BASE_WINDOW, (int) cw->offx + 1,
2442 (cw->type == NHW_TEXT) ? (int) ttyDisplay->rows - 1 : n);
2444 dmore(cw, quitchars);
2446 cw->flags |= WIN_CANCELLED;
2452 tty_display_nhwindow(window, blocking)
2454 boolean blocking; /* with ttys, all windows are blocking */
2456 register struct WinDesc *cw = 0;
2460 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
2461 panic(winpanicstr, window);
2462 if (cw->flags & WIN_CANCELLED)
2464 ttyDisplay->lastwin = window;
2465 ttyDisplay->rawprint = 0;
2467 print_vt_code2(AVTC_SELECT_WINDOW, window);
2471 if (ttyDisplay->toplin == 1) {
2473 ttyDisplay->toplin = 1; /* more resets this */
2474 tty_clear_nhwindow(window);
2476 ttyDisplay->toplin = 0;
2477 cw->curx = cw->cury = 0;
2479 iflags.window_inited = TRUE;
2484 if (!ttyDisplay->toplin)
2485 ttyDisplay->toplin = 1;
2486 tty_display_nhwindow(WIN_MESSAGE, TRUE);
2491 (void) fflush(stdout);
2494 cw->maxcol = ttyDisplay->cols; /* force full-screen mode */
2498 /* cw->maxcol is a long, but its value is constrained to
2499 be <= ttyDisplay->cols, so is sure to fit within a short */
2500 s_maxcol = (short) cw->maxcol;
2502 cw->offx = (cw->type == NHW_TEXT)
2504 : min(min(82, ttyDisplay->cols / 2),
2505 ttyDisplay->cols - s_maxcol - 1);
2507 /* avoid converting to uchar before calculations are finished */
2508 cw->offx = (uchar) max((int) 10,
2509 (int) (ttyDisplay->cols - s_maxcol - 1));
2513 if (cw->type == NHW_MENU)
2515 if (ttyDisplay->toplin == 1)
2516 tty_display_nhwindow(WIN_MESSAGE, TRUE);
2518 if (cw->maxrow >= (int) ttyDisplay->rows
2519 || !iflags.menu_overlay)
2521 if (cw->offx == 10 || cw->maxrow >= (int) ttyDisplay->rows
2522 || !iflags.menu_overlay)
2526 if (cw->offy || iflags.menu_overlay) {
2527 tty_curs(window, 1, 0);
2531 ttyDisplay->toplin = 0;
2533 if (WIN_MESSAGE != WIN_ERR)
2534 tty_clear_nhwindow(WIN_MESSAGE);
2537 if (cw->data || !cw->maxrow)
2538 process_text_window(window, cw);
2540 process_menu_window(window, cw);
2547 tty_dismiss_nhwindow(window)
2550 register struct WinDesc *cw = 0;
2553 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
2554 panic(winpanicstr, window);
2556 print_vt_code2(AVTC_SELECT_WINDOW, window);
2560 if (ttyDisplay->toplin)
2561 tty_display_nhwindow(WIN_MESSAGE, TRUE);
2567 * these should only get dismissed when the game is going away
2570 tty_curs(BASE_WINDOW, 1, (int) ttyDisplay->rows - 1);
2576 if (iflags.window_inited) {
2577 /* otherwise dismissing the text endwin after other windows
2578 * are dismissed tries to redraw the map and panics. since
2579 * the whole reason for dismissing the other windows was to
2580 * leave the ending window on the screen, we don't want to
2583 erase_menu_or_text(window, cw, FALSE);
2593 tty_destroy_nhwindow(window)
2596 register struct WinDesc *cw = 0;
2598 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
2599 panic(winpanicstr, window);
2602 tty_dismiss_nhwindow(window);
2603 if (cw->type == NHW_MESSAGE)
2604 iflags.window_inited = 0;
2605 if (cw->type == NHW_MAP)
2608 free_window_info(cw, TRUE);
2609 free((genericptr_t) cw);
2610 wins[window] = 0; /* available for re-use */
2614 tty_curs(window, x, y)
2616 register int x, y; /* not xchar: perhaps xchar is unsigned and
2617 curx-x would be unsigned as well */
2619 struct WinDesc *cw = 0;
2620 int cx = ttyDisplay->curx;
2621 int cy = ttyDisplay->cury;
2624 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
2625 panic(winpanicstr, window);
2626 ttyDisplay->lastwin = window;
2628 print_vt_code2(AVTC_SELECT_WINDOW, window);
2630 #if defined(USE_TILES) && defined(MSDOS)
2631 adjust_cursor_flags(cw);
2633 cw->curx = --x; /* column 0 is never used */
2636 if (x < 0 || y < 0 || y >= cw->rows || x > cw->cols) {
2637 const char *s = "[unknown type]";
2641 s = "[topl window]";
2644 s = "[status window]";
2650 s = "[corner window]";
2653 s = "[text window]";
2656 s = "[base window]";
2659 debugpline4("bad curs positioning win %d %s (%d,%d)", window, s, x, y);
2660 /* This return statement caused a functional difference between
2661 DEBUG and non-DEBUG operation, so it is being commented
2662 out. It caused tty_curs() to fail to move the cursor to the
2663 location it needed to be if the x,y range checks failed,
2664 leaving the next piece of output to be displayed at whatever
2665 random location the cursor happened to be at prior. */
2674 if (clipping && window == WIN_MAP) {
2680 if (y == cy && x == cx)
2683 if (cw->type == NHW_MAP)
2687 if (!nh_ND && (cx != x || x <= 3)) { /* Extremely primitive */
2688 cmov(x, y); /* bunker!wtm */
2697 if (cy <= 3 && cx <= 3) {
2700 } else if ((x <= 3 && cy <= 3) || (!nh_CM && x < cx)) {
2702 (void) putchar('\r');
2704 (void) cputchar('\r');
2706 ttyDisplay->curx = 0;
2708 } else if (!nh_CM) {
2714 ttyDisplay->curx = x;
2715 ttyDisplay->cury = y;
2719 tty_putsym(window, x, y, ch)
2724 register struct WinDesc *cw = 0;
2727 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0)
2728 panic(winpanicstr, window);
2730 print_vt_code2(AVTC_SELECT_WINDOW, window);
2733 #ifndef STATUS_HILITES
2738 tty_curs(window, x, y);
2742 if(cw->type!=NHW_MAP)
2743 (void) jputchar(ch);
2745 (void) cputchar(ch);
2754 impossible("Can't putsym to window type %d", cw->type);
2759 STATIC_OVL const char *
2763 static char cbuf[BUFSZ];
2765 /* compress out consecutive spaces if line is too long;
2766 topline wrapping converts space at wrap point into newline,
2767 we reverse that here */
2768 if ((int) strlen(str) >= CO || index(str, '\n')) {
2769 const char *in_str = str;
2770 char c, *outstr = cbuf, *outend = &cbuf[sizeof cbuf - 1];
2771 boolean was_space = TRUE; /* True discards all leading spaces;
2772 False would retain one if present */
2774 while ((c = *in_str++) != '\0' && outstr < outend) {
2777 if (was_space && c == ' ')
2780 was_space = (c == ' ');
2782 if ((was_space && outstr > cbuf) || outstr == outend)
2783 --outstr; /* remove trailing space or make room for terminator */
2791 tty_putstr(window, attr, str)
2796 register struct WinDesc *cw = 0;
2798 register long i, n0;
2799 #ifndef STATUS_HILITES
2800 register const char *nb;
2804 int kchar2 = 0; /* if 1, kanji 2nd byte */
2808 /* Assume there's a real problem if the window is missing --
2809 * probably a panic message
2812 jputchar('\0'); /* RESET */
2815 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0) {
2820 if (str == (const char *) 0
2821 || ((cw->flags & WIN_CANCELLED) && (cw->type != NHW_MESSAGE)))
2823 if (cw->type != NHW_MESSAGE)
2824 str = compress_str(str);
2826 ttyDisplay->lastwin = window;
2828 print_vt_code2(AVTC_SELECT_WINDOW, window);
2832 int suppress_history = (attr & ATR_NOHISTORY);
2834 /* in case we ever support display attributes for topline
2835 messages, clear flag mask leaving only display attr */
2836 /*attr &= ~(ATR_URGENT | ATR_NOHISTORY);*/
2838 /* really do this later */
2839 #if defined(USER_SOUNDS) && defined(WIN32CON)
2840 play_sound_for_message(str);
2842 if (!suppress_history) {
2843 /* normal output; add to current top line if room, else flush
2844 whatever is there to history and then write this */
2847 /* put anything already on top line into history */
2849 /* write to top line without remembering what we're writing */
2854 #ifndef STATUS_HILITES
2856 ob = &cw->data[cw->cury][j = cw->curx];
2859 if (!cw->cury && (int) strlen(str) >= CO) {
2860 /* the characters before "St:" are unnecessary */
2861 nb = index(str, ':');
2862 if (nb && nb > str + 2)
2866 for (i = cw->curx + 1, n0 = cw->cols; i < n0; i++, nb++) {
2868 if (*ob || context.botlx) {
2869 /* last char printed may be in middle of line */
2870 tty_curs(WIN_STATUS, i, cw->cury);
2876 #else /* this code updates all status line at any time */
2878 /* check 2-bytes character for Japanese */
2879 /* by issei 93/12/2 */
2880 uc = *((unsigned char *)nb);
2881 if((!(uc & 0x80) && *ob != *nb) || kflg){
2882 tty_putsym(WIN_STATUS, i, cw->cury, *nb);
2885 if(*ob != *nb || *(ob+1)!= *(nb+1)){
2886 tty_putsym(WIN_STATUS, i, cw->cury, *nb);
2891 #define ismbchar(c) (((unsigned char)(c)) & 0x80)
2894 if (kchar2) /* kanji 2nd byte */
2896 if (kchar2 & KUPDATE)
2897 tty_putsym(WIN_STATUS, i, cw->cury, *nb);
2900 else if (ismbchar(*nb)) /* kanji 1st byte */
2903 /* Kanji char must be checked as 2-bytes pair. */
2904 /* check i to prevent putting only kanji 1st byte at last. */
2905 if ((*nb != *ob || *(nb+1) != *(ob+1)) && i < n0-1)
2907 tty_putsym(WIN_STATUS, i, cw->cury, *nb);
2908 kchar2 |= KUPDATE; /* must do update */
2910 /* else nb is the same char as old, so need not to update */
2912 /* not kanji char */
2916 tty_putsym(WIN_STATUS, i, cw->cury, *nb);
2921 (void) strncpy(&cw->data[cw->cury][j], str, cw->cols - j - 1);
2922 cw->data[cw->cury][cw->cols - 1] = '\0'; /* null terminate */
2923 cw->cury = (cw->cury + 1) % cw->maxrow;
2926 #endif /* STATUS_HILITES */
2928 tty_curs(window, cw->curx + 1, cw->cury);
2929 term_start_attr(attr);
2930 while (*str && (int) ttyDisplay->curx < (int) ttyDisplay->cols - 1) {
2932 (void) putchar(*str);
2934 (void) cputchar(*str);
2941 term_end_attr(attr);
2944 tty_curs(window, cw->curx + 1, cw->cury);
2945 term_start_attr(attr);
2947 if ((int) ttyDisplay->curx >= (int) ttyDisplay->cols - 1) {
2950 tty_curs(window, cw->curx + 1, cw->cury);
2953 (void) putchar(*str);
2955 (void) jputchar(*str);
2962 term_end_attr(attr);
2967 if (cw->type == NHW_TEXT
2968 && (cw->cury + cw->offy) == ttyDisplay->rows - 1)
2970 if (cw->type == NHW_TEXT && cw->cury == ttyDisplay->rows - 1)
2973 /* not a menu, so save memory and output 1 page at a time */
2974 cw->maxcol = ttyDisplay->cols; /* force full-screen mode */
2975 tty_display_nhwindow(window, TRUE);
2976 for (i = 0; i < cw->maxrow; i++)
2978 free((genericptr_t) cw->data[i]);
2981 cw->maxrow = cw->cury = 0;
2983 /* always grows one at a time, but alloc 12 at a time */
2984 if (cw->cury >= cw->rows) {
2988 tmp = (char **) alloc(sizeof(char *) * (unsigned) cw->rows);
2989 for (i = 0; i < cw->maxrow; i++)
2990 tmp[i] = cw->data[i];
2992 free((genericptr_t) cw->data);
2995 for (i = cw->maxrow; i < cw->rows; i++)
2998 if (cw->data[cw->cury])
2999 free((genericptr_t) cw->data[cw->cury]);
3000 n0 = (long) strlen(str) + 1L;
3001 ob = cw->data[cw->cury] = (char *) alloc((unsigned) n0 + 1);
3002 *ob++ = (char) (attr + 1); /* avoid nuls, for convenience */
3005 if (n0 > cw->maxcol)
3007 if (++cw->cury > cw->maxrow)
3008 cw->maxrow = cw->cury;
3010 /* attempt to break the line */
3011 for (i = CO - 1; i && str[i] != ' ' && str[i] != '\n';)
3014 cw->data[cw->cury - 1][++i] = '\0';
3015 tty_putstr(window, attr, &str[i]);
3021 jputchar('\0'); /* RESET */
3026 tty_display_file(fname, complain)
3030 #ifdef DEF_PAGER /* this implies that UNIX is defined */
3032 /* use external pager; this may give security problems */
3033 register int fd = open(fname, 0);
3037 pline("Cannot open %s.", fname);
3043 /* Now that child() does a setuid(getuid()) and a chdir(),
3044 we may not be able to open file fname anymore, so make
3049 raw_printf("Cannot open %s as stdin.", fname);
3051 (void) execlp(catmore, "page", (char *) 0);
3053 raw_printf("Cannot exec %s.", catmore);
3056 sleep(10); /* want to wait_synch() but stdin is gone */
3057 nh_terminate(EXIT_FAILURE);
3064 #else /* DEF_PAGER */
3070 tty_clear_nhwindow(WIN_MESSAGE);
3071 f = dlb_fopen(fname, "r");
3079 pline("Cannot open \"%s\".", fname);
3083 winid datawin = tty_create_nhwindow(NHW_TEXT);
3084 boolean empty = TRUE;
3091 /* attempt to scroll text below map window if there's room */
3092 wins[datawin]->offy = wins[WIN_STATUS]->offy + 3;
3093 if ((int) wins[datawin]->offy + 12 > (int) ttyDisplay->rows)
3094 wins[datawin]->offy = 0;
3096 while (dlb_fgets(buf, BUFSZ, f)) {
3097 if ((cr = index(buf, '\n')) != 0)
3100 if ((cr = index(buf, '\r')) != 0)
3103 if (index(buf, '\t') != 0)
3104 (void) tabexpand(buf);
3106 tty_putstr(datawin, 0, buf);
3107 if (wins[datawin]->flags & WIN_CANCELLED)
3111 tty_display_nhwindow(datawin, FALSE);
3112 tty_destroy_nhwindow(datawin);
3113 (void) dlb_fclose(f);
3116 #endif /* DEF_PAGER */
3120 tty_start_menu(window)
3123 tty_clear_nhwindow(window);
3129 * Add a menu item to the beginning of the menu list. This list is reversed
3133 tty_add_menu(window, glyph, identifier, ch, gch, attr, str, preselected)
3134 winid window; /* window to use, must be of type NHW_MENU */
3135 int glyph UNUSED; /* glyph to display with item (not used) */
3136 const anything *identifier; /* what to return if selected */
3137 char ch; /* keyboard accelerator (0 = pick our own) */
3138 char gch; /* group accelerator (0 = no group) */
3139 int attr; /* attribute for string (like tty_putstr()) */
3140 const char *str; /* menu string */
3141 boolean preselected; /* item is marked as selected */
3143 register struct WinDesc *cw = 0;
3144 tty_menu_item *item;
3146 char buf[4 + BUFSZ];
3149 if (str == (const char *) 0)
3152 if (window == WIN_ERR
3153 || (cw = wins[window]) == (struct WinDesc *) 0
3154 || cw->type != NHW_MENU)
3155 panic(winpanicstr, window);
3158 if (identifier->a_void) {
3159 int len = (int) strlen(str);
3162 /* We *think* everything's coming in off at most BUFSZ bufs... */
3163 impossible("Menu item too long (%d).", len);
3166 Sprintf(buf, "%c - ", ch ? ch : '?');
3167 (void) strncpy(buf + 4, str, len);
3168 buf[4 + len] = '\0';
3173 item = (tty_menu_item *) alloc(sizeof *item);
3174 item->identifier = *identifier;
3176 item->selected = preselected;
3177 item->selector = ch;
3178 item->gselector = gch;
3180 item->str = dupstr(newstr ? newstr : "");
3182 item->next = cw->mlist;
3186 /* Invert the given list, can handle NULL as an input. */
3187 STATIC_OVL tty_menu_item *
3189 tty_menu_item *curr;
3191 tty_menu_item *next, *head = 0;
3203 * End a menu in this window, window must a type NHW_MENU. This routine
3204 * processes the string list. We calculate the # of pages, then assign
3205 * keyboard accelerators as needed. Finally we decide on the width and
3206 * height of the window.
3209 tty_end_menu(window, prompt)
3210 winid window; /* menu to use */
3211 const char *prompt; /* prompt to for menu */
3213 struct WinDesc *cw = 0;
3214 tty_menu_item *curr;
3219 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0
3220 || cw->type != NHW_MENU)
3221 panic(winpanicstr, window);
3223 /* Reverse the list so that items are in correct order. */
3224 cw->mlist = reverse(cw->mlist);
3226 /* Put the prompt at the beginning of the menu. */
3230 any = zeroany; /* not selectable */
3231 tty_add_menu(window, NO_GLYPH, &any, 0, 0, ATR_NONE, "",
3233 tty_add_menu(window, NO_GLYPH, &any, 0, 0, ATR_NONE, prompt,
3237 /* 52: 'a'..'z' and 'A'..'Z'; avoids selector duplication within a page */
3238 lmax = min(52, (int) ttyDisplay->rows - 1); /* # lines per page */
3239 cw->npages = (cw->nitems + (lmax - 1)) / lmax; /* # of pages */
3242 * For really tall page, allow 53 if '$' or '#' is present and
3243 * 54 if both are. [Only for single page menu, otherwise pages
3244 * without those could try to use too many letters.]
3245 * Probably not worth bothering with; anyone with a display big
3246 * for this to matter will likely switch from tty to curses for
3247 * multi-line message window and/or persistent inventory window.
3250 /* make sure page list is large enough */
3251 if (cw->plist_size < cw->npages + 1) { /* +1: need one slot beyond last */
3253 free((genericptr_t) cw->plist);
3254 cw->plist_size = cw->npages + 1;
3255 cw->plist = (tty_menu_item **) alloc(cw->plist_size
3256 * sizeof (tty_menu_item *));
3259 cw->cols = 0; /* cols is set when the win is initialized... (why?) */
3260 menu_ch = '?'; /* lint suppression */
3261 for (n = 0, curr = cw->mlist; curr; n++, curr = curr->next) {
3262 /* set page boundaries and character accelerators */
3263 if ((n % lmax) == 0) {
3265 cw->plist[n / lmax] = curr;
3267 if (curr->identifier.a_void && !curr->selector) {
3268 curr->str[0] = curr->selector = menu_ch;
3269 if (menu_ch++ == 'z')
3273 /* cut off any lines that are too long */
3274 len = strlen(curr->str) + 2; /* extra space at beg & end */
3275 if (len > (int) ttyDisplay->cols) {
3276 curr->str[ttyDisplay->cols - 2] = 0;
3277 len = ttyDisplay->cols;
3282 cw->plist[cw->npages] = 0; /* plist terminator */
3285 * If greater than 1 page, morestr is "(x of y) " otherwise, "(end) "
3287 if (cw->npages > 1) {
3289 /* produce the largest demo string */
3290 Sprintf(buf, "(%ld of %ld) ", cw->npages, cw->npages);
3292 cw->morestr = dupstr("");
3294 cw->morestr = dupstr("(end) ");
3295 len = strlen(cw->morestr);
3298 if (len > (int) ttyDisplay->cols) {
3299 /* truncate the prompt if it's too long for the screen */
3300 if (cw->npages <= 1) /* only str in single page case */
3301 cw->morestr[ttyDisplay->cols] = 0;
3302 len = ttyDisplay->cols;
3307 cw->maxcol = cw->cols;
3310 * The number of lines in the first page plus the morestr will be the
3311 * maximum size of the window.
3314 cw->maxrow = cw->rows = lmax + 1;
3316 cw->maxrow = cw->rows = cw->nitems + 1;
3320 tty_select_menu(window, how, menu_list)
3323 menu_item **menu_list;
3325 register struct WinDesc *cw = 0;
3326 tty_menu_item *curr;
3330 if (window == WIN_ERR || (cw = wins[window]) == (struct WinDesc *) 0
3331 || cw->type != NHW_MENU)
3332 panic(winpanicstr, window);
3334 *menu_list = (menu_item *) 0;
3335 cw->how = (short) how;
3337 tty_display_nhwindow(window, TRUE);
3338 cancelled = !!(cw->flags & WIN_CANCELLED);
3339 tty_dismiss_nhwindow(window); /* does not destroy window data */
3344 for (n = 0, curr = cw->mlist; curr; curr = curr->next)
3350 *menu_list = (menu_item *) alloc(n * sizeof(menu_item));
3351 for (mi = *menu_list, curr = cw->mlist; curr; curr = curr->next)
3352 if (curr->selected) {
3353 mi->item = curr->identifier;
3354 mi->count = curr->count;
3362 /* special hack for treating top line --More-- as a one item menu */
3364 tty_message_menu(let, how, mesg)
3370 /* "menu" without selection; use ordinary pline, no more() */
3371 if (how == PICK_NONE) {
3376 ttyDisplay->dismiss_more = let;
3378 /* barebones pline(); since we're only supposed to be called after
3379 response to a prompt, we'll assume that the display is up to date */
3380 tty_putstr(WIN_MESSAGE, 0, mesg);
3381 /* if `mesg' didn't wrap (triggering --More--), force --More-- now */
3382 if (ttyDisplay->toplin == 1) {
3384 ttyDisplay->toplin = 1; /* more resets this */
3385 tty_clear_nhwindow(WIN_MESSAGE);
3387 /* normally <ESC> means skip further messages, but in this case
3388 it means cancel the current prompt; any other messages should
3389 continue to be output normally */
3390 wins[WIN_MESSAGE]->flags &= ~WIN_CANCELLED;
3391 ttyDisplay->dismiss_more = 0;
3393 return ((how == PICK_ONE && morc == let) || morc == '\033') ? morc : '\0';
3397 tty_update_inventory()
3406 (void) fflush(stdout);
3413 /* we just need to make sure all windows are synch'd */
3414 if (!ttyDisplay || ttyDisplay->rawprint) {
3417 ttyDisplay->rawprint = 0;
3419 tty_display_nhwindow(WIN_MAP, FALSE);
3420 if (ttyDisplay->inmore) {
3421 addtopl("--More--");
3422 (void) fflush(stdout);
3423 } else if (ttyDisplay->inread > program_state.gameover) {
3424 /* this can only happen if we were reading and got interrupted */
3425 ttyDisplay->toplin = 3;
3426 /* do this twice; 1st time gets the Quit? message again */
3427 (void) tty_doprev_message();
3428 (void) tty_doprev_message();
3430 (void) fflush(stdout);
3436 docorner(xmin, ymax)
3437 register int xmin, ymax;
3440 register struct WinDesc *cw = wins[WIN_MAP];
3443 #if 0 /* this optimization is not valuable enough to justify
3444 abusing core internals... */
3445 if (u.uswallow) { /* Can be done more efficiently */
3447 /* without this flush, if we happen to follow --More-- displayed in
3448 leftmost column, the cursor gets left in the wrong place after
3449 <docorner<more<update_topl<tty_putstr calls unwind back to core */
3455 #if defined(SIGWINCH) && defined(CLIPPING)
3457 ymax = LI; /* can happen if window gets smaller */
3459 for (y = 0; y < ymax; y++) {
3460 tty_curs(BASE_WINDOW, xmin, y); /* move cursor */
3461 cl_end(); /* clear to end of line */
3463 if (y < (int) cw->offy || y + clipy > ROWNO)
3464 continue; /* only refresh board */
3465 #if defined(USE_TILES) && defined(MSDOS)
3466 if (iflags.tile_view)
3467 row_refresh((xmin / 2) + clipx - ((int) cw->offx / 2), COLNO - 1,
3468 y + clipy - (int) cw->offy);
3471 row_refresh(xmin + clipx - (int) cw->offx, COLNO - 1,
3472 y + clipy - (int) cw->offy);
3474 if (y < cw->offy || y > ROWNO)
3475 continue; /* only refresh board */
3476 row_refresh(xmin - (int) cw->offx, COLNO - 1, y - (int) cw->offy);
3481 if (ymax >= (int) wins[WIN_STATUS]->offy) {
3482 /* we have wrecked the bottom line */
3492 #if defined(ASCIIGRAPH) && !defined(NO_TERMS)
3499 if (ttyDisplay->color != NO_COLOR) {
3501 ttyDisplay->color = NO_COLOR;
3511 register char ch = (char) in_ch;
3514 #if defined(ASCIIGRAPH) && !defined(NO_TERMS)
3515 if (SYMHANDLING(H_IBM)
3516 /* for DECgraphics, lower-case letters with high bit set mean
3517 switch character set and render with high bit clear;
3518 user might want 8-bits for other characters */
3519 || (iflags.eight_bit_tty && (!SYMHANDLING(H_DEC)
3520 || (in_ch & 0x7f) < 0x60))) {
3521 /* IBM-compatible displays don't need other stuff */
3525 (void) cputchar(ch);
3527 } else if (ch & 0x80) {
3528 if (!GFlag || HE_resets_AS) {
3533 (void) putchar((ch ^ 0x80)); /* Strip 8th bit */
3535 (void) cputchar((ch ^ 0x80)); /* Strip 8th bit */
3545 (void) jputchar(ch);
3552 #endif /* ASCIIGRAPH && !NO_TERMS */
3565 clipymax = LI - 1 - iflags.wc2_statuslines;
3569 tty_cliparound(x, y)
3572 int oldx = clipx, oldy = clipy;
3577 if (x < clipx + 5) {
3578 clipx = max(0, x - 20);
3579 clipxmax = clipx + CO;
3580 } else if (x > clipxmax - 5) {
3581 clipxmax = min(COLNO, clipxmax + 20);
3582 clipx = clipxmax - CO;
3584 if (y < clipy + 2) {
3585 clipy = max(0, y - (clipymax - clipy) / 2);
3586 clipymax = clipy + (LI - 1 - iflags.wc2_statuslines);
3587 } else if (y > clipymax - 2) {
3588 clipymax = min(ROWNO, clipymax + (clipymax - clipy) / 2);
3589 clipy = clipymax - (LI - 1 - iflags.wc2_statuslines);
3591 if (clipx != oldx || clipy != oldy) {
3592 redraw_map(); /* ask the core to resend the map window's data */
3595 #endif /* CLIPPING */
3600 * Print the glyph to the output device. Don't flush the output device.
3602 * Since this is only called from show_glyph(), it is assumed that the
3603 * position and glyph are always correct (checked there)!
3607 tty_print_glyph(window, x, y, glyph, bkglyph)
3614 boolean reverse_on = FALSE;
3621 if (x <= clipx || y < clipy || x >= clipxmax || y >= clipymax)
3625 /* map glyph to character and color */
3626 (void) mapglyph(glyph, &ch, &color, &special, x, y, 0);
3628 print_vt_code2(AVTC_SELECT_WINDOW, window);
3630 /* Move the cursor. */
3631 tty_curs(window, x, y);
3633 print_vt_code3(AVTC_GLYPH_START, glyph2tile[glyph], special);
3636 if (ul_hack && ch == '_') { /* non-destructive underscore */
3638 (void) putchar((char) ' ');
3640 (void) cputchar((char) ' ');
3647 if (color != ttyDisplay->color) {
3648 if (ttyDisplay->color != NO_COLOR)
3650 ttyDisplay->color = color;
3651 if (color != NO_COLOR)
3652 term_start_color(color);
3654 #endif /* TEXTCOLOR */
3656 /* must be after color check; term_end_color may turn off inverse too */
3657 if (((special & MG_PET) && iflags.hilite_pet)
3658 || ((special & MG_OBJPILE) && iflags.hilite_pile)
3659 || ((special & MG_DETECT) && iflags.use_inverse)
3660 || ((special & MG_BW_LAVA) && iflags.use_inverse)) {
3661 term_start_attr(ATR_INVERSE);
3665 #if defined(USE_TILES) && defined(MSDOS)
3666 if (iflags.grmode && iflags.tile_view)
3667 xputg(glyph, ch, special);
3670 g_putch(ch); /* print the character */
3673 term_end_attr(ATR_INVERSE);
3675 /* turn off color as well, ATR_INVERSE may have done this already */
3676 if (ttyDisplay->color != NO_COLOR) {
3678 ttyDisplay->color = NO_COLOR;
3683 print_vt_code1(AVTC_GLYPH_END);
3685 wins[window]->curx++; /* one character over */
3686 ttyDisplay->curx++; /* the real cursor moved too */
3695 ttyDisplay->rawprint++;
3696 print_vt_code2(AVTC_SELECT_WINDOW, NHW_BASE);
3697 #if defined(MICRO) || defined(WIN32CON)
3705 (void) fflush(stdout);
3710 tty_raw_print_bold(str)
3715 ttyDisplay->rawprint++;
3716 print_vt_code2(AVTC_SELECT_WINDOW, NHW_BASE);
3717 term_start_raw_bold();
3718 #if defined(MICRO) || defined(WIN32CON)
3722 (void) fputs(str, stdout);
3727 term_end_raw_bold();
3728 #if defined(MICRO) || defined(WIN32CON)
3734 (void) fflush(stdout);
3743 /* kludge alert: Some Unix variants return funny values if getc()
3744 * is called, interrupted, and then called again. There
3745 * is non-reentrant code in the internal _filbuf() routine, called by
3748 static volatile int nesting = 0;
3752 HUPSKIP_RESULT('\033');
3753 print_vt_code1(AVTC_INLINE_SYNC);
3754 (void) fflush(stdout);
3755 /* Note: if raw_print() and wait_synch() get called to report terminal
3756 * initialization problems, then wins[] and ttyDisplay might not be
3757 * available yet. Such problems will probably be fatal before we get
3758 * here, but validate those pointers just in case...
3760 if (WIN_MESSAGE != WIN_ERR && wins[WIN_MESSAGE])
3761 wins[WIN_MESSAGE]->flags &= ~WIN_STOP;
3762 if (iflags.debug_fuzzer) {
3766 i = (++nesting == 1)
3768 : (read(fileno(stdin), (genericptr_t) &nestbuf, 1) == 1)
3769 ? (int) nestbuf : EOF;
3776 i = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */
3778 i = '\033'; /* same for EOF */
3779 if (ttyDisplay && ttyDisplay->toplin == 1)
3780 ttyDisplay->toplin = 2;
3781 #ifdef TTY_TILES_ESCCODES
3783 /* hack to force output of the window select code */
3784 int tmp = vt_tile_current_window;
3786 vt_tile_current_window++;
3787 print_vt_code2(AVTC_SELECT_WINDOW, tmp);
3789 #endif /* TTY_TILES_ESCCODES */
3794 * return a key, or 0, in which case a mouse button was pressed
3795 * mouse events should be returned as character postitions in the map window.
3796 * Since normal tty's don't have mice, just return a key.
3800 tty_nh_poskey(x, y, mod)
3805 HUPSKIP_RESULT('\033');
3806 #if defined(WIN32CON)
3807 (void) fflush(stdout);
3808 /* Note: if raw_print() and wait_synch() get called to report terminal
3809 * initialization problems, then wins[] and ttyDisplay might not be
3810 * available yet. Such problems will probably be fatal before we get
3811 * here, but validate those pointers just in case...
3813 if (WIN_MESSAGE != WIN_ERR && wins[WIN_MESSAGE])
3814 wins[WIN_MESSAGE]->flags &= ~WIN_STOP;
3815 i = ntposkey(x, y, mod);
3816 if (!i && mod && (*mod == 0 || *mod == EOF))
3817 i = '\033'; /* map NUL or EOF to ESC, nethack doesn't expect either */
3818 if (ttyDisplay && ttyDisplay->toplin == 1)
3819 ttyDisplay->toplin = 2;
3820 #else /* !WIN32CON */
3826 #endif /* ?WIN32CON */
3841 tty_update_positionbar(posbar)
3845 video_update_positionbar(posbar);
3848 #endif /* POSITIONBAR */
3852 * +------------------+
3854 * +------------------+
3858 * ----------------------------------------------------------------
3861 * Called from the NetHack core and receives info hand-offs
3862 * from the core, processes them, storing some information
3863 * for rendering to the tty.
3864 * -> make_things_fit()
3865 * -> render_status()
3869 * Called from tty_status_update(). It calls check_fields()
3870 * (described next) to get the required number of columns
3871 * and tries a few different ways to squish a representation
3872 * of the status window values onto the 80 column tty display.
3874 * ->set_condition_length() - update the width of conditions
3875 * ->shrink_enc() - shrink encumbrance message word
3876 * ->shrink_dlvl() - reduce the width of Dlvl:42
3880 * Verifies that all the fields are ready for display, and
3881 * returns FALSE if called too early in the startup
3882 * processing sequence. It also figures out where everything
3883 * needs to go, taking the current shrinking attempts into
3884 * account. It returns number of columns needed back to
3885 * make_things_fit(), so make_things_fit() can make attempt
3886 * to make adjustments.
3890 * Goes through each of the status row's fields and
3891 * calls tty_putstatusfield() to place them on the display.
3892 * ->tty_putstatusfield()
3893 * At the end of the for-loop, the NOW values get copied
3896 * tty_putstatusfield()
3898 * Move the cursor to the target spot, and output the field
3899 * asked for by render_status().
3901 * ----------------------------------------------------------------
3905 * The following data structures come from the genl_ routines in
3906 * src/windows.c and as such are considered to be on the window-port
3907 * "side" of things, rather than the NetHack-core "side" of things.
3909 extern const char *status_fieldfmt[MAXBLSTATS];
3910 extern char *status_vals[MAXBLSTATS];
3911 extern boolean status_activefields[MAXBLSTATS];
3912 extern winid WIN_STATUS;
3914 #ifdef STATUS_HILITES
3916 STATIC_DCL int FDECL(condcolor, (long, unsigned long *));
3918 STATIC_DCL int FDECL(condattr, (long, unsigned long *));
3919 static unsigned long *tty_colormasks;
3920 static long tty_condition_bits;
3921 static struct tty_status_fields tty_status[2][MAXBLSTATS]; /* 2: NOW,BEFORE */
3922 static int hpbar_percent, hpbar_color;
3923 static struct condition_t {
3925 const char *text[3]; /* 3: potential display vals, progressively shorter */
3927 /* The sequence order of these matters */
3928 { BL_MASK_STONE, { "Stone", "Ston", "Sto" } },
3929 { BL_MASK_SLIME, { "Slime", "Slim", "Slm" } },
3930 { BL_MASK_STRNGL, { "Strngl", "Stngl", "Str" } },
3931 { BL_MASK_FOODPOIS, { "FoodPois", "Fpois", "Poi" } },
3932 { BL_MASK_TERMILL, { "TermIll" , "Ill", "Ill" } },
3933 { BL_MASK_BLIND, { "Blind", "Blnd", "Bl" } },
3934 { BL_MASK_DEAF, { "Deaf", "Def", "Df" } },
3935 { BL_MASK_STUN, { "Stun", "Stun", "St" } },
3936 { BL_MASK_CONF, { "Conf", "Cnf", "Cf" } },
3937 { BL_MASK_HALLU, { "Hallu", "Hal", "Hl" } },
3938 { BL_MASK_LEV, { "Lev", "Lev", "Lv" } },
3939 { BL_MASK_FLY, { "Fly", "Fly", "Fl" } },
3940 { BL_MASK_RIDE, { "Ride", "Rid", "Rd" } },
3942 static const char *encvals[3][6] = {
3943 { "", "Burdened", "Stressed", "Strained", "Overtaxed", "Overloaded" },
3944 { "", "Burden", "Stress", "Strain", "Overtax", "Overload" },
3945 { "", "Brd", "Strs", "Strn", "Ovtx", "Ovld" }
3947 #define blPAD BL_FLUSH
3948 #define MAX_PER_ROW 15
3949 /* 2 or 3 status lines */
3950 static const enum statusfields
3951 twolineorder[3][MAX_PER_ROW] = {
3952 { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, BL_ALIGN,
3953 BL_SCORE, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD },
3954 { BL_LEVELDESC, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX,
3955 BL_AC, BL_XP, BL_EXP, BL_HD, BL_TIME, BL_HUNGER,
3956 BL_CAP, BL_CONDITION, BL_FLUSH },
3957 /* third row of array isn't used for twolineorder */
3958 { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
3959 blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
3961 /* Align moved from 1 to 2, Leveldesc+Time+Condition moved from 2 to 3 */
3962 threelineorder[3][MAX_PER_ROW] = {
3963 { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
3964 BL_SCORE, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
3965 { BL_ALIGN, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX,
3966 BL_AC, BL_XP, BL_EXP, BL_HD, BL_HUNGER,
3967 BL_CAP, BL_FLUSH, blPAD, blPAD },
3968 { BL_LEVELDESC, BL_TIME, BL_CONDITION, BL_FLUSH, blPAD, blPAD,
3969 blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
3971 static const enum statusfields (*fieldorder)[MAX_PER_ROW];
3973 static int finalx[3][2]; /* [rows][NOW or BEFORE] */
3974 static boolean windowdata_init = FALSE;
3975 static int cond_shrinklvl = 0;
3976 static int enclev = 0, enc_shrinklvl = 0;
3977 static int dlvl_shrinklvl = 0;
3978 static boolean truncation_expected = FALSE;
3979 #define FORCE_RESET TRUE
3980 #define NO_RESET FALSE
3982 /* This controls whether to skip fields that aren't
3983 * flagged as requiring updating during the current
3986 * Hopefully that can be confirmed as working correctly
3987 * for all platforms eventually and the conditional
3988 * setting below can be removed.
3990 static int do_field_opt =
3991 #if defined(DISABLE_TTY_FIELD_OPT)
3997 #endif /* STATUS_HILITES */
4001 * -- initialize the tty-specific data structures.
4002 * -- call genl_status_init() to initialize the general data.
4007 #ifdef STATUS_HILITES
4010 num_rows = (iflags.wc2_statuslines < 3) ? 2 : 3;
4011 fieldorder = (num_rows != 3) ? twolineorder : threelineorder;
4013 for (i = 0; i < MAXBLSTATS; ++i) {
4014 tty_status[NOW][i].idx = BL_FLUSH;
4015 tty_status[NOW][i].color = NO_COLOR; /* no color */
4016 tty_status[NOW][i].attr = ATR_NONE;
4017 tty_status[NOW][i].x = tty_status[NOW][i].y = 0;
4018 tty_status[NOW][i].valid = FALSE;
4019 tty_status[NOW][i].dirty = FALSE;
4020 tty_status[NOW][i].redraw = FALSE;
4021 tty_status[NOW][i].sanitycheck = FALSE;
4022 tty_status[BEFORE][i] = tty_status[NOW][i];
4024 tty_condition_bits = 0L;
4025 hpbar_percent = 0, hpbar_color = NO_COLOR;
4026 #endif /* STATUS_HILITES */
4028 /* let genl_status_init do most of the initialization */
4033 tty_status_enablefield(fieldidx, nm, fmt, enable)
4039 genl_status_enablefield(fieldidx, nm, fmt, enable);
4042 #ifdef STATUS_HILITES
4046 * -- update the value of a status field.
4047 * -- the fldindex identifies which field is changing and
4048 * is an integer index value from botl.h
4049 * -- fldindex could be any one of the following from botl.h:
4050 * BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
4051 * BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX,
4052 * BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, BL_HPMAX,
4053 * BL_LEVELDESC, BL_EXP, BL_CONDITION
4054 * -- fldindex could also be BL_FLUSH (-1), which is not really
4055 * a field index, but is a special trigger to tell the
4056 * windowport that it should output all changes received
4057 * to this point. It marks the end of a bot() cycle.
4058 * -- fldindex could also be BL_RESET (-3), which is not really
4059 * a field index, but is a special advisory to to tell the
4060 * windowport that it should redisplay all its status fields,
4061 * even if no changes have been presented to it.
4062 * -- ptr is usually a "char *", unless fldindex is BL_CONDITION.
4063 * If fldindex is BL_CONDITION, then ptr is a long value with
4064 * any or none of the following bits set (from botl.h):
4065 * BL_MASK_STONE 0x00000001L
4066 * BL_MASK_SLIME 0x00000002L
4067 * BL_MASK_STRNGL 0x00000004L
4068 * BL_MASK_FOODPOIS 0x00000008L
4069 * BL_MASK_TERMILL 0x00000010L
4070 * BL_MASK_BLIND 0x00000020L
4071 * BL_MASK_DEAF 0x00000040L
4072 * BL_MASK_STUN 0x00000080L
4073 * BL_MASK_CONF 0x00000100L
4074 * BL_MASK_HALLU 0x00000200L
4075 * BL_MASK_LEV 0x00000400L
4076 * BL_MASK_FLY 0x00000800L
4077 * BL_MASK_RIDE 0x00001000L
4078 * -- The value passed for BL_GOLD usually includes an encoded leading
4079 * symbol for GOLD "\GXXXXNNNN:nnn". If the window port needs to use
4080 * the textual gold amount without the leading "$:" the port will
4081 * have to skip past ':' in the passed "ptr" for the BL_GOLD case.
4082 * -- color is an unsigned int.
4083 * color_index = color & 0x00FF; CLR_* value
4084 * attribute = (color >> 8) & 0x00FF; HL_ATTCLR_* mask
4085 * This holds the color and attribute that the field should
4087 * This is relevant for everything except BL_CONDITION fldindex.
4088 * If fldindex is BL_CONDITION, this parameter should be ignored,
4089 * as condition highlighting is done via the next colormasks
4090 * parameter instead.
4092 * -- colormasks - pointer to cond_hilites[] array of colormasks.
4093 * Only relevant for BL_CONDITION fldindex. The window port
4094 * should ignore this parameter for other fldindex values.
4095 * Each condition bit must only ever appear in one of the
4096 * CLR_ array members, but can appear in multiple HL_ATTCLR_
4097 * offsets (because more than one attribute can co-exist).
4098 * See doc/window.doc for more details.
4102 tty_status_update(fldidx, ptr, chg, percent, color, colormasks)
4103 int fldidx, chg UNUSED, percent, color;
4105 unsigned long *colormasks;
4108 long *condptr = (long *) ptr;
4109 char *text = (char *) ptr;
4110 char goldbuf[40], *lastchar, *p;
4112 boolean reset_state = NO_RESET;
4114 if ((fldidx < BL_RESET) || (fldidx >= MAXBLSTATS))
4117 if ((fldidx >= 0 && fldidx < MAXBLSTATS) && !status_activefields[fldidx])
4122 reset_state = FORCE_RESET;
4125 if (make_things_fit(reset_state) || truncation_expected) {
4127 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
4128 status_sanity_check();
4133 tty_status[NOW][fldidx].idx = fldidx;
4134 tty_condition_bits = *condptr;
4135 tty_colormasks = colormasks;
4136 tty_status[NOW][fldidx].valid = TRUE;
4137 tty_status[NOW][fldidx].dirty = TRUE;
4138 tty_status[NOW][fldidx].sanitycheck = TRUE;
4139 truncation_expected = FALSE;
4142 text = decode_mixed(goldbuf, text);
4145 attrmask = (color >> 8) & 0x00FF;
4149 fmt = status_fieldfmt[fldidx];
4152 /* should be checking for first enabled field here rather than
4153 just first field, but 'fieldorder' doesn't start any rows
4154 with fields which can be disabled so [any_row][0] suffices */
4155 if (*fmt == ' ' && (fldidx == fieldorder[0][0]
4156 || fldidx == fieldorder[1][0]
4157 || fldidx == fieldorder[2][0]))
4158 ++fmt; /* skip leading space for first field on line */
4159 Sprintf(status_vals[fldidx], fmt, text);
4160 tty_status[NOW][fldidx].idx = fldidx;
4161 tty_status[NOW][fldidx].color = (color & 0x00FF);
4162 tty_status[NOW][fldidx].attr = term_attr_fixup(attrmask);
4163 tty_status[NOW][fldidx].lth = strlen(status_vals[fldidx]);
4164 tty_status[NOW][fldidx].valid = TRUE;
4165 tty_status[NOW][fldidx].dirty = TRUE;
4166 tty_status[NOW][fldidx].sanitycheck = TRUE;
4170 /* The core botl engine sends a single blank to the window port
4171 for carrying-capacity when its unused. Let's suppress that */
4172 if (fldidx >= 0 && fldidx < MAXBLSTATS
4173 && tty_status[NOW][fldidx].lth == 1
4174 && status_vals[fldidx][0] == ' ') {
4175 status_vals[fldidx][0] = '\0';
4176 tty_status[NOW][fldidx].lth = 0;
4179 /* default processing above was required before these */
4182 if (iflags.wc2_hitpointbar) {
4183 /* Special additional processing for hitpointbar */
4184 hpbar_percent = percent;
4185 hpbar_color = (color & 0x00FF);
4186 tty_status[NOW][BL_TITLE].color = hpbar_color;
4187 tty_status[NOW][BL_TITLE].dirty = TRUE;
4191 dlvl_shrinklvl = 0; /* caller is passing full length string */
4194 /* The core sends trailing blanks for some fields.
4195 Let's suppress the trailing blanks */
4196 if (tty_status[NOW][fldidx].lth > 0) {
4197 p = status_vals[fldidx];
4198 for (lastchar = eos(p); lastchar > p && *--lastchar == ' '; ) {
4200 tty_status[NOW][fldidx].lth--;
4205 /* when hitpointbar is enabled, rendering will enforce a length
4206 of 30 on title, padding with spaces or truncating if necessary */
4207 if (iflags.wc2_hitpointbar)
4208 tty_status[NOW][fldidx].lth = 30 + 2; /* '[' and ']' */
4211 /* \GXXXXNNNN counts as 1 [moot since we use decode_mixed() above] */
4212 if ((p = index(status_vals[fldidx], '\\')) != 0 && p[1] == 'G')
4213 tty_status[NOW][fldidx].lth -= (10 - 1);
4216 enc_shrinklvl = 0; /* caller is passing full length string */
4217 enclev = stat_cap_indx();
4220 /* As of 3.6.2 we only render on BL_FLUSH (or BL_RESET) */
4225 make_things_fit(force_update)
4226 boolean force_update;
4228 int trycnt, fitting = 0, requirement;
4229 int rowsz[3], num_rows, condrow, otheroptions = 0;
4231 num_rows = (iflags.wc2_statuslines < 3) ? 2 : 3;
4232 condrow = num_rows - 1; /* always last row, 1 for 0..1 or 2 for 0..2 */
4234 if (enc_shrinklvl > 0 && num_rows == 2)
4236 if (dlvl_shrinklvl > 0)
4238 set_condition_length();
4239 for (trycnt = 0; trycnt < 6 && !fitting; ++trycnt) {
4240 /* FIXME: this remeasures each line every time even though it
4241 is only attempting to shrink one of them and the other one
4242 (or two) remains the same */
4243 if (!check_fields(force_update, rowsz)) {
4248 requirement = rowsz[condrow] - 1;
4249 if (requirement <= wins[WIN_STATUS]->cols - 1) {
4250 fitting = requirement;
4251 break; /* we're good */
4254 if (cond_shrinklvl < trycnt + 1) {
4255 cond_shrinklvl = trycnt + 1;
4256 set_condition_length();
4260 if (cond_shrinklvl >= 2) {
4261 /* We've exhausted the condition identifiers shrinkage,
4262 * so let's try shrinking other things...
4264 if (otheroptions < 2) {
4265 /* try shrinking the encumbrance word, but
4266 only when it's on the same line as conditions */
4268 shrink_enc(otheroptions + 1);
4269 } else if (otheroptions == 2) {
4272 /* Last resort - turn on trunction */
4273 truncation_expected = TRUE;
4283 * This is the routine where we figure out where each field
4284 * should be placed, and flag whether the on-screen details
4285 * must be updated because they need to change.
4286 * This is now done at an individual field case-by-case level.
4289 check_fields(forcefields, sz)
4290 boolean forcefields;
4293 int c, i, row, col, num_rows, idx;
4294 boolean valid = TRUE, matchprev, update_right;
4296 if (!windowdata_init && !check_windowdata())
4299 num_rows = (iflags.wc2_statuslines < 3) ? 2 : 3;
4301 for (row = 0; row < num_rows; ++row) {
4304 update_right = FALSE;
4305 for (i = 0; (idx = fieldorder[row][i]) != BL_FLUSH; ++i) {
4306 if (!status_activefields[idx])
4308 if (!tty_status[NOW][idx].valid)
4310 /* might be called more than once for shrink tests, so need
4311 to reset these (redraw and x at any rate) each time */
4312 tty_status[NOW][idx].redraw = FALSE;
4313 tty_status[NOW][idx].y = row;
4314 tty_status[NOW][idx].x = col;
4316 /* On a change to the field location, everything further
4317 to the right must be updated as well. (Not necessarily
4318 everything; it's possible for complementary changes across
4319 multiple fields to put stuff further right back in sync.) */
4320 if (tty_status[NOW][idx].x + tty_status[NOW][idx].lth
4321 != tty_status[BEFORE][idx].x + tty_status[BEFORE][idx].lth)
4322 update_right = TRUE;
4323 else if (tty_status[NOW][idx].lth != tty_status[BEFORE][idx].lth
4324 || tty_status[NOW][idx].x != tty_status[BEFORE][idx].x)
4325 tty_status[NOW][idx].redraw = TRUE;
4326 else /* in case update_right is set, we're back in sync now */
4327 update_right = FALSE;
4329 matchprev = FALSE; /* assume failure */
4330 if (valid && !update_right && !forcefields
4331 && !tty_status[NOW][idx].redraw) {
4333 * Check values against those already on the display.
4334 * - Is the additional processing time for this worth it?
4337 /* color/attr checks aren't right for 'condition'
4338 and neither is examining status_vals[BL_CONDITION]
4339 so skip same-contents optimization for conditions */
4340 && idx != BL_CONDITION
4341 && (tty_status[NOW][idx].color
4342 == tty_status[BEFORE][idx].color)
4343 && (tty_status[NOW][idx].attr
4344 == tty_status[BEFORE][idx].attr)) {
4345 matchprev = TRUE; /* assume success */
4346 if (tty_status[NOW][idx].dirty) {
4347 /* compare values */
4348 const char *ob, *nb; /* old byte, new byte */
4349 struct WinDesc *cw = wins[WIN_STATUS];
4352 ob = &cw->data[row][c];
4353 nb = status_vals[idx];
4354 while (*nb && c < cw->cols) {
4361 /* if we're not at the end of new string, no match;
4362 we don't need to worry about whether there might
4363 be leftover old string; that could only happen
4364 if they have different lengths, in which case
4365 'update_right' will be set and we won't get here */
4369 if (*nb || (*ob && *ob != ' '
4370 && (*ob != '/' || idx != BL_XP)
4371 && (*ob != '(' || (idx != BL_HP
4372 && idx != BL_ENE))))
4379 if (forcefields || update_right
4380 || (tty_status[NOW][idx].dirty && !matchprev))
4381 tty_status[NOW][idx].redraw = TRUE;
4383 col += tty_status[NOW][idx].lth;
4390 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
4392 status_sanity_check(VOID_ARGS)
4395 static boolean in_sanity_check = FALSE;
4396 static const char *const idxtext[] = {
4397 "BL_TITLE", "BL_STR", "BL_DX", "BL_CO", "BL_IN", "BL_WI", /* 0.. 5 */
4398 "BL_CH","BL_ALIGN", "BL_SCORE", "BL_CAP", "BL_GOLD", /* 6.. 10 */
4399 "BL_ENE", "BL_ENEMAX", "BL_XP", "BL_AC", "BL_HD", /* 11.. 15 */
4400 "BL_TIME", "BL_HUNGER", "BL_HP", "BL_HPMAX", /* 16.. 19 */
4401 "BL_LEVELDESC", "BL_EXP", "BL_CONDITION" /* 20.. 22 */
4404 if (in_sanity_check)
4406 in_sanity_check = TRUE;
4408 * Make sure that every field made it down to the
4409 * bottom of the render_status() for-loop.
4411 for (i = 0; i < MAXBLSTATS; ++i) {
4412 if (tty_status[NOW][i].sanitycheck) {
4413 char panicmsg[BUFSZ];
4415 Sprintf(panicmsg, "failed on tty_status[NOW][%s].", idxtext[i]);
4416 paniclog("status_sanity_check", panicmsg);
4417 tty_status[NOW][i].sanitycheck = FALSE;
4419 * Attention developers: If you encounter the above
4420 * message in paniclog, it almost certainly means that
4421 * a recent code change has caused a failure to reach
4422 * the bottom of render_status(), at least for the BL_
4423 * field identified in the impossible() message.
4425 * That could be because of the addition of a continue
4426 * statement within the render_status() for-loop, or a
4427 * premature return from render_status() before it finished
4428 * its housekeeping chores.
4432 in_sanity_check = FALSE;
4434 #endif /* NHDEVEL_STATUS */
4437 * This is what places a field on the tty display.
4440 tty_putstatusfield(text, x, y)
4444 int i, n, ncols, nrows, lth = 0;
4445 struct WinDesc *cw = 0;
4447 if (WIN_STATUS == WIN_ERR
4448 || (cw = wins[WIN_STATUS]) == (struct WinDesc *) 0)
4449 panic("tty_putstatusfield: Invalid WinDesc\n");
4453 lth = (int) strlen(text);
4455 print_vt_code2(AVTC_SELECT_WINDOW, NHW_STATUS);
4457 if (x < ncols && y < nrows) {
4458 if (x != cw->curx || y != cw->cury)
4459 tty_curs(NHW_STATUS, x, y);
4460 for (i = 0; i < lth; ++i) {
4462 if (n < ncols && *text) {
4464 (void) putchar(*text);
4466 (void) jputchar(*(unsigned char *)text);
4470 cw->data[y][n - 1] = *text;
4477 if (truncation_expected) {
4478 /* Now we're truncating */
4479 ; /* but we knew in advance */
4485 /* caller must set cond_shrinklvl (0..2) before calling us */
4487 set_condition_length()
4492 if (tty_condition_bits) {
4493 for (c = 0; c < SIZE(conditions); ++c) {
4494 mask = conditions[c].mask;
4495 if ((tty_condition_bits & mask) == mask)
4496 lth += 1 + (int) strlen(conditions[c].text[cond_shrinklvl]);
4499 tty_status[NOW][BL_CONDITION].lth = lth;
4506 /* shrink or restore the encumbrance word */
4508 enc_shrinklvl = lvl;
4509 Sprintf(status_vals[BL_CAP], " %s", encvals[lvl][enclev]);
4511 tty_status[NOW][BL_CAP].lth = strlen(status_vals[BL_CAP]);
4518 /* try changing Dlvl: to Dl: */
4520 char *levval = index(status_vals[BL_LEVELDESC], ':');
4523 dlvl_shrinklvl = lvl;
4524 Strcpy(buf, (lvl == 0) ? "Dlvl" : "Dl");
4525 Strcat(buf, levval);
4526 Strcpy(status_vals[BL_LEVELDESC], buf);
4527 tty_status[NOW][BL_LEVELDESC].lth = strlen(status_vals[BL_LEVELDESC]);
4532 * Ensure the underlying status window data start out
4533 * blank and null-terminated.
4536 check_windowdata(VOID_ARGS)
4538 if (WIN_STATUS == WIN_ERR || wins[WIN_STATUS] == (struct WinDesc *) 0) {
4539 paniclog("check_windowdata", " null status window.");
4541 } else if (!windowdata_init) {
4542 tty_clear_nhwindow(WIN_STATUS); /* also sets cw->data[] to spaces */
4543 windowdata_init = TRUE;
4550 * Return what color this condition should
4551 * be displayed in based on user settings.
4554 condcolor(bm, bmarray)
4556 unsigned long *bmarray;
4561 for (i = 0; i < CLR_MAX; ++i) {
4562 if ((bm & bmarray[i]) != 0)
4568 /* might need something more elaborate if some compiler complains that
4569 the condition where this gets used always has the same value */
4570 #define condcolor(bm,bmarray) NO_COLOR
4571 #define term_start_color(color) /*empty*/
4572 #define term_end_color() /*empty*/
4573 #endif /* TEXTCOLOR */
4576 condattr(bm, bmarray)
4578 unsigned long *bmarray;
4583 if (bm && bmarray) {
4584 for (i = HL_ATTCLR_DIM; i < BL_ATTCLR_MAX; ++i) {
4585 if ((bm & bmarray[i]) != 0) {
4590 case HL_ATTCLR_BLINK:
4593 case HL_ATTCLR_ULINE:
4596 case HL_ATTCLR_INVERSE:
4599 case HL_ATTCLR_BOLD:
4609 #define Begin_Attr(m) \
4612 if ((m) & HL_BOLD) \
4613 term_start_attr(ATR_BOLD); \
4614 if ((m) & HL_INVERSE) \
4615 term_start_attr(ATR_INVERSE); \
4616 if ((m) & HL_ULINE) \
4617 term_start_attr(ATR_ULINE); \
4618 if ((m) & HL_BLINK) \
4619 term_start_attr(ATR_BLINK); \
4621 term_start_attr(ATR_DIM); \
4625 #define End_Attr(m) \
4629 term_end_attr(ATR_DIM); \
4630 if ((m) & HL_BLINK) \
4631 term_end_attr(ATR_BLINK); \
4632 if ((m) & HL_ULINE) \
4633 term_end_attr(ATR_ULINE); \
4634 if ((m) & HL_INVERSE) \
4635 term_end_attr(ATR_INVERSE); \
4636 if ((m) & HL_BOLD) \
4637 term_end_attr(ATR_BOLD); \
4642 render_status(VOID_ARGS)
4645 int i, x, y, idx, c, row, tlth, num_rows, coloridx = 0, attrmask = 0;
4647 struct WinDesc *cw = 0;
4649 if (WIN_STATUS == WIN_ERR
4650 || (cw = wins[WIN_STATUS]) == (struct WinDesc *) 0) {
4651 paniclog("render_status", "WIN_ERR on status window.");
4655 num_rows = (iflags.wc2_statuslines < 3) ? 2 : 3;
4656 for (row = 0; row < num_rows; ++row) {
4659 tty_curs(WIN_STATUS, 1, y);
4660 for (i = 0; (idx = fieldorder[row][i]) != BL_FLUSH; ++i) {
4661 if (!status_activefields[idx])
4663 x = tty_status[NOW][idx].x;
4664 text = status_vals[idx]; /* always "" for BL_CONDITION */
4665 tlth = (int) tty_status[NOW][idx].lth; /* valid for BL_CONDITION */
4667 if (tty_status[NOW][idx].redraw || !do_field_opt) {
4668 boolean hitpointbar = (idx == BL_TITLE
4669 && iflags.wc2_hitpointbar);
4671 if (idx == BL_CONDITION) {
4673 * +-----------------+
4674 * | Condition Codes |
4675 * +-----------------+
4677 bits = tty_condition_bits;
4678 /* if no bits are set, we can fall through condition
4679 rendering code to finalx[] handling (and subsequent
4680 rest-of-line erasure if line is shorter than before) */
4681 if (num_rows == 3 && bits != 0L) {
4683 char *dat = &cw->data[y][0];
4685 /* line up with hunger (or where it would have
4686 been when currently omitted); if there isn't
4687 enough room for that, right justify; or place
4688 as-is if not even enough room for /that/; we
4689 expect hunger to be on preceding row, in which
4690 case its current data has been moved to [BEFORE] */
4691 if (tty_status[BEFORE][BL_HUNGER].y < row
4692 && x < tty_status[BEFORE][BL_HUNGER].x
4693 && (tty_status[BEFORE][BL_HUNGER].x + tlth
4695 k = tty_status[BEFORE][BL_HUNGER].x;
4696 else if (x + tlth < cw->cols - 1)
4697 k = cw->cols - tlth;
4701 if (dat[x - 1] != ' ')
4702 tty_putstatusfield(" ", x, y);
4705 tty_status[NOW][BL_CONDITION].x = x;
4706 tty_curs(WIN_STATUS, x, y);
4708 for (c = 0; c < SIZE(conditions) && bits != 0L; ++c) {
4709 mask = conditions[c].mask;
4711 const char *condtext;
4713 tty_putstatusfield(" ", x++, y);
4714 if (iflags.hilite_delta) {
4715 attrmask = condattr(mask, tty_colormasks);
4716 Begin_Attr(attrmask);
4717 coloridx = condcolor(mask, tty_colormasks);
4718 if (coloridx != NO_COLOR)
4719 term_start_color(coloridx);
4721 condtext = conditions[c].text[cond_shrinklvl];
4722 if (x >= cw->cols && !truncation_expected) {
4724 "Unexpected condition placement overflow for \"%s\"",
4727 bits = 0L; /* skip any remaining conditions */
4729 tty_putstatusfield(condtext, x, y);
4730 x += (int) strlen(condtext);
4731 if (iflags.hilite_delta) {
4732 if (coloridx != NO_COLOR)
4739 /* 'x' is 1-based and 'cols' and 'data' are 0-based,
4740 so x==cols means we just stored in data[N-2] and
4741 are now positioned at data[N-1], the terminator;
4742 that's ok as long as we don't write there */
4744 static unsigned once_only = 0;
4746 if (!truncation_expected && !once_only++)
4747 paniclog("render_status()",
4748 " unexpected truncation.");
4751 } else if (hitpointbar) {
4753 * +-------------------------+
4754 * | Title with Hitpoint Bar |
4755 * +-------------------------+
4757 /* hitpointbar using hp percent calculation */
4758 int bar_len, bar_pos = 0;
4759 char bar[MAXCO], *bar2 = (char *) 0, savedch = '\0';
4760 boolean twoparts = (hpbar_percent < 100);
4762 /* force exactly 30 characters, padded with spaces
4763 if shorter or truncated if longer */
4764 if (strlen(text) != 30) {
4765 Sprintf(bar, "%-30.30s", text);
4766 Strcpy(status_vals[BL_TITLE], bar);
4769 bar_len = (int) strlen(bar); /* always 30 */
4771 /* when at full HP, the whole title will be highlighted;
4772 when injured or dead, there will be a second portion
4773 which is not highlighted */
4775 /* figure out where to separate the two parts */
4776 bar_pos = (bar_len * hpbar_percent) / 100;
4777 if (bar_pos < 1 && hpbar_percent > 0)
4779 if (bar_pos >= bar_len && hpbar_percent < 100)
4780 bar_pos = bar_len - 1;
4781 bar2 = &bar[bar_pos];
4785 tty_putstatusfield("[", x++, y);
4786 if (*bar) { /* always True, unless twoparts+dead (0 HP) */
4787 term_start_attr(ATR_INVERSE);
4788 if (iflags.hilite_delta && hpbar_color != NO_COLOR)
4789 term_start_color(hpbar_color);
4790 tty_putstatusfield(bar, x, y);
4791 x += (int) strlen(bar);
4792 if (iflags.hilite_delta && hpbar_color != NO_COLOR)
4794 term_end_attr(ATR_INVERSE);
4796 if (twoparts) { /* no highlighting for second part */
4798 tty_putstatusfield(bar2, x, y);
4799 x += (int) strlen(bar2);
4801 tty_putstatusfield("]", x++, y);
4804 * +-----------------------------+
4805 * | Everything else that is not |
4806 * | in a special case above |
4807 * +-----------------------------+
4809 if (iflags.hilite_delta) {
4810 while (*text == ' ') {
4811 tty_putstatusfield(" ", x++, y);
4814 if (*text == '/' && idx == BL_EXP) {
4815 tty_putstatusfield("/", x++, y);
4818 attrmask = tty_status[NOW][idx].attr;
4819 Begin_Attr(attrmask);
4820 coloridx = tty_status[NOW][idx].color;
4821 if (coloridx != NO_COLOR)
4822 term_start_color(coloridx);
4824 tty_putstatusfield(text, x, y);
4825 x += (int) strlen(text);
4826 if (iflags.hilite_delta) {
4827 if (coloridx != NO_COLOR)
4833 /* not rendered => same text as before */
4836 finalx[row][NOW] = x - 1;
4837 /* reset .redraw and .dirty now that field has been rendered */
4838 tty_status[NOW][idx].dirty = FALSE;
4839 tty_status[NOW][idx].redraw = FALSE;
4840 tty_status[NOW][idx].sanitycheck = FALSE;
4842 * For comparison of current and previous:
4843 * - Copy the entire tty_status struct.
4845 tty_status[BEFORE][idx] = tty_status[NOW][idx];
4847 x = finalx[row][NOW];
4848 if ((x < finalx[row][BEFORE] || !finalx[row][BEFORE])
4849 && x + 1 < cw->cols) {
4850 tty_curs(WIN_STATUS, x + 1, y);
4854 * For comparison of current and previous:
4855 * - Copy the last written column number on the row.
4857 finalx[row][BEFORE] = finalx[row][NOW];
4862 #endif /* STATUS_HILITES */
4864 #endif /* TTY_GRAPHICS */