# Foundry VTT Module Generation Guide

This guide describes the processes and technical standards for extracting D&D content (Subclasses, Features, and Spells) into a functional `dnd5e` system module.

## Overview
The goal is to maintain a source-controlled set of JSON files that can be compiled into the binary LevelDB format required by modern Foundry VTT (V11+). This workflow prioritizes referencing existing system data (like SRD or PHB spells) over duplication.

---

## Item & Subclass Extraction
When extracting data from source documents (PDFs/Backups), map them into the `dnd5e` JSON schema.

- **Source Storage:** Individual JSON files are kept in the `src/` directory of the module.
- **Key Fields:**
  - `type`: Must be `"feat"` for features or `"subclass"` for the main archetype.
  - `_id`: A unique 16-character alphanumeric string (e.g., `msfeature0000001`).
  - `system.identifier`: A slugified name (e.g., `hunters-sense`) required for internal system linking.
  - `system.classIdentifier`: For subclasses, this must match the parent class slug (e.g., `ranger`).

## UUID Discovery (Linking Existing Data)
To link to standard spells or features without duplicating them, you must find their unique `UUID`.

- **Tool:** Use `find_spells.py` to query LevelDB databases in `user-data/`.
- **Database Keys:** Items in LevelDB are stored with the prefix `!items!{id}`.
- **UUID Format:** `Compendium.{module-name}.{pack-name}.Item.{item-id}`.

Example Python lookup:
```python
db = plyvel.DB('user-data/Data/modules/dnd-players-handbook/packs/spells', create_if_missing=False)
# Iterate and find _id for "Protection from Evil and Good"
```

## Advancement & Spell Mapping
Advancements define what happens when a character levels up.

- **ItemGrant Schema:** Modern `dnd5e` (v3+) requires the `configuration.items` array to contain objects with a `uuid` key:
  ```json
  "items": [{ "uuid": "...", "optional": false }]
  ```
- **Always Prepared Spells:** Subclass spells should be configured to not count against the character's limits:
  ```json
  "spell": {
    "preparation": "always",
    "alwaysPrepared": true
  }
  ```
- **Applied Effects:** To ensure an Active Effect is applied when an activity is used, add its ID to both the `effects` array of the activity and the `appliedEffects` array:
  ```json
  "activities": {
    "activity-id": {
      "effects": [{ "_id": "effect-id" }],
      "appliedEffects": ["effect-id"]
    }
  }
  ```
- **Limited Uses Schema (v3+):** Features with limited uses must use the new `spent` and `recovery` structure:
  ```json
  "uses": {
    "spent": 0,
    "max": "max(1, @abilities.wis.mod)",
    "recovery": [{ "period": "lr", "type": "recoverAll" }]
  }
  ```
  Where `spent` is the number of used charges (usually 0 initially) and `recovery` is an array of objects defining the reset period (`sr` for short rest, `lr` for long rest).
- **Best Practice:** Merge features granted at the same level into a single `ItemGrant` advancement to minimize UI prompts.

## Asset & Icon Management
Icons must be reachable by the client browser.

- **Pathing:** Always use **leading slashes** for absolute paths.
- **Referencing:**
  - **Core:** `/icons/...`
  - **System:** `/systems/dnd5e/icons/...`
  - **Modules:** `/modules/{module-id}/assets/...`
- **Example:** `"img": "/modules/dnd-players-handbook/assets/icons/classes/hunter.webp"`

## Compendium Compilation
Foundry VTT requires a specific LevelDB directory structure.

- **Compiler:** `build_db.py` reads from `src/` and writes to `packs/`.
- **LevelDB Format:** The binary value in LevelDB is the raw JSON string (no prefix), while the key is prefixed with `!items!`.
- **Embedded Effects:** Modern LevelDB stores embedded documents (like Active Effects) as separate keys: `!items.effects!{item_id}.{effect_id}`. `build_db.py` automatically handles this by extracting the `effects` array from the source JSON and creating these sub-keys.
- **Autodetection:** The `module.json` must contain the following flags for the pack to be scanned for subclasses:
  ```json
  "flags": { "dnd5e": { "types": ["feat", "subclass"] } }
  ```

## Automated Module Logic
To automate complex mechanics, include JavaScript files in the `scripts/` directory.

- **Modular Design:** Keep different features in separate files (e.g., `hunters-sense.mjs`, `slayers-prey.mjs`).
- **Registration:** Add every script to `module.json` under `esmodules`:
  ```json
  "esmodules": [
    "scripts/hunters-sense.mjs",
    "scripts/slayers-prey.mjs"
  ]
  ```
- **Hooks:** Use system-specific hooks to trigger logic.
  - `dnd5e.preUseActivity`: Triggered before an activity is processed. Return `false` to cancel and refund usage.
  - `dnd5e.postUseActivity`: Triggered after successful usage.
- **Example Pattern:**
  1. Add a `utility` activity to the feature JSON.
  2. Create a specific `.mjs` file for the feature.
  3. Filter hooks by `activity.item.system.identifier`.
- **Socketlib GM Execution with Direct Target Tracking:**
  - Use `socketlib` (`farling42/foundryvtt-socketlib`) to execute active effect operations on a connected GM client when non-GM players modify target NPC/Monster tokens.
  - **Direct Target Tracking Optimization:** Instead of sweeping all scenes/actors (`game.scenes` loops), store a tracking effect (`Slayer's Prey (Active)`) on the Ranger recording `targetUuid`.
  - When re-targeting or resting, fetch the previous target document directly via `const targetDoc = await fromUuid(targetUuid)` and delete its active effect directly.
  - Register module and handlers on `socketlib.ready`:
    ```javascript
    Hooks.once("socketlib.ready", () => {
      socket = socketlib.registerModule("monster-slayer-subclass");
      socket.register("removeSlayersPreyMarks", removeSlayersPreyMarks);
      socket.register("applySlayersPreyMark", applySlayersPreyMark);
    });
    ```
  - Call handlers as GM via `await socket.executeAsGM("applySlayersPreyMark", rangerUuid, rangerId, targetUuid, effectData)`.

## Exporting to SQLite
If you need to perform complex relational queries or analyze your compendium data using SQL, you can dump the LevelDB into a SQLite database.

- **Tool:** `ldb_to_sqlite.py`
- **Usage:**
  ```bash
  python3 ldb_to_sqlite.py [path/to/leveldb] [output-name.db]
  ```
- **Structure:**
  - `key`: The raw LevelDB key (e.g., `!items!msfeature0000001` or `!folders!...`).
  - `value`: The full raw JSON string or data associated with that key.

## Automation with Justfile
The project includes a `Justfile` to simplify common tasks.

- **Build & Package:** Recompiles the LevelDB and creates `monster-slayer-subclass.zip`.
  ```bash
  just build
  ```
- **Zip Only:** Manually triggers the zipping process.
  ```bash
  just zip
  ```
- **Dump to SQLite:** Exports the Monster Slayer pack to a SQLite DB.
  ```bash
  just dump
  ```
- **Generic Dump:** Exports any LevelDB to a SQLite file.
  ```bash
  just dump-pack path/to/ldb output.db
  ```

## Maintenance & Updates
To update the module:
1. Edit the JSON files in `src/`.
2. Update the `item_files` list in `build_db.py` if new files were added.
3. Run `python3 build_db.py`.
4. **Restart Foundry VTT** (or lock/unlock the world) to force a re-index.
