Files
nethack/win/curses/curswins.c
PatR b264e17cf2 fix use-after-free bug in curses shutdown
A year ago a use-free-bug was reported for curses.  I don't use ASAN
so haven't reproduced it, but I think this should fix it.

If the RIP window is deleted after the map window has gone away, the
code from commit 5e572d3d5f (post 3.6.7)
would execute and access the internals of the deleted map window.
2026-04-11 01:02:42 -07:00

1059 lines
29 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;
/* during shutdown, RIP window could still be active after mapwin goes
away; so, avoid 'if (mapwin)' above when deleting RIP window later */
if (win == mapwin)
win = mapwin = 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 */
#if 0 /* this works but confuses the static analyzer (from llvm-19,
* which reports "warning: Use of memory after it is freed") */
while (nhwids)
curses_del_wid(nhwids->nhwid);
#else
while (nhwids) {
nethack_wid *tmpptr = nhwids;
curses_del_wid(tmpptr->nhwid);
}
#endif
}
/* Print a single character in the given window at the given coordinates */
void
curses_putch(winid wid, int x, int y, int ch,
#ifdef ENHANCED_SYMBOLS
struct unicode_representation *unicode_representation,
#endif
int color, int framecolor, int attr)
{
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;
}