# Flexible Survival RenPy Writer Guide

This project uses small RenPy files plus Python dictionaries to register content. A finished contribution should be playable from the game loop, not only present as data.

## Where Content Goes

- `game/scripts/Content/Locations/<Writer>/`: rooms and room description labels.
- `game/scripts/Content/Enemies/<Writer>/`: combat encounters and win/loss labels.
- `game/scripts/Content/Infections/<Writer>/`: transformation data.
- `game/scripts/Content/NPCs/<Writer>/`: NPC records and conversation hooks.
- `game/scripts/Content/Events/<Writer>/`: random or triggered scenes.
- `game/scripts/Content/Quests/<Writer>/`: staged quest records and scene labels.
- `game/scripts/Content/Items/`: shared items, equipment, consumables, and writer-specific items.

Use one file per closely related subject. If an Inform file defines a location, an NPC, and a quest, split those into the relevant RenPy folders unless keeping them together makes the logic easier to maintain.

## Minimum Finished Content

Every ported scene should have:

- A registered dictionary with a stable lowercase `id`.
- At least one callable `label`.
- Real display text, not debug markers or placeholder strings.
- Conditions that prevent it from appearing in the wrong place.
- Any state changes needed to avoid repeating one-time scenes forever.

Every location should have:

- `id`, `name`, `area`, `description`, `connections`, and `features`.
- A description label that says what the player sees now.
- `features.safe = True` only when exploring/combat should be disabled.

Every enemy should have:

- A registered enemy dictionary with a stable lowercase `id`.
- Core combat fields such as `name`, `description`, `area`, `stats`, and `special_combat`.
- Win/loss labels or whatever scene hooks the encounter needs after combat resolves.
- `special_combat` set only for the enemy's combat mode. Use `"default"` unless the enemy truly needs a custom retaliation pattern.
- `combat_prep` only when the enemy needs pre-combat behavior before round one. Most enemies should omit this entirely.
- `tags`, `traits`, `flags`, and `content_flags` only when the creature needs content labeling for the Tastes system. Omit empty fields.

Every quest should have:

- A quest dictionary registered with `register_quest(...)` (not `register_event()`). Quests call `register_quest()`, events call `register_event()`.
- Scene labels for each playable stage.
- Calls to `start_quest`, `set_quest_stage`, and `complete_quest` when story state changes.

Every event should have:

- A dictionary registered with `register_event(...)`.
- At least one scene label.
- A `trigger` field that controls when the event fires (see Event Trigger System below).
- Scene labels that end with `return` so control unwinds back to `game_loop`.

### Event Trigger System

Events can auto-fire during normal gameplay when the `event_system.rpy` hook runs in `game_loop`. The trigger field controls behavior:

```python
register_event({
    "id": "my_event",
    "name": "My Event",
    "trigger": "area:Campus",   # fires when player is at Campus
    "scenes": ["my_event_label"],
    "rewards": {},
})
```

Supported trigger formats:

| Trigger value | Behavior |
|---|---|
| `"area:LocationName"` | Fires when player is at that location (case-insensitive name match). |
| `"rest:LocationName"` | Fires during rest actions (`_rest_mode` set in `rest_system.rpy`) at the matching location, with 1/3 chance. |
| `"random"` | 1/40 chance (~2.5%) each time `game_loop` checks events. |
| Empty / missing | Never auto-fires (dev console only). |

**Important:** Events are tracked by index — if an event has multiple scenes `["label_1", "label_2"]`, the first one fires on the first match, the second on the next match, and so on. Once all scenes have fired, the event stops auto-firing. Use this to create multi-stage events without writing state machines.

One event fires per `game_loop` cycle. If multiple events match, the first matching event in the registry dictionary fires. This prevents stacking and keeps gameplay manageable.

### Scene label requirements

- Labels should end with `return` (not `jump game_loop`). This ensures control returns to `_dev_do_event` which then resumes `game_loop`.
- Labels can use `show_game_message()`, `pass_turn()`, menus, etc. freely — they run in a normal RenPy label context.
- Labels can modify game state directly (player stats, inventory, NPC positions, etc.). Changes persist after `return`.

## Style Rules

- Keep comments rare. Prefer clear names and compact data.
- Use no more than one blank line between blocks.
- Do not leave commented-out source code in `.rpy` files.
- Do not ship visible strings containing `DEBUG ->`, `<TODO`, or generic placeholder text.
- Keep labels lowercase with underscores: `label wahn_missing_farm_hand_stage_1:`.
- Keep IDs lowercase with underscores: `"missing_farm_hand"`.

## Enemy Combat Hooks

Enemy combat now has two separate extension points:

- `special_combat`: selects the enemy's combat AI mode and retaliation behavior.
- `combat_prep` or `combat: { "prep": ... }`: runs an optional hook during `prepforfight()` before normal combat starts.

Use `combat_prep` only for genuine opener/setup behavior such as:

- Showing a one-time pre-combat line that belongs to this encounter.
- Applying a pre-fight status, stance, or battlefield setup.
- Triggering a creature-specific opener that should happen before the first normal exchange.

Do not use `combat_prep` for:

- Standard enemy descriptions.
- Normal attack logic that belongs in the combat mode.
- Behavior that every creature should share.

The engine resolves `combat_prep` by callable or by function name string. If you use a string, that function must exist in store at init time.

## Content Labels And Tastes

Enemies and events can expose content labels for the Tastes menu through any of these optional fields:

- `tags`: list of labels.
- `traits`: list of labels.
- `flags`: dictionary of `label: True/False`.
- `content_flags`: dictionary of `label: True/False`.

The runtime gathers every truthy label it can find and shows the combined list in the Tastes screen. Writers should therefore:

- Use stable lowercase snake_case labels such as `vore`, `noncon`, or `body_horror`.
- Reuse existing labels when possible instead of inventing near-duplicates.
- Set a flag key to `True` only when that content is actually present.
- Omit fields that do not add meaningful player-facing filtering.

Enemy taste labels currently inherit tags from the linked infection as a fallback, but that is only a bridge. New content should declare labels directly on the enemy or event when the label matters for player filtering.

## Example Enemy

```renpy
init python:
    def frost_drake_combat_prep():
        renpy.store.monmindbonus += 2
        return "Cold mist spills from the drake's nostrils as it braces for the fight."

    ENEMY_FROST_DRAKE = {
        "id": "frost_drake",
        "name": "Frost Drake",
        "description": "A pale-scaled drake prowls the ruined street, frosting the ground with each breath.",
        "area": "High Rise",
        "stats": {
            "hp": 24,
            "damage": 6,
            "dexterity": 12,
            "level": 4,
        },
        "special_combat": "firebreath",
        "combat_prep": "frost_drake_combat_prep",
        "tags": ["body_horror"],
        "content_flags": {
            "oviposition": True,
        },
    }

    register_enemy(ENEMY_FROST_DRAKE)
```

## Example Quest

```renpy
init python:
    MISSING_FARM_HAND_QUEST = {
        "id": "missing_farm_hand",
        "name": "Missing Farm Hand",
        "description": "Anthony asks you to track down one of his workers.",
        "trigger": "npc:anthony",
        "prereqs": [],
        "stages": [0, 1, 100],
        "scenes": [
            "missing_farm_hand_stage_0",
            "missing_farm_hand_stage_1",
            "missing_farm_hand_stage_100",
        ],
        "rewards": {},
    }

    register_quest(MISSING_FARM_HAND_QUEST)

label missing_farm_hand_stage_0:
    "Anthony explains that one of the farm hands vanished near the edge of the fields."
    $ start_quest("missing_farm_hand", 1)
    return

label missing_farm_hand_stage_1:
    "You follow the tracks until the trail disappears into broken streets."
    $ set_quest_stage("missing_farm_hand", 100)
    return

label missing_farm_hand_stage_100:
    "You return with enough evidence for Anthony to act."
    $ complete_quest("missing_farm_hand")
    return
```

## Pre-Submit Checklist

- Run RenPy lint.
- Start a new game and reach the game loop.
- Open Status, Inventory, Equipment, Body Status, and Quest Log.
- Trigger the new scene once.
- Confirm the scene cannot repeat incorrectly.
- Search your files for `TODO`, `DEBUG ->`, `Placeholder`, and `pass`.

## Dev Console

The project includes an in-game developer console for testing content without manual save-editing or recompilation. Press `` ` `` (backtick/grave key, usually below Escape) or `F1` during gameplay to toggle it. Press `Esc` to close.

The console has seven tabs:

### Locations
Search all registered locations by name or ID. Click to teleport instantly — the game loop updates to the new room. Useful for jumping directly to a scene entry point without navigating the full connection graph.

### Items
Search all items in `ITEMS_REGISTRY`. Click to add one copy to your inventory. After spawning, open the Inventory screen to verify the item appears with its expected name, weight, and category.

### Enemies
Search all registered enemies by name or ID. Click to start combat immediately — the console closes and you fight that exact enemy. Use this to test:
- Victory/defeat scene text
- Loot drops (check inventory after winning)
- Transformation triggers (check body status after losing)
- Combat balance (damage, HP, AI behavior)

### Infections
Search all infections by name or ID. Click to apply at intensity 3 via `transform_player()`. Check body status afterward to verify the transformation text and stat changes. The Resistant feat is respected; use the System tab to toggle `DEBUG_MODE` if you need to bypass it for testing.

### State
Editable fields for HP, MaxHP, XP, level, all six core stats, libido, hunger, thirst, and humanity. Quick button shortcuts:
- **Full Heal** — HP to max, hunger/thirst/libido to 0
- **Max Stats** — all stats to 20, humanity to 100
- **Set XP Near Level-Up** — sets XP just below the threshold so the next kill triggers a level up (for testing `level_up()` flow)
- **Apply Changes** — commits all edited values to the player (edits are not live until you click this)

### Quests
Lists every registered quest with the current stage. Three actions per quest:
- **Advance** — moves to the next stage in the quest's `stages` list
- **Complete** — calls `complete_quest()` (uses max stage from quest data, or `complete_quest()` if available)
- **Reset** — calls `start_quest(id, 0)` to restart the quest

Use this to test quest branching, stage gating, and completion rewards without replaying the full chain.

### Events
Lists all registered events by name. Each event shows:
- A **Run** button that fires the event's first unfired scene label
- The event ID for reference

Below the list, a **Force Run by ID** section lets you type an event ID directly and run it. The input field is read at click time (not render time), so it reflects what you actually type.

Events fire via `renpy.call()` in a safe context (outside the screen interaction, using the jump→label pattern). Event labels must use `return` at the end to properly unwind.

Multiple-scene events advance through scenes sequentially — the same event won't re-fire its first scene until all scenes have been played.

### System
Global controls:
- **DEBUG_MODE** toggle — when on, the engine prints verbose transformation, navigation, and encounter diagnostics (visible in-game and in the Ren'Py console). Toggle on before testing infection flows to see detailed Resistant/Mutable/Microwaved roll info.
- **Day/Night** — force time of day to test daycycle-dependent scenes
- **Turns** — advance +1/+10/+50/+100 turns to test time-gated content or trigger time-based events
- **Force Encounter** — rolls a random enemy from the current area and starts combat. Tests encounter table wiring.
- **Quick shortcuts** — Full Heal, Max Stats, teleport to Bunker
- **Registry counts** — read-only summary of all seven registries (ENEMIES, INFECTIONS, LOCATIONS, ITEMS, QUESTS, NPCS, EVENTS)

### Testing workflow

The typical loop for testing new content:

1. Start a new game, reach the game loop.
2. Press `` ` `` to open the dev console.
3. **Locations tab** — teleport to your new scene's entry location.
4. **State tab** — Full Heal + set stats to a level appropriate for your encounter.
5. **Enemies tab** (if testing combat) — spawn your enemy and fight.
6. **Items tab** (if testing item gating) — spawn any key items or prerequisites.
7. **Quests tab** (if testing quests) — advance/reset stages to hit every branch.
8. **Events tab** — run your event by name (click Run) or by ID (type in the "Force Run by ID" field and click Run). Multi-scene events advance through scenes on repeated runs.
9. Re-open the console at any point to adjust state mid-test.

### Testing events

Event triggers are checked automatically each time the player reaches `game_loop` (after location entry). To test:

1. **Area triggers** — teleport to the matching location via Locations tab. Wait one turn (explore, rest, or use System tab's Turns button) so `game_loop` runs and checks events.
2. **Random triggers** — advance turns via System tab until the 1/40 roll succeeds. Or just use the Events tab's Run button for immediate testing.
3. **Rest triggers** — rest at the matching location. The `_rest_mode` flag is set around `pass_turn()` during rest actions.
4. **Multi-scene events** — run the event once, verify scene 1. Run again, verify scene 2. Once all scenes have fired, the event stops auto-firing.
5. **Dev console Force Run by ID** — type the exact event ID and click Run. This bypasses trigger conditions entirely.

The console is always available as an overlay — you can open it during dialogue, combat, menus, or exploration. Any action that requires an interaction context (combat, teleport, item use) uses the game's stash-and-jump pattern to avoid engine conflicts.
