/* =========================================================================== Copyright (C) 2025 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // DESCRIPTION: // Some tools used to drawing 2d stuff #include "cg_local.h" /* ================ CG_AdjustFrom640 Adjusted for resolution and screen aspect ratio ================ */ void CG_AdjustFrom640(float *x, float *y, float *w, float *h) { #if 0 // adjust for wide screens if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); } #endif // scale for screen sizes *x *= cgs.screenXScale; *y *= cgs.screenYScale; *w *= cgs.screenXScale; *h *= cgs.screenYScale; } /* ============= CG_TileClearBox This repeats a 64*64 tile graphic to fill the screen around a sized down refresh window. ============= */ void CG_TileClearBox(int x, int y, int w, int h, qhandle_t hShader) { float s1, t1, s2, t2; s1 = x / 64.0; t1 = y / 64.0; s2 = (x + w) / 64.0; t2 = (y + h) / 64.0; cgi.R_DrawStretchPic(x, y, w, h, s1, t1, s2, t2, hShader); } /* ============== CG_TileClear Clear around a sized down screen ============== */ void CG_TileClear(void) { int top, bottom, left, right; int w, h; w = cgs.glconfig.vidWidth; h = cgs.glconfig.vidHeight; if (cg.refdef.x == 0 && cg.refdef.y == 0 && cg.refdef.width == w && cg.refdef.height == h) { return; // full screen rendering } top = cg.refdef.y; bottom = top + cg.refdef.height - 1; left = cg.refdef.x; right = left + cg.refdef.width - 1; // clear above view screen CG_TileClearBox(0, 0, w, top, cgs.media.backTileShader); // clear below view screen CG_TileClearBox(0, bottom, w, h - bottom, cgs.media.backTileShader); // clear left of view screen CG_TileClearBox(0, top, left, bottom - top + 1, cgs.media.backTileShader); // clear right of view screen CG_TileClearBox(right, top, w - right, bottom - top + 1, cgs.media.backTileShader); } /* =============================================================================== LAGOMETER =============================================================================== */ #define LAG_SAMPLES 128 typedef struct { int frameSamples[LAG_SAMPLES]; int frameCount; int snapshotFlags[LAG_SAMPLES]; int snapshotSamples[LAG_SAMPLES]; int snapshotCount; } lagometer_t; lagometer_t lagometer; /* ============== CG_AddLagometerFrameInfo Adds the current interpolate / extrapolate bar for this frame ============== */ void CG_AddLagometerFrameInfo(void) { int offset; offset = cg.time - cg.latestSnapshotTime; lagometer.frameSamples[lagometer.frameCount & (LAG_SAMPLES - 1)] = offset; lagometer.frameCount++; } /* ============== CG_AddLagometerSnapshotInfo Each time a snapshot is received, log its ping time and the number of snapshots that were dropped before it. Pass NULL for a dropped packet. ============== */ void CG_AddLagometerSnapshotInfo(snapshot_t *snap) { // dropped packet if (!snap) { lagometer.snapshotSamples[lagometer.snapshotCount & (LAG_SAMPLES - 1)] = -1; lagometer.snapshotCount++; return; } // add this snapshot's info lagometer.snapshotSamples[lagometer.snapshotCount & (LAG_SAMPLES - 1)] = snap->ping; lagometer.snapshotFlags[lagometer.snapshotCount & (LAG_SAMPLES - 1)] = snap->snapFlags; lagometer.snapshotCount++; } /* ============== CG_DrawDisconnect ============== */ void CG_DrawDisconnect(void) { float x, y; float w, h; int cmdNum; qhandle_t handle; usercmd_t cmd; // draw the phone jack if we are completely past our buffers cmdNum = cgi.GetCurrentCmdNumber() - CMD_BACKUP + 1; cgi.GetUserCmd(cmdNum, &cmd); if (!cg.snap || cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time) { // special check for map_restart return; } // blink it if ((cg.time >> 9) & 1) { return; } handle = cgi.R_RegisterShader("gfx/2d/net.tga"); w = cgi.R_GetShaderWidth(handle) * cgs.uiHiResScale[0]; h = cgi.R_GetShaderHeight(handle) * cgs.uiHiResScale[1]; x = ((float)cgs.glconfig.vidWidth - w) * 0.5; y = (float)cgs.glconfig.vidHeight - h; cgi.R_DrawStretchPic(x, y, w, h, 0, 0, 1, 1, handle); } #define MAX_LAGOMETER_PING 900 #define MAX_LAGOMETER_RANGE 300 /* ============== CG_DrawLagometer ============== */ void CG_DrawLagometer(void) { int a, i; float v; float ax, ay, aw, ah, mid, range; int color; float vscale; if (!cg_lagometer->integer) { CG_DrawDisconnect(); return; } // // draw the graph // ax = 272.0; ay = 432.0; aw = 96.0; ah = 48.0; CG_AdjustFrom640(&ax, &ay, &aw, &ah); cgi.R_SetColor(NULL); cgi.R_DrawStretchPic(ax, ay, aw, ah, 0, 0, 1, 1, cgs.media.lagometerShader); color = -1; range = ah / 3; mid = ay + range; vscale = range / MAX_LAGOMETER_RANGE; // draw the frame interpoalte / extrapolate graph for (a = 0; a < aw; a++) { i = (lagometer.frameCount - 1 - a) & (LAG_SAMPLES - 1); v = lagometer.frameSamples[i]; v *= vscale; if (v > 0) { if (color != 1) { color = 1; cgi.R_SetColor(g_color_table[ColorIndex(COLOR_YELLOW)]); } if (v > range) { v = range; } cgi.R_DrawBox(ax + aw - a, mid - v, 1, v); } else if (v < 0) { if (color != 2) { color = 2; cgi.R_SetColor(g_color_table[ColorIndex(COLOR_BLUE)]); } v = -v; if (v > range) { v = range; } cgi.R_DrawBox(ax + aw - a, mid, 1, v); } } // draw the snapshot latency / drop graph range = ah / 2; vscale = range / MAX_LAGOMETER_PING; for (a = 0; a < aw; a++) { i = (lagometer.snapshotCount - 1 - a) & (LAG_SAMPLES - 1); v = lagometer.snapshotSamples[i]; if (v > 0) { if (lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED) { if (color != 5) { color = 5; // YELLOW for rate delay cgi.R_SetColor(g_color_table[ColorIndex(COLOR_YELLOW)]); } } else { if (color != 3) { color = 3; cgi.R_SetColor(g_color_table[ColorIndex(COLOR_GREEN)]); } } v = v * vscale; if (v > range) { v = range; } cgi.R_DrawBox(ax + aw - a, ay + ah - v, 1, v); } else if (v < 0) { if (color != 4) { color = 4; // RED for dropped snapshots cgi.R_SetColor(g_color_table[ColorIndex(COLOR_RED)]); } cgi.R_DrawBox(ax + aw - a, ay + ah - range, 1, range); } } cgi.R_SetColor(NULL); CG_DrawDisconnect(); } static void CG_DrawPauseIcon() { qhandle_t handle; float x, y, w, h; if (paused->integer) { handle = cgs.media.pausedShader; } else { if (cg.predicted_player_state.pm_flags & PMF_LEVELEXIT) { // blink it if ((cg.time >> 9) & 1) { return; } handle = cgs.media.levelExitShader; } else { return; } } w = cgi.R_GetShaderWidth(handle); h = cgi.R_GetShaderHeight(handle); if (cg.snap && cg.snap->ps.blend[3] > 0) { y = cgs.glconfig.vidHeight * 0.45f - h / 2.f; } else { y = cgs.glconfig.vidHeight * 0.75f - h / 2.f; } x = (cgs.glconfig.vidWidth - w) / 2.f; cgi.R_SetColor(colorWhite); cgi.R_DrawStretchPic(x, y, w * cgs.uiHiResScale[0], h * cgs.uiHiResScale[1], 0, 0, 1, 1, handle); } static void CG_DrawServerLag() { float x, y; float w, h; qhandle_t handle; if (!cg_drawsvlag->integer) { return; } if (!developer->integer && !cgs.gametype) { return; } if (!cgs.serverLagTime) { return; } if (cg.time - cgs.serverLagTime > 3000) { return; } // blink it if ((cg.time >> 9) & 1) { return; } handle = cgi.R_RegisterShader("gfx/2d/slowserver"); w = (float)cgi.R_GetShaderWidth(handle) * cgs.uiHiResScale[0] / 4; h = (float)cgi.R_GetShaderHeight(handle) * cgs.uiHiResScale[1] / 4; x = ((float)cgs.glconfig.vidWidth - w) / 2; y = (float)cgs.glconfig.vidHeight - h; cgi.R_DrawStretchPic(x, y, w, h, 0.0, 0.0, 1.0, 1.0, handle); } /* ============== CG_DrawIcons ============== */ void CG_DrawIcons(void) { if (!cg_hud->integer) { return; } CG_DrawPauseIcon(); CG_DrawServerLag(); } void CG_DrawOverlayTopBottom(qhandle_t handleTop, qhandle_t handleBottom, float fAlpha) { int iHalfWidth; int iWidthOffset; vec4_t color; color[0] = 1.0; color[1] = 1.0; color[2] = 1.0; color[3] = fAlpha; cgi.R_SetColor(color); iHalfWidth = cgs.glconfig.vidHeight >> 1; iWidthOffset = (cgs.glconfig.vidWidth - cgs.glconfig.vidHeight) >> 1; cgi.R_DrawStretchPic(iWidthOffset, 0.0, iHalfWidth, iHalfWidth, 1.0, 0.0, 0.0, 1.0, handleTop); cgi.R_DrawStretchPic(iWidthOffset + iHalfWidth, 0.0, iHalfWidth, iHalfWidth, 0.0, 0.0, 1.0, 1.0, handleTop); cgi.R_DrawStretchPic(iWidthOffset, iHalfWidth, iHalfWidth, iHalfWidth, 1.0, 0.0, 0.0, 1.0, handleBottom); cgi.R_DrawStretchPic( iWidthOffset + iHalfWidth, iHalfWidth, iHalfWidth, iHalfWidth, 0.0, 0.0, 1.0, 1.0, handleBottom ); color[0] = 0.0; color[1] = 0.0; color[2] = 0.0; cgi.R_SetColor(color); cgi.R_DrawStretchPic(0.0, 0.0, iWidthOffset, cgs.glconfig.vidHeight, 0.0, 0.0, 1.0, 1.0, cgs.media.lagometerShader); cgi.R_DrawStretchPic( cgs.glconfig.vidWidth - iWidthOffset, 0.0, iWidthOffset, cgs.glconfig.vidHeight, 0.0, 0.0, 1.0, 1.0, cgs.media.lagometerShader ); } void CG_DrawOverlayMiddle(qhandle_t handle, float fAlpha) { int iHalfWidth; int iWidthOffset; vec4_t color; color[0] = 1.0; color[1] = 1.0; color[2] = 1.0; color[3] = fAlpha; cgi.R_SetColor(color); iHalfWidth = cgs.glconfig.vidHeight >> 1; iWidthOffset = (cgs.glconfig.vidWidth - cgs.glconfig.vidHeight) >> 1; cgi.R_DrawStretchPic(iWidthOffset, 0.0, iHalfWidth, iHalfWidth, 0.0, 0.0, 1.0, 1.0, handle); cgi.R_DrawStretchPic(iWidthOffset + iHalfWidth, 0.0, iHalfWidth, iHalfWidth, 1.0, 0.0, 0.0, 1.0, handle); cgi.R_DrawStretchPic(iWidthOffset, iHalfWidth, iHalfWidth, iHalfWidth, 0.0, 1.0, 1.0, 0.0, handle); cgi.R_DrawStretchPic(iWidthOffset + iHalfWidth, iHalfWidth, iHalfWidth, iHalfWidth, 1.0, 1.0, 0.0, 0.0, handle); color[0] = 0.0; color[1] = 0.0; color[2] = 0.0; cgi.R_SetColor(color); cgi.R_DrawStretchPic(0.0, 0.0, iWidthOffset, cgs.glconfig.vidHeight, 0.0, 0.0, 1.0, 1.0, cgs.media.lagometerShader); cgi.R_DrawStretchPic( cgs.glconfig.vidWidth - iWidthOffset, 0.0, iWidthOffset, cgs.glconfig.vidHeight, .0, 0.0, 1.0, 1.0, cgs.media.lagometerShader ); } void CG_DrawOverlayFullScreen(qhandle_t handle, float fAlpha) { int iHalfWidth, iHalfHeight; vec4_t color; color[0] = 1.0; color[1] = 1.0; color[2] = 1.0; color[3] = fAlpha; cgi.R_SetColor(color); iHalfHeight = cgs.glconfig.vidHeight >> 1; iHalfWidth = cgs.glconfig.vidWidth >> 1; cgi.R_DrawStretchPic(0.0, 0.0, iHalfWidth, iHalfHeight, 0.0, 0.0, 1.0, 1.0, handle); cgi.R_DrawStretchPic(iHalfWidth, 0.0, iHalfWidth, iHalfHeight, 1.0, 0.0, 0.0, 1.0, handle); cgi.R_DrawStretchPic(0.0, iHalfHeight, iHalfWidth, iHalfHeight, 0.0, 1.0, 1.0, 0.0, handle); cgi.R_DrawStretchPic(iHalfWidth, iHalfHeight, iHalfWidth, iHalfHeight, 1.0, 1.0, 0.0, 0.0, handle); } void CG_DrawZoomOverlay() { static int zoomType; static float fAlpha; const char *weaponstring; qboolean bDrawOverlay; weaponstring = ""; bDrawOverlay = qtrue; if (!cg.snap) { return; } if (cg.snap->ps.activeItems[1] >= 0) { weaponstring = CG_ConfigString(CS_WEAPONS + cg.snap->ps.activeItems[1]); } if (!Q_stricmp(weaponstring, "Spy Camera")) { zoomType = 2; } else if (!Q_stricmp(weaponstring, "Binoculars")) { zoomType = 3; } else { if (cg.snap->ps.stats[STAT_INZOOM] && cg.snap->ps.stats[STAT_INZOOM] <= 30) { if (!Q_stricmp(weaponstring, "KAR98 - Sniper")) { zoomType = 1; } else { zoomType = 0; } } else { bDrawOverlay = qfalse; } } if (bDrawOverlay) { fAlpha += cg.frametime * 0.015; if (fAlpha > 1.0) { fAlpha = 1.0; } } else { fAlpha -= cg.frametime * 0.015; if (fAlpha < 0.0) { fAlpha = 0.0; } if (!fAlpha) { return; } } switch (zoomType) { case 1: CG_DrawOverlayTopBottom(cgs.media.kar98TopOverlayShader, cgs.media.kar98BottomOverlayShader, fAlpha); break; case 3: CG_DrawOverlayFullScreen(cgs.media.binocularsOverlayShader, fAlpha); break; default: CG_DrawOverlayMiddle(cgs.media.zoomOverlayShader, fAlpha); break; } } void CG_HudDrawShader(int iInfo) { if (cgi.HudDrawElements[iInfo].shaderName[0]) { cgi.HudDrawElements[iInfo].hShader = cgi.R_RegisterShaderNoMip(cgi.HudDrawElements[iInfo].shaderName); } else { cgi.HudDrawElements[iInfo].hShader = 0; } } void CG_HudDrawFont(int iInfo) { if (cgi.HudDrawElements[iInfo].fontName[0]) { cgi.HudDrawElements[iInfo].pFont = cgi.R_LoadFont(cgi.HudDrawElements[iInfo].fontName); } else { cgi.HudDrawElements[iInfo].pFont = nullptr; } } void CG_RefreshHudDrawElements() { int i; for (i = 0; i < MAX_HUDDRAW_ELEMENTS; ++i) { CG_HudDrawShader(i); CG_HudDrawFont(i); } } void CG_HudDrawElements() { int i; float fX, fY; float fWidth, fHeight; vec2_t virtualScale; if (!cg_huddraw_force->integer && !cg_hud->integer) { return; } virtualScale[0] = cgs.glconfig.vidWidth / 640.0; virtualScale[1] = cgs.glconfig.vidHeight / 480.0; for (i = 0; i < MAX_HUDDRAW_ELEMENTS; i++) { if ((!cgi.HudDrawElements[i].hShader && !cgi.HudDrawElements[i].string[0]) || !cgi.HudDrawElements[i].vColor[3]) { // skip invisible elements continue; } fX = cgi.HudDrawElements[i].iX; fY = cgi.HudDrawElements[i].iY; fWidth = cgi.HudDrawElements[i].iWidth; fHeight = cgi.HudDrawElements[i].iHeight; if (!cgi.HudDrawElements[i].bVirtualScreen) { fWidth *= cgs.uiHiResScale[0]; fHeight *= cgs.uiHiResScale[1]; fX *= cgs.uiHiResScale[0]; fY *= cgs.uiHiResScale[1]; } if (cgi.HudDrawElements[i].iHorizontalAlign == HUD_ALIGN_X_CENTER) { if (cgi.HudDrawElements[i].bVirtualScreen) { fX += 320.0 - fWidth * 0.5; } else { fX += cgs.glconfig.vidWidth * 0.5 - fWidth * 0.5; } } else if (cgi.HudDrawElements[i].iHorizontalAlign == HUD_ALIGN_X_RIGHT) { if (cgi.HudDrawElements[i].bVirtualScreen) { fX += 640.0; } else { fX += cgs.glconfig.vidWidth; } } if (cgi.HudDrawElements[i].iVerticalAlign == HUD_ALIGN_Y_CENTER) { if (cgi.HudDrawElements[i].bVirtualScreen) { fY += 240.0 - fHeight * 0.5; } else { fY += cgs.glconfig.vidHeight * 0.5 - fHeight * 0.5; } } else if (cgi.HudDrawElements[i].iVerticalAlign == HUD_ALIGN_Y_BOTTOM) { if (cgi.HudDrawElements[i].bVirtualScreen) { fY += 480.0; } else { fY += cgs.glconfig.vidHeight; } } cgi.R_SetColor(cgi.HudDrawElements[i].vColor); if (cgi.HudDrawElements[i].string[0]) { fontheader_t *pFont = cgi.HudDrawElements[i].pFont; if (!pFont) { pFont = cgs.media.hudDrawFont; } if (cgi.HudDrawElements[i].bVirtualScreen) { cgi.R_DrawString(pFont, cgi.LV_ConvertString(cgi.HudDrawElements[i].string), fX, fY, -1, virtualScale); } else { cgi.R_DrawString( pFont, cgi.LV_ConvertString(cgi.HudDrawElements[i].string), fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } } else { if (cgi.HudDrawElements[i].bVirtualScreen) { CG_AdjustFrom640(&fX, &fY, &fWidth, &fHeight); } cgi.R_DrawStretchPic(fX, fY, fWidth, fHeight, 0.0, 0.0, 1.0, 1.0, cgi.HudDrawElements[i].hShader); } } } void CG_InitializeObjectives() { int i; cg.ObjectivesAlphaTime = 0.0; cg.ObjectivesBaseAlpha = 0.0; cg.ObjectivesDesiredAlpha = 0.0; cg.ObjectivesCurrentAlpha = 0.0; for (i = 0; i < MAX_OBJECTIVES; i++) { cg.Objectives[i].flags = 0; cg.Objectives[i].text[0] = 0; } } void CG_DrawObjectives() { float vColor[4]; float fX, fY; int iNumObjectives; float fObjectivesTop; static float fWidth; float fHeight; int iNumLines[20]; int iTotalNumLines; int i; int iCurrentObjective; float fTimeDelta; const char *pszLocalizedText; const char *pszLine; iTotalNumLines = 0; for (i = CS_OBJECTIVES; i < CS_OBJECTIVES + MAX_OBJECTIVES; ++i) { CG_ProcessConfigString(i, qfalse); } iCurrentObjective = atoi(CG_ConfigString(CS_CURRENT_OBJECTIVE)); fTimeDelta = cg.ObjectivesAlphaTime - cg.time; cg.ObjectivesCurrentAlpha = cg.ObjectivesDesiredAlpha; if (fTimeDelta > 0) { cg.ObjectivesCurrentAlpha = (cg.ObjectivesBaseAlpha - cg.ObjectivesDesiredAlpha) * sin(fTimeDelta / (M_PI * 50.f + 2.f)) + cg.ObjectivesDesiredAlpha; } if (cg.ObjectivesCurrentAlpha < 0.02) { return; } // Added in 2.0 // Get the minimum Y value, it should be below the compass fObjectivesTop = cgi.UI_GetObjectivesTop(); iNumObjectives = 0; for (i = 0; i < MAX_OBJECTIVES; i++) { if ((cg.Objectives[i].flags == OBJ_FLAG_NONE) || (cg.Objectives[i].flags & OBJ_FLAG_HIDDEN)) { continue; } iNumObjectives++; iNumLines[i] = 0; pszLocalizedText = cgi.LV_ConvertString(cg.Objectives[i].text); for (pszLine = strchr(pszLocalizedText, '\n'); pszLine; pszLine = strchr(pszLine + 1, '\n')) { iNumLines[i]++; } iTotalNumLines += iNumLines[i]; } fX = 25.0; fY = fObjectivesTop + 5; fWidth = (float)(iTotalNumLines * 12 + fObjectivesTop + iNumObjectives * 25 + 32) - fY; vColor[2] = 0.2f; vColor[1] = 0.2f; vColor[0] = 0.2f; vColor[3] = cg.ObjectivesCurrentAlpha * 0.75; cgi.R_SetColor(vColor); cgi.R_DrawStretchPic( fX, fY, 450.0 * cgs.uiHiResScale[0], fWidth * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, cgs.media.objectivesBackShader ); fX = 30.0; fY = fObjectivesTop + 10; vColor[0] = 1.0; vColor[1] = 1.0; vColor[2] = 1.0; vColor[3] = cg.ObjectivesCurrentAlpha; cgi.R_SetColor(vColor); cgi.R_DrawString( cgs.media.objectiveFont, cgi.LV_ConvertString("Mission Objectives:"), fX, fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); fY = fY + 5.0; cgi.R_DrawString( cgs.media.objectiveFont, "_______________________________________________________", fX, fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); fHeight = fObjectivesTop + 35 * cgs.uiHiResScale[1]; for (i = 0; i < MAX_OBJECTIVES; ++i) { qhandle_t hBoxShader; if ((cg.Objectives[i].flags == OBJ_FLAG_NONE) || (cg.Objectives[i].flags & OBJ_FLAG_HIDDEN)) { continue; } if ((cg.Objectives[i].flags & OBJ_FLAG_CURRENT) != 0) { vColor[0] = 1.0; vColor[1] = 1.0; vColor[2] = 1.0; vColor[3] = cg.ObjectivesCurrentAlpha; hBoxShader = cgs.media.uncheckedBoxShader; } else if ((cg.Objectives[i].flags & OBJ_FLAG_COMPLETED) != 0) { vColor[0] = 0.75; vColor[1] = 0.75; vColor[2] = 0.75; vColor[3] = cg.ObjectivesCurrentAlpha; hBoxShader = cgs.media.checkedBoxShader; } else { vColor[0] = 1.0; vColor[1] = 1.0; vColor[2] = 1.0; vColor[3] = cg.ObjectivesCurrentAlpha; hBoxShader = cgs.media.uncheckedBoxShader; } if (i == iCurrentObjective && !(cg.Objectives[i].flags & OBJ_FLAG_COMPLETED)) { vColor[0] = 1.0; vColor[1] = 1.0; vColor[2] = 0.0; vColor[3] = cg.ObjectivesCurrentAlpha; } cgi.R_SetColor(vColor); fX = 55.0; fY = fHeight; cgi.R_DrawString( cgs.media.objectiveFont, cgi.LV_ConvertString(cg.Objectives[i].text), 55.0, fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); fX = 30.0; fY = fHeight; vColor[0] = 1.0; vColor[1] = 1.0; vColor[2] = 1.0; vColor[3] = cg.ObjectivesCurrentAlpha; cgi.R_SetColor(vColor); cgi.R_DrawStretchPic( fX * cgs.uiHiResScale[0], fY, 16.0 * cgs.uiHiResScale[0], 16.0 * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, hBoxShader ); fHeight += iNumLines[i] * 12 + 25 * cgs.uiHiResScale[1]; } } void CG_DrawPlayerTeam() { qhandle_t handle; if (!cg_hud->integer) { return; } if (!cg.snap || cgs.gametype <= GT_FFA) { return; } handle = 0; if (cg.snap->ps.stats[STAT_TEAM] == 3) { handle = cgi.R_RegisterShader("textures/hud/allies"); } else if (cg.snap->ps.stats[STAT_TEAM] == 4) { handle = cgi.R_RegisterShader("textures/hud/axis"); } if (handle) { cgi.R_SetColor(NULL); cgi.R_DrawStretchPic( 96.0 * cgs.uiHiResScale[0], cgs.glconfig.vidHeight - 46 * cgs.uiHiResScale[1], 24.0 * cgs.uiHiResScale[0], 24.0 * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, handle ); } } void CG_DrawPlayerEntInfo() { int iClientNum; const char *pszClientInfo; const char *pszName; float fX, fY; float color[4]; qhandle_t handle; if (!cg_hud->integer) { return; } if (!cg.snap || cg.snap->ps.stats[STAT_INFOCLIENT] == -1) { return; } iClientNum = cg.snap->ps.stats[STAT_INFOCLIENT]; handle = 0; pszClientInfo = CG_ConfigString(iClientNum + CS_PLAYERS); pszName = Info_ValueForKey(pszClientInfo, "name"); color[0] = 0.5; color[1] = 1.0; color[2] = 0.5; color[3] = 1.0; fX = 56.0; fY = (float)cgs.glconfig.vidHeight * 0.5; if (cg.clientinfo[iClientNum].team == TEAM_ALLIES) { handle = cgi.R_RegisterShader("textures/hud/allies"); } else if (cg.clientinfo[iClientNum].team == TEAM_AXIS) { handle = cgi.R_RegisterShader("textures/hud/axis"); } if (handle) { cgi.R_SetColor(0); cgi.R_DrawStretchPic( fX, fY, 16.0 * cgs.uiHiResScale[0], 16.0 * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, handle ); } cgi.R_SetColor(color); cgi.R_DrawString( cgs.media.hudDrawFont, (char *)pszName, fX / cgs.uiHiResScale[0] + 24.0, fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); cgi.R_DrawString( cgs.media.hudDrawFont, va("%i", cg.snap->ps.stats[STAT_INFOCLIENT_HEALTH]), fX / cgs.uiHiResScale[0] + 24.0, fY / cgs.uiHiResScale[1] + 20.0, -1, cgs.uiHiResScale ); } void CG_UpdateAttackerDisplay() { int iClientNum; const char *pszClientInfo; const char *pszName; float fX, fY; float color[4]; if (!cg_hud->integer) { return; } if (!cg.snap || cg.snap->ps.stats[STAT_ATTACKERCLIENT] == -1) { return; } iClientNum = cg.snap->ps.stats[STAT_ATTACKERCLIENT]; pszClientInfo = CG_ConfigString(CS_PLAYERS + iClientNum); pszName = Info_ValueForKey(pszClientInfo, "name"); color[3] = 1.0; fY = (float)(cgs.glconfig.vidHeight - 90); fX = 56.0; if (cgs.gametype > GT_FFA) { qhandle_t handle; handle = 0; if (cg.clientinfo[iClientNum].team == TEAM_ALLIES) { handle = cgi.R_RegisterShader("textures/hud/allies"); } else if (cg.clientinfo[iClientNum].team == TEAM_AXIS) { handle = cgi.R_RegisterShader("textures/hud/axis"); } if (handle) { cgi.R_SetColor(0); cgi.R_DrawStretchPic( 56.0 * cgs.uiHiResScale[0], fY, 24.0 * cgs.uiHiResScale[0], 24.0 * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, handle ); } if ((cg.snap->ps.stats[STAT_TEAM] == TEAM_ALLIES || cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS) && cg.clientinfo[iClientNum].team == cg.snap->ps.stats[STAT_TEAM]) { color[0] = 0.5; color[1] = 1.0; color[2] = 0.5; } else { color[0] = 1.0; color[1] = 0.5; color[2] = 0.5; } fX = 56.0; } else { color[0] = 1.0; color[1] = 0.5; color[2] = 0.5; } cgi.R_SetColor(color); cgi.R_DrawString( cgs.media.attackerFont, pszName, fX / cgs.uiHiResScale[0] + 32.0, fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } void CG_UpdateCountdown() { const char *message = ""; if (!cg.snap) { return; } if (cg.matchStartTime != -1) { int iSecondsLeft, iMinutesLeft; iSecondsLeft = (cgs.matchEndTime - cg.time) / 1000; if (iSecondsLeft >= 0) { iMinutesLeft = iSecondsLeft / 60; message = va("%s %2i:%02i", cgi.LV_ConvertString("Time Left:"), iMinutesLeft, iSecondsLeft % 60); } else if (!cgs.matchEndTime) { message = ""; } } else { // The match has not started yet message = "Waiting For Players"; } if (strcmp(ui_timemessage->string, message)) { cgi.Cvar_Set("ui_timemessage", message); } } static void CG_RemoveStopwatch() { cgi.Cmd_Execute(EXEC_NOW, "ui_removehud hud_stopwatch\n"); cgi.Cmd_Execute(EXEC_NOW, "ui_removehud hud_fuse\n"); cgi.Cmd_Execute(EXEC_NOW, "ui_removehud hud_fuse_wet\n"); } void CG_DrawStopwatch() { int iFraction; if (!cg_hud->integer) { CG_RemoveStopwatch(); return; } if (!cgi.stopWatch->iStartTime) { CG_RemoveStopwatch(); return; } if (cgi.stopWatch->iStartTime >= cgi.stopWatch->iEndTime) { CG_RemoveStopwatch(); return; } if (cgi.stopWatch->iEndTime <= cg.time) { CG_RemoveStopwatch(); return; } if (cg.ObjectivesCurrentAlpha >= 0.02) { CG_RemoveStopwatch(); return; } if (cg.snap && cg.snap->ps.stats[STAT_HEALTH] <= 0) { CG_RemoveStopwatch(); return; } if (cgi.stopWatch->eType >= SWT_FUSE_WET) { iFraction = cgi.stopWatch->iEndTime - cgi.stopWatch->iStartTime; } else { iFraction = cgi.stopWatch->iEndTime - cg.time; } cgi.Cvar_Set("ui_stopwatch", va("%i", iFraction)); switch (cgi.stopWatch->eType) { case SWT_NORMAL: default: cgi.Cmd_Execute(EXEC_NOW, "ui_addhud hud_stopwatch\n"); break; case SWT_FUSE: cgi.Cmd_Execute(EXEC_NOW, "ui_addhud hud_fuse\n"); break; case SWT_FUSE_WET: cgi.Cmd_Execute(EXEC_NOW, "ui_removehud hud_fuse\n"); cgi.Cmd_Execute(EXEC_NOW, "ui_addhud hud_fuse_wet\n"); break; } } void CG_DrawInstantMessageMenu() { float w, h; float x, y; qhandle_t handle; if (!cg.iInstaMessageMenu) { return; } if (cg.iInstaMessageMenu > 0) { handle = cgi.R_RegisterShader(va("textures/hud/instamsg_group_%c", cg.iInstaMessageMenu + 96)); } else { handle = cgi.R_RegisterShader("textures/hud/instamsg_main"); } w = cgi.R_GetShaderWidth(handle); h = cgi.R_GetShaderHeight(handle); x = 8.0; y = ((float)cgs.glconfig.vidHeight - h) * 0.5; cgi.R_SetColor(0); cgi.R_DrawStretchPic( x * cgs.uiHiResScale[0], y, w * cgs.uiHiResScale[0], h * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, handle ); } void CG_DrawSpectatorView_ver_15() { const char *pszString; int iKey1, iKey2; int iKey1b, iKey2b; float fX, fY; qboolean bOnTeam; if (!(cg.predicted_player_state.pm_flags & PMF_SPECTATING)) { return; } bOnTeam = qfalse; if (cg.snap->ps.stats[STAT_TEAM] == TEAM_ALLIES || cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS) { bOnTeam = 1; } if (!bOnTeam) { cgi.Key_GetKeysForCommand("+attackprimary", &iKey1, &iKey2); pszString = cgi.LV_ConvertString(va("Press Fire(%s) to join the battle!", cgi.Key_KeynumToBindString(iKey1))); fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = cgs.glconfig.vidHeight - 64.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(NULL); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } if (cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW) { cgi.Key_GetKeysForCommand("+moveup", &iKey1, &iKey2); cgi.Key_GetKeysForCommand("+movedown", &iKey1b, &iKey2b); pszString = cgi.LV_ConvertString( va("Press Jump(%s) or Duck(%s) to follow a different player.", cgi.Key_KeynumToBindString(iKey1), cgi.Key_KeynumToBindString(iKey1b)) ); fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 40.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(0); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } if (!bOnTeam && (cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW)) { cgi.Key_GetKeysForCommand("+use", &iKey1, &iKey2); pszString = cgi.LV_ConvertString(va("Press Use(%s) to enter free spectate mode.", cgi.Key_KeynumToBindString(iKey1))); fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 24.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(0); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } if (!(cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW)) { cgi.Key_GetKeysForCommand("+use", &iKey1, &iKey2); pszString = cgi.LV_ConvertString( va("Press Use(%s) to enter player following spectate mode.", cgi.Key_KeynumToBindString(iKey1)) ); fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 24.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(0); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } if ((cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW) != 0 && cg.snap && cg.snap->ps.stats[STAT_INFOCLIENT] != -1) { int iClientNum; qhandle_t hShader; vec4_t color; char buf[128]; iClientNum = cg.snap->ps.stats[STAT_INFOCLIENT]; Com_sprintf( buf, sizeof(buf), "%s : %i", cg.clientinfo[iClientNum].name, cg.snap->ps.stats[STAT_INFOCLIENT_HEALTH] ); hShader = 0; color[0] = 0.5; color[1] = 1.0; color[2] = 0.5; color[3] = 1.0; fX = (float)(cgs.glconfig.vidWidth - (cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) - 16) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 80.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(color); cgi.R_DrawString( cgs.media.attackerFont, buf, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); if (cg.clientinfo[iClientNum].team == TEAM_ALLIES) { hShader = cgi.R_RegisterShader("textures/hud/allies"); } else if (cg.clientinfo[iClientNum].team == TEAM_AXIS) { hShader = cgi.R_RegisterShader("textures/hud/axis"); } if (hShader) { cgi.R_SetColor(NULL); cgi.R_DrawStretchPic( fX - 20.0 * cgs.uiHiResScale[0], fY, 16.0 * cgs.uiHiResScale[0], 16.0 * cgs.uiHiResScale[1], 0.0, 0.0, 1.0, 1.0, hShader ); } } } void CG_DrawSpectatorView_ver_6() { const char *pszString; int iKey1, iKey2; int iKey1b, iKey2b; float fX, fY; qboolean bOnTeam; if (!(cg.predicted_player_state.pm_flags & PMF_SPECTATING)) { return; } bOnTeam = qfalse; if (cg.snap->ps.stats[STAT_TEAM] == TEAM_ALLIES || cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS) { bOnTeam = 1; } // retrieve keys for +use cgi.Key_GetKeysForCommand("+use", &iKey1, &iKey2); if (cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW) { pszString = cgi.LV_ConvertString(va("Press Use(%s) to follow a different player.", cgi.Key_KeynumToBindString(iKey1))); } else { pszString = cgi.LV_ConvertString(va("Press Use(%s) to follow a player.", cgi.Key_KeynumToBindString(iKey1))); } fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 40.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(0); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); if (!bOnTeam && (cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW)) { cgi.Key_GetKeysForCommand("+moveup", &iKey1, &iKey2); cgi.Key_GetKeysForCommand("+movedown", &iKey1b, &iKey2b); pszString = cgi.LV_ConvertString( va("Press Jump(%s) or Duck(%s) to free spectate.", cgi.Key_KeynumToBindString(iKey1), cgi.Key_KeynumToBindString(iKey1b)) ); fX = (float)(cgs.glconfig.vidWidth - cgi.UI_FontStringWidth(cgs.media.attackerFont, pszString, -1) * cgs.uiHiResScale[0]) * 0.5; fY = (float)cgs.glconfig.vidHeight - 24.0 * cgs.uiHiResScale[1]; cgi.R_SetColor(0); cgi.R_DrawString( cgs.media.attackerFont, pszString, fX / cgs.uiHiResScale[0], fY / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); } } void CG_DrawSpectatorView() { if (cg_protocol >= PROTOCOL_MOHTA_MIN) { CG_DrawSpectatorView_ver_15(); } else { CG_DrawSpectatorView_ver_6(); } } void CG_DrawCrosshair() { centity_t *friendEnt; qhandle_t shader; vec3_t forward; vec3_t end; vec3_t mins, maxs; trace_t trace; float x, y; float width, height; shader = (qhandle_t)0; if (!cg_hud->integer || !ui_crosshair->integer) { return; } if (!cg.snap) { return; } if ((cg.snap->ps.pm_flags & PMF_NO_HUD) || (cg.snap->ps.pm_flags & PMF_INTERMISSION)) { return; } if (!cg.snap->ps.stats[STAT_CROSSHAIR] && (!cg.snap->ps.stats[STAT_INZOOM] || cg.snap->ps.stats[STAT_INZOOM] > 30)) { return; } // Fixed in OPM: R_RegisterShaderNoMip // Use R_RegisterShaderNoMip, as it's UI stuff if (cgs.gametype != GT_FFA) { AngleVectorsLeft(cg.refdefViewAngles, forward, NULL, NULL); VectorMA(cg.refdef.vieworg, 8192, forward, end); VectorClear(mins); VectorClear(maxs); CG_Trace(&trace, cg.refdef.vieworg, mins, maxs, end, 9999, MASK_SOLID, qfalse, qtrue, "CG_DrawCrosshair"); // ENTITYNUM_WORLD check added in OPM if ((trace.entityNum != ENTITYNUM_NONE && trace.entityNum != ENTITYNUM_WORLD) && trace.entityNum != cg.snap->ps.clientNum) { int myFlags; friendEnt = &cg_entities[trace.entityNum]; if (cgs.gametype != GT_SINGLE_PLAYER) { myFlags = cg_entities[cg.snap->ps.clientNum].currentState.eFlags & EF_ANY_TEAM; } else { // the player will always be considered as an allied // in single-player myFlags = EF_ALLIES; } if (((myFlags & EF_ALLIES) && (friendEnt->currentState.eFlags & EF_ALLIES)) || ((myFlags & EF_AXIS) && (friendEnt->currentState.eFlags & EF_AXIS))) { // friend if (cg.snap->ps.stats[STAT_CROSSHAIR]) { shader = cgi.R_RegisterShaderNoMip(cg_crosshair_friend->string); } } else { // enemy if (cg.snap->ps.stats[STAT_CROSSHAIR]) { shader = cgi.R_RegisterShaderNoMip(cg_crosshair->string); } } } else { if (cg.snap->ps.stats[STAT_CROSSHAIR]) { shader = cgi.R_RegisterShaderNoMip(cg_crosshair->string); } } } else { // FFA if (cg.snap->ps.stats[STAT_CROSSHAIR]) { shader = cgi.R_RegisterShaderNoMip(cg_crosshair->string); } } if (shader) { width = cgi.R_GetShaderWidth(shader); height = cgi.R_GetShaderHeight(shader); x = (cgs.glconfig.vidWidth - width) * 0.5f; y = (cgs.glconfig.vidHeight - height) * 0.5f; cgi.R_SetColor(NULL); cgi.R_DrawStretchPic(x, y, width * cgs.uiHiResScale[0], height * cgs.uiHiResScale[1], 0, 0, 1, 1, shader); } } void CG_DrawVote() { const char *text; int seconds; int percentYes; int percentNo; int percentUndecided; float x, y; vec4_t col; if (!cgs.voteTime) { return; } if (cgs.voteRefreshed) { cgs.voteRefreshed = qfalse; } seconds = (30000 - (cg.time - cgs.voteTime)) / 1000 + 1; if (seconds < 0) { seconds = 0; } percentYes = cgs.numVotesYes * 100 / (cgs.numUndecidedVotes + cgs.numVotesNo + cgs.numVotesYes); percentNo = cgs.numVotesNo * 100 / (cgs.numUndecidedVotes + cgs.numVotesNo + cgs.numVotesYes); percentUndecided = cgs.numUndecidedVotes * 100 / (cgs.numUndecidedVotes + cgs.numVotesNo + cgs.numVotesYes); x = 8 * cgs.uiHiResScale[0]; y = ((cgs.glconfig.vidHeight > 480) ? (cgs.glconfig.vidHeight * 0.725f) : (cgs.glconfig.vidHeight * 0.75f)); cgi.R_SetColor(NULL); text = va("%s: %s", cgi.LV_ConvertString("Vote Running"), cgs.voteString); cgi.R_DrawString( cgs.media.attackerFont, text, x / cgs.uiHiResScale[0], y / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); y += 12 * cgs.uiHiResScale[1]; text = va("%s: %isec %s: %i%% %s: %i%% %s: %i%%", cgi.LV_ConvertString("Time"), seconds, cgi.LV_ConvertString("Yes"), percentYes, cgi.LV_ConvertString("No"), percentNo, cgi.LV_ConvertString("Undecided"), percentUndecided); cgi.R_DrawString( cgs.media.attackerFont, text, x / cgs.uiHiResScale[0], y / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); if (cg.snap && !cg.snap->ps.voted) { col[0] = 0.5; col[1] = 1.0; col[2] = 0.5; col[3] = 1.0; cgi.R_SetColor(col); y += 12 * cgs.uiHiResScale[1]; text = cgi.LV_ConvertString("Vote now, it's your patriotic duty!"); cgi.R_DrawString( cgs.media.attackerFont, text, x / cgs.uiHiResScale[0], y / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); y += 12 * cgs.uiHiResScale[1]; text = cgi.LV_ConvertString("To vote Yes, press F1. To vote No, press F2."); cgi.R_DrawString( cgs.media.attackerFont, text, x / cgs.uiHiResScale[0], y / cgs.uiHiResScale[1], -1, cgs.uiHiResScale ); cgi.R_SetColor(NULL); } } /* ============== CG_Draw2D ============== */ void CG_Draw2D(void) { CG_UpdateCountdown(); CG_DrawZoomOverlay(); CG_DrawLagometer(); CG_HudDrawElements(); CG_DrawObjectives(); CG_DrawIcons(); CG_DrawStopwatch(); CG_DrawSpectatorView(); CG_DrawPlayerTeam(); CG_DrawPlayerEntInfo(); CG_UpdateAttackerDisplay(); CG_DrawVote(); CG_DrawInstantMessageMenu(); CG_DrawCrosshair(); }