> For the complete documentation index, see [llms.txt](https://kinematicsoup.gitbook.io/reactor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kinematicsoup.gitbook.io/reactor/tutorials/shared_entities.md).

# Shared Entities and Ownership Transfer

## Summary

This tutorial shows how to build an entity that several players take turns controlling, such as a vehicle, a turret or a mounted weapon, by transferring ownership between them instead of spawning one per player. It continues from [Client Authority and State Relay](/reactor/tutorials/authoritative_client.md), reusing that scene, room and player prefab, and adds a vehicle a player can get into and out of.

The previous tutorial built a character that belongs to one player: it spawns already owned, stays owned for its whole life, and goes away when that player leaves. A shared entity inverts all three. It exists before anyone owns it, its owner changes hands, and it must outlive whoever is currently driving. Those differences change several settings from the values a player entity wants, and they make ownership something the server hands out on request rather than something set at spawn time.

### Requirements

* [Reactor Requirements](/reactor/overview.md#requirements)
* Completion of [Client Authority and State Relay](/reactor/tutorials/authoritative_client.md)

## How a Shared Entity Differs

For this tutorial, there are some differences in how a shared entity is configured so you will need to change some settings from their default values.

**It cannot be permanent.** Permanent entities never move and are not synced to clients, since clients are assumed to already know where they are. Anything a player drives fails both conditions. Permanence also makes an entity unspawnable, which matters when you come to restore the player's own character. Make sure `IsPermanent` is not checked.

**It must survive its owner disconnecting.** `DestroyOnOwnerDisconnect` is on by default, which is correct for a player's own character and wrong for anything shared: the vehicle would be destroyed the moment its driver's connection dropped, taking it away from everyone else in the room.

**Its owner should not get every permission.** A driver needs `TRANSFORM` to move it and usually `PROPERTIES` to publish state. It rarely needs `DESTROY`, which would let any one player delete an object the whole room shares. See [Ownership and Authority](/reactor/architecture/ownership-and-authority.md) for the full permission set.

> *A shared entity usually belongs to the level rather than to a player, so it is placed in the scene rather than spawned from Resources. That is the opposite of the player prefab in the previous tutorial, and it brings its own requirement, covered in 'Publish the Scene Entity' below.*

## Scene Setup

### Create the Vehicle

1. Open the Playground scene from the previous tutorial.
2. Create a cube and name it 'Vehicle'. Position it a few meters from the player's start point.
3. Add a *CharacterController* so the vehicle can be driven around the level. Set its 'Height' and 'Radius' to cover the cube.
4. Add a *ksEntityComponent*.
5. **Uncheck 'Is Permanent'.** A permanent entity never moves and is never synced, so leaving this checked produces a vehicle that appears to work locally and never moves for anyone else.
6. **Uncheck 'Destroy On Owner Disconnect'.** Otherwise the vehicle is destroyed for every player the moment its driver disconnects.
7. In the inspector for the *CharacterController*, expand the 'Reactor Collider Data' foldout and set 'Existence' to 'Client-Only'. Movement here is client-authoritative, so the server has no need to simulate the capsule.

Do **not** add a *ksAutoSpawn*. That component spawns one entity per player, which is what a player character wants and the opposite of what a shared vehicle wants. Leave the vehicle in the scene as a single object.

### Publish the Scene Entity

Adding a *ksEntityComponent* to a scene object is not enough on its own. A scene entity exists for the server only once the scene configs are built, which assigns it an `EntityId`.

1. Build the scene configs (**CTRL + F2**).
2. Select the 'Vehicle' and confirm 'Entity Id' in the *ksEntityComponent* inspector is no longer zero.
3. **Save the scene.** The id is written into the scene file, and other clients resolve it from there.

> *An unpublished scene entity does not raise an error. Every client instead gets an ordinary local game object: the scene looks correct, nothing syncs, and each client quietly interacts with its own copy. If a shared object behaves as though every player has their own, check its 'Entity Id' before anything else.*

Rebuild the scene configs whenever you add or remove a scene entity.

## Scripting

### Create a Class for Constants

The ids for properties and RPCs have to agree on the client and the server, so they belong in a common script. In *'Assets/ReactorScripts/Common'*, create a script named 'VehicleConsts'.

*VehicleConsts.cs*

```c#
// Synced property IDs
public class VehicleProp
{
    public const uint TURRET_YAW = 100;
}

// RPC IDs
public class VehicleRPC
{
    public const uint REQUEST_CONTROL = 100;
    public const uint RELEASE_CONTROL = 101;
    public const uint CONTROL_DENIED = 102;
}
```

> *If the entity also has a* ksAnimationSync\*, that component claims a range of property ids for animator parameters starting at its 'Animation Property Ids' value, which defaults to 1000. Keep your own property ids clear of that range.\*

### Create the Server Entity Script

Only the server can assign ownership. `SetOwner` does not exist on the client, and a client cannot take an entity or hand one on. Getting in is therefore a request the server answers, and the answer arrives as an ownership change rather than as a reply.

```
client                         server
  |                              |
  |-- CallRPC(REQUEST_CONTROL) ->|
  |                              |  seat free?
  |                              |  Entity.SetOwner(player, TRANSFORM | PROPERTIES)
  |<------ ownership change -----|
  |                              |
  Entity.IsOwner is now true
```

1. Select the 'Vehicle' object.
2. In the **'Add Component'** menu, select **'Reactor->New Server Entity Script'**.
3. Name the script 'ServerVehicle'.

*ServerVehicle.cs*

```c#
using KS.Reactor.Server;
using KS.Reactor;

public class ServerVehicle : ksServerEntityScript
{
    // Called when the script is attached.
    public override void Initialize()
    {
        // A shared vehicle outlives its driver. This can also be set in the inspector.
        Entity.DestroyOnOwnerDisconnect = false;
        Room.OnPlayerLeave += PlayerLeave;
    }

    // Called when the script is detached.
    public override void Detached()
    {
        Room.OnPlayerLeave -= PlayerLeave;
    }

    // A player asked to drive. Granted only when the vehicle is free.
    [ksRPC(VehicleRPC.REQUEST_CONTROL)]
    private void OnRequestControl(ksIServerPlayer player)
    {
        if (Entity.Owner != null)
        {
            if (Entity.Owner != player)
            {
                // Tell only this player, so their UI can explain why nothing happened.
                Entity.CallRPC(player, VehicleRPC.CONTROL_DENIED, Entity.Owner.Id);
            }
            return;
        }

        // DESTROY is deliberately withheld: no single player should be able to delete
        // an object the whole room shares.
        Entity.SetOwner(player, ksOwnerPermissions.TRANSFORM | ksOwnerPermissions.PROPERTIES);
        ksLog.Info("Player " + player.Id + " took control of the vehicle.");
    }

    // A player asked to get out. Only the current driver can.
    [ksRPC(VehicleRPC.RELEASE_CONTROL)]
    private void OnReleaseControl(ksIServerPlayer player)
    {
        if (Entity.Owner == player)
        {
            Entity.SetOwner(null);
            ksLog.Info("Player " + player.Id + " released the vehicle.");
        }
    }

    // A driver who disconnects must not leave the vehicle locked.
    private void PlayerLeave(ksIServerPlayer player)
    {
        Entity.SetOwner(null);
    }
}
```

> *Ownership has already been cleared by the time* `Room.OnPlayerLeave` *runs. Do not gate disconnect cleanup on* `Entity.Owner == player`*: the comparison will not match, and any cleanup behind it is skipped without an error. If you keep state of your own alongside ownership, clear it here unconditionally.*

### Create the Client Entity Script

The client script sends the requests and reacts to ownership changing hands.

1. Select the 'Vehicle' object.
2. In the **'Add Component'** menu, select **'Reactor->New Client Entity Script'**.
3. Name the script 'ClientVehicle'.

*ClientVehicle.cs*

```c#
using UnityEngine;
using KS.Reactor.Client.Unity;
using KS.Reactor;

public class ClientVehicle : ksEntityScript
{
    // The script that reads input and drives the vehicle. Enabled only for the driver.
    public MonoBehaviour DriverScript;

    private bool m_wasOwner;

    // ksEntityScript implements Awake. Override it and call the base implementation; declaring
    // Awake without 'override' hides the base and the script is never attached to its entity.
    protected override void Awake()
    {
        base.Awake();
        if (DriverScript != null)
        {
            DriverScript.enabled = false;
        }
    }

    // True when someone other than the local player is driving. Clearing an owner forces
    // permissions back to NONE, so this is an exact test for 'has an owner'.
    public bool IsOccupiedByOther
    {
        get { return !Entity.IsOwner && Entity.OwnerPermissions != ksOwnerPermissions.NONE; }
    }

    public void RequestControl()
    {
        Entity.CallRPC(VehicleRPC.REQUEST_CONTROL);
    }

    public void ReleaseControl()
    {
        Entity.CallRPC(VehicleRPC.RELEASE_CONTROL);
    }

    private void Update()
    {
        // Drive local state from ownership, never from the key press. If the key press enables
        // control directly, every client believes it succeeded and two players end up driving
        // the same vehicle on their own screens.
        if (Entity.IsOwner != m_wasOwner)
        {
            m_wasOwner = Entity.IsOwner;
            if (DriverScript != null)
            {
                DriverScript.enabled = m_wasOwner;
            }
        }
    }

    // The server refused because someone else is already driving.
    [ksRPC(VehicleRPC.CONTROL_DENIED)]
    private void OnControlDenied(uint driverId)
    {
        ksLog.Info("Vehicle is already being driven by player " + driverId + ".");
    }
}
```

4. Create the script that actually reads input and moves the vehicle, and assign it to the 'Driver Script' field. Because the local player owns the entity with the `TRANSFORM` permission, moving the transform is enough. Reactor detects the change and syncs it.

### Ask for Control

Give the player a way to send the request. This example uses a proximity check and a key press, and belongs on whatever object manages your interactions.

```c#
private void Update()
{
    if (!Input.GetKeyDown(KeyCode.E))
    {
        return;
    }

    if (m_vehicle.Entity.IsOwner)
    {
        m_vehicle.ReleaseControl();
    }
    else if (Vector3.Distance(m_player.position, m_vehicle.transform.position) <= InteractRange)
    {
        m_vehicle.RequestControl();
    }
}
```

Note that both branches only *ask*. Nothing about the local state changes here; that happens in `ClientVehicle.Update` when ownership actually changes.

## Deciding What Syncs

A shared entity usually carries three kinds of state, and they travel by different routes.

| State                 | How it syncs                       |
| --------------------- | ---------------------------------- |
| Position and rotation | The owner's `TRANSFORM` permission |
| Animator parameters   | *ksAnimationSync*                  |
| Anything else         | Entity properties                  |

The third row is easy to overlook. Presentation that is computed in script rather than driven by the Animator, such as a turret's yaw, a steering angle or a hatch position, is not covered by *ksAnimationSync* and needs a property of its own. The owner writes it; every other client reads it.

```c#
private void LateUpdate()
{
    if (Entity.IsOwner)
    {
        // Only send when it has actually moved, to keep an idle vehicle off the wire.
        if (Mathf.Abs(Mathf.DeltaAngle(m_sentYaw, m_turretYaw)) >= SendThreshold)
        {
            m_sentYaw = m_turretYaw;
            Properties[VehicleProp.TURRET_YAW] = m_sentYaw;
        }
    }
    else
    {
        m_turretYaw = Properties[VehicleProp.TURRET_YAW];
    }

    Turret.localRotation = Quaternion.Euler(0f, m_turretYaw, 0f);
}
```

### Read Occupancy From Ownership

It is tempting to publish a property naming the current driver and let your UI read that. Ownership already answers the question, and Reactor keeps it correct on its own, including when a player disconnects. A second property is a second copy of the same truth, and the two drift: the vehicle ends up genuinely free while the interface still reports it occupied.

Keep a "who is driving" property only for what ownership cannot express, such as a display name, and derive availability from `Entity.OwnerPermissions` as `ClientVehicle.IsOccupiedByOther` does above.

## Scripts on Remote Clients

The previous tutorial set *ksOwnershipScriptManager* to 'Disable When Unowned', which suits a player character: a remote instance needs nothing but its animation. A shared entity is usually different, because some of its presentation has to be applied on every client from synced state rather than from input.

Two approaches work:

* Add a *ksOwnershipScriptManager* and set the scripts that must always run to 'Leave Unchanged', as the previous tutorial does for the *Animator*.
* Leave the manager off the entity and let one always-enabled script decide, as *ClientVehicle* does with its 'Driver Script' field. This is often clearer for a vehicle, because the same script already has to handle the ownership change.

Whichever you choose, a script that runs on a client which does not own the entity must not fight the synced state. It should not move the transform and it should not write animator parameters, because both are already arriving from the owner.

> *Do not use 'Destroy When Unowned' on an entity whose ownership can change. Destroyed scripts cannot be recreated when the local player later takes over.*

## Removing and Restoring the Player's Character

Many games hide the player's own character while they are driving. Because the player owns their character entity with the `DESTROY` permission, destroying the game object asks the server to destroy the entity, so the character disappears for everyone rather than only on the local client.

```c#
// Getting in.
Destroy(m_playerCharacter);
```

Getting out has to put one back. `ksAutoSpawn.Spawn` spawns an entity for an instantiated prefab:

```c#
// Getting out.
GameObject character = Instantiate(PlayerPrefab, exitPoint, exitRotation);
ksAutoSpawn autoSpawn = character.GetComponent<ksAutoSpawn>();
if (autoSpawn.IsSpawnable())
{
    autoSpawn.Spawn(Entity.Room);
}
```

`IsSpawnable` requires the object to have a *ksEntityComponent*, an asset id, no entity already linked, and **not** to be permanent.

> *Those conditions are read from the prefab you are instantiating, not from any instance of it in the scene. A prefab marked permanent, with 'Is Permanent' overridden on the scene instance, passes in the scene and fails on every respawn. The failure is silent, because "cannot spawn" and "already spawned" both report* `IsSpawnable() == false`*. Check* `ksEntityComponent.Entity` *to tell the two cases apart.*

If a follow camera or any other system holds a reference into the old character, re-point it at the new one. The old transform was destroyed with the old entity.

## Testing

Ownership transfer cannot be verified with a single client. Use [Unity Multiplayer Play Mode](https://docs.unity3d.com/Packages/com.unity.multiplayer.playmode@1.6/manual/index.html), [ParrelSync](https://github.com/VeriorPies/ParrelSync), or a built client alongside the editor.

1. Build the scene configs (**CTRL + F2**) and save the scene.
2. Start a local server.
3. Connect two clients.

Check each of these, since several only fail with more than one client connected:

* One client takes control and drives. The vehicle moves on the other client.
* The second client tries to take control while it is occupied and is refused.
* The first client releases control, and the second can then take it.
* A client disconnects while driving. The vehicle stops, stays in the room, and becomes available.

## Common Problems

| Symptom                                                        | Cause                                                                                                                   |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Every client drives its own copy and nothing syncs             | The scene entity was never published. Build the scene configs and confirm 'Entity Id' is non-zero, then save the scene. |
| The entity never moves for other players                       | 'Is Permanent' is still checked.                                                                                        |
| The vehicle disappears when its driver disconnects             | 'Destroy On Owner Disconnect' is still checked.                                                                         |
| Two players both believe they are driving                      | Local control is being enabled by the key press instead of by the ownership change.                                     |
| Cleanup on disconnect never runs                               | It is gated on `Entity.Owner == player`, which no longer matches by the time `OnPlayerLeave` fires.                     |
| A vehicle reads as occupied after its driver has left          | Availability is being read from a property of your own rather than from ownership.                                      |
| A respawned prefab exists only on the client that spawned it   | `IsSpawnable` returned false. Check the prefab for 'Is Permanent' and a missing asset id.                               |
| A client entity script never receives RPCs or property changes | `Awake` was declared without `override`, hiding the base implementation that attaches the script to its entity.         |

## Where to Go Next

* [Ownership and Authority](/reactor/architecture/ownership-and-authority.md): the permissions and owner-update validators this tutorial builds on.
* [Client Authority and State Relay](/reactor/tutorials/authoritative_client.md): the per-player case, for contrast.
* [Messaging and RPCs](/reactor/architecture/messaging-rpcs.md): the request half of the handshake.
* [Validators](/reactor/examples/validators.md): keeping a client-authoritative owner honest.
* [Parenting Entities](/reactor/tutorials/parenting.md): attach a turret or a rider to the vehicle players take turns driving.
* [Handling Disconnects and Reconnecting](/reactor/tutorials/reconnecting.md): what happens to a shared object when its driver drops, and how to hold it for their return.
