// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "UserInterfaceManager.h" #include "UIWindow.h" #include "UICrosshairInfo.h" #include "UserInterfaceLocal.h" #include "../CrosshairInfo.h" #include "../Player.h" #include "../misc/WorldToScreen.h" #include "../script/Script_Helper.h" #include "../roles/WayPointManager.h" #include "../ContentMask.h" #include "../../sys/sys_local.h" idCVar g_damageIndicatorFadeTime( "g_damageIndicatorFadeTime", "2.0", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "number of seconds that a damage indicator stays visible" ); idCVar g_damageIndicatorWidth( "g_damageIndicatorWidth", "256", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "width of the damage indicators" ); idCVar g_damageIndicatorHeight( "g_damageIndicatorHeight", "128", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "height of the damage indicators" ); idCVar g_damageIndicatorColor( "g_damageIndicatorColor", "1 0 0", CVAR_GAME | CVAR_NOCHEAT, "color of the damage indicators" ); idCVar g_damageIndicatorAlphaScale( "g_damageIndicatorAlphaScale", "0.3", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "alpha of the damage indicators" ); idCVar g_repairIndicatorColor( "g_repairIndicatorColor", "0 1 0", CVAR_GAME | CVAR_NOCHEAT, "color of the repair indicators" ); idCVar g_repairIndicatorAlphaScale( "g_repairIndicatorAlphaScale", "0.3", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "alpha of the repair indicators" ); idCVar g_waypointAlphaScale( "g_waypointAlphaScale", "0.7", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE | CVAR_PROFILE, "alpha to apply to world-based objective icons" ); idCVar g_showWayPoints( "g_showWayPoints", "1", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE | CVAR_PROFILE, "show or hide world-based objective icons" ); idCVar g_waypointSizeMin( "g_waypointSizeMin", "16", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE | CVAR_PROFILE, "min world-view icon size" ); idCVar g_waypointSizeMax( "g_waypointSizeMax", "32", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE | CVAR_PROFILE, "max world-view icon size" ); idCVar g_waypointDistanceMin( "g_waypointDistanceMin", "512", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE | CVAR_PROFILE, "max distance at which to show min icon size" ); idCVar g_waypointDistanceMax( "g_waypointDistanceMax", "3084", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE | CVAR_PROFILE, "min distance at which to show max icon size" ); using namespace sdProperties; const char sdUITemplateFunctionInstance_IdentifierCrosshairInfo[] = "sdUICrosshairInfoFunction"; idHashMap< sdUICrosshairInfo::CrosshairInfoTemplateFunction* > sdUICrosshairInfo::crosshairInfoFunctions; const int sdUICrosshairInfo::lineTextScale = 12; static const float HIGHLIGHT_TIME = 2000.0f; /* ================ sdUICrosshairInfo::sdUICrosshairInfo ================ */ sdUICrosshairInfo::sdUICrosshairInfo( void ) { crosshairParts.SetNum( CP_MAX ); scriptState.GetProperties().RegisterProperty( "barMaterial", barMaterial ); scriptState.GetProperties().RegisterProperty( "barFillMaterial", barFillMaterial ); scriptState.GetProperties().RegisterProperty( "barLineMaterial", barLineMaterial ); scriptState.GetProperties().RegisterProperty( "barLineColor", barLineColor ); scriptState.GetProperties().RegisterProperty( "bracketBaseColor", bracketBaseColor ); scriptState.GetProperties().RegisterProperty( "bracketLeftMaterial", bracketLeftMaterial ); scriptState.GetProperties().RegisterProperty( "bracketRightMaterial", bracketRightMaterial ); scriptState.GetProperties().RegisterProperty( "damageMaterial", damageMaterial ); scriptState.GetProperties().RegisterProperty( "crosshairInfoFlags", crosshairInfoFlags ); barLineColor = colorWhite; bracketBaseColor = colorWhite; UI_ADD_STR_CALLBACK( barMaterial, sdUICrosshairInfo, OnBarMaterialChanged ); UI_ADD_STR_CALLBACK( barFillMaterial, sdUICrosshairInfo, OnBarFillMaterialChanged ); UI_ADD_STR_CALLBACK( barLineMaterial, sdUICrosshairInfo, OnBarLineMaterialChanged ); UI_ADD_STR_CALLBACK( bracketLeftMaterial, sdUICrosshairInfo, OnLeftBracketMaterialChanged ); UI_ADD_STR_CALLBACK( bracketRightMaterial, sdUICrosshairInfo, OnRightBracketMaterialChanged ); UI_ADD_STR_CALLBACK( damageMaterial, sdUICrosshairInfo, OnDamageMaterialChanged ); waypointTextFadeTime = 0; bracketFadeTime = 0; dockedIcons.SetNum( 6 ); } /* ================ sdUICrosshairInfo::~sdUICrosshairInfo ================ */ sdUICrosshairInfo::~sdUICrosshairInfo( void ) { DisconnectGlobalCallbacks(); } /* ============ sdUICrosshairInfo::GetRepeaterCrosshairInfo ============ */ const sdCrosshairInfo& sdUICrosshairInfo::GetRepeaterCrosshairInfo( void ) { return gameLocal.playerView.CalcRepeaterCrosshairInfo(); } /* ============ sdUICrosshairInfo::DrawCrosshairInfo ============ */ void sdUICrosshairInfo::DrawCrosshairInfo( idPlayer* player ) { const float width = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); const sdCrosshairInfo& info = gameLocal.serverIsRepeater ? GetRepeaterCrosshairInfo() : player->GetCrosshairInfoDirect(); if( info.IsValid() && ( TestCrosshairInfoFlag( CF_CROSSHAIR ) || g_radialMenuStyle.GetInteger() == 1 ) ) { float w, h; idVec4 color; idVec2 point( ( width * 0.5f ), ( SCREEN_HEIGHT * 0.5f ) + 30.0f ); for( int i = 0; i < info.GetNumLines(); i++ ) { const chInfoLine_t& line = info.GetLine( i ); switch( line.type ) { case CI_TEXT: { sdBounds2D rect( 0.0f, point.y, width, lineTextHeight ); color = line.foreColor; color[ 3 ] *= info.GetAlpha(); sdUIWindow::DrawText( line.text.c_str(), color, lineTextScale, rect, cachedFontHandle, DTF_CENTER | DTF_BOTTOM | DTF_SINGLELINE | DTF_DROPSHADOW ); point.y += lineTextHeight + 4.0f; break; } case CI_BAR: { w = line.xy.x; h = line.xy.y; color = borderColor;//line.backColor; color[ 3 ] *= info.GetAlpha(); idVec4 rect( point.x - ( w * 0.5f ), point.y, w, h ); DrawThreeHorizontalParts( rect, color, vec2_one, crosshairParts[ CP_BAR_FILL_LEFT ], crosshairParts[ CP_BAR_FILL_CENTER ], crosshairParts[ CP_BAR_FILL_RIGHT ] ); color = line.foreColor; color[ 3 ] *= info.GetAlpha(); sdBounds2D clipRect( rect.x, rect.y, rect.z * line.frac, rect.w ); deviceContext->PushClipRect( clipRect ); DrawHorizontalProgress( rect, color, vec2_one, crosshairParts[ CP_BAR_LEFT ], crosshairParts[ CP_BAR_CENTER ], crosshairParts[ CP_BAR_RIGHT ] ); deviceContext->PopClipRect(); color = barLineColor; color[ 3 ] *= info.GetAlpha(); DrawThreeHorizontalParts( rect, color, vec2_one, crosshairParts[ CP_BAR_LINE_LEFT ], crosshairParts[ CP_BAR_LINE_CENTER ], crosshairParts[ CP_BAR_LINE_RIGHT ] ); point.y += h + 4.0f; break; } case CI_IMAGE: { color = line.foreColor; color[ 3 ] *= info.GetAlpha(); deviceContext->DrawMaterial( point.x - ( line.xy.x * 0.5f ), point.y, line.xy.x, line.xy.y, line.material, color ); point.y += line.xy.y + 4.0f; break; } } } } } /* ============ sdUICrosshairInfo::DrawDamageIndicators ============ */ void sdUICrosshairInfo::DrawDamageIndicators( idPlayer* player ) { const float width = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); static idWinding2D drawWinding; float w = g_damageIndicatorWidth.GetFloat(); float h = g_damageIndicatorHeight.GetFloat(); float x = -( w * 0.5f ); float y = -( h * 0.5f ) - g_damageIndicatorHeight.GetFloat(); float viewYaw = player->renderView.viewaxis.ToAngles().yaw; float fadeTime = SEC2MS( g_damageIndicatorFadeTime.GetFloat() ); idMat2 matrix; for( int i = 0; i < player->damageEvents.Num(); i++ ) { const idPlayer::damageEvent_t& event = player->damageEvents[ i ]; if( event.hitTime <= 0 ) { continue; } idVec4 color; if ( event.hitDamage > 0 ) { color.ToVec3() = sdTypeFromString< idVec3 >( g_damageIndicatorColor.GetString() ); } else { color.ToVec3() = sdTypeFromString< idVec3 >( g_repairIndicatorColor.GetString() ); } color.w = 1.0f - static_cast< float > ( gameLocal.time - event.hitTime ) / fadeTime; if ( event.hitDamage > 0 ) { color.w *= g_damageIndicatorAlphaScale.GetFloat(); } else { color.w *= g_repairIndicatorAlphaScale.GetFloat(); } if( color.w <= 0.0f || color.w > 1.0f ) { continue; } uiDrawPart_t* part = NULL; if( event.hitDamage <= 20.0f ) { part = &crosshairParts[ CP_SMALL_DAMAGE ]; } else if( event.hitDamage <= 40.0f ) { part = &crosshairParts[ CP_MEDIUM_DAMAGE ]; } else { part = &crosshairParts[ CP_LARGE_DAMAGE ]; } drawWinding.Clear(); drawWinding.AddPoint( x, y, part->mi.st0.x, part->mi.st0.y ); drawWinding.AddPoint( x + w, y, part->mi.st1.x, part->mi.st0.y ); drawWinding.AddPoint( x + w, y + h, part->mi.st1.x, part->mi.st1.y ); drawWinding.AddPoint( x, y + h, part->mi.st0.x, part->mi.st1.y ); float angle = event.hitAngle; if ( event.updateDirection ) { angle -= viewYaw; } matrix.Rotation( DEG2RAD( angle ) ); for( int i = 0; i < drawWinding.GetNumPoints(); i++ ) { idVec2& pt = drawWinding[ i ]; pt *= matrix; pt.x += width * 0.5f; pt.y += SCREEN_HEIGHT * 0.5f; } deviceContext->DrawWindingMaterial( drawWinding, part->mi.material, color ); } } /* ============ sdUICrosshairInfo::DrawLocal ============ */ void sdUICrosshairInfo::DrawLocal() { if( g_showCrosshairInfo.GetInteger() != 1 ) { return; } idPlayer* player = gameLocal.GetLocalViewPlayer(); if ( player == NULL ) { if ( gameLocal.serverIsRepeater ) { DrawCrosshairInfo( NULL ); } return; } idEntity* entity = DrawContextEntity( player ); DrawWayPoints( entity ); DrawEntityIcons(); DrawPlayerIcons2D(); ClearInactiveDockIcons(); DrawDockedIcons(); DrawCrosshairInfo( player ); DrawDamageIndicators( player ); } /* ============ sdUICrosshairInfo::ApplyLayout ============ */ void sdUICrosshairInfo::ApplyLayout() { if( windowState.recalculateLayout ) { BeginLayout(); lineTextHeight = deviceContext->GetFontHeight( cachedFontHandle, lineTextScale ); EndLayout(); } sdUIObject::ApplyLayout(); } /* ============ sdUICrosshairInfo::FindFunction ============ */ const sdUICrosshairInfo::CrosshairInfoTemplateFunction* sdUICrosshairInfo::FindFunction( const char* name ) { sdUICrosshairInfo::CrosshairInfoTemplateFunction** ptr; return crosshairInfoFunctions.Get( name, &ptr ) ? *ptr : NULL; } /* ============ sdUICrosshairInfo::GetFunction ============ */ sdUIFunctionInstance* sdUICrosshairInfo::GetFunction( const char* name ) { const CrosshairInfoTemplateFunction* function = sdUICrosshairInfo::FindFunction( name ); if ( !function ) { return sdUIWindow::GetFunction( name ); } return new sdUITemplateFunctionInstance< sdUICrosshairInfo, sdUITemplateFunctionInstance_IdentifierCrosshairInfo >( this, function ); } /* ============ sdUICrosshairInfo::RunNamedMethod ============ */ bool sdUICrosshairInfo::RunNamedMethod( const char* name, sdUIFunctionStack& stack ) { const CrosshairInfoTemplateFunction* func = sdUICrosshairInfo::FindFunction( name ); if ( !func ) { return sdUIWindow::RunNamedMethod( name, stack ); } CALL_MEMBER_FN_PTR( this, func->GetFunction() )( stack ); return true; } /* ============ sdUICrosshairInfo::InitFunctions ============ */ void sdUICrosshairInfo::InitFunctions() { sdDeclGUI::AddDefine( va( "CF_CROSSHAIR %i", CF_CROSSHAIR ) ); sdDeclGUI::AddDefine( va( "CF_PLAYER_ICONS %i", CF_PLAYER_ICONS ) ); sdDeclGUI::AddDefine( va( "CF_TASKS %i", CF_TASKS ) ); sdDeclGUI::AddDefine( va( "CF_OBJ_MIS %i", CF_OBJ_MIS ) ); } /* ============ sdUICrosshairInfo::OnBarMaterialChanged ============ */ void sdUICrosshairInfo::OnBarMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_BAR_LEFT ], "b", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_CENTER ], "c", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_RIGHT ], "e", newValue ); } /* ============ sdUICrosshairInfo::OnBarFillMaterialChanged ============ */ void sdUICrosshairInfo::OnBarFillMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_BAR_FILL_LEFT ], "l", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_FILL_CENTER ], "c", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_FILL_RIGHT ], "r", newValue ); } /* ============ sdUICrosshairInfo::OnBarLineMaterialChanged ============ */ void sdUICrosshairInfo::OnBarLineMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_BAR_LINE_LEFT ], "l", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_LINE_CENTER ], "c", newValue ); GetUI()->SetupPart( crosshairParts[ CP_BAR_LINE_RIGHT ], "r", newValue ); } /* ============ sdUICrosshairInfo::EndLevelLoad ============ */ void sdUICrosshairInfo::EndLevelLoad() { sdUIWindow::EndLevelLoad(); sdUserInterfaceLocal::LookupPartSizes( crosshairParts.Begin(), crosshairParts.Num() ); } /* ============ sdUICrosshairInfo::OnDamageMaterialChanged ============ */ void sdUICrosshairInfo::OnDamageMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_SMALL_DAMAGE ], "s", newValue ); GetUI()->SetupPart( crosshairParts[ CP_MEDIUM_DAMAGE ], "m", newValue ); GetUI()->SetupPart( crosshairParts[ CP_LARGE_DAMAGE ], "l", newValue ); } /* ============ sdUICrosshairInfo::OnLeftBracketMaterialChanged ============ */ void sdUICrosshairInfo::OnLeftBracketMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_L_BRACKET_TOP ], "t", newValue ); GetUI()->SetupPart( crosshairParts[ CP_L_BRACKET_CENTER ], "c", newValue ); GetUI()->SetupPart( crosshairParts[ CP_L_BRACKET_BOTTOM ], "b", newValue ); } /* ============ sdUICrosshairInfo::OnRightBracketMaterialChanged ============ */ void sdUICrosshairInfo::OnRightBracketMaterialChanged( const idStr& oldValue, const idStr& newValue ) { GetUI()->SetupPart( crosshairParts[ CP_R_BRACKET_TOP ], "t", newValue ); GetUI()->SetupPart( crosshairParts[ CP_R_BRACKET_CENTER ], "c", newValue ); GetUI()->SetupPart( crosshairParts[ CP_R_BRACKET_BOTTOM ], "b", newValue ); } /* =============== sdUICrosshairInfo::DrawEntityIcons ============== */ void sdUICrosshairInfo::DrawEntityIcons( void ) { idPlayer* viewPlayer = gameLocal.GetLocalViewPlayer(); if ( viewPlayer == NULL ) { return; } // TODO: Add crosshair info flag like for player icons? const float width = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); sdWorldToScreenConverter converter( gameLocal.playerView.GetCurrentView() ); // find out how many icons we need to draw int numIcons = 0; for ( idLinkList< idEntity >* node = gameLocal.GetIconEntities(); node; node = node->NextNode() ) { idEntity* ent = node->Owner(); assert( ent->GetDisplayIconInterface() ); if ( ent->GetDisplayIconInterface()->HasIcon( viewPlayer, converter ) ) { numIcons++; } } if ( numIcons == 0 ) { return; } // allocate temporary storage for the icons & fill them in sdEntityDisplayIconInfo* icons = ( sdEntityDisplayIconInfo* )_alloca16( numIcons * sizeof( sdEntityDisplayIconInfo ) ); int iconUpto = 0; for ( idLinkList< idEntity >* node = gameLocal.GetIconEntities(); node; node = node->NextNode() ) { idEntity* ent = node->Owner(); assert( ent->GetDisplayIconInterface() ); if ( ent->GetDisplayIconInterface()->GetEntityDisplayIconInfo( viewPlayer, converter, icons[ iconUpto ] ) ) { iconUpto++; } } // sort them by distance sdEntityDisplayIconInfo** sortedIcons = ( sdEntityDisplayIconInfo** )_alloca16( numIcons * sizeof( sdEntityDisplayIconInfo* ) ); for ( int i = 0; i < numIcons; i++ ) { sortedIcons[ i ] = &icons[ i ]; } sdQuickSort( sortedIcons, sortedIcons + numIcons, sdEntityDisplayIconInfo::SortByDistance ); // now we can draw for ( int i = 0; i < numIcons; i++ ) { sdEntityDisplayIconInfo* icon = sortedIcons[ i ]; bool draw = true; const float MARGIN_OFFSET = ( width * 0.0125f ); const float LEFT_MARGIN = MARGIN_OFFSET; const float RIGHT_MARGIN = width - MARGIN_OFFSET; if ( icon->origin.x + icon->size.x <= LEFT_MARGIN || icon->origin.x - icon->size.x >= RIGHT_MARGIN ) { draw = false; } if ( draw ) { float height = icon->origin.y; idVec2 screenPos( icon->origin.x - ( icon->size.x * 0.5f ), height - icon->size.y ); deviceContext->DrawMaterial( screenPos.x, screenPos.y, icon->size.x, icon->size.y, icon->material, icon->color ); } } } /* =============== sdUICrosshairInfo::DrawPlayerIcons2D ============== */ void sdUICrosshairInfo::DrawPlayerIcons2D( void ) { idPlayer* viewPlayer = gameLocal.GetLocalViewPlayer(); if ( viewPlayer == NULL ) { return; } if ( viewPlayer->IsBeingBriefed() ) { return; } if ( !TestCrosshairInfoFlag( CF_PLAYER_ICONS ) ) { return; } const float width = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); sdWorldToScreenConverter converter( gameLocal.playerView.GetCurrentView() ); // fill the icons list up sdPlayerDisplayIconList playerIcons; for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* other = gameLocal.GetClient( i ); if ( other == NULL || other->IsInLimbo() ) { continue; } if ( !pm_thirdPerson.GetBool() && viewPlayer == other ) { continue; } sdPlayerDisplayIcon icon; if ( other->GetPlayerIcon( viewPlayer, icon, converter ) ) { playerIcons.Append( icon ); } } // sort the list by distance sdPlayerDisplayIconPtrList sortedPlayerIcons; sortedPlayerIcons.SetNum( playerIcons.Num() ); for ( int i = 0; i < playerIcons.Num(); i++ ) { sortedPlayerIcons[ i ] = &playerIcons[ i ]; } sortedPlayerIcons.Sort( sdPlayerDisplayIcon::SortByDistance ); for ( int i = 0; i < sortedPlayerIcons.Num(); i++ ) { sdPlayerDisplayIcon* icon = sortedPlayerIcons[ i ]; bool draw = true; const float HALF_ICON_WIDTH = icon->icon.size.x * 0.5f; const float MARGIN_OFFSET = ( width * 0.0125f ); const float LEFT_MARGIN = MARGIN_OFFSET; const float RIGHT_MARGIN = width - MARGIN_OFFSET; sdBounds2D iconBounds; iconBounds.Clear(); iconBounds.AddPoint( icon->icon.size * 0.5f ); iconBounds.AddPoint( icon->arrowIcon.size * 0.5f ); if ( icon->offScreenIcon.material != NULL ) { if ( icon->origin.x - iconBounds.GetMaxs().x <= LEFT_MARGIN ) { draw = false; idVec2 startOrg( LEFT_MARGIN, icon->origin.y ); idVec2 endOrg( startOrg.x, SCREEN_HEIGHT * 0.5f ); MakeDockIcon( icon->player, true, startOrg, endOrg, icon->offScreenIcon, icon->icon ); } else if ( ( icon->origin.x + iconBounds.GetMaxs().x ) >= RIGHT_MARGIN ) { draw = false; idVec2 startOrg( RIGHT_MARGIN, icon->origin.y ); idVec2 endOrg( startOrg.x, SCREEN_HEIGHT * 0.5f ); MakeDockIcon( icon->player, false, startOrg, endOrg, icon->offScreenIcon, icon->icon ); } } else { if ( icon->origin.x <= LEFT_MARGIN || ( icon->origin.x + icon->icon.size.x ) >= RIGHT_MARGIN ) { draw = false; } } if ( draw ) { float height = icon->origin.y; if ( icon->arrowIcon.material != NULL ) { idVec2 screenPos( icon->origin.x - ( icon->arrowIcon.size.x * 0.5f ), height - icon->arrowIcon.size.y ); deviceContext->DrawMaterial( screenPos.x, screenPos.y, icon->arrowIcon.size.x, icon->arrowIcon.size.y, icon->arrowIcon.material, icon->arrowIcon.color ); height -= icon->arrowIcon.size.y; } idVec2 screenPos( icon->origin.x - ( icon->icon.size.x * 0.5f ), height - icon->icon.size.y ); deviceContext->DrawMaterial( screenPos.x, screenPos.y, icon->icon.size.x, icon->icon.size.y, icon->icon.material, icon->icon.color ); } } } /* ============ sdUICrosshairInfo::DrawWayPoints ============ */ void sdUICrosshairInfo::DrawWayPoints( idEntity* exclude ) { if ( !g_showWayPoints.GetBool() && !sdWayPointManager::GetInstance().GetShowWayPoints() ) { return; } if ( !TestCrosshairInfoFlag( CF_TASKS | CF_OBJ_MIS ) ) { return; } idVec4 color = colorWhite; idPlayer* localPlayer = gameLocal.GetLocalPlayer(); if ( localPlayer == NULL || localPlayer->IsSpectating() ) { return; } idPlayer* viewPlayer = gameLocal.GetLocalViewPlayer(); if ( viewPlayer == NULL ) { return; } if ( viewPlayer->IsBeingBriefed() ) { return; } const float screenWidth = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); const float MARGIN_SIZE = ( screenWidth * 0.0125f ); const float LEFT_MARGIN = MARGIN_SIZE; const float RIGHT_MARGIN = screenWidth - MARGIN_SIZE; idVec3 org = viewPlayer->renderView.vieworg; idMat3 axes = viewPlayer->renderView.viewaxis; sdWorldToScreenConverter converter( gameLocal.playerView.GetCurrentView() ); sdWayPoint* highlightedWaypoint = NULL; idVec2 bestDistanceFromCenter( idMath::INFINITY, idMath::INFINITY ); idVec2 bestDrawCenter; float bestIconWidth; float bestWidth; float bestDistSqr; float bestLeft; for ( sdWayPoint* wayPoint = sdWayPointManager::GetInstance().GetFirstActiveWayPoint(); wayPoint != NULL; wayPoint = wayPoint->GetActiveNode().Next() ) { if ( !wayPoint->IsValid() ) { continue; } idBounds bounds = wayPoint->GetBounds(); idVec3 size = bounds.GetSize(); idVec3 center = bounds.GetCenter(); float smallestAxis = idMath::INFINITY; float largestAxis = -idMath::INFINITY; for( int i = 0; i < 3; i++ ) { if( size[ i ] > largestAxis ) { largestAxis = size[ i ]; } if( size[ i ] < smallestAxis ) { smallestAxis = size[ i ]; } } //bounds.GetMins().Set( -smallestAxis, -smallestAxis, -smallestAxis ); //bounds.GetMaxs().Set( smallestAxis, smallestAxis, smallestAxis ); //bounds.TranslateSelf( center - bounds.GetCenter() ); idMat3 axes = wayPoint->GetOrientation(); idVec3 origin = wayPoint->GetPosition(); float distSquare = ( org - origin ).LengthSqr(); float fadeAlpha = distSquare / Square( largestAxis * 2.0f ) - 0.25f; float playerDist = ( viewPlayer->GetPhysics()->GetOrigin() - wayPoint->GetPosition() ).LengthFast() - g_waypointDistanceMin.GetFloat(); float waypointDist = g_waypointDistanceMax.GetFloat() - g_waypointDistanceMin.GetFloat(); float rangePct = waypointDist != 0.0f ? idMath::ClampFloat( 0.0f, 1.0f, playerDist / waypointDist ) : 0.0f; float iconWidth = g_waypointSizeMin.GetFloat() + ( g_waypointSizeMax.GetFloat() - g_waypointSizeMin.GetFloat() ) * ( 1.0f - rangePct ); if ( fadeAlpha <= 0.0f ) { continue; } fadeAlpha = idMath::ClampFloat( 0.0f, 1.0f, fadeAlpha ); sdBounds2D screenBounds; converter.Transform( bounds, axes, origin, screenBounds ); float left = screenBounds.GetLeft(); float right = screenBounds.GetRight(); float top = screenBounds.GetTop(); float bottom = screenBounds.GetBottom(); float width = screenBounds.GetWidth(); float height = screenBounds.GetHeight(); float visiblePct = idMath::ClampFloat( 0.0f, 100.0f, 100.0f * ( ( width * height ) / ( screenWidth * SCREEN_HEIGHT ) ) ); // update the visibility of the waypoint idVec3 temp; bool isVisible = true; if ( wayPoint->ShouldCheckLineOfSight() ){ isVisible = wayPoint->GetOwner()->IsVisibleOcclusionTest(); // ao: implicitly know it's a task if there's LOS check if ( !TestCrosshairInfoFlag( CF_TASKS ) ) { isVisible = false; } } else { if ( !wayPoint->IsVisible() ) { isVisible = false; } // ao: implicitly know it's a mission/objective if there's no LOS check if ( !TestCrosshairInfoFlag( CF_OBJ_MIS ) ) { isVisible = false; } } if ( !isVisible ) { visiblePct = 0.0f; } wayPoint->SetVisible( isVisible ); // update highlighting if ( visiblePct > 0.05f && visiblePct < 40.f ) { if ( !wayPoint->IsHighlightActive() ) { wayPoint->MakeHighlightActive(); } } else { if ( wayPoint->IsHighlightActive() ) { wayPoint->MakeHighlightInActive(); } } idVec4 color = colorWhite; if ( wayPoint->GetFlags() & sdWayPoint::WF_TEAMCOLOR ) { idEntity* owner = wayPoint->GetOwner(); if ( owner != NULL ) { teamAllegiance_t allegiance = owner->GetEntityAllegiance( viewPlayer ); if ( allegiance != TA_NEUTRAL ) { color = idEntity::GetColorForAllegiance( allegiance ); } } } float factor = wayPoint->GetActiveFraction() * fadeAlpha; color[ 3 ] *= factor; idVec4 highlightColor = color; highlightColor[ 3 ] *= wayPoint->GetHighLightActiveFraction() * fadeAlpha; float scaleFactor = ( 1.0f - ( visiblePct / 0.33f ) ); converter.Transform( bounds, axes, origin, screenBounds ); idVec2 drawCenter = screenBounds.GetCenter(); float resize = 0.0f; if ( gameLocal.ToGuiTime( gameLocal.time ) < wayPoint->GetFlashEndTime() ) { resize = 2.0f; } idVec2 drawSize( iconWidth + resize, iconWidth + resize ); idVec2 drawHalfSize = drawSize * 0.5f; idVec2 drawOrigin = drawCenter - ( drawSize * 0.5f ); drawOrigin.y = idMath::ClampFloat( MARGIN_SIZE, SCREEN_HEIGHT - MARGIN_SIZE - drawSize.y, drawOrigin.y ); idVec2 distanceFromCenter( idMath::Fabs( ( screenWidth * 0.5f ) - drawCenter.x ) / ( screenWidth * 0.5f ), idMath::Fabs( ( SCREEN_HEIGHT * 0.5f ) - drawCenter.y ) / ( SCREEN_HEIGHT * 0.5f ) ); idVec4 localColor = colorWhite; if ( gameLocal.ToGuiTime( gameLocal.time ) < wayPoint->GetFlashEndTime() ) { localColor.w = idMath::Cos( gameLocal.ToGuiTime( gameLocal.time ) * 0.02f ) * 0.5f + 0.5f; } localColor.w = localColor.w * fadeAlpha * g_waypointAlphaScale.GetFloat(); iconInfo_t icon; icon.material = wayPoint->GetOffScreenMaterial(); icon.color = idVec4( color.x, color.y, color.z, idMath::ClampFloat( 0.35f, 1.0f, color.w * scaleFactor ) ); icon.size = drawHalfSize; if ( drawOrigin.x <= LEFT_MARGIN ) { if ( localColor.w >= 0.01f ) { idVec2 startOrg( LEFT_MARGIN, drawOrigin.y ); idVec2 endOrg( startOrg.x, SCREEN_HEIGHT * 0.5f ); if ( isVisible ) { MakeDockIcon( wayPoint, true, endOrg, endOrg, icon, iconInfo_t() ); } } } else if ( ( drawOrigin.x + drawSize.x ) >= RIGHT_MARGIN ) { if ( localColor.w >= 0.01f ) { idVec2 startOrg( RIGHT_MARGIN, drawOrigin.y ); idVec2 endOrg( startOrg.x, SCREEN_HEIGHT * 0.5f ); if ( isVisible ) { MakeDockIcon( wayPoint, false, endOrg, endOrg, icon, iconInfo_t() ); } } } else { if ( isVisible ) { if ( wayPoint->GetMaterial() != NULL ) { float time = wayPoint->Selected(); if( time >= 0.0f ) { float value = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, static_cast< float >( gameLocal.time - time ) / HIGHLIGHT_TIME ); deviceContext->SetRegister( 4, value ); } else { deviceContext->SetRegister( 4, 0.0f ); } deviceContext->DrawMaterial( drawOrigin.x, drawOrigin.y, drawSize.x, drawSize.y, wayPoint->GetMaterial(), localColor ); } } } if( isVisible ) { sdDockedScreenIcon* icon = FindDockedIcon( wayPoint ); if( icon != NULL ) { icon->highlightedTime = wayPoint->Selected(); } } if( isVisible && distanceFromCenter.x < 0.1f && distanceFromCenter.y < 0.1f && distanceFromCenter.x < bestDistanceFromCenter.x && distanceFromCenter.y < bestDistanceFromCenter.y ) { waypointTextFadeTime = gameLocal.time; bestDistanceFromCenter = distanceFromCenter; highlightedWaypoint = wayPoint; bestDrawCenter = drawCenter; bestIconWidth = iconWidth; bestWidth = width; bestDistSqr = distSquare; bestLeft = left; } if ( wayPoint->Bracketed() ) { if( exclude != NULL && wayPoint->GetOwner() == exclude ) { if( idMath::ClampFloat( 0.0f, 1.0f, idMath::Fabs( bracketFadeTime - gameLocal.time ) / ( float )sdWayPoint::ACTIVATE_TIME ) <= idMath::FLT_EPSILON ) { continue; } } idVec3 bracketOrigin = wayPoint->GetBracketPosition(); converter.Transform( bounds, axes, bracketOrigin, screenBounds ); DrawBrackets( screenBounds, highlightColor ); } } static const int TEXT_FADE_TIME = SEC2MS( 1 ); const sdCrosshairInfo& info = localPlayer->GetCrosshairInfoDirect(); if( !info.IsValid() && highlightedWaypoint != NULL && ( gameLocal.time - waypointTextFadeTime ) <= TEXT_FADE_TIME ) { static const int TEXT_SCALE = 12; idVec4 colorLocal = colorWhite; colorLocal[ 3 ] *= 1.0f - ( static_cast< float >( gameLocal.time - waypointTextFadeTime ) / TEXT_FADE_TIME ); idWStrList args; args.SetNum( 2 ); args[ 0 ] = va( L"%i", ( int )InchesToMetres( idMath::Sqrt( bestDistSqr ) ) ); args[ 1 ] = common->LocalizeText( "game/meters" ); idWStr text = common->LocalizeText( "game/range", args ); sdBounds2D rect( bestLeft, bestDrawCenter.y + bestIconWidth, bestWidth, lineTextHeight ); sdUIWindow::DrawText( text.c_str(), colorLocal, TEXT_SCALE, rect, cachedFontHandle, DTF_CENTER | DTF_BOTTOM | DTF_SINGLELINE | DTF_DROPSHADOW ); rect.TranslateSelf( 0.0f, lineTextHeight ); sdUIWindow::DrawText( highlightedWaypoint->GetText(), colorLocal, TEXT_SCALE, rect, cachedFontHandle, DTF_CENTER | DTF_BOTTOM | DTF_SINGLELINE | DTF_DROPSHADOW ); } } /* ============ sdUICrosshairInfo::DrawDockedIcons ============ */ void sdUICrosshairInfo::DrawDockedIcons( void ) { idVec2 offsets( 0.f, 0.f ); const float DOCK_SPACING = 32.0f; for ( int i = 0; i < dockedIcons.Num(); i++ ) { const sdDockedScreenIcon& dock = dockedIcons[ i ]; if ( !dock.active ) { continue; } if ( dock.arrowIcon.material == NULL ) { assert( false ); continue; } float percent = static_cast< float >( gameLocal.time - ( dock.startTime + 300 ) ) / sdDockedScreenIcon::DOCK_ANIMATION_MS; percent = idMath::ClampFloat( 0.0f, 1.0f, percent ); idVec2 drawOrigin = Lerp( dock.startOrigin, dock.endOrigin, percent ); int index = dock.flipped ? 1 : 0; drawOrigin.y += offsets[ index ]; float scale = dock.flipped ? 1.0f : -1.0f; float xOffset = 0.f; float yOffset = 0.f; float time = dock.highlightedTime; float value = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, dock.highlightedTime < 0 ? 1.0f : ( static_cast< float >( gameLocal.time - time ) / HIGHLIGHT_TIME ) ); float drawScale = 1.0f + ( 1.0f * value ); deviceContext->SetRegister( 4, value ); if ( dock.icon.material != NULL ) { idVec2 screenPos = drawOrigin; screenPos.y -= ( DOCK_SPACING * 0.5f ); if ( !dock.flipped ) { screenPos.x -= dock.icon.size.x; } float drawScale = 1.0f; deviceContext->DrawMaterial( screenPos.x, screenPos.y, dock.icon.size.x * drawScale, dock.icon.size.y * drawScale, dock.icon.material, dock.icon.color, 1.f ); xOffset -= scale * dock.arrowIcon.size.x; yOffset = dock.icon.size.y; } idVec2 screenPos = drawOrigin; if ( dock.icon.material != NULL ) { screenPos.y -= ( DOCK_SPACING * 0.5f - dock.icon.size.y * 0.25f ); } else { screenPos.y -= ( DOCK_SPACING * 0.5f ); } if ( !dock.flipped ) { screenPos.x -= dock.arrowIcon.size.x; } deviceContext->DrawMaterial( xOffset + screenPos.x, screenPos.y, dock.arrowIcon.size.x * drawScale, dock.arrowIcon.size.y * drawScale, dock.arrowIcon.material, dock.arrowIcon.color, scale ); if ( dock.arrowIcon.size.y > yOffset ) { yOffset = dock.arrowIcon.size.y; } offsets[ index ] += yOffset * 1.25f; } } /* ============ sdUICrosshairInfo::FindDockedIcon ============ */ sdDockedScreenIcon* sdUICrosshairInfo::FindDockedIcon( void* tag ) { for ( int i = 0; i < dockedIcons.Num(); i++ ) { if ( !dockedIcons[ i ].active ) { continue; } if ( dockedIcons[ i ].tag == tag ) { return &dockedIcons[ i ]; } } return NULL; } /* ============ sdUICrosshairInfo::FindFreeDockedIcon ============ */ sdDockedScreenIcon* sdUICrosshairInfo::FindFreeDockedIcon( void ) { for( int i = 0; i < dockedIcons.Num(); i++ ) { if ( !dockedIcons[ i ].active ) { return &dockedIcons[ i ]; } } return NULL; } /* ============ sdUICrosshairInfo::MakeDockIcon ============ */ void sdUICrosshairInfo::MakeDockIcon( void* tag, bool flipped, const idVec2& startOrg, const idVec2& endOrg, const iconInfo_t& arrowInfo, const iconInfo_t& iconInfo ) { sdDockedScreenIcon* icon = FindDockedIcon( tag ); if ( icon == NULL ) { icon = FindFreeDockedIcon(); if ( icon == NULL ) { return; } icon->tag = tag; icon->startTime = gameLocal.time; icon->active = true; } icon->icon = iconInfo; icon->arrowIcon = arrowInfo; icon->endOrigin = endOrg; icon->flipped = flipped; icon->lastUpdateTime = gameLocal.time; icon->startOrigin = startOrg; // Can get some very large y values if ( icon->startOrigin.y > SCREEN_HEIGHT ) { icon->startOrigin.y = SCREEN_HEIGHT + icon->icon.size.y; } else if ( icon->startOrigin.y + icon->icon.size.y < 0 ) { icon->startOrigin.y = -icon->icon.size.y; } } /* ============ sdUICrosshairInfo::ClearInactiveDockIcons ============ */ void sdUICrosshairInfo::ClearInactiveDockIcons( void ) { for ( int i = 0; i < dockedIcons.Num(); i++ ) { if ( dockedIcons[ i ].lastUpdateTime != gameLocal.time ) { dockedIcons[ i ].active = false; } } } /* ============ sdUICrosshairInfo::DrawBrackets ============ */ void sdUICrosshairInfo::DrawBrackets( const sdBounds2D& bounds, const idVec4& color ) { const float screenWidth = SCREEN_WIDTH * 1.0f / deviceContext->GetAspectRatioCorrection(); float left = bounds.GetLeft(); float right = bounds.GetRight(); float top = bounds.GetTop(); float bottom = bounds.GetBottom(); float width = bounds.GetWidth(); float height = bounds.GetHeight(); idVec2 center( left + width * 0.5f, top + height * 0.5f ); float size = width * 0.25f; // // left half // idVec4 rect; rect.Set( left - crosshairParts[ CP_L_BRACKET_TOP ].width, top, crosshairParts[ CP_L_BRACKET_TOP ].width, height ); DrawThreeVerticalParts( rect, color, vec2_one, crosshairParts[ CP_L_BRACKET_TOP ], crosshairParts[ CP_L_BRACKET_CENTER ], crosshairParts[ CP_L_BRACKET_BOTTOM ] ); rect.Set( right, top, crosshairParts[ CP_L_BRACKET_TOP ].width, height ); DrawThreeVerticalParts( rect, color, vec2_one, crosshairParts[ CP_R_BRACKET_TOP ], crosshairParts[ CP_R_BRACKET_CENTER ], crosshairParts[ CP_R_BRACKET_BOTTOM ] ); } /* ============ sdUICrosshairInfo::DrawContextEntity ============ */ idEntity* sdUICrosshairInfo::DrawContextEntity( idPlayer* player ) { return NULL; /* jrad - disabled this, as it was confusing idEntity* entity = gameLocal.localPlayerProperties.GetContextEntity(); // see if we've moused over something that can be acted on if( entity == NULL ) { const sdProgram::sdFunction* callback = player->GetScriptFunction( "ContextUpdateOrder" ); if ( callback != NULL ) { sdScriptHelper h; h.Push( "" ); player->CallNonBlockingScriptEvent( callback, h ); const char* returnVal = h.GetReturnedString(); idStrList list; idSplitStringIntoList( list, returnVal, "|" ); bool enabled; if ( list.Num() >= 5 ) { enabled = sdTypeFromString< bool >( list[ 4 ] ); } else { enabled = true; } if ( enabled && list.Num() >= 6 ) { enabled &= list[ 5 ].Icmp( "Invalid" ) != 0; } if( enabled ) { const sdCrosshairInfo& info = player->GetCrosshairInfoDirect(); entity = info.GetEntity(); bracketFadeTime = gameLocal.time; lastBracketEntity = entity; } } } // try using a cached one if( entity == NULL ) { entity = lastBracketEntity; } if( entity != NULL ) { idVec3 org = entity->GetPhysics()->GetOrigin(); idMat3 axes = entity->GetAxis(); idBounds bounds = entity->GetPhysics()->GetBounds(); sdWorldToScreenConverter converter( gameLocal.playerView.GetCurrentView() ); sdBounds2D bracketBounds; converter.Transform( bounds, axes, org, bracketBounds ); idVec4 color = colorWhite; color.w = idMath::ClampFloat( 0.0f, 1.0f, idMath::Fabs( bracketFadeTime - gameLocal.time ) / ( float )sdWayPoint::ACTIVATE_TIME ); color.w = 1.0f - color.w; DrawBrackets( bracketBounds, color ); } return entity; */ }