+ con_linewidth = width;
+
+ for(i = 0; i < CON_LINES_COUNT; ++i)
+ CON_LINES(i).height = -1; // recalculate when next needed
+
+ Con_ClearNotify();
+ con_backscroll = 0;
+}
+
+//[515]: the simplest command ever
+//LordHavoc: not so simple after I made it print usage...
+static void Con_Maps_f (void)
+{
+ if (Cmd_Argc() > 2)
+ {
+ Con_Printf("usage: maps [mapnameprefix]\n");
+ return;
+ }
+ else if (Cmd_Argc() == 2)
+ GetMapList(Cmd_Argv(1), NULL, 0);
+ else
+ GetMapList("", NULL, 0);
+}
+
+void Con_ConDump_f (void)
+{
+ int i;
+ qfile_t *file;
+ if (Cmd_Argc() != 2)
+ {
+ Con_Printf("usage: condump <filename>\n");
+ return;
+ }
+ file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
+ if (!file)
+ {
+ Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
+ return;
+ }
+ for(i = 0; i < CON_LINES_COUNT; ++i)
+ {
+ FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
+ FS_Write(file, "\n", 1);
+ }
+ FS_Close(file);
+}
+
+void Con_Clear_f (void)
+{
+ ConBuffer_Clear(&con);
+}
+
+/*
+================
+Con_Init
+================
+*/
+void Con_Init (void)
+{
+ con_linewidth = 80;
+ ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
+
+ // Allocate a log queue, this will be freed after configs are parsed
+ logq_size = MAX_INPUTLINE;
+ logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
+ logq_ind = 0;
+
+ Cvar_RegisterVariable (&sys_colortranslation);
+ Cvar_RegisterVariable (&sys_specialcharactertranslation);
+
+ Cvar_RegisterVariable (&log_file);
+ Cvar_RegisterVariable (&log_dest_udp);
+
+ // support for the classic Quake option
+// COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
+ if (COM_CheckParm ("-condebug") != 0)
+ Cvar_SetQuick (&log_file, "qconsole.log");
+
+ // register our cvars
+ Cvar_RegisterVariable (&con_chat);
+ Cvar_RegisterVariable (&con_chatpos);
+ Cvar_RegisterVariable (&con_chatrect_x);
+ Cvar_RegisterVariable (&con_chatrect_y);
+ Cvar_RegisterVariable (&con_chatrect);
+ Cvar_RegisterVariable (&con_chatsize);
+ Cvar_RegisterVariable (&con_chattime);
+ Cvar_RegisterVariable (&con_chatwidth);
+ Cvar_RegisterVariable (&con_notify);
+ Cvar_RegisterVariable (&con_notifyalign);
+ Cvar_RegisterVariable (&con_notifysize);
+ Cvar_RegisterVariable (&con_notifytime);
+ Cvar_RegisterVariable (&con_textsize);
+ Cvar_RegisterVariable (&con_chatsound);
+
+ // --blub
+ Cvar_RegisterVariable (&con_nickcompletion);
+ Cvar_RegisterVariable (&con_nickcompletion_flags);
+
+ Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
+ Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
+ Cvar_RegisterVariable (&con_completion_exec); // *.cfg
+
+ // register our commands
+ Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
+ Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
+ Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
+ Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
+ Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
+ Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
+ Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
+
+ con_initialized = true;
+ Con_DPrint("Console initialized.\n");
+}
+
+void Con_Shutdown (void)
+{
+ ConBuffer_Shutdown(&con);
+}
+
+/*
+================
+Con_PrintToHistory
+
+Handles cursor positioning, line wrapping, etc
+All console printing must go through this in order to be displayed
+If no console is visible, the notify window will pop up.
+================
+*/
+void Con_PrintToHistory(const char *txt, int mask)
+{
+ // process:
+ // \n goes to next line
+ // \r deletes current line and makes a new one
+
+ static int cr_pending = 0;
+ static char buf[CON_TEXTSIZE];
+ static int bufpos = 0;
+
+ if(!con.text) // FIXME uses a non-abstracted property of con
+ return;
+
+ for(; *txt; ++txt)
+ {
+ if(cr_pending)
+ {
+ ConBuffer_DeleteLastLine(&con);
+ cr_pending = 0;
+ }
+ switch(*txt)
+ {
+ case 0:
+ break;
+ case '\r':
+ ConBuffer_AddLine(&con, buf, bufpos, mask);
+ bufpos = 0;
+ cr_pending = 1;
+ break;
+ case '\n':
+ ConBuffer_AddLine(&con, buf, bufpos, mask);
+ bufpos = 0;
+ break;
+ default:
+ buf[bufpos++] = *txt;
+ if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
+ {
+ ConBuffer_AddLine(&con, buf, bufpos, mask);
+ bufpos = 0;
+ }
+ break;
+ }
+ }
+}
+
+/*! The translation table between the graphical font and plain ASCII --KB */
+static char qfont_table[256] = {
+ '\0', '#', '#', '#', '#', '.', '#', '#',
+ '#', 9, 10, '#', ' ', 13, '.', '.',
+ '[', ']', '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', '.', '<', '=', '>',
+ ' ', '!', '"', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', '{', '|', '}', '~', '<',
+
+ '<', '=', '>', '#', '#', '.', '#', '#',
+ '#', '#', ' ', '#', ' ', '>', '.', '.',
+ '[', ']', '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', '.', '<', '=', '>',
+ ' ', '!', '"', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', '{', '|', '}', '~', '<'
+};
+
+void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
+{
+ rcon_redirect_sock = sock;
+ rcon_redirect_dest = dest;
+ rcon_redirect_proquakeprotocol = proquakeprotocol;
+ if (rcon_redirect_proquakeprotocol)
+ {
+ // reserve space for the packet header
+ rcon_redirect_buffer[0] = 0;
+ rcon_redirect_buffer[1] = 0;
+ rcon_redirect_buffer[2] = 0;
+ rcon_redirect_buffer[3] = 0;
+ // this is a reply to a CCREQ_RCON
+ rcon_redirect_buffer[4] = CCREP_RCON;
+ }
+ else
+ memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
+ rcon_redirect_bufferpos = 5;
+}
+
+void Con_Rcon_Redirect_Flush(void)
+{
+ rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
+ if (rcon_redirect_proquakeprotocol)
+ {
+ // update the length in the packet header
+ StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
+ }
+ NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
+ memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
+ rcon_redirect_bufferpos = 5;
+ rcon_redirect_proquakeprotocol = false;
+}
+
+void Con_Rcon_Redirect_End(void)
+{
+ Con_Rcon_Redirect_Flush();
+ rcon_redirect_dest = NULL;
+ rcon_redirect_sock = NULL;
+}
+
+void Con_Rcon_Redirect_Abort(void)
+{
+ rcon_redirect_dest = NULL;
+ rcon_redirect_sock = NULL;
+}
+
+/*
+================
+Con_Rcon_AddChar
+================
+*/
+/// Adds a character to the rcon buffer.
+void Con_Rcon_AddChar(int c)
+{
+ if(log_dest_buffer_appending)
+ return;
+ ++log_dest_buffer_appending;
+
+ // if this print is in response to an rcon command, add the character
+ // to the rcon redirect buffer
+
+ if (rcon_redirect_dest)
+ {
+ rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
+ if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
+ Con_Rcon_Redirect_Flush();
+ }
+ else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
+ {
+ if(log_dest_buffer_pos == 0)
+ Log_DestBuffer_Init();
+ log_dest_buffer[log_dest_buffer_pos++] = c;
+ if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
+ Log_DestBuffer_Flush();
+ }
+ else
+ log_dest_buffer_pos = 0;
+
+ --log_dest_buffer_appending;
+}
+
+/**
+ * Convert an RGB color to its nearest quake color.
+ * I'll cheat on this a bit by translating the colors to HSV first,
+ * S and V decide if it's black or white, otherwise, H will decide the
+ * actual color.
+ * @param _r Red (0-255)
+ * @param _g Green (0-255)
+ * @param _b Blue (0-255)
+ * @return A quake color character.
+ */
+static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
+{
+ float r = ((float)_r)/255.0;
+ float g = ((float)_g)/255.0;
+ float b = ((float)_b)/255.0;
+ float min = min(r, min(g, b));
+ float max = max(r, max(g, b));
+
+ int h; ///< Hue angle [0,360]
+ float s; ///< Saturation [0,1]
+ float v = max; ///< In HSV v == max [0,1]
+
+ if(max == min)
+ s = 0;
+ else
+ s = 1.0 - (min/max);
+
+ // Saturation threshold. We now say 0.2 is the minimum value for a color!
+ if(s < 0.2)
+ {
+ // If the value is less than half, return a black color code.
+ // Otherwise return a white one.
+ if(v < 0.5)
+ return '0';
+ return '7';
+ }
+
+ // Let's get the hue angle to define some colors:
+ if(max == min)
+ h = 0;
+ else if(max == r)
+ h = (int)(60.0 * (g-b)/(max-min))%360;
+ else if(max == g)
+ h = (int)(60.0 * (b-r)/(max-min) + 120);
+ else // if(max == b) redundant check
+ h = (int)(60.0 * (r-g)/(max-min) + 240);
+
+ if(h < 36) // *red* to orange
+ return '1';
+ else if(h < 80) // orange over *yellow* to evilish-bright-green
+ return '3';
+ else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
+ return '2';
+ else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
+ return '5';
+ else if(h < 270) // darkish blue over *dark blue* to cool purple
+ return '4';
+ else if(h < 330) // cool purple over *purple* to ugly swiny red
+ return '6';
+ else // ugly red to red closes the circly
+ return '1';
+}
+
+/*
+================
+Con_MaskPrint
+================
+*/
+extern cvar_t timestamps;
+extern cvar_t timeformat;
+extern qboolean sys_nostdout;
+void Con_MaskPrint(int additionalmask, const char *msg)
+{
+ static int mask = 0;
+ static int index = 0;
+ static char line[MAX_INPUTLINE];
+
+ for (;*msg;msg++)
+ {
+ Con_Rcon_AddChar(*msg);
+ if (index == 0)
+ mask |= additionalmask;
+ // if this is the beginning of a new line, print timestamp
+ if (index == 0)
+ {
+ const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
+ // reset the color
+ // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
+ line[index++] = STRING_COLOR_TAG;
+ // assert( STRING_COLOR_DEFAULT < 10 )
+ line[index++] = STRING_COLOR_DEFAULT + '0';
+ // special color codes for chat messages must always come first
+ // for Con_PrintToHistory to work properly
+ if (*msg == 1 || *msg == 2)
+ {
+ // play talk wav
+ if (*msg == 1)
+ {
+ if (con_chatsound.value)
+ {
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ if(msg[1] == '\r' && cl.foundtalk2wav)
+ S_LocalSound ("sound/misc/talk2.wav");
+ else
+ S_LocalSound ("sound/misc/talk.wav");
+ }
+ else
+ {
+ if (msg[1] == '(' && cl.foundtalk2wav)
+ S_LocalSound ("sound/misc/talk2.wav");
+ else
+ S_LocalSound ("sound/misc/talk.wav");
+ }
+ }
+ mask = CON_MASK_CHAT;
+ }
+ line[index++] = STRING_COLOR_TAG;
+ line[index++] = '3';
+ msg++;
+ Con_Rcon_AddChar(*msg);
+ }
+ // store timestamp
+ for (;*timestamp;index++, timestamp++)
+ if (index < (int)sizeof(line) - 2)
+ line[index] = *timestamp;
+ }
+ // append the character
+ line[index++] = *msg;
+ // if this is a newline character, we have a complete line to print
+ if (*msg == '\n' || index >= (int)sizeof(line) / 2)
+ {
+ // terminate the line
+ line[index] = 0;
+ // send to log file
+ Log_ConPrint(line);
+ // send to scrollable buffer
+ if (con_initialized && cls.state != ca_dedicated)
+ {
+ Con_PrintToHistory(line, mask);
+ mask = 0;
+ }
+ // send to terminal or dedicated server window
+ if (!sys_nostdout)
+ {
+ unsigned char *p;
+ if(sys_specialcharactertranslation.integer)
+ {
+ for (p = (unsigned char *) line;*p; p++)
+ *p = qfont_table[*p];
+ }
+
+ if(sys_colortranslation.integer == 1) // ANSI
+ {
+ static char printline[MAX_INPUTLINE * 4 + 3];
+ // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
+ // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
+ int lastcolor = 0;
+ const char *in;
+ char *out;
+ int color;
+ for(in = line, out = printline; *in; ++in)
+ {
+ switch(*in)
+ {
+ case STRING_COLOR_TAG:
+ if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
+ {
+ char r = tolower(in[2]);
+ char g = tolower(in[3]);
+ char b = tolower(in[4]);
+ // it's a hex digit already, so the else part needs no check --blub
+ if(isdigit(r)) r -= '0';
+ else r -= 87;
+ if(isdigit(g)) g -= '0';
+ else g -= 87;
+ if(isdigit(b)) b -= '0';
+ else b -= 87;
+
+ color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
+ in += 3; // 3 only, the switch down there does the fourth
+ }
+ else
+ color = in[1];
+
+ switch(color)
+ {
+ case STRING_COLOR_TAG:
+ ++in;
+ *out++ = STRING_COLOR_TAG;
+ break;
+ case '0':
+ case '7':
+ // normal color
+ ++in;
+ if(lastcolor == 0) break; else lastcolor = 0;
+ *out++ = 0x1B; *out++ = '['; *out++ = 'm';
+ break;
+ case '1':
+ // light red
+ ++in;
+ if(lastcolor == 1) break; else lastcolor = 1;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
+ break;
+ case '2':
+ // light green
+ ++in;
+ if(lastcolor == 2) break; else lastcolor = 2;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
+ break;
+ case '3':
+ // yellow
+ ++in;
+ if(lastcolor == 3) break; else lastcolor = 3;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
+ break;
+ case '4':
+ // light blue
+ ++in;
+ if(lastcolor == 4) break; else lastcolor = 4;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
+ break;
+ case '5':
+ // light cyan
+ ++in;
+ if(lastcolor == 5) break; else lastcolor = 5;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
+ break;
+ case '6':
+ // light magenta
+ ++in;
+ if(lastcolor == 6) break; else lastcolor = 6;
+ *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
+ break;
+ // 7 handled above
+ case '8':
+ case '9':
+ // bold normal color
+ ++in;
+ if(lastcolor == 8) break; else lastcolor = 8;
+ *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
+ break;
+ default:
+ *out++ = STRING_COLOR_TAG;
+ break;
+ }
+ break;
+ case '\n':
+ if(lastcolor != 0)
+ {
+ *out++ = 0x1B; *out++ = '['; *out++ = 'm';
+ lastcolor = 0;
+ }
+ *out++ = *in;
+ break;
+ default:
+ *out++ = *in;
+ break;
+ }
+ }
+ if(lastcolor != 0)
+ {
+ *out++ = 0x1B;
+ *out++ = '[';
+ *out++ = 'm';
+ }
+ *out++ = 0;
+ Sys_PrintToTerminal(printline);
+ }
+ else if(sys_colortranslation.integer == 2) // Quake
+ {
+ Sys_PrintToTerminal(line);
+ }
+ else // strip
+ {
+ static char printline[MAX_INPUTLINE]; // it can only get shorter here
+ const char *in;
+ char *out;
+ for(in = line, out = printline; *in; ++in)
+ {
+ switch(*in)
+ {
+ case STRING_COLOR_TAG:
+ switch(in[1])
+ {
+ case STRING_COLOR_RGB_TAG_CHAR:
+ if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
+ {
+ in+=4;
+ break;
+ }
+ *out++ = STRING_COLOR_TAG;
+ *out++ = STRING_COLOR_RGB_TAG_CHAR;
+ ++in;
+ break;
+ case STRING_COLOR_TAG:
+ ++in;
+ *out++ = STRING_COLOR_TAG;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ ++in;
+ break;
+ default:
+ *out++ = STRING_COLOR_TAG;
+ break;
+ }
+ break;
+ default:
+ *out++ = *in;
+ break;
+ }
+ }
+ *out++ = 0;
+ Sys_PrintToTerminal(printline);
+ }
+ }
+ // empty the line buffer
+ index = 0;
+ }
+ }
+}
+
+/*
+================
+Con_MaskPrintf
+================
+*/
+void Con_MaskPrintf(int mask, const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAX_INPUTLINE];
+
+ va_start(argptr,fmt);
+ dpvsnprintf(msg,sizeof(msg),fmt,argptr);
+ va_end(argptr);
+
+ Con_MaskPrint(mask, msg);
+}
+
+/*
+================
+Con_Print
+================
+*/
+void Con_Print(const char *msg)
+{
+ Con_MaskPrint(CON_MASK_PRINT, msg);
+}
+
+/*
+================
+Con_Printf
+================
+*/
+void Con_Printf(const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAX_INPUTLINE];
+
+ va_start(argptr,fmt);
+ dpvsnprintf(msg,sizeof(msg),fmt,argptr);
+ va_end(argptr);
+
+ Con_MaskPrint(CON_MASK_PRINT, msg);
+}
+
+/*
+================
+Con_DPrint
+================
+*/
+void Con_DPrint(const char *msg)
+{
+ if(developer.integer < 0) // at 0, we still add to the buffer but hide
+ return;
+
+ Con_MaskPrint(CON_MASK_DEVELOPER, msg);
+}
+
+/*
+================
+Con_DPrintf
+================
+*/
+void Con_DPrintf(const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAX_INPUTLINE];
+
+ if(developer.integer < 0) // at 0, we still add to the buffer but hide
+ return;
+
+ va_start(argptr,fmt);
+ dpvsnprintf(msg,sizeof(msg),fmt,argptr);
+ va_end(argptr);
+
+ Con_MaskPrint(CON_MASK_DEVELOPER, msg);
+}
+
+
+/*
+==============================================================================
+
+DRAWING
+
+==============================================================================
+*/
+
+/*
+================
+Con_DrawInput
+
+The input line scrolls horizontally if typing goes beyond the right edge
+
+Modified by EvilTypeGuy eviltypeguy@qeradiant.com
+================
+*/
+extern cvar_t r_font_disable_freetype;
+void Con_DrawInput (void)
+{
+ int y;
+ int i;
+ char editlinecopy[MAX_INPUTLINE+1], *text;
+ float x, xo;
+ size_t len_out;
+ int col_out;
+
+ if (!key_consoleactive)
+ return; // don't draw anything
+
+ strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
+ text = editlinecopy;
+
+ // Advanced Console Editing by Radix radix@planetquake.com
+ // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
+ // use strlen of edit_line instead of key_linepos to allow editing
+ // of early characters w/o erasing
+
+ y = (int)strlen(text);
+
+ // append enoug nul-bytes to cover the utf8-versions of the cursor too
+ for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
+ text[i] = 0;
+
+ // add the cursor frame
+ if (r_font_disable_freetype.integer)
+ {
+ // this code is freetype incompatible!
+ if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
+ {
+ if (!utf8_enable.integer)
+ text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
+ else if (y + 3 < (int)sizeof(editlinecopy)-1)
+ {
+ int ofs = u8_bytelen(text + key_linepos, 1);
+ size_t len;
+ const char *curbuf;
+ curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
+
+ if (curbuf)
+ {
+ memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
+ memcpy(text + key_linepos, curbuf, len);
+ }
+ } else
+ text[key_linepos] = '-' + ('+' - '-') * key_insert;
+ }
+ }
+
+// text[key_linepos + 1] = 0;
+
+ len_out = key_linepos;
+ col_out = -1;
+ xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
+ x = vid_conwidth.value * 0.95 - xo; // scroll
+ if(x >= 0)
+ x = 0;
+
+ // draw it
+ DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
+
+ // add a cursor on top of this (when using freetype)
+ if (!r_font_disable_freetype.integer)
+ {
+ if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
+ {
+ if (!utf8_enable.integer)
+ {
+ text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
+ text[1] = 0;
+ }
+ else
+ {
+ size_t len;
+ const char *curbuf;
+ curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
+ memcpy(text, curbuf, len);
+ text[len] = 0;
+ }
+ DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE);
+ }
+ }
+
+ // remove cursor
+// key_line[key_linepos] = 0;
+}
+
+typedef struct
+{
+ dp_font_t *font;
+ float alignment; // 0 = left, 0.5 = center, 1 = right
+ float fontsize;
+ float x;
+ float y;
+ float width;
+ float ymin, ymax;
+ const char *continuationString;
+
+ // PRIVATE:
+ int colorindex; // init to -1
+}
+con_text_info_t;
+
+float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
+{
+ con_text_info_t *ti = (con_text_info_t *) passthrough;
+ if(w == NULL)
+ {
+ ti->colorindex = -1;
+ return ti->fontsize * ti->font->maxwidth;
+ }
+ if(maxWidth >= 0)
+ return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
+ else if(maxWidth == -1)
+ return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
+ else
+ {
+ printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
+ // Note: this is NOT a Con_Printf, as it could print recursively
+ return 0;
+ }
+}
+
+int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
+{
+ (void) passthrough;
+ (void) line;
+ (void) length;
+ (void) width;
+ (void) isContinuation;
+ return 1;
+}
+
+int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
+{
+ con_text_info_t *ti = (con_text_info_t *) passthrough;
+
+ if(ti->y < ti->ymin - 0.001)
+ (void) 0;
+ else if(ti->y > ti->ymax - ti->fontsize + 0.001)
+ (void) 0;
+ else
+ {
+ int x = (int) (ti->x + (ti->width - width) * ti->alignment);
+ if(isContinuation && *ti->continuationString)
+ x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
+ if(length > 0)
+ DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
+ }
+
+ ti->y += ti->fontsize;
+ return 1;
+}
+
+int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
+{
+ int i;
+ int lines = 0;
+ int maxlines = (int) floor(height / fontsize + 0.01f);
+ int startidx;
+ int nskip = 0;
+ int continuationWidth = 0;
+ size_t l;
+ double t = cl.time; // saved so it won't change
+ con_text_info_t ti;
+
+ ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
+ ti.fontsize = fontsize;
+ ti.alignment = alignment_x;
+ ti.width = width;
+ ti.ymin = y;
+ ti.ymax = y + height;
+ ti.continuationString = continuationString;
+
+ l = 0;
+ Con_WordWidthFunc(&ti, NULL, &l, -1);
+ l = strlen(continuationString);
+ continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
+
+ // first find the first line to draw by backwards iterating and word wrapping to find their length...
+ startidx = CON_LINES_COUNT;
+ for(i = CON_LINES_COUNT - 1; i >= 0; --i)
+ {
+ con_lineinfo_t *l = &CON_LINES(i);
+ int mylines;
+
+ if((l->mask & mask_must) != mask_must)
+ continue;
+ if(l->mask & mask_mustnot)
+ continue;
+ if(maxage && (l->addtime < t - maxage))
+ continue;
+
+ // WE FOUND ONE!
+ // Calculate its actual height...
+ mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
+ if(lines + mylines >= maxlines)
+ {
+ nskip = lines + mylines - maxlines;
+ lines = maxlines;
+ startidx = i;
+ break;
+ }
+ lines += mylines;
+ startidx = i;
+ }
+
+ // then center according to the calculated amount of lines...
+ ti.x = x;
+ ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
+
+ // then actually draw
+ for(i = startidx; i < CON_LINES_COUNT; ++i)
+ {
+ con_lineinfo_t *l = &CON_LINES(i);
+
+ if((l->mask & mask_must) != mask_must)
+ continue;
+ if(l->mask & mask_mustnot)
+ continue;
+ if(maxage && (l->addtime < t - maxage))
+ continue;
+
+ COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
+ }
+
+ return lines;
+}
+
+/*
+================
+Con_DrawNotify
+
+Draws the last few lines of output transparently over the game top
+================
+*/
+void Con_DrawNotify (void)
+{
+ float x, v, xr;
+ float chatstart, notifystart, inputsize, height;
+ float align;
+ char temptext[MAX_INPUTLINE];
+ int numChatlines;
+ int chatpos;
+
+ ConBuffer_FixTimes(&con);
+
+ numChatlines = con_chat.integer;
+
+ chatpos = con_chatpos.integer;
+
+ if (con_notify.integer < 0)
+ Cvar_SetValueQuick(&con_notify, 0);
+ if (gamemode == GAME_TRANSFUSION)
+ v = 8; // vertical offset
+ else
+ v = 0;
+
+ // GAME_NEXUIZ: center, otherwise left justify
+ align = con_notifyalign.value;
+ if(!*con_notifyalign.string) // empty string, evaluated to 0 above
+ {
+ if(gamemode == GAME_NEXUIZ)
+ align = 0.5;
+ }
+
+ if(numChatlines || !con_chatrect.integer)
+ {
+ if(chatpos == 0)
+ {
+ // first chat, input line, then notify
+ chatstart = v;
+ notifystart = v + (numChatlines + 1) * con_chatsize.value;
+ }
+ else if(chatpos > 0)
+ {
+ // first notify, then (chatpos-1) empty lines, then chat, then input
+ notifystart = v;
+ chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
+ }
+ else // if(chatpos < 0)
+ {
+ // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
+ notifystart = v;
+ chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
+ }
+ }
+ else
+ {
+ // just notify and input
+ notifystart = v;
+ chatstart = 0; // shut off gcc warning
+ }
+
+ v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
+
+ if(con_chatrect.integer)
+ {
+ x = con_chatrect_x.value * vid_conwidth.value;
+ v = con_chatrect_y.value * vid_conheight.value;
+ }
+ else
+ {
+ x = 0;
+ if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
+ v = chatstart;
+ }
+ height = numChatlines * con_chatsize.value;
+
+ if(numChatlines)
+ {
+ Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga
+ v += height;
+ }
+ if (key_dest == key_message)
+ {
+ //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
+ int colorindex = -1;
+ const char *cursor;
+ cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
+
+ // LordHavoc: speedup, and other improvements
+ if (chat_mode < 0)
+ dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
+ else if(chat_mode)
+ dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
+ else
+ dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
+
+ // FIXME word wrap
+ inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
+ xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
+ x = min(xr, x);
+ DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
+ }
+}
+
+/*
+================
+Con_LineHeight
+
+Returns the height of a given console line; calculates it if necessary.
+================
+*/
+int Con_LineHeight(int lineno)
+{
+ con_lineinfo_t *li = &CON_LINES(lineno);
+ if(li->height == -1)
+ {
+ float width = vid_conwidth.value;
+ con_text_info_t ti;
+ con_lineinfo_t *li = &CON_LINES(lineno);
+ ti.fontsize = con_textsize.value;
+ ti.font = FONT_CONSOLE;
+ li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
+ }
+ return li->height;
+}
+
+/*
+================
+Con_DrawConsoleLine
+
+Draws a line of the console; returns its height in lines.
+If alpha is 0, the line is not drawn, but still wrapped and its height
+returned.
+================
+*/
+int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
+{
+ float width = vid_conwidth.value;
+ con_text_info_t ti;
+ con_lineinfo_t *li = &CON_LINES(lineno);
+
+ if((li->mask & mask_must) != mask_must)
+ return 0;
+ if((li->mask & mask_mustnot) != 0)
+ return 0;
+
+ ti.continuationString = "";
+ ti.alignment = 0;
+ ti.fontsize = con_textsize.value;
+ ti.font = FONT_CONSOLE;
+ ti.x = 0;
+ ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
+ ti.ymin = ymin;
+ ti.ymax = ymax;
+ ti.width = width;
+
+ return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
+}
+
+/*
+================
+Con_LastVisibleLine
+
+Calculates the last visible line index and how much to show of it based on
+con_backscroll.
+================
+*/
+static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
+{
+ int lines_seen = 0;
+ int i;
+
+ if(con_backscroll < 0)
+ con_backscroll = 0;
+
+ *last = 0;
+
+ // now count until we saw con_backscroll actual lines
+ for(i = CON_LINES_COUNT - 1; i >= 0; --i)
+ if((CON_LINES(i).mask & mask_must) == mask_must)
+ if((CON_LINES(i).mask & mask_mustnot) == 0)
+ {
+ int h = Con_LineHeight(i);
+
+ // line is the last visible line?
+ *last = i;
+ if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
+ {
+ *limitlast = lines_seen + h - con_backscroll;
+ return;
+ }
+
+ lines_seen += h;
+ }
+
+ // if we get here, no line was on screen - scroll so that one line is
+ // visible then.
+ con_backscroll = lines_seen - 1;
+ *limitlast = 1;
+}
+
+/*
+================
+Con_DrawConsole
+
+Draws the console with the solid background
+The typing input line at the bottom should only be drawn if typing is allowed
+================
+*/
+void Con_DrawConsole (int lines)
+{
+ float alpha, alpha0;
+ double sx, sy;
+ int mask_must = 0;
+ int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
+ cachepic_t *conbackpic;
+
+ if (lines <= 0)
+ return;
+
+ if (con_backscroll < 0)
+ con_backscroll = 0;