gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
1048 lines
28 KiB
C
1048 lines
28 KiB
C
/* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
|
|
/* NetHack 3.7 curswins.c */
|
|
/* Copyright (c) Karl Garrison, 2010. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#if defined(CURSES_UNICODE) && !defined(_XOPEN_SOURCE_EXTENDED)
|
|
#define _XOPEN_SOURCE_EXTENDED 1
|
|
#endif
|
|
#include "curses.h"
|
|
#include "hack.h"
|
|
#include "wincurs.h"
|
|
#include "cursinit.h"
|
|
#include "cursmisc.h"
|
|
#include "curswins.h"
|
|
#include "cursstat.h"
|
|
|
|
/* Window handling for curses interface */
|
|
|
|
/* Private declarations */
|
|
|
|
typedef struct nhw {
|
|
winid nhwin; /* NetHack window id */
|
|
WINDOW *curwin; /* Curses window pointer */
|
|
int width; /* Usable width not counting border */
|
|
int height; /* Usable height not counting border */
|
|
int x; /* start of window on terminal (left) */
|
|
int y; /* start of window on terminal (top) */
|
|
int orientation; /* Placement of window relative to map */
|
|
boolean clr_inited; /* fg/bg/colorpair inited? */
|
|
int fg, bg; /* foreground, background color index */
|
|
int colorpair; /* color pair of fg, bg */
|
|
boolean border; /* Whether window has a visible border */
|
|
} nethack_window;
|
|
|
|
typedef struct nhwd {
|
|
winid nhwid; /* NetHack window id */
|
|
struct nhwd *prev_wid; /* Pointer to previous entry */
|
|
struct nhwd *next_wid; /* Pointer to next entry */
|
|
} nethack_wid;
|
|
|
|
typedef struct nhchar {
|
|
int ch; /* character */
|
|
int color; /* color info for character */
|
|
int framecolor; /* background color info for character */
|
|
int attr; /* attributes of character */
|
|
struct unicode_representation *unicode_representation;
|
|
} nethack_char;
|
|
|
|
static boolean map_clipped; /* Map window smaller than 80x21 */
|
|
static nethack_window nhwins[NHWIN_MAX]; /* NetHack window array */
|
|
static nethack_char map[ROWNO][COLNO]; /* Map window contents */
|
|
static nethack_wid *nhwids = NULL; /* NetHack wid array */
|
|
|
|
static boolean is_main_window(winid wid);
|
|
static void write_char(WINDOW * win, int x, int y, nethack_char ch);
|
|
static void clear_map(void);
|
|
|
|
/* Create a window with the specified size and orientation */
|
|
|
|
WINDOW *
|
|
curses_create_window(int wid, int width, int height, orient orientation)
|
|
{
|
|
int mapx = 0, mapy = 0, maph = 0, mapw = 0;
|
|
int startx = 0;
|
|
int starty = 0;
|
|
WINDOW *win;
|
|
boolean map_border = FALSE;
|
|
int mapb_offset = 0;
|
|
|
|
if ((orientation == UP) || (orientation == DOWN) ||
|
|
(orientation == LEFT) || (orientation == RIGHT)) {
|
|
if (svm.moves > 0) {
|
|
map_border = curses_window_has_border(MAP_WIN);
|
|
curses_get_window_xy(MAP_WIN, &mapx, &mapy);
|
|
curses_get_window_size(MAP_WIN, &maph, &mapw);
|
|
} else {
|
|
map_border = TRUE;
|
|
mapx = 0;
|
|
mapy = 0;
|
|
maph = term_rows;
|
|
mapw = term_cols;
|
|
}
|
|
}
|
|
|
|
if (map_border) {
|
|
mapb_offset = 1;
|
|
}
|
|
|
|
width += 2; /* leave room for bounding box */
|
|
height += 2;
|
|
|
|
if ((width > term_cols) || (height > term_rows)) {
|
|
impossible(
|
|
"curses_create_window: Terminal too small for dialog window");
|
|
width = term_cols;
|
|
height = term_rows;
|
|
}
|
|
switch (orientation) {
|
|
default:
|
|
impossible("curses_create_window: Bad orientation");
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case CENTER:
|
|
startx = (term_cols / 2) - (width / 2);
|
|
starty = (term_rows / 2) - (height / 2);
|
|
break;
|
|
case UP:
|
|
if (svm.moves > 0) {
|
|
startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
|
|
} else {
|
|
startx = 0;
|
|
}
|
|
|
|
starty = mapy + mapb_offset;
|
|
break;
|
|
case DOWN:
|
|
if (svm.moves > 0) {
|
|
startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
|
|
} else {
|
|
startx = 0;
|
|
}
|
|
|
|
starty = height - mapy - 1 - mapb_offset;
|
|
break;
|
|
case LEFT:
|
|
if (map_border && (width < term_cols))
|
|
startx = 1;
|
|
else
|
|
startx = 0;
|
|
starty = term_rows - height;
|
|
break;
|
|
case RIGHT:
|
|
if (svm.moves > 0) {
|
|
startx = (mapw + mapx + (mapb_offset * 2)) - width;
|
|
} else {
|
|
startx = term_cols - width;
|
|
}
|
|
|
|
starty = 0;
|
|
break;
|
|
}
|
|
|
|
if (startx < 0) {
|
|
startx = 0;
|
|
}
|
|
|
|
if (starty < 0) {
|
|
starty = 0;
|
|
}
|
|
|
|
win = newwin(height, width, starty, startx);
|
|
|
|
if (curses_is_text(wid))
|
|
wid = TEXT_WIN;
|
|
else if (curses_is_menu(wid))
|
|
wid = MENU_WIN;
|
|
|
|
if (nhwins[wid].clr_inited < 1)
|
|
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
|
|
box(win, 0, 0);
|
|
if (nhwins[wid].clr_inited < 1)
|
|
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
|
|
return win;
|
|
}
|
|
|
|
int
|
|
curses_win_clr_inited(int wid)
|
|
{
|
|
if (curses_is_text(wid)) {
|
|
wid = TEXT_WIN;
|
|
} else if (curses_is_menu(wid)) {
|
|
wid = MENU_WIN;
|
|
}
|
|
return nhwins[wid].clr_inited;
|
|
}
|
|
|
|
void
|
|
curses_set_wid_colors(int wid, WINDOW *win)
|
|
{
|
|
if (wid == TEXT_WIN || curses_is_text(wid)) {
|
|
wid = TEXT_WIN;
|
|
if (!nhwins[wid].clr_inited)
|
|
curses_parse_wid_colors(wid, iflags.wcolors[wcolor_text].fg,
|
|
iflags.wcolors[wcolor_text].bg);
|
|
} else if (wid == MENU_WIN || curses_is_menu(wid)) {
|
|
wid = MENU_WIN;
|
|
if (!nhwins[wid].clr_inited)
|
|
curses_parse_wid_colors(wid, iflags.wcolors[wcolor_menu].fg,
|
|
iflags.wcolors[wcolor_menu].bg);
|
|
}
|
|
/* FIXME: colors and nhwins[] entry for perm invent window */
|
|
if (nhwins[wid].clr_inited > 0) {
|
|
wbkgd(win ? win : nhwins[wid].curwin,
|
|
COLOR_PAIR(nhwins[wid].colorpair));
|
|
}
|
|
}
|
|
|
|
/* Erase and delete curses window, and refresh standard windows */
|
|
|
|
void
|
|
curses_destroy_win(WINDOW *win)
|
|
{
|
|
int mapwidth = 0, winwidth, dummyht;
|
|
|
|
/*
|
|
* In case map is narrower than the space alloted for it, if we
|
|
* are destroying a popup window and it is wider than the map,
|
|
* erase the popup first. It probably has overwritten some of
|
|
* the next-to-map empty space. If we don't clear that now, the
|
|
* base window will remember it and redisplay it during refreshes.
|
|
*
|
|
* Note: since we almost never destroy non-popups, we don't really
|
|
* need to determine whether 'win' is one. Overhead for unnecessary
|
|
* erasure is negligible.
|
|
*/
|
|
getmaxyx(win, dummyht, winwidth); /* macro, assigns to its args */
|
|
if (mapwin)
|
|
getmaxyx(mapwin, dummyht, mapwidth);
|
|
if (winwidth > mapwidth) {
|
|
werase(win);
|
|
wnoutrefresh(win);
|
|
}
|
|
|
|
delwin(win);
|
|
if (win == activemenu)
|
|
activemenu = NULL;
|
|
curses_refresh_nethack_windows();
|
|
nhUse(dummyht);
|
|
}
|
|
|
|
|
|
/* Refresh nethack windows if they exist, or base window if not */
|
|
|
|
void
|
|
curses_refresh_nethack_windows(void)
|
|
{
|
|
WINDOW *status_window, *message_window, *map_window, *inv_window;
|
|
|
|
status_window = curses_get_nhwin(STATUS_WIN);
|
|
message_window = curses_get_nhwin(MESSAGE_WIN);
|
|
map_window = curses_get_nhwin(MAP_WIN);
|
|
inv_window = curses_get_nhwin(INV_WIN);
|
|
|
|
if (!iflags.window_inited) {
|
|
return;
|
|
}
|
|
|
|
if (svm.moves == 0) {
|
|
/* Main windows not yet displayed; refresh base window instead */
|
|
touchwin(stdscr);
|
|
refresh();
|
|
} else {
|
|
if (status_window != NULL) {
|
|
curses_set_wid_colors(STATUS_WIN, NULL);
|
|
touchwin(status_window);
|
|
wnoutrefresh(status_window);
|
|
}
|
|
if (map_window != NULL) {
|
|
touchwin(map_window);
|
|
wnoutrefresh(map_window);
|
|
}
|
|
if (message_window != NULL) {
|
|
curses_set_wid_colors(MESSAGE_WIN, NULL);
|
|
touchwin(message_window);
|
|
wnoutrefresh(message_window);
|
|
}
|
|
if (inv_window) {
|
|
touchwin(inv_window);
|
|
wnoutrefresh(inv_window);
|
|
}
|
|
doupdate();
|
|
}
|
|
}
|
|
|
|
|
|
/* Return curses window pointer for given NetHack winid */
|
|
|
|
WINDOW *
|
|
curses_get_nhwin(winid wid)
|
|
{
|
|
if (!is_main_window(wid)) {
|
|
impossible("curses_get_nhwin: wid %d out of range. Not a main window.",
|
|
wid);
|
|
return NULL;
|
|
}
|
|
|
|
return nhwins[wid].curwin;
|
|
}
|
|
|
|
boolean
|
|
parse_hexstr(char *colorbuf, int *red, int *green, int *blue)
|
|
{
|
|
int len = colorbuf ? strlen(colorbuf) : 0;
|
|
|
|
if (len == 7 && colorbuf[0] == '#') {
|
|
char tmpbuf[16];
|
|
|
|
Sprintf(tmpbuf, "0x%c%c", colorbuf[1], colorbuf[2]);
|
|
*red = strtol(tmpbuf, NULL, 0);
|
|
Sprintf(tmpbuf, "0x%c%c", colorbuf[3], colorbuf[4]);
|
|
*green = strtol(tmpbuf, NULL, 0);
|
|
Sprintf(tmpbuf, "0x%c%c", colorbuf[5], colorbuf[6]);
|
|
*blue = strtol(tmpbuf, NULL, 0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
curses_parse_wid_colors(int wid, char *fg, char *bg)
|
|
{
|
|
|
|
if (curses_is_text(wid)) {
|
|
wid = TEXT_WIN;
|
|
} else if (curses_is_menu(wid)) {
|
|
wid = MENU_WIN;
|
|
}
|
|
|
|
if (nhwins[wid].clr_inited)
|
|
return;
|
|
|
|
int nh_fg = fg ? match_str2clr(fg, TRUE) : CLR_MAX;
|
|
int nh_bg = bg ? match_str2clr(bg, TRUE) : CLR_MAX;
|
|
int r, g, b;
|
|
|
|
if (nh_fg == CLR_MAX) {
|
|
if (fg && parse_hexstr(fg, &r, &g, &b)) {
|
|
nh_fg = curses_init_rgb(r, g, b);
|
|
} else {
|
|
nh_fg = -1;
|
|
}
|
|
}
|
|
if (nh_bg == CLR_MAX) {
|
|
if (bg && parse_hexstr(bg, &r, &g, &b)) {
|
|
nh_bg = curses_init_rgb(r, g, b);
|
|
} else {
|
|
nh_bg = -1;
|
|
}
|
|
}
|
|
|
|
nhwins[wid].fg = nh_fg;
|
|
nhwins[wid].bg = nh_bg;
|
|
if (nh_fg == -1 || nh_bg == -1) {
|
|
nhwins[wid].clr_inited = -1;
|
|
} else {
|
|
nhwins[wid].colorpair = curses_init_pair(nh_fg, nh_bg);
|
|
nhwins[wid].clr_inited = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/* Add curses window pointer and window info to list for given NetHack winid */
|
|
|
|
void
|
|
curses_add_nhwin(
|
|
winid wid, int height, int width, int y, int x,
|
|
orient orientation, boolean border)
|
|
{
|
|
WINDOW *win;
|
|
int real_width = width;
|
|
int real_height = height;
|
|
|
|
if (!is_main_window(wid)) {
|
|
impossible("curses_add_nhwin: wid %d out of range. Not a main window.",
|
|
wid);
|
|
return;
|
|
}
|
|
|
|
nhwins[wid].nhwin = wid;
|
|
nhwins[wid].border = border;
|
|
nhwins[wid].width = width;
|
|
nhwins[wid].height = height;
|
|
nhwins[wid].x = x;
|
|
nhwins[wid].y = y;
|
|
nhwins[wid].orientation = orientation;
|
|
nhwins[wid].fg = nhwins[wid].bg = 0;
|
|
nhwins[wid].colorpair = -1;
|
|
nhwins[wid].clr_inited = 0;
|
|
|
|
if (border) {
|
|
real_width += 2; /* leave room for bounding box */
|
|
real_height += 2;
|
|
}
|
|
|
|
win = newwin(real_height, real_width, y, x);
|
|
nhwins[wid].curwin = win;
|
|
|
|
switch (wid) {
|
|
case MESSAGE_WIN:
|
|
messagewin = win;
|
|
curses_parse_wid_colors(wid, iflags.wcolors[wcolor_message].fg,
|
|
iflags.wcolors[wcolor_message].bg);
|
|
curses_set_wid_colors(wid, NULL);
|
|
break;
|
|
case STATUS_WIN:
|
|
statuswin = win;
|
|
curses_parse_wid_colors(wid, iflags.wcolors[wcolor_status].fg,
|
|
iflags.wcolors[wcolor_status].bg);
|
|
curses_set_wid_colors(wid, NULL);
|
|
break;
|
|
case MAP_WIN:
|
|
mapwin = win;
|
|
map_clipped = (width < COLNO || height < ROWNO);
|
|
break;
|
|
}
|
|
|
|
if (border) {
|
|
box(win, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Add wid to list of known window IDs */
|
|
|
|
void
|
|
curses_add_wid(winid wid)
|
|
{
|
|
nethack_wid *new_wid;
|
|
nethack_wid *widptr = nhwids;
|
|
|
|
new_wid = (nethack_wid *) alloc((unsigned) sizeof (nethack_wid));
|
|
new_wid->nhwid = wid;
|
|
|
|
new_wid->next_wid = NULL;
|
|
|
|
if (widptr == NULL) {
|
|
new_wid->prev_wid = NULL;
|
|
nhwids = new_wid;
|
|
} else {
|
|
while (widptr->next_wid != NULL) {
|
|
widptr = widptr->next_wid;
|
|
}
|
|
new_wid->prev_wid = widptr;
|
|
widptr->next_wid = new_wid;
|
|
}
|
|
}
|
|
|
|
|
|
/* refresh a curses window via given nethack winid */
|
|
|
|
void
|
|
curses_refresh_nhwin(winid wid)
|
|
{
|
|
curses_set_wid_colors(wid, NULL);
|
|
wnoutrefresh(curses_get_nhwin(wid));
|
|
doupdate();
|
|
}
|
|
|
|
|
|
/* Delete curses window via given NetHack winid and remove entry from list */
|
|
|
|
void
|
|
curses_del_nhwin(winid wid)
|
|
{
|
|
if (curses_is_menu(wid) || curses_is_text(wid)) {
|
|
curses_del_menu(wid, TRUE);
|
|
return;
|
|
} else if (wid == INV_WIN) {
|
|
curses_del_menu(wid, TRUE);
|
|
/* don't return yet */
|
|
}
|
|
|
|
if (!is_main_window(wid)) {
|
|
impossible("curses_del_nhwin: wid %d out of range. Not a main window.",
|
|
wid);
|
|
return;
|
|
}
|
|
if (nhwins[wid].curwin != NULL) {
|
|
delwin(nhwins[wid].curwin);
|
|
nhwins[wid].curwin = NULL;
|
|
}
|
|
nhwins[wid].nhwin = -1;
|
|
}
|
|
|
|
|
|
/* Delete wid from list of known window IDs */
|
|
|
|
void
|
|
curses_del_wid(winid wid)
|
|
{
|
|
nethack_wid *tmpwid;
|
|
nethack_wid *widptr;
|
|
|
|
if (curses_is_menu(wid) || curses_is_text(wid)) {
|
|
curses_del_menu(wid, FALSE);
|
|
}
|
|
|
|
for (widptr = nhwids; widptr; widptr = widptr->next_wid) {
|
|
if (widptr->nhwid == wid) {
|
|
if ((tmpwid = widptr->prev_wid) != NULL) {
|
|
tmpwid->next_wid = widptr->next_wid;
|
|
} else {
|
|
nhwids = widptr->next_wid; /* New head mode, or NULL */
|
|
}
|
|
if ((tmpwid = widptr->next_wid) != NULL) {
|
|
tmpwid->prev_wid = widptr->prev_wid;
|
|
}
|
|
free(widptr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* called by destroy_nhwindows() prior to exit */
|
|
void
|
|
curs_destroy_all_wins(void)
|
|
{
|
|
curses_count_window((char *) 0); /* clean up orphan */
|
|
|
|
while (nhwids)
|
|
curses_del_wid(nhwids->nhwid);
|
|
}
|
|
|
|
/* Print a single character in the given window at the given coordinates */
|
|
|
|
void
|
|
#ifdef ENHANCED_SYMBOLS
|
|
curses_putch(winid wid, int x, int y, int ch,
|
|
struct unicode_representation *unicode_representation,
|
|
int color, int framecolor, int attr)
|
|
#else
|
|
curses_putch(winid wid, int x, int y, int ch, int color, int framecolor, int attr)
|
|
#endif
|
|
{
|
|
static boolean map_initted = FALSE;
|
|
int sx, sy, ex, ey;
|
|
boolean border = curses_window_has_border(wid);
|
|
nethack_char nch;
|
|
/*
|
|
if (wid == STATUS_WIN) {
|
|
curses_update_stats();
|
|
}
|
|
*/
|
|
if (wid != MAP_WIN) {
|
|
return;
|
|
}
|
|
|
|
if (!map_initted) {
|
|
clear_map();
|
|
map_initted = TRUE;
|
|
}
|
|
|
|
--x; /* map column [0] is not used; draw column [1] in first screen col */
|
|
map[y][x].ch = ch;
|
|
map[y][x].color = color;
|
|
map[y][x].framecolor = framecolor;
|
|
map[y][x].attr = attr;
|
|
#ifdef ENHANCED_SYMBOLS
|
|
map[y][x].unicode_representation = unicode_representation;
|
|
#endif
|
|
nch = map[y][x];
|
|
|
|
(void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);
|
|
|
|
if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
|
|
if (border) {
|
|
x++;
|
|
y++;
|
|
}
|
|
|
|
write_char(mapwin, x - sx, y - sy, nch);
|
|
}
|
|
/* refresh after every character?
|
|
* Fair go, mate! Some of us are playing from Australia! */
|
|
/* wrefresh(mapwin); */
|
|
}
|
|
|
|
|
|
/* Get x, y coordinates of curses window on the physical terminal window */
|
|
|
|
void
|
|
curses_get_window_xy(winid wid, int *x, int *y)
|
|
{
|
|
if (!is_main_window(wid)) {
|
|
impossible(
|
|
"curses_get_window_xy: wid %d out of range. Not a main window.",
|
|
wid);
|
|
*x = 0;
|
|
*y = 0;
|
|
return;
|
|
}
|
|
|
|
*x = nhwins[wid].x;
|
|
*y = nhwins[wid].y;
|
|
}
|
|
|
|
|
|
/* Get usable width and height curses window on the physical terminal window */
|
|
|
|
void
|
|
curses_get_window_size(winid wid, int *height, int *width)
|
|
{
|
|
*height = nhwins[wid].height;
|
|
*width = nhwins[wid].width;
|
|
}
|
|
|
|
|
|
/* Determine if given window has a visible border */
|
|
|
|
boolean
|
|
curses_window_has_border(winid wid)
|
|
{
|
|
if (curses_is_menu(wid))
|
|
wid = MENU_WIN;
|
|
else if (curses_is_text(wid))
|
|
wid = TEXT_WIN;
|
|
return nhwins[wid].border;
|
|
}
|
|
|
|
|
|
/* Determine if window for given winid exists */
|
|
|
|
boolean
|
|
curses_window_exists(winid wid)
|
|
{
|
|
nethack_wid *widptr;
|
|
|
|
for (widptr = nhwids; widptr; widptr = widptr->next_wid)
|
|
if (widptr->nhwid == wid)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Return the orientation of the specified window */
|
|
|
|
int
|
|
curses_get_window_orientation(winid wid)
|
|
{
|
|
if (!is_main_window(wid)) {
|
|
impossible(
|
|
"curses_get_window_orientation: wid %d out of range. Not a main window.",
|
|
wid);
|
|
return CENTER;
|
|
}
|
|
|
|
return nhwins[wid].orientation;
|
|
}
|
|
|
|
|
|
/* Output a line of text to specified NetHack window with given coordinates
|
|
and text attributes */
|
|
|
|
void
|
|
curses_puts(winid wid, int attr, const char *text)
|
|
{
|
|
anything Id;
|
|
WINDOW *win = NULL;
|
|
|
|
if (is_main_window(wid)) {
|
|
win = curses_get_nhwin(wid);
|
|
}
|
|
|
|
if (wid == MESSAGE_WIN) {
|
|
/* if a no-history message is being shown, remove it */
|
|
if (counting)
|
|
curses_count_window((char *) 0);
|
|
|
|
curses_message_win_puts(text, FALSE);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
if (wid == STATUS_WIN) {
|
|
curses_update_stats(); /* We will do the write ourselves */
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (curses_is_menu(wid) || curses_is_text(wid)) {
|
|
if (!curses_menu_exists(wid)) {
|
|
impossible(
|
|
"curses_puts: Attempted write to nonexistent window %d!",
|
|
wid);
|
|
return;
|
|
}
|
|
Id = cg.zeroany;
|
|
curses_add_nhmenu_item(wid, &nul_glyphinfo, &Id, 0, 0,
|
|
attr, NO_COLOR, text, MENU_ITEMFLAGS_NONE);
|
|
} else {
|
|
curses_set_wid_colors(wid, NULL);
|
|
waddstr(win, text);
|
|
wnoutrefresh(win);
|
|
}
|
|
}
|
|
|
|
|
|
/* Clear the contents of a window via the given NetHack winid */
|
|
|
|
void
|
|
curses_clear_nhwin(winid wid)
|
|
{
|
|
WINDOW *win = curses_get_nhwin(wid);
|
|
boolean border = curses_window_has_border(wid);
|
|
|
|
if (wid == MAP_WIN) {
|
|
clearok(win, TRUE); /* Redraw entire screen when refreshed */
|
|
clear_map();
|
|
}
|
|
|
|
curses_set_wid_colors(wid, NULL);
|
|
werase(win);
|
|
|
|
if (border) {
|
|
box(win, 0, 0);
|
|
}
|
|
}
|
|
|
|
/* Change colour of window border to alert player to something */
|
|
void
|
|
curses_alert_win_border(winid wid, boolean onoff)
|
|
{
|
|
WINDOW *win = curses_get_nhwin(wid);
|
|
|
|
if (!win || !curses_window_has_border(wid))
|
|
return;
|
|
curses_set_wid_colors(wid, NULL);
|
|
if (onoff)
|
|
curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
|
|
box(win, 0, 0);
|
|
if (onoff)
|
|
curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
|
|
wnoutrefresh(win);
|
|
}
|
|
|
|
|
|
void
|
|
curses_alert_main_borders(boolean onoff)
|
|
{
|
|
curses_alert_win_border(MAP_WIN, onoff);
|
|
curses_alert_win_border(MESSAGE_WIN, onoff);
|
|
curses_alert_win_border(STATUS_WIN, onoff);
|
|
curses_alert_win_border(INV_WIN, onoff);
|
|
}
|
|
|
|
/* Return true if given wid is a main NetHack window */
|
|
|
|
static boolean
|
|
is_main_window(winid wid)
|
|
{
|
|
if (wid == MESSAGE_WIN || wid == MAP_WIN
|
|
|| wid == STATUS_WIN || wid == INV_WIN)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Unconditionally write a single character to a window at the given
|
|
coordinates without a refresh. Currently only used for the map. */
|
|
|
|
/* convert nhcolor (fg) and framecolor (bg) to curses colorpair */
|
|
int
|
|
get_framecolor(int nhcolor, int framecolor)
|
|
{
|
|
/* curses_toggle_color_attr() adds the +1 and takes care of COLORS < 16 */
|
|
return (16 * (framecolor % 8)) + (nhcolor % 16);
|
|
}
|
|
|
|
static void
|
|
write_char(WINDOW * win, int x, int y, nethack_char nch)
|
|
{
|
|
int curscolor = nch.color, cursattr = nch.attr;
|
|
|
|
if (nch.framecolor != NO_COLOR) {
|
|
curscolor = get_framecolor(nch.color, nch.framecolor);
|
|
if (nch.attr == A_REVERSE)
|
|
cursattr = A_NORMAL; /* hilited pet looks odd otherwise */
|
|
}
|
|
curses_toggle_color_attr(win, curscolor, cursattr, ON);
|
|
#if defined(CURSES_UNICODE) && defined(ENHANCED_SYMBOLS)
|
|
if ((nch.unicode_representation && nch.unicode_representation->utf8str)
|
|
|| SYMHANDLING(H_IBM)) {
|
|
/* CP437 to Unicode mapping according to the Unicode Consortium */
|
|
static const uint16 cp437[256] = {
|
|
0x0020, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
|
|
0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
|
|
0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
|
|
0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
|
|
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
|
|
0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
|
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
|
0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
|
|
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
|
0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
|
|
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
|
|
0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
|
|
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
|
0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
|
|
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
|
|
0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
|
|
0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
|
|
0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
|
|
0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
|
|
0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
|
|
0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
|
|
0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
|
|
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
|
|
0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
|
|
0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
|
|
0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
|
|
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
|
|
0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
|
|
0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
|
|
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
|
|
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
|
|
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
|
|
};
|
|
attr_t attr;
|
|
short pair;
|
|
wchar_t wch[3];
|
|
uint32 utf32ch;
|
|
cchar_t cch;
|
|
|
|
if (SYMHANDLING(H_UTF8)) {
|
|
utf32ch = nch.unicode_representation->utf32ch;
|
|
} else if (SYMHANDLING(H_IBM)) {
|
|
utf32ch = cp437[(uint8)nch.ch];
|
|
} else {
|
|
utf32ch = (uint8)nch.ch;
|
|
}
|
|
if (sizeof(wchar_t) == 2 && utf32ch >= 0x10000) {
|
|
/* UTF-16 surrogate pair */
|
|
wch[0] = (wchar_t)((utf32ch >> 10) + 0xD7C0);
|
|
wch[1] = (wchar_t)((utf32ch & 0x3FF) + 0xDC00);
|
|
wch[2] = L'\0';
|
|
} else {
|
|
wch[0] = (wchar_t)utf32ch;
|
|
wch[1] = L'\0';
|
|
}
|
|
wmove(win, y, x);
|
|
wattr_get(win, &attr, &pair, NULL);
|
|
setcchar(&cch, wch, attr, pair, NULL);
|
|
mvwadd_wch(win, y, x, &cch);
|
|
} else
|
|
#endif
|
|
#ifdef PDCURSES
|
|
mvwaddrawch(win, y, x, nch.ch);
|
|
#else
|
|
mvwaddch(win, y, x, nch.ch);
|
|
#endif
|
|
curses_toggle_color_attr(win, curscolor, cursattr, OFF);
|
|
}
|
|
|
|
|
|
/* Draw the entire visible map onto the screen given the visible map
|
|
boundaries */
|
|
|
|
void
|
|
curses_draw_map(int sx, int sy, int ex, int ey)
|
|
{
|
|
int curx, cury;
|
|
int bspace = 0;
|
|
|
|
#ifdef MAP_SCROLLBARS
|
|
int sbsx, sbsy, sbex, sbey, count;
|
|
nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
|
|
#endif
|
|
|
|
if (curses_window_has_border(MAP_WIN)) {
|
|
bspace++;
|
|
}
|
|
#ifdef MAP_SCROLLBARS
|
|
hsb_back.ch = '-';
|
|
hsb_back.color = SCROLLBAR_BACK_COLOR;
|
|
hsb_back.framecolor = NO_COLOR;
|
|
hsb_back.attr = A_NORMAL;
|
|
hsb_back.unicode_representation = NULL;
|
|
hsb_bar.ch = '*';
|
|
hsb_bar.color = SCROLLBAR_COLOR;
|
|
hsb_bar.framecolor = NO_COLOR;
|
|
hsb_bar.attr = A_NORMAL;
|
|
hsb_bar.unicode_representation = NULL;
|
|
vsb_back.ch = '|';
|
|
vsb_back.color = SCROLLBAR_BACK_COLOR;
|
|
vsb_back.framecolor = NO_COLOR;
|
|
vsb_back.attr = A_NORMAL;
|
|
vsb_back.unicode_representation = NULL;
|
|
vsb_bar.ch = '*';
|
|
vsb_bar.color = SCROLLBAR_COLOR;
|
|
vsb_bar.framecolor = NO_COLOR;
|
|
vsb_bar.attr = A_NORMAL;
|
|
vsb_bar.unicode_representation = NULL;
|
|
|
|
/* Horizontal scrollbar */
|
|
if (sx > 0 || ex < (COLNO - 1)) {
|
|
sbsx = (int) (((long) sx * (long) (ex - sx + 1)) / (long) COLNO);
|
|
sbex = (int) (((long) ex * (long) (ex - sx + 1)) / (long) COLNO);
|
|
|
|
if (sx > 0 && sbsx == 0)
|
|
++sbsx;
|
|
if (ex < COLNO - 1 && sbex == COLNO - 1)
|
|
--sbex;
|
|
|
|
for (count = 0; count < sbsx; count++) {
|
|
write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
|
|
}
|
|
for (count = sbsx; count <= sbex; count++) {
|
|
write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
|
|
}
|
|
for (count = sbex + 1; count <= (ex - sx); count++) {
|
|
write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
|
|
}
|
|
}
|
|
|
|
/* Vertical scrollbar */
|
|
if (sy > 0 || ey < (ROWNO - 1)) {
|
|
sbsy = (int) (((long) sy * (long) (ey - sy + 1)) / (long) ROWNO);
|
|
sbey = (int) (((long) ey * (long) (ey - sy + 1)) / (long) ROWNO);
|
|
|
|
if (sy > 0 && sbsy == 0)
|
|
++sbsy;
|
|
if (ey < ROWNO - 1 && sbey == ROWNO - 1)
|
|
--sbey;
|
|
|
|
for (count = 0; count < sbsy; count++) {
|
|
write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
|
|
}
|
|
for (count = sbsy; count <= sbey; count++) {
|
|
write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
|
|
}
|
|
for (count = sbey + 1; count <= (ey - sy); count++) {
|
|
write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
|
|
}
|
|
}
|
|
#endif /* MAP_SCROLLBARS */
|
|
|
|
for (curx = sx; curx <= ex; curx++) {
|
|
for (cury = sy; cury <= ey; cury++) {
|
|
write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
|
|
map[cury][curx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Init map array to blanks */
|
|
|
|
static void
|
|
clear_map(void)
|
|
{
|
|
int x, y;
|
|
|
|
for (x = 0; x < COLNO; x++) {
|
|
for (y = 0; y < ROWNO; y++) {
|
|
map[y][x].ch = ' ';
|
|
map[y][x].color = NO_COLOR;
|
|
map[y][x].attr = A_NORMAL;
|
|
map[y][x].unicode_representation = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Determine visible boundaries of map, and determine if it needs to be
|
|
based on the location of the player. */
|
|
|
|
boolean
|
|
curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
|
|
{
|
|
static int width = 0;
|
|
static int height = 0;
|
|
static int osx = 0;
|
|
static int osy = 0;
|
|
static int oex = 0;
|
|
static int oey = 0;
|
|
static int oux = -1;
|
|
static int ouy = -1;
|
|
|
|
if ((oux == -1) || (ouy == -1)) {
|
|
oux = u.ux;
|
|
ouy = u.uy;
|
|
}
|
|
|
|
if (ux == -1) {
|
|
ux = oux;
|
|
} else {
|
|
oux = ux;
|
|
}
|
|
|
|
if (uy == -1) {
|
|
uy = ouy;
|
|
} else {
|
|
ouy = uy;
|
|
}
|
|
|
|
curses_get_window_size(MAP_WIN, &height, &width);
|
|
|
|
#ifdef MAP_SCROLLBARS
|
|
if (width < COLNO) {
|
|
height--; /* room for horizontal scrollbar */
|
|
}
|
|
|
|
if (height < ROWNO) {
|
|
width--; /* room for vertical scrollbar */
|
|
|
|
if (width == COLNO) {
|
|
height--;
|
|
}
|
|
}
|
|
#endif /* MAP_SCROLLBARS */
|
|
|
|
if (width >= COLNO) {
|
|
*sx = 0;
|
|
*ex = COLNO - 1;
|
|
} else {
|
|
*ex = (width / 2) + ux;
|
|
*sx = *ex - (width - 1);
|
|
|
|
if (*ex >= COLNO) {
|
|
*sx = COLNO - width;
|
|
*ex = COLNO - 1;
|
|
} else if (*sx < 0) {
|
|
*sx = 0;
|
|
*ex = width - 1;
|
|
}
|
|
}
|
|
|
|
if (height >= ROWNO) {
|
|
*sy = 0;
|
|
*ey = ROWNO - 1;
|
|
} else {
|
|
*ey = (height / 2) + uy;
|
|
*sy = *ey - (height - 1);
|
|
|
|
if (*ey >= ROWNO) {
|
|
*sy = ROWNO - height;
|
|
*ey = ROWNO - 1;
|
|
} else if (*sy < 0) {
|
|
*sy = 0;
|
|
*ey = height - 1;
|
|
}
|
|
}
|
|
|
|
if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
|
|
map_clipped) {
|
|
osx = *sx;
|
|
osy = *sy;
|
|
oex = *ex;
|
|
oey = *ey;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|