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