+================
+*/
+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);
+}
+
+
+/**
+ * @brief Returns a horizontal line
+ * @details Returns a graphical horizontal line of length len, but never wider than the
+ * console. Includes a newline, unless len is >= to the console width
+ * @note Authored by johnfitz
+ *
+ * @param[in] len Length of the horizontal line
+ *
+ * @return A string of the line
+ */
+const char *Con_Quakebar(int len, char *bar, size_t barsize)
+{
+ assert(barsize >= 5);
+
+ len = min(len, (int)barsize - 2);
+ len = min(len, con_linewidth);
+
+ bar[0] = '\35';
+ memset(&bar[1], '\36', len - 2);
+ bar[len - 1] = '\37';
+
+ if (len < con_linewidth)
+ {
+ bar[len] = '\n';
+ bar[len + 1] = 0;
+ }
+ else
+ bar[len] = 0;
+
+ return bar;
+}
+
+/**
+ * @brief Left-pad a string with spaces to make it appear centered
+ * @note Authored by johnfitz
+ *
+ * @param[in] maxLineLength Center-align
+ * @param[in] fmt A printf format string
+ * @param[in] <unnamed> Zero or more values used by fmt
+ */
+void Con_CenterPrintf(int maxLineLength, const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAX_INPUTLINE]; // the original message
+ char line[MAX_INPUTLINE]; // one line from the message
+ char spaces[21]; // buffer for spaces
+ char *msgCursor, *lineEnding;
+ int lineLength, msgLength;
+ size_t indentSize;
+
+ va_start(argptr, fmt);
+ msgLength = dpvsnprintf(msg, sizeof (msg), fmt, argptr);
+ va_end(argptr);
+
+ if (msgLength < 0)
+ {
+ Con_Printf(CON_WARN "The message given to Con_CenterPrintf was too long\n");
+ return;
+ }
+
+ maxLineLength = min(maxLineLength, con_linewidth);
+
+ for (msgCursor = msg; *msgCursor;)
+ {
+ lineEnding = strchr(msgCursor, '\n');
+ if (lineEnding)
+ {
+ lineLength = dp_ustr2stp(line, sizeof(line), msgCursor, lineEnding - msgCursor) - line;
+ msgCursor = lineEnding + 1;
+ }
+ else // last line
+ {
+ lineLength = dp_strlcpy(line, msgCursor, sizeof(line));
+ msgCursor = msg + msgLength;
+ }
+
+ if (lineLength < maxLineLength)
+ {
+ indentSize = min(sizeof(spaces) - 1, (size_t)(maxLineLength - lineLength) / 2);
+ memset(spaces, ' ', indentSize);
+ spaces[indentSize] = 0;
+ Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s%s\n", spaces, line);
+ }
+ else
+ Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s\n", line);
+ }
+}
+
+/**
+ * @brief Prints a center-aligned message to the console
+ * @note Authored by johnfitz
+ *
+ * @param[in] str A multiline string to print
+ */
+void Con_CenterPrint(const char *str)
+{
+ char bar[42];
+
+ Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", Con_Quakebar(40, bar, sizeof(bar)));
+ Con_CenterPrintf(40, "%s\n", str);
+ Con_MaskPrintf(CON_MASK_HIDENOTIFY, "%s", bar);
+}
+
+
+
+/*
+==============================================================================
+
+DRAWING
+
+==============================================================================
+*/
+
+/*
+================
+Con_DrawInput
+
+It draws either the console input line or the chat input line (if is_console is false)
+The input line scrolls horizontally if typing goes beyond the right edge
+
+Modified by EvilTypeGuy eviltypeguy@qeradiant.com
+================
+*/
+static void Con_DrawInput(qbool is_console, float x, float v, float inputsize)
+{
+ int y, i, col_out, linepos, text_start, prefix_start = 0;
+ char text[MAX_INPUTLINE + 5 + 9 + 1]; // space for ^xRGB, "say_team:" and \0
+ float xo;
+ size_t len_out;
+ const char *prefix;
+ dp_font_t *fnt;
+
+ if (is_console && !key_consoleactive)
+ return; // don't draw anything
+
+ if (is_console)
+ {
+ // empty prefix because ] is part of the console edit line
+ prefix = "";
+ dp_strlcpy(text, key_line, sizeof(text));
+ linepos = key_linepos;
+ fnt = FONT_CONSOLE;
+ }
+ else
+ {
+ if (chat_mode < 0)
+ prefix = "]";
+ else if(chat_mode)
+ prefix = "say_team:";
+ else
+ prefix = "say:";
+ dp_strlcpy(text, chat_buffer, sizeof(text));
+ linepos = chat_bufferpos;
+ fnt = FONT_CHAT;
+ }
+
+ y = (int)strlen(text);
+
+ // make the color code visible when the cursor is inside it
+ if(text[linepos] != 0)
+ {
+ for(i=1; i < 5 && linepos - i > 0; ++i)
+ if(text[linepos-i] == STRING_COLOR_TAG)
+ {
+ int caret_pos, ofs = 0;
+ caret_pos = linepos - i;
+ if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
+ ofs = 1;
+ else if(i == 1 && isdigit(text[caret_pos+1]))
+ ofs = 2;
+ else if(text[caret_pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(text[caret_pos+2]) && isxdigit(text[caret_pos+3]) && isxdigit(text[caret_pos+4]))
+ ofs = 5;
+ if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
+ {
+ int carets = 1;
+ while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
+ ++carets;
+ if(carets & 1)
+ {
+ // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
+ // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
+ memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
+ text[caret_pos + ofs] = STRING_COLOR_TAG;
+ y += ofs + 1;
+ text[y] = 0;
+ }
+ }
+ break;
+ }
+ }
+
+ if (!is_console)
+ {
+ prefix_start = x;
+ x += DrawQ_TextWidth(prefix, 0, inputsize, inputsize, false, fnt);
+ }
+
+ len_out = linepos;
+ col_out = -1;
+ xo = 0;
+ if (linepos > 0)
+ xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, inputsize, inputsize, &col_out, false, fnt, 1000000000);
+
+ text_start = x + (vid_conwidth.value - x) * 0.95 - xo; // scroll
+ if(text_start >= x)
+ text_start = x;
+ else if (!is_console)
+ prefix_start -= (x - text_start);
+
+ if (!is_console)
+ DrawQ_String(prefix_start, v, prefix, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
+
+ DrawQ_String(text_start, v, text, y + 3, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, fnt);
+
+ // draw a cursor on top of this
+ if ((int)(host.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;
+ char charbuf16[16];
+ curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
+ memcpy(text, curbuf, len);
+ text[len] = 0;
+ }
+ DrawQ_String(text_start + xo, v, text, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, fnt);
+ }
+}
+
+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;
+
+static 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
+ {
+ Sys_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;
+ }
+}
+
+static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qbool isContinuation)
+{
+ (void) passthrough;
+ (void) line;
+ (void) length;
+ (void) width;
+ (void) isContinuation;
+ return 1;
+}
+
+static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qbool 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;
+}
+
+static int Con_DrawNotifyRect(unsigned mask_must, unsigned 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 len;
+ 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;
+
+ len = 0;
+ Con_WordWidthFunc(&ti, NULL, &len, -1);
+ len = strlen(continuationString);
+ continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -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;
+ float chatstart, notifystart, inputsize, height;
+ float align;
+ int numChatlines;
+ int chatpos;
+
+ if (con_mutex) Thread_LockMutex(con_mutex);
+ 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(IS_OLDNEXUIZ_DERIVED(gamemode))
+ 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, "^3 ... ");
+ v += height;
+ }
+ if (key_dest == key_message)
+ {
+ inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
+ Con_DrawInput(false, x, v, inputsize);
+ }
+ else
+ chat_bufferpos = 0;