Last active
August 5, 2025 04:21
-
-
Save thsbrown/633e524634a6b932ffcecd8d6da9b0a4 to your computer and use it in GitHub Desktop.
Example of a Service in Command Center Earth (Standard C# class)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using BreakstepStudios.Scripts.Runtime.PlayFab; | |
| using PlayFab; | |
| using PlayFab.ClientModels; | |
| using PlayFab.SharedModels; | |
| using ThomasBrown.PlayFab; | |
| using UniRx; | |
| namespace _Game_Assets.Scripts.Runtime.Systems | |
| { | |
| public class PlayFabFriendsListService | |
| { | |
| /// <summary> | |
| /// The tag in our friends list that denotes confirmed friends (from sent or received friend request) | |
| /// </summary> | |
| public const string FRIEND_CONFIRMED_TAG = "confirmed"; | |
| /// <summary> | |
| /// The tag in our friends list that denotes we have sent a friend request to a specific player | |
| /// </summary> | |
| public const string FRIEND_REQUEST_SENT_TAG = "sent"; | |
| /// <summary> | |
| /// The tag in our friends list that denotes we have received a friend request from a specific player | |
| /// </summary> | |
| public const string FRIEND_REQUEST_RECEIVED_TAG = "received"; | |
| /// <summary> | |
| /// Behavior subject we will utilize to alert subscribers when our friends list has been modified | |
| /// </summary> | |
| private BehaviorSubject<List<FriendInfo>> friendsListModifiedBehaviorSubject; | |
| /// <summary> | |
| /// Behavior subject we will utilize to alert subscribers when our friend requests have been modified | |
| /// </summary> | |
| private BehaviorSubject<List<FriendInfo>> friendRequestsModifiedBehaviorSubject; | |
| /// <summary> | |
| /// The cloud script revision that our friends list service is operating on | |
| /// </summary> | |
| private CloudScriptRevision cloudScriptRevision; | |
| /// <summary> | |
| /// A callback used to allow modification of display names in friends list and friend requests before they are output via | |
| /// <see cref="ObserveFriendsListModified"/>, <see cref="ObserveFriendRequestsModified"/> and <see cref="GetFriendsList"/> | |
| /// </summary> | |
| private Func<FriendInfo, string> displayNameModifierCallback; | |
| public PlayFabFriendsListService() | |
| { | |
| cloudScriptRevision = new CloudScriptRevision(); | |
| //set values to null initially so we have an identifier of when initial values haven't yet loaded | |
| friendsListModifiedBehaviorSubject = new BehaviorSubject<List<FriendInfo>>(null); | |
| friendRequestsModifiedBehaviorSubject = new BehaviorSubject<List<FriendInfo>>(null); | |
| } | |
| /// <summary> | |
| /// Initializes the friends list service with appropriate parameters | |
| /// <para>In most cases initialization should occur once player is signed into PlayFab whereas instantiation can occur before hand.</para> | |
| /// </summary> | |
| /// <param name="cloudScriptRevision"> | |
| /// The cloud script revision that contains our backend leaderboards code. | |
| /// </param> | |
| /// <param name="displayNameModifierCallback"> | |
| /// A callback used to allow modification of display names in friends list and friend requests before they are output via | |
| /// <see cref="ObserveFriendsListModified"/>, <see cref="ObserveFriendRequestsModified"/> and <see cref="GetFriendsList"/> | |
| /// </param> | |
| public void Initialize(CloudScriptRevision cloudScriptRevision = null, Func<FriendInfo, string> displayNameModifierCallback = null) | |
| { | |
| this.displayNameModifierCallback = displayNameModifierCallback ?? (info => info.TitleDisplayName ?? ""); | |
| this.cloudScriptRevision = cloudScriptRevision ?? new CloudScriptRevision(); | |
| } | |
| /// <summary> | |
| /// Retrieves friends list / request from PlayFab sorted as follows: 1. isFriendRequest, 2. isOutgoingRequest, 3.titleDisplayName | |
| /// </summary> | |
| /// <returns>List of our confirmed friends and friend requests sent and received</returns> | |
| //TODO we should likely add a param to GetFriendsList that allows for specifying if we want confirmed friend, unconfirmed friends or both | |
| //TODO we already have this functionality server side so it would just be a matter of specifying the right FunctionName below | |
| public async Task<PlayFabCloudScriptResponse<GetFriendsListResult>> GetFriendsList() | |
| { | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "getFriendsList", | |
| FunctionParameter = new | |
| { | |
| GetFriendsListRequest = new GetFriendsListRequest | |
| { | |
| IncludeFacebookFriends = false, | |
| IncludeSteamFriends = false | |
| } | |
| }, | |
| GeneratePlayStreamEvent = true, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync<GetFriendsListResult>(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| friendsListModifiedBehaviorSubject.OnNext(result.FunctionResult.Friends.Where(x => !x.IsFriendRequest).ToList()); | |
| friendRequestsModifiedBehaviorSubject.OnNext(result.FunctionResult.Friends.Where(x => x.IsFriendRequest).ToList()); | |
| //Sort our combined friends / friends requests list and return it | |
| var returnList = await ObserveFriendsListModified.Take(1).ToTask(); | |
| returnList.AddRange(await ObserveFriendRequestsModified.Take(1).ToTask()); | |
| result.FunctionResult.Friends = returnList; | |
| return result; | |
| } | |
| /// <summary> | |
| /// Sends a friend request to a player | |
| /// </summary> | |
| /// <param name="friendIdInfo">The type of friend id info we are sending to server</param> | |
| /// <param name="friendId">The identification for the friend we are requesting (based on friendIdInfo above)</param> | |
| /// <returns>The cloud script result containing the FriendInfo of the player friend request was sent to</returns> | |
| public async Task<PlayFabCloudScriptResponse<FriendInfo>> SendFriendRequest(FriendIdInfo friendIdInfo, string friendId) | |
| { | |
| if (friendIdInfo != FriendIdInfo.PlayFabId) | |
| { | |
| var getAccountInfoRequest = new GetAccountInfoRequest(); | |
| switch (friendIdInfo) | |
| { | |
| case FriendIdInfo.TitleDisplayName: | |
| getAccountInfoRequest.TitleDisplayName = friendId; | |
| break; | |
| case FriendIdInfo.Username: | |
| getAccountInfoRequest.Username = friendId; | |
| break; | |
| case FriendIdInfo.Email: | |
| getAccountInfoRequest.Email = friendId; | |
| break; | |
| case FriendIdInfo.PlayFabId: | |
| default: | |
| throw new ArgumentOutOfRangeException(nameof(friendIdInfo), friendIdInfo, null); | |
| } | |
| var getAccountInfoResult = await PlayFabClientAPIWrapper.GetAccountInfoAsync(getAccountInfoRequest); | |
| if (getAccountInfoResult.ContainsError) | |
| { | |
| //convert our error here to a cloudscript response logs error to make error checking easier | |
| return PlayFabCloudScriptResponse.ConvertToLogsError<FriendInfo>(getAccountInfoResult.Error); | |
| } | |
| friendId = getAccountInfoResult.Result.AccountInfo.PlayFabId; | |
| } | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "sendFriendRequest", | |
| GeneratePlayStreamEvent = true, | |
| FunctionParameter = new | |
| { | |
| FriendPlayFabId = friendId | |
| }, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync<FriendInfo>(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| //remove to ensure we always have latest info for the player and then pass along updated friend requests list | |
| var friendRequests = friendRequestsModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendRequests.RemoveAll(x => x.FriendPlayFabId == result.FunctionResult.FriendPlayFabId); | |
| friendRequests.Add(result.FunctionResult); | |
| friendRequestsModifiedBehaviorSubject.OnNext(friendRequests); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Accepts an incoming friend request | |
| /// </summary> | |
| /// <param name="friendPlayFabId">The PlayFabId of the friend we want to accept friend request from</param> | |
| /// <returns>The cloud script result containing the FriendInfo of the player that we accept friend reqeust for</returns> | |
| public async Task<PlayFabCloudScriptResponse<FriendInfo>> AcceptFriendRequest(string friendPlayFabId) | |
| { | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "acceptFriendRequest", | |
| FunctionParameter = new | |
| { | |
| FriendPlayFabId = friendPlayFabId | |
| }, | |
| GeneratePlayStreamEvent = true, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync<FriendInfo>(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| //remove to ensure we always have latest info for the player and then pass along updated friends list | |
| var friendsList = friendsListModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendsList.RemoveAll(x => x.FriendPlayFabId == result.FunctionResult.FriendPlayFabId); | |
| friendsList.Add(result.FunctionResult); | |
| friendsListModifiedBehaviorSubject.OnNext(friendsList); | |
| //ensure we remove the added friend from our friend requests list and then pass along updated friend requests list | |
| var friendRequests = friendRequestsModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendRequests.RemoveAll(x => x.FriendPlayFabId == result.FunctionResult.FriendPlayFabId); | |
| friendRequestsModifiedBehaviorSubject.OnNext(friendRequests); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Denys an incoming friend request | |
| /// </summary> | |
| /// <param name="friendPlayFabId">The playfab id of the friend we want to deny the friend request from</param> | |
| /// <returns>The cloud script response containing the result of the operation</returns> | |
| public async Task<PlayFabCloudScriptResponse> DenyFriendRequest(string friendPlayFabId) | |
| { | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "denyFriendRequest", | |
| FunctionParameter = new | |
| { | |
| FriendPlayFabId = friendPlayFabId | |
| }, | |
| GeneratePlayStreamEvent = true, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| //ensure we remove the denied friend from our friend requests list and then pass along updated friend requests list | |
| var friendRequests = friendRequestsModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendRequests.RemoveAll(x => x.FriendPlayFabId == friendPlayFabId); | |
| friendRequestsModifiedBehaviorSubject.OnNext(friendRequests); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Cancels an outgoing friend request | |
| /// </summary> | |
| /// <param name="friendPlayFabId">The playfab id of the friend we want to cancel the friend request to</param> | |
| /// <returns>The cloud script response containing the result of the operation</returns> | |
| public async Task<PlayFabCloudScriptResponse> CancelFriendRequest(string friendPlayFabId) | |
| { | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "cancelFriendRequest", | |
| FunctionParameter = new | |
| { | |
| FriendPlayFabId = friendPlayFabId | |
| }, | |
| GeneratePlayStreamEvent = true, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| //ensure we remove the cancelled friend from our friend requests list and then pass along updated friend requests list | |
| var friendRequests = friendRequestsModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendRequests.RemoveAll(x => x.FriendPlayFabId == friendPlayFabId); | |
| friendRequestsModifiedBehaviorSubject.OnNext(friendRequests); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Unfriends a player, removing them from friendsList and removing you from their friends list. | |
| /// </summary> | |
| /// <param name="friendPlayFabId">The playFabId of the player on our friends list we want to unfriend</param> | |
| /// <returns>The cloud script response containing the result of the operation</returns> | |
| public async Task<PlayFabCloudScriptResponse> UnFriend(string friendPlayFabId) | |
| { | |
| var executeCloudScriptRequest = new ExecuteCloudScriptRequest | |
| { | |
| FunctionName = "unFriend", | |
| FunctionParameter = new | |
| { | |
| FriendPlayFabId = friendPlayFabId | |
| }, | |
| GeneratePlayStreamEvent = true, | |
| RevisionSelection = cloudScriptRevision.RevisionOption, | |
| SpecificRevision = cloudScriptRevision.RevisionNumber | |
| }; | |
| var result = | |
| await PlayFabClientAPIWrapper.ExecuteCloudScriptAsync(executeCloudScriptRequest); | |
| if (result.ContainsError) | |
| { | |
| return result; | |
| } | |
| //ensure we remove the unfriended friend from our friend list and then pass along updated friend list | |
| var friendList = friendsListModifiedBehaviorSubject.Value ?? new List<FriendInfo>(); | |
| friendList.RemoveAll(x => x.FriendPlayFabId == friendPlayFabId); | |
| friendsListModifiedBehaviorSubject.OnNext(friendList); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Completes, disposes and then recreates <see cref="ObserveFriendsListModified"/> and <see cref="ObserveFriendRequestsModified"/> | |
| /// initializing them with null values. | |
| /// </summary> | |
| public void ResetFriendsModifiedObservables() | |
| { | |
| //clear friendsList Observable | |
| try | |
| { | |
| friendsListModifiedBehaviorSubject.OnCompleted(); | |
| } | |
| finally | |
| { | |
| friendsListModifiedBehaviorSubject.Dispose(); | |
| friendsListModifiedBehaviorSubject = new BehaviorSubject<List<FriendInfo>>(null); | |
| } | |
| //clear friendRequests Observable | |
| try | |
| { | |
| friendRequestsModifiedBehaviorSubject.OnCompleted(); | |
| } | |
| finally | |
| { | |
| friendRequestsModifiedBehaviorSubject.Dispose(); | |
| friendRequestsModifiedBehaviorSubject = new BehaviorSubject<List<FriendInfo>>(null); | |
| } | |
| } | |
| /// <summary> | |
| /// Observable firing when our friends list is modified returns the latest friends list | |
| /// </summary> | |
| public IObservable<List<FriendInfo>> ObserveFriendsListModified | |
| { | |
| get | |
| { | |
| return friendsListModifiedBehaviorSubject | |
| .Where(x => x != null) | |
| .Select(x => | |
| { | |
| return x | |
| .Select(x => { x.TitleDisplayName = displayNameModifierCallback(x); return x; }) | |
| .OrderBy(x => x.TitleDisplayName) | |
| .ToList(); | |
| }); | |
| } | |
| } | |
| /// <summary> | |
| /// Observable firing when our friends requests are modified returns the latest friend requests (ordered by outgoing first) | |
| /// </summary> | |
| public IObservable<List<FriendInfo>> ObserveFriendRequestsModified | |
| { | |
| get | |
| { | |
| return friendRequestsModifiedBehaviorSubject | |
| .Where(x => x != null) | |
| .Select(x => | |
| { | |
| return x | |
| .Select(x => { x.TitleDisplayName = displayNameModifierCallback(x); return x; }) | |
| .OrderBy(x => x.IsOutgoingFriendRequest) | |
| .ThenBy(x => x.TitleDisplayName).ToList(); | |
| }); | |
| } | |
| } | |
| /// <inheritdoc cref="PlayFab.ClientModels.GetFriendsListResult"/> | |
| /// <remarks> | |
| /// Mirrors <see cref="PlayFab.ClientModels.GetFriendsListResult"/> but uses | |
| /// <see cref="FriendInfo"/> instead of <see cref="PlayFab.ClientModels.FriendInfo"/> | |
| /// </remarks> | |
| [Serializable] | |
| public class GetFriendsListResult : PlayFabResultCommon | |
| { | |
| /// <summary> | |
| /// Array of friends found. | |
| /// </summary> | |
| public List<FriendInfo> Friends; | |
| } | |
| /// <summary> | |
| /// Extended FriendInfo used to derive additional data about a FriendInfo such as if it's apart of a friend request | |
| /// </summary> | |
| public class FriendInfo : PlayFab.ClientModels.FriendInfo | |
| { | |
| /// <summary> | |
| /// Returns true if <see cref="IsIncomingFriendRequest"/> or <see cref="IsOutgoingFriendRequest"/> is true | |
| /// <remarks>If this is false, then we have a confirmed friend that should be marked with a <see cref="PlayFabFriendsListService.FRIEND_CONFIRMED_TAG"/></remarks> | |
| /// </summary> | |
| public bool IsFriendRequest => IsIncomingFriendRequest || IsOutgoingFriendRequest; | |
| /// <summary> | |
| /// Returns true if we have a friend in our friends list marked with a <see cref="PlayFabFriendsListService.FRIEND_REQUEST_RECEIVED_TAG"/> | |
| /// </summary> | |
| public bool IsIncomingFriendRequest => Tags.Contains(FRIEND_REQUEST_RECEIVED_TAG); | |
| /// <summary> | |
| /// Returns true if we have a friend in our friends list marked with a <see cref="PlayFabFriendsListService.FRIEND_REQUEST_SENT_TAG"/> | |
| /// </summary> | |
| public bool IsOutgoingFriendRequest => Tags.Contains(FRIEND_REQUEST_SENT_TAG); | |
| } | |
| } | |
| /// <summary> | |
| /// Used for identifying type of f |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment