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