1 /*
2 * Copyright (C) 1997-2001 Id Software, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.
12 *
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59
17 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 *
19 */
20 /* console.c */
21
22 #include "client.h"
23
24 console_t con;
25
26 cvar_t *con_notify;
27 cvar_t *con_notifytime;
28 cvar_t *con_transparency;
29
30
31 #define MAXCMDLINE 256
32 extern char key_lines[32][MAXCMDLINE];
33 extern int edit_line;
34 extern int key_linepos;
35
36
37 void
DrawString(int x,int y,char * s)38 DrawString(int x, int y, char *s)
39 {
40 while (*s) {
41 re.DrawChar(x, y, *s, 256);
42 x += 8;
43 s++;
44 }
45 }
46
47 void
DrawAltString(int x,int y,char * s)48 DrawAltString(int x, int y, char *s)
49 {
50 while (*s) {
51 re.DrawChar(x, y, *s ^ 0x80, 256);
52 x += 8;
53 s++;
54 }
55 }
56
57
58 void
Key_ClearTyping(void)59 Key_ClearTyping(void)
60 {
61 key_lines[edit_line][1] = 0; /* clear any typing */
62 key_linepos = 1;
63 }
64
65 /*
66 * ================ Con_ToggleConsole_f ================
67 */
68 void
Con_ToggleConsole_f(void)69 Con_ToggleConsole_f(void)
70 {
71 SCR_EndLoadingPlaque(); /* get rid of loading plaque */
72
73 if (cl.attractloop) {
74 Cbuf_AddText("killserver\n");
75 return;
76 }
77 if (cls.state == ca_disconnected) { /* start the demo loop again */
78 Cbuf_AddText("d1\n");
79 return;
80 }
81 Key_ClearTyping();
82 Con_ClearNotify();
83
84 if (cls.key_dest == key_console) {
85 M_ForceMenuOff();
86 Cvar_Set("paused", "0");
87 } else {
88 M_ForceMenuOff();
89 cls.key_dest = key_console;
90
91 if (Cvar_VariableValue("maxclients") == 1
92 && Com_ServerState())
93 Cvar_Set("paused", "1");
94 }
95 }
96
97 /*
98 * ================ Con_ToggleChat_f ================
99 */
100 void
Con_ToggleChat_f(void)101 Con_ToggleChat_f(void)
102 {
103 Key_ClearTyping();
104 if (cls.key_dest == key_console)
105 {
106 if (cls.state == ca_active) {
107 M_ForceMenuOff();
108 cls.key_dest = key_game;
109 }
110 } else
111 cls.key_dest = key_console;
112 Con_ClearNotify();
113 }
114
115 /*
116 * ================ Con_Clear_f ================
117 */
118 void
Con_Clear_f(void)119 Con_Clear_f(void)
120 {
121 memset(con.text, ' ', CON_TEXTSIZE);
122 }
123
124
125 /*
126 * ================
127 * Con_Dump_f
128 *
129 * Save the console contents out to a file.
130 * ================
131 */
132 void
Con_Dump_f(void)133 Con_Dump_f(void)
134 {
135 int l, x;
136 char *line;
137 FILE *f;
138 char buffer[1024];
139 char name[MAX_OSPATH];
140
141 if (Cmd_Argc() != 2) {
142 Com_Printf("usage: condump <filename>\n");
143 return;
144 }
145 Com_sprintf(name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));
146
147 Com_Printf("Dumped console text to %s.\n", name);
148 FS_CreatePath(name);
149 f = fopen(name, "w");
150 if (!f) {
151 Com_Printf("ERROR: couldn't open.\n");
152 return;
153 }
154 /* skip empty lines */
155 for (l = con.current - con.totallines + 1; l <= con.current; l++) {
156 line = con.text + (l % con.totallines) * con.linewidth;
157 for (x = 0; x < con.linewidth; x++)
158 if (line[x] != ' ')
159 break;
160 if (x != con.linewidth)
161 break;
162 }
163
164 /* write the remaining lines */
165 buffer[con.linewidth] = 0;
166 for (; l <= con.current; l++) {
167 line = con.text + (l % con.totallines) * con.linewidth;
168 Q_strncpyz(buffer, line, con.linewidth);
169 for (x = con.linewidth - 1; x >= 0; x--) {
170 if (buffer[x] == ' ')
171 buffer[x] = 0;
172 else
173 break;
174 }
175 for (x = 0; buffer[x]; x++)
176 buffer[x] &= 0x7f;
177
178 fprintf(f, "%s\n", buffer);
179 }
180
181 fclose(f);
182 }
183
184
185 /*
186 * ================ Con_ClearNotify ================
187 */
188 void
Con_ClearNotify(void)189 Con_ClearNotify(void)
190 {
191 int i;
192
193 for (i = 0; i < NUM_CON_TIMES; i++)
194 con.times[i] = 0;
195 }
196
197
198 /*
199 * ================ Con_MessageMode_f ================
200 */
201 void
Con_MessageMode_f(void)202 Con_MessageMode_f(void)
203 {
204 chat_team = false;
205 cls.key_dest = key_message;
206 }
207
208 /*
209 * ================ Con_MessageMode2_f ================
210 */
211 void
Con_MessageMode2_f(void)212 Con_MessageMode2_f(void)
213 {
214 chat_team = true;
215 cls.key_dest = key_message;
216 }
217
218 /*
219 * ================ Con_CheckResize
220 *
221 * If the line width has changed, reformat the buffer. ================
222 */
223 void
Con_CheckResize(void)224 Con_CheckResize(void)
225 {
226 int i , j, width, oldwidth, oldtotallines, numlines, numchars;
227 char tbuf [CON_TEXTSIZE];
228
229 width = (viddef.width >> 3) - 2;
230
231 if (width == con.linewidth)
232 return;
233
234 if (width < 1) { /* video hasn't been initialized yet */
235 width = 38;
236 con.linewidth = width;
237 con.totallines = CON_TEXTSIZE / con.linewidth;
238 memset(con.text, ' ', CON_TEXTSIZE);
239 } else {
240 oldwidth = con.linewidth;
241 con.linewidth = width;
242 oldtotallines = con.totallines;
243 con.totallines = CON_TEXTSIZE / con.linewidth;
244 numlines = oldtotallines;
245
246 if (con.totallines < numlines)
247 numlines = con.totallines;
248
249 numchars = oldwidth;
250
251 if (con.linewidth < numchars)
252 numchars = con.linewidth;
253
254 memcpy(tbuf, con.text, CON_TEXTSIZE);
255 memset(con.text, ' ', CON_TEXTSIZE);
256
257 for (i = 0; i < numlines; i++) {
258 for (j = 0; j < numchars; j++) {
259 con.text[(con.totallines - 1 - i) * con.linewidth + j] =
260 tbuf[((con.current - i + oldtotallines) %
261 oldtotallines) * oldwidth + j];
262 }
263 }
264
265 Con_ClearNotify();
266 }
267
268 con.current = con.totallines - 1;
269 con.display = con.current;
270 }
271
272
273 /*
274 * ================ Con_Init ================
275 */
276 void
Con_Init(void)277 Con_Init(void)
278 {
279 con.linewidth = -1;
280
281 Con_CheckResize();
282
283 Com_Printf("Console initialized.\n");
284
285 //
286 /* register our commands */
287 //
288 con_notify = Cvar_Get("con_notify", "1", 0);
289 con_notifytime = Cvar_Get("con_notifytime", "3", 0);
290 con_transparency = Cvar_Get("con_transparency", "0.5", CVAR_ARCHIVE);
291
292 Cmd_AddCommand("toggleconsole", Con_ToggleConsole_f);
293 Cmd_AddCommand("togglechat", Con_ToggleChat_f);
294 Cmd_AddCommand("messagemode", Con_MessageMode_f);
295 Cmd_AddCommand("messagemode2", Con_MessageMode2_f);
296 Cmd_AddCommand("clear", Con_Clear_f);
297 Cmd_AddCommand("condump", Con_Dump_f);
298 con.initialized = true;
299 }
300
301
302 /*
303 * =============== Con_Linefeed ===============
304 */
305 void
Con_Linefeed(void)306 Con_Linefeed(void)
307 {
308 con.x = 0;
309 if (con.display == con.current)
310 con.display++;
311 con.current++;
312 memset(&con.text[(con.current % con.totallines) * con.linewidth]
313 ,' ', con.linewidth);
314 }
315
316 /*
317 * ================ Con_Print
318 *
319 * Handles cursor positioning, line wrapping, etc All console printing must go
320 * through this in order to be logged to disk If no console is visible, the
321 * text will appear at the top of the game window ================
322 */
323 void
Con_Print(char * txt)324 Con_Print(char *txt)
325 {
326 int y;
327 int c , l;
328 static int cr;
329 int mask;
330
331 if (!con.initialized)
332 return;
333
334 if (txt[0] == 1 || txt[0] == 2) {
335 mask = 128; /* go to colored text */
336 txt++;
337 } else
338 mask = 0;
339
340
341 while ((c = *txt)) {
342 /* count word length */
343 for (l = 0; l < con.linewidth; l++)
344 if (txt[l] <= ' ')
345 break;
346
347 /* word wrap */
348 if (l != con.linewidth && (con.x + l > con.linewidth))
349 con.x = 0;
350
351 txt++;
352
353 if (cr) {
354 con.current--;
355 cr = false;
356 }
357 if (!con.x) {
358 Con_Linefeed();
359 /* mark time for transparent overlay */
360 if (con.current >= 0)
361 con.times[con.current % NUM_CON_TIMES] = cls.realtime;
362 }
363 switch (c) {
364 case '\n':
365 con.x = 0;
366 break;
367
368 case '\r':
369 con.x = 0;
370 cr = 1;
371 break;
372
373 default: /* display character and advance */
374 y = con.current % con.totallines;
375 con.text[y * con.linewidth + con.x] = c | mask | con.ormask;
376 con.x++;
377 if (con.x >= con.linewidth)
378 con.x = 0;
379 break;
380 }
381
382 }
383 }
384
385
386 /*
387 * ============== Con_CenteredPrint ==============
388 */
389 void
Con_CenteredPrint(char * text)390 Con_CenteredPrint(char *text)
391 {
392 int l;
393 char buffer[1024];
394
395 l = strlen(text);
396 l = (con.linewidth - l) / 2;
397 if (l < 0)
398 l = 0;
399 memset(buffer, ' ', l);
400 Q_strncpyz(buffer + l, text, sizeof(buffer));
401 strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
402 Con_Print(buffer);
403 }
404
405 /*
406 *
407 * ============================================================================
408 *
409 * DRAWING
410 *
411 * ======================================================================
412 */
413
414
415 /*
416 * ================
417 * Con_DrawInput
418 *
419 * The input line scrolls horizontally if typing goes beyond the right edge.
420 * ================
421 */
422 void
Con_DrawInput(void)423 Con_DrawInput(void)
424 {
425 int y;
426 int i;
427 char *text;
428
429 if (cls.key_dest == key_menu)
430 return;
431 if (cls.key_dest != key_console && cls.state == ca_active)
432 return; /* don't draw anything (always draw if not active) */
433 text = key_lines[edit_line];
434
435 /* add the cursor frame */
436 text[key_linepos] = 10 + ((int)(cls.realtime >> 8) & 1);
437
438 /* fill out remainder with spaces */
439 for (i = key_linepos + 1; i < con.linewidth; i++)
440 text[i] = ' ';
441
442 /* prestep if horizontally scrolling */
443 if (key_linepos >= con.linewidth)
444 text += 1 + key_linepos - con.linewidth;
445
446 /* draw it */
447 y = con.vislines - 16;
448
449 for (i = 0; i < con.linewidth; i++)
450 re.DrawChar((i + 1) << 3, con.vislines - 22, text[i], 256);
451
452 /* remove cursor */
453 key_lines[edit_line][key_linepos] = 0;
454 }
455
456
457 /*
458 * ================ Con_DrawNotify
459 *
460 * Draws the last few lines of output transparently over the game top
461 * ================
462 */
463 void
Con_DrawNotify(void)464 Con_DrawNotify(void)
465 {
466 int x , v;
467 char *text;
468 int i;
469 int time;
470 char *s;
471 int skip;
472
473 v = 0;
474 for (i = con.current - NUM_CON_TIMES + 1; i <= con.current; i++) {
475 if (i < 0)
476 continue;
477 time = con.times[i % NUM_CON_TIMES];
478 if (time == 0)
479 continue;
480 time = cls.realtime - time;
481 if (time > con_notifytime->value * 1000)
482 continue;
483 text = con.text + (i % con.totallines) * con.linewidth;
484
485 for (x = 0; x < con.linewidth; x++)
486 if (con_notify->value)
487 re.DrawChar((x + 1) << 3, v, text[x], 256);
488 v += 8;
489 }
490
491
492 if (cls.key_dest == key_message) {
493 if (chat_team) {
494 DrawString(8, v, "say_team:");
495 skip = 11;
496 } else {
497 DrawString(8, v, "say:");
498 skip = 5;
499 }
500
501 s = chat_buffer;
502 if (chat_bufferlen > (viddef.width >> 3) - (skip + 1))
503 s += chat_bufferlen - ((viddef.width >> 3) - (skip + 1));
504 x = 0;
505 while (s[x]) {
506 re.DrawChar((x + skip) << 3, v, s[x], 256);
507 x++;
508 }
509 re.DrawChar((x + skip) << 3, v, 10 + ((cls.realtime >> 8) & 1), 256);
510 v += 8;
511 }
512 if (v) {
513 SCR_AddDirtyPoint(0, 0);
514 SCR_AddDirtyPoint(viddef.width - 1, v);
515 }
516 }
517
518 /*
519 * ================ Con_DrawConsole
520 *
521 * Draws the console with the solid background ================
522 */
523 void
Con_DrawConsole(float frac,qboolean in_game)524 Con_DrawConsole(float frac, qboolean in_game)
525 {
526 int i, j, x, y, n;
527 int rows;
528 char *text;
529 int row;
530 int lines;
531 char version[64];
532 char dlbar[1024];
533 float alpha;
534 char *conback_image;
535 int length;
536
537 struct tm *ntime;
538 char stime[32];
539 time_t l_time;
540
541 lines = viddef.height * frac;
542 if (lines <= 0)
543 return;
544
545 if (lines > viddef.height)
546 lines = viddef.height;
547
548 if (!in_game)
549 alpha = 1;
550 else
551 alpha = con_transparency->value;
552
553 /* draw the background */
554
555 conback_image = cl_conback_image->string;
556
557 re.DrawStretchPic(0, -viddef.height + lines, viddef.width, viddef.height, conback_image, alpha);
558 SCR_AddDirtyPoint(0, 0);
559 SCR_AddDirtyPoint(viddef.width - 1, lines - 1);
560
561 Com_sprintf(version, sizeof(version), "%s", QUDOS_VERSION);
562 length = strlen(QUDOS_VERSION);
563 for (x = 0; x < length; x++)
564 re.DrawChar(viddef.width - 4 -length * 8 + x * 8, lines - 12, 128 + QUDOS_VERSION[x], 256);
565
566 time(&l_time);
567 ntime = localtime(&l_time);
568 strftime(stime, sizeof(stime), "%a, %d %b %Y - %H:%M:%S", ntime);
569 for (x = 0; x < 50; ++x)
570 re.DrawChar(viddef.width - 215 + x * 8, 0, 128 + stime[x], 256);
571
572 /* draw the text */
573 con.vislines = lines;
574 rows = (lines - 22) >> 3; /* rows of text to draw */
575
576 y = lines - 30;
577
578 /* draw from the bottom up */
579 if (con.display != con.current) {
580 /* draw arrows to show the buffer is backscrolled */
581 for (x = 0; x < con.linewidth; x += 4)
582 re.DrawChar((x + 1) << 3, y, '^', 256);
583 y -= 8;
584 rows--;
585 }
586 row = con.display;
587 for (i = 0; i < rows; i++, y -= 8, row--) {
588 if (row < 0)
589 break;
590 if (con.current - row >= con.totallines)
591 break; /* past scrollback wrap point */
592
593 text = con.text + (row % con.totallines) * con.linewidth;
594
595 for (x = 0; x < con.linewidth; x++)
596 re.DrawChar((x + 1) << 3, y, text[x], 256);
597 }
598
599 /* ZOID */
600 /* draw the download bar */
601 /* figure out width */
602 if (cls.download) {
603 if ((text = strrchr(cls.downloadname, '/')) != NULL)
604 text++;
605 else
606 text = cls.downloadname;
607
608 x = con.linewidth - ((con.linewidth * 7) / 40);
609 y = x - strlen(text) - 8;
610 i = con.linewidth / 3;
611 if (strlen(text) > i) {
612 y = x - i - 11;
613 Q_strncpyz(dlbar, text, i);
614 strncat(dlbar, "...", sizeof(dlbar) - strlen(dlbar) - 1);
615 } else
616 Q_strncpyz(dlbar, text, sizeof(dlbar));
617 strncat(dlbar, ": ", sizeof(dlbar) - strlen(dlbar) - 1);
618 i = strlen(dlbar);
619 dlbar[i++] = '\x80';
620 /* where's the dot go? */
621 if (cls.downloadpercent == 0)
622 n = 0;
623 else
624 n = y * cls.downloadpercent / 100;
625
626 for (j = 0; j < y; j++)
627 if (j == n)
628 dlbar[i++] = '\x83';
629 else
630 dlbar[i++] = '\x81';
631 dlbar[i++] = '\x82';
632 dlbar[i] = 0;
633
634 Com_sprintf(dlbar + strlen(dlbar), sizeof(dlbar) - strlen(dlbar), " %02d%%", cls.downloadpercent);
635
636 /* draw it */
637 y = con.vislines - 12;
638 for (i = 0; i < strlen(dlbar); i++)
639 re.DrawChar((i + 1) << 3, y, dlbar[i], 256);
640 }
641 /* ZOID */
642
643 /* draw the input prompt, user text, and cursor if desired */
644 Con_DrawInput();
645 }
646