Authored enchantments
EliteMobs and FreeMinecraftModels supply custom enchantments through a shared MagmaCore system. Each plugin owns its definitions and Lua effects under its own enchantments/ folder. Native Minecraft enchantments remain in the minecraft: namespace.
Updating bundled definitions
The catalog restores missing bundled YAML and Lua files and adds missing default fields to existing bundled definitions. It preserves values already configured and existing Lua source. This works even when the enchantments/ directory already exists.
The owning plugin may first archive files that match a retired format. For example, EliteMobs preserves enchantment YAML containing maxLevelV2 before generating current defaults. EliteMobs also archives unedited copies of earlier bundled enchantment scripts, such as an old grappling_hook.lua, so the current default is written in their place; a script you edited stays. Review custom settings in the configuration archive and reapply them using the current fields. An updated DLC or custom effect still needs its own matching definition and Lua source.
Item references
Custom effects use a lowercase namespaced ID and a positive integer level. This example is an FMM item definition:
enchantments:
- "minecraft:unbreaking,3"
- "elitemobs:critical_strikes,2"
For example, critical_strikes.yml in EliteMobs' enchantment catalog defines elitemobs:critical_strikes. An FMM definition uses freeminecraftmodels:. Subfolders do not become part of the ID; give content files unique filenames.
EliteMobs custom-item YAML retains Bukkit names such as UNBREAKING,3 for native enchantments, while requiring namespaced IDs for custom effects. Do not copy FMM's native-item syntax into another plugin without checking that plugin's item format.
The installed provider must recognize the ID. Compatibility, item type, slot, conflicts, and required capabilities come from its definition. Removing a provider does not turn its custom effect into a native enchantment.
Definition format
This is the shipped FMM Ignition definition, paired with ignition.lua in the same enchantment catalog:
isEnabled: true
name: Ignition
description: Sets surviving targets on fire after a staff fireball hits.
maxLevel: 3
curse: false
validSlots: [MAINHAND]
itemTypes: [STAFF]
attackKinds: [STAFF_FIREBALL]
requires: []
conflicts: []
stacking: source_item
script: ignition.lua
parameters:
base_ticks: 20
ticks_per_level: 20
return {
api_version = 1,
on_ignition_ticks = function(context)
return math.max(0, math.min(200, context.parameters.base_ticks
+ context.parameters.ticks_per_level * context.enchantment.level))
end
}
This query returns an ignition duration to FMM. It does not run as a prop script or an equipped-item script.
| Field | Meaning |
|---|---|
isEnabled | Defaults to true; disabled definitions do not enter the catalog |
name | Required plain display name |
description | Optional description, empty by default |
maxLevel | Positive enchanting level cap; explicitly authored items may exceed it |
curse | Whether the enchantment is a curse; defaults to false |
validSlots | Equipment slots: HEAD, CHEST, LEGS, FEET, MAINHAND, OFFHAND |
itemTypes | Accepted categories: SWORD, AXE, PICKAXE, SHOVEL, HOE, BOW, CROSSBOW, TRIDENT, MACE, SPEAR, HELMET, CHESTPLATE, LEGGINGS, BOOTS, SHIELD, FISHING_ROD, SHEARS, ELYTRA, WAND, STAFF, OTHER |
attackKinds | Optional magic attack filter: WAND_MISSILE, STAFF_MELEE, STAFF_FIREBALL |
requires | Provider capability requirements |
conflicts | Other lowercase namespaced enchantment IDs |
stacking | source_item by default, or sum_equipped_levels |
script | Required lowercase .lua filename without a directory |
parameters | Named scalar values or per-level scalar lists |
A per-level list must have exactly maxLevel entries. Above that level, the last list value is retained while the script still receives the actual enchantment level. Parameters belong to the particular effect; putting an arbitrary key here does not create a new mechanic.
Unknown top-level fields, duplicate IDs, missing scripts, and unsupported hooks reject the candidate catalog. Fix the file named in the error before reloading. A failed candidate is not published over the active catalog.
Effect ownership
Start from an effect shipped by the plugin that will own your new definition. Its supported hooks and domain operations determine what it can do. Query hooks return values such as missile count, critical chance, or threat bonus. Action hooks handle accepted item inputs and can own timed effects.
The old FMM context.item API and its equip/unequip lifecycle do not apply here. Enchantment scripts use their own context.enchantment, context.parameters, captured source information, and action operations. Do not rename an old item script and assume its context is compatible.
Books and anvils
Custom enchanted books use the same provider definitions as equipped items. An anvil validates the source item, book, and complete result, including native enchantments. Equal custom levels combine to the next level; unequal levels keep the higher one. Each changed custom enchantment adds its resulting level to the native repair cost. The resulting level must pass the definition's anvil level limit, slot, type, capability, and conflict checks.
Mixed books must transfer every native entry accepted by the server as well as their custom entries. If the server rejects a native entry, the custom operation is rejected instead of silently discarding that part of the book. Invalid or unavailable definitions also reject the preview. The inputs and provider state are checked again at pickup; a visible preview is not a reservation that survives a catalog change.
Lua query reference
Numeric queries run a fresh script instance and close it immediately after the result. Return one finite number or nil for no contribution. State and scheduled work do not survive the query. A failed definition is quarantined until a successful catalog reload; exhausting the caller's remaining budget does not itself quarantine the definition.
| Context | Contents |
|---|---|
context.enchantment | id and actual level |
context.parameters | This definition's values resolved for that level |
context.input | Scalar facts supplied by the owning mechanic; use the shipped effect for the exact keys |
context.target | Target Entity or Living Entity table, or nil |
context.player | Triggering player when the query has one |
FMM supplies on_missile_count, on_missile_damage_factor, on_blast_radius_factor, and on_ignition_ticks. Other providers expose their own numeric hooks. A definition may only declare hooks accepted by its provider.
Lua action reference
Action scripts use the shared scheduler, cooldowns, world, zones, player, and event tables. They run for one accepted input or attack, using a captured source for subsequent callbacks. Chat snapshots the message during the chat event, then captures equipment when dispatch runs on the server thread. An optional parameters.lifetime_ticks must be a positive whole number; without it the activation has a one-tick lifetime. Scheduled effects need an authored lifetime long enough to finish.
Activations stop when their lifetime ends, the actor dies, disconnects or changes world, the owning world unloads, a chunk containing one of their owned entities unloads, or the provider reloads or disables. They can finish earlier once the final input has no remaining owned work. parameters.stop_on_unequip: true also stops an activation when its captured item is no longer equipped. Local cooldowns belong to the player and enchantment ID; global cooldowns belong to the player within the provider. Both survive equipment swaps, completed activations, world changes, and valid reloads, and clear on disconnect or provider shutdown.
| Hook | Accepted input |
|---|---|
on_attack_entity | Successful, positive native melee damage with the main-hand item, or an explicit plugin-owned attack |
on_projectile_hit | Successful living-target damage from a captured bow projectile, delivered once per projectile and target, or an explicit plugin-owned impact |
on_take_damage | Positive damage to the player; eligible equipped slots are inspected |
on_right_click, on_left_click | Hand interaction while not sneaking |
on_shift_right_click, on_shift_left_click | Hand interaction while sneaking; these replace the corresponding ordinary click hook |
on_break_block | Accepted player block break using the main-hand item |
on_chat | Accepted chat, dispatched on the server thread with the message snapshot |
on_sneak | Entering sneak; equipment contributions are collected per definition |
on_projectile_launch | Consumed enchanted ammunition launching as an arrow from a bow event |
on_game_tick | Tick while this activation remains active |
on_zone_enter, on_zone_leave | Players crossing a zone watched by this activation |
These hooks do not create an equip/unequip script runtime. A tick-only definition still needs an input hook to start its activation. Native attack, damage, chat, and mining hooks observe already accepted inputs; they cannot cancel the original event. The four interaction hooks expose a synchronous cancellation flag: context.event:cancel() denies the associated item and block interaction. Scheduled callbacks cannot retroactively cancel an input.
| Context | Contents |
|---|---|
context.enchantment | id, level |
context.parameters | Level-resolved definition parameters |
context.player | Current actor Player Table |
context.target | Current affected Living Entity Table, or nil |
context.source | Captured facts: item, slot, inventory_slot, equipment, attack_kind, providers; some inputs also supply ammunition, projectile, message, block, drop_items, sneaking, or equipped_levels |
context.item | Captured-slot durability and consumption methods, when an inventory item remains addressable; see item methods |
context.action | id (attack UUID), instance (activation token), actor (UUID), world (world UUID), and methods below |
Item snapshots contain material, amount, and enchantments. They are data, not live inventory objects. UUIDs in source facts are strings. Mining's source.block contains x, y, z, and lowercase material.
| Action method | Returns | Behavior |
|---|---|---|
stop() | nil | Stop this activation and clean up its owned work |
has_previous() | bool | Test for another active instance of this enchantment for the same player |
replace_previous() | nil | Stop those earlier instances, preserving this one |
damage_target(target_uuid, amount) | bool | Ask the provider to apply finite positive damage with the captured source attribution; false when rejected |
own_entity(uuid) | bool | Register a valid same-world nonplayer entity for removal at cleanup; use after spawning it |
launch_projectile(location, direction, speed, range, ticks, impact, visual?, protected_path?=false) | bool | Launch an owned scripted projectile; speed/range must be finite and positive and ticks at least 1 |
attribute_projectile(uuid, callback, protect?=false) | bool | Bind an owned native projectile's damage to an authored callback |
place_block(x, y, z, expected_material, block_data) | bool | Place through player block-action validation only if the current material matches |
break_block(x, y, z, expected_material, drop_material?, amount?=1) | bool | Break through player block-action validation with an optional authored item drop |
break_naturally(x, y, z, expected_material) | bool | Break using the captured tool and the source break event's drop policy |
temporary_block(x, y, z, block_data, ticks?=0) | bool | Lease a loaded block, restoring it at cleanup or after positive ticks; zero lasts until cleanup |
can_own_block(x, y, z) | bool | Check loaded coordinates and whether temporary ownership is available |
temporary_gravity(uuid, enabled) | bool | Lease gravity state and restore it at cleanup |
restore_gravity(uuid) | bool | Restore this activation's gravity lease early |
temporary_scale(uuid, scale) | bool | Lease a living entity's scale until cleanup |
temporary_potion(uuid, type, ticks, amplifier) | bool | Lease a potion effect for positive ticks and a nonnegative amplifier |
restore_potion(uuid, type) | bool | Restore this activation's potion lease early |
Projectile impact callbacks receive (fresh_context, target_or_nil, location); visual callbacks receive (fresh_context, location). Native projectile damage callbacks receive (fresh_context, target, native_damage) on the next tick. They replace the native application and must explicitly apply the intended damage, normally through fresh_context.action:damage_target(target.uuid, amount). With native protect=true, protected explosion blocks and ignition are filtered. Scripted protected_path=true absorbs flights crossing protected or unloaded positions.
Owned cleanup is limited to operations that register ownership. Ordinary world:spawn_entity, entity:set_gravity, and world:set_block_at do not automatically gain action cleanup. Temporary state methods manage restoration; they are not substitutes for checking whether a hostile effect or block edit is permitted.
Upgrading existing content
FMM skips custom items with nonempty scripts: or retired unnamespaced enchantment entries. Replace those item definitions with namespaced enchantments; prop scripts remain supported.
EliteMobs custom effects now use maxLevel and effect-specific parameters in provider YAML. Native and elite-level enchantment configuration is a separate format. Follow the EliteMobs enchantment reference rather than copying an old maxLevelV2 file into a provider definition.