Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1416415
5
StoffiPoff Oct 28, 2025
26a4567
5
StoffiPoff Oct 28, 2025
5b0f0db
5
StoffiPoff Oct 28, 2025
68c21ff
I have updated the `NeoForge/build.gradle` file with the correct NeoF…
google-labs-jules[bot] Oct 28, 2025
2a606df
test
StoffiPoff Oct 29, 2025
ec1e1f2
Refactor: Update JEI dependency and remove deprecated method
google-labs-jules[bot] Oct 29, 2025
17d0b0b
update to 1.21.5 continuing progress
google-labs-jules[bot] Oct 29, 2025
3fceb5c
Merge remote-tracking branch 'origin/refactor-patchouli-1.21.5-jei-de…
StoffiPoff Oct 30, 2025
4ee8bac
Refactor: Implement custom ItemModel for books
google-labs-jules[bot] Oct 30, 2025
a6b15ce
Fixes an issue where the guidebook GUI would not open when the book w…
google-labs-jules[bot] Oct 31, 2025
41afc01
External test book now kind of works to open and is able to be naviga…
google-labs-jules[bot] Oct 31, 2025
29e8307
Merge remote-tracking branch 'origin/fix-custom-item-model' into fix-…
StoffiPoff Nov 1, 2025
792f8c0
Merge remote-tracking branch 'origin/fix-custom-item-model' into fix-…
StoffiPoff Nov 1, 2025
6cde272
Merge remote-tracking branch 'origin/fix-custom-item-model' into fix-…
StoffiPoff Nov 2, 2025
451f567
Merge remote-tracking branch 'origin/fix-custom-item-model' into fix-…
StoffiPoff Nov 2, 2025
f49cf04
Fix crafting ingredients not rendering in GUI
google-labs-jules[bot] Nov 2, 2025
b1b6b6c
Ingredients and result item now renders in the gui book, however a se…
StoffiPoff Nov 2, 2025
29c50c7
MultiblockVisualizationHandler works almost fully now.
StoffiPoff Nov 5, 2025
3b88df4
Merge pull request #1 from StoffiPoff/fix-crafting-ingredient-rendering
StoffiPoff Nov 5, 2025
674dca9
Add files via upload
StoffiPoff Nov 5, 2025
b81c556
MultiblockVisualizationHandler works almost fully now.
StoffiPoff Nov 5, 2025
dc8089d
Entity renderers kind of work now, still not gotten to the bookmodel …
StoffiPoff Nov 5, 2025
fbb50e8
Merge branch 'patchouli-1.21.5-neoforge-refactor' into fix-crafting-i…
StoffiPoff Nov 5, 2025
c7e6e7e
Merge pull request #2 from StoffiPoff/fix-crafting-ingredient-rendering
StoffiPoff Nov 5, 2025
c9868c5
Model loading fix progress
StoffiPoff Nov 7, 2025
236f8c9
modified: .vscode/launch.json
StoffiPoff Nov 7, 2025
501886c
Delete .vscode directory
StoffiPoff Nov 7, 2025
c9598cb
Update FabricClientInitializer.java
StoffiPoff Nov 7, 2025
b7ca75f
Update FabricClientXplatImpl.java
StoffiPoff Nov 7, 2025
e43ee45
Update MixinGameRenderer.java
StoffiPoff Nov 7, 2025
61ad169
Fixed recipe output display for smithing
StoffiPoff Nov 7, 2025
bc3f12a
Fixed rendering GuiButtonCategory on landing page.
StoffiPoff Nov 8, 2025
f84b3a0
Add files via upload
StoffiPoff Nov 8, 2025
c140188
Fix: hook patchouli book item loader into new
StoffiPoff Nov 9, 2025
2f03aea
Add files via upload
StoffiPoff Nov 9, 2025
68a80f2
Trying to implement ghost block pick handling
StoffiPoff Nov 9, 2025
58c553c
Merge branch 'patchouli-1.21.5-neoforge-refactor' of https://github.c…
StoffiPoff Nov 9, 2025
bac4706
Fixed Multiblockvisualization not rendering as translucent, also impl…
StoffiPoff Nov 10, 2025
bbb91c6
Add files via upload
StoffiPoff Nov 10, 2025
df9e533
NBT support for blocks in multiblock renders
StoffiPoff Nov 12, 2025
5a84d7b
Merge branch 'patchouli-1.21.5-neoforge-refactor' of https://github.c…
StoffiPoff Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ out
.idea/*
!.idea/scopes
Xplat/logs/
.vscode

## ForgeGradle
/run/*
Expand Down
9 changes: 5 additions & 4 deletions NeoForge/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repositories {
}

neoForge {
version = "21.1.143"
version = "21.5.95"

runs {
configureEach {
Expand Down Expand Up @@ -49,8 +49,8 @@ neoForge {
dependencies {
implementation project(":Xplat")

compileOnly "mezz.jei:jei-1.21-common-api:19.5.0.33"
testCompileOnly "mezz.jei:jei-1.21-common-api:19.5.0.33"
compileOnly "mezz.jei:jei-1.21.5-neoforge-api:21.4.0.27"
runtimeOnly "mezz.jei:jei-1.21.5-neoforge:21.4.0.27"
}

tasks.named('test').configure {
Expand Down Expand Up @@ -82,7 +82,8 @@ processResources {
from project(":Xplat").sourceSets.main.resources
}

task apiJar(type: Jar, dependsOn: classes) {
tasks.register('apiJar', Jar) {
dependsOn classes
// Sources included because of MinecraftForge/ForgeGradle#369
from project(":Xplat").sourceSets.main.allJava
from(sourceSets.main.allJava)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package vazkii.patchouli.neoforge.client;

import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.client.IItemDecorator;
import vazkii.patchouli.common.item.ItemModBook;

public class BookCompletionDecoration implements IItemDecorator {
@Override
public boolean render(GuiGraphics guiGraphics, Font font, ItemStack stack, int xOffset, int yOffset) {
float completion = ItemModBook.getCompletion(stack);
if (completion < 1) {
int color = 0x8800FF00;
int width = Math.round(13.0F * completion);
guiGraphics.fill(xOffset + 2, yOffset + 13, xOffset + 2 + width, yOffset + 14, color);
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,156 +1,174 @@
package vazkii.patchouli.neoforge.client;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.client.renderer.item.ItemPropertyFunction;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent;
import net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent;
import net.neoforged.neoforge.client.event.RenderFrameEvent;
import net.neoforged.neoforge.client.event.RenderTooltipEvent;
import net.neoforged.neoforge.client.gui.VanillaGuiLayers;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;

import vazkii.patchouli.api.PatchouliAPI;
import vazkii.patchouli.client.base.BookModel;
import vazkii.patchouli.client.base.ClientAdvancements;
import vazkii.patchouli.client.base.ClientTicker;
import vazkii.patchouli.client.base.PersistentData;
import vazkii.patchouli.client.book.BookContentResourceListenerLoader;
import vazkii.patchouli.client.book.ClientBookRegistry;
import vazkii.patchouli.client.handler.BookRightClickHandler;
import vazkii.patchouli.client.handler.MultiblockVisualizationHandler;
import vazkii.patchouli.client.handler.TooltipHandler;
import vazkii.patchouli.common.book.BookRegistry;
import vazkii.patchouli.common.item.ItemModBook;
import vazkii.patchouli.common.item.PatchouliItems;

import vazkii.patchouli.common.item.PatchouliItems;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterItemModelsEvent;
import net.neoforged.neoforge.client.model.standalone.StandaloneModelBaker;
import net.neoforged.neoforge.client.model.standalone.StandaloneModelKey;
import vazkii.patchouli.common.book.BookRegistry;
import vazkii.patchouli.client.base.ModelLog;

@EventBusSubscriber(modid = PatchouliAPI.MOD_ID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
@EventBusSubscriber(modid = PatchouliAPI.MOD_ID, value = Dist.CLIENT)
public class NeoForgeClientInitializer {
/**
* Why are these necessary?
* BookRegistry.init is called from CommonSetupEvent. We need the models to be known in ModelRegistryEvent.
* However, there is no defined ordering for those events. They all run concurrently during the initial resource
* reload.
* We need a way of waiting for the books to become known.
* Another critical point to note is that loading runs on a fixed-size ForkJoinPool.
* Blocking the thread can starve loading completely.
* Fortunately, the implementation of Condition.await for ReentrantLock uses ForkJoinPool.managedBlock,
* which is aware of potentially blocking operations and can resize the pool accordingly.
* If parallel mod loading didn't exist we wouldn't need any of this, but here we are :))))
*/
private static final Lock BOOK_LOAD_LOCK = new ReentrantLock();
private static final Condition BOOK_LOAD_CONDITION = BOOK_LOAD_LOCK.newCondition();
private static boolean booksLoaded = false;

public static void signalBooksLoaded() {
BOOK_LOAD_LOCK.lock();
booksLoaded = true;
BOOK_LOAD_CONDITION.signalAll();
BOOK_LOAD_LOCK.unlock();
}

private static List<ResourceLocation> getBookModels() {
BOOK_LOAD_LOCK.lock();
try {
while (!booksLoaded) {
BOOK_LOAD_CONDITION.awaitUninterruptibly();
}
return BookRegistry.INSTANCE.books.values().stream().map(b -> b.model).toList();
} finally {
BOOK_LOAD_LOCK.unlock();
}
}

@SubscribeEvent
public static void modelRegistry(ModelEvent.RegisterAdditional e) {
getBookModels()
.stream()
.map(ModelResourceLocation::standalone)
.forEach(e::register);

ItemPropertyFunction prop = (stack, world, entity, seed) -> ItemModBook.getCompletion(stack);
ItemProperties.register(PatchouliItems.BOOK, ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "completion"), prop);
}

@SubscribeEvent
public static void registerReloadListeners(RegisterClientReloadListenersEvent e) {
e.registerReloadListener(BookContentResourceListenerLoader.INSTANCE);

e.registerReloadListener((ResourceManagerReloadListener) manager -> {
if (Minecraft.getInstance().level != null) {
PatchouliAPI.LOGGER.info("Reloading resource pack-based books");
ClientBookRegistry.INSTANCE.reload();
} else {
PatchouliAPI.LOGGER.debug("Not reloading resource pack-based books as client world is missing");
}
});
}

@SubscribeEvent
public static void registerOverlays(RegisterGuiLayersEvent evt) {
evt.registerAbove(VanillaGuiLayers.CROSSHAIR, ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "book_overlay"),
BookRightClickHandler::onRenderHUD
);
evt.registerBelow(VanillaGuiLayers.BOSS_OVERLAY, ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "multiblock_progress"),
MultiblockVisualizationHandler::onRenderHUD
);
}

@SubscribeEvent
public static void onInitializeClient(FMLClientSetupEvent evt) {
ClientBookRegistry.INSTANCE.init();
PersistentData.setup();
NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post e) -> {
ClientTicker.endClientTick(Minecraft.getInstance());
});
NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickBlock e) -> BookRightClickHandler.onRightClick(e.getEntity(), e.getLevel(), e.getHand(), e.getHitVec()));
NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickBlock e) -> {
InteractionResult result = MultiblockVisualizationHandler.onPlayerInteract(e.getEntity(), e.getLevel(), e.getHand(), e.getHitVec());
if (result.consumesAction()) {
e.setCanceled(true);
e.setCancellationResult(result);
}
});
NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post e) -> {
MultiblockVisualizationHandler.onClientTick(Minecraft.getInstance());
});

NeoForge.EVENT_BUS.addListener((RenderFrameEvent.Pre e) -> {
ClientTicker.renderTickStart(e.getPartialTick().getGameTimeDeltaPartialTick(false));
});
NeoForge.EVENT_BUS.addListener((RenderFrameEvent.Post e) -> {
ClientTicker.renderTickEnd();
});

NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingOut e) -> {
ClientAdvancements.playerLogout();
});

NeoForge.EVENT_BUS.addListener((RenderTooltipEvent.Pre e) -> {
TooltipHandler.onTooltip(e.getGraphics(), e.getItemStack(), e.getX(), e.getY());
});
}

@SubscribeEvent
public static void replaceBookModel(ModelEvent.ModifyBakingResult evt) {
ModelResourceLocation key = ModelResourceLocation.inventory(PatchouliItems.BOOK_ID);
evt.getModels().computeIfPresent(key, (k, oldModel) -> new BookModel(oldModel, (model) -> Minecraft.getInstance().getModelManager().getModel(ModelResourceLocation.standalone(model))));
}

private static boolean booksLoaded = false;
private static final Lock BOOK_LOAD_LOCK = new ReentrantLock();
private static final Condition BOOK_LOAD_CONDITION = BOOK_LOAD_LOCK.newCondition();

public static void signalBooksLoaded() {
BOOK_LOAD_LOCK.lock();
booksLoaded = true;
BOOK_LOAD_CONDITION.signalAll();
BOOK_LOAD_LOCK.unlock();
}

private static List<ResourceLocation> getBookModels() {
BOOK_LOAD_LOCK.lock();
try {
while (!booksLoaded) {
BOOK_LOAD_CONDITION.awaitUninterruptibly();
}
return BookRegistry.INSTANCE.books.values().stream()
.map(b -> {
if (b.model.getPath().startsWith("item/")) {
return b.model;
} else {
return ResourceLocation.fromNamespaceAndPath(b.model.getNamespace(), "item/" + b.model.getPath());
}
})
.toList();
} finally {
BOOK_LOAD_LOCK.unlock();
}
}

@SubscribeEvent
public static void registerOverlays(RegisterGuiLayersEvent evt) {
evt.registerAbove(VanillaGuiLayers.CROSSHAIR,
ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "book_overlay"),
BookRightClickHandler::onRenderHUD
);
evt.registerBelow(VanillaGuiLayers.BOSS_OVERLAY,
ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "multiblock_progress"),
MultiblockVisualizationHandler::onRenderHUD
);
}

@SubscribeEvent
public static void registerModels(RegisterItemModelsEvent event) {
ResourceLocation loaderId = ResourceLocation.fromNamespaceAndPath(PatchouliAPI.MOD_ID, "book");
String msg = ">>> PATCHOULI_MODEL >>> Registering book item model codec for loader " + loaderId;
System.out.println(msg);
ModelLog.log(msg);
event.register(loaderId, BookModel.Unbaked.MAP_CODEC);
}

@SubscribeEvent
public static void registerAdditional(ModelEvent.RegisterStandalone event) {
for (final ResourceLocation bookModel : getBookModels()) {
PatchouliAPI.LOGGER.info("Registering book model: " + bookModel);
String msg = ">>> PATCHOULI_MODEL >>> Registering book model: " + bookModel;
System.out.println(msg);
ModelLog.log(msg);
event.register(new StandaloneModelKey<>(bookModel), StandaloneModelBaker.quadCollection());
}
}


@SubscribeEvent
public static void registerItemDecorations(RegisterItemDecorationsEvent event) {
Item item;
try {
Object bookObj = PatchouliItems.BOOK;
if (bookObj instanceof Item it) {
item = it;
} else {
throw new IllegalStateException("Unsupported PatchouliItems.BOOK type: " + bookObj.getClass());
}

event.register(item, new BookCompletionDecoration());
} catch (Throwable t) {
PatchouliAPI.LOGGER.error("Failed to register book completion property", t);
}
}




@SubscribeEvent
public static void onInitializeClient(FMLClientSetupEvent evt) {
evt.enqueueWork(() -> {
ClientBookRegistry.INSTANCE.init();
PersistentData.setup();
});


NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post e) ->
ClientTicker.endClientTick(Minecraft.getInstance())
);

NeoForge.EVENT_BUS.addListener((net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.RightClickBlock e) ->
BookRightClickHandler.onRightClick(e.getEntity(), e.getLevel(), e.getHand(), e.getHitVec())
);

NeoForge.EVENT_BUS.addListener((net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.RightClickBlock e) -> {
InteractionResult result = MultiblockVisualizationHandler.onPlayerInteract(
e.getEntity(), e.getLevel(), e.getHand(), e.getHitVec()
);
if (result.consumesAction()) {
e.setCanceled(true);
e.setCancellationResult(result);
}
});

NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post e) ->
MultiblockVisualizationHandler.onClientTick(Minecraft.getInstance())
);

NeoForge.EVENT_BUS.addListener((RenderFrameEvent.Pre e) ->
ClientTicker.renderTickStart(e.getPartialTick().getGameTimeDeltaPartialTick(false))
);

NeoForge.EVENT_BUS.addListener((RenderFrameEvent.Post e) ->
ClientTicker.renderTickEnd()
);

NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingOut e) ->
ClientAdvancements.playerLogout()
);

NeoForge.EVENT_BUS.addListener((RenderTooltipEvent.Pre e) ->
TooltipHandler.onTooltip(e.getGraphics(), e.getItemStack(), e.getX(), e.getY())
);
}
}
Loading