> For the complete documentation index, see [llms.txt](https://infinitypbr.gitbook.io/magic-pig-games/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://infinitypbr.gitbook.io/magic-pig-games/other/locks-lock-picking-and-ciphers-skyrim-style-lock-picking/cipher.md).

# Cipher

v1.0

### How it works

`Cipher` manages an array of rotating wheels. Each wheel has a secret `unlockAngle`. The player rotates the wheels until all are within `closeEnough` degrees of their solution. Calling `TryOpen()` checks all wheels and opens the lock if they all match.

### Wiring up player input

The core input methods are:

```csharp
// Select which wheel is "active"
cipher.SelectWheel(int index);

// Convenience properties for cycling through wheels
cipher.SelectWheel(cipher.NextWheelIndex); // move to next
cipher.SelectWheel(cipher.PrevWheelIndex); // move to previous

// Move the currently active wheel: -1 = up, 1 = down
cipher.MoveActiveWheel(int speed);

// Move a specific wheel by index: -1 = up, 1 = down
cipher.MoveWheel(int wheelIndex, int speed);

// Try to open the lock (call on button press)
cipher.TryOpen();
```

Example input script:

```csharp
using Lockpicking;
using UnityEngine;

public class MyCipherControls : MonoBehaviour
{
    public Cipher cipher;

    void Update()
    {
        // Cycle active wheel
        if (Input.GetKeyDown(KeyCode.Tab))
        {
            bool prev = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
            cipher.SelectWheel(prev ? cipher.PrevWheelIndex : cipher.NextWheelIndex);
        }

        // Move active wheel
        if (Input.GetKey(KeyCode.UpArrow))   cipher.MoveActiveWheel(-1);
        if (Input.GetKey(KeyCode.DownArrow))  cipher.MoveActiveWheel(1);

        // Attempt to open
        if (Input.GetKeyDown(KeyCode.Return))
            cipher.TryOpen();
    }
}
```

To move individual wheels independently (e.g. each mapped to a separate axis):

```csharp
// Move wheel 0 and wheel 1 with separate keys simultaneously
if (Input.GetKey(KeyCode.Q)) cipher.MoveWheel(0, -1);
if (Input.GetKey(KeyCode.A)) cipher.MoveWheel(0,  1);
if (Input.GetKey(KeyCode.W)) cipher.MoveWheel(1, -1);
if (Input.GetKey(KeyCode.S)) cipher.MoveWheel(1,  1);
```

### Resetting the lock

```csharp
cipher.ResetLock();
// Assigns a new random unlockAngle and startAngle to every wheel,
// resets isOpen, and plays the close animation.
```

`ResetLock` picks angles aligned to symbol positions — the result is always a valid, snapped angle. If `quickReset` is true, the wheels snap instantly. If false, they rotate to their start positions.

### Setting a specific solution

To define an exact combination rather than a random one, set `resetOnAwake = false` in the Inspector, then set angles before the scene starts (or after calling `ResetLock`):

```csharp
// Angles are in degrees, snapped to the nearest symbol.
// Valid range is roughly -180 to 179.
cipher.unlockAngle[0] = 0f;
cipher.unlockAngle[1] = 60f;
cipher.unlockAngle[2] = -120f;

// Optionally set the start positions the wheels reset to
cipher.startAngle[0] = 180f;
cipher.startAngle[1] = -60f;
cipher.startAngle[2] = 0f;
```

### Reading cipher state

```csharp
bool  opened      = cipher.isOpen;            // true once opened
int   activeWheel = cipher.ActiveWheel;       // index of the selected wheel
float remaining   = cipher.DistanceLeft(i);    // degrees still needed on wheel i (0 = solved)

// Check whether all wheels are solved without triggering the open
bool allSolved = true;
for (int i = 0; i < cipher.wheels.Length; i++)
{
    if (Mathf.Abs(cipher.DistanceLeft(i)) > cipher.closeEnough)
    {
        allSolved = false;
        break;
    }
}
```

### Events

```csharp
cipher.lockOpenEvents.AddListener(OnCipherOpened);

void OnDestroy()
{
    cipher.lockOpenEvents.RemoveListener(OnCipherOpened);
}
```

### Difficulty variables

| Field               | Default | Effect                                                                                                                                                      |
| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `closeEnough`       | `3`     | Degrees of tolerance for each wheel to count as solved. Larger = easier.                                                                                    |
| `symbolCount`       | `18`    | Number of symbols on each wheel (determines how many valid stop positions exist). Changing this requires a matching texture change — see Inspector warning. |
| `moveToClosestSpot` | `false` | When true, wheels automatically snap to the nearest symbol when the player stops. Enable for a more forgiving feel.                                         |

### Speed variables

| Field        | Default | Effect                                                       |
| ------------ | ------- | ------------------------------------------------------------ |
| `turnSpeed`  | `180`   | Degrees per second each wheel rotates at full input speed    |
| `quickReset` | `false` | If true, wheels snap instantly on reset instead of animating |
