csgo-2018-source/engine/cl_splitscreen.cpp
2021-07-24 21:11:47 -07:00

419 lines
11 KiB
C++

#include "client_pch.h"
#include "cl_splitscreen.h"
#if defined( _PS3 )
#include "tls_ps3.h"
#define m_SplitSlot reinterpret_cast< SplitSlot_t *& >(GetTLSGlobals()->pEngineSplitSlot)
#endif // _PS3
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CSplitScreen : public ISplitScreen
{
public:
CSplitScreen();
virtual bool Init();
virtual void Shutdown();
virtual bool AddSplitScreenUser( int nSlot, int nPlayerIndex );
virtual bool AddBaseUser( int nSlot, int nPlayerIndex );
virtual bool RemoveSplitScreenUser( int nSlot, int nPlayerIndex );
virtual int GetActiveSplitScreenPlayerSlot();
virtual int SetActiveSplitScreenPlayerSlot( int slot );
virtual bool IsValidSplitScreenSlot( int nSlot );
virtual int FirstValidSplitScreenSlot(); // -1 == invalid
virtual int NextValidSplitScreenSlot( int nPreviousSlot ); // -1 == invalid
virtual int GetNumSplitScreenPlayers();
virtual int GetSplitScreenPlayerEntity( int nSlot );
virtual INetChannel *GetSplitScreenPlayerNetChan( int nSlot );
virtual bool IsDisconnecting( int nSlot );
virtual void SetDisconnecting( int nSlot, bool bState );
virtual bool SetLocalPlayerIsResolvable( char const *pchContext, int nLine, bool bResolvable );
virtual bool IsLocalPlayerResolvable();
CClientState &GetLocalPlayer( int nSlot = -1 );
struct SplitSlot_t
{
SplitSlot_t() : m_nActiveSplitScreenPlayer( 0 ), m_bLocalPlayerResolvable( false ), m_bMainThread( false ) { }
short m_nActiveSplitScreenPlayer;
// Can a call to C_BasePlayer::GetLocalPlayer be resolved in client .dll (inside a setactivesplitscreenuser scope?)
unsigned short m_bLocalPlayerResolvable : 1;
unsigned short m_bMainThread : 1;
unsigned short pad : 14;
};
private:
int FindSplitPlayerSlot( int nPlayerEntityIndex );
struct SplitPlayer_t
{
SplitPlayer_t() :
m_bActive( false )
{
}
bool m_bActive;
CClientState m_Client;
};
SplitPlayer_t *m_SplitScreenPlayers[ MAX_SPLITSCREEN_CLIENTS ];
int m_nActiveSplitScreenUserCount;
#if defined( _PS3 )
#elif !defined( _X360 )
// Each thread (mainly an issue in the client .dll) can have it's own "active" context. The per thread data is allocated as needed
#else
// xbox uses 12 bit thread id key to do direct lookup
SplitSlot_t m_SplitSlotTable[0x1000];
#endif
SplitSlot_t *GetSplitSlot();
bool m_bInitialized;
};
static CTHREADLOCALPTR( CSplitScreen::SplitSlot_t ) s_SplitSlot;
static CSplitScreen g_SplitScreenMgr;
ISplitScreen *splitscreen = &g_SplitScreenMgr;
CSplitScreen::CSplitScreen()
{
m_bInitialized = false;
}
#if defined( _X360 )
inline int BucketForThreadId()
{
DWORD id = GetCurrentThreadId();
// e.g.: 0xF9000028 -- or's the 9 and the 28 to give 12 bits (slot array is 0x1000 in size), the first nibble is(appears to be) always F so is masked off (0x0F00)
return ( ( id >> 16 ) & 0x00000F00 ) | ( id & 0x000000FF );
}
#endif
CSplitScreen::SplitSlot_t *CSplitScreen::GetSplitSlot()
{
#if defined( _X360 )
// pix shows this function to be enormously expensive due to high frequency of inner loop calls
// avoid conditionals and TLS, use a direct lookup instead
return &m_SplitSlotTable[ BucketForThreadId() ];
#else
if ( !s_SplitSlot )
{
s_SplitSlot = new SplitSlot_t();
}
return s_SplitSlot;
#endif
}
bool CSplitScreen::Init()
{
m_bInitialized = true;
Assert( ThreadInMainThread() );
SplitSlot_t *pSlot = GetSplitSlot();
pSlot->m_bLocalPlayerResolvable = false;
pSlot->m_nActiveSplitScreenPlayer = 0;
pSlot->m_bMainThread = true;
m_nActiveSplitScreenUserCount = 1;
for ( int i = 0 ; i < MAX_SPLITSCREEN_CLIENTS; ++i )
{
MEM_ALLOC_CREDIT();
m_SplitScreenPlayers[ i ] = new SplitPlayer_t();
SplitPlayer_t *sp = m_SplitScreenPlayers[ i ];
sp->m_bActive = ( i == 0 ) ? true : false;
sp->m_Client.m_bSplitScreenUser = ( i != 0 ) ? true : false;
}
return true;
}
void CSplitScreen::Shutdown()
{
Assert( ThreadInMainThread() );
for ( int i = 0; i < MAX_SPLITSCREEN_CLIENTS; ++i )
{
delete m_SplitScreenPlayers[ i ];
m_SplitScreenPlayers[ i ] = NULL;
}
}
bool CSplitScreen::AddBaseUser( int nSlot, int nPlayerIndex )
{
Assert( ThreadInMainThread() );
SplitPlayer_t *sp = m_SplitScreenPlayers[ nSlot ];
sp->m_bActive = true;
sp->m_Client.m_nSplitScreenSlot = nSlot;
return true;
}
bool CSplitScreen::AddSplitScreenUser( int nSlot, int nPlayerEntityIndex )
{
Assert( ThreadInMainThread() );
SplitPlayer_t *sp = m_SplitScreenPlayers[ nSlot ];
if ( sp->m_bActive == true )
{
Assert( sp->m_Client.m_nSplitScreenSlot == nSlot );
Assert( sp->m_Client.m_nPlayerSlot == nPlayerEntityIndex - 1 );
return true;
}
// Msg( "Attached %d to slot %d\n", nPlayerEntityIndex, nSlot );
// 0.0.0.0:0 signifies a bot. It'll plumb all the way down to winsock calls but it won't make them.
ns_address adr;
adr.SetAddrType( NSAT_NETADR );
adr.m_adr.SetIPAndPort( 0, 0 );
char szName[ 256 ];
Q_snprintf( szName, sizeof( szName), "SPLIT%d", nSlot );
sp->m_bActive = true;
sp->m_Client.Clear();
sp->m_Client.m_nPlayerSlot = nPlayerEntityIndex - 1;
sp->m_Client.m_nSplitScreenSlot = nSlot;
sp->m_Client.m_NetChannel = NET_CreateNetChannel( NS_CLIENT, &adr, szName, &sp->m_Client, NULL, true );
GetBaseLocalClient().m_NetChannel->AttachSplitPlayer( nSlot, sp->m_Client.m_NetChannel );
sp->m_Client.m_nViewEntity = nPlayerEntityIndex;
++m_nActiveSplitScreenUserCount;
SetDisconnecting( nSlot, false );
ClientDLL_OnSplitScreenStateChanged();
return true;
}
bool CSplitScreen::RemoveSplitScreenUser( int nSlot, int nPlayerIndex )
{
Assert( ThreadInMainThread() );
// Msg( "Detached %d from slot %d\n", nPlayerIndex, nSlot );
int idx = FindSplitPlayerSlot( nPlayerIndex );
if ( idx != -1 )
{
SplitPlayer_t *sp = m_SplitScreenPlayers[ idx ];
if ( sp->m_Client.m_NetChannel )
{
GetBaseLocalClient().m_NetChannel->DetachSplitPlayer( idx );
sp->m_Client.m_NetChannel->Shutdown( "RemoveSplitScreenUser" );
sp->m_Client.m_NetChannel = NULL;
}
sp->m_Client.m_nPlayerSlot = -1;
sp->m_bActive = false;
SetDisconnecting( nSlot, true );
--m_nActiveSplitScreenUserCount;
ClientDLL_OnSplitScreenStateChanged();
}
return true;
}
int CSplitScreen::GetActiveSplitScreenPlayerSlot()
{
#if !defined( SPLIT_SCREEN_STUBS )
SplitSlot_t *pSlot = GetSplitSlot();
int nSlot = pSlot->m_nActiveSplitScreenPlayer;
#if defined( _DEBUG )
if ( nSlot >= host_state.max_splitscreen_players_clientdll )
{
static bool warned = false;
if ( !warned )
{
warned = true;
Warning( "GetActiveSplitScreenPlayerSlot() returning bogus slot #%d\n", nSlot );
}
}
#endif
return nSlot;
#else
return 0;
#endif
}
int CSplitScreen::SetActiveSplitScreenPlayerSlot( int slot )
{
#if !defined( SPLIT_SCREEN_STUBS )
Assert( m_bInitialized );
slot = clamp( slot, 0, host_state.max_splitscreen_players_clientdll - 1 );
SplitSlot_t *pSlot = GetSplitSlot();
Assert( pSlot );
int old = pSlot->m_nActiveSplitScreenPlayer;
if ( slot == old )
return slot;
pSlot->m_nActiveSplitScreenPlayer = slot;
// Only change netchannel in main thread and only change vgui message context id in main thread (for now)
if ( pSlot->m_bMainThread )
{
if ( m_SplitScreenPlayers[ slot ] && m_SplitScreenPlayers[ 0 ] )
{
INetChannel *nc = m_SplitScreenPlayers[ slot ]->m_Client.m_NetChannel;
CBaseClientState &bcs = m_SplitScreenPlayers[ 0 ]->m_Client;
if ( bcs.m_NetChannel && nc )
{
bcs.m_NetChannel->SetActiveChannel( nc );
}
}
}
return old;
#else
return 0;
#endif
}
int CSplitScreen::GetNumSplitScreenPlayers()
{
return m_nActiveSplitScreenUserCount;
}
int CSplitScreen::GetSplitScreenPlayerEntity( int nSlot )
{
Assert( nSlot >= 0 && nSlot < host_state.max_splitscreen_players );
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
if ( nSlot < 0 || nSlot >= host_state.max_splitscreen_players )
return -1;
if ( !m_SplitScreenPlayers[ nSlot ]->m_bActive )
return -1;
return m_SplitScreenPlayers[ nSlot ]->m_Client.m_nPlayerSlot + 1;
}
INetChannel *CSplitScreen::GetSplitScreenPlayerNetChan( int nSlot )
{
Assert( nSlot >= 0 && nSlot < host_state.max_splitscreen_players );
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
if ( nSlot < 0 || nSlot >= host_state.max_splitscreen_players )
return NULL;
if ( !m_SplitScreenPlayers[ nSlot ]->m_bActive )
return NULL;
return m_SplitScreenPlayers[ nSlot ]->m_Client.m_NetChannel;
}
bool CSplitScreen::IsValidSplitScreenSlot( int nSlot )
{
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
if ( nSlot < 0 || nSlot >= host_state.max_splitscreen_players )
return false;
return m_SplitScreenPlayers[ nSlot ]->m_bActive;
}
int CSplitScreen::FirstValidSplitScreenSlot()
{
return 0;
}
int CSplitScreen::NextValidSplitScreenSlot( int nPreviousSlot )
{
for ( ;; )
{
++nPreviousSlot;
if ( nPreviousSlot >= host_state.max_splitscreen_players )
{
return -1;
}
if ( m_SplitScreenPlayers[ nPreviousSlot ]->m_bActive )
{
break;
}
}
return nPreviousSlot;
}
int CSplitScreen::FindSplitPlayerSlot( int nPlayerEntityIndex )
{
int nPlayerSlot = nPlayerEntityIndex - 1;
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
for ( int i = 1 ; i < host_state.max_splitscreen_players ; ++i )
{
if ( m_SplitScreenPlayers[ i ]->m_Client.m_nPlayerSlot == nPlayerSlot )
{
return i;
}
}
return -1;
}
bool CSplitScreen::IsDisconnecting( int nSlot )
{
Assert( nSlot >= 0 && nSlot < host_state.max_splitscreen_players );
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
if ( nSlot < 0 || nSlot >= host_state.max_splitscreen_players )
return false;
return ( m_SplitScreenPlayers[ nSlot ]->m_Client.m_nSignonState == SIGNONSTATE_NONE ) ? true : false;
}
void CSplitScreen::SetDisconnecting( int nSlot, bool bState )
{
Assert( nSlot >= 0 && nSlot < host_state.max_splitscreen_players );
Assert( host_state.max_splitscreen_players <= MAX_SPLITSCREEN_CLIENTS );
if ( nSlot < 0 || nSlot >= host_state.max_splitscreen_players )
return;
Assert( nSlot != 0 );
m_SplitScreenPlayers[ nSlot ]->m_Client.m_nSignonState = bState ? SIGNONSTATE_NONE : SIGNONSTATE_FULL;
}
CClientState &CSplitScreen::GetLocalPlayer( int nSlot /*= -1*/ )
{
if ( nSlot == -1 )
{
Assert( IsLocalPlayerResolvable() );
return m_SplitScreenPlayers[ GetActiveSplitScreenPlayerSlot() ]->m_Client;
}
return m_SplitScreenPlayers[ nSlot ]->m_Client;
}
bool CSplitScreen::SetLocalPlayerIsResolvable( char const *pchContext, int line, bool bResolvable )
{
SplitSlot_t *pSlot = GetSplitSlot();
Assert( pSlot );
bool bPrev = pSlot->m_bLocalPlayerResolvable;
pSlot->m_bLocalPlayerResolvable = bResolvable;
return bPrev;
}
bool CSplitScreen::IsLocalPlayerResolvable()
{
#if defined( SPLIT_SCREEN_STUBS )
return true;
#else
SplitSlot_t *pSlot = GetSplitSlot();
return pSlot->m_bLocalPlayerResolvable;
#endif
}
// Singleton client state
CClientState &GetLocalClient( int nSlot /*= -1*/ )
{
return g_SplitScreenMgr.GetLocalPlayer( nSlot );
}
CClientState &GetBaseLocalClient()
{
return g_SplitScreenMgr.GetLocalPlayer( 0 );
}