> 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/keyhole.md).

# Keyhole

v1.0

### How it works

`Keyhole` uses two float inputs you set every frame from your input script:

| Field              | Range       | Purpose                                                      |
| ------------------ | ----------- | ------------------------------------------------------------ |
| `lockpickPressure` | `-1` to `1` | Moves the pick left (negative) or right (positive)           |
| `openPressure`     | `0` to `1`  | Applies rotational pressure to the keyhole to try to open it |

When `openPressure > 0`, the keyhole tries to turn. If the pick is within the lock's secret angle range (`LockAngle ± LockGive`), the lock opens. If not, the lock shakes and — after `breakTime` seconds — the pick breaks.

### Wiring up player input

Create a script that holds a reference to `Keyhole` and sets those two values each frame:

```csharp
using Lockpicking;
using UnityEngine;

public class MyLockControls : MonoBehaviour
{
    public Keyhole keyhole;

    void Update()
    {
        // Reset every frame first
        keyhole.openPressure    = 0f;
        keyhole.lockpickPressure = 0f;

        // Move the pick left/right (keyboard example)
        if (Input.GetKey(KeyCode.LeftArrow))
            keyhole.lockpickPressure = -1f;
        else if (Input.GetKey(KeyCode.RightArrow))
            keyhole.lockpickPressure = 1f;

        // Apply opening pressure
        if (Input.GetKey(KeyCode.Space))
            keyhole.openPressure = 1f;
    }
}
```

For analogue input (gamepad sticks), pass the raw axis value directly:

```csharp
keyhole.lockpickPressure = Input.GetAxis("Horizontal");
keyhole.openPressure     = Input.GetAxis("RightTrigger"); // 0–1
```

For reduced pressure (e.g. a "careful" mode), pass a fraction:

```csharp
keyhole.openPressure = Input.GetKey(KeyCode.LeftShift) ? 0.4f : 1f;
```

### Opening the lock from code

Normally the lock opens automatically when the pick is in position and pressure is applied. You can also open it directly:

```csharp
keyhole.OpenLock();
```

This plays the open animation and audio, fires `lockOpenEvents`, and sets `LockIsOpen = true`. Calling it again after it is already open does nothing.

### Resetting the lock

```csharp
keyhole.ResetLock();
// Randomises LockAngle, LockGive, and CloseDistance within their configured ranges,
// resets LockIsOpen to false, and replays the lock-pick-insert animation.
```

To set specific values instead of randomising:

```csharp
// SetLock(angle, give, closeDistance)
keyhole.SetLock(45f, 10f, 15f);

// Or pass min/max ranges — a random value within each range is chosen
keyhole.SetLock(
    lockAngleMin:     -90f, lockAngleMax:     90f,
    lockGiveMin:        5f, lockGiveMax:      20f,
    closeDistanceMin:   5f, closeDistanceMax: 20f
);
```

### Reading lock state

```csharp
bool   opened        = keyhole.LockIsOpen;       // true once the lock has been opened
float  pickAngle     = keyhole.LockPickAngle;    // current angle of the pick (-180 to 180)
float  keyholeAngle  = keyhole.KeyholeAngle;     // current rotation of the keyhole
float  turnValue     = keyhole.KeyholeTurnValue; // 0 = fully closed, 1 = fully open
float  breakProgress = keyhole.BreakTimeCounter;  // how long the pick has been stressed (0–breakTime)
```

### Events

Wire these in the Inspector or subscribe in code:

```csharp
// Lock opened successfully
keyhole.lockOpenEvents.AddListener(OnLockOpened);

// Lock pick broke (player must start again)
keyhole.lockpickBrokeEvents.AddListener(OnPickBroke);

// Player applied pressure but pick was in the wrong position
keyhole.lockFailedEvents.AddListener(OnLockFailed);

// Always unsubscribe when done
void OnDestroy()
{
    keyhole.lockOpenEvents.RemoveListener(OnLockOpened);
    keyhole.lockpickBrokeEvents.RemoveListener(OnPickBroke);
    keyhole.lockFailedEvents.RemoveListener(OnLockFailed);
}
```

### Difficulty variables

Set these in the Inspector or at runtime before calling `ResetLock` / `SetLock`.

| Field                                   | Default      | Effect                                                                                                       |
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
| `minLockAngle` / `maxLockAngle`         | `-90` / `90` | Range from which the secret angle is chosen                                                                  |
| `minGiveAmount` / `maxGiveAmount`       | `1` / `45`   | How forgiving the correct angle window is (degrees either side). Smaller = harder.                           |
| `minCloseDistance` / `maxCloseDistance` | `5` / `20`   | How far away the pick can be and still cause a partial turn (feedback to the player). Larger = more helpful. |
| `breakTime`                             | `1`          | Seconds of incorrect pressure before the pick breaks. Lower = harder.                                        |
| `breakPause`                            | `2`          | Seconds of input lockout after a break.                                                                      |

{% hint style="info" %}
**Tip — skill-based difficulty:** multiply `LockGive` by a player-skill factor before setting the lock. A high-skill player gets a larger window:
{% endhint %}

```csharp
float give = baseLockGive * playerSkillMultiplier;
keyhole.SetLock(randomAngle, give, randomCloseDistance);
```

### Speed variables

| Field                | Default | Effect                                                                   |
| -------------------- | ------- | ------------------------------------------------------------------------ |
| `turnSpeedLockpick`  | `25`    | Degrees per second the pick moves (at full input)                        |
| `turnSpeedKeyhole`   | `25`    | Degrees per second the keyhole rotates (at full input)                   |
| `returnSpeedKeyhole` | `150`   | Degrees per second the keyhole returns to rest when pressure is released |
