# Cross-behaviour calls in CloudBehaviours

> Call another CloudBehaviour's endpoint from inside your own, including across different scopes.

> **Important:**
>
> This page describes an experimental feature that might change significantly before release. It's not recommended to use or rely on experimental features in production environments due to potential instability.

Cloud Code generates an `I{Behaviour}` interface for every CloudBehaviour in your module. Inject the interface with `[CloudCodeService]`, then call a member on it to get back an unexecuted `ICall`. Run the `ICall` with `ForScope(scopeId)` to invoke another instance of that CloudBehaviour right now. An `ICall` does nothing until you run it. If you build one and never call `ForScope` or `ScheduleAsync` on it, the build fails with `CCSG133`.

## Call another CloudBehaviour with ForScope

The following example shows a `MultiplayerSession`-scoped `MatchRoom` updating each player's `PlayerRecord` at the end of a match. A `PlayerRecord` is `Player`-scoped because it belongs to one player and outlives any single match:

```csharp title="MatchRoom calling PlayerRecord"
[StateScope(Scope.Player)]
public class PlayerRecord : CloudBehaviour
{
    public int MatchesPlayed;
    public int PersonalBest;

    [CloudCodeAccess(Access.Service)]
    public int RecordMatch(int score)
    {
        MatchesPlayed++;
        if (score > PersonalBest) PersonalBest = score;
        return PersonalBest;
    }
}

[StateScope(Scope.MultiplayerSession)]
public class MatchRoom : CloudBehaviour
{
    [CloudCodeSerializeProperty] private Dictionary<string, int> _scores = new();

    public async Task EndMatch([CloudCodeService] IPlayerRecord playerRecord)
    {
        foreach (var entry in _scores)
        {
            var best = await playerRecord.RecordMatch(entry.Value).ForScope(entry.Key);
            Logger.LogInformation("{PlayerId} personal best is now {Best}", entry.Key, best);
        }
    }
}
```

`ForScope` takes the scope ID of the target CloudBehaviour's own `[StateScope]`, not the caller's. In this example, `entry.Key` is a player ID, matching `PlayerRecord`'s `Scope.Player`. Awaiting the call returns that endpoint's return value directly.

### Get a scope ID

Read the scope ID to pass to `ForScope` from the CloudBehaviour's `Context`:

| Target scope               | Scope ID             | Notes                                                                                                                 |
| -------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `Scope.Player`             | `Context.PlayerId`   | The ID of the player making the current call. For the other players in a session, use `Context.Session.PlayerIds`.    |
| `Scope.MultiplayerSession` | `Context.Session.Id` | Only available when the calling CloudBehaviour is `MultiplayerSession`-scoped. `Context.Session` is `null` otherwise. |

`Context.PlayerId` is `null` when a service calls the endpoint, such as a timer or another CloudBehaviour. In those calls, pass the scope ID as an endpoint argument instead.

## Lock the target down

A cross-behaviour call arrives with no player identity. The player ID you pass to `ForScope` only selects which scope instance runs the endpoint. It doesn't make the call on that player's behalf.
The target is still an endpoint that a client can call directly. Mark the target `[CloudCodeAccess(Access.Service)]` so no player can call it directly. Refer to [Access control in CloudBehaviours](/cloud-code/cloudbehaviours/access-control.md).

## Requirements

A cross-behaviour call has the following requirements:

* Both CloudBehaviours must live in the same module. If the target is in another module, call it as a plain Cloud Code module instead.
* Mark the injected parameter with `[CloudCodeService]`. Refer to [Inject Cloud Code services](/cloud-code/cloudbehaviours/rpcs.md#inject-cloud-code-services).
* Register the target's interface in your module's `ICloudCodeSetup`. In this example, call `config.AddPlayerRecordClient()`. Refer to [Register the generated interface](/cloud-code/cloudbehaviours/rpcs.md#register-the-generated-interface).
* Name only the endpoint's real arguments. The generator strips every `[CloudCodeService]` parameter from `I{Behaviour}`, so there's no slot to pass a value into.
* Class names must be unique within the module, even across namespaces.

> **Note:**
>
> A timer always runs on the CloudBehaviour that scheduled it, so calling `ScheduleAsync` on another CloudBehaviour's `I{Behaviour}` fails the build with `CCSG132`. To defer work on another CloudBehaviour, schedule your own endpoint and call `ForScope` from inside it. Refer to [Timers in CloudBehaviours](/cloud-code/cloudbehaviours/timers.md).

## Additional resources

* [Game organization in CloudBehaviours](/cloud-code/cloudbehaviours/organize-your-game-into-behaviours.md)
* [Multiplayer game security in CloudBehaviours](/cloud-code/cloudbehaviours/secure-multiplayer-game-logic.md)
* [Timers in CloudBehaviours](/cloud-code/cloudbehaviours/timers.md)
