-
Notifications
You must be signed in to change notification settings - Fork 4
Dynamic CreateCustom Overrides
createCustombuilders can now override individual non-final entity methods with.override(methodKey, callback).
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.
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.
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.
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().
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.
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.
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.