1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | #include "cl_console.h" |
30 | #include "client.h" |
31 | #include "cgame/cl_game.h" |
32 | #include "input/cl_keys.h" |
33 | #include "renderer/r_draw.h" |
34 | #include "../shared/utf8.h" |
35 | |
36 | #define ColorIndex(c)(((c) - '0') & 0x07) (((c) - '0') & 0x07) |
37 | |
38 | |
39 | static const uint32_t g_color_table[] = |
40 | { |
41 | 0xFF000000, |
42 | 0xFF0000FF, |
43 | 0xFF00FF00, |
44 | 0xFF00FFFF, |
45 | 0xFFFF0000, |
46 | 0xFFFFFF00, |
47 | 0xFFFF00FF, |
48 | 0xFFFFFFFF |
49 | }; |
50 | |
51 | #define CONSOLE_CHAR_ALIGN4 4 |
52 | #define NUM_CON_TIMES8 8 |
53 | #define CON_TEXTSIZE32768 32768 |
54 | #define CONSOLE_CURSOR_CHAR11 11 |
55 | #define CONSOLE_HISTORY_FILENAME"history" "history" |
56 | |
57 | typedef struct { |
58 | bool initialized; |
59 | |
60 | short text[CON_TEXTSIZE32768]; |
61 | int currentLine; |
62 | int pos; |
63 | int displayLine; |
64 | |
65 | int lineWidth; |
66 | int totalLines; |
67 | |
68 | int visLines; |
69 | } console_t; |
70 | |
71 | static console_t con; |
72 | static cvar_t *con_notifytime; |
73 | static cvar_t *con_history; |
74 | static cvar_t *con_background; |
75 | const int con_fontHeight = 12; |
76 | const int con_fontWidth = 10; |
77 | const int con_fontShift = 3; |
78 | |
79 | static void Con_Clear (void) |
80 | { |
81 | unsigned int i; |
82 | const size_t size = lengthof(con.text)(sizeof(con.text) / sizeof(*(con.text))); |
83 | |
84 | for (i = 0; i < size; i++) |
85 | con.text[i] = (CON_COLOR_WHITE7 << 8) | ' '; |
86 | } |
87 | |
88 | |
89 | |
90 | |
91 | |
92 | |
93 | |
94 | static void Con_DrawText (const short *text, int x, int y, size_t width) |
95 | { |
96 | unsigned int xPos; |
97 | for (xPos = 0; xPos < width; xPos++) { |
| 4 | Loop condition is true. Entering loop body |
|
98 | const int currentColor = (text[xPos] >> 8) & 7; |
| 5 | The left operand of '>>' is a garbage value |
|
99 | R_DrawChar(x + ((xPos + 1) << con_fontShift), y, text[xPos], g_color_table[currentColor]); |
100 | } |
101 | } |
102 | |
103 | |
104 | |
105 | |
106 | |
107 | |
108 | |
109 | void Con_DrawString (const char *txt, int x, int y, unsigned int width) |
110 | { |
111 | short buf[512], *pos; |
112 | const size_t size = lengthof(buf)(sizeof(buf) / sizeof(*(buf))); |
113 | char c; |
114 | int color; |
115 | |
116 | if (width > size || strlen(txt) > size) |
| |
117 | Sys_Error("Overflow in Con_DrawString"); |
118 | |
119 | color = CON_COLOR_WHITE7; |
120 | pos = buf; |
121 | |
122 | while ((c = *txt) != 0) { |
| 2 | Loop condition is false. Execution continues on line 134 |
|
123 | if (Q_IsColorString(txt)((txt) && *(txt) == '^' && *((txt) + 1) && isalnum(*((txt) + 1))) ) { |
124 | color = ColorIndex(*(txt + 1))(((*(txt + 1)) - '0') & 0x07); |
125 | txt += 2; |
126 | continue; |
127 | } |
128 | |
129 | *pos = (color << 8) | c; |
130 | |
131 | txt++; |
132 | pos++; |
133 | } |
134 | Con_DrawText(buf, x, y, width); |
| |
135 | } |
136 | |
137 | static void Key_ClearTyping (void) |
138 | { |
139 | keyLines[editLine][1] = 0; |
140 | keyLinePos = 1; |
141 | } |
142 | |
143 | void Con_ToggleConsole_f (void) |
144 | { |
145 | Key_ClearTyping(); |
146 | |
147 | if (cls.keyDest == key_console) { |
148 | Key_SetDest(key_game); |
149 | } else { |
150 | Key_SetDest(key_console); |
151 | } |
152 | } |
153 | |
154 | static void Con_ToggleChat_f (void) |
155 | { |
156 | Key_ClearTyping(); |
157 | |
158 | if (cls.keyDest == key_console) { |
159 | if (cls.state == ca_active) |
160 | Key_SetDest(key_game); |
161 | } else |
162 | Key_SetDest(key_console); |
163 | } |
164 | |
165 | |
166 | |
167 | |
168 | static void Con_Clear_f (void) |
169 | { |
170 | Con_Clear(); |
171 | } |
172 | |
173 | |
174 | |
175 | |
176 | |
177 | void Con_Scroll (int scroll) |
178 | { |
179 | con.displayLine += scroll; |
180 | if (con.displayLine > con.currentLine) |
181 | con.displayLine = con.currentLine; |
182 | else if (con.displayLine < 0) |
183 | con.displayLine = 0; |
184 | } |
185 | |
186 | |
187 | |
188 | |
189 | void Con_CheckResize (void) |
190 | { |
191 | int i, j, oldWidth, oldTotalLines, numLines, numChars; |
192 | short tbuf[CON_TEXTSIZE32768]; |
193 | const int width = (viddef.context.width >> con_fontShift); |
194 | |
195 | if (width < 1 || width == con.lineWidth) |
196 | return; |
197 | |
198 | oldWidth = con.lineWidth; |
199 | con.lineWidth = width; |
200 | oldTotalLines = con.totalLines; |
201 | con.totalLines = CON_TEXTSIZE32768 / con.lineWidth; |
202 | numLines = oldTotalLines; |
203 | |
204 | if (con.totalLines < numLines) |
205 | numLines = con.totalLines; |
206 | |
207 | numChars = oldWidth; |
208 | |
209 | if (con.lineWidth < numChars) |
210 | numChars = con.lineWidth; |
211 | |
212 | memcpy(tbuf, con.text, sizeof(tbuf)); |
213 | Con_Clear(); |
214 | |
215 | for (i = 0; i < numLines; i++) { |
216 | for (j = 0; j < numChars; j++) { |
217 | con.text[(con.totalLines - 1 - i) * con.lineWidth + j] = tbuf[((con.currentLine - i + oldTotalLines) % oldTotalLines) * oldWidth + j]; |
218 | } |
219 | } |
220 | |
221 | con.currentLine = con.totalLines - 1; |
222 | con.displayLine = con.currentLine; |
223 | } |
224 | |
225 | |
226 | |
227 | |
228 | |
229 | void Con_LoadConsoleHistory (void) |
230 | { |
231 | qFILE f; |
232 | char line[MAXCMDLINE256]; |
233 | |
234 | if (!con_history->integer) |
235 | return; |
236 | |
237 | OBJZERO(f)(memset(&((f)), (0), sizeof((f)))); |
238 | |
239 | FS_OpenFile(CONSOLE_HISTORY_FILENAME"history", &f, FILE_READ); |
240 | if (!f.f) |
241 | return; |
242 | |
243 | |
244 | while (fgets(line, MAXCMDLINE256 - 2, f.f)) { |
245 | if (line[strlen(line) - 1] == '\n') |
246 | line[strlen(line) - 1] = 0; |
247 | Q_strncpyz(&keyLines[editLine][1], line, MAXCMDLINE - 1)Q_strncpyzDebug( &keyLines[editLine][1], line, 256 - 1, "src/client/cl_console.cpp" , 247 ); |
248 | editLine = (editLine + 1) % MAXKEYLINES32; |
249 | historyLine = editLine; |
250 | keyLines[editLine][1] = 0; |
251 | } |
252 | |
253 | FS_CloseFile(&f); |
254 | } |
255 | |
256 | |
257 | |
258 | |
259 | |
260 | void Con_SaveConsoleHistory (void) |
261 | { |
262 | int i; |
263 | qFILE f; |
264 | const char *lastLine = NULL__null; |
265 | |
266 | |
267 | if (!con_history || !con_history->integer) |
268 | return; |
269 | |
270 | FS_OpenFile(CONSOLE_HISTORY_FILENAME"history", &f, FILE_WRITE); |
271 | if (!f.f) { |
272 | Com_Printf("Can not open " CONSOLE_HISTORY_FILENAME"history" " for writing\n"); |
273 | return; |
274 | } |
275 | |
276 | for (i = 0; i < historyLine; i++) { |
277 | if (lastLine && !strncmp(lastLine, &(keyLines[i][1]), MAXCMDLINE256 - 1)) |
278 | continue; |
279 | |
280 | lastLine = &(keyLines[i][1]); |
281 | if (*lastLine) { |
282 | FS_Write(lastLine, strlen(lastLine), &f); |
283 | FS_Write("\n", 1, &f); |
284 | } |
285 | } |
286 | FS_CloseFile(&f); |
287 | } |
288 | |
289 | void Con_Init (void) |
290 | { |
291 | Com_Printf("\n----- console initialization -------\n"); |
292 | |
293 | |
294 | con_notifytime = Cvar_Get("con_notifytime", "10", CVAR_ARCHIVE1, "How many seconds console messages should be shown before they fade away"); |
295 | con_history = Cvar_Get("con_history", "1", CVAR_ARCHIVE1, "Permanent console history"); |
296 | con_background = Cvar_Get("con_background", "1", CVAR_ARCHIVE1, "Console is rendered with background image"); |
297 | |
298 | Cmd_AddCommand("toggleconsole", Con_ToggleConsole_f, N_("Show/hide ufoconsole.")"Show/hide ufoconsole."); |
299 | Cmd_AddCommand("togglechat", Con_ToggleChat_f); |
300 | Cmd_AddCommand("clear", Con_Clear_f, "Clear console text"); |
301 | |
302 | |
303 | Con_LoadConsoleHistory(); |
304 | |
305 | OBJZERO(con)(memset(&((con)), (0), sizeof((con)))); |
306 | con.lineWidth = VID_NORM_WIDTH1024 / con_fontWidth; |
307 | con.totalLines = lengthof(con.text)(sizeof(con.text) / sizeof(*(con.text))) / con.lineWidth; |
308 | con.initialized = true; |
309 | |
310 | Com_Printf("Console initialized.\n"); |
311 | } |
312 | |
313 | |
314 | static void Con_Linefeed (void) |
315 | { |
316 | int i; |
317 | |
318 | con.pos = 0; |
319 | if (con.displayLine == con.currentLine) |
320 | con.displayLine++; |
321 | con.currentLine++; |
322 | |
323 | for (i = 0; i < con.lineWidth; i++) |
324 | con.text[(con.currentLine % con.totalLines) * con.lineWidth + i] = (CON_COLOR_WHITE7 << 8) | ' '; |
325 | } |
326 | |
327 | |
328 | |
329 | |
330 | |
331 | |
332 | |
333 | void Con_Print (const char *txt) |
334 | { |
335 | int y; |
336 | int c, l; |
337 | static bool cr; |
338 | int color; |
339 | |
340 | if (!con.initialized) |
341 | return; |
342 | |
343 | color = CON_COLOR_WHITE7; |
344 | |
345 | while ((c = *txt) != 0) { |
346 | const int charLength = UTF8_char_len(c); |
347 | if (Q_IsColorString(txt)((txt) && *(txt) == '^' && *((txt) + 1) && isalnum(*((txt) + 1))) ) { |
348 | color = ColorIndex(*(txt + 1))(((*(txt + 1)) - '0') & 0x07); |
349 | txt += 2; |
350 | continue; |
351 | } |
352 | |
353 | |
354 | for (l = 0; l < con.lineWidth; l++) |
355 | if (txt[l] <= ' ') |
356 | break; |
357 | |
358 | |
359 | if (l != con.lineWidth && (con.pos + l > con.lineWidth)) |
360 | con.pos = 0; |
361 | |
362 | txt += charLength; |
363 | |
364 | if (cr) { |
365 | con.currentLine--; |
366 | cr = false; |
367 | } |
368 | |
369 | if (!con.pos) { |
370 | Con_Linefeed(); |
371 | } |
372 | |
373 | if (charLength > 1) |
374 | c = '.'; |
375 | |
376 | switch (c) { |
377 | case '\n': |
378 | con.pos = 0; |
379 | color = CON_COLOR_WHITE7; |
380 | break; |
381 | |
382 | case '\r': |
383 | con.pos = 0; |
384 | color = CON_COLOR_WHITE7; |
385 | cr = true; |
386 | break; |
387 | |
388 | default: |
389 | y = con.currentLine % con.totalLines; |
390 | con.text[y * con.lineWidth + con.pos] = (color << 8) | c; |
391 | con.pos++; |
392 | if (con.pos >= con.lineWidth) |
393 | con.pos = 0; |
394 | break; |
395 | } |
396 | } |
397 | } |
398 | |
399 | |
400 | |
401 | |
402 | |
403 | |
404 | |
405 | |
406 | |
407 | |
408 | void Con_Close (void) |
409 | { |
410 | if (cls.keyDest == key_console) |
411 | Key_SetDest(key_game); |
412 | } |
413 | |
414 | |
415 | |
416 | |
417 | static void Con_DrawInput (void) |
418 | { |
419 | int y; |
420 | unsigned int i; |
421 | short editlinecopy[MAXCMDLINE256], *text; |
422 | const size_t size = lengthof(editlinecopy)(sizeof(editlinecopy) / sizeof(*(editlinecopy))); |
423 | |
424 | if (cls.keyDest != key_console && cls.state == ca_active) |
425 | return; |
426 | |
427 | y = 0; |
428 | for (i = 0; i < size; i++) { |
429 | editlinecopy[i] = (CON_COLOR_WHITE7 << 8) | keyLines[editLine][i]; |
430 | if (keyLines[editLine][i] == '\0') |
431 | break; |
432 | y++; |
433 | } |
434 | text = editlinecopy; |
435 | |
436 | |
437 | if ((int)(CL_Milliseconds() >> 8) & 1) { |
438 | text[keyLinePos] = (CON_COLOR_WHITE7 << 8) | CONSOLE_CURSOR_CHAR11; |
439 | if (keyLinePos == y) |
440 | y++; |
441 | } |
442 | |
443 | |
444 | for (i = y; i < size; i++) |
445 | text[i] = (CON_COLOR_WHITE7 << 8) | ' '; |
446 | |
447 | |
448 | if (keyLinePos >= con.lineWidth) |
449 | text += 1 + keyLinePos - con.lineWidth; |
450 | |
451 | |
452 | y = con.visLines - con_fontHeight; |
453 | |
454 | Con_DrawText(text, 0, y - CONSOLE_CHAR_ALIGN4, con.lineWidth); |
455 | } |
456 | |
457 | |
458 | |
459 | |
460 | void Con_DrawConsole (float frac) |
461 | { |
462 | int i, x, y; |
463 | int rows, row; |
464 | unsigned int lines; |
465 | short *text; |
466 | char consoleMessage[128]; |
467 | |
468 | lines = viddef.context.height * frac; |
469 | if (lines == 0) |
470 | return; |
471 | |
472 | if (lines > viddef.context.height) |
473 | lines = viddef.context.height; |
474 | |
475 | |
476 | if (con_background->integer) |
477 | R_DrawStretchImage(0, viddef.virtualHeight * (frac - 1) , viddef.virtualWidth, viddef.virtualHeight, R_FindImage("pics/background/conback", it_pic)); |
478 | |
479 | Com_sprintf(consoleMessage, sizeof(consoleMessage), "Hit esc to close - v%s", UFO_VERSION"2.5-dev"); |
480 | { |
481 | const int len = strlen(consoleMessage); |
482 | const int versionX = viddef.context.width - (len * con_fontWidth) - CONSOLE_CHAR_ALIGN4; |
483 | const int versionY = lines - (con_fontHeight + CONSOLE_CHAR_ALIGN4); |
484 | const uint32_t color = g_color_table[CON_COLOR_WHITE7]; |
485 | |
486 | for (x = 0; x < len; x++) |
487 | R_DrawChar(versionX + x * con_fontWidth, versionY, consoleMessage[x], color); |
488 | } |
489 | |
490 | |
491 | con.visLines = lines; |
492 | |
493 | rows = (lines - con_fontHeight * 2) >> con_fontShift; |
494 | |
495 | y = lines - con_fontHeight * 3; |
496 | |
497 | |
498 | if (con.displayLine != con.currentLine) { |
499 | const uint32_t color = g_color_table[CON_COLOR_GREEN2]; |
500 | |
501 | for (x = 0; x < con.lineWidth; x += 4) |
502 | R_DrawChar((x + 1) << con_fontShift, y, '^', color); |
503 | |
504 | y -= con_fontHeight; |
505 | rows--; |
506 | } |
507 | |
508 | row = con.displayLine; |
509 | for (i = 0; i < rows; i++, y -= con_fontHeight, row--) { |
510 | if (row < 0) |
511 | break; |
512 | if (con.currentLine - row >= con.totalLines) |
513 | break; |
514 | |
515 | text = con.text + (row % con.totalLines) * con.lineWidth; |
516 | |
517 | Con_DrawText(text, 0, y, con.lineWidth); |
518 | } |
519 | |
520 | |
521 | Con_DrawInput(); |
522 | } |