mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-11-25 13:31:12 +00:00
1256 lines
No EOL
39 KiB
C++
1256 lines
No EOL
39 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "Session_local.h"
|
|
#include "../sys/sys_session_savegames.h"
|
|
#include "../sys/sys_voicechat.h"
|
|
|
|
|
|
/*
|
|
========================
|
|
SteamAPIDebugTextHook
|
|
========================
|
|
*/
|
|
extern "C" void __cdecl SteamAPIDebugTextHook( int nSeverity, const char * pchDebugText ) {
|
|
// if you're running in the debugger, only warnings (nSeverity >= 1) will be sent
|
|
// if you add -debug_steamapi to the command-line, a lot of extra informational messages will also be sent
|
|
idLib::Printf( "%s", pchDebugText );
|
|
|
|
if ( nSeverity >= 1 ) {
|
|
// place to set a breakpoint for catching API errors
|
|
int x = 3;
|
|
x;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
SESSION LOCAL
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::idSessionLocal
|
|
===============
|
|
*/
|
|
idSessionLocal::idSessionLocal() {
|
|
|
|
localState = STATE_PRESS_START;
|
|
|
|
InitBaseState();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::~idSessionLocal
|
|
===============
|
|
*/
|
|
idSessionLocal::~idSessionLocal() {
|
|
if ( sessionCallbacks != NULL ) {
|
|
delete sessionCallbacks;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::InitBaseState
|
|
========================
|
|
*/
|
|
void idSessionLocal::InitBaseState() {
|
|
|
|
|
|
localState = STATE_PRESS_START;
|
|
sessionOptions = 0;
|
|
currentID = 0;
|
|
|
|
sessionCallbacks = new idSessionLocalCallbacks( this );
|
|
|
|
connectType = CONNECT_NONE;
|
|
|
|
isSysUIShowing = false;
|
|
|
|
pendingInviteDevice = 0;
|
|
pendingInviteMode = PENDING_INVITE_NONE;
|
|
|
|
|
|
flushedStats = false;
|
|
|
|
enumerationHandle = 0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Shutdown
|
|
===============
|
|
*/
|
|
void idSessionLocal::Shutdown() {
|
|
|
|
delete signInManager;
|
|
|
|
if ( achievementSystem != NULL ) {
|
|
achievementSystem->Shutdown();
|
|
delete achievementSystem;
|
|
}
|
|
|
|
DestroySteamObjects();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idSessionLocal::Init
|
|
|
|
Called in an orderly fashion at system startup,
|
|
so commands, cvars, files, etc are all available
|
|
===============
|
|
*/
|
|
void idSessionLocal::Init() {
|
|
|
|
common->Printf( "-------- Initializing Session --------\n" );
|
|
|
|
InitSteam();
|
|
ConstructSteamObjects();
|
|
|
|
signInManager = new idSignInManagerWin();
|
|
achievementSystem = new idAchievementSystemWin();
|
|
achievementSystem->Init();
|
|
Initialize();
|
|
|
|
common->Printf( "session initialized\n" );
|
|
common->Printf( "--------------------------------------\n" );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::InitSteam
|
|
========================
|
|
*/
|
|
void idSessionLocal::InitSteam() {
|
|
if ( steamInitialized || steamFailed ) {
|
|
if ( steamFailed ) {
|
|
net_usePlatformBackend.SetBool( false );
|
|
}
|
|
return;
|
|
}
|
|
|
|
steamInitialized = SteamAPI_Init();
|
|
steamFailed = !steamInitialized;
|
|
|
|
if ( steamFailed ) {
|
|
if ( net_usePlatformBackend.GetBool() ) {
|
|
idLib::Warning( "Steam failed to initialize. Usually this happens because the Steam client isn't running." );
|
|
// Turn off the usage of steam if it fails to initialize
|
|
// FIXME: We'll want to bail (nicely) in the shipping product most likely
|
|
net_usePlatformBackend.SetBool( false );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// from now on, all Steam API functions should return non-null interface pointers
|
|
|
|
assert( SteamUtils() );
|
|
SteamUtils()->SetWarningMessageHook( &SteamAPIDebugTextHook );
|
|
|
|
ConstructSteamObjects();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ConstructSteamObjects
|
|
========================
|
|
*/
|
|
void idSessionLocal::ConstructSteamObjects() {
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::DestroySteamObjects
|
|
========================
|
|
*/
|
|
void idSessionLocal::DestroySteamObjects() {
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::MoveToPressStart
|
|
========================
|
|
*/
|
|
void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) {
|
|
if ( localState != STATE_PRESS_START ) {
|
|
MoveToPressStart();
|
|
|
|
common->Dialog().ClearDialogs();
|
|
common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::MoveToPressStart
|
|
========================
|
|
*/
|
|
void idSessionLocal::MoveToPressStart() {
|
|
if ( localState != STATE_PRESS_START ) {
|
|
GetSignInManager().RemoveAllLocalUsers();
|
|
SetState( STATE_PRESS_START );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::SetState
|
|
========================
|
|
*/
|
|
void idSessionLocal::SetState( idSessionLocal::state_t newState ) {
|
|
assert( newState < NUM_STATES );
|
|
assert( localState < NUM_STATES );
|
|
|
|
if ( newState == localState ) {
|
|
return;
|
|
}
|
|
|
|
localState = newState;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GetState
|
|
========================
|
|
*/
|
|
idSessionLocal::sessionState_t idSessionLocal::GetState() const {
|
|
// Convert our internal state to one of the external states
|
|
switch ( localState ) {
|
|
case STATE_PRESS_START: return PRESS_START;
|
|
case STATE_IDLE: return IDLE;
|
|
case STATE_PARTY_LOBBY_HOST: return PARTY_LOBBY;
|
|
case STATE_PARTY_LOBBY_PEER: return PARTY_LOBBY;
|
|
case STATE_GAME_LOBBY_HOST: return GAME_LOBBY;
|
|
case STATE_GAME_LOBBY_PEER: return GAME_LOBBY;
|
|
//case STATE_GAME_STATE_LOBBY_HOST: return GAME_LOBBY;
|
|
//case STATE_GAME_STATE_LOBBY_PEER: return GAME_LOBBY;
|
|
case STATE_LOADING: return LOADING;
|
|
case STATE_INGAME: return INGAME;
|
|
case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return CONNECTING;
|
|
case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return CONNECTING;
|
|
//case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return CONNECTING;
|
|
case STATE_FIND_OR_CREATE_MATCH: return SEARCHING;
|
|
case STATE_CONNECT_AND_MOVE_TO_PARTY: return CONNECTING;
|
|
case STATE_CONNECT_AND_MOVE_TO_GAME: return CONNECTING;
|
|
//case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return CONNECTING;
|
|
case STATE_BUSY: return BUSY;
|
|
default: {
|
|
idLib::Error( "GetState: Unknown state in idSessionLocal" );
|
|
return IDLE;
|
|
}
|
|
};
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::Pump
|
|
========================
|
|
*/
|
|
void idSessionLocal::Pump() {
|
|
|
|
GetSignInManager().Pump();
|
|
|
|
idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser();
|
|
|
|
if ( masterUser != NULL && localState == STATE_PRESS_START ) {
|
|
// If we have a master user, and we are at press start, move to the menu area
|
|
SetState( STATE_IDLE );
|
|
|
|
|
|
}
|
|
|
|
GetAchievementSystem().Pump();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::OnMasterLocalUserSignin
|
|
========================
|
|
*/
|
|
void idSessionLocal::OnMasterLocalUserSignin() {
|
|
enumerationHandle = EnumerateSaveGames( 0 );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::LoadGame
|
|
========================
|
|
*/
|
|
saveGameHandle_t idSessionLocal::LoadGame( const char * name, const idList< idSaveFileEntry > & files ) {
|
|
if ( processorLoadFiles.InitLoadFiles( name, files ) ) {
|
|
return saveGameManager.ExecuteProcessor( &processorLoadFiles );
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::SaveGame
|
|
========================
|
|
*/
|
|
saveGameHandle_t idSessionLocal::SaveGame( const char * name, const idList< idSaveFileEntry > & files, const idSaveGameDetails & description, uint64 skipErrorMask ) {
|
|
saveGameHandle_t ret = 0;
|
|
|
|
// serialize the description file behind their back...
|
|
idList< idSaveFileEntry > filesWithDetails( files );
|
|
idFile_Memory * gameDetailsFile = new idFile_Memory( SAVEGAME_DETAILS_FILENAME );
|
|
//gameDetailsFile->MakeWritable();
|
|
description.descriptors.WriteToIniFile( gameDetailsFile );
|
|
filesWithDetails.Append( idSaveFileEntry( gameDetailsFile, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE, SAVEGAME_DETAILS_FILENAME ) );
|
|
|
|
if ( processorSave.InitSave( name, filesWithDetails, description ) ) {
|
|
processorSave.SetSkipSystemErrorDialogMask( skipErrorMask );
|
|
ret = GetSaveGameManager().ExecuteProcessor( &processorSave );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::EnumerateSaveGames
|
|
========================
|
|
*/
|
|
saveGameHandle_t idSessionLocal::EnumerateSaveGames( uint64 skipErrorMask ) {
|
|
saveGameHandle_t ret = 0;
|
|
|
|
// flush the old enumerated list
|
|
GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear();
|
|
|
|
if ( processorEnumerate.Init() ) {
|
|
processorEnumerate.SetSkipSystemErrorDialogMask( skipErrorMask );
|
|
ret = GetSaveGameManager().ExecuteProcessor( &processorEnumerate );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::DeleteSaveGame
|
|
========================
|
|
*/
|
|
saveGameHandle_t idSessionLocal::DeleteSaveGame( const char * name, uint64 skipErrorMask ) {
|
|
saveGameHandle_t ret = 0;
|
|
if ( processorDelete.InitDelete( name ) ) {
|
|
processorDelete.SetSkipSystemErrorDialogMask( skipErrorMask );
|
|
ret = GetSaveGameManager().ExecuteProcessor( &
|
|
processorDelete );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::IsEnumerating
|
|
========================
|
|
*/
|
|
bool idSessionLocal::IsEnumerating() const {
|
|
return !session->IsSaveGameCompletedFromHandle( processorEnumerate.GetHandle() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GetEnumerationHandle
|
|
========================
|
|
*/
|
|
saveGameHandle_t idSessionLocal::GetEnumerationHandle() const {
|
|
return processorEnumerate.GetHandle();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::CancelSaveGameWithHandle
|
|
========================
|
|
*/
|
|
void idSessionLocal::CancelSaveGameWithHandle( const saveGameHandle_t & handle ) {
|
|
GetSaveGameManager().CancelWithHandle( handle );
|
|
}
|
|
|
|
|
|
// FIXME: Move to sys_stats.cpp
|
|
leaderboardDefinition_t * registeredLeaderboards[MAX_LEADERBOARDS];
|
|
int numRegisteredLeaderboards = 0;
|
|
|
|
/*
|
|
========================
|
|
Sys_FindLeaderboardDef
|
|
========================
|
|
*/
|
|
const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) {
|
|
for ( int i = 0; i < numRegisteredLeaderboards; i++ ) {
|
|
if ( registeredLeaderboards[i]->id == id ) {
|
|
return registeredLeaderboards[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GetActiveLobby
|
|
========================
|
|
*/
|
|
idLobby * idSessionLocal::GetActiveLobby() {
|
|
sessionState_t state = GetState();
|
|
|
|
if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
|
|
return &GetGameLobby();
|
|
} else if ( state == PARTY_LOBBY ) {
|
|
return &GetPartyLobby();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GetActiveLobby
|
|
========================
|
|
*/
|
|
const idLobby * idSessionLocal::GetActiveLobby() const {
|
|
sessionState_t state = GetState();
|
|
|
|
if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
|
|
return &GetGameLobby();
|
|
} else if ( state == PARTY_LOBBY ) {
|
|
return &GetPartyLobby();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GetActiveLobbyBase
|
|
This returns the base version for the idSession version
|
|
========================
|
|
*/
|
|
idLobbyBase & idSessionLocal::GetActiveLobbyBase() {
|
|
idLobby * activeLobby = GetActiveLobby();
|
|
|
|
if ( activeLobby != NULL ) {
|
|
return *activeLobby;
|
|
}
|
|
|
|
return stubLobby; // So we can return at least something
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::PrePickNewHost
|
|
This is called when we have determined that we need to pick a new host.
|
|
Call PickNewHostInternal to continue on with the host picking process.
|
|
========================
|
|
*/
|
|
void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() );
|
|
|
|
if ( GetActiveLobby() == NULL ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActiveLobby() == NULL (%s)\n", lobby.GetLobbyName() );
|
|
return;
|
|
}
|
|
|
|
// Check to see if we can migrate AT ALL
|
|
// This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
|
|
if ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() );
|
|
|
|
// Can't migrate, shut both lobbies down, and create a new match using the original parms
|
|
GetGameLobby().Shutdown();
|
|
GetPartyLobby().Shutdown();
|
|
|
|
// Throw up the appropriate dialog message so the player knows what happeend
|
|
if ( localState >= STATE_LOADING ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
|
|
common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
|
|
} else {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
|
|
common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
|
|
}
|
|
|
|
CreateMatch( GetActiveLobby()->parms );
|
|
|
|
return;
|
|
}
|
|
|
|
// Check to see if the match is searchable
|
|
if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() );
|
|
// Searchable games migrate lobbies independently, and don't need to stay in sync
|
|
lobby.PickNewHostInternal( forceMe, inviteOldHost );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
|
|
// So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
|
|
//
|
|
|
|
// Check to see if we should go back to a party lobby
|
|
if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
|
|
// Force the party lobby to start picking a new host if we lost the game lobby host
|
|
GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost );
|
|
|
|
// End the game lobby, and go back to party lobby
|
|
GetGameLobby().Shutdown();
|
|
SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER );
|
|
} else {
|
|
NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
|
|
if ( localState >= STATE_LOADING ) {
|
|
common->Dialog().AddDialog( GDM_HOST_QUIT, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); // The host has quit the session. Returning to the main menu.
|
|
}
|
|
|
|
// Go back to main menu
|
|
GetGameLobby().Shutdown();
|
|
GetPartyLobby().Shutdown();
|
|
SetState( STATE_IDLE );
|
|
}
|
|
}
|
|
/*
|
|
========================
|
|
idSessionLocal::PreMigrateInvite
|
|
This is called just before we get invited to a migrated session
|
|
If we return false, the invite will be ignored
|
|
========================
|
|
*/
|
|
bool idSessionLocal::PreMigrateInvite( idLobby & lobby )
|
|
{
|
|
if ( GetActiveLobby() == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
// Check to see if we can migrate AT ALL
|
|
// This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
|
|
if ( !verify( ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
|
|
return false; // Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION))
|
|
}
|
|
|
|
// Check to see if the match is searchable
|
|
if ( MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
|
|
// Searchable games migrate lobbies independently, and don't need to stay in sync
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
|
|
// So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
|
|
//
|
|
|
|
if ( lobby.lobbyType != idLobby::TYPE_PARTY ) {
|
|
return false; // We shouldn't be getting invites from non party lobbies when in a non searchable game
|
|
}
|
|
|
|
// Non placeholder Party lobbies can always migrate
|
|
if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) {
|
|
// Non searchable games go back to the party lobby
|
|
GetGameLobby().Shutdown();
|
|
SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER );
|
|
}
|
|
|
|
return true; // Non placeholder Party lobby invites joinable
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandleDedicatedServerQueryRequest
|
|
========================
|
|
*/
|
|
void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) {
|
|
NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() );
|
|
|
|
bool canJoin = true;
|
|
|
|
const unsigned long localChecksum = 0;
|
|
const unsigned long remoteChecksum = msg.ReadLong();
|
|
|
|
if ( remoteChecksum != localChecksum ) {
|
|
NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() );
|
|
canJoin = false;
|
|
}
|
|
|
|
// Make sure we are the host of this party session
|
|
if ( !GetPartyLobby().IsHost() ) {
|
|
NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" );
|
|
canJoin = false;
|
|
}
|
|
|
|
// Make sure there is a session active
|
|
if ( GetActiveLobby() == NULL ) {
|
|
canJoin = false;
|
|
}
|
|
|
|
// Make sure we have enough free slots
|
|
if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) {
|
|
NET_VERBOSE_PRINT( "No free slots\n" );
|
|
canJoin = false;
|
|
}
|
|
|
|
if ( MatchTypeInviteOnly( GetPartyLobby().parms.GetSessionMatchFlags() ) ) {
|
|
canJoin = false;
|
|
}
|
|
|
|
// Buffer to hold reply msg
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
|
|
idBitMsg retmsg;
|
|
retmsg.InitWrite( buffer, sizeof( buffer ) );
|
|
|
|
idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser();
|
|
|
|
if ( masterUser == NULL ) {
|
|
canJoin = false;
|
|
}
|
|
|
|
// Send the info about this game session to the caller
|
|
retmsg.WriteBool( canJoin );
|
|
|
|
if ( canJoin ) {
|
|
retmsg.WriteBool( session->GetState() >= idSession::LOADING );
|
|
|
|
retmsg.WriteString( masterUser->GetGamerTag() );
|
|
retmsg.WriteLong( GetActiveLobby()->parms.GetGameType() ); // We need to write out the game type whether we are in a game or not
|
|
|
|
if ( GetGameLobby().IsSessionActive() ) {
|
|
retmsg.WriteLong( GetGameLobby().parms.GetGameMap() );
|
|
retmsg.WriteLong( GetGameLobby().parms.GetGameMode() );
|
|
} else {
|
|
retmsg.WriteLong( -1 );
|
|
retmsg.WriteLong( -1 );
|
|
}
|
|
|
|
retmsg.WriteLong( GetActiveLobby()->GetNumLobbyUsers() );
|
|
retmsg.WriteLong( GetActiveLobby()->parms.GetNumSlots() );
|
|
for ( int i = 0; i < GetActiveLobby()->GetNumLobbyUsers(); i++ ) {
|
|
retmsg.WriteString( GetActiveLobby()->GetLobbyUserName( i ) );
|
|
}
|
|
}
|
|
|
|
// Send it
|
|
GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetWriteData(), retmsg.GetSize() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandleDedicatedServerQueryAck
|
|
========================
|
|
*/
|
|
void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) {
|
|
NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::StartSessions
|
|
========================
|
|
*/
|
|
void idSessionLocal::StartSessions() {
|
|
if ( GetPartyLobby().lobbyBackend != NULL ) {
|
|
GetPartyLobby().lobbyBackend->StartSession();
|
|
}
|
|
|
|
if ( GetGameLobby().lobbyBackend != NULL ) {
|
|
GetGameLobby().lobbyBackend->StartSession();
|
|
}
|
|
|
|
SetLobbiesAreJoinable( false );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::EndSessions
|
|
========================
|
|
*/
|
|
void idSessionLocal::EndSessions() {
|
|
if ( GetPartyLobby().lobbyBackend != NULL ) {
|
|
GetPartyLobby().lobbyBackend->EndSession();
|
|
}
|
|
|
|
if ( GetGameLobby().lobbyBackend != NULL ) {
|
|
GetGameLobby().lobbyBackend->EndSession();
|
|
}
|
|
|
|
SetLobbiesAreJoinable( true );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::SetLobbiesAreJoinable
|
|
========================
|
|
*/
|
|
void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) {
|
|
// NOTE - We don't manipulate the joinable state when we are supporting join in progress
|
|
// Lobbies will naturally be non searchable when there are no free slots
|
|
if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.GetSessionMatchFlags() ) ) {
|
|
NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable );
|
|
GetPartyLobby().lobbyBackend->SetIsJoinable( joinable );
|
|
}
|
|
|
|
if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
|
|
GetGameLobby().lobbyBackend->SetIsJoinable( joinable );
|
|
NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable );
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::EndMatchForMigration
|
|
========================
|
|
*/
|
|
void idSessionLocal::EndMatchForMigration() {
|
|
ClearVoiceGroups();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ClearVoiceGroups
|
|
========================
|
|
*/
|
|
void idSessionLocal::ClearVoiceGroups() {
|
|
/*
|
|
for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) {
|
|
SetGameSessionUserChatGroup( i, 0 );
|
|
}
|
|
SetActiveChatGroup( 0 );
|
|
*/
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::GoodbyeFromHost
|
|
========================
|
|
*/
|
|
void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) {
|
|
if ( !verify( localState > STATE_IDLE ) ) {
|
|
idLib::Printf( "NET: Got disconnected from host %s on session %s when we were not in a lobby or game.\n", remoteAddress.ToString(), lobby.GetLobbyName() );
|
|
MoveToMainMenu();
|
|
return; // Ignore if we are not past the main menu
|
|
}
|
|
|
|
// Goodbye from host. See if we were connecting vs connected
|
|
if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) {
|
|
// We were denied a connection attempt
|
|
idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
|
|
// This will try to move to the next connection if one exists, otherwise will create a match
|
|
HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL );
|
|
} else {
|
|
// We were disconnected from a server we were previously connected to
|
|
idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
|
|
|
|
const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY );
|
|
|
|
if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 &&
|
|
lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) {
|
|
// If a host is telling us goodbye from a game lobby, and the game host is the same as our party host,
|
|
// and we aren't in a game, and the host wants us to leave with him, then do so now
|
|
GetGameLobby().Shutdown();
|
|
SetState( STATE_PARTY_LOBBY_PEER );
|
|
} else {
|
|
// Host left, so pick a new host (possibly even us) for this lobby
|
|
lobby.PickNewHost();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandlePackets
|
|
========================
|
|
*/
|
|
bool idSessionLocal::HandlePackets() {
|
|
byte packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
|
|
lobbyAddress_t remoteAddress;
|
|
int recvSize = 0;
|
|
|
|
while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, sizeof( packetBuffer ) ) && recvSize > 0 ) {
|
|
// fragMsg will hold the raw packet
|
|
idBitMsg fragMsg;
|
|
fragMsg.InitRead( packetBuffer, recvSize );
|
|
|
|
// Peek at the session ID
|
|
idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg );
|
|
|
|
// idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize );
|
|
|
|
// Make sure it's valid
|
|
if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
|
|
idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() );
|
|
continue;
|
|
}
|
|
|
|
// Distribute the packet to the proper lobby
|
|
if ( sessionID & 1 ) {
|
|
GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID );
|
|
} else {
|
|
GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" );
|
|
idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." );
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandleConnectAndMoveToLobby
|
|
Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game
|
|
========================
|
|
*/
|
|
bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) {
|
|
assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME );
|
|
assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
|
|
|
|
if ( lobby.GetState() == idLobby::STATE_FAILED ) {
|
|
// If we get here, we were trying to connect to a lobby (from state State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game)
|
|
HandleConnectionFailed( lobby, false );
|
|
return true;
|
|
}
|
|
|
|
if ( lobby.GetState() != idLobby::STATE_IDLE ) {
|
|
return HandlePackets(); // Valid but busy
|
|
}
|
|
|
|
assert( !GetPartyLobby().waitForPartyOk );
|
|
|
|
//
|
|
// Past this point, we've connected to the lobby
|
|
//
|
|
|
|
// If we are connecting to a game lobby, see if we need to keep waiting as either a host or peer while we're confirming all party members made it
|
|
if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
|
|
if ( GetPartyLobby().IsHost() ) {
|
|
// As a host, wait until all party members make it
|
|
assert( !GetGameLobby().waitForPartyOk );
|
|
|
|
const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000;
|
|
|
|
if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
|
|
// Took too long, move to next result, or create a game instead
|
|
HandleConnectionFailed( lobby, false );
|
|
return true;
|
|
}
|
|
|
|
int numUsersIn = 0;
|
|
|
|
for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) {
|
|
|
|
if ( net_testPartyMemberConnectFail.GetInteger() == i ) {
|
|
continue;
|
|
}
|
|
|
|
bool foundUser = false;
|
|
|
|
lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i );
|
|
|
|
for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) {
|
|
lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j );
|
|
|
|
if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) {
|
|
numUsersIn++;
|
|
foundUser = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser );
|
|
}
|
|
|
|
if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) {
|
|
return HandlePackets(); // All users not in, keep waiting until all user make it, or we time out
|
|
}
|
|
|
|
NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" );
|
|
|
|
// Let all the party members know everyone made it, and it's ok to stay at this server
|
|
for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) {
|
|
if ( GetPartyLobby().peers[ i ].IsConnected() ) {
|
|
GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK );
|
|
}
|
|
}
|
|
} else {
|
|
if ( !verify ( lobby.host != -1 ) ) {
|
|
MoveToMainMenu();
|
|
connectType = CONNECT_NONE;
|
|
return false;
|
|
}
|
|
|
|
// As a peer, wait for server to tell us everyone made it
|
|
if ( GetGameLobby().waitForPartyOk ) {
|
|
const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000;
|
|
|
|
if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
|
|
GetGameLobby().waitForPartyOk = false; // Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration
|
|
}
|
|
}
|
|
|
|
if ( GetGameLobby().waitForPartyOk ) {
|
|
return HandlePackets(); // Waiting on party host to tell us everyone made it
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success
|
|
SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_PARTY_LOBBY_PEER : STATE_GAME_LOBBY_PEER );
|
|
connectType = CONNECT_NONE;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::MoveToMainMenu
|
|
========================
|
|
*/
|
|
void idSessionLocal::MoveToMainMenu() {
|
|
GetPartyLobby().Shutdown();
|
|
GetGameLobby().Shutdown();
|
|
SetState( STATE_IDLE );
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandleConnectionFailed
|
|
Called anytime a connection fails, and does the right thing.
|
|
========================
|
|
*/
|
|
void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) {
|
|
assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME );
|
|
assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
|
|
bool canPlayOnline = true;
|
|
|
|
// Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us
|
|
if ( GetSignInManager().GetMasterLocalUser() != NULL ) {
|
|
canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline();
|
|
}
|
|
|
|
if ( connectType == CONNECT_FIND_OR_CREATE ) {
|
|
// Clear the "Lobby was Full" dialog in case it's up
|
|
// We only want to see this msg when doing a direct connect (CONNECT_DIRECT)
|
|
common->Dialog().ClearDialog( GDM_LOBBY_FULL );
|
|
|
|
assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME );
|
|
assert( lobby.lobbyType == idLobby::TYPE_GAME );
|
|
if ( !lobby.ConnectToNextSearchResult() ) {
|
|
CreateMatch( GetGameLobby().parms ); // Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match
|
|
}
|
|
} else if ( connectType == CONNECT_DIRECT ) {
|
|
if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) {
|
|
|
|
int flags = GetPartyLobby().parms.GetSessionMatchFlags();
|
|
|
|
if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
|
|
// We get here when our party host told us to connect to a game, but the game didn't exist.
|
|
// Just drop back to the party lobby and wait for further orders.
|
|
SetState( STATE_PARTY_LOBBY_PEER );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( wasFull ) {
|
|
common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false );
|
|
} else if ( !canPlayOnline ) {
|
|
common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false );
|
|
} else {
|
|
// TEMP HACK: We detect the steam lobby is full in idLobbyBackendWin, and then STATE_FAILED, which brings us here. Need to find a way to notify
|
|
// session local that the game was full so we don't do this check here
|
|
// eeubanks: Pollard, how do you think we should handle this?
|
|
if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) {
|
|
common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false );
|
|
}
|
|
}
|
|
MoveToMainMenu();
|
|
} else {
|
|
// Shouldn't be possible, but just in case
|
|
MoveToMainMenu();
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::SendRawPacket
|
|
========================
|
|
*/
|
|
void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) {
|
|
GetPort().SendRawPacket( to, data, size );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ReadRawPacket
|
|
========================
|
|
*/
|
|
bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ) {
|
|
return GetPort().ReadRawPacket( from, data, size, maxSize );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ConnectAndMoveToLobby
|
|
========================
|
|
*/
|
|
void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
|
|
|
|
// Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results
|
|
// If we don't do this, we might think we should attempt to connect to an old search result, and we don't want to in this case
|
|
lobby.searchResults.Clear();
|
|
|
|
// Attempt to connect to the lobby
|
|
lobby.ConnectTo( connectInfo, fromInvite );
|
|
|
|
connectType = CONNECT_DIRECT;
|
|
|
|
// Wait for connection
|
|
SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_CONNECT_AND_MOVE_TO_PARTY : STATE_CONNECT_AND_MOVE_TO_GAME );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::WriteLeaderboardToMsg
|
|
========================
|
|
*/
|
|
void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
|
|
assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard );
|
|
|
|
msg.WriteLong( leaderboard->id );
|
|
|
|
for ( int i = 0; i < leaderboard->numColumns; ++i ) {
|
|
uint64 value = stats[i].value;
|
|
|
|
//idLib::Printf( "value = %i\n", (int32)value );
|
|
|
|
for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
|
|
msg.WriteBits( value & 1, 1 );
|
|
value >>= 1;
|
|
}
|
|
//msg.WriteData( &stats[i].value, sizeof( stats[i].value ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ReadLeaderboardFromMsg
|
|
========================
|
|
*/
|
|
const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) {
|
|
int id = msg.ReadLong();
|
|
|
|
const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id );
|
|
|
|
if ( leaderboard == NULL ) {
|
|
idLib::Printf( "NET: Invalid leaderboard id: %i\n", id );
|
|
return NULL;
|
|
}
|
|
|
|
for ( int i = 0; i < leaderboard->numColumns; ++i ) {
|
|
uint64 value = 0;
|
|
|
|
for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
|
|
value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j;
|
|
}
|
|
|
|
stats[i].value = value;
|
|
|
|
//idLib::Printf( "value = %i\n", (int32)value );
|
|
//msg.ReadData( &stats[i].value, sizeof( stats[i].value ) );
|
|
}
|
|
|
|
return leaderboard;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::EndMatchInternal
|
|
========================
|
|
*/
|
|
void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) {
|
|
ClearVoiceGroups();
|
|
|
|
|
|
for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) {
|
|
// If we are the host, increment the session ID. The client will use a rolling check to catch it
|
|
if ( GetGameLobby().IsHost() ) {
|
|
if ( GetGameLobby().peers[p].IsConnected() ) {
|
|
if ( GetGameLobby().peers[p].packetProc != NULL ) {
|
|
GetGameLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG );
|
|
}
|
|
GetGameLobby().peers[p].sessionID = GetGameLobby().IncrementSessionID( GetGameLobby().peers[p].sessionID );
|
|
}
|
|
}
|
|
GetGameLobby().peers[p].ResetMatchData();
|
|
}
|
|
|
|
|
|
GetGameLobby().loaded = false;
|
|
//gameLobbyWasCoalesced = false; // Reset this back to false. We use this so the lobby code doesn't randomly choose a map when we coalesce
|
|
|
|
ClearMigrationState();
|
|
|
|
if ( common->IsMultiplayer() ) {
|
|
if ( GetGameLobby().IsSessionActive() ) {
|
|
// All peers need to remove disconnected users to stay in sync
|
|
GetGameLobby().CompactDisconnectedUsers();
|
|
|
|
// Go back to the game lobby
|
|
if ( GetGameLobby().IsHost() ) {
|
|
SetState( STATE_GAME_LOBBY_HOST );
|
|
} else {
|
|
SetState( STATE_GAME_LOBBY_PEER );
|
|
}
|
|
} else {
|
|
// Oops, no game lobby?
|
|
assert( false ); // how is this possible?
|
|
MoveToMainMenu();
|
|
}
|
|
} else {
|
|
SetState( STATE_IDLE );
|
|
}
|
|
|
|
if ( GetGameLobby().IsHost() ) {
|
|
// Send a reliable msg to all peers to also "EndMatch"
|
|
for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) {
|
|
GetGameLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH );
|
|
}
|
|
} else if( premature ) {
|
|
// Notify client that host left early and thats why we are back in the lobby
|
|
bool stats = MatchTypeHasStats( GetGameLobby().GetMatchParms().GetSessionMatchFlags() ) && ( GetFlushedStats() == false );
|
|
common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::HandleOobVoiceAudio
|
|
========================
|
|
*/
|
|
void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) {
|
|
|
|
idLobby * activeLobby = GetActiveLobby();
|
|
|
|
if ( activeLobby == NULL ) {
|
|
return;
|
|
}
|
|
|
|
voiceChat->SetActiveLobby( activeLobby->lobbyType );
|
|
|
|
voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetSize() - msg.GetReadCount() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::ClearMigrationState
|
|
========================
|
|
*/
|
|
void idSessionLocal::ClearMigrationState() {
|
|
// We are ending the match without migration, so clear that state
|
|
GetPartyLobby().ResetAllMigrationState();
|
|
GetGameLobby().ResetAllMigrationState();
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::SendLeaderboardStatsToPlayer
|
|
========================
|
|
*/
|
|
void idSessionLocal::SendLeaderboardStatsToPlayer( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
|
|
|
|
if ( GetGameLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) {
|
|
idLib::Warning( "Tried to tell disconnected user to report stats" );
|
|
return;
|
|
}
|
|
|
|
const int peerIndex = GetGameLobby().PeerIndexFromLobbyUserIndex( sessionUserIndex );
|
|
|
|
if ( peerIndex == -1 ) {
|
|
idLib::Warning( "Tried to tell invalid peer index to report stats" );
|
|
return;
|
|
}
|
|
|
|
if ( !verify( GetGameLobby().IsHost() ) ||
|
|
!verify( peerIndex < GetGameLobby().peers.Num() ) ||
|
|
!verify( GetGameLobby().peers[ peerIndex ].IsConnected() ) ) {
|
|
idLib::Warning( "Tried to tell invalid peer to report stats" );
|
|
return;
|
|
}
|
|
|
|
NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex );
|
|
|
|
lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( sessionUserIndex );
|
|
|
|
if ( !verify( gameUser != NULL ) ) {
|
|
return;
|
|
}
|
|
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
|
|
idBitMsg msg;
|
|
msg.InitWrite( buffer, sizeof( buffer ) );
|
|
|
|
// Use the user ID
|
|
msg.WriteLong( gameUser->userID );
|
|
|
|
WriteLeaderboardToMsg( msg, leaderboard, stats );
|
|
|
|
GetGameLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetWriteData(), msg.GetSize() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::RecvLeaderboardStatsForPlayer
|
|
========================
|
|
*/
|
|
void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) {
|
|
column_t stats[ MAX_LEADERBOARD_COLUMNS ];
|
|
|
|
int userID = msg.ReadLong();
|
|
|
|
int sessionUserIndex = GetGameLobby().FindSessionUserByUserId( userID );
|
|
|
|
const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats );
|
|
|
|
if ( leaderboard == NULL ) {
|
|
idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" );
|
|
return;
|
|
}
|
|
|
|
LeaderboardUpload( sessionUserIndex, leaderboard, stats );
|
|
} |