English
Configuration
EmakiDungeon ships three configuration files: the global plugins/EmakiDungeon/config.yml, the per-dungeon plugins/EmakiDungeon/world/<dungeonId>/config.yml, and the reward box GUI plugins/EmakiDungeon/gui/rewardbox.yml. After you edit them, run /edg admin reload (this command is run by a player with emakidungeon.admin); running instances are unaffected, only newly created instances pick the changes up
The top-level version and language fields are maintained by the resource sync process — do not edit them by hand. Per-dungeon configs have no schema compatibility layer and are validated strictly at load time. The "Range" column below marks keys with source-level validation; keys showing — are not range-checked by the code, and invalid values are handled in one of two ways:
- Load error (the dungeon is skipped, console reports an invalid dungeon configuration):
pool.max-worlds,enter.prepare-window-seconds,enter.wait-timeout-seconds,enter.wait-channel, thelimits.daily.time-windowformat, anddays/weeksentries underlimits - Silent fallback: the plugin drops invalid entries, clamps them to a boundary, or replaces them with the default (see each row)
Global config.yml
debug
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
debug.enabled | boolean | false | true / false | Debug anchor logging; prints detailed instance, party, and limit events to console |
debug.retention-days | int | 30 | — | How many days audit and debug logs are kept before cleanup |
limit and commands
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
limit.max-instances | int | 20 | — | Global cap on concurrent instance worlds (including spare worlds); new challenges are refused once reached |
commands.mode | string | blacklist | blacklist / whitelist | Command filter mode: blacklist refuses only what commands.rules lists, whitelist allows only what it lists |
commands.rules | list<string> | [] | See description | Rule = command root + argument pattern, for example "/tpa player"; write the placeholder player where a player name goes |
escape.blacklist | list<string> | [] | Supports * wildcards | Escape-command blacklist (for example "/tp *", "/back"); highest priority, no allow-rule can exempt it |
party and isolation
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
party.max-size | int | 4 | — | Party member cap |
party.invite-mode | string | public | public / apply / invite | What happens when a player without a party asks to join: public joins directly, apply files an application for approval, invite only lets invited players in (other values behave like invite) |
isolation.global-chat-default | boolean | false | true / false | Default global listening for new players; each player can switch with /edg globalchat at any time |
general and rewardbox
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
general.timezone | string | "" | Empty or a timezone id | Timezone for entry-limit bucketing; empty uses the server timezone |
rewardbox.enabled | boolean | true | true / false | Reward box master switch; when off, dungeon payouts go straight to the inventory, falling to the ground as drops when it is full |
script GraalJS host
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
script.enabled | boolean | true | true / false | Load scripts/<dungeonId>.js with every instance; turning it off disables scripting entirely |
script.timeout-ms | int | 5000 | — | Timeout for a single script handler (milliseconds); on timeout the script is disabled and the instance continues |
script.fail-on-error | boolean | false | true / false | Whether a script failure fails the run; false only disables the script and keeps everything else running |
monitor
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
monitor.mspt-p95-ms | int | 55 | > 0, or <= 0 to disable | Console alert plus audit log entry when the steady-state MSPT P95 exceeds the value |
monitor.mspt-p95-enter-ms | int | 200 | > 0, or <= 0 to disable | MSPT P95 threshold for the enter/prepare phase: within 65 seconds after an enter-flow marker (world acquisition, wait, or entry completion) this value is judged instead, alerting and auditing the same way; all other times it falls back to monitor.mspt-p95-ms |
monitor.memory-slope-mb-per-min | int | 64 | > 0, or <= 0 to disable | Heap slope (MB/minute) alert threshold |
monitor.queue-backlog-seconds | int | 5 | > 0, or <= 0 to disable | Sustained write-queue backlog (seconds) alert threshold |
storage / folia / action
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
storage.bstats | string | disabled | disabled only for now | bStats is not wired up this phase; it will be integrated through CoreLib once the interface lands |
folia.held-worlds-max | int | 10 | — | Global cap on suspended worlds on Folia; at capacity new challenges are refused as full and the state resets on restart |
action.<segmentId> | map | {} | Action pipeline lines | Dungeon lifecycle actions; the segment id is the key and the value is a CoreLib action pipeline |
The action section supports 11 segment ids: dungeon_enter, dungeon_exit, dungeon_room_clear, dungeon_gate_open, dungeon_wave_start, dungeon_score, dungeon_complete, dungeon_fail, dungeon_reward, dungeon_ending, dungeon_destroy. You write them with the same syntax as the CoreLib actions, for example:
yaml
action:
dungeon_enter:
- 'self | send_message text="<green>进入副本</green>"'Per-dungeon world/<dungeonId>/config.yml
When this file is missing, the plugin generates it from the bundled template at startup or reload; existing files are never overwritten
Metadata and entry
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
display-name | string | 示例副本 | Any text | Display name shown in lists and messages (bundled template default) |
description | string | 一份用于演示的线性副本配置 | Any text | Dungeon description (bundled template default) |
difficulty-group | string | default | Any identifier | Difficulty group identifier |
enter.timeout-seconds | int | 1800 | — | Per-run time limit (seconds); when it expires the run is failed: DungeonFailEvent (TIMEOUT) fires and a failure settlement runs (audit [settle] records event=fail, reason=timeout and duration_ms; settlement commands and drops still execute), players are sent back to their pre-entry position, and the instance is destroyed as fail:timeout; DungeonCompleteEvent is not fired and the leaderboard is not updated |
enter.prepare-window-seconds | int | 10 | >= 1 | Ready-confirmation window (seconds); missing the window cancels the challenge; below 1 the dungeon fails to load |
enter.wait-channel | string | bossbar | bossbar / scoreboard / chat | Progress channel for the world preparation wait; any other value fails dungeon loading |
enter.wait-timeout-seconds | int | 60 | >= 1 | Maximum world preparation wait (seconds) before auto-cancel; below 1 the dungeon fails to load |
pool.max-worlds | int | 5 | >= 1 | Instance world cap for this dungeon (including spares); at capacity players are told to retry later; below 1 the dungeon fails to load |
pool.warm-reserves | int | 2 | >= 1 (lower values clamp to 1) | Hot spare count = active instances + this value (never above pool.max-worlds); 2–3 is recommended so clone/mount finishes before the next entry and spare creation never overlaps the player's entry spike; defaults to 2 when absent |
limits entry limits
The evaluation order is fixed as month > week > day: if any tier is unsatisfied or exhausted, entry is refused and lower tiers are not evaluated. count: 0 means unlimited for that tier, and negative values are treated as 0
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
limits.daily.count | int | 1 | >= 0 | Entries per day; 0 is unlimited |
limits.daily.time-window | string | "" | "HH:mm-HH:mm" or empty | Daily challenge window; may cross midnight (for example "23:00-01:00"); a malformed value fails dungeon loading |
limits.weekly.count | int | 0 | >= 0 | Entries per week |
limits.weekly.days | list<int> | [] | 1–7 (1 = Monday) | Allowed weekdays; empty list does not restrict; a bad entry fails dungeon loading |
limits.monthly.count | int | 0 | >= 0 | Entries per month |
limits.monthly.weeks | list<int> | [] | 1–6 | Allowed week-of-month (Monday-based); empty list does not restrict; a bad entry fails dungeon loading |
limits.cooldown-seconds | int | 0 | >= 0 | Cooldown between entries; negatives become 0 |
world and difficulty
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
world.simulation-distance | int | 6 | — | Simulation distance applied per instance world |
world.entity-limit | int | 0 | — | Entity cap per instance world; 0 means unlimited |
difficulty.<tier>.mob-multiplier | double | 1.0 (normal) | >= 0 | Mob stat multiplier; negatives clamp to 0 |
difficulty.<tier>.drop-multiplier | double | 1.0 (normal) | >= 0 | Drop amount multiplier; negatives clamp to 0 |
difficulty.<tier>.respawn-count | int | 3 (normal) | — | Remaining player respawns before spectating |
The template ships normal and hard tiers (hard uses 1.5 / 1.2 / 1), and players pick one in the difficulty panel of the entry flow. Leave the difficulty section empty to keep the default two tiers, or add and remove tiers freely
score and drop
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
score.damage-weight | double | 1.0 | — | Damage contribution weight |
score.kill-weight | double | 1.0 | — | Kill contribution weight |
score.heal-weight | double | 1.0 | — | Healing contribution weight |
score.death-weight | double | 1.0 | — | Death contribution weight |
score.defense-weight | double | 1.0 | — | Damage-taken / defense contribution weight |
score.max | double | 100.0 | Falls back when <= 0 | Score cap (completion time is not weighted yet); 0 or a negative value silently restores the default 100 |
drop.levels | map | common: 1, rare: 3, epic: 5 | Integers, values below 1 become 1 | Drop level weights, used by the score-based distribution mode |
drop.default-mode | string | free | See mode-allowed | Default drop distribution mode; an unknown mode falls back to free |
drop.mode-allowed | list<string> | [free, roll, killer, score, share] | Subset of the five modes | Modes offered to the leader in the distribution panel; invalid entries are dropped, and if all are invalid the default five are used |
The five modes: free vanilla drops for anyone to pick up, roll automatic rolling, killer to the killer, score weighted by run contribution, share split across the party; drop.mode-allowed decides which of them appear in the panel
Remaining per-dungeon keys
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
commands.allow | list<string> | [] | Same syntax as global commands.rules | Extra commands allowed in this dungeon; can override the regular guard, never the escape blacklist |
interaction.containers | boolean | true | true / false | Whether containers can be opened inside the dungeon |
interaction.hoppers | boolean | false | true / false | Whether hoppers work; off by default |
timer.messages | list<int> | [300, 60, 10] | Second marks | Countdown reminder marks pushing "N seconds left" to players |
settlement.commands | list<string> | [] | Console command lines | Commands executed one by one on clear; %player% placeholder supported, handy for EmakiItem/EmakiLevel payouts |
leaderboard.max-entries | int | 100 | Falls back when < 1 | Leaderboard entries kept before evicting the worst; 0 or a negative value restores the default 100 |
Reward box GUI gui/rewardbox.yml
The layout of this GUI is fixed and not configurable; only two numbers are exposed:
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
default-pages | int | 1 | — | Base visible pages for players without a higher page permission |
capacity-slots | int | 135 | — | Total reward box capacity in slots; reaching it means the box is full - see Challenges & Rewards for the full rules |
Flow file world/<dungeonId>/flow.yml
This file holds every flow module (rooms, waves, gates, options) and is generated when missing. See Flow & Triggers for the full structure