1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-30 05:07:46 +00:00

Updating for 4.3; Use 64bit ID for data control; Cleaning up;

This commit is contained in:
Maru 2018-06-03 15:28:37 -03:00
parent 634ad59bd5
commit c64650f7aa
14 changed files with 155 additions and 68 deletions

View file

@ -47,4 +47,7 @@ ALTER TABLE `charainfo` CHANGE `Mounts` `Mounts` BINARY(15) NULL DEFAULT NULL;
ALTER TABLE `charainfo` CHANGE `Orchestrion` `Orchestrion` BINARY(40) NULL DEFAULT NULL;
ALTER TABLE `charainfo` CHANGE `Minions` `Minions` BINARY(37) NULL DEFAULT NULL;
ALTER TABLE `charainfo` CHANGE `QuestCompleteFlags` `QuestCompleteFlags` VARBINARY(396) NULL DEFAULT NULL;
ALTER TABLE `charainfo` ADD COLUMN `EquipDisplayFlags` INT(3) NULL DEFAULT '0' AFTER `GMRank`;
ALTER TABLE `charainfo` ADD COLUMN `EquipDisplayFlags` INT(3) NULL DEFAULT '0' AFTER `GMRank`;
ALTER TABLE `charainfofriendlist` CHANGE `CharacterId` `ContentId` bigint(20) NULL DEFAULT NULL;
ALTER TABLE `charainfofriendlist` CHANGE `CharacterIdList` `ContentIdList` blob NULL DEFAULT NULL;

View file

@ -140,5 +140,5 @@ void Core::Db::CharaDbConnection::doPrepareStatements()
// SOCIAL GROUPS
prepareStatement( CHARA_SOCIAL_FRIENDS_INS, "INSERT INTO charainfofriendlist ( CharacterId, CharacterIdList, InviteDataList, UPDATE_DATE ) VALUES ( ?, ?, ?, NOW() );", CONNECTION_SYNC ); // todo: maybe not sync
prepareStatement( CHARA_SOCIAL_FRIENDS_INS, "INSERT INTO charainfofriendlist ( ContentId, ContentIdList, InviteDataList, UPDATE_DATE ) VALUES ( ?, ?, ?, NOW() );", CONNECTION_SYNC ); // todo: maybe not sync
}

View file

@ -82,22 +82,24 @@ struct FFXIVIpcPlayTime : FFXIVIpcBasePacket<Playtime>
struct PlayerEntry {
uint64_t contentId;
uint32_t timestamp;
uint8_t bytes[4];
uint32_t padding;
uint8_t status; // bitmask. friend: if it's a request, if added, recipient/sender
uint8_t unknown; // maybe bitmask? a value of 4 sets it to linkshell request
uint8_t entryIcon; // observed in friend group icon, sideffects for displaying text
uint8_t unavailable; // bool
uint16_t zoneId;
uint8_t grandCompany;
uint8_t unknown_43_1;
uint8_t unknown_43_2;
uint8_t grandCompany;
uint8_t clientLanguage;
uint8_t knownLanguages; // bitmask, J 0x01, E 0x02, D 0x04, F 0x08
uint8_t searchComment; // bool
char bytes1[6];
uint8_t knownLanguages; // bitmask, J 0x01, E 0x02, D 0x04, F 0x08
uint8_t searchComment; // bool
char bytes1[4];
uint64_t onlineStatusMask;
Common::ClassJob classJob;
uint8_t padding;
uint8_t padding_1;
uint16_t level;
uint16_t padding2;
uint16_t padding_2;
uint8_t one;
char name[0x20];
char fcTag[5];
@ -139,7 +141,7 @@ struct FFXIVIpcSocialRequestResponse : FFXIVIpcBasePacket<SocialRequestResponse>
uint64_t contentId;
uint32_t unknown;
Common::SocialCategory category; // Common::SocialCategory
Common::SocialRequestResponse response; // Common::SocialRequestResponse
Common::SocialRequestExecute execute; // Common::SocialRequestResponse
uint8_t unknown2; // possibly padding
char name[0x20];
uint16_t padding;

View file

@ -301,9 +301,9 @@ namespace Core {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SET UP SOCIAL GROUPS
createFriendsListContainer( m_id );
createFriendsListContainer( m_contentId );
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SETUP EQUIPMENT / STARTING GEAR
auto classJobInfo = g_exdDataGen.get< Core::Data::ClassJob >( m_class );
uint32_t weaponId = classJobInfo->itemStartingWeapon;
@ -377,14 +377,14 @@ namespace Core {
g_charaDb.directExecute( stmtCreateInv );
}
void PlayerMinimal::createFriendsListContainer( uint32_t characterId ) const
void PlayerMinimal::createFriendsListContainer( uint64_t contentId ) const
{
// todo: check if size is a-ok
std::vector< uint8_t > friendsList( 1600 );
std::vector< uint8_t > inviteDateList( 1600 );
auto stmtCreateFrnList = g_charaDb.getPreparedStatement( Db::CHARA_SOCIAL_FRIENDS_INS );
stmtCreateFrnList->setInt( 1, characterId );
stmtCreateFrnList->setInt64( 1, contentId );
stmtCreateFrnList->setBinary( 2, friendsList );
stmtCreateFrnList->setBinary( 3, inviteDateList );

View file

@ -162,7 +162,7 @@ namespace Core {
void createInvDbContainer( uint16_t slot ) const;
void createFriendsListContainer( uint32_t characterId ) const;
void createFriendsListContainer( uint64_t characterId ) const;
uint32_t m_modelEquip[10];

View file

@ -211,12 +211,12 @@ bool Core::Entity::Player::load( uint32_t charId, SessionPtr pSession )
auto friendListMgr = g_fw.get< Social::SocialMgr < Social::FriendList > >();
if( !friendListMgr->loadFriendsList( m_id ) )
if( !friendListMgr->loadFriendsList( m_contentId ) )
{
pLog->error( "[" + char_id_str + "] Failed to load friends list!" );
pLog->error( "[" + std::to_string( m_contentId ) + "] Failed to load friends list!" );
}
pLog->debug( std::to_string( m_id ) + " ID, has group ID: " + std::to_string( m_friendsListId ) );
pLog->debug( std::to_string( m_contentId ) + " ID, has group ID: " + std::to_string( m_friendsListId ) );
// first login, run the script event
if( m_bNewGame )

View file

@ -16,7 +16,7 @@ file(GLOB SERVER_PUBLIC_INCLUDE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
Network/*.h
Network/Handlers/*.h
Network/PacketWrappers/*.h
Social/*.h
Social/*.h
Social/Manager/*.h
Script/*.h
StatusEffect/*.h
@ -25,8 +25,6 @@ file(GLOB SERVER_PUBLIC_INCLUDE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
file(GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.c*
Actor/*.c*
Actor/Social/*.c*
Actor/Social/Manager/*.c*
Action/*.c*
DebugCommand/*.c*
Event/*.c*
@ -36,7 +34,7 @@ file(GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
Network/*.c*
Network/Handlers/*.c*
Network/PacketWrappers/*.c*
Social/*.c*
Social/*.c*
Social/Manager/*.c*
Script/*.c*
StatusEffect/*.c*

View file

@ -56,6 +56,7 @@ Core::Network::GameConnection::GameConnection( Core::Network::HivePtr pHive,
setZoneHandler( ClientZoneIpcType::SocialReqSendHandler, "SocialReqSendHandler", &GameConnection::socialReqSendHandler );
setZoneHandler( ClientZoneIpcType::SocialReqProcessHandler, "SocialReqProcessHandler", &GameConnection::socialReqProcessHandler );
setZoneHandler( ClientZoneIpcType::SocialReqRemoveHandler, "SocialReqRemoveHandler", &GameConnection::socialReqRemoveHandler );
setZoneHandler( ClientZoneIpcType::SocialListHandler, "SocialListHandler", &GameConnection::socialListHandler );
setZoneHandler( ClientZoneIpcType::SetSearchInfoHandler, "SetSearchInfoHandler", &GameConnection::setSearchInfoHandler );
setZoneHandler( ClientZoneIpcType::ReqSearchInfoHandler, "ReqSearchInfoHandler", &GameConnection::reqSearchInfoHandler );

View file

@ -88,6 +88,7 @@ public:
DECLARE_HANDLER( blackListHandler );
DECLARE_HANDLER( socialReqProcessHandler );
DECLARE_HANDLER( socialReqSendHandler );
DECLARE_HANDLER( socialReqRemoveHandler );
DECLARE_HANDLER( socialListHandler );
DECLARE_HANDLER( linkshellListHandler );
DECLARE_HANDLER( playTimeHandler );

View file

@ -453,26 +453,23 @@ void Core::Network::GameConnection::socialListHandler( const Packets::GamePacket
int32_t entrysizes = sizeof( listPacket.data().entries );
memset( listPacket.data().entries, 0, sizeof( listPacket.data().entries ) );
/*
listPacket.data().entries[0].bytes[2] = player.getCurrentZone()->getTerritoryId();
listPacket.data().entries[0].bytes[3] = 0x80;
listPacket.data().entries[0].bytes[4] = 0x02;
listPacket.data().entries[0].bytes[6] = 0x3B;
listPacket.data().entries[0].bytes[11] = 0x10;
listPacket.data().entries[0].bytes[11] = 0x10;*/
listPacket.data().entries[0].classJob = player.getClass();
listPacket.data().entries[0].contentId = player.getContentId();
listPacket.data().entries[0].level = player.getLevel();
listPacket.data().entries[0].zoneId = player.getCurrentZone()->getTerritoryId();
// listPacket.data().entries[0].zoneId1 = 0x0100;
// TODO: no idea what this does
//listPacket.data().entries[0].one = 1;
listPacket.data().entries[0].one = 1;
memcpy( listPacket.data().entries[0].name, player.getName().c_str(), strlen( player.getName().c_str() ) );
// TODO: actually store and read language from somewhere
listPacket.data().entries[0].bytes1[0] = 0x01;//flags (lang)
// TODO: these flags need to be figured out
//listPacket.data().entries[0].bytes1[1] = 0x00;//flags
listPacket.data().entries[0].onlineStatusMask = player.getOnlineStatusMask();
queueOutPacket( listPacket );
@ -487,8 +484,8 @@ void Core::Network::GameConnection::socialListHandler( const Packets::GamePacket
memset( listPacket.data().entries, 0, sizeof( listPacket.data().entries ) );
uint16_t i = 0;
auto playerFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getFriendsListId() );
auto test = g_fw.get< Social::SocialMgr< Social::FriendList > >();
auto playerFriendsList = test->findGroupById( player.getContentId() );
// todo: move this garbage else fucking where
for ( auto member : playerFriendsList->getMembers() )
@ -497,6 +494,9 @@ void Core::Network::GameConnection::socialListHandler( const Packets::GamePacket
if ( i == 10 )
break;
if( member == 0 )
continue;
g_fw.get< Logger >()->debug( "aaa" + std::to_string( i ) );
// todo: replace this with call for generating the entire vector
listPacket.data().entries[i] = playerFriendsList->generatePlayerEntry( member );
@ -516,23 +516,26 @@ void Core::Network::GameConnection::socialListHandler( const Packets::GamePacket
void Core::Network::GameConnection::socialReqProcessHandler( const Packets::GamePacket& inPacket,
Entity::Player& player )
{
auto targetId = inPacket.getValAt< uint32_t >( 0x20 );
auto targetId = inPacket.getValAt< uint64_t >( 0x20 );
auto category = inPacket.getValAt< Common::SocialCategory >( 0x28 );
auto action = inPacket.getValAt< Common::SocialRequestAction >( 0x29 );
auto execute = inPacket.getValAt< Common::SocialRequestExecute >( 0x29 );
auto friendListMgr = g_fw.get< Social::SocialMgr< Social::FriendList > >();
ZoneChannelPacket< FFXIVIpcSocialRequestError > info( targetId, player.getId() );
ZoneChannelPacket< FFXIVIpcSocialRequestResponse > response( targetId, player.getId() );
g_fw.get< Logger >()->debug( std::to_string( targetId ) + std::to_string( ( uint32_t )category ) + std::to_string( ( uint32_t )execute ) );
info.data().category = category;
response.data().category = category;
switch( action )
switch( execute )
{
case SocialRequestAction::Accept:
case SocialRequestExecute::Accept:
{
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getId() );
g_fw.get< Logger >()->debug( "uuuuuuuuh" );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getContentId() );
// Load our target's friendlist, hoping it's still in memory.. (target could have gone offline at this point)
if( !friendListMgr->loadFriendsList( targetId ) )
@ -542,17 +545,18 @@ void Core::Network::GameConnection::socialReqProcessHandler( const Packets::Game
auto senderFriendsList = friendListMgr->findGroupById( targetId );
senderFriendsList->processInvite( player.getContentId(), SocialRequestAction::Accept );
senderFriendsList->processInvite( player.getContentId(), SocialRequestExecute::Accept );
//todo: FICK
recipientFriendsList->processInvite( targetId, SocialRequestAction::Accept );
recipientFriendsList->processInvite( targetId, SocialRequestExecute::Accept );
break;
}
default:
break;
}
//auto pQR = g_database.query( "SELECT Name FROM dbchara WHERE CharacterId = " + to_string( targetId ) );
auto name = player.getName();
/*
if( pQR->getRowCount() > 0 )
{
@ -574,19 +578,79 @@ void Core::Network::GameConnection::socialReqProcessHandler( const Packets::Game
return;
}*/
g_fw.get< Logger >()->debug( std::to_string( static_cast<uint8_t>( action ) ) );
auto pSession = g_fw.get< ServerZone >()->getSession( targetId );
// todo: notify both inviter/invitee with 0x00CB packet
if( pSession )
{
g_fw.get< Logger >()->debug( std::to_string( static_cast< uint8_t >( action ) ) );
// If they're online, send a packet for them both.
// First the sender, then recipient..
auto name = player.getName();
response.data().execute = Common::SocialRequestExecute::Accept;
memcpy( &( response.data().name ), name.c_str(), 32 );
pSession->getPlayer()->queuePacket( response );
// TODO: is this safe?
memcpy( &( response.data().name ), player.getName().c_str(), 32 );
player.queuePacket( response );
}
}
void Core::Network::GameConnection::socialReqRemoveHandler( const Packets::GamePacket& inPacket,
Entity::Player& player )
{
auto category = inPacket.getValAt< Common::SocialCategory >( 0x20 );
auto execute = inPacket.getValAt< Common::SocialRequestExecute >( 0x29 );
auto friendListMgr = g_fw.get< Social::SocialMgr< Social::FriendList > >();
ZoneChannelPacket< FFXIVIpcSocialRequestError > info( targetId, player.getId() );
ZoneChannelPacket< FFXIVIpcSocialRequestResponse > response( targetId, player.getId() );
g_fw.get< Logger >()->debug( std::to_string( targetId ) + std::to_string( ( uint32_t )category ) + std::to_string( ( uint32_t )execute ) );
info.data().category = category;
response.data().category = category;
switch( execute )
{
case SocialRequestExecute::Accept:
{
g_fw.get< Logger >()->debug( "uuuuuuuuh" );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getContentId() );
// Load our target's friendlist, hoping it's still in memory.. (target could have gone offline at this point)
if( !friendListMgr->loadFriendsList( targetId ) )
{
g_fw.get< Logger >()->error( "Failed to load friend list for character ID " + std::to_string( targetId ) + ", removing entry." );
}
auto senderFriendsList = friendListMgr->findGroupById( targetId );
senderFriendsList->processInvite( player.getContentId(), SocialRequestExecute::Accept );
//todo: FICK
recipientFriendsList->processInvite( targetId, SocialRequestExecute::Accept );
break;
}
default:
break;
}
auto pSession = g_fw.get< ServerZone >()->getSession( targetId );
if( pSession )
{
// If they're online, send a packet for them both.
// First the sender, then recipient..
auto name = player.getName();
response.data().execute = Common::SocialRequestExecute::Accept;
memcpy( &( response.data().name ), name.c_str(), 32 );
pSession->getPlayer()->queuePacket( response );
// TODO: is this safe?
memcpy( &( response.data().name ), player.getName().c_str(), 32 );
player.queuePacket( response );
}
response.data().response = Common::SocialRequestResponse::Accept;
memcpy( &( response.data().name ), name.c_str(), 32 );
player.queuePacket( response );
}
void Core::Network::GameConnection::socialReqSendHandler( const Packets::GamePacket& inPacket,
@ -671,8 +735,8 @@ void Core::Network::GameConnection::socialReqSendHandler( const Packets::GamePac
auto recipientId = pRecipient->getContentId();
auto senderId = player.getContentId();
auto senderFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getFriendsListId() );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( pRecipient->getFriendsListId() );
auto senderFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( player.getContentId() );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( pRecipient->getContentId() );
// If any of these are true, an error has occured.
if( senderFriendsList->hasMember( recipientId ) )
@ -735,16 +799,16 @@ void Core::Network::GameConnection::socialReqSendHandler( const Packets::GamePac
pRecipient->queuePacket( packet );
pRecipient->sendDebug( "ding ding" );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( pRecipient->getFriendsListId() );
auto recipientFriendsList = g_fw.get< Social::SocialMgr< Social::FriendList > >()->findGroupById( pRecipient->getContentId() );
g_fw.get< Logger >()->debug( "Player " + player.getName() + " has added " + pRecipient->getName() );
recipientFriendsList->addMember( player.getContentId(), Social::FriendEntryType::ReceivedRequest );
auto senderResultPacket = GamePacketNew< Server::FFXIVIpcSocialRequestResponse, ServerZoneIpcType >( pRecipient->getId(), player.getId() );
auto senderResultPacket = GamePacketNew< Server::FFXIVIpcSocialRequestResponse, ServerZoneIpcType >( pRecipient->getContentId(), player.getId() );
senderResultPacket.data().contentId = pRecipient->getContentId();
senderResultPacket.data().category = Common::SocialCategory::Friends;
senderResultPacket.data().response = Common::SocialRequestResponse::Cancel;
senderResultPacket.data().execute = Common::SocialRequestExecute::Cancel;
memcpy( &( senderResultPacket.data().name ), pRecipient->getName().c_str(), 32 );

View file

@ -14,6 +14,7 @@
#include <Database/DatabaseDef.h>
#include <Network/GamePacketNew.h>
#include <Common.h>
#include "Group.h"
#include "FriendList.h"
@ -71,7 +72,7 @@ uint32_t FriendList::removeMember( uint64_t contentId )
}
uint32_t FriendList::processInvite( uint64_t contentId, Common::SocialRequestAction action )
uint32_t FriendList::processInvite( uint64_t contentId, Core::Common::SocialRequestExecute execute )
{
uint32_t logMessage = 0;
@ -81,25 +82,30 @@ uint32_t FriendList::processInvite( uint64_t contentId, Common::SocialRequestAct
auto dataIndex = std::distance( m_members.begin(), it );
auto friendEntryData = m_entries.at( dataIndex );
auto& friendEntryData = m_entries.at( dataIndex );
// todo: check timestamp, if expired etc.
switch( action )
g_fw.get< Logger >()->debug( "Process Invite: stepping through " );
switch( execute )
{
case Common::SocialRequestAction::Accept:
case Common::SocialRequestExecute::Accept:
{
friendEntryData.entryStatus = FriendEntryType::Added;
g_fw.get< Logger >()->debug( "Process Invite: Adding " + std::to_string( contentId ) + " to " + to_string( m_ownerId ) );
break;
}
case Common::SocialRequestAction::Decline:
case Common::SocialRequestExecute::Decline:
{
removeMember( contentId );
break;
}
default:
break;
}
return 0;
}
uint32_t FriendList::getFriendIndex( uint64_t contentId )

View file

@ -45,7 +45,7 @@ public:
uint32_t addMember( uint64_t contentId, FriendEntryType friendEntryType );
uint32_t processInvite( uint64_t contentId, Common::SocialRequestAction );
uint32_t processInvite( uint64_t contentId, Core::Common::SocialRequestExecute execute );
uint32_t removeMember( uint64_t contentId );

View file

@ -69,7 +69,7 @@ Core::Network::Packets::GamePacketPtr Group::processInvite( uint64_t recipientId
auto packet = GamePacketNew< Server::FFXIVIpcSocialRequestResponse, ServerZoneIpcType >( recipientId, senderId );
packet.data().contentId = recipientContentId;
packet.data().category = Common::SocialCategory::Friends;
packet.data().response = Common::SocialRequestResponse::Accept;
packet.data().execute = Common::SocialRequestExecute::Accept;
//packet.data().
if ( m_members.size() < m_maxCapacity )
@ -182,18 +182,21 @@ void Group::sendPacketToMembers( Core::Network::Packets::GamePacketPtr pPacket,
}
std::vector< uint64_t >& Group::getMembers()
{
return m_members;
}
uint32_t Group::getCapacity() const
{
return m_maxCapacity;
}
uint64_t Group::getId() const
{
return m_id;
}
uint32_t Group::getTotalSize() const
{
return m_members.size();

View file

@ -15,6 +15,8 @@
#include <Social/FriendList.h>
#include <Database/DatabaseDef.h>
#include <Logging/Logger.h>
#include "Framework.h"
extern Core::Framework g_fw;
@ -40,7 +42,6 @@ public:
}
bool init();
//bool< FriendList > init();
boost::shared_ptr< T > findGroupByInviteIdForPlayer( uint64_t playerId ) const
{
@ -54,6 +55,9 @@ public:
boost::shared_ptr< T > findGroupById( uint64_t groupId ) const
{
// todo: lol
g_fw.get< Logger >()->debug( std::to_string( m_groups.size() ) );
auto it = m_groups.find( groupId );
if ( it != m_groups.end() )
{
@ -72,7 +76,7 @@ public:
return false;
}
bool loadFriendsList( uint32_t characterId );
bool loadFriendsList( uint64_t contentId );
protected:
// those would be implemented in T, so you'd have T.m_type and T.m_maxEntries
@ -109,17 +113,22 @@ bool Core::Social::SocialMgr< T >::init()
}
template<> inline
bool Core::Social::SocialMgr< Core::Social::FriendList >::loadFriendsList( uint32_t characterId )
bool Core::Social::SocialMgr< Core::Social::FriendList >::loadFriendsList( uint64_t contentId )
{
// Check if our group has already been loaded..
auto group = findGroupById( characterId );
auto group = findGroupById( contentId );
if( group )
return true;
// TODO: Remove this message
g_fw.get< Logger >()->debug( "Crashing here usually implies pre-social implementation database and/or character, recreate the database and characters." );
g_fw.get< Logger >()->debug( std::to_string( contentId ) );
auto pDb = g_fw.get< Db::DbWorkerPool< Db::CharaDbConnection > >();
auto res = pDb->query( "SELECT CharacterId, CharacterIdList, InviteDataList "
auto res = pDb->query( "SELECT ContentId, ContentIdList, InviteDataList "
"FROM charainfofriendlist "
"WHERE CharacterId = " + std::to_string( characterId ) );
"WHERE ContentId = " + std::to_string( contentId ) );
if ( !res->next() )
@ -156,7 +165,7 @@ bool Core::Social::SocialMgr< Core::Social::FriendList >::loadFriendsList( uint3
auto friendListPtr = boost::make_shared< Social::FriendList >( friendsList );
m_groups.emplace( characterId, friendListPtr );
m_groups.emplace( contentId, friendListPtr );
return true;
}