aboutsummaryrefslogtreecommitdiff
path: root/building_guide/courses.md
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-05 22:35:21 -0400
committerhistoria <[not public]>2026-07-05 22:35:21 -0400
commit94823b52168b44894fb5e9c960358ed02ebb122b (patch)
tree3060799b89bb7edfa708c72bdd981107d40f4d09 /building_guide/courses.md
parent843fa6db681e494bf46e191655323a40577542b5 (diff)
downloadthehouseoficarus-94823b52168b44894fb5e9c960358ed02ebb122b.tar.gz
feat: courses gui overhaul, add course tab to map, add hidden exits to give courses structure
Diffstat (limited to 'building_guide/courses.md')
-rw-r--r--building_guide/courses.md75
1 files changed, 41 insertions, 34 deletions
diff --git a/building_guide/courses.md b/building_guide/courses.md
index e11f909..e5deb32 100644
--- a/building_guide/courses.md
+++ b/building_guide/courses.md
@@ -4,6 +4,10 @@ Agility courses are defined in `data/courses/<id>.yaml`. Each course defines a s
**The filename is the ID** (`vent_shaft.yaml` → `vent_shaft`); the loader derives it from the filename. Do not put an `id:` field in the file — it is ignored.
+> Editing courses is done from the **Map screen** in the admin web GUI: select an obstacle room and use its **Course** side-panel tab to create a course, add the room as a step, edit all of its parameters, or remove it. The legacy standalone "Courses" editor tab has been removed.
+>
+> When a room is added as a course step, the admin GUI auto-creates **hidden one-way exits** between consecutive obstacle rooms so the in-game map can lay them out correctly. The direction of these hidden exits is chosen by the builder. Hidden exits are invisible in `look` output and cannot be traversed — they exist solely for map rendering. On the map, course links appear as blue dashed one-way arrows (colored via the `map_course` theme token).
+
### Course YAML
```yaml
@@ -14,29 +18,27 @@ completion_xp: 40
obstacles:
- room_id: 201
verb: scramble
- ticks_per_phase: 2
xp: 8
fail_damage: [1, 2]
- messages:
- - "You approach the corroded ventilation wall..."
- - "You find footholds in the rusted panels and begin to climb..."
- - "You scramble up the wall and haul yourself onto the ledge!"
+ fail_chance: 0.25 # optional; omit to derive from agility level
+ phases:
+ - message: "You approach the corroded ventilation wall..."
+ delay: 0
+ - message: "You find footholds in the rusted panels and begin to climb..."
+ delay: 2
+ fail_check: true # the failure roll happens on this phase
+ - message: "You scramble up the wall and haul yourself onto the ledge!"
+ delay: 2
- room_id: 202
verb: balance
- ticks_per_phase: 2
- xp: 8
- fail_damage: [1, 2]
- messages:
- - "You step onto the narrow coolant pipe..."
- - "Arms outstretched, you carefully place one foot in front of the other..."
- - "You reach the other side of the pipe and step onto solid ground!"
+ ...
```
### CourseConfig Fields
| Field | Type | Description |
| ---------------- | ------------- | ---------------------------------------------------- |
-| `id` | string | Unique course identifier |
+| `id` | string | Unique course identifier (filename stem) |
| `name` | string | Display name shown to players |
| `required_level` | int | Minimum Agility level to attempt the course |
| `start_room` | int | Hub room — teleported here on fail or lap completion |
@@ -45,31 +47,36 @@ obstacles:
### ObstacleDef Fields
-| Field | Type | Description |
-| ----------------- | -------- | --------------------------------------------------------------------------- |
-| `room_id` | int | Room ID for this obstacle |
-| `verb` | string | Command the player types to attempt it (e.g. `scramble`, `jump`, `climb`) |
-| `ticks_per_phase` | float64 | Ticks between each phase message (supports fractional via `engine.ToTicks`) |
-| `xp` | int | XP awarded for successfully completing this single obstacle |
-| `fail_damage` | [2]int | `[min, max]` damage on failure (HP clamped to minimum 1 — cannot kill) |
-| `messages` | []string | Exactly 3 strings for the 3-phase advancement system |
+| Field | Type | Description |
+| -------------- | ------------- | ---------------------------------------------------------------------------- |
+| `room_id` | int | Room ID for this obstacle |
+| `verb` | string | Command the player types to attempt it (e.g. `scramble`, `jump`, `climb`) |
+| `xp` | int | XP awarded for successfully completing this single obstacle |
+| `fail_damage` | [2]int | `[min, max]` damage on failure (HP clamped to minimum 1 — cannot kill) |
+| `fail_chance` | float64 | Optional explicit failure probability (0–1). Omit to derive dynamically. |
+| `phases` | []ObstaclePhase | Preferred form: ordered list of phases (see below) |
+| `ticks_per_phase` | float64 | **Legacy form only:** shared delay between phases (used when `phases` absent)|
+| `messages` | []string | **Legacy form only:** exactly 3 messages (used when `phases` absent) |
-### 3-Phase Message System
+**Two forms are supported.** If `phases` is provided it is used directly. Otherwise the legacy `messages` + `ticks_per_phase` form is auto-migrated: 3 phases with delays `[0, ticks_per_phase, ticks_per_phase]` and the failure check on the middle (index 1) phase. The admin GUI always writes the `phases` form when saving.
-Each obstacle advances through 3 phases, printing one message per phase:
+### ObstaclePhase Fields
-```
-Phase 0 (start): messages[0] printed immediately
-Phase 1 (middle): messages[1] printed — FAILURE CHECK happens here
-Phase 2 (end): messages[2] printed — XP awarded, teleport to next room
-```
+| Field | Type | Description |
+| ------------ | -------- | -------------------------------------------------------------------------- |
+| `message` | string | Text printed when this phase advances |
+| `delay` | float64 | Ticks to wait before printing this phase (supports fractional via `engine.ToTicks`) |
+| `fail_check` | bool | If true, the obstacle's failure roll happens on this phase (set on at most one phase) |
+
+### Phase Advancement
+
+An obstacle advances through its phases in order. For each phase, the engine waits `delay` ticks, then prints the message. If the phase carries `fail_check`, the failure roll is made *before* awarding success. On the final phase, XP is awarded and the player is teleported to the next obstacle's room (or to `start_room` if this was the last obstacle — completing a lap).
-Failure only occurs at phase 1. Success chance is based on Agility level:
-- At required level: 70% success (30% fail)
-- Each level above reduces fail chance by 1% (down to minimum 5%)
-- Fail chance capped at 60%
+### Failure
-On failure, the player takes random damage in `[fail_damage[0], fail_damage[1]]`, is teleported back to `start_room`, and must restart the course.
+- If the obstacle defines `fail_chance`, that value (clamped to 0–1) is the failure probability for every attempt regardless of the player's level.
+- Otherwise the chance is derived from Agility level vs `required_level`: 30% fail at the required level, −1% per level above, clamped to 5–60%.
+- On failure, the player takes random damage in `[fail_damage[0], fail_damage[1]]`, is teleported back to `start_room`, and must restart the course.
### Lap Counting
@@ -115,4 +122,4 @@ exits:
| `leap` | leaping |
| `slide` | sliding |
-Obstacle verbs are auto-registered at startup from all course YAML files. No changes to `cmd_registry.go` needed when adding new courses — any verb in a `data/courses/` file will work.
+Obstacle verbs are auto-registered at startup from all course YAML files. No changes to `cmd_registry.go` needed when adding new courses — any verb in a `data/courses/` file will work. \ No newline at end of file