Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ public AbstractContainerMenu createMenu(int syncId, @NonNull Inventory playerInv
return new CrusherScreenHandler(syncId, playerInventory, this, propertyDelegate);
}

/**
* Drops this block entity's inventory at the given position when the block is removed, then
* invokes superclass removal side-effects.
*
* @param pos the position of the block being removed
* @param oldState the previous block state at that position
*/
@Override
public void preRemoveSideEffects(@NonNull BlockPos pos, @NonNull BlockState oldState) {
assert level != null;
Expand Down Expand Up @@ -191,6 +198,14 @@ private void updateMaxProgress(Level world) {
.orElse(CrusherRecipe.DEFAULT_CRUSHING_TIME);
}

/**
* Determines whether the current crusher recipe can be executed given available output space.
*
* Checks that a matching recipe exists and that the recipe's primary output and optional auxiliary
* output can both fit into their respective output slots.
*
* @return true if both the main output and the auxiliary output (if present) can be inserted into their slots, false otherwise.
*/
private boolean canCraft() {
Optional<RecipeHolder<CrusherRecipe>> recipe = getCurrentRecipe();
if (recipe.isEmpty()) return false;
Expand Down Expand Up @@ -227,13 +242,11 @@ private boolean canInsertIntoSlot(int slot, ItemStack stack) {
}

/**
* Apply the currently matched crusher recipe: produce the recipe's main output, optionally produce
* the auxiliary output based on its chance, and consume one input item.
* Apply the currently matched crusher recipe, producing the recipe's outputs and consuming one input item.
*
* If no matching recipe is available, the method makes no changes. The main output is always
* inserted (or stacked) into the main output slot; the auxiliary output is inserted only if the
* recipe provides one and its configured chance succeeds. One item is removed from the input slot
* when a recipe is applied.
* If no matching recipe is available, no changes are made. The recipe's main output is inserted into (or stacked
* onto) the main output slot; the auxiliary output is inserted into the auxiliary output slot only if the recipe
* provides one and its configured chance succeeds. One item is removed from the input slot when the recipe is applied.
*/
private void craftItem() {
Optional<RecipeHolder<CrusherRecipe>> recipe = getCurrentRecipe();
Expand Down Expand Up @@ -276,6 +289,11 @@ private void insertOrIncrement(int slot, ItemStack result, double chance) {
}
}

/**
* Look up the crusher recipe that matches the current input stack on the server.
*
* @return An Optional containing the matching RecipeHolder<CrusherRecipe> when running on a server and a recipe exists; {@code Optional.empty()} otherwise.
*/
private Optional<RecipeHolder<CrusherRecipe>> getCurrentRecipe() {
Level level = this.getLevel();
if (!(level instanceof ServerLevel serverLevel)) {
Expand All @@ -285,6 +303,13 @@ private Optional<RecipeHolder<CrusherRecipe>> getCurrentRecipe() {
.getRecipeFor(ModTypes.CRUSHER_TYPE, new CrusherRecipeInput(inventory.getFirst()), level);
}

/**
* Get the inventory slot indices that are accessible from the given block face.
*
* @param side the block face from which access is attempted
* @return an array of slot indices accessible from the specified face; if {@link Direction#DOWN} returns
* OUTPUT_SLOT and AUXILIARY_OUTPUT_SLOT, otherwise returns INPUT_SLOT and FUEL_SLOT
*/
@Override
public int @NonNull [] getSlotsForFace(@NonNull Direction side) {
return side == Direction.DOWN ? new int[]{OUTPUT_SLOT, AUXILIARY_OUTPUT_SLOT} : new int[]{INPUT_SLOT, FUEL_SLOT};
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/anya/pizza/houseki/datagen/ModRecipeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ public ModRecipeProvider(FabricPackOutput output, CompletableFuture<HolderLookup
super(output, registriesFuture);
}

/**
* Create a RecipeProvider that registers all Houseki mod recipes.
*
* <p>The provider's buildRecipes implementation defines crushing, smelting, blasting,
* stonecutting, crafting, tool/armor, upgrade, and related recipes and emits them to
* the given recipe output with mod-specific resource keys.</p>
*
* @param wrapperLookup a registry lookup provider used when constructing recipes
* @param recipeExporter the recipe output to which generated recipes will be saved
* @return a RecipeProvider configured to generate the Houseki mod's recipes
*/
@Override
protected RecipeProvider createRecipeProvider(HolderLookup.Provider wrapperLookup, RecipeOutput recipeExporter) {
return new RecipeProvider(wrapperLookup, recipeExporter) {
Expand Down Expand Up @@ -469,4 +480,4 @@ public void buildRecipes() {
public @NonNull String getName() {
return "Houseki Recipes";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,65 @@ public class CrusherRecipeBuilder implements RecipeBuilder {
@Nullable
public String group;

/**
* Creates a new CrusherRecipeBuilder for building a crusher recipe.
*
* @param input the ingredient(s) consumed by the recipe
* @param output the resulting item produced by the recipe
* @param crushingTime the crushing duration in ticks
*/
public CrusherRecipeBuilder(Ingredient input, ItemLike output, int crushingTime) {
this.input = input;
this.output = output;
this.crushingTime = crushingTime;
}

/**
* Create a builder for a crusher recipe.
*
* @param input the ingredient consumed by the recipe
* @param output the item produced by the recipe
* @param crushingTime the crushing duration for the recipe
* @return a CrusherRecipeBuilder configured with the provided input, output, and crushing time
*/
public static CrusherRecipeBuilder create(Ingredient input, ItemLike output, int crushingTime) {
return new CrusherRecipeBuilder(input, output, crushingTime);
}

/**
* Sets an optional auxiliary output item for the crusher recipe.
*
* @param stack the auxiliary output item to produce in addition to the primary output
* @return this builder instance
*/
public CrusherRecipeBuilder auxiliary(ItemLike stack) {
this.auxiliaryOutput = Optional.of(stack);
return this;
}

/**
* Set the probability that the auxiliary output is produced.
*
* @param chance the probability (0.0 to 1.0) that the auxiliary output is produced
* @return the current CrusherRecipeBuilder instance
*/
public CrusherRecipeBuilder chance(double chance) {
this.auxiliaryChance = chance;
return this;
}

/**
* Builds an advancement that unlocks the given recipe and exports the constructed
* CrusherRecipe together with that advancement.
*
* The advancement will include a `"has_the_recipe"` criterion for the provided
* recipe key, any additional criteria configured on this builder, and will reward
* the recipe. The built advancement is placed under the `"recipes/"` advancement
* path when exported.
*
* @param exporter target consumer that accepts the recipe and its advancement
* @param recipeKey resource key identifying the recipe to export
*/
public void save(RecipeOutput exporter, ResourceKey<Recipe<?>> recipeKey) {
// 1. Build the advancement
// We use recipeKey.getValue() to get the Identifier (e.g., "modid:item_crushing")
Expand Down Expand Up @@ -76,6 +115,16 @@ public void save(RecipeOutput exporter, ResourceKey<Recipe<?>> recipeKey) {
);
}

/**
* Builds a CrusherRecipe and its unlocking advancement, then registers them with the provided exporter under the "recipes/..." advancement path.
*
* The method parses the given recipeId into an Identifier and ResourceKey, adds a "has_the_recipe" criterion and any configured criteria to the advancement,
* sets the recipe as the advancement reward, constructs a CrusherRecipe using this builder's input, output, crushing time, optional auxiliary output, and auxiliary chance,
* and finally passes the recipe, its resource key, and the built advancement (placed under "recipes/<id>") to the exporter.
*
* @param exporter the RecipeOutput consumer that receives the recipe ResourceKey, the built CrusherRecipe, and its corresponding Advancement
* @param recipeId the string identifier for the recipe (parsed into an Identifier and used to derive the advancement path)
*/
public void save(RecipeOutput exporter, String recipeId) {
// 1. Convert the String ID into an Identifier and a ResourceKey
Identifier id = Identifier.parse(recipeId);
Expand Down Expand Up @@ -103,6 +152,13 @@ public void save(RecipeOutput exporter, String recipeId) {
exporter.accept(recipeKey, recipe, advancement.build(id.withPrefix("recipes/")));
}

/**
* Adds an advancement criterion that will be required to unlock the resulting recipe.
*
* @param name a unique identifier for the criterion within the recipe's advancement
* @param criterion the criterion describing the unlocking condition
* @return this builder instance for method chaining
*/
@Override
public @NonNull RecipeBuilder unlockedBy(@NonNull String name, @NonNull Criterion<?> criterion) {
this.criteria.put(name, criterion);
Expand All @@ -115,11 +171,21 @@ public void save(RecipeOutput exporter, String recipeId) {
return this;
}

/**
* Indicates this builder does not supply a default recipe identifier.
*
* @return {@code null} to indicate no default {@code ResourceKey<Recipe<?>>} is provided.
*/
@Override
public @Nullable ResourceKey<Recipe<?>> defaultId() {
return null;
}

/**
* Gets the Item representation of the configured recipe output.
*
* @return the Item corresponding to the builder's output
*/
public Item getResult() {
return output.asItem();
}
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/anya/pizza/houseki/recipe/CrusherRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,43 @@ public String toString() {
}
}

/**
* Create an ItemStack containing the recipe's primary output.
*
* @param registries the registry provider (not used by this implementation)
* @return an ItemStack containing one unit of the recipe's output
*/
public ItemStack getResult(HolderLookup.Provider registries) {
return new ItemStack(this.output);
}

/**
* Determines whether this recipe matches the provided crusher input.
*
* @param input the crusher input whose first slot will be tested against the recipe's ingredient
* @param level the current level/world context
* @return true if the recipe's input ingredient matches the item in the first slot of {@code input}, false otherwise
*/
@Override
public boolean matches(CrusherRecipeInput input, Level level) {
return this.inputItem.test(input.getItem(0));
}

/**
* Create an ItemStack representing this recipe's primary output.
*
* @return an ItemStack containing the recipe's output item with a count of 1
*/
@Override
public ItemStack assemble(CrusherRecipeInput input) {
return new ItemStack(this.output);
}

/**
* Indicates whether using this recipe triggers a player notification.
*
* @return true if a notification should be shown when the recipe is used, false otherwise.
*/
@Override
public boolean showNotification() {
return true;
Expand All @@ -65,6 +88,11 @@ public PlacementInfo placementInfo() {
return PlacementInfo.create(this.inputItem);
}

/**
* Specifies the recipe book category for this recipe.
*
* @return the RecipeBookCategory under which this recipe appears (RecipeBookCategories.CRAFTING_MISC)
*/
@Override
public RecipeBookCategory recipeBookCategory() {
// Use a standard category or your own
Expand Down Expand Up @@ -98,6 +126,11 @@ public RecipeSerializer<? extends Recipe<CrusherRecipeInput>> getSerializer() {
return SERIALIZER;
}

/**
* Identifies the recipe type for crusher recipes.
*
* @return the RecipeType instance that identifies crusher recipes
*/
@Override
public RecipeType<? extends Recipe<CrusherRecipeInput>> getType() {
return ModTypes.CRUSHER_TYPE;
Expand Down