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 #include <time.h>
24
25 console_t con;
26
27 cvar_t *con_notifytime;
28
29 extern char key_lines[32][MAXCMDLINE];
30 extern int edit_line;
31 extern int key_linepos;
32
33
DrawString(int x,int y,const char * s)34 void DrawString (int x, int y, const char *s)
35 {
36 //r1: don't draw if obscured
37 if (viddef.height * scr_conlines > y)
38 return;
39
40 while (*s)
41 {
42 re.DrawChar (x, y, *s);
43 x+=8;
44 s++;
45 }
46 }
47
DrawAltString(int x,int y,const char * s)48 void DrawAltString (int x, int y, const char *s)
49 {
50 //r1: don't draw if obscured
51 if (viddef.height * scr_conlines > y)
52 return;
53
54 while (*s)
55 {
56 re.DrawChar (x, y, *s ^ 0x80);
57 x+=8;
58 s++;
59 }
60 }
61
62
Key_ClearTyping(void)63 void Key_ClearTyping (void)
64 {
65 key_lines[edit_line][1] = 0; // clear any typing
66 key_linepos = 1;
67 }
68
69 /*
70 ================
71 Con_ToggleConsole_f
72 ================
73 */
Con_ToggleConsole_f(void)74 void Con_ToggleConsole_f (void)
75 {
76 SCR_EndLoadingPlaque (); // get rid of loading plaque
77
78 //r1: fucking annoying.
79 /*if (cl.attractloop)
80 {
81 cl.attractloop = false;
82 Cmd_ExecuteString ("disconnect\n");
83 return;
84 }*/
85
86 if (cls.state == ca_disconnected)
87 { // start the demo loop again
88
89 //r1: fucking annoying.
90 //Cbuf_AddText ("d1\n");
91 cls.key_dest = key_console;
92 return;
93 }
94
95 Key_ClearTyping ();
96 Con_ClearNotify ();
97
98 if (cls.key_dest == key_console)
99 {
100 M_ForceMenuOff ();
101 //if (chat_bufferlen)
102 // cls.key_dest = key_message;
103 //Cvar_Set ("paused", "0");
104 }
105 else
106 {
107 M_ForceMenuOff ();
108 cls.key_dest = key_console;
109
110 /*if (Cvar_VariableValue ("maxclients") == 1
111 && Com_ServerState ())
112 Cvar_Set ("paused", "1");*/
113 }
114 }
115
116 /*
117 ================
118 Con_ToggleChat_f
119 ================
120 */
Con_ToggleChat_f(void)121 void Con_ToggleChat_f (void)
122 {
123 Key_ClearTyping ();
124
125 if (cls.key_dest == key_console)
126 {
127 if (cls.state == ca_active)
128 {
129 M_ForceMenuOff ();
130 cls.key_dest = key_game;
131 }
132 }
133 else
134 cls.key_dest = key_console;
135
136 Con_ClearNotify ();
137 }
138
139 /*
140 ================
141 Con_Clear_f
142 ================
143 */
Con_Clear_f(void)144 static void Con_Clear_f (void)
145 {
146 memset (con.text, ' ', sizeof(con.text));
147 }
148
149
150 /*
151 ================
152 Con_Dump_f
153
154 Save the console contents out to a file
155 ================
156 */
Con_Dump_f(void)157 static void Con_Dump_f (void)
158 {
159 int l, x;
160 char *line;
161 FILE *f;
162 char buffer[1024];
163 char name[MAX_OSPATH];
164
165 if (Cmd_Argc() != 2)
166 {
167 Com_Printf ("usage: condump <filename>\n", LOG_CLIENT);
168 return;
169 }
170
171 //r1: consolidate condumps to their own dir
172 Com_sprintf (name, sizeof(name), "%s/condumps/%s.txt", FS_Gamedir(), Cmd_Argv(1));
173
174 if (strstr (Cmd_Argv(1), "..") || strchr (Cmd_Argv(1), '/') || strchr (Cmd_Argv(1), '\\') )
175 {
176 Com_Printf ("Illegal filename.\n", LOG_CLIENT);
177 return;
178 }
179
180 FS_CreatePath (name);
181 f = fopen (name, "w");
182 if (!f)
183 {
184 Com_Printf ("ERROR: couldn't open.\n", LOG_CLIENT);
185 return;
186 }
187
188 //r1: save some basic infos
189 if (cls.state == ca_active) {
190 time_t timet;
191 time(&timet);
192 fprintf (f, "%s on server %s, map %s.\n-----------------\n", ctime (&timet), cls.servername, cl.configstrings[CS_MODELS+1] + 5);
193 }
194
195 // skip empty lines
196 for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
197 {
198 line = con.text + (l%con.totallines)*con.linewidth;
199 for (x=0 ; x<con.linewidth ; x++)
200 if (line[x] != ' ')
201 break;
202 if (x != con.linewidth)
203 break;
204 }
205
206 // write the remaining lines
207 buffer[con.linewidth] = 0;
208 for ( ; l <= con.current ; l++)
209 {
210 line = con.text + (l%con.totallines)*con.linewidth;
211 strncpy (buffer, line, con.linewidth);
212 for (x=con.linewidth-1 ; x>=0 ; x--)
213 {
214 if (buffer[x] == ' ')
215 buffer[x] = 0;
216 else
217 break;
218 }
219 for (x=0; buffer[x]; x++)
220 buffer[x] &= 0x7f;
221
222 fprintf (f, "%s\n", buffer);
223 }
224
225 Com_Printf ("Dumped console text to %s.\n", LOG_CLIENT, name);
226
227 fclose (f);
228 }
229
230
231 /*
232 ================
233 Con_ClearNotify
234 ================
235 */
Con_ClearNotify(void)236 void Con_ClearNotify (void)
237 {
238 /*int i;
239
240 for (i=0 ; i<NUM_CON_TIMES ; i++)
241 con.times[i] = 0;*/
242 memset (&con.times, 0, sizeof (con.times));
243 }
244
245
246 /*
247 ================
248 Con_MessageMode_f
249 ================
250 */
Con_MessageMode_f(void)251 static void Con_MessageMode_f (void)
252 {
253 if (cls.state != ca_active)
254 return;
255
256 chat_mode = CHAT_MODE_PUBLIC;
257
258 if (!chat_bufferlen && Cmd_Argc() > 1)
259 {
260 Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args(), sizeof(chat_buffer[chat_curbuffer])-2);
261 if (chat_buffer[chat_curbuffer][0])
262 {
263 strcat (chat_buffer[chat_curbuffer], " ");
264 chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
265 chat_cursorpos = chat_bufferlen;
266 }
267 }
268
269 cls.key_dest = key_message;
270 }
271
272 /*
273 ================
274 Con_MessageMode2_f
275 ================
276 */
Con_MessageMode2_f(void)277 static void Con_MessageMode2_f (void)
278 {
279 if (cls.state != ca_active)
280 return;
281
282 chat_mode = CHAT_MODE_TEAM;
283
284 if (!chat_bufferlen && Cmd_Argc() > 1)
285 {
286 Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args(), sizeof(chat_buffer[chat_curbuffer])-2);
287 if (chat_buffer[chat_curbuffer][0])
288 {
289 strcat (chat_buffer[chat_curbuffer], " ");
290 chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
291 chat_cursorpos = chat_bufferlen;
292 }
293 }
294
295 cls.key_dest = key_message;
296 }
297
Con_MessageModex_f(void)298 static void Con_MessageModex_f (void)
299 {
300 if (cls.state != ca_active)
301 return;
302
303 if (Cmd_Argc() < 2)
304 {
305 Com_Printf ("Usage: messagemodex prompt [default]\n", LOG_GENERAL);
306 return;
307 }
308
309 chat_mode = CHAT_MODE_CUSTOM;
310
311 strncpy (chat_custom_prompt, Cmd_Argv(1), sizeof(chat_custom_prompt)-3);
312 strcpy (chat_custom_cmd, chat_custom_prompt);
313 strcat (chat_custom_prompt, ": ");
314
315 if (!chat_bufferlen && Cmd_Argc() > 2)
316 {
317 Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args2(2), sizeof(chat_buffer[chat_curbuffer])-2);
318 if (chat_buffer[chat_curbuffer][0])
319 {
320 strcat (chat_buffer[chat_curbuffer], " ");
321 chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
322 chat_cursorpos = chat_bufferlen;
323 }
324 }
325
326 cls.key_dest = key_message;
327 }
328
Con_Resize(int width)329 static void Con_Resize (int width)
330 {
331 char tbuf[CON_TEXTSIZE];
332 int i, j, oldwidth, oldtotallines, numlines, numchars;
333
334 oldwidth = con.linewidth;
335 con.linewidth = width;
336 oldtotallines = con.totallines;
337 con.totallines = CON_TEXTSIZE / con.linewidth;
338 numlines = oldtotallines;
339
340 if (con.totallines < numlines)
341 numlines = con.totallines;
342
343 numchars = oldwidth;
344
345 if (con.linewidth < numchars)
346 numchars = con.linewidth;
347
348 memcpy (tbuf, con.text, CON_TEXTSIZE);
349 memset (con.text, ' ', CON_TEXTSIZE);
350
351 for (i=0 ; i<numlines ; i++)
352 {
353 for (j=0 ; j<numchars ; j++)
354 {
355 con.text[(con.totallines - 1 - i) * con.linewidth + j] =
356 tbuf[((con.current - i + oldtotallines) %
357 oldtotallines) * oldwidth + j];
358 }
359 }
360 }
361 /*
362 ================
363 Con_CheckResize
364
365 If the line width has changed, reformat the buffer.
366 ================
367 */
Con_CheckResize(void)368 void Con_CheckResize (void)
369 {
370 int width;
371
372 width = (viddef.width >> 3) - 2;
373
374 if (width == con.linewidth)
375 return;
376
377 if (width < 1) // video hasn't been initialized yet
378 {
379 width = 38;
380 con.linewidth = width;
381 con.totallines = sizeof(con.text) / con.linewidth;
382 memset (con.text, ' ', sizeof(con.text));
383 }
384 else
385 {
386 Con_Resize (width);
387 Con_ClearNotify ();
388 }
389
390 con.current = con.totallines - 1;
391 con.display = con.current;
392 }
393
394
395 /*
396 ================
397 Con_Init
398 ================
399 */
Con_Init(void)400 void Con_Init (void)
401 {
402 con.linewidth = -1;
403
404 Con_CheckResize ();
405
406 Com_Printf ("Console initialized.\n", LOG_CLIENT);
407
408 cls.key_dest = key_console;
409
410 //
411 // register our commands
412 //
413 con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
414
415 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
416 Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
417 Cmd_AddCommand ("messagemode", Con_MessageMode_f);
418 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
419 Cmd_AddCommand ("messagemodex", Con_MessageModex_f);
420 Cmd_AddCommand ("clear", Con_Clear_f);
421 Cmd_AddCommand ("condump", Con_Dump_f);
422 con.initialized = true;
423 }
424
425
426 /*
427 ===============
428 Con_Linefeed
429 ===============
430 */
Con_Linefeed(void)431 static void Con_Linefeed (void)
432 {
433 con.x = 0;
434 if (con.display == con.current)
435 con.display++;
436 con.current++;
437 memset (&con.text[(con.current%con.totallines)*con.linewidth]
438 , ' ', con.linewidth);
439 }
440
441 /*
442 ================
443 Con_Print
444
445 Handles cursor positioning, line wrapping, etc
446 All console printing must go through this in order to be logged to disk
447 If no console is visible, the text will appear at the top of the game window
448 ================
449 */
Con_Print(const char * txt)450 void Con_Print (const char *txt)
451 {
452 int y;
453 int c, l;
454 static int cr;
455 int mask;
456
457 if (!con.initialized)
458 return;
459
460 if (txt[0] == 1 || txt[0] == 2)
461 {
462 mask = 128; // go to colored text
463 txt++;
464 }
465 else
466 mask = 0;
467
468
469 while ( (c = *txt) )
470 {
471 // count word length
472 for (l=0 ; l< con.linewidth ; l++)
473 if ( txt[l] <= ' ')
474 break;
475
476 // word wrap
477 if (l != con.linewidth && (con.x + l > con.linewidth) )
478 con.x = 0;
479
480 txt++;
481
482 if (cr)
483 {
484 con.current--;
485 cr = false;
486 }
487
488
489 if (!con.x)
490 {
491 Con_Linefeed ();
492 // mark time for transparent overlay
493 if (con.current >= 0)
494 con.times[con.current % NUM_CON_TIMES] = cls.realtime;
495 }
496
497 switch (c)
498 {
499 case '\n':
500 con.x = 0;
501 break;
502
503 case '\r':
504 con.x = 0;
505 cr = 1;
506 break;
507
508 default: // display character and advance
509 y = con.current % con.totallines;
510 con.text[y*con.linewidth+con.x] = c | mask | con.ormask;
511 con.x++;
512 if (con.x >= con.linewidth)
513 con.x = 0;
514 break;
515 }
516
517 }
518 }
519
520
521 /*
522 ==============
523 Con_CenteredPrint
524 ==============
525 */
526 /*void Con_CenteredPrint (char *text)
527 {
528 int l;
529 char buffer[1024];
530
531 l = strlen(text);
532 l = (con.linewidth-l)/2;
533 if (l < 0)
534 l = 0;
535 memset (buffer, ' ', l);
536 strcpy (buffer+l, text);
537 strcat (buffer, "\n");
538 Con_Print (buffer);
539 }*/
540
541 /*
542 ==============================================================================
543
544 DRAWING
545
546 ==============================================================================
547 */
548
549
550 /*
551 ================
552 Con_DrawInput
553
554 The input line scrolls horizontally if typing goes beyond the right edge
555 ================
556 */
Con_DrawInput(void)557 static void Con_DrawInput (void)
558 {
559 int linepos;
560 int length;
561 int i;
562 char *text;
563
564 if (cls.key_dest == key_menu)
565 return;
566 if (cls.key_dest != key_console && cls.state == ca_active)
567 return; // don't draw anything (always draw if not active)
568
569 text = key_lines[edit_line];
570
571 // add the cursor frame
572 //text[key_linepos] = 10+((int)(cls.realtime>>8)&1);
573
574 // fill out remainder with spaces
575 //for (i=key_linepos+1 ; i< con.linewidth ; i++)
576 // text[i] = ' ';
577
578 // prestep if horizontally scrolling
579 if (key_linepos + 1 >= con.linewidth)
580 {
581 linepos = con.linewidth;
582 text += 1 + key_linepos - con.linewidth;
583 }
584 else
585 {
586 linepos = key_linepos + 1;
587 }
588
589 // draw it
590 //y = con.vislines-16;
591
592 length = (int)strlen (text);
593
594 for (i=0 ; i<length; i++)
595 re.DrawChar ( (i+1)<<3, con.vislines - 22, text[i]);
596
597 if (((int)(cls.realtime>>8)&1))
598 re.DrawChar ( (linepos)<<3, con.vislines - 21, '_');
599
600 // remove cursor
601 //key_lines[edit_line][key_linepos] = 0;
602 }
603
604
605 /*
606 ================
607 Con_DrawNotify
608
609 Draws the last few lines of output transparently over the game top
610 ================
611 */
Con_DrawNotify(void)612 void Con_DrawNotify (void)
613 {
614 int x, v;
615 const char *text;
616 int i;
617 int time;
618 char *s;
619
620 v = 0;
621 for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
622 {
623 if (i < 0)
624 continue;
625 time = con.times[i % NUM_CON_TIMES];
626 if (time == 0)
627 continue;
628 time = cls.realtime - time;
629 if (time > con_notifytime->intvalue*1000)
630 continue;
631 text = con.text + (i % con.totallines)*con.linewidth;
632
633 for (x = 0 ; x < con.linewidth ; x++)
634 re.DrawChar ( (x+1)<<3, v, text[x]);
635
636 v += 8;
637 }
638
639
640 if (cls.key_dest == key_message)
641 {
642 int cursorpos;
643 int maxwidth;
644 int skip = 0;
645
646 switch (chat_mode)
647 {
648 case CHAT_MODE_PUBLIC:
649 DrawString (8, v, "say:");
650 skip = 5;
651 break;
652 case CHAT_MODE_TEAM:
653 DrawString (8, v, "say_team:");
654 skip = 11;
655 break;
656 case CHAT_MODE_CUSTOM:
657 DrawString (8, v, chat_custom_prompt);
658 skip = (int)strlen (chat_custom_prompt)+1;
659 break;
660 }
661
662 s = chat_buffer[chat_curbuffer];
663
664 maxwidth = (viddef.width>>3);
665
666 if (chat_cursorpos > maxwidth-(skip+1))
667 {
668 s += chat_cursorpos - (maxwidth-(skip+1));
669 cursorpos = maxwidth-1;
670 }
671 else
672 {
673 cursorpos = chat_cursorpos + skip;
674 }
675
676 x = 0;
677 while(s[x])
678 {
679 re.DrawChar ( (x+skip)<<3, v, s[x]);
680 x++;
681 }
682
683 if (((cls.realtime>>8)&1))
684 re.DrawChar ( (cursorpos)<<3, v+1, '_');
685 v += 8;
686 }
687
688 if (v)
689 {
690 SCR_AddDirtyPoint (0,0);
691 SCR_AddDirtyPoint (viddef.width-1, v);
692 }
693 }
694
695 /*
696 ================
697 Con_DrawConsole
698
699 Draws the console with the solid background
700 ================
701 */
Con_DrawConsole(float frac)702 void Con_DrawConsole (float frac)
703 {
704 int i, j, x, y, n, len, offset;
705 int rows;
706 char *text;
707 int row;
708 int lines;
709 char version[24];
710 char dlbar[1024];
711
712 time_t t;
713 struct tm *today;
714
715 lines = (int)(viddef.height * frac);
716
717 if (lines <= 0)
718 return;
719
720 if (lines > viddef.height)
721 lines = viddef.height;
722
723 // draw the background
724 re.DrawStretchPic (0, lines - viddef.height, viddef.width, viddef.height, "conback");
725 SCR_AddDirtyPoint (0,0);
726 SCR_AddDirtyPoint (viddef.width-1,lines-1);
727
728 len = (int)strlen(key_lines[edit_line]);
729
730 i = Com_sprintf (version, sizeof(version), PRODUCTNAMELOWER " " VERSION);
731
732 if (len >= (viddef.width * 0.125f) - (i+2))
733 offset = 20;
734 else
735 offset = 0;
736
737 for (x=i-1; x>=0 ; x--)
738 re.DrawChar (viddef.width-2-(i*8)+x*8, lines-12-offset, 128 + version[x] );
739
740 t = time (NULL);
741 today = localtime(&t);
742
743 i = (int)strftime (version, sizeof(version), "%H:%M:%S", today);
744 for (x=0 ; x<i ; x++)
745 re.DrawChar (viddef.width-66+x*8, lines-22-offset, 128 + version[x] );
746
747 // draw the text
748 con.vislines = lines;
749
750 #if 0
751 rows = (lines-8)>>3; // rows of text to draw
752
753 y = lines - 24;
754 #else
755 rows = (lines-22)>>3; // rows of text to draw
756
757 y = lines - 30;
758 #endif
759
760 // draw from the bottom up
761 if (con.display != con.current)
762 {
763 // draw arrows to show the buffer is backscrolled
764 for (x=0 ; x<con.linewidth ; x+=4)
765 re.DrawChar ( (x+1)<<3, y, '^');
766
767 y -= 8;
768 rows--;
769 }
770
771 row = con.display;
772 for (i=0 ; i<rows ; i++, y-=8, row--)
773 {
774 if (row < 0)
775 break;
776 if (con.current - row >= con.totallines)
777 break; // past scrollback wrap point
778
779 text = con.text + (row % con.totallines)*con.linewidth;
780
781 for (x=0 ; x<con.linewidth ; x++)
782 re.DrawChar ( (x+1)<<3, y, text[x]);
783 }
784
785 //ZOID
786 // draw the download bar
787 // figure out width
788 if (cls.downloadname[0] && (cls.download || cls.downloadposition))
789 {
790 if ((text = strrchr(cls.downloadname, '/')) != NULL)
791 text++;
792 else
793 text = cls.downloadname;
794
795 x = con.linewidth - ((con.linewidth * 7) / 40);
796 y = x - (int)strlen(text) - 20;
797 i = con.linewidth/3;
798 if (strlen(text) > i) {
799 y = x - i - 11;
800 Q_strncpy(dlbar, text, i);
801 strcat(dlbar, "...");
802 } else
803 strcpy(dlbar, text);
804 strcat(dlbar, ": ");
805 i = (int)strlen(dlbar);
806 dlbar[i++] = '\x80';
807 // where's the dot go?
808 if (cls.downloadpercent == 0)
809 n = 0;
810 else
811 n = y * cls.downloadpercent / 100;
812
813 for (j = 0; j < y; j++)
814 if (j == n)
815 dlbar[i++] = '\x83';
816 else
817 dlbar[i++] = '\x81';
818 dlbar[i++] = '\x82';
819
820 if (cls.download)
821 cls.downloadposition = ftell (cls.download);
822
823 sprintf (dlbar + i, " %02d%% (%.02f KB)", cls.downloadpercent, (float)cls.downloadposition / 1024.0);
824
825 j = (int)strlen(dlbar);
826
827 // draw it
828 y = con.vislines-12;
829 for (i = 0; i < j; i++)
830 re.DrawChar ( (i+1)<<3, y, dlbar[i]);
831 }
832 //ZOID
833
834 // draw the input prompt, user text, and cursor if desired
835 Con_DrawInput ();
836 }
837
838
839