← Roadmap

EffectiveEntity

Entities

Why

A custom mob is a vanilla entity with a PDC tag, tuned attributes and listeners. The problem: the tag must be set on every spawn, and finding "all my mobs" means walking every entity in every world.

EffectiveEntity sets the key itself, caches live instances as chunks load and gives click and hit handlers per type. Spawn eggs and composite entities are built on top of it.

Minimal example

Minimal entity
object Guard : EffectiveEntityWithSpawnEgg() {
    override fun getEntityType() = EntityType.ZOMBIE
    override fun getNamespacedData() = MyPlugin.instance to "guard"

    override fun editEntity(entity: Entity) {
        entity.customName(Component.text("Guard"))
        entity.isCustomNameVisible = true
        (entity as Zombie).isBaby = false
    }

    fun init() {
        addInteractHandler(Click.RIGHT) { e ->
            e.player.sendMessage("Hello")
            Result.CANCEL_EVENT
        }
        doEntityNearLookable(lookDistance = 6f)
    }
}

EffectiveEntityWithSpawnEgg is EffectiveEntity plus an automatic guard_spawn_egg egg. If you do not need the egg, extend EffectiveEntity.

Spawn and lookup
Guard.spawnEntity(location)
Guard.spawnEntity(location, listOf("5"))   // с AdditionalArgs

val all = Guard.getEntities()
val here = Guard.getEntitiesInBlock(block)
if (EffectiveEntity.getNamespacedKeyByEntity(e) == Guard.getNamespacedKey()) { … }

Interfaces and the egg

Clicks and looking are Effective* interfaces, like for items; the egg is a separate wrapper item.

EffectiveEntityInteractable — click and hit
addInteractHandler(Click.RIGHT, { e ->
    e.player.sendMessage("hi from " + e.clickedEntity.name)
    Result.CANCEL_EVENT
})

addInteractHandler(Click.LEFT, { e -> … Result.ALLOW_EVENT }, cooldownData = CooldownData(20))

EffectiveEntityInteractable.addInteractHandler(cowEntity, Click.RIGHT, { e -> … })

RIGHT — right-click on the entity, LEFT — attack; sneak variants as for items. In e: player, clickedEntity, hand, click. Via EffectiveEntityInteractable.addInteractHandler with a vanilla entity (no key) the handler hooks its whole EntityType.

EffectiveEntityLookable — looking
doEntityNearLookable()

doEntityNearLookable(lookDistance = 10f)

doEntityNearLookable(whoToLook = Look.TO_NEAR_ENTITY)

doEntityNearLookable(whoToLook = { it is Player && it.isSneaking }, lookDistance = 4f)

The entity turns its head towards the nearest target within lookDistance blocks. Look.TO_NEAR_PLAYER (default) and TO_NEAR_ENTITY, or your own (Entity) -> Boolean predicate.

SummoningEggItem — spawn egg
object Guard : EffectiveEntityWithSpawnEgg() {
    …
    override fun getSpawnEggMaterial() = Material.ZOMBIE_SPAWN_EGG
    override fun getSpawnPlacement() = SpawnPlacement.TOP
    override fun editSpawnEggMeta(meta: ItemMeta) {
        meta.displayName(Component.text("Guard egg"))
    }
}

player.inventory.addItem(Guard.getSpawnEggItem(3))

object GuardEgg : SummoningEggItem() {
    override fun getSpawnEffectiveEntity() = Guard
    override fun getMaterial() = Material.EGG
    override fun getNamespacedData() = MyPlugin.instance to "guard_egg"
    override fun editMeta(meta: ItemMeta) {}
}

EffectiveEntityWithSpawnEgg makes the egg for you (key <id>_spawn_egg); SummoningEggItem — when the egg should be a separate item. SpawnPlacement: VANILLA — like a vanilla egg (into a passable block or against the clicked face), TOP/BOTTOM — above/below the block, CENTER — block centre, EXACT — the click point. The entity AdditionalArgs travel through the egg.

AdditionalArgs — per-instance parameters
override fun getAdditionalArgs() = AdditionalArgs(
    MyPlugin.instance,
    listOf("power" to PersistentDataType.INTEGER),
)

override fun editEntity(entity: Entity) {
    val power = EffectiveDataContainerUtils.getContainerValue<Int>(entity, additionalKey("power")) ?: 1
    (entity as Zombie).getAttribute(Attribute.ATTACK_DAMAGE)?.baseValue = power.toDouble()
}

Guard.spawnEntity(location, listOf("5"))

Same as for items: values in declaration order, in the entity PDC, available already in editEntity. Command: /emob guard 5. With EffectiveEntityWithSpawnEgg the parameters travel in the egg and apply on spawn.

What to override

  • getEntityType() *
    Base vanilla type
  • getNamespacedData() *
    Plugin and id
  • editEntity(entity) *
    Attributes, name, equipment — called on every spawn
  • getAdditionalArgs()
    Default: null
    Per-instance parameters in PDC
  • getSpawnEggMaterial()
    Default: PIG_SPAWN_EGG
    EffectiveEntityWithSpawnEgg only
  • getSpawnPlacement()
    Default: VANILLA
    TOP / BOTTOM / VANILLA / CENTER / EXACT
  • editSpawnEggMeta(meta)
    Default: —
    Egg name and lore

* required

Pitfalls

  • addInteractHandler on a non-custom entity (say a plain cow) fires for every cow — matching falls back to EntityType.
  • doEntityNearLookable sends a rotation packet, not a teleport — on purpose, so the client is not jerked around.
  • Egg methods are called from the constructor — constants only, not subclass fields.