Subversion Repositories Games.Descent

Rev

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
}