Repository navigation
Developers Worldgen Info Nodes And Widgets
The worldgen tree is made of data nodes. A node is built on the server, serialized, and reconstructed and
rendered on the client by a matching widget. You only need your own node type when your content does not fit
any existing one — for a plain block, reuse AWI's BlockNode.
A node implements com.yanny.awi.api.IDataNode:
| Member | Purpose |
|---|---|
Identifier getId() |
the node type ID; the key that pairs the node with its widget |
TooltipNode getTooltip() |
the tooltip tree of this node |
void encode(IServerUtils utils, RegistryFriendlyByteBuf buf) |
writes the node to the network buffer |
float getChance() (default 1) |
used to sort siblings by descending chance |
Every node has two constructors by convention:
- a server constructor that takes the domain objects and builds the tooltip,
- a client constructor
(IClientUtils utils, RegistryFriendlyByteBuf buf)that reads back exactly whatencodewrote.
Tooltips are not sent inline — they are interned and referenced by ID:
public class MyNode implements IDataNode {
public static final Identifier ID = Identifier.fromNamespaceAndPath("mymod", "my_node");
private final TooltipNode tooltip;
public MyNode(TooltipNode tooltip) {
this.tooltip = tooltip;
}
public MyNode(IClientUtils utils, RegistryFriendlyByteBuf buf) {
this.tooltip = utils.getTooltipCache().getNodeById(buf.readVarInt());
}
@Override
public void encode(IServerUtils utils, RegistryFriendlyByteBuf buf) {
buf.writeVarInt(utils.getTooltipCache().getNodeId(tooltip));
}
@Override
public @NotNull TooltipNode getTooltip() {
return tooltip;
}
@Override
public @NotNull Identifier getId() {
return ID;
}
}On 1.21.1:
ResourceLocationinstead ofIdentifier, created withResourceLocation.fromNamespaceAndPath("mymod", "my_node").
On 1.20.1: the ID is a
ResourceLocationbuilt with its constructor, and the buffer is a plainFriendlyByteBuf— inencode, in the client constructor and inregisterDataNode:public class MyNode implements IDataNode { public static final ResourceLocation ID = new ResourceLocation("mymod", "my_node"); private final TooltipNode tooltip; public MyNode(TooltipNode tooltip) { this.tooltip = tooltip; } public MyNode(IClientUtils utils, FriendlyByteBuf buf) { this.tooltip = utils.getTooltipCache().getNodeById(buf.readVarInt()); } @Override public void encode(IServerUtils utils, FriendlyByteBuf buf) { buf.writeVarInt(utils.getTooltipCache().getNodeId(tooltip)); } @Override public @NotNull TooltipNode getTooltip() { return tooltip; } @Override public @NotNull ResourceLocation getId() { return ID; } }
encodeand the client constructor must stay in exact sync. A mismatch corrupts everything that follows in the buffer, not just your node.
For a node that contains other nodes, extend com.yanny.awi.api.ListNode instead of implementing IDataNode
directly. It handles the child list, sorting and the child part of the encoding; you add your own payload in
encodeNode, and read it back in the client constructor after calling super. That is what AWI's own
LevelStemNode, BiomeNode, GenerationStepNode, BaseTerrainNode and PlacedFeatureNode are.
A recipe viewer needs a flat block list next to the rendered tree: the entry's outputs, and the reverse index that
answers which biome generates this block. AWI does not receive those lists from the server — it reads them out
of the tree — so a node holding a block has to say so, by implementing com.yanny.awi.api.IBlockNode:
| Member | Purpose |
|---|---|
Either<Block, TagKey<Block>> getBlock() |
what the slot stands for: one block, or the tag the feature picks from |
List<Block> getBlocks() |
the blocks it resolves to — a tag's members, expanded against the client's registry |
void retainBlocks(Predicate<Block> isVisible) (default: nothing) |
drop the members the viewer hides |
float getChance() |
sorting |
Resolve the tag in the client constructor, into a mutable list, and keep getBlocks() returning that same
list — this is what lets retainBlocks narrow it and what makes a tag follow datapack reloads. Members are kept
in registry order, so a cycling slot does not jitter between reloads.
The index and the pruning both go through this interface, so they cannot disagree: a tag contributes its members
to the reverse index, retainBlocks drops the ones the viewer hides, and a node left with nothing is dropped
entirely.
Reusing BlockNode gives you all of this. Only a node that holds a block in some shape of its own needs to
implement the interface.
The client side of the pair renders the node. Both halves are registered in registerClient under the same ID:
@Override
public void registerClient(IClientRegistry registry) {
registry.registerWidget(MyNode.ID, MyWidget::new);
registry.registerDataNode(MyNode.ID, MyNode::new);
}-
registerDataNodetakes(IClientUtils, RegistryFriendlyByteBuf) -> IDataNode— your client constructor (FriendlyByteBufon 1.20.1). -
registerWidgettakes(IWidgetUtils utils, IDataNode entry, RelativeRect rect, int maxWidth) -> IWidget.
A widget implements com.yanny.aci.api.IWidget (getRect, getDirection, render, getTooltipLines); for
a widget that lays out children, extend com.yanny.awi.api.ListWidget, which draws the connecting branch lines
for you. Geometry uses RelativeRect, which is positioned relative to its parent. render receives a
GuiGraphicsExtractor.
1.20.1–1.21.11:
renderreceives aGuiGraphicsinstead.
The same widget is used in EMI, JEI and REI — each viewer integration adapts IWidget to its own API, so you do
not write anything viewer-specific.
Before writing your own, check whether one of AWI's existing nodes fits: BlockNode (a block or a block tag, with
its own tooltip), PlacedFeatureNode, GenerationStepNode, BaseTerrainNode, BiomeNode, LevelStemNode.
Their widgets are already registered, so you register nothing on the client.
Users
Loot Info
Worldgen Info
Developers
Loot Info
Examples
Worldgen Info