mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-24 04:51:25 +00:00
eb9fe030c4
################################################################################ This Patch fixes: ################################################################################ - The "fraglimit warning" was not played at all, if on the blue team. - The "where" console command was broken. - Obelisk explosion wasn't drawn if no Rocketlauncher was loaded. - Impact marks sometimes didn't draw at all. - IMPORTANT BUGFIX: No killing for cheaters with Lightning gun and Gauntlet. - If two doors are close to each other a spectator couldn't fly through them. - More robust, efficient and logical respawning routine. NOTE: The game.qvm will get notable smaller and will use LESS MEMORY! - Drowning sounds are fixed. Now they are played as intended. (as the id comment in the source code shows). - Some AI bugs (OVERFLOW!) in the bot movement code. - Several "Team Arena" Overload and Harvester bugs. - Stops bots from attacking a team mate (player) who only changed teams. - Some voice chats and CTF commands fixed. - "Team_ReturnFlag" was called twice, which did wired things sometimes. NOTE: (G_RunItem checks CONTENTS_NODROP already!) - A bugfix for Gauntlet animation. - Incorrect CTF scoring. - A bunch of corrected comments and print lines ("\n"). - Some regularity of expression and some small trivial bugs. ################################################################################ Details: ################################################################################ ******************************************************************************** BUG: in gamemode GT_TEAM the fraglimit warning will not be played if joining the blue team! -------------------------------------------------------------------------------- Solution: In "CG_CheckLocalSounds": if cgs.scores2 > highScore, highScore should be cgs.scores2. ******************************************************************************** BUG: the "where" console command doesn't work as expected (it's always 0 0 0) but not in id Quake 3 Arena. It seems that now Ioquake3 is affected! -------------------------------------------------------------------------------- Solution: In Function "Cmd_Where_f" ent->s.origin should be ent->r.currentOrigin. ******************************************************************************** BUG: in gamemode GT_OBELISK obelisk explosion won't be drawn if there is no Rocketlauncher loaded. (The "maps without Rocketlauncher" bug) -------------------------------------------------------------------------------- Solution: in "cg_main.c": cgs.media.rocketExplosionShader should be registered if gamemode is GT_OBELISK. ******************************************************************************** BUG: Impact marks sometimes doesn't draw at all. Not easy to reproduce if you don't play (io)Quake3 every day and know the places where it happens! ;) But anyway... Test: start q3dm12 go to "Long Jump Canyon" (where the small platform teleporter for the BFG is) place yourself at the point where the railgun spawns, look in the direction where the red suspended armor is. Now shoot at the sloped wall on the out/leftside of the door you see. (the sloped wall should be nearly in the center of your screen now). If you choose the correct brush face and shoot up and down at this brush face, the impact marks sometimes aren't visible. There are hundreds of custom maps where this can happen! -------------------------------------------------------------------------------- Solution: I replaced the function "SnapVectorTowards" with the one from "Wolfenstein - Enemy Territory (GPL Source Code)" ******************************************************************************** BUG: Normally "NOCLIP" cheaters are logically not allowed to fire a gun. Unfortunatly the Gauntlet (and Lightning gun) was forgotten and not restricted to that. All weapons except those two were handled correct. -------------------------------------------------------------------------------- Solution: Make Gauntlet and Lightning gun not firing for someone who cheats with "NOCLIP" (like all other weapons). ******************************************************************************** NOTE: A few bugfixes are not mine and are reported here: http://www.quake3world.com/forum/viewtopic.php?f=16&t=9179. Thanks to Quake3world, for all those years and the good guys there! ******************************************************************************** BUG: During making a mod I found a very strange bug, which mainly occurs if someone tries to implement a lot of singleplayer monsters which should walk slowly (like the "Crash" bot). So if someone wants to make slow down bots or monsters when they are walking towards a goal and alter the function "BotMoveInGoalArea" then the bots/monsters do stupid things. Otherwise and this is the default (also buggy) behavior they start running although they shouldn't (as seen with the "Crash" bot and will not be fixed here). -------------------------------------------------------------------------------- Solution: Fix overflow in bot user command. BUGFIX from "Hunt" mod by J. Hoffman. ******************************************************************************** BUG: in function "BotMoveToGoal" the special elevator case doesn't make sense. -------------------------------------------------------------------------------- Solution: in "be_ai_move.c": ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) should be ((result->flags & MOVERESULT_ONTOPOF_ELEVATOR) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)). ******************************************************************************** BUG: in function "BotWantsToRetreat" and "BotWantsToChase" this is wrong: "(bs->enemy != redobelisk.entitynum || bs->enemy != blueobelisk.entitynum)" -------------------------------------------------------------------------------- Solution: "... redobelisk.entitynum) && (bs->enemy != blueobelisk.." is correct. ******************************************************************************** BUG: in gamemode GT_OBELISK there are too many node switches for bots (test: mpq3tourney6 with many bots). If that happens, game becomes unplayable. I don't know if this is the best solution but here it is: -------------------------------------------------------------------------------- Solution: In function "AINode_Battle_Fight" right after: if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { I added this: #ifdef MISSIONPACK if (bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum) { AIEnter_Battle_Chase(bs, "battle fight: obelisk out of sight"); return qfalse; } #endif ******************************************************************************** BUG: in gamemode >= GT_TEAM, after team change, bots will (sometimes) not stop shooting at you, although you are on their team now. It seems that the configstrings are f***** up or not reliable in this case! -------------------------------------------------------------------------------- Solution: In function "BotTeam" and "BotSameTeam" get the real team values. ******************************************************************************** BUG: Some of the bots voice commands are wrong. They are commanded to attack the enemy base but they say "Okay, I will defend!" -------------------------------------------------------------------------------- Solution: Corrected some voice commands in "BotCTFOrders_FlagNotAtBase" and "Bot1FCTFOrders_EnemyDroppedFlag" ******************************************************************************** BUG: Spectators couldn't fly through doors if they are very close to each other. You can test it with some regular id maps (q3dm14, q3dm12) but there are also many custom maps where this can happen! This is annoying because in the worst case you can't move at all and are caught inside a door. -------------------------------------------------------------------------------- Solution: There is a solution in a mod called "Hunt" by J. Hoffman. Bugfix is included in this patch! ******************************************************************************** BUG: During making a mod I found it very hard to implement some of my ideas (something like "Limbo" or "Meeting") because of the way the player spawn effect, intermission and spawning on victory pads is handled. I reworked it a bit and simplified it so that the effect is handled when a client respawns (as the name says) and not when a client begins. I think this will help more mod makers everytime they want to make changes to spawning of players, bots on victory pads or monsters... and want to avoid spectators with Machineguns which can kill and score... :() NOTE: I also renamed the poorly named function "respawn" to "ClientRespawn". If someone searches the code base for "respawn" it was really hard to find the correct place for what was meant. "respawn" is used so often, that you really get headache ... now with "ClientRespawn" it's easier! IMPORTANT: The whole respawning, moving to intermission point and everything related to that is now done in a more reliable way without changing the default behavior. (How critical the whole spwaning mess was did you see by yourself (ioquake3 rev. 2076). With this patch it's safer. Trust me, I spent hours of fixing silly problems... -------------------------------------------------------------------------------- Solution: Simplified "ClientBegin" and moved the teleport event to "ClientSpawn". ******************************************************************************** BUG: If a player is dying or hurted under water the hurt/dying sounds AND the drowning sounds are played together. This is silly. Moreover it's no good idea to let the server play client sounds! There was a solution in a mod called "Q3A++" by Dan 'Neurobasher' Gomes which fixes the problem. -------------------------------------------------------------------------------- Solution: Created a "CG_WaterLevel" function to play the appropriate sounds. ******************************************************************************** ################################################################################
2660 lines
58 KiB
C
2660 lines
58 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena 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.
|
|
|
|
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
//
|
|
// cg_draw.c -- draw all of the graphical elements during
|
|
// active (after loading) gameplay
|
|
|
|
#include "cg_local.h"
|
|
|
|
#ifdef MISSIONPACK
|
|
#include "../ui/ui_shared.h"
|
|
|
|
// used for scoreboard
|
|
extern displayContextDef_t cgDC;
|
|
menuDef_t *menuScoreboard = NULL;
|
|
#else
|
|
int drawTeamOverlayModificationCount = -1;
|
|
#endif
|
|
|
|
int sortedTeamPlayers[TEAM_MAXOVERLAY];
|
|
int numSortedTeamPlayers;
|
|
|
|
char systemChat[256];
|
|
char teamChat1[256];
|
|
char teamChat2[256];
|
|
|
|
#ifdef MISSIONPACK
|
|
|
|
int CG_Text_Width(const char *text, float scale, int limit) {
|
|
int count,len;
|
|
float out;
|
|
glyphInfo_t *glyph;
|
|
float useScale;
|
|
// FIXME: see ui_main.c, same problem
|
|
// const unsigned char *s = text;
|
|
const char *s = text;
|
|
fontInfo_t *font = &cgDC.Assets.textFont;
|
|
if (scale <= cg_smallFont.value) {
|
|
font = &cgDC.Assets.smallFont;
|
|
} else if (scale > cg_bigFont.value) {
|
|
font = &cgDC.Assets.bigFont;
|
|
}
|
|
useScale = scale * font->glyphScale;
|
|
out = 0;
|
|
if (text) {
|
|
len = strlen(text);
|
|
if (limit > 0 && len > limit) {
|
|
len = limit;
|
|
}
|
|
count = 0;
|
|
while (s && *s && count < len) {
|
|
if ( Q_IsColorString(s) ) {
|
|
s += 2;
|
|
continue;
|
|
} else {
|
|
glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
|
|
out += glyph->xSkip;
|
|
s++;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
return out * useScale;
|
|
}
|
|
|
|
int CG_Text_Height(const char *text, float scale, int limit) {
|
|
int len, count;
|
|
float max;
|
|
glyphInfo_t *glyph;
|
|
float useScale;
|
|
// TTimo: FIXME
|
|
// const unsigned char *s = text;
|
|
const char *s = text;
|
|
fontInfo_t *font = &cgDC.Assets.textFont;
|
|
if (scale <= cg_smallFont.value) {
|
|
font = &cgDC.Assets.smallFont;
|
|
} else if (scale > cg_bigFont.value) {
|
|
font = &cgDC.Assets.bigFont;
|
|
}
|
|
useScale = scale * font->glyphScale;
|
|
max = 0;
|
|
if (text) {
|
|
len = strlen(text);
|
|
if (limit > 0 && len > limit) {
|
|
len = limit;
|
|
}
|
|
count = 0;
|
|
while (s && *s && count < len) {
|
|
if ( Q_IsColorString(s) ) {
|
|
s += 2;
|
|
continue;
|
|
} else {
|
|
glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
|
|
if (max < glyph->height) {
|
|
max = glyph->height;
|
|
}
|
|
s++;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
return max * useScale;
|
|
}
|
|
|
|
void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) {
|
|
float w, h;
|
|
w = width * scale;
|
|
h = height * scale;
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
|
|
}
|
|
|
|
void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) {
|
|
int len, count;
|
|
vec4_t newColor;
|
|
glyphInfo_t *glyph;
|
|
float useScale;
|
|
fontInfo_t *font = &cgDC.Assets.textFont;
|
|
if (scale <= cg_smallFont.value) {
|
|
font = &cgDC.Assets.smallFont;
|
|
} else if (scale > cg_bigFont.value) {
|
|
font = &cgDC.Assets.bigFont;
|
|
}
|
|
useScale = scale * font->glyphScale;
|
|
if (text) {
|
|
// TTimo: FIXME
|
|
// const unsigned char *s = text;
|
|
const char *s = text;
|
|
trap_R_SetColor( color );
|
|
memcpy(&newColor[0], &color[0], sizeof(vec4_t));
|
|
len = strlen(text);
|
|
if (limit > 0 && len > limit) {
|
|
len = limit;
|
|
}
|
|
count = 0;
|
|
while (s && *s && count < len) {
|
|
glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build
|
|
//int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top;
|
|
//float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height);
|
|
if ( Q_IsColorString( s ) ) {
|
|
memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
|
|
newColor[3] = color[3];
|
|
trap_R_SetColor( newColor );
|
|
s += 2;
|
|
continue;
|
|
} else {
|
|
float yadj = useScale * glyph->top;
|
|
if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) {
|
|
int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
|
|
colorBlack[3] = newColor[3];
|
|
trap_R_SetColor( colorBlack );
|
|
CG_Text_PaintChar(x + ofs, y - yadj + ofs,
|
|
glyph->imageWidth,
|
|
glyph->imageHeight,
|
|
useScale,
|
|
glyph->s,
|
|
glyph->t,
|
|
glyph->s2,
|
|
glyph->t2,
|
|
glyph->glyph);
|
|
colorBlack[3] = 1.0;
|
|
trap_R_SetColor( newColor );
|
|
}
|
|
CG_Text_PaintChar(x, y - yadj,
|
|
glyph->imageWidth,
|
|
glyph->imageHeight,
|
|
useScale,
|
|
glyph->s,
|
|
glyph->t,
|
|
glyph->s2,
|
|
glyph->t2,
|
|
glyph->glyph);
|
|
// CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph);
|
|
x += (glyph->xSkip * useScale) + adjust;
|
|
s++;
|
|
count++;
|
|
}
|
|
}
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
/*
|
|
==============
|
|
CG_DrawField
|
|
|
|
Draws large numbers for status bar and powerups
|
|
==============
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawField (int x, int y, int width, int value) {
|
|
char num[16], *ptr;
|
|
int l;
|
|
int frame;
|
|
|
|
if ( width < 1 ) {
|
|
return;
|
|
}
|
|
|
|
// draw number string
|
|
if ( width > 5 ) {
|
|
width = 5;
|
|
}
|
|
|
|
switch ( width ) {
|
|
case 1:
|
|
value = value > 9 ? 9 : value;
|
|
value = value < 0 ? 0 : value;
|
|
break;
|
|
case 2:
|
|
value = value > 99 ? 99 : value;
|
|
value = value < -9 ? -9 : value;
|
|
break;
|
|
case 3:
|
|
value = value > 999 ? 999 : value;
|
|
value = value < -99 ? -99 : value;
|
|
break;
|
|
case 4:
|
|
value = value > 9999 ? 9999 : value;
|
|
value = value < -999 ? -999 : value;
|
|
break;
|
|
}
|
|
|
|
Com_sprintf (num, sizeof(num), "%i", value);
|
|
l = strlen(num);
|
|
if (l > width)
|
|
l = width;
|
|
x += 2 + CHAR_WIDTH*(width - l);
|
|
|
|
ptr = num;
|
|
while (*ptr && l)
|
|
{
|
|
if (*ptr == '-')
|
|
frame = STAT_MINUS;
|
|
else
|
|
frame = *ptr -'0';
|
|
|
|
CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] );
|
|
x += CHAR_WIDTH;
|
|
ptr++;
|
|
l--;
|
|
}
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
================
|
|
CG_Draw3DModel
|
|
|
|
================
|
|
*/
|
|
void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) {
|
|
refdef_t refdef;
|
|
refEntity_t ent;
|
|
|
|
if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) {
|
|
return;
|
|
}
|
|
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
|
|
memset( &refdef, 0, sizeof( refdef ) );
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
AnglesToAxis( angles, ent.axis );
|
|
VectorCopy( origin, ent.origin );
|
|
ent.hModel = model;
|
|
ent.customSkin = skin;
|
|
ent.renderfx = RF_NOSHADOW; // no stencil shadows
|
|
|
|
refdef.rdflags = RDF_NOWORLDMODEL;
|
|
|
|
AxisClear( refdef.viewaxis );
|
|
|
|
refdef.fov_x = 30;
|
|
refdef.fov_y = 30;
|
|
|
|
refdef.x = x;
|
|
refdef.y = y;
|
|
refdef.width = w;
|
|
refdef.height = h;
|
|
|
|
refdef.time = cg.time;
|
|
|
|
trap_R_ClearScene();
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
trap_R_RenderScene( &refdef );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawHead
|
|
|
|
Used for both the status bar and the scoreboard
|
|
================
|
|
*/
|
|
void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) {
|
|
clipHandle_t cm;
|
|
clientInfo_t *ci;
|
|
float len;
|
|
vec3_t origin;
|
|
vec3_t mins, maxs;
|
|
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
if ( cg_draw3dIcons.integer ) {
|
|
cm = ci->headModel;
|
|
if ( !cm ) {
|
|
return;
|
|
}
|
|
|
|
// offset the origin y and z to center the head
|
|
trap_R_ModelBounds( cm, mins, maxs );
|
|
|
|
origin[2] = -0.5 * ( mins[2] + maxs[2] );
|
|
origin[1] = 0.5 * ( mins[1] + maxs[1] );
|
|
|
|
// calculate distance so the head nearly fills the box
|
|
// assume heads are taller than wide
|
|
len = 0.7 * ( maxs[2] - mins[2] );
|
|
origin[0] = len / 0.268; // len / tan( fov/2 )
|
|
|
|
// allow per-model tweaking
|
|
VectorAdd( origin, ci->headOffset, origin );
|
|
|
|
CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles );
|
|
} else if ( cg_drawIcons.integer ) {
|
|
CG_DrawPic( x, y, w, h, ci->modelIcon );
|
|
}
|
|
|
|
// if they are deferred, draw a cross out
|
|
if ( ci->deferred ) {
|
|
CG_DrawPic( x, y, w, h, cgs.media.deferShader );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawFlagModel
|
|
|
|
Used for both the status bar and the scoreboard
|
|
================
|
|
*/
|
|
void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) {
|
|
qhandle_t cm;
|
|
float len;
|
|
vec3_t origin, angles;
|
|
vec3_t mins, maxs;
|
|
qhandle_t handle;
|
|
|
|
if ( !force2D && cg_draw3dIcons.integer ) {
|
|
|
|
VectorClear( angles );
|
|
|
|
cm = cgs.media.redFlagModel;
|
|
|
|
// offset the origin y and z to center the flag
|
|
trap_R_ModelBounds( cm, mins, maxs );
|
|
|
|
origin[2] = -0.5 * ( mins[2] + maxs[2] );
|
|
origin[1] = 0.5 * ( mins[1] + maxs[1] );
|
|
|
|
// calculate distance so the flag nearly fills the box
|
|
// assume heads are taller than wide
|
|
len = 0.5 * ( maxs[2] - mins[2] );
|
|
origin[0] = len / 0.268; // len / tan( fov/2 )
|
|
|
|
angles[YAW] = 60 * sin( cg.time / 2000.0 );;
|
|
|
|
if( team == TEAM_RED ) {
|
|
handle = cgs.media.redFlagModel;
|
|
} else if( team == TEAM_BLUE ) {
|
|
handle = cgs.media.blueFlagModel;
|
|
} else if( team == TEAM_FREE ) {
|
|
handle = cgs.media.neutralFlagModel;
|
|
} else {
|
|
return;
|
|
}
|
|
CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles );
|
|
} else if ( cg_drawIcons.integer ) {
|
|
gitem_t *item;
|
|
|
|
if( team == TEAM_RED ) {
|
|
item = BG_FindItemForPowerup( PW_REDFLAG );
|
|
} else if( team == TEAM_BLUE ) {
|
|
item = BG_FindItemForPowerup( PW_BLUEFLAG );
|
|
} else if( team == TEAM_FREE ) {
|
|
item = BG_FindItemForPowerup( PW_NEUTRALFLAG );
|
|
} else {
|
|
return;
|
|
}
|
|
if (item) {
|
|
CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawStatusBarHead
|
|
|
|
================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
|
|
static void CG_DrawStatusBarHead( float x ) {
|
|
vec3_t angles;
|
|
float size, stretch;
|
|
float frac;
|
|
|
|
VectorClear( angles );
|
|
|
|
if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) {
|
|
frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME;
|
|
size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 );
|
|
|
|
stretch = size - ICON_SIZE * 1.25;
|
|
// kick in the direction of damage
|
|
x -= stretch * 0.5 + cg.damageX * stretch * 0.5;
|
|
|
|
cg.headStartYaw = 180 + cg.damageX * 45;
|
|
|
|
cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI );
|
|
cg.headEndPitch = 5 * cos( crandom()*M_PI );
|
|
|
|
cg.headStartTime = cg.time;
|
|
cg.headEndTime = cg.time + 100 + random() * 2000;
|
|
} else {
|
|
if ( cg.time >= cg.headEndTime ) {
|
|
// select a new head angle
|
|
cg.headStartYaw = cg.headEndYaw;
|
|
cg.headStartPitch = cg.headEndPitch;
|
|
cg.headStartTime = cg.headEndTime;
|
|
cg.headEndTime = cg.time + 100 + random() * 2000;
|
|
|
|
cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI );
|
|
cg.headEndPitch = 5 * cos( crandom()*M_PI );
|
|
}
|
|
|
|
size = ICON_SIZE * 1.25;
|
|
}
|
|
|
|
// if the server was frozen for a while we may have a bad head start time
|
|
if ( cg.headStartTime > cg.time ) {
|
|
cg.headStartTime = cg.time;
|
|
}
|
|
|
|
frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime );
|
|
frac = frac * frac * ( 3 - 2 * frac );
|
|
angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac;
|
|
angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac;
|
|
|
|
CG_DrawHead( x, 480 - size, size, size,
|
|
cg.snap->ps.clientNum, angles );
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
================
|
|
CG_DrawStatusBarFlag
|
|
|
|
================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawStatusBarFlag( float x, int team ) {
|
|
CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse );
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
================
|
|
CG_DrawTeamBackground
|
|
|
|
================
|
|
*/
|
|
void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team )
|
|
{
|
|
vec4_t hcolor;
|
|
|
|
hcolor[3] = alpha;
|
|
if ( team == TEAM_RED ) {
|
|
hcolor[0] = 1;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0;
|
|
} else if ( team == TEAM_BLUE ) {
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 1;
|
|
} else {
|
|
return;
|
|
}
|
|
trap_R_SetColor( hcolor );
|
|
CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar );
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawStatusBar
|
|
|
|
================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawStatusBar( void ) {
|
|
int color;
|
|
centity_t *cent;
|
|
playerState_t *ps;
|
|
int value;
|
|
vec4_t hcolor;
|
|
vec3_t angles;
|
|
vec3_t origin;
|
|
|
|
static float colors[4][4] = {
|
|
// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} };
|
|
{ 1.0f, 0.69f, 0.0f, 1.0f }, // normal
|
|
{ 1.0f, 0.2f, 0.2f, 1.0f }, // low health
|
|
{ 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing
|
|
{ 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100
|
|
|
|
if ( cg_drawStatus.integer == 0 ) {
|
|
return;
|
|
}
|
|
|
|
// draw the team background
|
|
CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] );
|
|
|
|
cent = &cg_entities[cg.snap->ps.clientNum];
|
|
ps = &cg.snap->ps;
|
|
|
|
VectorClear( angles );
|
|
|
|
// draw any 3D icons first, so the changes back to 2D are minimized
|
|
if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) {
|
|
origin[0] = 70;
|
|
origin[1] = 0;
|
|
origin[2] = 0;
|
|
angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 );
|
|
CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE,
|
|
cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles );
|
|
}
|
|
|
|
CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE );
|
|
|
|
if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) {
|
|
CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED );
|
|
} else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) {
|
|
CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE );
|
|
} else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) {
|
|
CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE );
|
|
}
|
|
|
|
if ( ps->stats[ STAT_ARMOR ] ) {
|
|
origin[0] = 90;
|
|
origin[1] = 0;
|
|
origin[2] = -10;
|
|
angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0;
|
|
CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE,
|
|
cgs.media.armorModel, 0, origin, angles );
|
|
}
|
|
//
|
|
// ammo
|
|
//
|
|
if ( cent->currentState.weapon ) {
|
|
value = ps->ammo[cent->currentState.weapon];
|
|
if ( value > -1 ) {
|
|
if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING
|
|
&& cg.predictedPlayerState.weaponTime > 100 ) {
|
|
// draw as dark grey when reloading
|
|
color = 2; // dark grey
|
|
} else {
|
|
if ( value >= 0 ) {
|
|
color = 0; // green
|
|
} else {
|
|
color = 1; // red
|
|
}
|
|
}
|
|
trap_R_SetColor( colors[color] );
|
|
|
|
CG_DrawField (0, 432, 3, value);
|
|
trap_R_SetColor( NULL );
|
|
|
|
// if we didn't draw a 3D icon, draw a 2D icon for ammo
|
|
if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) {
|
|
qhandle_t icon;
|
|
|
|
icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon;
|
|
if ( icon ) {
|
|
CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// health
|
|
//
|
|
value = ps->stats[STAT_HEALTH];
|
|
if ( value > 100 ) {
|
|
trap_R_SetColor( colors[3] ); // white
|
|
} else if (value > 25) {
|
|
trap_R_SetColor( colors[0] ); // green
|
|
} else if (value > 0) {
|
|
color = (cg.time >> 8) & 1; // flash
|
|
trap_R_SetColor( colors[color] );
|
|
} else {
|
|
trap_R_SetColor( colors[1] ); // red
|
|
}
|
|
|
|
// stretch the health up when taking damage
|
|
CG_DrawField ( 185, 432, 3, value);
|
|
CG_ColorForHealth( hcolor );
|
|
trap_R_SetColor( hcolor );
|
|
|
|
|
|
//
|
|
// armor
|
|
//
|
|
value = ps->stats[STAT_ARMOR];
|
|
if (value > 0 ) {
|
|
trap_R_SetColor( colors[0] );
|
|
CG_DrawField (370, 432, 3, value);
|
|
trap_R_SetColor( NULL );
|
|
// if we didn't draw a 3D icon, draw a 2D icon for armor
|
|
if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) {
|
|
CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon );
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===========================================================================================
|
|
|
|
UPPER RIGHT CORNER
|
|
|
|
===========================================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
CG_DrawAttacker
|
|
|
|
================
|
|
*/
|
|
static float CG_DrawAttacker( float y ) {
|
|
int t;
|
|
float size;
|
|
vec3_t angles;
|
|
const char *info;
|
|
const char *name;
|
|
int clientNum;
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) {
|
|
return y;
|
|
}
|
|
|
|
if ( !cg.attackerTime ) {
|
|
return y;
|
|
}
|
|
|
|
clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER];
|
|
if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) {
|
|
return y;
|
|
}
|
|
|
|
t = cg.time - cg.attackerTime;
|
|
if ( t > ATTACKER_HEAD_TIME ) {
|
|
cg.attackerTime = 0;
|
|
return y;
|
|
}
|
|
|
|
size = ICON_SIZE * 1.25;
|
|
|
|
angles[PITCH] = 0;
|
|
angles[YAW] = 180;
|
|
angles[ROLL] = 0;
|
|
CG_DrawHead( 640 - size, y, size, size, clientNum, angles );
|
|
|
|
info = CG_ConfigString( CS_PLAYERS + clientNum );
|
|
name = Info_ValueForKey( info, "n" );
|
|
y += size;
|
|
CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 );
|
|
|
|
return y + BIGCHAR_HEIGHT + 2;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawSnapshot
|
|
==================
|
|
*/
|
|
static float CG_DrawSnapshot( float y ) {
|
|
char *s;
|
|
int w;
|
|
|
|
s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime,
|
|
cg.latestSnapshotNum, cgs.serverCommandSequence );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w, y + 2, s, 1.0F);
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawFPS
|
|
==================
|
|
*/
|
|
#define FPS_FRAMES 4
|
|
static float CG_DrawFPS( float y ) {
|
|
char *s;
|
|
int w;
|
|
static int previousTimes[FPS_FRAMES];
|
|
static int index;
|
|
int i, total;
|
|
int fps;
|
|
static int previous;
|
|
int t, frameTime;
|
|
|
|
// don't use serverTime, because that will be drifting to
|
|
// correct for internet lag changes, timescales, timedemos, etc
|
|
t = trap_Milliseconds();
|
|
frameTime = t - previous;
|
|
previous = t;
|
|
|
|
previousTimes[index % FPS_FRAMES] = frameTime;
|
|
index++;
|
|
if ( index > FPS_FRAMES ) {
|
|
// average multiple frames together to smooth changes out a bit
|
|
total = 0;
|
|
for ( i = 0 ; i < FPS_FRAMES ; i++ ) {
|
|
total += previousTimes[i];
|
|
}
|
|
if ( !total ) {
|
|
total = 1;
|
|
}
|
|
fps = 1000 * FPS_FRAMES / total;
|
|
|
|
s = va( "%ifps", fps );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w, y + 2, s, 1.0F);
|
|
}
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTimer
|
|
=================
|
|
*/
|
|
static float CG_DrawTimer( float y ) {
|
|
char *s;
|
|
int w;
|
|
int mins, seconds, tens;
|
|
int msec;
|
|
|
|
msec = cg.time - cgs.levelStartTime;
|
|
|
|
seconds = msec / 1000;
|
|
mins = seconds / 60;
|
|
seconds -= mins * 60;
|
|
tens = seconds / 10;
|
|
seconds -= tens * 10;
|
|
|
|
s = va( "%i:%i%i", mins, tens, seconds );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w, y + 2, s, 1.0F);
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTeamOverlay
|
|
=================
|
|
*/
|
|
|
|
static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) {
|
|
int x, w, h, xx;
|
|
int i, j, len;
|
|
const char *p;
|
|
vec4_t hcolor;
|
|
int pwidth, lwidth;
|
|
int plyrs;
|
|
char st[16];
|
|
clientInfo_t *ci;
|
|
gitem_t *item;
|
|
int ret_y, count;
|
|
|
|
if ( !cg_drawTeamOverlay.integer ) {
|
|
return y;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) {
|
|
return y; // Not on any team
|
|
}
|
|
|
|
plyrs = 0;
|
|
|
|
// max player name width
|
|
pwidth = 0;
|
|
count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers;
|
|
for (i = 0; i < count; i++) {
|
|
ci = cgs.clientinfo + sortedTeamPlayers[i];
|
|
if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) {
|
|
plyrs++;
|
|
len = CG_DrawStrlen(ci->name);
|
|
if (len > pwidth)
|
|
pwidth = len;
|
|
}
|
|
}
|
|
|
|
if (!plyrs)
|
|
return y;
|
|
|
|
if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH)
|
|
pwidth = TEAM_OVERLAY_MAXNAME_WIDTH;
|
|
|
|
// max location name width
|
|
lwidth = 0;
|
|
for (i = 1; i < MAX_LOCATIONS; i++) {
|
|
p = CG_ConfigString(CS_LOCATIONS + i);
|
|
if (p && *p) {
|
|
len = CG_DrawStrlen(p);
|
|
if (len > lwidth)
|
|
lwidth = len;
|
|
}
|
|
}
|
|
|
|
if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH)
|
|
lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH;
|
|
|
|
w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH;
|
|
|
|
if ( right )
|
|
x = 640 - w;
|
|
else
|
|
x = 0;
|
|
|
|
h = plyrs * TINYCHAR_HEIGHT;
|
|
|
|
if ( upper ) {
|
|
ret_y = y + h;
|
|
} else {
|
|
y -= h;
|
|
ret_y = y;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) {
|
|
hcolor[0] = 1.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 0.0f;
|
|
hcolor[3] = 0.33f;
|
|
} else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE )
|
|
hcolor[0] = 0.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 1.0f;
|
|
hcolor[3] = 0.33f;
|
|
}
|
|
trap_R_SetColor( hcolor );
|
|
CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar );
|
|
trap_R_SetColor( NULL );
|
|
|
|
for (i = 0; i < count; i++) {
|
|
ci = cgs.clientinfo + sortedTeamPlayers[i];
|
|
if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) {
|
|
|
|
hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0;
|
|
|
|
xx = x + TINYCHAR_WIDTH;
|
|
|
|
CG_DrawStringExt( xx, y,
|
|
ci->name, hcolor, qfalse, qfalse,
|
|
TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH);
|
|
|
|
if (lwidth) {
|
|
p = CG_ConfigString(CS_LOCATIONS + ci->location);
|
|
if (!p || !*p)
|
|
p = "unknown";
|
|
// len = CG_DrawStrlen(p);
|
|
// if (len > lwidth)
|
|
// len = lwidth;
|
|
|
|
// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth +
|
|
// ((lwidth/2 - len/2) * TINYCHAR_WIDTH);
|
|
xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth;
|
|
CG_DrawStringExt( xx, y,
|
|
p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
TEAM_OVERLAY_MAXLOCATION_WIDTH);
|
|
}
|
|
|
|
CG_GetColorForHealth( ci->health, ci->armor, hcolor );
|
|
|
|
Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor);
|
|
|
|
xx = x + TINYCHAR_WIDTH * 3 +
|
|
TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth;
|
|
|
|
CG_DrawStringExt( xx, y,
|
|
st, hcolor, qfalse, qfalse,
|
|
TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 );
|
|
|
|
// draw weapon icon
|
|
xx += TINYCHAR_WIDTH * 3;
|
|
|
|
if ( cg_weapons[ci->curWeapon].weaponIcon ) {
|
|
CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
cg_weapons[ci->curWeapon].weaponIcon );
|
|
} else {
|
|
CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
cgs.media.deferShader );
|
|
}
|
|
|
|
// Draw powerup icons
|
|
if (right) {
|
|
xx = x;
|
|
} else {
|
|
xx = x + w - TINYCHAR_WIDTH;
|
|
}
|
|
for (j = 0; j <= PW_NUM_POWERUPS; j++) {
|
|
if (ci->powerups & (1 << j)) {
|
|
|
|
item = BG_FindItemForPowerup( j );
|
|
|
|
if (item) {
|
|
CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
trap_R_RegisterShader( item->icon ) );
|
|
if (right) {
|
|
xx -= TINYCHAR_WIDTH;
|
|
} else {
|
|
xx += TINYCHAR_WIDTH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
y += TINYCHAR_HEIGHT;
|
|
}
|
|
}
|
|
|
|
return ret_y;
|
|
//#endif
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawUpperRight
|
|
|
|
=====================
|
|
*/
|
|
static void CG_DrawUpperRight(stereoFrame_t stereoFrame)
|
|
{
|
|
float y;
|
|
|
|
y = 0;
|
|
|
|
if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) {
|
|
y = CG_DrawTeamOverlay( y, qtrue, qtrue );
|
|
}
|
|
if ( cg_drawSnapshot.integer ) {
|
|
y = CG_DrawSnapshot( y );
|
|
}
|
|
if (cg_drawFPS.integer && (stereoFrame == STEREO_CENTER || stereoFrame == STEREO_RIGHT)) {
|
|
y = CG_DrawFPS( y );
|
|
}
|
|
if ( cg_drawTimer.integer ) {
|
|
y = CG_DrawTimer( y );
|
|
}
|
|
if ( cg_drawAttacker.integer ) {
|
|
y = CG_DrawAttacker( y );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===========================================================================================
|
|
|
|
LOWER RIGHT CORNER
|
|
|
|
===========================================================================================
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
CG_DrawScores
|
|
|
|
Draw the small two score display
|
|
=================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static float CG_DrawScores( float y ) {
|
|
const char *s;
|
|
int s1, s2, score;
|
|
int x, w;
|
|
int v;
|
|
vec4_t color;
|
|
float y1;
|
|
gitem_t *item;
|
|
|
|
s1 = cgs.scores1;
|
|
s2 = cgs.scores2;
|
|
|
|
y -= BIGCHAR_HEIGHT + 8;
|
|
|
|
y1 = y;
|
|
|
|
// draw from the right side to left
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
x = 640;
|
|
color[0] = 0.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 1.0f;
|
|
color[3] = 0.33f;
|
|
s = va( "%2i", s2 );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) {
|
|
CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader );
|
|
}
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
|
|
if ( cgs.gametype == GT_CTF ) {
|
|
// Display flag status
|
|
item = BG_FindItemForPowerup( PW_BLUEFLAG );
|
|
|
|
if (item) {
|
|
y1 = y - BIGCHAR_HEIGHT - 8;
|
|
if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) {
|
|
CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] );
|
|
}
|
|
}
|
|
}
|
|
color[0] = 1.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 0.0f;
|
|
color[3] = 0.33f;
|
|
s = va( "%2i", s1 );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) {
|
|
CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader );
|
|
}
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
|
|
if ( cgs.gametype == GT_CTF ) {
|
|
// Display flag status
|
|
item = BG_FindItemForPowerup( PW_REDFLAG );
|
|
|
|
if (item) {
|
|
y1 = y - BIGCHAR_HEIGHT - 8;
|
|
if( cgs.redflag >= 0 && cgs.redflag <= 2 ) {
|
|
CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( cgs.gametype >= GT_CTF ) {
|
|
v = cgs.capturelimit;
|
|
} else {
|
|
v = cgs.fraglimit;
|
|
}
|
|
if ( v ) {
|
|
s = va( "%2i", v );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
}
|
|
|
|
} else {
|
|
qboolean spectator;
|
|
|
|
x = 640;
|
|
score = cg.snap->ps.persistant[PERS_SCORE];
|
|
spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR );
|
|
|
|
// always show your score in the second box if not in first place
|
|
if ( s1 != score ) {
|
|
s2 = score;
|
|
}
|
|
if ( s2 != SCORE_NOT_PRESENT ) {
|
|
s = va( "%2i", s2 );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
if ( !spectator && score == s2 && score != s1 ) {
|
|
color[0] = 1.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 0.0f;
|
|
color[3] = 0.33f;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader );
|
|
} else {
|
|
color[0] = 0.5f;
|
|
color[1] = 0.5f;
|
|
color[2] = 0.5f;
|
|
color[3] = 0.33f;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
}
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
}
|
|
|
|
// first place
|
|
if ( s1 != SCORE_NOT_PRESENT ) {
|
|
s = va( "%2i", s1 );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
if ( !spectator && score == s1 ) {
|
|
color[0] = 0.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 1.0f;
|
|
color[3] = 0.33f;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader );
|
|
} else {
|
|
color[0] = 0.5f;
|
|
color[1] = 0.5f;
|
|
color[2] = 0.5f;
|
|
color[3] = 0.33f;
|
|
CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color );
|
|
}
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
}
|
|
|
|
if ( cgs.fraglimit ) {
|
|
s = va( "%2i", cgs.fraglimit );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
|
|
x -= w;
|
|
CG_DrawBigString( x + 4, y, s, 1.0F);
|
|
}
|
|
|
|
}
|
|
|
|
return y1 - 8;
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
================
|
|
CG_DrawPowerups
|
|
================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static float CG_DrawPowerups( float y ) {
|
|
int sorted[MAX_POWERUPS];
|
|
int sortedTime[MAX_POWERUPS];
|
|
int i, j, k;
|
|
int active;
|
|
playerState_t *ps;
|
|
int t;
|
|
gitem_t *item;
|
|
int x;
|
|
int color;
|
|
float size;
|
|
float f;
|
|
static float colors[2][4] = {
|
|
{ 0.2f, 1.0f, 0.2f, 1.0f } ,
|
|
{ 1.0f, 0.2f, 0.2f, 1.0f }
|
|
};
|
|
|
|
ps = &cg.snap->ps;
|
|
|
|
if ( ps->stats[STAT_HEALTH] <= 0 ) {
|
|
return y;
|
|
}
|
|
|
|
// sort the list by time remaining
|
|
active = 0;
|
|
for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
|
|
if ( !ps->powerups[ i ] ) {
|
|
continue;
|
|
}
|
|
t = ps->powerups[ i ] - cg.time;
|
|
// ZOID--don't draw if the power up has unlimited time (999 seconds)
|
|
// This is true of the CTF flags
|
|
if ( t < 0 || t > 999000) {
|
|
continue;
|
|
}
|
|
|
|
// insert into the list
|
|
for ( j = 0 ; j < active ; j++ ) {
|
|
if ( sortedTime[j] >= t ) {
|
|
for ( k = active - 1 ; k >= j ; k-- ) {
|
|
sorted[k+1] = sorted[k];
|
|
sortedTime[k+1] = sortedTime[k];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
sorted[j] = i;
|
|
sortedTime[j] = t;
|
|
active++;
|
|
}
|
|
|
|
// draw the icons and timers
|
|
x = 640 - ICON_SIZE - CHAR_WIDTH * 2;
|
|
for ( i = 0 ; i < active ; i++ ) {
|
|
item = BG_FindItemForPowerup( sorted[i] );
|
|
|
|
if (item) {
|
|
|
|
color = 1;
|
|
|
|
y -= ICON_SIZE;
|
|
|
|
trap_R_SetColor( colors[color] );
|
|
CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 );
|
|
|
|
t = ps->powerups[ sorted[i] ];
|
|
if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) {
|
|
trap_R_SetColor( NULL );
|
|
} else {
|
|
vec4_t modulate;
|
|
|
|
f = (float)( t - cg.time ) / POWERUP_BLINK_TIME;
|
|
f -= (int)f;
|
|
modulate[0] = modulate[1] = modulate[2] = modulate[3] = f;
|
|
trap_R_SetColor( modulate );
|
|
}
|
|
|
|
if ( cg.powerupActive == sorted[i] &&
|
|
cg.time - cg.powerupTime < PULSE_TIME ) {
|
|
f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME );
|
|
size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f );
|
|
} else {
|
|
size = ICON_SIZE;
|
|
}
|
|
|
|
CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2,
|
|
size, size, trap_R_RegisterShader( item->icon ) );
|
|
}
|
|
}
|
|
trap_R_SetColor( NULL );
|
|
|
|
return y;
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawLowerRight
|
|
|
|
=====================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawLowerRight( void ) {
|
|
float y;
|
|
|
|
y = 480 - ICON_SIZE;
|
|
|
|
if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) {
|
|
y = CG_DrawTeamOverlay( y, qtrue, qfalse );
|
|
}
|
|
|
|
y = CG_DrawScores( y );
|
|
y = CG_DrawPowerups( y );
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
===================
|
|
CG_DrawPickupItem
|
|
===================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static int CG_DrawPickupItem( int y ) {
|
|
int value;
|
|
float *fadeColor;
|
|
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
return y;
|
|
}
|
|
|
|
y -= ICON_SIZE;
|
|
|
|
value = cg.itemPickup;
|
|
if ( value ) {
|
|
fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 );
|
|
if ( fadeColor ) {
|
|
CG_RegisterItemVisuals( value );
|
|
trap_R_SetColor( fadeColor );
|
|
CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon );
|
|
CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] );
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
}
|
|
|
|
return y;
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawLowerLeft
|
|
|
|
=====================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawLowerLeft( void ) {
|
|
float y;
|
|
|
|
y = 480 - ICON_SIZE;
|
|
|
|
if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) {
|
|
y = CG_DrawTeamOverlay( y, qfalse, qfalse );
|
|
}
|
|
|
|
|
|
y = CG_DrawPickupItem( y );
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
|
|
//===========================================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTeamInfo
|
|
=================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawTeamInfo( void ) {
|
|
int h;
|
|
int i;
|
|
vec4_t hcolor;
|
|
int chatHeight;
|
|
|
|
#define CHATLOC_Y 420 // bottom end
|
|
#define CHATLOC_X 0
|
|
|
|
if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT)
|
|
chatHeight = cg_teamChatHeight.integer;
|
|
else
|
|
chatHeight = TEAMCHAT_HEIGHT;
|
|
if (chatHeight <= 0)
|
|
return; // disabled
|
|
|
|
if (cgs.teamLastChatPos != cgs.teamChatPos) {
|
|
if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) {
|
|
cgs.teamLastChatPos++;
|
|
}
|
|
|
|
h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT;
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) {
|
|
hcolor[0] = 1.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 0.0f;
|
|
hcolor[3] = 0.33f;
|
|
} else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) {
|
|
hcolor[0] = 0.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 1.0f;
|
|
hcolor[3] = 0.33f;
|
|
} else {
|
|
hcolor[0] = 0.0f;
|
|
hcolor[1] = 1.0f;
|
|
hcolor[2] = 0.0f;
|
|
hcolor[3] = 0.33f;
|
|
}
|
|
|
|
trap_R_SetColor( hcolor );
|
|
CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar );
|
|
trap_R_SetColor( NULL );
|
|
|
|
hcolor[0] = hcolor[1] = hcolor[2] = 1.0f;
|
|
hcolor[3] = 1.0f;
|
|
|
|
for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) {
|
|
CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH,
|
|
CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT,
|
|
cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse,
|
|
TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 );
|
|
}
|
|
}
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
/*
|
|
===================
|
|
CG_DrawHoldableItem
|
|
===================
|
|
*/
|
|
#ifndef MISSIONPACK
|
|
static void CG_DrawHoldableItem( void ) {
|
|
int value;
|
|
|
|
value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM];
|
|
if ( value ) {
|
|
CG_RegisterItemVisuals( value );
|
|
CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon );
|
|
}
|
|
|
|
}
|
|
#endif // MISSIONPACK
|
|
|
|
#ifdef MISSIONPACK
|
|
/*
|
|
===================
|
|
CG_DrawPersistantPowerup
|
|
===================
|
|
*/
|
|
#if 0 // sos001208 - DEAD
|
|
static void CG_DrawPersistantPowerup( void ) {
|
|
int value;
|
|
|
|
value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP];
|
|
if ( value ) {
|
|
CG_RegisterItemVisuals( value );
|
|
CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon );
|
|
}
|
|
}
|
|
#endif
|
|
#endif // MISSIONPACK
|
|
|
|
|
|
/*
|
|
===================
|
|
CG_DrawReward
|
|
===================
|
|
*/
|
|
static void CG_DrawReward( void ) {
|
|
float *color;
|
|
int i, count;
|
|
float x, y;
|
|
char buf[32];
|
|
|
|
if ( !cg_drawRewards.integer ) {
|
|
return;
|
|
}
|
|
|
|
color = CG_FadeColor( cg.rewardTime, REWARD_TIME );
|
|
if ( !color ) {
|
|
if (cg.rewardStack > 0) {
|
|
for(i = 0; i < cg.rewardStack; i++) {
|
|
cg.rewardSound[i] = cg.rewardSound[i+1];
|
|
cg.rewardShader[i] = cg.rewardShader[i+1];
|
|
cg.rewardCount[i] = cg.rewardCount[i+1];
|
|
}
|
|
cg.rewardTime = cg.time;
|
|
cg.rewardStack--;
|
|
color = CG_FadeColor( cg.rewardTime, REWARD_TIME );
|
|
trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
/*
|
|
count = cg.rewardCount[0]/10; // number of big rewards to draw
|
|
|
|
if (count) {
|
|
y = 4;
|
|
x = 320 - count * ICON_SIZE;
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] );
|
|
x += (ICON_SIZE*2);
|
|
}
|
|
}
|
|
|
|
count = cg.rewardCount[0] - count*10; // number of small rewards to draw
|
|
*/
|
|
|
|
if ( cg.rewardCount[0] >= 10 ) {
|
|
y = 56;
|
|
x = 320 - ICON_SIZE/2;
|
|
CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] );
|
|
Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]);
|
|
x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2;
|
|
CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue,
|
|
SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 );
|
|
}
|
|
else {
|
|
|
|
count = cg.rewardCount[0];
|
|
|
|
y = 56;
|
|
x = 320 - count * ICON_SIZE/2;
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] );
|
|
x += ICON_SIZE;
|
|
}
|
|
}
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
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
|
|
|
|
Should we draw something differnet for long lag vs no packets?
|
|
==============
|
|
*/
|
|
static void CG_DrawDisconnect( void ) {
|
|
float x, y;
|
|
int cmdNum;
|
|
usercmd_t cmd;
|
|
const char *s;
|
|
int w;
|
|
|
|
// draw the phone jack if we are completely past our buffers
|
|
cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
|
|
trap_GetUserCmd( cmdNum, &cmd );
|
|
if ( cmd.serverTime <= cg.snap->ps.commandTime
|
|
|| cmd.serverTime > cg.time ) { // special check for map_restart
|
|
return;
|
|
}
|
|
|
|
// also add text in center of screen
|
|
s = "Connection Interrupted";
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString( 320 - w/2, 100, s, 1.0F);
|
|
|
|
// blink the icon
|
|
if ( ( cg.time >> 9 ) & 1 ) {
|
|
return;
|
|
}
|
|
|
|
x = 640 - 48;
|
|
y = 480 - 48;
|
|
|
|
CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) );
|
|
}
|
|
|
|
|
|
#define MAX_LAGOMETER_PING 900
|
|
#define MAX_LAGOMETER_RANGE 300
|
|
|
|
/*
|
|
==============
|
|
CG_DrawLagometer
|
|
==============
|
|
*/
|
|
static void CG_DrawLagometer( void ) {
|
|
int a, x, y, i;
|
|
float v;
|
|
float ax, ay, aw, ah, mid, range;
|
|
int color;
|
|
float vscale;
|
|
|
|
if ( !cg_lagometer.integer || cgs.localServer ) {
|
|
CG_DrawDisconnect();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// draw the graph
|
|
//
|
|
#ifdef MISSIONPACK
|
|
x = 640 - 48;
|
|
y = 480 - 144;
|
|
#else
|
|
x = 640 - 48;
|
|
y = 480 - 48;
|
|
#endif
|
|
|
|
trap_R_SetColor( NULL );
|
|
CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader );
|
|
|
|
ax = x;
|
|
ay = y;
|
|
aw = 48;
|
|
ah = 48;
|
|
CG_AdjustFrom640( &ax, &ay, &aw, &ah );
|
|
|
|
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;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
|
|
}
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
} else if ( v < 0 ) {
|
|
if ( color != 2 ) {
|
|
color = 2;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] );
|
|
}
|
|
v = -v;
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
// 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
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
|
|
}
|
|
} else {
|
|
if ( color != 3 ) {
|
|
color = 3;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] );
|
|
}
|
|
}
|
|
v = v * vscale;
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
} else if ( v < 0 ) {
|
|
if ( color != 4 ) {
|
|
color = 4; // RED for dropped snapshots
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
|
|
if ( cg_nopredict.integer || cg_synchronousClients.integer ) {
|
|
CG_DrawBigString( ax, ay, "snc", 1.0 );
|
|
}
|
|
|
|
CG_DrawDisconnect();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
CENTER PRINTING
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==============
|
|
CG_CenterPrint
|
|
|
|
Called for important messages that should stay in the center of the screen
|
|
for a few moments
|
|
==============
|
|
*/
|
|
void CG_CenterPrint( const char *str, int y, int charWidth ) {
|
|
char *s;
|
|
|
|
Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) );
|
|
|
|
cg.centerPrintTime = cg.time;
|
|
cg.centerPrintY = y;
|
|
cg.centerPrintCharWidth = charWidth;
|
|
|
|
// count the number of lines for centering
|
|
cg.centerPrintLines = 1;
|
|
s = cg.centerPrint;
|
|
while( *s ) {
|
|
if (*s == '\n')
|
|
cg.centerPrintLines++;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
CG_DrawCenterString
|
|
===================
|
|
*/
|
|
static void CG_DrawCenterString( void ) {
|
|
char *start;
|
|
int l;
|
|
int x, y, w;
|
|
#ifdef MISSIONPACK
|
|
int h;
|
|
#endif
|
|
float *color;
|
|
|
|
if ( !cg.centerPrintTime ) {
|
|
return;
|
|
}
|
|
|
|
color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
|
|
if ( !color ) {
|
|
return;
|
|
}
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
start = cg.centerPrint;
|
|
|
|
y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;
|
|
|
|
while ( 1 ) {
|
|
char linebuffer[1024];
|
|
|
|
for ( l = 0; l < 50; l++ ) {
|
|
if ( !start[l] || start[l] == '\n' ) {
|
|
break;
|
|
}
|
|
linebuffer[l] = start[l];
|
|
}
|
|
linebuffer[l] = 0;
|
|
|
|
#ifdef MISSIONPACK
|
|
w = CG_Text_Width(linebuffer, 0.5, 0);
|
|
h = CG_Text_Height(linebuffer, 0.5, 0);
|
|
x = (SCREEN_WIDTH - w) / 2;
|
|
CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
|
|
y += h + 6;
|
|
#else
|
|
w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer );
|
|
|
|
x = ( SCREEN_WIDTH - w ) / 2;
|
|
|
|
CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue,
|
|
cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 );
|
|
|
|
y += cg.centerPrintCharWidth * 1.5;
|
|
#endif
|
|
while ( *start && ( *start != '\n' ) ) {
|
|
start++;
|
|
}
|
|
if ( !*start ) {
|
|
break;
|
|
}
|
|
start++;
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================================================================================
|
|
|
|
CROSSHAIR
|
|
|
|
================================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawCrosshair
|
|
=================
|
|
*/
|
|
static void CG_DrawCrosshair(void)
|
|
{
|
|
float w, h;
|
|
qhandle_t hShader;
|
|
float f;
|
|
float x, y;
|
|
int ca;
|
|
|
|
if ( !cg_drawCrosshair.integer ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.renderingThirdPerson ) {
|
|
return;
|
|
}
|
|
|
|
// set color based on health
|
|
if ( cg_crosshairHealth.integer ) {
|
|
vec4_t hcolor;
|
|
|
|
CG_ColorForHealth( hcolor );
|
|
trap_R_SetColor( hcolor );
|
|
} else {
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
w = h = cg_crosshairSize.value;
|
|
|
|
// pulse the size of the crosshair when picking up items
|
|
f = cg.time - cg.itemPickupBlendTime;
|
|
if ( f > 0 && f < ITEM_BLOB_TIME ) {
|
|
f /= ITEM_BLOB_TIME;
|
|
w *= ( 1 + f );
|
|
h *= ( 1 + f );
|
|
}
|
|
|
|
x = cg_crosshairX.integer;
|
|
y = cg_crosshairY.integer;
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
|
|
ca = cg_drawCrosshair.integer;
|
|
if (ca < 0) {
|
|
ca = 0;
|
|
}
|
|
hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ];
|
|
|
|
trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w),
|
|
y + cg.refdef.y + 0.5 * (cg.refdef.height - h),
|
|
w, h, 0, 0, 1, 1, hShader );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawCrosshair3D
|
|
=================
|
|
*/
|
|
static void CG_DrawCrosshair3D(void)
|
|
{
|
|
float w;
|
|
qhandle_t hShader;
|
|
float f;
|
|
int ca;
|
|
|
|
trace_t trace;
|
|
vec3_t endpos;
|
|
float stereoSep, zProj, maxdist, xmax;
|
|
char rendererinfos[128];
|
|
refEntity_t ent;
|
|
|
|
if ( !cg_drawCrosshair.integer ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.renderingThirdPerson ) {
|
|
return;
|
|
}
|
|
|
|
w = cg_crosshairSize.value;
|
|
|
|
// pulse the size of the crosshair when picking up items
|
|
f = cg.time - cg.itemPickupBlendTime;
|
|
if ( f > 0 && f < ITEM_BLOB_TIME ) {
|
|
f /= ITEM_BLOB_TIME;
|
|
w *= ( 1 + f );
|
|
}
|
|
|
|
ca = cg_drawCrosshair.integer;
|
|
if (ca < 0) {
|
|
ca = 0;
|
|
}
|
|
hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ];
|
|
|
|
// Use a different method rendering the crosshair so players don't see two of them when
|
|
// focusing their eyes at distant objects with high stereo separation
|
|
// We are going to trace to the next shootable object and place the crosshair in front of it.
|
|
|
|
// first get all the important renderer information
|
|
trap_Cvar_VariableStringBuffer("r_zProj", rendererinfos, sizeof(rendererinfos));
|
|
zProj = atof(rendererinfos);
|
|
trap_Cvar_VariableStringBuffer("r_stereoSeparation", rendererinfos, sizeof(rendererinfos));
|
|
stereoSep = zProj / atof(rendererinfos);
|
|
|
|
xmax = zProj * tan(cg.refdef.fov_x * M_PI / 360.0f);
|
|
|
|
// let the trace run through until a change in stereo separation of the crosshair becomes less than one pixel.
|
|
maxdist = cgs.glconfig.vidWidth * stereoSep * zProj / (2 * xmax);
|
|
VectorMA(cg.refdef.vieworg, maxdist, cg.refdef.viewaxis[0], endpos);
|
|
CG_Trace(&trace, cg.refdef.vieworg, NULL, NULL, endpos, 0, MASK_SHOT);
|
|
|
|
memset(&ent, 0, sizeof(ent));
|
|
ent.reType = RT_SPRITE;
|
|
ent.renderfx = RF_DEPTHHACK | RF_CROSSHAIR;
|
|
|
|
VectorCopy(trace.endpos, ent.origin);
|
|
|
|
// scale the crosshair so it appears the same size for all distances
|
|
ent.radius = w / 640 * xmax * trace.fraction * maxdist / zProj;
|
|
ent.customShader = hShader;
|
|
|
|
trap_R_AddRefEntityToScene(&ent);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_ScanForCrosshairEntity
|
|
=================
|
|
*/
|
|
static void CG_ScanForCrosshairEntity( void ) {
|
|
trace_t trace;
|
|
vec3_t start, end;
|
|
int content;
|
|
|
|
VectorCopy( cg.refdef.vieworg, start );
|
|
VectorMA( start, 131072, cg.refdef.viewaxis[0], end );
|
|
|
|
CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
|
|
cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY );
|
|
if ( trace.entityNum >= MAX_CLIENTS ) {
|
|
return;
|
|
}
|
|
|
|
// if the player is in fog, don't show it
|
|
content = CG_PointContents( trace.endpos, 0 );
|
|
if ( content & CONTENTS_FOG ) {
|
|
return;
|
|
}
|
|
|
|
// if the player is invisible, don't show it
|
|
if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) {
|
|
return;
|
|
}
|
|
|
|
// update the fade timer
|
|
cg.crosshairClientNum = trace.entityNum;
|
|
cg.crosshairClientTime = cg.time;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawCrosshairNames
|
|
=====================
|
|
*/
|
|
static void CG_DrawCrosshairNames( void ) {
|
|
float *color;
|
|
char *name;
|
|
float w;
|
|
|
|
if ( !cg_drawCrosshair.integer ) {
|
|
return;
|
|
}
|
|
if ( !cg_drawCrosshairNames.integer ) {
|
|
return;
|
|
}
|
|
if ( cg.renderingThirdPerson ) {
|
|
return;
|
|
}
|
|
|
|
// scan the known entities to see if the crosshair is sighted on one
|
|
CG_ScanForCrosshairEntity();
|
|
|
|
// draw the name of the player being looked at
|
|
color = CG_FadeColor( cg.crosshairClientTime, 1000 );
|
|
if ( !color ) {
|
|
trap_R_SetColor( NULL );
|
|
return;
|
|
}
|
|
|
|
name = cgs.clientinfo[ cg.crosshairClientNum ].name;
|
|
#ifdef MISSIONPACK
|
|
color[3] *= 0.5f;
|
|
w = CG_Text_Width(name, 0.3f, 0);
|
|
CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED);
|
|
#else
|
|
w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f );
|
|
#endif
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_DrawSpectator
|
|
=================
|
|
*/
|
|
static void CG_DrawSpectator(void) {
|
|
CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F);
|
|
if ( cgs.gametype == GT_TOURNAMENT ) {
|
|
CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F);
|
|
}
|
|
else if ( cgs.gametype >= GT_TEAM ) {
|
|
CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawVote
|
|
=================
|
|
*/
|
|
static void CG_DrawVote(void) {
|
|
char *s;
|
|
int sec;
|
|
|
|
if ( !cgs.voteTime ) {
|
|
return;
|
|
}
|
|
|
|
// play a talk beep whenever it is modified
|
|
if ( cgs.voteModified ) {
|
|
cgs.voteModified = qfalse;
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
}
|
|
|
|
sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000;
|
|
if ( sec < 0 ) {
|
|
sec = 0;
|
|
}
|
|
#ifdef MISSIONPACK
|
|
s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo);
|
|
CG_DrawSmallString( 0, 58, s, 1.0F );
|
|
s = "or press ESC then click Vote";
|
|
CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F );
|
|
#else
|
|
s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo );
|
|
CG_DrawSmallString( 0, 58, s, 1.0F );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTeamVote
|
|
=================
|
|
*/
|
|
static void CG_DrawTeamVote(void) {
|
|
char *s;
|
|
int sec, cs_offset;
|
|
|
|
if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED )
|
|
cs_offset = 0;
|
|
else if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE )
|
|
cs_offset = 1;
|
|
else
|
|
return;
|
|
|
|
if ( !cgs.teamVoteTime[cs_offset] ) {
|
|
return;
|
|
}
|
|
|
|
// play a talk beep whenever it is modified
|
|
if ( cgs.teamVoteModified[cs_offset] ) {
|
|
cgs.teamVoteModified[cs_offset] = qfalse;
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
}
|
|
|
|
sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000;
|
|
if ( sec < 0 ) {
|
|
sec = 0;
|
|
}
|
|
s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset],
|
|
cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] );
|
|
CG_DrawSmallString( 0, 90, s, 1.0F );
|
|
}
|
|
|
|
|
|
static qboolean CG_DrawScoreboard( void ) {
|
|
#ifdef MISSIONPACK
|
|
static qboolean firstTime = qtrue;
|
|
|
|
if (menuScoreboard) {
|
|
menuScoreboard->window.flags &= ~WINDOW_FORCED;
|
|
}
|
|
if (cg_paused.integer) {
|
|
cg.deferredPlayerLoading = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
|
|
// should never happen in Team Arena
|
|
if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
|
|
cg.deferredPlayerLoading = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
|
|
// don't draw scoreboard during death while warmup up
|
|
if ( cg.warmup && !cg.showScores ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
|
|
} else {
|
|
if ( !CG_FadeColor( cg.scoreFadeTime, FADE_TIME ) ) {
|
|
// next time scoreboard comes up, don't print killer
|
|
cg.deferredPlayerLoading = 0;
|
|
cg.killerName[0] = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if (menuScoreboard == NULL) {
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
menuScoreboard = Menus_FindByName("teamscore_menu");
|
|
} else {
|
|
menuScoreboard = Menus_FindByName("score_menu");
|
|
}
|
|
}
|
|
|
|
if (menuScoreboard) {
|
|
if (firstTime) {
|
|
CG_SetScoreSelection(menuScoreboard);
|
|
firstTime = qfalse;
|
|
}
|
|
Menu_Paint(menuScoreboard, qtrue);
|
|
}
|
|
|
|
// load any models that have been deferred
|
|
if ( ++cg.deferredPlayerLoading > 10 ) {
|
|
CG_LoadDeferredPlayers();
|
|
}
|
|
|
|
return qtrue;
|
|
#else
|
|
return CG_DrawOldScoreboard();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawIntermission
|
|
=================
|
|
*/
|
|
static void CG_DrawIntermission( void ) {
|
|
// int key;
|
|
#ifdef MISSIONPACK
|
|
//if (cg_singlePlayer.integer) {
|
|
// CG_DrawCenterString();
|
|
// return;
|
|
//}
|
|
#else
|
|
if ( cgs.gametype == GT_SINGLE_PLAYER ) {
|
|
CG_DrawCenterString();
|
|
return;
|
|
}
|
|
#endif
|
|
cg.scoreFadeTime = cg.time;
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawFollow
|
|
=================
|
|
*/
|
|
static qboolean CG_DrawFollow( void ) {
|
|
float x;
|
|
vec4_t color;
|
|
const char *name;
|
|
|
|
if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) {
|
|
return qfalse;
|
|
}
|
|
color[0] = 1;
|
|
color[1] = 1;
|
|
color[2] = 1;
|
|
color[3] = 1;
|
|
|
|
|
|
CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F );
|
|
|
|
name = cgs.clientinfo[ cg.snap->ps.clientNum ].name;
|
|
|
|
x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) );
|
|
|
|
CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawAmmoWarning
|
|
=================
|
|
*/
|
|
static void CG_DrawAmmoWarning( void ) {
|
|
const char *s;
|
|
int w;
|
|
|
|
if ( cg_drawAmmoWarning.integer == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( !cg.lowAmmoWarning ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.lowAmmoWarning == 2 ) {
|
|
s = "OUT OF AMMO";
|
|
} else {
|
|
s = "LOW AMMO WARNING";
|
|
}
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString(320 - w / 2, 64, s, 1.0F);
|
|
}
|
|
|
|
|
|
#ifdef MISSIONPACK
|
|
/*
|
|
=================
|
|
CG_DrawProxWarning
|
|
=================
|
|
*/
|
|
static void CG_DrawProxWarning( void ) {
|
|
char s [32];
|
|
int w;
|
|
static int proxTime;
|
|
static int proxCounter;
|
|
static int proxTick;
|
|
|
|
if( !(cg.snap->ps.eFlags & EF_TICKING ) ) {
|
|
proxTime = 0;
|
|
return;
|
|
}
|
|
|
|
if (proxTime == 0) {
|
|
proxTime = cg.time + 5000;
|
|
proxCounter = 5;
|
|
proxTick = 0;
|
|
}
|
|
|
|
if (cg.time > proxTime) {
|
|
proxTick = proxCounter--;
|
|
proxTime = cg.time + 1000;
|
|
}
|
|
|
|
if (proxTick != 0) {
|
|
Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick);
|
|
} else {
|
|
Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED");
|
|
}
|
|
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawWarmup
|
|
=================
|
|
*/
|
|
static void CG_DrawWarmup( void ) {
|
|
int w;
|
|
int sec;
|
|
int i;
|
|
#ifdef MISSIONPACK
|
|
float scale;
|
|
#else
|
|
int cw;
|
|
#endif
|
|
clientInfo_t *ci1, *ci2;
|
|
const char *s;
|
|
|
|
sec = cg.warmup;
|
|
if ( !sec ) {
|
|
return;
|
|
}
|
|
|
|
if ( sec < 0 ) {
|
|
s = "Waiting for players";
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString(320 - w / 2, 24, s, 1.0F);
|
|
cg.warmupCount = 0;
|
|
return;
|
|
}
|
|
|
|
if (cgs.gametype == GT_TOURNAMENT) {
|
|
// find the two active players
|
|
ci1 = NULL;
|
|
ci2 = NULL;
|
|
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
|
|
if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) {
|
|
if ( !ci1 ) {
|
|
ci1 = &cgs.clientinfo[i];
|
|
} else {
|
|
ci2 = &cgs.clientinfo[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ci1 && ci2 ) {
|
|
s = va( "%s vs %s", ci1->name, ci2->name );
|
|
#ifdef MISSIONPACK
|
|
w = CG_Text_Width(s, 0.6f, 0);
|
|
CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
|
|
#else
|
|
w = CG_DrawStrlen( s );
|
|
if ( w > 640 / GIANT_WIDTH ) {
|
|
cw = 640 / w;
|
|
} else {
|
|
cw = GIANT_WIDTH;
|
|
}
|
|
CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite,
|
|
qfalse, qtrue, cw, (int)(cw * 1.5f), 0 );
|
|
#endif
|
|
}
|
|
} else {
|
|
if ( cgs.gametype == GT_FFA ) {
|
|
s = "Free For All";
|
|
} else if ( cgs.gametype == GT_TEAM ) {
|
|
s = "Team Deathmatch";
|
|
} else if ( cgs.gametype == GT_CTF ) {
|
|
s = "Capture the Flag";
|
|
#ifdef MISSIONPACK
|
|
} else if ( cgs.gametype == GT_1FCTF ) {
|
|
s = "One Flag CTF";
|
|
} else if ( cgs.gametype == GT_OBELISK ) {
|
|
s = "Overload";
|
|
} else if ( cgs.gametype == GT_HARVESTER ) {
|
|
s = "Harvester";
|
|
#endif
|
|
} else {
|
|
s = "";
|
|
}
|
|
#ifdef MISSIONPACK
|
|
w = CG_Text_Width(s, 0.6f, 0);
|
|
CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
|
|
#else
|
|
w = CG_DrawStrlen( s );
|
|
if ( w > 640 / GIANT_WIDTH ) {
|
|
cw = 640 / w;
|
|
} else {
|
|
cw = GIANT_WIDTH;
|
|
}
|
|
CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite,
|
|
qfalse, qtrue, cw, (int)(cw * 1.1f), 0 );
|
|
#endif
|
|
}
|
|
|
|
sec = ( sec - cg.time ) / 1000;
|
|
if ( sec < 0 ) {
|
|
cg.warmup = 0;
|
|
sec = 0;
|
|
}
|
|
s = va( "Starts in: %i", sec + 1 );
|
|
if ( sec != cg.warmupCount ) {
|
|
cg.warmupCount = sec;
|
|
switch ( sec ) {
|
|
case 0:
|
|
trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 1:
|
|
trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 2:
|
|
trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef MISSIONPACK
|
|
switch ( cg.warmupCount ) {
|
|
case 0:
|
|
scale = 0.54f;
|
|
break;
|
|
case 1:
|
|
scale = 0.51f;
|
|
break;
|
|
case 2:
|
|
scale = 0.48f;
|
|
break;
|
|
default:
|
|
scale = 0.45f;
|
|
break;
|
|
}
|
|
|
|
w = CG_Text_Width(s, scale, 0);
|
|
CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
|
|
#else
|
|
switch ( cg.warmupCount ) {
|
|
case 0:
|
|
cw = 28;
|
|
break;
|
|
case 1:
|
|
cw = 24;
|
|
break;
|
|
case 2:
|
|
cw = 20;
|
|
break;
|
|
default:
|
|
cw = 16;
|
|
break;
|
|
}
|
|
|
|
w = CG_DrawStrlen( s );
|
|
CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite,
|
|
qfalse, qtrue, cw, (int)(cw * 1.5), 0 );
|
|
#endif
|
|
}
|
|
|
|
//==================================================================================
|
|
#ifdef MISSIONPACK
|
|
/*
|
|
=================
|
|
CG_DrawTimedMenus
|
|
=================
|
|
*/
|
|
void CG_DrawTimedMenus( void ) {
|
|
if (cg.voiceTime) {
|
|
int t = cg.time - cg.voiceTime;
|
|
if ( t > 2500 ) {
|
|
Menus_CloseByName("voiceMenu");
|
|
trap_Cvar_Set("cl_conXOffset", "0");
|
|
cg.voiceTime = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
=================
|
|
CG_Draw2D
|
|
=================
|
|
*/
|
|
static void CG_Draw2D(stereoFrame_t stereoFrame)
|
|
{
|
|
#ifdef MISSIONPACK
|
|
if (cgs.orderPending && cg.time > cgs.orderTime) {
|
|
CG_CheckOrderPending();
|
|
}
|
|
#endif
|
|
// if we are taking a levelshot for the menu, don't draw anything
|
|
if ( cg.levelShot ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg_draw2D.integer == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
|
|
CG_DrawIntermission();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if (cg.cameraMode) {
|
|
return;
|
|
}
|
|
*/
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
|
|
CG_DrawSpectator();
|
|
|
|
if(stereoFrame == STEREO_CENTER)
|
|
CG_DrawCrosshair();
|
|
|
|
CG_DrawCrosshairNames();
|
|
} else {
|
|
// don't draw any status if dead or the scoreboard is being explicitly shown
|
|
if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) {
|
|
|
|
#ifdef MISSIONPACK
|
|
if ( cg_drawStatus.integer ) {
|
|
Menu_PaintAll();
|
|
CG_DrawTimedMenus();
|
|
}
|
|
#else
|
|
CG_DrawStatusBar();
|
|
#endif
|
|
|
|
CG_DrawAmmoWarning();
|
|
|
|
#ifdef MISSIONPACK
|
|
CG_DrawProxWarning();
|
|
#endif
|
|
if(stereoFrame == STEREO_CENTER)
|
|
CG_DrawCrosshair();
|
|
CG_DrawCrosshairNames();
|
|
CG_DrawWeaponSelect();
|
|
|
|
#ifndef MISSIONPACK
|
|
CG_DrawHoldableItem();
|
|
#else
|
|
//CG_DrawPersistantPowerup();
|
|
#endif
|
|
CG_DrawReward();
|
|
}
|
|
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
#ifndef MISSIONPACK
|
|
CG_DrawTeamInfo();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
CG_DrawVote();
|
|
CG_DrawTeamVote();
|
|
|
|
CG_DrawLagometer();
|
|
|
|
#ifdef MISSIONPACK
|
|
if (!cg_paused.integer) {
|
|
CG_DrawUpperRight(stereoFrame);
|
|
}
|
|
#else
|
|
CG_DrawUpperRight(stereoFrame);
|
|
#endif
|
|
|
|
#ifndef MISSIONPACK
|
|
CG_DrawLowerRight();
|
|
CG_DrawLowerLeft();
|
|
#endif
|
|
|
|
if ( !CG_DrawFollow() ) {
|
|
CG_DrawWarmup();
|
|
}
|
|
|
|
// don't draw center string if scoreboard is up
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
if ( !cg.scoreBoardShowing) {
|
|
CG_DrawCenterString();
|
|
}
|
|
}
|
|
|
|
|
|
static void CG_DrawTourneyScoreboard( void ) {
|
|
#ifdef MISSIONPACK
|
|
#else
|
|
CG_DrawOldTourneyScoreboard();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawActive
|
|
|
|
Perform all drawing needed to completely fill the screen
|
|
=====================
|
|
*/
|
|
void CG_DrawActive( stereoFrame_t stereoView ) {
|
|
// optionally draw the info screen instead
|
|
if ( !cg.snap ) {
|
|
CG_DrawInformation();
|
|
return;
|
|
}
|
|
|
|
// optionally draw the tournement scoreboard instead
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR &&
|
|
( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) {
|
|
CG_DrawTourneyScoreboard();
|
|
return;
|
|
}
|
|
|
|
// clear around the rendered view if sized down
|
|
CG_TileClear();
|
|
|
|
if(stereoView != STEREO_CENTER)
|
|
CG_DrawCrosshair3D();
|
|
|
|
// draw 3D view
|
|
trap_R_RenderScene( &cg.refdef );
|
|
|
|
// draw status bar and other floating elements
|
|
CG_Draw2D(stereoView);
|
|
}
|
|
|
|
|
|
|