# Use Friend In-Game

In this How-to, we will implement a mutual Friends system in-game using LootLocker. You will learn how to send friend requests, accept or decline them, list your friends and pending requests, and handle blocking relationships. This creates opt-in, two-way connections between players.

## Prerequisites

* [A LootLocker account and a created game](https://lootlocker.com/sign-up)
* Multiple player profiles (e.g. create via separate sessions)
* [An active Game Session](/players/authentication.md)
* (Optional) Some player ULIDs collected from the [Player Manager](https://console.lootlocker.com/players)
* (Optional) Familiarity with [managing relationships in the Web Console](/players/friends-and-followers/how-to/manage-relationships-web-console.md)

## Friends vs Followers

Friends are a two-way relationship requiring mutual consent. Player A sends a friend request to Player B, and they only become friends once B accepts. This powers you to build:

* Private messaging systems
* Co-op gameplay invitations
* Trusted player groups for guilds or parties
* Mutual achievement sharing and challenges

If you need one-way relationships that don't require approval, use Followers instead (see: [Use Followers in Game](/players/friends-and-followers/how-to/use-followers-in-game.md)).

## Core Concepts

* **Friends list**: Players who have mutually accepted each other's friend requests.
* **Incoming requests**: Friend requests sent to the current player by others.
* **Outgoing requests**: Friend requests the current player has sent but not yet accepted/declined.
* **Blocked players**: Players who cannot send friend requests or interact with the current player.
* **Offset pagination**: Lists use page numbers and per-page counts to paginate the data.

## Typical UX Flow

1. Open Social UI → fetch friend counts for header
2. Browse friends list, incoming requests, or outgoing requests in tabs
3. Send friend request from player profile → appears in target's incoming list
4. Accept/decline incoming requests → moves to friends list or disappears
5. Cancel outgoing requests if changed mind
6. Block/unblock players to manage harassment

## List Friends and Requests

The foundation of any friends system is showing existing relationships and pending requests.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Listing Friends, Incoming/Outgoing Requests with Offset Pagination
// Unity Friends API uses Page (0-indexed) and PerPage to paginate the data.
// Assumptions:
// - UI layer methods: UIFriendsList.SetLoading(bool), UIFriendsList.ReplaceItems(array), etc.
// - Error handling via LogFailure(context, response)

using System.Collections.Generic;
using LootLocker.Requests;

public class FriendsListManager
{
    private readonly List<LootLockerAcceptedFriend> _friends = new();
    private readonly List<LootLockerFriend> _incomingRequests = new();
    private readonly List<LootLockerFriend> _outgoingRequests = new();
    private readonly List<LootLockerBlockedPlayer> _blockedPlayers = new();

    // Page tracking (0-indexed)
    private int _friendsCurrentPage = 0;
    private int _incomingCurrentPage = 0;
    private int _outgoingCurrentPage = 0;
    private int _blockedCurrentPage = 0;

    private const int PageSize = 20;

    // Friends List
    public void LoadFriendsPage()
    {
        _friends.Clear();
        _friendsCurrentPage = 0;
        UIFriendsList.SetLoading(true);
        
        LootLockerSDKManager.ListFriendsPaginated(PageSize, _friendsCurrentPage, (resp) =>
        {
            UIFriendsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Friends list", resp);
                return;
            }

            if (resp.friends != null)
            {
                _friends.AddRange(resp.friends);
                UIFriendsList.ReplaceItems(resp.friends);
            }

            bool hasMore = resp.friends != null && resp.pagination.total > _friends.Length;
            UIFriendsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreFriends()
    {
        _friendsCurrentPage++;
        UIFriendsList.SetLoading(true);
        
        LootLockerSDKManager.ListFriendsPaginated(PageSize, _friendsCurrentPage, (resp) =>
        {
            UIFriendsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Friends list (more)", resp);
                _friendsCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.friends != null && resp.friends.Length > 0)
            {
                _friends.AddRange(resp.friends);
                UIFriendsList.AppendItems(resp.friends);
            }

            bool hasMore = resp.friends != null && resp.pagination.total > _friends.Length;
            UIFriendsList.SetHasMorePages(hasMore);
        });
    }

    // Incoming Friend Requests
    public void LoadIncomingRequestsFirstPage()
    {
        _incomingRequests.Clear();
        _incomingCurrentPage = 0;
        UIIncomingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(PageSize, _incomingCurrentPage, (resp) =>
        {
            UIIncomingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Incoming requests", resp);
                return;
            }

            if (resp.incoming != null)
            {
                _incomingRequests.AddRange(resp.incoming);
                UIIncomingRequestsList.ReplaceItems(resp.incoming);
            }

            bool hasMore = resp.incoming != null && resp.pagination.total > _incomingRequests.Length;
            UIIncomingRequestsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreIncomingRequests()
    {
        _incomingCurrentPage++;
        UIIncomingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(PageSize, _incomingCurrentPage, (resp) =>
        {
            UIIncomingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Incoming requests (more)", resp);
                _incomingCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.incoming != null && resp.incoming.Length > 0)
            {
                _incomingRequests.AddRange(resp.incoming);
                UIIncomingRequestsList.AppendItems(resp.incoming);
            }

            bool hasMore = resp.incoming != null && resp.pagination.total > _incomingRequests.Length;
            UIIncomingRequestsList.SetHasMorePages(hasMore);
        });
    }

    // Outgoing Friend Requests
    public void LoadOutgoingRequestsFirstPage()
    {
        _outgoingRequests.Clear();
        _outgoingCurrentPage = 0;
        UIOutgoingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(PageSize, _outgoingCurrentPage, (resp) =>
        {
            UIOutgoingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Outgoing requests", resp);
                return;
            }

            if (resp.outgoing != null)
            {
                _outgoingRequests.AddRange(resp.outgoing);
                UIOutgoingRequestsList.ReplaceItems(resp.outgoing);
            }

            bool hasMore = resp.outgoing != null && resp.pagination.total > _outgoingRequests.Length;
            UIOutgoingRequestsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreOutgoingRequests()
    {
        _outgoingCurrentPage++;
        UIOutgoingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(PageSize, _outgoingCurrentPage, (resp) =>
        {
            UIOutgoingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Outgoing requests (more)", resp);
                _outgoingCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.outgoing != null && resp.outgoing.Length > 0)
            {
                _outgoingRequests.AddRange(resp.outgoing);
                UIOutgoingRequestsList.AppendItems(resp.outgoing);
            }

            bool hasMore = resp.outgoing != null && resp.pagination.total > _outgoingRequests.Length;
            UIOutgoingRequestsList.SetHasMorePages(hasMore);
        });
    }

    // Blocked Players
    public void LoadBlockedPlayersFirstPage()
    {
        _blockedPlayers.Clear();
        _blockedCurrentPage = 0;
        UIBlockedPlayersList.SetLoading(true);
        
        LootLockerSDKManager.ListBlockedPlayersPaginated(PageSize, _blockedCurrentPage, (resp) =>
        {
            UIBlockedPlayersList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Blocked players", resp);
                return;
            }

            if (resp.blocked != null)
            {
                _blockedPlayers.AddRange(resp.blocked);
                UIBlockedPlayersList.ReplaceItems(resp.blocked);
            }

            bool hasMore = resp.blocked != null && resp.pagination.total > _blockedPlayers.Length;
            UIBlockedPlayersList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreBlockedPlayers()
    {
        _blockedCurrentPage++;
        UIBlockedPlayersList.SetLoading(true);
        
        LootLockerSDKManager.ListBlockedPlayersPaginated(PageSize, _blockedCurrentPage, (resp) =>
        {
            UIBlockedPlayersList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Blocked players (more)", resp);
                _blockedCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.blocked != null && resp.blocked.Length > 0)
            {
                _blockedPlayers.AddRange(resp.blocked);
                UIBlockedPlayersList.AppendItems(resp.blocked);
            }

            bool hasMore = resp.blocked != null && resp.pagination.total > _blockedPlayers.Length;
            UIBlockedPlayersList.SetHasMorePages(hasMore);
        });
    }

    private void LogFailure(string context, LootLockerResponse resp)
    {
        UnityEngine.Debug.LogWarning($"[FriendsListManager] {context} failed: {resp?.errorData?.message}");
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friends List Management - Unreal C++
// Shows listing friends, incoming/outgoing requests, and blocked players using offset pagination.
// Assumptions:
// - UI layer methods for loading states and data display

#include "LootLockerManager.h"
#include "LootLockerSDK/LLFriends.h"

class FFriendsListManager
{
public:
    TArray<FLootLockerAcceptedFriend> Friends;
    TArray<FLootLockerFriend> IncomingRequests;
    TArray<FLootLockerFriend> OutgoingRequests;
    TArray<FLootLockerBlockedPlayer> BlockedPlayers;

    // Page tracking (0-indexed)
    int32 FriendsCurrentPage = 0;
    int32 IncomingCurrentPage = 0;
    int32 OutgoingCurrentPage = 0;
    int32 BlockedCurrentPage = 0;

    static constexpr int32 PageSize = 20;

    // Friends List
    void LoadFriendsFirstPage()
    {
        Friends.Empty();
        FriendsCurrentPage = 0;
        UFriendsListWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListFriendsPaginated(PageSize, FriendsCurrentPage,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnFriendsLoaded));
    }

    void LoadMoreFriends()
    {
        FriendsCurrentPage++;
        UFriendsListWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListFriendsPaginated(PageSize, FriendsCurrentPage,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreFriendsLoaded));
    }

    // Incoming Requests
    void LoadIncomingRequestsFirstPage()
    {
        IncomingRequests.Empty();
        IncomingCurrentPage = 0;
        UIncomingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(PageSize, IncomingCurrentPage,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnIncomingRequestsLoaded));
    }

    void LoadMoreIncomingRequests()
    {
        IncomingCurrentPage++;
        UIncomingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(PageSize, IncomingCurrentPage,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreIncomingRequestsLoaded));
    }

    // Outgoing Requests
    void LoadOutgoingRequestsFirstPage()
    {
        OutgoingRequests.Empty();
        OutgoingCurrentPage = 0;
        UOutgoingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(PageSize, OutgoingCurrentPage,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnOutgoingRequestsLoaded));
    }

    void LoadMoreOutgoingRequests()
    {
        OutgoingCurrentPage++;
        UOutgoingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(PageSize, OutgoingCurrentPage,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreOutgoingRequestsLoaded));
    }

    // Blocked Players
    void LoadBlockedPlayersFirstPage()
    {
        BlockedPlayers.Empty();
        BlockedCurrentPage = 0;
        UBlockedPlayersWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListBlockedPlayersPaginated(PageSize, BlockedCurrentPage,
            FLootLockerListBlockedPlayersResponseDelegate::CreateRaw(this, &FFriendsListManager::OnBlockedPlayersLoaded));
    }

    void LoadMoreBlockedPlayers()
    {
        BlockedCurrentPage++;
        UBlockedPlayersWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListBlockedPlayersPaginated(PageSize, BlockedCurrentPage,
            FLootLockerListBlockedPlayersResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreBlockedPlayersLoaded));
    }

private:
    void OnFriendsLoaded(const FLootLockerListFriendsResponse& Response)
    {
        UFriendsListWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Friends list failed: %s"), *Response.Error);
            return;
        }

        Friends = Response.Friends;
        UFriendsListWidget::ReplaceItems(Friends);
        
        bool bHasMore = Friends.Num < Response.Pagination.Total;
        UFriendsListWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreFriendsLoaded(const FLootLockerListFriendsResponse& Response)
    {
        UFriendsListWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More friends failed: %s"), *Response.Error);
            FriendsCurrentPage--; // Rollback
            return;
        }

        if (Response.Friends.Num() > 0)
        {
            Friends.Append(Response.Friends);
            UFriendsListWidget::AppendItems(Response.Friends);
        }

        bool bHasMore = Friends.Num < Response.Pagination.Total;
        UFriendsListWidget::SetHasMorePages(bHasMore);
    }

    void OnIncomingRequestsLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        UIncomingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Incoming requests failed: %s"), *Response.Error);
            return;
        }

        IncomingRequests = Response.Incoming;
        UIncomingRequestsWidget::ReplaceItems(IncomingRequests);
        
        bool bHasMore = IncomingRequests.Num() < Response.Pagination.Total;
        UIncomingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreIncomingRequestsLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        UIncomingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More incoming requests failed: %s"), *Response.Error);
            IncomingCurrentPage--; // Rollback
            return;
        }

        if (Response.Incoming.Num() > 0)
        {
            IncomingRequests.Append(Response.Incoming);
            UIncomingRequestsWidget::AppendItems(Response.Incoming);
        }

        bool bHasMore = IncomingRequests.Num() < Response.Pagination.Total;
        UIncomingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnOutgoingRequestsLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        UOutgoingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Outgoing requests failed: %s"), *Response.Error);
            return;
        }

        OutgoingRequests = Response.Outgoing;
        UOutgoingRequestsWidget::ReplaceItems(OutgoingRequests);
        
        bool bHasMore = OutgoingRequests.Num() < Response.Pagination.Total;
        UOutgoingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreOutgoingRequestsLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        UOutgoingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More outgoing requests failed: %s"), *Response.Error);
            OutgoingCurrentPage--; // Rollback
            return;
        }

        if (Response.Outgoing.Num() > 0)
        {
            OutgoingRequests.Append(Response.Outgoing);
            UOutgoingRequestsWidget::AppendItems(Response.Outgoing);
        }

        bool bHasMore = OutgoingRequests.Num() < Response.Pagination.Total;
        UOutgoingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnBlockedPlayersLoaded(const FLootLockerListBlockedPlayersResponse& Response)
    {
        UBlockedPlayersWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Blocked players failed: %s"), *Response.Error);
            return;
        }

        BlockedPlayers = Response.Blocked;
        UBlockedPlayersWidget::ReplaceItems(BlockedPlayers);
        
        bool bHasMore = BlockedPlayers.Num() < Response.Pagination.Total;
        UBlockedPlayersWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreBlockedPlayersLoaded(const FLootLockerListBlockedPlayersResponse& Response)
    {
        UBlockedPlayersWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More blocked players failed: %s"), *Response.Error);
            BlockedCurrentPage--; // Rollback
            return;
        }

        if (Response.Blocked.Num() > 0)
        {
            BlockedPlayers.Append(Response.Blocked);
            UBlockedPlayersWidget::AppendItems(Response.Blocked);
        }

        bool bHasMore = BlockedPlayers.Num() < Response.Pagination.Total;
        UBlockedPlayersWidget::SetHasMorePages(bHasMore);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

### Offset Pagination Strategy

* Use page numbers (0-indexed) and `PerPage` count to paginate the data
* Track current page for each list type separately
* Consider a page "complete" if returned items equal the requested `PerPage`
* Reset to page 0 when refreshing lists

{% hint style="info" %}
Unlike cursor-based pagination, offset pagination lets you jump to specific pages without knowing the content on them
{% endhint %}

## Send and Cancel Friend Requests

Allow players to initiate and manage outgoing friend requests.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Send and Cancel Friend Requests with Optimistic UI
// Assumptions:
// - _outgoingRequests list is maintained locally
// - UI shows button states: Send Request / Pending / Friends
// - ShowToast for user feedback

public class FriendRequestController
{
    private readonly List<LootLockerFriend> _outgoingRequests;
    private readonly HashSet<string> _friendIds; // Track current friends for button state

    public FriendRequestController(List<LootLockerFriend> outgoingRequests, HashSet<string> friendIds)
    {
        _outgoingRequests = outgoingRequests;
        _friendIds = friendIds;
    }

    public void SendFriendRequest(string targetPlayerULID, string displayName)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) return;
        
        // Check if already friends or request pending
        if (_friendIds.Contains(targetPlayerULID))
        {
            ShowToast("Already friends");
            return;
        }
        
        if (_outgoingRequests.Any(r => r.player_ulid == targetPlayerULID))
        {
            ShowToast("Request already sent");
            return;
        }

        // Optimistic update
        var tempRequest = new LootLockerFriend 
        { 
            player_ulid = targetPlayerULID, 
            player_name = displayName,
            created_at = DateTime.Now
        };
        _outgoingRequests.Add(tempRequest);
        UIProfileButton.SetState(FriendButtonState.Pending);
        UIOutgoingRequestsList.AddItem(tempRequest);

        LootLockerSDKManager.SendFriendRequest(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback optimistic update
                _outgoingRequests.RemoveAll(r => r.player_ulid == targetPlayerULID);
                UIProfileButton.SetState(FriendButtonState.SendRequest);
                UIOutgoingRequestsList.RemoveItem(targetPlayerULID);
                ShowToast($"Failed to send request: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Friend request sent to {displayName}");
        });
    }

    public void CancelFriendRequest(string targetPlayerULID, string displayName)
    {
        var request = _outgoingRequests.FirstOrDefault(r => r.player_ulid == targetPlayerULID);
        if (request == null) return;

        // Optimistic removal
        _outgoingRequests.Remove(request);
        UIProfileButton.SetState(FriendButtonState.SendRequest);
        UIOutgoingRequestsList.RemoveItem(targetPlayerULID);

        LootLockerSDKManager.CancelFriendRequest(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _outgoingRequests.Add(request);
                UIProfileButton.SetState(FriendButtonState.Pending);
                UIOutgoingRequestsList.AddItem(request);
                ShowToast($"Failed to cancel request: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Cancelled request to {displayName}");
        });
    }

    private void ShowToast(string message)
    {
        // Implementation depends on your UI framework
        Debug.Log(message);
    }
}

public enum FriendButtonState
{
    SendRequest,
    Pending,
    Friends
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friend Request Management - Unreal C++
// Handles sending and canceling friend requests with optimistic updates

class FFriendRequestController
{
public:
    TArray<FLootLockerFriend> OutgoingRequests;
    TSet<FString> FriendIds; // Track current friends for UI state

    void SendFriendRequest(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        if (TargetPlayerULID.IsEmpty()) return;
        
        if (FriendIds.Contains(TargetPlayerULID))
        {
            ShowToast(TEXT("Already friends"));
            return;
        }
        
        // Check if request already exists
        if (OutgoingRequests.ContainsByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        }))
        {
            ShowToast(TEXT("Request already sent"));
            return;
        }

        // Optimistic update
        FLootLockerFriend TempRequest;
        TempRequest.Player_ulid = TargetPlayerULID;
        TempRequest.Player_name = DisplayName;
        TempRequest.Created_at = FDateTime::Now().ToString();
        
        OutgoingRequests.Add(TempRequest);
        UProfileButtonWidget::SetState(EFriendButtonState::Pending);
        UOutgoingRequestsWidget::AddItem(TempRequest);

        ULootLockerSDKManager::SendFriendRequest(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, TargetPlayerULID, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                OutgoingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
                    return Request.Player_ulid == TargetPlayerULID;
                });
                UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
                UOutgoingRequestsWidget::RemoveItem(TargetPlayerULID);
                ShowToast(FString::Printf(TEXT("Failed to send request: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Friend request sent to %s"), *DisplayName));
        }));
    }

    void CancelFriendRequest(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = OutgoingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic removal
        OutgoingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        });
        UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
        UOutgoingRequestsWidget::RemoveItem(TargetPlayerULID);

        ULootLockerSDKManager::CancelFriendRequest(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                OutgoingRequests.Add(RequestCopy);
                UProfileButtonWidget::SetState(EFriendButtonState::Pending);
                UOutgoingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to cancel request: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Cancelled request to %s"), *DisplayName));
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
        // Add your toast UI implementation
    }
};

UENUM(BlueprintType)
enum class EFriendButtonState : uint8
{
    SendRequest,
    Pending,
    Friends
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Accept and Decline Friend Requests

Handle incoming friend requests to build your friends network.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Accept and Decline Incoming Friend Requests
// Move requests between lists when processed

public class IncomingRequestsController
{
    private readonly List<LootLockerFriend> _incomingRequests;
    private readonly List<LootLockerAcceptedFriend> _friends;

    public IncomingRequestsController(List<LootLockerFriend> incomingRequests, List<LootLockerAcceptedFriend> friends)
    {
        _incomingRequests = incomingRequests;
        _friends = friends;
    }

    public void AcceptFriendRequest(string fromPlayerULID)
    {
        var request = _incomingRequests.FirstOrDefault(r => r.player_ulid == fromPlayerULID);
        if (request == null) return;

        // Optimistic update: move from incoming to friends
        _incomingRequests.Remove(request);
        var newFriend = new LootLockerAcceptedFriend
        {
            player_ulid = request.player_ulid,
            player_name = request.player_name,
            player_id = request.player_id,
            created_at = request.created_at,
            accepted_at = DateTime.Now
        };
        _friends.Add(newFriend);

        UIIncomingRequestsList.RemoveItem(fromPlayerULID);
        UIFriendsList.AddItem(newFriend);
        ShowToast($"Now friends with {request.player_name}");

        LootLockerSDKManager.AcceptFriendRequest(fromPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback: move back to incoming
                _friends.Remove(newFriend);
                _incomingRequests.Add(request);
                UIFriendsList.RemoveItem(fromPlayerULID);
                UIIncomingRequestsList.AddItem(request);
                ShowToast($"Failed to accept request: {resp.errorData?.message}");
            }
        });
    }

    public void DeclineFriendRequest(string fromPlayerULID)
    {
        var request = _incomingRequests.FirstOrDefault(r => r.player_ulid == fromPlayerULID);
        if (request == null) return;

        // Optimistic removal
        _incomingRequests.Remove(request);
        UIIncomingRequestsList.RemoveItem(fromPlayerULID);
        ShowToast($"Declined request from {request.player_name}");

        LootLockerSDKManager.DeclineFriendRequest(fromPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _incomingRequests.Add(request);
                UIIncomingRequestsList.AddItem(request);
                ShowToast($"Failed to decline request: {resp.errorData?.message}");
            }
        });
    }

    private void ShowToast(string message)
    {
        Debug.Log(message);
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Accept and Decline Friend Requests - Unreal C++

class FIncomingRequestsController
{
public:
    TArray<FLootLockerFriend> IncomingRequests;
    TArray<FLootLockerAcceptedFriend> Friends;

    void AcceptFriendRequest(const FString& FromPlayerULID)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = IncomingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic update: move from incoming to friends
        IncomingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });

        FLootLockerAcceptedFriend NewFriend;
        NewFriend.Player_ulid = RequestCopy.Player_ulid;
        NewFriend.Player_name = RequestCopy.Player_name;
        NewFriend.Player_id = RequestCopy.Player_id;
        NewFriend.Created_at = RequestCopy.Created_at;
        NewFriend.Accepted_at = FDateTime::Now().ToString();
        
        Friends.Add(NewFriend);
        UIncomingRequestsWidget::RemoveItem(FromPlayerULID);
        UFriendsListWidget::AddItem(NewFriend);
        ShowToast(FString::Printf(TEXT("Now friends with %s"), *RequestCopy.Player_name));

        ULootLockerSDKManager::AcceptFriendRequest(FromPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy, NewFriend, FromPlayerULID](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback: move back to incoming
                Friends.RemoveAll([&](const FLootLockerAcceptedFriend& Friend) {
                    return Friend.Player_ulid == FromPlayerULID;
                });
                IncomingRequests.Add(RequestCopy);
                UFriendsListWidget::RemoveItem(FromPlayerULID);
                UIncomingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to accept request: %s"), *Response.Error));
            }
        }));
    }

    void DeclineFriendRequest(const FString& FromPlayerULID)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = IncomingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic removal
        IncomingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        UIncomingRequestsWidget::RemoveItem(FromPlayerULID);
        ShowToast(FString::Printf(TEXT("Declined request from %s"), *RequestCopy.Player_name));

        ULootLockerSDKManager::DeclineFriendRequest(FromPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                IncomingRequests.Add(RequestCopy);
                UIncomingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to decline request: %s"), *Response.Error));
            }
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Block and Unblock Players

Manage your blocked players list to prevent unwanted interactions.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Block and Unblock Players
// Blocking removes from friends and prevents future friend requests

public class BlockingController
{
    private readonly List<LootLockerBlockedPlayer> _blockedPlayers;
    private readonly List<LootLockerAcceptedFriend> _friends;

    public BlockingController(List<LootLockerBlockedPlayer> blockedPlayers, List<LootLockerAcceptedFriend> friends)
    {
        _blockedPlayers = blockedPlayers;
        _friends = friends;
    }

    public void BlockPlayer(string targetPlayerULID, string displayName)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) return;

        // Check if already blocked
        if (_blockedPlayers.Any(p => p.player_ulid == targetPlayerULID))
        {
            ShowToast("Player already blocked");
            return;
        }

        // Optimistic update: remove from friends if present, add to blocked
        var existingFriend = _friends.FirstOrDefault(f => f.player_ulid == targetPlayerULID);
        if (existingFriend != null)
        {
            _friends.Remove(existingFriend);
            UIFriendsList.RemoveItem(targetPlayerULID);
        }

        var blockedPlayer = new LootLockerBlockedPlayer
        {
            player_ulid = targetPlayerULID,
            player_name = displayName,
            blocked_at = DateTime.Now
        };
        _blockedPlayers.Add(blockedPlayer);
        UIBlockedPlayersList.AddItem(blockedPlayer);
        UIProfileButton.SetState(FriendButtonState.Blocked);

        LootLockerSDKManager.BlockPlayer(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _blockedPlayers.Remove(blockedPlayer);
                UIBlockedPlayersList.RemoveItem(targetPlayerULID);
                if (existingFriend != null)
                {
                    _friends.Add(existingFriend);
                    UIFriendsList.AddItem(existingFriend);
                    UIProfileButton.SetState(FriendButtonState.Friends);
                }
                else
                {
                    UIProfileButton.SetState(FriendButtonState.SendRequest);
                }
                ShowToast($"Failed to block player: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Blocked {displayName}");
        });
    }

    public void UnblockPlayer(string targetPlayerULID, string displayName)
    {
        var blockedPlayer = _blockedPlayers.FirstOrDefault(p => p.player_ulid == targetPlayerULID);
        if (blockedPlayer == null) return;

        // Optimistic removal
        _blockedPlayers.Remove(blockedPlayer);
        UIBlockedPlayersList.RemoveItem(targetPlayerULID);
        UIProfileButton.SetState(FriendButtonState.SendRequest);

        LootLockerSDKManager.UnblockPlayer(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _blockedPlayers.Add(blockedPlayer);
                UIBlockedPlayersList.AddItem(blockedPlayer);
                UIProfileButton.SetState(FriendButtonState.Blocked);
                ShowToast($"Failed to unblock player: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Unblocked {displayName}");
        });
    }

    private void ShowToast(string message)
    {
        Debug.Log(message);
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Block and Unblock Players - Unreal C++

class FBlockingController
{
public:
    TArray<FLootLockerBlockedPlayer> BlockedPlayers;
    TArray<FLootLockerAcceptedFriend> Friends;

    void BlockPlayer(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        if (TargetPlayerULID.IsEmpty()) return;

        // Check if already blocked
        if (BlockedPlayers.ContainsByPredicate([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        }))
        {
            ShowToast(TEXT("Player already blocked"));
            return;
        }

        // Remove from friends if present
        FLootLockerAcceptedFriend* ExistingFriend = Friends.FindByPredicate([&](const FLootLockerAcceptedFriend& Friend) {
            return Friend.Player_ulid == TargetPlayerULID;
        });

        FLootLockerAcceptedFriend FriendCopy;
        bool bWasFriend = false;
        if (ExistingFriend)
        {
            FriendCopy = *ExistingFriend;
            bWasFriend = true;
            Friends.RemoveAll([&](const FLootLockerAcceptedFriend& Friend) {
                return Friend.Player_ulid == TargetPlayerULID;
            });
            UFriendsListWidget::RemoveItem(TargetPlayerULID);
        }

        // Add to blocked
        FLootLockerBlockedPlayer BlockedPlayer;
        BlockedPlayer.Player_ulid = TargetPlayerULID;
        BlockedPlayer.Player_name = DisplayName;
        BlockedPlayer.Blocked_at = FDateTime::Now().ToString();
        
        BlockedPlayers.Add(BlockedPlayer);
        UBlockedPlayersWidget::AddItem(BlockedPlayer);
        UProfileButtonWidget::SetState(EFriendButtonState::Blocked);

        ULootLockerSDKManager::BlockPlayer(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, BlockedPlayer, FriendCopy, bWasFriend, TargetPlayerULID, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                BlockedPlayers.RemoveAll([&](const FLootLockerBlockedPlayer& Player) {
                    return Player.Player_ulid == TargetPlayerULID;
                });
                UBlockedPlayersWidget::RemoveItem(TargetPlayerULID);
                
                if (bWasFriend)
                {
                    Friends.Add(FriendCopy);
                    UFriendsListWidget::AddItem(FriendCopy);
                    UProfileButtonWidget::SetState(EFriendButtonState::Friends);
                }
                else
                {
                    UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
                }
                
                ShowToast(FString::Printf(TEXT("Failed to block player: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Blocked %s"), *DisplayName));
        }));
    }

    void UnblockPlayer(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        FLootLockerBlockedPlayer* BlockedPlayerPtr = BlockedPlayers.FindByPredicate([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        });
        
        if (!BlockedPlayerPtr) return;
        FLootLockerBlockedPlayer BlockedPlayerCopy = *BlockedPlayerPtr;

        // Optimistic removal
        BlockedPlayers.RemoveAll([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        });
        UBlockedPlayersWidget::RemoveItem(TargetPlayerULID);
        UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);

        ULootLockerSDKManager::UnblockPlayer(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, BlockedPlayerCopy, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                BlockedPlayers.Add(BlockedPlayerCopy);
                UBlockedPlayersWidget::AddItem(BlockedPlayerCopy);
                UProfileButtonWidget::SetState(EFriendButtonState::Blocked);
                ShowToast(FString::Printf(TEXT("Failed to unblock player: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Unblocked %s"), *DisplayName));
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Check Friendship Status

Determine the current relationship between players for UI state management.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Check Friendship Status Using GetFriend
// Returns specific friend data if they are friends, null/error otherwise

public static class FriendshipProbe
{
    public static void CheckFriendshipStatus(string targetPlayerULID, System.Action<FriendshipStatus> onResult)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) 
        { 
            onResult?.Invoke(FriendshipStatus.None); 
            return; 
        }

        LootLockerSDKManager.GetFriend(targetPlayerULID, (resp) =>
        {
            if (resp.success && !string.IsNullOrEmpty(resp.player_ulid))
            {
                // They are friends - check when friendship was established
                onResult?.Invoke(FriendshipStatus.Friends);
            }
            else
            {
                // Not friends - could be pending request, blocked, or no relationship
                // You might need additional checks to distinguish these states
                onResult?.Invoke(FriendshipStatus.None);
            }
        });
    }
}

public enum FriendshipStatus
{
    None,           // No relationship
    Friends,        // Mutual friends
    IncomingRequest, // They sent us a request
    OutgoingRequest, // We sent them a request
    Blocked         // One party blocked the other
}

// Usage:
// FriendshipProbe.CheckFriendshipStatus(otherPlayerUid, (status) => {
//     switch(status) {
//         case FriendshipStatus.Friends:
//             UIProfileButton.SetState(FriendButtonState.Friends);
//             break;
//         case FriendshipStatus.None:
//             UIProfileButton.SetState(FriendButtonState.SendRequest);
//             break;
//     }
// });
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friendship Status Check - Unreal C++

UENUM(BlueprintType)
enum class EFriendshipStatus : uint8
{
    None,           // No relationship
    Friends,        // Mutual friends  
    IncomingRequest, // They sent us a request
    OutgoingRequest, // We sent them a request
    Blocked         // One party blocked the other
};

class FFriendshipProbe
{
public:
    static void CheckFriendshipStatus(const FString& TargetPlayerULID, TFunction<void(EFriendshipStatus)> Callback)
    {
        if (TargetPlayerULID.IsEmpty()) 
        { 
            Callback(EFriendshipStatus::None); 
            return; 
        }

        ULootLockerSDKManager::GetFriend(TargetPlayerULID,
            FLootLockerGetFriendResponseDelegate::CreateLambda([Callback](const FLootLockerGetFriendResponse& Response)
        {
            if (Response.success && !Response.Player_ulid.IsEmpty())
            {
                // They are friends
                Callback(EFriendshipStatus::Friends);
            }
            else
            {
                // Not friends - could be pending, blocked, or no relationship
                // Additional logic could check pending requests lists
                Callback(EFriendshipStatus::None);
            }
        }));
    }
};

// Usage:
// FFriendshipProbe::CheckFriendshipStatus(OtherPlayerUid, [](EFriendshipStatus Status) {
//     switch(Status) {
//         case EFriendshipStatus::Friends:
//             UProfileButtonWidget::SetState(EFriendButtonState::Friends);
//             break;
//         case EFriendshipStatus::None:
//             UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
//             break;
//     }
// });
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Displaying Counts and Status

Show meaningful counts and relationship indicators in your UI.

{% tabs %}
{% tab title="Unity" %}

```csharp
// Friends Counts and Status Display
// Use first page loads to estimate counts and provide status context

public class FriendsCountsManager
{
    private int _friendsCount = 0;
    private int _incomingRequestsCount = 0;
    private int _outgoingRequestsCount = 0;

    public void LoadAllCounts()
    {
        // Load first pages to get count estimates
        LoadFriendsCount();
        LoadIncomingRequestsCount();
        LoadOutgoingRequestsCount();
    }

    private void LoadFriendsCount()
    {
        LootLockerSDKManager.ListFriendsPaginated(1, 0, (resp) =>
        {
            if (resp.success)
            {
                UIFriendsHeader.SetCount(resp.pagination.total.ToString());
            }
        });
    }

    private void LoadIncomingRequestsCount()
    {
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(1, 0, (resp) =>
        {
            if (resp.success && resp.incoming != null)
            {
                _incomingRequestsCount = resp.pagination.total;
                UIIncomingRequestsHeader.SetCount(_incomingRequestsCount);
                
                // Show notification badge if there are pending requests
                if (_incomingRequestsCount > 0)
                {
                    UIFriendsTabButton.ShowBadge(_incomingRequestsCount);
                }
            }
        });
    }

    private void LoadOutgoingRequestsCount()
    {
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(1, 0, (resp) =>
        {
            if (resp.success && resp.outgoing != null)
            {
                UIOutgoingRequestsHeader.SetCount(resp.pagination.total);
            }
        });
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friends Counts Display - Unreal C++

class FFriendsCountsManager
{
public:
    int32 FriendsCount = 0;
    int32 IncomingRequestsCount = 0;
    int32 OutgoingRequestsCount = 0;

    void LoadAllCounts()
    {
        LoadFriendsCount();
        LoadIncomingRequestsCount();
        LoadOutgoingRequestsCount();
    }

private:
    void LoadFriendsCount()
    {
        ULootLockerSDKManager::ListFriendsPaginated(1, 0,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnFriendsCountLoaded));
    }

    void LoadIncomingRequestsCount()
    {
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(1, 0,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnIncomingCountLoaded));
    }

    void LoadOutgoingRequestsCount()
    {
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(1, 0,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnOutgoingCountLoaded));
    }

    void OnFriendsCountLoaded(const FLootLockerListFriendsResponse& Response)
    {
        if (Response.success)
        {
            UFriendsHeaderWidget::SetCount(FString::FromInt(Response.Pagination.Total));
        }
    }

    void OnIncomingCountLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        if (Response.success)
        {
            IncomingRequestsCount = Response.Pagination.Total;
            UIncomingRequestsHeaderWidget::SetCount(FString::FromInt(IncomingRequestsCount));
            
            // Show notification badge if there are pending requests
            if (IncomingRequestsCount > 0)
            {
                UFriendsTabButtonWidget::ShowBadge(IncomingRequestsCount);
            }
        }
    }

    void OnOutgoingCountLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        if (Response.success)
        {
            OutgoingRequestsCount = Response.Pagination.Total;
            UOutgoingRequestsHeaderWidget::SetCount(FString::FromInt(OutgoingRequestsCount));
        }
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Performance Tips

* **Batch relationship checks**: When showing many players (leaderboards for example), only fetch data for visible items
* **Cache friend IDs locally**: Maintain a set of friend ULIDs for instant button state rendering
* **Moderate page sizes**: 20-50 items per page balances responsiveness with network efficiency
* **Debounce rapid requests**: Prevent spam-clicking send/cancel buttons
* **Lazy load secondary lists**: Load friends first, then incoming/outgoing requests when tabs are opened

## Example Feature Ideas

* **Mutual Friends**: "You have 3 mutual friends with this player"
* **Friend Recommendations**: Suggest friends-of-friends or players with similar interests
* **Private Groups**: Create invite-only spaces using your friends list
* **Co-op Quick Join**: Join friends' game sessions directly from the friends list

## Conclusion

In this How-to we implemented friend requests, list management, acceptance/declining workflows, blocking functionality, and relationship status checking. The friends system provides the foundation for deeper social features like messaging, co-op play, and community building.

Key differences from followers include the mutual consent model, request management workflow, and offset-based pagination. Next, consider adding [Followers](/players/friends-and-followers/how-to/use-followers-in-game.md) for asymmetric relationships or explore [Web Console management](/players/friends-and-followers/how-to/manage-relationships-web-console.md) for administrative oversight.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.lootlocker.com/players/friends-and-followers/how-to/use-friends-in-game.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
