+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;
+
+ if (con_mutex) Thread_UnlockMutex(con_mutex);
+}
+
+/*
+================
+Con_LineHeight
+
+Returns the height of a given console line; calculates it if necessary.
+================
+*/
+static 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;
+ 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.
+================
+*/
+static int Con_DrawConsoleLine(unsigned mask_must, unsigned 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(unsigned mask_must, unsigned 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, qbool forcedfullscreen)
+{
+ float alpha, alpha0;
+ double sx, sy;
+ int mask_must = 0;
+ int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
+ cachepic_t *conbackpic;
+ unsigned int conbackflags;
+
+ if (lines <= 0)
+ return;
+
+ if (con_mutex) Thread_LockMutex(con_mutex);
+
+ if (con_backscroll < 0)
+ con_backscroll = 0;
+
+ con_vislines = lines;
+
+ r_draw2d_force = true;
+
+// draw the background
+ alpha0 = forcedfullscreen ? 1.0f : scr_conalpha.value; // always full alpha when not forced fullscreen
+ if((alpha = alpha0 * scr_conalphafactor.value) > 0)
+ {
+ sx = scr_conscroll_x.value;
+ sy = scr_conscroll_y.value;
+ conbackflags = CACHEPICFLAG_FAILONMISSING; // So console is readable when game content is missing
+ if (sx != 0 || sy != 0)
+ conbackflags &= CACHEPICFLAG_NOCLAMP;
+ conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", conbackflags) : NULL;
+ sx *= host.realtime; sy *= host.realtime;
+ sx -= floor(sx); sy -= floor(sy);
+ if (Draw_IsPicLoaded(conbackpic))
+ DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
+ 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0);
+ else
+ DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
+ }
+ if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
+ {
+ sx = scr_conscroll2_x.value;
+ sy = scr_conscroll2_y.value;
+ conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
+ sx *= host.realtime; sy *= host.realtime;
+ sx -= floor(sx); sy -= floor(sy);
+ if(Draw_IsPicLoaded(conbackpic))
+ DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
+ 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0);
+ }
+ if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
+ {
+ sx = scr_conscroll3_x.value;
+ sy = scr_conscroll3_y.value;
+ conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
+ sx *= host.realtime; sy *= host.realtime;
+ sx -= floor(sx); sy -= floor(sy);
+ if(Draw_IsPicLoaded(conbackpic))
+ DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
+ 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
+ 0);
+ }
+ DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(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);
+
+// draw the text
+#if 0
+ {
+ int i;
+ int count = CON_LINES_COUNT;
+ float ymax = con_vislines - 2 * con_textsize.value;
+ float y = ymax + con_textsize.value * con_backscroll;
+ for (i = 0;i < count && y >= 0;i++)
+ y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
+ // fix any excessive scrollback for the next frame
+ if (i >= count && y >= 0)
+ {
+ con_backscroll -= (int)(y / con_textsize.value);
+ if (con_backscroll < 0)
+ con_backscroll = 0;
+ }
+ }
+#else
+ if(CON_LINES_COUNT > 0)
+ {
+ int i, last, limitlast;
+ float y;
+ float ymax = con_vislines - 2 * con_textsize.value;
+ Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
+ //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
+ y = ymax - con_textsize.value;
+
+ if(limitlast)
+ y += (CON_LINES(last).height - limitlast) * con_textsize.value;
+ i = last;
+
+ for(;;)
+ {
+ y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
+ if(i == 0)
+ break; // top of console buffer
+ if(y < 0)
+ break; // top of console window
+ limitlast = 0;
+ --i;
+ }
+ }
+#endif
+
+// draw the input prompt, user text, and cursor if desired
+ Con_DrawInput(true, 0, con_vislines - con_textsize.value * 2, con_textsize.value);
+
+ r_draw2d_force = false;
+ if (con_mutex) Thread_UnlockMutex(con_mutex);
+}
+
+/*
+GetMapList
+
+Made by [515]
+Prints not only map filename, but also
+its format (q1/q2/q3/hl) and even its message
+*/
+//[515]: here is an ugly hack.. two gotos... oh my... *but it works*
+//LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
+//LadyHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
+//LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
+qbool GetMapList (const char *s, char *completedname, int completednamebufferlength)
+{
+ fssearch_t *t;
+ char message[1024];
+ int i, k, max, p, o, min;
+ unsigned char *len;
+ qfile_t *f;
+ unsigned char buf[1024];
+
+ dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
+ t = FS_Search(message, 1, true, NULL);
+ if(!t)
+ return false;
+ if (t->numfilenames > 1)
+ Con_Printf("^1 %i maps found :\n", t->numfilenames);
+ len = (unsigned char *)Z_Malloc(t->numfilenames);
+ min = 666;
+ for(max=i=0;i<t->numfilenames;i++)
+ {
+ k = (int)strlen(t->filenames[i]);
+ k -= 9;
+ if(max < k)
+ max = k;
+ else
+ if(min > k)
+ min = k;
+ len[i] = k;
+ }
+ o = (int)strlen(s);
+ for(i=0;i<t->numfilenames;i++)
+ {
+ int lumpofs = 0, lumplen = 0;
+ char *entities = NULL;
+ const char *data = NULL;
+ char keyname[64];
+ char entfilename[MAX_QPATH];
+ char desc[64];
+ desc[0] = 0;
+ dp_strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
+ p = 0;
+ f = FS_OpenVirtualFile(t->filenames[i], true);
+ if(f)
+ {
+ dp_strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
+ memset(buf, 0, 1024);
+ FS_Read(f, buf, 1024);
+ if (!memcmp(buf, "IBSP", 4))
+ {
+ p = LittleLong(((int *)buf)[1]);
+ if (p == Q3BSPVERSION || p == Q3BSPVERSION_LIVE || p == Q3BSPVERSION_IG)
+ {
+ q3dheader_t *header = (q3dheader_t *)buf;
+ lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
+ lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
+ dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
+ }
+ else if (p == Q2BSPVERSION)
+ {
+ q2dheader_t *header = (q2dheader_t *)buf;
+ lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
+ lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
+ dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
+ }
+ else
+ dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
+ }
+ else if (BuffLittleLong(buf) == BSPVERSION)
+ {
+ lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
+ lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
+ dpsnprintf(desc, sizeof(desc), "BSP29");
+ }
+ else if (BuffLittleLong(buf) == 30)
+ {
+ lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
+ lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
+ dpsnprintf(desc, sizeof(desc), "BSPHL");
+ }
+ else if (!memcmp(buf, "BSP2", 4))
+ {
+ lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
+ lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
+ dpsnprintf(desc, sizeof(desc), "BSP2");
+ }
+ else if (!memcmp(buf, "2PSB", 4))
+ {
+ lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
+ lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
+ dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
+ }
+ else if(!memcmp(buf, "VBSP", 4))
+ {
+ hl2dheader_t *header = (hl2dheader_t *)buf;
+ lumpofs = LittleLong(header->lumps[HL2LUMP_ENTITIES].fileofs);
+ lumplen = LittleLong(header->lumps[HL2LUMP_ENTITIES].filelen);
+ dpsnprintf(desc, sizeof(desc), "VBSP%i", LittleLong(((int *)buf)[1]));
+ }
+ else
+ dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
+ dp_strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
+ memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
+ entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
+ if (!entities && lumplen >= 10)
+ {
+ FS_Seek(f, lumpofs, SEEK_SET);
+ entities = (char *)Z_Malloc(lumplen + 1);
+ FS_Read(f, entities, lumplen);
+ }
+ if (entities)
+ {
+ // if there are entities to parse, a missing message key just
+ // means there is no title, so clear the message string now
+ message[0] = 0;
+ data = entities;
+ for (;;)
+ {
+ int l;
+ if (!COM_ParseToken_Simple(&data, false, false, true))
+ break;
+ if (com_token[0] == '{')
+ continue;
+ if (com_token[0] == '}')
+ break;
+ // skip leading whitespace
+ for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
+ for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
+ keyname[l] = com_token[k+l];
+ keyname[l] = 0;
+ if (!COM_ParseToken_Simple(&data, false, false, true))
+ break;
+ if (developer_extra.integer)
+ Con_DPrintf("key: %s %s\n", keyname, com_token);
+ if (!strcmp(keyname, "message"))
+ {
+ // get the message contents
+ dp_strlcpy(message, com_token, sizeof(message));
+ break;
+ }
+ }
+ }
+ }
+ if (entities)
+ Z_Free(entities);
+ if(f)
+ FS_Close(f);
+ *(t->filenames[i]+len[i]+5) = 0;
+ Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
+ }
+ Con_Print("\n");
+ for(p=o;p<min;p++)
+ {
+ k = *(t->filenames[0]+5+p);
+ if(k == 0)
+ goto endcomplete;
+ for(i=1;i<t->numfilenames;i++)
+ if(*(t->filenames[i]+5+p) != k)
+ goto endcomplete;
+ }
+endcomplete:
+ if(p > o && completedname && completednamebufferlength > 0)
+ {
+ memset(completedname, 0, completednamebufferlength);
+ memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
+ }
+ Z_Free(len);
+ FS_FreeSearch(t);
+ return p > o;
+}
+
+/*