Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
8 changes: 6 additions & 2 deletions src/client/java/com/tcm/MineTale/MineTaleClient.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.tcm.MineTale;

import com.tcm.MineTale.block.workbenches.screen.FurnaceWorkbenchScreen;

import java.util.List;

import com.tcm.MineTale.block.workbenches.screen.CampfireWorkbenchScreen;
import com.tcm.MineTale.registry.ModMenuTypes;
import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.world.item.crafting.RecipeBookCategories;

public class MineTaleClient implements ClientModInitializer {

Expand All @@ -21,7 +27,5 @@ public class MineTaleClient implements ClientModInitializer {
public void onInitializeClient() {
MenuScreens.register(ModMenuTypes.FURNACE_WORKBENCH_MENU, FurnaceWorkbenchScreen::new);
MenuScreens.register(ModMenuTypes.CAMPFIRE_WORKBENCH_MENU, CampfireWorkbenchScreen::new);


}
}
5 changes: 5 additions & 0 deletions src/client/java/com/tcm/MineTale/MineTaleDataGen.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.tcm.MineTale;

import com.tcm.MineTale.datagen.ModBlockTagProvider;
import com.tcm.MineTale.datagen.ModLangProvider;
import com.tcm.MineTale.datagen.ModModelProvider;
import com.tcm.MineTale.datagen.ModRecipeProvider;

import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;

Expand All @@ -21,5 +24,7 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {

pack.addProvider(ModLangProvider::new);
pack.addProvider(ModModelProvider::new);
pack.addProvider(ModRecipeProvider::new);
pack.addProvider(ModBlockTagProvider::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
import com.tcm.MineTale.registry.ModBlocks;
import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.navigation.ScreenPosition;
Expand Down Expand Up @@ -44,7 +45,7 @@ private static MineTaleRecipeBookComponent createRecipeBookComponent(CampfireWor
new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.CAMPFIRE_SEARCH)
);

return new MineTaleRecipeBookComponent(menu, tabs);
return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.CAMPFIRE_TYPE);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu;
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
import com.tcm.MineTale.registry.ModBlocks;
import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.navigation.ScreenPosition;
Expand All @@ -15,7 +17,6 @@
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeBookCategories;
import net.minecraft.network.chat.Component;

public class FurnaceWorkbenchScreen extends AbstractRecipeBookScreen<FurnaceWorkbenchMenu> {
Expand All @@ -38,10 +39,10 @@ private static MineTaleRecipeBookComponent createRecipeBookComponent(FurnaceWork
ItemStack tabIcon = new ItemStack(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1.asItem());

List<RecipeBookComponent.TabInfo> tabs = List.of(
new RecipeBookComponent.TabInfo(tabIcon.getItem(), RecipeBookCategories.CRAFTING_MISC)
new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.FURNACE_T1_SEARCH)
);

return new MineTaleRecipeBookComponent(menu, tabs);
return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.FURNACE_T1_TYPE);
}

@Override
Expand Down Expand Up @@ -81,17 +82,8 @@ protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) {
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
renderBackground(graphics, mouseX, mouseY, delta);

// if (this.recipeBookComponent != null) {
// this.recipeBookComponent.render(graphics, mouseX, mouseY, delta);
// }

super.render(graphics, mouseX, mouseY, delta);

// if (this.recipeBookComponent != null) {
// this.recipeBookComponent.renderGhostRecipe(graphics, true);
// this.recipeBookComponent.renderTooltip(graphics, this.leftPos, this.topPos, this.hoveredSlot);
// }

renderTooltip(graphics, mouseX, mouseY);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.tcm.MineTale.MineTale;
import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder;
import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
Expand Down Expand Up @@ -46,20 +47,20 @@ protected RecipeProvider createRecipeProvider(HolderLookup.Provider registryLook
public void buildRecipes() {
new WorkbenchRecipeBuilder(ModRecipes.CAMPFIRE_TYPE, ModRecipes.CAMPFIRE_SERIALIZER)
.input(Ingredient.of(Items.PORKCHOP))
// Note: Slot 1 is optional in our logic, so we just don't add a second input
.output(new ItemStack(Items.COOKED_PORKCHOP))
.time(10) // Campfires usually take longer (30 seconds)
.time(10)
.unlockedBy("has_porkchop", has(Items.PORKCHOP))
.category(CraftingBookCategory.MISC)
.bookCategory(ModRecipeDisplay.CAMPFIRE_SEARCH)
.save(exporter, Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "campfire_pork_cooking"));

new WorkbenchRecipeBuilder(ModRecipes.FURNACE_TYPE, ModRecipes.FURNACE_SERIALIZER)
new WorkbenchRecipeBuilder(ModRecipes.FURNACE_T1_TYPE, ModRecipes.FURNACE_SERIALIZER)
.input(Ingredient.of(Items.PORKCHOP))
// Note: Slot 1 is optional in our logic, so we just don't add a second input
.output(new ItemStack(Items.COOKED_PORKCHOP))
.time(10) // Campfires usually take longer (30 seconds)
.output(new ItemStack(Items.ACACIA_BOAT))
.time(10)
.unlockedBy("has_porkchop", has(Items.PORKCHOP))
.category(CraftingBookCategory.MISC)
.bookCategory(ModRecipeDisplay.FURNACE_T1_SEARCH)
.save(exporter, Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "furnace_pork_cooking"));
Comment on lines +57 to 64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Furnace recipe outputs ACACIA_BOAT from PORKCHOP — is this intentional?

This looks like a debug/test placeholder. Cooking a porkchop into a boat doesn't make sense as a real recipe. If this is intentionally different from the campfire recipe to verify type-based filtering works, please add a comment — otherwise revert to a sensible output.

Also, the comment on line 62 ("Campfires usually take longer") is copy-pasted from the campfire recipe block above and doesn't apply here.

🤖 Prompt for AI Agents
In `@src/client/java/com/tcm/MineTale/datagen/ModRecipeProvider.java` around lines
58 - 66, The furnace recipe currently cooks PORKCHOP into an ACACIA_BOAT in the
WorkbenchRecipeBuilder block using
ModRecipes.FURNACE_T1_TYPE/ModRecipes.FURNACE_SERIALIZER (likely a leftover
test); change the output to a sensible cooked result (e.g., COOKED_PORKCHOP) or,
if the boat output is intentionally used to test type-based filtering, add a
clarifying comment explaining that it's a deliberate test placeholder and not a
real recipe; also update or remove the inaccurate time comment ("Campfires
usually take longer") so the time(10) line has a correct explanatory comment for
the furnace recipe.

}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.tcm.MineTale.recipe.WorkbenchRecipe;
import com.tcm.MineTale.registry.ModRecipeDisplay;

import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementRequirements;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.criterion.RecipeUnlockedTrigger;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.data.recipes.RecipeOutput;
Expand All @@ -28,6 +30,7 @@
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeBookCategory;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;

Expand All @@ -38,6 +41,8 @@ public class WorkbenchRecipeBuilder implements RecipeBuilder {
private final List<ItemStack> results = new ArrayList<>();
private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
private CraftingBookCategory category = CraftingBookCategory.MISC;
private Identifier bookCategory = BuiltInRegistries.RECIPE_BOOK_CATEGORY
.getKey(ModRecipeDisplay.CAMPFIRE_SEARCH);
private int cookTime = 200;
@Nullable private String group;

Expand All @@ -56,9 +61,11 @@ public static final MapCodec<WorkbenchRecipe> CODEC(RecipeType<WorkbenchRecipe>
ItemStack.STRICT_CODEC.listOf().fieldOf("results").forGetter(WorkbenchRecipe::results),
Codec.INT.optionalFieldOf("cookTime", 200).forGetter(WorkbenchRecipe::cookTime),
// Updated to CraftingBookCategory codec
CraftingBookCategory.CODEC.optionalFieldOf("category", CraftingBookCategory.MISC).forGetter(WorkbenchRecipe::category)
).apply(inst, (ingredients, results, cookTime, category) ->
new WorkbenchRecipe(ingredients, results, cookTime, type, serializer, category)
CraftingBookCategory.CODEC.optionalFieldOf("category", CraftingBookCategory.MISC).forGetter(WorkbenchRecipe::category),
Identifier.CODEC.fieldOf("book_category").forGetter(WorkbenchRecipe::bookCategory)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

book_category is a required codec field — will fail deserialization for recipes without it.

Using fieldOf("book_category") means any recipe JSON missing this key will fail to parse. Since this is a newly added field, consider optionalFieldOf with a sensible default to avoid breaking existing or hand-authored recipe data packs.

Proposed fix
-            Identifier.CODEC.fieldOf("book_category").forGetter(WorkbenchRecipe::bookCategory)
+            Identifier.CODEC.optionalFieldOf("book_category",
+                Identifier.fromNamespaceAndPath("minetale", "campfire_recipe_book_category"))
+                .forGetter(WorkbenchRecipe::bookCategory)

The same change should also be applied in WorkbenchRecipe.Serializer (lines 182 in WorkbenchRecipe.java).

🤖 Prompt for AI Agents
In
`@src/client/java/com/tcm/MineTale/datagen/builders/WorkbenchRecipeBuilder.java`
around lines 64 - 65, The serializer currently requires the "book_category" key
which breaks deserialization for recipes that lack it; change the codec
declaration in WorkbenchRecipeBuilder to use
Identifier.CODEC.optionalFieldOf("book_category",
<defaultIdentifier>).forGetter(WorkbenchRecipe::bookCategory) (choose a sensible
default Identifier, e.g., a MISC or null-safe constant) and mirror the same
optionalFieldOf usage and default handling in WorkbenchRecipe.Serializer (in
WorkbenchRecipe) so missing book_category falls back to the chosen default
instead of failing parse. Ensure code references: WorkbenchRecipeBuilder,
WorkbenchRecipe::bookCategory, and WorkbenchRecipe.Serializer.

).apply(inst, (ingredients, results, cookTime, category, bookCategory) ->
// 2. Pass the new bookCategory into the constructor
new WorkbenchRecipe(ingredients, results, cookTime, type, serializer, category, bookCategory)
));
}

Expand Down Expand Up @@ -100,6 +107,15 @@ public WorkbenchRecipeBuilder category(CraftingBookCategory category) {
return this;
}

public WorkbenchRecipeBuilder bookCategory(RecipeBookCategory category) {
Identifier id = BuiltInRegistries.RECIPE_BOOK_CATEGORY.getKey(category);
if (id != null) {
this.bookCategory = id;
}
return this;
}


/**
* Adds an output item stack to the recipe.
*
Expand Down Expand Up @@ -190,6 +206,9 @@ public void save(RecipeOutput recipeOutput, ResourceKey<Recipe<?>> resourceKey)
.requirements(AdvancementRequirements.Strategy.OR);

// Add your criteria here (omitted for brevity)
for (Map.Entry<String, Criterion<?>> entry : this.criteria.entrySet()) {
advancement.addCriterion(entry.getKey(), entry.getValue());
}

// 3. Create the Recipe Instance
WorkbenchRecipe recipe = new WorkbenchRecipe(
Expand All @@ -198,7 +217,8 @@ public void save(RecipeOutput recipeOutput, ResourceKey<Recipe<?>> resourceKey)
cookTime,
this.type,
this.serializer,
this.category
this.category,
this.bookCategory
);

// 4. Accept the recipe into the generator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.world.item.crafting.RecipeBookCategory;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType;

@Mixin(ClientRecipeBook.class)
public abstract class ClientRecipeBookMixin {
@Inject(method = "getCategory", at = @At("HEAD"), cancellable = true)
private static void minetale$addCustomCategory(RecipeHolder<?> recipe, CallbackInfoReturnable<RecipeBookCategory> cir) {
if (recipe.value().getType() == ModRecipes.FURNACE_SERIALIZER) {
// This tells the search engine to put your recipes into your custom tab
RecipeType<?> type = recipe.value().getType();

// Using .equals() satisfies the compiler
if (ModRecipes.CAMPFIRE_TYPE.equals(type)) {
cir.setReturnValue(ModRecipeDisplay.CAMPFIRE_SEARCH);
} else if (ModRecipes.FURNACE_T1_TYPE.equals(type)) {
cir.setReturnValue(ModRecipeDisplay.FURNACE_T1_SEARCH);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.RecipeBookMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.display.RecipeDisplay;

public class MineTaleRecipeBookComponent extends RecipeBookComponent<RecipeBookMenu> {
private final RecipeType<?> filterType; // The specific machine type

// Standard button sprites (the "Filter" checkmark button)
protected static final WidgetSprites FILTER_BUTTON_SPRITES = new WidgetSprites(
Expand All @@ -27,19 +29,31 @@ public class MineTaleRecipeBookComponent extends RecipeBookComponent<RecipeBookM
Identifier.withDefaultNamespace("recipe_book/filter_disabled_focused")
);

public MineTaleRecipeBookComponent(RecipeBookMenu recipeBookMenu, List<TabInfo> list) {
public MineTaleRecipeBookComponent(RecipeBookMenu recipeBookMenu, List<TabInfo> list, RecipeType<?> filterType) {
super(recipeBookMenu, list);
this.filterType = filterType;
}

@Override
protected void selectMatchingRecipes(RecipeCollection recipeCollection, StackedItemContents stackedItemContents) {
// Force everything to be "selected"
// recipeCollection.selectRecipes(stackedItemContents, (recipeDisplay) -> true);

// recipeCollection.selectRecipes(stackedItemContents, (recipeDisplay) -> {
// // Only allow recipes that use your custom Workbench display type
// // This effectively filters out vanilla CraftingRecipeDisplays (the boats)
// return recipeDisplay.type() == ModRecipeDisplay.WORKBENCH_TYPE;
// });

recipeCollection.selectRecipes(stackedItemContents, (recipeDisplay) -> {
// Only allow recipes that use your custom Workbench display type
// This effectively filters out vanilla CraftingRecipeDisplays (the boats)
return recipeDisplay.type() == ModRecipeDisplay.WORKBENCH_TYPE;
// 1. Check if the display matches your custom type
if (recipeDisplay.type() != ModRecipeDisplay.WORKBENCH_TYPE) return false;

// 2. We need to verify if the underlying recipe matches the current block's type
// Note: In 1.21+, you may need to cast the display or check the recipe's origin
// Here is the logic to ensure we only show recipes meant for THIS specific machine:
return recipeDisplay instanceof WorkbenchRecipeDisplay wbDisplay &&
wbDisplay.getRecipeType() == this.filterType;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.tcm.MineTale.registry.ModBlockEntities;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
Expand All @@ -19,8 +18,6 @@
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;

Expand Down Expand Up @@ -110,23 +107,7 @@ public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, Co
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
// AbstractWorkbench logic ensures only the Master block gets the entity.
// We override it here to point specifically to our Furnace entity.
// We override it here to point specifically to our Campfire entity.
return super.newBlockEntity(pos, state);
}

/**
* Compute the master (base) block position for this block based on its state.
*
* @param state the block state of the current block
* @param pos the position of the current block
* @return the position of the master (base) block: if the block is the upper half, the block below is used; if the block's type is `RIGHT`, the position is offset one block counterclockwise from its facing direction; otherwise the original position
*/
public BlockPos getMasterPos(BlockState state, BlockPos pos) {
BlockPos master = pos;
Direction facing = state.getValue(FACING);
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) master = master.below();
if (state.getValue(TYPE) == ChestType.RIGHT) master = master.relative(facing.getCounterClockWise());
return master;
}

}
Loading