Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 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 | } |