Skip to content

NativeMethodAccess

friendlyhj edited this page Feb 19, 2026 · 10 revisions

Native Method Access

@since 1.19.0

A native method is a method whose implementation is provided by other programming languages.

You can directly access java native classes and methods in zenscript.

Import a native class

Besides the name of the native class, native. prefix is required.

import native.net.minecraft.world.World; to import net.minecraft.world.World class.

All public methods and fields are accessible by zenscript.

Conversion between Minecraft classes and CraftTweaker classes

CraftTweaker has tons of classes, like IWorld, IPlayer and so on, which are basically wrappers of Minecraft classes. Native Minecraft classes are required for native methods, but in crafttweaker events and other zenscript api, only CraftTweaker wrappers are given. Thus, conversion between each other is necessary.

For (almost all) CraftTweaker classes, call native getter to convert it to mc classes.

For mc classes with the corresponding CraftTweaker class, call wrapper getter to convert it to CraftTweaker classes.

The conversion also be applied as caster, so as XXX is available, and conversion will be automatic when passing in a function or method.

Method remap

It is widely known that Minecraft is a commercial game, its code is obfuscated. MCP provides a mapping between obfuscated and human-readable names to ease the development of mods. ZenUtils bundles MCP version stable-39 to remap methods. Then MCP names are available in zenscript, ZenUtils translates them to mc obfuscated names to access mc methods.

Getter/Setter method

About getFoo() and setFoo(foo) java getter/setter method, zenscript foo getter/setter can be translated to them.

Iterable interface

For a java type Iterable<T> or any subclasses, you can iterate it, get its length and cast it to list.

Example:

val set = foo(); // foo returns Set<String>, Set is a subclass of Iterable.
val list = set as [string]; // casts it to string list
print(set.length); // gets its length
for s in set { // iterate it, the type of s variable is string
    print("a string:" ~ s);
}

Check Equals

== != operator are available to all native classes, it calls Objects.equals java method.

Downcast Note

Left unchecked cast is allowed to the crt class. But the native class is not, and requires right checked cast.

// crt
val entity as IEntity = event.entity;
if (entity instanceof IPlayer) {
    val player as IPlayer = entity;
}

// mc
val mcEntity as Entity = entity.native;
if (mcEntity instanceof Player) {
    val mcPlayer = entity as Player;
}

Example

import crafttweaker.event.PlayerLoggedInEvent;
import crafttweaker.player.IPlayer;
import native.net.minecraft.world.gen.ChunkProviderServer;
import native.net.minecraft.world.chunk.Chunk;
import native.net.minecraft.world.World;

// expansion method, syntax:
// https://docs.blamejared.com/1.12/en/AdvancedFunctions/Expansion_Methods
$expand World$loadedChunkCount() as int {
    val chunkProvider = this.chunkProvider;
    if (chunkProvider instanceof ChunkProviderServer) {
        // loadedChunks getter calls getLoadedChunks() method and returns Set<Chunk> 
        return (chunkProvider as ChunkProviderServer).loadedChunks.length;
    }
    return 0;
}

events.onPlayerLoggedIn(function(event as PlayerLoggedInEvent) {
    val player = event.player;
    if (event.player.world.remote) return;
    event.player.world.catenation()
        .sleep(10)
        .run(function(world, context) {
            print(world.native.loadedChunkCount() ~ " chunks loaded");
        })
        .start();
});

Class Inheriting

@since 1.21.0

A custom zenClass can inherit other native classes or interfaces. By it, you can:

  • Skip ContentTweaker and Create a custom block/item by extending minecraft Block Item class. (but need register it and its model manually)
  • Create custom impls for other mods API
  • With Mixin, make an existed class to inherit other classes.

Basically, add extends ClassA, ClassB, ClassC, ... after zenClass declaration. ClassA can be class or interface, while ClassB and ClassC must be interface. These classes should be native ones, extending another zenClass is not supported yet.

If ClassA is class, then the class is the parent of the zenClass. super keyword is used to call members and constructors of the parent class. In class body, you can write method override. However, MCP remap is not implemented for it currently, you have to write srg name. (func_123456_a)

A example to add a new crop:

#loader preinit
import native.net.minecraft.init.Blocks;
import native.net.minecraft.block.Block;
import native.net.minecraft.block.BlockCrops;
import native.net.minecraft.util.ResourceLocation;
import native.net.minecraft.world.World;
import native.net.minecraft.item.Item;
import native.net.minecraft.util.math.BlockPos;
import native.net.minecraft.block.state.IBlockState;
import native.net.minecraft.item.ItemSeeds;
import native.java.util.Random;
import native.net.minecraftforge.event.RegistryEvent;

zenClass CrystalAlga extends BlockCrops {
    zenConstructor() {
        super(); // in constructor, call the parent's constructor is required, even zero argument.
    }

    // canUseBonemeal
    // the function name must be srg, you'd better write mcp name as comment to improve readability
    function func_180670_a(world as World, rand as Random, pos as BlockPos, state as IBlockState) as bool {
        return false;
    }

    // getSeed
    function func_149866_i() as Item {
        return itemUtils.getItem("contenttweaker:crystal_alga_seeds").definition.native;
    }

    // getCrop
    function func_149865_P() as Item {
        return itemUtils.getItem("contenttweaker:crystal_alga").definition.native;
    }
}

// create the custom crop and its seed
static crops as Block = CrystalAlga().setRegistryName("contenttweaker", "crystal_alga_crops");
static seeds as Item = ItemSeeds(crops, Blocks.FARMLAND).setTranslationKey("contenttweaker.crystal_alga_seeds").setRegistryName("contenttweaker", "crystal_alga_seeds");

// and register them
events.register(function(event as RegistryEvent.Register) {
    val registryName = event.name.toString();
    if (registryName == "minecraft:blocks") {
        event.registry.register(crops);
    } else if (registryName == "minecraft:items") {
        event.registry.register(seeds);
    }
});
#loader preinit
#priority -1
#sideonly client

import native.net.minecraft.client.renderer.block.model.ModelResourceLocation;
import native.net.minecraftforge.client.model.ModelLoader;
import native.net.minecraftforge.client.event.ModelRegistryEvent;
import scripts.preinit.crops.seeds;

// in client only script, register the model of seeds item
events.register(function(event as ModelRegistryEvent) {
    ModelLoader.setCustomModelResourceLocation(seeds, 0, ModelResourceLocation(seeds.registryName, "inventory"));
});

Sensitive Operations Blacklist

Not all native codes are exposed to zenscript, sensitive operations are blacklisted from being called or referenced.

It includes:

  • File IO
  • Network
  • Window
  • Multithreading
  • Mixin/ASM
  • Other language lib (scala kotlin groovy)
  • Java low-level code (java.lang, jdk., sun., ...)

java.lang.Class access

@since 1.22.4

java.lang.Class is the core of Java Reflection API, it should be forbidden generally speaking. However, many mod codes also use it. In order to call these codes, ZenUtils allows to access it with any reflection permission denied.

import native.java.lang.Class; // import
import native.java.util.ArrayList;
import native.net.minecraft.item.ItemStack;

// call `class` static getter to get a class
val listClass = ArrayList.class;

// prints "class java.util.ArrayList"
print(toString(listClass));

val item as ItemStack = <minecraft:apple>.native;

// gets the runtime class of an object
// these two are equivalent
// only native objects are supported
// if the object is a forbidden one, the getter will return its unbanned nearest parent class
val itemClass = item.class;
val itemClass2 = item.getClass();

// all members of Class are forbidden, you can only pass Class object to mod code
print(itemClass.getName()); // error
val methods = itemClass.getMethods(); // error

// of course, you can not get a class from a string
Class.forName("java.io.InputStream"); // error

Clone this wiki locally