You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1340 lines
30 KiB
1340 lines
30 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: MapOverview.cpp: implementation of the CMapOverview class. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "mapoverview.h" |
|
#include <vgui/ISurface.h> |
|
#include <vgui/ILocalize.h> |
|
#include <filesystem.h> |
|
#include <KeyValues.h> |
|
#include <convar.h> |
|
#include "mathlib/mathlib.h" |
|
#include <game/client/iviewport.h> |
|
#include <igameresources.h> |
|
#include "gamevars_shared.h" |
|
#include "spectatorgui.h" |
|
#include "c_playerresource.h" |
|
#include "view.h" |
|
|
|
#include "clientmode.h" |
|
#include <vgui_controls/AnimationController.h> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar overview_health( "overview_health", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Show player's health in map overview.\n" ); |
|
ConVar overview_names ( "overview_names", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Show player's names in map overview.\n" ); |
|
ConVar overview_tracks( "overview_tracks", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Show player's tracks in map overview.\n" ); |
|
ConVar overview_locked( "overview_locked", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Locks map angle, doesn't follow view angle.\n" ); |
|
ConVar overview_alpha( "overview_alpha", "1.0", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Overview map translucency.\n" ); |
|
|
|
IMapOverviewPanel *g_pMapOverview = NULL; // we assume only one overview is created |
|
|
|
static int AdjustValue( int curValue, int targetValue, int amount ) |
|
{ |
|
if ( curValue > targetValue ) |
|
{ |
|
curValue -= amount; |
|
|
|
if ( curValue < targetValue ) |
|
curValue = targetValue; |
|
} |
|
else if ( curValue < targetValue ) |
|
{ |
|
curValue += amount; |
|
|
|
if ( curValue > targetValue ) |
|
curValue = targetValue; |
|
} |
|
|
|
return curValue; |
|
} |
|
|
|
CON_COMMAND( overview_zoom, "Sets overview map zoom: <zoom> [<time>] [rel]" ) |
|
{ |
|
if ( !g_pMapOverview || args.ArgC() < 2 ) |
|
return; |
|
|
|
float zoom = Q_atof( args[ 1 ] ); |
|
|
|
float time = 0; |
|
|
|
if ( args.ArgC() >= 3 ) |
|
time = Q_atof( args[ 2 ] ); |
|
|
|
if ( args.ArgC() == 4 ) |
|
zoom *= g_pMapOverview->GetZoom(); |
|
|
|
// We are going to store their zoom pick as the resultant overview size that it sees. This way, the value will remain |
|
// correct even on a different map that has a different intrinsic zoom. |
|
float desiredViewSize = 0.0f; |
|
desiredViewSize = (zoom * OVERVIEW_MAP_SIZE * g_pMapOverview->GetFullZoom()) / g_pMapOverview->GetMapScale(); |
|
g_pMapOverview->SetPlayerPreferredViewSize( desiredViewSize ); |
|
|
|
if( !g_pMapOverview->AllowConCommandsWhileAlive() ) |
|
{ |
|
C_BasePlayer *localPlayer = CBasePlayer::GetLocalPlayer(); |
|
if( localPlayer && CBasePlayer::GetLocalPlayer()->IsAlive() ) |
|
return;// Not allowed to execute commands while alive |
|
else if( localPlayer && localPlayer->GetObserverMode() == OBS_MODE_DEATHCAM ) |
|
return;// In the death cam spiral counts as alive |
|
} |
|
|
|
g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( g_pMapOverview->GetAsPanel(), "zoom", zoom, 0.0, time, vgui::AnimationController::INTERPOLATOR_LINEAR ); |
|
} |
|
|
|
CON_COMMAND( overview_mode, "Sets overview map mode off,small,large: <0|1|2>" ) |
|
{ |
|
if ( !g_pMapOverview ) |
|
return; |
|
|
|
int mode; |
|
|
|
if ( args.ArgC() < 2 ) |
|
{ |
|
// toggle modes |
|
mode = g_pMapOverview->GetMode() + 1; |
|
|
|
if ( mode > CMapOverview::MAP_MODE_FULL ) |
|
mode = CMapOverview::MAP_MODE_OFF; |
|
} |
|
else |
|
{ |
|
// set specific mode |
|
mode = Q_atoi( args[ 1 ] ); |
|
} |
|
|
|
if( mode != CMapOverview::MAP_MODE_RADAR ) |
|
g_pMapOverview->SetPlayerPreferredMode( mode ); |
|
|
|
if( !g_pMapOverview->AllowConCommandsWhileAlive() ) |
|
{ |
|
C_BasePlayer *localPlayer = CBasePlayer::GetLocalPlayer(); |
|
if( localPlayer && CBasePlayer::GetLocalPlayer()->IsAlive() ) |
|
return;// Not allowed to execute commands while alive |
|
else if( localPlayer && localPlayer->GetObserverMode() == OBS_MODE_DEATHCAM ) |
|
return;// In the death cam spiral counts as alive |
|
} |
|
|
|
g_pMapOverview->SetMode( mode ); |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
// Construction/Destruction |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
using namespace vgui; |
|
|
|
CMapOverview::CMapOverview( const char *pElementName ) : BaseClass( NULL, pElementName ), CHudElement( pElementName ) |
|
{ |
|
SetParent( g_pClientMode->GetViewport()->GetVPanel() ); |
|
|
|
SetBounds( 0,0, 256, 256 ); |
|
SetBgColor( Color( 0,0,0,100 ) ); |
|
SetPaintBackgroundEnabled( true ); |
|
ShowPanel( false ); |
|
|
|
// Make sure we actually have the font... |
|
vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); |
|
|
|
m_hIconFont = pScheme->GetFont( "DefaultSmall" ); |
|
|
|
m_nMapTextureID = -1; |
|
m_MapKeyValues = NULL; |
|
|
|
m_MapOrigin = Vector( 0, 0, 0 ); |
|
m_fMapScale = 1.0f; |
|
m_bFollowAngle = false; |
|
SetMode( MAP_MODE_OFF ); |
|
|
|
m_fZoom = 3.0f; |
|
m_MapCenter = Vector2D( 512, 512 ); |
|
m_ViewOrigin = Vector2D( 512, 512 ); |
|
m_fViewAngle = 0; |
|
m_fTrailUpdateInterval = 1.0f; |
|
|
|
m_bShowNames = true; |
|
m_bShowHealth = true; |
|
m_bShowTrails = true; |
|
|
|
m_flChangeSpeed = 1000; |
|
m_flIconSize = 64.0f; |
|
|
|
m_ObjectCounterID = 1; |
|
|
|
Reset(); |
|
|
|
Q_memset( m_Players, 0, sizeof(m_Players) ); |
|
|
|
InitTeamColorsAndIcons(); |
|
|
|
g_pMapOverview = this; // for cvars access etc |
|
} |
|
|
|
void CMapOverview::Init( void ) |
|
{ |
|
// register for events as client listener |
|
ListenForGameEvent( "game_newmap" ); |
|
ListenForGameEvent( "round_start" ); |
|
ListenForGameEvent( "player_connect_client" ); |
|
ListenForGameEvent( "player_info" ); |
|
ListenForGameEvent( "player_team" ); |
|
ListenForGameEvent( "player_spawn" ); |
|
ListenForGameEvent( "player_death" ); |
|
ListenForGameEvent( "player_disconnect" ); |
|
} |
|
|
|
void CMapOverview::InitTeamColorsAndIcons() |
|
{ |
|
Q_memset( m_TeamIcons, 0, sizeof(m_TeamIcons) ); |
|
Q_memset( m_TeamColors, 0, sizeof(m_TeamColors) ); |
|
Q_memset( m_ObjectIcons, 0, sizeof(m_ObjectIcons) ); |
|
|
|
m_TextureIDs.RemoveAll(); |
|
} |
|
|
|
int CMapOverview::AddIconTexture(const char *filename) |
|
{ |
|
int index = m_TextureIDs.Find( filename ); |
|
|
|
if ( m_TextureIDs.IsValidIndex( index ) ) |
|
{ |
|
// already known, return texture ID |
|
return m_TextureIDs.Element(index); |
|
} |
|
|
|
index = surface()->CreateNewTextureID(); |
|
surface()->DrawSetTextureFile( index , filename, true, false); |
|
|
|
m_TextureIDs.Insert( filename, index ); |
|
|
|
return index; |
|
} |
|
|
|
void CMapOverview::ApplySchemeSettings(vgui::IScheme *scheme) |
|
{ |
|
BaseClass::ApplySchemeSettings( scheme ); |
|
|
|
SetBgColor( Color( 0,0,0,100 ) ); |
|
SetPaintBackgroundEnabled( true ); |
|
} |
|
|
|
CMapOverview::~CMapOverview() |
|
{ |
|
if ( m_MapKeyValues ) |
|
m_MapKeyValues->deleteThis(); |
|
|
|
g_pMapOverview = NULL; |
|
|
|
//TODO release Textures ? clear lists |
|
} |
|
|
|
void CMapOverview::UpdatePlayers() |
|
{ |
|
if ( !g_PR ) |
|
return; |
|
|
|
// first disable all players health |
|
for ( int i=0; i<MAX_PLAYERS; i++ ) |
|
{ |
|
m_Players[i].health = 0; |
|
m_Players[i].team = TEAM_SPECTATOR; |
|
} |
|
|
|
for ( int i = 1; i<= gpGlobals->maxClients; i++) |
|
{ |
|
// update from global player resources |
|
if ( g_PR && g_PR->IsConnected(i) ) |
|
{ |
|
MapPlayer_t *player = &m_Players[i-1]; |
|
|
|
player->health = g_PR->GetHealth( i ); |
|
|
|
if ( !g_PR->IsAlive( i ) ) |
|
{ |
|
player->health = 0; |
|
} |
|
|
|
if ( player->team != g_PR->GetTeam( i ) ) |
|
{ |
|
player->team = g_PR->GetTeam( i ); |
|
player->icon = m_TeamIcons[ GetIconNumberFromTeamNumber(player->team) ]; |
|
player->color = m_TeamColors[ GetIconNumberFromTeamNumber(player->team) ]; |
|
} |
|
} |
|
|
|
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
// don't update if player is dormant |
|
if ( pPlayer->IsDormant() ) |
|
continue; |
|
|
|
// update position of active players in our PVS |
|
Vector position = pPlayer->EyePosition(); |
|
QAngle angles = pPlayer->EyeAngles(); |
|
|
|
SetPlayerPositions( i-1, position, angles ); |
|
} |
|
} |
|
|
|
void CMapOverview::UpdatePlayerTrails() |
|
{ |
|
if ( m_fNextTrailUpdate > m_fWorldTime ) |
|
return; |
|
|
|
m_fNextTrailUpdate = m_fWorldTime + 1.0f; // update once a second |
|
|
|
for (int i=0; i<MAX_PLAYERS; i++) |
|
{ |
|
MapPlayer_t *p = &m_Players[i]; |
|
|
|
// no trails for spectators or dead players |
|
if ( (p->team <= TEAM_SPECTATOR) || (p->health <= 0) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// move old trail points |
|
for ( int j=MAX_TRAIL_LENGTH-1; j>0; j--) |
|
{ |
|
p->trail[j]=p->trail[j-1]; |
|
} |
|
|
|
p->trail[0] = WorldToMap ( p->position ); |
|
} |
|
} |
|
|
|
void CMapOverview::UpdateFollowEntity() |
|
{ |
|
if ( m_nFollowEntity != 0 ) |
|
{ |
|
C_BaseEntity *ent = ClientEntityList().GetEnt( m_nFollowEntity ); |
|
|
|
if ( ent ) |
|
{ |
|
Vector position = MainViewOrigin(); // Use MainViewOrigin so SourceTV works in 3rd person |
|
QAngle angle = ent->EyeAngles(); |
|
|
|
if ( m_nFollowEntity <= MAX_PLAYERS ) |
|
{ |
|
SetPlayerPositions( m_nFollowEntity-1, position, angle ); |
|
} |
|
|
|
SetCenter( WorldToMap(position) ); |
|
SetAngle( angle[YAW] ); |
|
} |
|
} |
|
else |
|
{ |
|
SetCenter( Vector2D(OVERVIEW_MAP_SIZE/2,OVERVIEW_MAP_SIZE/2) ); |
|
SetAngle( 0 ); |
|
} |
|
} |
|
|
|
void CMapOverview::Paint() |
|
{ |
|
UpdateSizeAndPosition(); |
|
|
|
UpdateFollowEntity(); |
|
|
|
UpdateObjects(); |
|
|
|
UpdatePlayers(); |
|
|
|
UpdatePlayerTrails(); |
|
|
|
DrawMapTexture(); |
|
|
|
DrawMapPlayerTrails(); |
|
|
|
DrawObjects(); |
|
|
|
DrawMapPlayers(); |
|
|
|
DrawCamera(); |
|
|
|
BaseClass::Paint(); |
|
} |
|
|
|
bool CMapOverview::CanPlayerBeSeen(MapPlayer_t *player) |
|
{ |
|
C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
if ( !localPlayer || !player ) |
|
return false; |
|
|
|
// don't draw ourself |
|
if ( localPlayer->GetUserID() == (player->userid) ) |
|
return false; |
|
|
|
// Invalid guy. |
|
if( player->position == Vector(0,0,0) ) |
|
return false; |
|
|
|
// if local player is on spectator team, he can see everyone |
|
if ( localPlayer->GetTeamNumber() <= TEAM_SPECTATOR ) |
|
return true; |
|
|
|
// we never track unassigned or real spectators |
|
if ( player->team <= TEAM_SPECTATOR ) |
|
return false; |
|
|
|
// if observer is an active player, check mp_forcecamera: |
|
|
|
if ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) |
|
return false; |
|
|
|
if ( mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) |
|
{ |
|
// true if both players are on the same team |
|
return (localPlayer->GetTeamNumber() == player->team ); |
|
} |
|
|
|
// by default we can see all players |
|
return true; |
|
} |
|
|
|
/// allows mods to restrict health |
|
/// Note: index is 0-based |
|
bool CMapOverview::CanPlayerHealthBeSeen(MapPlayer_t *player) |
|
{ |
|
C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
if ( !localPlayer ) |
|
return false; |
|
|
|
// real spectators can see everything |
|
if ( localPlayer->GetTeamNumber() <= TEAM_SPECTATOR ) |
|
return true; |
|
|
|
if ( mp_forcecamera.GetInt() != OBS_ALLOW_ALL ) |
|
{ |
|
// if forcecamera is on, only show health for teammates |
|
return ( localPlayer->GetTeamNumber() == player->team ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// usually name rule is same as health rule |
|
bool CMapOverview::CanPlayerNameBeSeen(MapPlayer_t *player) |
|
{ |
|
return CanPlayerHealthBeSeen( player ); |
|
} |
|
|
|
void CMapOverview::SetPlayerPositions(int index, const Vector &position, const QAngle &angle) |
|
{ |
|
MapPlayer_t *p = &m_Players[index]; |
|
|
|
p->angle = angle; |
|
p->position = position; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: shows/hides the buy menu |
|
//----------------------------------------------------------------------------- |
|
void CMapOverview::ShowPanel(bool bShow) |
|
{ |
|
SetVisible( bShow ); |
|
} |
|
|
|
void CMapOverview::OnThink( void ) |
|
{ |
|
if ( NeedsUpdate() ) |
|
{ |
|
Update(); |
|
m_fNextUpdateTime = gpGlobals->curtime + 0.2f; // update 5 times a second |
|
} |
|
} |
|
|
|
bool CMapOverview::NeedsUpdate( void ) |
|
{ |
|
return m_fNextUpdateTime < gpGlobals->curtime; |
|
} |
|
|
|
void CMapOverview::Update( void ) |
|
{ |
|
// update settings |
|
m_bShowNames = overview_names.GetBool() && ( GetMode() != MAP_MODE_RADAR ); |
|
m_bShowHealth = overview_health.GetBool() && ( GetMode() != MAP_MODE_RADAR ); |
|
m_bFollowAngle = ( GetMode() != MAP_MODE_RADAR && !overview_locked.GetBool() ) || ( GetMode() == MAP_MODE_RADAR && !IsRadarLocked() ); |
|
m_fTrailUpdateInterval = overview_tracks.GetInt() && ( GetMode() != MAP_MODE_RADAR ); |
|
|
|
m_fWorldTime = gpGlobals->curtime; |
|
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
if ( !pPlayer ) |
|
return; |
|
|
|
int specmode = GetSpectatorMode(); |
|
|
|
if ( specmode == OBS_MODE_IN_EYE || specmode == OBS_MODE_CHASE ) |
|
{ |
|
// follow target |
|
SetFollowEntity( GetSpectatorTarget() ); |
|
} |
|
else |
|
{ |
|
// follow ourself otherwise |
|
SetFollowEntity( pPlayer->entindex() ); |
|
} |
|
} |
|
|
|
void CMapOverview::Reset( void ) |
|
{ |
|
m_fNextUpdateTime = 0; |
|
} |
|
|
|
void CMapOverview::SetData(KeyValues *data) |
|
{ |
|
m_fZoom = data->GetFloat( "zoom", m_fZoom ); |
|
m_nFollowEntity = data->GetInt( "entity", m_nFollowEntity ); |
|
} |
|
|
|
|
|
CMapOverview::MapPlayer_t* CMapOverview::GetPlayerByUserID( int userID ) |
|
{ |
|
for (int i=0; i<MAX_PLAYERS; i++) |
|
{ |
|
MapPlayer_t *player = &m_Players[i]; |
|
|
|
if ( player->userid == userID ) |
|
return player; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
bool CMapOverview::IsInPanel(Vector2D &pos) |
|
{ |
|
int x,y,w,t; |
|
|
|
GetBounds( x,y,w,t ); |
|
|
|
return ( pos.x >= 0 && pos.x < w && pos.y >= 0 && pos.y < t ); |
|
} |
|
|
|
void CMapOverview::DrawMapTexture() |
|
{ |
|
// now draw a box around the outside of this panel |
|
int x0, y0, x1, y1; |
|
int wide, tall; |
|
|
|
GetSize(wide, tall); |
|
x0 = 0; y0 = 0; x1 = wide - 2; y1 = tall - 2 ; |
|
|
|
if ( m_nMapTextureID < 0 ) |
|
return; |
|
|
|
Vertex_t points[4] = |
|
{ |
|
Vertex_t( MapToPanel ( Vector2D(0,0) ), Vector2D(0,0) ), |
|
Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,0) ), Vector2D(1,0) ), |
|
Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,OVERVIEW_MAP_SIZE-1) ), Vector2D(1,1) ), |
|
Vertex_t( MapToPanel ( Vector2D(0,OVERVIEW_MAP_SIZE-1) ), Vector2D(0,1) ) |
|
}; |
|
|
|
int alpha = 255.0f * overview_alpha.GetFloat(); clamp( alpha, 1, 255 ); |
|
|
|
surface()->DrawSetColor( 255,255,255, alpha ); |
|
surface()->DrawSetTexture( m_nMapTextureID ); |
|
surface()->DrawTexturedPolygon( 4, points ); |
|
} |
|
|
|
void CMapOverview::DrawMapPlayerTrails() |
|
{ |
|
if ( m_fTrailUpdateInterval <= 0 ) |
|
return; // turned off |
|
|
|
for (int i=0; i<MAX_PLAYERS; i++) |
|
{ |
|
MapPlayer_t *player = &m_Players[i]; |
|
|
|
if ( !CanPlayerBeSeen(player) ) |
|
continue; |
|
|
|
player->trail[0] = WorldToMap ( player->position ); |
|
|
|
for ( int iTrail=0; iTrail<(MAX_TRAIL_LENGTH-1); iTrail++) |
|
{ |
|
if ( player->trail[iTrail +1].x == 0 && player->trail[iTrail +1].y == 0 ) |
|
break; |
|
|
|
Vector2D pos1 = MapToPanel( player->trail[iTrail] ); |
|
Vector2D pos2 = MapToPanel( player->trail[iTrail +1] ); |
|
|
|
int intensity = 255 - float(255.0f * iTrail ) / MAX_TRAIL_LENGTH; |
|
|
|
Vector2D dist = pos1 - pos2; |
|
|
|
// don't draw too long lines, player probably teleported |
|
if ( dist.LengthSqr() < (128*128) ) |
|
{ |
|
surface()->DrawSetColor( player->color[0], player->color[1], player->color[2], intensity ); |
|
surface()->DrawLine( pos1.x, pos1.y, pos2.x, pos2.y ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CMapOverview::DrawObjects( ) |
|
{ |
|
surface()->DrawSetTextFont( m_hIconFont ); |
|
|
|
for (int i=0; i<m_Objects.Count(); i++) |
|
{ |
|
MapObject_t *obj = &m_Objects[i]; |
|
|
|
const char *text = NULL; |
|
|
|
if ( Q_strlen(obj->name) > 0 ) |
|
text = obj->name; |
|
|
|
float flAngle = obj->angle[YAW]; |
|
|
|
if ( obj->flags & MAP_OBJECT_ALIGN_TO_MAP && m_bRotateMap ) |
|
{ |
|
if ( m_bRotateMap ) |
|
flAngle = 90; |
|
else |
|
flAngle = 0; |
|
} |
|
|
|
MapObject_t tempObj = *obj; |
|
tempObj.angle[YAW] = flAngle; |
|
tempObj.text = text; |
|
tempObj.statusColor = obj->color; |
|
|
|
// draw icon |
|
if ( !DrawIcon( &tempObj ) ) |
|
continue; |
|
} |
|
} |
|
|
|
bool CMapOverview::DrawIcon( MapObject_t *obj ) |
|
{ |
|
int textureID = obj->icon; |
|
Vector pos = obj->position; |
|
float scale = obj->size; |
|
float angle = obj->angle[YAW]; |
|
const char *text = obj->text; |
|
Color *textColor = &obj->color; |
|
float status = obj->status; |
|
Color *statusColor = &obj->statusColor; |
|
|
|
Vector offset; offset.z = 0; |
|
|
|
Vector2D pospanel = WorldToMap( pos ); |
|
pospanel = MapToPanel( pospanel ); |
|
|
|
if ( !IsInPanel( pospanel ) ) |
|
return false; // player is not within overview panel |
|
|
|
offset.x = -scale; offset.y = scale; |
|
VectorYawRotate( offset, angle, offset ); |
|
Vector2D pos1 = WorldToMap( pos + offset ); |
|
|
|
offset.x = scale; offset.y = scale; |
|
VectorYawRotate( offset, angle, offset ); |
|
Vector2D pos2 = WorldToMap( pos + offset ); |
|
|
|
offset.x = scale; offset.y = -scale; |
|
VectorYawRotate( offset, angle, offset ); |
|
Vector2D pos3 = WorldToMap( pos + offset ); |
|
|
|
offset.x = -scale; offset.y = -scale; |
|
VectorYawRotate( offset, angle, offset ); |
|
Vector2D pos4 = WorldToMap( pos + offset ); |
|
|
|
Vertex_t points[4] = |
|
{ |
|
Vertex_t( MapToPanel ( pos1 ), Vector2D(0,0) ), |
|
Vertex_t( MapToPanel ( pos2 ), Vector2D(1,0) ), |
|
Vertex_t( MapToPanel ( pos3 ), Vector2D(1,1) ), |
|
Vertex_t( MapToPanel ( pos4 ), Vector2D(0,1) ) |
|
}; |
|
|
|
surface()->DrawSetColor( 255, 255, 255, 255 ); |
|
surface()->DrawSetTexture( textureID ); |
|
surface()->DrawTexturedPolygon( 4, points ); |
|
|
|
int d = GetPixelOffset( scale); |
|
|
|
pospanel.y += d + 4; |
|
|
|
if ( status >=0.0f && status <= 1.0f && statusColor ) |
|
{ |
|
// health bar is 50x3 pixels |
|
surface()->DrawSetColor( 0,0,0,255 ); |
|
surface()->DrawFilledRect( pospanel.x-d, pospanel.y-1, pospanel.x+d, pospanel.y+1 ); |
|
|
|
int length = (float)(d*2)*status; |
|
surface()->DrawSetColor( statusColor->r(), statusColor->g(), statusColor->b(), 255 ); |
|
surface()->DrawFilledRect( pospanel.x-d, pospanel.y-1, pospanel.x-d+length, pospanel.y+1 ); |
|
|
|
pospanel.y += 3; |
|
} |
|
|
|
if ( text && textColor ) |
|
{ |
|
wchar_t iconText[ MAX_PLAYER_NAME_LENGTH*2 ]; |
|
|
|
g_pVGuiLocalize->ConvertANSIToUnicode( text, iconText, sizeof( iconText ) ); |
|
|
|
int wide, tall; |
|
surface()->GetTextSize( m_hIconFont, iconText, wide, tall ); |
|
|
|
int x = pospanel.x-(wide/2); |
|
int y = pospanel.y; |
|
|
|
// draw black shadow text |
|
surface()->DrawSetTextColor( 0, 0, 0, 255 ); |
|
surface()->DrawSetTextPos( x+1, y ); |
|
surface()->DrawPrintText( iconText, wcslen(iconText) ); |
|
|
|
// draw name in color |
|
surface()->DrawSetTextColor( textColor->r(), textColor->g(), textColor->b(), 255 ); |
|
surface()->DrawSetTextPos( x, y ); |
|
surface()->DrawPrintText( iconText, wcslen(iconText) ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int CMapOverview::GetPixelOffset( float height ) |
|
{ |
|
Vector2D pos2 = WorldToMap( Vector( height,0,0) ); |
|
pos2 = MapToPanel( pos2 ); |
|
|
|
Vector2D pos3 = WorldToMap( Vector(0,0,0) ); |
|
pos3 = MapToPanel( pos3 ); |
|
|
|
int a = pos2.y-pos3.y; |
|
int b = pos2.x-pos3.x; |
|
|
|
return (int)sqrt((float)(a*a+b*b)); // number of panel pixels for "scale" units in world |
|
} |
|
|
|
void CMapOverview::DrawMapPlayers() |
|
{ |
|
surface()->DrawSetTextFont( m_hIconFont ); |
|
|
|
Color colorGreen( 0, 255, 0, 255 ); // health bar color |
|
|
|
for (int i=0; i<MAX_PLAYERS; i++) |
|
{ |
|
MapPlayer_t *player = &m_Players[i]; |
|
|
|
if ( !CanPlayerBeSeen( player ) ) |
|
continue; |
|
|
|
// don't draw dead players / spectators |
|
if ( player->health <= 0 ) |
|
continue; |
|
|
|
float status = -1; |
|
const char *name = NULL; |
|
|
|
if ( m_bShowNames && CanPlayerNameBeSeen( player ) ) |
|
name = player->name; |
|
|
|
if ( m_bShowHealth && CanPlayerHealthBeSeen( player ) ) |
|
status = player->health/100.0f; |
|
|
|
// convert from PlayerObject_t |
|
MapObject_t tempObj; |
|
memset( &tempObj, 0, sizeof(MapObject_t) ); |
|
tempObj.icon = player->icon; |
|
tempObj.position = player->position; |
|
tempObj.size = m_flIconSize; |
|
tempObj.angle = player->angle; |
|
tempObj.text = name; |
|
tempObj.color = player->color; |
|
tempObj.status = status; |
|
tempObj.statusColor = colorGreen; |
|
|
|
DrawIcon( &tempObj ); |
|
} |
|
} |
|
|
|
Vector2D CMapOverview::WorldToMap( const Vector &worldpos ) |
|
{ |
|
Vector2D offset( worldpos.x - m_MapOrigin.x, worldpos.y - m_MapOrigin.y); |
|
|
|
offset.x /= m_fMapScale; |
|
offset.y /= -m_fMapScale; |
|
|
|
return offset; |
|
} |
|
|
|
float CMapOverview::GetViewAngle( void ) |
|
{ |
|
float viewAngle = m_fViewAngle - 90.0f; |
|
|
|
if ( !m_bFollowAngle ) |
|
{ |
|
// We don't use fViewAngle. We just show straight at all times. |
|
if ( m_bRotateMap ) |
|
viewAngle = 90.0f; |
|
else |
|
viewAngle = 0.0f; |
|
} |
|
|
|
return viewAngle; |
|
} |
|
|
|
Vector2D CMapOverview::MapToPanel( const Vector2D &mappos ) |
|
{ |
|
int pwidth, pheight; |
|
Vector2D panelpos; |
|
float viewAngle = GetViewAngle(); |
|
|
|
GetSize(pwidth, pheight); |
|
|
|
Vector offset; |
|
offset.x = mappos.x - m_MapCenter.x; |
|
offset.y = mappos.y - m_MapCenter.y; |
|
offset.z = 0; |
|
|
|
VectorYawRotate( offset, viewAngle, offset ); |
|
|
|
// find the actual zoom from the animationvar m_fZoom and the map zoom scale |
|
float fScale = (m_fZoom * m_fFullZoom) / OVERVIEW_MAP_SIZE; |
|
|
|
offset.x *= fScale; |
|
offset.y *= fScale; |
|
|
|
panelpos.x = (pwidth * 0.5f) + (pheight * offset.x); |
|
panelpos.y = (pheight * 0.5f) + (pheight * offset.y); |
|
|
|
return panelpos; |
|
} |
|
|
|
void CMapOverview::SetTime( float time ) |
|
{ |
|
m_fWorldTime = time; |
|
} |
|
|
|
void CMapOverview::SetMap(const char * levelname) |
|
{ |
|
// Reset players and objects, even if the map is the same as the previous one |
|
m_Objects.RemoveAll(); |
|
|
|
m_fNextTrailUpdate = 0;// Set to 0 for immediate update. Our WorldTime var hasn't been updated to 0 for the new map yet |
|
m_fWorldTime = 0;// In release, we occasionally race and get this bug again if we gt a paint before an update. Reset this before the old value gets in to the timer. |
|
// Please note, UpdatePlayerTrails comes from PAINT, not UPDATE. |
|
|
|
InitTeamColorsAndIcons(); |
|
|
|
// load new KeyValues |
|
if ( m_MapKeyValues && Q_strcmp( levelname, m_MapKeyValues->GetName() ) == 0 ) |
|
{ |
|
return; // map didn't change |
|
} |
|
|
|
if ( m_MapKeyValues ) |
|
m_MapKeyValues->deleteThis(); |
|
|
|
m_MapKeyValues = new KeyValues( levelname ); |
|
|
|
char tempfile[MAX_PATH]; |
|
Q_snprintf( tempfile, sizeof( tempfile ), "resource/overviews/%s.txt", levelname ); |
|
|
|
if ( !m_MapKeyValues->LoadFromFile( g_pFullFileSystem, tempfile, "GAME" ) ) |
|
{ |
|
DevMsg( 1, "Error! CMapOverview::SetMap: couldn't load file %s.\n", tempfile ); |
|
m_nMapTextureID = -1; |
|
m_MapOrigin.x = 0; |
|
m_MapOrigin.y = 0; |
|
m_fMapScale = 1; |
|
m_bRotateMap = false; |
|
m_fFullZoom = 1; |
|
return; |
|
} |
|
|
|
// TODO release old texture ? |
|
|
|
m_nMapTextureID = surface()->CreateNewTextureID(); |
|
|
|
//if we have not uploaded yet, lets go ahead and do so |
|
surface()->DrawSetTextureFile( m_nMapTextureID, m_MapKeyValues->GetString("material"), true, false); |
|
|
|
int wide, tall; |
|
|
|
surface()->DrawGetTextureSize( m_nMapTextureID, wide, tall ); |
|
|
|
if ( wide != tall ) |
|
{ |
|
DevMsg( 1, "Error! CMapOverview::SetMap: map image must be a square.\n" ); |
|
m_nMapTextureID = -1; |
|
return; |
|
} |
|
|
|
m_MapOrigin.x = m_MapKeyValues->GetInt("pos_x"); |
|
m_MapOrigin.y = m_MapKeyValues->GetInt("pos_y"); |
|
m_fMapScale = m_MapKeyValues->GetFloat("scale", 1.0f); |
|
m_bRotateMap = m_MapKeyValues->GetInt("rotate")!=0; |
|
m_fFullZoom = m_MapKeyValues->GetFloat("zoom", 1.0f ); |
|
} |
|
|
|
void CMapOverview::ResetRound() |
|
{ |
|
for (int i=0; i<MAX_PLAYERS; i++) |
|
{ |
|
MapPlayer_t *p = &m_Players[i]; |
|
|
|
if ( p->team > TEAM_SPECTATOR ) |
|
{ |
|
p->health = 100; |
|
} |
|
|
|
Q_memset( p->trail, 0, sizeof(p->trail) ); |
|
|
|
p->position = Vector( 0, 0, 0 ); |
|
} |
|
|
|
m_Objects.RemoveAll(); |
|
} |
|
|
|
void CMapOverview::OnMousePressed( MouseCode code ) |
|
{ |
|
|
|
} |
|
|
|
void CMapOverview::DrawCamera() |
|
{ |
|
// draw a red center point |
|
surface()->DrawSetColor( 255,0,0,255 ); |
|
Vector2D center = MapToPanel( m_ViewOrigin ); |
|
surface()->DrawFilledRect( center.x-2, center.y-2, center.x+2, center.y+2); |
|
} |
|
|
|
void CMapOverview::FireGameEvent( IGameEvent *event ) |
|
{ |
|
const char * type = event->GetName(); |
|
|
|
if ( Q_strcmp(type, "game_newmap") == 0 ) |
|
{ |
|
SetMap( event->GetString("mapname") ); |
|
ResetRound(); |
|
} |
|
|
|
else if ( Q_strcmp(type, "round_start") == 0 ) |
|
{ |
|
ResetRound(); |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_connect_client") == 0 ) |
|
{ |
|
int index = event->GetInt("index"); // = entity index - 1 |
|
|
|
if ( index < 0 || index >= MAX_PLAYERS ) |
|
return; |
|
|
|
MapPlayer_t *player = &m_Players[index]; |
|
|
|
player->index = index; |
|
player->userid = event->GetInt("userid"); |
|
Q_strncpy( player->name, event->GetString("name","unknown"), sizeof(player->name) ); |
|
|
|
// Reset settings |
|
Q_memset( player->trail, 0, sizeof(player->trail) ); |
|
player->team = TEAM_UNASSIGNED; |
|
player->health = 0; |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_info") == 0 ) |
|
{ |
|
int index = event->GetInt("index"); // = entity index - 1 |
|
|
|
if ( index < 0 || index >= MAX_PLAYERS ) |
|
return; |
|
|
|
MapPlayer_t *player = &m_Players[index]; |
|
|
|
player->index = index; |
|
player->userid = event->GetInt("userid"); |
|
Q_strncpy( player->name, event->GetString("name","unknown"), sizeof(player->name) ); |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_team") == 0 ) |
|
{ |
|
MapPlayer_t *player = GetPlayerByUserID( event->GetInt("userid") ); |
|
|
|
if ( !player ) |
|
return; |
|
|
|
player->team = event->GetInt("team"); |
|
player->icon = m_TeamIcons[ GetIconNumberFromTeamNumber(player->team) ]; |
|
player->color = m_TeamColors[ GetIconNumberFromTeamNumber(player->team) ]; |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_death") == 0 ) |
|
{ |
|
MapPlayer_t *player = GetPlayerByUserID( event->GetInt("userid") ); |
|
|
|
if ( !player ) |
|
return; |
|
|
|
player->health = 0; |
|
Q_memset( player->trail, 0, sizeof(player->trail) ); // clear trails |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_spawn") == 0 ) |
|
{ |
|
MapPlayer_t *player = GetPlayerByUserID( event->GetInt("userid") ); |
|
|
|
if ( !player ) |
|
return; |
|
|
|
player->health = 100; |
|
Q_memset( player->trail, 0, sizeof(player->trail) ); // clear trails |
|
} |
|
|
|
else if ( Q_strcmp(type,"player_disconnect") == 0 ) |
|
{ |
|
MapPlayer_t *player = GetPlayerByUserID( event->GetInt("userid") ); |
|
|
|
if ( !player ) |
|
return; |
|
|
|
Q_memset( player, 0, sizeof(MapPlayer_t) ); // clear player field |
|
} |
|
} |
|
|
|
void CMapOverview::SetMode(int mode) |
|
{ |
|
m_flChangeSpeed = 0; // change size instantly |
|
|
|
if ( mode == MAP_MODE_OFF ) |
|
{ |
|
ShowPanel( false ); |
|
|
|
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapOff" ); |
|
} |
|
else if ( mode == MAP_MODE_INSET ) |
|
{ |
|
if( m_nMapTextureID == -1 ) |
|
{ |
|
SetMode( MAP_MODE_OFF ); |
|
return; |
|
} |
|
|
|
if ( m_nMode != MAP_MODE_OFF ) |
|
m_flChangeSpeed = 1000; // zoom effect |
|
|
|
C_BasePlayer *pPlayer = CBasePlayer::GetLocalPlayer(); |
|
|
|
if ( pPlayer ) |
|
SetFollowEntity( pPlayer->entindex() ); |
|
|
|
ShowPanel( true ); |
|
|
|
if ( mode != m_nMode && RunHudAnimations() ) |
|
{ |
|
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomToSmall" ); |
|
} |
|
} |
|
else if ( mode == MAP_MODE_FULL ) |
|
{ |
|
if( m_nMapTextureID == -1 ) |
|
{ |
|
SetMode( MAP_MODE_OFF ); |
|
return; |
|
} |
|
|
|
if ( m_nMode != MAP_MODE_OFF ) |
|
m_flChangeSpeed = 1000; // zoom effect |
|
|
|
SetFollowEntity( 0 ); |
|
|
|
ShowPanel( true ); |
|
|
|
if ( mode != m_nMode && RunHudAnimations() ) |
|
{ |
|
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomToLarge" ); |
|
} |
|
} |
|
|
|
// finally set mode |
|
m_nMode = mode; |
|
|
|
UpdateSizeAndPosition(); |
|
} |
|
|
|
bool CMapOverview::ShouldDraw( void ) |
|
{ |
|
return ( m_nMode != MAP_MODE_OFF ) && CHudElement::ShouldDraw(); |
|
} |
|
|
|
void CMapOverview::UpdateSizeAndPosition() |
|
{ |
|
if ( g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() ) |
|
{ |
|
int iScreenWide, iScreenTall; |
|
GetHudSize( iScreenWide, iScreenTall ); |
|
|
|
int iTopBarHeight = g_pSpectatorGUI->GetTopBarHeight(); |
|
int iBottomBarHeight = g_pSpectatorGUI->GetBottomBarHeight(); |
|
|
|
iScreenTall -= ( iTopBarHeight + iBottomBarHeight ); |
|
|
|
int x,y,w,h; |
|
GetBounds( x,y,w,h ); |
|
|
|
if ( y < iTopBarHeight ) |
|
y = iTopBarHeight; |
|
|
|
SetBounds( x,y,w,MIN(h,iScreenTall) ); |
|
} |
|
} |
|
|
|
void CMapOverview::SetCenter(const Vector2D &mappos) |
|
{ |
|
int width, height; |
|
|
|
GetSize( width, height); |
|
|
|
m_ViewOrigin = mappos; |
|
m_MapCenter = mappos; |
|
|
|
float fTwiceZoom = m_fZoom * m_fFullZoom * 2; |
|
|
|
width = height = OVERVIEW_MAP_SIZE / (fTwiceZoom); |
|
|
|
if( GetMode() != MAP_MODE_RADAR ) |
|
{ |
|
if ( m_MapCenter.x < width ) |
|
m_MapCenter.x = width; |
|
|
|
if ( m_MapCenter.x > (OVERVIEW_MAP_SIZE-width) ) |
|
m_MapCenter.x = (OVERVIEW_MAP_SIZE-width); |
|
|
|
if ( m_MapCenter.y < height ) |
|
m_MapCenter.y = height; |
|
|
|
if ( m_MapCenter.y > (OVERVIEW_MAP_SIZE-height) ) |
|
m_MapCenter.y = (OVERVIEW_MAP_SIZE-height); |
|
|
|
//center if in full map mode |
|
if ( m_fZoom <= 1.0 ) |
|
{ |
|
m_MapCenter.x = OVERVIEW_MAP_SIZE/2; |
|
m_MapCenter.y = OVERVIEW_MAP_SIZE/2; |
|
} |
|
} |
|
|
|
} |
|
|
|
void CMapOverview::SetFollowAngle(bool state) |
|
{ |
|
m_bFollowAngle = state; |
|
} |
|
|
|
void CMapOverview::SetFollowEntity(int entindex) |
|
{ |
|
m_nFollowEntity = entindex; |
|
} |
|
|
|
float CMapOverview::GetZoom( void ) |
|
{ |
|
return m_fZoom; |
|
} |
|
|
|
int CMapOverview::GetMode( void ) |
|
{ |
|
return m_nMode; |
|
} |
|
|
|
void CMapOverview::SetAngle(float angle) |
|
{ |
|
m_fViewAngle = angle; |
|
} |
|
|
|
void CMapOverview::ShowPlayerNames(bool state) |
|
{ |
|
m_bShowNames = state; |
|
} |
|
|
|
|
|
void CMapOverview::ShowPlayerHealth(bool state) |
|
{ |
|
m_bShowHealth = state; |
|
} |
|
|
|
void CMapOverview::ShowPlayerTracks(float seconds) |
|
{ |
|
m_fTrailUpdateInterval = seconds; |
|
} |
|
|
|
bool CMapOverview::SetTeamColor(int team, Color color) |
|
{ |
|
if ( team < 0 || team>= MAX_TEAMS ) |
|
return false; |
|
|
|
m_TeamColors[team] = color; |
|
|
|
return true; |
|
} |
|
|
|
CMapOverview::MapObject_t* CMapOverview::FindObjectByID(int objectID) |
|
{ |
|
for ( int i = 0; i < m_Objects.Count(); i++ ) |
|
{ |
|
if ( m_Objects[i].objectID == objectID ) |
|
return &m_Objects[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
int CMapOverview::AddObject( const char *icon, int entity, float timeToLive ) |
|
{ |
|
MapObject_t obj; Q_memset( &obj, 0, sizeof(obj) ); |
|
|
|
obj.objectID = m_ObjectCounterID++; |
|
obj.index = entity; |
|
obj.icon = AddIconTexture( icon ); |
|
obj.size = m_flIconSize; |
|
obj.status = -1; |
|
|
|
if ( timeToLive > 0 ) |
|
obj.endtime = gpGlobals->curtime + timeToLive; |
|
else |
|
obj.endtime = -1; |
|
|
|
m_Objects.AddToTail( obj ); |
|
|
|
return obj.objectID; |
|
} |
|
|
|
void CMapOverview::SetObjectText( int objectID, const char *text, Color color ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
if ( text ) |
|
{ |
|
Q_strncpy( obj->name, text, sizeof(obj->name) ); |
|
} |
|
else |
|
{ |
|
Q_memset( obj->name, 0, sizeof(obj->name) ); |
|
} |
|
|
|
obj->color = color; |
|
} |
|
|
|
void CMapOverview::SetObjectStatus( int objectID, float status, Color color ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
obj->status = status; |
|
obj->statusColor = color; |
|
} |
|
|
|
void CMapOverview::SetObjectIcon( int objectID, const char *icon, float size ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
obj->icon = AddIconTexture( icon ); |
|
obj->size = size; |
|
} |
|
|
|
void CMapOverview::SetObjectPosition( int objectID, const Vector &position, const QAngle &angle ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
obj->angle = angle; |
|
obj->position = position; |
|
} |
|
|
|
void CMapOverview::AddObjectFlags( int objectID, int flags ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
obj->flags |= flags; |
|
} |
|
|
|
void CMapOverview::SetObjectFlags( int objectID, int flags ) |
|
{ |
|
MapObject_t* obj = FindObjectByID( objectID ); |
|
|
|
if ( !obj ) |
|
return; |
|
|
|
obj->flags = flags; |
|
} |
|
|
|
void CMapOverview::RemoveObjectByIndex( int index ) |
|
{ |
|
for ( int i = 0; i < m_Objects.Count(); i++ ) |
|
{ |
|
if ( m_Objects[i].index == index ) |
|
{ |
|
m_Objects.Remove( i ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
void CMapOverview::RemoveObject( int objectID ) |
|
{ |
|
for ( int i = 0; i < m_Objects.Count(); i++ ) |
|
{ |
|
if ( m_Objects[i].objectID == objectID ) |
|
{ |
|
m_Objects.Remove( i ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
void CMapOverview::UpdateObjects() |
|
{ |
|
for ( int i = 0; i < m_Objects.Count(); i++ ) |
|
{ |
|
MapObject_t *obj = &m_Objects[i]; |
|
|
|
if ( obj->endtime > 0 && obj->endtime < gpGlobals->curtime ) |
|
{ |
|
m_Objects.Remove( i ); |
|
i--; |
|
continue; |
|
} |
|
|
|
if ( obj->index <= 0 ) |
|
continue; |
|
|
|
C_BaseEntity *entity = ClientEntityList().GetEnt( obj->index ); |
|
|
|
if ( !entity ) |
|
continue; |
|
|
|
obj->position = entity->GetAbsOrigin(); |
|
obj->angle = entity->GetAbsAngles(); |
|
} |
|
} |