From dafaf7b121521b45d07bd5295817a93dbe958255 Mon Sep 17 00:00:00 2001 From: divverent Date: Sat, 1 Dec 2007 14:29:38 +0000 Subject: [PATCH] the big chat area/font patch... hope it works well. Tested in Nexuiz and Quake. Fonts and a font generator for it will follow. Font specification is: a TGA as usual, and a .width file defining all character widths (first line is character spacing, then width of 0, of 1, of 2, of 3, ..., of 65 == "A", ..., of 255). An existing font is in Nexuiz svn as gfx/font_user0 git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@7739 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_screen.c | 62 ++-- common.c | 297 +++++++++++++++++++ common.h | 4 + console.c | 760 +++++++++++++++++++++++++++++++++++-------------- darkplaces.txt | 7 + draw.h | 27 +- gl_draw.c | 271 ++++++++++++++---- keys.c | 6 +- menu.c | 12 +- progsvm.h | 2 +- prvm_cmds.c | 21 +- prvm_edict.c | 1 + quakedef.h | 1 + sbar.c | 78 ++--- 14 files changed, 1211 insertions(+), 338 deletions(-) diff --git a/cl_screen.c b/cl_screen.c index 09f56f8a..99f5d166 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -57,6 +57,7 @@ int jpeg_supported = false; qboolean scr_initialized; // ready to draw float scr_con_current; +int scr_con_margin_bottom; extern int con_vislines; @@ -140,14 +141,14 @@ void SCR_DrawCenterString (void) // scan the number of characters on the line, not counting color codes char *newline = strchr(start, '\n'); int l = newline ? (newline - start) : (int)strlen(start); - int chars = COM_StringLengthNoColors(start, l, NULL); + float width = DrawQ_TextWidth_Font(start, l, 8, 8, false, FONT_CENTERPRINT); - x = (vid_conwidth.integer - chars*8)/2; + x = (vid_conwidth.integer - width)/2; if (l > 0) { if (remaining < l) l = remaining; - DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false); + DrawQ_String_Font(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false, FONT_CENTERPRINT); remaining -= l; if (remaining <= 0) return; @@ -438,10 +439,12 @@ SCR_DrawQWDownload */ static int SCR_DrawQWDownload(int offset) { + // sync with SCR_DownloadHeight int len; float x, y; float size = 8; char temp[256]; + if (!cls.qw_downloadname[0]) { cls.qw_downloadspeedrate = 0; @@ -460,10 +463,10 @@ static int SCR_DrawQWDownload(int offset) else dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i/%i) at %i bytes/s\n", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize, cls.qw_downloadspeedrate); len = (int)strlen(temp); - x = (vid_conwidth.integer - len*size) / 2; + x = (vid_conwidth.integer - DrawQ_TextWidth_Font(temp, len, size, size, 0, FONT_INFOBAR)) / 2; y = vid_conheight.integer - size - offset; - DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0); - DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true); + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String_Font(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); return 8; } @@ -474,6 +477,7 @@ SCR_DrawCurlDownload */ static int SCR_DrawCurlDownload(int offset) { + // sync with SCR_DownloadHeight int len; int nDownloads; int i; @@ -492,9 +496,9 @@ static int SCR_DrawCurlDownload(int offset) if(addinfo) { len = (int)strlen(addinfo); - x = (vid_conwidth.integer - len*size) / 2; - DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, 0.8, 0); - DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true); + x = (vid_conwidth.integer - DrawQ_TextWidth_Font(addinfo, len, size, size, false, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, cls.signon == SIGNONS ? 0.8 : 1, 0); + DrawQ_String_Font(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); } for(i = 0; i != nDownloads; ++i) @@ -506,9 +510,9 @@ static int SCR_DrawCurlDownload(int offset) else dpsnprintf(temp, sizeof(temp), "Downloading %s ... %5.1f%% @ %.1f KiB/s\n", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0); len = (int)strlen(temp); - x = (vid_conwidth.integer - len*size) / 2; - DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, 0.8, 0); - DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true); + x = (vid_conwidth.integer - DrawQ_TextWidth_Font(temp, len, size, size, false, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String_Font(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); } Z_Free(downinfo); @@ -526,6 +530,28 @@ static void SCR_DrawDownload() int offset = 0; offset += SCR_DrawQWDownload(offset); offset += SCR_DrawCurlDownload(offset); + if(offset != scr_con_margin_bottom) + Con_DPrintf("broken console margin calculation: %d != %d\n", offset, scr_con_margin_bottom); +} + +static int SCR_DownloadHeight() +{ + int offset = 0; + Curl_downloadinfo_t *downinfo; + const char *addinfo; + int nDownloads; + + if(cls.qw_downloadname[0]) + offset += 0; + + downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo); + if(downinfo) + { + offset += 8 * (nDownloads + (addinfo ? 1 : 0)); + Z_Free(downinfo); + } + + return offset; } //============================================================================= @@ -574,19 +600,16 @@ SCR_DrawConsole */ void SCR_DrawConsole (void) { + scr_con_margin_bottom = SCR_DownloadHeight(); if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED) { // full screen - Con_DrawConsole (vid_conheight.integer); + Con_DrawConsole (vid_conheight.integer - scr_con_margin_bottom); } else if (scr_con_current) - Con_DrawConsole ((int)scr_con_current); + Con_DrawConsole (min((int)scr_con_current, vid_conheight.integer - scr_con_margin_bottom)); else - { con_vislines = 0; - if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value) - Con_DrawNotify (); // only draw notify in game - } } /* @@ -1940,6 +1963,9 @@ void SCR_DrawScreen (void) } // draw 2D stuff + if(!scr_con_current && !(key_consoleactive & KEY_CONSOLEACTIVE_FORCED)) + if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value) + Con_DrawNotify (); // only draw notify in game if (cls.signon == SIGNONS) { diff --git a/common.c b/common.c index 32a57efc..aaa9fd68 100644 --- a/common.c +++ b/common.c @@ -662,6 +662,303 @@ void SZ_HexDumpToConsole(const sizebuf_t *buf) //============================================================================ +/* +============== +COM_Wordwrap + +Word wraps a string. The wordWidth function is guaranteed to be called exactly +once for each word in the string, so it may be stateful, no idea what that +would be good for any more. At the beginning of the string, it will be called +for the char 0 to initialize a clean state, and then once with the string " " +(a space) so the routine knows how long a space is. + +Wrapped lines get the isContinuation flag set and are continuationWidth less wide. + +The sum of the return values of the processLine function will be returned. +============== +*/ +int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL) +{ + // Logic is as follows: + // + // For each word or whitespace: + // Newline found? Output current line, advance to next line. This is not a continuation. Continue. + // Space found? Always add it to the current line, no matter if it fits. + // Word found? Check if current line + current word fits. + // If it fits, append it. Continue. + // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue. + + qboolean isContinuation = false; + float spaceWidth; + const char *startOfLine = string; + const char *cursor = string; + const char *end = string + length; + float spaceUsedInLine = 0; + float spaceUsedForWord; + int result = 0; + size_t wordLen; + size_t dummy; + + dummy = 0; + wordWidth(passthroughCW, NULL, &dummy, -1); + dummy = 1; + spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1); + + for(;;) + { + char ch = (cursor < end) ? *cursor : 0; + switch(ch) + { + case 0: // end of string + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = false; + goto out; + break; + case '\n': // end of line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = false; + ++cursor; + startOfLine = cursor; + break; + case ' ': // space + ++cursor; + spaceUsedInLine += spaceWidth; + break; + default: // word + wordLen = 1; + while(cursor + wordLen < end) + { + switch(cursor[wordLen]) + { + case 0: + case '\n': + case ' ': + goto out_inner; + default: + ++wordLen; + break; + } + } + out_inner: + spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line + if(wordLen < 1) + { + wordLen = 1; + spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself + } + if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine) + { + // we can simply append it + cursor += wordLen; + spaceUsedInLine += spaceUsedForWord; + } + else + { + // output current line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = true; + startOfLine = cursor; + cursor += wordLen; + spaceUsedInLine = continuationWidth + spaceUsedForWord; + } + } + } + out: + + return result; + +/* + qboolean isContinuation = false; + float currentWordSpace = 0; + const char *currentWord = 0; + float minReserve = 0; + + float spaceUsedInLine = 0; + const char *currentLine = 0; + const char *currentLineEnd = 0; + float currentLineFinalWhitespace = 0; + const char *p; + + int result = 0; + minReserve = charWidth(passthroughCW, 0); + minReserve += charWidth(passthroughCW, ' '); + + if(maxWidth < continuationWidth + minReserve) + maxWidth = continuationWidth + minReserve; + + charWidth(passthroughCW, 0); + + for(p = string; p < string + length; ++p) + { + char c = *p; + float w = charWidth(passthroughCW, c); + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(!currentLine) + { + currentLine = p; + spaceUsedInLine = isContinuation ? continuationWidth : 0; + currentLineEnd = 0; + } + + if(c == ' ') + { + // 1. I can add the word AND a space - then just append it. + if(spaceUsedInLine + currentWordSpace + w <= maxWidth) + { + currentLineEnd = p; // note: space not included here + currentLineFinalWhitespace = w; + spaceUsedInLine += currentWordSpace + w; + } + // 2. I can just add the word - then append it, output current line and go to next one. + else if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + // 3. Otherwise, output current line and go to next one, where I can add the word. + else if(continuationWidth + currentWordSpace + w <= maxWidth) + { + if(currentLineEnd) + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + currentLine = currentWord; + spaceUsedInLine = continuationWidth + currentWordSpace + w; + currentLineEnd = p; + currentLineFinalWhitespace = w; + isContinuation = true; + } + // 4. We can't even do that? Then output both current and next word as new lines. + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + currentWord = 0; + } + else if(c == '\n') + { + // 1. I can add the word - then do it. + if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + } + // 2. Otherwise, output current line, next one and make tabula rasa. + else + { + if(currentLineEnd) + { + processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + currentWord = 0; + currentLine = 0; + isContinuation = false; + } + else + { + currentWordSpace += w; + if( + spaceUsedInLine + currentWordSpace > maxWidth // can't join this line... + && + continuationWidth + currentWordSpace > maxWidth // can't join any other line... + ) + { + // this word cannot join ANY line... + // so output the current line... + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + + // then this word's beginning... + if(isContinuation) + { + // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces + float pieceWidth = maxWidth - continuationWidth; + const char *pos = currentWord; + currentWordSpace = 0; + + // reset the char width function to a state where no kerning occurs (start of word) + charWidth(passthroughCW, ' '); + while(pos <= p) + { + float w = charWidth(passthroughCW, *pos); + if(currentWordSpace + w > pieceWidth) // this piece won't fit any more + { + // print everything until it + result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true); + // go to here + currentWord = pos; + currentWordSpace = 0; + } + currentWordSpace += w; + ++pos; + } + // now we have a currentWord that fits... set up its next line + // currentWordSpace has been set + // currentWord has been set + spaceUsedInLine = continuationWidth; + currentLine = currentWord; + currentLineEnd = 0; + isContinuation = true; + } + else + { + // we have a guarantee that it will fix (see if clause) + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation); + + // and use the rest of this word as new start of a line + currentWordSpace = w; + currentWord = p; + spaceUsedInLine = continuationWidth; + currentLine = p; + currentLineEnd = 0; + isContinuation = true; + } + } + } + } + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(currentLine) // Same procedure as \n + { + // Can I append the current word? + if(spaceUsedInLine + currentWordSpace <= maxWidth) + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + } + + return result; +*/ +} /* ============== diff --git a/common.h b/common.h index 460d5325..3111ee22 100644 --- a/common.h +++ b/common.h @@ -203,6 +203,10 @@ float MSG_ReadAngle (protocolversion_t protocol); //============================================================================ +typedef float (*COM_WordWidthFunc_t) (void *passthrough, const char *w, size_t *length, float maxWidth); // length is updated to the longest fitting string into maxWidth; if maxWidth < 0, all characters are used and length is used as is +typedef int (*COM_LineProcessorFunc) (void *passthrough, const char *line, size_t length, float width, qboolean isContination); +int COM_Wordwrap(const char *string, size_t length, float continuationSize, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL); + extern char com_token[MAX_INPUTLINE]; int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash); diff --git a/console.c b/console.c index c11f548c..3a26df8f 100644 --- a/console.c +++ b/console.c @@ -26,25 +26,52 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #include -int con_linewidth; - float con_cursorspeed = 4; #define CON_TEXTSIZE 131072 +#define CON_MAXLINES 4096 -// total lines in console scrollback -int con_totallines; // lines up from bottom to display int con_backscroll; -// where next message will be printed -int con_current; -// offset in current line for next print -int con_x; + +// console buffer char con_text[CON_TEXTSIZE]; +#define CON_MASK_HIDENOTIFY 128 +#define CON_MASK_CHAT 1 + +typedef struct +{ + char *start; + int len; + + double addtime; + int mask; + + int height; // recalculated line height when needed (-1 to unset) +} +con_lineinfo; +con_lineinfo con_lines[CON_MAXLINES]; + +int con_lines_first; // cyclic buffer +int con_lines_count; +#define CON_LINES_IDX(i) ((con_lines_first + (i)) % CON_MAXLINES) +#define CON_LINES_LAST CON_LINES_IDX(con_lines_count - 1) +#define CON_LINES(i) con_lines[CON_LINES_IDX(i)] +#define CON_LINES_PRED(i) (((i) + CON_MAXLINES - 1) % CON_MAXLINES) +#define CON_LINES_SUCC(i) (((i) + 1) % CON_MAXLINES) + cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"}; -cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show (0-32)"}; +cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"}; +cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"}; + +cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"}; +cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"}; +cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"}; +cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"}; cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"}; +cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"}; +cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"}; cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"}; @@ -69,10 +96,7 @@ cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", #define NICKS_ALPHANUMERICS_ONLY 8 #define NICKS_NO_SPACES 16 -#define MAX_NOTIFYLINES 32 -// cl.time time the line was generated for transparent notify lines -float con_times[MAX_NOTIFYLINES]; - +int con_linewidth; int con_vislines; qboolean con_initialized; @@ -356,7 +380,7 @@ void Con_ToggleConsole_f (void) { // toggle the 'user wants console' bit key_consoleactive ^= KEY_CONSOLEACTIVE_USER; - memset (con_times, 0, sizeof(con_times)); + Con_ClearNotify(); } /* @@ -366,22 +390,22 @@ Con_Clear_f */ void Con_Clear_f (void) { - //if (con_text) - memset (con_text, ' ', CON_TEXTSIZE); + con_lines_count = 0; } /* ================ Con_ClearNotify + +Clear all notify lines. ================ */ void Con_ClearNotify (void) { int i; - - for (i=0 ; i\n"); @@ -497,25 +493,10 @@ void Con_ConDump_f (void) Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1)); return; } - // iterate over the entire console history buffer line by line - allblankssofar = true; - for (i = 0;i < con_totallines;i++) + for(i = 0; i < con_lines_count; ++i) { - text = con_text + ((con_current + 1 + i) % con_totallines)*con_linewidth; - // count the used characters on this line - for (l = min(con_linewidth, (int)sizeof(temp));l > 0 && text[l-1] == ' ';l--); - // if not a blank line, begin output - if (l) - allblankssofar = false; - // output the current line to the file - if (!allblankssofar) - { - if (l) - memcpy(temp, text, l); - temp[l] = '\n'; - temp[l+1] = 0; - FS_Print(file, temp); - } + FS_Write(file, CON_LINES(i).start, CON_LINES(i).len); + FS_Write(file, "\n", 1); } FS_Close(file); } @@ -527,9 +508,9 @@ Con_Init */ void Con_Init (void) { - memset (con_text, ' ', CON_TEXTSIZE); con_linewidth = 80; - con_totallines = CON_TEXTSIZE / con_linewidth; + con_lines_first = 0; + con_lines_count = 0; // Allocate a log queue, this will be freed after configs are parsed logq_size = MAX_INPUTLINE; @@ -548,8 +529,15 @@ void Con_Init (void) Cvar_SetQuick (&log_file, "qconsole.log"); // register our cvars - Cvar_RegisterVariable (&con_notifytime); + Cvar_RegisterVariable (&con_chat); + Cvar_RegisterVariable (&con_chatpos); + 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); // --blub @@ -570,89 +558,182 @@ void Con_Init (void) /* -=============== -Con_Linefeed -=============== +================ +Con_DeleteLine + +Deletes the first line from the console history. +================ */ -void Con_Linefeed (void) +void Con_DeleteLine() { - if (con_backscroll) - con_backscroll++; - - con_x = 0; - con_current++; - memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth); + if(con_lines_count == 0) + return; + --con_lines_count; + con_lines_first = CON_LINES_IDX(1); } /* ================ -Con_PrintToHistory +Con_DeleteLastLine -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. +Deletes the last line from the console history. ================ */ -void Con_PrintToHistory(const char *txt, int mask) +void Con_DeleteLastLine() { - int y, c, l; - static int cr; - - while ( (c = *txt) ) - { - // count word length - for (l=0 ; l< con_linewidth ; l++) - if ( txt[l] <= ' ') - break; - - // word wrap - if (l != con_linewidth && (con_x + l > con_linewidth) ) - con_x = 0; + if(con_lines_count == 0) + return; + --con_lines_count; +} - txt++; +/* +================ +Con_BytesLeft - if (cr) +Checks if there is space for a line of the given length, and if yes, returns a +pointer to the start of such a space, and NULL otherwise. +================ +*/ +char *Con_BytesLeft(int len) +{ + if(len > CON_TEXTSIZE) + return NULL; + if(con_lines_count == 0) + return con_text; + else + { + char *firstline_start = con_lines[con_lines_first].start; + char *lastline_onepastend = con_lines[CON_LINES_LAST].start + con_lines[CON_LINES_LAST].len; + // the buffer is cyclic, so we first have two cases... + if(firstline_start < lastline_onepastend) // buffer is contiguous { - con_current--; - cr = false; + // put at end? + if(len <= con_text + CON_TEXTSIZE - lastline_onepastend) + return lastline_onepastend; + // put at beginning? + else if(len <= firstline_start - con_text) + return con_text; + else + return NULL; } + else // buffer has a contiguous hole + { + if(len <= firstline_start - lastline_onepastend) + return lastline_onepastend; + else + return NULL; + } + } +} +/* +================ +Con_FixTimes - if (!con_x) +Notifies the console code about the current time +(and shifts back times of other entries when the time +went backwards) +================ +*/ +void Con_FixTimes() +{ + int i; + if(con_lines_count >= 1) + { + double diff = cl.time - (con_lines + CON_LINES_LAST)->addtime; + if(diff < 0) { - Con_Linefeed (); - // mark time for transparent overlay - if (con_current >= 0) - { - if (con_notify.integer < 0) - Cvar_SetValueQuick(&con_notify, 0); - if (con_notify.integer > MAX_NOTIFYLINES) - Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES); - if (con_notify.integer > 0) - con_times[con_current % con_notify.integer] = cl.time; - } + for(i = 0; i < con_lines_count; ++i) + CON_LINES(i).addtime += diff; } + } +} - switch (c) - { - case '\n': - con_x = 0; - break; +/* +================ +Con_AddLine - case '\r': - con_x = 0; - cr = 1; - break; +Appends a given string as a new line to the console. +================ +*/ +void Con_AddLine(const char *line, int len, int mask) +{ + char *putpos; + con_lineinfo *p; - default: // display character and advance - y = con_current % con_totallines; - con_text[y*con_linewidth+con_x] = c | mask; - con_x++; - if (con_x >= con_linewidth) - con_x = 0; - break; - } + Con_FixTimes(); + + if(len >= CON_TEXTSIZE) + { + // line too large? + // only display end of line. + line += len - CON_TEXTSIZE + 1; + len = CON_TEXTSIZE - 1; + } + while(!(putpos = Con_BytesLeft(len + 1)) || con_lines_count >= CON_MAXLINES) + Con_DeleteLine(); + memcpy(putpos, line, len); + putpos[len] = 0; + ++con_lines_count; + + //fprintf(stderr, "Now have %d lines (%d -> %d).\n", con_lines_count, con_lines_first, CON_LINES_LAST); + + p = con_lines + CON_LINES_LAST; + p->start = putpos; + p->len = len; + p->addtime = cl.time; + p->mask = mask; + p->height = -1; // calculate when needed +} + +/* +================ +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; + + for(; *txt; ++txt) + { + if(cr_pending) + { + Con_DeleteLastLine(); + cr_pending = 0; + } + switch(*txt) + { + case 0: + break; + case '\r': + Con_AddLine(buf, bufpos, mask); + bufpos = 0; + cr_pending = 1; + break; + case '\n': + Con_AddLine(buf, bufpos, mask); + bufpos = 0; + break; + default: + buf[bufpos++] = *txt; + if(bufpos >= CON_TEXTSIZE - 1) + { + Con_AddLine(buf, bufpos, mask); + bufpos = 0; + } + break; + } } } @@ -760,10 +841,21 @@ void Con_Print(const char *msg) // play talk wav if (*msg == 1) { - if (msg[1] == '(' && cl.foundtalk2wav) - S_LocalSound ("sound/misc/talk2.wav"); + if(gamemode == GAME_NEXUIZ) + { + if(msg[1] == '\r' && cl.foundtalk2wav) + S_LocalSound ("sound/misc/talk2.wav"); + else + S_LocalSound ("sound/misc/talk.wav"); + } else - S_LocalSound ("sound/misc/talk.wav"); + { + 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'; @@ -786,7 +878,10 @@ void Con_Print(const char *msg) 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) { @@ -1025,6 +1120,7 @@ void Con_DrawInput (void) int y; int i; char editlinecopy[MAX_INPUTLINE+1], *text; + float x; if (!key_consoleactive) return; // don't draw anything @@ -1049,17 +1145,150 @@ void Con_DrawInput (void) // text[key_linepos + 1] = 0; - // prestep if horizontally scrolling - if (key_linepos >= con_linewidth) - text += 1 + key_linepos - con_linewidth; + x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, con_textsize.value, con_textsize.value, false, FONT_CONSOLE); + if(x >= 0) + x = 0; // draw it - DrawQ_String(0, con_vislines - con_textsize.value*2, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false ); + DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE ); // remove cursor // key_lines[edit_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->width_of[0]; + } + return DrawQ_TextWidth_Font_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, maxWidth); +} + +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 = ti->x + (ti->width - width) * ti->alignment; + if(isContinuation && *ti->continuationString) + x += DrawQ_String_Font(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_Font(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 = 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 *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 *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; +} /* ================ @@ -1071,87 +1300,185 @@ Draws the last few lines of output transparently over the game top void Con_DrawNotify (void) { float x, v; - char *text; - int i, stop; - float time; + float chatstart, notifystart, inputsize; + float align; char temptext[MAX_INPUTLINE]; - int colorindex = -1; //-1 for default + int numChatlines; + int chatpos; + + Con_FixTimes(); + + numChatlines = con_chat.integer; + chatpos = con_chatpos.integer; if (con_notify.integer < 0) Cvar_SetValueQuick(&con_notify, 0); - if (con_notify.integer > MAX_NOTIFYLINES) - Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES); if (gamemode == GAME_TRANSFUSION) - v = 8; + v = 8; // vertical offset else v = 0; - // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text - stop = con_current; - for (i= stop-con_notify.integer+1 ; i<=stop ; i++) - { - if (i < 0) - continue; - time = con_times[i % con_notify.integer]; - if (time == 0) - continue; - time = cl.time - time; - if (time > con_notifytime.value) - continue; - text = con_text + (i % con_totallines)*con_linewidth; - - if (gamemode == GAME_NEXUIZ) { - int chars = 0; - int finalchars = 0; - int j; + // 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; + } - // count up to the last non-whitespace, and ignore color codes - for (j = 0;j < con_linewidth && text[j];j++) - { - if (text[j] == STRING_COLOR_TAG && (text[j+1] >= '0' && text[j+1] <= '9')) - { - j++; - continue; - } - chars++; - if (text[j] == ' ') - continue; - finalchars = chars; - } - // center the line using the calculated width - x = (vid_conwidth.integer - finalchars * con_textsize.value) * 0.5; - } else - x = 0; + if(numChatlines) + { + 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 + } - DrawQ_String( x, v, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false ); + v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, ""); - v += con_textsize.value; + // chat? + if(numChatlines) + { + v = chatstart + numChatlines * con_chatsize.value; + Con_DrawNotifyRect(CON_MASK_CHAT, 0, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ยท> character in conchars.tga } - if (key_dest == key_message) { int colorindex = -1; - x = 0; - // LordHavoc: speedup, and other improvements if (chat_team) sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1)); else sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1)); - while ((int)strlen(temptext) >= con_linewidth) - { - DrawQ_String( 0, v, temptext, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false ); - strlcpy(temptext, &temptext[con_linewidth], sizeof(temptext)); - v += con_textsize.value; - } - if (strlen(temptext) > 0) + + // FIXME word wrap + inputsize = (numChatlines ? con_chatsize : con_notifysize).value; + x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, inputsize, inputsize, false, FONT_CHAT); + if(x > 0) + x = 0; + DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT); + } +} + +/* +================ +Con_MeasureConsoleLine + +Counts the number of lines for a line on the console. +================ +*/ +int Con_MeasureConsoleLine(int lineno) +{ + float width = vid_conwidth.value; + con_text_info_t ti; + ti.fontsize = con_textsize.value; + ti.font = FONT_CONSOLE; + + return COM_Wordwrap(con_lines[lineno].start, con_lines[lineno].len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL); +} + +/* +================ +Con_LineHeight + +Returns the height of a given console line; calculates it if necessary. +================ +*/ +int Con_LineHeight(int i) +{ + int h = con_lines[i].height; + if(h != -1) + return h; + return con_lines[i].height = Con_MeasureConsoleLine(i); +} + +/* +================ +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(float y, int lineno, float ymin, float ymax) +{ + float width = vid_conwidth.value; + + con_text_info_t ti; + 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(con_lines[lineno].start, con_lines[lineno].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. +================ +*/ +void Con_LastVisibleLine(int *last, int *limitlast) +{ + int lines_seen = 0; + int ic; + + if(con_backscroll < 0) + con_backscroll = 0; + + // now count until we saw con_backscroll actual lines + for(ic = 0; ic < con_lines_count; ++ic) + { + int i = CON_LINES_IDX(con_lines_count - 1 - ic); + int h = Con_LineHeight(i); + + // line is the last visible line? + if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll) { - DrawQ_String( 0, v, temptext, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false ); - v += con_textsize.value; + *last = i; + *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; + *last = con_lines_first; + *limitlast = 1; } /* @@ -1164,32 +1491,39 @@ The typing input line at the bottom should only be drawn if typing is allowed */ void Con_DrawConsole (int lines) { - int i, rows, j, stop; + int i, last, limitlast; float y; - char *text; - int colorindex = -1; if (lines <= 0) return; -// draw the background - DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0); - DrawQ_String(vid_conwidth.integer - strlen(engineversion) * con_textsize.value - con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true); - -// draw the text con_vislines = lines; - rows = (int)ceil((lines/con_textsize.value)-2); // rows of text to draw - y = lines - (rows+2)*con_textsize.value; // may start slightly negative +// draw the background + DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic("gfx/conback", true) : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game + DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE); - // make a copy of con_current here so that we can't get in a runaway loop printing new messages while drawing the notify text - stop = con_current; - for (i = stop - rows + 1;i <= stop;i++, y += con_textsize.value) +// draw the text + if(con_lines_count > 0) { - j = max(i - con_backscroll, 0); - text = con_text + (j % con_totallines)*con_linewidth; + float ymax = con_vislines - 2 * con_textsize.value; + Con_LastVisibleLine(&last, &limitlast); + y = ymax - con_textsize.value; - DrawQ_String( 0, y, text, con_linewidth, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false ); + if(limitlast) + y += (con_lines[last].height - limitlast) * con_textsize.value; + i = last; + + for(;;) + { + y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value; + if(i == con_lines_first) + break; // top of console buffer + if(y < 0) + break; // top of console window + limitlast = 0; + i = CON_LINES_PRED(i); + } } // draw the input prompt, user text, and cursor if desired diff --git a/darkplaces.txt b/darkplaces.txt index 9fc31c04..da06f689 100644 --- a/darkplaces.txt +++ b/darkplaces.txt @@ -488,7 +488,14 @@ collision_leavenudge 0 how much t collision_prefernudgedfraction 1 whether to sort collision events by nudged fraction (1) or real fraction (0) collision_startnudge 0 how much to bias collision trace start con_closeontoggleconsole 1 allows toggleconsole binds to close the console as well +con_chat 0 how many chat lines to show in a dedicated chat area +con_chatpos 0 where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top) +con_chatsize 8 chat text size in virtual 2D pixels +con_chattime 30 how long chat lines last, in seconds +con_chatwidth 1.0 relative chat window width con_notify 4 how many notify lines to show (0-32) +con_notifyalign 3 how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default) +con_notifysize 8 notify text size in virtual 2D pixels con_notifytime 3 how long notify lines last, in seconds con_textsize 8 console text size in virtual 2D pixels coop 0 coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch) diff --git a/draw.h b/draw.h index 8d325860..a94d79ca 100644 --- a/draw.h +++ b/draw.h @@ -69,6 +69,28 @@ DRAWFLAG_2XMODULATE, DRAWFLAG_NUMFLAGS }; +typedef struct dp_font_s +{ + rtexture_t *tex; + float width_of[256]; // width_of[0] == max width of any char; 1.0f is base width (1/16 of texture width); therefore, all widths have to be <= 1 + char texpath[MAX_QPATH]; + char title[MAX_QPATH]; +} +dp_font_t; + +#define MAX_FONTS 16 +extern dp_font_t dp_fonts[MAX_FONTS]; +#define FONT_DEFAULT (&dp_fonts[0]) // should be fixed width +#define FONT_CONSOLE (&dp_fonts[1]) // REALLY should be fixed width (ls!) +#define FONT_SBAR (&dp_fonts[2]) // must be fixed width +#define FONT_NOTIFY (&dp_fonts[3]) // free +#define FONT_CHAT (&dp_fonts[4]) // free +#define FONT_CENTERPRINT (&dp_fonts[5]) // free +#define FONT_INFOBAR (&dp_fonts[6]) // free +#define FONT_MENU (&dp_fonts[7]) // should be fixed width +#define FONT_USER (&dp_fonts[8]) // userdefined fonts +#define MAX_USERFONTS (MAX_FONTS - (FONT_USER - dp_fonts)) + // shared color tag printing constants #define STRING_COLOR_TAG '^' #define STRING_COLOR_DEFAULT 7 @@ -86,7 +108,10 @@ void DrawQ_Fill(float x, float y, float width, float height, float red, float gr // if outcolor is provided the initial color is read from it, and it is updated at the end with the new value at the end of the text (not at the end of the clipped part) // the color is tinted by the provided base color // if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow) -float DrawQ_String(float x, float y, const char *text, int maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes); +float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes); +float DrawQ_String_Font(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth_Font(const char *text, size_t maxlen, float scalex, float scaley, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth_Font_UntilWidth(const char *text, size_t *maxlen, float scalex, float scaley, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); // draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags); // draw a triangle mesh diff --git a/gl_draw.c b/gl_draw.c index 527db40d..cc85ad05 100644 --- a/gl_draw.c +++ b/gl_draw.c @@ -25,10 +25,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "cl_video.h" #include "cl_dyntexture.h" +dp_font_t dp_fonts[MAX_FONTS] = {{0}}; + cvar_t r_textshadow = {CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"}; cvar_t r_textbrightness = {CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"}; -static rtexture_t *char_texture; cachepic_t *r_crosshairs[NUMCROSSHAIRS+1]; //============================================================================= @@ -300,7 +301,7 @@ Draw_CachePic ================ */ // FIXME: move this to client somehow -cachepic_t *Draw_CachePic (const char *path, qboolean persistent) +static cachepic_t *Draw_CachePic_Compression (const char *path, qboolean persistent, qboolean allow_compression) { int crc, hashkey; cachepic_t *pic; @@ -343,13 +344,15 @@ cachepic_t *Draw_CachePic (const char *path, qboolean persistent) flags |= TEXF_PRECACHE; if (!strcmp(path, "gfx/colorcontrol/ditherpattern")) flags |= TEXF_CLAMP; + if(allow_compression && gl_texturecompression_2d.integer) + flags |= TEXF_COMPRESS; // load a high quality image from disk if possible - pic->tex = loadtextureimage(drawtexturepool, path, false, flags | (gl_texturecompression_2d.integer ? TEXF_COMPRESS : 0), true); + pic->tex = loadtextureimage(drawtexturepool, path, false, flags, true); if (pic->tex == NULL && !strncmp(path, "gfx/", 4)) { // compatibility with older versions which did not require gfx/ prefix - pic->tex = loadtextureimage(drawtexturepool, path + 4, false, flags | (gl_texturecompression_2d.integer ? TEXF_COMPRESS : 0), true); + pic->tex = loadtextureimage(drawtexturepool, path + 4, false, flags, true); } // if a high quality image was loaded, set the pic's size to match it, just // in case there's no low quality version to get the size from @@ -372,7 +375,7 @@ cachepic_t *Draw_CachePic (const char *path, qboolean persistent) pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; // if no high quality replacement image was found, upload the original low quality texture if (!pic->tex) - pic->tex = R_LoadTexture2D(drawtexturepool, path, pic->width, pic->height, lmpdata + 8, TEXTYPE_PALETTE, flags, palette_bgra_transparent); + pic->tex = R_LoadTexture2D(drawtexturepool, path, pic->width, pic->height, lmpdata + 8, TEXTYPE_PALETTE, flags & ~TEXF_COMPRESS, palette_bgra_transparent); } Mem_Free(lmpdata); } @@ -385,7 +388,7 @@ cachepic_t *Draw_CachePic (const char *path, qboolean persistent) pic->height = 128; // if no high quality replacement image was found, upload the original low quality texture if (!pic->tex) - pic->tex = R_LoadTexture2D(drawtexturepool, path, 128, 128, lmpdata, TEXTYPE_PALETTE, flags, palette_bgra_font); + pic->tex = R_LoadTexture2D(drawtexturepool, path, 128, 128, lmpdata, TEXTYPE_PALETTE, flags & ~TEXF_COMPRESS, palette_bgra_font); } else { @@ -393,7 +396,7 @@ cachepic_t *Draw_CachePic (const char *path, qboolean persistent) pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; // if no high quality replacement image was found, upload the original low quality texture if (!pic->tex) - pic->tex = R_LoadTexture2D(drawtexturepool, path, pic->width, pic->height, lmpdata + 8, TEXTYPE_PALETTE, flags, palette_bgra_transparent); + pic->tex = R_LoadTexture2D(drawtexturepool, path, pic->width, pic->height, lmpdata + 8, TEXTYPE_PALETTE, flags & ~TEXF_COMPRESS, palette_bgra_transparent); } } @@ -445,6 +448,10 @@ cachepic_t *Draw_CachePic (const char *path, qboolean persistent) return pic; } +cachepic_t *Draw_CachePic (const char *path, qboolean persistent) +{ + return Draw_CachePic_Compression(path, persistent, true); +} cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels_bgra) { @@ -511,6 +518,97 @@ void Draw_FreePic(const char *picname) } } +extern int con_linewidth; // to force rewrapping +static void LoadFont(qboolean override, const char *name, dp_font_t *fnt) +{ + int i; + float maxwidth; + char widthfile[MAX_QPATH]; + char *widthbuf; + fs_offset_t widthbufsize; + + if(override || !fnt->texpath[0]) + strlcpy(fnt->texpath, name, sizeof(fnt->texpath)); + + if(drawtexturepool == NULL) + return; // before gl_draw_start, so will be loaded later + + fnt->tex = Draw_CachePic_Compression(fnt->texpath, true, false)->tex; + if(fnt->tex == r_texture_notexture) + { + fnt->tex = Draw_CachePic_Compression("gfx/conchars", true, false)->tex; + strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile)); + } + else + dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath); + + // unspecified width == 1 (base width) + for(i = 1; i < 256; ++i) + fnt->width_of[i] = 1; + + // FIXME load "name.width", if it fails, fill all with 1 + if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize))) + { + float extraspacing = 0; + const char *p = widthbuf; + int ch = 0; + + while(ch < 256) + { + if(!COM_ParseToken_Simple(&p, false, false)) + return; + + if(!strcmp(com_token, "extraspacing")) + { + if(!COM_ParseToken_Simple(&p, false, false)) + return; + extraspacing = atof(com_token); + } + else + fnt->width_of[ch++] = atof(com_token) + extraspacing; + } + + Mem_Free(widthbuf); + } + + maxwidth = fnt->width_of[1]; + for(i = 2; i < 256; ++i) + maxwidth = max(maxwidth, fnt->width_of[i]); + fnt->width_of[0] = maxwidth; + + if(fnt == FONT_CONSOLE) + con_linewidth = -1; // rewrap console in next frame +} + +static dp_font_t *FindFont(const char *title) +{ + int i; + for(i = 0; i < MAX_FONTS; ++i) + if(!strcmp(dp_fonts[i].title, title)) + return &dp_fonts[i]; + return NULL; +} + +static void LoadFont_f() +{ + dp_font_t *f; + int i; + if(Cmd_Argc() < 2) + { + Con_Printf("Available font commands:\n"); + for(i = 0; i < MAX_FONTS; ++i) + Con_Printf(" loadfont %s gfx/tgafile\n", dp_fonts[i].title); + return; + } + f = FindFont(Cmd_Argv(1)); + if(f == NULL) + { + Con_Printf("font function not found\n"); + return; + } + LoadFont(true, (Cmd_Argc() < 3) ? "gfx/conchars" : Cmd_Argv(2), f); +} + /* =============== Draw_Init @@ -524,7 +622,9 @@ static void gl_draw_start(void) numcachepics = 0; memset(cachepichash, 0, sizeof(cachepichash)); - char_texture = Draw_CachePic("gfx/conchars", true)->tex; + for(i = 0; i < MAX_FONTS; ++i) + LoadFont(false, va("gfx/font_%s", dp_fonts[i].title), &dp_fonts[i]); + for (i = 1;i <= NUMCROSSHAIRS;i++) r_crosshairs[i] = Draw_CachePic(va("gfx/crosshair%i", i), true); @@ -546,9 +646,24 @@ static void gl_draw_newmap(void) void GL_Draw_Init (void) { + int i, j; Cvar_RegisterVariable(&r_textshadow); Cvar_RegisterVariable(&r_textbrightness); + Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions"); R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap); + + strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title)); + strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath)); + strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title)); + strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title)); + strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title)); + strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title)); + strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title)); + strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title)); + strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title)); + for(i = 0, j = 0; i < MAX_FONTS; ++i) + if(!FONT_USER[i].title[0]) + dpsnprintf(FONT_USER[i].title, sizeof(FONT_USER[i].title), "user%d", j++); } static void _DrawQ_Setup(void) @@ -695,9 +810,10 @@ static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, } } -float DrawQ_String(float startx, float starty, const char *text, int maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes) +static float DrawQ_String_Font_UntilX(float startx, float starty, const char *text, size_t *maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxx) { - int i, num, shadow, colorindex = STRING_COLOR_DEFAULT; + int num, shadow, colorindex = STRING_COLOR_DEFAULT; + size_t i; float x = startx, y, s, t, u, v; float *av, *at, *ac; float color[4]; @@ -705,30 +821,37 @@ float DrawQ_String(float startx, float starty, const char *text, int maxlen, flo float vertex3f[QUADELEMENTS_MAXQUADS*4*3]; float texcoord2f[QUADELEMENTS_MAXQUADS*4*2]; float color4f[QUADELEMENTS_MAXQUADS*4*4]; + qboolean checkwidth; - if (maxlen < 1) - maxlen = 1<<30; + if (*maxlen < 1) + *maxlen = 1<<30; - _DrawQ_ProcessDrawFlag(flags); + // when basealpha == 0, skip as much as possible (just return width) + if(basealpha > 0) + { + _DrawQ_ProcessDrawFlag(flags); - R_Mesh_ColorPointer(color4f, 0, 0); - R_Mesh_ResetTextureState(); - R_Mesh_TexBind(0, R_GetTexture(char_texture)); - R_Mesh_TexCoordPointer(0, 2, texcoord2f, 0, 0); - R_Mesh_VertexPointer(vertex3f, 0, 0); + R_Mesh_ColorPointer(color4f, 0, 0); + R_Mesh_ResetTextureState(); + R_Mesh_TexBind(0, R_GetTexture(fnt->tex)); + R_Mesh_TexCoordPointer(0, 2, texcoord2f, 0, 0); + R_Mesh_VertexPointer(vertex3f, 0, 0); + } ac = color4f; at = texcoord2f; av = vertex3f; batchcount = 0; + checkwidth = (maxx >= startx); - for (shadow = r_textshadow.value != 0;shadow >= 0;shadow--) + for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--) { if (!outcolor || *outcolor == -1) colorindex = STRING_COLOR_DEFAULT; else colorindex = *outcolor; - DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow); + if(basealpha > 0) + DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow); x = startx; y = starty; @@ -737,66 +860,80 @@ float DrawQ_String(float startx, float starty, const char *text, int maxlen, flo x += r_textshadow.value; y += r_textshadow.value; } - for (i = 0;i < maxlen && text[i];i++, x += w) + for (i = 0;i < *maxlen && text[i];i++) { if (text[i] == ' ') + { + if(checkwidth) + if(x + fnt->width_of[' '] * w > maxx) + break; // oops, can't draw this + x += fnt->width_of[' '] * w; continue; - if (text[i] == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < maxlen) + } + if (text[i] == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < *maxlen) { if (text[i+1] == STRING_COLOR_TAG) { i++; - if (text[i] == ' ') - continue; } else if (text[i+1] >= '0' && text[i+1] <= '9') { colorindex = text[i+1] - '0'; DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow); i++; - x -= w; continue; } } - num = text[i]; - s = (num & 15)*0.0625f + (0.5f / 256.0f); - t = (num >> 4)*0.0625f + (0.5f / 256.0f); - u = 0.0625f - (1.0f / 256.0f); - v = 0.0625f - (1.0f / 256.0f); - ac[ 0] = color[0];ac[ 1] = color[1];ac[ 2] = color[2];ac[ 3] = color[3]; - ac[ 4] = color[0];ac[ 5] = color[1];ac[ 6] = color[2];ac[ 7] = color[3]; - ac[ 8] = color[0];ac[ 9] = color[1];ac[10] = color[2];ac[11] = color[3]; - ac[12] = color[0];ac[13] = color[1];ac[14] = color[2];ac[15] = color[3]; - at[ 0] = s ;at[ 1] = t ; - at[ 2] = s+u;at[ 3] = t ; - at[ 4] = s+u;at[ 5] = t+v; - at[ 6] = s ;at[ 7] = t+v; - av[ 0] = x ;av[ 1] = y ;av[ 2] = 10; - av[ 3] = x+w;av[ 4] = y ;av[ 5] = 10; - av[ 6] = x+w;av[ 7] = y+h;av[ 8] = 10; - av[ 9] = x ;av[10] = y+h;av[11] = 10; - ac += 16; - at += 8; - av += 12; - batchcount++; - if (batchcount >= QUADELEMENTS_MAXQUADS) + num = (unsigned char) text[i]; + if(checkwidth) + if(x + fnt->width_of[num] * w > maxx) + break; // oops, can't draw this + if(basealpha > 0) { - if (basealpha >= (1.0f / 255.0f)) + // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering + s = (num & 15)*0.0625f + (0.5f / 256.0f); + t = (num >> 4)*0.0625f + (0.5f / 256.0f); + u = 0.0625f - (1.0f / 256.0f); + v = 0.0625f - (1.0f / 256.0f); + ac[ 0] = color[0];ac[ 1] = color[1];ac[ 2] = color[2];ac[ 3] = color[3]; + ac[ 4] = color[0];ac[ 5] = color[1];ac[ 6] = color[2];ac[ 7] = color[3]; + ac[ 8] = color[0];ac[ 9] = color[1];ac[10] = color[2];ac[11] = color[3]; + ac[12] = color[0];ac[13] = color[1];ac[14] = color[2];ac[15] = color[3]; + at[ 0] = s ;at[ 1] = t ; + at[ 2] = s+u;at[ 3] = t ; + at[ 4] = s+u;at[ 5] = t+v; + at[ 6] = s ;at[ 7] = t+v; + av[ 0] = x ;av[ 1] = y ;av[ 2] = 10; + av[ 3] = x+w;av[ 4] = y ;av[ 5] = 10; + av[ 6] = x+w;av[ 7] = y+h;av[ 8] = 10; + av[ 9] = x ;av[10] = y+h;av[11] = 10; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) { - GL_LockArrays(0, batchcount * 4); - R_Mesh_Draw(0, batchcount * 4, batchcount * 2, quadelements, 0, 0); - GL_LockArrays(0, 0); + if (basealpha >= (1.0f / 255.0f)) + { + GL_LockArrays(0, batchcount * 4); + R_Mesh_Draw(0, batchcount * 4, batchcount * 2, quadelements, 0, 0); + GL_LockArrays(0, 0); + } + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; } - batchcount = 0; - ac = color4f; - at = texcoord2f; - av = vertex3f; } + x += fnt->width_of[num] * w; } + if(checkwidth) + *maxlen = i; + checkwidth = 0; // we've done all we had to } - if (batchcount > 0) + if (basealpha > 0) { - if (basealpha >= (1.0f / 255.0f)) + if (batchcount > 0) { GL_LockArrays(0, batchcount * 4); R_Mesh_Draw(0, batchcount * 4, batchcount * 2, quadelements, 0, 0); @@ -811,6 +948,26 @@ float DrawQ_String(float startx, float starty, const char *text, int maxlen, flo return x; } +float DrawQ_String_Font(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_String_Font_UntilX(startx, starty, text, &maxlen, w, h, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt, startx-1); +} + +float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes) +{ + return DrawQ_String_Font(startx, starty, text, maxlen, w, h, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, &dp_fonts[0]); +} + +float DrawQ_TextWidth_Font_UntilWidth(const char *text, size_t *maxlen, float scalex, float scaley, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) +{ + return DrawQ_String_Font_UntilX(0, 0, text, maxlen, scalex, scaley, 1, 1, 1, 0, 0, NULL, ignorecolorcodes, fnt, maxWidth); +} + +float DrawQ_TextWidth_Font(const char *text, size_t maxlen, float scalex, float scaley, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_TextWidth_Font_UntilWidth(text, &maxlen, scalex, scaley, ignorecolorcodes, fnt, -1); +} + #if 0 // not used static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor) diff --git a/keys.c b/keys.c index 11280e0f..7627d30b 100644 --- a/keys.c +++ b/keys.c @@ -449,23 +449,19 @@ Key_Console (int key, char ascii) if (key == K_PGUP || key == K_KP_PGUP || key == K_MWHEELUP) { con_backscroll += ((int) vid_conheight.integer >> 5); - if (con_backscroll > con_totallines - (vid_conheight.integer>>3) - 1) - con_backscroll = con_totallines - (vid_conheight.integer>>3) - 1; return; } if (key == K_PGDN || key == K_KP_PGDN || key == K_MWHEELDOWN) { con_backscroll -= ((int) vid_conheight.integer >> 5); - if (con_backscroll < 0) - con_backscroll = 0; return; } if (key == K_HOME || key == K_KP_HOME) { if (keydown[K_CTRL]) - con_backscroll = con_totallines - (vid_conheight.integer>>3) - 1; + con_backscroll = INT_MAX; else key_linepos = 1; return; diff --git a/menu.c b/menu.c index fd0d3a13..a7ba26e4 100644 --- a/menu.c +++ b/menu.c @@ -187,30 +187,30 @@ static void M_DrawCharacter (float cx, float cy, int num) char temp[2]; temp[0] = num; temp[1] = 0; - DrawQ_String(menu_x + cx, menu_y + cy, temp, 1, 8, 8, 1, 1, 1, 1, 0, NULL, true); + DrawQ_String_Font(menu_x + cx, menu_y + cy, temp, 1, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); } static void M_PrintColored(float cx, float cy, const char *str) { - DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false); + DrawQ_String_Font(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_MENU); } static void M_Print(float cx, float cy, const char *str) { - DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true); + DrawQ_String_Font(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); } static void M_PrintRed(float cx, float cy, const char *str) { - DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 0, 0, 1, 0, NULL, true); + DrawQ_String_Font(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 0, 0, 1, 0, NULL, true, FONT_MENU); } static void M_ItemPrint(float cx, float cy, const char *str, int unghosted) { if (unghosted) - DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true); + DrawQ_String_Font(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); else - DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 0.4, 0.4, 0.4, 1, 0, NULL, true); + DrawQ_String_Font(menu_x + cx, menu_y + cy, str, 0, 8, 8, 0.4, 0.4, 0.4, 1, 0, NULL, true, FONT_MENU); } static void M_DrawPic(float cx, float cy, const char *picname) diff --git a/progsvm.h b/progsvm.h index c6fc6653..e31c2ffa 100644 --- a/progsvm.h +++ b/progsvm.h @@ -243,7 +243,7 @@ typedef struct prvm_prog_globaloffsets_s int dmg_save; // csqc int dmg_origin; // csqc int sb_showscores; // csqc - + int drawfont; // csqc / menu } prvm_prog_globaloffsets_t; diff --git a/prvm_cmds.c b/prvm_cmds.c index e0ec50e2..33ee342c 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -2604,6 +2604,19 @@ void VM_freepic(void) Draw_FreePic(s); } +dp_font_t *getdrawfont() +{ + if(prog->globaloffsets.drawfont >= 0) + { + int f = PRVM_G_FLOAT(prog->globaloffsets.drawfont); + if(f < 0 || f >= MAX_FONTS) + return FONT_DEFAULT; + return &dp_fonts[f]; + } + else + return FONT_DEFAULT; +} + /* ========= VM_drawcharacter @@ -2648,7 +2661,7 @@ void VM_drawcharacter(void) return; } - DrawQ_String (pos[0], pos[1], &character, 1, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); + DrawQ_String_Font(pos[0], pos[1], &character, 1, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont()); PRVM_G_FLOAT(OFS_RETURN) = 1; } @@ -2689,7 +2702,7 @@ void VM_drawstring(void) if(pos[2] || scale[2]) Con_Printf("VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); - DrawQ_String (pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); + DrawQ_String_Font(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont()); PRVM_G_FLOAT(OFS_RETURN) = 1; } @@ -2730,7 +2743,7 @@ void VM_drawcolorcodedstring(void) Con_Printf("VM_drawcolorcodedstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); color = -1; - DrawQ_String (pos[0], pos[1], string, 0, scale[0], scale[1], 1, 1, 1, PRVM_G_FLOAT(OFS_PARM3), flag, NULL, false); + DrawQ_String_Font(pos[0], pos[1], string, 0, scale[0], scale[1], 1, 1, 1, PRVM_G_FLOAT(OFS_PARM3), flag, NULL, false, getdrawfont()); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* @@ -2749,7 +2762,7 @@ void VM_stringwidth(void) string = PRVM_G_STRING(OFS_PARM0); colors = (int)PRVM_G_FLOAT(OFS_PARM1); - PRVM_G_FLOAT(OFS_RETURN) = DrawQ_String(0, 0, string, 0, 1, 1, 0, 0, 0, 0, 0, NULL, !colors); // 1x1 characters, don't actually draw + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_String_Font(0, 0, string, 0, 1, 1, 0, 0, 0, 0, 0, NULL, !colors, getdrawfont()); // 1x1 characters, don't actually draw } /* ========= diff --git a/prvm_edict.c b/prvm_edict.c index e3bb497f..aee3eba5 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -1461,6 +1461,7 @@ void PRVM_FindOffsets(void) prog->globaloffsets.dmg_save = PRVM_ED_FindGlobalOffset("dmg_save"); prog->globaloffsets.dmg_origin = PRVM_ED_FindGlobalOffset("dmg_origin"); prog->globaloffsets.sb_showscores = PRVM_ED_FindGlobalOffset("sb_showscores"); + prog->globaloffsets.drawfont = PRVM_ED_FindGlobalOffset("drawfont"); // menu qc only uses some functions, nothing else prog->funcoffsets.m_display = PRVM_ED_FindFunctionOffset("m_display"); diff --git a/quakedef.h b/quakedef.h index c2843b31..36221f3a 100644 --- a/quakedef.h +++ b/quakedef.h @@ -41,6 +41,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include +#include #include #include "qtypes.h" diff --git a/sbar.c b/sbar.c index b8621358..149d5a5b 100644 --- a/sbar.c +++ b/sbar.c @@ -427,7 +427,7 @@ Draws one solid graphics character */ void Sbar_DrawCharacter (int x, int y, int num) { - DrawQ_String (sbar_x + x + 4 , sbar_y + y, va("%c", num), 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, true); + DrawQ_String_Font (sbar_x + x + 4 , sbar_y + y, va("%c", num), 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, true, FONT_SBAR); } /* @@ -437,7 +437,7 @@ Sbar_DrawString */ void Sbar_DrawString (int x, int y, char *str) { - DrawQ_String (sbar_x + x, sbar_y + y, str, 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, false); + DrawQ_String_Font (sbar_x + x, sbar_y + y, str, 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR); } /* @@ -754,6 +754,7 @@ static void Sbar_DrawWeapon(int nr, float fade, int active) const int w_width = 32, w_height = 12, w_space = 2, font_size = 8; DrawQ_Pic((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr, vid_conheight.integer - w_height, sb_weapons[0][nr], w_width, w_height, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 0.6, (active ? 1 : 0.6) * fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); + // FIXME ?? DrawQ_String((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr + w_space, vid_conheight.integer - w_height + w_space, va("%i",nr+1), 0, font_size, font_size, 1, 1, 0, sbar_alpha_fg.value, 0, NULL, true); } else @@ -1079,26 +1080,26 @@ void Sbar_ShowFPS(void) fps_y = vid_conheight.integer - fps_height; if (fpsstring[0]) { - fps_x = vid_conwidth.integer - fps_scalex * strlen(fpsstring); - DrawQ_Fill(fps_x, fps_y, fps_scalex * strlen(fpsstring), fps_scaley, 0, 0, 0, 0.5, 0); + fps_x = vid_conwidth.integer - DrawQ_TextWidth_Font(fpsstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); if (red) - DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 0, 0, 1, 0, NULL, true); + DrawQ_String_Font(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); else - DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true); + DrawQ_String_Font(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (timestring[0]) { - fps_x = vid_conwidth.integer - fps_scalex * strlen(timestring); - DrawQ_Fill(fps_x, fps_y, fps_scalex * strlen(timestring), fps_scaley, 0, 0, 0, 0.5, 0); - DrawQ_String(fps_x, fps_y, timestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true); + fps_x = vid_conwidth.integer - DrawQ_TextWidth_Font(timestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String_Font(fps_x, fps_y, timestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (datestring[0]) { - fps_x = vid_conwidth.integer - fps_scalex * strlen(datestring); - DrawQ_Fill(fps_x, fps_y, fps_scalex * strlen(datestring), fps_scaley, 0, 0, 0, 0.5, 0); - DrawQ_String(fps_x, fps_y, datestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true); + fps_x = vid_conwidth.integer - DrawQ_TextWidth_Font(datestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String_Font(fps_x, fps_y, datestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } } @@ -1612,9 +1613,9 @@ float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) if (s->qw_spectator) { if (s->qw_ping || s->qw_packetloss) - DrawQ_String(x, y, va("%4i %3i %4i spectator %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va("%4i %3i %4i spectator %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else - DrawQ_String(x, y, va(" %4i spectator %c%s", minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va(" %4i spectator %c%s", minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { @@ -1625,15 +1626,15 @@ float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) // // c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; - DrawQ_Fill(x + 14*8, y+1, 40, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + DrawQ_Fill(x + 14*8*FONT_SBAR->width_of[0], y+1, 40*FONT_SBAR->width_of[0], 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; - DrawQ_Fill(x + 14*8, y+4, 40, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + DrawQ_Fill(x + 14*8*FONT_SBAR->width_of[0], y+4, 40*FONT_SBAR->width_of[0], 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // print the text //DrawQ_String(x, y, va("%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true); if (s->qw_ping || s->qw_packetloss) - DrawQ_String(x, y, va("%4i %3i %4i %5i %-4s %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va("%4i %3i %4i %5i %-4s %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else - DrawQ_String(x, y, va(" %4i %5i %-4s %c%s", minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va(" %4i %5i %-4s %c%s", minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } } else @@ -1641,23 +1642,23 @@ float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) if (s->qw_spectator) { if (s->qw_ping || s->qw_packetloss) - DrawQ_String(x, y, va("%4i %3i spect %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va("%4i %3i spect %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else - DrawQ_String(x, y, va(" spect %c%s", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va(" spect %c%s", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { // draw colors behind score c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; - DrawQ_Fill(x + 9*8, y+1, 40, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + DrawQ_Fill(x + 9*8*FONT_SBAR->width_of[0], y+1, 40*FONT_SBAR->width_of[0], 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; - DrawQ_Fill(x + 9*8, y+4, 40, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + DrawQ_Fill(x + 9*8*FONT_SBAR->width_of[0], y+4, 40*FONT_SBAR->width_of[0], 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // print the text //DrawQ_String(x, y, va("%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true); if (s->qw_ping || s->qw_packetloss) - DrawQ_String(x, y, va("%4i %3i %5i %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va("%4i %3i %5i %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else - DrawQ_String(x, y, va(" %5i %c%s", (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(x, y, va(" %5i %c%s", (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } } return 8; @@ -1665,7 +1666,7 @@ float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) void Sbar_DeathmatchOverlay (void) { - int i, x, y; + int i, y, xmin, xmax, ymin, ymax; // request new ping times every two second if (cl.last_ping_request < realtime - 2 && cls.netcon) @@ -1702,21 +1703,32 @@ void Sbar_DeathmatchOverlay (void) } } - DrawQ_Pic ((vid_conwidth.integer - sb_ranking->width)/2, 8, sb_ranking, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); - // scores Sbar_SortFrags (); + + ymin = 8; + ymax = 40 + 8 + (Sbar_IsTeammatch() ? (teamlines * 8 + 5): 0) + scoreboardlines * 8 - 1; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + xmin = (vid_conwidth.integer - (26 + 15) * 8 * FONT_SBAR->width_of[0]) / 2; // 26 characters until name, then we assume 15 character names (they can be longer but usually aren't) + else + xmin = (vid_conwidth.integer - (16 + 25) * 8 * FONT_SBAR->width_of[0]) / 2; // 16 characters until name, then we assume 25 character names (they can be longer but usually aren't) + xmax = vid_conwidth.integer - xmin; + + if(gamemode == GAME_NEXUIZ) + DrawQ_Pic (xmin - 8, ymin - 8, 0, xmax-xmin+1 + 2*8, ymax-ymin+1 + 2*8, 0, 0, 0, sbar_alpha_bg.value, 0); + + DrawQ_Pic ((vid_conwidth.integer - sb_ranking->width)/2, 8, sb_ranking, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + // draw the text y = 40; if (cls.protocol == PROTOCOL_QUAKEWORLD) { - x = (vid_conwidth.integer - (26 + 15) * 8) / 2; // 26 characters until name, then we assume 15 character names (they can be longer but usually aren't) - DrawQ_String(x, y, va("ping pl%% time frags team name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(xmin, y, va("ping pl%% time frags team name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { - x = (vid_conwidth.integer - (16 + 15) * 8) / 2; // 16 characters until name, then we assume 15 character names (they can be longer but usually aren't) - DrawQ_String(x, y, va("ping pl%% frags name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false ); + DrawQ_String_Font(xmin, y, va("ping pl%% frags name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } y += 8; @@ -1724,12 +1736,12 @@ void Sbar_DeathmatchOverlay (void) { // show team scores first for (i = 0;i < teamlines && y < vid_conheight.integer;i++) - y += (int)Sbar_PrintScoreboardItem((teams + teamsort[i]), x, y); + y += (int)Sbar_PrintScoreboardItem((teams + teamsort[i]), xmin, y); y += 5; } for (i = 0;i < scoreboardlines && y < vid_conheight.integer;i++) - y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); + y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], xmin, y); } /* -- 2.39.2