summaryrefslogtreecommitdiff
path: root/game/shared/playergroupmanager.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/playergroupmanager.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/playergroupmanager.cpp')
-rw-r--r--game/shared/playergroupmanager.cpp849
1 files changed, 849 insertions, 0 deletions
diff --git a/game/shared/playergroupmanager.cpp b/game/shared/playergroupmanager.cpp
new file mode 100644
index 0000000..69d88bc
--- /dev/null
+++ b/game/shared/playergroupmanager.cpp
@@ -0,0 +1,849 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+
+#include "cbase.h"
+#include "gcsdk/gcsdk.h"
+#include "base_gcmessages.h"
+#include "playergroupmanager.h"
+#include "gcsdk/accountdetails.h"
+
+using namespace GCSDK;
+
+CPlayerGroupManager::mapPlayersMemcacheJobCount_t CPlayerGroupManager::sm_mapPlayersMemcacheJobCount;
+
+CPlayerGroupManager::CPlayerGroupManager() :
+m_hashPlayerGroupIDLocks( k_nLocksRunInterval / k_cMicroSecPerShellFrame )
+{
+ m_hashPlayerGroupIDLocks.Init( k_cGCLocksInit, k_cBucketGCLocks );
+ m_GroupIDGenerationLock.SetName( "GroupIDGenerationLock" );
+ m_GroupIDGenerationLock.SetLockType( kGroupIDGenerationLockType );
+}
+
+PlayerGroupID_t CPlayerGroupManager::GeneratePlayerGroupID()
+{
+ return GGCHost()->GenerateGID();
+}
+
+void CPlayerGroupManager::MemcachedUpdateAllMemberAssocation( IPlayerGroup *pPlayerGroup )
+{
+ CUtlVector<CUtlString> memcachedMemberKeys;
+ CUtlVector<CGCBase::GCMemcachedBuffer_t> memcachedMemberValues;
+
+ CUtlBuffer memcachedMemberValue;
+ memcachedMemberValue.PutInt64( (int64) pPlayerGroup->GetGroupID() );
+
+ for ( int i = 0; i < pPlayerGroup->GetNumMembers(); ++i )
+ {
+ CUtlString &memberKey = memcachedMemberKeys[ memcachedMemberKeys.AddToTail() ];
+ memberKey.Format( "GC:%u:%sMEMBER:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), pPlayerGroup->GetMember(i).ConvertToUint64() );
+
+ CGCBase::GCMemcachedBuffer_t &memcachedBuffer = memcachedMemberValues[ memcachedMemberValues.AddToTail() ];
+ memcachedBuffer.m_pubData = memcachedMemberValue.Base();
+ memcachedBuffer.m_cubData = memcachedMemberValue.TellPut();
+ }
+
+ GGCBase()->BMemcachedSet( memcachedMemberKeys, memcachedMemberValues );
+}
+
+void CPlayerGroupManager::MemcachedRemoveAllMemberAssocation( IPlayerGroup *pPlayerGroup )
+{
+ CUtlVector<CUtlString> memcachedMemberKeys;
+
+ for ( int i = 0; i < pPlayerGroup->GetNumMembers(); ++i )
+ {
+ CUtlString &memberKey = memcachedMemberKeys[ memcachedMemberKeys.AddToTail() ];
+ memberKey.Format( "GC:%u:%sMEMBER:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), pPlayerGroup->GetMember(i).ConvertToUint64() );
+ }
+
+ GGCBase()->BMemcachedDelete( memcachedMemberKeys );
+}
+
+void CPlayerGroupManager::MemcachedUpdateMemberAssocation( PlayerGroupID_t nPlayerGroupID, const CSteamID &steamID )
+{
+ CUtlBuffer memcachedMemberValue;
+ memcachedMemberValue.PutInt64( (int64) nPlayerGroupID );
+ CUtlString memberKey;
+ memberKey.Format( "GC:%u:%sMEMBER:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), steamID.ConvertToUint64() );
+
+ GGCBase()->BMemcachedSet( memberKey, memcachedMemberValue );
+}
+
+void CPlayerGroupManager::MemcachedRemoveMemberAssocation( PlayerGroupID_t nPlayerGroupID, const CSteamID &steamID )
+{
+ CUtlString memberKey;
+ memberKey.Format( "GC:%u:%sMEMBER:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), steamID.ConvertToUint64() );
+ GGCBase()->BMemcachedDelete( memberKey );
+}
+
+bool CPlayerGroupManager::BYldAddMemberToGroup( PlayerGroupID_t nPlayerGroupID, const CSteamID &steamIDNewMember )
+{
+ // If member is already in a group, abort
+ IPlayerGroup *pOldPlayerGroup = FindGroupByMemberID( steamIDNewMember );
+ if ( pOldPlayerGroup )
+ {
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYldAddMemberToGroup not adding member: %s to player group: %016llx as he's already in a group\n", steamIDNewMember.Render(), nPlayerGroupID );
+ return false;
+ }
+
+ // check group exists
+ IPlayerGroup *pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ Assert( pPlayerGroup );
+ if ( !pPlayerGroup )
+ {
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYldAddMemberToGroup not adding member: %s to player group: %016llx as that group does not exist\n", steamIDNewMember.Render(), nPlayerGroupID );
+ return false;
+ }
+
+ // check member isn't already in the group
+ if ( pPlayerGroup->GetMemberIndexBySteamID( steamIDNewMember ) != -1 )
+ {
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYldAddMemberToGroup not adding member: %s to player group: %016llx as he's already in the group\n", steamIDNewMember.Render(), nPlayerGroupID );
+ UnlockGroupID( nPlayerGroupID );
+ return false;
+ }
+
+ // Lock the user's cache so we can add the group to it
+ CGCSharedObjectCache *pCache = GGCBase()->YieldingGetLockedSOCache( steamIDNewMember );
+ if ( !pCache )
+ {
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYldAddMemberToGroup not adding member: %s to player group: %016llx as his cache could not be loaded\n", steamIDNewMember.Render(), nPlayerGroupID );
+ GGCBase()->UnlockSteamID( steamIDNewMember );
+ UnlockGroupID( nPlayerGroupID );
+ return false;
+ }
+
+ // If member is already in a group, abort (must do this again after yielding as another job could have put him in a group while we yielded)
+ pOldPlayerGroup = FindGroupByMemberID( steamIDNewMember );
+ if ( pOldPlayerGroup )
+ {
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYldAddMemberToGroup not adding member: %s to player group: %016llx as he's already in a group (2)\n", steamIDNewMember.Render(), nPlayerGroupID );
+ GGCBase()->UnlockSteamID( steamIDNewMember );
+ UnlockGroupID( nPlayerGroupID );
+ return false;
+ }
+
+ // Add shared object to this member's cache, before dirtying fields
+ pCache->AddObject( pPlayerGroup->GetSharedObject() );
+
+ // Add member to group. This will dirty shared object fields.
+ pPlayerGroup->AddMember( steamIDNewMember );
+ m_mapMemberToGroup.InsertOrReplace( steamIDNewMember, pPlayerGroup );
+ GGCBase()->UnlockSteamID( steamIDNewMember );
+
+ // Update memcached
+ MemcachedUpdateMemberAssocation( nPlayerGroupID, steamIDNewMember );
+
+ // notify derived classes
+ YldOnPlayerJoinedGroup( pPlayerGroup, steamIDNewMember );
+
+ UnlockGroupID( nPlayerGroupID );
+
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Steam id: %s added to player group: %016llx\n", steamIDNewMember.Render(), nPlayerGroupID );
+
+ return true;
+}
+
+void CPlayerGroupManager::YldRemoveMemberFromGroup( PlayerGroupID_t nPlayerGroupID, const CSteamID &steamIDRemovingMember )
+{
+ // check group exists
+ IPlayerGroup *pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ Assert( pPlayerGroup );
+ if ( !pPlayerGroup )
+ return;
+
+ // check member is in the group
+ if ( pPlayerGroup->GetMemberIndexBySteamID( steamIDRemovingMember ) == -1 )
+ {
+ UnlockGroupID( nPlayerGroupID );
+ return;
+ }
+
+ pPlayerGroup->RemoveMember( steamIDRemovingMember );
+ m_mapMemberToGroup.Remove( steamIDRemovingMember );
+
+ // Update memcached
+ MemcachedRemoveMemberAssocation( nPlayerGroupID, steamIDRemovingMember );
+
+ CGCSharedObjectCache *pCache = GGCBase()->YieldingGetLockedSOCache( steamIDRemovingMember );
+ if ( pCache )
+ {
+ pCache->RemoveObject( *pPlayerGroup->GetSharedObject() );
+ pCache->SendAllNetworkUpdates();
+ }
+ GGCBase()->UnlockSteamID( steamIDRemovingMember );
+
+ YldOnPlayerLeftGroup( pPlayerGroup, steamIDRemovingMember );
+
+ UnlockGroupID( nPlayerGroupID );
+
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Steam id: %s removed from player group: %016llx\n", steamIDRemovingMember.Render(), nPlayerGroupID );
+}
+
+IPlayerGroup* CPlayerGroupManager::FindGroup( PlayerGroupID_t nPlayerGroupID )
+{
+ hashPlayerGroups_t::IndexType_t nPlayerGroupIndex = m_mapGroups.Find( nPlayerGroupID );
+ if ( nPlayerGroupIndex == m_mapGroups.InvalidIndex() )
+ return NULL;
+
+ return m_mapGroups.Element( nPlayerGroupIndex );
+}
+
+IPlayerGroup* CPlayerGroupManager::YldFindAndLockGroup( PlayerGroupID_t nPlayerGroupID )
+{
+ hashPlayerGroups_t::IndexType_t nPlayerGroupIndex = m_mapGroups.Find( nPlayerGroupID );
+ if ( nPlayerGroupIndex == m_mapGroups.InvalidIndex() )
+ return NULL;
+
+ // try to lock it
+ if ( !BYieldingLockGroupID( nPlayerGroupID ) )
+ return NULL;
+
+ // check the group is still valid
+ nPlayerGroupIndex = m_mapGroups.Find( nPlayerGroupID );
+ if ( nPlayerGroupIndex == m_mapGroups.InvalidIndex() )
+ {
+ // group went away, unlock and abort
+ UnlockGroupID( nPlayerGroupID );
+ return NULL;
+ }
+
+ return m_mapGroups.Element( nPlayerGroupIndex );
+}
+
+IPlayerGroup* CPlayerGroupManager::FindGroupByMemberID( const CSteamID &steamID )
+{
+ mapMembersToPlayerGroups_t::IndexType_t nPlayerGroupIndex = m_mapMemberToGroup.Find( steamID );
+ if ( nPlayerGroupIndex == m_mapMemberToGroup.InvalidIndex() )
+ return NULL;
+
+ return m_mapMemberToGroup.Element( nPlayerGroupIndex );
+}
+
+IPlayerGroup* CPlayerGroupManager::YldFindAndLockGroupByMemberID( const CSteamID &steamID )
+{
+ mapMembersToPlayerGroups_t::IndexType_t nPlayerGroupIndex = m_mapMemberToGroup.Find( steamID );
+ if ( nPlayerGroupIndex == m_mapMemberToGroup.InvalidIndex() )
+ return NULL;
+
+ IPlayerGroup *pPlayerGroup = m_mapMemberToGroup.Element( nPlayerGroupIndex );
+
+ return YldFindAndLockGroup( pPlayerGroup->GetGroupID() );
+}
+
+bool CPlayerGroupManager::BYieldingLockGroupID( PlayerGroupID_t nPlayerGroupID )
+{
+ AssertRunningJob();
+
+ // lookup
+ CLock *pLock = m_hashPlayerGroupIDLocks.PvRecordFind( nPlayerGroupID );
+ if ( pLock == NULL )
+ {
+ // no lock yet, insert one
+ pLock = m_hashPlayerGroupIDLocks.PvRecordInsert( nPlayerGroupID );
+ Assert( pLock != NULL );
+ if ( pLock == NULL )
+ {
+ EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Unable to create lock for PlayerGroup:%u\n", nPlayerGroupID );
+ return false;
+ }
+ pLock->SetName( "PlayerGroup:", nPlayerGroupID );
+ pLock->SetLockType( GetGroupLockType() );
+ }
+
+ return GJobCur().BYieldingAcquireLock( pLock );
+}
+
+void CPlayerGroupManager::UnlockGroupID( PlayerGroupID_t nPlayerGroupID )
+{
+ AssertRunningJob();
+
+ // lookup
+ CLock *pLock = m_hashPlayerGroupIDLocks.PvRecordFind( nPlayerGroupID );
+ Assert( pLock );
+ if ( pLock == NULL )
+ {
+ return;
+ }
+
+ if ( pLock->GetJobLocking() != &GJobCur() )
+ {
+ AssertMsg1( false, "UnlockGroupID() called when job (%s) doesn't own the lock", GJobCur().GetName() );
+ return;
+ }
+
+ GJobCur().ReleaseLock( pLock );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns true if the specified PlayerGroupID_t is locked
+//-----------------------------------------------------------------------------
+bool CPlayerGroupManager::IsGroupIDLocked( PlayerGroupID_t nPlayerGroupID )
+{
+ CLock *pLock = m_hashPlayerGroupIDLocks.PvRecordFind( nPlayerGroupID );
+ if ( pLock )
+ return pLock->BIsLocked();
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks the start of a frame
+//-----------------------------------------------------------------------------
+void CPlayerGroupManager::StartFrameSchedule()
+{
+ m_hashPlayerGroupIDLocks.StartFrameSchedule( true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets rid of any locks that haven't been touched in a while
+//-----------------------------------------------------------------------------
+bool CPlayerGroupManager::BExpireLocks( CLimitTimer &limitTimer )
+{
+ VPROF_BUDGET( "Expire PlayerGroup Locks", VPROF_BUDGETGROUP_STEAM );
+
+ for ( CLock *pLock = m_hashPlayerGroupIDLocks.PvRecordRun(); NULL != pLock; pLock = m_hashPlayerGroupIDLocks.PvRecordRun() )
+ {
+ if ( !pLock->BIsLocked() && pLock->GetMicroSecondsSinceLock() > k_cMicroSecLockLifetime )
+ {
+ m_hashPlayerGroupIDLocks.Remove( pLock );
+ }
+
+ if ( limitTimer.BLimitReached() )
+ return true;
+ }
+
+ return false;
+}
+
+
+void CPlayerGroupManager::YldDestroyGroupIfEmpty( PlayerGroupID_t nPlayerGroupID )
+{
+ IPlayerGroup *pGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( pGroup )
+ {
+ if ( pGroup->GetNumMembers() <= 0 )
+ {
+ YldDestroyGroup( nPlayerGroupID );
+ }
+ }
+}
+
+void CPlayerGroupManager::YldDestroyGroup( PlayerGroupID_t nPlayerGroupID )
+{
+ IPlayerGroup* pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( !pPlayerGroup )
+ return;
+
+ // allow derived classes to clean up
+ YldOnGroupDestroyed( pPlayerGroup );
+
+ // remove all members
+ while( pPlayerGroup->GetNumMembers() > 0 )
+ {
+ YldRemoveMemberFromGroup( nPlayerGroupID, pPlayerGroup->GetMember( 0 ) );
+ }
+
+ m_mapGroups.Remove( nPlayerGroupID );
+
+ // Remove the group from memcached
+ GGCBase()->BMemcachedDelete( CFmtStr( "GC:%u:%s:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), nPlayerGroupID ).Access() );
+
+ delete pPlayerGroup;
+}
+
+// Update memcached and players when the group changes
+void CPlayerGroupManager::SendGroupStorageAndNetworkUpdate( IPlayerGroup *pPlayerGroup )
+{
+ for ( int i = 0; i < pPlayerGroup->GetNumMembers(); i++ )
+ {
+ CGCUserSession *pMemberSession = GGCBase()->FindUserSession( pPlayerGroup->GetMember( i ) );
+ if ( !pMemberSession )
+ continue;
+
+ pMemberSession->GetSOCache()->SendAllNetworkUpdates();
+ }
+
+ // Update memcached
+ VPROF_BUDGET( "SendGroupStorageAndNetworkUpdate - Memcached", VPROF_BUDGETGROUP_STEAM );
+
+ CUtlString memcachedGroupKey;
+ memcachedGroupKey.Format( "GC:%u:%s:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), pPlayerGroup->GetGroupID() );
+
+ CUtlBuffer memcachedGroupValue;
+ CSharedObject *pObj = pPlayerGroup->GetSharedObject();
+ Assert( pObj );
+ if ( !pObj )
+ {
+ return;
+ }
+ pObj->BAddFullCreateToMessage( memcachedGroupValue );
+
+ GGCBase()->BMemcachedSet( memcachedGroupKey, memcachedGroupValue );
+}
+
+void CPlayerGroupManager::DumpGroups()
+{
+ // Sort groups so we can dump in-order
+ CUtlSortVector<PlayerGroupID_t> vecIDs( 0, m_mapGroups.Count() );
+ FOR_EACH_MAP_FAST( m_mapGroups, i )
+ {
+ vecIDs.InsertNoSort( m_mapGroups.Key( i ) );
+ }
+ vecIDs.RedoSort( true );
+
+ FOR_EACH_VEC( vecIDs, i )
+ {
+ IPlayerGroup *pPlayerGroup = m_mapGroups[m_mapGroups.Find( vecIDs[i] )];
+ if ( !pPlayerGroup || !pPlayerGroup->GetSharedObject() )
+ continue;
+
+ EmitInfo( SPEW_SHAREDOBJ, SPEW_ALWAYS, LOG_ALWAYS, "Group: %llu NumMembers: %d\n", pPlayerGroup->GetGroupID(), pPlayerGroup->GetNumMembers() );
+ pPlayerGroup->GetSharedObject()->Dump();
+ }
+}
+
+void CPlayerGroupManager::YieldingSessionStartPlaying( CGCUserSession *pSession )
+{
+ mapMembersToPlayerGroups_t::IndexType_t nPlayerGroupIndex = m_mapMemberToGroup.Find( pSession->GetSteamID() );
+ if ( nPlayerGroupIndex == m_mapMemberToGroup.InvalidIndex() )
+ {
+ mapPlayersMemcacheJobCount_t::IndexType_t mIndex = sm_mapPlayersMemcacheJobCount.Find( pSession->GetSteamID() );
+ if ( mIndex == sm_mapPlayersMemcacheJobCount.InvalidIndex() )
+ {
+ sm_mapPlayersMemcacheJobCount.Insert( pSession->GetSteamID(), 1 );
+ }
+ else
+ {
+ sm_mapPlayersMemcacheJobCount.Element( mIndex )++;
+ }
+
+ // could not find a group in memory for this user, see if we can find it in memcached
+ CGCJobFindGroupFromMemcached *pJob = new CGCJobFindGroupFromMemcached( GGCBase(), this, pSession->GetSteamID() );
+ pJob->StartJob( NULL );
+ }
+}
+
+void CPlayerGroupManager::YieldingSessionStartServer( CGCGSSession *pSession )
+{
+}
+
+void CPlayerGroupManager::YieldingSessionStopServer( CGCGSSession *pSession )
+{
+}
+
+void CPlayerGroupManager::YldDoFindGroupFromMemcached( const CSteamID &memberSteamID )
+{
+ // See if we can find him in memcached
+ CUtlVector<CUtlString> memcachedMemberKeys;
+ CUtlVector<CGCBase::GCMemcachedGetResult_t> memcachedMemberResults;
+ CUtlString &memberKey = memcachedMemberKeys[ memcachedMemberKeys.AddToTail() ];
+ memberKey.Format( "GC:%u:%sMEMBER:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), memberSteamID.ConvertToUint64() );
+
+ if ( !GGCBase()->BYieldingMemcachedGet( memcachedMemberKeys, memcachedMemberResults ) )
+ {
+ return; // Not found
+ }
+
+ if ( memcachedMemberResults.Count() != 1 || !memcachedMemberResults[0].m_bKeyFound )
+ {
+ return; // Not found
+ }
+ CUtlBuffer memcachedGroupResult;
+ memcachedGroupResult.SetExternalBuffer( memcachedMemberResults[0].m_bufValue.Base(), memcachedMemberResults[0].m_bufValue.Count(), memcachedMemberResults[0].m_bufValue.Count() );
+ PlayerGroupID_t nPlayerGroupID = (uint64) memcachedGroupResult.GetInt64();
+
+ if ( !BYieldingLockGroupID( nPlayerGroupID ) )
+ {
+ EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "YldFindGroupFromMemcached: Unable to create lock for PlayerGroup:%u\n", nPlayerGroupID );
+ return;
+ }
+
+ // check no-one else recreated this group while we were waiting for the lock
+ if ( FindGroup( nPlayerGroupID ) )
+ return;
+
+ // We have a group id, try read the group from memcached
+ CUtlVector<CUtlString> memcachedGroupKeys;
+ CUtlVector<CGCBase::GCMemcachedGetResult_t> memcachedGroupResults;
+ CUtlString &memcachedGroupKey = memcachedGroupKeys[ memcachedGroupKeys.AddToTail() ];
+ memcachedGroupKey.Format( "GC:%u:%s:%llu", GGCBase()->GetAppID(), GetMemcachedIdentityKey(), nPlayerGroupID );
+
+ if ( !GGCBase()->BYieldingMemcachedGet( memcachedGroupKeys, memcachedGroupResults ) )
+ {
+ return; // Not found
+ }
+
+ if ( memcachedGroupResults.Count() != 1 || !memcachedGroupResults[0].m_bKeyFound )
+ {
+ return; // Not found
+ }
+
+
+ CUtlBuffer memcachedPartyResult;
+ memcachedPartyResult.SetExternalBuffer( memcachedGroupResults[0].m_bufValue.Base(), memcachedGroupResults[0].m_bufValue.Count(), memcachedGroupResults[0].m_bufValue.Count() );
+ IPlayerGroup *pPlayerGroup = YldCreateAndLockPlayerGroupFromMemcached( memcachedPartyResult );
+
+ // pPlayerGroup won't be NULL because creating it won't fail. Also, the group id is already locked, so only one player
+ // will get to this point. So no checks needed before inserting this group id into the map
+ m_mapGroups.Insert( nPlayerGroupID, pPlayerGroup );
+
+ // Group loaded, update all objects
+
+ // Add members to the map
+ for ( int i = 0; i < pPlayerGroup->GetNumMembers(); ++i )
+ {
+ // It's ok to add the Nth steam id even with the yield in this loop,
+ // because the group is locked. Any player that stops playing will be
+ // blocked on this group id.
+ CSteamID memberSteamID = pPlayerGroup->GetMember(i);
+ m_mapMemberToGroup.InsertOrReplace( memberSteamID, pPlayerGroup );
+
+ CGCSharedObjectCache *pCache = GGCBase()->YieldingGetLockedSOCache( memberSteamID );
+ if ( pCache )
+ {
+ pCache->AddObject( pPlayerGroup->GetSharedObject() );
+ }
+ GGCBase()->UnlockSteamID( memberSteamID );
+ }
+
+ YldOnGroupLoadedFromMemcached( pPlayerGroup );
+
+ for ( int i = 0; i < pPlayerGroup->GetNumMembers(); i++ )
+ {
+ CGCUserSession *pMemberSession = GGCBase()->FindUserSession( pPlayerGroup->GetMember( i ) );
+ if ( !pMemberSession )
+ continue;
+
+ pMemberSession->GetSOCache()->SendAllNetworkUpdates();
+ }
+}
+
+void CPlayerGroupManager::YldFindGroupFromMemcached( const CSteamID &memberSteamID )
+{
+ YldDoFindGroupFromMemcached( memberSteamID );
+
+ mapPlayersMemcacheJobCount_t::IndexType_t mIndex = sm_mapPlayersMemcacheJobCount.Find( memberSteamID );
+ if ( mIndex != sm_mapPlayersMemcacheJobCount.InvalidIndex() )
+ {
+ if ( --sm_mapPlayersMemcacheJobCount.Element( mIndex ) <= 0 )
+ {
+ sm_mapPlayersMemcacheJobCount.RemoveAt( mIndex );
+ }
+ }
+}
+
+bool CGCJobDestroyGroup::BYieldingRunGCJob()
+{
+ if ( m_pGroupManager )
+ {
+ m_pGroupManager->YldDestroyGroup( m_nPlayerGroupID );
+ }
+ return true;
+}
+
+bool CGCJobLeaveGroup::BYieldingRunGCJob()
+{
+ if ( m_pGroupManager )
+ {
+ m_pGroupManager->BYldRequestLeaveGroup( m_SteamID );
+ }
+ return true;
+}
+
+bool CGCJobFindGroupFromMemcached::BYieldingRunGCJob()
+{
+ if ( m_pGroupManager )
+ {
+ m_pGroupManager->YldFindGroupFromMemcached( m_memberSteamID );
+ }
+ return true;
+}
+
+
+// === Invites ===
+
+
+void CPlayerGroupManager::YldInviteToGroup( const CSteamID &steamIDLeader, const CSteamID &steamIDNewMember )
+{
+ if ( !steamIDLeader.IsValid() || ! steamIDLeader.BIndividualAccount()
+ || !steamIDNewMember.IsValid() || ! steamIDNewMember.BIndividualAccount() )
+ return;
+
+ // create group if this leader isn't in one already
+ IPlayerGroup *pPlayerGroup = YldFindAndLockGroupByMemberID( steamIDLeader );
+ if ( !pPlayerGroup )
+ {
+ pPlayerGroup = YldCreateAndLockPlayerGroup();
+ m_mapGroups.Insert( pPlayerGroup->GetGroupID(), pPlayerGroup );
+ if ( !BYldAddMemberToGroup( pPlayerGroup->GetGroupID(), steamIDLeader ) )
+ return;
+ pPlayerGroup->SetLeader( steamIDLeader );
+ }
+ else if ( pPlayerGroup && pPlayerGroup->GetLeader() != steamIDLeader )
+ {
+ // non-leader attempting to invite
+ return;
+ }
+
+ // don't send any invite if our group is already full
+ if ( GetMaxGroupMembers() != k_NoGroupMemberLimit && pPlayerGroup->GetNumMembers() >= GetMaxGroupMembers() )
+ {
+ CProtoBufMsg<CMsgGCError> msg( k_EMsgGCError );
+ msg.Body().set_error_text( "Your party is full." );
+ GGCBase()->BSendGCMsgToClient( steamIDLeader, msg );
+ return;
+ }
+
+ if ( pPlayerGroup->GetPendingInviteIndexBySteamID( steamIDNewMember ) == -1 )
+ {
+ pPlayerGroup->AddPendingInvite( steamIDNewMember );
+ }
+
+ if ( !YldHasGroupInvite( steamIDNewMember, pPlayerGroup->GetGroupID() ) )
+ {
+ CGCSharedObjectCache *pCache = GGCBase()->YieldingGetLockedSOCache( steamIDNewMember );
+ if ( pCache )
+ {
+ // Send invite to the new member
+ IPlayerGroupInvite *pInvite = CreateInvite();
+ pInvite->YldInitFromPlayerGroup( pPlayerGroup );
+
+ pCache->AddObject( pInvite->GetSharedObject() );
+ }
+ GGCBase()->UnlockSteamID( steamIDNewMember );
+ }
+
+ // Send a message back to the sender telling him his invitation was created
+ GCSDK::CProtoBufMsg<CMsgInvitationCreated> msg( k_EMsgGCInvitationCreated );
+ msg.Body().set_group_id( pPlayerGroup->GetGroupID() );
+ msg.Body().set_steam_id( steamIDNewMember.ConvertToUint64() );
+ GGCBase()->BSendGCMsgToClient( steamIDLeader, msg );
+
+ SendGroupStorageAndNetworkUpdate( pPlayerGroup );
+}
+
+void CPlayerGroupManager::YldRemovePendingInvite( PlayerGroupID_t nPlayerGroupID, const CSteamID &steamIDNewMember )
+{
+ IPlayerGroup *pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( pPlayerGroup != NULL )
+ {
+ pPlayerGroup->RemovePendingInvite( steamIDNewMember );
+ UnlockGroupID( nPlayerGroupID );
+ }
+
+ // Derived classes will remove the CPartyInvite/CLobbyInvite objects from the user's SO cache
+}
+
+void CPlayerGroupManager::YldGroupInviteResponse( const CSteamID &steamID, const PlayerGroupID_t nPlayerGroupID, bool bAccepted )
+{
+ if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
+ return;
+
+ // check if user is already in a group
+ if ( bAccepted )
+ {
+ IPlayerGroup* pOtherGroup = YldFindAndLockGroupByMemberID( steamID );
+ if ( pOtherGroup )
+ {
+ // remove him from the other group
+ PlayerGroupID_t nOtherPlayerGroupID = pOtherGroup->GetGroupID();
+ YldRemoveMemberFromGroup( nOtherPlayerGroupID, steamID );
+ YldDestroyGroupIfEmpty( nOtherPlayerGroupID );
+ UnlockGroupID( nOtherPlayerGroupID );
+
+ // If the group wasn't deleted, update others about this person leaving
+ pOtherGroup = FindGroup( nOtherPlayerGroupID );
+ if ( pOtherGroup )
+ {
+ SendGroupStorageAndNetworkUpdate( pOtherGroup );
+ }
+ GJobCur().ReleaseLocks();
+ }
+ GJobCur().ReleaseLocks();
+ }
+
+ IPlayerGroup* pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( !pPlayerGroup )
+ {
+ CProtoBufMsg<CMsgGCError> msg( k_EMsgGCError );
+ msg.Body().set_error_text( CFmtStr( "Couldn't respond to party invite, couldn't find a party with ID %llu.", nPlayerGroupID ) );
+ GGCBase()->BSendGCMsgToClient( steamID, msg );
+ return;
+ }
+
+ // check an invite is pending for this user
+ if ( pPlayerGroup->GetPendingInviteIndexBySteamID( steamID ) == -1 )
+ {
+ CProtoBufMsg<CMsgGCError> msg( k_EMsgGCError );
+ msg.Body().set_error_text( CFmtStr( "Couldn't respond to party invite, that party (%llu) doesn't have an invite pending for you.", nPlayerGroupID ) );
+ GGCBase()->BSendGCMsgToClient( steamID, msg );
+ return;
+ }
+
+ YldRemovePendingInvite( nPlayerGroupID, steamID );
+
+ if ( bAccepted )
+ {
+ // don't exceed the max size for this group
+ if ( GetMaxGroupMembers() != k_NoGroupMemberLimit && pPlayerGroup->GetNumMembers() >= GetMaxGroupMembers() )
+ {
+ CProtoBufMsg<CMsgGCError> msg( k_EMsgGCError );
+ msg.Body().set_error_text( "Cannot join party. Party is full." );
+ GGCBase()->BSendGCMsgToClient( steamID, msg );
+ return;
+ }
+
+ BYldAddMemberToGroup( nPlayerGroupID, steamID );
+ }
+ else
+ {
+ // TODO: Praps notify the group that this person rejected the invite?
+ }
+
+ SendGroupStorageAndNetworkUpdate( pPlayerGroup );
+}
+
+void CPlayerGroupManager::YldRequestKickFromGroup( const CSteamID &steamIDLeader, const CSteamID &steamIDVictim )
+{
+ if ( !steamIDLeader.IsValid() || ! steamIDLeader.BIndividualAccount()
+ || !steamIDVictim.IsValid() || ! steamIDVictim.BIndividualAccount() )
+ return;
+
+ // find leader's group
+ IPlayerGroup* pPlayerGroup = YldFindAndLockGroupByMemberID( steamIDLeader );
+ if ( !pPlayerGroup )
+ return;
+
+ if ( pPlayerGroup->GetLeader() != steamIDLeader || steamIDLeader == steamIDVictim )
+ {
+ // non-leader attempting to kick
+ return;
+ }
+
+ // clear any pending invites for this person
+ if ( pPlayerGroup->GetPendingInviteIndexBySteamID( steamIDVictim ) != -1 )
+ {
+ YldRemovePendingInvite( pPlayerGroup->GetGroupID(), steamIDVictim );
+ }
+
+ PlayerGroupID_t nPlayerGroupID = pPlayerGroup->GetGroupID();
+ YldRemoveMemberFromGroup( nPlayerGroupID, steamIDVictim );
+ YldDestroyGroupIfEmpty( nPlayerGroupID );
+
+ pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( pPlayerGroup )
+ {
+ SendGroupStorageAndNetworkUpdate( pPlayerGroup );
+ }
+}
+
+bool CPlayerGroupManager::BYldRequestLeaveGroup( const CSteamID &steamID )
+{
+ if ( !steamID.IsValid() || ! steamID.BIndividualAccount() )
+ return false;
+
+ // find group
+ IPlayerGroup *pPlayerGroup = YldFindAndLockGroupByMemberID( steamID );
+ if ( !pPlayerGroup )
+ {
+ CProtoBufMsg<CMsgGCError> msg( k_EMsgGCError );
+ msg.Body().set_error_text( CFmtStr( "%s: Failed to remove you from your group. Couldn't find your group in the group map.", GetMemcachedIdentityKey() ) );
+ GGCBase()->BSendGCMsgToClient( steamID, msg );
+ return false;
+ }
+
+ // clear any pending invites for this person
+ if ( pPlayerGroup->GetPendingInviteIndexBySteamID( steamID ) != -1 )
+ {
+ YldRemovePendingInvite( pPlayerGroup->GetGroupID(), steamID );
+ }
+
+ PlayerGroupID_t nPlayerGroupID = pPlayerGroup->GetGroupID();
+ YldRemoveMemberFromGroup( nPlayerGroupID, steamID );
+ YldDestroyGroupIfEmpty( nPlayerGroupID );
+
+ pPlayerGroup = YldFindAndLockGroup( nPlayerGroupID );
+ if ( pPlayerGroup )
+ {
+ SendGroupStorageAndNetworkUpdate( pPlayerGroup );
+ }
+
+ return true;
+}
+
+void CPlayerGroupManager::YldOnPlayerLeftGroup( IPlayerGroup *pPlayerGroup, const CSteamID& steamIDRemovingMember )
+{
+ // if the group leader just left, then set a new leader
+ if ( pPlayerGroup->GetLeader() == steamIDRemovingMember && pPlayerGroup->GetNumMembers() > 0 )
+ {
+ pPlayerGroup->SetLeader( pPlayerGroup->GetMember( 0 ) );
+ }
+}
+
+void CPlayerGroupManager::YldOnGroupDestroyed( IPlayerGroup *pPlayerGroup )
+{
+ PlayerGroupID_t nPlayerGroupID = pPlayerGroup->GetGroupID();
+
+ // remove all pending invites
+ EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "CPlayerGroupManager::YldOnGroupDestroyed [%s] Removing all pending invites %016llx\n", GetMemcachedIdentityKey(), nPlayerGroupID );
+ for ( int i = pPlayerGroup->GetNumPendingInvites() - 1; i >= 0; i-- ) // iterate backwards as pending invites will be removed from the list
+ {
+ YldRemovePendingInvite( nPlayerGroupID, pPlayerGroup->GetPendingInvite( i ) );
+ }
+}
+
+void CPlayerGroupManager::YldCreateInvitesForGroup( PlayerGroupID_t nPlayerGroupID )
+{
+ // Check to see if the group was loaded, if not discard, it should always be loaded first
+ IPlayerGroup *pPlayerGroup = FindGroup( nPlayerGroupID );
+ if ( !pPlayerGroup )
+ return;
+
+ for ( int i = 0; i < pPlayerGroup->GetNumPendingInvites(); ++i )
+ {
+ CSteamID steamIDNewMember = pPlayerGroup->GetPendingInvite(i);
+
+ const char *szAccountName = "Unknown Player";
+ CAccountDetails *pDetails = GGCBase()->YieldingGetAccountDetails( pPlayerGroup->GetLeader() );
+ if ( pDetails )
+ {
+ szAccountName = pDetails->GetPersonaName();
+ }
+
+ // Send invite to the new member
+ IPlayerGroupInvite *pInvite = CreateInvite();
+ pInvite->SetGroupID( pPlayerGroup->GetGroupID() );
+ pInvite->SetSenderID( pPlayerGroup->GetLeader().ConvertToUint64() );
+ pInvite->SetSenderName( szAccountName );
+ CGCSharedObjectCache *pCache = GGCBase()->YieldingGetLockedSOCache( steamIDNewMember );
+ if ( pCache )
+ {
+ pCache->AddObject( pInvite->GetSharedObject() );
+ }
+ GGCBase()->UnlockSteamID( steamIDNewMember );
+ }
+}
+
+void CPlayerGroupManager::YldOnGroupLoadedFromMemcached( GCSDK::IPlayerGroup *pPlayerGroup )
+{
+ YldCreateInvitesForGroup( pPlayerGroup->GetGroupID() );
+}
+
+bool CPlayerGroupManager::IsPlayerWaitingForMemcache( const CSteamID &steamID ) const
+{
+ mapPlayersMemcacheJobCount_t::IndexType_t mIndex = sm_mapPlayersMemcacheJobCount.Find( steamID );
+ if ( mIndex != sm_mapPlayersMemcacheJobCount.InvalidIndex() )
+ {
+ return true;
+ }
+
+ return false;
+}
+