A Kotlin-first data generator for Patchouli books on NeoForge 1.21.1.
Extend PatchouliBookProvider and return a book from buildBook. Categories and entries are registered on the book, so no consumers, explicit save calls, or terminal build calls are needed.
class GuideBookProvider(
generator: DataGenerator,
registries: HolderLookup.Provider
) : PatchouliBookProvider(generator, registries, "guide") {
private lateinit var basics: PatchouliBookCategory
override fun buildBook() = patchouliBook(
namespace = "example",
name = "Example Guide",
landingText = "Welcome to the guide."
) {
creativeTab = "minecraft:tools_and_utilities"
addCategories()
addEntries()
}
private fun PatchouliBook.addCategories() {
basics = category(
"basics",
"Basics",
"Everything needed to get started.",
Items.BOOK
)
}
private fun PatchouliBook.addEntries() {
entry("welcome", basics, "Welcome", Items.PAPER) {
textPage("Your first page", "Getting Started")
craftingPage(Items.CRAFTING_TABLE) {
text = "Craft one before continuing."
}
}
}
}Splitting categories and entries into receiver methods keeps large books shallow. The complete example includes subcategories, item stacks, recipes, formatted text, and inline multiblocks.
Category and entry icons accept either an ItemLike or an ItemStack. Stack components are included in the generated Patchouli item string.
Entry definitions are applied after the patchouliBook block has registered every entry. This allows entries to link directly to entries declared later, including circular links:
lateinit var firstEntry: PatchouliBookEntry
lateinit var secondEntry: PatchouliBookEntry
firstEntry = entry("first", basics, "First", Items.PAPER) {
textPage(internalLink(secondEntry, "Continue"))
}
secondEntry = entry("second", basics, "Second", Items.BOOK) {
textPage(internalLink(firstEntry, "Go back"))
}This resolution step is handled by patchouliBook; providers receive a completed book.
Register the provider during GatherDataEvent:
generator.addProvider(
event.includeClient(),
GuideBookProvider(generator, event.lookupProvider.get())
)Modonomicon support is optional. Patchouli DataGen can be installed and loaded without Modonomicon; projects that use this DSL must add Modonomicon to their own compile and runtime dependencies.
Extend ModonomiconBookProvider and build the book with modonomiconBook:
class GuideBookProvider(
defaultLanguage: BiConsumer<String, String>
) : ModonomiconBookProvider(
bookId = "guide",
namespace = "example",
name = "Example Guide",
tooltip = "A useful guide",
defaultLanguage = defaultLanguage
) {
override fun buildBook() = modonomiconBook(
namespace = "example",
saveName = "guide",
name = "Example Guide",
tooltip = "A useful guide",
registries = registries()
) {
displayMode = BookDisplayMode.INDEX
category("basics", "Basics", icon = Items.BOOK) {
entryMap("w")
entry("welcome", "Welcome", Items.PAPER, mapKey = 'w') {
textPage {
title = "Getting Started"
text = "Welcome to the guide."
}
spotlightPage(Items.CRAFTING_TABLE) {
text = "Craft one before continuing."
}
}
}
}
}Register it through Modonomicon after creating your language provider:
generator.addProvider(
event.includeClient(),
NeoBookProvider.of(
event,
lookupProvider,
GuideBookProvider(languageProvider::add)
)
)Category and entry objects are usable as type-safe links through ModonomiconText.categoryLink and ModonomiconText.entryLink. Entry definitions are deferred until generation, allowing links to entries declared later. Page IDs are assigned automatically, while entry-map keys, duplicate IDs, anchors, ownership, and required pages are validated before output is written. customize hooks on books, categories, and entries expose the underlying Modonomicon models for features outside the convenience DSL.
Java uses the same model with ordinary constructors and a Consumer for entry contents:
@Override
public PatchouliBook buildBook() {
PatchouliBook book = new PatchouliBook(
"example",
"Example Guide",
"Welcome to the guide."
);
PatchouliBookCategory basics = book.category(
"basics",
"Basics",
"Everything needed to get started.",
Items.BOOK
);
book.entry("welcome", basics, "Welcome", Items.PAPER, entry -> {
entry.page(new TextPage("Your first page", "Getting Started"));
entry.page(new CraftingRecipePage(Items.CRAFTING_TABLE));
});
return book;
}See ExampleBookProviderJava for a complete provider and registration example.
Add the project repository and dependency to your Gradle build. Replace VERSION with the desired published version.
repositories {
maven {
url = "https://maven.latvian.dev/releases"
}
}
dependencies {
implementation "dev.aaronhowser.mods:patchoulidatagen-1.21.1:VERSION"
}