mirror of
https://github.com/bearstonem/openmohaa-vr.git
synced 2026-10-23 17:00:27 +02:00
The retail HUD is five menus from ui/hud_*.urc, each aligned to a corner of a 4:3 screen. On a monitor those corners are a glance away; through a lens they are the far edge, present and unreadable. cg_vrhud.cpp draws the same information again as one group below the centre of the view. Nothing is read back out of the widgets, because there is nothing in them to read: every one is a formatter over cl.snap.ps.stats[], thirty-two integers, and the .urc files are pure layout - hud_health.urc is "playerstat 0, maxplayerstat 1, draw it as a vertical bar". cgame has the same array as cg.snap->ps.stats[]. The .urc files were still worth reading, as the spec for which stat index and which shader each element uses, and the decode of the two compass angles is taken from the widgets that already do it rather than guessed: the heading is the eye yaw less stats[STAT_COMPASSNORTH]/182, a sixteen bit angle, and the objective is stat/10 - 180 and is already relative to the view, because hud_compass.urc draws it as a plain spinner and not the headingspinner variant that subtracts the view angles itself. It is drawn from cgame rather than as another widget for a reason beyond taste. cgame's 2D goes through the one full screen window UIView3D::set2D sets up, and that window is converged as a whole. A widget would arrive with its own window, which is the first bug below. Convergence: the correction belongs to the display, not to the window. R_VRAdjust2DOrtho bent whatever ortho window it was handed, and in this engine that window is usually one widget - UIWidget::set2D cuts a viewport and an ortho window to each widget's own frame. The correction was therefore scaled to the widget, and a 150 pixel compass in a 1680 pixel eye got about a twelfth of the shift it needed while the objectives, drawn through the full screen window, got all of it. Every element ended up with a disparity in proportion to its own width, so each sat at a different apparent depth, and no vergence resolves that set - which is what "the HUD will not converge" was. It is R_VRAdjust2DRect now, applied to the viewport and scissor rectangle in eye buffer pixels, which every 2D window shares. The scissor has to move with it or a widget the shift has displaced is clipped away at where it used to be. vr_hudOffset goes back to 1 in autoexec.cfg: 0 was set on a measurement of the old bug, and it is the one value that cannot fuse at all, because the centre of each eye's buffer is about seven degrees off its own axis in opposite directions, so it asks the eyes to diverge by fourteen degrees. Text: the font atlases had no texture. Every string drew as a flat black rectangle - 0,0,0 with no glyph structure, measured off an eye dump rather than a compositor screenshot. The atlas is a 32 bit TGA with a real alpha channel and the shader is correct, so neither of the obvious causes held. The shader's own stage reported texnum 0, and GL_Bind binds image->texnum straight, so it was binding texture zero, which samples opaque black. The image is alive and correctly named, so it was never freed - R_FreeImage and R_DeleteTextures both memset, and the name survived. texnum was zero from creation. R_LoadFontShader now notices and reloads, but it has to free the image first: R_FindImageFileOld looks in the hash before it looks on disk, so asking by name returns the very image that has no texture. Three of the four fonts come back with a texture; verdana-14, the first one created, still comes back as zero on a fresh load from disk, which means qglGenTextures is not writing to it and the real bug is upstream in image creation. That is not fixed here, and CG_HudDrawElements still draws its text as boxes because of it. Also in here: R_DrawTrianglePic added to the cgame imports, which the UI has always had as re.DrawTrianglePic and is the only way to draw anything rotated, so the compass card can turn under a fixed needle the way hud_compass.urc does it. The retail corner HUD is hidden from the "Hide the HUD when necessary" block in UI_Update rather than from the explicit Display calls above it, because that branch returns early and is not the path taken - when it is not, UpdateViews walks the whole tree and draws every shown menu regardless, which is how hud_health and hud_ammo stayed on screen through the first attempt. Bars are R_DrawBox rather than a stretched gfx/2d/blank. That shader is declared alphaGen oneMinusVertex, which inverts the alpha: the bars asked for at 1 came out at 0 and the backdrop asked for at 0.35 came out at 0.65. vr_hudGroupY is 0.73 and does not mean what it looks like. It places the cluster in the flat frame, which is then scaled to vr_hudScale about the middle of the display and lifted by the eye's vertical frustum asymmetry, 170 pixels of 1760 here - so 0.73 is a little below the middle of what is actually seen and 0.62 was a little above it. The reachable range is about 0.13 to 0.68 of the eye. Not done: the damage direction ring, the objective as compassobjarrow rather than a pip, and the compass worth having, which is one the player takes out of a pocket and holds.
1716 lines
50 KiB
C++
1716 lines
50 KiB
C++
/*
|
|
===========================================================================
|
|
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 (cgs.gametype == GT_SINGLE_PLAYER) {
|
|
// Fixed in 2.0
|
|
// Don't draw server lag in single-player mode
|
|
return;
|
|
}
|
|
|
|
if (!cg_drawsvlag->integer) {
|
|
return;
|
|
}
|
|
|
|
if (!developer->integer) {
|
|
// Changed in 2.0
|
|
// Svlag should only be drawn when developer mode is enabled
|
|
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;
|
|
float fAlpha;
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Added in OPM
|
|
// Full on the wrist panel, whatever the fade says. In the flat game the
|
|
// objectives are a hold-the-key overlay - CG_ScoresDown_f fades them in
|
|
// while the key is held - and a transient overlay on a panel that is
|
|
// itself only visible while the player is looking at their wrist would
|
|
// almost never be seen at all. Turning your wrist up *is* the gesture
|
|
// that asks for them, so the fade has nothing left to do there.
|
|
//
|
|
// Held in a local rather than written back to cg.ObjectivesCurrentAlpha:
|
|
// CG_DrawStopwatch reads that field to decide the stopwatch is covered
|
|
// and removes it, and CG_SetDesiredObjectiveAlpha animates from it.
|
|
//
|
|
fAlpha = cg.ObjectivesCurrentAlpha;
|
|
|
|
//
|
|
// Added in OPM
|
|
// Always up in VR, whatever the fade says. In the flat game the objectives
|
|
// are a hold-the-key overlay - CG_ScoresDown_f fades them in while the key
|
|
// is held - and there is no key to hold in a headset.
|
|
//
|
|
// Its own switch rather than a test on the draw pass. It was written as
|
|
// pass == 1 while the HUD lived on the wrist panel; when the HUD moved
|
|
// back into the eye buffers the pass became 0 and the objectives went
|
|
// straight back to invisible, which took a device run to notice.
|
|
//
|
|
if (vr_objectivesAlways && vr_objectivesAlways->integer) {
|
|
fAlpha = 1.0f;
|
|
}
|
|
|
|
//
|
|
// Added in OPM
|
|
// The objectives were reported missing from the wrist panel and there are
|
|
// three quite different reasons they could be, so this says which. The
|
|
// pass names where it is drawing; alpha under 0.02 is the fade still
|
|
// holding them off; and a set count of zero means the level never sent any
|
|
// - which a map entered straight through vr_startMap, past the scripting
|
|
// that hands them out, is a very good way to arrange.
|
|
//
|
|
if (vr_traceObjectives && vr_traceObjectives->integer) {
|
|
static int lastReport;
|
|
|
|
if (!lastReport || cg.time - lastReport > 2000) {
|
|
int iSet = 0;
|
|
int k;
|
|
|
|
for (k = 0; k < MAX_OBJECTIVES; k++) {
|
|
if (cg.Objectives[k].flags != OBJ_FLAG_NONE && !(cg.Objectives[k].flags & OBJ_FLAG_HIDDEN)) {
|
|
iSet++;
|
|
}
|
|
}
|
|
|
|
lastReport = cg.time;
|
|
cgi.Printf(
|
|
"objectives: pass %d alpha %.2f (raw %.2f) set %d current %d\n",
|
|
vr_hudPass ? vr_hudPass->integer : -1,
|
|
fAlpha,
|
|
cg.ObjectivesCurrentAlpha,
|
|
iSet,
|
|
iCurrentObjective
|
|
);
|
|
}
|
|
}
|
|
|
|
if (fAlpha < 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] = fAlpha * 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] = fAlpha;
|
|
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] = fAlpha;
|
|
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] = fAlpha;
|
|
hBoxShader = cgs.media.checkedBoxShader;
|
|
} else {
|
|
vColor[0] = 1.0;
|
|
vColor[1] = 1.0;
|
|
vColor[2] = 1.0;
|
|
vColor[3] = fAlpha;
|
|
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] = fAlpha;
|
|
}
|
|
|
|
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] = fAlpha;
|
|
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) {
|
|
if (cgs.gametype != GT_LIBERATION) {
|
|
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 {
|
|
// No clock on liberation game mode
|
|
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);
|
|
if (!shader) {
|
|
// Fixed in OPM
|
|
// Fallback to normal crosshair texture if it doesn't exist
|
|
shader = cgi.R_RegisterShaderNoMip(cg_crosshair->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) * cgs.uiHiResScale[0];
|
|
height = cgi.R_GetShaderHeight(shader) * cgs.uiHiResScale[1];
|
|
x = (cgs.glconfig.vidWidth - width) * 0.5f;
|
|
y = (cgs.glconfig.vidHeight - height) * 0.5f;
|
|
|
|
cgi.R_SetColor(NULL);
|
|
cgi.R_DrawStretchPic(x, y, width, height, 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
|
|
==============
|
|
*/
|
|
/*
|
|
=================
|
|
CG_DrawVRWheel
|
|
|
|
The weapon wheel, while the player is holding it open.
|
|
|
|
Deliberately plain: names in a ring with the one under the controller lit up.
|
|
A wheel is only worth having if it can be read at a glance while something is
|
|
shooting at you, and the thing that makes it readable is the highlight moving
|
|
the moment the wrist does - not what the segments are made of.
|
|
|
|
Drawn with the world rather than on the wrist panel, because the hand aiming it
|
|
is out in front of the player and a menu that lives somewhere else would have
|
|
them looking in two places at once.
|
|
=================
|
|
*/
|
|
static void CG_DrawVRWheel(void)
|
|
{
|
|
const char *names[8];
|
|
int selection = -1;
|
|
int count = 0;
|
|
int i;
|
|
float cx, cy, radius;
|
|
|
|
if (!cgi.VR_GetWheel || !cgi.VR_GetWheel(&selection, names, (int)ARRAY_LEN(names), &count)) {
|
|
return;
|
|
}
|
|
|
|
if (count <= 0) {
|
|
return;
|
|
}
|
|
|
|
// The same virtual screen the objectives text uses. Computing a scale
|
|
// from vidWidth/640 instead is not the same thing on this device - the
|
|
// eye buffer is 1680x1760, nothing like 4:3 - and it put the ring off
|
|
// the edge of the picture.
|
|
cx = 320.0f;
|
|
cy = 240.0f;
|
|
radius = 90.0f;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
// Clockwise from straight up, matching the order the VR layer numbers
|
|
// the segments in - the two have to agree or the highlight lands on
|
|
// the wrong word.
|
|
const float angle = (float)i * (2.0f * M_PI / (float)count);
|
|
const float x = cx + sinf(angle) * radius;
|
|
const float y = cy - cosf(angle) * radius;
|
|
vec4_t colour;
|
|
|
|
if (i == selection) {
|
|
colour[0] = 1.0f; colour[1] = 0.85f; colour[2] = 0.4f; colour[3] = 1.0f;
|
|
} else {
|
|
colour[0] = 0.75f; colour[1] = 0.75f; colour[2] = 0.75f; colour[3] = 0.6f;
|
|
}
|
|
|
|
cgi.R_SetColor(colour);
|
|
cgi.R_DrawString(cgs.media.objectiveFont, names[i], x, y / cgs.uiHiResScale[1], -1, cgs.uiHiResScale);
|
|
}
|
|
|
|
cgi.R_SetColor(NULL);
|
|
}
|
|
|
|
void CG_Draw2D(void)
|
|
{
|
|
//
|
|
// Added in OPM
|
|
// In VR this is called twice with the frame split in half: once for the
|
|
// panel on the player's wrist, and once more with the world, for the
|
|
// couple of things that would be useless anywhere else. 0 is every flat
|
|
// frame, where it is called once and draws the lot.
|
|
//
|
|
const int pass = vr_hudPass ? vr_hudPass->integer : 0;
|
|
|
|
if (pass != 2) {
|
|
CG_UpdateCountdown();
|
|
CG_DrawLagometer();
|
|
CG_HudDrawElements();
|
|
|
|
//
|
|
// Added in OPM
|
|
// The gathered VR HUD carries the objectives itself, laid out beside
|
|
// the compass rather than across the top left of a screen nobody in a
|
|
// headset is looking at. Drawing both would put the same text on the
|
|
// view twice, in two places, at two sizes.
|
|
//
|
|
if (vr_hud && vr_hud->integer) {
|
|
CG_DrawVRHud();
|
|
} else {
|
|
CG_DrawObjectives();
|
|
}
|
|
|
|
CG_DrawIcons();
|
|
CG_DrawStopwatch();
|
|
CG_DrawSpectatorView();
|
|
CG_DrawPlayerTeam();
|
|
CG_DrawPlayerEntInfo();
|
|
CG_UpdateAttackerDisplay();
|
|
CG_DrawVote();
|
|
CG_DrawInstantMessageMenu();
|
|
}
|
|
|
|
if (pass != 1) {
|
|
CG_DrawVRWheel();
|
|
|
|
// The scope surround has to sit around the magnified view, so it stays
|
|
// with the world rather than travelling to the wrist.
|
|
CG_DrawZoomOverlay();
|
|
|
|
//
|
|
// Added in OPM
|
|
// The reticle is drawn at the centre of the screen, which in a headset
|
|
// is the centre of the player's head - and the weapon is aimed by the
|
|
// controller, not by where they are looking. So it marks a spot the
|
|
// gun is not pointing at, pinned to their face wherever they turn.
|
|
//
|
|
// pass 0 is the flat frame, where the reticle means what it always
|
|
// meant and is drawn as before. vr_crosshair brings it back in VR for
|
|
// anyone who wants it.
|
|
//
|
|
if (!pass || (vr_crosshair && vr_crosshair->integer)) {
|
|
CG_DrawCrosshair();
|
|
}
|
|
}
|
|
}
|