← Roadmap

EffectiveBlock work in progress

This subsystem is not finished yet: some mechanics may work partially or with bugs, the API may change.

Blocks

Why

Minecraft has no custom blocks. The usual trick is the note block: it has 800 states (instrument × note × powered), and a resource pack can replace the model of each. But vanilla resets the state at every touch, plays the note, refuses placement onto it and breaks it like wood.

EffectiveBlock handles all of it: generates the model and blockstates into the pack, forces the state on placement, mutes the note, computes mining time with the tool in mind and draws cracks, places blocks from hand onto itself and protects against pistons.

Minimal example

Minimal block
object RubyOre : EffectiveBlock() {
    override fun getNamespacedData() = MyPlugin.instance to "ruby_ore"
    override fun getVariation() = 0

    override fun getResourcePackData() =
        ResourcePackData(texture = "textures/block/ruby_ore.png")

    override fun editItemMeta(meta: ItemMeta) {
        meta.displayName(Component.text("Ruby ore"))
    }

    override fun getHardness() = 3.0
    override fun getCorrectTools() = setOf(EffectiveToolType.PICKAXE)
    override fun getMinTier() = EffectiveToolTier.IRON
    override fun requiresCorrectTool() = true

    override fun getDrop() = arrayListOf(
        CustomLootable.ItemCellData(RubyItem.createItemStack(), 1.0)
    )

    fun init() {}
}

getVariation is a number 0..799, unique across all blocks of all plugins on the server. Two blocks with the same variation are the same note-block state.

Hooks
override fun onPlace(event: BlockPlaceEvent) {
    event.player.sendMessage("placed")
}

override fun onBreak(event: BlockBreakEvent) {
    event.player.sendMessage("broken")
}

fun init() {
    addInteractHandler(Click.RIGHT) { e ->
        e.player.sendMessage("clicked " + e.clickedBlock.location)
        Result.CANCEL_EVENT
    }
}

Interfaces and tools

EffectiveBlockInteractable — block click
fun init() {
    addInteractHandler(Click.RIGHT) { e ->
        e.player.sendMessage("face: " + e.blockFace)
        Result.CANCEL_EVENT
    }
    addInteractHandler(Click.LEFT_SHIFT) { e -> … Result.ALLOW_EVENT }
}

In e: player, clickedBlock, effectiveBlock, blockFace, hand, click. Blocks have no cooldown. The vanilla note-block interaction is already suppressed by the framework, Result decides the PlayerInteractEvent.

EffectiveBlockWithEntity — block with an entity
object Altar : EffectiveBlockWithEntity() {
    …
    override fun onPlace(event: BlockPlaceEvent) {
        val marker = getMarkerEntity(event.blockPlaced) ?: return
        EffectiveDataContainerUtils.setContainerValue(marker, OWNER, event.player.uniqueId.toString())
    }
}

val marker = Altar.getMarkerEntity(block)
val owner = EffectiveDataContainerUtils.getContainerValue<String>(marker!!, OWNER)

On placement a Marker entity (invisible, no hitbox) spawns at the block centre, on break it is removed; removing the marker removes the block. The marker is where per-block PDC data lives: owner, charge, contents.

Tools and tiers
override fun getCorrectTools() = setOf(EffectiveToolType.PICKAXE, EffectiveToolType.AXE)
override fun getMinTier() = EffectiveToolTier.DIAMOND
override fun requiresCorrectTool() = true

EffectiveToolType: PICKAXE, AXE, SHOVEL, HOE. EffectiveToolTier: HAND, WOOD, GOLD (same level as wood), STONE, IRON, DIAMOND, NETHERITE. The correct tool mines faster; requiresCorrectTool — no drop without it; getMinTier — a tier below the minimum drops nothing either.

What to override

  • getNamespacedData() *
    Plugin and block id
  • getVariation() *
    Note-block state 0..799, unique on the server
  • getResourcePackData() *
    Texture; per-face overrides via up/down/north/…
  • editItemMeta(meta) *
    Meta of the block item in the inventory
  • getHardness()
    Default: 0.8
    Hardness like vanilla (stone 1.5, obsidian 50)
  • getCorrectTools()
    Default: emptySet()
    Tools that mine faster
  • getMinTier()
    Default: HAND
    Minimum tier for the drop
  • requiresCorrectTool()
    Default: false
    No drop without the correct tool
  • getDrop()
    Default: null
    Own loot with chances; null — the block itself drops
  • isIgnitable()
    Default: false
    Flammable: lit by flint and steel and burns like wood
  • getPlaceSound() / getBreakSound() / getStepSound()
    Default: required.wood.*
    Sounds; vanilla note-block sounds are muted by the pack
  • onPlace(event) / onBreak(event)
    Default: —
    Called after placement / break

* required

Pitfalls

  • Work in progress: getCustomBlocks() goes through the internal world cache (EffectiveWorld) which has known bugs; mining/drops/sounds are less battle-tested than items.
  • The block places but looks like a note block — EffectiveResourcepack.addServerResourcepack(this, "", "") in onEnable after init() is missing: the model and blockstates are generated only into a built pack.
  • paper-global.yml must have block-updates.disable-noteblock-updates: true — otherwise the server resets the note-block state on neighbour updates.
  • Variations are a server-wide resource. Keep a registry of your plugins' variations so they do not collide.
  • Right-click with an item in hand: the framework denies the vanilla interaction (the note), so items like flint and steel only work through isIgnitable / addInteractHandler.
  • EffectiveBlockWithEntity puts a marker entity into the block — removing the marker removes the block and vice versa.