English
Configuration
In plugins/EmakiAccessory/config.yml and the pages/*.yml accessory pages, every key has a purpose, a default value, and an allowed range. In the main configuration, version is a configuration-structure marker maintained by the project resource sync flow; the plugin merges newly added default keys based on it
Top-Level Keys
Shipped defaults (full contents):
yaml
version: "1.1.8"
language: "zh_CN"
release_default_data: true
drop_on_death: false
unique: true
slot_sources:
pdc:
enabled: false
key: ""
lore:
enabled: false
patterns:
- "^穿戴部位[::]\\s*(.+)$"
separator: ","
aliases:
"戒指": "ring"
"项链": "necklace"
"腰带": "belt"
"护符": "charm"
persistence:
autosave_seconds: 60
drain_timeout_seconds: 10| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
version | string | No | "1.1.8" | — | Configuration-structure version maintained by the bundled resource sync flow; used to merge newly added default keys. Falls back to "1.0.0" when absent |
language | string | No | "zh_CN" | — | Language file name, resolving to lang/<language>.yml. An empty or all-whitespace value falls back to zh_CN; a missing file loads with the default language |
release_default_data | boolean | No | true | true / false | Whether missing bundled data files are released on startup. While true, the missing parts.yml, pages/main.yml, pages/secondary.yml and sets/example_set.yml are created and existing files are never overwritten; while false the default-data write is skipped (config.yml, lang/*.yml and gui/accessory_gui.yml are not governed by this switch) |
drop_on_death | boolean | No | false | true / false | Whether the enabled page's accessories are added to the death drop list |
unique | boolean | No | true | true / false | Whether two identical accessories may sit on the same page at once |
slot_sources | object | No | see "slot_sources" | — | Additional sources declaring which slots an accessory may occupy; falls back to the built-in defaults |
durability | object | No | see "durability" | — | Accessory durability switch and per-hit damage points; falls back to the built-in defaults (disabled) |
persistence | object | No | see "persistence" | — | Autosave and shutdown flush parameters |
drop_on_death
When set to true, the dead player's enabled page accessories are appended to the death drop list and handled by the vanilla death flow rather than being pushed back into the inventory — pushing them back would leave the same accessory in both the drops and the inventory
- The option has no effect when another plugin already enabled
keepInventory(the inventory is kept on death); accessories are kept either way - Only the enabled page is cleared: accessories on other pages stay in the data and are neither cleared nor dropped. When the enabled page is empty (for example the player has no access to that page), nothing is dropped
yaml
# Allow the enabled page's accessories to drop on death
drop_on_death: trueunique
Deduplication is judged within one page: only the accessories the player already holds on that page are checked, so the same accessory on two different pages never conflicts
The preferred criterion is EmakiItem's item definition id (the item's identity string emakiitem:<definitionId>): two accessories of the same definition count as duplicates even when their levels or affixes differ. When EmakiItem is unavailable, or the item carries no definition id, the check falls back to vanilla material comparison (material:<lowercased material name>), so two accessories sharing a material are treated as duplicates
yaml
# Turn deduplication off and allow two of the same accessory on one page
unique: falseslot_sources
This section adds sources for the slots an accessory can occupy. The item-carried declaration comes from emakiitem:accessory_slots, written by EmakiItem; the two options below are extra sources you opt into on top of it, and any match allows the accessory to be worn. With both options disabled (the default) only the item-carried declaration counts. Note that EmakiAttribute's active_slot and the EmakiSkills skill payload no longer take part in this decision
| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
slot_sources.pdc.enabled | boolean | No | false | true / false | Whether a slot list is appended from the item's PDC |
slot_sources.pdc.key | string | No | "" | — | Full NamespacedKey, used verbatim without case rewriting. An empty value or one that does not parse as a NamespacedKey silently disables this source (nothing is read even when enabled is true). A string list is preferred; when absent, a single string is read and split by separator |
slot_sources.lore.enabled | boolean | No | false | true / false | Whether a slot list is appended from lore using regular expressions |
slot_sources.lore.patterns | list<string> | No | see the default snippet below | — | Regex list; capture group 1 of each pattern holds the slot list text, matched with find (a substring hit is enough). MiniMessage tags are stripped and whitespace collapsed before matching, so write plain-text patterns. A pattern that fails to compile logs accessory.invalid_slot_pattern and is skipped; an empty (or fully invalid) list silently disables this source |
slot_sources.lore.separator | string | No | "," | — | Slot list separator, also used by the pdc string fallback; an empty or all-whitespace value falls back to "," |
slot_sources.lore.aliases | map<string,string> | No | see the default snippet below | — | Maps lore slot names to part ids in parts.yml. Lookup tries the original text first, then the lowercase form; unlisted values are used as normalized (trimmed, lowercased, spaces turned into underscores). Entries whose key or value is blank are dropped while loading |
Shipped default snippet:
yaml
slot_sources:
pdc:
enabled: false
key: ""
lore:
enabled: false
patterns:
- "^穿戴部位[::]\\s*(.+)$"
separator: ","
aliases:
"戒指": "ring"
"项链": "necklace"
"腰带": "belt"
"护符": "charm"Example: read 穿戴部位: 戒指,项链 from lore and map the localized slot names onto part ids
yaml
slot_sources:
lore:
enabled: true
patterns:
- "^穿戴部位[::]\\s*(.+)$"
separator: ","
aliases:
"戒指": "ring"
"项链": "necklace"Accessory pages: pages/*.yml
Each file defines one page. The page id comes from page_id, independent of the file name
yaml
page_id: "main"
display_name: "<gold>Main Accessories</gold>"
order: 1
gui_template: "accessory_gui"
permission: ""
parts:
- "necklace"
- "ring"
- "belt"
- "charm"| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
page_id | string | Yes | — | — | Page id, also the storage partition key inside player data; when it normalizes to blank the whole file is skipped |
display_name | string | No | "" | — | Page display name with MiniMessage support, used by switch cells, the enable cell and /eacc pages; when blank the window falls back to showing page_id |
order | integer | No | 2147483647 (Integer.MAX_VALUE) | — | Page sort order, ascending; the lowest value is the default enabled page, ties broken by page_id ascending |
gui_template | string | No | "accessory_gui" | — | Layout id used by this page (the id of gui/<id>.yml); several pages may share one layout. A missing layout makes the page impossible to open (accessory.gui_template_missing) |
permission | string | No | "" | — | Empty means available to everyone; a non-empty value is matched verbatim |
parts | list<string> | Yes | — | — | Parts enabled on this page; every value must be a part id defined in parts.yml. Duplicates are removed and an empty list discards the whole page (accessory.page_without_parts) |
Remember these three page rules:
- Isolated storage: the same slot instance id holds a separate accessory on each page, with no overwriting
- Only the enabled page contributes: accessories on other pages are kept but produce no attributes, skills, or set piece counts
- Losing permission keeps data: when a player loses the enabled page's permission, nothing is cleared and no page switch happens. The page cannot be opened and its accessories stop contributing, and the player recovers items with
/eacc retrieve <page>
Parts listed on a page expand into slot instances according to count in parts.yml; you must then place each instance in the layout referenced by gui_template, otherwise the slot is unusable and a warning is logged. A parts entry naming an undefined part does not appear on the page and logs accessory.page_unknown_part
durability
Accessories never occupy vanilla equipment slots, so vanilla durability events (PlayerItemDamageEvent) never reach them; durability wear is deducted by EmakiAccessory itself when the player takes damage. The feature is disabled by default
yaml
durability:
enabled: false
damage_per_hit: 1| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
durability.enabled | boolean | No | false | true / false | Feature switch. While disabled no durability is deducted and the durability data on items stays untouched (equivalent to vanilla durability never being consumed) |
durability.damage_per_hit | integer | No | 1 | ≥ 1 (values below 1 are clamped) | Durability points deducted from every eligible accessory each time the player takes damage |
Scope and criterion:
- Only accessories on the enabled page are checked; other pages never take damage
- Only accessories that carry a durability ceiling take damage: the ceiling comes from the item's
max_damagecomponent, falling back to the material's own durability when unset (the same criterion EmakiItem's repair system uses); items with a ceiling of 0 are never deducted - You can opt a part out in
parts.ymlwithdurability_deduction: false(allowed by default) - Deduction listens to
EntityDamageEvent(MONITOR, cancelled events ignored); damage withfinalDamage ≤ 0does not deduct - Deduction never exceeds the ceiling and stops once it is reached
After it is exhausted: that accessory's attributes, skills and set piece count all stop applying, the item stays in its cell, a gui.durability_broken marker shows in the window and a single durability.broken_notify message is sent. Take the item out, restore durability through EmakiItem's repair flow and it becomes effective again. Wear is written by the autosave, so no extra save command is needed
persistence
| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
persistence.autosave_seconds | integer | No | 60 | ≥ 0 (negatives are clamped to 0) | Autosave interval in seconds. Setting it to 0 disables timed saving; the actual timer period is max(100, seconds × 20) ticks, i.e. at most once every 5 seconds |
persistence.drain_timeout_seconds | integer | No | 10 | ≥ 1 (smaller values are clamped to 1) | How long, in seconds, shutdown waits for unflushed writes to finish |
yaml
persistence:
autosave_seconds: 120
drain_timeout_seconds: 15Once you disable timed saving, data is flushed only when the window closes, when the player quits, on /eacc admin save and on server shutdown
After drain_timeout_seconds elapses, the saved/failed/remaining-dirty counts are logged and shutdown continues; it never waits forever and blocks the server shutdown flow
Player data files
Each player's accessory data lives in plugins/EmakiAccessory/data/<player UUID>.yml. The structure currently written to disk (format_version: 2):
yaml
format_version: 2
player_name: "Steve"
enabled_page: "main"
pages:
main:
ring_1: "<Base64 item bytes>"
combat:
ring_1: "<Base64 item bytes>"| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
format_version | integer | Yes (on write) | 2 | always written as 2 today | On-disk structure version. The value is not validated when reading; recognition is structural only (see the next two sections) |
player_name | string | No | "" | — | Player name at the last write; when blank on read it is filled in from the online name |
enabled_page | string | No | the default page (lowest order) | — | The page id the player currently enables; at runtime an unknown page falls back to the default page |
pages | map<string, map<string, string>> | No | {} | — | Main structure: pages.<pageId>.<slotInstanceId> = Base64 item bytes. The same slot instance holds a separate entry per page with no overwriting; empty pages are not written |
pages.<pageId>.<slotInstanceId> | string | No | — | — | Text produced by Base64-encoding the result of ItemStack.serializeAsBytes; an entry that fails to decode is dropped with a warning |
slots | map<string, string> | No | — | — | Legacy (v1) key, read during migration only and never written again |
Automatic v1 → v2 upgrade
Legacy (v1) files store accessories under a top-level slots: section and have no pages. When the plugin loads such a file, it moves the items into the default enabled page (the page with the lowest order) and logs Migrated accessory data ... from format 1 to 2. The trigger is "no pages: and a top-level slots: exists"; the actual format_version value is not inspected. When no default page is available (no pages configured), migration does not happen and the file is left untouched
No overwrite when the structure is unrecognized
Two situations hit this protection (a file has neither pages: nor slots: -- for example after manual corruption or because it came from another structure; or a legacy slots: file has no default page to migrate into):
- The file is left untouched and never overwritten; the player starts this session with empty accessories, and an
Unrecognized accessory data structureor migration warning is logged - Every later save for that player is skipped, logging
Skipped saving accessories ... because its data file structure was not recognized
So back up plugins/EmakiAccessory/data/ before upgrading or hand-editing: once a structure is unrecognized, the plugin will not overwrite the original file with empty data