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