1 /*
2  * display.c
3  * Copyright (C) 2009-2020 Joachim de Groot <jdegroot@web.de>
4  *
5  * NLarn is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * NLarn is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <ctype.h>
20 #include <glib.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "display.h"
25 #include "fov.h"
26 #include "map.h"
27 #include "extdefs.h"
28 #include "spheres.h"
29 
30 typedef struct _display_colset
31 {
32     const char *name;
33     const int  val;
34 } display_colset;
35 
36 const display_colset display_default_colset[] =
37 {
38     { "",             COLOURLESS },
39     { "black",        BLACK },
40     { "red",          RED },
41     { "green",        GREEN },
42     { "brown",        BROWN },
43     { "blue",         BLUE },
44     { "magenta",      MAGENTA },
45     { "cyan",         CYAN },
46     { "lightgray",    LIGHTGRAY },
47     { "darkgrey",     DARKGRAY },
48     { "lightred",     LIGHTRED },
49     { "lightgreen",   LIGHTGREEN },
50     { "yellow",       YELLOW },
51     { "lightblue",    LIGHTBLUE },
52     { "lightmagenta", LIGHTMAGENTA },
53     { "lightcyan",    LIGHTCYAN },
54     { "white",        WHITE }
55 };
56 
57 const display_colset display_dialog_colset[] =
58 {
59     { "black",        DDC_BLACK },
60     { "red",          DDC_RED },
61     { "green",        DDC_GREEN },
62     { "brown",        DDC_BROWN },
63     { "blue",         DDC_BLUE },
64     { "magenta",      DDC_MAGENTA },
65     { "cyan",         DDC_CYAN },
66     { "lightgray",    DDC_LIGHTGRAY },
67     { "darkgrey",     DDC_DARKGRAY },
68     { "lightred",     DDC_LIGHTRED },
69     { "lightgreen",   DDC_LIGHTGREEN },
70     { "yellow",       DDC_YELLOW },
71     { "lightblue",    DDC_LIGHTBLUE },
72     { "lightmagenta", DDC_LIGHTMAGENTA },
73     { "lightcyan",    DDC_LIGHTCYAN },
74     { "white",        DDC_WHITE }
75 };
76 
77 static gboolean display_initialised = FALSE;
78 
79 /* linked list of opened windows */
80 static GList *windows = NULL;
81 
82 static int mvwcprintw(WINDOW *win, int defattr, int currattr,
83         const display_colset *colset, int y, int x, const char *fmt, ...);
84 
85 static int display_get_colval(const display_colset *colset, const char *name);
86 
87 static void display_inventory_help(GPtrArray *callbacks);
88 
89 static display_window *display_window_new(int x1, int y1, int width,
90         int height, const char *title);
91 
92 static int display_window_move(display_window *dwin, int key);
93 static void display_window_update_title(display_window *dwin, const char *title);
94 static void display_window_update_caption(display_window *dwin, char *caption);
95 static void display_window_update_arrow_up(display_window *dwin, gboolean on);
96 static void display_window_update_arrow_down(display_window *dwin, gboolean on);
97 
98 static display_window *display_item_details(guint x1, guint y1, guint width,
99                                             item *it, player *p, gboolean shop);
100 
101 static void display_spheres_paint(sphere *s, player *p);
102 
display_init()103 void display_init()
104 {
105 #ifdef NCURSES_VERSION
106     /*
107      * Don't wait for trailing key codes after an ESC key is pressed.
108      * This breaks compatibility with very old terminals connected over
109      * very slow serial lines - I think I can live with that.
110      */
111     set_escdelay(0);
112 #endif
113 
114 #ifdef SDLPDCURSES
115     /* default to 90 columns when using SDL PDCurses */
116     g_setenv("PDC_COLS", "90", 0);
117     g_setenv("PDC_LINES", "25", 0);
118 
119     /* Set the window icon */
120     char *icon_name = g_strdup_printf("%s/nlarn-128.bmp", nlarn_libdir);
121     g_setenv("PDC_ICON", icon_name, 1);
122     g_free(icon_name);
123 
124     /* Set the font - allow overriding this default */
125     gchar *font_name = g_strdup_printf("%s/FiraMono-Medium.otf", nlarn_libdir);
126     g_setenv("PDC_FONT", font_name, 0);
127     g_free(font_name);
128 #endif
129 
130     /* Start curses mode */
131     initscr();
132 
133 #ifdef SDLPDCURSES
134     /* These initialisations have to be done after initscr(), otherwise
135        the window is not yet available. */
136     /* Set the window title */
137     char *window_title = g_strdup_printf("NLarn %s", nlarn_version);
138 
139     PDC_set_title(window_title);
140     g_free(window_title);
141 
142     /* return modifier keys pressed with key */
143     PDC_return_key_modifiers(TRUE);
144 #endif
145 
146     /* initialize colours */
147     start_color();
148 
149     /* black background */
150     init_pair(DCP_WHITE_BLACK,   COLOR_WHITE,   COLOR_BLACK);
151     init_pair(DCP_RED_BLACK,     COLOR_RED,     COLOR_BLACK);
152     init_pair(DCP_GREEN_BLACK,   COLOR_GREEN,   COLOR_BLACK);
153     init_pair(DCP_BLUE_BLACK,    COLOR_BLUE,    COLOR_BLACK);
154     init_pair(DCP_YELLOW_BLACK,  COLOR_YELLOW,  COLOR_BLACK);
155     init_pair(DCP_MAGENTA_BLACK, COLOR_MAGENTA, COLOR_BLACK);
156     init_pair(DCP_CYAN_BLACK,    COLOR_CYAN,    COLOR_BLACK);
157     init_pair(DCP_BLACK_BLACK,   COLOR_BLACK,   COLOR_BLACK);
158 
159     /* these colour pairs are used by dialogues */
160     init_pair(DCP_WHITE_RED,    COLOR_WHITE,    COLOR_RED);
161     init_pair(DCP_RED_RED,      COLOR_RED,      COLOR_RED);
162     init_pair(DCP_GREEN_RED,    COLOR_GREEN,    COLOR_RED);
163     init_pair(DCP_BLUE_RED,     COLOR_BLUE,     COLOR_RED);
164     init_pair(DCP_YELLOW_RED,   COLOR_YELLOW,   COLOR_RED);
165     init_pair(DCP_MAGENTA_RED,  COLOR_MAGENTA,  COLOR_RED);
166     init_pair(DCP_CYAN_RED,     COLOR_CYAN,     COLOR_RED);
167     init_pair(DCP_BLACK_RED,    COLOR_BLACK,    COLOR_RED);
168     init_pair(DCP_BLACK_WHITE,  COLOR_BLACK,    COLOR_WHITE);
169     init_pair(DCP_RED_WHITE,    COLOR_RED,      COLOR_WHITE);
170 
171     /* control special keys in application */
172     raw();
173 
174     /* suppress input echo */
175     noecho();
176 
177     /* enable function keys */
178     keypad(stdscr, TRUE);
179 
180     /* want all 8 bits */
181     meta(stdscr, TRUE);
182 
183     /* make cursor invisible */
184     curs_set(0);
185 
186     /* update display initialisation status */
187     display_initialised = TRUE;
188 }
189 
attr_colour(int colour,int reverse)190 static int attr_colour(int colour, int reverse)
191 {
192     if (reverse)
193         return (A_REVERSE | colour);
194 
195     return colour;
196 }
197 
198 /* convenience helper against endless repetition */
199 #define waaddch(win, attrs, ch) \
200     wattron(win, attrs); \
201     waddch(win, ch); \
202     wattroff(win, attrs)
203 
204 #define aaddch(attrs, ch) waaddch(stdscr, attrs, ch)
205 
206 /* mvaddch with an additional attribute parameter */
207 #define mvaaddch(y, x, attrs, ch) \
208     attron(attrs); \
209     mvaddch(y, x, ch); \
210     attroff(attrs)
211 
212 /* printw with an additional attribute parameter */
213 #define aprintw(attrs, fmt, ...) \
214     attron(attrs); \
215     printw(fmt, ##__VA_ARGS__); \
216     attroff(attrs)
217 
218 /* mvwprintw with an additional attribute parameter */
219 #define mvwaprintw(win, y, x, attrs, fmt, ...) \
220     wattron(win, attrs); \
221     mvwprintw(win, y, x, fmt, ##__VA_ARGS__); \
222     wattroff(win, attrs)
223 
224 /* mvprintw with an additional attribute parameter */
225 #define mvaprintw(y, x, attrs, fmt, ...) \
226     mvwaprintw(stdscr, y, x, attrs, fmt, ##__VA_ARGS__)
227 
228 /* mvwhline with an additional attribute parameter */
229 #define mvwahline(win, y, x, attrs, ch, n) \
230     wattron(win, attrs); \
231     mvwhline(win, y, x, ch, n); \
232     wattroff(win, attrs)
233 
display_paint_screen(player * p)234 void display_paint_screen(player *p)
235 {
236     position pos = pos_invalid;
237     map *vmap;
238     int attrs;              /* curses attributes */
239 
240     /* draw line around map */
241     (void)mvhline(MAP_MAX_Y, 0, ACS_HLINE, MAP_MAX_X);
242     (void)mvvline(0, MAP_MAX_X, ACS_VLINE, MAP_MAX_Y);
243     (void)mvaddch(MAP_MAX_Y, MAP_MAX_X, ACS_LRCORNER);
244 
245     /* make shortcut to the visible map */
246     vmap = game_map(nlarn, Z(p->pos));
247 
248     /* draw map */
249     Z(pos) = Z(p->pos);
250     for (Y(pos) = 0; Y(pos) < MAP_MAX_Y; Y(pos)++)
251     {
252         /* position cursor */
253         move(Y(pos), 0);
254 
255         for (X(pos) = 0; X(pos) < MAP_MAX_X; X(pos)++)
256         {
257             if (game_fullvis(nlarn) || fov_get(p->fv, pos))
258             {
259                 /* draw the truth */
260                 inventory **inv = map_ilist_at(vmap, pos);
261                 const gboolean has_items = inv_length(*inv) > 0;
262 
263                 if (map_sobject_at(vmap, pos))
264                 {
265                     /* draw stationary objects first */
266                     gchar glyph;
267                     if (map_sobject_at(vmap, pos) == LS_CLOSEDDOOR || map_sobject_at(vmap, pos) == LS_OPENDOOR)
268                         glyph = map_get_door_glyph(vmap, pos);
269                     else
270                         glyph = so_get_glyph(map_sobject_at(vmap, pos));
271 
272                     aaddch(attr_colour(so_get_colour(map_sobject_at(vmap, pos)), has_items),
273                            glyph);
274                 }
275                 else if (has_items)
276                 {
277                     /* draw items */
278                     item *it;
279 
280                     /* memorize the most interesting item on the tile */
281                     if (inv_length_filtered(*inv, item_filter_gems) > 0)
282                     {
283                         /* there's a gem in the stack */
284                         it = inv_get_filtered(*inv, 0, item_filter_gems);
285                     }
286                     else if (inv_length_filtered(*inv, item_filter_gold) > 0)
287                     {
288                         /* there is gold in the stack */
289                         it = inv_get_filtered(*inv, 0, item_filter_gold);
290                     }
291                     else
292                     {
293                         /* memorize the topmost item on the stack */
294                         it = inv_get(*inv, inv_length(*inv) - 1);
295                     }
296 
297                     const gboolean has_trap = (map_trap_at(vmap, pos)
298                                                && player_memory_of(p, pos).trap);
299 
300                     aaddch(attr_colour(item_colour(it), has_trap),
301                            item_glyph(it->type));
302                 }
303                 else if (map_trap_at(vmap, pos) && (game_fullvis(nlarn) || player_memory_of(p, pos).trap))
304                 {
305                     /* FIXME - displays trap when unknown!! */
306                     aaddch(trap_colour(map_trap_at(vmap, pos)), '^');
307                 }
308                 else
309                 {
310                     /* draw tile */
311                     aaddch(mt_get_colour(map_tiletype_at(vmap, pos)),
312                            mt_get_glyph(map_tiletype_at(vmap, pos)));
313                 }
314             }
315             else /* i.e. !fullvis && !visible: draw players memory */
316             {
317                 const gboolean has_items = player_memory_of(p, pos).item;
318                 if (player_memory_of(p, pos).sobject)
319                 {
320                     /* draw stationary object */
321                     sobject_t ms = map_sobject_at(vmap, pos);
322 
323                     gchar glyph;
324                     if (ms == LS_CLOSEDDOOR || ms == LS_OPENDOOR)
325                         glyph = map_get_door_glyph(vmap, pos);
326                     else
327                         glyph = so_get_glyph(ms);
328 
329                     aaddch(attr_colour(so_get_colour(ms), has_items), glyph);
330                 }
331                 else if (has_items)
332                 {
333                     /* draw items */
334                     const gboolean has_trap = (player_memory_of(p, pos).trap);
335 
336                     aaddch(attr_colour(player_memory_of(p, pos).item_colour, has_trap),
337                            item_glyph(player_memory_of(p, pos).item));
338                 }
339                 else if (player_memory_of(p, pos).trap)
340                 {
341                     /* draw trap */
342                     aaddch(trap_colour(map_trap_at(vmap, pos)), '^');
343                 }
344                 else
345                 {
346                     /* draw tile */
347                     aaddch(DARKGRAY, mt_get_glyph(player_memory_of(p, pos).type));
348                 }
349             }
350 
351             /* draw monsters */
352             monster *monst = map_get_monster_at(vmap, pos);
353 
354             if (monst == NULL)
355             {
356                 /* no monster found */
357                 continue;
358             }
359 
360             if (game_fullvis(nlarn)
361                     || player_effect(p, ET_DETECT_MONSTER)
362                     || monster_in_sight(monst))
363             {
364                 position mpos = monster_pos(monst);
365                 mvaaddch(Y(mpos), X(mpos), monster_color(monst), monster_glyph(monst));
366             }
367         }
368     }
369 
370     /* draw spheres */
371     g_ptr_array_foreach(nlarn->spheres, (GFunc)display_spheres_paint, p);
372 
373     /* draw player */
374     char pc;
375     if (player_effect(p, ET_INVISIBILITY))
376     {
377         pc = ' ';
378         attrs = A_REVERSE | WHITE;
379     }
380     else
381     {
382         pc = '@';
383         attrs = WHITE;
384     }
385 
386     mvaaddch(Y(p->pos), X(p->pos), attrs, pc);
387 
388 
389     /* *** first status line below map *** */
390     move(MAP_MAX_Y + 1, 0);
391     clrtoeol();
392 
393     /* player name */
394     if (p->name)
395     {
396         /* the player's name can be NULL directly after starting the game */
397         printw("%s", p->name);
398     }
399 
400     /* current HPs */
401     if (p->hp <= ((int)p->hp_max / 10))      /* 10% hp left */
402         attrs = LIGHTRED | A_BLINK;
403     else if (p->hp <= ((int)p->hp_max / 4))  /* 25% hp left */
404         attrs = RED;
405     else if (p->hp <= ((int)p->hp_max / 2))  /* 50% hp left */
406         attrs = GREEN;
407     else
408         attrs = LIGHTGREEN;
409 
410 #ifdef SDLPDCURSES
411     /* enable blinking on SDL PDCurses display for very low hp */
412     if (attrs & A_BLINK) {
413         PDC_set_blink(TRUE);
414     } else {
415         PDC_set_blink(FALSE);
416     }
417 #endif
418 
419     mvaprintw(MAP_MAX_Y + 1, MAP_MAX_X - 21, attrs, "HP %3d", p->hp, player_get_hp_max(p));
420 
421     /* max HPs */
422     mvaprintw(MAP_MAX_Y + 1, MAP_MAX_X - 15, LIGHTGREEN, "/%-3d", player_get_hp_max(p));
423 
424     /* current MPs */
425     if (p->mp <= ((int)p->mp_max / 10)) /* 10% mp left */
426         attrs = LIGHTMAGENTA;
427     else if (p->mp <= ((int)p->mp_max / 4))  /* 25% mp left */
428         attrs = MAGENTA;
429     else if (p->mp <= ((int)p->mp_max / 2))  /* 50% mp left */
430         attrs = CYAN;
431     else
432         attrs = LIGHTCYAN;
433 
434     mvaprintw(MAP_MAX_Y + 1, MAP_MAX_X - 10, attrs, "MP %3d", p->mp);
435 
436     /* max MPs */
437     mvaprintw(MAP_MAX_Y + 1, MAP_MAX_X - 4, LIGHTCYAN, "/%-3d", player_get_mp_max(p));
438 
439     /* game time */
440     mvprintw(MAP_MAX_Y + 1, MAP_MAX_X + 1, "T %-6d", game_turn(nlarn));
441 
442     /* *** second status line below map *** */
443     move(MAP_MAX_Y + 2, 0);
444     clrtoeol();
445 
446     /* player level description */
447     char *pld = g_strdup(player_get_level_desc(p));
448     pld[0] = g_ascii_toupper(pld[0]);
449     printw(pld);
450     g_free(pld);
451 
452     /* experience points / level */
453     mvaprintw(MAP_MAX_Y + 2, MAP_MAX_X - 21, LIGHTBLUE, "XP %3d/%-5d",
454              p->level, p->experience);
455 
456     /* dungeon map */
457     mvprintw(MAP_MAX_Y + 2, MAP_MAX_X + 1, "Lvl: %s", map_name(vmap));
458 
459 
460     /* *** RIGHT STATUS *** */
461 
462     /* strength */
463     mvprintw(1, MAP_MAX_X + 3, "STR ");
464 
465     if (player_get_str(p) > (int)p->strength)
466         attrs = YELLOW;
467     else if (player_get_str(p) < (int)p->strength)
468         attrs = LIGHTRED;
469     else
470         attrs = WHITE;
471 
472     aprintw(attrs, "%2d", player_get_str(p));
473     clrtoeol();
474 
475     /* dexterity */
476     mvprintw(2, MAP_MAX_X + 3, "DEX ");
477 
478     if (player_get_dex(p) > (int)p->dexterity)
479         attrs = YELLOW;
480     else if (player_get_dex(p) < (int)p->dexterity)
481         attrs = LIGHTRED;
482     else
483         attrs = WHITE;
484 
485     aprintw(attrs, "%2d", player_get_dex(p));
486     clrtoeol();
487 
488     /* constitution */
489     mvprintw(3, MAP_MAX_X + 3, "CON ");
490 
491     if (player_get_con(p) > (int)p->constitution)
492         attrs = YELLOW;
493     else if (player_get_con(p) < (int)p->constitution)
494         attrs = LIGHTRED;
495     else
496         attrs = WHITE;
497 
498     aprintw(attrs, "%2d", player_get_con(p));
499     clrtoeol();
500 
501     /* intelligence */
502     mvprintw(4, MAP_MAX_X + 3, "INT ");
503 
504     if (player_get_int(p) > (int)p->intelligence)
505         attrs = YELLOW;
506     else if (player_get_int(p) < (int)p->intelligence)
507         attrs = LIGHTRED;
508     else
509         attrs = WHITE;
510 
511     aprintw(attrs, "%2d", player_get_int(p));
512     clrtoeol();
513 
514     /* wisdom */
515     mvprintw(5, MAP_MAX_X + 3, "WIS ");
516 
517     if (player_get_wis(p) > (int)p->wisdom)
518         attrs = YELLOW;
519     else if (player_get_wis(p) < (int)p->wisdom)
520         attrs = LIGHTRED;
521     else
522         attrs = WHITE;
523 
524     aprintw(attrs, "%2d", player_get_wis(p));
525     clrtoeol();
526 
527     /* clear line below Wisdom */
528     move(6, MAP_MAX_X + 1);
529     clrtoeol();
530 
531     /* wielded weapon */
532     if (p->eq_weapon)
533     {
534         char *wdesc = weapon_shortdesc(p->eq_weapon, COLS - MAP_MAX_X - 4);
535         mvprintw(7, MAP_MAX_X + 3, "%s", wdesc);
536         g_free(wdesc);
537     }
538     else
539     {
540         mvaprintw(7, MAP_MAX_X + 3, LIGHTRED, "unarmed");
541     }
542     clrtoeol();
543 
544     /* armour class */
545     mvprintw(8, MAP_MAX_X + 3, "AC: %2d", player_get_ac(p));
546     clrtoeol();
547 
548     /* gold */
549     mvprintw(9, MAP_MAX_X + 3, "$%-7d", player_get_gold(p));
550     clrtoeol();
551 
552     /* clear line below gold */
553     move(10, MAP_MAX_X + 1);
554     clrtoeol();
555 
556     /* clear lines */
557     for (guint i = 0; i < 7; i++)
558     {
559         move(11 + i, MAP_MAX_X + 3);
560         clrtoeol();
561     }
562 
563     /* display effect descriptions */
564     if (p->effects->len > 0)
565     {
566         const guint available_space = COLS - MAP_MAX_X - 4;
567         char **efdescs = strv_new();
568 
569         /* collect effect descriptions */
570         for (guint i = 0; i < p->effects->len; i++)
571         {
572             effect *e = game_effect_get(nlarn, g_ptr_array_index(p->effects, i));
573 
574             if (effect_get_desc(e) == NULL)
575                 continue;
576 
577             char *desc = g_strdup(effect_get_desc(e));
578 
579             if (strlen(desc) > available_space)
580             {
581                 desc[available_space - 1] = '.';
582                 desc[available_space] = '\0';
583             }
584 
585             if ((e->type == ET_WALL_WALK || e->type == ET_LEVITATION)
586                     && e->turns < 6)
587             {
588                 /* fading effects */
589                 gchar *cdesc = g_strdup_printf("`lightred`%s`end`", desc);
590                 strv_append_unique(&efdescs, cdesc);
591                 g_free(cdesc);
592             }
593             else if (e->type > ET_LEVITATION)
594             {
595                 /* negative effects */
596                 gchar *cdesc = g_strdup_printf("`lightmagenta`%s`end`", desc);
597                 strv_append_unique(&efdescs, cdesc);
598                 g_free(cdesc);
599             }
600             else
601             {
602                 /* other effects */
603                 strv_append_unique(&efdescs, desc);
604             }
605 
606             g_free(desc);
607         }
608 
609         /* display effect descriptions */
610         int currattr = COLOURLESS;
611         for (guint i = 0; i < g_strv_length(efdescs); i++)
612         {
613             currattr = mvwcprintw(stdscr, LIGHTCYAN, currattr,
614                     display_default_colset, 11 + i, MAP_MAX_X + 3, efdescs[i]);
615 
616         }
617 
618         g_strfreev(efdescs);
619     }
620 
621     /* *** MESSAGES *** */
622     /* number of lines which can be displayed */
623     guint y = LINES > 20 ? LINES - 20 : 0;
624 
625     /* storage for the game time of messages */
626     guint ttime[y];
627 
628     /* hold original length of text */
629     guint x = 1;
630 
631     /* line counter */
632     guint i = 0;
633 
634     /* storage for formatted messages */
635     GPtrArray *text = NULL;
636 
637     /* if log contains buffered messaged, display them */
638     if (log_buffer(nlarn->log))
639     {
640         text = text_wrap(log_buffer(nlarn->log), COLS, 2);
641         for (x = 1; x <= (unsigned)min(text->len, y); x++)
642             ttime[x - 1] = game_turn(nlarn);
643     }
644 
645     /* retrieve game log and reformat messages to window width */
646     while (((text == NULL) || (text->len < y)) && (log_length(nlarn->log) > i))
647     {
648         message_log_entry *le = log_get_entry(nlarn->log,
649                                               log_length(nlarn->log) - 1 - i);
650 
651         if (text == NULL)
652             text = text_wrap(le->message, COLS, 2);
653         else
654             text = text_append(text, text_wrap(le->message, COLS, 2));
655 
656         /* store game time for associated text line */
657         while ((x <= text->len) && (x <= y))
658         {
659             ttime[x - 1] = le->gtime;
660             x++;
661         }
662 
663         i++;
664     }
665 
666     /* ensure consistent colours for messages spanning multiple lines */
667     int currattr = COLOURLESS;
668     for (y = 20, i = 0; (y < (unsigned)LINES) && (i < text->len); i++, y++)
669     {
670         /* default colour for the line */
671         int def_attrs = (i == 0 && ttime[i] > game_turn(nlarn) - 5)
672             ? WHITE
673             : DARKGRAY;
674 
675         /* reset current color when switching log entries */
676         if (i > 0 && ttime[i - 1] != ttime[i])
677             currattr = COLOURLESS;
678 
679         currattr = mvwcprintw(stdscr, def_attrs, currattr,
680             display_default_colset, y, 0, g_ptr_array_index(text, i));
681     }
682 
683     text_destroy(text);
684 
685     display_draw();
686 }
687 
display_shutdown()688 void display_shutdown()
689 {
690     /* only terminate curses mode when the display has been initialised */
691     if (display_initialised)
692     {
693         /* end curses mode */
694         endwin();
695 
696         /* update display initialisation status */
697         display_initialised = FALSE;
698     }
699 }
700 
display_available()701 gboolean display_available()
702 {
703     return display_initialised;
704 }
705 
display_draw()706 void display_draw()
707 {
708 #ifdef PDCURSES
709     /* I have no idea why, but panels are not redrawn when
710      * using PDCurses without calling touchwin for it. */
711     GList *iterator = windows;
712     while (iterator) {
713         display_window *win = (display_window *)iterator->data;
714         touchwin(win->window);
715         iterator = iterator->next;
716     }
717 #endif
718 
719     /* mark stdscr and all panels for redraw */
720     update_panels();
721 
722     /* finally commit all the prepared updates */
723     doupdate();
724 }
725 
item_sort_normal(gconstpointer a,gconstpointer b,gpointer data)726 static int item_sort_normal(gconstpointer a, gconstpointer b, gpointer data)
727 {
728     return item_sort(a, b, data, FALSE);
729 }
730 
item_sort_shop(gconstpointer a,gconstpointer b,gpointer data)731 static int item_sort_shop(gconstpointer a, gconstpointer b, gpointer data)
732 {
733     return item_sort(a, b, data, TRUE);
734 }
735 
display_inventory(const char * title,player * p,inventory ** inv,GPtrArray * callbacks,gboolean show_price,gboolean show_weight,gboolean show_account,int (* ifilter)(item *))736 item *display_inventory(const char *title, player *p, inventory **inv,
737                         GPtrArray *callbacks, gboolean show_price,
738                         gboolean show_weight, gboolean show_account,
739                         int (*ifilter)(item *))
740 {
741     /* the inventory window */
742     display_window *iwin = NULL;
743     /* the item description pop-up */
744     display_window *ipop = NULL;
745 
746     /* the dialogue width */
747     const guint width = COLS - 4;
748 
749     guint len_orig, len_curr;
750     gboolean redraw = FALSE;
751 
752     /* the window title used for shops */
753     char *stitle = NULL;
754 
755     gboolean keep_running = TRUE;
756     int key;
757 
758     /* string array used to assemble the window caption
759        from the callback descriptions */
760     char **captions;
761 
762     /* offset to element position (when displaying more than maxvis items) */
763     guint offset = 0;
764 
765     /* position of currently selected item */
766     guint curr = 1;
767 
768     item *it;
769 
770     /* curses attributes */
771     int attrs;
772 
773     g_assert(p != NULL && inv != NULL);
774 
775     /* sort inventory by item type */
776     if (show_price)
777         inv_sort(*inv, (GCompareDataFunc)item_sort_shop, (gpointer)p);
778     else
779         inv_sort(*inv, (GCompareDataFunc)item_sort_normal, (gpointer)p);
780 
781     /* store inventory length */
782     len_orig = len_curr = inv_length_filtered(*inv, ifilter);
783 
784     /* main loop */
785     do
786     {
787         /* calculate the dialogue height */
788         guint height = min((LINES - 10), len_curr + 2);
789 
790         /* calculate how many items can be displayed at a time */
791         guint maxvis = min(len_curr, height - 2);
792 
793         /* fix selected item */
794         if (curr > len_curr)
795             curr = len_curr;
796 
797         /* rebuild screen if needed */
798         if (iwin != NULL && redraw)
799         {
800             display_window_destroy(iwin);
801             iwin = NULL;
802 
803             display_paint_screen(p);
804             redraw = FALSE;
805 
806             /* check for inventory modifications */
807             if (len_orig > len_curr)
808             {
809                 /* inventory length is smaller than before */
810                 /* if on the last page, reduce offset */
811                 if ((offset > 0) && ((offset + maxvis) > len_curr))
812                     offset--;
813 
814                 /* remember current length */
815                 len_orig = len_curr;
816             }
817             else if (len_curr > len_orig)
818             {
819                 /* inventory has grown - sort inventory again */
820                 if (show_price)
821                     inv_sort(*inv, (GCompareDataFunc)item_sort_shop, (gpointer)p);
822                 else
823                     inv_sort(*inv, (GCompareDataFunc)item_sort_normal, (gpointer)p);
824             }
825         }
826 
827         if (!iwin)
828         {
829             iwin = display_window_new(2, 2, width, height, title);
830         }
831 
832         /* draw all items */
833         for (guint pos = 1; pos <= (unsigned)min(len_curr, maxvis); pos++)
834         {
835             it = inv_get_filtered(*inv, (pos - 1) + offset, ifilter);
836 
837             gboolean item_equipped = FALSE;
838 
839             if (!show_price)
840             {
841                 /* shop items are definitely not equipped */
842                 item_equipped = player_item_is_equipped(p, it);
843             }
844 
845             /* currently selected */
846             if (curr == pos)
847             {
848                 if (item_equipped)
849                     attrs = COLOR_PAIR(DCP_BLACK_WHITE);
850                 else
851                     attrs = COLOR_PAIR(DCP_RED_WHITE);
852             }
853             else if (item_equipped)
854                 attrs = COLOR_PAIR(DCP_WHITE_RED) | A_BOLD;
855             else
856                 attrs = COLOR_PAIR(DCP_WHITE_RED);
857 
858             if (show_price)
859             {
860                 /* inside shop */
861                 gchar *item_desc = item_describe(it, TRUE, FALSE, FALSE);
862                 mvwaprintw(iwin->window, pos, 1, attrs, " %-*s %5d gold ",
863                           width - 15, item_desc, item_price(it));
864 
865                 g_free(item_desc);
866             }
867             else
868             {
869                 gchar *item_desc = item_describe(it, player_item_known(p, it), FALSE, FALSE);
870                 mvwaprintw(iwin->window, pos, 1, attrs, " %-*s %c ",
871                           width - 6, item_desc,
872                           player_item_is_equipped(p, it) ? '*' : ' ');
873 
874                 g_free(item_desc);
875             }
876         }
877 
878         /* prepare the window title */
879         if (show_account)
880         {
881             /* show the balance of the bank account */
882             stitle = g_strdup_printf("%s - %d gold on bank account",
883                                      title, p->bank_account);
884 
885             display_window_update_title(iwin, stitle);
886             g_free(stitle);
887         }
888         else if (show_weight)
889         {
890             /* show the weight of the inventory */
891             stitle = g_strdup_printf("%s - %s of %s carried",
892                                      title, player_inv_weight(p),
893                                      player_can_carry(p));
894 
895             display_window_update_title(iwin, stitle);
896             g_free(stitle);
897         }
898 
899         /* get the currently selected item */
900         it = inv_get_filtered(*inv, curr + offset - 1, ifilter);
901 
902         /* prepare the string array which will hold all the captions */
903         captions = strv_new();
904 
905         /* assemble window caption (if callbacks have been defined) */
906         for (guint cb_nr = 0; callbacks != NULL && cb_nr < callbacks->len; cb_nr++)
907         {
908             display_inv_callback *cb = g_ptr_array_index(callbacks, cb_nr);
909 
910             /* check if callback is appropriate for this item */
911             /* if no check function is set, always display item */
912             if ((cb->checkfun == NULL) || cb->checkfun(p, it))
913             {
914                 cb->active = TRUE;
915                 strv_append(&captions, cb->description);
916             }
917             else
918             {
919                 /* it isn't */
920                 cb->active = FALSE;
921             }
922         }
923 
924         /* refresh the item description pop-up */
925         if (ipop != NULL)
926             display_window_destroy(ipop);
927 
928         ipop = display_item_details(iwin->x1, iwin->y1 + iwin->height,
929                                     iwin->width, it, p, show_price);
930 
931         if (g_strv_length(captions) > 0)
932         {
933             /* append "(?) help" to trigger the help pop-up */
934             strv_append(&captions, "(?) help");
935 
936             /* update the window's caption with the assembled array of captions */
937             display_window_update_caption(iwin, g_strjoinv(" ", captions));
938         }
939         else
940         {
941             /* reset the window caption */
942             display_window_update_caption(iwin, NULL);
943         }
944 
945         /* free the array of caption strings */
946         g_strfreev(captions);
947 
948         display_window_update_arrow_up(iwin, offset > 0);
949         display_window_update_arrow_down(iwin, (offset + maxvis) < len_curr);
950 
951         wrefresh(iwin->window);
952 
953         switch (key = display_getch(iwin->window))
954         {
955 
956         case '7':
957         case KEY_HOME:
958         case KEY_A1:
959 
960             curr = 1;
961             offset = 0;
962 
963             break;
964 
965         case '9':
966         case KEY_PPAGE:
967         case KEY_A3:
968         case 21: /* ^U */
969 
970             if ((curr == maxvis) || offset == 0)
971                 curr = 1;
972             else
973                 offset = (offset > maxvis) ? (offset - maxvis) : 0;
974 
975             break;
976 
977         case 'k':
978         case '8':
979         case KEY_UP:
980 #ifdef KEY_A2
981         case KEY_A2:
982 #endif
983 
984             if (curr > 1)
985                 curr--;
986 
987             else if ((curr == 1) && (offset > 0))
988                 offset--;
989 
990             break;
991 
992         case 'j':
993         case '2':
994         case KEY_DOWN:
995 #ifdef KEY_C2
996         case KEY_C2:
997 #endif
998             if ((curr + offset) < len_curr)
999             {
1000                 if (curr == maxvis)
1001                     offset++;
1002                 else
1003                     curr++;
1004             }
1005 
1006             break;
1007 
1008         case '3':
1009         case KEY_NPAGE:
1010         case KEY_C3:
1011         case 4: /* ^D */
1012 
1013             if (curr == 1)
1014             {
1015                 curr = maxvis;
1016             }
1017             else
1018             {
1019                 offset = offset + maxvis;
1020 
1021                 if ((offset + maxvis) >= len_curr)
1022                 {
1023                     curr = min(len_curr, maxvis);
1024                     offset = len_curr - curr;
1025                 }
1026             }
1027             break;
1028 
1029         case '1':
1030         case KEY_END:
1031         case KEY_C1:
1032 
1033             if (len_curr > maxvis)
1034             {
1035                 curr = maxvis;
1036                 offset = len_curr - maxvis;
1037             }
1038             else
1039             {
1040                 curr = len_curr;
1041             }
1042             break;
1043 
1044         case KEY_ESC:
1045             keep_running = FALSE;
1046             break;
1047 
1048         case '?':
1049             display_inventory_help(callbacks);
1050             break;
1051 
1052         case KEY_LF:
1053         case KEY_CR:
1054 #ifdef PADENTER
1055         case PADENTER:
1056 #endif
1057         case KEY_ENTER:
1058             if (callbacks == NULL)
1059             {
1060                 /* if no callbacks have been defined, enter selects item */
1061                 keep_running = FALSE;
1062             }
1063             break;
1064 
1065         default:
1066             /* check callback function keys (if defined) */
1067             for (guint cb_nr = 0; callbacks != NULL && cb_nr < callbacks->len; cb_nr++)
1068             {
1069                 display_inv_callback *cb = g_ptr_array_index(callbacks, cb_nr);
1070 
1071                 if ((cb->key == key) && cb->active)
1072                 {
1073                     /* trigger callback */
1074                     cb->function(p, cb->inv, inv_get_filtered(*inv, curr + offset - 1, ifilter));
1075 
1076                     redraw = TRUE;
1077 
1078                     /* don't check other callback functions */
1079                     break;
1080                 }
1081             }
1082         };
1083 
1084         len_curr = inv_length_filtered(*inv, ifilter);
1085     }
1086     while (keep_running && (len_curr > 0)); /* ESC pressed or empty inventory*/
1087 
1088     display_window_destroy(ipop);
1089     display_window_destroy(iwin);
1090 
1091     if ((callbacks == NULL) && (key != KEY_ESC))
1092     {
1093         /* return selected item if no callbacks have been provided */
1094         return inv_get_filtered(*inv, offset + curr - 1, ifilter);
1095     }
1096     else
1097     {
1098         return NULL;
1099     }
1100 }
1101 
display_inv_callbacks_clean(GPtrArray * callbacks)1102 void display_inv_callbacks_clean(GPtrArray *callbacks)
1103 {
1104     if (!callbacks) return;
1105 
1106     while (callbacks->len > 0)
1107     {
1108         g_free(g_ptr_array_remove_index_fast(callbacks, callbacks->len - 1));
1109     }
1110 
1111     g_ptr_array_free(callbacks, TRUE);
1112 }
1113 
display_config_autopickup(gboolean settings[IT_MAX])1114 void display_config_autopickup(gboolean settings[IT_MAX])
1115 {
1116     int RUN = TRUE;
1117     int attrs; /* curses attributes */
1118 
1119     const int height = 13;
1120     const int width = 38;
1121 
1122     const int starty = (LINES - height) / 2;
1123     const int startx = (min(MAP_MAX_X, COLS) - width) / 2;
1124 
1125     display_window *cwin = display_window_new(startx, starty, width, height, "Configure auto pick-up");
1126 
1127     mvwaprintw(cwin->window, 1, 2, COLOR_PAIR(DCP_WHITE_RED), "Item types which will be picked up");
1128     mvwaprintw(cwin->window, 2, 2, COLOR_PAIR(DCP_WHITE_RED), "automatically are shown inverted. ");
1129 
1130     mvwaprintw(cwin->window, 4,  6, COLOR_PAIR(DCP_WHITE_RED), "amulets");
1131     mvwaprintw(cwin->window, 5,  6, COLOR_PAIR(DCP_WHITE_RED), "ammunition");
1132     mvwaprintw(cwin->window, 6,  6, COLOR_PAIR(DCP_WHITE_RED), "armour");
1133     mvwaprintw(cwin->window, 7,  6, COLOR_PAIR(DCP_WHITE_RED), "books");
1134     mvwaprintw(cwin->window, 8,  6, COLOR_PAIR(DCP_WHITE_RED), "containers");
1135     mvwaprintw(cwin->window, 9,  6, COLOR_PAIR(DCP_WHITE_RED), "gems");
1136     mvwaprintw(cwin->window, 4, 23, COLOR_PAIR(DCP_WHITE_RED), "money");
1137     mvwaprintw(cwin->window, 5, 23, COLOR_PAIR(DCP_WHITE_RED), "potions");
1138     mvwaprintw(cwin->window, 6, 23, COLOR_PAIR(DCP_WHITE_RED), "rings");
1139     mvwaprintw(cwin->window, 7, 23, COLOR_PAIR(DCP_WHITE_RED), "scrolls");
1140     mvwaprintw(cwin->window, 8, 23, COLOR_PAIR(DCP_WHITE_RED), "weapons");
1141 
1142     mvwaprintw(cwin->window, 11, 6, COLOR_PAIR(DCP_WHITE_RED), "Type a symbol to toggle.");
1143 
1144     do
1145     {
1146         int key; /* keyboard input */
1147 
1148         for (item_t it = 1; it < IT_MAX; it++)
1149         {
1150             if (settings[it])
1151                 attrs = COLOR_PAIR(DCP_RED_WHITE);
1152             else
1153                 attrs = COLOR_PAIR(DCP_WHITE_RED);
1154 
1155             /* x / y position of the glyph depends on the item type number */
1156             int xpos = it < 7 ? 4 : 21;
1157             int ypos = it < 7 ? 3 + it : it - 3;
1158             mvwaprintw(cwin->window, ypos, xpos, attrs, "%c", item_glyph(it));
1159         }
1160 
1161         wrefresh(cwin->window);
1162 
1163         switch (key = display_getch(cwin->window))
1164         {
1165         case KEY_LF:
1166         case KEY_CR:
1167 #ifdef PADENTER
1168         case PADENTER:
1169 #endif
1170         case KEY_ENTER:
1171         case KEY_ESC:
1172         case KEY_SPC:
1173             RUN = FALSE;
1174             break;
1175 
1176         default:
1177             if (!display_window_move(cwin, key))
1178             {
1179                 for (item_t it = 1; it < IT_MAX; it++)
1180                 {
1181                     if (item_glyph(it) == key)
1182                     {
1183                         settings[it] = !settings[it];
1184                         break;
1185                     }
1186                 }
1187             }
1188         }
1189     }
1190     while (RUN);
1191 
1192     display_window_destroy(cwin);
1193 }
1194 
display_spell_select(const char * title,player * p)1195 spell *display_spell_select(const char *title, player *p)
1196 {
1197     display_window *swin, *ipop = NULL;
1198     guint width, height;
1199     guint startx, starty;
1200     guint maxvis;
1201     int key; /* keyboard input */
1202     int RUN = TRUE;
1203 
1204     /* currently displayed spell; return value */
1205     spell *sp;
1206 
1207     /* offset to element position (when displaying more than maxvis items) */
1208     guint offset = 0;
1209 
1210     /* currently selected item */
1211     guint curr = 1;
1212 
1213     /* curses attributes */
1214     int attrs;
1215 
1216     g_assert(p != NULL);
1217 
1218     /* buffer for spell code type ahead */
1219     char *code_buf = g_malloc0(sizeof(char) * 4);
1220 
1221     /* sort spell list  */
1222     g_ptr_array_sort(p->known_spells, &spell_sort);
1223 
1224     /* set height according to spell count */
1225     height = min((LINES - 7), (p->known_spells->len + 2));
1226     maxvis = min(p->known_spells->len, height - 2);
1227 
1228     width = 46;
1229     starty = (LINES - 3 - height) / 2;
1230     startx = (min(MAP_MAX_X, COLS) - width) / 2;
1231 
1232     swin = display_window_new(startx, starty, width, height, title);
1233 
1234     int prev_key = 0;
1235     do
1236     {
1237         /* display spells */
1238         for (guint pos = 1; pos <= maxvis; pos++)
1239         {
1240             sp = g_ptr_array_index(p->known_spells, pos + offset - 1);
1241 
1242             if (curr == pos) attrs = COLOR_PAIR(DCP_RED_WHITE);
1243             else attrs = COLOR_PAIR(DCP_WHITE_RED);
1244 
1245             mvwaprintw(swin->window, pos, 1, attrs,
1246                       " %3s - %-23s (Level %d) %2d ",
1247                       spell_code(sp),
1248                       spell_name(sp),
1249                       spell_level(sp),
1250                       sp->knowledge);
1251         }
1252 
1253         /* display up / down markers */
1254         display_window_update_arrow_up(swin, (offset > 0));
1255         display_window_update_arrow_down(swin, ((offset + maxvis) < p->known_spells->len));
1256 
1257         /* construct the window caption: display type ahead keys */
1258         gchar *caption = g_strdup_printf("%s%s%s",
1259                 (strlen(code_buf) ? "[" : ""),
1260                 code_buf,
1261                 (strlen(code_buf) ? "]" : ""));
1262 
1263         display_window_update_caption(swin, caption);
1264 
1265         /* store currently highlighted spell */
1266         sp = g_ptr_array_index(p->known_spells, curr + offset - 1);
1267 
1268         /* refresh the spell description pop-up */
1269         if (ipop != NULL)
1270             display_window_destroy(ipop);
1271 
1272         gchar *spdesc = spell_desc_by_id(sp->id);;
1273         ipop = display_popup(swin->x1, swin->y1 + swin->height, width,
1274                 spell_name(sp), spdesc, 0);
1275         g_free(spdesc);
1276 
1277         switch ((key = display_getch(swin->window)))
1278         {
1279         case '7':
1280         case KEY_HOME:
1281         case KEY_A1:
1282 
1283             curr = 1;
1284             offset = 0;
1285             code_buf[0] = '\0';
1286 
1287             break;
1288 
1289         case '9':
1290         case KEY_PPAGE:
1291         case KEY_A3:
1292         case 21: /* ^U */
1293 
1294             if ((curr == maxvis) || offset == 0)
1295                 curr = 1;
1296             else
1297                 offset = (offset > maxvis) ? (offset - maxvis) : 0;
1298 
1299             code_buf[0] = '\0';
1300             break;
1301 
1302         case 'k':
1303         case '8':
1304         case KEY_UP:
1305 #ifdef KEY_A2
1306         case KEY_A2:
1307 #endif
1308             if (key == 'k' && strlen(code_buf) > 0)
1309                 /* yuck, I *hate* gotos, but this one makes sense:
1310                    it allows to type e.g. 'ckl' */
1311                 goto mnemonics;
1312 
1313             if (curr > 1)
1314                 curr--;
1315 
1316             else if ((curr == 1) && (offset > 0))
1317                 offset--;
1318 
1319             code_buf[0] = '\0';
1320             break;
1321 
1322         case 'j':
1323         case '2':
1324         case KEY_DOWN:
1325 #ifdef KEY_C2
1326         case KEY_C2:
1327 #endif
1328             if (key == 'j' && strlen(code_buf) > 0)
1329                 /* see lame excuse above */
1330                 goto mnemonics;
1331 
1332             if ((curr + offset) < p->known_spells->len)
1333             {
1334                 if (curr == maxvis)
1335                     offset++;
1336                 else
1337                     curr++;
1338             }
1339 
1340             code_buf[0] = '\0';
1341             break;
1342 
1343         case '3':
1344         case KEY_NPAGE:
1345         case KEY_C3:
1346         case 4: /* ^D */
1347             if (curr == 1)
1348             {
1349                 curr = maxvis;
1350             }
1351             else
1352             {
1353                 offset = offset + maxvis;
1354 
1355                 if ((offset + maxvis) >= p->known_spells->len)
1356                 {
1357                     curr = min(p->known_spells->len, maxvis);
1358                     offset = p->known_spells->len - curr;
1359                 }
1360             }
1361 
1362             code_buf[0] = '\0';
1363             break;
1364 
1365         case '1':
1366         case KEY_END:
1367         case KEY_C1:
1368             if (p->known_spells->len > maxvis)
1369             {
1370                 curr = maxvis;
1371                 offset = p->known_spells->len - maxvis;
1372             }
1373             else
1374             {
1375                 curr = p->known_spells->len;
1376             }
1377 
1378             code_buf[0] = '\0';
1379             break;
1380 
1381         case KEY_ESC:
1382             RUN = FALSE;
1383             sp = NULL;
1384 
1385             break;
1386 
1387         case KEY_LF:
1388         case KEY_CR:
1389 #ifdef PADENTER
1390         case PADENTER:
1391 #endif
1392         case KEY_ENTER:
1393         case KEY_SPC:
1394             // It is much too easy to accidentally cast alter reality,
1395             // simply by pressing m + Enter. If the first key press in
1396             // the menu confirms this auto selected first spell, prompt.
1397             if (curr == 1 && prev_key == 0 && sp->id == SP_ALT)
1398             {
1399                 char prompt[60];
1400                 g_snprintf(prompt, 80, "Really cast %s?", spell_name(sp));
1401                 if (!display_get_yesno(prompt, NULL, NULL, NULL))
1402                     sp = NULL;
1403             }
1404             RUN = FALSE;
1405             break;
1406 
1407         case KEY_BS:
1408         case KEY_BACKSPACE:
1409             if (strlen(code_buf))
1410             {
1411                 code_buf[strlen(code_buf) - 1] = '\0';
1412             }
1413             else
1414             {
1415                 if (!beep())
1416                     flash();
1417             }
1418             break;
1419 
1420         default:
1421             /* check if the key is used for window placement */
1422             if (display_window_move(swin, key))
1423             {
1424                 break;
1425             }
1426 mnemonics:
1427             /* add key to spell code buffer */
1428             if ((key >= 'a') && (key <= 'z'))
1429             {
1430                 if (strlen(code_buf) < 3)
1431                 {
1432                     code_buf[strlen(code_buf)] = key;
1433                     /* search for match */
1434 
1435                     for (guint pos = 1; pos <= p->known_spells->len; pos++)
1436                     {
1437                         sp = g_ptr_array_index(p->known_spells, pos - 1);
1438 
1439                         if (g_str_has_prefix(spell_code(sp), code_buf))
1440                         {
1441                             /* match found - reposition selection */
1442                             if (pos > maxvis)
1443                             {
1444                                 offset = pos - maxvis;
1445                                 curr = pos - offset;
1446                             }
1447                             else
1448                             {
1449                                 offset = 0;
1450                                 curr = pos;
1451                             }
1452 
1453                             break;
1454                         }
1455                     }
1456 
1457                     /* if no match has been found remove key from buffer */
1458                     sp = g_ptr_array_index(p->known_spells, curr + offset - 1);
1459                     if (!g_str_has_prefix(spell_code(sp), code_buf))
1460                     {
1461                         code_buf[strlen(code_buf) - 1] = '\0';
1462 
1463                         if (!beep())
1464                             flash();
1465                     }
1466 
1467                 }
1468                 else
1469                 {
1470                     if (!beep())
1471                         flash();
1472                 }
1473             }
1474 
1475             break;
1476         }
1477         prev_key = key;
1478     }
1479     while (RUN);
1480 
1481     g_free(code_buf);
1482 
1483     display_window_destroy(swin);
1484     display_window_destroy(ipop);
1485 
1486     return sp;
1487 }
1488 
display_get_count(const char * caption,int value)1489 int display_get_count(const char *caption, int value)
1490 {
1491     display_window *mwin;
1492     int height, width, basewidth;
1493     int startx, starty;
1494 
1495     GPtrArray *text;
1496 
1497     /* toggle insert / overwrite mode; start with overwrite */
1498     int insert_mode = FALSE;
1499 
1500     /* user input */
1501     int key;
1502 
1503     /* cursor position */
1504     int ipos = 0;
1505 
1506     /* input as char */
1507     char ivalue[8] = { 0 };
1508 
1509     /* continue editing the number */
1510     int cont = TRUE;
1511 
1512     /* 8: input field width; 5: 3 spaces between border, caption + input field, 2 border */
1513     basewidth = 8 + 5;
1514 
1515     /* choose a sane dialogue width */
1516     width = min(basewidth + strlen(caption), COLS - 4);
1517 
1518     text = text_wrap(caption, width - basewidth, 0);
1519     height = 2 + text->len;
1520 
1521     starty = (LINES - height) / 2;
1522     startx = (COLS - width) / 2;
1523 
1524     mwin = display_window_new(startx, starty, width, height, NULL);
1525 
1526     for (guint line = 0; line < text->len; line++)
1527     {
1528         /* fill the box background */
1529         mvwaprintw(mwin->window, 1 + line, 1, COLOR_PAIR(DCP_WHITE_RED), "%-*s", width - 2, "");
1530         /* print text */
1531         mvwaprintw(mwin->window, 1 + line, 2, COLOR_PAIR(DCP_WHITE_RED), g_ptr_array_index(text, line));
1532     }
1533 
1534     /* prepare string to edit */
1535     g_snprintf(ivalue, 8, "%d", value);
1536 
1537     do
1538     {
1539         int ilen = strlen(ivalue); /* input length */
1540 
1541         /* make cursor visible and set according to insert mode */
1542         if (insert_mode)
1543             curs_set(1); /* underline */
1544         else
1545             curs_set(2); /* block */
1546 
1547         mvwaprintw(mwin->window,  mwin->height - 2, mwin->width - 10,
1548                   COLOR_PAIR(DCP_BLACK_WHITE), "%-8s", ivalue);
1549 
1550         wmove(mwin->window, mwin->height - 2, mwin->width - 10 + ipos);
1551         wrefresh(mwin->window);
1552 
1553         key = display_getch(mwin->window);
1554         switch (key)
1555         {
1556         case KEY_LEFT:
1557             if (ipos > 0)
1558                 ipos--;
1559             break;
1560 
1561         case KEY_RIGHT:
1562             if (ipos < ilen)
1563                 ipos++;
1564             break;
1565 
1566         case KEY_BS:
1567         case KEY_BACKSPACE:
1568             if ((ipos == ilen) && (ipos > 0))
1569             {
1570                 ivalue[ipos - 1] = '\0';
1571                 ipos--;
1572             }
1573             else if (ipos > 0)
1574             {
1575                 for (int tmp = ipos - 1; tmp < ilen; tmp++)
1576                     ivalue[tmp] = ivalue[tmp + 1];
1577 
1578                 ipos--;
1579             }
1580             break;
1581 
1582         case KEY_IC:
1583             /* toggle insert mode */
1584             insert_mode = !insert_mode;
1585             break;
1586 
1587         case KEY_DC:
1588             if (ipos < ilen)
1589             {
1590                 for (int tmp = ipos; tmp < ilen; tmp++)
1591                     ivalue[tmp] = ivalue[tmp + 1];
1592             }
1593             break;
1594 
1595         case KEY_END:
1596             ipos = ilen;
1597             break;
1598 
1599         case KEY_HOME:
1600             ipos = 0;
1601             break;
1602 
1603             /* special cases to speed up getting/dropping multiple items */
1604         case 'y': /* yes */
1605         case 'd': /* drop */
1606         case 'g': /* get */
1607         case 'p': /* put */
1608             /* reset value to original value */
1609             g_snprintf(ivalue, 8, "%d", value);
1610             cont = FALSE;
1611             break;
1612 
1613             /* special case to speed up aborting */
1614         case 'n': /* no */
1615             /* set value to 0 */
1616             g_snprintf(ivalue, 8, "%d", 0);
1617             cont = FALSE;
1618             break;
1619 
1620 
1621         case KEY_LF:
1622         case KEY_CR:
1623 #ifdef PADENTER
1624         case PADENTER:
1625 #endif
1626         case KEY_ENTER:
1627         case KEY_ESC:
1628             cont = FALSE;
1629             break;
1630 
1631         default:
1632             if ((key >= '0') && (key <= '9'))
1633             {
1634                 if (insert_mode)
1635                 {
1636                     /* insert */
1637                     if (strlen(ivalue) < 7)
1638                     {
1639                         int cppos = strlen(ivalue) + 1;
1640 
1641                         while (cppos >= ipos)
1642                         {
1643                             ivalue[cppos] = ivalue[cppos - 1];
1644                             cppos--;
1645                         }
1646 
1647                         ivalue[ipos] = key;
1648 
1649                         /* move position */
1650                         if (ipos < 7) ipos++;
1651                     }
1652                     else if (!beep()) flash();
1653                 }
1654                 else
1655                 {
1656                     /* overwrite */
1657                     if (ipos < 8)
1658                     {
1659                         ivalue[ipos] = key;
1660                         /* move position */
1661                         if (ipos < 7) ipos++;
1662                     }
1663 
1664                     else if (!beep()) flash();
1665                 }
1666             }
1667             else if (!display_window_move(mwin, key))
1668             {
1669                 if (!beep())
1670                     flash();
1671             }
1672 
1673             break;
1674         }
1675     }
1676     while (cont);
1677 
1678     /* hide cursor */
1679     curs_set(0);
1680 
1681     text_destroy(text);
1682     display_window_destroy(mwin);
1683 
1684     if (key == KEY_ESC)
1685         return 0;
1686 
1687     return atoi(ivalue);
1688 }
1689 
display_get_string(const char * title,const char * caption,const char * value,size_t max_len)1690 char *display_get_string(const char *title, const char *caption, const char *value, size_t max_len)
1691 {
1692     /* user input */
1693     int key;
1694 
1695     /* toggle insert / overwrite mode */
1696     int insert_mode = TRUE;
1697 
1698     /* cursor position */
1699     guint ipos = 0;
1700 
1701     /* text to be edited */
1702     GString *string = g_string_new(value);
1703 
1704     /* continue editing the number */
1705     int cont = TRUE;
1706 
1707     /* 3 spaces between border, caption + input field, 2 border */
1708     int basewidth = 5;
1709 
1710     /* choose a sane dialogue width */
1711     guint width;
1712     guint maxwidth = COLS - 4;
1713 
1714     /* prevent overly large input fields */
1715     if (max_len + basewidth > maxwidth)
1716     {
1717         max_len = maxwidth - basewidth;
1718     }
1719 
1720     if (basewidth + strlen(caption) + max_len > maxwidth)
1721     {
1722         if (strlen(caption) + basewidth > maxwidth)
1723         {
1724             width = maxwidth - basewidth;
1725         }
1726         else
1727         {
1728             width = basewidth + max(strlen(caption), max_len);
1729         }
1730     }
1731     else
1732     {
1733         /* input box fits on same line as caption */
1734         width = basewidth + strlen(caption) + max_len + 1;
1735     }
1736 
1737     GPtrArray *text = text_wrap(caption, width - basewidth, 0);
1738 
1739     /* determine if the input box fits on the last line */
1740     int box_start = 3 + strlen(g_ptr_array_index(text, text->len - 1));
1741     if (box_start + max_len + 2 > width)
1742         box_start = 2;
1743 
1744     int height = 2 + text->len; /* borders and text length */
1745     if (box_start == 2) height += 1; /* grow the dialogue if input box doesn't fit */
1746 
1747     int starty = (LINES - height) / 2;
1748     int startx = (COLS - width) / 2;
1749 
1750     display_window *mwin = display_window_new(startx, starty, width, height, title);
1751 
1752     for (guint line = 0; line < text->len; line++)
1753     {
1754         /* print text */
1755         mvwaprintw(mwin->window, 1 + line, 1, COLOR_PAIR(DCP_WHITE_RED),
1756                   " %-*s ", width - 4, g_ptr_array_index(text, line));
1757     }
1758 
1759     do
1760     {
1761         mvwaprintw(mwin->window,  mwin->height - 2, box_start,
1762                    COLOR_PAIR(DCP_BLACK_WHITE),
1763                   "%-*s", max_len + 1, string->str);
1764         wmove(mwin->window, mwin->height - 2, box_start + ipos);
1765 
1766         /* make cursor visible and set according to insert mode */
1767         if (insert_mode)
1768             curs_set(1); /* underline */
1769         else
1770             curs_set(2); /* block */
1771 
1772         wrefresh(mwin->window);
1773 
1774         key = display_getch(mwin->window);
1775         switch (key)
1776         {
1777         case KEY_LEFT:
1778             if (ipos > 0)
1779                 ipos--;
1780             break;
1781 
1782         case KEY_RIGHT:
1783             if (ipos < string->len)
1784                 ipos++;
1785             break;
1786 
1787         case KEY_BACKSPACE:
1788         case 8: /* backspace generates 8, not KEY_BACKSPACE on Windows */
1789             if (ipos > 0)
1790             {
1791                 g_string_erase(string, ipos - 1, 1);
1792                 ipos--;
1793             }
1794             break;
1795 
1796         case KEY_IC:
1797             /* toggle insert mode */
1798             insert_mode = !insert_mode;
1799             break;
1800 
1801         case KEY_DC:
1802             if (ipos < string->len)
1803             {
1804                 g_string_erase(string, ipos, 1);
1805             }
1806             break;
1807 
1808         case KEY_END:
1809             ipos = string->len;
1810             break;
1811 
1812         case KEY_HOME:
1813             ipos = 0;
1814             break;
1815 
1816         case KEY_LF:
1817         case KEY_CR:
1818 #ifdef PADENTER
1819         case PADENTER:
1820 #endif
1821         case KEY_ENTER:
1822         case KEY_ESC:
1823             cont = FALSE;
1824             break;
1825 
1826         default:
1827             /* handle window movement first */
1828             if (display_window_move(mwin, key))
1829                 break;
1830 
1831             /* filter out unwanted keys */
1832             if (!g_ascii_isalnum(key) && !g_ascii_ispunct(key) && (key != ' '))
1833             {
1834                 if (!beep()) flash();
1835                 break;
1836             }
1837 
1838             if (insert_mode)
1839             {
1840                 if (string->len < max_len)
1841                 {
1842                     g_string_insert_c(string, ipos, key);
1843                     if (ipos < max_len) ipos++;
1844                 }
1845                 else
1846                 {
1847                     if (!beep()) flash();
1848                 }
1849             }
1850             else
1851             {
1852                 if (ipos < string->len)
1853                 {
1854                     string->str[ipos] = key;
1855                     if (ipos < max_len) ipos++;
1856                 }
1857                 else if (string->len < max_len)
1858                 {
1859                     g_string_append_c(string, key);
1860                     ipos++;
1861                 }
1862                 else
1863                 {
1864                     if (!beep()) flash();
1865                 }
1866             }
1867             break;
1868         }
1869     }
1870     while (cont);
1871 
1872     /* hide cursor */
1873     curs_set(0);
1874 
1875     text_destroy(text);
1876     display_window_destroy(mwin);
1877 
1878     if (key == KEY_ESC || string->len == 0)
1879     {
1880         g_string_free(string, TRUE);
1881         return NULL;
1882     }
1883 
1884     return g_string_free(string, FALSE);
1885 }
1886 
display_get_yesno(const char * question,const char * title,const char * yes,const char * no)1887 int display_get_yesno(const char *question, const char *title, const char *yes, const char *no)
1888 {
1889     display_window *ywin;
1890     int RUN = TRUE;
1891     int selection = FALSE;
1892     guint line;
1893     GPtrArray *text;
1894 
1895     const guint padding = 1;
1896     const guint margin = 2;
1897 
1898     /* default values */
1899     if (!yes)
1900         yes = "Yes";
1901 
1902     if (!no)
1903         no = "No";
1904 
1905     /* determine text width, either defined by space available  for the window
1906      * or the length of question */
1907     guint text_width = min(COLS - 2 /* borders */
1908                      - (2 * margin) /* space outside window */
1909                      - (2 * padding), /* space between border and text */
1910                      strlen(question));
1911 
1912     /* broad windows are hard to read */
1913     if (text_width > 60)
1914         text_width = 60;
1915 
1916     /* wrap question according to width */
1917     text = text_wrap(question, text_width + 1, 0);
1918 
1919     /* Determine window width. Either defined by the length of the button
1920      * labels or width of the text */
1921     guint width = max(strlen(yes) + strlen(no)
1922                 + 2 /* borders */
1923                 + (4 * padding)  /* space between "button" border and label */
1924                 + margin, /* space between "buttons" */
1925                 text_width + 2 /* borders */ + (2 * padding));
1926 
1927     /* set startx and starty to something that makes sense */
1928     guint startx = (COLS / 2) - (width / 2);
1929     guint starty = (LINES / 2) - 4;
1930 
1931     ywin = display_window_new(startx, starty, width, text->len + 4, title);
1932 
1933     for (line = 0; line < text->len; line++)
1934     {
1935         mvwaprintw(ywin->window, line + 1, 1 + padding, COLOR_PAIR(DCP_WHITE_RED),
1936                    g_ptr_array_index(text, line));
1937     }
1938 
1939     text_destroy(text);
1940 
1941     do
1942     {
1943         /* paint */
1944         int attrs;
1945 
1946         if (selection) attrs = COLOR_PAIR(DCP_RED_WHITE);
1947         else           attrs = COLOR_PAIR(DCP_BLACK_WHITE) | A_BOLD;
1948 
1949         mvwaprintw(ywin->window, line + 2, margin, attrs,
1950                    "%*s%s%*s", padding, " ", yes, padding, " ");
1951 
1952         if (selection) attrs = COLOR_PAIR(DCP_BLACK_WHITE) | A_BOLD;
1953         else           attrs = COLOR_PAIR(DCP_RED_WHITE);
1954 
1955         mvwaprintw(ywin->window, line + 2,
1956                    width - margin - strlen(no) - (2 * padding),
1957                    attrs, "%*s%s%*s", padding, " ", no, padding, " ");
1958 
1959         wattroff(ywin->window, attrs);
1960         wrefresh(ywin->window);
1961 
1962         int key = tolower(display_getch(ywin->window)); /* input key buffer */
1963         // Special case for the movement keys and y/n.
1964         if (key != 'h' && key != 'l' && key != 'y' && key != 'n')
1965         {
1966             char input_yes = g_ascii_tolower(yes[0]);
1967             char input_no  = g_ascii_tolower(no[0]);
1968             // If both answers share the same initial letter, we're out of luck.
1969             if (input_yes != input_no || input_yes == 'n' || input_no == 'y')
1970             {
1971                 if (key == input_yes)
1972                     key = 'y';
1973                 else if (key == input_no)
1974                     key = 'n';
1975             }
1976         }
1977 
1978 
1979         /* wait for input */
1980         switch (key)
1981         {
1982         case KEY_ESC:
1983             selection = FALSE;
1984             /* fall-through */
1985 
1986         case KEY_LF:
1987         case KEY_CR:
1988 #ifdef PADENTER
1989         case PADENTER:
1990 #endif
1991         case KEY_ENTER:
1992         case KEY_SPC:
1993             RUN = FALSE;
1994             break;
1995 
1996         case 'h':
1997         case '4':
1998 #ifdef KEY_B1
1999         case KEY_B1:
2000 #endif
2001         case KEY_LEFT:
2002             if (!selection)
2003                 selection = TRUE;
2004             break;
2005 
2006         case 'l':
2007         case '6':
2008 #ifdef KEY_B3
2009         case KEY_B3:
2010 #endif
2011         case KEY_RIGHT:
2012             if (selection)
2013                 selection = FALSE;
2014             break;
2015 
2016             /* shortcuts */
2017         case 'y':
2018             selection = TRUE;
2019             RUN = FALSE;
2020             break;
2021 
2022         case 'n':
2023             selection = FALSE;
2024             RUN = FALSE;
2025             break;
2026 
2027         default:
2028             /* perhaps the window shall be moved */
2029             display_window_move(ywin, key);
2030         }
2031     }
2032     while (RUN);
2033 
2034     display_window_destroy(ywin);
2035 
2036     return selection;
2037 }
2038 
display_get_direction(const char * title,int * available)2039 direction display_get_direction(const char *title, int *available)
2040 {
2041     display_window *dwin;
2042 
2043     int *dirs = NULL;
2044     int startx, starty;
2045     int width;
2046     int RUN = TRUE;
2047 
2048     /* direction to return */
2049     direction dir = GD_NONE;
2050 
2051     if (!available)
2052     {
2053         dirs = g_malloc0(sizeof(int) * GD_MAX);
2054         for (int x = 0; x < GD_MAX; x++)
2055             dirs[x] = TRUE;
2056 
2057         dirs[GD_CURR] = FALSE;
2058     }
2059     else
2060     {
2061         dirs = available;
2062     }
2063 
2064 
2065     width = max(9, strlen(title) + 4);
2066 
2067     /* set startx and starty to something that makes sense */
2068     startx = (min(MAP_MAX_X, COLS) / 2) - (width / 2);
2069     starty = (LINES / 2) - 4;
2070 
2071     dwin = display_window_new(startx, starty, width, 9, title);
2072 
2073     mvwaprintw(dwin->window, 3, 3, COLOR_PAIR(DCP_WHITE_RED), "\\|/");
2074     mvwaprintw(dwin->window, 4, 3, COLOR_PAIR(DCP_WHITE_RED), "- -");
2075     mvwaprintw(dwin->window, 5, 3, COLOR_PAIR(DCP_WHITE_RED), "/|\\");
2076 
2077     for (int x = 0; x < 3; x++)
2078         for (int y = 0; y < 3; y++)
2079         {
2080             if (dirs[(x + 1) + (y * 3)])
2081             {
2082                 mvwaprintw(dwin->window,
2083                           6 - (y * 2), /* start in the last row, move up, skip one */
2084                           (x * 2) + 2, /* start in the second col, skip one */
2085                           COLOR_PAIR(DCP_YELLOW_RED),
2086                           "%d",
2087                           (x + 1) + (y * 3));
2088             }
2089         }
2090 
2091     if (!available)
2092         g_free(dirs);
2093 
2094     wrefresh(dwin->window);
2095 
2096     do
2097     {
2098         int key; /* input key buffer */
2099 
2100         switch ((key = display_getch(dwin->window)))
2101         {
2102 
2103         case 'h':
2104         case '4':
2105         case KEY_LEFT:
2106 #ifdef KEY_B1
2107         case KEY_B1:
2108 #endif
2109             if (dirs[GD_WEST]) dir = GD_WEST;
2110             break;
2111 
2112         case 'y':
2113         case '7':
2114         case KEY_HOME:
2115         case KEY_A1:
2116             if (dirs[GD_NW]) dir = GD_NW;
2117             break;
2118 
2119         case 'l':
2120         case '6':
2121         case KEY_RIGHT:
2122 #ifdef KEY_B3
2123         case KEY_B3:
2124 #endif
2125             if (dirs[GD_EAST]) dir = GD_EAST;
2126             break;
2127 
2128         case 'n':
2129         case '3':
2130         case KEY_NPAGE:
2131         case KEY_C3:
2132             if (dirs[GD_SE]) dir = GD_SE;
2133             break;
2134 
2135         case 'k':
2136         case '8':
2137         case KEY_UP:
2138 #ifdef KEY_A2
2139         case KEY_A2:
2140 #endif
2141             if (dirs[GD_NORTH]) dir = GD_NORTH;
2142             break;
2143 
2144         case 'u':
2145         case '9':
2146         case KEY_PPAGE:
2147         case KEY_A3:
2148             if (dirs[GD_NE]) dir = GD_NE;
2149             break;
2150 
2151         case 'j':
2152         case '2':
2153         case KEY_DOWN:
2154 #ifdef KEY_C2
2155         case KEY_C2:
2156 #endif
2157             if (dirs[GD_SOUTH]) dir = GD_SOUTH;
2158             break;
2159 
2160         case 'b':
2161         case '1':
2162         case KEY_END:
2163         case KEY_C1:
2164             if (dirs[GD_SW]) dir = GD_SW;
2165             break;
2166 
2167         case '.':
2168         case '5':
2169             if (dirs[GD_CURR]) dir = GD_CURR;
2170             break;
2171 
2172         case KEY_ESC:
2173             RUN = FALSE;
2174             break;
2175 
2176         default:
2177             /* perhaps the window shall be moved */
2178             display_window_move(dwin, key);
2179         }
2180     }
2181     while ((dir == GD_NONE) && RUN);
2182 
2183     if (!available)
2184     {
2185         g_free(dirs);
2186     }
2187 
2188     display_window_destroy(dwin);
2189 
2190     return dir;
2191 }
2192 
display_get_position(player * p,const char * message,gboolean ray,gboolean ball,guint radius,gboolean passable,gboolean visible)2193 position display_get_position(player *p,
2194                               const char *message,
2195                               gboolean ray,
2196                               gboolean ball,
2197                               guint radius,
2198                               gboolean passable,
2199                               gboolean visible)
2200 {
2201     /* start at player's position */
2202     position start = p->pos;
2203 
2204     /* the position chosen by the player */
2205     position cpos;
2206 
2207     /* if the player has recently targeted a monster.. */
2208     if (visible && p->ptarget != NULL)
2209     {
2210         monster *m = game_monster_get(nlarn, p->ptarget);
2211 
2212         /* ..check if it is still alive */
2213         if (m == NULL)
2214         {
2215             /* the monster has been eliminated */
2216             p->ptarget = NULL;
2217         }
2218         else
2219         {
2220             /* start at the monster's current position */
2221             start = monster_pos(m);
2222 
2223             /* don't use invisible position if unwanted */
2224             if (!fov_get(p->fv, start)) start = p->pos;
2225         }
2226     }
2227 
2228     /* check for visible opponents if no previous opponent has been found */
2229     if (visible && pos_identical(p->pos, start))
2230     {
2231         monster *m = fov_get_closest_monster(p->fv);
2232 
2233         /* found a visible monster -> use it as target */
2234         if (m != NULL)
2235             start = monster_pos(m);
2236     }
2237 
2238     cpos = display_get_new_position(p, start, message, ray, ball, FALSE,
2239                                     radius, passable, visible);
2240 
2241     if (pos_valid(cpos))
2242     {
2243         /* the player has chosen a valid position, check if there
2244          * is a monster at that position. */
2245         map *gmap = game_map(nlarn, Z(cpos));
2246         monster *m = map_get_monster_at(gmap, cpos);
2247 
2248         if (m != NULL)
2249         {
2250             /* there is a monster, store its oid for later use */
2251             p->ptarget = monster_oid(m);
2252         }
2253     }
2254 
2255     return cpos;
2256 }
2257 
display_get_new_position(player * p,position start,const char * message,gboolean ray,gboolean ball,gboolean travel,guint radius,gboolean passable,gboolean visible)2258 position display_get_new_position(player *p,
2259                                   position start,
2260                                   const char *message,
2261                                   gboolean ray,
2262                                   gboolean ball,
2263                                   gboolean travel,
2264                                   guint radius,
2265                                   gboolean passable,
2266                                   gboolean visible)
2267 {
2268     gboolean RUN = TRUE;
2269     direction dir = GD_NONE;
2270     position pos;
2271     int attrs; /* curses attributes */
2272     display_window *msgpop = NULL;
2273 
2274     /* list of visible monsters and the iterator for these */
2275     GList *mlist = NULL, *miter = NULL;
2276 
2277     /* variables for ray or ball painting */
2278     GList *r = NULL;      /* a ray position list */
2279     monster *m;
2280 
2281     /* check the starting position makes sense */
2282     if (pos_valid(start) && Z(start) == Z(p->pos))
2283         pos = start;
2284     else
2285         pos = p->pos;
2286 
2287     /* make shortcut to visible map */
2288     map *vmap = game_map(nlarn, Z(p->pos));
2289 
2290     /* get the list of visible monsters if looking for a visible position */
2291     if (visible)
2292         miter = mlist = fov_get_visible_monsters(p->fv);
2293 
2294     if (!visible)
2295         msgpop = display_popup(3, min(MAP_MAX_Y + 4, LINES - 4), 0, NULL, message, 0);
2296 
2297     /* if a starting position for a ray has been provided, check if it works */
2298     if (ray && !pos_identical(p->pos, start))
2299     {
2300         /* paint a ray to validate the starting position */
2301         r = map_ray(vmap, p->pos, pos);
2302 
2303         if (r == NULL)
2304         {
2305             /* it's not possible to draw a ray between the points
2306                -> leave everything as it has been */
2307             pos = p->pos;
2308         }
2309         else
2310         {
2311             /* position is valid */
2312             g_list_free(r);
2313             r = NULL;
2314         }
2315     } /* ray starting position validity check */
2316 
2317     do
2318     {
2319         /* refresh the pop-up content for every position change
2320            while looking for visible positions */
2321         if (visible)
2322         {
2323             /* clean old pop-up windows */
2324             if (msgpop != NULL)
2325                 display_window_destroy(msgpop);
2326 
2327             /* display message or description of selected position */
2328             if (pos_identical(pos, p->pos))
2329             {
2330                 msgpop = display_popup(3, min(MAP_MAX_Y + 4, LINES - 4), 0, NULL, message, 0);
2331             }
2332             else
2333             {
2334                 char *desc = map_pos_examine(pos);
2335                 msgpop = display_popup(3, min(MAP_MAX_Y + 4, LINES - 4), 0, message, desc, 0);
2336                 g_free(desc);
2337             }
2338         } /* visible */
2339 
2340         /* redraw screen to erase previous modifications */
2341         display_paint_screen(p);
2342 
2343         /* reset npos to an invalid position */
2344         position npos = pos_invalid;
2345 
2346         /* draw a ray if the starting position is not the player's position */
2347         if (ray && !pos_identical(pos, p->pos))
2348         {
2349             r = map_ray(vmap, p->pos, pos);
2350 
2351             if (r == NULL)
2352             {
2353                 /* It wasn't possible to paint a ray to the target position.
2354                    Revert to the player's position.*/
2355                 pos = p->pos;
2356             }
2357         }
2358 
2359         if (ray && (r != NULL))
2360         {
2361             /* draw a line between source and target if told to */
2362             monster *target = map_get_monster_at(vmap, pos);
2363 
2364             if (target && monster_in_sight(target)) attrs = LIGHTRED;
2365             else                                    attrs = LIGHTCYAN;
2366 
2367             attron(attrs);
2368             GList *iter = r;
2369 
2370             do
2371             {
2372                 position tpos;
2373                 pos_val(tpos) = GPOINTER_TO_UINT(iter->data);
2374 
2375                 /* skip the player's position */
2376                 if (pos_identical(p->pos, tpos))
2377                     continue;
2378 
2379                 if (target && pos_identical(monster_pos(target), tpos)
2380                     && monster_in_sight(target))
2381                 {
2382                     /* ray is targeted at a visible monster */
2383                     mvaaddch(Y(tpos), X(tpos), attrs, monster_glyph(target));
2384                 }
2385                 else if ((m = map_get_monster_at(vmap, tpos))
2386                          && monster_in_sight(m))
2387                 {
2388                     /* ray sweeps over a visible monster */
2389                     mvaaddch(Y(tpos), X(tpos), attrs, monster_glyph(m));
2390                 }
2391                 else
2392                 {
2393                     /* a position with no or an invisible monster on it */
2394                     mvaaddch(Y(tpos), X(tpos), attrs, '*');
2395                 }
2396             } while ((iter = iter->next));
2397 
2398             g_list_free(r);
2399             r = NULL;
2400         }
2401         else if (ball && radius)
2402         {
2403             /* paint a ball if told to */
2404             area *obstacles = map_get_obstacles(vmap, pos, radius, FALSE);
2405             area *b = area_new_circle_flooded(pos, radius, obstacles);
2406             position cursor = pos;
2407 
2408             for (Y(cursor) = b->start_y; Y(cursor) < b->start_y + b->size_y; Y(cursor)++)
2409             {
2410                 for (X(cursor) = b->start_x; X(cursor) < b->start_x + b->size_x; X(cursor)++)
2411                 {
2412                     if (area_pos_get(b, cursor))
2413                     {
2414                         move(Y(cursor), X(cursor));
2415 
2416                         if ((m = map_get_monster_at(vmap, cursor)) && monster_in_sight(m))
2417                         {
2418                             aaddch(monster_color(m) == RED ? LIGHTRED : RED, monster_glyph(m));
2419                         }
2420                         else if (pos_identical(p->pos, cursor))
2421                         {
2422                             aaddch(LIGHTRED, '@');
2423                         }
2424                         else
2425                         {
2426                             aaddch(LIGHTCYAN, '*');
2427                         }
2428                     }
2429                 }
2430             }
2431             area_destroy(b);
2432         }
2433         else
2434         {
2435             /* show the position of the cursor by inverting the attributes */
2436             (void)mvwchgat(stdscr, Y(pos), X(pos), 1, A_BOLD | A_STANDOUT, DCP_WHITE_BLACK, NULL);
2437         }
2438 
2439         /* wait for input */
2440         const int ch = display_getch(NULL);
2441         switch (ch)
2442         {
2443             /* abort */
2444         case KEY_ESC:
2445             pos = pos_invalid;
2446             RUN = FALSE;
2447             break;
2448 
2449             /* finish */
2450         case KEY_LF:
2451         case KEY_CR:
2452 #ifdef PADENTER
2453         case PADENTER:
2454 #endif
2455         case KEY_ENTER:
2456             RUN = FALSE;
2457             /* if a passable position has been requested check if it
2458                actually is passable. Only known positions are allowed. */
2459             if (passable
2460                 && (!(player_memory_of(nlarn->p, pos).type > LT_NONE
2461                       || game_wizardmode(nlarn))
2462                     || !map_pos_passable(vmap, pos)))
2463             {
2464                 if (!beep()) flash();
2465                 RUN = TRUE;
2466             }
2467             break;
2468 
2469             /* jump to next visible monster */
2470         case KEY_SPC:
2471             if ((mlist == NULL) && visible)
2472             {
2473                 flash();
2474             }
2475             else if (mlist != NULL)
2476             {
2477                 /* jump to the next list entry or to the start of
2478                    the list if the end has been reached */
2479                 if ((miter = miter->next) == NULL)
2480                     miter = mlist;
2481 
2482                 /* get the currently selected monster */
2483                 m = (monster *)miter->data;
2484 
2485                 /* jump to the selected monster */
2486                 npos = monster_pos(m);
2487             }
2488             break;
2489 
2490             /* move cursor */
2491         case 'h':
2492         case '4':
2493         case KEY_LEFT:
2494 #ifdef KEY_B1
2495         case KEY_B1:
2496 #endif
2497             dir = GD_WEST;
2498             break;
2499 
2500         case 'y':
2501         case '7':
2502         case KEY_HOME:
2503         case KEY_A1:
2504             dir = GD_NW;
2505             break;
2506 
2507         case 'l':
2508         case '6':
2509         case KEY_RIGHT:
2510 #ifdef KEY_B3
2511         case KEY_B3:
2512 #endif
2513             dir = GD_EAST;
2514             break;
2515 
2516         case 'n':
2517         case '3':
2518         case KEY_NPAGE:
2519         case KEY_C3:
2520             dir = GD_SE;
2521             break;
2522 
2523         case 'k':
2524         case '8':
2525         case KEY_UP:
2526 #ifdef KEY_A2
2527         case KEY_A2:
2528 #endif
2529             dir = GD_NORTH;
2530             break;
2531 
2532         case 'u':
2533         case '9':
2534         case KEY_PPAGE:
2535         case KEY_A3:
2536             dir = GD_NE;
2537             break;
2538 
2539         case 'j':
2540         case '2':
2541         case KEY_DOWN:
2542 #ifdef KEY_C2
2543         case KEY_C2:
2544 #endif
2545             dir = GD_SOUTH;
2546             break;
2547 
2548         case 'b':
2549         case '1':
2550         case KEY_END:
2551         case KEY_C1:
2552             dir = GD_SW;
2553             break;
2554 
2555         case '@':
2556             /* bring the cursor back to the player */
2557             npos = p->pos;
2558             break;
2559 
2560         default:
2561             /* if travelling, use sobject glyphs as shortcuts */
2562             if (travel)
2563             {
2564                 sobject_t sobj = LS_NONE;
2565                 for (int i = LS_NONE + 1; i < LS_MAX; i++)
2566                     if (so_get_glyph(i) == (char) ch)
2567                     {
2568                         sobj = i;
2569                         log_add_entry(nlarn->log, "Looking for '%c' (%s).\n",
2570                                       (char) ch, so_get_desc(sobj));
2571                         break;
2572                     }
2573 
2574                 /* found a matching glyph, now search the remembered level */
2575                 if (sobj != LS_NONE)
2576                 {
2577                     position origin = pos;
2578                     while (TRUE)
2579                     {
2580                         if (++X(pos) >= MAP_MAX_X)
2581                         {
2582                             X(pos) = 0;
2583                             if (++Y(pos) >= MAP_MAX_Y)
2584                                 Y(pos) = 0;
2585                         }
2586                         if (pos_identical(origin, pos))
2587                             break;
2588 
2589                         /* When in wizard mode, compare the selected glyph
2590                            with the real map, otherwise with the player's
2591                            memory of the map.
2592                            As multiple objects share the same glyph, it is
2593                            required to compare the glyph of the present
2594                            object with the glyph of the requested object. */
2595                         if ((game_wizardmode(nlarn)
2596                              && so_get_glyph(map_sobject_at(vmap, pos)) == (char) ch)
2597                             || (player_memory_of(nlarn->p, pos).sobject != LS_NONE
2598                                 && so_get_glyph(player_memory_of(nlarn->p, pos).sobject) == (char) ch))
2599                         {
2600                             break;
2601                         }
2602                     }
2603                 }
2604             } /* if (travel) */
2605             break;
2606         }
2607 
2608         /* get new position if cursor has been move */
2609         if (dir)
2610         {
2611             npos = pos_move(pos, dir);
2612             dir = GD_NONE;
2613         }
2614 
2615         /* don't want to deal with invalid positions */
2616         if (pos_valid(npos))
2617         {
2618             /* don't use invisible positions */
2619             if (visible && !fov_get(p->fv, npos))
2620                 npos = pos;
2621 
2622             if (ray)
2623             {
2624                 /* paint a ray to validate the new position */
2625                 r = map_ray(vmap, p->pos, npos);
2626 
2627                 if (r == NULL)
2628                 {
2629                     /* it's not possible to draw a ray between the points
2630                        -> return to previous position */
2631                     npos = pos;
2632                 }
2633                 else
2634                 {
2635                     g_list_free(r);
2636                     r = NULL;
2637                 }
2638             }
2639 
2640             if (ball)
2641             {
2642                 /* check bounds of the ball */
2643                 if (!map_pos_passable(vmap, npos)) npos = pos;
2644             }
2645 
2646             /* new position is within bounds and visible */
2647             pos = npos;
2648         } /* if (pos_valid(npos) */
2649     }
2650     while (RUN);
2651 
2652     /* destroy list of visible monsters */
2653     if (mlist != NULL)
2654         g_list_free(mlist);
2655 
2656     /* destroy the message pop-up */
2657     display_window_destroy(msgpop);
2658 
2659     /* hide cursor */
2660     curs_set(0);
2661 
2662     return pos;
2663 }
2664 
display_show_history(message_log * log,const char * title)2665 void display_show_history(message_log *log, const char *title)
2666 {
2667     GString *text = g_string_new(NULL);
2668     char intrep[11] = { 0 }; /* string representation of the game time */
2669     int twidth; /* the number of characters of the current game time */
2670 
2671     /* determine the width of the current game turn */
2672     g_snprintf(intrep, 10, "%d", nlarn->gtime);
2673     twidth = strlen(intrep);
2674 
2675     /* assemble reversed game log */
2676     for (guint idx = log_length(log); idx > 0; idx--)
2677     {
2678         message_log_entry *le = log_get_entry(log, idx - 1);
2679         g_string_append_printf(text, "%*d: %s\n", twidth, le->gtime, le->message);
2680     }
2681 
2682     /* display the log */
2683     display_show_message(title, text->str, twidth + 2);
2684 
2685     /* free the assembled log */
2686     g_string_free(text, TRUE);
2687 }
2688 
display_show_message(const char * title,const char * message,int indent)2689 int display_show_message(const char *title, const char *message, int indent)
2690 {
2691     int key;
2692 
2693     /* Number of columns required for
2694          a) the window border and the text padding
2695          b) the margin around the window */
2696     const guint wred = 4;
2697 
2698     gboolean RUN = TRUE;
2699 
2700     /* default window width according to available screen space;
2701        wred/2 chars margin on each side */
2702     guint width = COLS - wred;
2703 
2704     /* wrap message according to width (minus border and padding) */
2705     GPtrArray *text = text_wrap(message, width - wred, indent);
2706 
2707     /* determine the length of longest text line */
2708     guint max_len = text_get_longest_line(text);
2709 
2710     /* shrink the window width if the default width is not required */
2711     if (max_len + wred < width)
2712         width = max_len + wred;
2713 
2714     /* set height according to message line count */
2715     guint height = min((LINES - 3), (text->len + 2));
2716 
2717     guint starty = (LINES - height) / 2;
2718     guint startx = (COLS - width) / 2;
2719     display_window *mwin = display_window_new(startx, starty, width, height, title);
2720     guint maxvis = min(text->len, height - 2);
2721     guint offset = 0;
2722 
2723     do
2724     {
2725         /* display the window content */
2726         int currattr = COLOURLESS;
2727 
2728         for (guint idx = 0; idx < maxvis; idx++)
2729         {
2730             currattr = mvwcprintw(mwin->window, DDC_LIGHTGRAY, currattr,
2731                     display_dialog_colset, idx + 1, 2, "%s",
2732                     g_ptr_array_index(text, idx + offset));
2733         }
2734 
2735         display_window_update_arrow_up(mwin, offset > 0);
2736         display_window_update_arrow_down(mwin, (offset + maxvis) < text->len);
2737 
2738         wrefresh(mwin->window);
2739 
2740         key = display_getch(mwin->window);
2741         switch (key)
2742         {
2743         case 'k':
2744         case '8':
2745         case KEY_UP:
2746 #ifdef KEY_A2
2747         case KEY_A2:
2748 #endif
2749             if (offset > 0)
2750                 offset--;
2751             break;
2752 
2753         case '9':
2754         case KEY_PPAGE:
2755         case KEY_A3:
2756         case 21: /* ^U */
2757             if (offset > maxvis + 1)
2758                 offset -= maxvis;
2759             else
2760                 offset = 0;
2761             break;
2762 
2763         case '7':
2764         case KEY_HOME:
2765         case KEY_A1:
2766             offset = 0;
2767             break;
2768 
2769         case 'j':
2770         case '2':
2771         case KEY_DOWN:
2772 #ifdef KEY_C2
2773         case KEY_C2:
2774 #endif
2775             if (text->len > (maxvis + offset))
2776                 offset++;
2777             break;
2778 
2779         case '3':
2780         case KEY_NPAGE:
2781         case KEY_C3:
2782         case 4: /* ^D */
2783             offset = min((offset + maxvis - 1), text->len - maxvis);
2784             break;
2785 
2786         case '1':
2787         case KEY_END:
2788         case KEY_C1:
2789             offset = text->len - maxvis;
2790             break;
2791 
2792         default:
2793             /* perhaps the window shall be moved */
2794             if (!display_window_move(mwin, key))
2795             {
2796                 /* some other key -> close window */
2797                 RUN = FALSE;
2798             }
2799         }
2800     }
2801     while (RUN);
2802 
2803     display_window_destroy(mwin);
2804     text_destroy(text);
2805 
2806     return key;
2807 }
2808 
display_popup(int x1,int y1,int width,const char * title,const char * msg,int indent)2809 display_window *display_popup(int x1, int y1, int width, const char *title, const char *msg, int indent)
2810 {
2811     display_window *win;
2812     GPtrArray *text;
2813     int height;
2814     const guint max_width = COLS - x1 - 1;
2815     const guint max_height = LINES - y1;
2816 
2817     if (width == 0)
2818     {
2819         guint maxlen;
2820 
2821         /* The title is padded by 6 additional characters */
2822         if ((title != NULL) && (strlen(title) + 6 > strlen(msg)))
2823             maxlen = strlen(title) + 6;
2824         else
2825             maxlen = strlen(msg);
2826 
2827         /* determine window width */
2828         if (maxlen > (max_width - 4))
2829             width = max_width - 4;
2830         else
2831             width = maxlen + 4;
2832     }
2833     else
2834     {
2835         /* width supplied. Sanity check */
2836         if ((unsigned)width > max_width)
2837             width = max_width;
2838     }
2839 
2840     text = text_wrap(msg, width - 4, indent);
2841     height = min(text->len + 2, max_height);
2842 
2843     win = display_window_new(x1, y1, width, height, title);
2844 
2845     /* display message */
2846     int currattr = COLOURLESS;
2847     for (guint idx = 0; idx < text->len; idx++)
2848     {
2849         currattr = mvwcprintw(win->window, DDC_WHITE, currattr,
2850                 display_dialog_colset, idx + 1, 1, " %-*s ",
2851                 width - 4, g_ptr_array_index(text, idx));
2852     }
2853 
2854     /* clean up */
2855     text_destroy(text);
2856 
2857     /* show the window */
2858     wrefresh(win->window);
2859 
2860     return win;
2861 }
2862 
display_window_destroy(display_window * dwin)2863 void display_window_destroy(display_window *dwin)
2864 {
2865     del_panel(dwin->panel);
2866     delwin(dwin->window);
2867 
2868     /* remove window from the list of windows */
2869     windows = g_list_remove(windows, dwin);
2870 
2871     /* free title as it is a clone */
2872     if (dwin->title != NULL) g_free(dwin->title);
2873 
2874     g_free(dwin);
2875 
2876     if (nlarn != NULL && nlarn->p != NULL)
2877     {
2878         /* repaint the screen if the game has been initialized */
2879         display_paint_screen(nlarn->p);
2880     }
2881     else
2882     {
2883         /* refresh the screen */
2884         display_draw();
2885     }
2886 }
2887 
display_windows_hide()2888 void display_windows_hide()
2889 {
2890     GList *iterator = windows;
2891 
2892     while (iterator)
2893     {
2894         display_window *win = (display_window *)iterator->data;
2895         hide_panel(win->panel);
2896         iterator = iterator->next;
2897     }
2898 }
2899 
display_windows_show()2900 void display_windows_show()
2901 {
2902     GList *iterator = windows;
2903 
2904     while (iterator)
2905     {
2906         display_window *win = (display_window *)iterator->data;
2907         show_panel(win->panel);
2908         iterator = iterator->next;
2909     }
2910 }
2911 
display_getch(WINDOW * win)2912 int display_getch(WINDOW *win) {
2913     int ch = wgetch(win ? win : stdscr);
2914 #ifdef SDLPDCURSES
2915         /* on SDL2 PDCurses, keys entered on the numeric keypad while num
2916            lock is enabled are returned twice. Hence we need to swallow
2917            the first one here. */
2918         if ((ch >= '1' && ch <= '9')
2919                 && (PDC_get_key_modifiers() & PDC_KEY_MODIFIER_NUMLOCK))
2920         {
2921             ch = wgetch(win ? win : stdscr);
2922         }
2923 #endif
2924     return ch;
2925 }
2926 
2927 
mvwcprintw(WINDOW * win,int defattr,int currattr,const display_colset * colset,int y,int x,const char * fmt,...)2928 static int mvwcprintw(WINDOW *win, int defattr, int currattr,
2929         const display_colset *colset, int y, int x, const char *fmt, ...)
2930 {
2931     va_list argp;
2932     gchar *msg;
2933     int attr;
2934 
2935     /* assemble the message */
2936     va_start(argp, fmt);
2937     msg = g_strdup_vprintf(fmt, argp);
2938     va_end(argp);
2939 
2940     /* move to the starting position */
2941     wmove(win, y, x);
2942 
2943     if (currattr != COLOURLESS)
2944         /* restore the previously used attribute */
2945         wattron(win, attr = currattr);
2946     else
2947         /* set the default attribute */
2948         wattron(win, attr = defattr);
2949 
2950     for (guint pos = 0; pos < strlen(msg); pos++)
2951     {
2952         /* parse tags */
2953         if (msg[pos] == '`')
2954         {
2955             /* position of the tag terminator */
2956             int tpos;
2957 
2958             /* find position of tag terminator */
2959             for (tpos = pos + 1; msg[tpos] != '`'; tpos++);
2960 
2961             /* extract the tag value */
2962             char *tval = g_strndup(&msg[pos + 1], tpos - pos - 1);
2963 
2964             /* find colour value for the tag content */
2965             if (strcmp(tval, "end") == 0)
2966             {
2967                 wattroff(win, attr);
2968                 wattron(win, attr = defattr);
2969             }
2970             else
2971             {
2972                 wattroff(win, attr);
2973 
2974                 attr = display_get_colval(colset, tval);
2975                 /* dim bright colous when the default colour is dark */
2976                 if (defattr == DARKGRAY && attr > LIGHTGRAY)
2977                 {
2978                     attr ^= A_BOLD;
2979                 }
2980 
2981                 wattron(win, attr);
2982             }
2983 
2984             /* free temporary memory */
2985             g_free(tval);
2986 
2987             /* advance position over the end of the tag */
2988             pos = tpos + 1;
2989         }
2990 
2991         /* the end of the string might be reached in the tag parser */
2992         if (pos >= strlen(msg))
2993             break;
2994 
2995         /* print the message character wise */
2996         waddch(win, msg[pos]);
2997     }
2998 
2999     /* erase to the end of the line (spare borders of windows) */
3000     for (int pos = getcurx(win); pos < getmaxx(win) - (win == stdscr ? 0 : 1); pos++)
3001     {
3002         waaddch(win, attr, ' ');
3003     }
3004 
3005     /* clean assembled string */
3006     g_free(msg);
3007 
3008     /* return active attribute */
3009     return attr;
3010 }
3011 
display_inventory_help(GPtrArray * callbacks)3012 static void display_inventory_help(GPtrArray *callbacks)
3013 {
3014     size_t maxlen = 0;
3015     GString *help = g_string_new(NULL);
3016 
3017     if (callbacks == NULL || callbacks->len == 0)
3018     {
3019         /* no callbacks available => select item with ENTER */
3020         g_string_append(help, "Select the desired item with ENTER.\n"
3021                         "You may abort by pressing the escape key.");
3022     }
3023     else
3024     {
3025         /* determine the maximum length of the description */
3026         for (guint i = 0; i < callbacks->len; i++)
3027         {
3028             display_inv_callback *cb = g_ptr_array_index(callbacks, i);
3029 
3030             /* skip this callback function if it is not active */
3031             if (!cb->active) continue;
3032 
3033             size_t desclen = strlen(cb->description);
3034             if (desclen > maxlen)
3035                 maxlen = desclen;
3036         }
3037 
3038         if (maxlen == 0)
3039         {
3040             /* no active callbacks */
3041             g_string_append(help, "There are no options available for the selected item.");
3042         }
3043         else
3044         {
3045             for (guint i = 0; i < callbacks->len; i++)
3046             {
3047                 display_inv_callback *cb = g_ptr_array_index(callbacks, i);
3048 
3049                 if (cb->active && cb->helpmsg != NULL)
3050                 {
3051                     g_string_append_printf(help, "`lightgreen`%*s:`end` %s\n",
3052                                            (int)maxlen,
3053                                            cb->description,
3054                                            cb->helpmsg);
3055                 }
3056             }
3057         }
3058     }
3059 
3060     display_show_message("Help", help->str, maxlen + 2);
3061     g_string_free(help, TRUE);
3062 }
3063 
display_get_colval(const display_colset * colset,const char * name)3064 static int display_get_colval(const display_colset *colset, const char *name)
3065 {
3066     int colour = 0;
3067     int pos = 0;
3068 
3069     while (colset[pos].name != NULL)
3070     {
3071         if (strcmp(name, colset[pos].name) == 0)
3072         {
3073             /* colour found */
3074             colour = colset[pos].val;
3075             break;
3076         }
3077 
3078         pos++;
3079     }
3080 
3081     return colour;
3082 }
3083 
display_window_new(int x1,int y1,int width,int height,const char * title)3084 static display_window *display_window_new(int x1, int y1, int width,
3085                                           int height, const char *title)
3086 {
3087     display_window *dwin;
3088 
3089     dwin = g_malloc0(sizeof(display_window));
3090 
3091     dwin->x1 = x1;
3092     dwin->y1 = y1;
3093     dwin->width = width;
3094     dwin->height = height;
3095 
3096     dwin->window = newwin(dwin->height, dwin->width, dwin->y1, dwin->x1);
3097     keypad(dwin->window, TRUE);
3098 
3099     /* fill window background */
3100     for (int i = 1; i < height; i++) {
3101         mvwaprintw(dwin->window, i, 1, COLOR_PAIR(DCP_WHITE_RED),
3102                    "%*s", width - 2, "");
3103     }
3104 
3105     /* draw borders */
3106     wattron(dwin->window, COLOR_PAIR(DCP_BLUE_RED));
3107     box(dwin->window, 0, 0);
3108     wattroff(dwin->window, COLOR_PAIR(DCP_BLUE_RED));
3109 
3110     /* set the window title */
3111     display_window_update_title(dwin, title);
3112 
3113     /* create a panel for the window */
3114     dwin->panel = new_panel(dwin->window);
3115 
3116     /* add window to the list of opened windows */
3117     windows = g_list_append(windows, dwin);
3118 
3119     if (nlarn != NULL && nlarn->p != NULL)
3120     {
3121         /* repaint the screen if the game has been initialized */
3122         display_paint_screen(nlarn->p);
3123     }
3124     else
3125     {
3126         /* refresh panels */
3127         update_panels();
3128     }
3129 
3130     return dwin;
3131 }
3132 
display_window_move(display_window * dwin,int key)3133 static int display_window_move(display_window *dwin, int key)
3134 {
3135     gboolean need_refresh = TRUE;
3136 
3137     g_assert (dwin != NULL);
3138 
3139     switch (key)
3140     {
3141     case 0:
3142         /* The windows keys generate two key presses, of which the first
3143            is a zero. Flush the buffer or the second key code will confuse
3144            everything. This happens here as all dialogue call this function
3145            after everything else. */
3146         flushinp();
3147         break;
3148 
3149         /* ^left */
3150     case 541: /* NCurses - Linux */
3151     case 443: /* PDCurses - Windows */
3152         if (dwin->x1 > 0) dwin->x1--;
3153         break;
3154 
3155         /* ^right */
3156     case 556: /* NCurses - Linux */
3157     case 444: /* PDCurses - Windows */
3158         if (dwin->x1 < (COLS - dwin->width)) dwin->x1++;
3159         break;
3160 
3161         /* ^up */
3162     case 562: /* NCurses - Linux */
3163     case 480: /* PDCurses - Windows */
3164         if (dwin->y1 > 0) dwin->y1--;
3165         break;
3166 
3167         /* ^down */
3168     case 521: /* NCurses - Linux */
3169     case 481: /* PDCurses - Windows */
3170         if (dwin->y1 < (LINES - dwin->height)) dwin->y1++;
3171         break;
3172 
3173     default:
3174         need_refresh = FALSE;
3175     }
3176 
3177     if (need_refresh)
3178     {
3179         move_panel(dwin->panel, dwin->y1, dwin->x1);
3180         display_draw();
3181     }
3182 
3183     return need_refresh;
3184 }
3185 
display_window_update_title(display_window * dwin,const char * title)3186 static void display_window_update_title(display_window *dwin, const char *title)
3187 {
3188     g_assert (dwin != NULL && dwin->window != NULL);
3189 
3190     if (dwin->title)
3191     {
3192         /* free the previous title */
3193         g_free(dwin->title);
3194 
3195         /* repaint line to overwrite previous title */
3196         mvwahline(dwin->window, 0, 2, COLOR_PAIR(DCP_BLUE_RED),
3197                   ACS_HLINE, dwin->width - 7);
3198     }
3199 
3200     /* print the provided title */
3201     if (title && strlen(title))
3202     {
3203         /* copy the new title
3204          * the maximum length is determined by the window width
3205          * minus the space required for the left corner (3)
3206          * minus the space required for the right corner
3207          *       and the scroll marker (7)
3208          */
3209         dwin->title = g_strndup(title, dwin->width - 10);
3210 
3211         /* make sure the first letter of the window title is upper case */
3212         dwin->title[0] = g_ascii_toupper(dwin->title[0]);
3213 
3214         mvwaprintw(dwin->window, 0, 2, COLOR_PAIR(DCP_WHITE_RED), " %s ", dwin->title);
3215     }
3216 
3217     wrefresh(dwin->window);
3218 }
3219 
display_window_update_caption(display_window * dwin,char * caption)3220 static void display_window_update_caption(display_window *dwin, char *caption)
3221 {
3222     g_assert (dwin != NULL && dwin->window != NULL);
3223 
3224     /* repaint line to overwrite previous captions */
3225     mvwahline(dwin->window, dwin->height - 1, 3, COLOR_PAIR(DCP_BLUE_RED),
3226               ACS_HLINE, dwin->width - 7);
3227 
3228     /* print caption if caption is set */
3229     if (caption && strlen(caption))
3230     {
3231         mvwaprintw(dwin->window, dwin->height - 1, 3, COLOR_PAIR(DCP_WHITE_RED),
3232                    " %s ", caption);
3233     }
3234 
3235     if (caption)
3236     {
3237         /* free the provided caption */
3238         g_free(caption);
3239     }
3240 
3241     wrefresh(dwin->window);
3242 }
3243 
display_window_update_arrow_up(display_window * dwin,gboolean on)3244 static void display_window_update_arrow_up(display_window *dwin, gboolean on)
3245 {
3246     g_assert (dwin != NULL && dwin->window != NULL);
3247 
3248     if (on)
3249     {
3250         mvwaprintw(dwin->window, 0, dwin->width - 5,
3251                   COLOR_PAIR(DCP_WHITE_RED), " ^ ");
3252     }
3253     else
3254     {
3255         mvwahline(dwin->window, 0, dwin->width - 5, COLOR_PAIR(DCP_BLUE_RED),
3256                  ACS_HLINE, 3);
3257     }
3258 }
3259 
display_window_update_arrow_down(display_window * dwin,gboolean on)3260 static void display_window_update_arrow_down(display_window *dwin, gboolean on)
3261 {
3262     g_assert (dwin != NULL && dwin->window != NULL);
3263 
3264     if (on)
3265     {
3266         mvwaprintw(dwin->window, dwin->height - 1, dwin->width - 5,
3267                   COLOR_PAIR(DCP_WHITE_RED), " v ");
3268     }
3269     else
3270     {
3271         mvwahline(dwin->window, dwin->height - 1, dwin->width - 5,
3272                   COLOR_PAIR(DCP_BLUE_RED), ACS_HLINE, 3);
3273     }
3274 }
3275 
display_item_details(guint x1,guint y1,guint width,item * it,player * p,gboolean shop)3276 static display_window *display_item_details(guint x1, guint y1, guint width,
3277                                             item *it, player *p, gboolean shop)
3278 {
3279     /* the pop-up window created by display_popup */
3280     display_window *idpop;
3281 
3282     /* determine if the item is known or displayed in the shop */
3283     const gboolean known = shop | player_item_known(p, it);
3284 
3285     /* the detailed item description */
3286     char *msg = item_detailed_description(it, known, shop);
3287     idpop = display_popup(x1, y1, width, "Item details", msg, 0);
3288 
3289     /* tidy up */
3290     g_free(msg);
3291 
3292     return idpop;
3293 }
3294 
display_spheres_paint(sphere * s,player * p)3295 static void display_spheres_paint(sphere *s, player *p)
3296 {
3297     /* check if sphere is on current level */
3298     if (!(Z(s->pos) == Z(p->pos)))
3299         return;
3300 
3301     if (game_fullvis(nlarn) || fov_get(p->fv, s->pos))
3302     {
3303         mvaaddch(Y(s->pos), X(s->pos), MAGENTA, '0');
3304     }
3305 }
3306