GS2-Friend

Friend/follow feature

Provides the ability to create a game-specific social graph. While social graphs are a feature that platforms also offer, there is also value in creating your own in-game social graph.

Players tend to be reluctant to register platform-wide friend relationships. This is because there is a case of “I want to play the game I’m playing with this person, but I don’t want to play other games with them”. If we treat the social graph provided by the platform as reflecting a relationship closer to real life, and the game-specific social graph as reflecting a relationship within that game, players will feel more comfortable using the friend feature.

Friends

In order to become friends, a mutual, consensual relationship is required.

A friend relationship is established when one of the players who wishes to become a friend sends a friend request to the other player, and the player who receives the friend request accepts it.

Follow

A follow can be established without the consent of the other party.

The person being followed has no way of knowing who is following them as a list. This specification may seem strange when you imagine the specifications of a social network follow. However, we have looked at the specifications of many games that implement the follow feature in-game and found that it is not necessary.

If a player who follows you goes on an adventure with your ghost character, it is sufficient to share the rewards obtained from the adventure, and there is little interest in knowing who accompanied you.

You cannot get a list, but when delivering a reward message to GS2-Inbox to share rewards earned from an adventure, it is possible to convey profiles by including the user IDs of the players who adventured together in the message payload.

Profile

GS2-Friend provides an area to hold a player’s profile. The profile can have any value and there are three areas.

  • “Public Profile”, which can be freely viewed by other players.
  • “Follower Profile”, which can be viewed by players who follow the player.
  • “Friend Profile”, which can be viewed by friends.

Each of these can be used for different purposes.

Blacklist

After playing a game for a long time, there may be players who make you feel uncomfortable. This feature allows you to create a list of such players and make it permanent.

It is only a persistent feature, and simply adding them here does not perform any function. It is necessary to pass the list to GS2-Matchmaking’s matchmaking conditions, or use the list recorded here separately as needed.

Script Triggers

Setting followScript unfollowScript sendRequestScript cancelRequestScript acceptRequestScript rejectRequestScript deleteFriendScript updateProfileScript in the namespace allows custom scripts to be executed before and after each operation such as follows, friend requests, and profile updates. Scripts support both synchronous and asynchronous execution, with asynchronous processing enabling external integration through GS2-Script or Amazon EventBridge. These settings can be templated and managed with GS2-Deploy or language-specific CDKs.

Main event triggers and script setting names are:

  • followScript (completion notification: followDone): before and after follows
  • unfollowScript (completion notification: unfollowDone): before and after unfollows
  • sendRequestScript (completion notification: sendRequestDone): before and after sending friend requests
  • cancelRequestScript (completion notification: cancelRequestDone): before and after canceling friend requests
  • acceptRequestScript (completion notification: acceptRequestDone): before and after accepting friend requests
  • rejectRequestScript (completion notification: rejectRequestDone): before and after rejecting friend requests
  • deleteFriendScript (completion notification: deleteFriendDone): before and after deleting friends
  • updateProfileScript (completion notification: updateProfileDone): before and after profile updates

Push Notifications

The main push notifications and their configuration names are as follows:

  • followNotification: Notifies when followed
  • receiveRequestNotification: Notifies when a friend request is received
  • cancelRequestNotification: Notifies when a friend request is canceled
  • acceptRequestNotification: Notifies when a friend request is accepted
  • rejectRequestNotification: Notifies when a friend request is rejected
  • deleteFriendNotification: Notifies when a friend is deleted

All notifications can be sent via GS2-Gateway and can be forwarded to offline devices as mobile push notifications.

Transaction Actions

GS2-Friend provides the following transaction actions:

  • Acquire Action: Updating Profile

By using “Updating Profile” as an acquire action, it is possible to integrate profile updates into a series of transactions, such as changing a player name by consuming in-game currency.

Example implementation

Updating Profile

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Profile(
    ).UpdateProfileAsync(
        publicProfile: "public",
        followerProfile: "follower",
        friendProfile: "friend"
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Profile(
    );
    const auto Future = Domain->UpdateProfile(
        "public", // publicProfile
        "follower", // followerProfile
        "friend" // friendProfile
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();

Get own profile

    var item = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Profile(
    ).ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Profile(
    );
    const auto item = Domain.Model();

Get someone else’s public profile

    var item = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).PublicProfile(
    ).ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->PublicProfile(
    );
    const auto item = Domain.Model();

Send a friend request

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestAsync(
        targetUserId: "user-0002"
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SendRequest(
        "user-0002"
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Get a list of friend requests you have sent

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SendRequests(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendRequestPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Get a list of received friend requests

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveRequestsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SendRequests(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendRequestPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Accept a friend request

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveFriendRequest(
        fromUserId: "user-0002"
    ).AcceptAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->ReceiveFriendRequest(
        nullptr, // targetUserId
        "user-0002" // fromUserId
    )->Accept(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Reject a friend request

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveFriendRequest(
        fromUserId: "user-0002"
    ).RejectAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->ReceiveFriendRequest(
        nullptr, // targetUserId
        "user-0002" // fromUserId
    )->Reject(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Get a list of friends

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FriendsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Friends( // withProfile
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendUserPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Delete a friend

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Friend(
        withProfile: null
    ).FriendUser(
        targetUserId: "user-0002"
    ).DeleteFriendAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Friend(
        nullptr // withProfile
    )->FriendUser(
        "user-0002" // targetUserId
    )->DeleteFriend(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Follow a player

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowUser(
        targetUserId: "user-0002",
        withProfile: true
    ).FollowAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->FollowUser(
        "user-0002", // targetUserId
        true // withProfile
    )->Follow(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Get a list of followers

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Follows(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFollowUserPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Unfollow a player

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowUser(
        targetUserId: "user-0002",
        withProfile: true
    ).UnfollowAsync(
    );
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->FollowUser(
        "user-0002", // targetUserId
        true // withProfile
    );
    const auto Future = Domain->Unfollow(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Detailed Reference