/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET 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 3 of the License, or (at your option) any later version. Wolf ET 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // cg_multiview.c: Multiview handling // ---------------------------------- // 02 Sep 02 // rhea@OrangeSmoothie.org // #include "cg_local.h" #include "../ui/ui_shared.h" #include "../game/bg_local.h" void CG_CalcVrect( void ); void CG_DrawPlayerWeaponIcon( rectDef_t *rect, qboolean drawHighlighted, int align, vec4_t *refcolor ); // Explicit server command to add a view to the client's snapshot void CG_mvNew_f( void ) { if ( cg.demoPlayback || trap_Argc() < 2 ) { return; } else { int pID; char aName[64]; trap_Args( aName, sizeof( aName ) ); pID = CG_findClientNum( aName ); if ( pID >= 0 && !CG_mvMergedClientLocate( pID ) ) { trap_SendClientCommand( va( "mvadd %d\n", pID ) ); } } } // Explicit server command to remove a view from the client's snapshot void CG_mvDelete_f( void ) { if ( cg.demoPlayback ) { return; } else { int pID = -1; if ( trap_Argc() > 1 ) { char aName[64]; trap_Args( aName, sizeof( aName ) ); pID = CG_findClientNum( aName ); } else { cg_window_t *w = cg.mvCurrentActive; if ( w != NULL ) { pID = ( w->mvInfo & MV_PID ); } } if ( pID >= 0 && CG_mvMergedClientLocate( pID ) ) { trap_SendClientCommand( va( "mvdel %d\n", pID ) ); } } } // Swap highlighted window with main view void CG_mvSwapViews_f( void ) { if ( cg.mv_cnt >= 2 && cg.mvCurrentActive != cg.mvCurrentMainview ) { CG_mvMainviewSwap( cg.mvCurrentActive ); } } // Shut down a window view for a particular MV client void CG_mvHideView_f( void ) { if ( cg.mvCurrentActive == NULL || cg.mvCurrentMainview == cg.mvCurrentActive ) { return; } CG_mvFree( cg.mvCurrentActive->mvInfo & MV_PID ); } // Activate a window view for a particular MV client void CG_mvShowView_f( void ) { int i; for ( i = 0; i < cg.mvTotalClients; i++ ) { if ( cg.mvOverlay[i].fActive ) { if ( cg.mvOverlay[i].w == NULL ) { CG_mvCreate( cg.mvOverlay[i].pID ); CG_mvOverlayUpdate(); } return; } } } // Toggle a view window on/off void CG_mvToggleView_f( void ) { int i; for ( i = 0; i < cg.mvTotalClients; i++ ) { if ( cg.mvOverlay[i].fActive ) { if ( cg.mvOverlay[i].w != NULL ) { CG_mvHideView_f(); } else { CG_mvCreate( cg.mvOverlay[i].pID ); CG_mvOverlayUpdate(); } return; } } } // Toggle all views void CG_mvToggleAll_f( void ) { if ( !cg.demoPlayback ) { trap_SendClientCommand( ( cg.mvTotalClients > 0 ) ? "mvnone\n" : "mvall\n" ); } } //////////////////////////////////////////////// // // Multiview Primitives // // /////////////////////////////// // Create a new view window // void CG_mvCreate( int pID ) { cg_window_t *w; if ( CG_mvClientLocate( pID ) != NULL ) { return; } w = CG_windowAlloc( WFX_MULTIVIEW, 100 ); if ( w == NULL ) { return; } // Window specific w->id = WID_NONE; w->x = ( cg.mv_cnt == 0 ) ? 0 : 30 + ( 12 * pID ); w->y = ( cg.mv_cnt == 0 ) ? 0 : 300 + ( 5 * pID ); w->w = ( cg.mv_cnt == 0 ) ? 640 : 128; w->h = ( cg.mv_cnt == 0 ) ? 480 : 96; w->mvInfo = ( pID & MV_PID ) | MV_SELECTED; w->state = ( cg.mv_cnt == 0 ) ? WSTATE_COMPLETE : WSTATE_START; if ( cg.mv_cnt == 0 ) { cg.mvCurrentMainview = w; cg.mvCurrentActive = cg.mvCurrentMainview; if ( cg_specHelp.integer > 0 && !cg.demoPlayback ) { CG_ShowHelp_On( &cg.spechelpWindow ); } } cg.mv_cnt++; } /////////////////////////// // Delete a view window // void CG_mvFree( int pID ) { cg_window_t *w = CG_mvClientLocate( pID ); if ( w != NULL ) { // Free it in mvDraw() w->targetTime = 100; w->time = trap_Milliseconds(); w->state = WSTATE_SHUTDOWN; } } cg_window_t *CG_mvClientLocate( int pID ) { int i; cg_window_t *w; cg_windowHandler_t *wh = &cg.winHandler; for ( i = 0; i < wh->numActiveWindows; i++ ) { w = &wh->window[wh->activeWindows[i]]; if ( ( w->effects & WFX_MULTIVIEW ) && ( w->mvInfo & MV_PID ) == pID ) { return( w ); } } return( NULL ); } // Swap a window-view with the main view void CG_mvMainviewSwap( cg_window_t *av ) { int swap_pID = ( cg.mvCurrentMainview->mvInfo & MV_PID ); cg.mvCurrentMainview->mvInfo = ( cg.mvCurrentMainview->mvInfo & ~MV_PID ) | ( av->mvInfo & MV_PID ); av->mvInfo = ( av->mvInfo & ~MV_PID ) | swap_pID; CG_mvOverlayUpdate(); } ///////////////////////////////////////////// // Track our list of merged clients // void CG_mvProcessClientList( void ) { int i, bit, newList = cg.snap->ps.powerups[PW_MVCLIENTLIST]; cg.mvTotalClients = 0; for ( i = 0; i < MAX_MVCLIENTS; i++ ) { bit = 1 << i; if ( ( cg.mvClientList & bit ) != ( newList & bit ) ) { if ( ( newList & bit ) == 0 ) { CG_mvFree( i ); } // If no active views at all, start up with the first one else if ( cg.mvCurrentMainview == NULL ) { CG_mvCreate( i ); } } if ( newList & bit ) { cg.mvTotalClients++; } } cg.mvClientList = newList; CG_mvOverlayUpdate(); } // Give handle to the current selected MV window cg_window_t *CG_mvCurrent( void ) { int i; cg_window_t *w; cg_windowHandler_t *wh = &cg.winHandler; for ( i = 0; i < wh->numActiveWindows; i++ ) { w = &wh->window[wh->activeWindows[i]]; if ( ( w->effects & WFX_MULTIVIEW ) && ( w->mvInfo & MV_SELECTED ) ) { return( w ); } } return( NULL ); } // Give handle to any MV window that isnt the mainview cg_window_t *CG_mvFindNonMainview( void ) { int i; cg_window_t *w; cg_windowHandler_t *wh = &cg.winHandler; // First, find a non-windowed client for ( i = 0; i < cg.mvTotalClients; i++ ) { if ( cg.mvOverlay[i].w == NULL ) { cg.mvCurrentMainview->mvInfo = ( cg.mvCurrentMainview->mvInfo & ~MV_PID ) | ( cg.mvOverlay[i].pID & MV_PID ); CG_mvOverlayClientUpdate( cg.mvOverlay[i].pID, i ); return( cg.mvCurrentMainview ); } } // If none available, pull one from a window for ( i = 0; i < wh->numActiveWindows; i++ ) { w = &wh->window[wh->activeWindows[i]]; if ( ( w->effects & WFX_MULTIVIEW ) && w != cg.mvCurrentMainview ) { CG_mvMainviewSwap( w ); return( w ); } } return( cg.mvCurrentMainview ); } ////////////////////////////////////////////// // // Rendering/Display Management // ////////////////////////////////////////////// /////////////////////////////////////////////////// // Update all info for a merged client // void CG_mvUpdateClientInfo( int pID ) { if ( pID >= 0 && pID < MAX_MVCLIENTS && ( cg.mvClientList & ( 1 << pID ) ) ) { int weap = cg_entities[pID].currentState.weapon; int id = MAX_WEAPONS - 1 - ( pID * 2 ); clientInfo_t *ci = &cgs.clientinfo[pID]; playerState_t *ps = &cg.snap->ps; ci->health = ( ps->ammo[id] ) & 0xFF; ci->hintTime = ( ps->ammo[id] >> 8 ) & 0x0F; ci->weapHeat = ( ps->ammo[id] >> 12 ) & 0x0F; ci->ammo = ( ps->ammo[id - 1] ) & 0x3FF; ci->weaponState = ( ps->ammo[id - 1] >> 11 ) & 0x03; ci->fCrewgun = ( ps->ammo[id - 1] >> 13 ) & 0x01; ci->cursorHint = ( ps->ammo[id - 1] >> 14 ) & 0x03; ci->ammoclip = ( ps->ammoclip[id - 1] ) & 0x1FF; ci->chargeTime = ( ps->ammoclip[id - 1] >> 9 ) & 0x0F; ci->sprintTime = ( ps->ammoclip[id - 1] >> 13 ) & 0x07; ci->weapHeat = (int)( 100.0f * (float)ci->weapHeat / 15.0f ); ci->chargeTime = ( ci->chargeTime == 0 ) ? -1 : (int)( 100.0f * (float)( ci->chargeTime - 1 ) / 15.0f ); ci->hintTime = ( ci->hintTime == 0 ) ? -1 : (int)( 100.0f * (float)( ci->hintTime - 1 ) / 15.0f ); ci->sprintTime = ( ci->sprintTime == 0 ) ? -1 : (int)( 100.0f * (float)( ci->sprintTime - 1 ) / 7.0f ); if ( ci->health == 0 ) { ci->weaponState = WSTATE_IDLE; } // Handle grenade pulsing for the main view if ( ci->weaponState != ci->weaponState_last ) { ci->weaponState_last = ci->weaponState; ci->grenadeTimeStart = ( ci->weaponState == WSTATE_FIRE && ( weap == WP_GRENADE_LAUNCHER || weap == WP_GRENADE_PINEAPPLE ) ) ? 4000 + cg.time : 0; } if ( ci->weaponState == WSTATE_FIRE && ( weap == WP_GRENADE_LAUNCHER || weap == WP_GRENADE_PINEAPPLE ) ) { ci->grenadeTimeLeft = ci->grenadeTimeStart - cg.time; if ( ci->grenadeTimeLeft < 0 ) { ci->grenadeTimeLeft = 0; } } else {ci->grenadeTimeLeft = 0;} } } //////////////////////////////// // Updates for main view // void CG_mvTransitionPlayerState( playerState_t* ps ) { int x, mult, pID = ( cg.mvCurrentMainview->mvInfo & MV_PID ); centity_t* cent = &cg_entities[pID]; clientInfo_t *ci = &cgs.clientinfo[pID]; cg.predictedPlayerEntity.currentState = cent->currentState; ps->clientNum = pID; ps->weapon = cent->currentState.weapon; cg.weaponSelect = ps->weapon; cent->currentState.eType = ET_PLAYER; ps->eFlags = cent->currentState.eFlags; cg.predictedPlayerState.eFlags = cent->currentState.eFlags; cg.zoomedBinoc = ( ( cent->currentState.eFlags & EF_ZOOMING ) != 0 && ci->health > 0 ); x = cent->currentState.teamNum; if ( x == PC_MEDIC ) { mult = cg.medicChargeTime[ci->team - 1]; } else if ( x == PC_ENGINEER ) { mult = cg.engineerChargeTime[ci->team - 1]; } else if ( x == PC_FIELDOPS ) { mult = cg.ltChargeTime[ci->team - 1]; } else if ( x == PC_COVERTOPS ) { mult = cg.covertopsChargeTime[ci->team - 1]; } else { mult = cg.soldierChargeTime[ci->team - 1];} ps->curWeapHeat = (int)( (float)ci->weapHeat * 255.0f / 100.0f ); ps->classWeaponTime = ( ci->chargeTime < 0 ) ? -1 : cg.time - (int)( (float)( mult * ci->chargeTime ) / 100.0f ); // FIXME: moved to pmext // ps->sprintTime = (ci->sprintTime < 0) ? 20000 : (int)((float)ci->sprintTime / 100.0f * 20000.0f); ps->serverCursorHintVal = ( ci->hintTime < 0 ) ? 0 : ci->hintTime * 255 / 100; ps->serverCursorHint = BG_simpleHintsExpand( ci->cursorHint, ( ( x == 2 ) ? ci->hintTime : -1 ) ); ps->stats[STAT_HEALTH] = ci->health; ps->stats[STAT_PLAYER_CLASS] = x; // Grenade ticks ps->grenadeTimeLeft = ci->grenadeTimeLeft; // Safe as we've already pull data before clobbering ps->ammo[BG_FindAmmoForWeapon( ps->weapon )] = ci->ammo; ps->ammoclip[BG_FindClipForWeapon( ps->weapon )] = ci->ammoclip; ps->persistant[PERS_SCORE] = ci->score; ps->persistant[PERS_TEAM] = ci->team; VectorCopy( cent->lerpOrigin, ps->origin ); VectorCopy( cent->lerpAngles, ps->viewangles ); } void CG_OffsetThirdPersonView( void ); /////////////////////////////////// // Draw the client view window // void CG_mvDraw( cg_window_t *sw ) { int pID = ( sw->mvInfo & MV_PID ); int x, base_pID = cg.snap->ps.clientNum; refdef_t refdef; float rd_x, rd_y, rd_w, rd_h; float b_x, b_y, b_w, b_h; float s = 1.0f; centity_t *cent = &cg_entities[pID]; memset( &refdef, 0, sizeof( refdef_t ) ); memcpy( refdef.areamask, cg.snap->areamask, sizeof( refdef.areamask ) ); CG_mvUpdateClientInfo( pID ); cg.snap->ps.clientNum = pID; rd_x = sw->x; rd_y = sw->y; rd_w = sw->w; rd_h = sw->h; // Cool zoomin/out effect if ( sw->state != WSTATE_COMPLETE ) { int tmp = trap_Milliseconds() - sw->time; if ( sw->state == WSTATE_START ) { if ( tmp < sw->targetTime ) { s = (float)tmp / (float)sw->targetTime; rd_x += rd_w * 0.5f * ( 1.0f - s ); rd_y += rd_h * 0.5f * ( 1.0f - s ); rd_w *= s; rd_h *= s; } else { sw->state = WSTATE_COMPLETE; } } else if ( sw->state == WSTATE_SHUTDOWN ) { if ( tmp < sw->targetTime ) { s = (float)tmp / (float)sw->targetTime; rd_x += rd_w * 0.5f * s; rd_y += rd_h * 0.5f * s; s = 1.0f - s; rd_w *= s; rd_h *= s; if ( sw == cg.mvCurrentMainview ) { trap_R_ClearScene(); } } else { // Main window is shutting down. // Try to swap it with another MV client, if available if ( sw == cg.mvCurrentMainview ) { sw = CG_mvFindNonMainview(); if ( cg.mvTotalClients > 0 ) { cg.mvCurrentMainview->targetTime = 100; cg.mvCurrentMainview->time = trap_Milliseconds(); cg.mvCurrentMainview->state = WSTATE_START; } // If we swap with a window, hang around so we can delete the window // Otherwise, if there are still active MV clients, don't close the mainview if ( sw == cg.mvCurrentMainview && cg.mvTotalClients > 0 ) { return; } } CG_windowFree( sw ); // We were on the last viewed client when the shutdown was issued, // go ahead and shut down the mainview window *sniff* if ( --cg.mv_cnt <= 0 ) { cg.mv_cnt = 0; cg.mvCurrentMainview = NULL; if ( cg.spechelpWindow == SHOW_ON ) { CG_ShowHelp_Off( &cg.spechelpWindow ); } } CG_mvOverlayUpdate(); return; } } } b_x = rd_x; b_y = rd_y; b_w = rd_w; b_h = rd_h; CG_AdjustFrom640( &rd_x, &rd_y, &rd_w, &rd_h ); refdef.x = rd_x; refdef.y = rd_y; refdef.width = rd_w; refdef.height = rd_h; refdef.fov_x = ( cgs.clientinfo[pID].health > 0 && ( /*cent->currentState.weapon == WP_SNIPERRIFLE ||*/ // ARNOUT: this needs updating? ( cent->currentState.eFlags & EF_ZOOMING ) ) ) ? cg_zoomDefaultSniper.value : ( cgs.clientinfo[pID].fCrewgun ) ? 55 : cg_fov.integer; x = refdef.width / tan( refdef.fov_x / 360 * M_PI ); refdef.fov_y = atan2( refdef.height, x ) * 360 / M_PI; refdef.rdflags = cg.refdef.rdflags; refdef.time = cg.time; AnglesToAxis( cent->lerpAngles, refdef.viewaxis ); VectorCopy( cent->lerpOrigin, refdef.vieworg ); VectorCopy( cent->lerpAngles, cg.refdefViewAngles ); cg.refdef_current = &refdef; trap_R_ClearScene(); if ( sw == cg.mvCurrentMainview && cg.renderingThirdPerson ) { cg.renderingThirdPerson = qtrue; // VectorCopy(cent->lerpOrigin, refdef.vieworg); CG_OffsetThirdPersonView(); AnglesToAxis( cg.refdefViewAngles, refdef.viewaxis ); } else { cg.renderingThirdPerson = qfalse; refdef.vieworg[2] += ( cent->currentState.eFlags & EF_CROUCHING ) ? CROUCH_VIEWHEIGHT : DEFAULT_VIEWHEIGHT; } CG_SetupFrustum(); CG_DrawSkyBoxPortal( qfalse ); if ( !cg.hyperspace ) { CG_AddPacketEntities(); CG_AddMarks(); CG_AddParticles(); CG_AddLocalEntities(); CG_AddSmokeSprites(); CG_AddAtmosphericEffects(); CG_AddFlameChunks(); CG_AddTrails(); // this must come last, so the trails dropped this frame get drawn } if ( sw == cg.mvCurrentMainview ) { CG_DrawActive( STEREO_CENTER ); if ( cg.mvCurrentActive == cg.mvCurrentMainview ) { trap_S_Respatialize( cg.clientNum, refdef.vieworg, refdef.viewaxis, qfalse ); } cg.snap->ps.clientNum = base_pID; cg.refdef_current = &cg.refdef; cg.renderingThirdPerson = qfalse; return; } memcpy( refdef.areamask, cg.snap->areamask, sizeof( refdef.areamask ) ); refdef.time = cg.time; trap_SetClientLerpOrigin( refdef.vieworg[0], refdef.vieworg[1], refdef.vieworg[2] ); trap_R_RenderScene( &refdef ); cg.refdef_current = &cg.refdef; #if 0 cg.refdef_current = &refdef; CG_DrawStringExt( 1, 1, ci->name, colorWhite, qtrue, qtrue, 8, 8, 0 ); cg.refdef_current = &cg.refdef; #endif CG_mvWindowOverlay( pID, b_x, b_y, b_w, b_h, s, sw->state, ( sw == cg.mvCurrentActive ) ); if ( sw == cg.mvCurrentActive ) { trap_S_Respatialize( cg.clientNum, refdef.vieworg, refdef.viewaxis, qfalse ); } cg.snap->ps.clientNum = base_pID; } //////////////////////////////////////////// // Simpler overlay for windows // void CG_mvWindowOverlay( int pID, float b_x, float b_y, float b_w, float b_h, float s, int wState, qboolean fSelected ) { int x; rectDef_t rect; float fw = 8.0f, fh = 8.0f; centity_t *cent = &cg_entities[pID]; clientInfo_t *ci = &cgs.clientinfo[pID]; const char *p_class = "?"; vec4_t *noSelectBorder = &colorDkGrey; // Overlays for zoomed views if ( ci->health > 0 ) { /*if(cent->currentState.weapon == WP_SNIPERRIFLE) CG_mvZoomSniper(b_x, b_y, b_w, b_h); // ARNOUT: this needs updating? else */if ( cent->currentState.eFlags & EF_ZOOMING ) { CG_mvZoomBinoc( b_x, b_y, b_w, b_h ); } } // Text info fw *= s; fh *= s; x = cent->currentState.teamNum; if ( x == PC_SOLDIER ) { p_class = "^1S"; noSelectBorder = &colorMdRed; } else if ( x == PC_MEDIC ) { p_class = "^7M"; noSelectBorder = &colorMdGrey; } else if ( x == PC_ENGINEER ) { p_class = "^5E"; noSelectBorder = &colorMdBlue; } else if ( x == PC_FIELDOPS ) { p_class = "^2F"; noSelectBorder = &colorMdGreen; } else if ( x == PC_COVERTOPS ) { p_class = "^3C"; noSelectBorder = &colorMdYellow; } CG_DrawStringExt( b_x + 1, b_y + b_h - ( fh * 2 + 1 + 2 ), ci->name, colorWhite, qfalse, qtrue, fw, fh, 0 ); CG_DrawStringExt( b_x + 1, b_y + b_h - ( fh + 2 ), va( "%s^7%d", CG_TranslateString( p_class ), ci->health ), colorWhite, qfalse, qtrue, fw, fh, 0 ); p_class = va( "%d^1/^7%d", ci->ammoclip, ci->ammo ); x = CG_DrawStrlen( p_class ); CG_DrawStringExt( b_x + b_w - ( x * fw + 1 ), b_y + b_h - ( fh + 2 ), p_class, colorWhite, qfalse, qtrue, fw, fh, 0 ); // Weapon icon rect.x = b_x + b_w - ( 50 + 1 ); rect.y = b_y + b_h - ( 25 + fh + 1 + 2 ); rect.w = 50; rect.h = 25; cg.predictedPlayerState.grenadeTimeLeft = 0; cg.predictedPlayerState.weapon = cent->currentState.weapon; CG_DrawPlayerWeaponIcon( &rect, ( ci->weaponState > WSTATE_IDLE ), ITEM_ALIGN_RIGHT, ( ( ci->weaponState == WSTATE_SWITCH ) ? &colorWhite : ( ci->weaponState == WSTATE_FIRE ) ? &colorRed : &colorYellow ) ); // Sprint charge info if ( ci->sprintTime >= 0 ) { p_class = va( "^2S^7%d%%", ci->sprintTime ); rect.y -= ( fh + 1 ); x = CG_DrawStrlen( p_class ); CG_DrawStringExt( b_x + b_w - ( x * fw + 1 ), rect.y, p_class, colorWhite, qfalse, qtrue, fw, fh, 0 ); } // Weapon charge info if ( ci->chargeTime >= 0 ) { p_class = va( "^1C^7%d%%", ci->chargeTime ); rect.y -= ( fh + 1 ); x = CG_DrawStrlen( p_class ); CG_DrawStringExt( b_x + b_w - ( x * fw + 1 ), rect.y, p_class, colorWhite, qfalse, qtrue, fw, fh, 0 ); } // Cursorhint work if ( ci->hintTime >= 0 ) { p_class = va( "^3W:^7%d%%", ci->hintTime ); rect.y -= ( fh + 1 ); x = CG_DrawStrlen( p_class ); CG_DrawStringExt( b_x + ( b_w - ( x * ( fw - 1 ) ) ) / 2, b_y + b_h - ( fh + 2 ), p_class, colorWhite, qfalse, qtrue, fw - 1, fh - 1, 0 ); } // Finally, the window border if ( fSelected && wState == WSTATE_COMPLETE ) { int t = trap_Milliseconds() & 0x07FF; // 2047 float scale; vec4_t borderColor; if ( t > 1024 ) { t = 2047 - t; } memcpy( &borderColor, *noSelectBorder, sizeof( vec4_t ) ); scale = ( (float)t / 1137.38f ) + 0.5f; if ( scale <= 1.0 ) { borderColor[0] *= scale; borderColor[1] *= scale; borderColor[2] *= scale; } else { scale -= 1.0; borderColor[0] = ( borderColor[0] + scale > 1.0 ) ? 1.0 : borderColor[0] + scale; borderColor[1] = ( borderColor[1] + scale > 1.0 ) ? 1.0 : borderColor[1] + scale; borderColor[2] = ( borderColor[2] + scale > 1.0 ) ? 1.0 : borderColor[2] + scale; } CG_DrawRect( b_x - 1, b_y - 1, b_w + 2, b_h + 2, 2, borderColor ); } else { CG_DrawRect( b_x - 1, b_y - 1, b_w + 2, b_h + 2, 2, *noSelectBorder ); } } //////////////////////////////////////////////////// // // MV Text Overlay Handling // //////////////////////////////////////////////////// char *strClassHighlights[] = { S_COLOR_RED, S_COLOR_MDRED, // Soldier S_COLOR_WHITE, S_COLOR_MDGREY, // Medic S_COLOR_BLUE, S_COLOR_MDBLUE, // Engineer S_COLOR_GREEN, S_COLOR_MDGREEN, // Lt. S_COLOR_YELLOW, S_COLOR_MDYELLOW // CovertOps }; // Update a particular client's info void CG_mvOverlayClientUpdate( int pID, int index ) { cg_window_t *w; cg.mvOverlay[index].pID = pID; cg.mvOverlay[index].classID = cg_entities[pID].currentState.teamNum; w = CG_mvClientLocate( pID ); cg.mvOverlay[index].w = w; if ( w != NULL ) { strcpy( cg.mvOverlay[index].info, va( "%s%s%2d", strClassHighlights[cg.mvOverlay[index].classID * 2], ( w == cg.mvCurrentMainview ) ? "*" : "", pID ) ); } else { strcpy( cg.mvOverlay[index].info, va( "%s%2d", strClassHighlights[( cg.mvOverlay[index].classID * 2 ) + 1], pID ) ); } cg.mvOverlay[index].width = CG_DrawStrlen( cg.mvOverlay[index].info ) * MVINFO_TEXTSIZE; } // Update info on all clients received for display/cursor interaction void CG_mvOverlayUpdate( void ) { int i, cnt; for ( i = 0, cnt = 0; i < MAX_MVCLIENTS && cnt < cg.mvTotalClients; i++ ) { if ( cg.mvClientList & ( 1 << i ) ) { CG_mvOverlayClientUpdate( i, cnt++ ); } } } // See if we have the client in our snapshot qboolean CG_mvMergedClientLocate( int pID ) { int i; for ( i = 0; i < cg.mvTotalClients; i++ ) { if ( cg.mvOverlay[i].pID == pID ) { return( qtrue ); } } return( qfalse ); } // Display available client info void CG_mvOverlayDisplay( void ) { int j, i, x, y, pID; cg_mvinfo_t *o; if ( cg.mvTotalClients < 1 ) { return; } y = MVINFO_TOP - ( 2 * ( MVINFO_TEXTSIZE + 1 ) ); for ( j = TEAM_AXIS; j <= TEAM_ALLIES; j++ ) { cg.mvTotalTeam[j] = 0; for ( i = 0; i < cg.mvTotalClients; i++ ) { o = &cg.mvOverlay[i]; pID = o->pID; if ( cgs.clientinfo[pID].team != j ) { continue; } if ( cg.mvTotalTeam[j] == 0 ) { char *flag = ( j == TEAM_AXIS ) ? "ui/assets/ger_flag.tga" : "ui/assets/usa_flag.tga"; y += 2 * ( MVINFO_TEXTSIZE + 1 ); CG_DrawPic( MVINFO_RIGHT - ( 2 * MVINFO_TEXTSIZE ), y, 2 * MVINFO_TEXTSIZE, MVINFO_TEXTSIZE, trap_R_RegisterShaderNoMip( flag ) ); } // Update team list info for mouse detection cg.mvTeamList[j][( cg.mvTotalTeam[j] )] = i; cg.mvTotalTeam[j]++; // Update any class changes if ( o->classID != cg_entities[pID].currentState.teamNum ) { CG_mvOverlayClientUpdate( o->pID, i ); } x = MVINFO_RIGHT - o->width; y += MVINFO_TEXTSIZE + 1; if ( o->fActive ) { CG_FillRect( x - 1, y, o->width + 2, MVINFO_TEXTSIZE + 2, colorMdYellow ); // Draw name info only if we're hovering over the text element if ( !( cg.mvCurrentActive->mvInfo & MV_SELECTED ) || cg.mvCurrentActive == cg.mvCurrentMainview ) { int w = CG_DrawStrlen( cgs.clientinfo[pID].name ) * ( MVINFO_TEXTSIZE - 1 ); CG_FillRect( x - 1 - w - 6, y + 1, w + 2, MVINFO_TEXTSIZE - 1 + 2, colorMdGrey ); CG_DrawStringExt( x - w - 6, y + 1, cgs.clientinfo[pID].name, colorYellow, qtrue, qtrue, MVINFO_TEXTSIZE - 1, MVINFO_TEXTSIZE - 1, 0 ); } } CG_DrawStringExt( x, y, o->info, colorWhite, qfalse, qtrue, MVINFO_TEXTSIZE, MVINFO_TEXTSIZE, 0 ); } } } ////////////////////////////////////// // // Wolf-specific utilities // ////////////////////////////////////// void CG_mvZoomSniper( float x, float y, float w, float h ) { float ws = w / 640; float hs = h / 480; // sides CG_FillRect( x, y, 80.0f * ws, 480.0f * hs, colorBlack ); CG_FillRect( x + 560.0f * ws, y, 80.0f * ws, 480.0f * hs, colorBlack ); // center if ( cgs.media.reticleShaderSimple ) { CG_DrawPic( x + 80.0f * ws, y, 480.0f * ws, 480.0f * hs, cgs.media.reticleShaderSimple ); } // hairs CG_FillRect( x + 84.0f * ws, y + 239.0f * hs, 177.0f * ws, 2.0f, colorBlack ); // left CG_FillRect( x + 320.0f * ws, y + 242.0f * hs, 1.0f, 58.0f * hs, colorBlack ); // center top CG_FillRect( x + 319.0f * ws, y + 300.0f * hs, 2.0f, 178.0f * hs, colorBlack ); // center bot CG_FillRect( x + 380.0f * ws, y + 239.0f * hs, 177.0f * ws, 2.0f, colorBlack ); // right } void CG_mvZoomBinoc( float x, float y, float w, float h ) { float ws = w / 640; float hs = h / 480; // an alternative. This gives nice sharp lines at the expense of a few extra polys if ( cgs.media.binocShaderSimple ) { CG_DrawPic( x, y, 640.0f * ws, 480.0f * ws, cgs.media.binocShaderSimple ); } CG_FillRect( x + 146.0f * ws, y + 239.0f * hs, 348.0f * ws, 1, colorBlack ); CG_FillRect( x + 188.0f * ws, y + 234.0f * hs, 1, 13.0f * hs, colorBlack ); // ll CG_FillRect( x + 234.0f * ws, y + 226.0f * hs, 1, 29.0f * hs, colorBlack ); // l CG_FillRect( x + 274.0f * ws, y + 234.0f * hs, 1, 13.0f * hs, colorBlack ); // lr CG_FillRect( x + 320.0f * ws, y + 213.0f * hs, 1, 55.0f * hs, colorBlack ); // center CG_FillRect( x + 360.0f * ws, y + 234.0f * hs, 1, 13.0f * hs, colorBlack ); // rl CG_FillRect( x + 406.0f * ws, y + 226.0f * hs, 1, 29.0f * hs, colorBlack ); // r CG_FillRect( x + 452.0f * ws, y + 234.0f * hs, 1, 13.0f * hs, colorBlack ); // rr }