Getting and setting player presence

Rajen Kishna, Technical Account Manager, Epic Games |
October 12, 2021
Welcome back to our regular series in which we dive into different aspects of using Epic Online Services for your projects. We implemented login and logout functionality using the Auth Interface in the previous article, so now let’s take a look at getting and setting presence status. Here’s what we’ll cover:
 

When to use the Presence Interface

The Presence Interface is used for two purposes:
 
  • Set (rich) presence status for the authenticated player
    • In addition to setting presence status, you can also use it to pass arbitrary key/value pair data to a player’s friends. This can be anything that your game uses to show additional context, like why they’re not able to join a game (e.g. for an invite-only party) or what character skin the player is using. Note that this is not real-time data, so it’s best to pass less time-sensitive data this way.
  • Query presence information of the player’s friends
    • Because the SDK will only return the player’s friends who have consented to the permissions requested by your game, this means that only friends who have logged in and consented to the game, or purchased the game through the Epic Games Store—as consent will be implicitly granted upon purchase—will be queryable. 

I want to make sure we cover both scenarios here, but we’ll start to see the real-world use-case of querying presence in the next article, where we’ll cover the Friends Interface.

Adding presence permission to the application

Speaking of consent, we’ll have to add the Online Presence (presence) permission to our Epic Account Services (EAS) application, in order for players to consent to the use of the presence APIs.
 
  1. Log in to the Developer Portal at https://dev.epicgames.com/portal/.
  2. Navigate to your product and click on Epic Account Services in the left menu.
  3. Click on the Configure button next to the application you’re using.
  4. Click on the Permissions tab at the top right.
  5. Enable the Online Presence (presence) permission. Note that you’ll see an update to the preview at the left, which now includes consent to presence information.
  6. Click Save to confirm.

Developer Portal Online Presence Permission

Adding the Online Presence permission to an EAS application


Lastly, we can open ApplicationSettings.cs in our Visual Studio solution and add the presence ScopeFlag:

public AuthScopeFlags ScopeFlags
{
    get
    {
        return AuthScopeFlags.BasicProfile | AuthScopeFlags.Presence;
    }
}


When you run the app and trigger Auth login, you should now see the updated consent screen in the browser and still be able to successfully complete authentication.

Querying for player presence

As mentioned, we’ll do a more generalized implementation of the query functionality, so we can reuse this in the next article to query the presence of friends. As this is the first interface (aside from authentication) we’re implementing, we’ll have a few extra steps to build out our UI to have tabs for each service we’ll add.
 
  1. Start by creating a folder called Views in the root of our project.
  2. Add a new UserControl to this folder, called PresenceView
  3. Paste in the following XAML, replacing the empty Grid node:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <StackPanel Grid.Column="1">
        <Button Width="100" Height="23" Margin="2" Content="Query presence" Command="{Binding PresenceQuery}" />
        <Button Width="100" Height="23" Margin="2" Content="Modify presence" Command="{Binding PresenceModifyStatus}" />
    </StackPanel>

    <StackPanel Grid.Column="0">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Status:" Margin="2" />
            <ComboBox Width="150" HorizontalAlignment="Left" SelectedItem="{Binding Status}" ItemsSource="{Binding StatusOptions}" IsEnabled="{Binding ProductIdText, Converter={StaticResource StringToBooleanConverter}}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="ProductId:" Margin="2" />
            <TextBlock Text="{Binding ProductIdText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="ProductVersion:" Margin="2" />
            <TextBlock Text="{Binding ProductVersionText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Platform:" Margin="2" />
            <TextBlock Text="{Binding PlatformText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Rich Text:" Margin="2" />
            <TextBlock Text="{Binding RichText}" Margin="2" />
        </StackPanel>
    </StackPanel>
</Grid>

 
  • As you’ll see in the design view, this adds a simple UI to show the presence information we’ll query, with the option of changing the status with a corresponding Modify button.
 
  1. Open PresenceView.xaml.cs and define the DataContext:

public partial class PresenceView : UserControl
{
    public PresenceViewModel ViewModel { get { return ViewModelLocator.Presence; } }

    public PresenceView()
    {
        InitializeComponent();
        DataContext = ViewModel;
    }
}

 
  1. Add a PresenceViewModel.cs class to the ViewModels folder:

public class PresenceViewModel : BindableBase
{
    private Status _status;
    public Status Status
    {
        get { return _status; }
        set { SetProperty(ref _status, value); }
    }

    private string _productIdText;
    public string ProductIdText
    {
        get { return _productIdText; }
        set { SetProperty(ref _productIdText, value); }
    }

    private string _productVersionText;
    public string ProductVersionText
    {
        get { return _productVersionText; }
        set { SetProperty(ref _productVersionText, value); }
    }

    private string _platformText;
    public string PlatformText
    {
        get { return _platformText; }
        set { SetProperty(ref _platformText, value); }
    }

    private string _richText;
    public string RichText
    {
        get { return _richText; }
        set { SetProperty(ref _richText, value); }
    }

    private List<Enum> _statusOptions;
    public List<Enum> StatusOptions
    {
        get { return _statusOptions; }
        set { SetProperty(ref _statusOptions, value); }
    }
}

 
  1. Add a static reference to PresenceViewModel to ViewModelLocator:

private static PresenceViewModel _presence;
public static PresenceViewModel Presence
{
    get { return _presence ??= new PresenceViewModel(); }
}

 
  1. Add a PresenceService.cs class to the Services folder:

public static void Query(EpicAccountId targetUserId)
{
    var queryPresenceOptions = new QueryPresenceOptions()
    {
        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
        TargetUserId = targetUserId
    };

    ViewModelLocator.Main.StatusBarText = "Querying user presence...";

   App.Settings.PlatformInterface.GetPresenceInterface()
.QueryPresence(queryPresenceOptions, null, (QueryPresenceCallbackInfo queryPresenceCallbackInfo) =>
    {
        Debug.WriteLine($"QueryPresence {queryPresenceCallbackInfo.ResultCode}");

        if (queryPresenceCallbackInfo.ResultCode == Result.Success)
        {
            Copy(queryPresenceCallbackInfo.LocalUserId, queryPresenceCallbackInfo.TargetUserId);
        }
        else if (Common.IsOperationComplete(queryPresenceCallbackInfo.ResultCode))
        {
            Debug.WriteLine("Query presence failed: " + queryPresenceCallbackInfo.ResultCode);
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });
}

public static void Copy(EpicAccountId localUserId, EpicAccountId targetUserId)
{
    var copyPresenceOptions = new CopyPresenceOptions()
    {
        LocalUserId = localUserId,
        TargetUserId = targetUserId
    };

    var result = App.Settings.PlatformInterface.GetPresenceInterface()
.CopyPresence(copyPresenceOptions, out var info);
    if (result == Result.Success)
    {
        ViewModelLocator.Main.StatusBarText = "User presence retrieved.";

        if (localUserId == targetUserId)
        {
            ViewModelLocator.Presence.Status = info.Status;
            ViewModelLocator.Presence.ProductIdText = info.ProductId;
            ViewModelLocator.Presence.ProductVersionText = info.ProductVersion;
            ViewModelLocator.Presence.PlatformText = info.Platform;
            ViewModelLocator.Presence.RichText = info.RichText;
        }

        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}

 
  • Here we implement two methods: 
    • Query() for when we want to retrieve the presence of a user other than the authenticated player. We follow the same pattern as we’ve seen using the Auth Interface: instantiate an options class, call QueryPresence on the Presence Interface passing in a callback method, and validating the result.
    • Copy() as QueryPresence caches the results locally and presence is automatically available after a successful Auth Interface Login call for the authenticated player. We use CopyPresence to retrieve the actual presence information from cache.
  • In this case where the local user and target user are the same, we use the presence information to populate PresenceViewModel, so our information is displayed in the UI.
 
  1. Add a PresenceQueryCommand.cs class to the Commands folder:

public class PresenceQueryCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    }

    public override void Execute(object parameter)
    {
        if (parameter == null)
        {
            PresenceService.Copy(EpicAccountId
.FromString(ViewModelLocator.Main.AccountId), EpicAccountId.FromString(ViewModelLocator.Main.AccountId));
        }
    }
}

 
  • We can check for a null parameter to skip our Query method here, but we’ll implement Query in the next article covering Friends.
 
  1. Now open PresenceViewModel.cs again and instantiate PresenceQueryCommand:

public PresenceQueryCommand PresenceQuery { get; set; }

public PresenceViewModel()
{
    StatusOptions = Enum.GetValues(Status.GetType()).Cast<Enum>().ToList();

    PresenceQuery = new PresenceQueryCommand();
}

 
  • Note, we’re also instantiating StatusOptions with all the options that the Epic.OnlineServices.Presence.Status enum holds.
 
  1. Add the following line in the ViewModelLocator RaiseAuthCanExecuteChanged() method to ensure that the Query presence button is only enabled when a user is logged in:

Presence.PresenceQuery.RaiseCanExecuteChanged();
 
  1. Finally, add the PresenceView to our MainWindow TabControl:

<TabItem x:Name="Presence" Header="Presence">
    <views:PresenceView />
</TabItem>


Now we can run the app, authenticate, and consent to the updated permissions in the browser, after which we’ll be able to use the Query presence button to get the current presence status and associated information from the cache.
 
App MainWindow Queried Presence UI
Presence information for the authenticated player

Note that you should also always implement AddNotifyOnPresenceChanged in a production game to get notified when there’s a change in presence status, without having to call QueryPresence again.

Setting presence status

Now we can move to setting the presence status based on the selection we make in the dropdown. 
 
  1. Open PresenceService.cs and add the following method:

public static void ModifyStatus()
{
    var createPresenceModificationOptions = new CreatePresenceModificationOptions()
    {
        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
    };

    ViewModelLocator.Main.StatusBarText = "Creating presence modification...";

    var result = App.Settings.PlatformInterface.GetPresenceInterface()
.CreatePresenceModification(createPresenceModificationOptions, out var presenceModification);

    Debug.WriteLine($"CreatePresenceModification {result}");

    if (result == Result.Success)
    {
        var setStatusOptions = new PresenceModificationSetStatusOptions()
        {
            Status = ViewModelLocator.Presence.Status
        };

        result = presenceModification.SetStatus(setStatusOptions);
        Debug.WriteLine($"SetStatus {result}");

        var setPresenceOptions = new SetPresenceOptions()
        {
            LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
            PresenceModificationHandle = presenceModification
        };

        ViewModelLocator.Main.StatusBarText = "Setting presence status...";

        App.Settings.PlatformInterface.GetPresenceInterface()
.SetPresence(setPresenceOptions, null, (SetPresenceCallbackInfo setPresenceCallbackInfo) =>
        {
            Debug.WriteLine($"SetPresence {setPresenceCallbackInfo.ResultCode}");
            if (Common.IsOperationComplete(setPresenceCallbackInfo.ResultCode))
            {
                if (presenceModification != null)
                {
                    presenceModification.Release();
                    presenceModification = null;
                }

                ViewModelLocator.Main.StatusBarText = string.Empty;
            }
        });
    }
    else if (Common.IsOperationComplete(result))
    {
        Debug.WriteLine("Create presence modification failed: " + result);
        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}

 
  • Presence is modified by first creating a PresenceModification object, manipulating that object, and passing it to the SetPresence method.
  • In this case, we only use SetStatus to change the status, but you can also modify the Rich Text associated with presence (to indicate what level the player is playing, for example). You can also add, replace, or delete custom presence data to pass arbitrary key/value pair data, as mentioned above.
  • Lastly, you can also set JoinInfo on presence, which you can use to pass information from the EOS Overlay to your game to enable friends to join the game based on your game’s logic. When doing so, you can subscribe to overlay notifications to be informed when a player accepts a join request, for example.
 
  1. We’ll also add a trigger to the Modify presence command’s CanExecuteChanged behavior, so it is only available after we do an initial query for the player’s presence:

Add: 

ViewModelLocator.Presence.PresenceModifyStatus.
RaiseCanExecuteChanged();


To the following code in PresenceService.Copy():

if (result == Result.Success)
{
    ViewModelLocator.Main.StatusBarText = "User presence retrieved.";

    ViewModelLocator.Presence.Status = info.Status;
    ViewModelLocator.Presence.ProductIdText = info.ProductId;
    ViewModelLocator.Presence.ProductVersionText = info.ProductVersion;
    ViewModelLocator.Presence.PlatformText = info.Platform;
    ViewModelLocator.Presence.RichText = info.RichText;

    ViewModelLocator.Main.StatusBarText = string.Empty;

ViewModelLocator.Presence.PresenceModifyStatus.
RaiseCanExecuteChanged();
}

 
  1. Add a PresenceModifyStatusCommand.cs class to the Commands folder:

public class PresenceModifyStatusCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Presence.ProductIdText);
    }

    public override void Execute(object parameter)
    {
        PresenceService.ModifyStatus();
    }
}

 
  1. Declare and initialize our new command in PresenceViewModel.cs:

public PresenceModifyStatusCommand PresenceModifyStatus { get; set; }
PresenceModifyStatus = new PresenceModifyStatusCommand();


Now when we run the app, we can query for our initial presence status, change the status with the dropdown and hit Modify presence to set our new status.

Get the code

As always, make sure you get the code and follow step five and nine of the Setting up our C# solution section in the Setting up a C# solution for EOS in Visual Studio 2019 article to add the SDK to the solution, and you edit ApplicationSettings.cs to include your SDK credentials.
Let us know if you have any questions or feedback, and I’ll join you for the next article, where we’ll dive into querying for Epic friends and their status. For other articles in this series, please check out the series reference.

    We succeed when you succeed

    Epic believes in an open, integrated games community. By offering our online services to everyone for free, we aim to empower more developers to serve their own player communities.