/* =========================================================================== 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 Foobar; 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 | RDF_NOLIGHTSCALE; 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_Draw3DWeaponModel ================ */ void CG_Draw3DWeaponModel(float x, float y, float w, float h, qhandle_t weaponModel, qhandle_t barrelModel, qhandle_t skin, vec3_t origin, vec3_t angles) { refdef_t refdef; refEntity_t ent; vec3_t lightOrigin; 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 = weaponModel; ent.customSkin = skin; ent.renderfx = RF_NOSHADOW; // no stencil shadows refdef.rdflags = RDF_NOWORLDMODEL | RDF_NOLIGHTSCALE; 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); if(barrelModel) { refEntity_t barrel; memset(&barrel, 0, sizeof(barrel)); barrel.hModel = barrelModel; VectorCopy(ent.lightingOrigin, barrel.lightingOrigin); barrel.shadowPlane = ent.shadowPlane; barrel.renderfx = ent.renderfx; CG_PositionRotatedEntityOnTag(&barrel, &ent, weaponModel, "tag_barrel"); AxisCopy(ent.axis, barrel.axis); barrel.nonNormalizedAxes = ent.nonNormalizedAxes; trap_R_AddRefEntityToScene(&barrel); } VectorCopy(origin, lightOrigin); lightOrigin[0] -= 90; lightOrigin[1] += 10; lightOrigin[2] += 30; trap_R_AddLightToScene(lightOrigin, 200, 1.0, 1.0, 1.0); 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_DrawStatusBarOld ================ */ #ifndef MISSIONPACK static void CG_DrawStatusBarOld(void) { int color; centity_t *cent; playerState_t *ps; int value; vec4_t hcolor; vec3_t angles; vec3_t origin; #ifdef MISSIONPACK qhandle_t handle; #endif 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); } #ifdef MISSIONPACK if(cgs.gametype == GT_HARVESTER) { origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = (cg.time & 2047) * 360 / 2048.0; if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { handle = cgs.media.redCubeModel; } else { handle = cgs.media.blueCubeModel; } CG_Draw3DModel(640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles); } #endif // // 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); } } #ifdef MISSIONPACK // // cubes // if(cgs.gametype == GT_HARVESTER) { value = ps->generic1; if(value > 99) { value = 99; } trap_R_SetColor(colors[0]); CG_DrawField(640 - (CHAR_WIDTH * 2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, 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) { if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { handle = cgs.media.redCubeIcon; } else { handle = cgs.media.blueCubeIcon; } CG_DrawPic(640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle); } } #endif } #endif /* ================ CG_DrawStatusBar ================ */ static void CG_DrawStatusBar(void) { int color; centity_t *cent; playerState_t *ps; int value; vec3_t angles; vec3_t origin; static float colors[4][4] = { {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 vec4_t *colorItem; vec4_t colorHudBlack = { 0.0f, 0.0f, 0.0f, 1.0f }; // b/w vec4_t colorTeamBlue = { 0.0f, 0.0f, 1.0f, 0.5f }; // blue vec4_t colorTeamRed = { 1.0f, 0.0f, 0.0f, 0.5f }; // red if(cg_drawStatus.integer == 0) { return; } colorItem = &colorHudBlack; if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { colorItem = &colorTeamBlue; } else if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { colorItem = &colorTeamRed; } cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; VectorClear(angles); 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); } // // ammo // trap_R_SetColor(*colorItem); CG_DrawPic(640 - 118, 480 - 38, 108, 36, cgs.media.sideBarItemRShader); trap_R_SetColor(NULL); 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 = 3; // white } else { color = 1; // red } } // CG_DrawStringExt(590 - (24 * CG_DrawStrlen(va("%i", value)), 480 - ICON_SIZE, va("%i", value), colors[color], qfalse, qtrue, 24, 24, 0); trap_R_SetColor(colors[color]); CG_DrawField(520, 480 - CHAR_HEIGHT - 7, 3, value); trap_R_SetColor(NULL); // if we didn't draw a 2D icon, draw a 3D icon for ammo if(!cg_draw3dIcons.integer && cg_drawIcons.integer) { qhandle_t icon; icon = cg_weapons[cg.predictedPlayerState.weapon].ammoIcon; if(icon) { CG_DrawPic(640 - ICON_SIZE - 12, 480 - ICON_SIZE - 5, ICON_SIZE, ICON_SIZE, icon); } } else { 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(640 - ICON_SIZE - 13, 480 - ICON_SIZE - 5, ICON_SIZE, ICON_SIZE, cg_weapons[cent->currentState.weapon].ammoModel, 0, origin, angles); } } } } // // health // trap_R_SetColor(*colorItem); CG_DrawPic(10, 480 - 38, 108, 36, cgs.media.sideBarItemLShader); trap_R_SetColor(NULL); value = ps->stats[STAT_HEALTH]; if(value > 100) { color = 0; // yellow } else if(value > 25) { color = 3; // white } else if(value > 0) { color = (cg.time >> 8) & 1; } else { color = 1; // red } trap_R_SetColor(colors[color]); CG_DrawField(ICON_SIZE + 5, 480 - CHAR_HEIGHT - 7, 3, value); trap_R_SetColor(NULL); CG_DrawStatusBarHead(10); // // armor // value = ps->stats[STAT_ARMOR]; if(value > 0) { trap_R_SetColor(*colorItem); CG_DrawPic(10, 480 - 79, 108, 36, cgs.media.sideBarItemLShader); trap_R_SetColor(NULL); CG_DrawField(ICON_SIZE + 5, 480 - 72, 3, value); // if we didn't draw a 2D icon, draw a 3D icon for armor if(!cg_draw3dIcons.integer && cg_drawIcons.integer) { CG_DrawPic(12, 480 - ICON_SIZE * 2 - 13, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon); } else { origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = (cg.time & 2047) * 360 / 2048.0; CG_Draw3DModel(13, 480 - ICON_SIZE * 2 - 13, ICON_SIZE, ICON_SIZE, cgs.media.armorModel, 0, origin, angles); } } } /* ================ CG_DrawSideBarItem ================ */ static void CG_DrawSideBarItem(int x, int y, int i) { char *ammo; // a colors[][] array would be better vec4_t *colorItemNormal; vec4_t *colorItemSelected; vec4_t colorItemBlackNormal = { 0.0f, 0.0f, 0.0f, 0.5f }; // black vec4_t colorItemBlackSelected = { 0.0f, 0.0f, 0.0f, 1.0f }; // b/w vec4_t colorTeamBlueNormal = { 0.0f, 0.0f, 1.0f, 1.0f }; // blue vec4_t colorTeamBlueSelected = { 0.0f, 0.0f, 1.0f, 0.5f }; vec4_t colorTeamRedNormal = { 1.0f, 0.0f, 0.0f, 1.0f }; // red vec4_t colorTeamRedSelected = { 1.0f, 0.0f, 0.0f, 0.5f }; vec4_t colorEmpty = { 1.0f, 0.0f, 0.0f, 0.7f }; // red vec4_t colorInActive = { 1.0f, 1.0f, 1.0f, 0.7f }; vec4_t colorActive = { 0.25f, 1.0f, 0.25f, 0.8f }; vec4_t colorActiveField = { 0.25f, 1.0f, 0.25f, 0.1f }; vec3_t angles; vec3_t origin; VectorClear(angles); colorItemNormal = &colorItemBlackNormal; colorItemSelected = &colorItemBlackSelected; if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { colorItemNormal = &colorTeamBlueNormal; colorItemSelected = &colorTeamBlueSelected; } else if(cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { colorItemNormal = &colorTeamRedNormal; colorItemSelected = &colorTeamRedSelected; } CG_RegisterWeapon(i); ammo = va("%i", cg.snap->ps.ammo[i]); if(cg_draw3dIcons.integer && !cg_simpleItems.integer) { origin[0] = 90; origin[1] = 0; origin[2] = 0; //angles[YAW] = 90 + 20 * sin(cg.time / 1000.0); angles[YAW] = 90 * (cg.time / 1000.0) + (30 * i); if(i == cg.weaponSelect) { // draw image with color trap_R_SetColor(*colorItemSelected); if(cg_drawSideBar.integer == 1) { CG_DrawPic(x, y + 10, 73, 25, cgs.media.sideBarItemRShader); } else if(cg_drawSideBar.integer == 2) { CG_DrawPic(x, y + 10, 73, 25, cgs.media.sideBarItemLShader); } trap_R_SetColor(NULL); CG_Draw3DWeaponModel(x, y - 5, 54, 54, cg_weapons[i].weaponModel, cg_weapons[i].barrelModel, 0, origin, angles); CG_DrawSmallStringColor(x + 48, y + 15, ammo, colorActive); } else { // draw image with color trap_R_SetColor(*colorItemNormal); if(cg_drawSideBar.integer == 1) { CG_DrawPic(x, y + 10, 73, 25, cgs.media.sideBarItemRShader); } else if(cg_drawSideBar.integer == 2) { CG_DrawPic(x, y + 10, 73, 25, cgs.media.sideBarItemLShader); } trap_R_SetColor(NULL); CG_Draw3DWeaponModel(x, y, 48, 48, cg_weapons[i].weaponModel, cg_weapons[i].barrelModel, 0, origin, angles); if(cg.snap->ps.ammo[i]) CG_DrawSmallStringColor(x + 48, y + 15, ammo, colorInActive); else CG_DrawSmallStringColor(x + 48, y + 15, ammo, colorEmpty); } } else { // SimpleItems on if(i == cg.weaponSelect) { CG_DrawRect(x, y, 70, 20, 20, colorActiveField); CG_DrawPic(x, y, 20, 20, cg_weapons[i].weaponIcon); CG_DrawSmallStringColor(x + 24, y, ammo, colorInActive); } else { CG_DrawPic(x, y, 16, 16, cg_weapons[i].weaponIcon); if(cg.snap->ps.ammo[i]) CG_DrawSmallStringColor(x + 24, y, ammo, colorInActive); else CG_DrawSmallStringColor(x + 24, y, ammo, colorEmpty); } } } /* ================ CG_DrawSideBar ================ */ static void CG_DrawSideBar(void) { int x, y; int i; int bits; int count; if(cg_drawSideBar.integer == 0) { return; } // do not show if the player is dead if(cg.predictedPlayerState.stats[STAT_HEALTH] <= 0) { return; } // count the number of weapons owned bits = cg.snap->ps.stats[STAT_WEAPONS]; count = 0; for(i = 1; i < 16; i++) { if(bits & (1 << i)) { count++; } } if(cg_drawSideBar.integer == 2) { x = 10; } else { x = 640 - 80; } y = 240 - count * 20; // do not count the gauntlet for(i = 2; i < 16; i++) { if(!(bits & (1 << i))) { continue; } CG_DrawSideBarItem(x, y, i); y += 40; } } /* =========================================================================================== 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(void) { 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) { 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]); } } } #ifdef MISSIONPACK if(cgs.gametype == GT_1FCTF) { // Display flag status item = BG_FindItemForPowerup(PW_NEUTRALFLAG); if(item) { y1 = y - BIGCHAR_HEIGHT - 8; if(cgs.flagStatus >= 0 && cgs.flagStatus <= 3) { CG_DrawPic(x, y1 - 4, w, BIGCHAR_HEIGHT + 8, cgs.media.flagShader[cgs.flagStatus]); } } } #endif 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 - 5; 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_drawPickupItem.integer) return y; if(cg.snap->ps.stats[STAT_HEALTH] <= 0) { return y; } y -= ICON_SIZE * 2 + 15; 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 w, h; int i, len; 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; w = 0; for(i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); if(len > w) w = len; } w *= TINYCHAR_WIDTH; w += TINYCHAR_WIDTH * 2; 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); } /* ============== CG_DrawBloom ============== */ static void CG_DrawBloom(void) { if(cg_drawBloom.integer == 1) { CG_DrawPic(0, 0, 640, 480, cgs.media.bloomShader); } else if(cg_drawBloom.integer == 2) { CG_DrawPic(0, 0, 640, 480, cgs.media.bloom2Shader); } } /* =============================================================================== 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; // bk010215 - FIXME char message[1024]; // 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 // bk 0102165 - FIXME return; } // also add text in center of screen s = "Connection Interrupted"; // bk 010215 - FIXME 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 // bk010221 - unused else 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_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 = trap_CM_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->team == TEAM_RED) cs_offset = 0; else if(cgs.clientinfo->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() { #ifdef MISSIONPACK static qboolean firstTime = qtrue; float fade, *fadeColor; 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) { fade = 1.0; fadeColor = colorWhite; } else { fadeColor = CG_FadeColor(cg.scoreFadeTime, FADE_TIME); if(!fadeColor) { // next time scoreboard comes up, don't print killer cg.deferredPlayerLoading = 0; cg.killerName[0] = 0; firstTime = qtrue; return qfalse; } fade = *fadeColor; } 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; float scale; clientInfo_t *ci1, *ci2; int cw; 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; } } scale = 0.45f; switch (cg.warmupCount) { case 0: cw = 28; scale = 0.54f; break; case 1: cw = 24; scale = 0.51f; break; case 2: cw = 20; scale = 0.48f; break; default: cw = 16; scale = 0.45f; break; } #ifdef MISSIONPACK w = CG_Text_Width(s, scale, 0); CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); #else 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() { 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(void) { #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(); 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(); CG_DrawSideBar(); #endif CG_DrawAmmoWarning(); #ifdef MISSIONPACK CG_DrawProxWarning(); #endif 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(); } #else CG_DrawUpperRight(); #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() { #ifdef MISSIONPACK #else CG_DrawOldTourneyScoreboard(); #endif } /* ===================== CG_DrawActive Perform all drawing needed to completely fill the screen ===================== */ void CG_DrawActive(stereoFrame_t stereoView) { float separation; vec3_t baseOrg; // 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; } switch (stereoView) { case STEREO_CENTER: separation = 0; break; case STEREO_LEFT: separation = -cg_stereoSeparation.value / 2; break; case STEREO_RIGHT: separation = cg_stereoSeparation.value / 2; break; default: separation = 0; CG_Error("CG_DrawActive: Undefined stereoView"); } // clear around the rendered view if sized down CG_TileClear(); // offset vieworg appropriately if we're doing stereo separation VectorCopy(cg.refdef.vieworg, baseOrg); if(separation != 0) { VectorMA(cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg); } // draw 3D view trap_R_RenderScene(&cg.refdef); // restore original viewpoint if running stereo if(separation != 0) { VectorCopy(baseOrg, cg.refdef.vieworg); } // draw bloom post process effect CG_DrawBloom(); // draw status bar and other floating elements CG_Draw2D(); }