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.
3053 lines
80 KiB
3053 lines
80 KiB
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
// HACKHACK fix this include
|
|
#if defined( _WIN32 ) && !defined( _X360 )
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
#include "tier0/vprof.h"
|
|
#include "server.h"
|
|
#include "host_cmd.h"
|
|
#include "keys.h"
|
|
#include "screen.h"
|
|
#include "vengineserver_impl.h"
|
|
#include "host_saverestore.h"
|
|
#include "sv_filter.h"
|
|
#include "gl_matsysiface.h"
|
|
#include "pr_edict.h"
|
|
#include "world.h"
|
|
#include "checksum_engine.h"
|
|
#include "const.h"
|
|
#include "sv_main.h"
|
|
#include "host.h"
|
|
#include "demo.h"
|
|
#include "cdll_int.h"
|
|
#include "networkstringtableserver.h"
|
|
#include "networkstringtableclient.h"
|
|
#include "host_state.h"
|
|
#include "string_t.h"
|
|
#include "tier0/dbg.h"
|
|
#include "testscriptmgr.h"
|
|
#include "r_local.h"
|
|
#include "PlayerState.h"
|
|
#include "enginesingleuserfilter.h"
|
|
#include "profile.h"
|
|
#include "protocol.h"
|
|
#include "cl_main.h"
|
|
#include "sv_steamauth.h"
|
|
#include "zone.h"
|
|
#include "GameEventManager.h"
|
|
#include "datacache/idatacache.h"
|
|
#include "sys_dll.h"
|
|
#include "cmd.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "filesystem.h"
|
|
#include "filesystem_engine.h"
|
|
#include "icliententitylist.h"
|
|
#include "icliententity.h"
|
|
#include "hltvserver.h"
|
|
#if defined( REPLAY_ENABLED )
|
|
#include "replayserver.h"
|
|
#endif
|
|
#include "cdll_engine_int.h"
|
|
#include "cl_steamauth.h"
|
|
#include "cl_splitscreen.h"
|
|
#ifndef DEDICATED
|
|
#include "vgui_baseui_interface.h"
|
|
#endif
|
|
#include "sound.h"
|
|
#include "voice.h"
|
|
#include "sv_rcon.h"
|
|
#if defined( _X360 )
|
|
#include "xbox/xbox_console.h"
|
|
#include "xbox/xbox_launch.h"
|
|
#elif defined( _PS3 )
|
|
#include "ps3/ps3_console.h"
|
|
#include "tls_ps3.h"
|
|
#endif
|
|
#include "filesystem/IQueuedLoader.h"
|
|
#include "filesystem/IXboxInstaller.h"
|
|
#include "toolframework/itoolframework.h"
|
|
#include "fmtstr.h"
|
|
#include "tier3/tier3.h"
|
|
#include "matchmaking/imatchframework.h"
|
|
#include "tier2/tier2.h"
|
|
#include "shaderapi/gpumemorystats.h"
|
|
#include "snd_audio_source.h"
|
|
#include "netconsole.h"
|
|
#include "tier2/fileutils.h"
|
|
|
|
#if POSIX
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include "ixboxsystem.h"
|
|
extern IXboxSystem *g_pXboxSystem;
|
|
|
|
extern IVEngineClient *engineClient;
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define STEAM_PREFIX "STEAM_"
|
|
|
|
#ifndef DEDICATED
|
|
bool g_bInEditMode = false;
|
|
bool g_bInCommentaryMode = false;
|
|
#endif
|
|
KeyValues *g_pLaunchOptions = NULL;
|
|
|
|
void PerformKick( cmd_source_t commandSource, int iSearchIndex, char* szSearchString, bool bForceKick, const char* pszMessage );
|
|
|
|
ConVar host_name_store( "host_name_store", "1", FCVAR_RELEASE, "Whether hostname is recorded in game events and GOTV." );
|
|
ConVar host_players_show( "host_players_show", "1", FCVAR_RELEASE, "How players are disclosed in server queries: 0 - query disabled, 1 - show only max players count, 2 - show all players" );
|
|
ConVar host_info_show( "host_info_show", "1", FCVAR_RELEASE, "How server info gets disclosed in server queries: 0 - query disabled, 1 - show only general info, 2 - show full info" );
|
|
ConVar host_rules_show( "host_rules_show", "1", FCVAR_RELEASE, "How server rules get disclosed in server queries: 0 - query disabled, 1 - query enabled" );
|
|
static void HostnameChanged( IConVar *pConVar, const char *pOldValue, float flOldValue )
|
|
{
|
|
Steam3Server().NotifyOfServerNameChange();
|
|
|
|
if ( sv.IsActive() && host_name_store.GetBool() )
|
|
{
|
|
// look up the descriptor first to avoid a DevMsg for HL2 and mods that don't define a
|
|
// hostname_change event
|
|
CGameEventDescriptor *descriptor = g_GameEventManager.GetEventDescriptor( "hostname_changed" );
|
|
if ( descriptor )
|
|
{
|
|
IGameEvent *event = g_GameEventManager.CreateEvent( "hostname_changed" );
|
|
if ( event )
|
|
{
|
|
ConVarRef var( pConVar );
|
|
event->SetString( "hostname", var.GetString() );
|
|
g_GameEventManager.FireEvent( event );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ConVar host_name( "hostname", "", FCVAR_RELEASE, "Hostname for server.", false, 0.0f, false, 0.0f, HostnameChanged );
|
|
ConVar host_map( "host_map", "", FCVAR_RELEASE, "Current map name." );
|
|
|
|
bool CanShowHostTvStatus()
|
|
{
|
|
if ( !serverGameDLL )
|
|
return true;
|
|
|
|
if ( serverGameDLL->IsValveDS() )
|
|
{
|
|
// By default OFFICIAL server will NOT print TV information in "status" output
|
|
// Running with -display_tv_status will reveal GOTV information
|
|
static bool s_bCanShowHostTvStatusOFFICIAL = !!CommandLine()->FindParm( "-display_tv_status" );
|
|
return s_bCanShowHostTvStatusOFFICIAL;
|
|
}
|
|
else
|
|
{
|
|
// By default COMMUNITY server will print TV information in "status" output
|
|
// Running with -disable_tv_status will conceal GOTV information
|
|
static bool s_bCanShowHostTvStatusCOMMUNITY = !CommandLine()->FindParm( "-disable_tv_status" );
|
|
return s_bCanShowHostTvStatusCOMMUNITY;
|
|
}
|
|
}
|
|
|
|
#ifdef _PS3
|
|
ConVar ps3_host_quit_graceperiod( "ps3_host_quit_graceperiod", "7", FCVAR_DEVELOPMENTONLY, "Time granted for save operations to finish" );
|
|
ConVar ps3_host_quit_debugpause( "ps3_host_quit_debugpause", "0", FCVAR_DEVELOPMENTONLY, "Time to stall quit for debug purposes" );
|
|
#endif
|
|
|
|
ConVar voice_recordtofile("voice_recordtofile", "0", FCVAR_RELEASE, "Record mic data and decompressed voice data into 'voice_micdata.wav' and 'voice_decompressed.wav'");
|
|
ConVar voice_inputfromfile("voice_inputfromfile", "0",FCVAR_RELEASE, "Get voice input from 'voice_input.wav' rather than from the microphone.");
|
|
|
|
uint GetSteamAppID()
|
|
{
|
|
#ifndef DEDICATED
|
|
if ( Steam3Client().SteamUtils() )
|
|
return Steam3Client().SteamUtils()->GetAppID();
|
|
#endif
|
|
|
|
if ( Steam3Server().SteamGameServerUtils() )
|
|
return Steam3Server().SteamGameServerUtils()->GetAppID();
|
|
|
|
return 215; // defaults to Source SDK Base (215) if no steam.inf can be found.
|
|
}
|
|
|
|
EUniverse GetSteamUniverse()
|
|
{
|
|
#ifndef DEDICATED
|
|
if ( Steam3Client().SteamUtils() )
|
|
return Steam3Client().SteamUtils()->GetConnectedUniverse();
|
|
#endif
|
|
|
|
if ( Steam3Server().SteamGameServerUtils() )
|
|
return Steam3Server().SteamGameServerUtils()->GetConnectedUniverse();
|
|
|
|
return k_EUniverseInvalid;
|
|
}
|
|
|
|
// Globals
|
|
int gHostSpawnCount = 0;
|
|
|
|
// If any quit handlers balk, then aborts quit sequence
|
|
bool EngineTool_CheckQuitHandlers();
|
|
|
|
#if defined( _GAMECONSOLE )
|
|
void Host_Quit_f (void);
|
|
void PS3_sysutil_callback_forwarder( uint64 uiStatus, uint64 uiParam );
|
|
void Quit_gameconsole_f( bool bWarmRestart, bool bUnused )
|
|
{
|
|
#if defined( _DEMO )
|
|
if ( Host_IsDemoExiting() )
|
|
{
|
|
// for safety, only want to play this under demo exit conditions
|
|
// which guaranteed us a safe exiting context
|
|
game->PlayVideoListAndWait( "media/DemoUpsellVids.txt" );
|
|
}
|
|
#endif
|
|
|
|
#if defined( _X360 ) && defined( _DEMO )
|
|
// demo version has to support variants of the launch structures
|
|
// demo version must reply with exact demo launch structure if provided
|
|
unsigned int launchID;
|
|
int launchSize;
|
|
void *pLaunchData;
|
|
bool bValid = XboxLaunch()->GetLaunchData( &launchID, &pLaunchData, &launchSize );
|
|
if ( bValid && Host_IsDemoHostedFromShell() )
|
|
{
|
|
XboxLaunch()->SetLaunchData( pLaunchData, launchSize, LF_UNKNOWNDATA );
|
|
g_pMaterialSystem->PersistDisplay();
|
|
XBX_DisconnectConsoleMonitor();
|
|
|
|
const char *pImageName = XLAUNCH_KEYWORD_DEFAULT_APP;
|
|
if ( launchID == LAUNCH_DATA_DEMO_ID )
|
|
{
|
|
pImageName = ((LD_DEMO*)pLaunchData)->szLauncherXEX;
|
|
}
|
|
XboxLaunch()->Launch( pImageName );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _X360
|
|
// must be first, will cause a reset of the launch if we have never been re-launched
|
|
// all further XboxLaunch() operations MUST be writes, otherwise reset
|
|
int launchFlags = LF_EXITFROMGAME;
|
|
|
|
// block until the installer stops
|
|
g_pXboxInstaller->IsStopped( true );
|
|
if ( g_pXboxInstaller->IsFullyInstalled() )
|
|
{
|
|
launchFlags |= LF_INSTALLEDTOCACHE;
|
|
}
|
|
|
|
// allocate the full payload
|
|
int nPayloadSize = XboxLaunch()->MaxPayloadSize();
|
|
byte *pPayload = (byte *)stackalloc( nPayloadSize );
|
|
V_memset( pPayload, 0, sizeof( nPayloadSize ) );
|
|
|
|
// payload is at least the command line
|
|
// any user data needed must be placed AFTER the command line
|
|
const char *pCmdLine = CommandLine()->GetCmdLine();
|
|
int nCmdLineLength = (int)strlen( pCmdLine ) + 1;
|
|
V_memcpy( pPayload, pCmdLine, min( nPayloadSize, nCmdLineLength ) );
|
|
|
|
// add any other data here to payload, after the command line
|
|
// ...
|
|
|
|
// Collect settings to preserve across restarts
|
|
int numGameUsers = XBX_GetNumGameUsers();
|
|
char slot2ctrlr[4];
|
|
char slot2guest[4];
|
|
int ctrlr2storage[4];
|
|
|
|
for ( int k = 0; k < 4; ++ k )
|
|
{
|
|
slot2ctrlr[k] = (char) XBX_GetUserId( k );
|
|
slot2guest[k] = (char) XBX_GetUserIsGuest( k );
|
|
ctrlr2storage[k] = XBX_GetStorageDeviceId( k );
|
|
}
|
|
|
|
// storage device may have changed since previous launch
|
|
XboxLaunch()->SetStorageID( ctrlr2storage );
|
|
|
|
// Close the storage devices
|
|
g_pXboxSystem->CloseAllContainers();
|
|
|
|
DWORD nUserID = XBX_GetPrimaryUserId();
|
|
XboxLaunch()->SetUserID( nUserID );
|
|
XboxLaunch()->SetSlotUsers( numGameUsers, slot2ctrlr, slot2guest );
|
|
|
|
if ( bWarmRestart )
|
|
{
|
|
// a restart is an attempt at a hidden reboot-in-place
|
|
launchFlags |= LF_WARMRESTART;
|
|
}
|
|
|
|
// set our own data and relaunch self
|
|
bool bLaunch = XboxLaunch()->SetLaunchData( pPayload, nPayloadSize, launchFlags );
|
|
#if defined( _DEMO )
|
|
bLaunch = true;
|
|
#endif
|
|
if ( bLaunch )
|
|
{
|
|
// Can't send anything to VXConsole; about to abandon connection
|
|
// VXConsole tries to respond but can't and throws the timeout crash
|
|
// COM_TimestampedLog( "Launching: \"%s\" Flags: 0x%8.8x", pCmdLine, XboxLaunch()->GetLaunchFlags() );
|
|
g_pMaterialSystem->PersistDisplay();
|
|
XBX_DisconnectConsoleMonitor();
|
|
#if defined( CSTRIKE15 )
|
|
XboxLaunch()->SetLaunchData( NULL, 0, 0 );
|
|
XboxLaunch()->Launch( XLAUNCH_KEYWORD_DASH_ARCADE );
|
|
#else
|
|
XboxLaunch()->Launch();
|
|
#endif // defined( CSTRIKE15 )
|
|
}
|
|
#elif defined( _PS3 )
|
|
// TODO: preserve when a "restart" is requested!
|
|
Assert( !bWarmRestart );
|
|
Assert( !bUnused );
|
|
if ( bWarmRestart )
|
|
{
|
|
DevWarning( "TODO: PS3 quit_x360 restart is not implemented yet!\n" );
|
|
}
|
|
|
|
// Prevent re-entry
|
|
static bool s_bQuitPreventReentry = false;
|
|
if ( s_bQuitPreventReentry )
|
|
return;
|
|
s_bQuitPreventReentry = true;
|
|
|
|
// We must go into single-threaded rendering
|
|
Host_AllowQueuedMaterialSystem( false );
|
|
|
|
// Make sure everybody received the EXITGAME callback, might happen multiple times now
|
|
float const flTimeStampStart = Plat_FloatTime();
|
|
float flGracePeriod = ps3_host_quit_graceperiod.GetFloat();
|
|
flGracePeriod = MIN( 7, flGracePeriod );
|
|
flGracePeriod = MAX( 0, flGracePeriod );
|
|
float const flTimeStampForceShutdown = flTimeStampStart + flGracePeriod;
|
|
uint64 uiLastCountdownNotificationSent = 0;
|
|
for ( ; ; )
|
|
{
|
|
enum ShutdownSystemsWait_t
|
|
{
|
|
kSysSaveRestore,
|
|
kSysSaveUtilV2,
|
|
kSysSteamClient,
|
|
kSysDebugPause,
|
|
kSysShutdownSystemsCount
|
|
};
|
|
char const *szSystems[kSysShutdownSystemsCount] = {0};
|
|
char const *szSystemsRequiredState[kSysShutdownSystemsCount] = {0};
|
|
|
|
// Poll systems whether they are ready to shutdown
|
|
if ( saverestore && saverestore->IsSaveInProgress() )
|
|
szSystems[kSysSaveRestore] = "saverestore";
|
|
extern bool SaveUtilV2_CanShutdown();
|
|
if ( !SaveUtilV2_CanShutdown() )
|
|
szSystems[kSysSaveUtilV2] = "SaveUtilV2";
|
|
if ( Steam3Client().SteamUtils() && !Steam3Client().SteamUtils()->BIsReadyToShutdown() )
|
|
szSystems[kSysSteamClient] = "steamclient";
|
|
if ( ( ps3_host_quit_debugpause.GetFloat() > 0 ) && ( Plat_FloatTime() < flTimeStampStart + ps3_host_quit_debugpause.GetFloat() ) )
|
|
szSystems[kSysDebugPause] = "debugpause";
|
|
|
|
if ( !Q_memcmp( szSystemsRequiredState, szSystems, sizeof( szSystemsRequiredState ) ) )
|
|
{
|
|
DevMsg( "PS3 shutdown procedure: all systems ready (%.2f sec elapsed)\n", ( Plat_FloatTime() - flTimeStampStart ) );
|
|
break;
|
|
}
|
|
|
|
uint64 uiCountdownNotification = 1 + ( flTimeStampForceShutdown - Plat_FloatTime() );
|
|
if ( uiCountdownNotification != uiLastCountdownNotificationSent )
|
|
{
|
|
uiLastCountdownNotificationSent = uiCountdownNotification;
|
|
PS3_sysutil_callback_forwarder( CELL_SYSUTIL_REQUEST_EXITGAME, uiCountdownNotification );
|
|
DevWarning( "PS3 shutdown procedure: %.2f sec elapsed...\n", ( Plat_FloatTime() - flTimeStampStart ) );
|
|
int nNotReadySystemsCount = 0;
|
|
for ( int jj = 0; jj < ARRAYSIZE( szSystems ); ++ jj )
|
|
{
|
|
if ( szSystems[jj] )
|
|
{
|
|
DevWarning( " system not ready : %s\n", szSystems[jj] );
|
|
++ nNotReadySystemsCount;
|
|
}
|
|
}
|
|
DevWarning( "PS3 shutdown procedure: waiting for %d systems to be ready for shutdown (%.2f sec remaining)...\n", nNotReadySystemsCount, ( flTimeStampForceShutdown - Plat_FloatTime() ) );
|
|
}
|
|
|
|
if ( Plat_FloatTime() >= flTimeStampForceShutdown )
|
|
{
|
|
DevWarning( "FORCING PS3 SHUTDOWN PROCEDURE: NOT ALL SYSTEMS READY (%.2f sec elapsed)...\n", ( Plat_FloatTime() - flTimeStampStart ) );
|
|
break;
|
|
}
|
|
|
|
// Perform blank vsync'ed flips
|
|
static ConVarRef mat_vsync( "mat_vsync" );
|
|
mat_vsync.SetValue( true );
|
|
g_pMaterialSystem->SetFlipPresentFrequency( 1 ); // let it flip every VSYNC, we let interrupt handler throttle this loop to conform with TCR#R092 [no more than 60 fps]
|
|
|
|
// Dummy frame
|
|
g_pMaterialSystem->BeginFrame( 1.0f/60.0f );
|
|
CMatRenderContextPtr pRenderContext;
|
|
pRenderContext.GetFrom( g_pMaterialSystem );
|
|
pRenderContext->ClearColor4ub( 0, 0, 0, 255 );
|
|
pRenderContext->ClearBuffers( true, true, true );
|
|
pRenderContext.SafeRelease();
|
|
g_pMaterialSystem->EndFrame();
|
|
g_pMaterialSystem->SwapBuffers();
|
|
|
|
// Pump system event queue
|
|
XBX_ProcessEvents();
|
|
XBX_DispatchEventsQueue();
|
|
}
|
|
|
|
// QUIT
|
|
Warning( "[PS3 SYSTEM] REQUEST EXITGAME INITIATING QUIT @ %.3f\n", Plat_FloatTime() );
|
|
Host_Quit_f();
|
|
#else
|
|
Assert( 0 );
|
|
#error
|
|
#endif
|
|
}
|
|
|
|
CON_COMMAND( quit_gameconsole, "" )
|
|
{
|
|
Quit_gameconsole_f(
|
|
args.FindArg( "restart" ) != NULL,
|
|
args.FindArg( "invite" ) != NULL );
|
|
}
|
|
#endif
|
|
|
|
// store arbitrary launch arguments in KeyValues to avoid having to add code for every new
|
|
// launch parameter (like edit mode, commentary mode, background, etc. do)
|
|
void SetLaunchOptions( const CCommand &args )
|
|
{
|
|
if ( g_pLaunchOptions )
|
|
{
|
|
g_pLaunchOptions->deleteThis();
|
|
}
|
|
g_pLaunchOptions = new KeyValues( "LaunchOptions" );
|
|
for ( int i = 0 ; i < args.ArgC() ; i++ )
|
|
{
|
|
g_pLaunchOptions->SetString( va("Arg%d", i), args[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Host_Quit_f
|
|
==================
|
|
*/
|
|
void Host_Quit_f (void)
|
|
{
|
|
#if !defined(DEDICATED)
|
|
if ( !EngineTool_CheckQuitHandlers() )
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
HostState_Shutdown();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( _restart, "Shutdown and restart the engine." )
|
|
{
|
|
/*
|
|
// FIXME: How to handle restarts?
|
|
#ifndef DEDICATED
|
|
if ( !EngineTool_CheckQuitHandlers() )
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
*/
|
|
|
|
HostState_Restart();
|
|
}
|
|
|
|
#ifndef DEDICATED
|
|
//-----------------------------------------------------------------------------
|
|
// A console command to spew out driver information
|
|
//-----------------------------------------------------------------------------
|
|
void Host_LightCrosshair (void);
|
|
|
|
static ConCommand light_crosshair( "light_crosshair", Host_LightCrosshair, "Show texture color at crosshair", FCVAR_CHEAT );
|
|
|
|
void Host_LightCrosshair (void)
|
|
{
|
|
Vector endPoint;
|
|
Vector lightmapColor;
|
|
|
|
// max_range * sqrt(3)
|
|
VectorMA( MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint );
|
|
|
|
R_LightVec( MainViewOrigin(), endPoint, true, lightmapColor );
|
|
int r = LinearToTexture( lightmapColor.x );
|
|
int g = LinearToTexture( lightmapColor.y );
|
|
int b = LinearToTexture( lightmapColor.z );
|
|
|
|
ConMsg( "Luxel Value: %d %d %d\n", r, g, b );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
==================
|
|
Host_Status_PrintClient
|
|
|
|
Print client info to console
|
|
==================
|
|
*/
|
|
void Host_Status_PrintClient( IClient *client, bool bShowAddress, void (*print) (const char *fmt, ...) )
|
|
{
|
|
INetChannelInfo *nci = client->GetNetChannel();
|
|
|
|
const char *state = "challenging";
|
|
|
|
if ( client->IsActive() )
|
|
state = "active";
|
|
else if ( client->IsSpawned() )
|
|
state = "spawning";
|
|
else if ( client->IsConnected() )
|
|
state = "connecting";
|
|
|
|
if ( nci != NULL )
|
|
{
|
|
print( "# %2i %i \"%s\" %s %s %i %i %s %d",
|
|
client->GetUserID(), client->GetPlayerSlot() + 1, client->GetClientName(), client->GetNetworkIDString(), COM_FormatSeconds( nci->GetTimeConnected() ),
|
|
(int)(1000.0f*nci->GetAvgLatency( FLOW_OUTGOING )), (int)(100.0f*nci->GetAvgLoss(FLOW_INCOMING)), state, (int)nci->GetDataRate() );
|
|
|
|
if ( bShowAddress )
|
|
{
|
|
print( " %s", nci->GetAddress() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print( "#%2i \"%s\" %s %s %.0f",
|
|
client->GetUserID(), client->GetClientName(), client->GetNetworkIDString(), state, client->GetUpdateRate() );
|
|
}
|
|
|
|
print( "\n" );
|
|
|
|
}
|
|
|
|
typedef void ( *FnPrintf_t )(const char *fmt, ...);
|
|
|
|
void Host_Client_Printf(const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[1024];
|
|
|
|
va_start (argptr,fmt);
|
|
Q_vsnprintf (string, sizeof( string ), fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
host_client->ClientPrintf( "%s", string );
|
|
}
|
|
|
|
static void Host_Client_PrintfStub(const char *fmt, ...)
|
|
{
|
|
}
|
|
|
|
void Host_PrintStatus( cmd_source_t commandSource, void ( *print )(const char *fmt, ...), bool bShort )
|
|
{
|
|
bool bWithAddresses = ( ( commandSource != kCommandSrcNetClient ) && ( commandSource != kCommandSrcNetServer ) && ( print == ConMsg ) ); // guarantee to never print for remote
|
|
|
|
IClient *client;
|
|
int j;
|
|
|
|
if ( !print ) { return; }
|
|
|
|
// ============================================================
|
|
// Server status information.
|
|
print( "hostname: %s\n", host_name.GetString() );
|
|
|
|
const char *pchSecureReasonString = "";
|
|
const char *pchUniverse = "";
|
|
bool bGSSecure = Steam3Server().BSecure();
|
|
if ( !bGSSecure && Steam3Server().BWantsSecure() )
|
|
{
|
|
if ( Steam3Server().BLoggedOn() )
|
|
{
|
|
pchSecureReasonString = "(secure mode enabled, connected to Steam3)";
|
|
}
|
|
else
|
|
{
|
|
pchSecureReasonString = "(secure mode enabled, disconnected from Steam3)";
|
|
}
|
|
}
|
|
|
|
switch ( GetSteamUniverse() )
|
|
{
|
|
case k_EUniversePublic:
|
|
pchUniverse = "";
|
|
break;
|
|
case k_EUniverseBeta:
|
|
pchUniverse = "(beta)";
|
|
break;
|
|
case k_EUniverseInternal:
|
|
pchUniverse = "(internal)";
|
|
break;
|
|
case k_EUniverseDev:
|
|
pchUniverse = "(dev)";
|
|
break;
|
|
/* no such universe anymore
|
|
case k_EUniverseRC:
|
|
pchUniverse = "(rc)";
|
|
break;
|
|
*/
|
|
default:
|
|
pchUniverse = "(unknown)";
|
|
break;
|
|
}
|
|
|
|
if ( bWithAddresses )
|
|
{
|
|
print( "version : %s/%d %d/%d %s %s %s %s\n",
|
|
Sys_GetVersionString(), GetHostVersion(),
|
|
GetServerVersion(), build_number(),
|
|
bGSSecure ? "secure" : "insecure", pchSecureReasonString,
|
|
Steam3Server().GetGSSteamID().IsValid() ? Steam3Server().GetGSSteamID().Render() : "[INVALID_STEAMID]", pchUniverse );
|
|
}
|
|
else
|
|
{
|
|
print( "version : %s %s\n",
|
|
Sys_GetVersionString(),
|
|
bGSSecure ? "secure" : "insecure" );
|
|
}
|
|
|
|
if ( NET_IsMultiplayer() )
|
|
{
|
|
CUtlString sPublicIPInfo;
|
|
if ( !Steam3Server().BLanOnly() )
|
|
{
|
|
uint32 unPublicIP = Steam3Server().GetPublicIP();
|
|
if ( ( unPublicIP != 0 ) && bWithAddresses && sv.IsDedicated() )
|
|
{
|
|
netadr_t addr;
|
|
addr.SetIP( unPublicIP );
|
|
sPublicIPInfo.Format(" (public ip: %s)", addr.ToString( true ) );
|
|
}
|
|
}
|
|
print( "udp/ip : %s:%i%s\n", net_local_adr.ToString(true), sv.GetUDPPort(), sPublicIPInfo.String() );
|
|
static ConVarRef sv_steamdatagramtransport_port( "sv_steamdatagramtransport_port" );
|
|
if ( bWithAddresses && sv_steamdatagramtransport_port.GetInt() > 0 )
|
|
{
|
|
print( "sdt : =%s on port %d\n", Steam3Server().GetGSSteamID().Render(), sv_steamdatagramtransport_port.GetInt() );
|
|
}
|
|
|
|
const char *osType =
|
|
#if defined( WIN32 )
|
|
"Windows";
|
|
#elif defined( _LINUX )
|
|
"Linux";
|
|
#elif defined( PLATFORM_OSX )
|
|
"OSX";
|
|
#else
|
|
"Unknown";
|
|
#endif
|
|
|
|
print( "os : %s\n", osType );
|
|
|
|
char const *serverType = sv.IsHLTV() ? "hltv" : ( sv.IsDedicated() ? ( serverGameDLL->IsValveDS() ? "official dedicated" : "community dedicated" ) : "listen" );
|
|
print( "type : %s\n", serverType );
|
|
}
|
|
|
|
#ifndef DEDICATED // no client on dedicated server
|
|
if ( !sv.IsDedicated() && GetBaseLocalClient().IsConnected() )
|
|
{
|
|
print( "map : %s at: %d x, %d y, %d z\n", sv.GetMapName(), (int)MainViewOrigin()[0], (int)MainViewOrigin()[1], (int)MainViewOrigin()[2]);
|
|
}
|
|
#else
|
|
{
|
|
print( "map : %s\n", sv.GetMapName() );
|
|
}
|
|
#endif
|
|
|
|
if ( CanShowHostTvStatus() )
|
|
{
|
|
for ( CActiveHltvServerIterator hltv; hltv; hltv.Next() )
|
|
{
|
|
print( "gotv[%i]: port %i, delay %.1fs, rate %.1f\n", hltv.GetIndex(), hltv->GetUDPPort(), hltv->GetDirector() ? hltv->GetDirector()->GetDelay() : 0.0f, hltv->GetSnapshotRate() );
|
|
}
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( replay && replay->IsActive() )
|
|
{
|
|
print( "replay: port %i, delay %.1fs\n", replay->GetUDPPort(), replay->GetDirector()->GetDelay() );
|
|
}
|
|
#endif
|
|
|
|
int nHumans;
|
|
int nMaxHumans;
|
|
int nBots;
|
|
|
|
sv.GetMasterServerPlayerCounts( nHumans, nMaxHumans, nBots );
|
|
|
|
print( "players : %i humans, %i bots (%i/%i max) (%s)\n\n",
|
|
nHumans, nBots, nMaxHumans, sv.GetNumGameSlots(), sv.IsHibernating() ? "hibernating" : "not hibernating" );
|
|
// ============================================================
|
|
#if SUPPORT_NET_CONSOLE
|
|
if ( g_pNetConsoleMgr && g_pNetConsoleMgr->IsActive() && bWithAddresses )
|
|
{
|
|
print( "netcon : %s:%i\n", net_local_adr.ToString( true), g_pNetConsoleMgr->GetAddress().GetPort() );
|
|
}
|
|
#endif
|
|
|
|
// Early exit for this server.
|
|
if ( bShort )
|
|
{
|
|
for ( j=0 ; j < sv.GetClientCount() ; j++ )
|
|
{
|
|
client = sv.GetClient( j );
|
|
|
|
if ( !client->IsActive() )
|
|
continue;
|
|
|
|
if ( !bWithAddresses && !CanShowHostTvStatus() && client->IsHLTV() )
|
|
continue;
|
|
|
|
print( "#%i - %s\n" , j + 1, client->GetClientName() );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// the header for the status rows
|
|
print( "# userid name uniqueid connected ping loss state rate" );
|
|
if ( bWithAddresses )
|
|
{
|
|
print( " adr" );
|
|
}
|
|
print( "\n" );
|
|
|
|
for ( j=0 ; j < sv.GetClientCount() ; j++ )
|
|
{
|
|
client = sv.GetClient( j );
|
|
|
|
if ( !client->IsConnected() )
|
|
continue; // not connected yet, maybe challenging
|
|
|
|
if ( !CanShowHostTvStatus() && client->IsHLTV() )
|
|
continue;
|
|
|
|
Host_Status_PrintClient( client, bWithAddresses, print );
|
|
}
|
|
print( "#end\n" );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Host_Status_f
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( status, "Display map and connection status." )
|
|
{
|
|
void (*print) (const char *fmt, ...) = Host_Client_PrintfStub;
|
|
|
|
if ( args.Source() != kCommandSrcNetClient )
|
|
{
|
|
if ( !sv.IsActive() )
|
|
{
|
|
Cmd_ForwardToServer( args );
|
|
return;
|
|
}
|
|
#ifndef DBGFLAG_STRINGS_STRIP
|
|
print = ConMsg;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
print = Host_Client_Printf;
|
|
}
|
|
|
|
bool bShort = false;
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
if ( !Q_stricmp( args[1], "short" ) )
|
|
{
|
|
bShort = true;
|
|
}
|
|
}
|
|
|
|
Host_PrintStatus( args.Source(), print, bShort );
|
|
}
|
|
|
|
CON_COMMAND( hltv_replay_status, "Show Killer Replay status and some statistics, works on listen or dedicated server." )
|
|
{
|
|
HltvReplayStats_t hltvStats;
|
|
|
|
for ( int j = 0; j < sv.GetClientCount(); j++ )
|
|
{
|
|
IClient *client = sv.GetClient( j );
|
|
|
|
if ( !client->IsConnected() )
|
|
continue; // not connected yet, maybe challenging
|
|
|
|
if ( !CanShowHostTvStatus() && client->IsHLTV() )
|
|
continue;
|
|
|
|
INetChannelInfo *nci = client->GetNetChannel();
|
|
|
|
const char *state = "challenging";
|
|
|
|
if ( client->IsActive() )
|
|
state = "active";
|
|
else if ( client->IsSpawned() )
|
|
state = "spawning";
|
|
else if ( client->IsConnected() )
|
|
state = "connecting";
|
|
|
|
if ( nci != NULL )
|
|
{
|
|
ConMsg( "# %2i %i \"%s\" %s %s %s %s",
|
|
client->GetUserID(), client->GetPlayerSlot() + 1, client->GetClientName(), client->GetNetworkIDString(), COM_FormatSeconds( nci->GetTimeConnected() ),
|
|
state, client->GetHltvReplayStatus() );
|
|
if ( client->GetHltvReplayDelay() )
|
|
ConMsg( ", in replay NOW" );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "#%2i \"%s\" %s %s %s",
|
|
client->GetUserID(), client->GetClientName(), client->GetNetworkIDString(), state, client->GetHltvReplayStatus() );
|
|
}
|
|
|
|
if ( CGameClient *pClient = dynamic_cast< CGameClient * >( client ) )
|
|
{
|
|
if ( pClient->m_HltvReplayStats.nStartRequests )
|
|
hltvStats += pClient->m_HltvReplayStats;
|
|
if ( pClient->m_nForceWaitForTick > 0 )
|
|
{
|
|
ConMsg( ", force-waiting for tick %d - server tick %d, current frame %d", pClient->m_nForceWaitForTick, sv.GetTick(), pClient->m_pCurrentFrame ? pClient->m_pCurrentFrame->tick_count : 0 );
|
|
}
|
|
}
|
|
|
|
ConMsg( "\n" );
|
|
}
|
|
|
|
extern HltvReplayStats_t m_DisconnectedClientsHltvReplayStats;
|
|
if ( m_DisconnectedClientsHltvReplayStats.nClients > 1 )
|
|
ConMsg( "%u disconnected clients: %s\n", m_DisconnectedClientsHltvReplayStats.nClients - 1, m_DisconnectedClientsHltvReplayStats.AsString() );
|
|
|
|
if ( hltvStats.nClients > 0 )
|
|
{
|
|
ConMsg( "%u current clients: %s\n", hltvStats.nClients, hltvStats.AsString() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if defined( _X360 )
|
|
CON_COMMAND( vx_mapinfo, "" )
|
|
{
|
|
Vector org;
|
|
QAngle ang;
|
|
const char *pName;
|
|
|
|
if ( GetBaseLocalClient().IsActive() )
|
|
{
|
|
pName = GetBaseLocalClient().m_szLevelNameShort;
|
|
org = MainViewOrigin();
|
|
VectorAngles( MainViewForward(), ang );
|
|
IClientEntity *localPlayer = entitylist->GetClientEntity( GetBaseLocalClient().m_nPlayerSlot + 1 );
|
|
if ( localPlayer )
|
|
{
|
|
org = localPlayer->GetAbsOrigin();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pName = "";
|
|
org.Init();
|
|
ang.Init();
|
|
}
|
|
|
|
// HACK: This is only relevant for portal2.
|
|
Msg( "BUG REPORT PORTAL POSITIONS:\n" );
|
|
Cbuf_AddText( Cbuf_GetCurrentPlayer(), "portal_report\n" );
|
|
|
|
// send to vxconsole
|
|
xMapInfo_t mapInfo;
|
|
mapInfo.position[0] = org[0];
|
|
mapInfo.position[1] = org[1];
|
|
mapInfo.position[2] = org[2];
|
|
mapInfo.angle[0] = ang[0];
|
|
mapInfo.angle[1] = ang[1];
|
|
mapInfo.angle[2] = ang[2];
|
|
mapInfo.build = build_number();
|
|
mapInfo.skill = skill.GetInt();
|
|
|
|
// generate the qualified path where .sav files are expected to be written
|
|
char savePath[MAX_PATH];
|
|
V_snprintf( savePath, sizeof( savePath ), "%s", saverestore->GetSaveDir() );
|
|
V_StripTrailingSlash( savePath );
|
|
g_pFileSystem->RelativePathToFullPath( savePath, "MOD", mapInfo.savePath, sizeof( mapInfo.savePath ) );
|
|
V_FixSlashes( mapInfo.savePath );
|
|
|
|
if ( pName[0] )
|
|
{
|
|
// generate the qualified path from where the map was loaded
|
|
char mapPath[MAX_PATH];
|
|
V_snprintf( mapPath, sizeof( mapPath ), "maps/%s" PLATFORM_EXT ".bsp", pName );
|
|
g_pFileSystem->GetLocalPath( mapPath, mapInfo.mapPath, sizeof( mapInfo.mapPath ) );
|
|
V_FixSlashes( mapInfo.mapPath );
|
|
}
|
|
else
|
|
{
|
|
mapInfo.mapPath[0] = '\0';
|
|
}
|
|
|
|
mapInfo.details[0] = '\0';
|
|
|
|
ConVarRef host_thread_mode( "host_thread_mode" );
|
|
ConVarRef mat_queue_mode( "mat_queue_mode" );
|
|
ConVarRef snd_surround_speakers( "snd_surround_speakers" );
|
|
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Build: %d\n", build_number() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
XVIDEO_MODE videoMode;
|
|
XGetVideoMode( &videoMode );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Display: %dx%d (%s)\n", videoMode.dwDisplayWidth, videoMode.dwDisplayHeight, videoMode.fIsWideScreen ? "widescreen" : "normal" ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
int backbufferWidth, backbufferHeight;
|
|
materials->GetBackBufferDimensions( backbufferWidth, backbufferHeight );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "BackBuffer: %dx%d\n", backbufferWidth, backbufferHeight ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
// audio info
|
|
const char *pAudioInfo = "Unknown";
|
|
switch ( snd_surround_speakers.GetInt() )
|
|
{
|
|
case 2:
|
|
pAudioInfo = "Stereo";
|
|
break;
|
|
case 5:
|
|
pAudioInfo = "5.1 Digital Surround";
|
|
break;
|
|
}
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Audio: %s\n", pAudioInfo ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
// ui language
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "UI: %s\n", cl_language.GetString() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
// cvars
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "host_thread_mode: %d\n", host_thread_mode.GetInt() ),
|
|
sizeof( mapInfo.details ) );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "mat_queue_mode: %d\n", mat_queue_mode.GetInt() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
XBX_rMapInfo( &mapInfo );
|
|
}
|
|
#elif defined( _PS3 )
|
|
#include "ps3/ps3_sn.h"
|
|
CON_COMMAND( vx_mapinfo, "" )
|
|
{
|
|
Vector org;
|
|
QAngle ang;
|
|
const char *pName;
|
|
|
|
if ( GetBaseLocalClient().IsActive() )
|
|
{
|
|
pName = GetBaseLocalClient().m_szLevelNameShort;
|
|
org = MainViewOrigin();
|
|
VectorAngles( MainViewForward(), ang );
|
|
IClientEntity *localPlayer = entitylist->GetClientEntity( GetBaseLocalClient().m_nPlayerSlot + 1 );
|
|
if ( localPlayer )
|
|
{
|
|
org = localPlayer->GetAbsOrigin();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pName = "";
|
|
org.Init();
|
|
ang.Init();
|
|
}
|
|
|
|
// HACK: This is only relevant for portal2.
|
|
Msg( "BUG REPORT PORTAL POSITIONS:\n" );
|
|
Cbuf_AddText( Cbuf_GetCurrentPlayer(), "portal_report\n" );
|
|
|
|
// send to vxconsole
|
|
xMapInfo_t mapInfo;
|
|
mapInfo.position[0] = org[0];
|
|
mapInfo.position[1] = org[1];
|
|
mapInfo.position[2] = org[2];
|
|
mapInfo.angle[0] = ang[0];
|
|
mapInfo.angle[1] = ang[1];
|
|
mapInfo.angle[2] = ang[2];
|
|
mapInfo.build = build_number();
|
|
mapInfo.skill = skill.GetInt();
|
|
|
|
// generate the qualified path where .sav files are expected to be written
|
|
char savePath[MAX_PATH];
|
|
V_snprintf( savePath, sizeof( savePath ), "%s", saverestore->GetSaveDir() );
|
|
V_StripTrailingSlash( savePath );
|
|
g_pFileSystem->RelativePathToFullPath( savePath, "MOD", mapInfo.savePath, sizeof( mapInfo.savePath ) );
|
|
V_FixSlashes( mapInfo.savePath );
|
|
|
|
if ( pName[0] )
|
|
{
|
|
// generate the qualified path from where the map was loaded
|
|
char mapPath[MAX_PATH];
|
|
V_snprintf( mapPath, sizeof( mapPath ), "maps/%s" PLATFORM_EXT ".bsp", pName );
|
|
g_pFileSystem->GetLocalPath( mapPath, mapInfo.mapPath, sizeof( mapInfo.mapPath ) );
|
|
V_FixSlashes( mapInfo.mapPath );
|
|
}
|
|
else
|
|
{
|
|
mapInfo.mapPath[0] = '\0';
|
|
}
|
|
|
|
mapInfo.details[0] = '\0';
|
|
|
|
ConVarRef host_thread_mode( "host_thread_mode" );
|
|
ConVarRef mat_queue_mode( "mat_queue_mode" );
|
|
ConVarRef snd_surround_speakers( "snd_surround_speakers" );
|
|
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Build: %d\n", build_number() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
/*
|
|
XVIDEO_MODE videoMode;
|
|
XGetVideoMode( &videoMode );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Display: %dx%d (%s)\n", videoMode.dwDisplayWidth, videoMode.dwDisplayHeight, videoMode.fIsWideScreen ? "widescreen" : "normal" ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
int backbufferWidth, backbufferHeight;
|
|
materials->GetBackBufferDimensions( backbufferWidth, backbufferHeight );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "BackBuffer: %dx%d\n", backbufferWidth, backbufferHeight ),
|
|
sizeof( mapInfo.details ) );
|
|
*/
|
|
|
|
// audio info
|
|
const char *pAudioInfo = "Unknown";
|
|
switch ( snd_surround_speakers.GetInt() )
|
|
{
|
|
case 2:
|
|
pAudioInfo = "Stereo";
|
|
break;
|
|
case 5:
|
|
pAudioInfo = "5.1 Digital Surround";
|
|
break;
|
|
}
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "Audio: %s\n", pAudioInfo ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
// ui language
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "UI: %s\n", cl_language.GetString() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
// cvars
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "host_thread_mode: %d\n", host_thread_mode.GetInt() ),
|
|
sizeof( mapInfo.details ) );
|
|
V_strncat(
|
|
mapInfo.details,
|
|
CFmtStr( "mat_queue_mode: %d\n", mat_queue_mode.GetInt() ),
|
|
sizeof( mapInfo.details ) );
|
|
|
|
XBX_rMapInfo( &mapInfo );
|
|
}
|
|
|
|
|
|
CON_COMMAND( vx_screenshot, "" )
|
|
{
|
|
#if 1
|
|
g_pMaterialSystem->TransmitScreenshotToVX( );
|
|
#else
|
|
// COMPILE_TIME_ASSERT( sizeof(g_pfnSwapBufferMarker) == 8);
|
|
union FunctionPointerIsReallyADescriptor
|
|
{
|
|
void (*pFunc_t)();
|
|
struct
|
|
{
|
|
uint32 funcaddress;
|
|
int32 iToc;
|
|
} fn8;
|
|
};
|
|
|
|
FunctionPointerIsReallyADescriptor *pBreakpoint = (FunctionPointerIsReallyADescriptor *)g_pfnSwapBufferMarker;
|
|
|
|
// breakpoint.pFunc_t = g_pfnSwapBufferMarker;
|
|
|
|
uint64 uBPAddress;
|
|
/// Address of a pointer that points to the image in memory
|
|
char * pFrameBuffer;
|
|
/// Width of image
|
|
uint32 uWidth;
|
|
/// Height of image
|
|
uint32 uHeight;
|
|
/// Image pitch (as described in CellGCMSurface) - in bytes
|
|
uint32 uPitch;
|
|
/// Image colour settings (0 = X8R8G8B8, 1 = X8B8G8R8, 2 = R16G16B16X16)
|
|
IMaterialSystem::VRAMScreenShotInfoColor_t colour ;
|
|
|
|
// get one of the screen buffers. Since we breakpoint the game anyway I don't think
|
|
// it really matters if we're two out of date. (For this test, anyway.)
|
|
g_pMaterialSystem->GetVRAMScreenShotInfo( &pFrameBuffer, &uWidth, &uHeight, &uPitch, &colour );
|
|
g_pValvePS3Console->VRAMDumpingInfo( (uint64)pBreakpoint->fn8.funcaddress,
|
|
(uint64)pFrameBuffer, uWidth, uHeight, uPitch, colour );
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Host_Ping_f
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( ping, "Display ping to server." )
|
|
{
|
|
if ( args.Source() != kCommandSrcNetClient )
|
|
{
|
|
Cmd_ForwardToServer( args );
|
|
return;
|
|
}
|
|
|
|
host_client->ClientPrintf( "Client ping times:\n" );
|
|
|
|
for ( int i=0; i< sv.GetClientCount(); i++ )
|
|
{
|
|
IClient *client = sv.GetClient(i);
|
|
|
|
if ( !client->IsConnected() || client->IsFakeClient() )
|
|
continue;
|
|
|
|
host_client->ClientPrintf ("%4.0f ms : %s\n",
|
|
1000.0f * client->GetNetChannel()->GetAvgLatency( FLOW_OUTGOING ), client->GetClientName() );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : editmode -
|
|
//-----------------------------------------------------------------------------
|
|
extern void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int maxLength );
|
|
|
|
bool CL_HL2Demo_MapCheck( const char *name )
|
|
{
|
|
if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() )
|
|
{
|
|
if ( !Q_stricmp( name, "d1_trainstation_01" ) ||
|
|
!Q_stricmp( name, "d1_trainstation_02" ) ||
|
|
!Q_stricmp( name, "d1_town_01" ) ||
|
|
!Q_stricmp( name, "d1_town_01a" ) ||
|
|
!Q_stricmp( name, "d1_town_02" ) ||
|
|
!Q_stricmp( name, "d1_town_03" ) ||
|
|
!Q_stricmp( name, "background01" ) ||
|
|
!Q_stricmp( name, "background03" )
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CL_PortalDemo_MapCheck( const char *name )
|
|
{
|
|
if ( IsPC() && CL_IsPortalDemo() && !sv.IsDedicated() )
|
|
{
|
|
if ( !Q_stricmp( name, "testchmb_a_00" ) ||
|
|
!Q_stricmp( name, "testchmb_a_01" ) ||
|
|
!Q_stricmp( name, "testchmb_a_02" ) ||
|
|
!Q_stricmp( name, "testchmb_a_03" ) ||
|
|
!Q_stricmp( name, "testchmb_a_04" ) ||
|
|
!Q_stricmp( name, "testchmb_a_05" ) ||
|
|
!Q_stricmp( name, "testchmb_a_06" ) ||
|
|
!Q_stricmp( name, "background1" )
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum EMapFlags
|
|
{
|
|
EMAP_NONE = 0,
|
|
|
|
EMAP_EDIT_MODE = (1<<0),
|
|
EMAP_BACKGROUND = (1<<1),
|
|
EMAP_COMMENTARY = (1<<2),
|
|
EMAP_SPLITSCREEN = (1<<3)
|
|
|
|
};
|
|
|
|
int _Host_Map_f_CompletionFunc( char const *cmdname, char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] );
|
|
|
|
// Note, leaves name alone if no match possible
|
|
static bool Host_Map_Helper_FuzzyName( const CCommand &args, char *name, size_t bufsize )
|
|
{
|
|
char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ];
|
|
CUtlString argv0;
|
|
argv0 = args.Arg( 0 );
|
|
argv0 += " ";
|
|
|
|
if ( _Host_Map_f_CompletionFunc( argv0, args[1], commands ) > 0 )
|
|
{
|
|
Q_strncpy( name, &commands[ 0 ][ argv0.Length() ], bufsize );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Host_Changelevel_f( const CCommand &args );
|
|
void Host_Map_Helper( const CCommand &args, EMapFlags flags )
|
|
{
|
|
char name[MAX_QPATH];
|
|
|
|
if (args.ArgC() < 2)
|
|
{
|
|
Warning("No map specified\n");
|
|
return;
|
|
}
|
|
|
|
if ( ( sv.IsActive() && !sv.IsSinglePlayerGame() && !sv.IsLevelMainMenuBackground() ) ||
|
|
( sv.IsActive() && sv.IsDedicated() ) )
|
|
{
|
|
// Using the 'map' command while in a map disconnects all players.
|
|
// Ease the pain of this common error by forwarding to the correct command.
|
|
Host_Changelevel_f( args );
|
|
return;
|
|
}
|
|
|
|
bool bBackground = ( flags & EMAP_BACKGROUND ) != 0;
|
|
bool bSplitScreenConnect = ( flags & EMAP_SPLITSCREEN ) != 0;
|
|
|
|
char ppath[ MAX_QPATH ];
|
|
|
|
// If there is a .bsp on the end, strip it off!
|
|
Q_StripExtension( args[ 1 ], ppath, sizeof( ppath ) );
|
|
|
|
// Call with quiet flag for initial search
|
|
if ( !modelloader->Map_IsValid( ppath, true ) )
|
|
{
|
|
Host_Map_Helper_FuzzyName( args, ppath, sizeof( ppath ) );
|
|
if ( !modelloader->Map_IsValid( ppath ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", ppath );
|
|
return;
|
|
}
|
|
}
|
|
|
|
GetPlatformMapPath( ppath, name, sizeof( name ) );
|
|
|
|
// If I was in edit mode reload config file
|
|
// to overwrite WC edit key bindings
|
|
#if !defined(DEDICATED)
|
|
bool bCommentary = ( flags & EMAP_COMMENTARY ) != 0;
|
|
bool bEditmode = ( flags & EMAP_EDIT_MODE ) != 0;
|
|
if ( !bEditmode )
|
|
{
|
|
if ( g_bInEditMode )
|
|
{
|
|
// Re-read config from disk
|
|
Host_ReadConfiguration( -1, false );
|
|
g_bInEditMode = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_bInEditMode = true;
|
|
}
|
|
|
|
g_bInCommentaryMode = bCommentary;
|
|
#endif
|
|
|
|
SetLaunchOptions( args );
|
|
if ( !CL_HL2Demo_MapCheck( name ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", name );
|
|
return;
|
|
}
|
|
|
|
if ( !CL_PortalDemo_MapCheck( name ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", name );
|
|
return;
|
|
}
|
|
|
|
#ifdef DEDICATED
|
|
if ( sv.IsDedicated() )
|
|
#else
|
|
// Stop demo loop
|
|
GetBaseLocalClient().demonum = -1;
|
|
if ( GetBaseLocalClient().m_nMaxClients == 0 || sv.IsDedicated() )
|
|
#endif
|
|
{
|
|
Host_Disconnect( false ); // stop old game
|
|
|
|
HostState_NewGame( name, false, bBackground, bSplitScreenConnect );
|
|
}
|
|
|
|
if (args.ArgC() == 10)
|
|
{
|
|
if (Q_stricmp(args[2], "setpos") == 0
|
|
&& Q_stricmp(args[6], "setang") == 0)
|
|
{
|
|
Vector newpos;
|
|
newpos.x = atof( args[3] );
|
|
newpos.y = atof( args[4] );
|
|
newpos.z = atof( args[5] );
|
|
|
|
QAngle newangle;
|
|
newangle.x = atof( args[7] );
|
|
newangle.y = atof( args[8] );
|
|
newangle.z = atof( args[9] );
|
|
|
|
HostState_SetSpawnPoint(newpos, newangle);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Handle a map command from the console. Active clients are kicked off.
|
|
void Host_Map_f( const CCommand &args )
|
|
{
|
|
Host_Map_Helper( args, (EMapFlags)0 );
|
|
}
|
|
|
|
// Handle a map group command from the console
|
|
void Host_MapGroup_f( const CCommand &args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Warning( "Host_MapGroup_f: No mapgroup specified\n" );
|
|
return;
|
|
}
|
|
|
|
Msg( "Setting mapgroup to '%s'\n", args[1] );
|
|
|
|
HostState_SetMapGroupName( args[1] );
|
|
}
|
|
|
|
// Handle smap command to connect multiple splitscreen users at once
|
|
void Host_SplitScreen_Map_f( const CCommand &args )
|
|
{
|
|
#ifndef _DEMO
|
|
Host_Map_Helper( args, EMAP_SPLITSCREEN );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handle a map_edit <servername> command from the console.
|
|
// Active clients are kicked off.
|
|
// UNDONE: protect this from use if not in dev. mode
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef DEDICATED
|
|
CON_COMMAND( map_edit, "" )
|
|
{
|
|
Host_Map_Helper( args, EMAP_EDIT_MODE );
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Runs a map as the background
|
|
//-----------------------------------------------------------------------------
|
|
void Host_Map_Background_f( const CCommand &args )
|
|
{
|
|
Host_Map_Helper( args, EMAP_BACKGROUND );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Runs a map in commentary mode
|
|
//-----------------------------------------------------------------------------
|
|
void Host_Map_Commentary_f( const CCommand &args )
|
|
{
|
|
Host_Map_Helper( args, EMAP_COMMENTARY );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Restarts the current server for a dead player
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( restart, "Restart the game on the same level (add setpos to jump to current view position on restart)." )
|
|
{
|
|
if (
|
|
#if !defined(DEDICATED)
|
|
demoplayer->IsPlayingBack() ||
|
|
#endif
|
|
!sv.IsActive() )
|
|
return;
|
|
|
|
if ( sv.IsMultiplayer() )
|
|
return;
|
|
|
|
bool bRememberLocation = ( args.ArgC() == 2 && !Q_stricmp( args[1], "setpos" ) );
|
|
|
|
bool bSplitScreenConnect = GET_NUM_SPLIT_SCREEN_PLAYERS() == 2 ;
|
|
|
|
Host_Disconnect(false); // stop old game
|
|
|
|
if ( !CL_HL2Demo_MapCheck( sv.GetMapName() ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", sv.GetMapName() );
|
|
return;
|
|
}
|
|
|
|
if ( !CL_PortalDemo_MapCheck( sv.GetMapName() ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", sv.GetMapName() );
|
|
return;
|
|
}
|
|
|
|
HostState_NewGame( sv.GetMapName(), bRememberLocation, false, bSplitScreenConnect );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Restarts the current server for a dead player
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( reload, "Reload the most recent saved game (add setpos to jump to current view position on reload).")
|
|
{
|
|
#ifndef DEDICATED
|
|
const char *pSaveName;
|
|
char name[MAX_OSPATH];
|
|
#endif
|
|
|
|
if (
|
|
#if !defined(DEDICATED)
|
|
demoplayer->IsPlayingBack() ||
|
|
#endif
|
|
!sv.IsActive() )
|
|
return;
|
|
|
|
if ( sv.IsMultiplayer() )
|
|
return;
|
|
|
|
if ( !serverGameDLL->SupportsSaveRestore() )
|
|
return;
|
|
|
|
bool remember_location = false;
|
|
if ( args.ArgC() == 2 &&
|
|
!Q_stricmp( args[1], "setpos" ) )
|
|
{
|
|
remember_location = true;
|
|
}
|
|
|
|
// See if there is a most recently saved game
|
|
// Restart that game if there is
|
|
// Otherwise, restart the starting game map
|
|
#ifndef DEDICATED
|
|
pSaveName = saverestore->FindRecentSave( name, sizeof( name ) );
|
|
|
|
// Put up loading plaque
|
|
SCR_BeginLoadingPlaque();
|
|
|
|
{
|
|
// Prepare the offline session for server reload
|
|
KeyValues *pEvent = new KeyValues( "OnEngineClientSignonStatePrepareChange" );
|
|
pEvent->SetString( "reason", "reload" );
|
|
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pEvent );
|
|
}
|
|
|
|
Host_Disconnect( false ); // stop old game
|
|
|
|
if ( pSaveName && saverestore->SaveFileExists( pSaveName ) )
|
|
{
|
|
HostState_LoadGame( pSaveName, remember_location, false );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if ( !CL_HL2Demo_MapCheck( host_map.GetString() ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", host_map.GetString() );
|
|
return;
|
|
}
|
|
|
|
if ( !CL_PortalDemo_MapCheck( host_map.GetString() ) )
|
|
{
|
|
Warning( "map load failed: %s not found or invalid\n", host_map.GetString() );
|
|
return;
|
|
}
|
|
|
|
#if !defined( DEDICATED )
|
|
if ( pSaveName && pSaveName[0] )
|
|
{
|
|
Warning( "SAVERESTORE PROBLEM: %s not found! Starting new game in %s\n", pSaveName, host_map.GetString() );
|
|
}
|
|
#endif
|
|
|
|
HostState_NewGame( host_map.GetString(), remember_location, false, false );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Goes to a new map, taking all clients along
|
|
// Output : void Host_Changelevel_f
|
|
//-----------------------------------------------------------------------------
|
|
void Host_Changelevel_f( const CCommand &args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ConMsg( "changelevel <levelname> : continue game on a new level\n" );
|
|
return;
|
|
}
|
|
|
|
if ( !sv.IsActive() )
|
|
{
|
|
ConMsg( "Can't changelevel, not running server\n" );
|
|
return;
|
|
}
|
|
|
|
char mapname[MAX_PATH];
|
|
Q_StripExtension( args[ 1 ], mapname, sizeof( mapname ) );
|
|
|
|
bool bMapMustExist = true;
|
|
static ConVarRef sv_workshop_allow_other_maps( "sv_workshop_allow_other_maps" );
|
|
if ( StringHasPrefix( mapname, "workshop" ) && ( ( mapname[8] == '/' ) || ( mapname[8] == '\\' ) ) &&
|
|
sv_workshop_allow_other_maps.GetBool() )
|
|
bMapMustExist = false;
|
|
|
|
if ( bMapMustExist && !modelloader->Map_IsValid( mapname, true ) )
|
|
{
|
|
Host_Map_Helper_FuzzyName( args, mapname, sizeof( mapname ) );
|
|
if ( !modelloader->Map_IsValid( mapname ) )
|
|
{
|
|
Warning( "changelevel failed: %s not found\n", mapname );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !CL_HL2Demo_MapCheck( mapname ) )
|
|
{
|
|
Warning( "changelevel failed: %s not found\n", mapname );
|
|
return;
|
|
}
|
|
|
|
if ( !CL_PortalDemo_MapCheck( mapname ) )
|
|
{
|
|
Warning( "changelevel failed: %s not found\n", mapname );
|
|
return;
|
|
}
|
|
|
|
SetLaunchOptions( args );
|
|
|
|
HostState_ChangeLevelMP( mapname, args[2] );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changing levels within a unit, uses save/restore
|
|
//-----------------------------------------------------------------------------
|
|
void Host_Changelevel2_f( const CCommand &args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ConMsg ("changelevel2 <levelname> : continue game on a new level in the unit\n");
|
|
return;
|
|
}
|
|
|
|
if ( !sv.IsActive() )
|
|
{
|
|
ConMsg( "Can't changelevel2, not in a map\n" );
|
|
return;
|
|
}
|
|
|
|
if ( !g_pVEngineServer->IsMapValid( args[1] ) )
|
|
{
|
|
if ( !CL_IsHL2Demo() || (CL_IsHL2Demo() && !(!Q_stricmp( args[1], "d1_trainstation_03" ) || !Q_stricmp( args[1], "d1_town_02a" ))) )
|
|
{
|
|
Warning( "changelevel2 failed: %s not found\n", args[1] );
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if !defined(DEDICATED)
|
|
// needs to be before CL_HL2Demo_MapCheck() check as d1_trainstation_03 isn't a valid map
|
|
if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() && !Q_stricmp( args[1], "d1_trainstation_03" ) )
|
|
{
|
|
void CL_DemoTransitionFromTrainstation();
|
|
CL_DemoTransitionFromTrainstation();
|
|
return;
|
|
}
|
|
|
|
// needs to be before CL_HL2Demo_MapCheck() check as d1_trainstation_03 isn't a valid map
|
|
if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() && !Q_stricmp( args[1], "d1_town_02a" ) && !Q_stricmp( args[2], "d1_town_02_02a" ))
|
|
{
|
|
void CL_DemoTransitionFromRavenholm();
|
|
CL_DemoTransitionFromRavenholm();
|
|
return;
|
|
}
|
|
|
|
if ( IsPC() && CL_IsPortalDemo() && !sv.IsDedicated() && !Q_stricmp( args[1], "testchmb_a_07" ) )
|
|
{
|
|
void CL_DemoTransitionFromTestChmb();
|
|
CL_DemoTransitionFromTestChmb();
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
// allow a level transition to d1_trainstation_03 so the Host_Changelevel() can act on it
|
|
if ( !CL_HL2Demo_MapCheck( args[1] ) )
|
|
{
|
|
Warning( "changelevel failed: %s not found\n", args[1] );
|
|
return;
|
|
}
|
|
|
|
SetLaunchOptions( args );
|
|
|
|
HostState_ChangeLevelSP( args[1], args[2] );
|
|
}
|
|
|
|
|
|
// On PS/3, due to Matchmaking framework event architecture, Host_Disconnect is called recursively on quit.
|
|
// Bad things happen. Since it's not really necessary to disconnect recursively, we'll track and prevent it on PS/3 during shutdown.
|
|
int g_nHostDisconnectReentrancyCounter = 0;
|
|
class CDisconnectReentrancyCounter
|
|
{
|
|
public:
|
|
CDisconnectReentrancyCounter() { g_nHostDisconnectReentrancyCounter++ ;}
|
|
~CDisconnectReentrancyCounter() { g_nHostDisconnectReentrancyCounter-- ;}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Shut down client connection and any server
|
|
//-----------------------------------------------------------------------------
|
|
void Host_Disconnect( bool bShowMainMenu )
|
|
{
|
|
#ifdef _PS3
|
|
if ( GetTLSGlobals()->bNormalQuitRequested )
|
|
{
|
|
if ( g_nHostDisconnectReentrancyCounter != 0 )
|
|
{
|
|
return; // do not disconnect recursively on QUIT
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
IGameEvent *disconnectEvent = g_GameEventManager.CreateEvent( "cs_game_disconnected" );
|
|
|
|
if ( disconnectEvent )
|
|
g_GameEventManager.FireEventClientSide( disconnectEvent );
|
|
|
|
CDisconnectReentrancyCounter autoReentrancyCounter;
|
|
|
|
#if !defined( DEDICATED )
|
|
if ( bShowMainMenu )
|
|
{
|
|
// exiting game
|
|
// ensure commentary state gets cleared
|
|
g_bInCommentaryMode = false;
|
|
}
|
|
#endif
|
|
|
|
if ( IsGameConsole() )
|
|
{
|
|
g_pQueuedLoader->EndMapLoading( false );
|
|
}
|
|
|
|
// Switch to single-threaded rendering during shutdown to
|
|
// avoid synchronization issues between destructed objects
|
|
// and the renderer
|
|
Host_AllowQueuedMaterialSystem( false );
|
|
|
|
#ifndef DEDICATED
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
|
|
{
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
|
|
GetLocalClient().Disconnect( bShowMainMenu );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !defined( DEDICATED )
|
|
if ( g_ClientDLL && bShowMainMenu )
|
|
{
|
|
// forcefully stop any of the full screen video panels used for loading or whatever
|
|
// this is a safety precaution to ensure we don't orpan any of the hacky global video panels
|
|
g_ClientDLL->ShutdownMovies();
|
|
}
|
|
#endif
|
|
|
|
HostState_GameShutdown();
|
|
|
|
#ifndef DEDICATED
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
if ( bShowMainMenu && !engineClient->IsDrawingLoadingImage() && ( GetBaseLocalClient().demonum == -1 ) )
|
|
{
|
|
if ( IsGameConsole() )
|
|
{
|
|
// Reset larger configuration material system memory (for map) back down for ui work
|
|
// This must be BEFORE ui gets-rectivated below
|
|
materials->ResetTempHWMemory( true );
|
|
}
|
|
|
|
#ifdef _PS3
|
|
if ( GetTLSGlobals()->bNormalQuitRequested )
|
|
{
|
|
return; // do not disconnect recursively on QUIT
|
|
}
|
|
#endif
|
|
|
|
EngineVGui()->ActivateGameUI();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Kill the client and any local server.
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( disconnect, "Disconnect game from server.", FCVAR_SERVER_CAN_EXECUTE )
|
|
{
|
|
#ifndef DEDICATED
|
|
GetBaseLocalClient().demonum = -1;
|
|
#endif
|
|
|
|
if ( args.ArgC() > 1 )
|
|
{
|
|
COM_ExplainDisconnection( false, "%s", args[ 1 ] );
|
|
}
|
|
|
|
Host_Disconnect(true);
|
|
}
|
|
|
|
CON_COMMAND( version, "Print version info string." )
|
|
{
|
|
ConMsg( "Protocol version %i [%i/%i]\nExe version %s (%s)\n", GetHostVersion(), GetServerVersion(), GetClientVersion(), Sys_GetVersionString(), Sys_GetProductString() );
|
|
ConMsg( "Exe build: " __TIME__ " " __DATE__ " (%i) (%i)\n", build_number(), GetSteamAppID() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( pause, "Toggle the server pause state." )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
#ifndef DEDICATED
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
if ( !GetBaseLocalClient().m_szLevelName[ 0 ] )
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ( !sv.IsPausable() )
|
|
return;
|
|
|
|
// toggle paused state
|
|
sv.SetPaused( !sv.IsPaused() );
|
|
|
|
// send text message who paused the game
|
|
if ( host_client )
|
|
sv.BroadcastPrintf( "%s %s the game\n", host_client->GetClientName(), sv.IsPaused() ? "paused" : "unpaused" );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( setpause, "Set the pause state of the server." )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
#ifndef DEDICATED
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
if ( !GetBaseLocalClient().m_szLevelName[ 0 ] )
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ( !sv.IsPausable() )
|
|
return;
|
|
|
|
sv.SetPaused( true );
|
|
|
|
if ( !args.FindArg( "nomsg" ) )
|
|
{
|
|
// send text message who paused the game
|
|
if ( host_client )
|
|
sv.BroadcastPrintf( "%s paused the game\n", host_client->GetClientName() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( unpause, "Unpause the game." )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
#ifndef DEDICATED
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
if ( !GetBaseLocalClient().m_szLevelName[ 0 ] )
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ( !sv.IsPaused() )
|
|
return;
|
|
|
|
sv.SetPaused( false );
|
|
|
|
if ( !args.FindArg( "nomsg" ) )
|
|
{
|
|
// send text messaage who unpaused the game
|
|
if ( host_client )
|
|
sv.BroadcastPrintf( "%s unpaused the game\n", host_client->GetClientName() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Kicks a user off of the server using their userid or uniqueid
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( kickid_ex, "Kick a player by userid or uniqueid, provide a force-the-kick flag and also assign a message." )
|
|
{
|
|
const char *pszArg1 = NULL, *pszMessage = NULL;
|
|
int iSearchIndex = -1;
|
|
char szSearchString[128];
|
|
int argsStartNum = 1;
|
|
bool bSteamID = false;
|
|
bool bForce = false;
|
|
|
|
if ( args.ArgC() <= 1 )
|
|
{
|
|
ConMsg( "Usage: kickid_ex < userid | uniqueid > < force ( 0 / 1 ) > { message }\n" );
|
|
return;
|
|
}
|
|
|
|
// get the first argument
|
|
pszArg1 = args[1];
|
|
|
|
// if the first letter is a charcter then
|
|
// we're searching for a uniqueid ( e.g. STEAM_ )
|
|
if ( *pszArg1 < '0' || *pszArg1 > '9' )
|
|
{
|
|
// SteamID (need to reassemble it)
|
|
if ( StringHasPrefix( pszArg1, STEAM_PREFIX ) && Q_strstr( args[2], ":" ) )
|
|
{
|
|
Q_snprintf( szSearchString, sizeof( szSearchString ), "%s:%s:%s", pszArg1, args[3], args[5] );
|
|
argsStartNum = 5;
|
|
bSteamID = true;
|
|
}
|
|
// some other ID (e.g. "UNKNOWN", "STEAM_ID_PENDING", "STEAM_ID_LAN")
|
|
// NOTE: assumed to be one argument
|
|
else
|
|
{
|
|
Q_snprintf( szSearchString, sizeof( szSearchString ), "%s", pszArg1 );
|
|
}
|
|
}
|
|
// this is a userid
|
|
else
|
|
{
|
|
iSearchIndex = Q_atoi( pszArg1 );
|
|
}
|
|
|
|
// check for game type and game mode
|
|
if ( args.ArgC() > argsStartNum )
|
|
{
|
|
if ( atoi( args[ argsStartNum + 1 ] ) == 1 )
|
|
{
|
|
bForce = true;
|
|
}
|
|
|
|
argsStartNum++;
|
|
}
|
|
|
|
// check for a message
|
|
if ( args.ArgC() > argsStartNum )
|
|
{
|
|
int j;
|
|
int dataLen = 0;
|
|
|
|
pszMessage = args.ArgS();
|
|
for ( j = 1; j <= argsStartNum; j++ )
|
|
{
|
|
dataLen += Q_strlen( args[j] ) + 1; // +1 for the space between args
|
|
}
|
|
|
|
if ( bSteamID )
|
|
{
|
|
dataLen -= 5; // SteamIDs don't have spaces between the args[) values
|
|
}
|
|
|
|
if ( dataLen > Q_strlen( pszMessage ) ) // saftey check
|
|
{
|
|
pszMessage = NULL;
|
|
}
|
|
else
|
|
{
|
|
pszMessage += dataLen;
|
|
}
|
|
}
|
|
|
|
PerformKick( args.Source(), iSearchIndex, szSearchString, bForce, pszMessage );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Kicks a user off of the server using their userid or uniqueid
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( kickid, "Kick a player by userid or uniqueid, with a message." )
|
|
{
|
|
const char *pszArg1 = NULL, *pszMessage = NULL;
|
|
int iSearchIndex = -1;
|
|
char szSearchString[128];
|
|
int argsStartNum = 1;
|
|
bool bSteamID = false;
|
|
|
|
if ( args.ArgC() <= 1 )
|
|
{
|
|
ConMsg( "Usage: kickid < userid | uniqueid > { message }\n" );
|
|
return;
|
|
}
|
|
|
|
// get the first argument
|
|
pszArg1 = args[1];
|
|
|
|
// if the first letter is a charcter then
|
|
// we're searching for a uniqueid ( e.g. STEAM_ )
|
|
if ( *pszArg1 < '0' || *pszArg1 > '9' )
|
|
{
|
|
// SteamID (need to reassemble it)
|
|
if ( StringHasPrefix( pszArg1, STEAM_PREFIX ) && Q_strstr( args[2], ":" ) )
|
|
{
|
|
Q_snprintf( szSearchString, sizeof( szSearchString ), "%s:%s:%s", pszArg1, args[3], args[5] );
|
|
argsStartNum = 5;
|
|
bSteamID = true;
|
|
}
|
|
// some other ID (e.g. "UNKNOWN", "STEAM_ID_PENDING", "STEAM_ID_LAN")
|
|
// NOTE: assumed to be one argument
|
|
else
|
|
{
|
|
Q_snprintf( szSearchString, sizeof( szSearchString ), "%s", pszArg1 );
|
|
}
|
|
}
|
|
// this is a userid
|
|
else
|
|
{
|
|
iSearchIndex = Q_atoi( pszArg1 );
|
|
}
|
|
|
|
// check for a message
|
|
if ( args.ArgC() > argsStartNum )
|
|
{
|
|
int j;
|
|
int dataLen = 0;
|
|
|
|
pszMessage = args.ArgS();
|
|
for ( j = 1; j <= argsStartNum; j++ )
|
|
{
|
|
dataLen += Q_strlen( args[j] ) + 1; // +1 for the space between args
|
|
}
|
|
|
|
if ( bSteamID )
|
|
{
|
|
dataLen -= 5; // SteamIDs don't have spaces between the args[) values
|
|
}
|
|
|
|
if ( dataLen > Q_strlen( pszMessage ) ) // saftey check
|
|
{
|
|
pszMessage = NULL;
|
|
}
|
|
else
|
|
{
|
|
pszMessage += dataLen;
|
|
}
|
|
}
|
|
|
|
PerformKick( args.Source(), iSearchIndex, szSearchString, false, pszMessage );
|
|
}
|
|
|
|
void PerformKick( cmd_source_t commandSource, int iSearchIndex, char* szSearchString, bool bForceKick, const char* pszMessage )
|
|
{
|
|
IClient *client = NULL;
|
|
char *who = "Console";
|
|
|
|
// find this client
|
|
int i;
|
|
for ( i = 0; i < sv.GetClientCount(); i++ )
|
|
{
|
|
client = sv.GetClient( i );
|
|
|
|
if ( !client->IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// searching by UserID
|
|
if ( iSearchIndex != -1 )
|
|
{
|
|
if ( client->GetUserID() == iSearchIndex )
|
|
{
|
|
// found!
|
|
break;
|
|
}
|
|
}
|
|
// searching by UniqueID
|
|
else
|
|
{
|
|
if ( Q_stricmp( client->GetNetworkIDString(), szSearchString ) == 0 )
|
|
{
|
|
// found!
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now kick them
|
|
if ( i < sv.GetClientCount() )
|
|
{
|
|
if ( client->IsSplitScreenUser() && client->GetSplitScreenOwner() )
|
|
{
|
|
client = client->GetSplitScreenOwner();
|
|
}
|
|
|
|
if ( commandSource == kCommandSrcNetClient )
|
|
{
|
|
who = host_client->m_Name;
|
|
}
|
|
|
|
if ( host_client == client && !sv.IsDedicated() && !bForceKick )
|
|
{
|
|
// can't kick yourself!
|
|
return;
|
|
}
|
|
|
|
if ( iSearchIndex != -1 || !client->IsFakeClient() )
|
|
{
|
|
if ( pszMessage )
|
|
{
|
|
client->Disconnect( CFmtStr( "Kicked by %s : %s", who, pszMessage ) );
|
|
}
|
|
else
|
|
{
|
|
client->Disconnect( CFmtStr( "Kicked by %s", who ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( iSearchIndex != -1 )
|
|
{
|
|
ConMsg( "userid \"%d\" not found\n", iSearchIndex );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "uniqueid \"%s\" not found\n", szSearchString );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Host_Kick_f
|
|
|
|
Kicks a user off of the server using their name
|
|
==================
|
|
*/
|
|
CON_COMMAND( kick, "Kick a player by name." )
|
|
{
|
|
char *who = "Console";
|
|
char *pszName = NULL;
|
|
IClient *client = NULL;
|
|
int i = 0;
|
|
char name[64];
|
|
|
|
if ( args.ArgC() <= 1 )
|
|
{
|
|
ConMsg( "Usage: kick < name >\n" );
|
|
return;
|
|
}
|
|
|
|
// copy the name to a local buffer
|
|
memset( name, 0, sizeof(name) );
|
|
Q_strncpy( name, args.ArgS(), sizeof(name) );
|
|
pszName = name;
|
|
|
|
// safety check
|
|
if ( pszName && pszName[0] != 0 )
|
|
{
|
|
//HACK-HACK
|
|
// check for the name surrounded by quotes (comes in this way from rcon)
|
|
int len = Q_strlen( pszName ) - 1; // (minus one since we start at 0)
|
|
if ( pszName[0] == '"' && pszName[len] == '"' )
|
|
{
|
|
// get rid of the quotes at the beginning and end
|
|
pszName[len] = 0;
|
|
pszName++;
|
|
}
|
|
|
|
for ( i = 0; i < sv.GetClientCount(); i++ )
|
|
{
|
|
client = sv.GetClient(i);
|
|
|
|
if ( !client->IsConnected() )
|
|
continue;
|
|
|
|
// found!
|
|
if ( Q_strcasecmp( client->GetClientName(), pszName ) == 0 )
|
|
break;
|
|
}
|
|
|
|
// now kick them
|
|
if ( i < sv.GetClientCount() )
|
|
{
|
|
if ( client->IsSplitScreenUser() && client->GetSplitScreenOwner() )
|
|
{
|
|
client = client->GetSplitScreenOwner();
|
|
}
|
|
|
|
if ( args.Source() == kCommandSrcNetClient )
|
|
{
|
|
who = host_client->m_Name;
|
|
}
|
|
|
|
// can't kick yourself!
|
|
if ( host_client == client && !sv.IsDedicated() )
|
|
return;
|
|
|
|
client->Disconnect( CFmtStr( "Kicked by %s", who ) );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "Can't kick \"%s\", name not found\n", pszName );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
DEBUGGING TOOLS
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
void Host_PrintMemoryStatus( const char *mapname )
|
|
{
|
|
const float MB = 1.0f / ( 1024*1024 );
|
|
Assert( mapname );
|
|
#ifdef PLATFORM_LINUX
|
|
struct mallinfo memstats = mallinfo( );
|
|
Msg( "[MEMORYSTATUS] [%s] Operating system reports sbrk size: %.2f MB, Used: %.2f MB, #mallocs = %d\n",
|
|
mapname, MB*memstats.arena, MB*memstats.uordblks, memstats.hblks );
|
|
#elif defined(PLATFORM_OSX)
|
|
struct mstats stats = mstats();
|
|
Msg( "[MEMORYSTATUS] [%s] Operating system reports Used: %.2f MB, Free: %.2f Total: %.2f\n",
|
|
mapname, MB*stats.bytes_used, MB*stats.bytes_free, MB*stats.bytes_total );
|
|
#elif defined( _PS3 )
|
|
|
|
// NOTE: for PS3 nFreeMemory can be negative (on a devkit, we can use more memory than a retail kit has)
|
|
int nUsedMemory, nFreeMemory, nAvailable;
|
|
g_pMemAlloc->GlobalMemoryStatus( (size_t *)&nUsedMemory, (size_t *)&nFreeMemory );
|
|
nAvailable = nUsedMemory + nFreeMemory;
|
|
Msg( "[MEMORYSTATUS] [%s] Operating system reports Available: %.2f MB, Used: %.2f MB, Free: %.2f MB\n",
|
|
mapname, MB*nAvailable, MB*nUsedMemory, MB*nFreeMemory );
|
|
#elif defined(PLATFORM_WINDOWS)
|
|
MEMORYSTATUSEX statex;
|
|
statex.dwLength = sizeof(statex);
|
|
GlobalMemoryStatusEx( &statex );
|
|
Msg( "[MEMORYSTATUSEX] [%s] Operating system reports Physical Available: %.2f MB, Physical Used: %.2f MB, Physical Free: %.2f MB\n Virtual Size: %.2f, Virtual Free: %.2f MB, PageFile Size: %.2f, PageFile Free: %.2f MB\n",
|
|
mapname, MB*statex.ullTotalPhys, MB*( statex.ullTotalPhys - statex.ullAvailPhys ), MB*statex.ullAvailPhys, MB*statex.ullTotalVirtual, MB*statex.ullAvailVirtual, MB*statex.ullTotalPageFile, MB*statex.ullAvailPageFile );
|
|
#endif
|
|
|
|
if ( IsPS3() )
|
|
{
|
|
// Include stats on GPU memory usage
|
|
GPUMemoryStats stats;
|
|
materials->GetGPUMemoryStats( stats );
|
|
g_pMemAlloc->SetStatsExtraInfo( mapname, CFmtStr( "%d %d %d %d %d %d %d",
|
|
stats.nGPUMemSize, stats.nGPUMemFree, stats.nTextureSize, stats.nRTSize, stats.nVBSize, stats.nIBSize, stats.nUnknown ) );
|
|
Msg( "[MEMORYSTATUS] [%s] RSX memory: total %.1fkb, free %.1fkb, textures %.1fkb, render targets %.1fkb, vertex buffers %.1fkb, index buffers %.1fkb, unknown %.1fkb\n",
|
|
mapname, stats.nGPUMemSize/1024.0f, stats.nGPUMemFree/1024.0f, stats.nTextureSize/1024.0f, stats.nRTSize/1024.0f, stats.nVBSize/1024.0f, stats.nIBSize/1024.0f, stats.nUnknown/1024.0f );
|
|
}
|
|
else
|
|
{
|
|
g_pMemAlloc->SetStatsExtraInfo( mapname, "" );
|
|
}
|
|
|
|
int nTotal = g_pMemAlloc->GetSize( 0 );
|
|
if (nTotal == -1)
|
|
{
|
|
Msg( "Internal heap corrupted!\n" );
|
|
}
|
|
else
|
|
{
|
|
Msg( "Internal heap reports: %5.2f MB (%d bytes)\n", nTotal/(1024.0f*1024.0f), nTotal );
|
|
}
|
|
|
|
Msg( "\nHunk Memory Used:\n" );
|
|
Hunk_Print();
|
|
|
|
Msg( "\nDatacache reports:\n" );
|
|
g_pDataCache->OutputReport( DC_SUMMARY_REPORT, NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Dump memory stats
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( memory, "Print memory stats." )
|
|
{
|
|
ConMsg( "Heap Used:\n" );
|
|
int nTotal = g_pMemAlloc->GetSize( 0 );
|
|
if (nTotal == -1)
|
|
{
|
|
ConMsg( "Corrupted!\n" );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "%5.2f MB (%d bytes)\n", nTotal/(1024.0f*1024.0f), nTotal );
|
|
}
|
|
|
|
#ifdef VPROF_ENABLED
|
|
ConMsg("\nVideo Memory Used:\n");
|
|
CVProfile *pProf = &g_VProfCurrentProfile;
|
|
int prefixLen = V_strlen( "TexGroup_Global_" );
|
|
float total = 0.0f;
|
|
for ( int i=0; i < pProf->GetNumCounters(); i++ )
|
|
{
|
|
if ( pProf->GetCounterGroup( i ) == COUNTER_GROUP_TEXTURE_GLOBAL )
|
|
{
|
|
float value = pProf->GetCounterValue( i ) * (1.0f/(1024.0f*1024.0f) );
|
|
total += value;
|
|
const char *pName = pProf->GetCounterName( i );
|
|
if ( StringHasPrefix( pName, "TexGroup_Global_" ) )
|
|
{
|
|
pName += prefixLen;
|
|
}
|
|
ConMsg( "%5.2f MB: %s\n", value, pName );
|
|
}
|
|
}
|
|
ConMsg("------------------\n");
|
|
ConMsg( "%5.2f MB: total\n", total );
|
|
#endif
|
|
|
|
ConMsg( "\nHunk Memory Used:\n" );
|
|
Hunk_Print();
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
DEMO LOOP CONTROL
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
|
|
#ifndef DEDICATED
|
|
|
|
//MOTODO move all demo commands to demoplayer
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets number of valid demo names
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int Host_GetNumDemos()
|
|
{
|
|
int c = 0;
|
|
#ifndef DEDICATED
|
|
for ( int i = 0; i < MAX_DEMOS; ++i )
|
|
{
|
|
const char *demoname = GetBaseLocalClient().demos[ i ];
|
|
if ( !demoname[ 0 ] )
|
|
break;
|
|
|
|
++c;
|
|
}
|
|
#endif
|
|
return c;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void Host_PrintDemoList()
|
|
{
|
|
int count = Host_GetNumDemos();
|
|
|
|
#ifndef DEDICATED
|
|
int next = GetBaseLocalClient().demonum;
|
|
if ( next >= count || next < 0 )
|
|
{
|
|
next = 0;
|
|
}
|
|
|
|
for ( int i = 0; i < MAX_DEMOS; ++i )
|
|
{
|
|
const char *demoname = GetBaseLocalClient().demos[ i ];
|
|
if ( !demoname[ 0 ] )
|
|
break;
|
|
|
|
bool isnextdemo = next == i ? true : false;
|
|
|
|
DevMsg( "%3s % 2i : %20s\n", isnextdemo ? "-->" : " ", i, GetBaseLocalClient().demos[ i ] );
|
|
}
|
|
#endif
|
|
|
|
if ( !count )
|
|
{
|
|
DevMsg( "No demos in list, use startdemos <demoname> <demoname2> to specify\n" );
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef DEDICATED
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Con commands related to demos, not available on dedicated servers
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Specify list of demos for the "demos" command
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( startdemos, "Play demos in demo sequence." )
|
|
{
|
|
int c = args.ArgC() - 1;
|
|
if (c > MAX_DEMOS)
|
|
{
|
|
Msg ("Max %i demos in demoloop\n", MAX_DEMOS);
|
|
c = MAX_DEMOS;
|
|
}
|
|
Msg ("%i demo(s) in loop\n", c);
|
|
|
|
for ( int i=1 ; i<c+1 ; i++ )
|
|
{
|
|
Q_strncpy( GetBaseLocalClient().demos[i-1], args[i], sizeof(GetBaseLocalClient().demos[0]) );
|
|
}
|
|
|
|
GetBaseLocalClient().demonum = 0;
|
|
|
|
Host_PrintDemoList();
|
|
|
|
if ( !sv.IsActive() && !demoplayer->IsPlayingBack() )
|
|
{
|
|
CL_NextDemo ();
|
|
}
|
|
else
|
|
{
|
|
GetBaseLocalClient().demonum = -1;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return to looping demos, optional resume demo index
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( demos, "Demo demo file sequence." )
|
|
{
|
|
CClientState &cl = GetBaseLocalClient();
|
|
int oldn = cl.demonum;
|
|
cl.demonum = -1;
|
|
Host_Disconnect(false);
|
|
cl.demonum = oldn;
|
|
|
|
if (cl.demonum == -1)
|
|
cl.demonum = 0;
|
|
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
int numdemos = Host_GetNumDemos();
|
|
if ( numdemos >= 1 )
|
|
{
|
|
cl.demonum = clamp( Q_atoi( args[1] ), 0, numdemos - 1 );
|
|
DevMsg( "Jumping to %s\n", cl.demos[ cl.demonum ] );
|
|
}
|
|
}
|
|
|
|
Host_PrintDemoList();
|
|
|
|
CL_NextDemo ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Stop current demo
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( stopdemo, "Stop playing back a demo.", FCVAR_DONTRECORD )
|
|
{
|
|
if ( !demoplayer->IsPlayingBack() )
|
|
return;
|
|
|
|
Host_Disconnect (true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Skip to next demo
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( nextdemo, "Play next demo in sequence." )
|
|
{
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
int numdemos = Host_GetNumDemos();
|
|
if ( numdemos >= 1 )
|
|
{
|
|
GetBaseLocalClient().demonum = clamp( Q_atoi( args[1] ), 0, numdemos - 1 );
|
|
DevMsg( "Jumping to %s\n", GetBaseLocalClient().demos[ GetBaseLocalClient().demonum ] );
|
|
}
|
|
}
|
|
Host_EndGame( false, "Moving to next demo..." );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Print out the current demo play order
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( demolist, "Print demo sequence list." )
|
|
{
|
|
Host_PrintDemoList();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Host_Soundfade_f
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( soundfade, "Fade client volume.", FCVAR_SERVER_CAN_EXECUTE )
|
|
{
|
|
float percent;
|
|
float inTime, holdTime, outTime;
|
|
|
|
if (args.ArgC() != 3 && args.ArgC() != 5)
|
|
{
|
|
Msg("soundfade <percent> <hold> [<out> <int>]\n");
|
|
return;
|
|
}
|
|
|
|
percent = clamp( atof(args[1]), 0.0f, 100.0f );
|
|
|
|
holdTime = MAX( 0.0f, atof(args[2]) );
|
|
|
|
inTime = 0.0f;
|
|
outTime = 0.0f;
|
|
if (args.ArgC() == 5)
|
|
{
|
|
outTime = MAX( 0.0f, atof(args[3]) );
|
|
inTime = MAX( 0.0f, atof( args[4]) );
|
|
}
|
|
|
|
S_SoundFade( percent, holdTime, outTime, inTime );
|
|
}
|
|
|
|
#endif // !DEDICATED
|
|
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Shutdown the server
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( killserver, "Shutdown the server." )
|
|
{
|
|
Host_Disconnect(true);
|
|
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
// close network sockets and reopen if multiplayer game
|
|
NET_SetMultiplayer( false );
|
|
NET_SetMultiplayer( !!( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_SETTING_MULTIPLAYER ) );
|
|
}
|
|
}
|
|
|
|
// [hpe:jason] Enable ENGINE_VOICE for Cstrike 1.5, all platforms
|
|
#if defined( CSTRIKE15 )
|
|
ConVar voice_vox( "voice_vox", "false", FCVAR_DEVELOPMENTONLY ); // Controls open microphone (no push to talk) settings
|
|
#undef NO_ENGINE_VOICE
|
|
#else
|
|
#define NO_ENGINE_VOICE
|
|
#endif
|
|
|
|
#ifdef NO_ENGINE_VOICE
|
|
ConVar voice_ptt( "voice_ptt", "-1.0", FCVAR_DEVELOPMENTONLY ); // Time when ptt key was released, 0 means to keep transmitting voice
|
|
#endif
|
|
|
|
#if !defined(DEDICATED)
|
|
void Host_VoiceRecordStart_f(void)
|
|
{
|
|
#ifdef NO_ENGINE_VOICE
|
|
voice_ptt.SetValue( 0 );
|
|
#else
|
|
ConVarRef voice_vox( "voice_vox" );
|
|
|
|
if ( voice_vox.GetBool() == true )
|
|
return;
|
|
|
|
int iSsSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
|
|
if ( GetLocalClient( iSsSlot ).IsActive() )
|
|
{
|
|
const char *pUncompressedFile = NULL;
|
|
const char *pDecompressedFile = NULL;
|
|
const char *pInputFile = NULL;
|
|
|
|
if (voice_recordtofile.GetInt())
|
|
{
|
|
pUncompressedFile = "voice_micdata.wav";
|
|
pDecompressedFile = "voice_decompressed.wav";
|
|
}
|
|
|
|
if (voice_inputfromfile.GetInt())
|
|
{
|
|
pInputFile = "voice_input.wav";
|
|
}
|
|
#if !defined( NO_VOICE )
|
|
if (Voice_RecordStart(pUncompressedFile, pDecompressedFile, pInputFile))
|
|
{
|
|
}
|
|
#endif
|
|
}
|
|
#endif // #ifndef NO_ENGINE_VOICE
|
|
}
|
|
|
|
void Host_VoiceRecordStop_f( const CCommand &args )
|
|
{
|
|
#ifdef NO_ENGINE_VOICE
|
|
voice_ptt.SetValue( (float) Plat_FloatTime() );
|
|
#else
|
|
ConVarRef voice_vox( "voice_vox" );
|
|
|
|
if ( voice_vox.GetBool() == true )
|
|
return;
|
|
|
|
int iSsSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
|
|
if ( GetLocalClient( iSsSlot ).IsActive() )
|
|
{
|
|
#if !defined( NO_VOICE )
|
|
if (Voice_IsRecording())
|
|
{
|
|
CL_SendVoicePacket(true);
|
|
Voice_RecordStop();
|
|
}
|
|
|
|
if ( args.ArgC() == 2 && V_strcasecmp( args[1], "force" ) == 0 )
|
|
{
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
|
|
voice_vox.SetValue( 0 );
|
|
}
|
|
#endif
|
|
}
|
|
#endif // #ifndef NO_ENGINE_VOICE
|
|
}
|
|
#endif
|
|
|
|
// TERROR: adding a toggle for voice
|
|
void Host_VoiceToggle_f( const CCommand &args )
|
|
{
|
|
#ifdef NO_ENGINE_VOICE
|
|
voice_ptt.SetValue( (float) ( voice_ptt.GetFloat() ? 0.0f : Plat_FloatTime() ) );
|
|
#endif
|
|
#if !defined( DEDICATED ) && !defined( NO_ENGINE_VOICE )
|
|
#if !defined( NO_VOICE )
|
|
if ( GetBaseLocalClient().IsActive() )
|
|
{
|
|
bool bToggle = false;
|
|
|
|
if ( args.ArgC() == 2 && V_strcasecmp( args[1], "on" ) == 0 )
|
|
{
|
|
bToggle = true;
|
|
}
|
|
|
|
if ( Voice_IsRecording() && bToggle == false )
|
|
{
|
|
CL_SendVoicePacket(true);
|
|
Voice_RecordStop();
|
|
}
|
|
else if ( bToggle == true && Voice_IsRecording() == false )
|
|
{
|
|
const char *pUncompressedFile = NULL;
|
|
const char *pDecompressedFile = NULL;
|
|
const char *pInputFile = NULL;
|
|
|
|
if (voice_recordtofile.GetInt())
|
|
{
|
|
pUncompressedFile = "voice_micdata.wav";
|
|
pDecompressedFile = "voice_decompressed.wav";
|
|
}
|
|
|
|
if (voice_inputfromfile.GetInt())
|
|
{
|
|
pInputFile = "voice_input.wav";
|
|
}
|
|
|
|
Voice_RecordStart( pUncompressedFile, pDecompressedFile, pInputFile );
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Wrapper for modelloader->Print() function call
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( listmodels, "List loaded models." )
|
|
{
|
|
modelloader->Print();
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Host_IncrementCVar
|
|
==================
|
|
*/
|
|
CON_COMMAND_F( incrementvar, "Increment specified convar value.", FCVAR_DONTRECORD )
|
|
{
|
|
if( args.ArgC() != 5 )
|
|
{
|
|
Warning( "Usage: incrementvar varName minValue maxValue delta\n" );
|
|
return;
|
|
}
|
|
|
|
const char *varName = args[ 1 ];
|
|
if( !varName )
|
|
{
|
|
ConDMsg( "Host_IncrementCVar_f without a varname\n" );
|
|
return;
|
|
}
|
|
|
|
ConVar *var = ( ConVar * )g_pCVar->FindVar( varName );
|
|
if( !var )
|
|
{
|
|
ConDMsg( "cvar \"%s\" not found\n", varName );
|
|
return;
|
|
}
|
|
|
|
float currentValue = var->GetFloat();
|
|
float startValue = atof( args[ 2 ] );
|
|
float endValue = atof( args[ 3 ] );
|
|
float delta = atof( args[ 4 ] );
|
|
float newValue = currentValue + delta;
|
|
if( newValue > endValue )
|
|
{
|
|
newValue = startValue;
|
|
}
|
|
else if ( newValue < startValue )
|
|
{
|
|
newValue = endValue;
|
|
}
|
|
|
|
// Conver incrementvar command to direct sets to avoid any problems with state in a demo loop.
|
|
Cbuf_AddText( Cbuf_GetCurrentPlayer(), va("%s %f", varName, newValue) );
|
|
|
|
ConDMsg( "%s = %f\n", var->GetName(), newValue );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Host_MultiplyCVar_f
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( multvar, "Multiply specified convar value.", FCVAR_DONTRECORD )
|
|
{
|
|
if (( args.ArgC() != 5 ))
|
|
{
|
|
Warning( "Usage: multvar varName minValue maxValue factor\n" );
|
|
return;
|
|
}
|
|
|
|
const char *varName = args[ 1 ];
|
|
if( !varName )
|
|
{
|
|
ConDMsg( "multvar without a varname\n" );
|
|
return;
|
|
}
|
|
|
|
ConVar *var = ( ConVar * )g_pCVar->FindVar( varName );
|
|
if( !var )
|
|
{
|
|
ConDMsg( "cvar \"%s\" not found\n", varName );
|
|
return;
|
|
}
|
|
|
|
float currentValue = var->GetFloat();
|
|
float startValue = atof( args[ 2 ] );
|
|
float endValue = atof( args[ 3 ] );
|
|
float factor = atof( args[ 4 ] );
|
|
float newValue = currentValue * factor;
|
|
if( newValue > endValue )
|
|
{
|
|
newValue = endValue;
|
|
}
|
|
else if ( newValue < startValue )
|
|
{
|
|
newValue = startValue;
|
|
}
|
|
|
|
// Conver incrementvar command to direct sets to avoid any problems with state in a demo loop.
|
|
Cbuf_AddText( Cbuf_GetCurrentPlayer(), va("%s %f", varName, newValue) );
|
|
|
|
ConDMsg( "%s = %f\n", var->GetName(), newValue );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND( dumpstringtables, "Print string tables to console." )
|
|
{
|
|
SV_PrintStringTables();
|
|
#ifndef DEDICATED
|
|
CL_PrintStringTables();
|
|
#endif
|
|
}
|
|
|
|
CON_COMMAND( stringtabledictionary, "Create dictionary for current strings." )
|
|
{
|
|
if ( !sv.IsActive() )
|
|
{
|
|
Warning( "stringtabledictionary: only valid when running a map\n" );
|
|
return;
|
|
}
|
|
|
|
SV_CreateDictionary( sv.GetMapName() );
|
|
}
|
|
|
|
// Register shared commands
|
|
CON_COMMAND_F( quit, "Exit the engine.", FCVAR_NONE )
|
|
{
|
|
if ( args.ArgC() > 1 && V_strcmp( args[ 1 ], "prompt" ) == 0 )
|
|
{
|
|
Cbuf_AddText( Cbuf_GetCurrentPlayer(), "quit_prompt" );
|
|
return;
|
|
}
|
|
|
|
Host_Quit_f();
|
|
}
|
|
|
|
static ConCommand cmd_exit("exit", Host_Quit_f, "Exit the engine.");
|
|
|
|
#ifndef DEDICATED
|
|
#ifdef VOICE_OVER_IP
|
|
static ConCommand startvoicerecord("+voicerecord", Host_VoiceRecordStart_f);
|
|
static ConCommand endvoicerecord("-voicerecord", Host_VoiceRecordStop_f);
|
|
static ConCommand togglevoicerecord("voicerecord_toggle", Host_VoiceToggle_f);
|
|
#endif // VOICE_OVER_IP
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Force a null pointer crash. useful for testing minidumps
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( crash, "Cause the engine to crash (Debug!!)", FCVAR_CHEAT )
|
|
{
|
|
Msg( "forcing crash\n" );
|
|
#if defined( _X360 )
|
|
DmCrashDump( FALSE );
|
|
#else
|
|
char *p = 0;
|
|
*p = 0;
|
|
#endif
|
|
}
|
|
|
|
CON_COMMAND_F( spincycle, "Cause the engine to spincycle (Debug!!)", FCVAR_CHEAT )
|
|
{
|
|
if ( args.ArgC() > 1 )
|
|
{
|
|
const char *pParam = args.Arg( 1 );
|
|
if ( pParam && *pParam && pParam[ V_strlen( pParam ) - 1 ] == 's' )
|
|
{
|
|
float flSeconds = V_atof( pParam );
|
|
if ( flSeconds > 0 )
|
|
{
|
|
Msg( "Sleeping for %.3f seconds\n", flSeconds );
|
|
ThreadSleep( flSeconds * 1000 );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int numCycles = ( args.ArgC() > 1 ) ? Q_atoi( args.Arg(1) ) : 10;
|
|
Msg( "forcing spincycle for %d cycles\n", numCycles );
|
|
for( int k = 0; k < numCycles; ++ k )
|
|
{
|
|
( void ) RandomInt( 0, numCycles );
|
|
}
|
|
}
|
|
|
|
#if POSIX
|
|
CON_COMMAND_F( forktest, "Cause the engine to fork and wait for child PID, parameter can be passed for requested exit code (Debug!!)", FCVAR_CHEAT )
|
|
{
|
|
EndWatchdogTimer(); // End the watchdog in case child takes too long
|
|
|
|
int nExitCodeRequested = ( args.ArgC() > 1 ) ? Q_atoi( args.Arg(1) ) : 0;
|
|
|
|
pid_t pID = fork();
|
|
Msg( "forktest: Forked, pID = %d\n", (int) pID );
|
|
if ( pID == 0 ) // are we the forked child?
|
|
{
|
|
//
|
|
// Enumerate all open file descriptors that are not #0 (stdin), #1 (stdout), #2 (stderr)
|
|
// and close them all.
|
|
// This will close all sockets and file handles that can result in process hanging
|
|
// when network events occur on the machine and make NFS handles go bad.
|
|
//
|
|
if ( !CommandLine()->FindParm( "-forkfdskeepall" ) )
|
|
{
|
|
FileFindHandle_t hFind = NULL;
|
|
CUtlVector< int > arrHandlesToClose;
|
|
for ( char const *szFileName = g_pFullFileSystem->FindFirst( "/proc/self/fd/*", &hFind );
|
|
szFileName && *szFileName; szFileName = g_pFullFileSystem->FindNext( hFind ) )
|
|
{
|
|
int iFdHandle = Q_atoi( szFileName );
|
|
if ( ( iFdHandle > 2 ) && ( arrHandlesToClose.Find( iFdHandle ) == arrHandlesToClose.InvalidIndex() ) )
|
|
arrHandlesToClose.AddToTail( iFdHandle );
|
|
}
|
|
g_pFullFileSystem->FindClose( hFind );
|
|
FOR_EACH_VEC( arrHandlesToClose, idxFd )
|
|
{
|
|
::close( arrHandlesToClose[idxFd] );
|
|
}
|
|
|
|
if ( !CommandLine()->FindParm( "-forkfdskeepstd" ) )
|
|
{
|
|
// Explicitly close #0 (stdin), #1 (stdout), #2 (stderr) and reopen them to /dev/null to consume 0-1-2 FDs (Posix spec requires to return lowest FDs first)
|
|
::close( 0 );
|
|
::close( 1 );
|
|
::close( 2 );
|
|
::open("/dev/null", O_RDONLY);
|
|
::open("/dev/null", O_RDWR);
|
|
::open("/dev/null", O_RDWR);
|
|
}
|
|
}
|
|
|
|
Msg( "Child finished successfully!\n" );
|
|
syscall( SYS_exit, nExitCodeRequested ); // don't do a normal c++ exit, don't want to call destructors, etc.
|
|
Warning( "Forked child just called SYS_exit.\n" );
|
|
}
|
|
else
|
|
{
|
|
int nRet = -1;
|
|
int nWait = waitpid( pID, &nRet, 0 );
|
|
Msg( "Parent finished wait: %d, ret: %d, exit: %d, code: %d\n", nWait, nRet, WIFEXITED( nRet ), WEXITSTATUS( nRet ) );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CON_COMMAND_F( flush, "Flush unlocked cache memory.", FCVAR_CHEAT )
|
|
{
|
|
#if !defined( DEDICATED )
|
|
g_ClientDLL->InvalidateMdlCache();
|
|
#endif // DEDICATED
|
|
serverGameDLL->InvalidateMdlCache();
|
|
g_pDataCache->Flush( true );
|
|
#if !defined( DEDICATED )
|
|
wavedatacache->Flush();
|
|
#endif
|
|
}
|
|
|
|
CON_COMMAND_F( flush_locked, "Flush unlocked and locked cache memory.", FCVAR_CHEAT )
|
|
{
|
|
#if !defined( DEDICATED )
|
|
g_ClientDLL->InvalidateMdlCache();
|
|
#endif // DEDICATED
|
|
serverGameDLL->InvalidateMdlCache();
|
|
g_pDataCache->Flush( false );
|
|
#if !defined( DEDICATED )
|
|
wavedatacache->Flush();
|
|
#endif
|
|
}
|
|
|
|
CON_COMMAND( cache_print, "cache_print [section]\nPrint out contents of cache memory." )
|
|
{
|
|
const char *pszSection = NULL;
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
pszSection = args[ 1 ];
|
|
}
|
|
g_pDataCache->OutputReport( DC_DETAIL_REPORT, pszSection );
|
|
}
|
|
|
|
CON_COMMAND( cache_print_lru, "cache_print_lru [section]\nPrint out contents of cache memory." )
|
|
{
|
|
const char *pszSection = NULL;
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
pszSection = args[ 1 ];
|
|
}
|
|
g_pDataCache->OutputReport( DC_DETAIL_REPORT_LRU, pszSection );
|
|
}
|
|
|
|
CON_COMMAND( cache_print_summary, "cache_print_summary [section]\nPrint out a summary contents of cache memory." )
|
|
{
|
|
const char *pszSection = NULL;
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
pszSection = args[ 1 ];
|
|
}
|
|
g_pDataCache->OutputReport( DC_SUMMARY_REPORT, pszSection );
|
|
}
|
|
|
|
#if defined( _X360 )
|
|
CON_COMMAND( vx_datacache_list, "vx_datacache_list" )
|
|
{
|
|
g_pDataCache->OutputReport( DC_DETAIL_REPORT_VXCONSOLE, NULL );
|
|
}
|
|
#endif
|
|
|
|
#ifndef _DEMO
|
|
#ifndef DEDICATED
|
|
// NOTE: As of shipping the 360 version of L4D, this command will not work correctly. See changelist 612757 (terror src) for why.
|
|
CON_COMMAND_F( ss_connect, "If connected with available split screen slots, connects a split screen player to this machine.", FCVAR_DEVELOPMENTONLY )
|
|
{
|
|
if ( host_state.max_splitscreen_players == 1 )
|
|
{
|
|
if ( toolframework->InToolMode() )
|
|
{
|
|
Msg( "Can't ss_connect, split screen not supported when running -tools mode.\n" );
|
|
}
|
|
else
|
|
{
|
|
Msg( "Can't ss_connect, game does not support split screen.\n" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !GetBaseLocalClient().IsConnected() )
|
|
{
|
|
Msg( "Can't ss_connect, not connected to game.\n" );
|
|
return;
|
|
}
|
|
|
|
|
|
int nSlot = 1;
|
|
#ifndef DEDICATED
|
|
while ( splitscreen->IsValidSplitScreenSlot( nSlot ) )
|
|
{
|
|
++nSlot;
|
|
}
|
|
#endif
|
|
|
|
if ( nSlot >= host_state.max_splitscreen_players )
|
|
{
|
|
Msg( "Can't ss_connect, no more split screen player slots!\n" );
|
|
return;
|
|
}
|
|
|
|
// Grab convars for next available slot
|
|
CCLCMsg_SplitPlayerConnect_t msg;
|
|
Host_BuildUserInfoUpdateMessage( nSlot, msg.mutable_convars(), false );
|
|
|
|
GetBaseLocalClient().m_NetChannel->SendNetMsg( msg );
|
|
}
|
|
|
|
|
|
CON_COMMAND_F( ss_disconnect, "If connected with available split screen slots, connects a split screen player to this machine.", FCVAR_DEVELOPMENTONLY )
|
|
{
|
|
if ( args.Source() == kCommandSrcNetClient )
|
|
{
|
|
#ifndef DEDICATED
|
|
host_client->SplitScreenDisconnect( args );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Get the first valid slot
|
|
int nSlot = -1;
|
|
for ( int i = 1; i < host_state.max_splitscreen_players; ++i )
|
|
{
|
|
if ( IS_VALID_SPLIT_SCREEN_SLOT( i ) )
|
|
{
|
|
nSlot = i;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if ( args.ArgC() > 1 )
|
|
{
|
|
int cmdslot = Q_atoi( args.Arg( 1 ) );
|
|
if ( IS_VALID_SPLIT_SCREEN_SLOT( cmdslot ) )
|
|
{
|
|
nSlot = cmdslot;
|
|
}
|
|
else
|
|
{
|
|
Msg( "Can't ss_disconnect, slot %d not active\n", cmdslot );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( ! IS_VALID_SPLIT_SCREEN_SLOT( nSlot ) )
|
|
{
|
|
Msg( "Can't ss_disconnect, no split screen users active\n" );
|
|
return;
|
|
}
|
|
|
|
char buf[ 256 ];
|
|
Q_snprintf( buf, sizeof( buf ), "ss_disconnect %d\n", nSlot );
|
|
|
|
CCommand argsClient;
|
|
argsClient.Tokenize( buf, kCommandSrcCode );
|
|
Cmd_ForwardToServer( argsClient );
|
|
#ifndef DEDICATED
|
|
splitscreen->SetDisconnecting( nSlot, true );
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#if 0
|
|
CON_COMMAND_F( infinite_loop, "Hang server with an infinite loop to test crash recovery.", FCVAR_CHEAT )
|
|
{
|
|
for(;;)
|
|
{
|
|
ThreadSleep( 500 );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND_F( null_ptr_references, "Produce a null ptr reference.", FCVAR_CHEAT )
|
|
{
|
|
*((int *) 0 ) = 77;
|
|
}
|
|
#endif
|
|
|
|
|