Files
Jedi-Academy/codemp/xbox/XBLive_Friends.cpp
2013-04-04 14:32:05 -07:00

717 lines
20 KiB
C++

// XBLIVE_FRIENDS.CPP
//
// wrapper to the Xbox online friends API
//
#include "..\xbox\XBLive.h"
#include "..\xbox\XBoxCommon.h"
#include "..\xbox\XBVoice.h"
#include "..\game\q_shared.h"
#include "..\qcommon\qcommon.h"
#include "..\cgame\cg_local.h"
#include "../ui/ui_shared.h"
#include "../qcommon/stringed_ingame.h"
#include "../qcommon/xb_settings.h"
extern int RE_RegisterShaderNoMip( const char *name );
//DEFINES
#define OPEN_SLOTS_JOIN_THRESHOLD 1 // the number of public slots needed to be open to make a player joinable (TCR recommends 3)
#define JOINABLE_CHECK_RATE 400 // 400 ticks (8ish seconds) between checks
#define FRIEND_DISPLAY_LIMIT 10 // only display a maximum of 10 friends at a time
#define MAX_TITLE_LENGTH 16 // allow 16 wide characters for a title's name
#define GAME_TITLE "Jedi Knight: Jedi Academy"
//VARIABLES
XONLINE_FRIEND friendsList[MAX_FRIENDS]; // Our copy of the user's friends list
XONLINETASK_HANDLE friendsGeneralTask;
XONLINETASK_HANDLE enumerationTask;
bool friendsInitialized = false;
bool generatingFriends = false; // flag stating that we are currently enumerating the friends list
BYTE friendChosen = 0; // the friend selcted for action
int numFriends = 0; // number of friends found while enumerating list
DWORD current_state = 0; // flags depicting current state as perceived by friends
//PROTOTYPES
void XBL_F_GetTitleString(const DWORD titleID, char* titleString);
HRESULT XBL_F_Invite(void);
void XBL_F_JoinFriendUninvited( XONLINE_FRIEND* friendPlaying);
// ******************************************************
// INITIALIZING FUNCTIONS
// *******************************************************
// Initialize the friends functionality
//
void XBL_F_Init()
{
if( friendsInitialized )
return;
// We have no friends (yet)
numFriends = 0;
// system startup
//
HRESULT result = XOnlineFriendsStartup( NULL, &friendsGeneralTask );
if( result != S_OK )
{
Com_Printf("XBLive_F - Error %d in startup\n", result);
friendsGeneralTask = NULL;
return;
}
// Default state after logging in
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_ONLINE, !Settings.appearOffline);
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_PLAYING, false);
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_VOICE, g_Voice.IsVoiceAllowed());
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_JOINABLE, false);
friendsInitialized = true;
}
// generate the list of friends
//
void XBL_F_GenerateFriendsList()
{
if(!logged_on || generatingFriends)
return;
// generate the list of records
//
HRESULT result = XOnlineFriendsEnumerate( IN_GetMainController(), NULL, &enumerationTask );
if( result != S_OK )
{
Com_Printf("XBLive_F - Error %d using XOnlineFriendsEnumerate\n", result);
enumerationTask = NULL;
return;
}
// Reset friends count, set flag to retrieve friends during tick
numFriends = 0;
generatingFriends = true;
}
// ******************************************************
// CLEANUP FUNCTIONS
// *******************************************************
// stop generating the friends list
//
void XBL_F_ReleaseFriendsList()
{
if( !generatingFriends )
return;
// wait for enumeration to complete
//
if(enumerationTask)
{
HRESULT result = XOnlineFriendsEnumerateFinish( enumerationTask );
if( IS_ERROR(result) )
{
Com_Printf("XBLive_F - Error %d using XOnlineFriendsEnumerateFinish\n", result);
}
// finish any related tasks
//
do
{
result = XOnlineTaskContinue( enumerationTask );
if( IS_ERROR(result) )
{
Com_Printf("XBLive_F - Error %d enumerating friends list\n", result);
}
}
while( result != XONLINETASK_S_SUCCESS );
XOnlineTaskClose(enumerationTask);
enumerationTask = NULL;
}
// List isn't valid anymore, so no friends, and tell tick to stop generating
generatingFriends = false;
numFriends = 0;
}
// shutdown and cleanup friends functionality
//
void XBL_F_Cleanup()
{
HRESULT result;
XBL_F_ReleaseFriendsList();
// finish off incomplete tasks
//
if(friendsGeneralTask)
{
do
{
result = XOnlineTaskContinue( friendsGeneralTask );
if( IS_ERROR(result) )
{
Com_Printf("XBLive_F - Error %d finishing off friends general tasks\n", result);
XOnlineTaskClose(friendsGeneralTask);
friendsGeneralTask = NULL;
break;
}
}
while( result != XONLINETASK_S_RUNNING_IDLE );
XOnlineTaskClose(friendsGeneralTask);
friendsGeneralTask = NULL;
}
friendsInitialized = false;
}
// ******************************************************
// FUNCTIONS THE MENU SYSTEM USES TO COMMUNICATE TO FRIENDS
// *******************************************************
//Return the number of friends found while enumerating list
int XBL_F_GetNumFriends(void)
{
return numFriends;
}
// Set the higlighted friend for use when friend is selected
void XBL_F_SetChosenFriendIndex(const int index)
{
// Sanity check - this does get called before we're done enumerating some times!
assert( (index >= 0 && index < numFriends) || !numFriends );
// SOF2 had checks for less than zero - probably forgot to do a
// Menu_SetFeederSelection before opening the menu.
friendChosen = index;
// The UI calls this when the selection in the listbox moves. Set the status lines:
Cvar_Set( "fl_voiceLine", XBL_F_GetVoiceString( index ) );
Cvar_Set( "fl_statusLine", XBL_F_GetStatusString( index ) );
Cvar_Set( "fl_statusLine2", XBL_F_GetStatusString2( index ) );
}
XONLINE_FRIEND *XBL_F_GetChosenFriend( void )
{
if( friendChosen >= 0 && friendChosen < numFriends )
return &friendsList[friendChosen];
return NULL;
}
// Handle a players attempt to join a friends game from an invite when they are potentially using another title
//
HRESULT XBL_F_JoinGameFromInvite()
{
HRESULT result;
result = XOnlineFriendsAnswerGameInvite( IN_GetMainController(), &friendsList[friendChosen], XONLINE_GAMEINVITE_YES); // writes invite to HD if needed
if( result != S_OK )
{
assert(!"faliure to accept invite");
// report error
}
// figure out if you're playing the same game as your friend
//
BOOL playingSameGame = XOnlineTitleIdIsSameTitle( friendsList[friendChosen].dwTitleID );
// if you are
//
if( playingSameGame )
{
XBL_MM_ConnectViaSessionID( &friendsList[friendChosen].sessionID, true );
return S_OK;
}
// else if its a different game return the code to show popup
//
return -1;
}
// Handle a players attempt to join a friends game when they are potentially using another title
HRESULT XBL_F_JoinGame()
{
// Figure out if you're playing the same game as your friend
BOOL playingSameGame = XOnlineTitleIdIsSameTitle( friendsList[friendChosen].dwTitleID );
// If you are
if( playingSameGame )
{
XBL_MM_ConnectViaSessionID( &friendsList[friendChosen].sessionID, false );
return S_OK;
}
// It's a different game, write out the info:
HRESULT result = XOnlineFriendsJoinGame( IN_GetMainController(), &friendsList[friendChosen] );
assert( result == S_OK );
// Show insert disk popup
return -1;
}
// This handle all possbile actions that the user can perform on a selected friend
//
HRESULT XBL_F_PerformMenuAction(friendChoices_t action)
{
HRESULT result = S_OK;
switch(action)
{
case UI_F_FRIENDREQUESTED:
result = XOnlineFriendsRequest(IN_GetMainController(), friendsList[friendChosen].xuid);
break;
case UI_F_FRIENDACCEPTED:
result = XOnlineFriendsAnswerRequest(IN_GetMainController(), &friendsList[friendChosen], XONLINE_REQUEST_YES);
break;
case UI_F_FRIENDREMOVE:
result = XOnlineFriendsRemove(IN_GetMainController(), &friendsList[friendChosen]);
break;
case UI_F_FRIENDDECLINE:
result = XOnlineFriendsAnswerRequest(IN_GetMainController(), &friendsList[friendChosen], XONLINE_REQUEST_NO);
break;
case UI_F_FRIENDBLOCK:
result = XOnlineFriendsAnswerRequest(IN_GetMainController(), &friendsList[friendChosen], XONLINE_REQUEST_BLOCK);
break;
case UI_F_FRIENDCANCEL:
result = XOnlineFriendsRemove(IN_GetMainController(), &friendsList[friendChosen]);
break;
case UI_F_GAMEREQUESTED:
result = XBL_F_Invite();
break;
case UI_F_GAMEACCEPTED:
result = XBL_F_JoinGameFromInvite();
break;
case UI_F_GAMEDECLINE:
result = XOnlineFriendsAnswerGameInvite(IN_GetMainController(), &friendsList[friendChosen], XONLINE_GAMEINVITE_NO);
break;
case UI_F_GAMEFRIENDREMOVED:
result = XOnlineFriendsAnswerGameInvite(IN_GetMainController(), &friendsList[friendChosen], XONLINE_GAMEINVITE_REMOVE);
break;
case UI_F_GAMECANCEL:
result = XOnlineFriendsRevokeGameInvite(IN_GetMainController(), *Net_GetXNKID(), 1, &friendsList[friendChosen]);
break;
case UI_F_JOINSESSION:
result = XBL_F_JoinGame();
break;
case UI_F_TOGGLEONLINE:
if(current_state & XONLINE_FRIENDSTATE_FLAG_ONLINE)
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_ONLINE, false);
else
XBL_F_SetState(XONLINE_FRIENDSTATE_FLAG_ONLINE, true);
break;
}
return result;
}
// Returns the gamertag of the given friend - used by UI listbox renderer
const char *XBL_F_GetFriendName( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return "";
return friendsList[index].szGamertag;
}
// Returns the voice icon to use in the friends list for the given friend
int XBL_F_GetVoiceIcon( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return -1;
// Friend has no voice?
if( !(friendsList[index].dwFriendState & XONLINE_FRIENDSTATE_FLAG_VOICE) )
return -1;
// They have voice. Just check if they're muted.
if( g_Voice.IsMuted( friendsList[index].xuid ) )
return RE_RegisterShaderNoMip( "gfx/mp/voice_mute_icon" );
else
return RE_RegisterShaderNoMip( "gfx/mp/voice_on_icon" );
}
// Returns the full voice info status line for the given friend
const char *XBL_F_GetVoiceString( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return "";
// Never display voice info for an offline friend
if( !(friendsList[index].dwFriendState & XONLINE_FRIENDSTATE_FLAG_ONLINE) )
return "";
// Friend has no voice?
if( !(friendsList[index].dwFriendState & XONLINE_FRIENDSTATE_FLAG_VOICE) )
return va( "%s %s", SE_GetString( "MENUS_VOICE" ), SE_GetString( "MENUS_OFF" ) );
// OK. Friend has voice. Just check if they're muted.
if( g_Voice.IsMuted( friendsList[index].xuid ) )
return va( "%s %s", SE_GetString( "MENUS_VOICE" ), SE_GetString( "MENUS_MUTED" ) );
else
return va( "%s %s", SE_GetString( "MENUS_VOICE" ), SE_GetString( "MENUS_ON" ) );
}
// Returns the status icon to use in the friends list for the given friend
int XBL_F_GetStatusIcon( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return -1;
// We'll be using this a lot...
const XONLINE_FRIEND *curFriend = &friendsList[index];
// In order of importance
// Player has received a request to a game
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDINVITE )
return RE_RegisterShaderNoMip( "gfx/mp/game_received_icon" );
// Player has received a request to be a friend
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDREQUEST )
return RE_RegisterShaderNoMip( "gfx/mp/friend_received_icon" );
// Player has sent invite to game
if( (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTINVITE) &&
!(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEACCEPTED) &&
!(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEREJECTED) )
return RE_RegisterShaderNoMip( "gfx/mp/game_sent_icon" );
// Player has sent friend request
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTREQUEST )
return RE_RegisterShaderNoMip( "gfx/mp/friend_sent_icon" );
// Friend is online, or online and playing
if( (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_PLAYING) ||
(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_ONLINE) )
return RE_RegisterShaderNoMip( "gfx/mp/online_icon" );
// Friend is offline
return -1;
}
// Returns the online/offline status line for the given friend
const char *XBL_F_GetStatusString( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return "";
// Some things we'lll be reusing a bit:
const XONLINE_FRIEND *curFriend = &friendsList[index];
char titleString[MAX_TITLENAME_LEN+1];
XBL_F_GetTitleString( curFriend->dwTitleID, titleString );
// In order of importance
// Friend is currently playing some game
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_PLAYING )
return va( SE_GetString( "MENUS_PLAYING" ), titleString );
// Friend is online - not in a session
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_ONLINE )
return va( SE_GetString( "MENUS_AVAILABLE_IN" ), titleString );
// Friend is offline
return SE_GetString( "MENUS_OFFLINE" );
}
// Returns the special case status info for the given friend
const char *XBL_F_GetStatusString2( const int index )
{
// Invalid index?
if( index < 0 || index >= numFriends )
return "";
// Some things we'lll be reusing a bit:
const XONLINE_FRIEND *curFriend = &friendsList[index];
char titleString[MAX_TITLENAME_LEN+1];
XBL_F_GetTitleString( curFriend->dwTitleID, titleString );
// In order of importance
// Player has received a game invite fom the indicated friend
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDINVITE )
return va( SE_GetString( "MENUS_WANTS_TO_PLAY" ), titleString ); // Cleared ui_gameInvite (now xbl_hudGame)
// Player has received a request to be a friend
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDREQUEST )
return SE_GetString( "MENUS_WANTS_TO_BE_FRIEND" ); // Cleared ui_friendInvite
// Player has sent invite to game
if( (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTINVITE) &&
!(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEACCEPTED) &&
!(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEREJECTED) )
return SE_GetString( "MENUS_INVITED_TO_PLAY" );
// Player has sent friend request
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTREQUEST )
return SE_GetString( "MENUS_YOU_ASKED_TO_BE_FRIEND" );
// No special state:
return "";
}
// Determines the friend status of a given user. Used by the player list
// code to set the state of each user, which is then used to determine icons.
FriendState XBL_F_GetFriendStatus( const XUID *xuid )
{
XONLINE_FRIEND *curFriend = NULL;
for( int i = 0; i < numFriends; i++ )
{
if( XOnlineAreUsersIdentical( &friendsList[i].xuid, xuid ) )
{
curFriend = &friendsList[i];
break;
}
}
if( !curFriend )
return FRIEND_NO;
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTREQUEST )
{
return FRIEND_RQST;
}
if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDREQUEST )
{
return FRIEND_RQST_RCV;
}
return FRIEND_YES;
}
// Send a friend invitation to this user.
void XBL_F_AddFriend( const XUID *pXuid )
{
// Send the request to the servers - VVFIXME - handle errors here?
HRESULT hr = XOnlineFriendsRequest( IN_GetMainController(), *pXuid );
}
// Cancel a friend invitation we may have sent to this user, and/or
// remove them from our friend list.
void XBL_F_RemoveFriend( const XUID *pXuid )
{
XONLINE_FRIEND *curFriend = NULL;
for( int i = 0; i < numFriends; i++ )
{
if( XOnlineAreUsersIdentical( &friendsList[i].xuid, pXuid ) )
{
curFriend = &friendsList[i];
break;
}
}
// Make sure we found them in our list
if( !curFriend )
return;
// OK. Remove them from the list
XOnlineFriendsRemove( IN_GetMainController(), curFriend );
}
// ******************************************************
// SUPPORT FUNCTIONS FOR MANAGING FRIENDS
// *******************************************************
// ******************************************************
// PUBLIC ** PUBLIC ** PUBLIC ** PUBLIC ** PUBLIC ** PUBLIC
// *******************************************************
// functionality to be run every game tick
//
void XBL_F_Tick()
{
if( !friendsInitialized )
return;
HRESULT result;
// Check if we are in a session with enough open public slots to make us joinable
XBL_F_CheckJoinableStatus( false );
// Process the general task, required during gameplay to handle invitations
if( friendsGeneralTask )
{
result = XOnlineTaskContinue( friendsGeneralTask );
if( IS_ERROR(result) )
{
Com_Printf("XBLive_F - Error %d running friends general tasks\n", result);
XOnlineTaskClose(friendsGeneralTask);
friendsGeneralTask = NULL;
}
}
// If the UI is currently displaying the friends list we must continue to update it
if( generatingFriends && enumerationTask )
{
// Keep enumerating
result = XOnlineTaskContinue( enumerationTask );
if( IS_ERROR(result) )
{
Com_Printf("XBLive_F - Error %d enumerating friends list\n", result);
}
// Do we have all the results?
if( result == XONLINETASK_S_RESULTS_AVAIL )
{
// Copy the list into our buffer
numFriends = XOnlineFriendsGetLatest(IN_GetMainController(), MAX_FRIENDS, &friendsList[0] );
// Also update the strings about the currently selected friend:
if( friendChosen >= 0 && friendChosen < numFriends )
{
Cvar_Set( "fl_voiceLine", XBL_F_GetVoiceString( friendChosen ) );
Cvar_Set( "fl_statusLine", XBL_F_GetStatusString( friendChosen ) );
Cvar_Set( "fl_statusLine2", XBL_F_GetStatusString2( friendChosen ) );
}
}
}
}
// Change our state - set or remove a notification flag
void XBL_F_SetState( DWORD flag, bool set_flag )
{
if( set_flag )
current_state |= flag;
else
current_state &= ~flag;
HRESULT hr = XOnlineNotificationSetState(IN_GetMainController(), current_state, *Net_GetXNKID(), 0, NULL );
assert( hr == S_OK );
// VVFIXME - Move this shite into the calling code
//jsw//Hack for the menus
if(flag == XONLINE_FRIENDSTATE_FLAG_ONLINE)
{
if(set_flag)
Cvar_Set( "ui_xblivefriendonline", SE_GetString("XBL_ONLINE") );
else
Cvar_Set( "ui_xblivefriendonline", SE_GetString("XBL_OFFLINE") );
}
}
// Just a way to get our own state information (things that are in our friend state)
bool XBL_F_GetState( DWORD flag )
{
return current_state & flag;
}
// Invite a friend to join the current game
HRESULT XBL_F_Invite(void)
{
return XOnlineFriendsGameInvite( IN_GetMainController(), *Net_GetXNKID(), 1, &friendsList[friendChosen] );
}
// we need to change our client friend states to show that we're not in a session
//
void XBL_F_OnClientLeaveSession()
{
XBL_F_SetState( XONLINE_FRIENDSTATE_FLAG_JOINABLE, false );
XBL_F_SetState( XONLINE_FRIENDSTATE_FLAG_PLAYING, false );
}
bool XBL_F_FriendNotice( void )
{
return (logged_on && XOnlineGetNotification( IN_GetMainController(), XONLINE_NOTIFICATION_FRIEND_REQUEST ));
}
bool XBL_F_GameNotice( void )
{
return (logged_on && XOnlineGetNotification( IN_GetMainController(), XONLINE_NOTIFICATION_GAME_INVITE ));
}
// Get friend details based on gamertag
//
XONLINE_FRIEND* XBL_F_GetFriendFromName( char* name )
{
for( int i = 0; i < numFriends; i++ )
{
if( !strcmp( friendsList[i].szGamertag, name ) )
{
return &friendsList[i];
}
}
return NULL;
}
// ******************************************************
// PRIVATE ** PRIVATE ** PRIVATE ** PRIVATE ** PRIVATE ** PRIVATE
// *******************************************************
//Get the title string of game being played
void XBL_F_GetTitleString(const DWORD titleID, char* titleString)
{
// Is their title ID valid?
if( !titleID )
{
strcpy( titleString, "" );
return;
}
WCHAR playingWstr[MAX_TITLENAME_LEN+1];
int attempt = 0;
// MS suggests polling to get the string:
do
{
attempt++;
XOnlineFriendsGetTitleName( titleID, XC_LANGUAGE_ENGLISH, MAX_TITLE_LENGTH, playingWstr );
} while( playingWstr[0] == 0 && attempt < 10000 );
// If we got something, use it, else just print the title ID
if( attempt < 10000 )
wcharToChar( titleString, playingWstr );
else
sprintf( titleString, "%d", titleID );
}
// periodically update your joinability status (your session has X+ public slots open)
//
void XBL_F_CheckJoinableStatus( bool force )
{
// if not currently in a session, leave
//
if( !(current_state & XONLINE_FRIENDSTATE_FLAG_PLAYING) )
return;
static int pass = 0;
// if its time to check...
//
if( ++pass > JOINABLE_CHECK_RATE || force )
{
// Just count how many clients we have valid info for, and compare to maxclients!
// Need a special case for dedicated host though:
extern cvar_t *sv_maxclients;
int numClients = 0;
int maxClients = com_dedicated->integer ? sv_maxclients->integer : cgs.maxclients;
for( int i = 0; i < maxClients; ++i )
if( xbOnlineInfo.xbPlayerList[i].isActive )
numClients++;
// Set our joinable state based on the number of empty slots.
XBL_F_SetState( XONLINE_FRIENDSTATE_FLAG_JOINABLE, numClients < maxClients );
pass = 0;
}
}