Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
  3.  * It is copyright by its individual contributors, as recorded in the
  4.  * project's Git history.  See COPYING.txt at the top level for license
  5.  * terms and a link to the Git history.
  6.  */
  7. /*
  8.  *
  9.  * Game console
  10.  *
  11.  */
  12.  
  13. #include <algorithm>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <stdarg.h>
  17. #include <string.h>
  18. #include <sys/time.h>
  19. #include <SDL.h>
  20. #include "window.h"
  21. #include "event.h"
  22. #include "console.h"
  23. #include "args.h"
  24. #include "gr.h"
  25. #include "physfsx.h"
  26. #include "gamefont.h"
  27. #include "game.h"
  28. #include "key.h"
  29. #include "vers_id.h"
  30. #include "timer.h"
  31. #include "cli.h"
  32. #include "cvar.h"
  33.  
  34. #include "dxxsconf.h"
  35. #include <array>
  36.  
  37. #ifdef _WIN32
  38. #include <windows.h>
  39. #endif
  40.  
  41. #ifndef DXX_CONSOLE_TIME_SHOW_YMD
  42. #define DXX_CONSOLE_TIME_SHOW_YMD       0
  43. #endif
  44.  
  45. #ifndef DXX_CONSOLE_TIME_SHOW_MSEC
  46. #define DXX_CONSOLE_TIME_SHOW_MSEC      1
  47. #endif
  48.  
  49. #ifndef DXX_CONSOLE_SHOW_TIME_STDOUT
  50. #define DXX_CONSOLE_SHOW_TIME_STDOUT    0
  51. #endif
  52.  
  53. constexpr unsigned CON_LINES_ONSCREEN = 18;
  54. constexpr auto CON_SCROLL_OFFSET = CON_LINES_ONSCREEN - 3;
  55. constexpr unsigned CON_LINES_MAX = 128;
  56.  
  57. enum con_state {
  58.         CON_STATE_CLOSING = -1,
  59.         CON_STATE_CLOSED = 0,
  60.         CON_STATE_OPENING = 1,
  61.         CON_STATE_OPEN = 2
  62. };
  63.  
  64. static RAIIPHYSFS_File gamelog_fp;
  65. static std::array<console_buffer, CON_LINES_MAX> con_buffer;
  66. static con_state con_state;
  67. static int con_scroll_offset, con_size;
  68. static void con_force_puts(con_priority priority, char *buffer, size_t len);
  69.  
  70. static void con_add_buffer_line(const con_priority priority, const char *const buffer, const size_t len)
  71. {
  72.         /* shift con_buffer for one line */
  73.         std::move(std::next(con_buffer.begin()), con_buffer.end(), con_buffer.begin());
  74.         console_buffer &c = con_buffer.back();
  75.         c.priority=priority;
  76.  
  77.         size_t copy = std::min(len, CON_LINE_LENGTH - 1);
  78.         c.line[copy] = 0;
  79.         memcpy(&c.line,buffer, copy);
  80. }
  81.  
  82. void (con_printf)(const con_priority_wrapper priority, const char *const fmt, ...)
  83. {
  84.         va_list arglist;
  85.         char buffer[CON_LINE_LENGTH];
  86.  
  87.         if (priority <= CGameArg.DbgVerbose)
  88.         {
  89.                 va_start (arglist, fmt);
  90.                 auto &&leader = priority.insert_location_leader(buffer);
  91.                 size_t len = vsnprintf (leader.first, leader.second, fmt, arglist);
  92.                 va_end (arglist);
  93.                 con_force_puts(priority, buffer, len);
  94.         }
  95. }
  96.  
  97. static void con_scrub_markup(char *buffer)
  98. {
  99.         char *p1 = buffer, *p2 = p1;
  100.         do
  101.                 switch (*p1)
  102.                 {
  103.                         case CC_COLOR:
  104.                         case CC_LSPACING:
  105.                                 if (!*++p1)
  106.                                         break;
  107.                                 DXX_BOOST_FALLTHROUGH;
  108.                         case CC_UNDERLINE:
  109.                                 p1++;
  110.                                 break;
  111.                         default:
  112.                                 *p2++ = *p1++;
  113.                 }
  114.         while (*p1);
  115.         *p2 = 0;
  116. }
  117.  
  118. static void con_print_file(const char *const buffer)
  119. {
  120.         char buf[1024];
  121. #if !DXX_CONSOLE_SHOW_TIME_STDOUT
  122. #ifndef _WIN32
  123.         /* Print output to stdout */
  124.         puts(buffer);
  125. #endif
  126.  
  127.         /* Print output to gamelog.txt */
  128.         if (gamelog_fp)
  129. #endif
  130.         {
  131. #if DXX_CONSOLE_TIME_SHOW_YMD
  132. #define DXX_CONSOLE_TIME_FORMAT_YMD     "%04i-%02i-%02i "
  133. #define DXX_CONSOLE_TIME_ARG_YMD        tm_year, tm_month, tm_day,
  134. #else
  135. #define DXX_CONSOLE_TIME_FORMAT_YMD     ""
  136. #define DXX_CONSOLE_TIME_ARG_YMD
  137. #endif
  138. #if DXX_CONSOLE_TIME_SHOW_MSEC
  139. #ifdef _WIN32
  140. #define DXX_CONSOLE_TIME_FORMAT_MSEC    ".%03i"
  141. #else
  142. #define DXX_CONSOLE_TIME_FORMAT_MSEC    ".%06i"
  143. #endif
  144. #define DXX_CONSOLE_TIME_ARG_MSEC       tm_msec,
  145. #else
  146. #define DXX_CONSOLE_TIME_FORMAT_MSEC    ""
  147. #define DXX_CONSOLE_TIME_ARG_MSEC
  148. #endif
  149.                 int
  150.                         DXX_CONSOLE_TIME_ARG_YMD
  151.                         DXX_CONSOLE_TIME_ARG_MSEC
  152.                         tm_hour, tm_min, tm_sec;
  153. #ifdef _WIN32
  154. #define DXX_LF  "\r\n"
  155.                 SYSTEMTIME st = {};
  156.                 GetLocalTime(&st);
  157. #if DXX_CONSOLE_TIME_SHOW_YMD
  158.                 tm_year = st.wYear;
  159.                 tm_month = st.wMonth;
  160.                 tm_day = st.wDay;
  161. #endif
  162.                 tm_hour = st.wHour;
  163.                 tm_min = st.wMinute;
  164.                 tm_sec = st.wSecond;
  165. #if DXX_CONSOLE_TIME_SHOW_MSEC
  166.                 tm_msec = st.wMilliseconds;
  167. #endif
  168. #else
  169. #define DXX_LF  "\n"
  170.                 struct timeval tv;
  171.                 if (gettimeofday(&tv, nullptr))
  172.                         tv = {};
  173.                 if (const auto lt = localtime(&tv.tv_sec))
  174.                 {
  175. #if DXX_CONSOLE_TIME_SHOW_YMD
  176.                         tm_year = lt->tm_year;
  177.                         tm_month = lt->tm_mon;
  178.                         tm_day = lt->tm_mday;
  179. #endif
  180.                         tm_hour = lt->tm_hour;
  181.                         tm_min = lt->tm_min;
  182.                         tm_sec = lt->tm_sec;
  183. #if DXX_CONSOLE_TIME_SHOW_MSEC
  184.                         tm_msec = tv.tv_usec;
  185. #endif
  186.                 }
  187.                 else
  188.                 {
  189. #if DXX_CONSOLE_TIME_SHOW_YMD
  190.                         tm_year = tm_month = tm_day =
  191. #endif
  192. #if DXX_CONSOLE_TIME_SHOW_MSEC
  193.                         tm_msec =
  194. #endif
  195.                         tm_hour = tm_min = tm_sec = -1;
  196.                 }
  197. #endif
  198.                 const size_t len = snprintf(buf, sizeof(buf), DXX_CONSOLE_TIME_FORMAT_YMD "%02i:%02i:%02i" DXX_CONSOLE_TIME_FORMAT_MSEC " %s" DXX_LF, DXX_CONSOLE_TIME_ARG_YMD tm_hour, tm_min, tm_sec, DXX_CONSOLE_TIME_ARG_MSEC buffer);
  199. #if DXX_CONSOLE_SHOW_TIME_STDOUT
  200. #ifndef _WIN32
  201.                 fputs(buf, stdout);
  202. #endif
  203.                 if (gamelog_fp)
  204. #endif
  205.                 {
  206.                         PHYSFS_write(gamelog_fp, buf, 1, len);
  207.                 }
  208. #undef DXX_LF
  209. #undef DXX_CONSOLE_TIME_ARG_MSEC
  210. #undef DXX_CONSOLE_TIME_FORMAT_MSEC
  211. #undef DXX_CONSOLE_TIME_ARG_YMD
  212. #undef DXX_CONSOLE_TIME_FORMAT_YMD
  213.         }
  214. }
  215.  
  216. /*
  217.  * The caller is assumed to have checked that the priority allows this
  218.  * entry to be logged.
  219.  */
  220. static void con_force_puts(const con_priority priority, char *const buffer, const size_t len)
  221. {
  222.         con_add_buffer_line(priority, buffer, len);
  223.         con_scrub_markup(buffer);
  224.         /* Produce a sanitised version and send it to the console */
  225.         con_print_file(buffer);
  226. }
  227.  
  228. void con_puts(const con_priority_wrapper priority, char *const buffer, const size_t len)
  229. {
  230.         if (priority <= CGameArg.DbgVerbose)
  231.         {
  232.                 typename con_priority_wrapper::scratch_buffer<CON_LINE_LENGTH> scratch_buffer;
  233.                 auto &&b = priority.prepare_buffer(scratch_buffer, buffer, len);
  234.                 con_force_puts(priority, b.first, b.second);
  235.         }
  236. }
  237.  
  238. void con_puts(const con_priority_wrapper priority, const char *const buffer, const size_t len)
  239. {
  240.         if (priority <= CGameArg.DbgVerbose)
  241.         {
  242.                 typename con_priority_wrapper::scratch_buffer<CON_LINE_LENGTH> scratch_buffer;
  243.                 auto &&b = priority.prepare_buffer(scratch_buffer, buffer, len);
  244.                 /* add given string to con_buffer */
  245.                 con_add_buffer_line(priority, b.first, b.second);
  246.                 con_print_file(b.first);
  247.         }
  248. }
  249.  
  250. static color_palette_index get_console_color_by_priority(const int priority)
  251. {
  252.         int r, g, b;
  253.         switch (priority)
  254.         {
  255.                 case CON_CRITICAL:
  256.                         r = 28 * 2, g = 0 * 2, b = 0 * 2;
  257.                         break;
  258.                 case CON_URGENT:
  259.                         r = 54 * 2, g = 54 * 2, b = 0 * 2;
  260.                         break;
  261.                 case CON_DEBUG:
  262.                 case CON_VERBOSE:
  263.                         r = 14 * 2, g = 14 * 2, b = 14 * 2;
  264.                         break;
  265.                 case CON_HUD:
  266.                         r = 0 * 2, g = 28 * 2, b = 0 * 2;
  267.                         break;
  268.                 default:
  269.                         r = 255 * 2, g = 255 * 2, b = 255 * 2;
  270.                         break;
  271.         }
  272.         return gr_find_closest_color(r, g, b);
  273. }
  274.  
  275. static void con_draw(void)
  276. {
  277.         int i = 0, y = 0;
  278.  
  279.         if (con_size <= 0)
  280.                 return;
  281.  
  282.         gr_set_default_canvas();
  283.         auto &canvas = *grd_curcanv;
  284.         auto &game_font = *GAME_FONT;
  285.         gr_set_curfont(canvas, GAME_FONT);
  286.         const uint8_t color = BM_XRGB(0, 0, 0);
  287.         gr_settransblend(canvas, 7, gr_blend::normal);
  288.         const auto &&fspacy1 = FSPACY(1);
  289.         const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
  290.         y = fspacy1 + (line_spacing * con_size);
  291.         gr_rect(canvas, 0, 0, SWIDTH, y, color);
  292.         gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
  293.         i+=con_scroll_offset;
  294.  
  295.         gr_set_fontcolor(canvas, BM_XRGB(255, 255, 255), -1);
  296.         y = cli_draw(y, line_spacing);
  297.  
  298.         const auto &&fspacx = FSPACX();
  299.         const auto &&fspacx1 = fspacx(1);
  300.         for (;;)
  301.         {
  302.                 auto &b = con_buffer[CON_LINES_MAX - 1 - i];
  303.                 gr_set_fontcolor(canvas, get_console_color_by_priority(b.priority), -1);
  304.                 int w,h;
  305.                 gr_get_string_size(game_font, b.line, &w, &h, nullptr);
  306.                 y -= h + fspacy1;
  307.                 gr_string(canvas, game_font, fspacx1, y, b.line, w, h);
  308.                 i++;
  309.  
  310.                 if (y<=0 || CON_LINES_MAX-1-i <= 0 || i < 0)
  311.                         break;
  312.         }
  313.         gr_rect(canvas, 0, 0, SWIDTH, line_spacing, color);
  314.         gr_set_fontcolor(canvas, BM_XRGB(255, 255, 255),-1);
  315.         gr_printf(canvas, game_font, fspacx1, fspacy1, "%s LOG", DESCENT_VERSION);
  316.         gr_string(canvas, game_font, SWIDTH - fspacx(110), fspacy1, "PAGE-UP/DOWN TO SCROLL");
  317. }
  318.  
  319. static window_event_result con_handler(window *wind,const d_event &event, const unused_window_userdata_t *)
  320. {
  321.         int key;
  322.         static fix64 last_scroll_time = 0;
  323.        
  324.         switch (event.type)
  325.         {
  326.                 case EVENT_WINDOW_ACTIVATED:
  327.                         key_toggle_repeat(1);
  328.                         break;
  329.  
  330.                 case EVENT_WINDOW_DEACTIVATED:
  331.                         key_toggle_repeat(0);
  332.                         con_size = 0;
  333.                         con_state = CON_STATE_CLOSED;
  334.                         break;
  335.  
  336.                 case EVENT_KEY_COMMAND:
  337.                         key = event_key_get(event);
  338.                         switch (key)
  339.                         {
  340.                                 case KEY_SHIFTED + KEY_ESC:
  341.                                         switch (con_state)
  342.                                         {
  343.                                                 case CON_STATE_OPEN:
  344.                                                 case CON_STATE_OPENING:
  345.                                                         con_state = CON_STATE_CLOSING;
  346.                                                         break;
  347.                                                 case CON_STATE_CLOSED:
  348.                                                 case CON_STATE_CLOSING:
  349.                                                         con_state = CON_STATE_OPENING;
  350.                                                 default:
  351.                                                         break;
  352.                                         }
  353.                                         break;
  354.                                 case KEY_PAGEUP:
  355.                                         con_scroll_offset+=CON_SCROLL_OFFSET;
  356.                                         if (con_scroll_offset >= CON_LINES_MAX-1)
  357.                                                 con_scroll_offset = CON_LINES_MAX-1;
  358.                                         while (con_buffer[CON_LINES_MAX-1-con_scroll_offset].line[0]=='\0')
  359.                                                 con_scroll_offset--;
  360.                                         break;
  361.                                 case KEY_PAGEDOWN:
  362.                                         con_scroll_offset-=CON_SCROLL_OFFSET;
  363.                                         if (con_scroll_offset<0)
  364.                                                 con_scroll_offset=0;
  365.                                         break;
  366.                                 case KEY_CTRLED + KEY_A:
  367.                                 case KEY_HOME:              cli_cursor_home();      break;
  368.                                 case KEY_END:
  369.                                 case KEY_CTRLED + KEY_E:    cli_cursor_end();       break;
  370.                                 case KEY_CTRLED + KEY_C:    cli_clear();            break;
  371.                                 case KEY_LEFT:              cli_cursor_left();      break;
  372.                                 case KEY_RIGHT:             cli_cursor_right();     break;
  373.                                 case KEY_BACKSP:            cli_cursor_backspace(); break;
  374.                                 case KEY_CTRLED + KEY_D:
  375.                                 case KEY_DELETE:            cli_cursor_del();       break;
  376.                                 case KEY_UP:                cli_history_prev();     break;
  377.                                 case KEY_DOWN:              cli_history_next();     break;
  378.                                 case KEY_TAB:               cli_autocomplete();     break;
  379.                                 case KEY_ENTER:             cli_execute();          break;
  380.                                 case KEY_INSERT:
  381.                                         cli_toggle_overwrite_mode();
  382.                                         break;
  383.                                 default:
  384.                                         int character = key_ascii();
  385.                                         if (character == 255)
  386.                                                 break;
  387.                                         cli_add_character(character);
  388.                                         break;
  389.                         }
  390.                         return window_event_result::handled;
  391.  
  392.                 case EVENT_WINDOW_DRAW:
  393.                         timer_delay2(50);
  394.                         if (con_state == CON_STATE_OPENING)
  395.                         {
  396.                                 if (con_size < CON_LINES_ONSCREEN && timer_query() >= last_scroll_time+(F1_0/30))
  397.                                 {
  398.                                         last_scroll_time = timer_query();
  399.                                         if (++ con_size >= CON_LINES_ONSCREEN)
  400.                                                 con_state = CON_STATE_OPEN;
  401.                                 }
  402.                         }
  403.                         else if (con_state == CON_STATE_CLOSING)
  404.                         {
  405.                                 if (con_size > 0 && timer_query() >= last_scroll_time+(F1_0/30))
  406.                                 {
  407.                                         last_scroll_time = timer_query();
  408.                                         if (! -- con_size)
  409.                                                 con_state = CON_STATE_CLOSED;
  410.                                 }
  411.                         }
  412.                         con_draw();
  413.                         if (con_state == CON_STATE_CLOSED && wind)
  414.                         {
  415.                                 return window_event_result::close;
  416.                         }
  417.                         break;
  418.                 case EVENT_WINDOW_CLOSE:
  419.                         break;
  420.                 default:
  421.                         break;
  422.         }
  423.        
  424.         return window_event_result::ignored;
  425. }
  426.  
  427. void con_showup(void)
  428. {
  429.         game_flush_inputs();
  430.         con_state = CON_STATE_OPENING;
  431.         const auto wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, con_handler, unused_window_userdata);
  432.        
  433.         if (!wind)
  434.         {
  435.                 d_event event = { EVENT_WINDOW_CLOSE };
  436.                 con_handler(NULL, event, NULL);
  437.                 return;
  438.         }
  439. }
  440.  
  441. void con_init(void)
  442. {
  443.         con_buffer = {};
  444.         if (CGameArg.DbgSafelog)
  445.                 gamelog_fp.reset(PHYSFS_openWrite("gamelog.txt"));
  446.         else
  447.                 gamelog_fp = PHYSFSX_openWriteBuffered("gamelog.txt");
  448.  
  449.         cli_init();
  450.         cmd_init();
  451.         cvar_init();
  452.  
  453. }
  454.