curses new file additions

This commit is contained in:
nhmall
2018-11-16 20:53:38 -05:00
parent cb43061076
commit 748280d5dc
18 changed files with 7515 additions and 0 deletions

13
win/curses/Bugs.txt Normal file
View File

@@ -0,0 +1,13 @@
Here is a list of known issues with the curses interface at the time of
this writing. Send any others you discover to me (Karl Garrison) at
kgarrison@obox.com, along with how to reproduce the problem, if
possible. Missing features are listed in the file Todo.txt.
* Resizing a window to 80 columns or less causes a crash (PDCurses for
SDL and X11 only). Windows starting at this size and below do not
cause a crash, however.
* Cursor position is wrong on map for smaller terminal windows in all
versions of PDCurses (smaller than 80 width or 24 height). This is
due to an incomplete workaround for an issue with the wmove() function
in PDCurses.

116
win/curses/Readme.txt Normal file
View File

@@ -0,0 +1,116 @@
INTRO
=====
The "curses" windowport is a new text-based interface for NetHack,
using high-level curses routines to control the display. Currently, it
has been compiled and tested on Linux and Windows, but it should also
be portable to a number of other systems, such as other forms of UNIX,
Mac OS X, MSDOS, and OS/2.
Some features of this interface compared to the traditional tty
interface include:
* Dynamic window resizing (e.g. maximizing a terminal window)
* Dynamic configurable placement of status and message windows,
relative to the map
* Makes better use of larger terminal windows
* Fancier display (e.g. window borders, optional popup dialogs)
* "cursesgraphics" option for fancier line-drawing characters for
drawing the dungeon - this should work on most terminals/platforms
BUILDING
========
As of this writing code has been compiled on Linux and Windows.
UNIX/Linux build instructions: Follow the instructions in
sys/unix/Install.unx. By default, the Makefile is setup to compile
against ncurses. Edit Makefile.src if you wish to compile against a
different curses library, such as PDCurses for SDL.
Windows build instructions: If you are using Mingw32 as your compiler,
then follow the instructions in sys/winnt/Install.nt with the following
changes:
* After running nhsetup, manually copy the file cursmake.gcc to the
src/ subdirectory
* Instead of typing "mingw32-make -f Makefile.gcc install" you will
type "mingw32-make -f cursmake.gcc install"
If you are using a different compiler, you will have to manually modify
the appropriate Makefile to include the curses windowport files.
GAMEPLAY
========
Gameplay should be similar to the tty interface for NetHack; the
differences are primarily visual. This windowport supports dymanic
resizing of the terminal window, so you can play with it to see how it
looks best to you during a game. Also, the align_status and
align_message options may be set during the game, so you can experiment
to see what arraingement looks best to you.
For menus, in addition to the normal configurable keybindings for menu
navigation descrived in the Guidebook, you can use the right and left
arrows to to forward or backward one page, respectively, and the home
and end keys to go to the first and last pages, respectively.
Some configuration options that are specific to or relevant to the
curses windowport are shown below. Copy any of these that you like to
your nethack configuration file (e.g. .nethackrc for UNIX or
NetHack.cnf for Windows):
#
# Use this if the binary was compiled with multiple window interfaces,
# and curses is not the default
OPTIONS=windowtype:curses
#
# Set this for Windows systems, or for PDCurses for SDL on any system.
# The latter uses a cp437 font, which works with this option
#OPTIONS=IBMgraphics
#
# Set this if IBMgraphics above won't work for your system. Mutually
# exclusive with the above option, and should work on nearly any
# system.
OPTIONS=cursesgraphics
#
# Optionally specify the alignment of the message and status windows
# relative to the map window. If not specified, the code will default
# to the locations used in the tty interface: message window on top,
# and status window on bottom. Placing either of these on the right or
# left really only works well for winder terminal windows.
OPTIONS=align_message:bottom,align_status:right
#
# Use a small popup "window" for short prompts, e.g. "Really save?".
# If this is not set, the message window will be used for these as is
# done for the tty interface.
OPTIONS=popup_dialog
#
# Specify the initial window size for NetHack in units of characters.
# This is supported on PDCurses for SDL as well as PDCurses for
# Windows.
OPTIONS=term_cols:110,term_rows:32
#
# Controls the usage of window borders for the main NetHack windows
# (message, map, and status windows). A value of 1 forces the borders
# to be drawn, a value of 2 forces them to be off, and a value of 3
# allows the code to decide if they should be drawn based on the size
# of the terminal window.
OPTIONS=windowborders:3
CONTACT
=======
Please send any bug reports, suggestions, patches, or miscellaneous
feedback to me (Karl Garrison) at: kgarrison@pobox.com. Note that as
of this writing, I only have sporatic Internet access, so I may not get
back to you right away.
Happy Hacking!
Karl Garrison
March, 2009

146
win/curses/Todo.txt Normal file
View File

@@ -0,0 +1,146 @@
Below are some things I would like to see
NETHACK INTERFACE
=================
(These are the functions in cursmain.c called by the core NetHack code)
* Implement curses_rip for optional fancier color tombstone, as well
as one that will display correctly on smaller terminals.
* I am confused as to how mark_synch, wait_synch, and delay_output
should work. Help, please?
* Both PDCurses and Ncurses have mouse support, so the poskey function
could probably be implemented easily enough.
* raw_print is supposed to be able to work before the windowing system
has been initialized, as well as after, so I am unsure if curses
functions should be used here. Maybe check to see if initscr() has
been called, and use curses functions if so, and call initscr() from
there is not? Right now it is just a call to puts() with no support
for bold text.
DISPLAY
=======
* Consolidate refreshes of the display for smoother output.
* Horizontal scrollbar to show position for displays < 80 columns.
* Calls to getch() should probably be turned into wgetch() for the
appropriate window. This causes quirty cursor behavior under
PDCurses, however.
* Animation effects do not display properly - this could probably be
fixed with a correct implementation of the delay_output function.
* Support option to set forground and background colors for individual
windows
MENUS
=====
(cursdial.c)
* Menus need to be able to accept a count as input, e.g. to specifiy
how many items to drop.
* Currently the "preselected" flag for an individual menu item is
ignored. This should eventually be implemented.
* Menus probably should never overlap with the message window, since
the user sometmes needs to be able to see the messages while the menu
is active, e.g. when identifying multiple items one at a time.
* Perhaps allow for keyboard navigation of individual items, e.g.
using the up and down arrows to move among the selectable items, and
selecting individual items with the spacebar. Perhaps the tab key
could jump to the first selectable item after the next heading, and
shift-tab could jump to the first item of the previous heading.
MESSAGE WINDOW
==============
(cursmesg.c)
* Hitting Esc at the more prompt (which is '>>' for the curses
interface) should suppress the display of any further messages for
that turn like the tty interface does.
MAP WINDOW
==========
(curswins.c)
* The map window would probably benefit from a total redesign. Right
now, it uses a pad instead of a regular curses window, which causes a
number of special cases in the code to account for it, and a seperate
window behind it just to draw the border. It feels kludgy and
annoying!
STATUS WINDOW
=============
(cursstat.c)
* If the status window is on the right or left, then we have much more
room to work with for each item horizontally. Expand out some of the
labels for clarity. We can also list the current dungeon (e.g.
Gnomish Mines) and perhaps show thermometer bars for hit points and
magical power.
* Conversely, if we have a narrower dislay, compress some of the
labels to save space, and do not display some items that never or
rarely change (e.g. name, level and title, and alignment). Perhaps
display changes to these fields in the message window if they do
happen to change (e.g. converting to a new alignment).
* Maybe add some configuration options for what colors are used and
the like.
OTHER DIALOGS
=============
(cursdial.c)
* curses_yn_function needs to accept a count if a '#' is present in
choices string.
* Extended commands should be enterable letter-by-letter via a '#'
prompt if user does not have the extmenu command set to TRUE.
* Character selection should allow for a random selection of any or
all choices.
OTHER PLATFORMS
===============
* PDCurses also work on DOS and OS/2. PDCurses for SDL and ncurses
exist for Mac OS X. Porting the curses interface to these platforms
should not be too difficult.
MISC
====
* Update documentation and in-game help to describe the newly-added
options: cursesgraphics, term_rows, term_cols, and windowborders.
* Recognize "Alt" key in a platform-independant way to allow its use
to select extended commands. Currently this works for PDCurses. For
Ncurses, the Alt key works in an xterm or rxvt if the -meta8 flag is
passed, but I'd like to see a general way of detecting it.
* PDCurses has a function named "addrawch" to output the visual
representation of a control character to the screen without having the
control character affect the display otherwise. I would like to find
a way to accomplish the same thing via Ncurses to e.g. be able to use
a font like nh10 with the correct symbol mappings in an xterm or the
like.

1387
win/curses/cursdial.c Normal file

File diff suppressed because it is too large Load Diff

23
win/curses/cursdial.h Normal file
View File

@@ -0,0 +1,23 @@
/* 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" -*-*/
#ifndef CURSDIAL_H
# define CURSDIAL_H
/* Global declarations */
void curses_line_input_dialog(const char *prompt, char *answer, int buffer);
int curses_character_input_dialog(const char *prompt, const char *choices,
CHAR_P def);
int curses_ext_cmd(void);
void curses_create_nhmenu(winid wid);
void curses_add_nhmenu_item(winid wid, int glyph, const ANY_P * identifier,
CHAR_P accelerator, CHAR_P group_accel, int attr,
const char *str, BOOLEAN_P presel);
void curses_finalize_nhmenu(winid wid, const char *prompt);
int curses_display_nhmenu(winid wid, int how, MENU_ITEM_P ** _selected);
boolean curses_menu_exists(winid wid);
void curses_del_menu(winid wid);
#endif /* CURSDIAL_H */

923
win/curses/cursinit.c Normal file
View File

@@ -0,0 +1,923 @@
/* 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 "cursinit.h"
#include "patchlevel.h"
#include <ctype.h>
/* Initialization and startup functions for curses interface */
/* Private declarations */
static void set_window_position(int *, int *, int *, int *, int,
int *, int *, int *, int *, int,
int, int);
/* array to save initial terminal colors for later restoration */
typedef struct nhrgb_type {
short r;
short g;
short b;
} nhrgb;
nhrgb orig_yellow;
nhrgb orig_white;
nhrgb orig_darkgray;
nhrgb orig_hired;
nhrgb orig_higreen;
nhrgb orig_hiyellow;
nhrgb orig_hiblue;
nhrgb orig_himagenta;
nhrgb orig_hicyan;
nhrgb orig_hiwhite;
/* Banners used for an optional ASCII splash screen */
#define NETHACK_SPLASH_A \
" _ _ _ _ _ _ "
#define NETHACK_SPLASH_B \
"| \\ | | | | | | | | | | "
#define NETHACK_SPLASH_C \
"| \\| | ___ | |_ | |__| | __ _ ___ | | __"
#define NETHACK_SPLASH_D \
"| . ` | / _ \\| __|| __ | / _` | / __|| |/ /"
#define NETHACK_SPLASH_E \
"| |\\ || __/| |_ | | | || (_| || (__ | < "
#define NETHACK_SPLASH_F \
"|_| \\_| \\___| \\__||_| |_| \\__,_| \\___||_|\\_\\"
/* win* is size and placement of window to change, x/y/w/h is baseline which can
decrease depending on alignment of win* in orientation.
Negative minh/minw: as much as possible, but at least as much as specified. */
static void
set_window_position(int *winx, int *winy, int *winw, int *winh, int orientation,
int *x, int *y, int *w, int *h, int border_space,
int minh, int minw)
{
*winw = *w;
*winh = *h;
/* Set window height/width */
if (orientation == ALIGN_TOP || orientation == ALIGN_BOTTOM) {
if (minh < 0) {
*winh = (*h - ROWNO - border_space);
if (-minh > *winh)
*winh = -minh;
} else
*winh = minh;
*h -= (*winh + border_space);
} else {
if (minw < 0) {
*winw = (*w - COLNO - border_space);
if (-minw > *winw)
*winw = -minw;
} else
*winw = minw;
*w -= (*winw + border_space);
}
*winx = *w + border_space + *x;
*winy = *h + border_space + *y;
/* Set window position */
if (orientation != ALIGN_RIGHT) {
*winx = *x;
if (orientation == ALIGN_LEFT)
*x += *winw + border_space;
}
if (orientation != ALIGN_BOTTOM) {
*winy = *y;
if (orientation == ALIGN_TOP)
*y += *winh + border_space;
}
}
/* Create the "main" nonvolitile windows used by nethack */
void
curses_create_main_windows()
{
int min_message_height = 1;
int message_orientation = 0;
int status_orientation = 0;
int border_space = 0;
int hspace = term_cols - 80;
boolean borders = FALSE;
switch (iflags.wc2_windowborders) {
case 1: /* On */
borders = TRUE;
break;
case 2: /* Off */
borders = FALSE;
break;
case 3: /* Auto */
if ((term_cols > 81) && (term_rows > 25)) {
borders = TRUE;
}
break;
default:
borders = FALSE;
}
if (borders) {
border_space = 2;
hspace -= border_space;
}
if ((term_cols - border_space) < COLNO) {
min_message_height++;
}
/* Determine status window orientation */
if (!iflags.wc_align_status || (iflags.wc_align_status == ALIGN_TOP)
|| (iflags.wc_align_status == ALIGN_BOTTOM)) {
if (!iflags.wc_align_status) {
iflags.wc_align_status = ALIGN_BOTTOM;
}
status_orientation = iflags.wc_align_status;
} else { /* left or right alignment */
/* Max space for player name and title horizontally */
if ((hspace >= 26) && (term_rows >= 24)) {
status_orientation = iflags.wc_align_status;
hspace -= (26 + border_space);
} else {
status_orientation = ALIGN_BOTTOM;
}
}
/* Determine message window orientation */
if (!iflags.wc_align_message || (iflags.wc_align_message == ALIGN_TOP)
|| (iflags.wc_align_message == ALIGN_BOTTOM)) {
if (!iflags.wc_align_message) {
iflags.wc_align_message = ALIGN_TOP;
}
message_orientation = iflags.wc_align_message;
} else { /* left or right alignment */
if ((hspace - border_space) >= 25) { /* Arbitrary */
message_orientation = iflags.wc_align_message;
} else {
message_orientation = ALIGN_TOP;
}
}
/* Figure out window positions and placements. Status and message area can be aligned
based on configuration. The priority alignment-wise is: status > msgarea > game.
Define everything as taking as much space as possible and shrink/move based on
alignment positions. */
int message_x = 0;
int message_y = 0;
int status_x = 0;
int status_y = 0;
int inv_x = 0;
int inv_y = 0;
int map_x = 0;
int map_y = 0;
int message_height = 0;
int message_width = 0;
int status_height = 0;
int status_width = 0;
int inv_height = 0;
int inv_width = 0;
int map_height = (term_rows - border_space);
int map_width = (term_cols - border_space);
boolean status_vertical = FALSE;
boolean msg_vertical = FALSE;
if (status_orientation == ALIGN_LEFT ||
status_orientation == ALIGN_RIGHT)
status_vertical = TRUE;
if (message_orientation == ALIGN_LEFT ||
message_orientation == ALIGN_RIGHT)
msg_vertical = TRUE;
int statusheight = 3;
if (iflags.statuslines < 3)
statusheight = 2;
/* Vertical windows have priority. Otherwise, priotity is:
status > inv > msg */
if (status_vertical)
set_window_position(&status_x, &status_y, &status_width, &status_height,
status_orientation, &map_x, &map_y, &map_width, &map_height,
border_space, statusheight, 26);
if (iflags.perm_invent) {
/* Take up all width unless msgbar is also vertical. */
int width = -25;
if (msg_vertical)
width = 25;
set_window_position(&inv_x, &inv_y, &inv_width, &inv_height,
ALIGN_RIGHT, &map_x, &map_y, &map_width, &map_height,
border_space, -1, width);
}
if (msg_vertical)
set_window_position(&message_x, &message_y, &message_width, &message_height,
message_orientation, &map_x, &map_y, &map_width, &map_height,
border_space, -1, -25);
/* Now draw horizontal windows */
if (!status_vertical)
set_window_position(&status_x, &status_y, &status_width, &status_height,
status_orientation, &map_x, &map_y, &map_width, &map_height,
border_space, statusheight, 26);
if (!msg_vertical)
set_window_position(&message_x, &message_y, &message_width, &message_height,
message_orientation, &map_x, &map_y, &map_width, &map_height,
border_space, -1, -25);
if (map_width > COLNO)
map_width = COLNO;
if (map_height > ROWNO)
map_height = ROWNO;
if (curses_get_nhwin(STATUS_WIN)) {
curses_del_nhwin(STATUS_WIN);
curses_del_nhwin(MESSAGE_WIN);
curses_del_nhwin(MAP_WIN);
curses_del_nhwin(INV_WIN);
clear();
}
curses_add_nhwin(STATUS_WIN, status_height, status_width, status_y,
status_x, status_orientation, borders);
curses_add_nhwin(MESSAGE_WIN, message_height, message_width, message_y,
message_x, message_orientation, borders);
if (iflags.perm_invent)
curses_add_nhwin(INV_WIN, inv_height, inv_width, inv_y, inv_x,
ALIGN_RIGHT, borders);
curses_add_nhwin(MAP_WIN, map_height, map_width, map_y, map_x, 0, borders);
refresh();
curses_refresh_nethack_windows();
/*
if (iflags.window_inited) {
curses_update_stats();
if (iflags.perm_invent)
curses_update_inventory();
} else {
iflags.window_inited = TRUE;
}
*/
}
/* Initialize curses colors to colors used by NetHack */
void
curses_init_nhcolors()
{
#ifdef TEXTCOLOR
if (has_colors()) {
use_default_colors();
init_pair(1, COLOR_BLACK, -1);
init_pair(2, COLOR_RED, -1);
init_pair(3, COLOR_GREEN, -1);
init_pair(4, COLOR_YELLOW, -1);
init_pair(5, COLOR_BLUE, -1);
init_pair(6, COLOR_MAGENTA, -1);
init_pair(7, COLOR_CYAN, -1);
init_pair(8, -1, -1);
{
int i;
int clr_remap[16] = {
COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
COLOR_BLUE,
COLOR_MAGENTA, COLOR_CYAN, -1, COLOR_WHITE,
COLOR_RED + 8, COLOR_GREEN + 8, COLOR_YELLOW + 8,
COLOR_BLUE + 8,
COLOR_MAGENTA + 8, COLOR_CYAN + 8, COLOR_WHITE + 8
};
for (i = 0; i < (COLORS >= 16 ? 16 : 8); i++) {
init_pair(17 + (i * 2) + 0, clr_remap[i], COLOR_RED);
init_pair(17 + (i * 2) + 1, clr_remap[i], COLOR_BLUE);
}
boolean hicolor = FALSE;
if (COLORS >= 16)
hicolor = TRUE;
/* Work around the crazy definitions above for more background colors... */
for (i = 0; i < (COLORS >= 16 ? 16 : 8); i++) {
init_pair((hicolor ? 49 : 9) + i, clr_remap[i], COLOR_GREEN);
init_pair((hicolor ? 65 : 33) + i, clr_remap[i], COLOR_YELLOW);
init_pair((hicolor ? 81 : 41) + i, clr_remap[i], COLOR_MAGENTA);
init_pair((hicolor ? 97 : 49) + i, clr_remap[i], COLOR_CYAN);
init_pair((hicolor ? 113 : 57) + i, clr_remap[i], COLOR_WHITE);
}
}
if (COLORS >= 16) {
init_pair(9, COLOR_WHITE, -1);
init_pair(10, COLOR_RED + 8, -1);
init_pair(11, COLOR_GREEN + 8, -1);
init_pair(12, COLOR_YELLOW + 8, -1);
init_pair(13, COLOR_BLUE + 8, -1);
init_pair(14, COLOR_MAGENTA + 8, -1);
init_pair(15, COLOR_CYAN + 8, -1);
init_pair(16, COLOR_WHITE + 8, -1);
}
if (can_change_color()) {
/* Preserve initial terminal colors */
color_content(COLOR_YELLOW, &orig_yellow.r, &orig_yellow.g,
&orig_yellow.b);
color_content(COLOR_WHITE, &orig_white.r, &orig_white.g,
&orig_white.b);
/* Set colors to appear as NetHack expects */
init_color(COLOR_YELLOW, 500, 300, 0);
init_color(COLOR_WHITE, 600, 600, 600);
if (COLORS >= 16) {
/* Preserve initial terminal colors */
color_content(COLOR_RED + 8, &orig_hired.r,
&orig_hired.g, &orig_hired.b);
color_content(COLOR_GREEN + 8, &orig_higreen.r,
&orig_higreen.g, &orig_higreen.b);
color_content(COLOR_YELLOW + 8, &orig_hiyellow.r,
&orig_hiyellow.g, &orig_hiyellow.b);
color_content(COLOR_BLUE + 8, &orig_hiblue.r,
&orig_hiblue.g, &orig_hiblue.b);
color_content(COLOR_MAGENTA + 8, &orig_himagenta.r,
&orig_himagenta.g, &orig_himagenta.b);
color_content(COLOR_CYAN + 8, &orig_hicyan.r,
&orig_hicyan.g, &orig_hicyan.b);
color_content(COLOR_WHITE + 8, &orig_hiwhite.r,
&orig_hiwhite.g, &orig_hiwhite.b);
/* Set colors to appear as NetHack expects */
init_color(COLOR_RED + 8, 1000, 500, 0);
init_color(COLOR_GREEN + 8, 0, 1000, 0);
init_color(COLOR_YELLOW + 8, 1000, 1000, 0);
init_color(COLOR_BLUE + 8, 0, 0, 1000);
init_color(COLOR_MAGENTA + 8, 1000, 0, 1000);
init_color(COLOR_CYAN + 8, 0, 1000, 1000);
init_color(COLOR_WHITE + 8, 1000, 1000, 1000);
# ifdef USE_DARKGRAY
if (COLORS > 16) {
color_content(CURSES_DARK_GRAY, &orig_darkgray.r,
&orig_darkgray.g, &orig_darkgray.b);
init_color(CURSES_DARK_GRAY, 300, 300, 300);
/* just override black colorpair entry here */
init_pair(1, CURSES_DARK_GRAY, -1);
}
# endif
} else {
/* Set flag to use bold for bright colors */
}
}
}
#endif
}
/* Allow player to pick character's role, race, gender, and alignment.
Borrowed from the Gnome window port. */
void
curses_choose_character()
{
int n, i, sel, count_off, pick4u;
int count = 0;
int cur_character = 0;
const char **choices;
int *pickmap;
char *prompt;
char pbuf[QBUFSZ];
char choice[QBUFSZ];
char tmpchoice[QBUFSZ];
prompt = build_plselection_prompt(pbuf, QBUFSZ, flags.initrole,
flags.initrace, flags.initgend,
flags.initalign);
/* This part is irritating: we have to strip the choices off of
the string and put them in a separate string in order to use
curses_character_input_dialog for this prompt. */
while (cur_character != '[') {
cur_character = prompt[count];
count++;
}
count_off = count;
while (cur_character != ']') {
tmpchoice[count - count_off] = prompt[count];
count++;
cur_character = prompt[count];
}
tmpchoice[count - count_off] = '\0';
lcase(tmpchoice);
while (!isspace(prompt[count_off])) {
count_off--;
}
prompt[count_off] = '\0';
sprintf(choice, "%s%c", tmpchoice, '\033');
if (strchr(tmpchoice, 't')) { /* Tutorial mode */
mvaddstr(0, 1, "New? Press t to enter a tutorial.");
}
/* Add capital letters as choices that aren't displayed */
for (count = 0; tmpchoice[count]; count++) {
tmpchoice[count] = toupper(tmpchoice[count]);
}
sprintf(choice, "%s%s", choice, tmpchoice);
/* prevent an unnecessary prompt */
rigid_role_checks();
if (!flags.randomall &&
(flags.initrole == ROLE_NONE || flags.initrace == ROLE_NONE ||
flags.initgend == ROLE_NONE || flags.initalign == ROLE_NONE)) {
pick4u = tolower(curses_character_input_dialog(prompt, choice, 'y'));
} else {
pick4u = 'y';
}
if (pick4u == 'q') { /* Quit or cancelled */
clearlocks();
curses_bail(0);
}
if (pick4u == 'y') {
flags.randomall = TRUE;
}
clear();
refresh();
if (!flags.randomall && flags.initrole < 0) {
/* select a role */
for (n = 0; roles[n].name.m; n++)
continue;
choices = (const char **) alloc(sizeof (char *) * (n + 1));
pickmap = (int *) alloc(sizeof (int) * (n + 1));
for (;;) {
for (n = 0, i = 0; roles[i].name.m; i++) {
if (ok_role(i, flags.initrace, flags.initgend, flags.initalign)) {
if (flags.initgend >= 0 && flags.female && roles[i].name.f)
choices[n] = roles[i].name.f;
else
choices[n] = roles[i].name.m;
pickmap[n++] = i;
}
}
if (n > 0)
break;
else if (flags.initalign >= 0)
flags.initalign = -1; /* reset */
else if (flags.initgend >= 0)
flags.initgend = -1;
else if (flags.initrace >= 0)
flags.initrace = -1;
else
panic("no available ROLE+race+gender+alignment combinations");
}
choices[n] = (const char *) 0;
if (n > 1)
sel =
curses_character_dialog(choices,
"Choose one of the following roles:");
else
sel = 0;
if (sel >= 0)
sel = pickmap[sel];
else if (sel == ROLE_NONE) { /* Quit */
clearlocks();
curses_bail(0);
}
free((genericptr_t) choices);
free((genericptr_t) pickmap);
} else if (flags.initrole < 0)
sel = ROLE_RANDOM;
else
sel = flags.initrole;
if (sel == ROLE_RANDOM) { /* Random role */
sel = pick_role(flags.initrace, flags.initgend,
flags.initalign, PICK_RANDOM);
if (sel < 0)
sel = randrole();
}
flags.initrole = sel;
/* Select a race, if necessary */
/* force compatibility with role, try for compatibility with
* pre-selected gender/alignment */
if (flags.initrace < 0 || !validrace(flags.initrole, flags.initrace)) {
if (flags.initrace == ROLE_RANDOM || flags.randomall) {
flags.initrace = pick_race(flags.initrole, flags.initgend,
flags.initalign, PICK_RANDOM);
if (flags.initrace < 0)
flags.initrace = randrace(flags.initrole);
} else {
/* Count the number of valid races */
n = 0; /* number valid */
for (i = 0; races[i].noun; i++) {
if (ok_race(flags.initrole, i, flags.initgend, flags.initalign))
n++;
}
if (n == 0) {
for (i = 0; races[i].noun; i++) {
if (validrace(flags.initrole, i))
n++;
}
}
choices = (const char **) alloc(sizeof (char *) * (n + 1));
pickmap = (int *) alloc(sizeof (int) * (n + 1));
for (n = 0, i = 0; races[i].noun; i++) {
if (ok_race(flags.initrole, i, flags.initgend, flags.initalign)) {
choices[n] = races[i].noun;
pickmap[n++] = i;
}
}
choices[n] = (const char *) 0;
/* Permit the user to pick, if there is more than one */
if (n > 1)
sel =
curses_character_dialog(choices,
"Choose one of the following races:");
else
sel = 0;
if (sel >= 0)
sel = pickmap[sel];
else if (sel == ROLE_NONE) { /* Quit */
clearlocks();
curses_bail(0);
}
flags.initrace = sel;
free((genericptr_t) choices);
free((genericptr_t) pickmap);
}
if (flags.initrace == ROLE_RANDOM) { /* Random role */
sel = pick_race(flags.initrole, flags.initgend,
flags.initalign, PICK_RANDOM);
if (sel < 0)
sel = randrace(flags.initrole);
flags.initrace = sel;
}
}
/* Select a gender, if necessary */
/* force compatibility with role/race, try for compatibility with
* pre-selected alignment */
if (flags.initgend < 0 ||
!validgend(flags.initrole, flags.initrace, flags.initgend)) {
if (flags.initgend == ROLE_RANDOM || flags.randomall) {
flags.initgend = pick_gend(flags.initrole, flags.initrace,
flags.initalign, PICK_RANDOM);
if (flags.initgend < 0)
flags.initgend = randgend(flags.initrole, flags.initrace);
} else {
/* Count the number of valid genders */
n = 0; /* number valid */
for (i = 0; i < ROLE_GENDERS; i++) {
if (ok_gend(flags.initrole, flags.initrace, i, flags.initalign))
n++;
}
if (n == 0) {
for (i = 0; i < ROLE_GENDERS; i++) {
if (validgend(flags.initrole, flags.initrace, i))
n++;
}
}
choices = (const char **) alloc(sizeof (char *) * (n + 1));
pickmap = (int *) alloc(sizeof (int) * (n + 1));
for (n = 0, i = 0; i < ROLE_GENDERS; i++) {
if (ok_gend(flags.initrole, flags.initrace, i, flags.initalign)) {
choices[n] = genders[i].adj;
pickmap[n++] = i;
}
}
choices[n] = (const char *) 0;
/* Permit the user to pick, if there is more than one */
if (n > 1)
sel =
curses_character_dialog(choices,
"Choose one of the following genders:");
else
sel = 0;
if (sel >= 0)
sel = pickmap[sel];
else if (sel == ROLE_NONE) { /* Quit */
clearlocks();
curses_bail(0);
}
flags.initgend = sel;
free((genericptr_t) choices);
free((genericptr_t) pickmap);
}
if (flags.initgend == ROLE_RANDOM) { /* Random gender */
sel = pick_gend(flags.initrole, flags.initrace,
flags.initalign, PICK_RANDOM);
if (sel < 0)
sel = randgend(flags.initrole, flags.initrace);
flags.initgend = sel;
}
}
/* Select an alignment, if necessary */
/* force compatibility with role/race/gender */
if (flags.initalign < 0 ||
!validalign(flags.initrole, flags.initrace, flags.initalign)) {
if (flags.initalign == ROLE_RANDOM || flags.randomall) {
flags.initalign = pick_align(flags.initrole, flags.initrace,
flags.initgend, PICK_RANDOM);
if (flags.initalign < 0)
flags.initalign = randalign(flags.initrole, flags.initrace);
} else {
/* Count the number of valid alignments */
n = 0; /* number valid */
for (i = 0; i < ROLE_ALIGNS; i++) {
if (ok_align(flags.initrole, flags.initrace, flags.initgend, i))
n++;
}
if (n == 0) {
for (i = 0; i < ROLE_ALIGNS; i++)
if (validalign(flags.initrole, flags.initrace, i))
n++;
}
choices = (const char **) alloc(sizeof (char *) * (n + 1));
pickmap = (int *) alloc(sizeof (int) * (n + 1));
for (n = 0, i = 0; i < ROLE_ALIGNS; i++) {
if (ok_align(flags.initrole, flags.initrace, flags.initgend, i)) {
choices[n] = aligns[i].adj;
pickmap[n++] = i;
}
}
choices[n] = (const char *) 0;
/* Permit the user to pick, if there is more than one */
if (n > 1)
sel =
curses_character_dialog(choices,
"Choose one of the following alignments:");
else
sel = 0;
if (sel >= 0)
sel = pickmap[sel];
else if (sel == ROLE_NONE) { /* Quit */
clearlocks();
curses_bail(0);
}
flags.initalign = sel;
free((genericptr_t) choices);
free((genericptr_t) pickmap);
}
if (flags.initalign == ROLE_RANDOM) {
sel = pick_align(flags.initrole, flags.initrace,
flags.initgend, PICK_RANDOM);
if (sel < 0)
sel = randalign(flags.initrole, flags.initrace);
flags.initalign = sel;
}
}
}
/* Prompt user for character race, role, alignment, or gender */
int
curses_character_dialog(const char **choices, const char *prompt)
{
int count, count2, ret, curletter;
char used_letters[52];
anything identifier;
menu_item *selected = NULL;
winid wid = curses_get_wid(NHW_MENU);
identifier.a_void = 0;
curses_start_menu(wid);
for (count = 0; choices[count]; count++) {
curletter = tolower(choices[count][0]);
for (count2 = 0; count2 < count; count2++) {
if (curletter == used_letters[count2]) {
curletter = toupper(curletter);
}
}
identifier.a_int = (count + 1); /* Must be non-zero */
curses_add_menu(wid, NO_GLYPH, &identifier, curletter, 0,
A_NORMAL, choices[count], FALSE);
used_letters[count] = curletter;
}
/* Random Selection */
identifier.a_int = ROLE_RANDOM;
curses_add_menu(wid, NO_GLYPH, &identifier, '*', 0, A_NORMAL, "Random",
FALSE);
/* Quit prompt */
identifier.a_int = ROLE_NONE;
curses_add_menu(wid, NO_GLYPH, &identifier, 'q', 0, A_NORMAL, "Quit",
FALSE);
curses_end_menu(wid, prompt);
ret = curses_select_menu(wid, PICK_ONE, &selected);
if (ret == 1) {
ret = (selected->item.a_int);
} else { /* Cancelled selection */
ret = ROLE_NONE;
}
if (ret > 0) {
ret--;
}
free((genericptr_t) selected);
return ret;
}
/* Initialize and display options appropriately */
void
curses_init_options()
{
set_wc_option_mod_status(WC_ALIGN_MESSAGE | WC_ALIGN_STATUS | WC_COLOR |
WC_HILITE_PET | WC_POPUP_DIALOG, SET_IN_GAME);
set_wc2_option_mod_status(WC2_GUICOLOR, SET_IN_GAME);
/* Remove a few options that are irrelevant to this windowport */
/*set_option_mod_status("DECgraphics", SET_IN_FILE); */
set_option_mod_status("eight_bit_tty", SET_IN_FILE);
/* Add those that are */
set_option_mod_status("statuslines", SET_IN_GAME);
/* Make sure that DECgraphics is not set to true via the config
file, as this will cause display issues. We can't disable it in
options.c in case the game is compiled with both tty and curses. */
if (!symset[PRIMARY].name || !strcmpi(symset[PRIMARY].name, "DECgraphics")) {
load_symset("curses",PRIMARY);
load_symset("default",ROGUESET);
}
#ifdef PDCURSES
/* PDCurses for SDL, win32 and OS/2 has the ability to set the
terminal size programatically. If the user does not specify a
size in the config file, we will set it to a nice big 110x32 to
take advantage of some of the nice features of this windowport. */
if (iflags.wc2_term_cols == 0) {
iflags.wc2_term_cols = 110;
}
if (iflags.wc2_term_rows == 0) {
iflags.wc2_term_rows = 32;
}
resize_term(iflags.wc2_term_rows, iflags.wc2_term_cols);
getmaxyx(base_term, term_rows, term_cols);
/* This is needed for an odd bug with PDCurses-SDL */
/* How to deal with this?
switch_graphics(ASCII_GRAPHICS);
if (iflags.IBMgraphics) {
switch_graphics(IBM_GRAPHICS);
} else if (iflags.cursesgraphics) {
switch_graphics(CURS_GRAPHICS);
} else {
switch_graphics(ASCII_GRAPHICS);
}
*/
#endif /* PDCURSES */
if (!iflags.wc2_windowborders) {
iflags.wc2_windowborders = 3; /* Set to auto if not specified */
}
if (!iflags.wc2_petattr) {
iflags.wc2_petattr = A_REVERSE;
} else { /* Pet attribute specified, so hilite_pet should be true */
iflags.hilite_pet = TRUE;
}
#ifdef NCURSES_MOUSE_VERSION
if (iflags.wc_mouse_support) {
mousemask(BUTTON1_CLICKED, NULL);
}
#endif
}
/* Display an ASCII splash screen if the splash_screen option is set */
void
curses_display_splash_window()
{
int x_start;
int y_start;
curses_get_window_xy(MAP_WIN, &x_start, &y_start);
if ((term_cols < 70) || (term_rows < 20)) {
iflags.wc_splash_screen = FALSE; /* No room for s.s. */
}
curses_toggle_color_attr(stdscr, CLR_WHITE, A_NORMAL, ON);
if (iflags.wc_splash_screen) {
mvaddstr(y_start, x_start, NETHACK_SPLASH_A);
mvaddstr(y_start + 1, x_start, NETHACK_SPLASH_B);
mvaddstr(y_start + 2, x_start, NETHACK_SPLASH_C);
mvaddstr(y_start + 3, x_start, NETHACK_SPLASH_D);
mvaddstr(y_start + 4, x_start, NETHACK_SPLASH_E);
mvaddstr(y_start + 5, x_start, NETHACK_SPLASH_F);
y_start += 7;
}
curses_toggle_color_attr(stdscr, CLR_WHITE, A_NORMAL, OFF);
#ifdef COPYRIGHT_BANNER_A
mvaddstr(y_start, x_start, COPYRIGHT_BANNER_A);
y_start++;
#endif
#ifdef COPYRIGHT_BANNER_B
mvaddstr(y_start, x_start, COPYRIGHT_BANNER_B);
y_start++;
#endif
#ifdef COPYRIGHT_BANNER_C
mvaddstr(y_start, x_start, COPYRIGHT_BANNER_C);
y_start++;
#endif
#ifdef COPYRIGHT_BANNER_D /* Just in case */
mvaddstr(y_start, x_start, COPYRIGHT_BANNER_D);
y_start++;
#endif
refresh();
}
/* Resore colors and cursor state before exiting */
void
curses_cleanup()
{
#ifdef TEXTCOLOR
if (has_colors() && can_change_color()) {
init_color(COLOR_YELLOW, orig_yellow.r, orig_yellow.g, orig_yellow.b);
init_color(COLOR_WHITE, orig_white.r, orig_white.g, orig_white.b);
if (COLORS >= 16) {
init_color(COLOR_RED + 8, orig_hired.r, orig_hired.g, orig_hired.b);
init_color(COLOR_GREEN + 8, orig_higreen.r, orig_higreen.g,
orig_higreen.b);
init_color(COLOR_YELLOW + 8, orig_hiyellow.r,
orig_hiyellow.g, orig_hiyellow.b);
init_color(COLOR_BLUE + 8, orig_hiblue.r, orig_hiblue.g,
orig_hiblue.b);
init_color(COLOR_MAGENTA + 8, orig_himagenta.r,
orig_himagenta.g, orig_himagenta.b);
init_color(COLOR_CYAN + 8, orig_hicyan.r, orig_hicyan.g,
orig_hicyan.b);
init_color(COLOR_WHITE + 8, orig_hiwhite.r, orig_hiwhite.g,
orig_hiwhite.b);
# ifdef USE_DARKGRAY
if (COLORS > 16) {
init_color(CURSES_DARK_GRAY, orig_darkgray.r,
orig_darkgray.g, orig_darkgray.b);
}
# endif
}
}
#endif
}

17
win/curses/cursinit.h Normal file
View File

@@ -0,0 +1,17 @@
/* 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" -*-*/
#ifndef CURSINIT_H
# define CURSINIT_H
/* Global declarations */
void curses_create_main_windows(void);
void curses_init_nhcolors(void);
void curses_choose_character(void);
int curses_character_dialog(const char **choices, const char *prompt);
void curses_init_options(void);
void curses_display_splash_window(void);
void curses_cleanup(void);
#endif /* CURSINIT_H */

111
win/curses/cursinvt.c Normal file
View File

@@ -0,0 +1,111 @@
/* 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 "cursinvt.h"
/* Permanent inventory for curses interface */
/* Runs when the game indicates that the inventory has been updated */
void
curses_update_inv(void)
{
WINDOW *win = curses_get_nhwin(INV_WIN);
/* Check if the inventory window is enabled in first place */
if (!win) {
/* It's not. Re-initialize the main windows if the
option was enabled. */
if (iflags.perm_invent) {
curses_create_main_windows();
curses_last_messages();
doredraw();
}
return;
}
boolean border = curses_window_has_border(INV_WIN);
/* Figure out drawing area */
int x = 0;
int y = 0;
if (border) {
x++;
y++;
}
/* Clear the window as it is at the moment. */
werase(win);
wmove(win, y, x);
attr_t attr = A_UNDERLINE;
wattron(win, attr);
wprintw(win, "Inventory:");
wattroff(win, attr);
/* The actual inventory will override this if we do carry stuff */
wmove(win, y + 1, x);
wprintw(win, "Not carrying anything");
display_inventory(NULL, FALSE);
if (border)
box(win, 0, 0);
wnoutrefresh(win);
}
/* Adds an inventory item. */
void
curses_add_inv(int y, int glyph, CHAR_P accelerator, attr_t attr,
const char *str)
{
WINDOW *win = curses_get_nhwin(INV_WIN);
/* Figure out where to draw the line */
int x = 0;
if (curses_window_has_border(INV_WIN)) {
x++;
y++;
}
wmove(win, y, x);
if (accelerator) {
attr_t bold = A_BOLD;
wattron(win, bold);
waddch(win, accelerator);
wattroff(win, bold);
wprintw(win, ") ");
}
#if 0 // FIXME: MENU GLYPHS
if (accelerator && glyph != NO_GLYPH && iflags.use_menu_glyphs) {
unsigned dummy = 0; /* Not used */
int color = 0;
int symbol = 0;
mapglyph(glyph, &symbol, &color, &dummy,
u.ux, u.uy);
attr_t glyphclr = curses_color_attr(color, 0);
wattron(win, glyphclr);
wprintw(win, "%c ", symbol);
wattroff(win, glyphclr);
}
#endif
int color = NO_COLOR;
if (accelerator && /* Don't colorize categories */
iflags.use_menu_color) {
boolean menu_color = FALSE;
char str_mutable[BUFSZ];
Strcpy(str_mutable, str);
attr = 0;
get_menu_coloring(str_mutable, &color, &attr);
attr = curses_convert_attr(attr);
}
if (color == NO_COLOR) color = NONE;
curses_toggle_color_attr(win, color, attr, ON);
//wattron(win, attr);
wprintw(win, "%s", str);
//wattroff(win, attr);
curses_toggle_color_attr(win, color, attr, OFF);
wclrtoeol(win);
}

11
win/curses/cursinvt.h Normal file
View File

@@ -0,0 +1,11 @@
/* 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" -*-*/
#ifndef CURSINVT_H
# define CURSINVT_H
/* Global declarations */
void curses_update_inv(void);
#endif /* CURSINVT_H */

820
win/curses/cursmain.c Normal file
View File

@@ -0,0 +1,820 @@
/* 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 "patchlevel.h"
#include "color.h"
#include "wincurs.h"
/* Public functions for curses NetHack interface */
/* Interface definition, for windows.c */
struct window_procs curses_procs = {
"curses",
WC_ALIGN_MESSAGE | WC_ALIGN_STATUS | WC_COLOR | WC_HILITE_PET |
WC_PERM_INVENT | WC_POPUP_DIALOG | WC_SPLASH_SCREEN,
WC2_DARKGRAY | WC2_HITPOINTBAR |
#if defined(STATUS_HILITES)
WC2_HILITE_STATUS |
#endif
WC2_HITPOINTBAR | WC2_FLUSH_STATUS |
WC2_TERM_SIZE | WC2_WINDOWBORDERS | WC2_PETATTR | WC2_GUICOLOR,
curses_init_nhwindows,
curses_player_selection,
curses_askname,
curses_get_nh_event,
curses_exit_nhwindows,
curses_suspend_nhwindows,
curses_resume_nhwindows,
curses_create_nhwindow,
curses_clear_nhwindow,
curses_display_nhwindow,
curses_destroy_nhwindow,
curses_curs,
curses_putstr,
genl_putmixed,
curses_display_file,
curses_start_menu,
curses_add_menu,
curses_end_menu,
curses_select_menu,
genl_message_menu,
curses_update_inventory,
curses_mark_synch,
curses_wait_synch,
#ifdef CLIPPING
curses_cliparound,
#endif
#ifdef POSITIONBAR
donull,
#endif
curses_print_glyph,
curses_raw_print,
curses_raw_print_bold,
curses_nhgetch,
curses_nh_poskey,
curses_nhbell,
curses_doprev_message,
curses_yn_function,
curses_getlin,
curses_get_ext_cmd,
curses_number_pad,
curses_delay_output,
#ifdef CHANGE_COLOR /* only a Mac option currently */
donull,
donull,
#endif
curses_start_screen,
curses_end_screen,
genl_outrip,
curses_preference_update,
genl_getmsghistory,
genl_putmsghistory,
curses_status_init,
genl_status_finish,
genl_status_enablefield,
curses_status_update,
genl_can_suspend_yes,
};
/* Track if we're performing an update to the permanent window.
Needed since we aren't using the normal menu functions to handle
the inventory window. */
static int inv_update = 0;
/*
init_nhwindows(int* argcp, char** argv)
-- Initialize the windows used by NetHack. This can also
create the standard windows listed at the top, but does
not display them.
-- Any commandline arguments relevant to the windowport
should be interpreted, and *argcp and *argv should
be changed to remove those arguments.
-- When the message window is created, the variable
iflags.window_inited needs to be set to TRUE. Otherwise
all plines() will be done via raw_print().
** Why not have init_nhwindows() create all of the "standard"
** windows? Or at least all but WIN_INFO? -dean
*/
void
curses_init_nhwindows(int *argcp, char **argv)
{
#ifdef PDCURSES
char window_title[BUFSZ];
#endif
#ifdef XCURSES
base_term = Xinitscr(*argcp, argv);
#else
base_term = initscr();
#endif
#ifdef TEXTCOLOR
if (has_colors()) {
start_color();
curses_init_nhcolors();
} else {
iflags.use_color = FALSE;
set_option_mod_status("color", SET_IN_FILE);
iflags.wc2_guicolor = FALSE;
set_wc2_option_mod_status(WC2_GUICOLOR, SET_IN_FILE);
}
#else
iflags.use_color = FALSE;
set_option_mod_status("color", SET_IN_FILE);
iflags.wc2_guicolor = FALSE;
set_wc2_option_mod_status(WC2_GUICOLOR, SET_IN_FILE);
#endif
noecho();
raw();
meta(stdscr, TRUE);
orig_cursor = curs_set(0);
keypad(stdscr, TRUE);
#ifdef NCURSES_VERSION
# ifdef __APPLE__
ESCDELAY = 25;
# else
set_escdelay(25);
# endif/* __APPLE__ */
#endif /* NCURSES_VERSION */
#ifdef PDCURSES
# ifdef DEF_GAME_NAME
# ifdef VERSION_STRING
sprintf(window_title, "%s %s", DEF_GAME_NAME, VERSION_STRING);
# else
sprintf(window_title, "%s", DEF_GAME_NAME);
# endif
/* VERSION_STRING */
# else
# ifdef VERSION_STRING
sprintf(window_title, "%s %s", "NetHack", VERSION_STRING);
# else
sprintf(window_title, "%s", "NetHack");
# endif
/* VERSION_STRING */
# endif/* DEF_GAME_NAME */
PDC_set_title(window_title);
PDC_set_blink(TRUE); /* Only if the user asks for it! */
timeout(1);
(void) getch();
timeout(-1);
#endif /* PDCURSES */
getmaxyx(base_term, term_rows, term_cols);
counting = FALSE;
curses_init_options();
if ((term_rows < 15) || (term_cols < 40)) {
panic("Terminal too small. Must be minumum 40 width and 15 height");
}
curses_create_main_windows();
curses_init_mesg_history();
curses_display_splash_window();
}
/* Do a window-port specific player type selection. If player_selection()
offers a Quit option, it is its responsibility to clean up and terminate
the process. You need to fill in pl_character[0].
*/
void
curses_player_selection()
{
curses_choose_character();
}
/* Ask the user for a player name. */
void
curses_askname()
{
curses_line_input_dialog("Who are you?", plname, PL_NSIZ);
}
/* Does window event processing (e.g. exposure events).
A noop for the tty and X window-ports.
*/
void
curses_get_nh_event()
{
#ifdef PDCURSES
if (is_termresized()) {
resize_term(0, 0);
getmaxyx(base_term, term_rows, term_cols);
curses_create_main_windows();
curses_last_messages();
doredraw();
}
#endif
#ifdef NCURSES_VERSION /* Is there a better way to detect ncurses? */
if (is_term_resized(term_rows, term_cols)) {
if (!isendwin()) {
endwin();
}
refresh();
getmaxyx(base_term, term_rows, term_cols);
curses_create_main_windows();
curses_last_messages();
doredraw();
}
#endif
}
/* Exits the window system. This should dismiss all windows,
except the "window" used for raw_print(). str is printed if possible.
*/
void
curses_exit_nhwindows(const char *str)
{
curses_cleanup();
curs_set(orig_cursor);
endwin();
iflags.window_inited = 0;
if (str != NULL) {
raw_print(str);
}
}
/* Prepare the window to be suspended. */
void
curses_suspend_nhwindows(const char *str)
{
endwin();
}
/* Restore the windows after being suspended. */
void
curses_resume_nhwindows()
{
curses_refresh_nethack_windows();
}
/* Create a window of type "type" which can be
NHW_MESSAGE (top line)
NHW_STATUS (bottom lines)
NHW_MAP (main dungeon)
NHW_MENU (inventory or other "corner" windows)
NHW_TEXT (help/text, full screen paged window)
*/
winid
curses_create_nhwindow(int type)
{
winid wid = curses_get_wid(type);
if (curses_is_menu(wid) || curses_is_text(wid)) {
curses_start_menu(wid);
curses_add_wid(wid);
}
return wid;
}
/* Clear the given window, when asked to. */
void
curses_clear_nhwindow(winid wid)
{
if (wid != NHW_MESSAGE) {
curses_clear_nhwin(wid);
}
}
/* -- Display the window on the screen. If there is data
pending for output in that window, it should be sent.
If blocking is TRUE, display_nhwindow() will not
return until the data has been displayed on the screen,
and acknowledged by the user where appropriate.
-- All calls are blocking in the tty window-port.
-- Calling display_nhwindow(WIN_MESSAGE,???) will do a
--more--, if necessary, in the tty window-port.
*/
void
curses_display_nhwindow(winid wid, BOOLEAN_P block)
{
menu_item *selected = NULL;
if (curses_is_menu(wid) || curses_is_text(wid)) {
curses_end_menu(wid, "");
curses_select_menu(wid, PICK_NONE, &selected);
return;
}
/* don't overwrite the splash screen first time through */
if (!iflags.window_inited && wid == MAP_WIN)
iflags.window_inited = TRUE;
else {
/* actually display the window */
wnoutrefresh(curses_get_nhwin(wid));
/* flush pending writes from other windows too */
doupdate();
}
if ((wid == MAP_WIN) && block) {
(void) curses_more();
}
if ((wid == MESSAGE_WIN) && block) {
(void) curses_block(TRUE);
}
}
/* Destroy will dismiss the window if the window has not
* already been dismissed.
*/
void
curses_destroy_nhwindow(winid wid)
{
curses_del_nhwin(wid);
}
/* Next output to window will start at (x,y), also moves
displayable cursor to (x,y). For backward compatibility,
1 <= x < cols, 0 <= y < rows, where cols and rows are
the size of window.
*/
void
curses_curs(winid wid, int x, int y)
{
curses_move_cursor(wid, x, y);
}
/*
putstr(window, attr, str)
-- Print str on the window with the given attribute. Only
printable ASCII characters (040-0126) must be supported.
Multiple putstr()s are output on separate lines.
Attributes
can be one of
ATR_NONE (or 0)
ATR_ULINE
ATR_BOLD
ATR_BLINK
ATR_INVERSE
If a window-port does not support all of these, it may map
unsupported attributes to a supported one (e.g. map them
all to ATR_INVERSE). putstr() may compress spaces out of
str, break str, or truncate str, if necessary for the
display. Where putstr() breaks a line, it has to clear
to end-of-line.
-- putstr should be implemented such that if two putstr()s
are done consecutively the user will see the first and
then the second. In the tty port, pline() achieves this
by calling more() or displaying both on the same line.
*/
void
curses_putstr(winid wid, int attr, const char *text)
{
int curses_attr = curses_convert_attr(attr);
/* We need to convert NetHack attributes to curses attributes */
curses_puts(wid, curses_attr, text);
}
/* Display the file named str. Complain about missing files
iff complain is TRUE.
*/
void
curses_display_file(const char *filename, BOOLEAN_P must_exist)
{
curses_view_file(filename, must_exist);
}
/* Start using window as a menu. You must call start_menu()
before add_menu(). After calling start_menu() you may not
putstr() to the window. Only windows of type NHW_MENU may
be used for menus.
*/
void
curses_start_menu(winid wid)
{
if (inv_update)
return;
curses_create_nhmenu(wid);
}
/*
add_menu(winid wid, int glyph, const anything identifier,
char accelerator, char groupacc,
int attr, char *str, boolean preselected)
-- Add a text line str to the given menu window. If identifier
is 0, then the line cannot be selected (e.g. a title).
Otherwise, identifier is the value returned if the line is
selected. Accelerator is a keyboard key that can be used
to select the line. If the accelerator of a selectable
item is 0, the window system is free to select its own
accelerator. It is up to the window-port to make the
accelerator visible to the user (e.g. put "a - " in front
of str). The value attr is the same as in putstr().
Glyph is an optional glyph to accompany the line. If
window port cannot or does not want to display it, this
is OK. If there is no glyph applicable, then this
value will be NO_GLYPH.
-- All accelerators should be in the range [A-Za-z].
-- It is expected that callers do not mix accelerator
choices. Either all selectable items have an accelerator
or let the window system pick them. Don't do both.
-- Groupacc is a group accelerator. It may be any character
outside of the standard accelerator (see above) or a
number. If 0, the item is unaffected by any group
accelerator. If this accelerator conflicts with
the menu command (or their user defined alises), it loses.
The menu commands and aliases take care not to interfere
with the default object class symbols.
-- If you want this choice to be preselected when the
menu is displayed, set preselected to TRUE.
*/
void
curses_add_menu(winid wid, int glyph, const ANY_P * identifier,
CHAR_P accelerator, CHAR_P group_accel, int attr,
const char *str, BOOLEAN_P presel)
{
int curses_attr = curses_convert_attr(attr);
if (inv_update) {
curses_add_inv(inv_update, glyph, accelerator, curses_attr, str);
inv_update++;
return;
}
curses_add_nhmenu_item(wid, glyph, identifier, accelerator, group_accel,
curses_attr, str, presel);
}
/*
end_menu(window, prompt)
-- Stop adding entries to the menu and flushes the window
to the screen (brings to front?). Prompt is a prompt
to give the user. If prompt is NULL, no prompt will
be printed.
** This probably shouldn't flush the window any more (if
** it ever did). That should be select_menu's job. -dean
*/
void
curses_end_menu(winid wid, const char *prompt)
{
if (inv_update)
return;
curses_finalize_nhmenu(wid, prompt);
}
/*
int select_menu(winid window, int how, menu_item **selected)
-- Return the number of items selected; 0 if none were chosen,
-1 when explicitly cancelled. If items were selected, then
selected is filled in with an allocated array of menu_item
structures, one for each selected line. The caller must
free this array when done with it. The "count" field
of selected is a user supplied count. If the user did
not supply a count, then the count field is filled with
-1 (meaning all). A count of zero is equivalent to not
being selected and should not be in the list. If no items
were selected, then selected is NULL'ed out. How is the
mode of the menu. Three valid values are PICK_NONE,
PICK_ONE, and PICK_N, meaning: nothing is selectable,
only one thing is selectable, and any number valid items
may selected. If how is PICK_NONE, this function should
never return anything but 0 or -1.
-- You may call select_menu() on a window multiple times --
the menu is saved until start_menu() or destroy_nhwindow()
is called on the window.
-- Note that NHW_MENU windows need not have select_menu()
called for them. There is no way of knowing whether
select_menu() will be called for the window at
create_nhwindow() time.
*/
int
curses_select_menu(winid wid, int how, MENU_ITEM_P ** selected)
{
if (inv_update)
return 0;
return curses_display_nhmenu(wid, how, selected);
}
void
curses_update_inventory(void)
{
/* Don't do anything if perm_invent is off unless we
changed the option. */
if (!iflags.perm_invent) {
if (curses_get_nhwin(INV_WIN)) {
curses_create_main_windows();
curses_last_messages();
doredraw();
}
return;
}
/* Update inventory sidebar. NetHack uses normal menu functions
when drawing the inventory, and we don't want to change the
underlying code. So instead, track if an inventory update is
being performed with a static variable. */
inv_update = 1;
curses_update_inv();
inv_update = 0;
}
/*
mark_synch() -- Don't go beyond this point in I/O on any channel until
all channels are caught up to here. Can be an empty call
for the moment
*/
void
curses_mark_synch()
{
}
/*
wait_synch() -- Wait until all pending output is complete (*flush*() for
streams goes here).
-- May also deal with exposure events etc. so that the
display is OK when return from wait_synch().
*/
void
curses_wait_synch()
{
}
/*
cliparound(x, y)-- Make sure that the user is more-or-less centered on the
screen if the playing area is larger than the screen.
-- This function is only defined if CLIPPING is defined.
*/
void
curses_cliparound(int x, int y)
{
int sx, sy, ex, ey;
boolean redraw = curses_map_borders(&sx, &sy, &ex, &ey, x, y);
if (redraw) {
curses_draw_map(sx, sy, ex, ey);
}
}
/*
print_glyph(window, x, y, glyph, bkglyph)
-- Print the glyph at (x,y) on the given window. Glyphs are
integers at the interface, mapped to whatever the window-
port wants (symbol, font, color, attributes, ...there's
a 1-1 map between glyphs and distinct things on the map).
bkglyph is to render the background behind the glyph.
It's not used here.
*/
void
curses_print_glyph(winid wid, XCHAR_P x, XCHAR_P y, int glyph, int bkglyph)
{
int ch;
int color;
unsigned int special;
int attr = -1;
/* map glyph to character and color */
mapglyph(glyph, &ch, &color, &special, x, y);
if ((special & MG_PET) && iflags.hilite_pet) {
attr = iflags.wc2_petattr;
}
if ((special & MG_DETECT) && iflags.use_inverse) {
attr = A_REVERSE;
}
if (!symset[PRIMARY].name || !strcmpi(symset[PRIMARY].name, "curses")) {
ch = curses_convert_glyph(ch, glyph);
}
if (wid == NHW_MAP) {
/* hilite stairs not in 3.6, yet
if ((special & MG_STAIRS) && iflags.hilite_hidden_stairs) {
color = 16 + (color * 2);
} else
*/
if ((special & MG_OBJPILE) && iflags.hilite_pile) {
color = 16 + (color * 2) + 1;
}
}
curses_putch(wid, x, y, ch, color, attr);
}
/*
raw_print(str) -- Print directly to a screen, or otherwise guarantee that
the user sees str. raw_print() appends a newline to str.
It need not recognize ASCII control characters. This is
used during startup (before windowing system initialization
-- maybe this means only error startup messages are raw),
for error messages, and maybe other "msg" uses. E.g.
updating status for micros (i.e, "saving").
*/
void
curses_raw_print(const char *str)
{
puts(str);
}
/*
raw_print_bold(str)
-- Like raw_print(), but prints in bold/standout (if possible).
*/
void
curses_raw_print_bold(const char *str)
{
curses_raw_print(str);
}
/*
int nhgetch() -- Returns a single character input from the user.
-- In the tty window-port, nhgetch() assumes that tgetch()
will be the routine the OS provides to read a character.
Returned character _must_ be non-zero.
*/
int
curses_nhgetch()
{
int ch;
curses_prehousekeeping();
ch = curses_read_char();
curses_posthousekeeping();
return ch;
}
/*
int nh_poskey(int *x, int *y, int *mod)
-- Returns a single character input from the user or a
a positioning event (perhaps from a mouse). If the
return value is non-zero, a character was typed, else,
a position in the MAP window is returned in x, y and mod.
mod may be one of
CLICK_1 -- mouse click type 1
CLICK_2 -- mouse click type 2
The different click types can map to whatever the
hardware supports. If no mouse is supported, this
routine always returns a non-zero character.
*/
int
curses_nh_poskey(int *x, int *y, int *mod)
{
int key = curses_nhgetch();
#ifdef NCURSES_MOUSE_VERSION
/* Mouse event if mouse_support is true */
if (key == KEY_MOUSE) {
key = curses_get_mouse(x, y, mod);
}
#endif
return key;
}
/*
nhbell() -- Beep at user. [This will exist at least until sounds are
redone, since sounds aren't attributable to windows anyway.]
*/
void
curses_nhbell()
{
beep();
}
/*
doprev_message()
-- Display previous messages. Used by the ^P command.
-- On the tty-port this scrolls WIN_MESSAGE back one line.
*/
int
curses_doprev_message()
{
curses_prev_mesg();
return 0;
}
/*
char yn_function(const char *ques, const char *choices, char default)
-- Print a prompt made up of ques, choices and default.
Read a single character response that is contained in
choices or default. If choices is NULL, all possible
inputs are accepted and returned. This overrides
everything else. The choices are expected to be in
lower case. Entering ESC always maps to 'q', or 'n',
in that order, if present in choices, otherwise it maps
to default. Entering any other quit character (SPACE,
RETURN, NEWLINE) maps to default.
-- If the choices string contains ESC, then anything after
it is an acceptable response, but the ESC and whatever
follows is not included in the prompt.
-- If the choices string contains a '#' then accept a count.
Place this value in the global "yn_number" and return '#'.
-- This uses the top line in the tty window-port, other
ports might use a popup.
*/
char
curses_yn_function(const char *question, const char *choices, CHAR_P def)
{
return (char) curses_character_input_dialog(question, choices, def);
}
/*
getlin(const char *ques, char *input)
-- Prints ques as a prompt and reads a single line of text,
up to a newline. The string entered is returned without the
newline. ESC is used to cancel, in which case the string
"\033\000" is returned.
-- getlin() must call flush_screen(1) before doing anything.
-- This uses the top line in the tty window-port, other
ports might use a popup.
*/
void
curses_getlin(const char *question, char *input)
{
curses_line_input_dialog(question, input, BUFSZ);
}
/*
int get_ext_cmd(void)
-- Get an extended command in a window-port specific way.
An index into extcmdlist[] is returned on a successful
selection, -1 otherwise.
*/
int
curses_get_ext_cmd()
{
return curses_ext_cmd();
}
/*
number_pad(state)
-- Initialize the number pad to the given state.
*/
void
curses_number_pad(int state)
{
}
/*
delay_output() -- Causes a visible delay of 50ms in the output.
Conceptually, this is similar to wait_synch() followed
by a nap(50ms), but allows asynchronous operation.
*/
void
curses_delay_output()
{
/* refreshing the whole display is a waste of time,
* but that's why we're here */
refresh();
napms(50);
}
/*
start_screen() -- Only used on Unix tty ports, but must be declared for
completeness. Sets up the tty to work in full-screen
graphics mode. Look at win/tty/termcap.c for an
example. If your window-port does not need this function
just declare an empty function.
*/
void
curses_start_screen()
{
}
/*
end_screen() -- Only used on Unix tty ports, but must be declared for
completeness. The complement of start_screen().
*/
void
curses_end_screen()
{
}
/*
outrip(winid, int)
-- The tombstone code. If you want the traditional code use
genl_outrip for the value and check the #if in rip.c.
*/
void
curses_outrip(winid wid, int how)
{
}
/*
preference_update(preference)
-- The player has just changed one of the wincap preference
settings, and the NetHack core is notifying your window
port of that change. If your window-port is capable of
dynamically adjusting to the change then it should do so.
Your window-port will only be notified of a particular
change if it indicated that it wants to be by setting the
corresponding bit in the wincap mask.
*/
void
curses_preference_update(const char *pref)
{
if ((strcmp(pref, "align_status") == 0) ||
(strcmp(pref, "align_message") == 0)) {
curses_create_main_windows();
curses_last_messages();
doredraw();
}
}

630
win/curses/cursmesg.c Normal file
View File

@@ -0,0 +1,630 @@
/* 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 "cursmesg.h"
#include <ctype.h>
/* Message window routines for curses interface */
/* Private declatations */
typedef struct nhpm {
char *str; /* Message text */
long turn; /* Turn number for message */
struct nhpm *prev_mesg; /* Pointer to previous message */
struct nhpm *next_mesg; /* Pointer to next message */
} nhprev_mesg;
static void scroll_window(winid wid);
static void unscroll_window(winid wid);
static void directional_scroll(winid wid, int nlines);
static void mesg_add_line(char *mline);
static nhprev_mesg *get_msg_line(boolean reverse, int mindex);
static int turn_lines = 1;
static int mx = 0;
static int my = 0; /* message window text location */
static nhprev_mesg *first_mesg = NULL;
static nhprev_mesg *last_mesg = NULL;
static int max_messages;
static int num_messages = 0;
/* Write a string to the message window. Attributes set by calling function. */
void
curses_message_win_puts(const char *message, boolean recursed)
{
int height, width, linespace;
char *tmpstr;
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
boolean border = curses_window_has_border(MESSAGE_WIN);
int message_length = strlen(message);
int border_space = 0;
static long suppress_turn = -1;
if (strncmp("Count:", message, 6) == 0) {
curses_count_window(message);
return;
}
if (suppress_turn == moves) {
return;
}
curses_get_window_size(MESSAGE_WIN, &height, &width);
if (border) {
border_space = 1;
if (mx < 1) {
mx = 1;
}
if (my < 1) {
my = 1;
}
}
linespace = ((width + border_space) - 3) - mx;
if (strcmp(message, "#") == 0) { /* Extended command or Count: */
if ((strcmp(toplines, "#") != 0) && (my >= (height - 1 + border_space)) && (height != 1)) { /* Bottom of message window */
scroll_window(MESSAGE_WIN);
mx = width;
my--;
strcpy(toplines, message);
}
return;
}
if (!recursed) {
strcpy(toplines, message);
mesg_add_line((char *) message);
}
if (linespace < message_length) {
if (my >= (height - 1 + border_space)) { /* bottom of message win */
if ((turn_lines > height) || (height == 1)) {
/* Pause until key is hit - Esc suppresses any further
messages that turn */
if (curses_more() == '\033') {
suppress_turn = moves;
return;
}
} else {
scroll_window(MESSAGE_WIN);
turn_lines++;
}
} else {
if (mx != border_space) {
my++;
mx = border_space;
}
}
}
if (height > 1) {
curses_toggle_color_attr(win, NONE, A_BOLD, ON);
}
if ((mx == border_space) && ((message_length + 2) > width)) {
tmpstr = curses_break_str(message, (width - 2), 1);
mvwprintw(win, my, mx, "%s", tmpstr);
mx += strlen(tmpstr);
if (strlen(tmpstr) < (size_t) (width - 2)) {
mx++;
}
free(tmpstr);
if (height > 1) {
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
}
curses_message_win_puts(tmpstr = curses_str_remainder(message, (width - 2), 1),
TRUE);
free(tmpstr);
} else {
mvwprintw(win, my, mx, "%s", message);
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
mx += message_length + 1;
}
wrefresh(win);
}
int
curses_block(boolean noscroll)
/* noscroll - blocking because of msgtype = stop/alert */
/* else blocking because window is full, so need to scroll after */
{
int height, width, ret;
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
char *resp = " \n\033"; /* space, enter, esc */
curses_get_window_size(MESSAGE_WIN, &height, &width);
curses_toggle_color_attr(win, MORECOLOR, NONE, ON);
mvwprintw(win, my, mx, iflags.msg_is_alert ? "<TAB!>" : ">>");
curses_toggle_color_attr(win, MORECOLOR, NONE, OFF);
if (iflags.msg_is_alert)
curses_alert_main_borders(TRUE);
wrefresh(win);
while (iflags.msg_is_alert && (ret = wgetch(win) != '\t'));
/* msgtype=stop should require space/enter rather than
* just any key, as we want to prevent YASD from
* riding direction keys. */
while (!iflags.msg_is_alert && (ret = wgetch(win)) && !index(resp,(char)ret));
if (iflags.msg_is_alert)
curses_alert_main_borders(FALSE);
if (height == 1) {
curses_clear_unhighlight_message_window();
} else {
mvwprintw(win, my, mx, " ");
if (!noscroll) {
scroll_window(MESSAGE_WIN);
turn_lines = 1;
}
wrefresh(win);
}
return ret;
}
int
curses_more()
{
return curses_block(FALSE);
}
/* Clear the message window if one line; otherwise unhighlight old messages */
void
curses_clear_unhighlight_message_window()
{
int mh, mw, count;
boolean border = curses_window_has_border(MESSAGE_WIN);
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
turn_lines = 1;
curses_get_window_size(MESSAGE_WIN, &mh, &mw);
mx = 0;
if (border) {
mx++;
}
if (mh == 1) {
curses_clear_nhwin(MESSAGE_WIN);
} else {
mx += mw; /* Force new line on new turn */
if (border) {
for (count = 0; count < mh; count++) {
mvwchgat(win, count + 1, 1, mw, COLOR_PAIR(8), A_NORMAL, NULL);
}
} else {
for (count = 0; count < mh; count++) {
mvwchgat(win, count, 0, mw, COLOR_PAIR(8), A_NORMAL, NULL);
}
}
wnoutrefresh(win);
}
}
/* Reset message window cursor to starting position, and display most
recent messages. */
void
curses_last_messages()
{
boolean border = curses_window_has_border(MESSAGE_WIN);
if (border) {
mx = 1;
my = 1;
} else {
mx = 0;
my = 0;
}
nhprev_mesg *mesg;
int i;
for (i = (num_messages - 1); i > 0; i--) {
mesg = get_msg_line(TRUE, i);
if (mesg && mesg->str && strcmp(mesg->str, ""))
curses_message_win_puts(mesg->str, TRUE);
}
curses_message_win_puts(toplines, TRUE);
}
/* Initialize list for message history */
void
curses_init_mesg_history()
{
max_messages = iflags.msg_history;
if (max_messages < 1) {
max_messages = 1;
}
if (max_messages > MESG_HISTORY_MAX) {
max_messages = MESG_HISTORY_MAX;
}
}
/* Display previous message window messages in reverse chron order */
void
curses_prev_mesg()
{
int count;
winid wid;
long turn = 0;
anything *identifier;
nhprev_mesg *mesg;
menu_item *selected = NULL;
wid = curses_get_wid(NHW_MENU);
curses_create_nhmenu(wid);
identifier = malloc(sizeof (anything));
identifier->a_void = NULL;
for (count = 0; count < num_messages; count++) {
mesg = get_msg_line(TRUE, count);
if ((turn != mesg->turn) && (count != 0)) {
curses_add_menu(wid, NO_GLYPH, identifier, 0, 0, A_NORMAL,
"---", FALSE);
}
curses_add_menu(wid, NO_GLYPH, identifier, 0, 0, A_NORMAL,
mesg->str, FALSE);
turn = mesg->turn;
}
curses_end_menu(wid, "");
curses_select_menu(wid, PICK_NONE, &selected);
}
/* Shows Count: in a separate window, or at the bottom of the message
window, depending on the user's settings */
void
curses_count_window(const char *count_text)
{
int startx, starty, winx, winy;
int messageh, messagew;
static WINDOW *countwin = NULL;
if ((count_text == NULL) && (countwin != NULL)) {
delwin(countwin);
countwin = NULL;
counting = FALSE;
return;
}
counting = TRUE;
if (iflags.wc_popup_dialog) { /* Display count in popup window */
startx = 1;
starty = 1;
if (countwin == NULL) {
countwin = curses_create_window(25, 1, UP);
}
} else { /* Display count at bottom of message window */
curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);
if (curses_window_has_border(MESSAGE_WIN)) {
winx++;
winy++;
}
winy += messageh - 1;
if (countwin == NULL) {
pline("#");
#ifndef PDCURSES
countwin = newwin(1, 25, winy, winx);
#endif /* !PDCURSES */
}
#ifdef PDCURSES
else {
curses_destroy_win(countwin);
}
countwin = newwin(1, 25, winy, winx);
#endif /* PDCURSES */
startx = 0;
starty = 0;
}
mvwprintw(countwin, starty, startx, "%s", count_text);
wrefresh(countwin);
}
/* Gets a "line" (buffer) of input. */
void
curses_message_win_getline(const char *prompt, char *answer, int buffer)
{
int height, width; /* of window */
char *tmpbuf, *p_answer; /* combined prompt + answer */
int nlines, maxlines, i; /* prompt + answer */
int promptline;
int promptx;
char **linestarts; /* pointers to start of each line */
char *tmpstr; /* for free() */
int maxy, maxx; /* linewrap / scroll */
int ch;
WINDOW *win = curses_get_nhwin(MESSAGE_WIN);
int border_space = 0;
int len = 0; /* of answer string */
boolean border = curses_window_has_border(MESSAGE_WIN);
int orig_cursor = curs_set(0);
curses_get_window_size(MESSAGE_WIN, &height, &width);
if (border) {
border_space = 1;
if (mx < 1) mx = 1;
if (my < 1) my = 1;
}
maxy = height - 1 + border_space;
maxx = width - 1 + border_space;
tmpbuf = (char *)malloc(strlen(prompt) + buffer + 2);
maxlines = buffer / width * 2;
strcpy(tmpbuf, prompt);
strcat(tmpbuf, " ");
nlines = curses_num_lines(tmpbuf,width);
maxlines += nlines * 2;
linestarts = (char **)malloc(sizeof(char*) * maxlines);
p_answer = tmpbuf + strlen(tmpbuf);
linestarts[0] = tmpbuf;
if (mx > border_space) { /* newline */
if (my >= maxy) scroll_window(MESSAGE_WIN);
else my++;
mx = border_space;
}
curses_toggle_color_attr(win, NONE, A_BOLD, ON);
for (i = 0; i < nlines-1; i++) {
tmpstr = curses_break_str(linestarts[i],width-1,1);
linestarts[i+1] = linestarts[i] + strlen(tmpstr);
if (*linestarts[i+1] == ' ') linestarts[i+1]++;
mvwaddstr(win,my,mx,tmpstr);
free(tmpstr);
if (++my >= maxy) {
scroll_window(MESSAGE_WIN);
my--;
}
}
mvwaddstr(win,my,mx,linestarts[nlines-1]);
mx = promptx = strlen(linestarts[nlines-1]) + border_space;
promptline = nlines - 1;
while(1) {
mx = strlen(linestarts[nlines - 1]) + border_space;
if (mx > maxx) {
if (nlines < maxlines) {
tmpstr = curses_break_str(linestarts[nlines - 1], width - 1, 1);
mx = strlen(tmpstr) + border_space;
mvwprintw(win, my, mx, "%*c", maxx - mx + 1, ' ');
if (++my > maxy) {
scroll_window(MESSAGE_WIN);
my--;
}
mx = border_space;
linestarts[nlines] = linestarts[nlines - 1] + strlen(tmpstr);
if (*linestarts[nlines] == ' ') linestarts[nlines]++;
mvwaddstr(win, my, mx, linestarts[nlines]);
mx = strlen(linestarts[nlines]) + border_space;
nlines++;
free(tmpstr);
} else {
p_answer[--len] = '\0';
mvwaddch(win, my, --mx, ' ');
}
}
wmove(win, my, mx);
curs_set(1);
wrefresh(win);
ch = getch();
curs_set(0);
switch(ch) {
case '\033': /* DOESCAPE */
/* blank the input but don't exit */
while(nlines - 1 > promptline) {
if (nlines-- > height) {
unscroll_window(MESSAGE_WIN);
tmpstr = curses_break_str(linestarts[nlines - height], width - 1, 1);
mvwaddstr(win, border_space, border_space, tmpstr);
free(tmpstr);
} else {
mx = border_space;
mvwprintw(win, my, mx, "%*c", maxx - mx, ' ');
my--;
}
}
mx = promptx;
mvwprintw(win, my, mx, "%*c", maxx - mx, ' ');
*p_answer = '\0';
len = 0;
break;
case ERR: /* should not happen */
*answer = '\0';
free(tmpbuf);
free(linestarts);
curs_set(orig_cursor);
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
return;
case '\r':
case '\n':
free(linestarts);
strncpy(answer, p_answer, buffer);
strcpy(toplines, tmpbuf);
mesg_add_line((char *) tmpbuf);
free(tmpbuf);
curs_set(orig_cursor);
curses_toggle_color_attr(win, NONE, A_BOLD, OFF);
if (++my > maxy) {
scroll_window(MESSAGE_WIN);
my--;
}
mx = border_space;
return;
case '\b':
case KEY_BACKSPACE:
if (len < 1) {
len = 1;
mx = promptx;
}
p_answer[--len] = '\0';
mvwaddch(win, my, --mx, ' ');
/* try to unwrap back to the previous line if there is one */
if (nlines > 1 && strlen(linestarts[nlines - 2]) < (size_t) width) {
mvwaddstr(win, my - 1, border_space, linestarts[nlines - 2]);
if (nlines-- > height) {
unscroll_window(MESSAGE_WIN);
tmpstr = curses_break_str(linestarts[nlines - height], width - 1, 1);
mvwaddstr(win, border_space, border_space, tmpstr);
free(tmpstr);
} else {
/* clean up the leftovers on the next line, if we didn't scroll it away */
mvwprintw(win, my--, border_space, "%*c", strlen(linestarts[nlines]), ' ');
}
}
break;
default:
p_answer[len++] = ch;
if (len >= buffer) len = buffer-1;
else mvwaddch(win, my, mx, ch);
p_answer[len] = '\0';
}
}
}
/* Scroll lines upward in given window, or clear window if only one line. */
static void
scroll_window(winid wid)
{
directional_scroll(wid,1);
}
static void
unscroll_window(winid wid)
{
directional_scroll(wid,-1);
}
static void
directional_scroll(winid wid, int nlines)
{
int wh, ww, s_top, s_bottom;
boolean border = curses_window_has_border(wid);
WINDOW *win = curses_get_nhwin(wid);
curses_get_window_size(wid, &wh, &ww);
if (wh == 1) {
curses_clear_nhwin(wid);
return;
}
if (border) {
s_top = 1;
s_bottom = wh;
} else {
s_top = 0;
s_bottom = wh - 1;
}
scrollok(win, TRUE);
wsetscrreg(win, s_top, s_bottom);
wscrl(win, nlines);
scrollok(win, FALSE);
if (wid == MESSAGE_WIN) {
if (border)
mx = 1;
else
mx = 0;
}
if (border) {
box(win, 0, 0);
}
wrefresh(win);
}
/* Add given line to message history */
static void
mesg_add_line(char *mline)
{
nhprev_mesg *tmp_mesg = NULL;
nhprev_mesg *current_mesg = malloc(sizeof (nhprev_mesg));
current_mesg->str = curses_copy_of(mline);
current_mesg->turn = moves;
current_mesg->next_mesg = NULL;
if (num_messages == 0) {
first_mesg = current_mesg;
}
if (last_mesg != NULL) {
last_mesg->next_mesg = current_mesg;
}
current_mesg->prev_mesg = last_mesg;
last_mesg = current_mesg;
if (num_messages < max_messages) {
num_messages++;
} else {
tmp_mesg = first_mesg->next_mesg;
free(first_mesg);
first_mesg = tmp_mesg;
}
}
/* Returns specified line from message history, or NULL if out of bounds */
static nhprev_mesg *
get_msg_line(boolean reverse, int mindex)
{
int count;
nhprev_mesg *current_mesg;
if (reverse) {
current_mesg = last_mesg;
for (count = 0; count < mindex; count++) {
if (current_mesg == NULL) {
return NULL;
}
current_mesg = current_mesg->prev_mesg;
}
return current_mesg;
} else {
current_mesg = first_mesg;
for (count = 0; count < mindex; count++) {
if (current_mesg == NULL) {
return NULL;
}
current_mesg = current_mesg->next_mesg;
}
return current_mesg;
}
}

19
win/curses/cursmesg.h Normal file
View File

@@ -0,0 +1,19 @@
/* 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" -*-*/
#ifndef CURSMESG_H
# define CURSMESG_H
/* Global declarations */
void curses_message_win_puts(const char *message, boolean recursed);
int curses_block(boolean require_tab);
int curses_more(void);
void curses_clear_unhighlight_message_window(void);
void curses_message_win_getline(const char *prompt, char *answer, int buffer);
void curses_last_messages(void);
void curses_init_mesg_history(void);
void curses_prev_mesg(void);
void curses_count_window(const char *count_text);
#endif /* CURSMESG_H */

880
win/curses/cursmisc.c Normal file
View File

@@ -0,0 +1,880 @@
/* 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 "cursmisc.h"
#include "func_tab.h"
#include "dlb.h"
#include <ctype.h>
/* Misc. curses interface functions */
/* Private declarations */
static int curs_x = -1;
static int curs_y = -1;
static int parse_escape_sequence(void);
/* Macros for Control and Alt keys */
#ifndef M
# ifndef NHSTDC
# define M(c) (0x80 | (c))
# else
# define M(c) ((c) - 128)
# endif/* NHSTDC */
#endif
#ifndef C
# define C(c) (0x1f & (c))
#endif
/* Read a character of input from the user */
int
curses_read_char()
{
int ch, tmpch;
ch = getch();
tmpch = ch;
ch = curses_convert_keys(ch);
if (ch == 0) {
ch = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */
}
#if defined(ALT_0) && defined(ALT_9) /* PDCurses, maybe others */
if ((ch >= ALT_0) && (ch <= ALT_9)) {
tmpch = (ch - ALT_0) + '0';
ch = M(tmpch);
}
#endif
#if defined(ALT_A) && defined(ALT_Z) /* PDCurses, maybe others */
if ((ch >= ALT_A) && (ch <= ALT_Z)) {
tmpch = (ch - ALT_A) + 'a';
ch = M(tmpch);
}
#endif
#ifdef KEY_RESIZE
/* Handle resize events via get_nh_event, not this code */
if (ch == KEY_RESIZE) {
ch = '\033'; /* NetHack doesn't know what to do with KEY_RESIZE */
}
#endif
if (counting && !isdigit(ch)) { /* Dismiss count window if necissary */
curses_count_window(NULL);
curses_refresh_nethack_windows();
}
return ch;
}
/* Turn on or off the specified color and / or attribute */
void
curses_toggle_color_attr(WINDOW * win, int color, int attr, int onoff)
{
#ifdef TEXTCOLOR
int curses_color;
/* Map color disabled */
if ((!iflags.wc_color) && (win == mapwin)) {
return;
}
/* GUI color disabled */
// if ((!iflags.wc2_guicolor) && (win != mapwin)) {
// return;
// }
if (color == 0) { /* make black fg visible */
# ifdef USE_DARKGRAY
if (iflags.wc2_darkgray) {
if (can_change_color() && (COLORS > 16)) {
/* colorpair for black is already darkgray */
} else { /* Use bold for a bright black */
wattron(win, A_BOLD);
}
} else
# endif/* USE_DARKGRAY */
color = CLR_BLUE;
}
curses_color = color + 1;
if (COLORS < 16) {
if (curses_color > 8 && curses_color < 17)
curses_color -= 8;
else if (curses_color > (17 + 16))
curses_color -= 16;
}
if (onoff == ON) { /* Turn on color/attributes */
if (color != NONE) {
if ((((color > 7) && (color < 17)) ||
(color > 17 + 17)) && (COLORS < 16)) {
wattron(win, A_BOLD);
}
wattron(win, COLOR_PAIR(curses_color));
}
if (attr != NONE) {
wattron(win, attr);
}
} else { /* Turn off color/attributes */
if (color != NONE) {
if ((color > 7) && (COLORS < 16)) {
wattroff(win, A_BOLD);
}
# ifdef USE_DARKGRAY
if ((color == 0) && (!can_change_color() || (COLORS <= 16))) {
wattroff(win, A_BOLD);
}
# else
if (iflags.use_inverse) {
wattroff(win, A_REVERSE);
}
# endif/* DARKGRAY */
wattroff(win, COLOR_PAIR(curses_color));
}
if (attr != NONE) {
wattroff(win, attr);
}
}
#endif /* TEXTCOLOR */
}
/* clean up and quit - taken from tty port */
void
curses_bail(const char *mesg)
{
clearlocks();
curses_exit_nhwindows(mesg);
nh_terminate(EXIT_SUCCESS);
}
/* Return a winid for a new window of the given type */
winid
curses_get_wid(int type)
{
winid ret;
static winid menu_wid = 20; /* Always even */
static winid text_wid = 21; /* Always odd */
switch (type) {
case NHW_MESSAGE:
return MESSAGE_WIN;
case NHW_MAP:
return MAP_WIN;
case NHW_STATUS:
return STATUS_WIN;
case NHW_MENU:
ret = menu_wid;
break;
case NHW_TEXT:
ret = text_wid;
break;
default:
panic("curses_get_wid: unsupported window type");
ret = -1; /* Not reached */
}
while (curses_window_exists(ret)) {
ret += 2;
if ((ret + 2) > 10000) { /* Avoid "wid2k" problem */
ret -= 9900;
}
}
if (type == NHW_MENU) {
menu_wid += 2;
} else {
text_wid += 2;
}
return ret;
}
/*
* Allocate a copy of the given string. If null, return a string of
* zero length.
*
* This is taken from copy_of() in tty/wintty.c.
*/
char *
curses_copy_of(const char *s)
{
if (!s)
s = "";
return strcpy((char *) alloc((unsigned) (strlen(s) + 1)), s);
}
/* Determine the number of lines needed for a string for a dialog window
of the given width */
int
curses_num_lines(const char *str, int width)
{
int last_space, count;
int curline = 1;
char substr[BUFSZ];
char tmpstr[BUFSZ];
strncpy(substr, str, BUFSZ-1);
substr[BUFSZ-1] = '\0';
while (strlen(substr) > (size_t) width) {
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ')
last_space = count;
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = (last_space + 1); (size_t) count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
curline++;
}
return curline;
}
/* Break string into smaller lines to fit into a dialog window of the
given width */
char *
curses_break_str(const char *str, int width, int line_num)
{
int last_space, count;
char *retstr;
int curline = 0;
int strsize = strlen(str) + 1;
#if __STDC_VERSION__ >= 199901L
char substr[strsize];
char curstr[strsize];
char tmpstr[strsize];
strcpy(substr, str);
#else
#ifndef BUFSZ
#define BUFSZ 256
#endif
char substr[BUFSZ * 2];
char curstr[BUFSZ * 2];
char tmpstr[BUFSZ * 2];
if (strsize > (BUFSZ * 2) - 1) {
paniclog("curses", "curses_break_str() string too long.");
strncpy(substr, str, (BUFSZ * 2) - 2);
substr[(BUFSZ * 2) - 1] = '\0';
} else
strcpy(substr, str);
#endif
while (curline < line_num) {
if (strlen(substr) == 0) {
break;
}
curline++;
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ') {
last_space = count;
} else if (substr[count] == '\0') {
last_space = count;
break;
}
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = 0; count < last_space; count++) {
curstr[count] = substr[count];
}
curstr[count] = '\0';
if (substr[count] == '\0') {
break;
}
for (count = (last_space + 1); (size_t) count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
}
if (curline < line_num) {
return NULL;
}
retstr = curses_copy_of(curstr);
return retstr;
}
/* Return the remaining portion of a string after hacking-off line_num lines */
char *
curses_str_remainder(const char *str, int width, int line_num)
{
int last_space, count;
char *retstr;
int curline = 0;
int strsize = strlen(str) + 1;
#if __STDC_VERSION__ >= 199901L
char substr[strsize];
char curstr[strsize];
char tmpstr[strsize];
strcpy(substr, str);
#else
#ifndef BUFSZ
#define BUFSZ 256
#endif
char substr[BUFSZ * 2];
char curstr[BUFSZ * 2];
char tmpstr[BUFSZ * 2];
if (strsize > (BUFSZ * 2) - 1) {
paniclog("curses", "curses_str_remainder() string too long.");
strncpy(substr, str, (BUFSZ * 2) - 2);
substr[(BUFSZ * 2) - 1] = '\0';
} else
strcpy(substr, str);
#endif
while (curline < line_num) {
if (strlen(substr) == 0) {
break;
}
curline++;
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ') {
last_space = count;
} else if (substr[count] == '\0') {
last_space = count;
break;
}
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = 0; count < last_space; count++) {
curstr[count] = substr[count];
}
curstr[count] = '\0';
if (substr[count] == '\0') {
break;
}
for (count = (last_space + 1); (size_t) count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
}
if (curline < line_num) {
return NULL;
}
retstr = curses_copy_of(substr);
return retstr;
}
/* Determine if the given NetHack winid is a menu window */
boolean
curses_is_menu(winid wid)
{
if ((wid > 19) && !(wid % 2)) { /* Even number */
return TRUE;
} else {
return FALSE;
}
}
/* Determine if the given NetHack winid is a text window */
boolean
curses_is_text(winid wid)
{
if ((wid > 19) && (wid % 2)) { /* Odd number */
return TRUE;
} else {
return FALSE;
}
}
/* Replace certain characters with portable drawing characters if
cursesgraphics option is enabled */
int
curses_convert_glyph(int ch, int glyph)
{
int symbol;
if (Is_rogue_level(&u.uz)) {
return ch;
}
/* Save some processing time by returning if the glyph represents
an object that we don't have custom characters for */
if (!glyph_is_cmap(glyph)) {
return ch;
}
symbol = glyph_to_cmap(glyph);
/* If user selected a custom character for this object, don't
override this. */
if (((glyph_is_cmap(glyph)) && (ch != showsyms[symbol]))) {
return ch;
}
switch (symbol) {
case S_vwall:
return ACS_VLINE;
case S_hwall:
return ACS_HLINE;
case S_tlcorn:
return ACS_ULCORNER;
case S_trcorn:
return ACS_URCORNER;
case S_blcorn:
return ACS_LLCORNER;
case S_brcorn:
return ACS_LRCORNER;
case S_crwall:
return ACS_PLUS;
case S_tuwall:
return ACS_BTEE;
case S_tdwall:
return ACS_TTEE;
case S_tlwall:
return ACS_RTEE;
case S_trwall:
return ACS_LTEE;
case S_tree:
return ACS_PLMINUS;
case S_corr:
return ACS_CKBOARD;
case S_litcorr:
return ACS_CKBOARD;
}
return ch;
}
/* Move text cursor to specified coordinates in the given NetHack window */
void
curses_move_cursor(winid wid, int x, int y)
{
int sx, sy, ex, ey;
int xadj = 0;
int yadj = 0;
#ifndef PDCURSES
WINDOW *win = curses_get_nhwin(MAP_WIN);
#endif
if (wid != MAP_WIN) {
return;
}
#ifdef PDCURSES
/* PDCurses seems to not handle wmove correctly, so we use move and
physical screen coordinates instead */
curses_get_window_xy(wid, &xadj, &yadj);
#endif
curs_x = x + xadj;
curs_y = y + yadj;
curses_map_borders(&sx, &sy, &ex, &ey, x, y);
if (curses_window_has_border(wid)) {
curs_x++;
curs_y++;
}
if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
curs_x -= sx;
curs_y -= sy;
#ifdef PDCURSES
move(curs_y, curs_x);
#else
wmove(win, curs_y, curs_x);
#endif
}
}
/* Perform actions that should be done every turn before nhgetch() */
void
curses_prehousekeeping()
{
#ifndef PDCURSES
WINDOW *win = curses_get_nhwin(MAP_WIN);
#endif /* PDCURSES */
if ((curs_x > -1) && (curs_y > -1)) {
curs_set(1);
#ifdef PDCURSES
/* PDCurses seems to not handle wmove correctly, so we use move
and physical screen coordinates instead */
move(curs_y, curs_x);
#else
wmove(win, curs_y, curs_x);
#endif /* PDCURSES */
curses_refresh_nhwin(MAP_WIN);
}
}
/* Perform actions that should be done every turn after nhgetch() */
void
curses_posthousekeeping()
{
curs_set(0);
//curses_decrement_highlights(FALSE);
curses_clear_unhighlight_message_window();
}
void
curses_view_file(const char *filename, boolean must_exist)
{
winid wid;
anything *identifier;
char buf[BUFSZ];
menu_item *selected = NULL;
dlb *fp = dlb_fopen(filename, "r");
if ((fp == NULL) && (must_exist)) {
pline("Cannot open %s for reading!", filename);
}
if (fp == NULL) {
return;
}
wid = curses_get_wid(NHW_MENU);
curses_create_nhmenu(wid);
identifier = malloc(sizeof (anything));
identifier->a_void = NULL;
while (dlb_fgets(buf, BUFSZ, fp) != NULL) {
curses_add_menu(wid, NO_GLYPH, identifier, 0, 0, A_NORMAL, buf, FALSE);
}
dlb_fclose(fp);
curses_end_menu(wid, "");
curses_select_menu(wid, PICK_NONE, &selected);
}
void
curses_rtrim(char *str)
{
char *s;
for (s = str; *s != '\0'; ++s);
for (--s; isspace(*s) && s > str; --s);
if (s == str)
*s = '\0';
else
*(++s) = '\0';
}
/* Read numbers until non-digit is encountered, and return number
in int form. */
int
curses_get_count(int first_digit)
{
long current_count = first_digit;
int current_char;
current_char = curses_read_char();
while (isdigit(current_char)) {
current_count = (current_count * 10) + (current_char - '0');
if (current_count > LARGEST_INT) {
current_count = LARGEST_INT;
}
pline("Count: %ld", current_count);
current_char = curses_read_char();
}
ungetch(current_char);
if (current_char == '\033') { /* Cancelled with escape */
current_count = -1;
}
return current_count;
}
/* Convert the given NetHack text attributes into the format curses
understands, and return that format mask. */
int
curses_convert_attr(int attr)
{
int curses_attr;
switch (attr) {
case ATR_NONE:
curses_attr = A_NORMAL;
break;
case ATR_ULINE:
curses_attr = A_UNDERLINE;
break;
case ATR_BOLD:
curses_attr = A_BOLD;
break;
case ATR_BLINK:
curses_attr = A_BLINK;
break;
case ATR_INVERSE:
curses_attr = A_REVERSE;
break;
default:
curses_attr = A_NORMAL;
}
return curses_attr;
}
/* Map letter attributes from a string to bitmask. Return mask on
success, or 0 if not found */
int
curses_read_attrs(char *attrs)
{
int retattr = 0;
if (strchr(attrs, 'b') || strchr(attrs, 'B')) {
retattr = retattr | A_BOLD;
}
if (strchr(attrs, 'i') || strchr(attrs, 'I')) {
retattr = retattr | A_REVERSE;
}
if (strchr(attrs, 'u') || strchr(attrs, 'U')) {
retattr = retattr | A_UNDERLINE;
}
if (strchr(attrs, 'k') || strchr(attrs, 'K')) {
retattr = retattr | A_BLINK;
}
#ifdef A_ITALIC
if (strchr(attrs, 't') || strchr(attrs, 'T')) {
retattr = retattr | A_ITALIC;
}
#endif
#ifdef A_RIGHTLINE
if (strchr(attrs, 'r') || strchr(attrs, 'R')) {
retattr = retattr | A_RIGHTLINE;
}
#endif
#ifdef A_LEFTLINE
if (strchr(attrs, 'l') || strchr(attrs, 'L')) {
retattr = retattr | A_LEFTLINE;
}
#endif
return retattr;
}
/* Convert special keys into values that NetHack can understand.
Currently this is limited to arrow keys, but this may be expanded. */
int
curses_convert_keys(int key)
{
int ret = key;
if (ret == '\033') {
ret = parse_escape_sequence();
}
/* Handle arrow keys */
switch (key) {
case KEY_LEFT:
if (iflags.num_pad) {
ret = '4';
} else {
ret = 'h';
}
break;
case KEY_RIGHT:
if (iflags.num_pad) {
ret = '6';
} else {
ret = 'l';
}
break;
case KEY_UP:
if (iflags.num_pad) {
ret = '8';
} else {
ret = 'k';
}
break;
case KEY_DOWN:
if (iflags.num_pad) {
ret = '2';
} else {
ret = 'j';
}
break;
#ifdef KEY_A1
case KEY_A1:
if (iflags.num_pad) {
ret = '7';
} else {
ret = 'y';
}
break;
#endif /* KEY_A1 */
#ifdef KEY_A3
case KEY_A3:
if (iflags.num_pad) {
ret = '9';
} else {
ret = 'u';
}
break;
#endif /* KEY_A3 */
#ifdef KEY_C1
case KEY_C1:
if (iflags.num_pad) {
ret = '1';
} else {
ret = 'b';
}
break;
#endif /* KEY_C1 */
#ifdef KEY_C3
case KEY_C3:
if (iflags.num_pad) {
ret = '3';
} else {
ret = 'n';
}
break;
#endif /* KEY_C3 */
#ifdef KEY_B2
case KEY_B2:
if (iflags.num_pad) {
ret = '5';
} else {
ret = 'g';
}
break;
#endif /* KEY_B2 */
}
return ret;
}
/* Process mouse events. Mouse movement is processed until no further
mouse movement events are available. Returns 0 for a mouse click
event, or the first non-mouse key event in the case of mouse
movement. */
int
curses_get_mouse(int *mousex, int *mousey, int *mod)
{
int key = '\033';
#ifdef NCURSES_MOUSE_VERSION
MEVENT event;
if (getmouse(&event) == OK) { /* When the user clicks left mouse button */
if (event.bstate & BUTTON1_CLICKED) {
/* See if coords are in map window & convert coords */
if (wmouse_trafo(mapwin, &event.y, &event.x, TRUE)) {
key = 0; /* Flag mouse click */
*mousex = event.x;
*mousey = event.y;
if (curses_window_has_border(MAP_WIN)) {
(*mousex)--;
(*mousey)--;
}
*mod = CLICK_1;
}
}
}
#endif /* NCURSES_MOUSE_VERSION */
return key;
}
static int
parse_escape_sequence(void)
{
#ifndef PDCURSES
int ret;
timeout(10);
ret = getch();
if (ret != ERR) { /* Likely an escape sequence */
if (((ret >= 'a') && (ret <= 'z')) || ((ret >= '0') && (ret <= '9'))) {
ret |= 0x80; /* Meta key support for most terminals */
} else if (ret == 'O') { /* Numeric keypad */
ret = getch();
if ((ret != ERR) && (ret >= 112) && (ret <= 121)) {
ret = ret - 112 + '0'; /* Convert to number */
} else {
ret = '\033'; /* Escape */
}
}
} else {
ret = '\033'; /* Just an escape character */
}
timeout(-1);
return ret;
#else
return '\033';
#endif /* !PDCURSES */
}

30
win/curses/cursmisc.h Normal file
View File

@@ -0,0 +1,30 @@
/* 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" -*-*/
#ifndef CURSMISC_H
# define CURSMISC_H
/* Global declarations */
int curses_read_char(void);
void curses_toggle_color_attr(WINDOW * win, int color, int attr, int onoff);
void curses_bail(const char *mesg);
winid curses_get_wid(int type);
char *curses_copy_of(const char *s);
int curses_num_lines(const char *str, int width);
char *curses_break_str(const char *str, int width, int line_num);
char *curses_str_remainder(const char *str, int width, int line_num);
boolean curses_is_menu(winid wid);
boolean curses_is_text(winid wid);
int curses_convert_glyph(int ch, int glyph);
void curses_move_cursor(winid wid, int x, int y);
void curses_prehousekeeping(void);
void curses_posthousekeeping(void);
void curses_view_file(const char *filename, boolean must_exist);
void curses_rtrim(char *str);
int curses_get_count(int first_digit);
int curses_convert_attr(int attr);
int curses_read_attrs(char *attrs);
int curses_convert_keys(int key);
int curses_get_mouse(int *mousex, int *mousey, int *mod);
#endif /* CURSMISC_H */

1583
win/curses/cursstat.c Normal file

File diff suppressed because it is too large Load Diff

21
win/curses/cursstat.h Normal file
View File

@@ -0,0 +1,21 @@
/* 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" -*-*/
#ifndef CURSSTAT_H
# define CURSSTAT_H
/* Used by handle_stat_change to handle some stats differently. Not an enum
because this is how NetHack code generally handles them. */
# define STAT_OTHER 0
# define STAT_STR 1
# define STAT_GOLD 2
# define STAT_AC 4
# define STAT_TIME 5
# define STAT_TROUBLE 6
/* Global declarations */
void curses_update_stats();
void curses_decrement_highlights(boolean);
attr_t curses_color_attr(int nh_color, int bg_color);
#endif /* CURSSTAT_H */

752
win/curses/curswins.c Normal file
View File

@@ -0,0 +1,752 @@
/* 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;
typedef struct nhchar {
int ch; /* character */
int color; /* color info for character */
int attr; /* attributes of character */
} 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 width, int height, orient orientation)
{
int mapx, mapy, maph, 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))
panic("curses_create_window: Terminal too small for dialog window");
switch (orientation) {
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;
default:
panic("curses_create_window: Bad orientation");
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()
{
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)) {
panic("curses_get_nhwin: wid out of range. Not a main window.");
}
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)) {
panic("curses_add_nhwin: wid out of range. Not a main window.");
}
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)) {
panic("curses_del_nhwin: wid out of range. Not a main window.");
}
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++;
}
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)) {
panic("curses_get_window_xy: wid out of range. Not a main window.");
}
*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)) {
panic
("curses_get_window_orientation: wid out of range. Not a main window.");
}
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) {
curses_message_win_puts(text, FALSE);
return;
}
#if 0
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;
}
#endif
if (curses_is_menu(wid) || curses_is_text(wid)) {
if (!curses_menu_exists(wid)) {
panic("curses_puts: Attempted write to nonexistant window!");
}
identifier = malloc(sizeof (anything));
identifier->a_void = NULL;
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;
}
}
/* Unconditionally write a single character to a window at the given
coordinates without a refresh. Currently only used for the map. */
static void
write_char(WINDOW * win, int x, int y, nethack_char nch)
{
curses_toggle_color_attr(win, nch.color, nch.attr, ON);
#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 * ((long) (ex - sx + 1) / COLNO));
sbex = (ex * ((long) (ex - sx + 1) / COLNO));
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 = (sy * ((long) (ey - sy + 1) / ROWNO));
sbey = (ey * ((long) (ey - sy + 1) / ROWNO));
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()
{
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;
}

33
win/curses/curswins.h Normal file
View File

@@ -0,0 +1,33 @@
/* 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" -*-*/
#ifndef CURSWIN_H
# define CURSWIN_H
/* Global declarations */
WINDOW *curses_create_window(int width, int height, orient orientation);
void curses_destroy_win(WINDOW * win);
void curses_refresh_nethack_windows(void);
WINDOW *curses_get_nhwin(winid wid);
void curses_add_nhwin(winid wid, int height, int width, int y, int x,
orient orientation, boolean border);
void curses_add_wid(winid wid);
void curses_refresh_nhwin(winid wid);
void curses_del_nhwin(winid wid);
void curses_del_wid(winid wid);
void curses_putch(winid wid, int x, int y, int ch, int color, int attrs);
void curses_get_window_xy(winid wid, int *x, int *y);
boolean curses_window_has_border(winid wid);
boolean curses_window_exists(winid wid);
int curses_get_window_orientation(winid wid);
void curses_puts(winid wid, int attr, const char *text);
void curses_clear_nhwin(winid wid);
void curses_alert_win_border(winid wid, boolean onoff);
void curses_alert_main_borders(boolean onoff);
void curses_draw_map(int sx, int sy, int ex, int ey);
boolean curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy);
#endif /* CURSWIN_H */