-
Notifications
You must be signed in to change notification settings - Fork 0
Using NMS
It is possible to add custom blocks and items without using Minecraft internals (also called NMS = net.minecraft.server). However, interacting with the internal code directly allows for more customization and better performance.
This guide assumes you already read the step-by-step guide.
To use NMS, you must have the Fiddle dev bundle as a dependency. The dev bundle of Fiddle is currently not published anywhere, so you should publish it to your Maven local:
- Clone the Fiddle repository and open a terminal in its root directory
./gradlew applyPatches(cd gradle-bin && ./publishDevBundleToMavenLocal)
Typically, you add the following to your plugin's build.gradle.kts:
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
dependencies {
paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT", "org.fiddlemc.fiddle")
}To use NMS when adding a block, do the same as usual, except cast the builder to BlockRegistryEntryBuilderNMS:
context.getLifecycleManager().registerEventHandler(
FiddleEvents.BLOCK,
event -> {
// Add ash block
TypedKey.create(RegistryKey.BLOCK, Key.key("example:ash_block")),
builder -> {
var builderNMS = (BlockRegistryEntryBuilderNMS) builder;
// Customize the block here
}
);
}
);BlockRegistryEntryBuilderNMS provides two highly flexible methods:
-
factoryNMS(..)
This method is more flexible than theinheritsFrom...API methods.
It allows you to use our own factory that returns anet.minecraft.world.level.Blockinstance. You call this method if you want to use a specificBlockclass, such asStairBlock,AnvilBlock,FlowerPotBlocketc., or even your own class that extendsBlock. -
propertiesNMS(..)
This method is more flexible than the API methods to change properties. It allows you to modify the block's properties directly on the internalBlockBehaviour.Propertiesclass.
Here is an example of creating a stairs block:
// Get the full block for these stairs
var fullBlock = BuiltInRegistries.BLOCK.getValue(Identifier.parse("example:ash"));
// Use a factory that returns StairBlock
builderNMS.factoryNMS(properties ->
new StairBlock(fullBlock.defaultBlockState(), properties) {}
); Here is an example of setting some properties:
builderNMS.propertiesNMS(properties -> {
properties.mapColor(MapColor.COLOR_LIGHT_GRAY);
properties.requiresCorrectToolForDrops();
properties.pushReaction(PushReaction.DESTROY);
});Here is an example of a custom netherite anvil:
event.registry().register(
TypedKey.create(RegistryKey.BLOCK, Key.key("example:netherite_anvil")),
builder -> {
var builderNMS = (BlockRegistryEntryBuilderNMS) builder;
// Use a factory that returns AnvilBlock
builderNMS.factoryNMS(AnvilBlock::new);
// Customize some properties
builderNMS.propertiesNMS(properties -> {
properties.mapColor(MapColor.METAL);
properties.requiresCorrectToolForDrops();
properties.strength(5.0F, 1200.0F);
properties.sound(SoundType.ANVIL);
properties.pushReaction(PushReaction.BLOCK);
});
}
);You can mix API calls (like builder.inheritsFromAnvil()) with NMS calls (like builderNMS.propertiesNMS(..)) on the same builder instance, in any order you like. Typically, it's easiest to use as much API as possible and only use NMS when extra customization is necessary.
Using NMS when adding items is the same as for blocks: do the same as usual, except cast the builder to ItemRegistryEntryBuilderNMS:
context.getLifecycleManager().registerEventHandler(
FiddleEvents.ITEM,
event -> {
// Add ash
TypedKey.create(RegistryKey.BLOCK, Key.key("example:ash")),
builder -> {
var builderNMS = (ItemRegistryEntryBuilderNMS) builder;
// Customize the item here
}
);
}
);Then, you can call factoryNMS and propertiesNMS:
// Use a factory that returns EggItem
builderNMS.factoryNMS(EggItem::new);
// Customize some properties
builderNMS.propertiesNMS(properties -> {
properties.stacksTo(32);
properties.fireResistant();
properties.rarity(Rarity.EPIC);
});Similarly as for blocks, typically it's easiest to use as much API as possible (especially builder.inheritsFromBlock()) and use NMS when extra customization is necessary.
Instead of defining mappings with Bukkit classes, they can also be defined by Minecraft classes. In almost every case, this gives better performance than using Bukkit classes, because the translation step can be skipped.
If you can, using NMS for mappings is highy recommended due to the performance increase.
You can cast the event to BlockMappingsComposeEventNMS:
context.getLifecycleManager().registerEventHandler(
FiddleEvents.BLOCK_MAPPING,
event -> {
var eventNMS = (BlockMappingsComposeEventNMS) event;
// Register mappings here
}
);Mappings are added by calling register(..):
eventNMS.register(builder -> {
// Define the mapping here
});Builders registered using registerNMS use Minecraft BlockState instead of Bukkit BlockData:
eventNMS.registerNMS(builder -> {
builder.from(BuiltInRegistries.BLOCK.getValue(Identifier.parse("example:ash_block")).defaultBlockState());
builder.to(Blocks.LIGHT_GRAY_CONCRETE_POWDER.defaultBlockState());
});The same shorthands as for the API version (builder.fromEveryStateOf, builder.toDefaultStateOf and eventNMS.registerStateToStateNMS) are also available.
Here is a full example:
eventNMS.registerNMS(builder -> {
// Only applies to player that have the resource pack
builder.awarenessLevel(ClientView.AwarenessLevel.RESOURCE_PACK);
// From ash block
builder.fromEveryStateOf(BuiltInRegistries.BLOCK.getValue(Identifier.parse("example:ash_block")));
// To a particular note block state
BlockState noteBlockState = Blocks.NOTE_BLOCK.defaultBlockState()
.setValue(NoteBlock.INSTRUMENT, NoteBlockInstrument.BELL)
.setValue(NoteBlock.NOTE, 1);
builder.to(noteBlockState);
});A custom function mapping also works the same:
If you want more control, you can also register a custom function for the mapping:
eventNMS.registerNMS(builder -> {
builder.from(Blocks.GRASS_BLOCK.defaultBlockState());
builder.to(
handle -> {
// Check that this block is physical (and not in a block display etc.)
if (handle.getContext().isStateOfPhysicalBlockInWorld()) {
// If it is above y = 128
if (handle.getContext().getPhysicalBlockY() > 128) {
// Replace it by snowy grass
BlockState grassBlockState = Blocks.GRASS_BLOCK.defaultBlockState()
.setValue(SnowyDirtBlock.SNOWY, true);
handle.set(grassBlockState);
}
}
},
true // Whether this mapping uses coordinates
);
});Note again that function mappings may hurt performance, especially if they apply to commonly sent blocks. Also, function mappings may or may not be run on the main thread; making sure your code is thread-safe is your own responsibility.
Using NMS when adding item mappings is the same as for block mappings: do the same as usual, except cast the event to ItemMappingsComposeEventNMS:
context.getLifecycleManager().registerEventHandler(
FiddleEvents.ITEM_MAPPING,
event -> {
var eventNMS = (ItemMappingsComposeEventNMS) event;
// Register mappings here
}
);Builders registered using registerNMS use Minecraft Item instead of Bukkit ItemType:
eventNMS.registerNMS(builder -> {
builder.from(BuiltInRegistries.ITEM.getValue(Identifier.parse("example:ash")));
builder.to(Items.GUNPOWDER);
});
You can also register function mappings:
```java
eventNMS.registerNMS(builder -> {
builder.everyAwarenessLevel();
builder.from(Items.CRAFTING_TABLE);
builder.to(handle -> {
// Add a lore line to each crafting table
var newLore = Component.literal("This is a very important block for beginners!")
.withStyle(Style.EMPTY.withItalic(false).withColor(5526612));
var lore = handle.getImmutable().get(DataComponents.LORE);
if (lore == null) {
lore = new ItemLore(newLore);
} else {
lore = lore.withLineAdded(newLore);
}
handle.getMutable().set(DataComponents.LORE, lore);
});
});Function mappings using NMS are faster than function mappings using the regular API.
Using NMS when adding component mappings is also the same: cast the event to ComponentMappingsComposeEventNMS, then use registerNMS to get a builder instance that uses the Minecraft Component instead of the Adventure Component.
context.getLifecycleManager().registerEventHandler(
FiddleEvents.COMPONENT_MAPPING,
event -> {
var eventNMS = (ComponentMappingsComposeEventNMS) event;
eventNMS.registerNMS(builder -> {
builder.from(ComponentTarget.TRANSLATABLE);
builder.to(handle -> {
if (handle.getOriginal().getContents() instanceof TranslatableContents contents) {
if (contents.getKey().equals("item.example.ash")) {
var boldStyle = handle.getImmutable().getStyle().withBold(true);
handle.getMutable().setStyle(boldStyle);
}
}
});
});
}
);Function mappings using NMS are much faster than function mappings using the regular API, but you must still be aware that a lot of components are sent to the client, so make sure the code in your mappings runs very fast.