English
Configuration
Every EmakiLevel configuration file is covered below: the main config.yml, the level-up requirements requirements.yml, the level types types/*.yml, and what each field in the player data file does
EmakiLevel configuration is split into the main config, requirement curves, level types, experience sources, GUI templates, and language files. The files you edit most are:
config.yml: global switchesrequirements.yml: required exp curvestypes/*.yml: level types, upgrade behavior, rewards, costs, and attributessources/*.yml: which events grant exp to which level types
Run /elv reload after editing most EmakiLevel files to reload configuration and synchronize online players
config.yml
Default main config:
yaml
version: "1.6.9"
language: "zh_CN"
release_default_data: true
primary_type: "main"
level:
default_start_level: 1
default_max_level: 100
max_auto_upgrade_steps: 10
keep_total_exp_at_max_level: true
multipliers:
enabled: true
global: 1.0
types:
main: 1.0
combat: 1.0
cooking: 1.0
forging: 1.0
reasons:
action: 1.0
mythic_drop: 1.0
event_bonus: 1.0
daily_caps:
enabled: true
default_limit: -1
types:
main: -1
combat: -1
cooking: -1
forging: -1
pdc:
enabled: true
namespace: "emakilevel"
attribute:
enabled: true
provider_id: "emakilevel"
mythicmobs:
enabled: true
kill_sources: true
drops:
enabled: true
names:
- "emakilevel_exp"
- "elv_exp"
anti_abuse:
placed_block_tracking: true
placed_block_exp: false
placed_block_record_ttl_ticks: 864000
last_damager_tracking:
enabled: true
expire_ticks: 200
gui:
enabled: true
default_template: "level_gui"| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
version | string | no | 1.6.9 | — | Default resource version marker, maintained by resource sync; not a feature switch |
language | string | no | zh_CN | — | Language file under lang/<language>.yml |
release_default_data | boolean | no | true | true / false | Whether missing bundled data files should be released |
primary_type | string | no | main | A loaded level type id | Default level type for /elv levelup without type and some actions without type |
level.default_start_level | int | no | 1 | — | Fallback start level when a type omits start_level |
level.default_max_level | int | no | 100 | — | Fallback max level when a type omits max_level |
level.max_auto_upgrade_steps | int | no | 10 | Values below 1 are treated as 1 | Maximum auto-level-up steps from one exp operation |
level.keep_total_exp_at_max_level | boolean | no | true | true / false | Keep increasing total_exp after a player reaches max level. Current-level exp stops increasing |
multipliers.enabled | boolean | no | true | true / false | Enable the experience multiplier stage |
multipliers.global | double | no | 1.0 | Result clamped to be non-negative | Global experience multiplier |
multipliers.types.<type> | double | no | 1.0 | Result clamped to be non-negative | Per level type multiplier. Keys are normalized to lower case; unlisted types use 1.0 |
multipliers.reasons.<reason> | double | no | 1.0 | Result clamped to be non-negative | Per experience reason multiplier. Keys are normalized to lower case; unlisted reasons use 1.0 |
daily_caps.enabled | boolean | no | true | true / false | Enable the daily experience cap stage |
daily_caps.default_limit | double | no | -1 | -1 or a positive number | Daily cap used when a type has no explicit entry. -1 means unlimited |
daily_caps.types.<type> | double | no | -1 | -1 or a positive number | Per level type daily cap. -1 means unlimited |
pdc.enabled | boolean | no | true | true / false | Write level data to online player PDC |
pdc.namespace | string | no | emakilevel | — | PDC namespace; a blank value falls back to emakilevel |
attribute.enabled | boolean | no | true | true / false | Try to connect to EmakiAttribute contributions |
attribute.provider_id | string | no | emakilevel | — | Contribution provider ID registered into EmakiAttribute |
mythicmobs.enabled | boolean | no | true | true / false | Global MythicMobs switch |
mythicmobs.kill_sources | boolean | no | true | true / false | Enable mythic_mob_kill sources |
mythicmobs.drops.enabled | boolean | no | true | true / false | Register MythicMobs intangible exp drops |
mythicmobs.drops.names | list<string> | no | emakilevel_exp, elv_exp | — | Recognized drop names. Empty list falls back to defaults |
anti_abuse.placed_block_tracking | boolean | no | true | true / false | Track player-placed blocks |
anti_abuse.placed_block_exp | boolean | no | false | true / false | Whether player-placed blocks can grant break exp |
anti_abuse.placed_block_record_ttl_ticks | int | no | 864000 | <=0 disables pruning | Lifetime of placed-block records in ticks. Expired records are pruned on the next read/write |
anti_abuse.last_damager_tracking.enabled | boolean | no | true | true / false | Whether indirect kills (attributed through the recent damager) are accepted. When false, only direct Bukkit-killer kills are processed |
anti_abuse.last_damager_tracking.expire_ticks | int | no | 200 | — | Key kept in the default config. Not read by the current implementation: the recent damager window is controlled by CoreLib config.yml > gameplay_events.last_damager_expire_ticks (also 200 by default) |
gui.enabled | boolean | no | true | true / false | Enables the level GUI template entry |
gui.default_template | string | no | level_gui | — | Default GUI template ID |
Multipliers and daily caps
multipliers and daily_caps run before experience is written to the player, in this order: multiplier → daily cap
Multiplier calculation:
text
multiplier = multipliers.global × multipliers.types.<type> × multipliers.reasons.<reason>- Keys not listed under
typesorreasonsare skipped instead of being treated as0 - With
multipliers.enabled: falsethe multiplier is fixed at1.0 - The result is clamped to be non-negative
<reason>is the reason passed by the exp source, for example the source rule trigger ID, an actionreasonargument, or a MythicMobs dropreason
Daily caps:
- With
daily_caps.enabled: falsethere is no cap - A type uses
daily_caps.types.<type>when present, otherwisedaily_caps.default_limit -1means unlimited- When the remaining daily budget is smaller than the incoming amount, the gain is clamped to the remaining budget. When the budget is exhausted the operation fails with reason
daily_cap_reached - Counters are bucketed by date in the system default time zone, kept in memory only, and reset when the server restarts
requirements.yml
requirements.yml controls required exp:
yaml
global:
formula: "floor(100 + 25 * %target_level% + 8 * pow(%target_level%, 2))"
values: {}
groups:
main:
formula: "floor(150 + 40 * %target_level% + 14 * pow(%target_level%, 2))"
values:
2: 150
3: 350
4: 750
5: 1200
combat:
formula: "floor(60 + 18 * %target_level% + 5 * pow(%target_level%, 2))"
values:
2: 80
3: 160
4: 300
# mining / logging / farming / fishing / gathering / taming use the same default curve as combat
crafting:
formula: "floor(80 + 22 * %target_level% + 6 * pow(%target_level%, 2))"
values: {}
# brewing / cooking / forging / smelting use the same default curve as craftingThe bundled requirements.yml defines one group per built-in level type: main, combat, mining, logging, farming, fishing, gathering, taming, crafting, brewing, cooking, forging, smelting. Each types/<type>.yml > requirement.group points at the group with the same name by default
| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
global | mapping | no | — | — | Global requirement group; the lowest fallback for every type |
global.formula | string | no | floor(100 + 25 * %target_level% + 8 * pow(%target_level%, 2)) | expression | Global requirement formula, used when neither the type nor the group provides one |
global.values.<level> | mapping<int, double> | no | {} | key is a positive integer level, value is required exp | Global exact target-level values; values greater than 0 take priority over the formula |
groups | mapping | no | — | — | Requirement groups keyed by the id referenced from requirement.group |
groups.<group> | mapping | no | — | — | One requirement group |
groups.<group>.formula | string | no | see bundled file (each built-in group ships its own curve) | expression | Group requirement formula |
groups.<group>.values.<level> | mapping<int, double> | no | see bundled file (main, combat, ... ship sample values) | key is a positive integer level, value is required exp | Group exact target-level values |
Resolution order
Required exp for a target level resolves in this order:
types/<type>.yml > requirement.values.<target_level>types/<type>.yml > requirement.formularequirements.yml > groups.<group>.values.<target_level>requirements.yml > groups.<group>.formularequirements.yml > global.values.<target_level>requirements.yml > global.formula
Exact values greater than zero take priority over formulas
Formula variables
Requirements are computed by the CoreLib expression engine using %key% variables. Note: requirements are cached per type and the formula only depends on the target level; no live player level or exp is injected:
| Variable | Description |
|---|---|
%current_level% | Always the type's start_level (the current implementation injects no live player level) |
%target_level% | Target level |
%max_level% | Type max level |
%exp% | Always 0 (the current implementation injects no live player exp) |
%total_exp% | Always 0 (the current implementation injects no live player total exp) |
Use /elv debug requirement <type> <level> to inspect the final value and source
types/*.yml
Each types/*.yml file defines one level type. File name is only a fallback; the internal id field is authoritative
yaml
id: "combat"
enabled: true
display_name: "<red>Combat Level</red>"
description:
- "<gray>Gain exp from vanilla or MythicMobs kills.</gray>"
primary: false
start_level: 1
max_level: 80
requirement:
group: "combat"
formula: ""
values: {}
upgrade:
enabled: true
auto_upgrade: true
manual_upgrade: true
cost:
enabled: false
economy:
enabled: false
currencies: []
materials: []
rewards:
items: []
actions:
gain: []
success: []
failure: []
pdc:
enabled: true
attributes:
enabled: true
values:
physical_attack: "%level% * 0.25"| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
id | string | no | file name without .yml | — | Unique level type ID referenced by commands, placeholders, sources, and actions |
enabled | boolean | no | true | true / false | Disabled types cannot gain exp or level up |
display_name | string | no | id | — | MiniMessage display name; blank falls back to id |
description | list<string> | no | [] | — | Description lines for configuration maintenance and GUI display |
primary | boolean | no | false | true / false | Marks a type as primary metadata. The active default is still config.yml > primary_type |
start_level | int | no | config.yml > level.default_start_level (ships as 1) | — | Initial and reset level |
max_level | int | no | config.yml > level.default_max_level (ships as 100) | — | Maximum level. Direct level edits are clamped to this range |
requirement.group | string | no | global | a defined group id | Requirement group in requirements.yml |
requirement.formula | string | no | "" | expression | Type-specific formula overriding group/global formulas |
requirement.values.<level> | mapping<int, double> | no | {} | key is a positive integer level | Type-specific exact target-level values. Highest priority |
upgrade.enabled | boolean | no | true | true / false | Whether this type can level up at all |
upgrade.auto_upgrade | boolean | no | true | true / false | Whether exp gain triggers automatic level-up |
upgrade.manual_upgrade | boolean | no | true | true / false | Whether players can use /elv levelup |
upgrade.cost.enabled | boolean | no | false | true / false | Whether level-up costs are charged at all |
upgrade.cost.economy.enabled | boolean | no | false | true / false | Whether economy costs are enabled; only read when cost.enabled: true |
upgrade.cost.economy.currencies[] | list<mapping> | no | [] | see "Economy cost" | Economy cost entries, only read when economy.enabled: true and applied when cost.enabled: true |
upgrade.cost.materials[] | list<mapping> | no | [] | see "Material cost" | Material cost entries, only applied when cost.enabled: true |
upgrade.rewards.items[] | list<mapping> | no | [] | see "Rewards" | Item rewards on successful level-up |
upgrade.actions.gain | list<string> | no | [] | CoreLib action line | CoreLib actions after exp gain |
upgrade.actions.success | list<string> | no | [] | CoreLib action line | CoreLib actions after successful level-up |
upgrade.actions.failure | list<string> | no | [] | CoreLib action line | CoreLib actions after failed level-up |
pdc.enabled | boolean | no | true | true / false | Whether this type syncs PDC. Requires global pdc.enabled: true |
attributes.enabled | boolean | no | false | true / false | Whether this type contributes EmakiAttribute attributes; false when the key is absent |
attributes.values.<attr> | mapping<string, string> | no | {} | attribute ID to formula | Map of attribute ID to formula |
Level-up costs
Costs only apply when upgrade.cost.enabled: true
Economy cost
yaml
upgrade:
cost:
enabled: true
economy:
enabled: true
currencies:
- provider: "auto"
currency_id: ""
base_cost: 100
cost_formula: "%base_cost% * %target_level%"
display_name: "<gold>Coins</gold>"| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
provider | string | no | auto | CoreLib economy provider id, usually auto, vault, or excellenteconomy | Economy provider |
currency_id | string | no | "" | — | Currency ID. Usually empty for Vault |
base_cost | double | no | 0 | — | Base amount exposed as %base_cost% |
cost_formula | string | no | %base_cost% * %target_level% | expression; a result ≤0 charges nothing | Final cost formula |
display_name | string | no | "" | — | Display label for readability and future UI |
Economy variables: %target_level%, %type%, %base_cost%
Material cost
yaml
upgrade:
cost:
enabled: true
materials:
- item_sources:
- "minecraft-diamond"
base_amount: 1
amount_formula: "%base_amount% + floor(%target_level% / 10)"| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
item_sources | list<string> | no | [] | valid CoreLib ItemSource | CoreLib ItemSource values to consume, processed in order; unparseable entries are ignored |
base_amount | long | no | 1 (amount is also accepted as input) | — | Base amount exposed as %base_amount%. New configs should use base_amount |
amount_formula | string | no | %base_amount% | expression, rounded to a long; a result ≤0 consumes nothing | Final amount formula, rounded to an integer |
Material variables: %target_level%, %type%, %base_amount%
Rewards
yaml
upgrade:
rewards:
items:
- levels: "*"
item_source: "minecraft-experience_bottle"
amount: 1
- levels: "10,20,30"
item_source: "minecraft-diamond"
amount: 2
- levels: "50-60"
item_source: "minecraft-nether_star"
amount: 1| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
levels | string | no | * | *, single level, comma list (10,20,30), or min-max range (50-60) | Level pattern |
item_source | string | yes (one of item_source / item_sources) | — | valid CoreLib ItemSource | Reward item source (canonical form, single) |
item_sources | list<string> | yes (one of item_source / item_sources) | — | exactly one valid ItemSource | Legacy form. Must contain exactly one entry |
amount | int | no | 1 | minimum 1 | Reward amount |
item_source and item_sources cannot both be declared, and item_sources may contain exactly one entry; violating either throws at load time. matcher is not allowed on reward entries. Rewards are created through CoreLib ItemSource and delivered with give-or-drop behavior
Upgrade action variables
upgrade.actions.gain, success, and failure are CoreLib action line lists. They receive these variables:
| Variable | Description |
|---|---|
%type% | Level type ID |
%type_display_name% | Type display name |
%level% | Current level |
%old_level% | Level before operation |
%new_level% | Level after operation |
%exp% | Current-level exp, formatted |
%old_exp% | Exp before operation, formatted |
%new_exp% | Exp after operation, formatted |
%total_exp% | Total exp, formatted |
%required_exp% | Required exp for next level, formatted |
%progress% | Progress from 0 to 1 |
%progress_percent% | Progress from 0 to 100, formatted |
%amount% | Operation amount, formatted |
%reason% | Operation reason |
%failure_reason% | Failure reason, especially useful in failure actions |
Example:
yaml
upgrade:
actions:
gain:
- '@chance=10 send_action_bar text="<gray>%type_display_name% exp +%amount%</gray>"'
success:
- 'send_message text="<green>%type_display_name% reached <yellow>%new_level%</yellow>!</green>"'
failure:
- 'send_message text="<red>Level-up failed: %failure_reason%</red>"'Attribute contributions
When EmakiAttribute is installed and enabled, EmakiLevel registers level-based contributions. The default provider ID is config.yml > attribute.provider_id, usually emakilevel
yaml
attributes:
enabled: true
values:
physical_attack: "%level% * 0.25"
physical_crit_rate: "%level% * 0.02"Attribute formula variables: %level%, %exp%, %total_exp%
If EmakiAttribute is not installed, this bridge is skipped automatically
PDC sync
When global pdc.enabled: true and type-level pdc.enabled: true, online players receive these logical PDC fields under the configured namespace, default emakilevel:
| Field | Type | Description |
|---|---|---|
player/<type>_level | int | Current level |
player/<type>_exp | double | Current-level exp |
player/<type>_total_exp | double | Total exp |
player/<type>_required_exp | double | Required exp for next level |
player/<type>_progress | double | Progress from 0 to 1 |
PDC sync happens on join, exp changes, level changes, reload, and explicit debug sync
Player data files
Player data is stored in data/<uuid>.yml, and the fields mean the following:
yaml
schema_version: 1
uuid: "00000000-0000-0000-0000-000000000000"
name: "PlayerName"
levels:
main:
level: 1
exp: 0.0
total_exp: 0.0
updated_at: 1730000000000Prefer commands such as /elv setexp, /elv setlevel, and /elv reset over editing data files while the server is running