e27c3d728177f6fb7ebefb107c5a5542985b7e28
[xonotic/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 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 #ifndef WIN32
23 # include <unistd.h>
24 #endif
25 #include "quakedef.h"
26
27 int con_linewidth;
28
29 float con_cursorspeed = 4;
30
31 #define         CON_TEXTSIZE    131072
32
33 // total lines in console scrollback
34 int con_totallines;
35 // lines up from bottom to display
36 int con_backscroll;
37 // where next message will be printed
38 int con_current;
39 // offset in current line for next print
40 int con_x;
41 char *con_text = 0;
42
43 //seconds
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
46
47 #define MAX_NOTIFYLINES 32
48 // cl.time time the line was generated for transparent notify lines
49 float con_times[MAX_NOTIFYLINES];
50
51 int con_vislines;
52
53 qboolean con_debuglog;
54
55 #define MAXCMDLINE      256
56 extern char key_lines[32][MAXCMDLINE];
57 extern int edit_line;
58 extern int key_linepos;
59 extern int key_insert;
60
61
62 qboolean con_initialized;
63
64 mempool_t *console_mempool;
65
66 extern void M_Menu_Main_f (void);
67
68 /*
69 ================
70 Con_ToggleConsole_f
71 ================
72 */
73 void Con_ToggleConsole_f (void)
74 {
75         // toggle the 'user wants console' bit
76         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
77         memset (con_times, 0, sizeof(con_times));
78 }
79
80 /*
81 ================
82 Con_Clear_f
83 ================
84 */
85 void Con_Clear_f (void)
86 {
87         if (con_text)
88                 memset (con_text, ' ', CON_TEXTSIZE);
89 }
90
91
92 /*
93 ================
94 Con_ClearNotify
95 ================
96 */
97 void Con_ClearNotify (void)
98 {
99         int i;
100
101         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
102                 con_times[i] = 0;
103 }
104
105
106 /*
107 ================
108 Con_MessageMode_f
109 ================
110 */
111 void Con_MessageMode_f (void)
112 {
113         key_dest = key_message;
114         chat_team = false;
115 }
116
117
118 /*
119 ================
120 Con_MessageMode2_f
121 ================
122 */
123 void Con_MessageMode2_f (void)
124 {
125         key_dest = key_message;
126         chat_team = true;
127 }
128
129
130 /*
131 ================
132 Con_CheckResize
133
134 If the line width has changed, reformat the buffer.
135 ================
136 */
137 void Con_CheckResize (void)
138 {
139         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
140         char tbuf[CON_TEXTSIZE];
141
142         width = (vid.conwidth >> 3);
143
144         if (width == con_linewidth)
145                 return;
146
147         if (width < 1)                  // video hasn't been initialized yet
148         {
149                 width = 80;
150                 con_linewidth = width;
151                 con_totallines = CON_TEXTSIZE / con_linewidth;
152                 memset (con_text, ' ', CON_TEXTSIZE);
153         }
154         else
155         {
156                 oldwidth = con_linewidth;
157                 con_linewidth = width;
158                 oldtotallines = con_totallines;
159                 con_totallines = CON_TEXTSIZE / con_linewidth;
160                 numlines = oldtotallines;
161
162                 if (con_totallines < numlines)
163                         numlines = con_totallines;
164
165                 numchars = oldwidth;
166
167                 if (con_linewidth < numchars)
168                         numchars = con_linewidth;
169
170                 memcpy (tbuf, con_text, CON_TEXTSIZE);
171                 memset (con_text, ' ', CON_TEXTSIZE);
172
173                 for (i=0 ; i<numlines ; i++)
174                 {
175                         for (j=0 ; j<numchars ; j++)
176                         {
177                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
178                                                 tbuf[((con_current - i + oldtotallines) %
179                                                           oldtotallines) * oldwidth + j];
180                         }
181                 }
182
183                 Con_ClearNotify ();
184         }
185
186         con_backscroll = 0;
187         con_current = con_totallines - 1;
188 }
189
190
191 void Con_InitLogging (void)
192 {
193 #define MAXGAMEDIRLEN 1000
194         char temp[MAXGAMEDIRLEN+1];
195         char *t2 = "/qconsole.log";
196
197         con_debuglog = COM_CheckParm("-condebug");
198         if (con_debuglog)
199         {
200                 if (strlen (fs_gamedir) < (MAXGAMEDIRLEN - strlen (t2)))
201                 {
202                         sprintf (temp, "%s%s", fs_gamedir, t2);
203                         unlink (temp);
204                 }
205         }
206 }
207
208 /*
209 ================
210 Con_Init
211 ================
212 */
213 void Con_Init (void)
214 {
215         console_mempool = Mem_AllocPool("console");
216         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
217         memset (con_text, ' ', CON_TEXTSIZE);
218         con_linewidth = -1;
219         Con_CheckResize ();
220
221         Con_Print("Console initialized.\n");
222
223 //
224 // register our commands
225 //
226         Cvar_RegisterVariable (&con_notifytime);
227         Cvar_RegisterVariable (&con_notify);
228
229         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
230         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
231         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
232         Cmd_AddCommand ("clear", Con_Clear_f);
233         con_initialized = true;
234 }
235
236
237 /*
238 ===============
239 Con_Linefeed
240 ===============
241 */
242 void Con_Linefeed (void)
243 {
244         con_x = 0;
245         con_current++;
246         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
247 }
248
249 /*
250 ================
251 Con_PrintToHistory
252
253 Handles cursor positioning, line wrapping, etc
254 All console printing must go through this in order to be displayed
255 If no console is visible, the notify window will pop up.
256 ================
257 */
258 void Con_PrintToHistory(const char *txt)
259 {
260         int y, c, l, mask;
261         static int cr;
262
263         con_backscroll = 0;
264
265         if (txt[0] == 1)
266         {
267                 mask = 128;             // go to colored text
268                 S_LocalSound ("misc/talk.wav");
269         // play talk wav
270                 txt++;
271         }
272         else if (txt[0] == 2)
273         {
274                 mask = 128;             // go to colored text
275                 txt++;
276         }
277         else
278                 mask = 0;
279
280
281         while ( (c = *txt) )
282         {
283         // count word length
284                 for (l=0 ; l< con_linewidth ; l++)
285                         if ( txt[l] <= ' ')
286                                 break;
287
288         // word wrap
289                 if (l != con_linewidth && (con_x + l > con_linewidth) )
290                         con_x = 0;
291
292                 txt++;
293
294                 if (cr)
295                 {
296                         con_current--;
297                         cr = false;
298                 }
299
300
301                 if (!con_x)
302                 {
303                         Con_Linefeed ();
304                 // mark time for transparent overlay
305                         if (con_current >= 0)
306                         {
307                                 if (con_notify.integer < 0)
308                                         Cvar_SetValueQuick(&con_notify, 0);
309                                 if (con_notify.integer > MAX_NOTIFYLINES)
310                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
311                                 if (con_notify.integer > 0)
312                                         con_times[con_current % con_notify.integer] = cl.time;
313                         }
314                 }
315
316                 switch (c)
317                 {
318                 case '\n':
319                         con_x = 0;
320                         break;
321
322                 case '\r':
323                         con_x = 0;
324                         cr = 1;
325                         break;
326
327                 default:        // display character and advance
328                         y = con_current % con_totallines;
329                         con_text[y*con_linewidth+con_x] = c | mask;
330                         con_x++;
331                         if (con_x >= con_linewidth)
332                                 con_x = 0;
333                         break;
334                 }
335
336         }
337 }
338
339 // LordHavoc: increased from 4096 to 16384
340 #define MAXPRINTMSG     16384
341
342 /*
343 ================
344 Con_LogPrint
345 ================
346 */
347 void Con_LogPrint(const char *logfilename, const char *msg)
348 {
349         qfile_t *file;
350         file = FS_Open(logfilename, "at", true);
351         if (file)
352         {
353                 FS_Print(file, msg);
354                 FS_Close(file);
355         }
356 }
357
358 /*
359 ================
360 Con_LogPrintf
361 ================
362 */
363 void Con_LogPrintf(const char *logfilename, const char *fmt, ...)
364 {
365         va_list argptr;
366         char msg[MAXPRINTMSG];
367
368         va_start(argptr,fmt);
369         vsprintf(msg,fmt,argptr);
370         va_end(argptr);
371
372         Con_LogPrint(logfilename, msg);
373 }
374
375 /*
376 ================
377 Con_Print
378
379 Prints to all appropriate console targets
380 ================
381 */
382 void Con_Print(const char *msg)
383 {
384         // also echo to debugging console
385         Sys_Print(msg);
386
387         // log all messages to file
388         if (con_debuglog)
389                 Con_LogPrint("qconsole.log", msg);
390
391         if (!con_initialized)
392                 return;
393
394         if (cls.state == ca_dedicated)
395                 return;         // no graphics mode
396
397         // write it to the scrollable buffer
398         Con_PrintToHistory(msg);
399 }
400
401 /*
402 ================
403 Con_Printf
404
405 Prints to all appropriate console targets
406 ================
407 */
408 void Con_Printf(const char *fmt, ...)
409 {
410         va_list argptr;
411         char msg[MAXPRINTMSG];
412
413         va_start(argptr,fmt);
414         vsprintf(msg,fmt,argptr);
415         va_end(argptr);
416
417         Con_Print(msg);
418 }
419
420 /*
421 ================
422 Con_DPrint
423
424 A Con_Print that only shows up if the "developer" cvar is set
425 ================
426 */
427 void Con_DPrint(const char *msg)
428 {
429         if (!developer.integer)
430                 return;                 // don't confuse non-developers with techie stuff...
431         Con_Print(msg);
432 }
433
434 /*
435 ================
436 Con_DPrintf
437
438 A Con_Printf that only shows up if the "developer" cvar is set
439 ================
440 */
441 void Con_DPrintf(const char *fmt, ...)
442 {
443         va_list argptr;
444         char msg[MAXPRINTMSG];
445
446         if (!developer.integer)
447                 return;                 // don't confuse non-developers with techie stuff...
448
449         va_start(argptr,fmt);
450         vsprintf(msg,fmt,argptr);
451         va_end(argptr);
452
453         Con_Print(msg);
454 }
455
456
457 /*
458 ================
459 Con_SafePrint
460
461 Okay to call even when the screen can't be updated
462 ==================
463 */
464 void Con_SafePrint(const char *msg)
465 {
466         Con_Print(msg);
467 }
468
469 /*
470 ==================
471 Con_SafePrintf
472
473 Okay to call even when the screen can't be updated
474 ==================
475 */
476 void Con_SafePrintf(const char *fmt, ...)
477 {
478         va_list argptr;
479         char msg[MAXPRINTMSG];
480
481         va_start(argptr,fmt);
482         vsprintf(msg,fmt,argptr);
483         va_end(argptr);
484
485         Con_Print(msg);
486 }
487
488
489 /*
490 ==============================================================================
491
492 DRAWING
493
494 ==============================================================================
495 */
496
497
498 /*
499 ================
500 Con_DrawInput
501
502 The input line scrolls horizontally if typing goes beyond the right edge
503
504 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
505 ================
506 */
507 void Con_DrawInput (void)
508 {
509         char editlinecopy[257], *text;
510
511         if (!key_consoleactive)
512                 return;         // don't draw anything
513
514         text = strcpy(editlinecopy, key_lines[edit_line]);
515
516         // Advanced Console Editing by Radix radix@planetquake.com
517         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
518         // use strlen of edit_line instead of key_linepos to allow editing
519         // of early characters w/o erasing
520
521         // add the cursor frame
522         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
523                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
524
525         text[key_linepos + 1] = 0;
526
527         // prestep if horizontally scrolling
528         if (key_linepos >= con_linewidth)
529                 text += 1 + key_linepos - con_linewidth;
530
531         // draw it
532         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
533
534         // remove cursor
535         key_lines[edit_line][key_linepos] = 0;
536 }
537
538
539 /*
540 ================
541 Con_DrawNotify
542
543 Draws the last few lines of output transparently over the game top
544 ================
545 */
546 void Con_DrawNotify (void)
547 {
548         int             x, v;
549         char    *text;
550         int             i;
551         float   time;
552         extern char chat_buffer[];
553         char    temptext[256];
554
555         if (con_notify.integer < 0)
556                 Cvar_SetValueQuick(&con_notify, 0);
557         if (con_notify.integer > MAX_NOTIFYLINES)
558                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
559         v = 0;
560         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
561         {
562                 if (i < 0)
563                         continue;
564                 time = con_times[i % con_notify.integer];
565                 if (time == 0)
566                         continue;
567                 time = cl.time - time;
568                 if (time > con_notifytime.value)
569                         continue;
570                 text = con_text + (i % con_totallines)*con_linewidth;
571
572                 clearnotify = 0;
573
574                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
575
576                 v += 8;
577         }
578
579
580         if (key_dest == key_message)
581         {
582                 clearnotify = 0;
583
584                 x = 0;
585
586                 // LordHavoc: speedup, and other improvements
587                 if (chat_team)
588                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
589                 else
590                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
591                 while (strlen(temptext) >= (size_t) con_linewidth)
592                 {
593                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
594                         strcpy(temptext, &temptext[con_linewidth]);
595                         v += 8;
596                 }
597                 if (strlen(temptext) > 0)
598                 {
599                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
600                         v += 8;
601                 }
602         }
603 }
604
605 /*
606 ================
607 Con_DrawConsole
608
609 Draws the console with the solid background
610 The typing input line at the bottom should only be drawn if typing is allowed
611 ================
612 */
613 extern char engineversion[40];
614 void Con_DrawConsole (int lines)
615 {
616         int i, y, rows, j;
617         char *text;
618
619         if (lines <= 0)
620                 return;
621
622 // draw the background
623         if (scr_conbrightness.value >= 0.01f)
624                 DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
625         else
626                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
627         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
628
629 // draw the text
630         con_vislines = lines;
631
632         rows = (lines-16)>>3;           // rows of text to draw
633         y = lines - 16 - (rows<<3);     // may start slightly negative
634
635         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
636         {
637                 j = max(i - con_backscroll, 0);
638                 text = con_text + (j % con_totallines)*con_linewidth;
639
640                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
641         }
642
643 // draw the input prompt, user text, and cursor if desired
644         Con_DrawInput ();
645 }
646
647 /*
648         Con_DisplayList
649
650         New function for tab-completion system
651         Added by EvilTypeGuy
652         MEGA Thanks to Taniwha
653
654 */
655 void Con_DisplayList(const char **list)
656 {
657         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
658         const char **walk = list;
659
660         while (*walk) {
661                 len = strlen(*walk);
662                 if (len > maxlen)
663                         maxlen = len;
664                 walk++;
665         }
666         maxlen += 1;
667
668         while (*list) {
669                 len = strlen(*list);
670                 if (pos + maxlen >= width) {
671                         Con_Print("\n");
672                         pos = 0;
673                 }
674
675                 Con_Print(*list);
676                 for (i = 0; i < (maxlen - len); i++)
677                         Con_Print(" ");
678
679                 pos += maxlen;
680                 list++;
681         }
682
683         if (pos)
684                 Con_Print("\n\n");
685 }
686
687 /*
688         Con_CompleteCommandLine
689
690         New function for tab-completion system
691         Added by EvilTypeGuy
692         Thanks to Fett erich@heintz.com
693         Thanks to taniwha
694
695 */
696 void Con_CompleteCommandLine (void)
697 {
698         const char *cmd = "", *s;
699         const char **list[3] = {0, 0, 0};
700         int c, v, a, i, cmd_len;
701
702         s = key_lines[edit_line] + 1;
703         // Count number of possible matches
704         c = Cmd_CompleteCountPossible(s);
705         v = Cvar_CompleteCountPossible(s);
706         a = Cmd_CompleteAliasCountPossible(s);
707
708         if (!(c + v + a))       // No possible matches
709                 return;
710
711         if (c + v + a == 1) {
712                 if (c)
713                         list[0] = Cmd_CompleteBuildList(s);
714                 else if (v)
715                         list[0] = Cvar_CompleteBuildList(s);
716                 else
717                         list[0] = Cmd_CompleteAliasBuildList(s);
718                 cmd = *list[0];
719                 cmd_len = strlen (cmd);
720         } else {
721                 if (c)
722                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
723                 if (v)
724                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
725                 if (a)
726                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
727
728                 cmd_len = strlen (s);
729                 do {
730                         for (i = 0; i < 3; i++) {
731                                 char ch = cmd[cmd_len];
732                                 const char **l = list[i];
733                                 if (l) {
734                                         while (*l && (*l)[cmd_len] == ch)
735                                                 l++;
736                                         if (*l)
737                                                 break;
738                                 }
739                         }
740                         if (i == 3)
741                                 cmd_len++;
742                 } while (i == 3);
743                 // 'quakebar'
744                 Con_Print("\n\35");
745                 for (i = 0; i < con_linewidth - 4; i++)
746                         Con_Print("\36");
747                 Con_Print("\37\n");
748
749                 // Print Possible Commands
750                 if (c) {
751                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
752                         Con_DisplayList(list[0]);
753                 }
754
755                 if (v) {
756                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
757                         Con_DisplayList(list[1]);
758                 }
759
760                 if (a) {
761                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
762                         Con_DisplayList(list[2]);
763                 }
764         }
765
766         if (cmd) {
767                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
768                 key_linepos = cmd_len + 1;
769                 if (c + v + a == 1) {
770                         key_lines[edit_line][key_linepos] = ' ';
771                         key_linepos++;
772                 }
773                 key_lines[edit_line][key_linepos] = 0;
774         }
775         for (i = 0; i < 3; i++)
776                 if (list[i])
777                         Mem_Free((void *)list[i]);
778 }
779