> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwacks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# اتصال اللاعبين

> صِل بين لاعبي الجلسة عبر Netcode for GameObjects أو شبكة Unreal نفسها، ومن خلال وسيط Flock عند الحاجة.

حين يتشارك اللاعبون [جلسة](/ar/guides/sessions.md)، تصل حزمة التطوير بينهم عبر شبكة لعبتك: Netcode for
GameObjects في Unity، وشبكة المحرّك نفسه في Unreal. لعبة المضيف هي الخادم. يتصل اللاعبون على شبكة
المضيف به مباشرة، ويمرّ الآخرون عبر **وسيط Flock**، فلا يحتاج أحد إلى فتح منفذ في الموجّه. ولا يُدخل
المضيف إلا لاعبي الجلسة، وحين يتغيّر المضيف تبدأ كل لعبة شبكتها من جديد لدورها الجديد.

> [!NOTE]
>
> **النطاق:** يُفعَّل الوسيط أو يُوقَف لكل **لعبة**، من **إعدادات اللعب الجماعي**.

> [!NOTE]
>
> يعمل وسيط Flock على شبكة Cloudflare، وهو مفعّل لكل لعبة ما لم يُوقَف من **إعدادات اللعب الجماعي**. وحين يكون موقوفًا،
> أو ما دام وسيط الاستوديو معلّقًا حتى تُسدَّد فاتورته، يتصل اللاعبون على شبكة المضيف أو عبر منفذ مُوجَّه إلى المضيف،
> ويحصل اللاعب على شبكة أخرى على `RelayFailed` (في Unreal: `relay_failed`) مع `relay_not_offered` أو `relay_paused`.

## بدء الشبكة

**Unity**

استدعاء واحد يتولّى المصافحة كلها، للمضيف ولكل لاعب. فعلى المضيف يبدأ Netcode for GameObjects
مضيفًا ويعلن مكانه. وعلى اللاعب ينتظر ذلك (حتى 60 ثانية)، ثم يتصل، ويثبت مقعد اللاعب.

```csharp theme={null}
FlockNetcodeStartResult started = await session.StartNetcodeAsync(NetworkManager.Singleton);
switch (started.Outcome)
{
    case FlockNetcodeStartOutcome.Restarted:
        return;   // a newer start on this NetworkManager took over
    case FlockNetcodeStartOutcome.StartedAsHost:
        ShowMessage("Hosting. Code: " + session.JoinCode);
        break;
    case FlockNetcodeStartOutcome.Connected:
        ShowMessage(started.ThroughRelay ? "Connected through Flock's relay." : "Connected.");
        break;
    case FlockNetcodeStartOutcome.Refused:
        ShowMessage("The host refused you: " + started.RefusedReason);
        break;
    case FlockNetcodeStartOutcome.RelayFailed:
        ShowMessage("The relay could not be used: " + started.RelayFailureReason);
        break;
    default:
        ShowMessage("Could not connect: " + started.Outcome);
        break;
}
```

البدء الذي لا يستضيف ولا يتصل يُبقي مقعد اللاعب؛ فاستدعِ `session.LeaveAsync()` إن لم يكن اللاعب
قادرًا على اللعب.

**Unreal**

استدعاء واحد يُدخل كل لعبة في لعبة الجلسة، لعبة المضيف ولعبة كل لاعب: `TravelToSession`. فعلى
المضيف يفتح مستوى المضيف مستمعًا للاعبين (`?listen` الخاص بالمحرّك) ويعلن مكانه. وعلى اللاعب ينتظر
ذلك (حتى 60 ثانية)، ثم يطلب من Flock رمز انضمام، وينتقل إلى المضيف، الذي يتحقّق من الرمز قبل أن
يُدخل اللاعب. ويحمل net driver الخاص بالمحرّك كل لاعب، عبر وسيط Flock أيضًا: إذ تقوم حزمة التطوير
مقام الطرف الآخر على عنوان الجهاز المحلي (loopback)، فتبقى شيفرة الشبكة في لعبتك كما هي.

استدعِه من Game Instance الخاصة بلعبتك: فهو يكتمل حين تصير اللعبة في مستواها الجديد، ويكون
المستوى الذي جرى منه الاستدعاء قد زال. و`TravelOptions` هنا من نوع `FFlockTravelToSessionOptions`
محفوظ في Game Instance، ليمرّره تغيّر المضيف نفسه من جديد.

```cpp theme={null}
#include "FlockSubsystem.h"
#include "Multiplayer/FlockMultiplayerSession.h"

// Every player's game, the host's included, once it holds the session.
TravelOptions.HostLevel = TEXT("/Game/Maps/Arena");   // the level the host opens; players travel to whatever the host has open
const TWeakObjectPtr<UMyGameInstance> Game = this;
Session->TravelToSession(this, TravelOptions, [Game](TFlockResult<FFlockTravelToSessionResult> Result)
{
    if (!Game.IsValid())
    {
        return;
    }
    if (!Result.IsSuccess())
    {
        UE_LOG(LogTemp, Warning, TEXT("%s"), *Result.Error.ToDisplayText());   // a mistake, a refused publish or a cancel, with its fix
        return;
    }
    const FFlockTravelToSessionResult& Travel = Result.Value;
    if (Travel.Outcome == FlockTravelToSessionOutcomes::Restarted)
    {
        return;   // a newer Travel To Session took over
    }
    if (Travel.Outcome == FlockTravelToSessionOutcomes::StartedAsHost)
    {
        Game->ShowMessage(TEXT("Hosting. Code: ") + Game->Session->GetJoinCode());
    }
    else if (Travel.Outcome == FlockTravelToSessionOutcomes::Connected)
    {
        Game->ShowMessage(Travel.bThroughRelay ? TEXT("Connected through Flock's relay.") : TEXT("Connected."));
    }
    else if (Travel.Outcome == FlockTravelToSessionOutcomes::Refused)
    {
        Game->ShowMessage(TEXT("The host refused you: ") + Travel.RefusedReason);
    }
    else if (Travel.Outcome == FlockTravelToSessionOutcomes::RelayFailed)
    {
        Game->ShowMessage(TEXT("The relay could not be used: ") + Travel.RelayFailureReason);
    }
    else
    {
        Game->ShowMessage(TEXT("Could not connect: ") + Travel.Outcome);
    }
});
```

المعامل الأول أي شيء في عالم اللعبة، مثل Game Instance. والانتقال الذي لا يستضيف ولا يتصل يُبقي
مقعد اللاعب؛ فاستدعِ `Session->Leave` إن لم يكن اللاعب قادرًا على اللعب.

**يتحقّق وضع اللعب (game mode) لدى المضيف من كل لاعب ينضم.** ابنِه على `AFlockGameModeBase` أو
`AFlockGameMode`، أو استدعِ تحقّق Flock من `PreLoginAsync` الخاص به؛ راجع
[من ينضم](#who-is-joining). والمضيف الذي لا يفعل وضع لعبه أيًّا من ذلك لا يُدخل أحدًا
(`join_check_missing`).

**يحتاج Travel To Session إلى شبكة IP الخاصة بالمحرّك.** فاللعبة التي يستخدم net driver فيها مقابس
Steam (‏`bUseSteamNetworking` في نظام Steam الفرعي على الإنترنت، مفعّل ما لم يُضبط على false) لا
تستطيع الاستماع على عنوان IP، فتنتهي بـ `could_not_start`. اضبط `bUseSteamNetworking=False` تحت
`[OnlineSubsystemSteam]` في `Config/DefaultEngine.ini`، أو انشر اتصال Steam خاصًّا بك (راجع
[شبكات أخرى](#other-networking)).

وفي Blueprint، استدعِ **Flock Travel To Session** من Game Instance Blueprint، حيث حدثا المحرّك
**NetworkError** و**TravelError**؛ وإن استُدعيت من داخل مستوى، فإنها تكتب تحذيرًا في السجل. وهي
تطلق **On Joined** (بدأت مضيفًا، أو اتصلت)، أو **On Not Joined** (ويقول Outcome وRefused Reason في
النتيجة لماذا)، أو **On Failure** (خطأ في الاستخدام، كجلسة منتهية)، بعد أن يتغيّر المستوى. ويعطي
طرفها **Async Action** العقدة نفسها، ويوقف **Cancel** فيها الانتقال. وقارن Outcome بعقد **Flock Travel
Outcome**، واحتفظ بحالة افتراضية.

نتيجة البدء هي ما انتهى إليه. في Unity تكون قيمة من `FlockNetcodeStartOutcome`؛ أما في Unreal فهي نصّ
يُقارَن بثوابت `FlockTravelToSessionOutcomes`، وهي بالأسماء نفسها
(فـ `FlockTravelToSessionOutcomes::StartedAsHost` هو `started_as_host`). وقد تأتي نتائج جديدة، فاحتفظ
بحالة افتراضية.

| Unity | Unreal | المعنى |
| - | - | - |
| `StartedAsHost` | `started_as_host` | هذا الجهاز يستضيف، ويستطيع اللاعبون الوصول إليه. |
| `Connected` | `connected` | انضم هذا الجهاز إلى المضيف. ويقول `ThroughRelay` (في Unreal: `bThroughRelay`) هل كان ذلك عبر الوسيط. |
| `Refused` | `refused` | رفض المضيف هذا اللاعب؛ ويقول `RefusedReason` لماذا (راجع [من ينضم](#who-is-joining)). وفي Unreal يكون المحرّك قد أعاد اللعبة إلى مستواها الافتراضي. |
| `RelayFailed` | `relay_failed` | احتيج إلى الوسيط ولم يمكن استخدامه؛ ويقول `RelayFailureReason` لماذا وماذا تفعل (راجع أدناه). |
| `HostNeverPublished` | `host_never_published` | لم يعلن المضيف مكانه خلال 60 ثانية (`FlockNetcodeOptions.WaitForHost`، أو `WaitForHostSeconds` في Unreal). تأكّد أن لعبة المضيف بدأت كذلك، ثم ابدأ من جديد. |
| `CouldNotConnect` | `could_not_connect` | لم يجب المضيف. وفي Unreal يستسلم المحرّك بعد مهلته الخاصة (نحو 20 ثانية) ويحمّل المستوى الافتراضي. وخارج شبكة المضيف مع إيقاف الوسيط، وجّه منفذًا إلى المضيف. |
| `CouldNotStart` | `could_not_start` | لم يستطع المضيف البدء. ففي Unity رفض Netcode for GameObjects البدء، كأن يكون المنفذ مستخدمًا: حرّر المنفذ أو اختر غيره. وفي Unreal لم يُفتح مستوى المضيف مستمعًا على عنوان IP، كما يحدث مع مقابس Steam: أوقف `bUseSteamNetworking`. |
| `SessionEnded` | `session_ended` | انتهت الجلسة أثناء الانتظار. استضِف جلسة أخرى أو انضم إليها. |
| `NotDirect` | `not_direct` | أعلن المضيف اتصالًا من نوعه الخاص، مثل Steam؛ راجع [شبكات أخرى](#other-networking). |
| `Restarted` | `restarted` | بدأت اللعبة من جديد قبل انتهاء هذا البدء، وتولّى البدء الأحدث الأمر. لا شيء تفعله: فجواب الأحدث يأتي بعده. |

## عبر الوسيط، أو مباشرة

يختار `FlockNetcodeOptions.Relay` في Unity، و`FFlockTravelToSessionOptions::Relay` في Unreal، من يمرّ عبر
الوسيط. واضبطه بالقيمة نفسها في لعبة كل لاعب.

| `FlockRelayUse` / `EFlockRelayUse` | من يمرّ عبر الوسيط | اختره لـ |
| - | - | - |
| `WhenNeeded` (الافتراضي) | يجرّب اللاعبون على شبكة المضيف عنوانه المحلي بضع ثوانٍ، ثم الوسيط إن لم يُجب. ويمرّ الآخرون جميعًا عبر الوسيط، ثم ينتقلون إلى مسار مباشر حين يتمكّنون هم والمضيف من الوصول بعضهم إلى بعض. | معظم الألعاب. |
| `Always` | كل لاعب، حتى على شبكة المضيف، ويبقون على الوسيط. ولا يعرف اللاعبون عنوان IP للمضيف أبدًا، ولا يعرف المضيف عناوينهم. وفي Unreal يردّ المضيف اللاعب الذي يصل إليه بطريق آخر (`relay_only`). | البثّ المباشر واللعب التنافسي، أو الشبكات التي تعزل أجهزتها. |
| `Never` | لا أحد. يصل اللاعبون إلى المضيف على شبكته، أو عبر منفذ مُوجَّه إليه. | ألعاب الشبكة المحلية، أو لعبة لا تريد حركة عبر الوسيط. |

```csharp Unity theme={null}
await session.StartNetcodeAsync(NetworkManager.Singleton, new FlockNetcodeOptions { Relay = FlockRelayUse.Always });
```

```cpp Unreal theme={null}
TravelOptions.Relay = EFlockRelayUse::Always;
Session->TravelToSession(this, TravelOptions, [](TFlockResult<FFlockTravelToSessionResult> Travel) { /* ... */ });
```

### المسارات المباشرة

مع `WhenNeeded`، وبعد أن يُدخل المضيف لاعبًا يمرّ عبر الوسيط، يحاول الطرفان الوصول أحدهما إلى الآخر مباشرة: يسأل كلٌّ منهما
الوسيط، أو خادم STUN يُدرجه Flock، عن العنوان الذي يراه منه، ويتبادلان هذين العنوانين عبر الوسيط، ثم يرسل كلٌّ منهما حزمًا صغيرة إلى الآخر. وحين تصل في
الاتجاهين، تمرّ حزم اللعبة مباشرة، وهو مسار أقصر في العادة من مسار الوسيط، ولا يبقى للوسيط ما يحمله. ويبقى الوسيط جاهزًا: فإذا
سكت المسار المباشر 4 ثوانٍ عادت الحزم إليه. ولا تفعل اللعبة شيئًا في الحالتين. تسمح معظم أجهزة التوجيه المنزلية بذلك؛ أمّا حين
يكون الطرفان كلاهما خلف أجهزة توجيه تمنح كل وجهة منفذًا خاصًا بها (كما تفعل بعض شبكات الجوّال)، فتبقى الحزم على الوسيط. منذ
الإصدار 1.83.0 من Unity SDK والإصدار 1.38.0 من Unreal SDK.

وفي Unreal يستمرّ اتصال المحرّك عبر كل انتقال: يسمع مُشغّل الشبكة لدى المضيف اللاعبَ من المنفذ نفسه طوال الوقت. ويُذكَر
كل انتقال مرة واحدة في السجلّ، بمستوى Info. وإن كانت اللعبة في أيّ من الطرفين على Unreal SDK أقدم من 1.38.0 بقي الطرفان على
الوسيط، إذ يُعلن كل طرف أولًا أنه يفهم رسائل المسار المباشر من الطرف الآخر.

المقابل: مع `WhenNeeded` يعرف المضيف واللاعب المارّ عبر الوسيط عنوان IP أحدهما للآخر بعد إدخال اللاعب. وفي
Unreal ينشر المضيف عناوينه الخاصة إلى جانب عنوانه على الوسيط، فيستطيع اللاعبون قراءة عنوانه. استخدم
`Always` لإبقائهما مخفيّين.

حين يتعذّر استخدام الوسيط، يكون `RelayFailureReason` واحدًا مما يلي، وهو نفسه في المحرّكين ما لم يذكر
السطر غير ذلك:

| السبب | متى | ماذا تفعل |
| - | - | - |
| `relay_not_offered` | **توزيع بيانات اعتماد الوسيط** موقوف للعبة، أو لا يُدرج Flock أي وسيط. | فعّله من **إعدادات اللعب الجماعي**، أو صِل اللاعبين مباشرة. |
| `relay_paused` | وسيط الاستوديو موقوف حتى تُدفع فاتورته. | سدّد الفاتورة من لوحة التحكم؛ وحتى ذلك الحين يتصل اللاعبون مباشرة. |
| `relay_unreachable` | الشبكة تحجب UDP عن الوسيط، كما تفعل بعض شبكات المدارس والمكاتب والفنادق. | تأكّد أن الجهاز متصل بالإنترنت وأن جدار الحماية يسمح للعبة بإرسال UDP، أو جرّب شبكة أخرى. |
| `relay_not_on_this_platform` | في Unity فقط: لاعب متصفح. لا يستطيع المتصفح إرسال UDP. | العب من نسخة للحاسوب أو الهاتف لاستخدام الوسيط. |
| `relay_not_offered_by_host` | لا يُدخل المضيف أحدًا عبر الوسيط: قيمة `Relay` لديه `Never`، أو تعذّر فتح وسيطه. | اضبط `Relay` بالقيمة نفسها في لعبة كل لاعب؛ ويقول سجل المضيف لماذا تعذّر فتح وسيطه. |
| `relay_too_many_logins` | أكثر من 12 تسجيل دخول إلى الوسيط في دقيقة، أو 10 للاعب واحد في جلسة واحدة. | لا تبدأ الشبكة (في Unreal: Travel To Session) في حلقة؛ والجلسة الأخرى تعطي مقعدًا خاصًّا بها. |
| `relay_server_not_listed` | لا يُدرج Flock لهذا اللاعب خادم الوسيط الذي عليه المضيف. | اجعل المضيف يبدأ من جديد، ليستخدم الطرفان خادم وسيط يُدرجه Flock لكلٍّ منهما. |
| `relay_not_in_session` | يقول Flock إن اللاعب لا يشغل مقعدًا في الجلسة. | انضم إلى الجلسة من جديد قبل الاتصال. |
| `flock_unreachable`، `relay_logins_refused` | لم يجب Flock طلب بيانات دخول الوسيط، أو رفضه. | تأكّد أن الجهاز متصل بالإنترنت، ثم ابدأ من جديد؛ ويذكر السجل ما رفضه Flock. |
| `relay_wrong_login`، `relay_full`، `relay_open_refused`، `relay_refused` | رفض خادم الوسيط بيانات الدخول، أو لم يكن فيه متّسع، أو لم يُدخل حزم المضيف، أو رفض لسبب آخر. | ابدأ من جديد ليعطي Flock بيانات دخول جديدة؛ وإن ظلّ يرفض، صِل اللاعبين مباشرة. |
| `relay_lost`، `relay_closed` | تخلّى الوسيط عن عنوان هذا الجهاز أو لم يردّ بشيء طوال 45 ثانية (فلم تعد حزم هذا الجهاز تصل إليه)، أو أغلقه انتهاء الجلسة. | بعد `relay_lost` ابدأ من جديد؛ وبعد `relay_closed` استضِف جلسة أخرى أو انضم إليها. |
| `relay_no_thread` | في Unreal فقط: تعمل اللعبة دون خيوط (`-nothreading`)، والوسيط يحتاج خيطًا خاصًّا به. | شغّل اللعبة دون `-nothreading`. |

وقد يضيف الخادم أسبابًا جديدة، فاحتفظ بحالة افتراضية.

## من ينضم

لا يُدخل المضيف إلا اللاعبين الذين يؤكّد Flock مقاعدهم، باتصال واحد لكل لاعب. سمِّ اللاعبين باتصالهم، لا
بما ترسله لعبة اللاعب. ويُحتسب تحقّق المضيف من كل لاعب ينضم استدعاءً مدفوعًا واحدًا.

**Unity**

يتحقّق المضيف الذي بدأه `StartNetcodeAsync` بنفسه من كل لاعب ينضم. واعرف من صاحب الاتصال:

```csharp theme={null}
string playerId = session.PlayerForConnection(clientId);   // on the host
```

أضف تحقّقك الخاص بعد تحقّق Flock، مثل قائمة حظر أو حدّ لعدد الفريق:

```csharp theme={null}
FlockNetcodeOptions options = new FlockNetcodeOptions
{
    ApproveJoiningPlayer = (player, response) =>
    {
        if (bannedPlayers.Contains(player.PlayerId))
        {
            response.Approved = false;
            response.Reason = "banned";
        }
    },
};
```

يكون `RefusedReason` للاعب المرفوض واحدًا من `no_join_token` و`join_token_invalid` و`already_connected`
و`flock_unreachable` و`session_ended` و`not_host` و`refused_by_the_game` و`game_check_failed`.

> [!NOTE]
>
> يجب أن يعمل طرفا اللعبة على Flock SDK بالإصدار 1.77.0 أو أحدث، وبالإصدار الرئيسي نفسه من Netcode for
> GameObjects: فالمضيف يرفض اللاعبين على Flock SDK أقدم، أو على إصدار رئيسي آخر من Netcode.

**Unreal**

يسأل المحرّك وضع اللعب لدى المضيف عن كل لاعب ينضم في `PreLoginAsync`، فيستدعي وضع اللعب لدى المضيف
تحقّق Flock هناك، بدل تحقّق المحرّك نفسه. وأبسط طريق أن تبنيه على `AFlockGameModeBase` (المبني على
`AGameModeBase`) أو `AFlockGameMode` (المبني على `AGameMode`، لحالات المباراة): في C++، أو Blueprint
مبنيًّا على **Flock Game Mode Base** أو **Flock Game Mode**، مضبوطًا في **Project Settings → Maps &
Modes**. أو استدعِ التحقّق من وضع لعب خاصّ بك:

```cpp theme={null}
// MyGameMode.h
virtual void PreLoginAsync(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId,
    const FOnPreLoginCompleteDelegate& OnComplete) override;

// MyGameMode.cpp
#include "Multiplayer/FlockJoinChecks.h"

void AMyGameMode::PreLoginAsync(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId,
    const FOnPreLoginCompleteDelegate& OnComplete)
{
    // Flock's check, then the engine's own (PreLogin, such as a full game), then your own below.
    const TWeakObjectPtr<AMyGameMode> Mode = this;
    FlockJoinChecks::CheckJoiningPlayer(this, Options, Address, UniqueId, OnComplete,
        [Mode](const FFlockJoiningPlayer& Player, FString& OutRefusedReason)
        {
            if (Mode.IsValid() && Mode->IsBanned(Player.PlayerId))
            {
                OutRefusedReason = TEXT("banned");   // the joining player's RefusedReason
                return false;
            }
            return true;
        });
}
```

تحقّقك الخاص اختياري، ولا يجري إلا للاعب أدخله Flock. ويصله `PlayerId` الذي تحقّق منه Flock،
و`Options` الخاصة بالانتقال دون رمز Flock، و`Address` (‏127.0.0.1 للاعب جاء عبر وسيط Flock). وفي
أوضاع اللعب الخاصة بـ Flock، تجاوز **Approve Joining Player** (`ApproveJoiningPlayer`) بدلًا من ذلك؛
وهو يُدخل الجميع ما لم تتجاوزه.

واعرف من صاحب الاتصال، على المضيف، كما في `PostLogin`:

```cpp theme={null}
FString PlayerId = Session->PlayerForConnection(PlayerController);   // empty for a connection Flock's check did not let in
```

يكون `RefusedReason` للاعب المرفوض واحدًا من `FlockTravelToSessionRefusedReasons`، أو سببك الخاص، أو
سبب المحرّك (مثل `Server full.`)، فاحتفظ بحالة افتراضية. ويذكر سجل اللاعب المنضم الإصلاح كذلك.

| السبب | متى | ماذا تفعل |
| - | - | - |
| `no_join_token` | لم يحمل انتقال اللاعب أي رمز: لم يأتِ عبر `TravelToSession`. | اتصل بـ `TravelToSession`. |
| `join_token_invalid` | قال Flock إن الرمز لا يصلح لهذه الجلسة: مختلَق، أو لجلسة أخرى، أو منتهي الصلاحية (تدوم الرموز 60 ثانية)، أو تخلّى لاعبه عن المقعد. | انضم إلى الجلسة من جديد، ثم انتقل. |
| `already_connected` | اللاعب نفسه داخلٌ أصلًا عبر اتصال آخر ما يزال المضيف يسمع منه. | أغلق لعبة اللاعب الأخرى، ثم انتقل من جديد. |
| `flock_unreachable` | لم يجب Flock المضيف خلال 8 ثوانٍ، أو تجاوز المضيف عدد تحقّقاته في الدقيقة. ولا يدخل أحد دون تحقّق. | تأكّد أن جهاز المضيف متصل بالإنترنت، ثم انتقل من جديد. |
| `session_ended` | انتهت الجلسة. | استضِف جلسة أخرى أو انضم إليها. |
| `not_host` | الجهاز الذي وُصل إليه لم يعد يستضيف الجلسة. | انتقل من جديد: فهو يجد المضيف الجديد. |
| `refused_by_the_game` | ردّ تحقّقك الخاص اللاعب دون سبب خاص به. | أعطِ اللاعب سببًا من عندك. |
| `join_check_missing` | لا يستدعي وضع اللعب لدى المضيف تحقّق Flock، فلا يدخل أحد. | ابنِ وضع اللعب على أحد أوضاع Flock، أو استدعِ `CheckJoiningPlayer` من `PreLoginAsync` الخاص به. |
| `relay_only` | قيمة `Relay` لدى المضيف `Always`، ووصل اللاعب إليه بطريق آخر. | اضبط `Relay` بالقيمة نفسها في لعبة كل لاعب. |

وفي Blueprint، تجاوز **Approve Joining Player** في وضع لعب مبني على **Flock Game Mode Base**؛ ويصل
Refused Reason فيه إلى Travel To Session لدى اللاعب (والفارغ يُقرأ Refused By The Game). وسمِّ
اللاعبين بـ **Player For Connection** على الجلسة، كما في **OnPostLogin**.

> [!NOTE]
>
> شغّل Flock Unreal SDK نفسه في لعبة كل لاعب. فاللاعب على الإصدار 1.30.0 أو أقدم لا يرسل رمز انضمام
> (`no_join_token`)، واللاعب على 1.33.0 أو أقدم لا يستطيع المرور عبر الوسيط.

## حين يتغيّر المضيف

حين يغادر المضيف أو تتوقف لعبته، يجعل Flock اللاعب الأقدم جلوسًا مضيفًا ويطلق حدث تغيّر المضيف في الجلسة
على كل لعبة (`HostChanged` في Unity، و`OnHostChanged` في Unreal). ولا يستطيع Netcode for GameObjects
ولا شبكة Unreal نقل لعبة جارية إلى مضيف جديد، فتبدأ كل لعبة شبكتها من جديد: يستضيف المضيف الجديد، وينضم
إليه الآخرون. وافعل الشيء نفسه حين يتوقف وسيط هذا الجهاز عن العمل (`RelayLost` في Unity، و`OnRelayLost`
في Unreal).

**Unity**

أغلق الشبكة وابدأها من جديد:

```csharp theme={null}
session.HostChanged += StartAgain;
session.RelayLost += reason => StartAgain();

async void StartAgain()
{
    if (session.HasEnded)
        return;
    NetworkManager.Singleton.Shutdown();
    await session.StartNetcodeAsync(NetworkManager.Singleton);   // waits for the shutdown to finish
}
```

يعود اللاعبون إلى اللعب بعد نحو 20 ثانية من مغادرة المضيف، وبعد نحو دقيقة من تعطّل لعبته (**مهلة
المضيف**). ويضيع كل ما كان يحمله المضيف القديم: كائناته المُنشأة، ومتغيّراته الشبكية، وحالة مشهده.
فاحفظ ما يحتاجه المضيف الجديد لدى كل لاعب، مثل لقطة من حالة اللعبة يرسلها المضيف كل بضع ثوانٍ، وأعد
البناء منها. وأثناء انتظار استبدال مضيف متعطّل، يفقد اللاعبون اتصال الشبكة أولًا؛ فأبقِ الجلسة وانتظر
`HostChanged`.

**Unreal**

استدعِ `TravelToSession` من جديد بالخيارات نفسها، من Game Instance الخاصة بلعبتك. والانتقال الأحدث
يُنهي الجاري بـ `restarted`، ويؤدّي دور هذا الجهاز من حيث كانت اللعبة.

```cpp theme={null}
// Once the session is held.
Session->OnHostChanged.AddDynamic(this, &UMyGameInstance::PlayAgain);
Session->OnRelayLost.AddDynamic(this, &UMyGameInstance::PlayAgainAfterRelayLost);

void UMyGameInstance::PlayAgain()
{
    if (!Session->HasEnded())
    {
        Session->TravelToSession(this, TravelOptions, [](TFlockResult<FFlockTravelToSessionResult> Result)
        {
            // restarted: a newer call took over, and its own answer follows.
        });
    }
}

void UMyGameInstance::PlayAgainAfterRelayLost(const FString& Reason)
{
    PlayAgain();
}
```

| كانت هذه اللعبة | والآن | ما يفعله Travel To Session |
| - | - | - |
| المضيف | ما تزال المضيف | يُبقي مستواه ولاعبيه، ويفتح عنوانه على الوسيط من جديد إن فُقد، وينشر من جديد. والنشر المرفوض، أو الإلغاء، أو استدعاء أحدث، يُبقي المستوى مستمعًا. |
| المضيف | لاعبًا | يتوقف عن الاستماع (فتُغلق اتصالات لاعبيه)، وينتظر المضيف الجديد، وينضم إليه. |
| لاعبًا | المضيف | يفتح المستوى الذي لعب فيه مع المضيف القديم مستمعًا، ما لم يسمِّ `HostLevel` غيره، وينشر. |
| لاعبًا | لاعبًا | ينتظر المضيف الجديد في المستوى الذي هو فيه، ثم يتصل؛ ويُغلق المحرّك الاتصال القديم. |
| لاعبًا في لعبة المضيف | لاعبًا، والمضيف نفسه | يجيب بـ `connected` في الحال ويُبقي اتصاله، فيمكن أن يعمل Begin Play لمستوى يستدعيه على كل لاعب. |

ويُطلَق **On Relay Lost** على اللاعب أيضًا حين ينشر مضيفه عنوانًا آخر على الوسيط (إذ فُقد وسيط
المضيف نفسه)، لأن وسيط اللاعب لم يعد يبلغه. أما عنوان مضيف جديد فيخبر به **On Host Changed** وحده.

تتلقّى الألعاب On Host Changed خلال نحو 20 ثانية من مغادرة المضيف، وتعود إلى لعبة المضيف الجديد بعد نحو
10 ثوانٍ أخرى (نحو 30 ثانية في المجموع)؛ أما لعبة المضيف التي تتوقف دون مغادرة فتُستبدل بعد ما يزيد قليلًا
على دقيقة (**مهلة المضيف**، ثم مسح Flock الدوري)، ويعود لاعبوها بعد نحو 80 ثانية. ويضيع كل ما كان يحمله المضيف القديم: ممثّلوه
(actors)، وحالة لعبته، وحالة مستواه. فاحفظ ما يحتاجه المضيف الجديد لدى كل لاعب، وأعد البناء منه.

واتصال اللاعب بمضيف رحل دون كلمة تنتهي مهلته في المحرّك (دقيقة افتراضيًا)، فيطلق المحرّك
**NetworkError** ويعيد اللعبة إلى مستواها الافتراضي. واستدعاء Travel To Session من جديد من حدث NetworkError في
Game Instance، ما دامت الجلسة قائمة، يأخذ اللاعب إلى مضيف الجلسة، أيًّا كان حينها. ويُدخل المضيف
اتصال اللاعب الأحدث متى لم يسمع شيئًا من الاتصال الأقدم مدة 5 ثوانٍ، برمز الانضمام الجديد الذي
يجلبه Travel To Session.

وفي Blueprint، اربط **On Host Changed** و**On Relay Lost** للجلسة في Game Instance Blueprint، كلًّا
منهما بـ **Flock Travel To Session** بالخيارات نفسها. ويطلق الانتقال الذي يحلّ محلّه **On Not Joined**
مع Outcome بالقيمة **Restarted**، ولا يحتاج شيئًا. وهذا بالضبط ما يفعله مثال اللعب الجماعي في الإضافة:

![Set Session ثم Bind Event إلى On Host Changed وOn Relay Lost وOn Ended؛ ويستدعي الحدثان HostChanged وRelayLost حدث Travel Again](https://mintcdn.com/qwacks/sKCVX05gMuqzHxSa/images/guides/unreal-multiplayer-sample-play.png?fit=max\&auto=format\&n=sKCVX05gMuqzHxSa\&q=85\&s=792db93d3303064888af1fe309d5da21)

يحفظ Game Instance في المثال الجلسة، ويربط On Host Changed وOn Relay Lost بحدث Travel Again.

![فرعان (Branch)، ثم Flock Travel To Session، وOn Joined يضبط الحالة، وOn Not Joined يقارن Outcome بقيمة Restarted، وOn Failure يغادر الجلسة](https://mintcdn.com/qwacks/sKCVX05gMuqzHxSa/images/guides/unreal-multiplayer-sample-travel-again.png?fit=max\&auto=format\&n=sKCVX05gMuqzHxSa\&q=85\&s=d4b8c6f1750cf95f0eeae1cf5066f9fe)

Travel Again: عقدة Flock Travel To Session للجلسة المحفوظة. لا تحتاج Restarted شيئًا؛ وأي تعذّر آخر للدخول يتخلّى عن المقعد.

## شبكات أخرى

الألعاب التي تستخدم Steam أو Epic Online Services أو شبكتها الخاصة تستفيد كذلك من فرق Flock ومطابقته
وجلساته وتحقّق الانضمام. وهي تنشر الاتصال وتقرؤه بنفسها.

**Unity**

```csharp theme={null}
// The host says how to reach it: a kind of its own and the values it needs, under 4 KB.
await session.PublishConnectionAsync("steam", new Dictionary<string, object> { ["lobby"] = lobbyId.ToString() });

// A player waits for it; null when the time passes or the session ends.
FlockMultiplayerSessionConnection connection = await session.WaitForConnectionAsync(TimeSpan.FromSeconds(60));

// The player proves its seat with a token, good for 60 seconds, sent over your own networking.
FlockMultiplayerSessionJoinToken token = await session.RequestJoinTokenAsync();

// The host checks it and learns who the player is; a bad token throws MultiplayerInvalidJoinToken.
string playerId = await session.VerifyJoinTokenAsync(tokenFromPlayer);
```

ينشر `PublishDirectConnectionAsync(port)` عنوان هذا الجهاز نفسه، ويختار `connection.FindDirectAddressAsync()`
لدى اللاعب العنوان المناسب: العنوان المحلي للمضيف على شبكة مشتركة، وعنوانه العام فيما عدا ذلك. وخارج
الشبكة المحلية لا يصل اللاعبون إلى ذلك العنوان إلا عبر منفذ مُوجَّه إلى المضيف.

**Unreal**

```cpp theme={null}
// The host says how to reach it: a mode of its own (32 characters at most) and the values it needs, under 4 KB.
Session->PublishConnection(TEXT("steam"), FFlockCommandData().Set(TEXT("lobby"), LobbyId), nullptr);

// A player waits for it: the session comes back with HasConnection(), HasEnded(), or neither when the time passes.
Session->WaitForConnection(60.f, [this](TFlockResult<UFlockMultiplayerSession*> Waited)
{
    FString Lobby;
    if (Waited.IsSuccess() && Waited.Value->HasConnection()
        && Waited.Value->GetConnection().Mode.Equals(TEXT("steam"), ESearchCase::CaseSensitive)
        && Waited.Value->GetConnection().Values.TryGetString(TEXT("lobby"), Lobby))
    {
        JoinSteamLobby(Lobby);
    }
});

// The player proves its seat with a token, good for 60 seconds, sent over your own networking.
Session->RequestJoinToken([this](TFlockResult<FFlockMultiplayerSessionJoinToken> Token)
{
    if (Token.IsSuccess()) { SendToHost(Token.Value.Token); }
});

// The host checks it and learns who the player is; a bad token fails with MultiplayerInvalidJoinToken.
Session->VerifyJoinToken(TokenFromPlayer, [this](TFlockResult<FString> PlayerId) { /* PlayerId.Value */ });
```

وينتهي Travel To Session كذلك بـ `not_direct` حين ينشر المضيف اتصالًا من نوعه الخاص، ويحمله في
`Connection` ضمن النتيجة. وينشر `PublishDirectConnection(Port)` عنوان هذا الجهاز نفسه، ويختار
`FindDirectAddress` لدى اللاعب العنوان المناسب: العنوان المحلي للمضيف على شبكة مشتركة، وعنوانه العام
فيما عدا ذلك. وخارج الشبكة المحلية لا يصل اللاعبون إلى ذلك العنوان إلا عبر منفذ مُوجَّه إلى المضيف.

وفي Blueprint هي **Flock Publish Connection**، و**Flock Wait For Connection** (تطلق On Connection أو
On Session Ended أو On Timed Out أو On Failure)، و**Flock Request Join Token**، و**Flock Verify Join
Token**، و**Flock Publish Direct Connection**، و**Flock Find Direct Address**.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.