Restore Stargate VGUI flow

This commit is contained in:
rowea 2026-05-31 13:29:37 +01:00
parent d2e77350e6
commit 2846c9e45d
12 changed files with 1586 additions and 118 deletions

View File

@ -14,7 +14,7 @@ $Configuration
{
$Compiler
{
$AdditionalIncludeDirectories "$BASE;hl2mp\ui,.\hl2mp,$SRCDIR\game\shared\hl2mp,.\hl2,.\hl2\elements,$SRCDIR\game\shared\hl2,$SRCDIR\game\shared\stargate"
$AdditionalIncludeDirectories "$BASE;hl2mp\ui,.\hl2mp,$SRCDIR\game\shared\hl2mp,.\hl2,.\hl2\elements,$SRCDIR\game\shared\hl2,$SRCDIR\game\shared\stargate,.\stargate"
$PreprocessorDefinitions "$BASE;HL2MP;HL2_CLIENT_DLL"
}
}
@ -176,6 +176,10 @@ $Project "Client (HL2MP)"
$File "$SRCDIR\game\shared\stargate\sg_weapons.cpp"
$File "$SRCDIR\game\shared\stargate\sg_mcp_debug_client.cpp"
$File "$SRCDIR\game\shared\stargate\sg_constants.h"
$File "stargate\sg_hud.cpp"
$File "stargate\sg_hud.h"
$File "stargate\sg_teammenu.cpp"
$File "stargate\sg_teammenu.h"
}
}
}

View File

@ -18,6 +18,7 @@
#include "hl2mpclientscoreboard.h"
#include "hl2mptextwindow.h"
#include "ienginevgui.h"
#include "stargate/sg_teammenu.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
@ -60,6 +61,12 @@ protected:
}
virtual IViewPortPanel *CreatePanelByName( const char *szPanelName );
virtual void CreateDefaultPanels( void )
{
BaseClass::CreateDefaultPanels();
AddNewPanel( CreatePanelByName( PANEL_TEAM ), "PANEL_TEAM" );
}
};
int ClientModeHL2MPNormal::GetDeathMessageStartHeight( void )
@ -83,12 +90,17 @@ IViewPortPanel* CHudViewport::CreatePanelByName( const char *szPanelName )
}
else if ( Q_strcmp(PANEL_SPECGUI, szPanelName) == 0 )
{
newpanel = new CHL2MPSpectatorGUI( this );
newpanel = new CHL2MPSpectatorGUI( this );
return newpanel;
}
else if ( Q_strcmp( PANEL_TEAM, szPanelName ) == 0 )
{
newpanel = new CSgTeamMenu( this );
return newpanel;
}
return BaseClass::CreatePanelByName( szPanelName );
return BaseClass::CreatePanelByName( szPanelName );
}
//-----------------------------------------------------------------------------

View File

@ -0,0 +1,570 @@
//========= StargateTC ============================================================//
//
// sg_hud.cpp - StargateTC custom HUD (1:1 port of the single GoldSrc client HUD
// element). Paints radar+compass+blips, vertical life/armor/stamina stat bars,
// naquada/progress bar, teammate portrait roster + local avatar, tactical alert,
// territory tickets, round timer, bomb key and the zat-stun overlay.
//
// Sprites come from the AI-enhanced VGUI materials at vgui/stargate/hud/* built
// by `stgs textures compile-hud`. Each is a 4x atlas padded to a power of two;
// DrawSpr() derives the content UV from the GoldSrc-native sprite size.
//=================================================================================//
#include "cbase.h"
#include "sg_hud.h"
#include "hud.h"
#include "hud_macros.h"
#include "iclientmode.h"
#include "c_baseplayer.h"
#include "view.h"
#include <vgui/ISurface.h>
#include <vgui_controls/Controls.h>
#include "vgui/IScheme.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
DECLARE_HUDELEMENT( CHudStargate );
DECLARE_HUD_MESSAGE( CHudStargate, SgAddPortrait );
DECLARE_HUD_MESSAGE( CHudStargate, SgReset );
DECLARE_HUD_MESSAGE( CHudStargate, SgAboutMe );
DECLARE_HUD_MESSAGE( CHudStargate, SgTogglePor );
DECLARE_HUD_MESSAGE( CHudStargate, SgSetRndTim );
DECLARE_HUD_MESSAGE( CHudStargate, SgNewRnd );
DECLARE_HUD_MESSAGE( CHudStargate, SgZatTouch );
DECLARE_HUD_MESSAGE( CHudStargate, SgRadio );
DECLARE_HUD_MESSAGE( CHudStargate, SgArmor );
DECLARE_HUD_MESSAGE( CHudStargate, SgStamina );
DECLARE_HUD_MESSAGE( CHudStargate, SgSubModel );
DECLARE_HUD_MESSAGE( CHudStargate, SgHasKey );
DECLARE_HUD_MESSAGE( CHudStargate, SgHideHud );
DECLARE_HUD_MESSAGE( CHudStargate, SgHudInfoMode );
DECLARE_HUD_MESSAGE( CHudStargate, SgTicketMode );
DECLARE_HUD_MESSAGE( CHudStargate, SgGtickets );
DECLARE_HUD_MESSAGE( CHudStargate, SgTtickets );
DECLARE_HUD_MESSAGE( CHudStargate, SgProgress );
DECLARE_HUD_MESSAGE( CHudStargate, SgBlip );
// GoldSrc cvars: view_radar / view_portraits.
ConVar sg_view_radar( "sg_view_radar", "1", FCVAR_ARCHIVE, "Show the StargateTC radar." );
ConVar sg_view_portraits( "sg_view_portraits", "1", FCVAR_ARCHIVE, "Show the teammate portrait roster." );
// Inject dummy state so every HUD block can be verified in a single screenshot.
ConVar sg_hud_test( "sg_hud_test", "0", FCVAR_CHEAT, "Force-populate all StargateTC HUD blocks for layout testing." );
// GoldSrc team coloring: Tau'ri side green, Goa'uld side red.
#define SG_TEAM_TAURI 2 // StargateTC: team 2 = Tau'ri
#define SG_TEAM_GOAULD 3 // StargateTC: team 3 = Goa'uld
static const char *SG_NUM[ 10 ] = {
"vgui/stargate/hud/num/0", "vgui/stargate/hud/num/1", "vgui/stargate/hud/num/2",
"vgui/stargate/hud/num/3", "vgui/stargate/hud/num/4", "vgui/stargate/hud/num/5",
"vgui/stargate/hud/num/6", "vgui/stargate/hud/num/7", "vgui/stargate/hud/num/8",
"vgui/stargate/hud/num/9",
};
#define SG_NUM_W 16
#define SG_NUM_H 24
//-----------------------------------------------------------------------------
CHudStargate::CHudStargate( const char *pElementName )
: CHudElement( pElementName ), BaseClass( NULL, "HudStargate" )
{
vgui::Panel *pParent = g_pClientMode->GetViewport();
SetParent( pParent );
SetPaintBackgroundEnabled( false );
SetPaintBorderEnabled( false );
// We manage our own visibility; only hide while fully HUD-suppressed.
SetHiddenBits( HIDEHUD_PLAYERDEAD );
}
//-----------------------------------------------------------------------------
void CHudStargate::Init( void )
{
HOOK_HUD_MESSAGE( CHudStargate, SgAddPortrait );
HOOK_HUD_MESSAGE( CHudStargate, SgReset );
HOOK_HUD_MESSAGE( CHudStargate, SgAboutMe );
HOOK_HUD_MESSAGE( CHudStargate, SgTogglePor );
HOOK_HUD_MESSAGE( CHudStargate, SgSetRndTim );
HOOK_HUD_MESSAGE( CHudStargate, SgNewRnd );
HOOK_HUD_MESSAGE( CHudStargate, SgZatTouch );
HOOK_HUD_MESSAGE( CHudStargate, SgRadio );
HOOK_HUD_MESSAGE( CHudStargate, SgArmor );
HOOK_HUD_MESSAGE( CHudStargate, SgStamina );
HOOK_HUD_MESSAGE( CHudStargate, SgSubModel );
HOOK_HUD_MESSAGE( CHudStargate, SgHasKey );
HOOK_HUD_MESSAGE( CHudStargate, SgHideHud );
HOOK_HUD_MESSAGE( CHudStargate, SgHudInfoMode );
HOOK_HUD_MESSAGE( CHudStargate, SgTicketMode );
HOOK_HUD_MESSAGE( CHudStargate, SgGtickets );
HOOK_HUD_MESSAGE( CHudStargate, SgTtickets );
HOOK_HUD_MESSAGE( CHudStargate, SgProgress );
HOOK_HUD_MESSAGE( CHudStargate, SgBlip );
Reset();
}
//-----------------------------------------------------------------------------
void CHudStargate::Reset( void )
{
m_iLocalTeam = 0;
m_iLocalId = 0;
m_flArmor = 0;
m_flStamina = 0;
m_iSubModel = 0;
m_bHasKey = false;
m_iHideBits = 0;
m_iInfoMode = 0;
m_bTicketMode = false;
m_iGtickets = 0;
m_iTtickets = 0;
m_iProgress = 0;
m_flRoundLen = 0;
m_flRoundStart = 0;
m_bZat = false;
m_flZatTime = 0;
m_flSlideOffset = 0;
Q_memset( m_Blips, 0, sizeof( m_Blips ) );
Q_memset( m_Portraits, 0, sizeof( m_Portraits ) );
}
void CHudStargate::VidInit( void )
{
// Texture ids are bound to the surface; drop the cache on resolution change.
m_TexIds.RemoveAll();
}
void CHudStargate::ApplySchemeSettings( vgui::IScheme *pScheme )
{
BaseClass::ApplySchemeSettings( pScheme );
int sw, sh;
surface()->GetScreenSize( sw, sh );
SetBounds( 0, 0, sw, sh );
}
//-----------------------------------------------------------------------------
bool CHudStargate::ShouldDraw( void )
{
if ( !engine->IsInGame() )
return false;
// NOTE: intentionally NOT gating on CHudElement::ShouldDraw() yet - the
// player sits in an unassigned/observer limbo until the team/class join
// flow exists, which would otherwise suppress the whole HUD. Re-add the
// CHudElement::ShouldDraw() gate once join/spawn is wired (Phase 7-8).
return true;
}
//-----------------------------------------------------------------------------
void CHudStargate::OnThink( void )
{
int sw, sh;
surface()->GetScreenSize( sw, sh );
if ( GetWide() != sw || GetTall() != sh )
SetBounds( 0, 0, sw, sh );
// Decay radar blips (GoldSrc: alpha -= 5 each frame).
for ( int i = 0; i < SG_MAX_BLIPS; i++ )
{
if ( m_Blips[ i ].alpha > 0 )
{
m_Blips[ i ].alpha -= 5;
if ( m_Blips[ i ].alpha < 0 )
m_Blips[ i ].alpha = 0;
}
}
}
//-----------------------------------------------------------------------------
// helpers
//-----------------------------------------------------------------------------
float CHudStargate::Scale( void ) const
{
int sw, sh;
surface()->GetScreenSize( sw, sh );
float s = sh / 600.0f; // GoldSrc HUD was authored ~640x480..800x600
return ( s < 1.0f ) ? 1.0f : s;
}
Color CHudStargate::TeamColor( int team ) const
{
if ( team == SG_TEAM_TAURI )
return Color( 0, 180, 0, 255 );
if ( team == SG_TEAM_GOAULD )
return Color( 210, 0, 0, 255 );
return Color( 200, 200, 200, 255 );
}
int CHudStargate::GetTex( const char *pszMat )
{
int idx = m_TexIds.Find( pszMat );
if ( idx != m_TexIds.InvalidIndex() )
return m_TexIds[ idx ];
int id = surface()->CreateNewTextureID();
surface()->DrawSetTextureFile( id, pszMat, true, false );
m_TexIds.Insert( pszMat, id );
return id;
}
static int SgNextPow2( int n ) { int p = 1; while ( p < n ) p <<= 1; return p; }
void CHudStargate::DrawSpr( const char *pszMat, float sx, float sy,
int nativeW, int nativeH, const Color &c )
{
// content texels = native * 4 (HAT upscale); atlas padded to next pow2.
const int texW = nativeW * 4, texH = nativeH * 4;
const float u = (float)texW / (float)SgNextPow2( texW );
const float v = (float)texH / (float)SgNextPow2( texH );
const float s = Scale();
const int x0 = (int)sx, y0 = (int)sy;
const int x1 = x0 + (int)( nativeW * s ), y1 = y0 + (int)( nativeH * s );
surface()->DrawSetColor( c );
surface()->DrawSetTexture( GetTex( pszMat ) );
surface()->DrawTexturedSubRect( x0, y0, x1, y1, 0.0f, 0.0f, u, v );
}
void CHudStargate::DrawHudNumber( int value, float x, float y, float s, const Color &c )
{
if ( value < 0 )
value = 0;
char buf[ 16 ];
Q_snprintf( buf, sizeof( buf ), "%d", value );
float cx = x;
for ( const char *p = buf; *p; ++p )
{
int d = *p - '0';
if ( d >= 0 && d <= 9 )
DrawSpr( SG_NUM[ d ], cx, y, SG_NUM_W, SG_NUM_H, c );
cx += SG_NUM_W * s;
}
}
//-----------------------------------------------------------------------------
void CHudStargate::Paint( void )
{
int sw, sh;
surface()->GetScreenSize( sw, sh );
const float s = Scale();
if ( sg_hud_test.GetBool() )
{
// Inject dummy state so every block is visible for layout verification.
m_flArmor = 70; m_flStamina = 55; m_bHasKey = true;
m_iInfoMode = 2; m_bTicketMode = true; m_iGtickets = 42; m_iTtickets = 37;
m_iProgress = 66; if ( m_flRoundLen == 0 ) { m_flRoundLen = 300; m_flRoundStart = gpGlobals->curtime; }
// A small paint probe makes VGUI-present-but-assets-missing cases obvious.
surface()->DrawSetColor( 0, 0, 0, 180 );
surface()->DrawFilledRect( 16, 16, 228, 88 );
surface()->DrawSetColor( 255, 60, 40, 255 );
surface()->DrawFilledRect( 24, 24, 220, 40 );
surface()->DrawSetColor( 0, 190, 90, 255 );
surface()->DrawFilledRect( 24, 48, 220, 64 );
surface()->DrawSetColor( 60, 140, 255, 255 );
surface()->DrawFilledRect( 24, 72, 220, 80 );
}
DrawFrame( sw, sh, s );
DrawStatBars( sw, sh, s );
if ( sg_view_radar.GetBool() )
DrawRadar( sw, sh, s );
DrawNaquada( sw, sh, s );
DrawTac( sw, sh, s );
DrawTickets( sw, sh, s );
DrawRoundTimer( sw, sh, s );
DrawBombKey( sw, sh, s );
if ( sg_view_portraits.GetBool() )
DrawPortraits( sw, sh, s );
DrawZatOverlay( sw, sh, s );
}
//-----------------------------------------------------------------------------
// sub-blocks
//-----------------------------------------------------------------------------
void CHudStargate::DrawFrame( int sw, int sh, float s )
{
// Bottom-left and bottom-right HUD chrome (hud_left 200x168, Hud_Right 200x168).
const Color white( 255, 255, 255, 255 );
DrawSpr( "vgui/stargate/hud/hud_left", 0, sh - (int)( 168 * s ), 200, 168, white );
DrawSpr( "vgui/stargate/hud/Hud_Right", sw - (int)( 200 * s ), sh - (int)( 168 * s ), 200, 168, white );
}
void CHudStargate::DrawRadar( int sw, int sh, float s )
{
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
int team = pPlayer ? pPlayer->GetTeamNumber() : m_iLocalTeam;
Color disk = ( team == SG_TEAM_TAURI ) ? Color( 0, 160, 0, 255 ) : Color( 160, 0, 0, 255 );
// Disk: top-right, 200x200 native. y shifts down when portraits present.
bool hasPortraits = false;
for ( int i = 0; i < SG_MAX_PORTRAITS; i++ ) if ( m_Portraits[ i ].used ) { hasPortraits = true; break; }
const int rx = sw - (int)( 208 * s );
const int ry = (int)( ( hasPortraits ? 112 : 8 ) * s );
DrawSpr( "vgui/stargate/hud/radar", rx, ry, 200, 200, disk );
// Compass needle: 16-frame ladder driven by view yaw, centered on the disk.
float yaw = pPlayer ? pPlayer->EyeAngles().y : 0.0f;
yaw = anglemod( yaw );
int frame = (int)( ( yaw / 360.0f ) * 16.0f ) & 15;
frame = ( frame + 3 ) & 15;
frame = 15 - frame;
char needle[ 64 ];
Q_snprintf( needle, sizeof( needle ), "vgui/stargate/hud/needle_%03d", frame );
const int ncx = rx + (int)( ( 100 - 16 ) * s ); // disk center - half needle
const int ncy = ry + (int)( ( 100 - 16 ) * s );
DrawSpr( needle, ncx, ncy, 32, 32, Color( 210, disk.g(), 0, 255 ) );
// Blips (decaying), positioned relative to disk centre.
const int cx = rx + (int)( 100 * s ), cy = ry + (int)( 100 * s );
for ( int i = 0; i < SG_MAX_BLIPS; i++ )
{
if ( m_Blips[ i ].alpha <= 0 )
continue;
int bf = m_Blips[ i ].type % 6; if ( bf < 0 ) bf += 6;
char blip[ 64 ];
Q_snprintf( blip, sizeof( blip ), "vgui/stargate/hud/blip_%03d", bf );
const int bx = cx - (int)( m_Blips[ i ].x * s ) - (int)( 8 * s );
const int by = cy + (int)( m_Blips[ i ].y * s ) - (int)( 8 * s );
DrawSpr( blip, bx, by, 16, 16, Color( 255, 255, 255, m_Blips[ i ].alpha ) );
}
}
void CHudStargate::DrawStatBars( int sw, int sh, float s )
{
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
const float maxv = 100.0f;
float life = pPlayer ? (float)pPlayer->GetHealth() : 0.0f;
const int barY = sh - (int)( 24 * s );
const int barH = 64;
// Vertical bars crop from the top by (max-value)/max - statbar.spr is 16x64.
struct { const char *mat; float val; int x; Color c; } bars[] = {
{ "vgui/stargate/hud/statbar", life, 4, Color( 0, 220, 0, 255 ) },
{ "vgui/stargate/hud/statbar", m_flArmor, 28, Color( 80, 160, 255, 255 ) },
{ "vgui/stargate/hud/statbar", m_flStamina, 52, Color( 255, 170, 0, 255 ) },
};
for ( int i = 0; i < 3; i++ )
{
float val = bars[ i ].val;
// life (i==0) draws even at full/empty; armor/stamina only when > 0.
if ( i > 0 && val <= 0 )
continue;
float frac = val / maxv; if ( frac < 0 ) frac = 0; if ( frac > 1 ) frac = 1;
int filledH = (int)( barH * frac );
int topGap = barH - filledH; // empty portion at top
// UV: statbar is 16x64 native -> 64x256 atlas, pow2 64x256, u=v=1.
const int texW = 16 * 4, texH = 64 * 4;
float u = (float)texW / SgNextPow2( texW );
float vTop = (float)topGap / 64.0f * ( (float)texH / SgNextPow2( texH ) );
float vBot = (float)texH / SgNextPow2( texH );
int x0 = bars[ i ].x;
int y0 = barY + (int)( topGap * s );
int x1 = x0 + (int)( 16 * s );
int y1 = barY + (int)( barH * s );
surface()->DrawSetColor( bars[ i ].c );
surface()->DrawSetTexture( GetTex( bars[ i ].mat ) );
surface()->DrawTexturedSubRect( x0, y0, x1, y1, 0.0f, vTop, u, vBot );
}
}
void CHudStargate::DrawNaquada( int sw, int sh, float s )
{
if ( m_iProgress <= 0 || m_iProgress >= 100 )
return;
const int bx = sw / 2 - (int)( 71 * s );
const int by = sh - sh / 4 - (int)( 7 * s );
DrawSpr( "vgui/stargate/hud/pbar", bx, by, 128, 32, Color( 255, 255, 255, 255 ) );
// Fill width scaled by progress.
float frac = m_iProgress / 100.0f;
const int fx = sw / 2 - (int)( 64 * s );
int fullW = 144, drawW = (int)( fullW * frac );
const int texW = 144 * 4, texH = 48 * 4;
float uFull = (float)texW / SgNextPow2( texW );
float v = (float)texH / SgNextPow2( texH );
float u = uFull * frac;
surface()->DrawSetColor( Color( (int)( 255 * ( 1 - frac ) ), (int)( 255 * frac ), 40, 255 ) );
surface()->DrawSetTexture( GetTex( "vgui/stargate/hud/pbare" ) );
surface()->DrawTexturedSubRect( fx, by, fx + (int)( drawW * s ), by + (int)( 48 * s ), 0.0f, 0.0f, u, v );
}
void CHudStargate::DrawTac( int sw, int sh, float s )
{
if ( m_iInfoMode <= 0 )
return;
const char *mat = "vgui/stargate/hud/tac_green";
if ( m_iInfoMode == 1 ) mat = "vgui/stargate/hud/tac_red";
else if ( m_iInfoMode == 2 ) mat = "vgui/stargate/hud/tac_yellow";
DrawSpr( mat, (int)( 115 * s ), sh - (int)( 76 * s ), 64, 8, Color( 200, 200, 200, 255 ) );
}
void CHudStargate::DrawTickets( int sw, int sh, float s )
{
if ( !m_bTicketMode )
return;
DrawSpr( "vgui/stargate/hud/ticketdock", (int)( 120 * s ), sh - (int)( 37 * s ), 120, 64, Color( 200, 200, 200, 255 ) );
DrawHudNumber( m_iGtickets, (int)( 70 * s ), sh - (int)( 37 * s ), s, Color( 210, 0, 0, 255 ) );
DrawHudNumber( m_iTtickets, (int)( 130 * s ), sh - (int)( 37 * s ), s, Color( 0, 180, 0, 255 ) );
}
void CHudStargate::DrawRoundTimer( int sw, int sh, float s )
{
if ( m_flRoundLen <= 0 )
return;
int remaining = (int)( m_flRoundLen - ( gpGlobals->curtime - m_flRoundStart ) );
if ( remaining < 0 ) remaining = 0;
int mins = remaining / 60, secs = remaining % 60;
const Color cyan( 168, 211, 255, 255 );
float cx = sw / 2 - (int)( 28 * s );
float cy = (int)( sh / 10 );
DrawHudNumber( mins, cx, cy, s, cyan );
DrawSpr( "vgui/stargate/hud/dots", cx + (int)( SG_NUM_W * 2 * s ), cy, 8, 24, cyan );
// seconds, zero-padded
char sb[ 8 ]; Q_snprintf( sb, sizeof( sb ), "%02d", secs );
float dx = cx + (int)( ( SG_NUM_W * 2 + 8 ) * s );
for ( const char *p = sb; *p; ++p )
{
DrawSpr( SG_NUM[ *p - '0' ], dx, cy, SG_NUM_W, SG_NUM_H, cyan );
dx += SG_NUM_W * s;
}
}
void CHudStargate::DrawBombKey( int sw, int sh, float s )
{
if ( !m_bHasKey )
return;
DrawSpr( "vgui/stargate/hud/key", (int)( 75 * s ), sh - (int)( 52 * s ), 72, 48, Color( 200, 200, 200, 255 ) );
}
void CHudStargate::DrawPortraits( int sw, int sh, float s )
{
// Bottom-right teammate avatar column (Portrait_middel cells, 80px wide).
int n = 0;
for ( int i = 0; i < SG_MAX_PORTRAITS; i++ )
{
if ( !m_Portraits[ i ].used )
continue;
int px = sw - (int)( ( ( n + 1 ) * 80 + 16 ) * s );
int py = sh - (int)( 120 * s );
DrawSpr( "vgui/stargate/hud/Portrait_middel", px, py, 80, 104, TeamColor( m_Portraits[ i ].team ) );
if ( m_Portraits[ i ].avatar[ 0 ] )
{
char av[ 96 ];
Q_snprintf( av, sizeof( av ), "vgui/stargate/avatars/%s", m_Portraits[ i ].avatar );
DrawSpr( av, px + (int)( 8 * s ), py + (int)( 8 * s ), 64, 64, Color( 255, 255, 255, 255 ) );
}
n++;
}
}
void CHudStargate::DrawZatOverlay( int sw, int sh, float s )
{
if ( !m_bZat )
return;
float age = gpGlobals->curtime - m_flZatTime;
if ( age > 1.5f ) { m_bZat = false; return; }
int a = (int)( 200 * ( 1.0f - age / 1.5f ) );
surface()->DrawSetColor( Color( 0, 130, 255, a ) );
surface()->DrawSetTexture( GetTex( "vgui/stargate/weapons/d_zap" ) );
surface()->DrawTexturedRect( 0, 0, sw, sh );
}
//-----------------------------------------------------------------------------
// message handlers
//-----------------------------------------------------------------------------
void CHudStargate::MsgFunc_SgArmor( bf_read &msg ) { m_flArmor = msg.ReadShort(); }
void CHudStargate::MsgFunc_SgStamina( bf_read &msg ) { m_flStamina = msg.ReadShort(); }
void CHudStargate::MsgFunc_SgSubModel( bf_read &msg ) { m_iSubModel = msg.ReadShort(); }
void CHudStargate::MsgFunc_SgHasKey( bf_read &msg ) { m_bHasKey = msg.ReadShort() != 0; }
void CHudStargate::MsgFunc_SgHideHud( bf_read &msg ) { m_iHideBits = msg.ReadShort(); }
void CHudStargate::MsgFunc_SgHudInfoMode( bf_read &msg ){ m_iInfoMode = msg.ReadByte(); }
void CHudStargate::MsgFunc_SgTicketMode( bf_read &msg ) { m_bTicketMode = msg.ReadByte() != 0; }
void CHudStargate::MsgFunc_SgGtickets( bf_read &msg ) { m_iGtickets = msg.ReadByte(); }
void CHudStargate::MsgFunc_SgTtickets( bf_read &msg ) { m_iTtickets = msg.ReadByte(); }
void CHudStargate::MsgFunc_SgTogglePor( bf_read &msg ) { sg_view_portraits.SetValue( !sg_view_portraits.GetBool() ); }
void CHudStargate::MsgFunc_SgNewRnd( bf_read &msg ) { m_iSubModel = 0; Q_memset( m_Portraits, 0, sizeof( m_Portraits ) ); }
void CHudStargate::MsgFunc_SgRadio( bf_read &msg ) { msg.ReadByte(); msg.ReadByte(); /* TODO radio text */ }
void CHudStargate::MsgFunc_SgAboutMe( bf_read &msg )
{
m_iLocalId = msg.ReadShort();
m_iLocalTeam = msg.ReadShort();
}
void CHudStargate::MsgFunc_SgSetRndTim( bf_read &msg )
{
m_flRoundLen = msg.ReadShort();
msg.ReadByte();
m_flRoundStart = gpGlobals->curtime;
}
void CHudStargate::MsgFunc_SgZatTouch( bf_read &msg )
{
m_bZat = true;
m_flZatTime = gpGlobals->curtime;
}
void CHudStargate::MsgFunc_SgProgress( bf_read &msg )
{
m_iProgress = msg.ReadByte();
// remaining payload (flags + 28-entry array + trailer) consumed but unused.
int remaining = msg.GetNumBytesLeft();
for ( int i = 0; i < remaining; i++ )
msg.ReadByte();
}
void CHudStargate::MsgFunc_SgBlip( bf_read &msg )
{
int cmd = msg.ReadByte();
float x = msg.ReadShort();
float y = msg.ReadShort();
if ( cmd == '=' ) // 0x3d - clear origin reference
return;
// add a blip into the first free/decayed slot
for ( int i = 0; i < SG_MAX_BLIPS; i++ )
{
if ( m_Blips[ i ].alpha <= 0 )
{
m_Blips[ i ].type = ( cmd == '2' ) ? 1 : 0;
m_Blips[ i ].x = x;
m_Blips[ i ].y = y;
m_Blips[ i ].alpha = 255;
return;
}
}
}
void CHudStargate::MsgFunc_SgReset( bf_read &msg )
{
int id = msg.ReadShort();
int flag = msg.ReadByte();
if ( flag == 0 )
{
Q_memset( m_Portraits, 0, sizeof( m_Portraits ) );
return;
}
for ( int i = 0; i < SG_MAX_PORTRAITS; i++ )
if ( m_Portraits[ i ].used && m_Portraits[ i ].id == id )
m_Portraits[ i ].used = false;
}
void CHudStargate::MsgFunc_SgAddPortrait( bf_read &msg )
{
int id = msg.ReadShort();
int team = msg.ReadShort();
char avatar[ 32 ];
msg.ReadString( avatar, sizeof( avatar ) );
// find existing or free slot
int slot = -1;
for ( int i = 0; i < SG_MAX_PORTRAITS; i++ )
{
if ( m_Portraits[ i ].used && m_Portraits[ i ].id == id ) { slot = i; break; }
if ( slot < 0 && !m_Portraits[ i ].used ) slot = i;
}
if ( slot < 0 )
return;
m_Portraits[ slot ].used = true;
m_Portraits[ slot ].id = id;
m_Portraits[ slot ].team = team;
Q_strncpy( m_Portraits[ slot ].avatar, avatar, sizeof( m_Portraits[ slot ].avatar ) );
}

View File

@ -0,0 +1,128 @@
//========= StargateTC ============================================================//
//
// sg_hud.h - StargateTC custom HUD element.
//
// 1:1 port of the single large GoldSrc client HUD element (decompiled
// FUN_100718df / Init FUN_10071193 / VidInit FUN_10071390, singleton
// &DAT_100e67f0). The original drew ALL Stargate-specific HUD from one element;
// we keep that structure here as one full-screen CHudElement that paints every
// sub-block with ScreenWidth/Height-relative coordinates, matching the original.
//
// See StargateTCSource/docs/HUD_PORT_SPEC.md for the full element/message map.
//=================================================================================//
#ifndef SG_HUD_H
#define SG_HUD_H
#ifdef _WIN32
#pragma once
#endif
#include "hudelement.h"
#include <vgui_controls/Panel.h>
#include "tier1/UtlDict.h"
#define SG_MAX_BLIPS 256
#define SG_MAX_PORTRAITS 33
struct SgBlip_t
{
int type;
float x; // world-relative offset supplied by server
float y;
int alpha; // 0..255, decays each frame
};
struct SgPortrait_t
{
bool used;
int id; // player entindex
int team;
char avatar[32];
};
//-----------------------------------------------------------------------------
// CHudStargate - the whole Stargate HUD in one element (faithful to GoldSrc).
//-----------------------------------------------------------------------------
class CHudStargate : public CHudElement, public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CHudStargate, vgui::Panel );
public:
explicit CHudStargate( const char *pElementName );
virtual void Init( void );
virtual void VidInit( void );
virtual void Reset( void );
virtual bool ShouldDraw( void );
virtual void OnThink( void );
virtual void Paint( void );
virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
// --- Server user messages (19, prefixed Sg to avoid HL2MP clashes) ---
void MsgFunc_SgAddPortrait( bf_read &msg );
void MsgFunc_SgReset( bf_read &msg );
void MsgFunc_SgAboutMe( bf_read &msg );
void MsgFunc_SgTogglePor( bf_read &msg );
void MsgFunc_SgSetRndTim( bf_read &msg );
void MsgFunc_SgNewRnd( bf_read &msg );
void MsgFunc_SgZatTouch( bf_read &msg );
void MsgFunc_SgRadio( bf_read &msg );
void MsgFunc_SgArmor( bf_read &msg );
void MsgFunc_SgStamina( bf_read &msg );
void MsgFunc_SgSubModel( bf_read &msg );
void MsgFunc_SgHasKey( bf_read &msg );
void MsgFunc_SgHideHud( bf_read &msg );
void MsgFunc_SgHudInfoMode( bf_read &msg );
void MsgFunc_SgTicketMode( bf_read &msg );
void MsgFunc_SgGtickets( bf_read &msg );
void MsgFunc_SgTtickets( bf_read &msg );
void MsgFunc_SgProgress( bf_read &msg );
void MsgFunc_SgBlip( bf_read &msg );
private:
// texture cache + sprite draw helpers
int GetTex( const char *pszMat );
// Draw a HUD sprite sized in GoldSrc-native px (auto pow2-UV from 4x atlas).
void DrawSpr( const char *pszMat, float sx, float sy,
int nativeW, int nativeH, const Color &c );
float Scale( void ) const; // screen-height based HUD scale
Color TeamColor( int team ) const;
// draw sub-blocks
void DrawFrame( int sw, int sh, float s );
void DrawRadar( int sw, int sh, float s );
void DrawStatBars( int sw, int sh, float s );
void DrawNaquada( int sw, int sh, float s );
void DrawTickets( int sw, int sh, float s );
void DrawRoundTimer( int sw, int sh, float s );
void DrawTac( int sw, int sh, float s );
void DrawBombKey( int sw, int sh, float s );
void DrawPortraits( int sw, int sh, float s );
void DrawZatOverlay( int sw, int sh, float s );
void DrawHudNumber( int value, float x, float y, float s, const Color &c );
CUtlDict< int, int > m_TexIds; // material name -> surface texture id
// --- HUD state (mirrors the GoldSrc element's fields) ---
int m_iLocalTeam;
int m_iLocalId;
float m_flArmor;
float m_flStamina;
int m_iSubModel;
bool m_bHasKey;
int m_iHideBits;
int m_iInfoMode; // 0 none / 1 red / 2 yellow / 3 green
bool m_bTicketMode;
int m_iGtickets;
int m_iTtickets;
int m_iProgress; // 0..100
float m_flRoundLen;
float m_flRoundStart;
bool m_bZat;
float m_flZatTime;
float m_flSlideOffset;
SgBlip_t m_Blips[ SG_MAX_BLIPS ];
SgPortrait_t m_Portraits[ SG_MAX_PORTRAITS ];
};
#endif // SG_HUD_H

View File

@ -0,0 +1,275 @@
//========= StargateTC ============================================================//
//
// sg_teammenu.cpp - combined team + class selection panel.
// See sg_teammenu.h. Buttons are built in code (no .res dependency) and laid out
// in PerformLayout, so the panel is self-contained.
//=================================================================================//
#include "cbase.h"
#include "sg_teammenu.h"
#include <cdll_client_int.h>
#include <vgui/IScheme.h>
#include <vgui/ISurface.h>
#include <vgui/IVGui.h>
#include <KeyValues.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/Label.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
// StargateTC team numbering (matches server: 2 = Tau'ri, 3 = Goa'uld).
#define SG_TEAM_AUTO 0
#define SG_TEAM_SPECTATOR 1
#define SG_TEAM_TAURI 2
#define SG_TEAM_GOAULD 3
struct SgClassDef_t { const char *cmd; const char *label; };
// Class rosters recovered from the GoldSrc panel (FUN_1002bec0 / Titles.txt).
static const SgClassDef_t s_TauClasses[] = {
{ "oneill", "O'Neill" },
{ "carter", "Carter" },
{ "jackson", "Jackson" },
{ "tealc", "Teal'c" },
{ "soldat", "Soldier" },
};
static const SgClassDef_t s_GoaClasses[] = {
{ "dieu", "System Lord" },
{ "anubis", "Anubis Guard" },
{ "jaffa", "Jaffa" },
{ "prima", "First Prime" },
{ "esclave", "Horus Guard" },
};
static void SgStyleButton( Button *pButton, const Color &fg, const Color &bg )
{
if ( !pButton )
return;
pButton->SetButtonBorderEnabled( true );
pButton->SetDefaultColor( fg, bg );
pButton->SetArmedColor( Color( 255, 255, 255, 255 ), Color( 45, 94, 110, 245 ) );
pButton->SetDepressedColor( Color( 255, 255, 255, 255 ), Color( 23, 58, 72, 255 ) );
pButton->SetSelectedColor( fg, Color( 29, 68, 82, 245 ) );
}
//-----------------------------------------------------------------------------
CSgTeamMenu::CSgTeamMenu( IViewPort *pViewPort ) : Frame( NULL, PANEL_TEAM )
{
m_pViewPort = pViewPort;
SetScheme( "ClientScheme" );
SetTitle( "", true );
SetMoveable( false );
SetSizeable( false );
SetTitleBarVisible( false );
SetProportional( false );
SetPaintBackgroundEnabled( true );
SetPaintBorderEnabled( false );
m_pTitle = new Label( this, "Title", "Identify yourself" );
m_pTauHeader = new Label( this, "TauHeader", "Tau'ri" );
m_pGoaHeader = new Label( this, "GoaHeader", "Goa'uld" );
for ( int i = 0; i < ARRAYSIZE( s_TauClasses ); i++ )
m_TauButtons.AddToTail( AddClassButton( SG_TEAM_TAURI, s_TauClasses[i].cmd, s_TauClasses[i].label ) );
for ( int i = 0; i < ARRAYSIZE( s_GoaClasses ); i++ )
m_GoaButtons.AddToTail( AddClassButton( SG_TEAM_GOAULD, s_GoaClasses[i].cmd, s_GoaClasses[i].label ) );
m_BottomButtons.AddToTail( AddTeamButton( SG_TEAM_AUTO, "Auto-Assign", "auto" ) );
m_BottomButtons.AddToTail( AddTeamButton( SG_TEAM_SPECTATOR, "Spectate", "spec" ) );
m_BottomButtons.AddToTail( AddTeamButton( -1, "Cancel", "cancel" ) );
SetVisible( false );
}
CSgTeamMenu::~CSgTeamMenu()
{
}
//-----------------------------------------------------------------------------
vgui::Button *CSgTeamMenu::AddClassButton( int teamId, const char *cls, const char *label )
{
Button *b = new Button( this, cls, label );
KeyValues *kv = new KeyValues( "JoinClass" );
kv->SetInt( "team", teamId );
kv->SetString( "cls", cls );
b->SetCommand( kv );
b->AddActionSignalTarget( this );
return b;
}
vgui::Button *CSgTeamMenu::AddTeamButton( int teamId, const char *label, const char *key )
{
Button *b = new Button( this, key, label );
KeyValues *kv = new KeyValues( "JoinTeam" );
kv->SetInt( "team", teamId );
b->SetCommand( kv );
b->AddActionSignalTarget( this );
return b;
}
//-----------------------------------------------------------------------------
void CSgTeamMenu::ApplySchemeSettings( vgui::IScheme *pScheme )
{
BaseClass::ApplySchemeSettings( pScheme );
SetBgColor( Color( 5, 9, 12, 238 ) );
m_pTitle->SetFgColor( Color( 235, 243, 235, 255 ) );
m_pTauHeader->SetFgColor( Color( 90, 220, 130, 255 ) );
m_pGoaHeader->SetFgColor( Color( 235, 88, 78, 255 ) );
for ( int i = 0; i < m_TauButtons.Count(); i++ )
SgStyleButton( m_TauButtons[i], Color( 210, 255, 220, 255 ), Color( 16, 46, 30, 235 ) );
for ( int i = 0; i < m_GoaButtons.Count(); i++ )
SgStyleButton( m_GoaButtons[i], Color( 255, 220, 210, 255 ), Color( 54, 25, 22, 235 ) );
for ( int i = 0; i < m_BottomButtons.Count(); i++ )
SgStyleButton( m_BottomButtons[i], Color( 230, 235, 230, 255 ), Color( 32, 39, 43, 235 ) );
}
//-----------------------------------------------------------------------------
void CSgTeamMenu::PaintBackground( void )
{
int w, h;
GetSize( w, h );
surface()->DrawSetColor( 5, 9, 12, 238 );
surface()->DrawFilledRect( 0, 0, w, h );
surface()->DrawSetColor( 76, 155, 118, 255 );
surface()->DrawOutlinedRect( 0, 0, w, h );
surface()->DrawSetColor( 18, 57, 36, 210 );
surface()->DrawFilledRect( 22, 44, w / 2 - 10, h - 58 );
surface()->DrawSetColor( 76, 32, 28, 210 );
surface()->DrawFilledRect( w / 2 + 10, 44, w - 22, h - 58 );
surface()->DrawSetColor( 28, 37, 40, 235 );
surface()->DrawFilledRect( 22, h - 54, w - 22, h - 16 );
}
//-----------------------------------------------------------------------------
void CSgTeamMenu::PerformLayout( void )
{
BaseClass::PerformLayout();
int sw, sh;
surface()->GetScreenSize( sw, sh );
const int w = 460, h = 360;
const int x = ( sw - w ) / 2, y = ( sh - h ) / 2;
SetBounds( x, y, w, h );
m_pTitle->SetBounds( 0, 12, w, 24 );
m_pTitle->SetContentAlignment( Label::a_center );
const int colW = 180, btnH = 26, gap = 6;
const int leftX = 30, rightX = w - 30 - colW;
int hy = 48;
m_pTauHeader->SetBounds( leftX, hy, colW, 22 );
m_pTauHeader->SetContentAlignment( Label::a_center );
m_pGoaHeader->SetBounds( rightX, hy, colW, 22 );
m_pGoaHeader->SetContentAlignment( Label::a_center );
int by = hy + 28;
for ( int i = 0; i < m_TauButtons.Count(); i++ )
m_TauButtons[i]->SetBounds( leftX, by + i * ( btnH + gap ), colW, btnH );
for ( int i = 0; i < m_GoaButtons.Count(); i++ )
m_GoaButtons[i]->SetBounds( rightX, by + i * ( btnH + gap ), colW, btnH );
// bottom row: auto / spectate / cancel
int bottomY = h - 40;
int n = m_BottomButtons.Count();
int bw = 130, totalW = n * bw + ( n - 1 ) * gap;
int bx = ( w - totalW ) / 2;
for ( int i = 0; i < n; i++ )
m_BottomButtons[i]->SetBounds( bx + i * ( bw + gap ), bottomY, bw, btnH );
}
//-----------------------------------------------------------------------------
void CSgTeamMenu::ShowPanel( bool bShow )
{
if ( BaseClass::IsVisible() == bShow )
return;
if ( bShow )
{
Activate();
SetVisible( true );
SetMouseInputEnabled( true );
SetKeyBoardInputEnabled( true );
InvalidateLayout();
}
else
{
SetVisible( false );
SetMouseInputEnabled( false );
}
if ( m_pViewPort )
m_pViewPort->ShowBackGround( bShow );
}
//-----------------------------------------------------------------------------
void CSgTeamMenu::OnJoinClass( KeyValues *data )
{
int team = data->GetInt( "team" );
const char *cls = data->GetString( "cls" );
char cmd[ 128 ];
Q_snprintf( cmd, sizeof( cmd ), "jointeam %d; joinclass %s\n", team, cls );
engine->ClientCmd( cmd );
ShowPanel( false );
}
void CSgTeamMenu::OnJoinTeam( KeyValues *data )
{
int team = data->GetInt( "team" );
if ( team < 0 ) // Cancel
{
ShowPanel( false );
return;
}
char cmd[ 96 ];
if ( team == SG_TEAM_AUTO )
Q_snprintf( cmd, sizeof( cmd ), "jointeam 0; joinclass auto\n" );
else
Q_snprintf( cmd, sizeof( cmd ), "jointeam %d\n", team );
engine->ClientCmd( cmd );
ShowPanel( false );
}
void CSgTeamMenu::OnKeyCodePressed( vgui::KeyCode code )
{
if ( code == KEY_ESCAPE )
{
ShowPanel( false );
return;
}
BaseClass::OnKeyCodePressed( code );
}
//-----------------------------------------------------------------------------
// Console commands to open the menu (also bind a key, e.g. bind m chooseteam).
//-----------------------------------------------------------------------------
CON_COMMAND( chooseteam, "Open the StargateTC team/class selection menu" )
{
if ( gViewPortInterface )
gViewPortInterface->ShowPanel( PANEL_TEAM, true );
}
CON_COMMAND( changeclass, "Open the StargateTC class selection menu" )
{
if ( gViewPortInterface )
gViewPortInterface->ShowPanel( PANEL_TEAM, true );
}
CON_COMMAND( showmenu, "Open the StargateTC team/class selection menu" )
{
if ( gViewPortInterface )
gViewPortInterface->ShowPanel( PANEL_TEAM, true );
}
CON_COMMAND( showequip, "Open the StargateTC equipment/class selection menu" )
{
if ( gViewPortInterface )
gViewPortInterface->ShowPanel( PANEL_TEAM, true );
}

View File

@ -0,0 +1,68 @@
//========= StargateTC ============================================================//
//
// sg_teammenu.h - combined team + class selection panel (IViewPortPanel).
//
// 1:1 port of the GoldSrc "Identifiez-vous" panel (decompiled FUN_1002bec0):
// two team columns (Tau'ri / Goa'uld), five classes each, plus Auto-Assign,
// Spectate and Cancel. Class buttons issue `jointeam <id>` + `joinclass <name>`.
// Opened by the `chooseteam` / `changeclass` console commands or server VGUIMenu.
//=================================================================================//
#ifndef SG_TEAMMENU_H
#define SG_TEAMMENU_H
#ifdef _WIN32
#pragma once
#endif
#include <vgui_controls/Frame.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/Label.h>
#include <game/client/iviewport.h>
#include <utlvector.h>
class CSgTeamMenu : public vgui::Frame, public IViewPortPanel
{
DECLARE_CLASS_SIMPLE( CSgTeamMenu, vgui::Frame );
public:
explicit CSgTeamMenu( IViewPort *pViewPort );
virtual ~CSgTeamMenu();
// IViewPortPanel
virtual const char *GetName( void ) { return PANEL_TEAM; }
virtual void SetData( KeyValues *data ) {}
virtual void Reset( void ) {}
virtual void Update( void ) {}
virtual bool NeedsUpdate( void ) { return false; }
virtual bool HasInputElements( void ) { return true; }
virtual void ShowPanel( bool bShow );
// both vgui::Frame and IViewPortPanel define these -> passthroughs
vgui::VPANEL GetVPanel( void ) { return BaseClass::GetVPanel(); }
virtual bool IsVisible() { return BaseClass::IsVisible(); }
virtual void SetParent( vgui::VPANEL parent ) { BaseClass::SetParent( parent ); }
virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; }
protected:
virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
virtual void PaintBackground( void );
virtual void PerformLayout( void );
virtual void OnKeyCodePressed( vgui::KeyCode code );
MESSAGE_FUNC_PARAMS( OnJoinClass, "JoinClass", data );
MESSAGE_FUNC_PARAMS( OnJoinTeam, "JoinTeam", data );
private:
vgui::Button *AddClassButton( int teamId, const char *cls, const char *label );
vgui::Button *AddTeamButton( int teamId, const char *label, const char *key );
IViewPort *m_pViewPort;
vgui::Label *m_pTitle;
vgui::Label *m_pTauHeader;
vgui::Label *m_pGoaHeader;
CUtlVector< vgui::Button * > m_TauButtons;
CUtlVector< vgui::Button * > m_GoaButtons;
CUtlVector< vgui::Button * > m_BottomButtons;
};
#endif // SG_TEAMMENU_H

View File

@ -35,12 +35,21 @@ void Host_Say( edict_t *pEdict, bool teamonly );
ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." );
// StargateTC: the MOTD info panel popped over the screen on every join and the
// mod ships no MOTD content, so it just obscured the game. Default it OFF;
// set "sv_show_motd 1" to restore the vanilla HL2DM message-of-the-day panel.
ConVar sv_show_motd( "sv_show_motd", "0", FCVAR_NOTIFY, "Show the MOTD info panel to players when they join." );
extern CBaseEntity* FindPickerEntityClass( CBasePlayer *pPlayer, char *classname );
extern bool g_fGameOver;
void FinishClientPutInServer( CHL2MP_Player *pPlayer )
{
pPlayer->InitialSpawn();
extern void Sg_ResetPlayerClass( CHL2MP_Player *pPlayer );
Sg_ResetPlayerClass( pPlayer );
pPlayer->Spawn();
@ -61,20 +70,24 @@ void FinishClientPutInServer( CHL2MP_Player *pPlayer )
if ( HL2MPRules()->IsTeamplay() == true )
{
ClientPrint( pPlayer, HUD_PRINTTALK, "You are on team %s1\n", pPlayer->GetTeam()->GetName() );
pPlayer->ShowViewPortPanel( PANEL_TEAM, true );
}
const ConVar *hostname = cvar->FindVar( "hostname" );
const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
if ( sv_show_motd.GetBool() )
{
const ConVar *hostname = cvar->FindVar( "hostname" );
const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
KeyValues *data = new KeyValues("data");
data->SetString( "title", title ); // info panel title
data->SetString( "type", "1" ); // show userdata from stringtable entry
data->SetString( "msg", "motd" ); // use this stringtable entry
data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() );
KeyValues *data = new KeyValues("data");
data->SetString( "title", title ); // info panel title
data->SetString( "type", "1" ); // show userdata from stringtable entry
data->SetString( "msg", "motd" ); // use this stringtable entry
data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() );
pPlayer->ShowViewPortPanel( PANEL_INFO, true, data );
pPlayer->ShowViewPortPanel( PANEL_INFO, true, data );
data->deleteThis();
data->deleteThis();
}
}
/*

View File

@ -20,6 +20,7 @@
#include "grenade_satchel.h"
#include "eventqueue.h"
#include "gamestats.h"
#include "viewport_panel_names.h"
#include "engine/IEngineSound.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
@ -35,6 +36,9 @@ extern CBaseEntity *g_pLastSpawn;
#define HL2MP_COMMAND_MAX_RATE 0.3
ConVar mp_wepselmode( "mp_wepselmode", "2", FCVAR_GAMEDLL | FCVAR_NOTIFY, "StargateTC weapon selection: 0=team melee only, 1=team melee plus class weapon, 2=team default pack plus class weapon." );
ConVar mp_autobalance( "mp_autobalance", "2", FCVAR_GAMEDLL | FCVAR_NOTIFY, "StargateTC team auto-balance: 0=off, non-zero blocks joins that would stack Tau'ri/Goa'uld." );
void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade );
LINK_ENTITY_TO_CLASS( player, CHL2MP_Player );
@ -48,6 +52,7 @@ IMPLEMENT_SERVERCLASS_ST(CHL2MP_Player, DT_HL2MP_Player)
SendPropEHandle( SENDINFO( m_hRagdoll ) ),
SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ),
SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ),
SendPropInt( SENDINFO( m_iStargateClass ), 4 ),
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
SendPropExclude( "DT_BaseFlex", "m_viewtarget" ),
@ -60,35 +65,82 @@ END_SEND_TABLE()
BEGIN_DATADESC( CHL2MP_Player )
END_DATADESC()
const char *g_ppszRandomCitizenModels[] =
// StargateTC: team 3 (Goa'uld, == TEAM_REBELS index) draws from this list.
const char *g_ppszRandomCitizenModels[] =
{
"models/humans/group03/male_01.mdl",
"models/humans/group03/male_02.mdl",
"models/humans/group03/female_01.mdl",
"models/humans/group03/male_03.mdl",
"models/humans/group03/female_02.mdl",
"models/humans/group03/male_04.mdl",
"models/humans/group03/female_03.mdl",
"models/humans/group03/male_05.mdl",
"models/humans/group03/female_04.mdl",
"models/humans/group03/male_06.mdl",
"models/humans/group03/female_06.mdl",
"models/humans/group03/male_07.mdl",
"models/humans/group03/female_07.mdl",
"models/humans/group03/male_08.mdl",
"models/humans/group03/male_09.mdl",
"models/stargate/player/goauld3/goauld3.mdl",
"models/stargate/player/anubis/anubis.mdl",
"models/stargate/player/horus/horus.mdl",
"models/stargate/player/systemlord/systemlord.mdl",
"models/stargate/player/snakegoauld/snakegoauld.mdl",
};
// StargateTC: team 2 (Tau'ri, == TEAM_COMBINE index) draws from this list (SG-1).
const char *g_ppszRandomCombineModels[] =
{
"models/combine_soldier.mdl",
"models/combine_soldier_prisonguard.mdl",
"models/combine_super_soldier.mdl",
"models/police.mdl",
"models/stargate/player/oneill/oneill.mdl",
"models/stargate/player/carter/carter.mdl",
"models/stargate/player/jackson/jackson.mdl",
"models/stargate/player/tealc/tealc.mdl",
"models/stargate/player/commander/commander.mdl",
};
static bool Sg_IsModelInList( const char *pModel, const char * const *ppModels, int iModelCount )
{
if ( !pModel || !pModel[0] )
return false;
#define MAX_COMBINE_MODELS 4
for ( int i = 0; i < iModelCount; i++ )
{
if ( !Q_stricmp( ppModels[i], pModel ) )
return true;
}
return false;
}
static int Sg_CountTeamPlayers( int iTeam )
{
CTeam *pTeam = GetGlobalTeam( iTeam );
return pTeam ? pTeam->GetNumPlayers() : 0;
}
static int Sg_GetAutoTeam()
{
const int iTorri = Sg_CountTeamPlayers( TEAM_COMBINE );
const int iGoauld = Sg_CountTeamPlayers( TEAM_REBELS );
if ( iTorri < iGoauld )
return TEAM_COMBINE;
if ( iGoauld < iTorri )
return TEAM_REBELS;
return random->RandomInt( 0, 1 ) ? TEAM_COMBINE : TEAM_REBELS;
}
static bool Sg_WouldStackTeams( CHL2MP_Player *pPlayer, int iTeam )
{
if ( mp_autobalance.GetInt() <= 0 )
return false;
if ( iTeam != TEAM_COMBINE && iTeam != TEAM_REBELS )
return false;
const int iOtherTeam = ( iTeam == TEAM_COMBINE ) ? TEAM_REBELS : TEAM_COMBINE;
int iJoinTeamPlayers = Sg_CountTeamPlayers( iTeam );
int iOtherTeamPlayers = Sg_CountTeamPlayers( iOtherTeam );
if ( pPlayer && pPlayer->GetTeamNumber() == iTeam )
return false;
if ( pPlayer && pPlayer->GetTeamNumber() == iOtherTeam )
iOtherTeamPlayers--;
return iJoinTeamPlayers > iOtherTeamPlayers;
}
#define MAX_COMBINE_MODELS 5
#define MODEL_CHANGE_INTERVAL 5.0f
#define TEAM_CHANGE_INTERVAL 5.0f
@ -106,6 +158,7 @@ CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this )
m_flNextTeamChangeTime = 0.0f;
m_iSpawnInterpCounter = 0;
m_iStargateClass = 0;
m_bEnterObserver = false;
m_bReady = false;
@ -198,38 +251,52 @@ void CHL2MP_Player::GiveDefaultItems( void )
{
EquipSuit();
CBasePlayer::GiveAmmo( 255, "Pistol");
CBasePlayer::GiveAmmo( 45, "SMG1");
CBasePlayer::GiveAmmo( 1, "grenade" );
CBasePlayer::GiveAmmo( 6, "Buckshot");
CBasePlayer::GiveAmmo( 6, "357" );
// StargateTC: no Half-Life 2 weapons. The recovered GoldSrc rules use
// mp_wepselmode 0/1/2 for melee-only, class choice, or the team pack.
CBasePlayer::GiveAmmo( 255, "Pistol" );
CBasePlayer::GiveAmmo( 255, "SMG1" );
CBasePlayer::GiveAmmo( 64, "Buckshot" );
CBasePlayer::GiveAmmo( 32, "357" );
CBasePlayer::GiveAmmo( 5, "Grenade" );
if ( GetPlayerModelType() == PLAYER_SOUNDS_METROPOLICE || GetPlayerModelType() == PLAYER_SOUNDS_COMBINESOLDIER )
const int iWeaponMode = mp_wepselmode.GetInt();
const char *pSwitchTo = "weapon_couteau";
if ( GetTeamNumber() == TEAM_REBELS ) // Goa'uld (StargateTC::TEAM_GOAULD == 3)
{
GiveNamedItem( "weapon_stunstick" );
GiveNamedItem( "weapon_coujaf" ); // team melee
pSwitchTo = "weapon_coujaf";
if ( iWeaponMode >= 2 )
{
GiveNamedItem( "weapon_zat" );
GiveNamedItem( "weapon_glstaff" ); // Goa'uld staff
GiveNamedItem( "weapon_gacgoa" );
pSwitchTo = "weapon_glstaff";
}
}
else if ( GetPlayerModelType() == PLAYER_SOUNDS_CITIZEN )
else if ( GetTeamNumber() == TEAM_COMBINE ) // Tau'ri / Torri (StargateTC::TEAM_TORRI == 2)
{
GiveNamedItem( "weapon_crowbar" );
}
GiveNamedItem( "weapon_pistol" );
GiveNamedItem( "weapon_smg1" );
GiveNamedItem( "weapon_frag" );
GiveNamedItem( "weapon_physcannon" );
GiveNamedItem( "weapon_couteau" ); // team melee (knife)
const char *szDefaultWeaponName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_defaultweapon" );
CBaseCombatWeapon *pDefaultWeapon = Weapon_OwnsThisType( szDefaultWeaponName );
if ( pDefaultWeapon )
{
Weapon_Switch( pDefaultWeapon );
if ( iWeaponMode >= 2 )
{
GiveNamedItem( "weapon_m92s" );
GiveNamedItem( "weapon_m16" );
GiveNamedItem( "weapon_zat" );
GiveNamedItem( "weapon_flashbang" );
pSwitchTo = "weapon_m16";
}
}
else
{
Weapon_Switch( Weapon_OwnsThisType( "weapon_physcannon" ) );
// Unassigned / spectator: only a knife so they spawn with something SG.
GiveNamedItem( "weapon_couteau" );
}
CBaseCombatWeapon *pWeapon = Weapon_OwnsThisType( pSwitchTo );
if ( pWeapon )
Weapon_Switch( pWeapon );
}
void CHL2MP_Player::PickDefaultSpawnTeam( void )
@ -256,28 +323,9 @@ void CHL2MP_Player::PickDefaultSpawnTeam( void )
}
else
{
CTeam *pCombine = g_Teams[TEAM_COMBINE];
CTeam *pRebels = g_Teams[TEAM_REBELS];
if ( pCombine == NULL || pRebels == NULL )
{
ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) );
}
else
{
if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() )
{
ChangeTeam( TEAM_REBELS );
}
else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() )
{
ChangeTeam( TEAM_COMBINE );
}
else
{
ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) );
}
}
// GoldSrc StargateTC starts clients as spectators and opens the
// team/class menu; it does not auto-spawn them onto a combat team.
BaseClass::ChangeTeam( TEAM_SPECTATOR );
}
}
}
@ -302,6 +350,10 @@ void CHL2MP_Player::Spawn(void)
RemoveEffects( EF_NODRAW );
GiveDefaultItems();
// StargateTC: re-apply the chosen class loadout + refresh HUD team state.
extern void Sg_OnPlayerSpawn( CHL2MP_Player *pPlayer );
Sg_OnPlayerSpawn( this );
}
SetNumAnimOverlays( 3 );
@ -366,25 +418,18 @@ bool CHL2MP_Player::ValidatePlayerModel( const char *pModel )
void CHL2MP_Player::SetPlayerTeamModel( void )
{
if ( GetTeamNumber() == TEAM_SPECTATOR )
return;
if ( GetStargateClass() > 0 )
return;
const char *szModelName = NULL;
szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" );
int modelIndex = modelinfo->GetModelIndex( szModelName );
if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false )
{
szModelName = "models/Combine_Soldier.mdl";
m_iModelType = TEAM_COMBINE;
char szReturnString[512];
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName );
engine->ClientCommand ( edict(), szReturnString );
}
if ( GetTeamNumber() == TEAM_COMBINE )
{
if ( Q_stristr( szModelName, "models/human") )
if ( !Sg_IsModelInList( szModelName, g_ppszRandomCombineModels, ARRAYSIZE( g_ppszRandomCombineModels ) ) )
{
int nHeads = ARRAYSIZE( g_ppszRandomCombineModels );
@ -396,7 +441,7 @@ void CHL2MP_Player::SetPlayerTeamModel( void )
}
else if ( GetTeamNumber() == TEAM_REBELS )
{
if ( !Q_stristr( szModelName, "models/human") )
if ( !Sg_IsModelInList( szModelName, g_ppszRandomCitizenModels, ARRAYSIZE( g_ppszRandomCitizenModels ) ) )
{
int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels );
@ -406,6 +451,11 @@ void CHL2MP_Player::SetPlayerTeamModel( void )
m_iModelType = TEAM_REBELS;
}
else
{
szModelName = g_ppszRandomCitizenModels[0];
m_iModelType = TEAM_REBELS;
}
SetModel( szModelName );
SetupPlayerSoundsByModel( szModelName );
@ -426,7 +476,7 @@ void CHL2MP_Player::SetPlayerModel( void )
if ( ValidatePlayerModel( pszCurrentModelName ) == false )
{
pszCurrentModelName = "models/Combine_Soldier.mdl";
pszCurrentModelName = "models/stargate/player/oneill/oneill.mdl";
}
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName );
@ -474,7 +524,7 @@ void CHL2MP_Player::SetPlayerModel( void )
if ( modelIndex == -1 )
{
szModelName = "models/Combine_Soldier.mdl";
szModelName = "models/stargate/player/oneill/oneill.mdl";
m_iModelType = TEAM_COMBINE;
char szReturnString[512];
@ -503,6 +553,10 @@ void CHL2MP_Player::SetupPlayerSoundsByModel( const char *pModelName )
{
m_iPlayerSoundType = (int)PLAYER_SOUNDS_COMBINESOLDIER;
}
else
{
m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN;
}
}
void CHL2MP_Player::ResetAnimation( void )
@ -900,6 +954,7 @@ void CHL2MP_Player::ChangeTeam( int iTeam )
}*/
bool bKill = false;
const int iOldTeam = GetTeamNumber();
if ( HL2MPRules()->IsTeamplay() != true && iTeam != TEAM_SPECTATOR )
{
@ -909,7 +964,7 @@ void CHL2MP_Player::ChangeTeam( int iTeam )
if ( HL2MPRules()->IsTeamplay() == true )
{
if ( iTeam != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED )
if ( iTeam != iOldTeam && iOldTeam != TEAM_UNASSIGNED && iOldTeam != TEAM_SPECTATOR && iTeam != TEAM_SPECTATOR && IsAlive() )
{
bKill = true;
}
@ -919,7 +974,13 @@ void CHL2MP_Player::ChangeTeam( int iTeam )
m_flNextTeamChangeTime = gpGlobals->curtime + TEAM_CHANGE_INTERVAL;
if ( HL2MPRules()->IsTeamplay() == true )
if ( iTeam == TEAM_SPECTATOR )
{
RemoveAllItems( true );
State_Transition( STATE_OBSERVER_MODE );
}
else if ( HL2MPRules()->IsTeamplay() == true )
{
SetPlayerTeamModel();
}
@ -928,13 +989,6 @@ void CHL2MP_Player::ChangeTeam( int iTeam )
SetPlayerModel();
}
if ( iTeam == TEAM_SPECTATOR )
{
RemoveAllItems( true );
State_Transition( STATE_OBSERVER_MODE );
}
if ( bKill == true )
{
CommitSuicide();
@ -943,7 +997,12 @@ void CHL2MP_Player::ChangeTeam( int iTeam )
bool CHL2MP_Player::HandleCommand_JoinTeam( int team )
{
if ( !GetGlobalTeam( team ) || team == 0 )
if ( team == 0 )
{
team = Sg_GetAutoTeam();
}
if ( !GetGlobalTeam( team ) )
{
Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team );
return false;
@ -974,12 +1033,25 @@ bool CHL2MP_Player::HandleCommand_JoinTeam( int team )
}
else
{
if ( Sg_WouldStackTeams( this, team ) )
{
ClientPrint( this, HUD_PRINTCENTER, "That team has too many players." );
ShowViewPortPanel( PANEL_TEAM, true );
return false;
}
const bool bNeedsRespawn = ( GetTeamNumber() == TEAM_UNASSIGNED || GetTeamNumber() == TEAM_SPECTATOR || IsObserver() || IsDead() );
StopObserverMode();
State_Transition(STATE_ACTIVE);
}
// Switch their actual team...
ChangeTeam( team );
// Switch their actual team...
ChangeTeam( team );
if ( bNeedsRespawn )
{
Spawn();
}
}
return true;
}
@ -1000,6 +1072,7 @@ bool CHL2MP_Player::ClientCommand( const CCommand &args )
if ( args.ArgC() < 2 )
{
Warning( "Player sent bad jointeam syntax\n" );
return true;
}
if ( ShouldRunRateLimitedCommand( args ) )
@ -1009,6 +1082,39 @@ bool CHL2MP_Player::ClientCommand( const CCommand &args )
}
return true;
}
else if ( FStrEq( args[0], "joinclass" ) )
{
extern void Sg_HandleJoinClass( CHL2MP_Player *pPlayer, const char *pszClass );
Sg_HandleJoinClass( this, args.ArgC() >= 2 ? args[1] : "" );
return true;
}
else if ( FStrEq( args[0], "autopick" ) )
{
if ( ShouldRunRateLimitedCommand( args ) )
{
HandleCommand_JoinTeam( 0 );
extern void Sg_HandleJoinClass( CHL2MP_Player *pPlayer, const char *pszClass );
Sg_HandleJoinClass( this, "auto" );
}
return true;
}
else if ( FStrEq( args[0], "classe" ) )
{
extern void Sg_HandleJoinClass( CHL2MP_Player *pPlayer, const char *pszClass );
Sg_HandleJoinClass( this, args.ArgC() >= 2 ? args[1] : "" );
return true;
}
else if ( FStrEq( args[0], "showmenu" ) )
{
ShowViewPortPanel( PANEL_TEAM, true );
return true;
}
else if ( FStrEq( args[0], "showequip" ) )
{
if ( mp_wepselmode.GetInt() > 0 )
ShowViewPortPanel( PANEL_TEAM, true );
return true;
}
else if ( FStrEq( args[0], "joingame" ) )
{
return true;
@ -1576,7 +1682,7 @@ CHL2MPPlayerStateInfo *CHL2MP_Player::State_LookupInfo( HL2MPPlayerState state )
bool CHL2MP_Player::StartObserverMode(int mode)
{
//we only want to go into observer mode if the player asked to, not on a death timeout
if ( m_bEnterObserver == true )
if ( m_bEnterObserver == true || GetTeamNumber() == TEAM_SPECTATOR )
{
VPhysicsDestroyObject();
return BaseClass::StartObserverMode( mode );

View File

@ -97,6 +97,8 @@ public:
void SetPlayerModel( void );
void SetPlayerTeamModel( void );
Activity TranslateTeamActivity( Activity ActToTranslate );
int GetStargateClass() const { return m_iStargateClass; }
void SetStargateClass( int iClass ) { m_iStargateClass = iClass; }
float GetNextModelChangeTime( void ) { return m_flNextModelChangeTime; }
float GetNextTeamChangeTime( void ) { return m_flNextTeamChangeTime; }
@ -147,6 +149,7 @@ private:
int m_iModelType;
CNetworkVar( int, m_iSpawnInterpCounter );
CNetworkVar( int, m_iPlayerSoundType );
CNetworkVar( int, m_iStargateClass );
float m_flNextModelChangeTime;
float m_flNextTeamChangeTime;

View File

@ -304,6 +304,7 @@ $Project "Server (HL2MP)"
$File "stargate\sg_kawoosh.cpp"
$File "stargate\sg_mcp_debug.cpp"
$File "stargate\sg_player_spawn.cpp"
$File "stargate\sg_player_class.cpp"
$File "stargate\STUBS.cpp"
$File "$SRCDIR\game\shared\stargate\sg_weapons.cpp"
$File "$SRCDIR\game\shared\stargate\sg_constants.h"

View File

@ -0,0 +1,295 @@
//============= Copyright (c) StargateTC Source Team ============================
//
// Server side of the StargateTC team/class selection flow.
//
// The behavior mirrors the recovered GoldSrc rules: clients enter spectator,
// choose Tau'ri or Goa'uld, then choose one of five team classes. mp_wepselmode
// controls whether that class contributes a weapon on top of the team melee or
// default team pack.
//
//==============================================================================
#include "cbase.h"
#include "hl2mp_player.h"
#include "recipientfilter.h"
#include "stargate/sg_constants.h"
#include <limits.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar mp_wepselmode;
struct SgClassInfo_t
{
const char *name; // joinclass token
const char *display; // scoreboard/chat label
const char *avatar; // avatar sprite name (vgui/stargate/avatars/<avatar>)
const char *model; // player model
const char *weapon; // signature loadout weapon
int team; // owning team
int slot; // original class slot, 1..5 inside team
int limit; // 0 means unlimited
};
// Class roster recovered from the GoldSrc class menu and class-limit checks.
static const SgClassInfo_t s_Classes[] = {
{ "oneill", "O'Neill", "oneill", "models/stargate/player/oneill/oneill.mdl", "weapon_m92s", StargateTC::TEAM_TORRI, 1, 2 },
{ "carter", "Carter", "carter", "models/stargate/player/carter/carter.mdl", "weapon_zat", StargateTC::TEAM_TORRI, 2, 3 },
{ "jackson", "Jackson", "jackson", "models/stargate/player/jackson/jackson.mdl", "weapon_zat", StargateTC::TEAM_TORRI, 3, 3 },
{ "tealc", "Teal'c", "tealc", "models/stargate/player/tealc/tealc.mdl", "weapon_lance", StargateTC::TEAM_TORRI, 4, 0 },
{ "soldat", "Soldier", "commder", "models/stargate/player/commander/commander.mdl", "weapon_m16", StargateTC::TEAM_TORRI, 5, 0 },
{ "dieu", "System Lord", "lord", "models/stargate/player/systemlord/systemlord.mdl", "weapon_zatarc", StargateTC::TEAM_GOAULD, 1, 2 },
{ "anubis", "Anubis Guard", "anubis", "models/stargate/player/anubis/anubis.mdl", "weapon_lance", StargateTC::TEAM_GOAULD, 2, 2 },
{ "jaffa", "Jaffa", "goauld3", "models/stargate/player/goauld3/goauld3.mdl", "weapon_lance", StargateTC::TEAM_GOAULD, 3, 0 },
{ "prima", "First Prime", "snake", "models/stargate/player/snakegoauld/snakegoauld.mdl", "weapon_lance", StargateTC::TEAM_GOAULD, 4, 3 },
{ "esclave", "Horus Guard", "slave", "models/stargate/player/horus/horus.mdl", "weapon_lance", StargateTC::TEAM_GOAULD, 5, 3 },
};
// Chosen class per player slot (1..MAX_PLAYERS).
static char s_PlayerClass[ MAX_PLAYERS + 1 ][ 32 ];
static const SgClassInfo_t *Sg_FindClass( const char *name )
{
for ( int i = 0; i < ARRAYSIZE( s_Classes ); i++ )
{
if ( name && !Q_stricmp( s_Classes[i].name, name ) )
return &s_Classes[i];
}
return NULL;
}
static const SgClassInfo_t *Sg_FindClassBySlot( int team, int slot )
{
for ( int i = 0; i < ARRAYSIZE( s_Classes ); i++ )
{
if ( s_Classes[i].team == team && s_Classes[i].slot == slot )
return &s_Classes[i];
}
return NULL;
}
static int Sg_CountPlayersUsingClass( const SgClassInfo_t *pClass, CHL2MP_Player *pIgnore )
{
if ( !pClass )
return 0;
int count = 0;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CHL2MP_Player *pOther = ToHL2MPPlayer( UTIL_PlayerByIndex( i ) );
if ( !pOther || pOther == pIgnore )
continue;
if ( pOther->GetTeamNumber() != pClass->team )
continue;
if ( !Q_stricmp( s_PlayerClass[i], pClass->name ) )
count++;
}
return count;
}
static bool Sg_ClassHasRoom( const SgClassInfo_t *pClass, CHL2MP_Player *pPlayer )
{
if ( !pClass || pClass->limit <= 0 )
return true;
return Sg_CountPlayersUsingClass( pClass, pPlayer ) < pClass->limit;
}
static const SgClassInfo_t *Sg_FindAutoClass( int team, CHL2MP_Player *pPlayer )
{
const int start = random->RandomInt( 0, ARRAYSIZE( s_Classes ) - 1 );
const SgClassInfo_t *pBest = NULL;
int bestCount = INT_MAX;
for ( int offset = 0; offset < ARRAYSIZE( s_Classes ); offset++ )
{
const SgClassInfo_t *pClass = &s_Classes[( start + offset ) % ARRAYSIZE( s_Classes )];
if ( pClass->team != team )
continue;
if ( !Sg_ClassHasRoom( pClass, pPlayer ) )
continue;
const int count = Sg_CountPlayersUsingClass( pClass, pPlayer );
if ( count < bestCount )
{
pBest = pClass;
bestCount = count;
}
}
return pBest;
}
static const SgClassInfo_t *Sg_GetStoredClass( CHL2MP_Player *pPlayer )
{
if ( !pPlayer )
return NULL;
const int idx = pPlayer->entindex();
if ( idx < 1 || idx > MAX_PLAYERS )
return NULL;
const SgClassInfo_t *pClass = Sg_FindClass( s_PlayerClass[idx] );
if ( pClass && pClass->team == pPlayer->GetTeamNumber() )
return pClass;
return NULL;
}
//-----------------------------------------------------------------------------
// HUD message senders (client side: CHudStargate::MsgFunc_*).
//-----------------------------------------------------------------------------
void Sg_SendAboutMe( CBasePlayer *pPlayer )
{
if ( !pPlayer )
return;
CSingleUserRecipientFilter filter( pPlayer );
filter.MakeReliable();
UserMessageBegin( filter, "SgAboutMe" );
WRITE_SHORT( pPlayer->entindex() );
WRITE_SHORT( pPlayer->GetTeamNumber() );
MessageEnd();
}
void Sg_SendAddPortrait( CBasePlayer *pPlayer, const char *avatar )
{
if ( !pPlayer )
return;
CSingleUserRecipientFilter filter( pPlayer );
filter.MakeReliable();
UserMessageBegin( filter, "SgAddPortrait" );
WRITE_SHORT( pPlayer->entindex() );
WRITE_SHORT( pPlayer->GetTeamNumber() );
WRITE_STRING( avatar ? avatar : "" );
MessageEnd();
}
void Sg_ResetPlayerClass( CHL2MP_Player *pPlayer )
{
if ( !pPlayer )
return;
const int idx = pPlayer->entindex();
if ( idx < 1 || idx > MAX_PLAYERS )
return;
s_PlayerClass[idx][0] = '\0';
pPlayer->SetStargateClass( 0 );
}
//-----------------------------------------------------------------------------
// Apply the stored class. Safe to call from spawn and from joinclass.
//-----------------------------------------------------------------------------
void Sg_ApplyClass( CHL2MP_Player *pPlayer, bool bResetLoadout )
{
if ( !pPlayer )
return;
Sg_SendAboutMe( pPlayer );
const SgClassInfo_t *pClass = Sg_GetStoredClass( pPlayer );
if ( !pClass )
{
pPlayer->SetStargateClass( 0 );
return;
}
pPlayer->SetStargateClass( pClass->slot );
pPlayer->SetModel( pClass->model );
pPlayer->SetupPlayerSoundsByModel( pClass->model );
Sg_SendAddPortrait( pPlayer, pClass->avatar );
if ( !pPlayer->IsAlive() )
return;
if ( bResetLoadout )
{
pPlayer->RemoveAllItems( true );
pPlayer->GiveDefaultItems();
}
if ( mp_wepselmode.GetInt() <= 0 || !pClass->weapon[0] )
return;
if ( !pPlayer->Weapon_OwnsThisType( pClass->weapon ) )
pPlayer->GiveNamedItem( pClass->weapon );
CBaseCombatWeapon *pWeapon = pPlayer->Weapon_OwnsThisType( pClass->weapon );
if ( pWeapon )
pPlayer->Weapon_Switch( pWeapon );
}
//-----------------------------------------------------------------------------
// `joinclass <name>`, `joinclass auto`, and legacy `classe <1..5>`.
//-----------------------------------------------------------------------------
void Sg_HandleJoinClass( CHL2MP_Player *pPlayer, const char *pszClass )
{
if ( !pPlayer )
return;
const int idx = pPlayer->entindex();
if ( idx < 1 || idx > MAX_PLAYERS )
return;
const SgClassInfo_t *pClass = NULL;
const char *pClassArg = pszClass ? pszClass : "";
if ( !Q_stricmp( pClassArg, "auto" ) )
{
if ( pPlayer->GetTeamNumber() != StargateTC::TEAM_TORRI && pPlayer->GetTeamNumber() != StargateTC::TEAM_GOAULD )
pPlayer->HandleCommand_JoinTeam( 0 );
pClass = Sg_FindAutoClass( pPlayer->GetTeamNumber(), pPlayer );
}
else if ( pClassArg[0] >= '0' && pClassArg[0] <= '9' )
{
pClass = Sg_FindClassBySlot( pPlayer->GetTeamNumber(), atoi( pClassArg ) );
}
else
{
pClass = Sg_FindClass( pClassArg );
}
if ( !pClass )
{
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "joinclass: unknown class\n" );
return;
}
if ( pPlayer->GetTeamNumber() != pClass->team )
{
if ( !pPlayer->HandleCommand_JoinTeam( pClass->team ) )
return;
}
if ( !Sg_ClassHasRoom( pClass, pPlayer ) )
{
ClientPrint( pPlayer, HUD_PRINTCENTER, "That class is full." );
return;
}
Q_strncpy( s_PlayerClass[idx], pClass->name, sizeof( s_PlayerClass[idx] ) );
char msg[ 96 ];
Q_snprintf( msg, sizeof( msg ), "Class: %s\n", pClass->display );
ClientPrint( pPlayer, HUD_PRINTTALK, msg );
Sg_ApplyClass( pPlayer, true );
}
//-----------------------------------------------------------------------------
// Called from CHL2MP_Player::Spawn so the class loadout/HUD survive respawns.
//-----------------------------------------------------------------------------
void Sg_OnPlayerSpawn( CHL2MP_Player *pPlayer )
{
Sg_ApplyClass( pPlayer, false );
}

View File

@ -811,14 +811,7 @@ void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer )
}
else
{
if ( Q_stristr( szModelName, "models/human") )
{
pHL2Player->ChangeTeam( TEAM_REBELS );
}
else
{
pHL2Player->ChangeTeam( TEAM_COMBINE );
}
pHL2Player->SetPlayerTeamModel();
}
}
if ( sv_report_client_settings.GetInt() == 1 )