# Tutorial 10 — Scripted Puzzle Room

Build a room where the player must collect **10 tokens** scattered across the dungeon floor. When all tokens are collected, a key spawns — letting them open the exit door.

This tutorial goes beyond the marker-only approach and shows how to add custom C# logic to your rooms via a **separate companion mod**.

---

## Why a companion mod?

Asset bundles can contain `MonoBehaviour` components, but the **class must exist in a loaded DLL at runtime**.
`Token` and `TokenGate` don't exist in the game — you need to provide them in a BepInEx mod.

The mod doesn't need to patch the game at all. It just needs to be **loaded** by BepInEx so the types are available when Unity resolves the bundle's components.

---

## Step 1 — Create the companion mod

### From the template

Copy `project-templates/new-bepinex-mod/` to a new directory outside this repo:

```
Copy project-templates\new-bepinex-mod\ → C:\my-mods\token-puzzle\
```

Rename the files:
```
token-puzzle/
  GamePath.props.template
  .gitignore
  TokenPuzzle/
    TokenPuzzle.csproj     ← renamed from MyMod.csproj
    TokenPuzzlePlugin.cs   ← renamed from MyModPlugin.cs
```

### Configure the game path

```
cd token-puzzle
copy GamePath.props.template GamePath.props
```

Open `GamePath.props` and set your path:
```xml
<GamePath>C:\Program Files (x86)\Steam\steamapps\common\VR_DungeonKnight</GamePath>
```

### Update the .csproj

In `TokenPuzzle.csproj`, change `AssemblyName` and `RootNamespace`:
```xml
<AssemblyName>TokenPuzzle</AssemblyName>
<RootNamespace>TokenPuzzle</RootNamespace>
```

### Update the plugin

In `TokenPuzzlePlugin.cs` (renamed from `MyModPlugin.cs`):
```csharp
using BepInEx;

namespace TokenPuzzle {
    [BepInPlugin("com.yourname.token-puzzle", "TokenPuzzle", "1.0.0")]
    public class TokenPuzzlePlugin : BaseUnityPlugin {
        protected void Awake() {
            Logger.LogInfo("TokenPuzzle loaded.");
        }
    }
}
```

---

## Step 2 — Add the logic components

Create two new files in `TokenPuzzle/`:

**`TokenGate.cs`**
```csharp
using UnityEngine;

namespace TokenPuzzle {
    public class TokenGate : MonoBehaviour {
        public int TotalTokens = 10;
        public GameObject KeySpawnPoint;

        private int _collected;

        public void RegisterToken() {
            _collected++;
            if (_collected >= TotalTokens && KeySpawnPoint != null) {
                KeySpawnPoint.SetActive(true);
            }
        }
    }
}
```

**`Token.cs`**
```csharp
using UnityEngine;

namespace TokenPuzzle {
    [RequireComponent(typeof(Collider))]
    public class Token : MonoBehaviour {
        public TokenGate Gate;

        private void OnTriggerEnter(Collider other) {
            if (Gate == null) return;
            Gate.RegisterToken();
            gameObject.SetActive(false);
        }
    }
}
```

---

## Step 3 — Build and deploy the mod

```
dotnet build -c Release
```

Copy `bin/TokenPuzzle.dll` into the game:
```
VR_DungeonKnight\BepInEx\plugins\token-puzzle\TokenPuzzle.dll
```

Launch the game once to verify it loads — look for `TokenPuzzle loaded.` in `BepInEx\LogOutput.log`.

---

## Step 4 — Set up the room in Unity

For Unity to reference `TokenGate` and `Token`, the mod DLL must be in `Assets/Plugins/` in the Unity project.

### Copy the DLL into the Unity project

Copy `bin/TokenPuzzle.dll` to:
```
room-template\Assets\Plugins\TokenPuzzle.dll
```

Unity automatically picks up the DLL and exposes its types in the editor.

### Room structure

You need three object types:

| Object | Component | Purpose |
|---|---|---|
| Root | `TokenGate` | Tracks collected count / activates key spawn |
| `DoorKeySpawner` | `DoorKeySpawner` (marker) | Where the key appears — starts **disabled** |
| Token objects ×10 | `Token` | Picked up on player touch |

### Create the DoorKeySpawner (disabled)

1. Right-click root → **Create Empty** → rename `DoorKeySpawner`
2. Place it near the "merchant" decoration
3. In the Inspector: **uncheck the checkbox** at the top — it starts inactive

### Add TokenGate to the root

1. Select the root object
2. **Add Component** → search `TokenGate` (namespace `TokenPuzzle`)
3. Set **Total Tokens** to `10`
4. Drag `DoorKeySpawner` into the **Key Spawn Point** field

### Create the tokens

1. Right-click root → **Create Empty** → rename `Token_01`
2. **Add Component** → `Sphere Collider` — check **Is Trigger**, Radius `0.4`
3. **Add Component** → `Token` (namespace `TokenPuzzle`)
4. Drag the root object into the **Gate** field
5. Add a child visual (Sphere with gold emissive material, scale `0.3`)
6. Position on the floor
7. Duplicate (`Ctrl+D`) nine times → rename `Token_02` … `Token_10` → scatter

---

## Step 5 — Add the merchant decoration

The "merchant" is visual only:

1. Tall thin Cube (Scale `(0.5, 1.8, 0.5)`) as a rough humanoid silhouette
2. `DoorKeySpawner` placed in front of it
3. A flat "counter" Cube + a warm Point Light (range 4) to draw the eye

---

## Step 6 — Use a key door

Under `WhatLockIsNeeded`, create `KeyLock` (not UnbarredDoor or MonsterLock).

Full hierarchy:

```
my-pack_TokenRoom
├── SpawnHere
├── Token_01 … Token_10  (Sphere Collider + Token)
├── DoorKeySpawner  [DISABLED]
│   └── [Merchant decoration]
├── LootSpawner (×3, optional)
└── WhatLockIsNeeded
    └── KeyLock
```

Root object has `TokenGate` with Total Tokens = `10`, Key Spawn Point = `DoorKeySpawner`.

---

## Step 7 — Pool and rooms.json

```json
{
  "pool": "PuzzleRoomStructures",
  "author": "your-name",
  "description": "Collect 10 tokens to buy a key from the merchant"
}
```

---

## Design ideas

### Tokens in hard spots
- Narrow corridors, raised platforms, near traps

### Token visuals
- Emissive Sphere (gold) + child Point Light (range 2, intensity 1, gold)

### Mix with enemies
- Add `EnemySpawn` markers — player must fight and collect simultaneously
- Switch the door to `MonsterLock` and extend `TokenGate` to check both conditions

### Fewer tokens, harder placement
- 3 tokens instead of 10, each guarded by a trap or dangerous zone
- Feels like "three key fragments" rather than a grind

---

## Troubleshooting

| Problem | Fix |
|---|---|
| `Token` component not found in Unity | `TokenPuzzle.dll` missing from `Assets/Plugins/` in the Unity project |
| `Token` appears but shows "missing script" in-game | DLL not copied to `BepInEx/plugins/token-puzzle/` in the game |
| Key never spawns | Check `DoorKeySpawner` is assigned to `TokenGate.KeySpawnPoint` |
| Player walks through tokens | `Is Trigger` must be checked on the Sphere Collider |

---

## Next

→ [11-lods.md](11-lods.md) — optimize large rooms with LOD groups
