#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 ); }