Skip to content

ImperaZim/LibCustom

Repository files navigation

LibCustom

Version 2.0.0 PocketMine-MP 5.0.0+ PHP 8.2+ License

LibCustom

Description

A comprehensive custom content registration library for PocketMine-MP Bedrock Edition servers that provides a component-based system for registering custom items, blocks, and entities with full client-side rendering support via Bedrock's data-driven item/block system.

Features

  • Custom Items: Register fully data-driven custom items with automatic serializer/deserializer wiring, StringToItemParser integration, and optional Creative Inventory placement.
  • Item Components: 37 built-in item components covering properties (damage, stack size, enchantability, animations, glint, etc.) and behaviors (food, durability, projectile, wearable, cooldown, digger, shooter, etc.).
  • Auto-Detection: CustomItemTrait automatically detects the item base class and applies the correct default components (Armor, Food, Tool, Durable, ProjectileItem, Sword).
  • Custom Blocks: Register custom blocks with full component NBT generation, permutation state support, block palette insertion sorted by fnv164 hash, and async worker thread synchronization.
  • Block Components: 12 built-in block components for geometry, materials, collision, selection, light, friction, flammability, destructibility, and more.
  • Block Permutations: Condition-based block state permutations with a Cartesian product helper, bitmask utilities, and a ready-made RotatableTrait for 4-direction rotation.
  • Custom Entities: Register custom entities with automatic identifier injection into PocketMine's EntityFactory via reflection.
  • Client-Side Packet Injection: Intercepts StartGamePacket to inject custom block palette and data_driven_items experiment, and ResourcePackStackPacket to inject experiments.
  • Inventory Lock: Built-in item locking system that cancels drop, interact, and transaction events for locked items.
  • Item Tick Task: Scheduled task scanning all online players' inventories every 20 ticks for items with ItemTaskComponent, executing callbacks when conditions match.
  • Async Block Palette Sync: AsyncTask-based worker thread synchronization for custom block palettes using igbinary-serialized caches.
  • Reflection Bridge: Static utilities for injecting into private PocketMine internals (item mappings, block palettes, entity identifiers).

How to Use

Include or autoload LibCustom in your PocketMine-MP plugin. LibCustom requires LibCommons for shared filesystem helpers used by the async block-palette cache. LibCustom loads at STARTUP and automatically intercepts game packets to inject custom content. Registration should be done during your plugin's onEnable:

Requirements:

  • PocketMine-MP API 5.0.0+
  • PHP 8.2+
  • LibCommons

Installation: Place LibCustom in your server's plugins/ directory. It loads at STARTUP and initializes CustomManager automatically.

Components

imperazim\custom

Highlights

  • Added LibCustom class in imperazim\custom namespace.
  • Added CustomManager class in imperazim\custom namespace.

Usage

LibCustom -- Final PluginBase entry point. On enable it calls CustomManager::init(), schedules a delayed worker init hook for block palette sync, and schedules ItemTickTask every 20 ticks:

use imperazim\custom\LibCustom;

// LibCustom is loaded as a plugin. No manual instantiation needed.
// It automatically:
// - Initializes CustomManager on enable
// - Schedules block palette worker sync
// - Runs ItemTickTask every 20 ticks

CustomManager -- Event listener that intercepts game packets and enforces inventory lock:

use imperazim\custom\CustomManager;

// CustomManager is initialized by LibCustom. It automatically:
// - Intercepts StartGamePacket to inject block palette + data_driven_items experiment
// - Intercepts ResourcePackStackPacket to inject experiments
// - Cancels drop/interact/transaction events for locked items

imperazim\custom\item

Highlights

  • Added CustomItemFactory class in imperazim\custom\item namespace.
  • Added CustomItemTrait trait in imperazim\custom\item namespace.
  • Added ItemComponent interface in imperazim\custom\item namespace.
  • Added ItemComponentsHolder interface in imperazim\custom\item namespace.
  • Added CreativeInventoryInfo class in imperazim\custom\item namespace.

Usage

CustomItemFactory -- Singleton for registering and retrieving custom items:

use imperazim\custom\item\CustomItemFactory;
use imperazim\custom\item\CreativeInventoryInfo;
use imperazim\custom\item\component\CreativeCategory;
use imperazim\custom\item\component\CreativeGroup;

// Register a simple custom item
CustomItemFactory::getInstance()->register(
    identifier: "myplugin:ruby",
    factory: fn() => new Ruby(),
    creative: new CreativeInventoryInfo(
        category: CreativeCategory::ITEMS,
        group: CreativeGroup::NONE
    )
);

// Retrieve a custom item by identifier
$ruby = CustomItemFactory::getInstance()->get("myplugin:ruby", amount: 5);

// Get all registered item table entries (used internally for packet injection)
$entries = CustomItemFactory::getInstance()->getItemTableEntries();

CustomItemTrait -- Trait for building component-based custom items. Auto-detects the base class and applies default components:

use pocketmine\item\Item;
use imperazim\custom\item\CustomItemTrait;
use imperazim\custom\item\ItemComponentsHolder;
use imperazim\custom\item\CreativeInventoryInfo;
use imperazim\custom\item\component\CreativeCategory;

class Ruby extends Item implements ItemComponentsHolder {
    use CustomItemTrait;

    public function __construct() {
        parent::__construct(/* ... */);
        // initComponents auto-detects base class:
        // - Armor -> WearableComponent
        // - Food -> FoodComponent + UseAnimation
        // - Tool -> HandEquipped + Damage
        // - Durable -> DurabilityComponent
        // - ProjectileItem -> ProjectileComponent + Throwable
        // - Sword -> CanDestroyInCreative(false)
        $this->initComponents(
            texture: "ruby",
            creative: new CreativeInventoryInfo(
                category: CreativeCategory::ITEMS
            )
        );
    }
}

Component management -- Add, check, and retrieve components on custom items:

use imperazim\custom\item\component\GlintComponent;
use imperazim\custom\item\component\MaxStackSizeComponent;
use imperazim\custom\item\component\LoreComponent;

// Add components after initialization
$item->addComponent(new GlintComponent(true));
$item->addComponent(new MaxStackSizeComponent(16));
$item->addComponent(new LoreComponent(["Rare gemstone", "Found in deep caves"]));

// Check and retrieve
if ($item->hasComponent("minecraft:glint")) {
    $glint = $item->getComponent("minecraft:glint");
    $value = $glint->getValue(); // true
}

// Get the full component NBT (CompoundTag)
$nbt = $item->getComponentNbt();

Item utility methods -- Off-hand, cooldown, duration, and inventory lock:

// Allow item to be placed in off-hand slot
$item->allowOffHand();

// Set use cooldown (seconds)
$item->setUseCooldown(category: "ruby_ability", duration: 2.5);

// Set use duration (ticks)
$item->setUseDuration(30);

// Lock item in inventory (prevents drop/move)
$item->setLockOnInventory();

// Check lock state
if ($item->isLockedInInventory()) {
    // Item cannot be dropped or moved
}

imperazim\custom\item\component

Highlights

  • Added AllowOffHandComponent class in imperazim\custom\item\component namespace.
  • Added CanDestroyInCreativeComponent class in imperazim\custom\item\component namespace.
  • Added DamageComponent class in imperazim\custom\item\component namespace.
  • Added EnchantableSlotComponent class in imperazim\custom\item\component namespace.
  • Added EnchantableValueComponent class in imperazim\custom\item\component namespace.
  • Added GlintComponent class in imperazim\custom\item\component namespace.
  • Added HandEquippedComponent class in imperazim\custom\item\component namespace.
  • Added HoverTextColorComponent class in imperazim\custom\item\component namespace.
  • Added LiquidClippedComponent class in imperazim\custom\item\component namespace.
  • Added LockInInventoryComponent class in imperazim\custom\item\component namespace.
  • Added MaxStackSizeComponent class in imperazim\custom\item\component namespace.
  • Added ShouldDespawnComponent class in imperazim\custom\item\component namespace.
  • Added StackedByDataComponent class in imperazim\custom\item\component namespace.
  • Added UseAnimationComponent class in imperazim\custom\item\component namespace.
  • Added UseDurationComponent class in imperazim\custom\item\component namespace.
  • Added BlockPlacerComponent class in imperazim\custom\item\component namespace.
  • Added BundleInteractionComponent class in imperazim\custom\item\component namespace.
  • Added CompostableComponent class in imperazim\custom\item\component namespace.
  • Added CooldownComponent class in imperazim\custom\item\component namespace.
  • Added DamageAbsorptionComponent class in imperazim\custom\item\component namespace.
  • Added DiggerComponent class in imperazim\custom\item\component namespace.
  • Added DisplayNameComponent class in imperazim\custom\item\component namespace.
  • Added DurabilityComponent class in imperazim\custom\item\component namespace.
  • Added DurabilitySensorComponent class in imperazim\custom\item\component namespace.
  • Added DyeableComponent class in imperazim\custom\item\component namespace.
  • Added EntityPlacerComponent class in imperazim\custom\item\component namespace.
  • Added FoodComponent class in imperazim\custom\item\component namespace.
  • Added FuelComponent class in imperazim\custom\item\component namespace.
  • Added IconComponent class in imperazim\custom\item\component namespace.
  • Added InteractButtonComponent class in imperazim\custom\item\component namespace.
  • Added ItemLockComponent class in imperazim\custom\item\component namespace.
  • Added ItemTaskComponent class in imperazim\custom\item\component namespace.
  • Added KeepOnDeathComponent class in imperazim\custom\item\component namespace.
  • Added LoreComponent class in imperazim\custom\item\component namespace.
  • Added OnHitEffectComponent class in imperazim\custom\item\component namespace.
  • Added ProjectileComponent class in imperazim\custom\item\component namespace.
  • Added RarityComponent class in imperazim\custom\item\component namespace.
  • Added RecordComponent class in imperazim\custom\item\component namespace.
  • Added RepairableComponent class in imperazim\custom\item\component namespace.
  • Added ShooterComponent class in imperazim\custom\item\component namespace.
  • Added TagsComponent class in imperazim\custom\item\component namespace.
  • Added ThrowableComponent class in imperazim\custom\item\component namespace.
  • Added UseModifiersComponent class in imperazim\custom\item\component namespace.
  • Added WearableComponent class in imperazim\custom\item\component namespace.

Usage

Property components -- Components that map to item_properties in the item NBT (isProperty = true):

use imperazim\custom\item\component\AllowOffHandComponent;
use imperazim\custom\item\component\CanDestroyInCreativeComponent;
use imperazim\custom\item\component\DamageComponent;
use imperazim\custom\item\component\EnchantableSlotComponent;
use imperazim\custom\item\component\EnchantableValueComponent;
use imperazim\custom\item\component\GlintComponent;
use imperazim\custom\item\component\HandEquippedComponent;
use imperazim\custom\item\component\HoverTextColorComponent;
use imperazim\custom\item\component\LiquidClippedComponent;
use imperazim\custom\item\component\LockInInventoryComponent;
use imperazim\custom\item\component\MaxStackSizeComponent;
use imperazim\custom\item\component\ShouldDespawnComponent;
use imperazim\custom\item\component\StackedByDataComponent;
use imperazim\custom\item\component\UseAnimationComponent;
use imperazim\custom\item\component\UseDurationComponent;

// Boolean properties
$item->addComponent(new AllowOffHandComponent(true));
$item->addComponent(new CanDestroyInCreativeComponent(false));
$item->addComponent(new GlintComponent(true));
$item->addComponent(new HandEquippedComponent(true));
$item->addComponent(new LiquidClippedComponent(true));
$item->addComponent(new LockInInventoryComponent(true));
$item->addComponent(new ShouldDespawnComponent(false));
$item->addComponent(new StackedByDataComponent(true));

// Integer properties
$item->addComponent(new DamageComponent(8));
$item->addComponent(new EnchantableValueComponent(14));
$item->addComponent(new MaxStackSizeComponent(16));
$item->addComponent(new UseDurationComponent(20)); // ticks

// String properties
$item->addComponent(new EnchantableSlotComponent("sword"));
$item->addComponent(new HoverTextColorComponent("gold"));

// Use animation constants
$item->addComponent(new UseAnimationComponent(UseAnimationComponent::ANIMATION_NONE));    // 0
$item->addComponent(new UseAnimationComponent(UseAnimationComponent::ANIMATION_EAT));     // 1
$item->addComponent(new UseAnimationComponent(UseAnimationComponent::ANIMATION_DRINK));   // 2
$item->addComponent(new UseAnimationComponent(UseAnimationComponent::ANIMATION_CROSSBOW));// 4
$item->addComponent(new UseAnimationComponent(UseAnimationComponent::ANIMATION_BRUSH));   // 12

Behavior components -- Components that map to components in the item NBT (isProperty = false):

use imperazim\custom\item\component\BlockPlacerComponent;
use imperazim\custom\item\component\BundleInteractionComponent;
use imperazim\custom\item\component\CompostableComponent;
use imperazim\custom\item\component\CooldownComponent;
use imperazim\custom\item\component\DamageAbsorptionComponent;
use imperazim\custom\item\component\DiggerComponent;
use imperazim\custom\item\component\DisplayNameComponent;
use imperazim\custom\item\component\DurabilityComponent;
use imperazim\custom\item\component\DurabilitySensorComponent;
use imperazim\custom\item\component\DyeableComponent;
use imperazim\custom\item\component\EntityPlacerComponent;
use imperazim\custom\item\component\FoodComponent;
use imperazim\custom\item\component\FuelComponent;
use imperazim\custom\item\component\IconComponent;
use imperazim\custom\item\component\InteractButtonComponent;
use imperazim\custom\item\component\ItemLockComponent;
use imperazim\custom\item\component\ItemTaskComponent;
use imperazim\custom\item\component\KeepOnDeathComponent;
use imperazim\custom\item\component\LoreComponent;
use imperazim\custom\item\component\OnHitEffectComponent;
use imperazim\custom\item\component\ProjectileComponent;
use imperazim\custom\item\component\RarityComponent;
use imperazim\custom\item\component\RecordComponent;
use imperazim\custom\item\component\RepairableComponent;
use imperazim\custom\item\component\ShooterComponent;
use imperazim\custom\item\component\TagsComponent;
use imperazim\custom\item\component\ThrowableComponent;
use imperazim\custom\item\component\UseModifiersComponent;
use imperazim\custom\item\component\WearableComponent;

// Block placer -- makes the item place a block when used
$item->addComponent(new BlockPlacerComponent(
    blockId: "minecraft:stone",
    useOn: ["minecraft:grass", "minecraft:dirt"]
));

// Bundle interaction -- bundle-like container behavior
$item->addComponent(new BundleInteractionComponent(slots: 27));

// Compostable -- can be placed in a composter
$item->addComponent(new CompostableComponent(chance: 0.65));

// Cooldown -- use cooldown category and duration
$item->addComponent(new CooldownComponent(category: "ender_pearl", duration: 1.0));

// Damage absorption -- absorbs damage from specified causes
$item->addComponent(new DamageAbsorptionComponent(causes: ["entity_attack", "projectile"]));

// Digger -- mining speed with fluent block/tag targeting
$digger = new DiggerComponent(useEfficiency: true);
$digger->withBlock("minecraft:stone", speed: 8.0);
$digger->withBlock("minecraft:cobblestone", speed: 8.0);
$digger->withTag("minecraft:is_pickaxe_mineable", speed: 6.0);
$item->addComponent($digger);

// Display name -- custom item display name
$item->addComponent(new DisplayNameComponent("Enchanted Ruby"));

// Durability -- item durability with damage chance range
$item->addComponent(new DurabilityComponent(
    max: 250,
    minChance: 60,
    maxChance: 100
));

// Durability sensor -- callbacks at durability thresholds
$item->addComponent(new DurabilitySensorComponent(thresholds: [50, 25, 10]));

// Dyeable -- item supports color dyeing
$item->addComponent(new DyeableComponent(defaultColor: "#FF0000"));

// Entity placer -- spawns an entity on use
$item->addComponent(new EntityPlacerComponent(
    entity: "minecraft:pig",
    useOn: ["minecraft:grass"]
));

// Food -- edible item with nutrition and saturation
$item->addComponent(new FoodComponent(
    canAlwaysEat: false,
    nutrition: 6,
    saturation: 9.6,
    convertsTo: "myplugin:empty_bowl"
));

// Fuel -- furnace fuel with burn duration
$item->addComponent(new FuelComponent(duration: 200.0));

// Icon -- item texture with optional dyed and trim variants
$item->addComponent(new IconComponent(
    texture: "ruby",
    dyed: "ruby_dyed",
    trim: "ruby_trim"
));

// Interact button -- shows interact button text on mobile
$item->addComponent(new InteractButtonComponent("Feed Animal"));
$item->addComponent(new InteractButtonComponent(true)); // default text

// Item lock -- lock mode for item
$item->addComponent(new ItemLockComponent(mode: "lock_in_inventory"));

// Item task -- server-side scheduled callback (runs via ItemTickTask)
$item->addComponent(new ItemTaskComponent(
    callback: function($player, $item): void {
        $player->sendMessage("Your item pulses with energy!");
    },
    filter: "myplugin:enchanted_ruby"
));

// Keep on death -- item persists through death
$item->addComponent(new KeepOnDeathComponent());

// Lore -- item description lines
$item->addComponent(new LoreComponent(lines: [
    "A rare gemstone",
    "Mined from the depths"
]));

// On-hit effect -- applies potion effect on hit (server-side only)
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;

$effect = new EffectInstance(VanillaEffects::POISON(), 100, 1);
$onHit = new OnHitEffectComponent($effect);
$item->addComponent($onHit);
// In your damage handler:
$onHit->applyEffect($targetLivingEntity);

// Projectile -- item acts as ammunition for a projectile
$item->addComponent(new ProjectileComponent(
    minCritPower: 0.5,
    entity: "minecraft:arrow"
));

// Rarity -- item rarity tier
$item->addComponent(new RarityComponent("epic"));

// Record -- music disc component
$item->addComponent(new RecordComponent(
    duration: 180.0,
    sound: "record.13",
    comparator: 1
));

// Repairable -- items used to repair this item
$item->addComponent(new RepairableComponent(items: [
    "myplugin:ruby",
    "minecraft:diamond"
]));

// Shooter -- bow-like shooting behavior
$item->addComponent(new ShooterComponent(
    ammo: "minecraft:arrow",
    chargeOnDraw: true
));

// Tags -- Bedrock item tags
$item->addComponent(new TagsComponent(tags: [
    "minecraft:is_sword",
    "minecraft:is_tool"
]));

// Throwable -- item can be thrown
$item->addComponent(new ThrowableComponent(
    swing: true,
    scale: 1.0
));

// Use modifiers -- movement and use duration modifiers
$item->addComponent(new UseModifiersComponent(
    moveMod: 0.5,
    useDuration: 30.0
));

// Wearable -- item can be worn as armor
$item->addComponent(new WearableComponent(
    slot: WearableComponent::SLOT_ARMOR_HEAD,
    protection: 3,
    dispensable: true
));
// Available slot constants:
// SLOT_ARMOR_HEAD, SLOT_ARMOR_CHEST, SLOT_ARMOR_LEGS, SLOT_ARMOR_FEET
// SLOT_WEAPON_OFFHAND, SLOT_SADDLE

imperazim\custom\item\exception

Highlights

  • Added ComponentValidationException class in imperazim\custom\item\exception namespace.
  • Added ComponentConflictException class in imperazim\custom\item\exception namespace.
  • Added ComponentNotFoundException class in imperazim\custom\item\exception namespace.

Usage

All exceptions extend RuntimeException and are thrown during component registration and retrieval:

use imperazim\custom\item\exception\ComponentValidationException;
use imperazim\custom\item\exception\ComponentConflictException;
use imperazim\custom\item\exception\ComponentNotFoundException;

try {
    $item->addComponent(new DurabilityComponent(max: -1));
} catch (ComponentValidationException $e) {
    // Invalid component parameters
    $logger->error("Validation failed: " . $e->getMessage());
}

try {
    $item->addComponent(new WearableComponent(slot: "head"));
    $item->addComponent(new WearableComponent(slot: "chest")); // conflict
} catch (ComponentConflictException $e) {
    // Duplicate or conflicting component
    $logger->error("Conflict: " . $e->getMessage());
}

try {
    $component = $item->getComponent("nonexistent:component");
} catch (ComponentNotFoundException $e) {
    // Component not found on item
    $logger->error("Not found: " . $e->getMessage());
}

imperazim\custom\block

Highlights

  • Added CustomBlockFactory class in imperazim\custom\block namespace.
  • Added BlockComponent interface in imperazim\custom\block namespace.
  • Added BlockComponentsHolder interface in imperazim\custom\block namespace.
  • Added BlockPaletteManager class in imperazim\custom\block namespace.
  • Added Material class in imperazim\custom\block namespace.

Usage

CustomBlockFactory -- Singleton for registering and retrieving custom blocks:

use imperazim\custom\block\CustomBlockFactory;
use imperazim\custom\item\CreativeInventoryInfo;

// Register a simple custom block
CustomBlockFactory::getInstance()->register(
    blockFunc: fn() => new RubyBlock(),
    identifier: "myplugin:ruby_block",
    creative: new CreativeInventoryInfo(
        category: CreativeCategory::CONSTRUCTION
    )
);

// Register with custom serializer/deserializer
CustomBlockFactory::getInstance()->register(
    blockFunc: fn() => new FancyBlock(),
    identifier: "myplugin:fancy_block",
    creative: null,
    serializer: function(FancyBlock $block): Writer {
        // Custom block state serialization
    },
    deserializer: function(Reader $in): FancyBlock {
        // Custom block state deserialization
    }
);

// Retrieve a custom block by identifier
$block = CustomBlockFactory::getInstance()->get("myplugin:ruby_block");

// Get all block palette entries (used internally for packet injection)
$entries = CustomBlockFactory::getInstance()->getBlockPaletteEntries();

// Add worker init hook for async block palette synchronization
CustomBlockFactory::getInstance()->addWorkerInitHook($cachePath);

BlockComponentsHolder -- Interface for blocks that carry component data:

use pocketmine\block\Block;
use imperazim\custom\block\BlockComponentsHolder;
use imperazim\custom\block\BlockComponent;
use imperazim\custom\block\component\GeometryComponent;
use imperazim\custom\block\component\MaterialInstancesComponent;
use imperazim\custom\block\component\DestructibleByMiningComponent;
use imperazim\custom\block\component\LightEmissionComponent;
use imperazim\custom\block\Material;

class RubyBlock extends Block implements BlockComponentsHolder {

    /** @return BlockComponent[] */
    public function getBlockComponents(): array {
        return [
            new GeometryComponent("minecraft:geometry.full_block"),
            new MaterialInstancesComponent([
                new Material(
                    target: "*",
                    texture: "ruby_block",
                    renderMethod: "opaque",
                    faceDimming: true,
                    ambientOcclusion: true
                )
            ]),
            new DestructibleByMiningComponent(3.0),
            new LightEmissionComponent(5),
        ];
    }
}

Material -- Value object for material instances used in block rendering:

use imperazim\custom\block\Material;

$material = new Material(
    target: "*",              // Face target: *, up, down, north, south, east, west
    texture: "ruby_block",    // Texture name from resource pack
    renderMethod: "opaque",   // opaque, alpha_test, blend, double_sided
    faceDimming: true,        // Apply face dimming
    ambientOcclusion: true    // Apply ambient occlusion
);

// Convert to NBT for block component registration
$nbt = $material->toNbt();

BlockPaletteManager -- Singleton managing the sorted block state palette:

use imperazim\custom\block\BlockPaletteManager;

// Get all block states in the palette
$allStates = BlockPaletteManager::getInstance()->getStates();

// Get only custom block states
$customStates = BlockPaletteManager::getInstance()->getCustomStates();

// Insert a new state (re-sorts palette by fnv164 hash)
BlockPaletteManager::getInstance()->insertState($compoundTag, meta: 0);

imperazim\custom\block\component

Highlights

  • Added BreathabilityComponent class in imperazim\custom\block\component namespace.
  • Added CollisionBoxComponent class in imperazim\custom\block\component namespace.
  • Added BlockDisplayNameComponent class in imperazim\custom\block\component namespace.
  • Added DestructibleByExplosionComponent class in imperazim\custom\block\component namespace.
  • Added DestructibleByMiningComponent class in imperazim\custom\block\component namespace.
  • Added FlammableComponent class in imperazim\custom\block\component namespace.
  • Added FrictionComponent class in imperazim\custom\block\component namespace.
  • Added GeometryComponent class in imperazim\custom\block\component namespace.
  • Added LightDampeningComponent class in imperazim\custom\block\component namespace.
  • Added LightEmissionComponent class in imperazim\custom\block\component namespace.
  • Added MaterialInstancesComponent class in imperazim\custom\block\component namespace.
  • Added SelectionBoxComponent class in imperazim\custom\block\component namespace.

Usage

All 12 block components implement the BlockComponent interface with getName(): string and toNbt(): CompoundTag:

use imperazim\custom\block\component\BreathabilityComponent;
use imperazim\custom\block\component\CollisionBoxComponent;
use imperazim\custom\block\component\BlockDisplayNameComponent;
use imperazim\custom\block\component\DestructibleByExplosionComponent;
use imperazim\custom\block\component\DestructibleByMiningComponent;
use imperazim\custom\block\component\FlammableComponent;
use imperazim\custom\block\component\FrictionComponent;
use imperazim\custom\block\component\GeometryComponent;
use imperazim\custom\block\component\LightDampeningComponent;
use imperazim\custom\block\component\LightEmissionComponent;
use imperazim\custom\block\component\MaterialInstancesComponent;
use imperazim\custom\block\component\SelectionBoxComponent;
use imperazim\custom\block\Material;

// Breathability -- solid or air-like
$components[] = new BreathabilityComponent(type: "solid");

// Collision box -- custom collision bounds
$components[] = new CollisionBoxComponent(
    enabled: true,
    origin: [-8.0, 0.0, -8.0],
    size: [16.0, 16.0, 16.0]
);

// Display name
$components[] = new BlockDisplayNameComponent("Ruby Block");

// Destructible by explosion -- explosion resistance
$components[] = new DestructibleByExplosionComponent(resistance: 6.0);

// Destructible by mining -- mining time in seconds
$components[] = new DestructibleByMiningComponent(seconds: 3.0);

// Flammable -- fire catch and destroy chances
$components[] = new FlammableComponent(catch: 5, destroy: 20);

// Friction -- surface friction (0.0 to 1.0)
$components[] = new FrictionComponent(friction: 0.6);

// Geometry -- block model with optional bone visibility
$geometry = new GeometryComponent(id: "geometry.custom_chair");
$geometry->addBoneVisibility("seat", true);
$geometry->addBoneVisibility("back", "query.block_state('myplugin:has_back') == 1");
$components[] = $geometry;

// Light dampening -- light absorption (0-15)
$components[] = new LightDampeningComponent(level: 15);

// Light emission -- light output (0-15)
$components[] = new LightEmissionComponent(level: 5);

// Material instances -- textures and render methods per face
$components[] = new MaterialInstancesComponent([
    new Material("*", "ruby_block", "opaque", true, true),
    new Material("up", "ruby_block_top", "opaque", true, true),
]);

// Selection box -- custom selection/highlight bounds
$components[] = new SelectionBoxComponent(
    enabled: true,
    origin: [-8.0, 0.0, -8.0],
    size: [16.0, 8.0, 16.0]
);

imperazim\custom\block\permutation

Highlights

  • Added Permutable interface in imperazim\custom\block\permutation namespace.
  • Added BlockProperty class in imperazim\custom\block\permutation namespace.
  • Added Permutation class in imperazim\custom\block\permutation namespace.
  • Added PermutationHelper class in imperazim\custom\block\permutation namespace.
  • Added RotatableTrait trait in imperazim\custom\block\permutation namespace.

Usage

Permutable -- Interface for blocks with state-based permutations:

use pocketmine\block\Block;
use imperazim\custom\block\BlockComponentsHolder;
use imperazim\custom\block\permutation\Permutable;
use imperazim\custom\block\permutation\BlockProperty;
use imperazim\custom\block\permutation\Permutation;
use pocketmine\data\bedrock\block\convert\BlockStateReader;
use pocketmine\data\bedrock\block\convert\BlockStateWriter;
use pocketmine\nbt\tag\CompoundTag;

class LampBlock extends Block implements BlockComponentsHolder, Permutable {

    private bool $lit = false;

    public function getBlockProperties(): array {
        return [
            new BlockProperty("myplugin:lit", [0, 1]),
        ];
    }

    public function getPermutations(): array {
        return [
            (new Permutation("query.block_state('myplugin:lit') == 1"))
                ->withComponent("minecraft:light_emission", 15),
            (new Permutation("query.block_state('myplugin:lit') == 0"))
                ->withComponent("minecraft:light_emission", 0),
        ];
    }

    public function getCurrentBlockProperties(): CompoundTag {
        return CompoundTag::create()
            ->setInt("myplugin:lit", $this->lit ? 1 : 0);
    }

    public function serializeState(BlockStateWriter $writer): void {
        $writer->writeInt("myplugin:lit", $this->lit ? 1 : 0);
    }

    public function deserializeState(BlockStateReader $reader): void {
        $this->lit = $reader->readBoundedInt("myplugin:lit", 0, 1) === 1;
    }
}

BlockProperty -- Value object defining a block state property and its valid values:

use imperazim\custom\block\permutation\BlockProperty;

// Boolean-like property (0 or 1)
$lit = new BlockProperty("myplugin:lit", [0, 1]);

// Multi-value property
$color = new BlockProperty("myplugin:color", [0, 1, 2, 3]); // 4 colors

// Convert to NBT for registration
$nbt = $lit->toNbt();

Permutation -- Condition-based component overrides with fluent builder:

use imperazim\custom\block\permutation\Permutation;

$permutation = new Permutation("query.block_state('myplugin:color') == 2");
$permutation->withComponent("minecraft:material_instances", [
    "*" => ["texture" => "lamp_blue", "render_method" => "opaque"]
]);
$permutation->withComponent("minecraft:light_emission", 10);

// Convert to NBT for registration
$nbt = $permutation->toNbt();

PermutationHelper -- Static utilities for permutation state math:

use imperazim\custom\block\permutation\PermutationHelper;
use imperazim\custom\block\permutation\BlockProperty;

$lit = new BlockProperty("myplugin:lit", [0, 1]);
$color = new BlockProperty("myplugin:color", [0, 1, 2, 3]);

// Get all combinations of block property values
$product = PermutationHelper::getCartesianProduct($lit, $color);
// [[0,0], [0,1], [0,2], [0,3], [1,0], [1,1], [1,2], [1,3]]

// Convert property values to a single meta integer
$meta = PermutationHelper::toMeta($lit, $color); // e.g. 5

// Convert meta back to property values
$values = PermutationHelper::fromMeta($meta, $lit, $color);

// Get the total state bitmask for all properties
$bitmask = PermutationHelper::getStateBitmask($lit, $color);

RotatableTrait -- Ready-made Permutable implementation for 4-direction block rotation:

use pocketmine\block\Block;
use imperazim\custom\block\BlockComponentsHolder;
use imperazim\custom\block\permutation\Permutable;
use imperazim\custom\block\permutation\RotatableTrait;

class CustomFurnace extends Block implements BlockComponentsHolder, Permutable {
    use RotatableTrait;

    // RotatableTrait automatically:
    // - Adds "customies:rotation" property with values [0, 1, 2, 3]
    // - Creates 4 permutations with minecraft:transformation for each direction
    // - Sets rotation on place() based on player facing direction
    // - Implements all Permutable interface methods

    public function getBlockComponents(): array {
        return [
            // Your block components here
        ];
    }
}

// The block automatically rotates to face the player when placed.
// No additional rotation logic needed.

imperazim\custom\entity

Highlights

  • Added CustomEntityFactory class in imperazim\custom\entity namespace.

Usage

CustomEntityFactory -- Singleton for registering custom entities:

use imperazim\custom\entity\CustomEntityFactory;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\nbt\tag\CompoundTag;

class CustomZombie extends Entity {
    public static function getNetworkTypeId(): string {
        return "myplugin:custom_zombie";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo {
        return new EntitySizeInfo(1.95, 0.6);
    }
}

// Register the custom entity
CustomEntityFactory::getInstance()->register(
    className: CustomZombie::class,
    identifier: "myplugin:custom_zombie",
    creationFunc: function(World $world, CompoundTag $nbt): CustomZombie {
        return new CustomZombie(
            Location::fromObject($world->getSpawnLocation(), $world),
            $nbt
        );
    },
    behaviourId: "minecraft:zombie"
);

// The entity identifier is automatically injected into PocketMine's
// EntityFactory via ReflectionBridge::injectEntityIdentifier.

imperazim\custom\registry

Highlights

  • Added ReflectionBridge class in imperazim\custom\registry namespace.
  • Added RegistrationResult class in imperazim\custom\registry namespace.

Usage

ReflectionBridge -- Static utilities for injecting into private PocketMine internals:

use imperazim\custom\registry\ReflectionBridge;

// Inject a custom item mapping into ItemTypeDictionary
ReflectionBridge::injectItemMapping($stringId, $intId, $isComponentBased);

// Inject a block-item mapping
ReflectionBridge::injectBlockItemMapping($stringId, $intId);

// Get all registered block states
$states = ReflectionBridge::getBlockStates();

// Apply a modified block palette to the server
ReflectionBridge::applyBlockPalette($palette);

// Inject an entity identifier into PocketMine's EntityFactory
ReflectionBridge::injectEntityIdentifier($className, $identifier);

RegistrationResult -- Value object for registration outcomes:

use imperazim\custom\registry\RegistrationResult;

// Create success result
$result = RegistrationResult::ok();
$result->success; // true
$result->error;   // null

// Create failure result
$result = RegistrationResult::fail("Identifier already registered");
$result->success; // false
$result->error;   // "Identifier already registered"

imperazim\custom\task

Highlights

  • Added AsyncBlockRegistrationTask class in imperazim\custom\task namespace.
  • Added ItemTickTask class in imperazim\custom\task namespace.

Usage

AsyncBlockRegistrationTask -- Worker thread block palette synchronization:

use imperazim\custom\task\AsyncBlockRegistrationTask;

// Typically triggered automatically by CustomBlockFactory::addWorkerInitHook.
// The task reads an igbinary-serialized cache file, re-sorts the worker
// BlockStateDictionary, and registers blocks in the worker's
// RuntimeBlockStateRegistry and GlobalBlockStateHandlers.

ItemTickTask -- Scheduled task that scans player inventories every 20 ticks:

use imperazim\custom\task\ItemTickTask;
use imperazim\custom\item\component\ItemTaskComponent;

// ItemTickTask is scheduled automatically by LibCustom on enable.
// It scans all online players' inventories for items with ItemTaskComponent.
// When an item's filter matches, the callback is executed:

$item->addComponent(new ItemTaskComponent(
    callback: function($player, $item): void {
        // Called every 20 ticks while the item is in the player's inventory
        $player->getEffects()->add(
            new EffectInstance(VanillaEffects::REGENERATION(), 25, 0)
        );
    },
    filter: "myplugin:healing_charm"
));

imperazim\custom\util

Highlights

  • Added NbtHelper class in imperazim\custom\util namespace.

Usage

NbtHelper -- Static NBT conversion utilities:

use imperazim\custom\util\NbtHelper;

// Convert a PHP value to the appropriate NBT Tag
$tag = NbtHelper::getTagType(42);          // IntTag
$tag = NbtHelper::getTagType(3.14);        // FloatTag
$tag = NbtHelper::getTagType("hello");     // StringTag
$tag = NbtHelper::getTagType(true);        // ByteTag

// Convert a PHP array to ListTag or CompoundTag
$listTag = NbtHelper::getArrayTag([1, 2, 3]);                     // ListTag<IntTag>
$compoundTag = NbtHelper::getArrayTag(["key" => "value"]);         // CompoundTag
$nestedTag = NbtHelper::getArrayTag(["a" => [1, 2], "b" => 3.0]); // CompoundTag with nested ListTag

Licensing information

This project is licensed under MIT. Please see the LICENSE file for details.

EasyLibrary internal package

LibCustom now publishes an EasyLibrary 3.x internal package asset for the migration from standalone-only libraries to installable internal packages.

Release assets now include:

  • LibCustom-2.0.0.phar
  • LibCustom-2.0.0.easylib.zip
  • package.yml
  • checksums.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.