Files
SteamworksSDK/SteamworksExample/SpaceWarClient.cpp
T
Riley Labrecque 2115d7ded5 v1.18
2016-07-07 05:55:31 -06:00

1919 lines
62 KiB
C++

//========= Copyright � 1996-2008, Valve LLC, All rights reserved. ============
//
// Purpose: Main class for the space war game client
//
// $NoKeywords: $
//=============================================================================
#include "stdafx.h"
#include "SpaceWarClient.h"
#include "SpaceWarServer.h"
#include "connectingmenu.h"
#include "MainMenu.h"
#include "QuitMenu.h"
#include "stdlib.h"
#include "time.h"
#include "ServerBrowser.h"
#include "Leaderboards.h"
#include "clanchatroom.h"
#include "Lobby.h"
#include "p2pauth.h"
#include "voicechat.h"
#include "steam/steamencryptedappticket.h"
CSpaceWarClient *g_pSpaceWarClient = NULL;
CSpaceWarClient* SpaceWarClient() { return g_pSpaceWarClient; }
#if defined(WIN32)
#define atoll _atoi64
#endif
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CSpaceWarClient::CSpaceWarClient( IGameEngine *pGameEngine ) :
m_CallbackP2PSessionConnectFail( this, &CSpaceWarClient::OnP2PSessionConnectFail ),
m_LobbyGameCreated( this, &CSpaceWarClient::OnLobbyGameCreated ),
m_AvatarImageLoadedCreated( this, &CSpaceWarClient::OnAvatarImageLoaded ),
m_IPCFailureCallback( this, &CSpaceWarClient::OnIPCFailure ),
m_SteamShutdownCallback( this, &CSpaceWarClient::OnSteamShutdown ),
m_SteamServersConnected( this, &CSpaceWarClient::OnSteamServersConnected ),
m_SteamServersDisconnected( this, &CSpaceWarClient::OnSteamServersDisconnected ),
m_SteamServerConnectFailure( this, &CSpaceWarClient::OnSteamServerConnectFailure ),
m_GameJoinRequested( this, &CSpaceWarClient::OnGameJoinRequested ),
m_CallbackGameOverlayActivated( this, &CSpaceWarClient::OnGameOverlayActivated )
{
Init( pGameEngine );
}
//-----------------------------------------------------------------------------
// Purpose: initialize our client for use
//-----------------------------------------------------------------------------
void CSpaceWarClient::Init( IGameEngine *pGameEngine )
{
// On PC/OSX we always know the user has a SteamID and is logged in already,
// as Steam enforces this before game launch. On PS3 however the game must
// initiate the logon and we need to check the state here and block the user
// while Steam connects.
if ( SteamUser()->BLoggedOn() )
{
m_SteamIDLocalUser = SteamUser()->GetSteamID();
m_eGameState = k_EClientGameMenu;
}
#ifdef _PS3
else
{
m_eGameState = k_EClientConnectingToSteam;
SteamUser()->LogOn( true );
}
#endif
g_pSpaceWarClient = this;
m_pGameEngine = pGameEngine;
m_uPlayerWhoWonGame = 0;
m_ulStateTransitionTime = m_pGameEngine->GetGameTickCount();
m_ulLastNetworkDataReceivedTime = 0;
m_pServer = NULL;
m_uPlayerShipIndex = 0;
m_eConnectedStatus = k_EClientNotConnected;
m_bTransitionedGameState = true;
m_rgchErrorText[0] = 0;
m_unServerIP = 0;
m_usServerPort = 0;
m_ulPingSentTime = 0;
for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i )
{
m_rguPlayerScores[i] = 0;
m_rgpShips[i] = NULL;
}
// Seed random num generator
srand( (uint32)time( NULL ) );
m_hHUDFont = pGameEngine->HCreateFont( HUD_FONT_HEIGHT, FW_BOLD, false, "Arial" );
if ( !m_hHUDFont )
OutputDebugString( "HUD font was not created properly, text won't draw\n" );
m_hInstructionsFont = pGameEngine->HCreateFont( INSTRUCTIONS_FONT_HEIGHT, FW_BOLD, false, "Arial" );
if ( !m_hHUDFont )
OutputDebugString( "HUD font was not created properly, text won't draw\n" );
// Initialize starfield
m_pStarField = new CStarField( pGameEngine );
// Initialize main menu
m_pMainMenu = new CMainMenu( pGameEngine );
// Initialize connecting menu
m_pConnectingMenu = new CConnectingMenu( pGameEngine );
// Initialize pause menu
m_pQuitMenu = new CQuitMenu( pGameEngine );
// Initialize sun
m_pSun = new CSun( pGameEngine );
// initialize P2P auth engine
m_pP2PAuthedGame = new CP2PAuthedGame( m_pGameEngine );
// Create matchmaking menus
m_pServerBrowser = new CServerBrowser( m_pGameEngine );
m_pLobbyBrowser = new CLobbyBrowser( m_pGameEngine );
m_pLobby = new CLobby( m_pGameEngine );
// Init stats
m_pStatsAndAchievements = new CStatsAndAchievements( pGameEngine );
m_pLeaderboards = new CLeaderboards( pGameEngine );
m_pClanChatRoom = new CClanChatRoom( pGameEngine );
// Remote Storage page
m_pRemoteStorage = new CRemoteStorage( pGameEngine );
// P2P voice chat
m_pVoiceChat = new CVoiceChat( pGameEngine );
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CSpaceWarClient::~CSpaceWarClient()
{
DisconnectFromServer();
if ( m_pP2PAuthedGame )
{
m_pP2PAuthedGame->EndGame();
delete m_pP2PAuthedGame;
m_pP2PAuthedGame = NULL;
}
if ( m_pServer )
{
delete m_pServer;
m_pServer = NULL;
}
if ( m_pStarField )
delete m_pStarField;
if ( m_pMainMenu )
delete m_pMainMenu;
if ( m_pConnectingMenu )
delete m_pConnectingMenu;
if ( m_pQuitMenu )
delete m_pQuitMenu;
if ( m_pSun )
delete m_pSun;
if ( m_pStatsAndAchievements )
delete m_pStatsAndAchievements;
if ( m_pServerBrowser )
delete m_pServerBrowser;
if ( m_pVoiceChat )
delete m_pVoiceChat;
for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
{
delete m_rgpShips[i];
m_rgpShips[i] = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Tell the connected server we are disconnecting (if we are connected)
//-----------------------------------------------------------------------------
void CSpaceWarClient::DisconnectFromServer()
{
if ( m_eConnectedStatus != k_EClientNotConnected )
{
#ifdef USE_GS_AUTH_API
if ( m_hAuthTicket != k_HAuthTicketInvalid )
SteamUser()->CancelAuthTicket( m_hAuthTicket );
m_hAuthTicket = k_HAuthTicketInvalid;
#else
SteamUser()->AdvertiseGame( k_steamIDNil, 0, 0 );
#endif
MsgClientLeavingServer_t msg;
BSendServerData( &msg, sizeof(msg) );
m_eConnectedStatus = k_EClientNotConnected;
}
if ( m_pP2PAuthedGame )
{
m_pP2PAuthedGame->EndGame();
}
if ( m_pVoiceChat )
{
m_pVoiceChat->StopVoiceChat();
}
// forget the game server ID
if ( m_steamIDGameServer.IsValid() )
{
SteamNetworking()->CloseP2PSessionWithUser( m_steamIDGameServer );
m_steamIDGameServer = CSteamID();
}
}
//-----------------------------------------------------------------------------
// Purpose: Receive basic server info from the server after we initiate a connection
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnReceiveServerInfo( CSteamID steamIDGameServer, bool bVACSecure, const char *pchServerName )
{
m_eConnectedStatus = k_EClientConnectedPendingAuthentication;
m_pQuitMenu->SetHeading( pchServerName );
m_steamIDGameServer = steamIDGameServer;
// look up the servers IP and Port from the connection
P2PSessionState_t p2pSessionState;
SteamNetworking()->GetP2PSessionState( steamIDGameServer, &p2pSessionState );
m_unServerIP = p2pSessionState.m_nRemoteIP;
m_usServerPort = p2pSessionState.m_nRemotePort;
// set how to connect to the game server, using the Rich Presence API
// this lets our friends connect to this game via their friends list
UpdateRichPresenceConnectionInfo();
MsgClientBeginAuthentication_t msg;
#ifdef USE_GS_AUTH_API
char rgchToken[1024];
uint32 unTokenLen = 0;
m_hAuthTicket = SteamUser()->GetAuthSessionTicket( rgchToken, sizeof( rgchToken ), &unTokenLen );
msg.SetToken( rgchToken, unTokenLen );
#else
// When you aren't using Steam auth you can still call AdvertiseGame() so you can communicate presence data to the friends
// system. Make sure to pass k_steamIDNonSteamGS
uint32 unTokenLen = SteamUser()->AdvertiseGame( k_steamIDNonSteamGS, m_unServerIP, m_usServerPort );
msg.SetSteamID( SteamUser()->GetSteamID().ConvertToUint64() );
#endif
Steamworks_TestSecret();
if ( msg.GetTokenLen() < 1 )
OutputDebugString( "Warning: Looks like GetAuthSessionTicket didn't give us a good ticket\n" );
BSendServerData( &msg, sizeof(msg) );
}
//-----------------------------------------------------------------------------
// Purpose: Receive an authentication response from the server
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnReceiveServerAuthenticationResponse( bool bSuccess, uint32 uPlayerPosition )
{
if ( !bSuccess )
{
SetConnectionFailureText( "Connection failure.\nMultiplayer authentication failed\n" );
SetGameState( k_EClientGameConnectionFailure );
DisconnectFromServer();
}
else
{
// Is this a duplicate message? If so ignore it...
if ( m_eConnectedStatus == k_EClientConnectedAndAuthenticated && m_uPlayerShipIndex == uPlayerPosition )
return;
m_uPlayerShipIndex = uPlayerPosition;
m_eConnectedStatus = k_EClientConnectedAndAuthenticated;
// set information so our friends can join the lobby
UpdateRichPresenceConnectionInfo();
// send a ping, to measure round-trip time
m_ulPingSentTime = m_pGameEngine->GetGameTickCount();
MsgClientPing_t msg;
BSendServerData( &msg, sizeof( msg ) );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles receiving a state update from the game server
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnReceiveServerUpdate( ServerSpaceWarUpdateData_t *pUpdateData )
{
// Update our client state based on what the server tells us
switch( pUpdateData->GetServerGameState() )
{
case k_EServerWaitingForPlayers:
if ( m_eGameState == k_EClientGameQuitMenu )
break;
else if (m_eGameState == k_EClientGameMenu )
break;
else if ( m_eGameState == k_EClientGameExiting )
break;
SetGameState( k_EClientGameWaitingForPlayers );
break;
case k_EServerActive:
if ( m_eGameState == k_EClientGameQuitMenu )
break;
else if (m_eGameState == k_EClientGameMenu )
break;
else if ( m_eGameState == k_EClientGameExiting )
break;
SetGameState( k_EClientGameActive );
break;
case k_EServerDraw:
if ( m_eGameState == k_EClientGameQuitMenu )
break;
else if ( m_eGameState == k_EClientGameMenu )
break;
else if ( m_eGameState == k_EClientGameExiting )
break;
SetGameState( k_EClientGameDraw );
break;
case k_EServerWinner:
if ( m_eGameState == k_EClientGameQuitMenu )
break;
else if ( m_eGameState == k_EClientGameMenu )
break;
else if ( m_eGameState == k_EClientGameExiting )
break;
SetGameState( k_EClientGameWinner );
break;
case k_EServerExiting:
if ( m_eGameState == k_EClientGameExiting )
break;
SetGameState( k_EClientGameMenu );
break;
}
// Update scores
for( int i=0; i < MAX_PLAYERS_PER_SERVER; ++i )
{
m_rguPlayerScores[i] = pUpdateData->GetPlayerScore(i);
}
// Update who won last
m_uPlayerWhoWonGame = pUpdateData->GetPlayerWhoWon();
if ( m_pP2PAuthedGame )
{
// has the player list changed?
if ( m_pServer )
{
// if i am the server owner i need to auth everyone who wants to play
// assume i am in slot 0, so start at slot 1
for( uint32 i=1; i < MAX_PLAYERS_PER_SERVER; ++i )
{
CSteamID steamIDNew( pUpdateData->GetPlayerSteamID(i) );
if ( steamIDNew == SteamUser()->GetSteamID() )
{
OutputDebugString( "Server player slot 0 is not server owner.\n" );
}
else if ( steamIDNew != m_rgSteamIDPlayers[i] )
{
if ( m_rgSteamIDPlayers[i].IsValid() )
{
m_pP2PAuthedGame->PlayerDisconnect( i );
}
if ( steamIDNew.IsValid() )
{
m_pP2PAuthedGame->RegisterPlayer( i, steamIDNew );
}
}
}
}
else
{
// i am just a client, i need to auth the game owner ( slot 0 )
CSteamID steamIDNew( pUpdateData->GetPlayerSteamID( 0 ) );
if ( steamIDNew == SteamUser()->GetSteamID() )
{
OutputDebugString( "Server player slot 0 is not server owner.\n" );
}
else if ( steamIDNew != m_rgSteamIDPlayers[0] )
{
if ( m_rgSteamIDPlayers[0].IsValid() )
{
OutputDebugString( "Server player slot 0 has disconnected - but thats the server owner.\n" );
m_pP2PAuthedGame->PlayerDisconnect( 0 );
}
if ( steamIDNew.IsValid() )
{
m_pP2PAuthedGame->StartAuthPlayer( 0, steamIDNew );
}
}
}
}
// update all players that are active
if ( m_pVoiceChat )
m_pVoiceChat->MarkAllPlayersInactive();
// Update the players
for( uint32 i=0; i < MAX_PLAYERS_PER_SERVER; ++i )
{
// Update steamid array with data from server
m_rgSteamIDPlayers[i].SetFromUint64( pUpdateData->GetPlayerSteamID( i ) );
if ( pUpdateData->GetPlayerActive( i ) )
{
// Check if we have a ship created locally for this player slot, if not create it
if ( !m_rgpShips[i] )
{
ServerShipUpdateData_t *pShipData = pUpdateData->AccessShipUpdateData( i );
m_rgpShips[i] = new CShip( m_pGameEngine, false, pShipData->GetXPosition(), pShipData->GetYPosition(), g_rgPlayerColors[i] );
if ( i == m_uPlayerShipIndex )
{
// If this is our local ship, then setup key bindings appropriately
m_rgpShips[i]->SetVKBindingLeft( 0x41 ); // A key
m_rgpShips[i]->SetVKBindingRight( 0x44 ); // D key
m_rgpShips[i]->SetVKBindingForwardThrusters( 0x57 ); // W key
m_rgpShips[i]->SetVKBindingReverseThrusters( 0x53 ); // S key
m_rgpShips[i]->SetVKBindingFire( VK_SPACE );
}
}
if ( i == m_uPlayerShipIndex )
m_rgpShips[i]->SetIsLocalPlayer( true );
else
m_rgpShips[i]->SetIsLocalPlayer( false );
m_rgpShips[i]->OnReceiveServerUpdate( pUpdateData->AccessShipUpdateData( i ) );
if ( m_pVoiceChat )
m_pVoiceChat->MarkPlayerAsActive( m_rgSteamIDPlayers[i] );
}
else
{
// Make sure we don't have a ship locally for this slot
if ( m_rgpShips[i] )
{
delete m_rgpShips[i];
m_rgpShips[i] = NULL;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Used to transition game state
//-----------------------------------------------------------------------------
void CSpaceWarClient::SetGameState( EClientGameState eState )
{
if ( m_eGameState == eState )
return;
m_bTransitionedGameState = true;
m_ulStateTransitionTime = m_pGameEngine->GetGameTickCount();
m_eGameState = eState;
// Let the stats handler check the state (so it can detect wins, losses, etc...)
m_pStatsAndAchievements->OnGameStateChange( eState );
// update any rich presence state
UpdateRichPresenceConnectionInfo();
}
//-----------------------------------------------------------------------------
// Purpose: set the error string to display in the UI
//-----------------------------------------------------------------------------
void CSpaceWarClient::SetConnectionFailureText( const char *pchErrorText )
{
_snprintf( m_rgchErrorText, sizeof( m_rgchErrorText ), "%s", pchErrorText );
}
//-----------------------------------------------------------------------------
// Purpose: Send data to the current server
//-----------------------------------------------------------------------------
bool CSpaceWarClient::BSendServerData( const void *pData, uint32 nSizeOfData )
{
if ( !SteamNetworking()->SendP2PPacket( m_steamIDGameServer, pData, nSizeOfData, k_EP2PSendUnreliable ) )
{
OutputDebugString( "Failed sending data to server\n" );
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Initiates a connection to a server
//-----------------------------------------------------------------------------
void CSpaceWarClient::InitiateServerConnection( uint32 unServerAddress, const int32 nPort )
{
if ( m_eGameState == k_EClientInLobby && m_steamIDLobby.IsValid() )
{
SteamMatchmaking()->LeaveLobby( m_steamIDLobby );
}
SetGameState( k_EClientGameConnecting );
// Update when we last retried the connection, as well as the last packet received time so we won't timeout too soon,
// and so we will retry at appropriate intervals if packets drop
m_ulLastNetworkDataReceivedTime = m_ulLastConnectionAttemptRetryTime = m_pGameEngine->GetGameTickCount();
// ping the server to find out what it's steamID is
m_unServerIP = unServerAddress;
m_usServerPort = (uint16)nPort;
m_GameServerPing.RetrieveSteamIDFromGameServer( this, m_unServerIP, m_usServerPort );
}
//-----------------------------------------------------------------------------
// Purpose: Initiates a connection to a server via P2P (NAT-traversing) connection
//-----------------------------------------------------------------------------
void CSpaceWarClient::InitiateServerConnection( CSteamID steamIDGameServer )
{
if ( m_eGameState == k_EClientInLobby && m_steamIDLobby.IsValid() )
{
SteamMatchmaking()->LeaveLobby( m_steamIDLobby );
}
SetGameState( k_EClientGameConnecting );
m_steamIDGameServer = steamIDGameServer;
// Update when we last retried the connection, as well as the last packet received time so we won't timeout too soon,
// and so we will retry at appropriate intervals if packets drop
m_ulLastNetworkDataReceivedTime = m_ulLastConnectionAttemptRetryTime = m_pGameEngine->GetGameTickCount();
// send the packet to the server
MsgClientInitiateConnection_t msg;
BSendServerData( &msg, sizeof( msg ) );
}
//-----------------------------------------------------------------------------
// Purpose: steam callback, triggered when our connection to another client fails
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnP2PSessionConnectFail( P2PSessionConnectFail_t *pCallback )
{
if ( pCallback->m_steamIDRemote == m_steamIDGameServer )
{
// failed, error out
OutputDebugString( "Failed to make P2P connection, quiting server\n" );
OnReceiveServerExiting();
}
}
//-----------------------------------------------------------------------------
// Purpose: Receives incoming network data
//-----------------------------------------------------------------------------
void CSpaceWarClient::ReceiveNetworkData()
{
char rgchRecvBuf[1024];
char *pchRecvBuf = rgchRecvBuf;
uint32 cubMsgSize;
for (;;)
{
// reset the receive buffer
if ( pchRecvBuf != rgchRecvBuf )
{
free( pchRecvBuf );
pchRecvBuf = rgchRecvBuf;
}
// see if there is any data waiting on the socket
if ( !SteamNetworking()->IsP2PPacketAvailable( &cubMsgSize ) )
break;
// not enough space in default buffer
// alloc custom size and try again
if ( cubMsgSize > sizeof(rgchRecvBuf) )
{
pchRecvBuf = (char *)malloc( cubMsgSize );
}
CSteamID steamIDRemote;
if ( !SteamNetworking()->ReadP2PPacket( pchRecvBuf, cubMsgSize, &cubMsgSize, &steamIDRemote ) )
break;
// see if it's from the game server
if ( steamIDRemote == m_steamIDGameServer )
{
m_ulLastNetworkDataReceivedTime = m_pGameEngine->GetGameTickCount();
// make sure we're connected
if ( m_eConnectedStatus == k_EClientNotConnected && m_eGameState != k_EClientGameConnecting )
continue;
if ( cubMsgSize < sizeof( DWORD ) )
{
OutputDebugString( "Got garbage on client socket, too short\n" );
}
EMessage eMsg = (EMessage)LittleDWord( *(DWORD*)pchRecvBuf );
switch ( eMsg )
{
case k_EMsgServerSendInfo:
{
if ( cubMsgSize != sizeof( MsgServerSendInfo_t ) )
{
OutputDebugString ("Bad server info msg\n" );
continue;
}
MsgServerSendInfo_t *pMsg = (MsgServerSendInfo_t*)pchRecvBuf;
// pull the IP address of the user from the socket
OnReceiveServerInfo( CSteamID( pMsg->GetSteamIDServer() ), pMsg->GetSecure(), pMsg->GetServerName() );
}
break;
case k_EMsgServerPassAuthentication:
{
if ( cubMsgSize != sizeof( MsgServerPassAuthentication_t ) )
{
OutputDebugString( "Bad accept connection msg\n" );
continue;
}
MsgServerPassAuthentication_t *pMsg = (MsgServerPassAuthentication_t*)pchRecvBuf;
// Our game client doesn't really care about whether the server is secure, or what its
// steamID is, but if it did we would pass them in here as they are part of the accept message
OnReceiveServerAuthenticationResponse( true, pMsg->GetPlayerPosition() );
}
break;
case k_EMsgServerFailAuthentication:
{
OnReceiveServerAuthenticationResponse( false, 0 );
}
break;
case k_EMsgServerUpdateWorld:
{
if ( cubMsgSize != sizeof( MsgServerUpdateWorld_t ) )
{
OutputDebugString( "Bad server world update msg\n" );
continue;
}
MsgServerUpdateWorld_t *pMsg = (MsgServerUpdateWorld_t*)pchRecvBuf;
OnReceiveServerUpdate( pMsg->AccessUpdateData() );
}
break;
case k_EMsgServerExiting:
{
if ( cubMsgSize != sizeof( MsgServerExiting_t ) )
{
OutputDebugString( "Bad server exiting msg\n" );
}
OnReceiveServerExiting();
}
break;
case k_EMsgServerPingResponse:
{
uint64 ulTimePassedMS = m_pGameEngine->GetGameTickCount() - m_ulPingSentTime;
char rgchT[256];
_snprintf( rgchT, sizeof(rgchT), "Round-trip ping time to server %d ms\n", (int)ulTimePassedMS );
OutputDebugString( rgchT );
m_ulPingSentTime = 0;
}
break;
default:
OutputDebugString( "Unhandled message from server\n" );
break;
}
}
else
{
// the message is from another player
EMessage eMsg = (EMessage)LittleDWord( *(DWORD*)pchRecvBuf );
if ( m_pP2PAuthedGame->HandleMessage( eMsg, pchRecvBuf ) )
continue; // this was a P2P auth message
if ( m_pVoiceChat->HandleMessage( steamIDRemote, eMsg, pchRecvBuf ) )
continue;
// Unhandled message
OutputDebugString( "Received unknown message on our listen socket\n" );
}
}
// if we're running a server, do that as well
if ( m_pServer )
{
m_pServer->ReceiveNetworkData();
}
}
//-----------------------------------------------------------------------------
// Purpose: Handle the server telling us it is exiting
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnReceiveServerExiting()
{
if ( m_pP2PAuthedGame )
m_pP2PAuthedGame->EndGame();
#ifdef USE_GS_AUTH_API
if ( m_hAuthTicket != k_HAuthTicketInvalid )
{
SteamUser()->CancelAuthTicket( m_hAuthTicket );
}
m_hAuthTicket = k_HAuthTicketInvalid;
#else
SteamUser()->AdvertiseGame( k_steamIDNil, 0, 0 );
#endif
if ( m_eGameState != k_EClientGameActive )
return;
m_eConnectedStatus = k_EClientNotConnected;
SetConnectionFailureText( "Game server has exited." );
SetGameState( k_EClientGameConnectionFailure );
}
//-----------------------------------------------------------------------------
// Purpose: Steam is asking us to join a game, based on the user selecting
// 'join game' on a friend in their friends list
// the string comes from the "connect" field set in the friends' rich presence
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnGameJoinRequested( GameRichPresenceJoinRequested_t *pCallback )
{
// parse out the connect
const char *pchServerAddress, *pchLobbyID;
extern void ParseCommandLine( const char *pchCmdLine, const char **ppchServerAddress, const char **ppchLobbyID );
ParseCommandLine( pCallback->m_rgchConnect, &pchServerAddress, &pchLobbyID );
// exec
ExecCommandLineConnect( pchServerAddress, pchLobbyID );
}
//-----------------------------------------------------------------------------
// Purpose: Finishes up entering a lobby of our own creation
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnLobbyCreated( LobbyCreated_t *pCallback, bool bIOFailure )
{
if ( m_eGameState != k_EClientCreatingLobby )
return;
// record which lobby we're in
if ( pCallback->m_eResult == k_EResultOK )
{
// success
m_steamIDLobby = pCallback->m_ulSteamIDLobby;
m_pLobby->SetLobbySteamID( m_steamIDLobby );
// set the name of the lobby if it's ours
char rgchLobbyName[256];
_snprintf( rgchLobbyName, sizeof( rgchLobbyName ), "%s's lobby", SteamFriends()->GetPersonaName() );
SteamMatchmaking()->SetLobbyData( m_steamIDLobby, "name", rgchLobbyName );
// mark that we're in the lobby
SetGameState( k_EClientInLobby );
}
else
{
// failed, show error
SetConnectionFailureText( "Failed to create lobby (lost connection to Steam back-end servers." );
SetGameState( k_EClientGameConnectionFailure );
}
}
//-----------------------------------------------------------------------------
// Purpose: Finishes up entering a lobby
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnLobbyEntered( LobbyEnter_t *pCallback, bool bIOFailure )
{
if ( m_eGameState != k_EClientJoiningLobby )
return;
if ( pCallback->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
{
// failed, show error
SetConnectionFailureText( "Failed to enter lobby" );
SetGameState( k_EClientGameConnectionFailure );
return;
}
// success
// move forward the state
m_steamIDLobby = pCallback->m_ulSteamIDLobby;
m_pLobby->SetLobbySteamID( m_steamIDLobby );
SetGameState( k_EClientInLobby );
}
//-----------------------------------------------------------------------------
// Purpose: Joins a game from a lobby
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnLobbyGameCreated( LobbyGameCreated_t *pCallback )
{
if ( m_eGameState != k_EClientInLobby )
return;
// join the game server specified, via whichever method we can
if ( CSteamID( pCallback->m_ulSteamIDGameServer ).IsValid() )
{
InitiateServerConnection( CSteamID( pCallback->m_ulSteamIDGameServer ) );
}
}
//-----------------------------------------------------------------------------
// Purpose: a large avatar image has been loaded for us
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnAvatarImageLoaded( AvatarImageLoaded_t *pCallback )
{
}
//-----------------------------------------------------------------------------
// Purpose: Handles menu actions in a lobby
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnMenuSelection( LobbyMenuItem_t selection )
{
if ( selection.m_eCommand == LobbyMenuItem_t::k_ELobbyMenuItemLeaveLobby )
{
// leave the lobby
SteamMatchmaking()->LeaveLobby( m_steamIDLobby );
m_steamIDLobby = CSteamID();
// return to main menu
SetGameState( k_EClientGameMenu );
}
else if ( selection.m_eCommand == LobbyMenuItem_t::k_ELobbyMenuItemToggleReadState )
{
// update our state
bool bOldState = ( 1 == atoi( SteamMatchmaking()->GetLobbyMemberData( m_steamIDLobby, SteamUser()->GetSteamID(), "ready" ) ) );
bool bNewState = !bOldState;
// publish to everyone
SteamMatchmaking()->SetLobbyMemberData( m_steamIDLobby, "ready", bNewState ? "1" : "0" );
}
else if ( selection.m_eCommand == LobbyMenuItem_t::k_ELobbyMenuItemStartGame )
{
// make sure we're not already starting a server
if ( m_pServer )
return;
// broadcast to everyone in the lobby that the game is starting
SteamMatchmaking()->SetLobbyData( m_steamIDLobby, "game_starting", "1" );
// start a local game server
m_pServer = new CSpaceWarServer( m_pGameEngine );
// we'll have to wait until the game server connects to the Steam server back-end
// before telling all the lobby members to join (so that the NAT traversal code has a path to contact the game server)
OutputDebugString( "Game server being created; game will start soon.\n" );
}
else if ( selection.m_eCommand == LobbyMenuItem_t::k_ELobbyMenuItemInviteToLobby )
{
SteamFriends()->ActivateGameOverlayInviteDialog( selection.m_steamIDLobby );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles menu actions when viewing a leaderboard
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnMenuSelection( LeaderboardMenuItem_t selection )
{
m_pLeaderboards->OnMenuSelection( selection );
}
//-----------------------------------------------------------------------------
// Purpose: Handles menu actions when viewing the remote storage sync screen
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnMenuSelection( ERemoteStorageSyncMenuCommand selection )
{
m_pRemoteStorage->OnMenuSelection( selection );
}
//-----------------------------------------------------------------------------
// Purpose: does work on transitioning from one game state to another
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnGameStateChanged( EClientGameState eGameStateNew )
{
if ( m_eGameState == k_EClientFindInternetServers )
{
// If we are just opening the find servers screen, then start a refresh
m_pServerBrowser->RefreshInternetServers();
SteamFriends()->SetRichPresence( "status", "Finding an internet game" );
}
else if ( m_eGameState == k_EClientFindLANServers )
{
m_pServerBrowser->RefreshLANServers();
SteamFriends()->SetRichPresence( "status", "Finding a LAN game" );
}
else if ( m_eGameState == k_EClientCreatingLobby )
{
// start creating the lobby
if ( !m_SteamCallResultLobbyCreated.IsActive() )
{
// ask steam to create a lobby
SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby( k_ELobbyTypePublic /* public lobby, anyone can find it */, 4 );
// set the function to call when this completes
m_SteamCallResultLobbyCreated.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyCreated );
}
SteamFriends()->SetRichPresence( "status", "Creating a lobby" );
}
else if ( m_eGameState == k_EClientFindLobby )
{
m_pLobbyBrowser->Refresh();
SteamFriends()->SetRichPresence( "status", "Main menu: finding lobbies" );
}
else if ( m_eGameState == k_EClientGameMenu )
{
// we've switched out to the main menu
// Tell the server we have left if we are connected
DisconnectFromServer();
// shut down any server we were running
if ( m_pServer )
{
delete m_pServer;
m_pServer = NULL;
}
SteamFriends()->SetRichPresence( "status", "Main menu" );
}
else if ( m_eGameState == k_EClientGameWinner || m_eGameState == k_EClientGameDraw )
{
// game over.. update the leaderboard
m_pLeaderboards->UpdateLeaderboards( m_pStatsAndAchievements );
}
else if ( m_eGameState == k_EClientLeaderboards )
{
// we've switched to the leaderboard menu
m_pLeaderboards->Show();
SteamFriends()->SetRichPresence( "status", "Viewing leaderboards" );
}
else if ( m_eGameState == k_EClientClanChatRoom )
{
// we've switched to the leaderboard menu
m_pClanChatRoom->Show();
SteamFriends()->SetRichPresence( "status", "Chatting" );
}
else if ( m_eGameState == k_EClientGameActive )
{
// start voice chat
m_pVoiceChat->StartVoiceChat();
SteamFriends()->SetRichPresence( "status", "In match" );
}
else if ( m_eGameState == k_EClientRemoteStorage )
{
// we've switched to the remote storage menu
m_pRemoteStorage->Show();
SteamFriends()->SetRichPresence( "status", "Viewing remote storage" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification of a steam ipc failure
// we may get multiple callbacks, one for each IPC operation we attempted
// since the actual failure, so protect ourselves from alerting more than once.
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnIPCFailure( IPCFailure_t *failure )
{
static bool bExiting = false;
if ( !bExiting )
{
OutputDebugString( "Steam IPC Failure, shutting down\n" );
#if defined( _WIN32 )
::MessageBoxA( NULL, "Connection to Steam Lost, Exiting", "Steam Connection Error", MB_OK );
#endif
m_pGameEngine->Shutdown();
bExiting = true;
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification of a Steam shutdown request since a Windows
// user in a second concurrent session requests to play this game. Shutdown
// this process immediately if possible.
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnSteamShutdown( SteamShutdown_t *callback )
{
static bool bExiting = false;
if ( !bExiting )
{
OutputDebugString( "Steam shutdown request, shutting down\n" );
m_pGameEngine->Shutdown();
bExiting = true;
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification that we are now connected to Steam
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnSteamServersConnected( SteamServersConnected_t *callback )
{
if ( SteamUser()->BLoggedOn() )
m_eGameState = k_EClientGameMenu;
else
{
OutputDebugString( "Got SteamServersConnected_t, but not logged on?\n" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification that we are now connected to Steam
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnSteamServersDisconnected( SteamServersDisconnected_t *callback )
{
SetGameState( k_EClientConnectingToSteam );
m_pConnectingMenu->OnConnectFailure();
OutputDebugString( "Got SteamServersDisconnected_t\n" );
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification that the Steam overlay is shown/hidden, note, this
// doesn't mean the overlay will or will not draw, it may still draw when not active.
// This does mean the time when the overlay takes over input focus from the game.
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnGameOverlayActivated( GameOverlayActivated_t *callback )
{
if ( callback->m_bActive )
OutputDebugString( "Steam overlay now active\n" );
else
OutputDebugString( "Steam overlay now inactive\n" );
}
//-----------------------------------------------------------------------------
// Purpose: Handles notification that we are failed to connected to Steam
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnSteamServerConnectFailure( SteamServerConnectFailure_t *callback )
{
char rgchString[256];
_snprintf( rgchString, 256, "SteamServerConnectFailure_t: %d\n", callback->m_eResult );
m_pConnectingMenu->OnConnectFailure();
}
//-----------------------------------------------------------------------------
// Purpose: Main frame function, updates the state of the world and performs rendering
//-----------------------------------------------------------------------------
void CSpaceWarClient::RunFrame()
{
// Get any new data off the network to begin with
ReceiveNetworkData();
if ( m_eConnectedStatus != k_EClientNotConnected && m_pGameEngine->GetGameTickCount() - m_ulLastNetworkDataReceivedTime > MILLISECONDS_CONNECTION_TIMEOUT )
{
SetConnectionFailureText( "Game server connection failure." );
DisconnectFromServer(); // cleanup on our side, even though server won't get our disconnect msg
SetGameState( k_EClientGameConnectionFailure );
}
// Check if escape has been pressed, we'll use that info in a couple places below
bool bEscapePressed = false;
if ( m_pGameEngine->BIsKeyDown( VK_ESCAPE ) )
{
static uint64 m_ulLastESCKeyTick = 0;
uint64 ulCurrentTickCount = m_pGameEngine->GetGameTickCount();
if ( ulCurrentTickCount - 250 > m_ulLastESCKeyTick )
{
m_ulLastESCKeyTick = ulCurrentTickCount;
bEscapePressed = true;
}
}
// Run Steam client callbacks
SteamAPI_RunCallbacks();
// For now, run stats/achievements every frame
m_pStatsAndAchievements->RunFrame();
// if we just transitioned state, perform on change handlers
if ( m_bTransitionedGameState )
{
m_bTransitionedGameState = false;
OnGameStateChanged( m_eGameState );
}
// Update state for everything
switch ( m_eGameState )
{
case k_EClientConnectingToSteam:
m_pStarField->Render();
m_pConnectingMenu->RunFrame();
break;
case k_EClientRetrySteamConnection:
#ifdef _PS3
m_pStarField->Render();
SteamUser()->LogOn( true );
m_pConnectingMenu->Reset();
SetGameState( k_EClientConnectingToSteam );
#else
OutputDebugString( "Invalidate state k_EClientRetrySteamConnection hit on non-PS3 platform" );
#endif
break;
case k_EClientLinkSteamAccount:
#ifdef _PS3
m_pStarField->Render();
SteamUser()->LogOnAndLinkSteamAccountToPSN( true, "jmccaskeybeta", "test123" );
m_pConnectingMenu->Reset();
SetGameState( k_EClientConnectingToSteam );
#else
OutputDebugString( "Invalidate state k_EClientLinkSteamAccount hit on non-PS3 platform" );
#endif
break;
case k_EClientAutoCreateAccount:
#ifdef _PS3
m_pStarField->Render();
m_pConnectingMenu->Reset();
SteamUser()->LogOnAndCreateNewSteamAccountIfNeeded( true );
SetGameState( k_EClientConnectingToSteam );
#else
OutputDebugString( "Invalidate state k_EClientAutoCreateAccount hit on non-PS3 platform" );
#endif
break;
case k_EClientGameMenu:
m_pStarField->Render();
m_pMainMenu->RunFrame();
break;
case k_EClientFindInternetServers:
case k_EClientFindLANServers:
m_pStarField->Render();
m_pServerBrowser->RunFrame();
break;
case k_EClientCreatingLobby:
m_pStarField->Render();
// draw some text about creating lobby (may take a second or two)
break;
case k_EClientInLobby:
m_pStarField->Render();
// display the lobby
m_pLobby->RunFrame();
// see if we have a game server ready to play on
if ( m_pServer && m_pServer->IsConnectedToSteam() )
{
// server is up; tell everyone else to connect
SteamMatchmaking()->SetLobbyGameServer( m_steamIDLobby, 0, 0, m_pServer->GetSteamID() );
// start connecting ourself via localhost (this will automatically leave the lobby)
InitiateServerConnection( m_pServer->GetSteamID() );
}
break;
case k_EClientFindLobby:
m_pStarField->Render();
// display the list of lobbies
m_pLobbyBrowser->RunFrame();
break;
case k_EClientJoiningLobby:
m_pStarField->Render();
// Draw text telling the user a connection attempt is in progress
DrawConnectionAttemptText();
// Check if we've waited too long and should time out the connection
if ( m_pGameEngine->GetGameTickCount() - m_ulStateTransitionTime > MILLISECONDS_CONNECTION_TIMEOUT )
{
SetConnectionFailureText( "Timed out connecting to lobby." );
SetGameState( k_EClientGameConnectionFailure );
}
break;
case k_EClientGameConnectionFailure:
m_pStarField->Render();
DrawConnectionFailureText();
if ( bEscapePressed )
SetGameState( k_EClientGameMenu );
break;
case k_EClientGameConnecting:
m_pStarField->Render();
// Draw text telling the user a connection attempt is in progress
DrawConnectionAttemptText();
// Check if we've waited too long and should time out the connection
if ( m_pGameEngine->GetGameTickCount() - m_ulStateTransitionTime > MILLISECONDS_CONNECTION_TIMEOUT )
{
if ( m_pP2PAuthedGame )
m_pP2PAuthedGame->EndGame();
if ( m_eConnectedStatus == k_EClientConnectedAndAuthenticated )
{
SteamUser()->TerminateGameConnection( m_unServerIP, m_usServerPort );
}
m_GameServerPing.CancelPing();
SetConnectionFailureText( "Timed out connecting to game server" );
SetGameState( k_EClientGameConnectionFailure );
}
break;
case k_EClientGameQuitMenu:
m_pStarField->Render();
// Update all the entities (this is client side interpolation)...
m_pSun->RunFrame();
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
m_rgpShips[i]->RunFrame();
}
// Now draw the menu
m_pQuitMenu->RunFrame();
break;
case k_EClientGameInstructions:
m_pStarField->Render();
DrawInstructions();
if ( bEscapePressed )
SetGameState( k_EClientGameMenu );
break;
case k_EClientStatsAchievements:
m_pStarField->Render();
m_pStatsAndAchievements->Render();
if ( bEscapePressed )
SetGameState( k_EClientGameMenu );
break;
case k_EClientLeaderboards:
m_pStarField->Render();
m_pLeaderboards->RunFrame();
if ( bEscapePressed )
SetGameState( k_EClientGameMenu );
break;
case k_EClientClanChatRoom:
m_pStarField->Render();
m_pClanChatRoom->RunFrame();
if ( bEscapePressed )
SetGameState( k_EClientGameMenu );
break;
case k_EClientRemoteStorage:
m_pStarField->Render();
m_pRemoteStorage->Render();
break;
case k_EClientMinidump:
#ifdef _WIN32
RaiseException( EXCEPTION_NONCONTINUABLE_EXCEPTION,
EXCEPTION_NONCONTINUABLE,
0, NULL );
#endif
SetGameState( k_EClientGameMenu );
break;
case k_EClientGameStartServer:
m_pStarField->Render();
if ( !m_pServer )
{
m_pServer = new CSpaceWarServer( m_pGameEngine );
}
if ( m_pServer && m_pServer->IsConnectedToSteam() )
{
// server is ready, connect to it
InitiateServerConnection( m_pServer->GetSteamID() );
}
break;
case k_EClientGameDraw:
case k_EClientGameWinner:
case k_EClientGameWaitingForPlayers:
m_pStarField->Render();
// Update all the entities (this is client side interpolation)...
m_pSun->RunFrame();
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
m_rgpShips[i]->RunFrame();
}
DrawHUDText();
DrawWinnerDrawOrWaitingText();
m_pVoiceChat->RunFrame();
if ( bEscapePressed )
SetGameState( k_EClientGameQuitMenu );
break;
case k_EClientGameActive:
m_pStarField->Render();
// Update all the entities...
m_pSun->RunFrame();
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
m_rgpShips[i]->RunFrame();
}
DrawHUDText();
m_pStatsAndAchievements->RunFrame();
m_pVoiceChat->RunFrame();
if ( bEscapePressed )
SetGameState( k_EClientGameQuitMenu );
break;
case k_EClientGameExiting:
DisconnectFromServer();
m_pGameEngine->Shutdown();
return;
default:
OutputDebugString( "Unhandled game state in CSpaceWar::RunFrame\n" );
}
// Send an update on our local ship to the server
if ( m_eConnectedStatus == k_EClientConnectedAndAuthenticated && m_rgpShips[ m_uPlayerShipIndex ] )
{
MsgClientSendLocalUpdate_t msg;
msg.SetShipPosition( m_uPlayerShipIndex );
// If this fails, it probably just means its not time to send an update yet
if ( m_rgpShips[ m_uPlayerShipIndex ]->BGetClientUpdateData( msg.AccessUpdateData() ) )
BSendServerData( &msg, sizeof( msg ) );
}
if ( m_pP2PAuthedGame )
{
if ( m_pServer )
{
// Now if we are the owner of the game, lets make sure all of our players are legit.
// if they are not, we tell the server to kick them off
// Start at 1 to skip myself
for ( int i = 1; i < MAX_PLAYERS_PER_SERVER; i++ )
{
if ( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i] && !m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i]->BIsAuthOk() )
{
m_pServer->KickPlayerOffServer( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i]->m_steamID );
}
}
}
else
{
// If we are not the owner of the game, lets make sure the game owner is legit
// if he is not, we leave the game
if ( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[0] )
{
if ( !m_pP2PAuthedGame->m_rgpP2PAuthPlayer[0]->BIsAuthOk() )
{
// leave the game
SetGameState( k_EClientGameMenu );
}
}
}
}
// If we've started a local server run it
if ( m_pServer )
{
m_pServer->RunFrame();
}
// Accumulate stats
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
m_rgpShips[i]->AccumulateStats( m_pStatsAndAchievements );
}
// Render everything that might have been updated by the server
switch ( m_eGameState )
{
case k_EClientGameDraw:
case k_EClientGameWinner:
case k_EClientGameActive:
// Now render all the objects
m_pSun->Render();
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
if ( m_rgpShips[i] )
m_rgpShips[i]->Render();
}
break;
default:
// Any needed drawing was already done above before server updates
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Draws some HUD text indicating game status
//-----------------------------------------------------------------------------
void CSpaceWarClient::DrawHUDText()
{
// Padding from the edge of the screen for hud elements
#ifdef _PS3
// Larger padding on PS3, since many of our test HDTVs truncate
// edges of the screen and can't be calibrated properly.
const int32 nHudPaddingVertical = 20;
const int32 nHudPaddingHorizontal = 35;
#else
const int32 nHudPaddingVertical = 15;
const int32 nHudPaddingHorizontal = 15;
#endif
const int32 width = m_pGameEngine->GetViewportWidth();
const int32 height = m_pGameEngine->GetViewportHeight();
const int32 nAvatarWidth = 64;
const int32 nAvatarHeight = 64;
const int32 nSpaceBetweenAvatarAndScore = 6;
LONG scorewidth = LONG((m_pGameEngine->GetViewportWidth() - nHudPaddingHorizontal*2.0f)/4.0f);
char rgchBuffer[256];
for( uint32 i=0; i<MAX_PLAYERS_PER_SERVER; ++i )
{
// Draw nothing in the spot for an inactive player
if ( !m_rgpShips[i] )
continue;
// We use Steam persona names for our players in-game name. To get these we
// just call SteamFriends()->GetFriendPersonaName() this call will work on friends,
// players on the same game server as us (if using the Steam game server auth API)
// and on ourself.
char rgchPlayerName[128];
CSteamID playerSteamID( m_rgSteamIDPlayers[i] );
const char *pszVoiceState = m_pVoiceChat->IsPlayerTalking( playerSteamID ) ? "(VoiceChat)" : "";
if ( m_rgSteamIDPlayers[i].IsValid() )
{
_snprintf( rgchPlayerName, ARRAYSIZE( rgchPlayerName ), "%s", SteamFriends()->GetFriendPersonaName( playerSteamID ) );
}
else
{
_snprintf( rgchPlayerName, ARRAYSIZE( rgchPlayerName ), "Unknown Player" );
}
// We also want to use the Steam Avatar image inside the HUD if it is available.
// We look it up via GetMediumFriendAvatar, which returns an image index we use
// to look up the actual RGBA data below.
int iImage = SteamFriends()->GetMediumFriendAvatar( playerSteamID );
HGAMETEXTURE hTexture = NULL;
if ( iImage != -1 )
hTexture = GetSteamImageAsTexture( iImage );
RECT rect;
switch( i )
{
case 0:
rect.top = nHudPaddingVertical;
rect.bottom = rect.top+nAvatarHeight;
rect.left = nHudPaddingHorizontal;
rect.right = rect.left + scorewidth;
if ( hTexture )
{
m_pGameEngine->BDrawTexturedQuad( (float)rect.left, (float)rect.top, (float)rect.left+nAvatarWidth, (float)rect.bottom,
0.0f, 0.0f, 1.0, 1.0, D3DCOLOR_ARGB( 255, 255, 255, 255 ), hTexture );
rect.left += nAvatarWidth + nSpaceBetweenAvatarAndScore;
rect.right += nAvatarWidth + nSpaceBetweenAvatarAndScore;
}
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s\nScore: %2u %s", rgchPlayerName, m_rguPlayerScores[i], pszVoiceState );
m_pGameEngine->BDrawString( m_hHUDFont, rect, g_rgPlayerColors[i], TEXTPOS_LEFT|TEXTPOS_VCENTER, rgchBuffer );
break;
case 1:
rect.top = nHudPaddingVertical;
rect.bottom = rect.top+nAvatarHeight;
rect.left = width-nHudPaddingHorizontal-scorewidth;
rect.right = width-nHudPaddingHorizontal;
if ( hTexture )
{
m_pGameEngine->BDrawTexturedQuad( (float)rect.right - nAvatarWidth, (float)rect.top, (float)rect.right, (float)rect.bottom,
0.0f, 0.0f, 1.0, 1.0, D3DCOLOR_ARGB( 255, 255, 255, 255 ), hTexture );
rect.right -= nAvatarWidth + nSpaceBetweenAvatarAndScore;
rect.left -= nAvatarWidth + nSpaceBetweenAvatarAndScore;
}
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s\nScore: %2u ", rgchPlayerName, m_rguPlayerScores[i] );
m_pGameEngine->BDrawString( m_hHUDFont, rect, g_rgPlayerColors[i], TEXTPOS_RIGHT|TEXTPOS_VCENTER, rgchBuffer );
break;
case 2:
rect.top = height-nHudPaddingVertical-nAvatarHeight;
rect.bottom = rect.top+nAvatarHeight;
rect.left = nHudPaddingHorizontal;
rect.right = rect.left + scorewidth;
if ( hTexture )
{
m_pGameEngine->BDrawTexturedQuad( (float)rect.left, (float)rect.top, (float)rect.left+nAvatarWidth, (float)rect.bottom,
0.0f, 0.0f, 1.0, 1.0, D3DCOLOR_ARGB( 255, 255, 255, 255 ), hTexture );
rect.right += nAvatarWidth + nSpaceBetweenAvatarAndScore;
rect.left += nAvatarWidth + nSpaceBetweenAvatarAndScore;
}
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s\nScore: %2u %s", rgchPlayerName, m_rguPlayerScores[i], pszVoiceState );
m_pGameEngine->BDrawString( m_hHUDFont, rect, g_rgPlayerColors[i], TEXTPOS_LEFT|TEXTPOS_BOTTOM, rgchBuffer );
break;
case 3:
rect.top = height-nHudPaddingVertical-nAvatarHeight;
rect.bottom = rect.top+nAvatarHeight;
rect.left = width-nHudPaddingHorizontal-scorewidth;
rect.right = width-nHudPaddingHorizontal;
if ( hTexture )
{
m_pGameEngine->BDrawTexturedQuad( (float)rect.right - nAvatarWidth, (float)rect.top, (float)rect.right, (float)rect.bottom,
0.0f, 0.0f, 1.0, 1.0, D3DCOLOR_ARGB( 255, 255, 255, 255 ), hTexture );
rect.right -= nAvatarWidth + nSpaceBetweenAvatarAndScore;
rect.left -= nAvatarWidth + nSpaceBetweenAvatarAndScore;
}
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s\nScore: %2u %s", rgchPlayerName, m_rguPlayerScores[i], pszVoiceState );
m_pGameEngine->BDrawString( m_hHUDFont, rect, g_rgPlayerColors[i], TEXTPOS_RIGHT|TEXTPOS_BOTTOM, rgchBuffer );
break;
default:
OutputDebugString( "DrawHUDText() needs updating for more players\n" );
break;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Draws some instructions on how to play the game
//-----------------------------------------------------------------------------
void CSpaceWarClient::DrawInstructions()
{
const int32 width = m_pGameEngine->GetViewportWidth();
RECT rect;
rect.top = 0;
rect.bottom = m_pGameEngine->GetViewportHeight();
rect.left = 0;
rect.right = width;
char rgchBuffer[256];
#ifdef _PS3
_snprintf( rgchBuffer, sizeof( rgchBuffer), "Turn Ship Left: 'Left'\nTurn Ship Right: 'Right'\nForward Thrusters: 'R2'\nReverse Thrusters: 'L2'\nFire Photon Beams: 'Cross'" );
#else
_snprintf( rgchBuffer, sizeof( rgchBuffer), "Turn Ship Left: 'A'\nTurn Ship Right: 'D'\nForward Thrusters: 'W'\nReverse Thrusters: 'S'\nFire Photon Beams: 'Space'" );
#endif
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
rect.left = 0;
rect.right = width;
rect.top = LONG(m_pGameEngine->GetViewportHeight() * 0.7);
rect.bottom = m_pGameEngine->GetViewportHeight();
_snprintf( rgchBuffer, sizeof( rgchBuffer ), "Press ESC to return to the Main Menu" );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_TOP, rgchBuffer );
}
//-----------------------------------------------------------------------------
// Purpose: Draws some text indicating a connection attempt is in progress
//-----------------------------------------------------------------------------
void CSpaceWarClient::DrawConnectionAttemptText()
{
const int32 width = m_pGameEngine->GetViewportWidth();
RECT rect;
rect.top = 0;
rect.bottom = m_pGameEngine->GetViewportHeight();
rect.left = 0;
rect.right = width;
// Figure out how long we are still willing to wait for success
uint32 uSecondsLeft = (MILLISECONDS_CONNECTION_TIMEOUT - uint32(m_pGameEngine->GetGameTickCount() - m_ulStateTransitionTime ))/1000;
char rgchTimeoutString[256];
if ( uSecondsLeft < 25 )
_snprintf( rgchTimeoutString, sizeof( rgchTimeoutString ), ", timeout in %u...\n", uSecondsLeft );
else
_snprintf( rgchTimeoutString, sizeof( rgchTimeoutString ), "...\n" );
char rgchBuffer[256];
if ( m_eGameState == k_EClientJoiningLobby )
_snprintf( rgchBuffer, sizeof( rgchBuffer ), "Connecting to lobby%s", rgchTimeoutString );
else
_snprintf( rgchBuffer, sizeof( rgchBuffer ), "Connecting to server%s", rgchTimeoutString );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
}
//-----------------------------------------------------------------------------
// Purpose: Draws some text indicating a connection failure
//-----------------------------------------------------------------------------
void CSpaceWarClient::DrawConnectionFailureText()
{
const int32 width = m_pGameEngine->GetViewportWidth();
RECT rect;
rect.top = 0;
rect.bottom = m_pGameEngine->GetViewportHeight();
rect.left = 0;
rect.right = width;
char rgchBuffer[256];
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s\n", m_rgchErrorText );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
rect.left = 0;
rect.right = width;
rect.top = LONG(m_pGameEngine->GetViewportHeight() * 0.7);
rect.bottom = m_pGameEngine->GetViewportHeight();
_snprintf( rgchBuffer, sizeof( rgchBuffer ), "Press ESC to return to the Main Menu" );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_TOP, rgchBuffer );
}
//-----------------------------------------------------------------------------
// Purpose: Draws some text about who just won (or that there was a draw)
//-----------------------------------------------------------------------------
void CSpaceWarClient::DrawWinnerDrawOrWaitingText()
{
int nSecondsToRestart = ((MILLISECONDS_BETWEEN_ROUNDS - (int)(m_pGameEngine->GetGameTickCount() - m_ulStateTransitionTime) )/1000) + 1;
RECT rect;
rect.top = 0;
rect.bottom = int(m_pGameEngine->GetViewportHeight()*0.6f);
rect.left = 0;
rect.right = m_pGameEngine->GetViewportWidth();
char rgchBuffer[256];
if ( m_eGameState == k_EClientGameWaitingForPlayers )
{
_snprintf( rgchBuffer, sizeof( rgchBuffer), "Server is waiting for players.\n\nStarting in %d seconds...", nSecondsToRestart );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
}
else if ( m_eGameState == k_EClientGameDraw )
{
_snprintf( rgchBuffer, sizeof( rgchBuffer), "The round is a draw!\n\nStarting again in %d seconds...", nSecondsToRestart );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
}
else if ( m_eGameState == k_EClientGameWinner )
{
if ( m_uPlayerWhoWonGame >= MAX_PLAYERS_PER_SERVER )
{
OutputDebugString( "Invalid winner value\n" );
return;
}
char rgchPlayerName[128];
if ( m_rgSteamIDPlayers[m_uPlayerWhoWonGame].IsValid() )
{
_snprintf( rgchPlayerName, ARRAYSIZE( rgchPlayerName ), "%s", SteamFriends()->GetFriendPersonaName( m_rgSteamIDPlayers[m_uPlayerWhoWonGame] ) );
}
else
{
_snprintf( rgchPlayerName, ARRAYSIZE( rgchPlayerName ), "Unknown Player" );
}
_snprintf( rgchBuffer, sizeof( rgchBuffer), "%s wins!\n\nStarting again in %d seconds...", rgchPlayerName, nSecondsToRestart );
m_pGameEngine->BDrawString( m_hInstructionsFont, rect, D3DCOLOR_ARGB( 255, 25, 200, 25 ), TEXTPOS_CENTER|TEXTPOS_VCENTER, rgchBuffer );
}
}
//-----------------------------------------------------------------------------
// Purpose: Did we win the last game?
//-----------------------------------------------------------------------------
bool CSpaceWarClient::BLocalPlayerWonLastGame()
{
if ( m_eGameState == k_EClientGameWinner )
{
if ( m_uPlayerWhoWonGame >= MAX_PLAYERS_PER_SERVER )
{
// ur
return false;
}
if ( m_rgpShips[m_uPlayerWhoWonGame] && m_rgpShips[m_uPlayerWhoWonGame]->BIsLocalPlayer() )
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose: Scale pixel sizes to "real" sizes
//-----------------------------------------------------------------------------
float CSpaceWarClient::PixelsToFeet( float flPixels )
{
// This game is actual size! (at 72dpi) LOL
// Those are very tiny ships, and an itty bitty neutron star
float flReturn = ( flPixels / 72 ) / 12;
return flReturn;
}
//-----------------------------------------------------------------------------
// Purpose: Get a specific Steam image RGBA as a game texture
//-----------------------------------------------------------------------------
HGAMETEXTURE CSpaceWarClient::GetSteamImageAsTexture( int iImage )
{
HGAMETEXTURE hTexture = NULL;
// iImage of 0 from steam means no avatar is set
if ( iImage )
{
std::map<int, HGAMETEXTURE>::iterator iter;
iter = m_MapSteamImagesToTextures.find( iImage );
if ( iter == m_MapSteamImagesToTextures.end() )
{
// We haven't created a texture for this image index yet, do so now
// Get the image size from Steam, making sure it looks valid afterwards
uint32 uAvatarWidth, uAvatarHeight;
SteamUtils()->GetImageSize( iImage, &uAvatarWidth, &uAvatarHeight );
if ( uAvatarWidth > 0 && uAvatarHeight > 0 )
{
// Get the actual raw RGBA data from Steam and turn it into a texture in our game engine
byte *pAvatarRGBA = new byte[ uAvatarWidth * uAvatarHeight * 4];
SteamUtils()->GetImageRGBA( iImage, (uint8*)pAvatarRGBA, uAvatarWidth * uAvatarHeight * 4 );
hTexture = m_pGameEngine->HCreateTexture( pAvatarRGBA, uAvatarWidth, uAvatarHeight );
delete[] pAvatarRGBA;
if ( hTexture )
{
m_MapSteamImagesToTextures[ iImage ] = hTexture;
}
}
}
else
{
hTexture = iter->second;
}
}
return hTexture;
}
//-----------------------------------------------------------------------------
// Purpose: Request an encrypted app ticket
//-----------------------------------------------------------------------------
uint32 k_unSecretData = 0x5444;
void CSpaceWarClient::RetrieveEncryptedAppTicket()
{
SteamAPICall_t hSteamAPICall = SteamUser()->RequestEncryptedAppTicket( &k_unSecretData, sizeof( k_unSecretData ) );
m_SteamCallResultEncryptedAppTicket.Set( hSteamAPICall, this, &CSpaceWarClient::OnRequestEncryptedAppTicket );
}
//-----------------------------------------------------------------------------
// Purpose: Called when requested app ticket asynchronously completes
//-----------------------------------------------------------------------------
void CSpaceWarClient::OnRequestEncryptedAppTicket( EncryptedAppTicketResponse_t *pEncryptedAppTicketResponse, bool bIOFailure )
{
if ( bIOFailure )
return;
if ( pEncryptedAppTicketResponse->m_eResult == k_EResultOK )
{
uint8 rgubTicket[1024];
uint32 cubTicket;
SteamUser()->GetEncryptedAppTicket( rgubTicket, sizeof( rgubTicket), &cubTicket );
#ifdef _WIN32
// normally at this point you transmit the encrypted ticket to the service that knows the decryption key
// this code is just to demonstrate the ticket cracking library
// included is the "secret" key for spacewar. normally this is secret
const uint8 rgubKey[k_nSteamEncryptedAppTicketSymmetricKeyLen] = { 0xed, 0x93, 0x86, 0x07, 0x36, 0x47, 0xce, 0xa5, 0x8b, 0x77, 0x21, 0x49, 0x0d, 0x59, 0xed, 0x44, 0x57, 0x23, 0xf0, 0xf6, 0x6e, 0x74, 0x14, 0xe1, 0x53, 0x3b, 0xa3, 0x3c, 0xd8, 0x03, 0xbd, 0xbd };
uint8 rgubDecrypted[1024];
uint32 cubDecrypted = sizeof( rgubDecrypted );
if ( !SteamEncryptedAppTicket_BDecryptTicket( rgubTicket, cubTicket, rgubDecrypted, &cubDecrypted, rgubKey, sizeof( rgubKey ) ) )
{
OutputDebugString( "Ticket failed to decrypt\n" );
return;
}
if ( !SteamEncryptedAppTicket_BIsTicketForApp( rgubDecrypted, cubDecrypted, SteamUtils()->GetAppID() ) )
OutputDebugString( "Ticket for wrong app id\n" );
CSteamID steamIDFromTicket;
SteamEncryptedAppTicket_GetTicketSteamID( rgubDecrypted, cubDecrypted, &steamIDFromTicket );
if ( steamIDFromTicket != SteamUser()->GetSteamID() )
OutputDebugString( "Ticket for wrong user\n" );
uint32 cubData;
uint32 *punSecretData = (uint32 *)SteamEncryptedAppTicket_GetUserVariableData( rgubDecrypted, cubDecrypted, &cubData );
if ( cubData != sizeof( uint32 ) || *punSecretData != k_unSecretData )
OutputDebugString( "Failed to retrieve secret data\n" );
#endif
}
else if ( pEncryptedAppTicketResponse->m_eResult == k_EResultLimitExceeded )
{
OutputDebugString( "Calling RequestEncryptedAppTicket more than once per minute returns this error\n" );
}
else if ( pEncryptedAppTicketResponse->m_eResult == k_EResultDuplicateRequest )
{
OutputDebugString( "Calling RequestEncryptedAppTicket while there is already a pending request results in this error\n" );
}
else if ( pEncryptedAppTicketResponse->m_eResult == k_EResultNoConnection )
{
OutputDebugString( "Calling RequestEncryptedAppTicket while not connected to steam results in this error\n" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Updates what we show to friends about what we're doing and how to connect
//-----------------------------------------------------------------------------
void CSpaceWarClient::UpdateRichPresenceConnectionInfo()
{
// connect string that will come back to us on the command line when a friend tries to join our game
char rgchConnectString[128];
rgchConnectString[0] = 0;
if ( m_eConnectedStatus == k_EClientConnectedAndAuthenticated && m_unServerIP && m_usServerPort )
{
// game server connection method
_snprintf( rgchConnectString, sizeof(rgchConnectString), "+connect %d:%d", m_unServerIP, m_usServerPort );
}
else if ( m_steamIDLobby.IsValid() )
{
// lobby connection method
_snprintf( rgchConnectString, sizeof(rgchConnectString), "+connect_lobby %llu", m_steamIDLobby.ConvertToUint64() );
}
SteamFriends()->SetRichPresence( "connect", rgchConnectString );
}
//-----------------------------------------------------------------------------
// Purpose: applies a command-line connect
//-----------------------------------------------------------------------------
void CSpaceWarClient::ExecCommandLineConnect( const char *pchServerAddress, const char *pchLobbyID )
{
if ( pchServerAddress )
{
int32 octet0 = 0, octet1 = 0, octet2 = 0, octet3 = 0;
int32 uPort = 0;
int nConverted = sscanf( pchServerAddress, "%d.%d.%d.%d:%d", &octet0, &octet1, &octet2, &octet3, &uPort );
if ( nConverted == 5 )
{
char rgchIPAddress[128];
_snprintf( rgchIPAddress, ARRAYSIZE( rgchIPAddress ), "%d.%d.%d.%d", octet0, octet1, octet2, octet3 );
uint32 unIPAddress = ( octet3 ) + ( octet2 << 8 ) + ( octet1 << 16 ) + ( octet0 << 24 );
InitiateServerConnection( unIPAddress, uPort );
}
}
// if +connect_lobby was used to specify a lobby to join, connect now
if ( pchLobbyID )
{
CSteamID steamIDLobby( (uint64)atoll( pchLobbyID ) );
if ( steamIDLobby.IsValid() )
{
// act just like we had selected it from the menu
LobbyBrowserMenuItem_t menuItem = { steamIDLobby, k_EClientJoiningLobby };
OnMenuSelection( menuItem );
}
}
}