A custom enchantment framework for PocketMine-MP that provides three behavior types (reactive, ticking, toggleable), automatic display rendering, and ready-to-use enchantment presets -- all with cooldown management, rarity-colored item tooltips, and automatic cleanup on disable.
- Custom Enchantment Base: Extensible base class with 8 usage types, 16 item types, cooldown management, chance-based activation, and priority ordering.
- Reactive Enchantments: Event-driven enchantments that fire on damage, bow shoot, block break, sneak, interact, and other reagent events with directional filtering and stack tracking.
- Ticking Enchantments: Interval-based enchantments that execute every N ticks while equipped, with configurable multi-item support.
- Toggleable Enchantments: State-tracked enchantments that activate on equip and deactivate on unequip, maintaining per-player stack and armor stack counts.
- Enchantment Factory: Static registration facade with auto-incrementing IDs starting at 100, reflection-based unregistration from PocketMine internals, and name/ID lookup.
- Enchantment Manager: Central event hub handling 14 event types plus outbound/inbound packet interception for display injection and stripping.
- Armor Effect Preset: Toggleable enchantment that applies potion effects while armor is equipped, with level-scaled amplifiers, Jump Boost fall damage immunity, and pre-existing effect preservation.
- Attacker Debuff Preset: Reactive armor enchantment that applies debuff effects to attackers when the wearer is hit, with duration and amplifier scaling per level.
- Damage Multiplier Preset: Reactive enchantment that conditionally multiplies outgoing damage based on a Closure condition, with per-level multiplier scaling.
- Projectile Preset: Reactive enchantment that replaces default arrows with custom Projectile subclasses on bow shoot, maintaining ProjectileTracker state.
- Weapon Effect Preset: Reactive weapon enchantment that applies effects to hit targets with duration and amplifier scaling per level.
- Display Rendering: Automatic rarity-colored enchantment names with Roman numeral levels injected into item tooltips via outbound inventory packets, with clean reversal on inbound packets.
- Automatic Cleanup: All custom enchantments are toggled OFF for all online players on plugin disable to prevent orphaned effects.
Include or autoload LibEnchantment in your PocketMine-MP plugin. No external dependencies are required. LibEnchantment registers itself as a listener, schedules EnchantmentTickTask every 1 tick and DisplayCleanupTask every 200 ticks, and handles automatic cleanup on disable.
- PocketMine-MP API 5.0.0+
- PHP 8.2+
- No external dependencies
- Loads at STARTUP
Register your custom enchantments in your plugin's onEnable:
use imperazim\enchantment\EnchantmentFactory;
public function onEnable(): void {
$id = EnchantmentFactory::newId();
EnchantmentFactory::register($id, new MyCustomEnchantment());
}- Added
LibEnchantmentclass inimperazim\enchantmentnamespace. - Added
EnchantmentFactoryclass inimperazim\enchantmentnamespace. - Added
EnchantmentManagerclass inimperazim\enchantmentnamespace. - Added
CustomEnchantmentclass inimperazim\enchantmentnamespace.
LibEnchantment -- Plugin entry point that bootstraps the entire framework:
use imperazim\enchantment\LibEnchantment;
// LibEnchantment is loaded as a plugin. No manual instantiation needed.
// It automatically:
// - Registers EnchantmentManager as a listener
// - Schedules EnchantmentTickTask every 1 tick
// - Schedules DisplayCleanupTask every 200 ticks
// - Toggles OFF all custom enchantments for all online players on disableEnchantmentFactory -- Static facade for registering and looking up custom enchantments:
use imperazim\enchantment\EnchantmentFactory;
use imperazim\enchantment\CustomEnchantment;
// Get next available ID (starts at 100 to avoid vanilla 0-50)
$id = EnchantmentFactory::newId(); // 100, 101, 102, ...
// Register an enchantment
EnchantmentFactory::register($id, new MyEnchantment());
// Look up by ID or name
$enchant = EnchantmentFactory::get($id); // CustomEnchantment|null
$enchant = EnchantmentFactory::getByName("my_ench"); // CustomEnchantment|null
// Get all registered custom enchantments
$all = EnchantmentFactory::getAll(); // array<int, CustomEnchantment>
// Unregister (also removes from PocketMine's EnchantmentIdMap and StringToEnchantmentParser via reflection)
EnchantmentFactory::unregister($id);
EnchantmentFactory::unregister($enchant);CustomEnchantment -- Base class extending PocketMine's Enchantment with usage types, item types, cooldowns, and chance-based activation:
use imperazim\enchantment\CustomEnchantment;
use pocketmine\item\enchantment\Rarity;
class MyEnchantment extends CustomEnchantment {
public function __construct() {
$this->name = "my_enchantment";
$this->rarity = Rarity::RARE;
$this->maxLevel = 3;
$this->displayName = "My Enchantment";
$this->description = "Does something cool";
$this->cooldownDuration = 5; // seconds
$this->chance = 50; // 0-100 percent
$this->usageType = self::USAGE_HAND;
$this->itemType = self::ITEM_SWORD;
}
}
// Usage type constants:
// USAGE_HAND = 0, USAGE_ANY_INVENTORY = 1, USAGE_INVENTORY = 2, USAGE_ARMOR = 3,
// USAGE_HELMET = 4, USAGE_CHESTPLATE = 5, USAGE_LEGGINGS = 6, USAGE_BOOTS = 7
// Item type constants:
// ITEM_GLOBAL = 0, ITEM_SWORD = 1, ITEM_BOW = 2, ITEM_TOOL = 3, ITEM_SHEARS = 4,
// ITEM_FLINT_AND_STEEL = 5, ITEM_PICKAXE = 6, ITEM_AXE = 7, ITEM_HOE = 8,
// ITEM_SHOVEL = 9, ITEM_HELMET = 10, ITEM_CHESTPLATE = 11, ITEM_LEGGINGS = 12,
// ITEM_BOOTS = 13, ITEM_COMPASS = 14, ITEM_ELYTRA = 15
// Cooldown management
$enchant->setCooldown($player, 10); // set 10-second cooldown
$remaining = $enchant->getCooldown($player); // get remaining cooldown
$enchant->clearCooldown($player); // clear cooldown
// Query capabilities
$enchant->canReact(); // false (override via ReactiveTrait)
$enchant->canTick(); // false (override via TickingTrait)
$enchant->canToggle(); // false (override via ToggleableTrait)EnchantmentManager -- Central event hub (registered automatically by LibEnchantment):
use imperazim\enchantment\EnchantmentManager;
// EnchantmentManager is registered as a Listener automatically.
// It handles the following events:
// - BlockBreakEvent
// - EntityDamageEvent (cancels fall damage if immunity active, triggers reactive for victim+attacker)
// - EntityEffectAddEvent
// - EntityShootBowEvent
// - PlayerDeathEvent
// - PlayerInteractEvent
// - PlayerItemHeldEvent (toggles OFF old item, ON new item)
// - PlayerJoinEvent (toggles ON all equipped + attaches CallbackInventoryListener)
// - PlayerMoveEvent (fall damage tracking, reactive on block boundary cross)
// - PlayerQuitEvent (toggles OFF all equipped)
// - PlayerToggleSneakEvent
// - ProjectileHitBlockEvent
// - ProjectileLaunchEvent (registers in ProjectileTracker)
//
// Packet interception:
// - Outbound InventorySlotPacket/InventoryContentPacket: injects display via EnchantmentDisplay
// - Inbound InventoryTransactionPacket/MobEquipmentPacket: strips display via filterDisplayedEnchants- Added
ReactiveTraittrait inimperazim\enchantment\traitnamespace. - Added
TickingTraittrait inimperazim\enchantment\traitnamespace. - Added
ToggleableTraittrait inimperazim\enchantment\traitnamespace.
ReactiveTrait -- Event-driven enchantment behavior with reagent matching, directional filtering, and stack tracking:
use imperazim\enchantment\CustomEnchantment;
use imperazim\enchantment\trait\ReactiveTrait;
use pocketmine\player\Player;
use pocketmine\item\Item;
use pocketmine\inventory\Inventory;
use pocketmine\event\Event;
use pocketmine\event\entity\EntityDamageByEntityEvent;
class PoisonStrikeEnchantment extends CustomEnchantment {
use ReactiveTrait;
public function __construct() {
$this->name = "poison_strike";
$this->rarity = Rarity::RARE;
$this->maxLevel = 3;
$this->displayName = "Poison Strike";
$this->description = "Poisons enemies on hit";
$this->cooldownDuration = 3;
$this->chance = 75;
$this->usageType = self::USAGE_HAND;
$this->itemType = self::ITEM_SWORD;
}
// Override reagent to specify which events trigger this enchantment
public function getReagent(): array {
return [EntityDamageByEntityEvent::class];
}
// Override react() -- called when reagent event fires, cooldown passes, and chance succeeds
public function react(
Player $player,
Item $item,
Inventory $inventory,
int $slot,
Event $event,
int $level,
int $stack
): void {
if ($event instanceof EntityDamageByEntityEvent) {
$target = $event->getEntity();
if ($target instanceof Player) {
$effect = new EffectInstance(VanillaEffects::POISON(), 60 * $level, $level - 1);
$target->getEffects()->add($effect);
}
}
}
// Optional: control directional filtering
public function shouldReactToDamage(): bool {
return true; // react when player deals damage
}
public function shouldReactToDamaged(): bool {
return false; // do not react when player takes damage
}
}TickingTrait -- Interval-based enchantment behavior that runs every N ticks while equipped:
use imperazim\enchantment\CustomEnchantment;
use imperazim\enchantment\trait\TickingTrait;
use pocketmine\player\Player;
use pocketmine\item\Item;
use pocketmine\inventory\Inventory;
class RegenerationAuraEnchantment extends CustomEnchantment {
use TickingTrait;
public function __construct() {
$this->name = "regeneration_aura";
$this->rarity = Rarity::UNCOMMON;
$this->maxLevel = 2;
$this->displayName = "Regeneration Aura";
$this->description = "Slowly regenerates health while held";
$this->usageType = self::USAGE_HAND;
$this->itemType = self::ITEM_SWORD;
}
// Override interval (default is 1 tick; here we tick every 40 ticks = 2 seconds)
public function getTickingInterval(): int {
return 40;
}
// Override tick() -- called at each interval while item is equipped
public function tick(
Player $player,
Item $item,
Inventory $inventory,
int $slot,
int $level
): void {
$health = $player->getHealth();
$max = $player->getMaxHealth();
if ($health < $max) {
$player->setHealth(min($max, $health + (0.5 * $level)));
}
}
// Optional: allow multiple items with this enchantment to tick simultaneously
public function supportsMultipleItems(): bool {
return false; // default: only the first matching item ticks
}
}ToggleableTrait -- State-tracked enchantment that activates on equip and deactivates on unequip:
use imperazim\enchantment\CustomEnchantment;
use imperazim\enchantment\trait\ToggleableTrait;
use pocketmine\player\Player;
use pocketmine\item\Item;
use pocketmine\inventory\Inventory;
class NightVisionEnchantment extends CustomEnchantment {
use ToggleableTrait;
public function __construct() {
$this->name = "night_vision";
$this->rarity = Rarity::RARE;
$this->maxLevel = 1;
$this->displayName = "Night Vision";
$this->description = "Grants night vision while wearing armor";
$this->usageType = self::USAGE_HELMET;
$this->itemType = self::ITEM_HELMET;
}
// Override toggle() -- called on equip ($toggle=true) and unequip ($toggle=false)
public function toggle(
Player $player,
Item $item,
Inventory $inventory,
int $slot,
int $level,
bool $toggle
): void {
if ($toggle) {
$effect = new EffectInstance(VanillaEffects::NIGHT_VISION(), 2147483647, 0, false);
$player->getEffects()->add($effect);
} else {
$player->getEffects()->remove(VanillaEffects::NIGHT_VISION());
}
}
}
// Query stack state (useful when multiple armor pieces have the same enchantment)
$stack = $enchant->getStack($player); // int: sum of levels across equipped items
$armorStack = $enchant->getArmorStack($player); // int: count of equipped armor pieces with this enchant- Added
ReactiveEnchantmentclass inimperazim\enchantment\typenamespace. - Added
TickingEnchantmentclass inimperazim\enchantment\typenamespace. - Added
ToggleableEnchantmentclass inimperazim\enchantment\typenamespace.
Convenience classes that combine CustomEnchantment with a single trait. Extend these instead of manually applying traits:
use imperazim\enchantment\type\ReactiveEnchantment;
use imperazim\enchantment\type\TickingEnchantment;
use imperazim\enchantment\type\ToggleableEnchantment;
// ReactiveEnchantment = CustomEnchantment + ReactiveTrait
class MyReactive extends ReactiveEnchantment {
public function react(Player $player, Item $item, Inventory $inventory, int $slot, Event $event, int $level, int $stack): void {
// handle reaction
}
}
// TickingEnchantment = CustomEnchantment + TickingTrait
class MyTicking extends TickingEnchantment {
public function tick(Player $player, Item $item, Inventory $inventory, int $slot, int $level): void {
// handle tick
}
}
// ToggleableEnchantment = CustomEnchantment + ToggleableTrait
class MyToggleable extends ToggleableEnchantment {
public function toggle(Player $player, Item $item, Inventory $inventory, int $slot, int $level, bool $toggle): void {
// handle equip/unequip
}
}- Added
ArmorEffectEnchantmentclass inimperazim\enchantment\presetnamespace. - Added
AttackerDebuffEnchantmentclass inimperazim\enchantment\presetnamespace. - Added
DamageMultiplierEnchantmentclass inimperazim\enchantment\presetnamespace. - Added
ProjectileEnchantmentclass inimperazim\enchantment\presetnamespace. - Added
WeaponEffectEnchantmentclass inimperazim\enchantment\presetnamespace.
ArmorEffectEnchantment -- Toggleable enchantment that applies a potion effect while armor is equipped. Handles Jump Boost fall damage immunity, saves/restores pre-existing stronger effects, and removes the effect only when the last armor piece is unequipped:
use imperazim\enchantment\preset\ArmorEffectEnchantment;
use imperazim\enchantment\EnchantmentFactory;
use imperazim\enchantment\CustomEnchantment;
use pocketmine\entity\effect\VanillaEffects;
// Speed boost while wearing any armor piece
$speedArmor = new ArmorEffectEnchantment(
name: "swiftness",
effect: VanillaEffects::SPEED(),
maxLevel: 3,
usageType: CustomEnchantment::USAGE_ARMOR,
rarity: Rarity::RARE,
baseAmplifier: 0, // amplifier at level 1 = base + multiplier * 1 = 1
amplifierMultiplier: 1 // each level adds 1 to amplifier
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $speedArmor);
// Jump Boost on boots (automatically grants fall damage immunity)
$jumpBoots = new ArmorEffectEnchantment(
name: "spring_boots",
effect: VanillaEffects::JUMP_BOOST(),
maxLevel: 2,
usageType: CustomEnchantment::USAGE_BOOTS,
rarity: Rarity::UNCOMMON,
baseAmplifier: 1,
amplifierMultiplier: 1
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $jumpBoots);
// Effect is applied with infinite duration (2147483647 ticks), visible=false
// Pre-existing stronger effects are saved and restored when armor is removedAttackerDebuffEnchantment -- Reactive armor enchantment that applies debuff effects to the attacker when the wearer is hit. Duration and amplifier scale with enchantment level:
use imperazim\enchantment\preset\AttackerDebuffEnchantment;
use imperazim\enchantment\EnchantmentFactory;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
// Thorns that slow and weaken attackers
$thornsCurse = new AttackerDebuffEnchantment(
name: "thorns_curse",
effects: [
new EffectInstance(VanillaEffects::SLOWNESS(), 60, 0), // 3s base, amplifier 0
new EffectInstance(VanillaEffects::WEAKNESS(), 40, 0), // 2s base, amplifier 0
],
rarity: Rarity::RARE
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $thornsCurse);
// At level 2: duration becomes 60*2=120 ticks (6s) and amplifier becomes 0*2=0
// At level 3: duration becomes 60*3=180 ticks (9s) and amplifier becomes 0*3=0DamageMultiplierEnchantment -- Reactive enchantment that conditionally multiplies outgoing damage based on a Closure condition. Multiplier scales per level:
use imperazim\enchantment\preset\DamageMultiplierEnchantment;
use imperazim\enchantment\EnchantmentFactory;
use pocketmine\event\entity\EntityDamageByEntityEvent;
// Smite: extra damage vs undead mobs
$smite = new DamageMultiplierEnchantment(
name: "holy_smite",
condition: function (EntityDamageByEntityEvent $event): bool {
$entity = $event->getEntity();
return $entity instanceof Zombie || $entity instanceof Skeleton;
},
multiplierPerLevel: 0.15, // +15% base damage per level
rarity: Rarity::UNCOMMON
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $smite);
// At level 3: baseDamage * (1 + 0.15 * 3) = baseDamage * 1.45
// Bane of Players: extra damage vs players
$bane = new DamageMultiplierEnchantment(
name: "bane_of_players",
condition: function (EntityDamageByEntityEvent $event): bool {
return $event->getEntity() instanceof Player;
},
multiplierPerLevel: 0.1,
rarity: Rarity::RARE
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $bane);ProjectileEnchantment -- Reactive enchantment that replaces the default arrow with a custom Projectile subclass on EntityShootBowEvent. Runs at priority 2 (before other bow enchantments) and maintains ProjectileTracker state:
use imperazim\enchantment\preset\ProjectileEnchantment;
use imperazim\enchantment\EnchantmentFactory;
// Replace arrows with a custom explosive arrow projectile
$explosive = new ProjectileEnchantment(
name: "explosive_arrows",
projectileClass: ExplosiveArrow::class, // must extend pocketmine Projectile
rarity: Rarity::MYTHIC
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $explosive);
// When a player shoots with a bow that has this enchantment:
// 1. The default arrow is replaced with an ExplosiveArrow instance
// 2. The source item is registered in ProjectileTracker
// 3. Other enchantments on the bow can still process the new projectileWeaponEffectEnchantment -- Reactive weapon enchantment that applies effects to the hit target. Duration and amplifier scale with enchantment level:
use imperazim\enchantment\preset\WeaponEffectEnchantment;
use imperazim\enchantment\EnchantmentFactory;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
// Frost sword: slows and blinds targets
$frostBlade = new WeaponEffectEnchantment(
name: "frost_blade",
effects: [
new EffectInstance(VanillaEffects::SLOWNESS(), 80, 1), // 4s base, amplifier 1
new EffectInstance(VanillaEffects::BLINDNESS(), 40, 0), // 2s base, amplifier 0
],
rarity: Rarity::RARE
);
EnchantmentFactory::register(EnchantmentFactory::newId(), $frostBlade);
// At level 2: Slowness becomes 80*2=160 ticks (8s) at amplifier 1*2=2
// Blindness becomes 40*2=80 ticks (4s) at amplifier 0*2=0- Added
EnchantmentDisplayclass inimperazim\enchantment\displaynamespace. - Added
RomanNumeralclass inimperazim\enchantment\displaynamespace.
EnchantmentDisplay -- Automatic rarity-colored enchantment rendering on item tooltips via packet interception. Rarity color mapping: COMMON=YELLOW, UNCOMMON=BLUE, RARE=GOLD, MYTHIC=LIGHT_PURPLE:
use imperazim\enchantment\display\EnchantmentDisplay;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
// Display injection is handled automatically by EnchantmentManager's packet interception.
// Outbound InventorySlotPacket / InventoryContentPacket:
// EnchantmentDisplay::displayEnchants(ItemStack) → ItemStack
// - Builds multi-line custom name with rarity-colored enchantment names + Roman numeral levels
// - Saves original display NBT under 'OriginalDisplayTag'
// Inbound InventoryTransactionPacket / MobEquipmentPacket:
// EnchantmentDisplay::filterDisplayedEnchants(ItemStack) → ItemStack
// - Reverses displayEnchants, restores original display from 'OriginalDisplayTag'
// Manual usage (if needed):
$displayedStack = EnchantmentDisplay::displayEnchants($itemStack);
$originalStack = EnchantmentDisplay::filterDisplayedEnchants($displayedStack);RomanNumeral -- Integer to Roman numeral string converter:
use imperazim\enchantment\display\RomanNumeral;
$numeral = RomanNumeral::convert(1); // "I"
$numeral = RomanNumeral::convert(4); // "IV"
$numeral = RomanNumeral::convert(5); // "V"
$numeral = RomanNumeral::convert(10); // "X"- Added
EnchantmentTickTaskclass inimperazim\enchantment\tasknamespace. - Added
DisplayCleanupTaskclass inimperazim\enchantment\tasknamespace.
EnchantmentTickTask -- Runs every 1 tick. Scans all online players' inventory and armor for enchantments with canTick(). Validates usage type, checks tick interval, prevents duplicates (unless supportsMultipleItems() returns true), and calls onTick():
use imperazim\enchantment\task\EnchantmentTickTask;
// Scheduled automatically by LibEnchantment::onEnable() every 1 tick.
// No manual setup required.
// For each online player:
// For each item in inventory + armor:
// For each CustomEnchantment on the item where canTick() is true:
// If usageType matches slot and interval has elapsed:
// enchantment->onTick(player, item, inventory, slot, level)DisplayCleanupTask -- Runs every 200 ticks. Safety net that removes orphaned OriginalDisplayTag NBT from items in all players' inventories:
use imperazim\enchantment\task\DisplayCleanupTask;
// Scheduled automatically by LibEnchantment::onEnable() every 200 ticks.
// No manual setup required.
// Cleans up leftover display NBT tags that may remain from edge cases.- Added
EnchantmentUtilsclass inimperazim\enchantment\utilnamespace. - Added
ProjectileTrackerclass inimperazim\enchantment\utilnamespace.
EnchantmentUtils -- Item type checks, enchantment sorting, and fall damage prevention tracking:
use imperazim\enchantment\util\EnchantmentUtils;
use imperazim\enchantment\CustomEnchantment;
// Armor slot checks
EnchantmentUtils::isHelmet($item); // bool
EnchantmentUtils::isChestplate($item); // bool
EnchantmentUtils::isLeggings($item); // bool
EnchantmentUtils::isBoots($item); // bool
// Check if an item matches any of the 16 ITEM_* type constants
EnchantmentUtils::itemMatchesItemType($item, CustomEnchantment::ITEM_SWORD); // bool
EnchantmentUtils::itemMatchesItemType($item, CustomEnchantment::ITEM_BOW); // bool
EnchantmentUtils::itemMatchesItemType($item, CustomEnchantment::ITEM_PICKAXE); // bool
EnchantmentUtils::itemMatchesItemType($item, CustomEnchantment::ITEM_ELYTRA); // bool
EnchantmentUtils::itemMatchesItemType($item, CustomEnchantment::ITEM_GLOBAL); // always true
// Sort enchantment instances by priority
$sorted = EnchantmentUtils::sortEnchantmentsByPriority($enchantmentInstances);
// Fall damage prevention (used internally by ArmorEffectEnchantment for Jump Boost)
EnchantmentUtils::setShouldTakeFallDamage($player, false, duration: 1);
$shouldTake = EnchantmentUtils::shouldTakeFallDamage($player); // bool
$duration = EnchantmentUtils::getNoFallDamageDuration($player); // int
EnchantmentUtils::increaseNoFallDamageDuration($player, 1);ProjectileTracker -- Static tracker that maps projectile entity IDs to their source items. Solves the weapon-switch-after-shoot problem by preserving the original bow's enchantments:
use imperazim\enchantment\util\ProjectileTracker;
// Register a projectile (called automatically by EnchantmentManager on ProjectileLaunchEvent)
ProjectileTracker::addProjectile($entity->getId(), $bowItem);
// Check if a projectile is tracked
ProjectileTracker::isTracked($entityId); // bool
// Retrieve source item and its enchantments
$item = ProjectileTracker::getItem($entityId); // Item|null
$enchants = ProjectileTracker::getEnchantments($entityId); // EnchantmentInstance[]
// Remove tracking (called when projectile hits or despawns)
ProjectileTracker::removeProjectile($entityId);- Added
EnchantmentExceptionclass inimperazim\enchantment\exceptionnamespace.
Runtime exception for enchantment-related errors, extending RuntimeException:
use imperazim\enchantment\exception\EnchantmentException;
try {
EnchantmentFactory::register($id, $enchantment);
} catch (EnchantmentException $e) {
$logger->warning("Enchantment error: " . $e->getMessage());
}This project is licensed under MIT. Please see the LICENSE file for details.
LibEnchantment now publishes an EasyLibrary 3.x internal package asset for the migration from standalone-only libraries to installable internal packages.
Release assets now include:
LibEnchantment-2.0.0.pharLibEnchantment-2.0.0.easylib.zippackage.ymlchecksums.txt
The standalone plugin remains supported. During the EasyLibrary 3.x migration, if the standalone plugin is installed on a server, EasyLibrary marks the internal package as shadowed and does not autoload it.