1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1998-2000 by DooM Legacy Team.
4 // Copyright (C) 1999-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file  console.c
11 /// \brief Console drawing and input
12 
13 #ifdef __GNUC__
14 #include <unistd.h>
15 #endif
16 
17 #include "doomdef.h"
18 #include "console.h"
19 #include "g_game.h"
20 #include "g_input.h"
21 #include "hu_stuff.h"
22 #include "keys.h"
23 #include "r_main.h"
24 #include "r_defs.h"
25 #include "sounds.h"
26 #include "st_stuff.h"
27 #include "s_sound.h"
28 #include "v_video.h"
29 #include "i_video.h"
30 #include "z_zone.h"
31 #include "i_system.h"
32 #include "i_threads.h"
33 #include "d_main.h"
34 #include "m_menu.h"
35 #include "filesrch.h"
36 #include "m_misc.h"
37 
38 #ifdef _WINDOWS
39 #include "win32/win_main.h"
40 #endif
41 
42 #ifdef HWRENDER
43 #include "hardware/hw_main.h"
44 #endif
45 
46 #define MAXHUDLINES 20
47 
48 #ifdef HAVE_THREADS
49 I_mutex con_mutex;
50 
51 #  define Lock_state()    I_lock_mutex(&con_mutex)
52 #  define Unlock_state() I_unlock_mutex(con_mutex)
53 #else/*HAVE_THREADS*/
54 #  define Lock_state()
55 #  define Unlock_state()
56 #endif/*HAVE_THREADS*/
57 
58 static boolean con_started = false; // console has been initialised
59        boolean con_startup = false; // true at game startup
60        boolean con_refresh = false; // screen needs refreshing
61 static boolean con_forcepic = true; // at startup toggle console translucency when first off
62        boolean con_recalc;          // set true when screen size has changed
63 
64 static tic_t con_tick; // console ticker for anim or blinking prompt cursor
65                         // con_scrollup should use time (currenttime - lasttime)..
66 
67 static boolean consoletoggle; // true when console key pushed, ticker will handle
68 static boolean consoleready;  // console prompt is ready
69 
70        INT32 con_destlines; // vid lines used by console at final position
71 static INT32 con_curlines;  // vid lines currently used by console
72 
73        INT32 con_clipviewtop; // (useless)
74 
75 static INT32 con_hudlines;        // number of console heads up message lines
76 static INT32 con_hudtime[MAXHUDLINES];      // remaining time of display for hud msg lines
77 
78        INT32 con_clearlines;      // top screen lines to refresh when view reduced
79        boolean con_hudupdate;   // when messages scroll, we need a backgrnd refresh
80 
81 // console text output
82 static char *con_line;          // console text output current line
83 static size_t con_cx;           // cursor position in current line
84 static size_t con_cy;           // cursor line number in con_buffer, is always
85                                 // increasing, and wrapped around in the text
86                                 // buffer using modulo.
87 
88 static size_t con_totallines;      // lines of console text into the console buffer
89 static size_t con_width;           // columns of chars, depend on vid mode width
90 
91 static size_t con_scrollup;        // how many rows of text to scroll up (pgup/pgdn)
92 UINT32 con_scalefactor;            // text size scale factor
93 
94 // hold 32 last lines of input for history
95 #define CON_MAXPROMPTCHARS 256
96 #define CON_PROMPTCHAR '$'
97 
98 static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines
99 
100 static INT32 inputline;    // current input line number
101 static INT32 inputhist;    // line number of history input line to restore
102 static size_t input_cur; // position of cursor in line
103 static size_t input_sel; // position of selection marker (I.E.: anything between this and input_cur is "selected")
104 static size_t input_len; // length of current line, used to bound cursor and such
105 // notice: input does NOT include the "$" at the start of the line. - 11/3/16
106 
107 // protos.
108 static void CON_InputInit(void);
109 static void CON_RecalcSize(void);
110 static void CON_ChangeHeight(void);
111 
112 static void CON_DrawBackpic(void);
113 static void CONS_hudlines_Change(void);
114 static void CONS_backcolor_Change(void);
115 
116 //======================================================================
117 //                   CONSOLE VARS AND COMMANDS
118 //======================================================================
119 #ifdef macintosh
120 #define CON_BUFFERSIZE 4096 // my compiler can't handle local vars >32k
121 #else
122 #define CON_BUFFERSIZE 16384
123 #endif
124 
125 static char con_buffer[CON_BUFFERSIZE];
126 
127 // how many seconds the hud messages lasts on the screen
128 static consvar_t cons_msgtimeout = CVAR_INIT ("con_hudtime", "5", CV_SAVE, CV_Unsigned, NULL);
129 
130 // number of lines displayed on the HUD
131 static consvar_t cons_hudlines = CVAR_INIT ("con_hudlines", "5", CV_CALL|CV_SAVE, CV_Unsigned, CONS_hudlines_Change);
132 
133 // number of lines console move per frame
134 // (con_speed needs a limit, apparently)
135 static CV_PossibleValue_t speed_cons_t[] = {{0, "MIN"}, {64, "MAX"}, {0, NULL}};
136 static consvar_t cons_speed = CVAR_INIT ("con_speed", "8", CV_SAVE, speed_cons_t, NULL);
137 
138 // percentage of screen height to use for console
139 static consvar_t cons_height = CVAR_INIT ("con_height", "50", CV_SAVE, CV_Unsigned, NULL);
140 
141 static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"}, {0, NULL}};
142 // whether to use console background picture, or translucent mode
143 static consvar_t cons_backpic = CVAR_INIT ("con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL);
144 
145 static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, 		{1, "Black"},		{2, "Sepia"},
146 												{3, "Brown"},		{4, "Pink"},		{5, "Raspberry"},
147 												{6, "Red"},			{7, "Creamsicle"},	{8, "Orange"},
148 												{9, "Gold"},		{10,"Yellow"},		{11,"Emerald"},
149 												{12,"Green"},		{13,"Cyan"},		{14,"Steel"},
150 												{15,"Periwinkle"},	{16,"Blue"},		{17,"Purple"},
151 												{18,"Lavender"},
152 												{0, NULL}};
153 
154 
155 consvar_t cons_backcolor = CVAR_INIT ("con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change);
156 
157 static void CON_Print(char *msg);
158 
159 //
160 //
CONS_hudlines_Change(void)161 static void CONS_hudlines_Change(void)
162 {
163 	INT32 i;
164 
165 	Lock_state();
166 
167 	// Clear the currently displayed lines
168 	for (i = 0; i < con_hudlines; i++)
169 		con_hudtime[i] = 0;
170 
171 	if (cons_hudlines.value < 1)
172 		cons_hudlines.value = 1;
173 	else if (cons_hudlines.value > MAXHUDLINES)
174 		cons_hudlines.value = MAXHUDLINES;
175 
176 	con_hudlines = cons_hudlines.value;
177 
178 	Unlock_state();
179 
180 	CONS_Printf(M_GetText("Number of console HUD lines is now %d\n"), con_hudlines);
181 }
182 
183 // Clear console text buffer
184 //
CONS_Clear_f(void)185 static void CONS_Clear_f(void)
186 {
187 	Lock_state();
188 
189 	memset(con_buffer, 0, CON_BUFFERSIZE);
190 
191 	con_cx = 0;
192 	con_cy = con_totallines-1;
193 	con_line = &con_buffer[con_cy*con_width];
194 	con_scrollup = 0;
195 
196 	Unlock_state();
197 }
198 
199 // Choose english keymap
200 //
201 /*static void CONS_English_f(void)
202 {
203 	shiftxform = english_shiftxform;
204 	CONS_Printf(M_GetText("%s keymap.\n"), M_GetText("English"));
205 }*/
206 
207 static char *bindtable[NUMINPUTS];
208 
CONS_Bind_f(void)209 static void CONS_Bind_f(void)
210 {
211 	size_t na;
212 	INT32 key;
213 
214 	na = COM_Argc();
215 
216 	if (na != 2 && na != 3)
217 	{
218 		CONS_Printf(M_GetText("bind <keyname> [<command>]: create shortcut keys to command(s)\n"));
219 		CONS_Printf("\x82%s", M_GetText("Bind table :\n"));
220 		na = 0;
221 		for (key = 0; key < NUMINPUTS; key++)
222 			if (bindtable[key])
223 			{
224 				CONS_Printf("%s : \"%s\"\n", G_KeynumToString(key), bindtable[key]);
225 				na = 1;
226 			}
227 		if (!na)
228 			CONS_Printf(M_GetText("(empty)\n"));
229 		return;
230 	}
231 
232 	key = G_KeyStringtoNum(COM_Argv(1));
233 	if (key <= 0 || key >= NUMINPUTS)
234 	{
235 		CONS_Alert(CONS_NOTICE, M_GetText("Invalid key name\n"));
236 		return;
237 	}
238 
239 	Z_Free(bindtable[key]);
240 	bindtable[key] = NULL;
241 
242 	if (na == 3)
243 		bindtable[key] = Z_StrDup(COM_Argv(2));
244 }
245 
246 //======================================================================
247 //                          CONSOLE SETUP
248 //======================================================================
249 
250 // Font colormap colors
251 // TODO: This could probably be improved somehow...
252 // These colormaps are 99% identical, with just a few changed bytes
253 // This could EASILY be handled by modifying a centralised colormap
254 // for software depending on the prior state - but yknow, OpenGL...
255 UINT8 *yellowmap, *magentamap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap, *skymap, *purplemap, *aquamap, *peridotmap, *azuremap, *brownmap, *rosymap, *invertmap;
256 
257 // Console BG color
258 UINT8 *consolebgmap = NULL;
259 UINT8 *promptbgmap = NULL;
260 static UINT8 promptbgcolor = UINT8_MAX;
261 
CON_SetupBackColormapEx(INT32 color,boolean prompt)262 void CON_SetupBackColormapEx(INT32 color, boolean prompt)
263 {
264 	UINT16 i, palsum;
265 	UINT8 j, palindex;
266 	UINT8 *pal = W_CacheLumpName(GetPalette(), PU_CACHE);
267 	INT32 shift = 6;
268 
269 	if (color == INT32_MAX)
270 		color = cons_backcolor.value;
271 
272 	shift = 6; // 12 colors -- shift of 7 means 6 colors
273 
274 	switch (color)
275 	{
276 		case 0:		palindex = 15; 	break; 	// White
277 		case 1:		palindex = 31;	break; 	// Black
278 		case 2:		palindex = 251;	break;	// Sepia
279 		case 3:		palindex = 239;	break; 	// Brown
280 		case 4:		palindex = 215; shift = 7; 	break; 	// Pink
281 		case 5:		palindex = 37; shift = 7;	break; 	// Raspberry
282 		case 6:		palindex = 47; shift = 7;	break; 	// Red
283 		case 7:		palindex = 53;	shift = 7;	break;	// Creamsicle
284 		case 8:		palindex = 63;	break; 	// Orange
285 		case 9:		palindex = 56; shift = 7;	break; 	// Gold
286 		case 10:	palindex = 79; shift = 7;	break; 	// Yellow
287 		case 11:	palindex = 119; shift = 7; 	break; 	// Emerald
288 		case 12:	palindex = 111;	break; 	// Green
289 		case 13:	palindex = 136;	shift = 7; break; 	// Cyan
290 		case 14:	palindex = 175; shift = 7;	break; 	// Steel
291 		case 15:	palindex = 166;	shift = 7; 	break; 	// Periwinkle
292 		case 16:	palindex = 159;	break; 	// Blue
293 		case 17:	palindex = 187; shift = 7; 	break; 	// Purple
294 		case 18:	palindex = 199; shift = 7; 	break; 	// Lavender
295 		// Default green
296 		default:	palindex = 111; break;
297 	}
298 
299 	if (prompt)
300 	{
301 		if (!promptbgmap)
302 			promptbgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
303 
304 		if (color == promptbgcolor)
305 			return;
306 		else
307 			promptbgcolor = color;
308 	}
309 	else if (!consolebgmap)
310 		consolebgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
311 
312 	// setup background colormap
313 	for (i = 0, j = 0; i < 768; i += 3, j++)
314 	{
315 		palsum = (pal[i] + pal[i+1] + pal[i+2]) >> shift;
316 		if (prompt)
317 			promptbgmap[j] = (UINT8)(palindex - palsum);
318 		else
319 			consolebgmap[j] = (UINT8)(palindex - palsum);
320 	}
321 }
322 
CON_SetupBackColormap(void)323 void CON_SetupBackColormap(void)
324 {
325 	CON_SetupBackColormapEx(cons_backcolor.value, false);
326 	CON_SetupBackColormapEx(1, true); // default to gray
327 }
328 
CONS_backcolor_Change(void)329 static void CONS_backcolor_Change(void)
330 {
331 	CON_SetupBackColormapEx(cons_backcolor.value, false);
332 }
333 
CON_SetupColormaps(void)334 static void CON_SetupColormaps(void)
335 {
336 	INT32 i;
337 	UINT8 *memorysrc = (UINT8 *)Z_Malloc((256*15), PU_STATIC, NULL);
338 
339 	magentamap = memorysrc;
340 	yellowmap  = (magentamap+256);
341 	lgreenmap  = (yellowmap+256);
342 	bluemap    = (lgreenmap+256);
343 	redmap     = (bluemap+256);
344 	graymap    = (redmap+256);
345 	orangemap  = (graymap+256);
346 	skymap     = (orangemap+256);
347 	purplemap  = (skymap+256);
348 	aquamap    = (purplemap+256);
349 	peridotmap = (aquamap+256);
350 	azuremap   = (peridotmap+256);
351 	brownmap   = (azuremap+256);
352 	rosymap    = (brownmap+256);
353 	invertmap  = (rosymap+256);
354 
355 	// setup the other colormaps, for console text
356 
357 	// these don't need to be aligned, unless you convert the
358 	// V_DrawMappedPatch() into optimised asm.
359 
360 	for (i = 0; i < (256*15); i++, ++memorysrc)
361 		*memorysrc = (UINT8)(i & 0xFF); // remap each color to itself...
362 
363 #define colset(map, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
364 	map[0x0] = (UINT8)a;\
365 	map[0x1] = (UINT8)b;\
366 	map[0x2] = (UINT8)c;\
367 	map[0x3] = (UINT8)d;\
368 	map[0x4] = (UINT8)e;\
369 	map[0x5] = (UINT8)f;\
370 	map[0x6] = (UINT8)g;\
371 	map[0x7] = (UINT8)h;\
372 	map[0x8] = (UINT8)i;\
373 	map[0x9] = (UINT8)j;\
374 	map[0xA] = (UINT8)k;\
375 	map[0xB] = (UINT8)l;\
376 	map[0xC] = (UINT8)m;\
377 	map[0xD] = (UINT8)n;\
378 	map[0xE] = (UINT8)o;\
379 	map[0xF] = (UINT8)p;
380 
381 	// Tried to keep the colors vanilla while adding some shades in between them ~SonicX8000
382 
383 	//                      0x1       0x3                           0x9                           0xF
384 	colset(magentamap, 177, 177, 178, 178, 178, 180, 180, 180, 182, 182, 182, 182, 184, 184, 184, 185);
385 	colset(yellowmap,   82,  82,  73,  73,  73,  64,  64,  64,  66,  66,  66,  66,  67,  67,  67,  68);
386 	colset(lgreenmap,   96,  96,  98,  98,  98, 101, 101, 101, 104, 104, 104, 104, 106, 106, 106, 107);
387 	colset(bluemap,    146, 146, 147, 147, 147, 149, 149, 149, 152, 152, 152, 152, 155, 155, 155, 157);
388 	colset(redmap,      32,  32,  33,  33,  33,  35,  35,  35,  39,  39,  39,  39,  42,  42,  42,  44);
389 	colset(graymap,      8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23);
390 	colset(orangemap,   50,  50,  52,  52,  52,  54,  54,  54,  56,  56,  56,  56,  59,  59,  59,  60);
391 	colset(skymap,     129, 129, 130, 130, 130, 131, 131, 131, 133, 133, 133, 133, 135, 135, 135, 136);
392 	colset(purplemap,  160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 163, 164, 164, 164, 165);
393 	colset(aquamap,    120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 125);
394 	colset(peridotmap,  72,  72, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
395 	colset(azuremap,   144, 144, 145, 145, 145, 146, 146, 146, 170, 170, 170, 170, 171, 171, 171, 172);
396 	colset(brownmap,   219, 219, 221, 221, 221, 222, 222, 222, 224, 224, 224, 224, 227, 227, 227, 229);
397 	colset(rosymap,    200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 205);
398 
399 #undef colset
400 
401 	// Yeah just straight up invert it like a normal person
402 	for (i = 0x00; i <= 0x1F; i++)
403 		invertmap[0x1F - i] = i;
404 
405 	// Init back colormap
406 	CON_SetupBackColormap();
407 }
408 
409 // Setup the console text buffer
410 //
CON_Init(void)411 void CON_Init(void)
412 {
413 	INT32 i;
414 
415 	for (i = 0; i < NUMINPUTS; i++)
416 		bindtable[i] = NULL;
417 
418 	Lock_state();
419 
420 	// clear all lines
421 	memset(con_buffer, 0, CON_BUFFERSIZE);
422 
423 	// make sure it is ready for the loading screen
424 	con_width = 0;
425 
426 	Unlock_state();
427 
428 	CON_RecalcSize();
429 
430 	CON_SetupColormaps();
431 
432 	Lock_state();
433 
434 	//note: CON_Ticker should always execute at least once before D_Display()
435 	con_clipviewtop = -1; // -1 does not clip
436 
437 	con_hudlines = atoi(cons_hudlines.defaultvalue);
438 
439 	Unlock_state();
440 
441 	// setup console input filtering
442 	CON_InputInit();
443 
444 	// register our commands
445 	//
446 	COM_AddCommand("cls", CONS_Clear_f);
447 	//COM_AddCommand("english", CONS_English_f);
448 	// set console full screen for game startup MAKE SURE VID_Init() done !!!
449 	Lock_state();
450 
451 	con_destlines = vid.height;
452 	con_curlines = vid.height;
453 
454 	Unlock_state();
455 
456 	if (!dedicated)
457 	{
458 		Lock_state();
459 
460 		con_started = true;
461 		con_startup = true;
462 		con_refresh = true; // needs explicit screen refresh until we are in the main game loop
463 		consoletoggle = false;
464 
465 		Unlock_state();
466 
467 		CV_RegisterVar(&cons_msgtimeout);
468 		CV_RegisterVar(&cons_hudlines);
469 		CV_RegisterVar(&cons_speed);
470 		CV_RegisterVar(&cons_height);
471 		CV_RegisterVar(&cons_backpic);
472 		CV_RegisterVar(&cons_backcolor);
473 		COM_AddCommand("bind", CONS_Bind_f);
474 	}
475 	else
476 	{
477 		Lock_state();
478 
479 		con_started = true;
480 		con_startup = false;
481 		con_refresh = false; // disable explicit screen refresh
482 		consoletoggle = true;
483 
484 		Unlock_state();
485 	}
486 }
487 // Console input initialization
488 //
CON_InputInit(void)489 static void CON_InputInit(void)
490 {
491 	Lock_state();
492 
493 	// prepare the first prompt line
494 	memset(inputlines, 0, sizeof (inputlines));
495 	inputline = 0;
496 	input_cur = input_sel = input_len = 0;
497 
498 	Unlock_state();
499 }
500 
501 //======================================================================
502 //                        CONSOLE EXECUTION
503 //======================================================================
504 
505 // Called at screen size change to set the rows and line size of the
506 // console text buffer.
507 //
CON_RecalcSize(void)508 static void CON_RecalcSize(void)
509 {
510 	size_t conw, oldcon_width, oldnumlines, i, oldcon_cy;
511 	char *tmp_buffer;
512 	char *string;
513 
514 	Lock_state();
515 
516 	switch (cv_constextsize.value)
517 	{
518 	case V_NOSCALEPATCH:
519 		con_scalefactor = 1;
520 		break;
521 	case V_SMALLSCALEPATCH:
522 		con_scalefactor = vid.smalldupx;
523 		break;
524 	case V_MEDSCALEPATCH:
525 		con_scalefactor = vid.meddupx;
526 		break;
527 	default:	// Full scaling
528 		con_scalefactor = vid.dupx;
529 		break;
530 	}
531 
532 	con_recalc = false;
533 
534 	if (dedicated)
535 		conw = 1;
536 	else
537 		conw = (vid.width>>3) / con_scalefactor - 2;
538 
539 	if (con_curlines == vid.height) // first init
540 	{
541 		con_curlines = vid.height;
542 		con_destlines = vid.height;
543 	}
544 
545 	if (con_destlines > 0) // Resize console if already open
546 	{
547 		CON_ChangeHeight();
548 		con_curlines = con_destlines;
549 	}
550 
551 	// check for change of video width
552 	if (conw == con_width)
553 	{
554 		Unlock_state();
555 		return; // didn't change
556 	}
557 
558 	Unlock_state();
559 
560 	tmp_buffer = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL);
561 	string = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); // BP: it is a line but who know
562 
563 	Lock_state();
564 
565 	oldcon_width = con_width;
566 	oldnumlines = con_totallines;
567 	oldcon_cy = con_cy;
568 	M_Memcpy(tmp_buffer, con_buffer, CON_BUFFERSIZE);
569 
570 	if (conw < 1)
571 		con_width = (BASEVIDWIDTH>>3) - 2;
572 	else
573 		con_width = conw;
574 
575 	con_width += 11; // Graue 06-19-2004 up to 11 control chars per line
576 
577 	con_totallines = CON_BUFFERSIZE / con_width;
578 	memset(con_buffer, ' ', CON_BUFFERSIZE);
579 
580 	con_cx = 0;
581 	con_cy = con_totallines-1;
582 	con_line = &con_buffer[con_cy*con_width];
583 	con_scrollup = 0;
584 
585 	Unlock_state();
586 
587 	// re-arrange console text buffer to keep text
588 	if (oldcon_width) // not the first time
589 	{
590 		for (i = oldcon_cy + 1; i < oldcon_cy + oldnumlines; i++)
591 		{
592 			if (tmp_buffer[(i%oldnumlines)*oldcon_width])
593 			{
594 				M_Memcpy(string, &tmp_buffer[(i%oldnumlines)*oldcon_width], oldcon_width);
595 				conw = oldcon_width - 1;
596 				while (string[conw] == ' ' && conw)
597 					conw--;
598 				string[conw+1] = '\n';
599 				string[conw+2] = '\0';
600 				CON_Print(string);
601 			}
602 		}
603 	}
604 
605 	Z_Free(string);
606 	Z_Free(tmp_buffer);
607 }
608 
CON_ChangeHeight(void)609 static void CON_ChangeHeight(void)
610 {
611 	INT32 minheight;
612 
613 	Lock_state();
614 
615 	minheight = 20 * con_scalefactor;	// 20 = 8+8+4
616 
617 	// toggle console in
618 	con_destlines = (cons_height.value*vid.height)/100;
619 	if (con_destlines < minheight)
620 		con_destlines = minheight;
621 	else if (con_destlines > vid.height)
622 		con_destlines = vid.height;
623 
624 	con_destlines &= ~0x3; // multiple of text row height
625 
626 	Unlock_state();
627 }
628 
629 // Handles Console moves in/out of screen (per frame)
630 //
CON_MoveConsole(void)631 static void CON_MoveConsole(void)
632 {
633 	fixed_t conspeed;
634 
635 	Lock_state();
636 
637 	conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
638 
639 	// instant
640 	if (!cons_speed.value)
641 	{
642 		con_curlines = con_destlines;
643 		return;
644 	}
645 
646 	// up/down move to dest
647 	if (con_curlines < con_destlines)
648 	{
649 		con_curlines += FixedInt(conspeed);
650 		if (con_curlines > con_destlines)
651 			con_curlines = con_destlines;
652 	}
653 	else if (con_curlines > con_destlines)
654 	{
655 		con_curlines -= FixedInt(conspeed);
656 		if (con_curlines < con_destlines)
657 			con_curlines = con_destlines;
658 	}
659 
660 	Unlock_state();
661 }
662 
663 // Clear time of console heads up messages
664 //
CON_ClearHUD(void)665 void CON_ClearHUD(void)
666 {
667 	INT32 i;
668 
669 	Lock_state();
670 
671 	for (i = 0; i < con_hudlines; i++)
672 		con_hudtime[i] = 0;
673 
674 	Unlock_state();
675 }
676 
677 // Force console to move out immediately
678 // note: con_ticker will set consoleready false
CON_ToggleOff(void)679 void CON_ToggleOff(void)
680 {
681 	Lock_state();
682 
683 	if (!con_destlines)
684 	{
685 		Unlock_state();
686 		return;
687 	}
688 
689 	con_destlines = 0;
690 	con_curlines = 0;
691 	CON_ClearHUD();
692 	con_forcepic = 0;
693 	con_clipviewtop = -1; // remove console clipping of view
694 
695 	I_UpdateMouseGrab();
696 
697 	Unlock_state();
698 }
699 
CON_Ready(void)700 boolean CON_Ready(void)
701 {
702 	boolean ready;
703 	Lock_state();
704 	{
705 		ready = consoleready;
706 	}
707 	Unlock_state();
708 	return ready;
709 }
710 
711 // Console ticker: handles console move in/out, cursor blinking
712 //
CON_Ticker(void)713 void CON_Ticker(void)
714 {
715 	INT32 i;
716 	INT32 minheight;
717 
718 	Lock_state();
719 
720 	minheight = 20 * con_scalefactor;	// 20 = 8+8+4
721 
722 	// cursor blinking
723 	con_tick++;
724 	con_tick &= 7;
725 
726 	// console key was pushed
727 	if (consoletoggle)
728 	{
729 		consoletoggle = false;
730 
731 		// toggle off console
732 		if (con_destlines > 0)
733 		{
734 			con_destlines = 0;
735 			CON_ClearHUD();
736 			I_UpdateMouseGrab();
737 		}
738 		else
739 			CON_ChangeHeight();
740 	}
741 
742 	// console movement
743 	if (con_destlines != con_curlines)
744 		CON_MoveConsole();
745 
746 	// clip the view, so that the part under the console is not drawn
747 	con_clipviewtop = -1;
748 	if (cons_backpic.value) // clip only when using an opaque background
749 	{
750 		if (con_curlines > 0)
751 			con_clipviewtop = con_curlines - viewwindowy - 1 - 10;
752 		// NOTE: BIG HACK::SUBTRACT 10, SO THAT WATER DON'T COPY LINES OF THE CONSOLE
753 		//       WINDOW!!! (draw some more lines behind the bottom of the console)
754 		if (con_clipviewtop < 0)
755 			con_clipviewtop = -1; // maybe not necessary, provided it's < 0
756 	}
757 
758 	// check if console ready for prompt
759 	if (con_destlines >= minheight)
760 		consoleready = true;
761 	else
762 		consoleready = false;
763 
764 	// make overlay messages disappear after a while
765 	for (i = 0; i < con_hudlines; i++)
766 	{
767 		con_hudtime[i]--;
768 		if (con_hudtime[i] < 0)
769 			con_hudtime[i] = 0;
770 	}
771 
772 	Unlock_state();
773 }
774 
775 //
776 // ----
777 //
778 // Shortcuts for adding and deleting characters, strings, and sections
779 // Necessary due to moving cursor
780 //
781 
CON_InputClear(void)782 static void CON_InputClear(void)
783 {
784 	Lock_state();
785 
786 	memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
787 	input_cur = input_sel = input_len = 0;
788 
789 	Unlock_state();
790 }
791 
CON_InputSetString(const char * c)792 static void CON_InputSetString(const char *c)
793 {
794 	Lock_state();
795 
796 	memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
797 	strcpy(inputlines[inputline], c);
798 	input_cur = input_sel = input_len = strlen(c);
799 
800 	Unlock_state();
801 }
802 
CON_InputAddString(const char * c)803 static void CON_InputAddString(const char *c)
804 {
805 	size_t csize = strlen(c);
806 
807 	Lock_state();
808 
809 	if (input_len + csize > CON_MAXPROMPTCHARS-1)
810 	{
811 		Unlock_state();
812 		return;
813 	}
814 	if (input_cur != input_len)
815 		memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur);
816 	memcpy(&inputlines[inputline][input_cur], c, csize);
817 	input_len += csize;
818 	input_sel = (input_cur += csize);
819 
820 	Unlock_state();
821 }
822 
CON_InputDelSelection(void)823 static void CON_InputDelSelection(void)
824 {
825 	size_t start, end, len;
826 
827 	Lock_state();
828 
829 	if (!input_cur)
830 	{
831 		Unlock_state();
832 		return;
833 	}
834 
835 	if (input_cur > input_sel)
836 	{
837 		start = input_sel;
838 		end = input_cur;
839 	}
840 	else
841 	{
842 		start = input_cur;
843 		end = input_sel;
844 	}
845 	len = (end - start);
846 
847 	if (end != input_len)
848 		memmove(&inputlines[inputline][start], &inputlines[inputline][end], input_len-end);
849 	memset(&inputlines[inputline][input_len - len], 0, len);
850 
851 	input_len -= len;
852 	input_sel = input_cur = start;
853 
854 	Unlock_state();
855 }
856 
CON_InputAddChar(char c)857 static void CON_InputAddChar(char c)
858 {
859 	if (input_len >= CON_MAXPROMPTCHARS-1)
860 		return;
861 
862 	Lock_state();
863 
864 	if (input_cur != input_len)
865 		memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur);
866 	inputlines[inputline][input_cur++] = c;
867 	inputlines[inputline][++input_len] = 0;
868 	input_sel = input_cur;
869 
870 	Unlock_state();
871 }
872 
CON_InputDelChar(void)873 static void CON_InputDelChar(void)
874 {
875 	if (!input_cur)
876 		return;
877 
878 	Lock_state();
879 
880 	if (input_cur != input_len)
881 		memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur);
882 	inputlines[inputline][--input_len] = 0;
883 	input_sel = --input_cur;
884 
885 	Unlock_state();
886 }
887 
888 //
889 // ----
890 //
891 
892 // Handles console key input
893 //
CON_Responder(event_t * ev)894 boolean CON_Responder(event_t *ev)
895 {
896 	static UINT8 consdown = false; // console is treated differently due to rare usage
897 
898 	// sequential completions a la 4dos
899 	static char completion[80];
900 
901 	static INT32 skips;
902 
903 	static INT32   com_skips;
904 	static INT32   var_skips;
905 	static INT32 alias_skips;
906 
907 	const char *cmd = NULL;
908 	INT32 key;
909 
910 	if (chat_on)
911 		return false;
912 
913 	// let go keyup events, don't eat them
914 	if (ev->type != ev_keydown && ev->type != ev_console)
915 	{
916 		if (ev->data1 == gamecontrol[gc_console][0] || ev->data1 == gamecontrol[gc_console][1])
917 			consdown = false;
918 		return false;
919 	}
920 
921 	key = ev->data1;
922 
923 	// check for console toggle key
924 	if (ev->type != ev_console)
925 	{
926 		if (modeattacking || metalrecording || marathonmode)
927 			return false;
928 
929 		if (key == gamecontrol[gc_console][0] || key == gamecontrol[gc_console][1])
930 		{
931 			if (consdown) // ignore repeat
932 				return true;
933 			consoletoggle = true;
934 			consdown = true;
935 			return true;
936 		}
937 
938 		// check other keys only if console prompt is active
939 		if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!!
940 		{
941 			if (! menuactive && bindtable[key])
942 			{
943 				COM_BufAddText(bindtable[key]);
944 				COM_BufAddText("\n");
945 				return true;
946 			}
947 			return false;
948 		}
949 
950 		// escape key toggle off console
951 		if (key == KEY_ESCAPE)
952 		{
953 			consoletoggle = true;
954 			return true;
955 		}
956 	}
957 
958 	// Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas
959 	if (key == KEY_LSHIFT || key == KEY_RSHIFT
960 	 || key == KEY_LCTRL || key == KEY_RCTRL
961 	 || key == KEY_LALT || key == KEY_RALT)
962 		return true;
963 
964 	if (key == KEY_LEFTARROW)
965 	{
966 		if (input_cur != 0)
967 		{
968 			if (ctrldown)
969 				input_cur = M_JumpWordReverse(inputlines[inputline], input_cur);
970 			else
971 				--input_cur;
972 		}
973 		if (!shiftdown)
974 			input_sel = input_cur;
975 		return true;
976 	}
977 	else if (key == KEY_RIGHTARROW)
978 	{
979 		if (input_cur < input_len)
980 		{
981 			if (ctrldown)
982 				input_cur += M_JumpWord(&inputlines[inputline][input_cur]);
983 			else
984 				++input_cur;
985 		}
986 		if (!shiftdown)
987 			input_sel = input_cur;
988 		return true;
989 	}
990 
991 	// backspace and delete command prompt
992 	if (input_sel != input_cur)
993 	{
994 		if (key == KEY_BACKSPACE || key == KEY_DEL)
995 		{
996 			CON_InputDelSelection();
997 			return true;
998 		}
999 	}
1000 	else if (key == KEY_BACKSPACE)
1001 	{
1002 		if (ctrldown)
1003 		{
1004 			input_sel = M_JumpWordReverse(inputlines[inputline], input_cur);
1005 			CON_InputDelSelection();
1006 		}
1007 		else
1008 			CON_InputDelChar();
1009 		return true;
1010 	}
1011 	else if (key == KEY_DEL)
1012 	{
1013 		if (input_cur == input_len)
1014 			return true;
1015 
1016 		if (ctrldown)
1017 		{
1018 			input_sel = input_cur + M_JumpWord(&inputlines[inputline][input_cur]);
1019 			CON_InputDelSelection();
1020 		}
1021 		else
1022 		{
1023 			++input_cur;
1024 			CON_InputDelChar();
1025 		}
1026 		return true;
1027 	}
1028 
1029 	// ctrl modifier -- changes behavior, adds shortcuts
1030 	if (ctrldown)
1031 	{
1032 		// show all cvars/commands that match what we have inputted
1033 		if (key == KEY_TAB)
1034 		{
1035 			size_t i, len;
1036 
1037 			if (!completion[0])
1038 			{
1039 				if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
1040 					return true;
1041 				strcpy(completion, inputlines[inputline]);
1042 			}
1043 			len = strlen(completion);
1044 
1045 			//first check commands
1046 			CONS_Printf("\nCommands:\n");
1047 			for (i = 0, cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, ++i))
1048 				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
1049 			if (i == 0) CONS_Printf("  (none)\n");
1050 
1051 			//now we move on to CVARs
1052 			CONS_Printf("Variables:\n");
1053 			for (i = 0, cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, ++i))
1054 				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
1055 			if (i == 0) CONS_Printf("  (none)\n");
1056 
1057 			//and finally aliases
1058 			CONS_Printf("Aliases:\n");
1059 			for (i = 0, cmd = COM_CompleteAlias(completion, i); cmd; cmd = COM_CompleteAlias(completion, ++i))
1060 				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
1061 			if (i == 0) CONS_Printf("  (none)\n");
1062 
1063 			completion[0] = 0;
1064 
1065 			return true;
1066 		}
1067 		// ---
1068 
1069 		if (key == KEY_HOME) // oldest text in buffer
1070 		{
1071 			con_scrollup = (con_totallines-((con_curlines-16)>>3));
1072 			return true;
1073 		}
1074 		else if (key == KEY_END) // most recent text in buffer
1075 		{
1076 			con_scrollup = 0;
1077 			return true;
1078 		}
1079 
1080 		if (key == 'x' || key == 'X')
1081 		{
1082 			if (input_sel > input_cur)
1083 				I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
1084 			else
1085 				I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
1086 			CON_InputDelSelection();
1087 			completion[0] = 0;
1088 			return true;
1089 		}
1090 		else if (key == 'c' || key == 'C')
1091 		{
1092 			if (input_sel > input_cur)
1093 				I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
1094 			else
1095 				I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
1096 			return true;
1097 		}
1098 		else if (key == 'v' || key == 'V')
1099 		{
1100 			const char *paste = I_ClipboardPaste();
1101 			if (input_sel != input_cur)
1102 				CON_InputDelSelection();
1103 			if (paste != NULL)
1104 				CON_InputAddString(paste);
1105 			completion[0] = 0;
1106 			return true;
1107 		}
1108 
1109 		// Select all
1110 		if (key == 'a' || key == 'A')
1111 		{
1112 			input_sel = 0;
1113 			input_cur = input_len;
1114 			return true;
1115 		}
1116 
1117 		// ...why shouldn't it eat the key? if it doesn't, it just means you
1118 		// can control Sonic from the console, which is silly
1119 		return true;//return false;
1120 	}
1121 
1122 	// command completion forward (tab) and backward (shift-tab)
1123 	if (key == KEY_TAB)
1124 	{
1125 		// sequential command completion forward and backward
1126 
1127 		// remember typing for several completions (a-la-4dos)
1128 		if (!completion[0])
1129 		{
1130 			if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
1131 				return true;
1132 			strcpy(completion, inputlines[inputline]);
1133 			skips       = 0;
1134 			com_skips   = 0;
1135 			var_skips   = 0;
1136 			alias_skips = 0;
1137 		}
1138 		else
1139 		{
1140 			if (shiftdown)
1141 			{
1142 				if (skips > 0)
1143 					skips--;
1144 			}
1145 			else
1146 			{
1147 				skips++;
1148 			}
1149 		}
1150 
1151 		if (skips <= com_skips)
1152 		{
1153 			cmd = COM_CompleteCommand(completion, skips);
1154 
1155 			if (cmd && skips == com_skips)
1156 			{
1157 				com_skips  ++;
1158 				var_skips  ++;
1159 				alias_skips++;
1160 			}
1161 		}
1162 
1163 		if (!cmd && skips <= var_skips)
1164 		{
1165 			cmd = CV_CompleteVar(completion, skips - com_skips);
1166 
1167 			if (cmd && skips == var_skips)
1168 			{
1169 				var_skips  ++;
1170 				alias_skips++;
1171 			}
1172 		}
1173 
1174 		if (!cmd && skips <= alias_skips)
1175 		{
1176 			cmd = COM_CompleteAlias(completion, skips - var_skips);
1177 
1178 			if (cmd && skips == alias_skips)
1179 			{
1180 				alias_skips++;
1181 			}
1182 		}
1183 
1184 		if (cmd)
1185 		{
1186 			CON_InputSetString(va("%s ", cmd));
1187 		}
1188 		else
1189 		{
1190 			skips--;
1191 		}
1192 
1193 		return true;
1194 	}
1195 
1196 	// move up (backward) in console textbuffer
1197 	if (key == KEY_PGUP)
1198 	{
1199 		if (con_scrollup < (con_totallines-((con_curlines-16)>>3)))
1200 			con_scrollup++;
1201 		return true;
1202 	}
1203 	else if (key == KEY_PGDN)
1204 	{
1205 		if (con_scrollup > 0)
1206 			con_scrollup--;
1207 		return true;
1208 	}
1209 	else if (key == KEY_HOME)
1210 	{
1211 		input_cur = 0;
1212 		if (!shiftdown)
1213 			input_sel = input_cur;
1214 		return true;
1215 	}
1216 	else if (key == KEY_END)
1217 	{
1218 		input_cur = input_len;
1219 		if (!shiftdown)
1220 			input_sel = input_cur;
1221 		return true;
1222 	}
1223 
1224 	// At this point we're messing with input
1225 	// Clear completion
1226 	completion[0] = 0;
1227 
1228 	// command enter
1229 	if (key == KEY_ENTER)
1230 	{
1231 		if (!input_len)
1232 			return true;
1233 
1234 		// push the command
1235 		COM_BufAddText(inputlines[inputline]);
1236 		COM_BufAddText("\n");
1237 
1238 		CONS_Printf("\x86""%c""\x80""%s\n", CON_PROMPTCHAR, inputlines[inputline]);
1239 
1240 		inputline = (inputline+1) & 31;
1241 		inputhist = inputline;
1242 		CON_InputClear();
1243 
1244 		return true;
1245 	}
1246 
1247 	// move back in input history
1248 	if (key == KEY_UPARROW)
1249 	{
1250 		// copy one of the previous inputlines to the current
1251 		do
1252 			inputhist = (inputhist - 1) & 31; // cycle back
1253 		while (inputhist != inputline && !inputlines[inputhist][0]);
1254 
1255 		// stop at the last history input line, which is the
1256 		// current line + 1 because we cycle through the 32 input lines
1257 		if (inputhist == inputline)
1258 			inputhist = (inputline + 1) & 31;
1259 
1260 		CON_InputSetString(inputlines[inputhist]);
1261 		return true;
1262 	}
1263 
1264 	// move forward in input history
1265 	if (key == KEY_DOWNARROW)
1266 	{
1267 		if (inputhist == inputline)
1268 			return true;
1269 		do
1270 			inputhist = (inputhist + 1) & 31;
1271 		while (inputhist != inputline && !inputlines[inputhist][0]);
1272 
1273 		// back to currentline
1274 		if (inputhist == inputline)
1275 			CON_InputClear();
1276 		else
1277 			CON_InputSetString(inputlines[inputhist]);
1278 		return true;
1279 	}
1280 
1281 	// allow people to use keypad in console (good for typing IP addresses) - Calum
1282 	if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL)
1283 	{
1284 		char keypad_translation[] = {'7','8','9','-',
1285 		                             '4','5','6','+',
1286 		                             '1','2','3',
1287 		                             '0','.'};
1288 
1289 		key = keypad_translation[key - KEY_KEYPAD7];
1290 	}
1291 	else if (key == KEY_KPADSLASH)
1292 		key = '/';
1293 
1294 	if (key >= 'a' && key <= 'z')
1295 	{
1296 		if (capslock ^ shiftdown)
1297 			key = shiftxform[key];
1298 	}
1299 	else if (shiftdown)
1300 		key = shiftxform[key];
1301 
1302 	// enter a char into the command prompt
1303 	if (key < 32 || key > 127)
1304 		return true;
1305 
1306 	if (input_sel != input_cur)
1307 		CON_InputDelSelection();
1308 	CON_InputAddChar(key);
1309 
1310 	return true;
1311 }
1312 
1313 // Insert a new line in the console text buffer
1314 //
CON_Linefeed(void)1315 static void CON_Linefeed(void)
1316 {
1317 	// set time for heads up messages
1318 	con_hudtime[con_cy%con_hudlines] = cons_msgtimeout.value*TICRATE;
1319 
1320 	con_cy++;
1321 	con_cx = 0;
1322 
1323 	con_line = &con_buffer[(con_cy%con_totallines)*con_width];
1324 	memset(con_line, ' ', con_width);
1325 
1326 	// make sure the view borders are refreshed if hud messages scroll
1327 	con_hudupdate = true; // see HU_Erase()
1328 }
1329 
1330 // Outputs text into the console text buffer
CON_Print(char * msg)1331 static void CON_Print(char *msg)
1332 {
1333 	size_t l;
1334 	INT32 controlchars = 0; // for color changing
1335 	char color = '\x80';  // keep color across lines
1336 
1337 	if (msg == NULL)
1338 		return;
1339 
1340 	if (*msg == '\3') // chat text, makes ding sound
1341 		S_StartSound(NULL, sfx_radio);
1342 	else if (*msg == '\4') // chat action, dings and is in yellow
1343 	{
1344 		*msg = '\x82'; // yellow
1345 		S_StartSound(NULL, sfx_radio);
1346 	}
1347 
1348 	Lock_state();
1349 
1350 	if (!(*msg & 0x80))
1351 	{
1352 		con_line[con_cx++] = '\x80';
1353 		controlchars = 1;
1354 	}
1355 
1356 	while (*msg)
1357 	{
1358 		// skip non-printable characters and white spaces
1359 		while (*msg && *msg <= ' ')
1360 		{
1361 			if (*msg & 0x80)
1362 			{
1363 				color = con_line[con_cx++] = *(msg++);
1364 				controlchars++;
1365 				continue;
1366 			}
1367 			else if (*msg == '\r') // carriage return
1368 			{
1369 				con_cy--;
1370 				CON_Linefeed();
1371 				color = '\x80';
1372 				controlchars = 0;
1373 			}
1374 			else if (*msg == '\n') // linefeed
1375 			{
1376 				CON_Linefeed();
1377 				con_line[con_cx++] = color;
1378 				controlchars = 1;
1379 			}
1380 			else if (*msg == ' ') // space
1381 			{
1382 				con_line[con_cx++] = ' ';
1383 				if (con_cx - controlchars >= con_width-11)
1384 				{
1385 					CON_Linefeed();
1386 					con_line[con_cx++] = color;
1387 					controlchars = 1;
1388 				}
1389 			}
1390 			else if (*msg == '\t')
1391 			{
1392 				// adds tab spaces for nice layout in console
1393 
1394 				do
1395 				{
1396 					con_line[con_cx++] = ' ';
1397 				} while ((con_cx - controlchars) % 4 != 0);
1398 
1399 				if (con_cx - controlchars >= con_width-11)
1400 				{
1401 					CON_Linefeed();
1402 					con_line[con_cx++] = color;
1403 					controlchars = 1;
1404 				}
1405 			}
1406 			msg++;
1407 		}
1408 
1409 		if (*msg == '\0')
1410 		{
1411 			Unlock_state();
1412 			return;
1413 		}
1414 
1415 		// printable character
1416 		for (l = 0; l < (con_width-11) && msg[l] > ' '; l++)
1417 			;
1418 
1419 		// word wrap
1420 		if ((con_cx - controlchars) + l > con_width-11)
1421 		{
1422 			CON_Linefeed();
1423 			con_line[con_cx++] = color;
1424 			controlchars = 1;
1425 		}
1426 
1427 		// a word at a time
1428 		for (; l > 0; l--)
1429 			con_line[con_cx++] = *(msg++);
1430 	}
1431 
1432 	Unlock_state();
1433 }
1434 
CON_LogMessage(const char * msg)1435 void CON_LogMessage(const char *msg)
1436 {
1437 	char txt[8192], *t;
1438 	const char *p = msg, *e = txt+sizeof (txt)-2;
1439 
1440 	for (t = txt; *p != '\0'; p++)
1441 	{
1442 		if (*p == '\n' || *p >= ' ') // don't log or console print CON_Print's control characters
1443 			*t++ = *p;
1444 
1445 		if (t >= e)
1446 		{
1447 			*t = '\0'; //end of string
1448 			I_OutputMsg("%s", txt); //print string
1449 			t = txt; //reset t pointer
1450 			memset(txt,'\0', sizeof (txt)); //reset txt
1451 		}
1452 	}
1453 	*t = '\0'; //end of string
1454 	I_OutputMsg("%s", txt);
1455 }
1456 
1457 // Console print! Wahooo! Lots o fun!
1458 //
1459 
CONS_Printf(const char * fmt,...)1460 void CONS_Printf(const char *fmt, ...)
1461 {
1462 	va_list argptr;
1463 	static char *txt = NULL;
1464 	boolean refresh;
1465 
1466 	if (txt == NULL)
1467 		txt = malloc(8192);
1468 
1469 	va_start(argptr, fmt);
1470 	vsprintf(txt, fmt, argptr);
1471 	va_end(argptr);
1472 
1473 	// echo console prints to log file
1474 	DEBFILE(txt);
1475 
1476 	// write message in con text buffer
1477 	if (con_started)
1478 		CON_Print(txt);
1479 
1480 	CON_LogMessage(txt);
1481 
1482 	Lock_state();
1483 
1484 	// make sure new text is visible
1485 	con_scrollup = 0;
1486 	refresh = con_refresh;
1487 
1488 	Unlock_state();
1489 
1490 	// if not in display loop, force screen update
1491 	if (refresh)
1492 	{
1493 		CON_Drawer(); // here we display the console text
1494 		I_FinishUpdate(); // page flip or blit buffer
1495 	}
1496 }
1497 
CONS_Alert(alerttype_t level,const char * fmt,...)1498 void CONS_Alert(alerttype_t level, const char *fmt, ...)
1499 {
1500 	va_list argptr;
1501 	static char *txt = NULL;
1502 
1503 	if (txt == NULL)
1504 		txt = malloc(8192);
1505 
1506 	va_start(argptr, fmt);
1507 	vsprintf(txt, fmt, argptr);
1508 	va_end(argptr);
1509 
1510 	switch (level)
1511 	{
1512 		case CONS_NOTICE:
1513 			// no notice for notices, hehe
1514 			CONS_Printf("\x83" "%s" "\x80 ", M_GetText("NOTICE:"));
1515 			break;
1516 		case CONS_WARNING:
1517 			refreshdirmenu |= REFRESHDIR_WARNING;
1518 			CONS_Printf("\x82" "%s" "\x80 ", M_GetText("WARNING:"));
1519 			break;
1520 		case CONS_ERROR:
1521 			refreshdirmenu |= REFRESHDIR_ERROR;
1522 			CONS_Printf("\x85" "%s" "\x80 ", M_GetText("ERROR:"));
1523 			break;
1524 	}
1525 
1526 	// I am lazy and I feel like just letting CONS_Printf take care of things.
1527 	// Is that okay?
1528 	CONS_Printf("%s", txt);
1529 }
1530 
CONS_Debug(INT32 debugflags,const char * fmt,...)1531 void CONS_Debug(INT32 debugflags, const char *fmt, ...)
1532 {
1533 	va_list argptr;
1534 	static char *txt = NULL;
1535 
1536 	if ((cv_debug & debugflags) != debugflags)
1537 		return;
1538 
1539 	if (txt == NULL)
1540 		txt = malloc(8192);
1541 
1542 	va_start(argptr, fmt);
1543 	vsprintf(txt, fmt, argptr);
1544 	va_end(argptr);
1545 
1546 	// Again I am lazy, oh well
1547 	CONS_Printf("%s", txt);
1548 }
1549 
1550 
1551 // Print an error message, and wait for ENTER key to continue.
1552 // To make sure the user has seen the message
1553 //
CONS_Error(const char * msg)1554 void CONS_Error(const char *msg)
1555 {
1556 #if defined(RPC_NO_WINDOWS_H) && defined(_WINDOWS)
1557 	if (!graphics_started)
1558 	{
1559 		MessageBoxA(vid.WndParent, msg, "SRB2 Warning", MB_OK);
1560 		return;
1561 	}
1562 #endif
1563 	CONS_Printf("\x82%s", msg); // write error msg in different colour
1564 	CONS_Printf(M_GetText("Press ENTER to continue\n"));
1565 
1566 	// dirty quick hack, but for the good cause
1567 	while (I_GetKey() != KEY_ENTER)
1568 		I_OsPolling();
1569 }
1570 
1571 //======================================================================
1572 //                          CONSOLE DRAW
1573 //======================================================================
1574 
1575 // draw console prompt line
1576 //
CON_DrawInput(void)1577 static void CON_DrawInput(void)
1578 {
1579 	INT32 charwidth = (INT32)con_scalefactor << 3;
1580 	const char *p = inputlines[inputline];
1581 	size_t c, clen, cend;
1582 	UINT8 lellip = 0, rellip = 0;
1583 	INT32 x, y, i;
1584 
1585 	y = con_curlines - 12 * con_scalefactor;
1586 	x = charwidth*2;
1587 
1588 	clen = con_width-13;
1589 
1590 	if (input_len <= clen)
1591 	{
1592 		c = 0;
1593 		clen = input_len;
1594 	}
1595 	else // input line scrolls left if it gets too long
1596 	{
1597 		clen -= 2; // There will always be some extra truncation -- but where is what we'll find out
1598 
1599 		if (input_cur <= clen/2)
1600 		{
1601 			// Close enough to right edge to show all
1602 			c = 0;
1603 			// Always will truncate right side from this position, so always draw right ellipsis
1604 			rellip = 1;
1605 		}
1606 		else
1607 		{
1608 			// Cursor in the middle (or right side) of input
1609 			// Move over for the ellipsis
1610 			c = input_cur - (clen/2) + 2;
1611 			x += charwidth*2;
1612 			lellip = 1;
1613 
1614 			if (c + clen >= input_len)
1615 			{
1616 				// Cursor in the right side of input
1617 				// We were too far over, so move back
1618 				c = input_len - clen;
1619 			}
1620 			else
1621 			{
1622 				// Cursor in the middle -- ellipses on both sides
1623 				clen -= 2;
1624 				rellip = 1;
1625 			}
1626 		}
1627 	}
1628 
1629 	if (lellip)
1630 	{
1631 		x -= charwidth*3;
1632 		if (input_sel < c)
1633 			V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
1634 		for (i = 0; i < 3; ++i, x += charwidth)
1635 			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
1636 	}
1637 	else
1638 		V_DrawCharacter(x-charwidth, y, CON_PROMPTCHAR | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
1639 
1640 	for (cend = c + clen; c < cend; ++c, x += charwidth)
1641 	{
1642 		if ((input_sel > c && input_cur <= c) || (input_sel <= c && input_cur > c))
1643 		{
1644 			V_DrawFill(x, y, charwidth, (10 * con_scalefactor), 77 | V_NOSCALESTART);
1645 			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, true);
1646 		}
1647 		else
1648 			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, true);
1649 
1650 		if (c == input_cur && con_tick >= 4)
1651 			V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
1652 	}
1653 	if (cend == input_cur && con_tick >= 4)
1654 		V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
1655 	if (rellip)
1656 	{
1657 		if (input_sel > cend)
1658 			V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
1659 		for (i = 0; i < 3; ++i, x += charwidth)
1660 			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
1661 	}
1662 }
1663 
1664 // draw the last lines of console text to the top of the screen
CON_DrawHudlines(void)1665 static void CON_DrawHudlines(void)
1666 {
1667 	UINT8 *p;
1668 	size_t i;
1669 	INT32 y;
1670 	INT32 charflags = 0;
1671 	INT32 charwidth = 8 * con_scalefactor;
1672 	INT32 charheight = 8 * con_scalefactor;
1673 
1674 	if (con_hudlines <= 0)
1675 		return;
1676 
1677 	if (chat_on && OLDCHAT)
1678 		y = charheight; // leave place for chat input in the first row of text (only do it if consolechat is on.)
1679 	else
1680 		y = 0;
1681 
1682 	for (i = con_cy - con_hudlines+1; i <= con_cy; i++)
1683 	{
1684 		size_t c;
1685 		INT32 x;
1686 
1687 		if ((signed)i < 0)
1688 			continue;
1689 		if (con_hudtime[i%con_hudlines] == 0)
1690 			continue;
1691 
1692 		p = (UINT8 *)&con_buffer[(i%con_totallines)*con_width];
1693 
1694 		for (c = 0, x = 0; c < con_width; c++, x += charwidth, p++)
1695 		{
1696 			while (*p & 0x80) // Graue 06-19-2004
1697 			{
1698 				charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
1699 				p++;
1700 			}
1701 			if (*p < HU_FONTSTART)
1702 				;//charwidth = 4 * con_scalefactor;
1703 			else
1704 			{
1705 				//charwidth = (hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
1706 				V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
1707 			}
1708 		}
1709 
1710 		//V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, true);
1711 		y += charheight;
1712 	}
1713 
1714 	// top screen lines that might need clearing when view is reduced
1715 	con_clearlines = y; // this is handled by HU_Erase();
1716 }
1717 
1718 // Lactozilla: Draws the console's background picture.
CON_DrawBackpic(void)1719 static void CON_DrawBackpic(void)
1720 {
1721 	patch_t *con_backpic;
1722 	lumpnum_t piclump;
1723 	int x, w, h;
1724 
1725 	// Get the lumpnum for CONSBACK, STARTUP (Only during game startup) or fallback into MISSING.
1726 	if (con_startup)
1727 		piclump = W_CheckNumForName("STARTUP");
1728 	else
1729 		piclump = W_CheckNumForName("CONSBACK");
1730 
1731 	if (piclump == LUMPERROR)
1732 		piclump = W_GetNumForName("MISSING");
1733 
1734 	// Cache the patch.
1735 	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
1736 
1737 	// Center the backpic, and draw a vertically cropped patch.
1738 	w = (con_backpic->width * vid.dupx);
1739 	x = (vid.width / 2) - (w / 2);
1740 	h = con_curlines/vid.dupy;
1741 
1742 	// If the patch doesn't fill the entire screen,
1743 	// then fill the sides with a solid color.
1744 	if (x > 0)
1745 	{
1746 		column_t *column = (column_t *)((UINT8 *)(con_backpic->columns) + (con_backpic->columnofs[0]));
1747 		if (!column->topdelta)
1748 		{
1749 			UINT8 *source = (UINT8 *)(column) + 3;
1750 			INT32 color = (source[0] | V_NOSCALESTART);
1751 			// left side
1752 			V_DrawFill(0, 0, x, con_curlines, color);
1753 			// right side
1754 			V_DrawFill((x + w), 0, (vid.width - w), con_curlines, color);
1755 		}
1756 	}
1757 
1758 	// Draw the patch.
1759 	V_DrawCroppedPatch(x << FRACBITS, 0, FRACUNIT, V_NOSCALESTART, con_backpic,
1760 			0, ( BASEVIDHEIGHT - h ), BASEVIDWIDTH, h);
1761 
1762 	// Unlock the cached patch.
1763 	W_UnlockCachedPatch(con_backpic);
1764 }
1765 
1766 // draw the console background, text, and prompt if enough place
1767 //
CON_DrawConsole(void)1768 static void CON_DrawConsole(void)
1769 {
1770 	UINT8 *p;
1771 	size_t i;
1772 	INT32 y;
1773 	INT32 charflags = 0;
1774 	INT32 charwidth = (INT32)con_scalefactor << 3;
1775 	INT32 charheight = charwidth;
1776 	INT32 minheight = 20 * con_scalefactor;	// 20 = 8+8+4
1777 
1778 	if (con_curlines <= 0)
1779 		return;
1780 
1781 	//FIXME: refresh borders only when console bg is translucent
1782 	con_clearlines = con_curlines; // clear console draw from view borders
1783 	con_hudupdate = true; // always refresh while console is on
1784 
1785 	// draw console background
1786 	if (cons_backpic.value || con_forcepic)
1787 		CON_DrawBackpic();
1788 	else
1789 	{
1790 		// inu: no more width (was always 0 and vid.width)
1791 		if (rendermode != render_none)
1792 			V_DrawFadeConsBack(con_curlines); // translucent background
1793 	}
1794 
1795 	// draw console text lines from top to bottom
1796 	if (con_curlines < minheight)
1797 		return;
1798 
1799 	i = con_cy - con_scrollup;
1800 
1801 	// skip the last empty line due to the cursor being at the start of a new line
1802 	i--;
1803 
1804 	i -= (con_curlines - minheight) / charheight;
1805 
1806 	if (rendermode == render_none) return;
1807 
1808 	for (y = (con_curlines-minheight) % charheight; y <= con_curlines-minheight; y += charheight, i++)
1809 	{
1810 		INT32 x;
1811 		size_t c;
1812 
1813 		p = (UINT8 *)&con_buffer[((i > 0 ? i : 0)%con_totallines)*con_width];
1814 
1815 		for (c = 0, x = charwidth; c < con_width; c++, x += charwidth, p++)
1816 		{
1817 			while (*p & 0x80)
1818 			{
1819 				charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
1820 				p++;
1821 			}
1822 			V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
1823 		}
1824 	}
1825 
1826 	// draw prompt if enough place (not while game startup)
1827 	if ((con_curlines == con_destlines) && (con_curlines >= minheight) && !con_startup)
1828 		CON_DrawInput();
1829 }
1830 
1831 // Console refresh drawer, call each frame
1832 //
CON_Drawer(void)1833 void CON_Drawer(void)
1834 {
1835 	Lock_state();
1836 
1837 	if (!con_started || !graphics_started)
1838 	{
1839 		Unlock_state();
1840 		return;
1841 	}
1842 
1843 	if (con_recalc)
1844 	{
1845 		CON_RecalcSize();
1846 		if (con_curlines <= 0)
1847 			CON_ClearHUD();
1848 	}
1849 
1850 	if (con_curlines > 0)
1851 		CON_DrawConsole();
1852 	else if (gamestate == GS_LEVEL
1853 	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
1854 	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
1855 		CON_DrawHudlines();
1856 
1857 	Unlock_state();
1858 }
1859