# mod-encounter-journal

Source-of-truth for the **MoonWellClient EncounterJournal** addon. Stores
all data (tiers, instances, encounters, sections, creatures, loot) in
`acore_world` under `custom_ej_*` tables and exports them as JSON / Lua
that the client can consume directly. No DBC dependency.

## Layout

```text
mod-encounter-journal/
├── conf/                                  module configuration
├── data/sql/db-world/base/
│   ├── 01_encounter_journal_schema.sql    table definitions
│   └── 02_encounter_journal_sample.sql    sample data (Deadmines, Naxxramas)
├── data/sql/db-world/updates/             future incremental migrations
├── docs/encounter_journal_format.md       output structure reference
├── src/                                   C++ implementation (manager + commands)
└── tools/json_to_lua.py                   offline JSON → Lua fallback
```

## Tables

| Table                       | Purpose                                                    |
| --------------------------- | ---------------------------------------------------------- |
| `custom_ej_tier`            | Top-level expansion / content bucket                       |
| `custom_ej_instance`        | Dungeon / raid header                                      |
| `custom_ej_tier_instance`   | N:M tier ↔ instance                                        |
| `custom_ej_encounter`       | Boss / encounter under an instance                         |
| `custom_ej_creature`        | Creature card (portrait, name, description) per encounter  |
| `custom_ej_section`         | Tree of Overview / Abilities sections                      |
| `custom_ej_loot`            | Explicit loot table per encounter                          |

References to standard AzerothCore tables:

- `custom_ej_instance.map_id` ↔ `instance_template.map`
- `custom_ej_creature.creature_id` ↔ `creature_template.entry`
- `custom_ej_loot.item_id` ↔ `item_template.entry`
- `custom_ej_creature.creature_display_id` is optional and only used by the
  client's 3D portrait tab; pull it from
  `creature_template.modelid1..modelid4` when populating manually.

## Bitmasks

### `instance.flags`

| Bit     | Meaning           |
| ------- | ----------------- |
| `0x000` | dungeon (default) |
| `0x010` | raid              |
| `0x040` | hide difficulty   |
| `0x800` | open world        |

### `encounter.difficulty_mask`

For dungeons:

| Bit   | Meaning         |
| ----- | --------------- |
| `0x1` | normal          |
| `0x2` | heroic          |
| `0x4` | mythic (custom) |

For raids:

| Bit   | Meaning   |
| ----- | --------- |
| `0x1` | 10 normal |
| `0x2` | 25 normal |
| `0x4` | 10 heroic |
| `0x8` | 25 heroic |

### `section.flags`

| Bit    | Meaning      |
| ------ | ------------ |
| `0x1`  | starts open  |
| `0x2`  | heroic only  |

## Console / GM commands

All commands require **administrator** rights, except `info` which is
gamemaster-only. They run from the in-game chat **and** the worldserver
console.

| Command          | What it does                                                                 |
| ---------------- | ---------------------------------------------------------------------------- |
| Command              | What it does                                                                                        |
| -------------------- | --------------------------------------------------------------------------------------------------- |
| `.ej info`           | Print row counts of the in-memory cache.                                                            |
| `.ej reload`         | Re-read all `custom_ej_*` tables.                                                                   |
| `.ej validate`       | Run integrity checks (see "Validation" below).                                                      |
| `.ej importskeleton` | Generate instance/encounter/creature/section rows from `instance_template` + `instance_encounters`. |
| `.ej importloot`     | Auto-populate `custom_ej_loot` from `creature_loot_template` (skips dupes).                         |
| `.ej importall`      | Run `importskeleton` then `importloot` back-to-back.                                                |
| `.ej export`         | Write `EncounterJournalData.json` (and `.lua`) to the configured path.                              |

## Configuration

`conf/mod_encounter_journal.conf.dist` (rename to `.conf` to override):

| Option                              | Default                        | Description                                                                                   |
| ----------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------- |
| `EncounterJournal.Enable`           | `1`                            | Master switch.                                                                                |
| `EncounterJournal.Export.Path`      | (current working dir)          | Output directory; must exist & be writable.                                                   |
| `EncounterJournal.Export.JsonFile`  | `EncounterJournalData.json`    | JSON filename.                                                                                |
| `EncounterJournal.Export.LuaFile`   | `EncounterJournalData.lua`     | Lua filename. Empty string skips Lua generation.                                              |
| `EncounterJournal.Export.LuaGlobal` | `MoonWellEncounterJournalData` | Name of the Lua global the addon reads.                                                       |
| `EncounterJournal.ExportOnStart`    | `0`                            | When `1`, exports JSON/Lua right after startup load (useful for headless docker without TTY). |

## Output format

The exporter writes the following top-level structure (full per-row layout
in [`docs/encounter_journal_format.md`](docs/encounter_journal_format.md)):

```jsonc
{
  "tiers":         [ /* [id, name, flags, sort_order] */ ],
  "instances":     { /* "instance_id": [...] */ },
  "tierInstances": { /* "tier_id": [instance_id, ...] */ },
  "encounters":    { /* "instance_id": [[...], [...]] */ },
  "creatures":     { /* "encounter_id": [[...], [...]] */ },
  "sections":      { /* "section_id": [...] */ },
  "items":         { /* "encounter_id": [[...], [...]] */ }
}
```

Field order inside each row matches the layout the MoonWellClient
EncounterJournal addon expects, so the Lua file can be loaded directly:

```lua
-- in your addon Init() or once on load
local data = MoonWellEncounterJournalData
for _, tier in ipairs(data.tiers) do
    -- tier[1]=id, tier[2]=name, tier[3]=flags, tier[4]=sort_order
end
```

## Workflow

1. **Edit data** — INSERT/UPDATE rows in `custom_ej_*` tables.
2. **Validate** — `.ej validate` (no errors expected).
3. **Export** — `.ej export` (writes JSON + Lua to `Export.Path`).
4. **Ship** — copy `EncounterJournalData.lua` into the
   MoonWellClient EncounterJournal addon directory.

For loot, you can shortcut step 1:

1. Insert the encounter and a `custom_ej_creature` row with `creature_id`
   set to the boss NPC id.
2. Run `.ej importloot` — entries from `creature_loot_template` (and one
   level of `reference_loot_template`) are inserted into `custom_ej_loot`
   with `difficulty_mask` copied from the creature card. Existing rows are
   skipped via the unique key.
3. Manually clean up: drop trash items, set `faction_mask` /
   `class_mask` / `sort_order` where needed.
4. Re-run `.ej validate` and `.ej export`.

## Validation

`.ej validate` verifies:

- every `tier_instance.tier_id` and `tier_instance.instance_id` exists;
- every `encounter.instance_id` exists;
- every `encounter.first_section_id` exists or equals `0`;
- `encounter.difficulty_mask` only uses bits valid for the parent
  instance type (dungeon → `0x7`, raid → `0xF`);
- every `creature.encounter_id` exists;
- every `creature.creature_id` (if non-zero) exists in `creature_template`;
- every `section.encounter_id` exists;
- every `section.parent_section_id` / `next_section_id` /
  `sub_section_id` (if non-zero) exists;
- every `loot.encounter_id` exists;
- every `loot.item_id` exists in `item_template`;
- no `loot` duplicates by
  `(encounter_id, item_id, difficulty_mask, faction_mask, class_mask)`.

`sort_order` stability is enforced by the schema (column has no
`AUTO_INCREMENT`/random default, exporter sorts on it).

## Offline JSON → Lua

If you produce the JSON outside the worldserver (e.g., from a CI job that
talks to MySQL directly), use the bundled converter:

```sh
python3 modules/mod-encounter-journal/tools/json_to_lua.py \
    --input  EncounterJournalData.json \
    --output EncounterJournalData.lua \
    --global MoonWellEncounterJournalData
```

The output uses the same field order, key sort, and global name as
`.ej export`, so the addon does not care which path produced the file.
Whitespace / float formatting may differ slightly.
