]> icculus.org git repositories - btb/d2x.git/blob - main/console.c
Rename include/error.h to include/dxxerror.h
[btb/d2x.git] / main / console.c
1 /*
2  *  Code for controlling the console
3  *  Based on an early version of SDL_Console
4  *
5  *  Written By: Garrett Banuk <mongoose@mongeese.org>
6  *  Code Cleanup and heavily extended by: Clemens Wacha <reflex-2000@gmx.net>
7  *  Ported to use native Descent interfaces by: Bradley Bell <btb@icculus.org>
8  *
9  *  This is free, just be sure to give us credit when using it
10  *  in any of your programs.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #include <conf.h>
15 #endif
16
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdarg.h>
21 #ifndef _WIN32_WCE
22 #include <fcntl.h>
23 #endif
24 #include <ctype.h>
25 #include <unistd.h>
26
27 #include "inferno.h"
28 #include "u_mem.h"
29 #include "gr.h"
30 #include "key.h"
31 #include "timer.h"
32 #include "pstypes.h"
33 #include "dxxerror.h"
34 #include "cfile.h"
35
36
37 #ifndef __MSDOS__
38 int text_console_enabled = 1;
39 #else
40 int isvga();
41 #define text_console_enabled (!isvga())
42 #endif
43
44
45 /* How discriminating we are about which messages are displayed */
46 cvar_t con_threshold = { "con_threshold", "0", CVAR_NONE };
47
48 #define CON_BG_HIRES (cfexist("scoresb.pcx")?"scoresb.pcx":"scores.pcx")
49 #define CON_BG_LORES (cfexist("scores.pcx")?"scores.pcx":"scoresb.pcx") // Mac datafiles only have scoresb.pcx
50 #define CON_BG ((SWIDTH>=640)?CON_BG_HIRES:CON_BG_LORES)
51 #define CON_FONT SMALL_FONT
52 #define CON_DEFAULT_COLOR gr_find_closest_color(29, 29, 47)
53
54 #define CON_NUM_LINES           128
55 // Cut the buffer line if it becomes longer than this
56 #define CON_CHARS_PER_LINE      128
57 // Border in pixels from the most left to the first letter
58 #define CON_CHAR_BORDER         4
59 // Spacing in pixels between lines
60 #define CON_LINE_SPACE          1
61 // Scroll this many lines at a time (when pressing PGUP or PGDOWN)
62 #define CON_LINE_SCROLL         2
63 // Indicator showing that you scrolled up the history
64 #define CON_SCROLL_INDICATOR    "^"
65 // Defines the opening/closing speed
66 #define CON_OPENCLOSE_SPEED 50
67
68
69 /* The console's data */
70 static int Visible;             // Enum that tells which visible state we are in CON_HIDE, CON_SHOW, CON_RAISE, CON_LOWER
71 static int RaiseOffset;         // Offset used when scrolling in the console
72 static char **ConsoleLines;     // List of all the past lines
73 static int TotalConsoleLines;   // Total number of lines in the console
74 static int ConsoleScrollBack;   // How much the user scrolled back in the console
75 static int LineBuffer;          // The number of visible lines in the console (autocalculated)
76 static int VChars;              // The number of visible characters in one console line (autocalculated)
77 static grs_canvas *ConsoleSurface;  // Canvas of the console
78 static grs_bitmap *BackgroundImage; // Background image for the console
79 static grs_bitmap *InputBackground; // Dirty rectangle to draw over behind the users background
80
81 /* console is ready to be written to */
82 static int con_initialized;
83
84 /* text foreground color */
85 int CON_color;
86
87
88 /* Internals */
89 static void con_update_offset(void);
90 /* Frees all the memory loaded by the console */
91 static void con_free(void);
92 static int con_background(grs_bitmap *image);
93 /* Sets font info for the console */
94 static void con_font(grs_font *font);
95 /* makes newline (same as printf("\n") or CON_Out("\n") ) */
96 static void con_newline(void);
97 /* updates console after resize etc. */
98 static void con_update(void);
99 /* Called if you press Ctrl-L (deletes the History) */
100 static void con_clear(void);
101
102
103 /* Takes keys from the keyboard and inputs them to the console
104  * If the event was not handled (i.e. WM events or unknown ctrl-shift
105  * sequences) the function returns the event for further processing. */
106 int con_key_handler(int key)
107 {
108         unsigned char character = key_to_ascii(key);
109
110         if (!con_is_visible())
111                 return key;
112
113         switch (key) {
114                 case KEY_LAPOSTRO:
115                 case KEY_ESC:
116                 case KEY_SHIFTED + KEY_ESC:
117                         con_hide();
118                         break;
119                 case KEY_CTRLED + KEY_L:
120                         con_clear();
121                         con_update();
122                         break;
123                 case KEY_SHIFTED + KEY_HOME:
124                         ConsoleScrollBack = LineBuffer-1;
125                         con_update();
126                         break;
127                 case KEY_SHIFTED + KEY_END:
128                         ConsoleScrollBack = 0;
129                         con_update();
130                         break;
131                 case KEY_PAGEUP:
132                         ConsoleScrollBack += CON_LINE_SCROLL;
133                         if(ConsoleScrollBack > LineBuffer-1)
134                                 ConsoleScrollBack = LineBuffer-1;
135                         con_update();
136                         break;
137                 case KEY_PAGEDOWN:
138                         ConsoleScrollBack -= CON_LINE_SCROLL;
139                         if(ConsoleScrollBack < 0)
140                                 ConsoleScrollBack = 0;
141                         con_update();
142                         break;
143                 case KEY_CTRLED + KEY_A:
144                 case KEY_HOME:              cli_cursor_home();      break;
145                 case KEY_END:
146                 case KEY_CTRLED + KEY_E:    cli_cursor_end();       break;
147                 case KEY_CTRLED + KEY_C:    cli_clear();            break;
148                 case KEY_LEFT:              cli_cursor_left();      break;
149                 case KEY_RIGHT:             cli_cursor_right();     break;
150                 case KEY_BACKSP:            cli_cursor_backspace(); break;
151                 case KEY_CTRLED + KEY_D:
152                 case KEY_DELETE:            cli_cursor_del();       break;
153                 case KEY_UP:                cli_history_prev();     break;
154                 case KEY_DOWN:              cli_history_next();     break;
155                 case KEY_TAB:               cli_autocomplete();     break;
156                 case KEY_ENTER:             cli_execute();          break;
157                 case KEY_INSERT:
158                         CLI_insert_mode = !CLI_insert_mode;
159                         break;
160                 default:
161                         if (character == 255)
162                                 break;
163                         cli_add_character(character);
164                         break;
165         }
166         return 0;
167 }
168
169
170 /* Updates the console buffer */
171 static void con_update(void)
172 {
173         int loop;
174         int loop2;
175         int Screenlines;
176         grs_canvas *canv_save;
177
178         /* Due to the Blits, the update is not very fast: So only update if it's worth it */
179         if (!con_is_visible())
180                 return;
181
182         Screenlines = ConsoleSurface->cv_h / (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h);
183
184         canv_save = grd_curcanv;
185         gr_set_current_canvas(ConsoleSurface);
186
187         gr_set_fontcolor(CON_color, -1);
188
189         /* draw the background image if there is one */
190         if (BackgroundImage)
191                 gr_bitmap(0, 0, BackgroundImage);
192
193         // now draw text from last but second line to top
194         for (loop = 0; loop < Screenlines-1 && loop < LineBuffer - ConsoleScrollBack; loop++) {
195                 if (ConsoleScrollBack != 0 && loop == 0)
196                         for (loop2 = 0; loop2 < (VChars / 5) + 1; loop2++)
197                         {
198                                 gr_string(CON_CHAR_BORDER + (loop2*5*ConsoleSurface->cv_font->ft_w), (Screenlines - loop - 2) * (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h), CON_SCROLL_INDICATOR);
199                         }
200                 else
201                 {
202                         gr_string(CON_CHAR_BORDER, (Screenlines - loop - 2) * (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h), ConsoleLines[ConsoleScrollBack + loop]);
203                 }
204         }
205
206         gr_set_current_canvas(canv_save);
207 }
208
209
210 static void con_update_offset(void)
211 {
212         switch (Visible) {
213                 case CON_CLOSING:
214                         RaiseOffset -= CON_OPENCLOSE_SPEED;
215                         if(RaiseOffset <= 0) {
216                                 RaiseOffset = 0;
217                                 Visible = CON_CLOSED;
218                         }
219                         break;
220                 case CON_OPENING:
221                         RaiseOffset += CON_OPENCLOSE_SPEED;
222                         if(RaiseOffset >= ConsoleSurface->cv_h) {
223                                 RaiseOffset = ConsoleSurface->cv_h;
224                                 Visible = CON_OPEN;
225                         }
226                         break;
227                 case CON_OPEN:
228                 case CON_CLOSED:
229                         break;
230         }
231 }
232
233
234 /* Draws the console buffer to the screen if the console is "visible" */
235 void con_draw(void)
236 {
237         grs_canvas *canv_save;
238         grs_bitmap *clip;
239
240         /* only draw if console is visible: here this means, that the console is not CON_CLOSED */
241         if (Visible == CON_CLOSED)
242                 return;
243
244         /* Update the scrolling offset */
245         con_update_offset();
246
247         canv_save = grd_curcanv;
248
249         /* Update the command line since it has a blinking cursor */
250         gr_set_current_canvas(ConsoleSurface);
251
252         // restore InputBackground
253         gr_bitmap(0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, InputBackground);
254
255         cli_draw(ConsoleSurface->cv_h);
256
257         gr_set_current_canvas(&grd_curscreen->sc_canvas);
258
259         clip = gr_create_sub_bitmap(&ConsoleSurface->cv_bitmap, 0, ConsoleSurface->cv_h - RaiseOffset, ConsoleSurface->cv_w, RaiseOffset);
260
261         gr_bitmap(0, 0, clip);
262         gr_free_sub_bitmap(clip);
263
264         gr_set_current_canvas(canv_save);
265 }
266
267
268 void con_cmd_toggleconsole(int argc, char **argv)
269 {
270         if (argc > 1) {
271                 cmd_appendf("help %s", argv[0]);
272                 return;
273         }
274
275         if (con_is_visible())
276                 con_hide();
277         else
278                 con_show();
279 }
280
281
282 /* Initializes the console */
283 void con_init()
284 {
285         int loop;
286
287         Visible = CON_CLOSED;
288         RaiseOffset = 0;
289         ConsoleLines = NULL;
290         TotalConsoleLines = 0;
291         ConsoleScrollBack = 0;
292         BackgroundImage = NULL;
293
294         /* load the console surface */
295         ConsoleSurface = NULL;
296
297         /* Load the dirty rectangle for user input */
298         InputBackground = NULL;
299
300         VChars = CON_CHARS_PER_LINE - 1;
301         LineBuffer = CON_NUM_LINES;
302
303         ConsoleLines = (char **)d_malloc(sizeof(char *) * LineBuffer);
304         for (loop = 0; loop <= LineBuffer - 1; loop++) {
305                 ConsoleLines[loop] = (char *)d_calloc(CON_CHARS_PER_LINE, sizeof(char));
306         }
307
308         cli_init();
309         cmd_init();
310         cvar_init();
311
312         cmd_addcommand("toggleconsole", con_cmd_toggleconsole, "toggleconsole\n" "    show or hide the console");
313         cvar_registervariable (&con_threshold);
314
315         con_initialized = 1;
316
317         atexit(con_free);
318 }
319
320
321 void gr_init_bitmap_alloc( grs_bitmap *bm, int mode, int x, int y, int w, int h, int bytesperline);
322 void con_init_gfx(int w, int h)
323 {
324         int pcx_error;
325         grs_bitmap bmp;
326         ubyte pal[256*3];
327
328         if (ConsoleSurface) {
329                 /* resize console surface */
330                 gr_free_bitmap_data(&ConsoleSurface->cv_bitmap);
331                 gr_init_bitmap_alloc(&ConsoleSurface->cv_bitmap, BM_LINEAR, 0, 0, w, h, w);
332         } else {
333                 /* load the console surface */
334                 ConsoleSurface = gr_create_canvas(w, h);
335         }
336
337         CON_color = CON_DEFAULT_COLOR;
338
339         /* Load the consoles font */
340         con_font(CON_FONT);
341
342         /* make sure that the size of the console is valid */
343         if (w > grd_curscreen->sc_w || w < ConsoleSurface->cv_font->ft_w * 32)
344                 w = grd_curscreen->sc_w;
345         if (h > grd_curscreen->sc_h || h < ConsoleSurface->cv_font->ft_h)
346                 h = grd_curscreen->sc_h;
347
348         /* Load the dirty rectangle for user input */
349         if (InputBackground)
350                 gr_free_bitmap(InputBackground);
351         InputBackground = gr_create_bitmap(w, ConsoleSurface->cv_font->ft_h);
352
353         /* calculate the number of visible characters in the command line */
354 #if 0 // doesn't work because proportional font
355         VChars = (w - CON_CHAR_BORDER) / ConsoleSurface->cv_font->ft_w;
356         if (VChars >= CON_CHARS_PER_LINE)
357                 VChars = CON_CHARS_PER_LINE - 1;
358 #endif
359
360         gr_init_bitmap_data(&bmp);
361         pcx_error = pcx_read_bitmap(CON_BG, &bmp, BM_LINEAR, pal);
362         Assert(pcx_error == PCX_ERROR_NONE);
363         gr_remap_bitmap_good(&bmp, pal, -1, -1);
364         con_background(&bmp);
365         gr_free_bitmap_data(&bmp);
366 }
367
368
369 /* Makes the console visible */
370 void con_show(void)
371 {
372         Visible = CON_OPENING;
373         con_update();
374 }
375
376
377 /* Hides the console (make it invisible) */
378 void con_hide(void)
379 {
380         Visible = CON_CLOSING;
381         key_flush();
382 }
383
384
385 /* tells wether the console is visible or not */
386 int con_is_visible(void)
387 {
388         return((Visible == CON_OPEN) || (Visible == CON_OPENING));
389 }
390
391
392 /* Frees all the memory loaded by the console */
393 static void con_free(void)
394 {
395         int i;
396
397         for (i = 0; i <= LineBuffer - 1; i++) {
398                 d_free(ConsoleLines[i]);
399         }
400         d_free(ConsoleLines);
401
402         ConsoleLines = NULL;
403
404         if (ConsoleSurface)
405                 gr_free_canvas(ConsoleSurface);
406         ConsoleSurface = NULL;
407
408         if (BackgroundImage)
409                 gr_free_bitmap(BackgroundImage);
410         BackgroundImage = NULL;
411
412         if (InputBackground)
413                 gr_free_bitmap(InputBackground);
414         InputBackground = NULL;
415
416         con_initialized = 0;
417 }
418
419
420 /* Increments the console lines */
421 static void con_newline(void)
422 {
423         int loop;
424         char *temp;
425
426         temp = ConsoleLines[LineBuffer - 1];
427
428         for (loop = LineBuffer - 1; loop > 0; loop--)
429                 ConsoleLines[loop] = ConsoleLines[loop - 1];
430
431         ConsoleLines[0] = temp;
432
433         memset(ConsoleLines[0], 0, CON_CHARS_PER_LINE);
434         if (TotalConsoleLines < LineBuffer - 1)
435                 TotalConsoleLines++;
436         
437         //Now adjust the ConsoleScrollBack
438         //dont scroll if not at bottom
439         if(ConsoleScrollBack != 0)
440                 ConsoleScrollBack++;
441         //boundaries
442         if(ConsoleScrollBack > LineBuffer-1)
443                 ConsoleScrollBack = LineBuffer-1;
444 }
445
446
447 static inline int con_get_width(void)
448 {
449         if (!ConsoleSurface)
450                 return 0;
451
452         return ConsoleSurface->cv_bitmap.bm_w - CON_CHAR_BORDER;
453 }
454
455
456 static inline int con_get_string_width(char *string)
457 {
458         grs_canvas *canv_save;
459         int w = 0, h, aw;
460
461         if (!ConsoleSurface)
462                 return 0;
463
464         canv_save = grd_curcanv;
465         gr_set_current_canvas(ConsoleSurface);
466         gr_get_string_size(string, &w, &h, &aw);
467         gr_set_current_canvas(canv_save);
468
469         return w;
470 }
471
472
473 #ifdef _MSC_VER
474 # define vsnprintf _vsnprintf
475 #endif
476
477 /* Outputs text to the console (in game), up to CON_CHARS_PER_LINE chars can be entered */
478 static void con_out(const char *str, ...)
479 {
480         va_list marker;
481         //keep some space free for stuff like CON_Out("blablabla %s", Command);
482         char temp[CON_CHARS_PER_LINE + 128];
483         char* ptemp;
484
485         va_start(marker, str);
486         vsnprintf(temp, CON_CHARS_PER_LINE + 127, str, marker);
487         va_end(marker);
488
489         ptemp = temp;
490
491         // temp now contains the complete string we want to output
492         // the only problem is that temp is maybe longer than the console
493         // width so we have to cut it into several pieces
494
495         if (ConsoleLines) {
496                 char *p = ptemp;
497
498                 while (*p) {
499                         if (*p == '\n') {
500                                 *p = '\0';
501                                 con_newline();
502                                 strcat(ConsoleLines[0], ptemp);
503                                 ptemp = p+1;
504                         } else if (p - ptemp > VChars - strlen(ConsoleLines[0]) ||
505                                            con_get_string_width(ptemp) > con_get_width()) {
506                                 con_newline();
507                                 strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
508                                 ConsoleLines[0][VChars] = '\0';
509                                 ptemp = p;
510                         }
511                         p++;
512                 }
513                 if (strlen(ptemp)) {
514                         strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
515                         ConsoleLines[0][VChars] = '\0';
516                 }
517                 con_update();
518         }
519 }
520
521
522 /* Adds background image to the console, scaled to size of console*/
523 static int con_background(grs_bitmap *image)
524 {
525         /* Free the background from the console */
526         if (image == NULL) {
527                 if (BackgroundImage)
528                         gr_free_bitmap(BackgroundImage);
529                 BackgroundImage = NULL;
530                 return 0;
531         }
532
533         /* Load a new background */
534         if (BackgroundImage)
535                 gr_free_bitmap(BackgroundImage);
536         BackgroundImage = gr_create_bitmap(ConsoleSurface->cv_w, ConsoleSurface->cv_h);
537         gr_bitmap_scale_to(image, BackgroundImage);
538
539         gr_bm_bitblt(BackgroundImage->bm_w, InputBackground->bm_h, 0, 0, 0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, BackgroundImage, InputBackground);
540
541         return 0;
542 }
543
544
545 /* Sets font info for the console */
546 static void con_font(grs_font *font)
547 {
548         grs_canvas *canv_save;
549
550         canv_save = grd_curcanv;
551         gr_set_current_canvas(ConsoleSurface);
552         gr_set_curfont(font);
553         gr_set_current_canvas(canv_save);
554 }
555
556
557 static void con_clear(void)
558 {
559         int loop;
560         
561         for (loop = 0; loop <= LineBuffer - 1; loop++)
562                 memset(ConsoleLines[loop], 0, CON_CHARS_PER_LINE);
563 }
564
565
566 /* convert to ansi rgb colors 17-231 */
567 #define PAL2ANSI(x) ((36*gr_palette[(x)*3]/11) + (6*gr_palette[(x)*3+1]/11) + (gr_palette[(x)*3+2]/11) + 16)
568
569 /* Print a message to the console */
570 void con_printf(int priority, char *fmt, ...)
571 {
572         va_list arglist;
573         char buffer[2048];
574
575         if (priority <= (con_threshold.intval))
576         {
577                 va_start (arglist, fmt);
578                 vsprintf (buffer,  fmt, arglist);
579                 va_end (arglist);
580
581                 if (con_initialized)
582                         con_out(buffer);
583
584                 if (!text_console_enabled)
585                         return;
586
587                 if (isatty(fileno(stdout))) {
588                         char *buf, *p;
589                         unsigned char color, spacing, underline;
590
591                         p = buf = buffer;
592                         do
593                                 switch (*p)
594                                 {
595                                 case CC_COLOR:
596                                         *p++ = 0;
597                                         printf("%s", buf);
598                                         color = *p++;
599                                         printf("\x1B[38;5;%dm", PAL2ANSI(color));
600                                         buf = p;
601                                         break;
602                                 case CC_LSPACING:
603                                         *p++ = 0;
604                                         printf("%s", buf);
605                                         spacing = *p++;
606                                         //printf("<SPACING %d>", color);
607                                         buf = p;
608                                         break;
609                                 case CC_UNDERLINE:
610                                         *p++ = 0;
611                                         printf("%s", buf);
612                                         underline = 1;
613                                         //printf("<UNDERLINE>");
614                                         buf = p;
615                                         break;
616                                 default:
617                                         p++;
618                                 }
619                         while (*p);
620
621                         printf("%s", buf);
622
623                 } else {
624                         /* Produce a sanitised version and send it to the standard output */
625                         char *p1, *p2;
626
627                         p1 = p2 = buffer;
628                         do
629                                 switch (*p1)
630                                 {
631                                 case CC_COLOR:
632                                 case CC_LSPACING:
633                                         p1++;
634                                 case CC_UNDERLINE:
635                                         p1++;
636                                         break;
637                                 default:
638                                         *p2++ = *p1++;
639                                 }
640                         while (*p1);
641                         *p2 = 0;
642
643                         printf("%s", buffer);
644                 }
645         }
646 }