cl_console.c (16316B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 // console.c 23 24 #include "client.h" 25 26 27 int g_console_field_width = 78; 28 29 30 #define NUM_CON_TIMES 4 31 32 #define CON_TEXTSIZE 32768 33 typedef struct { 34 qboolean initialized; 35 36 short text[CON_TEXTSIZE]; 37 int current; // line where next message will be printed 38 int x; // offset in current line for next print 39 int display; // bottom of console displays this line 40 41 int linewidth; // characters across screen 42 int totallines; // total lines in console scrollback 43 44 float xadjust; // for wide aspect screens 45 46 float displayFrac; // aproaches finalFrac at scr_conspeed 47 float finalFrac; // 0.0 to 1.0 lines of console to display 48 49 int vislines; // in scanlines 50 51 int times[NUM_CON_TIMES]; // cls.realtime time the line was generated 52 // for transparent notify lines 53 vec4_t color; 54 } console_t; 55 56 extern console_t con; 57 58 console_t con; 59 60 cvar_t *con_conspeed; 61 cvar_t *con_notifytime; 62 63 #define DEFAULT_CONSOLE_WIDTH 78 64 65 vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; 66 67 68 /* 69 ================ 70 Con_ToggleConsole_f 71 ================ 72 */ 73 void Con_ToggleConsole_f (void) { 74 // closing a full screen console restarts the demo loop 75 if ( cls.state == CA_DISCONNECTED && cls.keyCatchers == KEYCATCH_CONSOLE ) { 76 CL_StartDemoLoop(); 77 return; 78 } 79 80 Field_Clear( &g_consoleField ); 81 g_consoleField.widthInChars = g_console_field_width; 82 83 Con_ClearNotify (); 84 cls.keyCatchers ^= KEYCATCH_CONSOLE; 85 } 86 87 /* 88 ================ 89 Con_MessageMode_f 90 ================ 91 */ 92 void Con_MessageMode_f (void) { 93 chat_playerNum = -1; 94 chat_team = qfalse; 95 Field_Clear( &chatField ); 96 chatField.widthInChars = 30; 97 98 cls.keyCatchers ^= KEYCATCH_MESSAGE; 99 } 100 101 /* 102 ================ 103 Con_MessageMode2_f 104 ================ 105 */ 106 void Con_MessageMode2_f (void) { 107 chat_playerNum = -1; 108 chat_team = qtrue; 109 Field_Clear( &chatField ); 110 chatField.widthInChars = 25; 111 cls.keyCatchers ^= KEYCATCH_MESSAGE; 112 } 113 114 /* 115 ================ 116 Con_MessageMode3_f 117 ================ 118 */ 119 void Con_MessageMode3_f (void) { 120 chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); 121 if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { 122 chat_playerNum = -1; 123 return; 124 } 125 chat_team = qfalse; 126 Field_Clear( &chatField ); 127 chatField.widthInChars = 30; 128 cls.keyCatchers ^= KEYCATCH_MESSAGE; 129 } 130 131 /* 132 ================ 133 Con_MessageMode4_f 134 ================ 135 */ 136 void Con_MessageMode4_f (void) { 137 chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); 138 if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { 139 chat_playerNum = -1; 140 return; 141 } 142 chat_team = qfalse; 143 Field_Clear( &chatField ); 144 chatField.widthInChars = 30; 145 cls.keyCatchers ^= KEYCATCH_MESSAGE; 146 } 147 148 /* 149 ================ 150 Con_Clear_f 151 ================ 152 */ 153 void Con_Clear_f (void) { 154 int i; 155 156 for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { 157 con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; 158 } 159 160 Con_Bottom(); // go to end 161 } 162 163 164 /* 165 ================ 166 Con_Dump_f 167 168 Save the console contents out to a file 169 ================ 170 */ 171 void Con_Dump_f (void) 172 { 173 int l, x, i; 174 short *line; 175 fileHandle_t f; 176 char buffer[1024]; 177 178 if (Cmd_Argc() != 2) 179 { 180 Com_Printf ("usage: condump <filename>\n"); 181 return; 182 } 183 184 Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) ); 185 186 f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); 187 if (!f) 188 { 189 Com_Printf ("ERROR: couldn't open.\n"); 190 return; 191 } 192 193 // skip empty lines 194 for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) 195 { 196 line = con.text + (l%con.totallines)*con.linewidth; 197 for (x=0 ; x<con.linewidth ; x++) 198 if ((line[x] & 0xff) != ' ') 199 break; 200 if (x != con.linewidth) 201 break; 202 } 203 204 // write the remaining lines 205 buffer[con.linewidth] = 0; 206 for ( ; l <= con.current ; l++) 207 { 208 line = con.text + (l%con.totallines)*con.linewidth; 209 for(i=0; i<con.linewidth; i++) 210 buffer[i] = line[i] & 0xff; 211 for (x=con.linewidth-1 ; x>=0 ; x--) 212 { 213 if (buffer[x] == ' ') 214 buffer[x] = 0; 215 else 216 break; 217 } 218 strcat( buffer, "\n" ); 219 FS_Write(buffer, strlen(buffer), f); 220 } 221 222 FS_FCloseFile( f ); 223 } 224 225 226 /* 227 ================ 228 Con_ClearNotify 229 ================ 230 */ 231 void Con_ClearNotify( void ) { 232 int i; 233 234 for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { 235 con.times[i] = 0; 236 } 237 } 238 239 240 241 /* 242 ================ 243 Con_CheckResize 244 245 If the line width has changed, reformat the buffer. 246 ================ 247 */ 248 void Con_CheckResize (void) 249 { 250 int i, j, width, oldwidth, oldtotallines, numlines, numchars; 251 MAC_STATIC short tbuf[CON_TEXTSIZE]; 252 253 width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; 254 255 if (width == con.linewidth) 256 return; 257 258 if (width < 1) // video hasn't been initialized yet 259 { 260 width = DEFAULT_CONSOLE_WIDTH; 261 con.linewidth = width; 262 con.totallines = CON_TEXTSIZE / con.linewidth; 263 for(i=0; i<CON_TEXTSIZE; i++) 264 265 con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; 266 } 267 else 268 { 269 oldwidth = con.linewidth; 270 con.linewidth = width; 271 oldtotallines = con.totallines; 272 con.totallines = CON_TEXTSIZE / con.linewidth; 273 numlines = oldtotallines; 274 275 if (con.totallines < numlines) 276 numlines = con.totallines; 277 278 numchars = oldwidth; 279 280 if (con.linewidth < numchars) 281 numchars = con.linewidth; 282 283 Com_Memcpy (tbuf, con.text, CON_TEXTSIZE * sizeof(short)); 284 for(i=0; i<CON_TEXTSIZE; i++) 285 286 con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; 287 288 289 for (i=0 ; i<numlines ; i++) 290 { 291 for (j=0 ; j<numchars ; j++) 292 { 293 con.text[(con.totallines - 1 - i) * con.linewidth + j] = 294 tbuf[((con.current - i + oldtotallines) % 295 oldtotallines) * oldwidth + j]; 296 } 297 } 298 299 Con_ClearNotify (); 300 } 301 302 con.current = con.totallines - 1; 303 con.display = con.current; 304 } 305 306 307 /* 308 ================ 309 Con_Init 310 ================ 311 */ 312 void Con_Init (void) { 313 int i; 314 315 con_notifytime = Cvar_Get ("con_notifytime", "3", 0); 316 con_conspeed = Cvar_Get ("scr_conspeed", "3", 0); 317 318 Field_Clear( &g_consoleField ); 319 g_consoleField.widthInChars = g_console_field_width; 320 for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { 321 Field_Clear( &historyEditLines[i] ); 322 historyEditLines[i].widthInChars = g_console_field_width; 323 } 324 325 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f); 326 Cmd_AddCommand ("messagemode", Con_MessageMode_f); 327 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f); 328 Cmd_AddCommand ("messagemode3", Con_MessageMode3_f); 329 Cmd_AddCommand ("messagemode4", Con_MessageMode4_f); 330 Cmd_AddCommand ("clear", Con_Clear_f); 331 Cmd_AddCommand ("condump", Con_Dump_f); 332 } 333 334 335 /* 336 =============== 337 Con_Linefeed 338 =============== 339 */ 340 void Con_Linefeed (qboolean skipnotify) 341 { 342 int i; 343 344 // mark time for transparent overlay 345 if (con.current >= 0) 346 { 347 if (skipnotify) 348 con.times[con.current % NUM_CON_TIMES] = 0; 349 else 350 con.times[con.current % NUM_CON_TIMES] = cls.realtime; 351 } 352 353 con.x = 0; 354 if (con.display == con.current) 355 con.display++; 356 con.current++; 357 for(i=0; i<con.linewidth; i++) 358 con.text[(con.current%con.totallines)*con.linewidth+i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; 359 } 360 361 /* 362 ================ 363 CL_ConsolePrint 364 365 Handles cursor positioning, line wrapping, etc 366 All console printing must go through this in order to be logged to disk 367 If no console is visible, the text will appear at the top of the game window 368 ================ 369 */ 370 void CL_ConsolePrint( char *txt ) { 371 int y; 372 int c, l; 373 int color; 374 qboolean skipnotify = qfalse; // NERVE - SMF 375 int prev; // NERVE - SMF 376 377 // TTimo - prefix for text that shows up in console but not in notify 378 // backported from RTCW 379 if ( !Q_strncmp( txt, "[skipnotify]", 12 ) ) { 380 skipnotify = qtrue; 381 txt += 12; 382 } 383 384 // for some demos we don't want to ever show anything on the console 385 if ( cl_noprint && cl_noprint->integer ) { 386 return; 387 } 388 389 if (!con.initialized) { 390 con.color[0] = 391 con.color[1] = 392 con.color[2] = 393 con.color[3] = 1.0f; 394 con.linewidth = -1; 395 Con_CheckResize (); 396 con.initialized = qtrue; 397 } 398 399 color = ColorIndex(COLOR_WHITE); 400 401 while ( (c = *txt) != 0 ) { 402 if ( Q_IsColorString( txt ) ) { 403 color = ColorIndex( *(txt+1) ); 404 txt += 2; 405 continue; 406 } 407 408 // count word length 409 for (l=0 ; l< con.linewidth ; l++) { 410 if ( txt[l] <= ' ') { 411 break; 412 } 413 414 } 415 416 // word wrap 417 if (l != con.linewidth && (con.x + l >= con.linewidth) ) { 418 Con_Linefeed(skipnotify); 419 420 } 421 422 txt++; 423 424 switch (c) 425 { 426 case '\n': 427 Con_Linefeed (skipnotify); 428 break; 429 case '\r': 430 con.x = 0; 431 break; 432 default: // display character and advance 433 y = con.current % con.totallines; 434 con.text[y*con.linewidth+con.x] = (color << 8) | c; 435 con.x++; 436 if (con.x >= con.linewidth) { 437 Con_Linefeed(skipnotify); 438 con.x = 0; 439 } 440 break; 441 } 442 } 443 444 445 // mark time for transparent overlay 446 if (con.current >= 0) { 447 // NERVE - SMF 448 if ( skipnotify ) { 449 prev = con.current % NUM_CON_TIMES - 1; 450 if ( prev < 0 ) 451 prev = NUM_CON_TIMES - 1; 452 con.times[prev] = 0; 453 } 454 else 455 // -NERVE - SMF 456 con.times[con.current % NUM_CON_TIMES] = cls.realtime; 457 } 458 } 459 460 461 /* 462 ============================================================================== 463 464 DRAWING 465 466 ============================================================================== 467 */ 468 469 470 /* 471 ================ 472 Con_DrawInput 473 474 Draw the editline after a ] prompt 475 ================ 476 */ 477 void Con_DrawInput (void) { 478 int y; 479 480 if ( cls.state != CA_DISCONNECTED && !(cls.keyCatchers & KEYCATCH_CONSOLE ) ) { 481 return; 482 } 483 484 y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); 485 486 re.SetColor( con.color ); 487 488 SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); 489 490 Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, 491 SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); 492 } 493 494 495 /* 496 ================ 497 Con_DrawNotify 498 499 Draws the last few lines of output transparently over the game top 500 ================ 501 */ 502 void Con_DrawNotify (void) 503 { 504 int x, v; 505 short *text; 506 int i; 507 int time; 508 int skip; 509 int currentColor; 510 511 currentColor = 7; 512 re.SetColor( g_color_table[currentColor] ); 513 514 v = 0; 515 for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) 516 { 517 if (i < 0) 518 continue; 519 time = con.times[i % NUM_CON_TIMES]; 520 if (time == 0) 521 continue; 522 time = cls.realtime - time; 523 if (time > con_notifytime->value*1000) 524 continue; 525 text = con.text + (i % con.totallines)*con.linewidth; 526 527 if (cl.snap.ps.pm_type != PM_INTERMISSION && cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { 528 continue; 529 } 530 531 for (x = 0 ; x < con.linewidth ; x++) { 532 if ( ( text[x] & 0xff ) == ' ' ) { 533 continue; 534 } 535 if ( ( (text[x]>>8)&7 ) != currentColor ) { 536 currentColor = (text[x]>>8)&7; 537 re.SetColor( g_color_table[currentColor] ); 538 } 539 SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff ); 540 } 541 542 v += SMALLCHAR_HEIGHT; 543 } 544 545 re.SetColor( NULL ); 546 547 if (cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { 548 return; 549 } 550 551 // draw the chat line 552 if ( cls.keyCatchers & KEYCATCH_MESSAGE ) 553 { 554 if (chat_team) 555 { 556 SCR_DrawBigString (8, v, "say_team:", 1.0f ); 557 skip = 11; 558 } 559 else 560 { 561 SCR_DrawBigString (8, v, "say:", 1.0f ); 562 skip = 5; 563 } 564 565 Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, 566 SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue ); 567 568 v += BIGCHAR_HEIGHT; 569 } 570 571 } 572 573 /* 574 ================ 575 Con_DrawSolidConsole 576 577 Draws the console with the solid background 578 ================ 579 */ 580 void Con_DrawSolidConsole( float frac ) { 581 int i, x, y; 582 int rows; 583 short *text; 584 int row; 585 int lines; 586 // qhandle_t conShader; 587 int currentColor; 588 vec4_t color; 589 590 lines = cls.glconfig.vidHeight * frac; 591 if (lines <= 0) 592 return; 593 594 if (lines > cls.glconfig.vidHeight ) 595 lines = cls.glconfig.vidHeight; 596 597 // on wide screens, we will center the text 598 con.xadjust = 0; 599 SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); 600 601 // draw the background 602 y = frac * SCREEN_HEIGHT - 2; 603 if ( y < 1 ) { 604 y = 0; 605 } 606 else { 607 SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); 608 } 609 610 color[0] = 1; 611 color[1] = 0; 612 color[2] = 0; 613 color[3] = 1; 614 SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); 615 616 617 // draw the version number 618 619 re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); 620 621 i = strlen( Q3_VERSION ); 622 623 for (x=0 ; x<i ; x++) { 624 625 SCR_DrawSmallChar( cls.glconfig.vidWidth - ( i - x ) * SMALLCHAR_WIDTH, 626 627 (lines-(SMALLCHAR_HEIGHT+SMALLCHAR_HEIGHT/2)), Q3_VERSION[x] ); 628 629 } 630 631 632 // draw the text 633 con.vislines = lines; 634 rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH; // rows of text to draw 635 636 y = lines - (SMALLCHAR_HEIGHT*3); 637 638 // draw from the bottom up 639 if (con.display != con.current) 640 { 641 // draw arrows to show the buffer is backscrolled 642 re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); 643 for (x=0 ; x<con.linewidth ; x+=4) 644 SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, '^' ); 645 y -= SMALLCHAR_HEIGHT; 646 rows--; 647 } 648 649 row = con.display; 650 651 if ( con.x == 0 ) { 652 row--; 653 } 654 655 currentColor = 7; 656 re.SetColor( g_color_table[currentColor] ); 657 658 for (i=0 ; i<rows ; i++, y -= SMALLCHAR_HEIGHT, row--) 659 { 660 if (row < 0) 661 break; 662 if (con.current - row >= con.totallines) { 663 // past scrollback wrap point 664 continue; 665 } 666 667 text = con.text + (row % con.totallines)*con.linewidth; 668 669 for (x=0 ; x<con.linewidth ; x++) { 670 if ( ( text[x] & 0xff ) == ' ' ) { 671 continue; 672 } 673 674 if ( ( (text[x]>>8)&7 ) != currentColor ) { 675 currentColor = (text[x]>>8)&7; 676 re.SetColor( g_color_table[currentColor] ); 677 } 678 SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); 679 } 680 } 681 682 // draw the input prompt, user text, and cursor if desired 683 Con_DrawInput (); 684 685 re.SetColor( NULL ); 686 } 687 688 689 690 /* 691 ================== 692 Con_DrawConsole 693 ================== 694 */ 695 void Con_DrawConsole( void ) { 696 // check for console width changes from a vid mode change 697 Con_CheckResize (); 698 699 // if disconnected, render console full screen 700 if ( cls.state == CA_DISCONNECTED ) { 701 if ( !( cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { 702 Con_DrawSolidConsole( 1.0 ); 703 return; 704 } 705 } 706 707 if ( con.displayFrac ) { 708 Con_DrawSolidConsole( con.displayFrac ); 709 } else { 710 // draw notify lines 711 if ( cls.state == CA_ACTIVE ) { 712 Con_DrawNotify (); 713 } 714 } 715 } 716 717 //================================================================ 718 719 /* 720 ================== 721 Con_RunConsole 722 723 Scroll it up or down 724 ================== 725 */ 726 void Con_RunConsole (void) { 727 // decide on the destination height of the console 728 if ( cls.keyCatchers & KEYCATCH_CONSOLE ) 729 con.finalFrac = 0.5; // half screen 730 else 731 con.finalFrac = 0; // none visible 732 733 // scroll towards the destination height 734 if (con.finalFrac < con.displayFrac) 735 { 736 con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; 737 if (con.finalFrac > con.displayFrac) 738 con.displayFrac = con.finalFrac; 739 740 } 741 else if (con.finalFrac > con.displayFrac) 742 { 743 con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; 744 if (con.finalFrac < con.displayFrac) 745 con.displayFrac = con.finalFrac; 746 } 747 748 } 749 750 751 void Con_PageUp( void ) { 752 con.display -= 2; 753 if ( con.current - con.display >= con.totallines ) { 754 con.display = con.current - con.totallines + 1; 755 } 756 } 757 758 void Con_PageDown( void ) { 759 con.display += 2; 760 if (con.display > con.current) { 761 con.display = con.current; 762 } 763 } 764 765 void Con_Top( void ) { 766 con.display = con.totallines; 767 if ( con.current - con.display >= con.totallines ) { 768 con.display = con.current - con.totallines + 1; 769 } 770 } 771 772 void Con_Bottom( void ) { 773 con.display = con.current; 774 } 775 776 777 void Con_Close( void ) { 778 if ( !com_cl_running->integer ) { 779 return; 780 } 781 Field_Clear( &g_consoleField ); 782 Con_ClearNotify (); 783 cls.keyCatchers &= ~KEYCATCH_CONSOLE; 784 con.finalFrac = 0; // none visible 785 con.displayFrac = 0; 786 }