1
0
Fork 0
mirror of https://codeberg.org/noisytoot/notnotdnethack.git synced 2024-09-19 22:14:48 +01:00
notnotdnethack/win/curses/curswins.c
2024-05-15 21:38:02 +01:00

785 lines
19 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" -*-*/
#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "curswins.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 termial (top) */
int orientation; /* Placement of window relative to map */
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;
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 clear_map(void);
/* Create a window with the specified size and orientation */
WINDOW *
curses_create_window(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 (invent || (moves > 1)) {
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");
/* fall through to centre */
case CENTER:
startx = (term_cols / 2) - (width / 2);
starty = (term_rows / 2) - (height / 2);
break;
case UP:
if (invent || (moves > 1)) {
startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
} else {
startx = 0;
}
starty = mapy + mapb_offset;
break;
case DOWN:
if (invent || (moves > 1)) {
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 (invent || (moves > 1)) {
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);
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
box(win, 0, 0);
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
return win;
}
/* Erase and delete curses window, and refresh standard windows */
void
curses_destroy_win(WINDOW * win)
{
werase(win);
wrefresh(win);
delwin(win);
curses_refresh_nethack_windows();
}
/* 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 ((moves <= 1) && !invent) {
/* Main windows not yet displayed; refresh base window instead */
touchwin(stdscr);
refresh();
} else {
touchwin(status_window);
wnoutrefresh(status_window);
touchwin(map_window);
wnoutrefresh(map_window);
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;
}
/* 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;
if (border) {
real_width += 2; /* leave room for bounding box */
real_height += 2;
}
win = newwin(real_height, real_width, y, x);
switch (wid) {
case MESSAGE_WIN:
messagewin = win;
break;
case STATUS_WIN:
statuswin = win;
break;
case MAP_WIN:
mapwin = win;
if ((width < COLNO) || (height < ROWNO)) {
map_clipped = TRUE;
} else {
map_clipped = FALSE;
}
break;
}
if (border) {
box(win, 0, 0);
}
nhwins[wid].curwin = win;
}
/* Add wid to list of known window IDs */
void
curses_add_wid(winid wid)
{
nethack_wid *new_wid;
nethack_wid *widptr = nhwids;
new_wid = malloc(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)
{
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);
return;
}
if (!is_main_window(wid)) {
impossible("curses_del_nhwin: wid %d out of range. Not a main window.", wid);
return;
}
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 = nhwids;
if (curses_is_menu(wid) || curses_is_text(wid)) {
curses_del_menu(wid);
}
while (widptr != NULL) {
if (widptr->nhwid == wid) {
if (widptr->prev_wid != NULL) {
tmpwid = widptr->prev_wid;
tmpwid->next_wid = widptr->next_wid;
} else {
nhwids = widptr->next_wid; /* New head mode, or NULL */
}
if (widptr->next_wid != NULL) {
tmpwid = widptr->next_wid;
tmpwid->prev_wid = widptr->prev_wid;
}
free(widptr);
break;
}
widptr = widptr->next_wid;
}
}
/* Print a single character in the given window at the given coordinates */
void
curses_putch(winid wid, int x, int y, int ch, int color, int attr)
{
int sx, sy, ex, ey;
boolean border = curses_window_has_border(wid);
nethack_char nch;
static boolean map_initted = FALSE;
if (wid == STATUS_WIN) {
curses_update_stats();
}
if (wid != MAP_WIN) {
return;
}
if (!map_initted) {
clear_map();
map_initted = TRUE;
}
map[y][x].ch = ch;
map[y][x].color = color;
map[y][x].attr = attr;
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++;
}
curses_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)
{
return nhwins[wid].border;
}
/* Determine if window for given winid exists */
boolean
curses_window_exists(winid wid)
{
nethack_wid *widptr = nhwids;
while (widptr != NULL) {
if (widptr->nhwid == wid) {
return TRUE;
}
widptr = widptr->next_wid;
}
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 identifier;
WINDOW *win = NULL;
if (is_main_window(wid)) {
win = curses_get_nhwin(wid);
}
if (wid == MESSAGE_WIN) {
#ifdef USER_SOUNDS
play_sound_for_message(text);
#endif
curses_message_win_puts(text, FALSE);
return;
}
if (wid == STATUS_WIN) {
curses_update_stats(); /* We will do the write ourselves */
/* Inventory updating isn't performed on redraws, so
also update inventory here... */
curses_update_inventory();
return;
}
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;
}
identifier = zeroany;
curses_add_nhmenu_item(wid, NO_GLYPH, &identifier, 0, 0, attr, text,
FALSE);
} else {
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();
}
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;
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;
} else {
return FALSE;
}
}
static int
wpututf8char(WINDOW *win, int y, int x, glyph_t c)
{
if (c < 0x80) {
return mvwprintw(win, y, x, "%c", (char)c);
} else if (c < 0x800) {
return mvwprintw(win, y, x, "%c%c",
(char)(0xC0 | (c >> 6)),
(char)(0x80 | (c & 0x3F)));
} else if (c < 0x10000) {
return mvwprintw(win, y, x, "%c%c%c",
(char)(0xE0 | (c >> 12)),
(char)(0x80 | (c >> 6 & 0x3F)),
(char)(0x80 | (c & 0x3F)));
} else if (c < 0x200000) {
return mvwprintw(win, y, x, "%c%c%c%c",
(char)(0xF0 | (c >> 18)),
(char)(0x80 | (c >> 12 & 0x3F)),
(char)(0x80 | (c >> 6 & 0x3F)),
(char)(0x80 | (c & 0x3F)));
}
return 0;
}
/* Unconditionally write a single character to a window at the given
coordinates without a refresh. Currently only used for the map. */
void
curses_write_char(WINDOW * win, int x, int y, nethack_char nch)
{
curses_toggle_color_attr(win, nch.color, nch.attr, ON);
if (iflags.UTF8graphics) {
wpututf8char(win, y, x, nch.ch);
} else if (iflags.IBMgraphics) {
wpututf8char(win, y, x, get_unicode_codepoint(nch.ch));
} else {
#ifdef PDCURSES
mvwaddrawch(win, y, x, nch.ch);
#else
mvwaddch(win, y, x, nch.ch);
#endif
}
curses_toggle_color_attr(win, nch.color, nch.attr, 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.attr = A_NORMAL;
hsb_bar.ch = '*';
hsb_bar.color = SCROLLBAR_COLOR;
hsb_bar.attr = A_NORMAL;
vsb_back.ch = '|';
vsb_back.color = SCROLLBAR_BACK_COLOR;
vsb_back.attr = A_NORMAL;
vsb_bar.ch = '*';
vsb_bar.color = SCROLLBAR_COLOR;
vsb_bar.attr = A_NORMAL;
/* Horizontal scrollbar */
if ((sx > 0) || (ex < (COLNO - 1))) {
sbsx = (sx * ((float) (ex - sx + 1) / COLNO));
sbex = (ex * ((float) (ex - sx + 1) / COLNO));
for (count = 0; count < sbsx; count++) {
curses_write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
}
for (count = sbsx; count <= sbex; count++) {
curses_write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
}
for (count = sbex + 1; count <= (ex - sx); count++) {
curses_write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
}
}
/* Vertical scrollbar */
if ((sy > 0) || (ey < (ROWNO - 1))) {
sbsy = (sy * ((float) (ey - sy + 1) / ROWNO));
sbey = (ey * ((float) (ey - sy + 1) / ROWNO));
for (count = 0; count < sbsy; count++) {
curses_write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
}
for (count = sbsy; count <= sbey; count++) {
curses_write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
}
for (count = sbey + 1; count <= (ey - sy); count++) {
curses_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++) {
curses_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;
}
}
}
/* 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;
}