Skip to content

Dynamic CreateCustom Overrides

liopyu edited this page Jul 4, 2026 · 1 revision

createCustom builders can now override individual non-final entity methods with .override(methodKey, callback).


What This Is For

Use .override() when you are copying a vanilla or modded entity class and need to change a method that is not exposed by the normal EntityJS builder callbacks.

let Creeper = Java.loadClass('net.minecraft.world.entity.monster.Creeper')

StartupEvents.registry('entity_type', event => {
    let builder = event.createCustom('charged_copy', Creeper, modify => {
        modify.canFreeze(entity => false)
    })

    builder.override('isPowered()', context => {
        return true
    })
})

For normal EntityJS hooks such as tick, onHurt, canFreeze, isSunBurnTick, or finishConversion, prefer the regular builder callback when one exists. .override() is a reliable backup for class methods that are not already part of the entity builder.


Method Keys

The method key is the Java method name plus its parameter types, without a return type.

isPowered() No-argument method.
getSwelling(float) One primitive float argument.
isFood(net.minecraft.world.item.ItemStack) Uses the fully qualified class name for object parameters.
getBreedOffspring(net.minecraft.server.level.ServerLevel,net.minecraft.world.entity.AgeableMob) Multiple arguments are comma-separated with no spaces required.

EntityJS validates the key at startup. Final classes, final methods, static methods, hidden stability-sensitive methods, and methods outside the copied class tree cannot be registered.

After running /probejs dump, ProbeJS can suggest valid method keys for many copied entity classes.


Override Context

The callback receives an EntityJSDynamicOverrideContext.

context.entity The entity instance.
context.method The resolved method key.
context.args An object containing all arguments.
context.arg0, context.arg1 Fallback argument names, always available.
context.get('arg0') Reads an argument by name.
context.superCall(...args) Calls the original method implementation.

When EntityJS can find useful parameter names, those names are also exposed directly on context and through context.get(name). arg0, arg1, and so on are always present.


Calling Vanilla Logic

context.superCall() calls the original implementation once and caches the result for that override invocation. If you pass arguments, those replace the original arguments for the super call.

let Creeper = Java.loadClass('net.minecraft.world.entity.monster.Creeper')

StartupEvents.registry('entity_type', event => {
    let builder = event.createCustom('soft_swell_creeper', Creeper, modify => {})

    builder.override('getSwelling(float)', context => {
        let partialTick = context.get('arg0') || 0
        let vanilla = context.superCall(partialTick) || 0
        // For 1.21+ change entity.age to entity.tickCount for this example
        let pulse = (Math.sin((context.entity.age + partialTick) * 0.35) + 1) * 0.08

        return Math.min(1, vanilla * 0.55 + pulse)
    })
})

EntityJSUtils.superCall() is also available inside EntityJS override/callback frames, but the dynamic override context form is clearer when using .override().


Abstract Entity Classes

createCustom can be pointed at abstract entity classes. If an abstract method is not supplied, EntityJS generates a default stub and logs a warning. For real behavior, override every abstract method the entity needs.

let Animal = Java.loadClass('net.minecraft.world.entity.animal.Animal')

StartupEvents.registry('entity_type', event => {
    let builder = event.createCustom('simple_animal_copy', Animal, modify => {})

    builder.override('isFood(net.minecraft.world.item.ItemStack)', context => {
        return context.arg0.id == 'minecraft:wheat'
    })

    builder.override('getBreedOffspring(net.minecraft.server.level.ServerLevel,net.minecraft.world.entity.AgeableMob)', context => {
        return null
    })
})

Abstract methods do not have a vanilla super implementation, so context.superCall() is not useful for those methods.


String Class Names

createCustom accepts either a Java class or a fully qualified class-name string.

StartupEvents.registry('entity_type', event => {
    event.createCustom('string_creeper', 'net.minecraft.world.entity.monster.Creeper', modify => {})
        .mobCategory('monster')
        .sized(0.6, 1.7)
})

The string form is useful when you want to avoid keeping a top-level Java.loadClass variable for the copied entity class. Client-only renderer and model classes used in renderer/model setup still need client environment checks.


How It Runs

When a createCustom builder has dynamic overrides, EntityJS generates a runtime subclass for the copied entity class and routes only the selected methods through the override runtime. The method catalog is validated once, super bridge lookups are cached, and callback wrappers avoid the slow Rhino proxy path for common hot callbacks.

This matters for methods that can run every tick or every render frame: the entity still behaves like the copied Java class, and only the methods you override pay the script callback cost.

Clone this wiki locally