Skip to content

Commit

Permalink
Fix custom ingredient implementation (#4276)
Browse files Browse the repository at this point in the history
- Override isEmpty in CustomIngredientImpl
- Override acceptsItem in CustomIngredientImpl
- Override equals and hashCode in CustomIngredientImpl so CustomIngredients can also implement them
  - Adjust the class doc of CustomIngredient to encourage implementors to do so
- Implement Ingredient#hashCode
- Remove incorrect Nullable annotation from ItemStack parameter in CustomIngredientImpl#test
- Implement equals and hashCode on all builtin custom ingredient classes
  • Loading branch information
PepperCode1 authored Dec 5, 2024
1 parent e604fe7 commit fa62a02
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import net.minecraft.recipe.Ingredient;
import net.minecraft.recipe.display.SlotDisplay;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.entry.RegistryEntryList;

import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientImpl;

Expand All @@ -47,6 +46,9 @@
* }
* }</pre>
*
* <p>Implementors of this interface are strongly encouraged to also implement
* {@link Object#equals(Object)} and {@link Object#hashCode()}.
*
* @see CustomIngredientSerializer
*/
public interface CustomIngredient {
Expand Down Expand Up @@ -97,11 +99,7 @@ public interface CustomIngredient {
*/
default SlotDisplay toDisplay() {
// Matches the vanilla logic in Ingredient.toDisplay()
return RegistryEntryList.of(getMatchingItems().toList()).getStorage().map(
SlotDisplay.TagSlotDisplay::new,
(itemEntries) -> new SlotDisplay.CompositeSlotDisplay(
itemEntries.stream().map(Ingredient::createDisplayWithRemainder).toList()
));
return new SlotDisplay.CompositeSlotDisplay(getMatchingItems().map(Ingredient::createDisplayWithRemainder).toList());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.fabricmc.fabric.impl.recipe.ingredient;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -74,6 +75,8 @@ public static CustomIngredientSerializer<?> getSerializer(Identifier identifier)
// Actual custom ingredient logic

private final CustomIngredient customIngredient;
@Nullable
private List<RegistryEntry<Item>> customMatchingItems;

public CustomIngredientImpl(CustomIngredient customIngredient) {
// We must pass a registry entry list that contains something that isn't air. It doesn't actually get used.
Expand All @@ -82,6 +85,14 @@ public CustomIngredientImpl(CustomIngredient customIngredient) {
this.customIngredient = customIngredient;
}

private List<RegistryEntry<Item>> getCustomMatchingItems() {
if (customMatchingItems == null) {
customMatchingItems = customIngredient.getMatchingItems().toList();
}

return customMatchingItems;
}

@Override
public CustomIngredient getCustomIngredient() {
return customIngredient;
Expand All @@ -94,16 +105,38 @@ public boolean requiresTesting() {

@Override
public Stream<RegistryEntry<Item>> getMatchingItems() {
return customIngredient.getMatchingItems();
return getCustomMatchingItems().stream();
}

@Override
public boolean test(@Nullable ItemStack stack) {
return stack != null && customIngredient.test(stack);
public boolean isEmpty() {
return getCustomMatchingItems().isEmpty();
}

@Override
public boolean test(ItemStack stack) {
return customIngredient.test(stack);
}

@Override
public boolean acceptsItem(RegistryEntry<Item> registryEntry) {
return getCustomMatchingItems().contains(registryEntry);
}

@Override
public SlotDisplay toDisplay() {
return customIngredient.toDisplay();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomIngredientImpl that)) return false;
return customIngredient.equals(that.customIngredient);
}

@Override
public int hashCode() {
return customIngredient.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ public SlotDisplay toDisplay() {
);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CombinedIngredient that)) return false;
return ingredients.equals(that.ingredients);
}

@Override
public int hashCode() {
return ingredients.hashCode();
}

static class Serializer<I extends CombinedIngredient> implements CustomIngredientSerializer<I> {
private final Identifier identifier;
private final MapCodec<I> codec;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ private ComponentChanges getComponents() {
return components;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ComponentsIngredient that = (ComponentsIngredient) o;
return base.equals(that.base) && components.equals(that.components);
}

@Override
public int hashCode() {
return Objects.hash(base, components);
}

private static class Serializer implements CustomIngredientSerializer<ComponentsIngredient> {
private static final Identifier ID = Identifier.of("fabric", "components");
private static final MapCodec<ComponentsIngredient> CODEC = RecordCodecBuilder.mapCodec(instance ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.fabricmc.fabric.impl.recipe.ingredient.builtin;

import java.util.Objects;
import java.util.stream.Stream;

import com.mojang.serialization.MapCodec;
Expand All @@ -40,6 +41,7 @@

public class CustomDataIngredient implements CustomIngredient {
public static final CustomIngredientSerializer<CustomDataIngredient> SERIALIZER = new Serializer();

private final Ingredient base;
private final NbtCompound nbt;

Expand Down Expand Up @@ -95,6 +97,19 @@ private NbtCompound getNbt() {
return nbt;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomDataIngredient that = (CustomDataIngredient) o;
return base.equals(that.base) && nbt.equals(that.nbt);
}

@Override
public int hashCode() {
return Objects.hash(base, nbt);
}

private static class Serializer implements CustomIngredientSerializer<CustomDataIngredient> {
private static final Identifier ID = Identifier.of("fabric", "custom_data");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package net.fabricmc.fabric.impl.recipe.ingredient.builtin;

import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import com.mojang.serialization.MapCodec;
Expand Down Expand Up @@ -74,6 +75,19 @@ private Ingredient getSubtracted() {
return subtracted;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DifferenceIngredient that = (DifferenceIngredient) o;
return base.equals(that.base) && subtracted.equals(that.subtracted);
}

@Override
public int hashCode() {
return Objects.hash(base, subtracted);
}

private static class Serializer implements CustomIngredientSerializer<DifferenceIngredient> {
private static final Identifier ID = Identifier.of("fabric", "difference");
private static final MapCodec<DifferenceIngredient> CODEC = RecordCodecBuilder.mapCodec(instance ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.item.Item;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.recipe.Ingredient;
import net.minecraft.registry.entry.RegistryEntryList;

import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient;
import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer;
Expand All @@ -44,6 +47,10 @@ public class IngredientMixin implements FabricIngredient {
@Final
public static Codec<Ingredient> CODEC;

@Shadow
@Final
private RegistryEntryList<Item> entries;

@Inject(method = "<clinit>", at = @At("TAIL"), cancellable = true)
private static void injectCodec(CallbackInfo ci) {
Codec<CustomIngredient> customIngredientCodec = CustomIngredientImpl.CODEC.dispatch(
Expand Down Expand Up @@ -71,4 +78,18 @@ private static void injectCodec(CallbackInfo ci) {
private static PacketCodec<RegistryByteBuf, Ingredient> useCustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Ingredient> original) {
return new CustomIngredientPacketCodec(original);
}

@Inject(method = "equals(Ljava/lang/Object;)Z", at = @At("HEAD"))
private void onHeadEquals(Object obj, CallbackInfoReturnable<Boolean> cir) {
if (obj instanceof CustomIngredientImpl) {
// This will only get called when this isn't custom and other is custom, in which case the
// ingredients can never be equal.
cir.setReturnValue(false);
}
}

@Override
public int hashCode() {
return entries.hashCode();
}
}

0 comments on commit fa62a02

Please sign in to comment.