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