Skip to content

Commit

Permalink
Fix more custom ingredient implementation issues (#4287)
Browse files Browse the repository at this point in the history
- Fix Ingredient.OPTIONAL_PACKET_CODEC not being modified to work with custom ingredients
- Fix ShapelessRecipeMixin sometimes not collecting all non-empty item stacks
- If client does not support certain custom ingredient, send CustomIngredientImpl#getCustomMatchingItems instead of dummy default (stone)
  • Loading branch information
PepperCode1 authored Dec 12, 2024
1 parent b874120 commit 74b7397
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public CustomIngredientImpl(CustomIngredient customIngredient) {
this.customIngredient = customIngredient;
}

private List<RegistryEntry<Item>> getCustomMatchingItems() {
public List<RegistryEntry<Item>> getCustomMatchingItems() {
if (customMatchingItems == null) {
customMatchingItems = customIngredient.getMatchingItems().toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.util.Set;

import org.jetbrains.annotations.Nullable;

import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.recipe.Ingredient;
Expand All @@ -27,7 +29,7 @@
import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer;

public class CustomIngredientPacketCodec implements PacketCodec<RegistryByteBuf, Ingredient> {
private static final int PACKET_MARKER = -1;
static final int PACKET_MARKER = -1;
private final PacketCodec<RegistryByteBuf, Ingredient> fallback;

public CustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Ingredient> fallback) {
Expand Down Expand Up @@ -72,7 +74,7 @@ public void encode(RegistryByteBuf buf, Ingredient value) {
packetCodec.encode(buf, customIngredient);
}

private static boolean shouldEncodeFallback(CustomIngredient customIngredient) {
static boolean shouldEncodeFallback(@Nullable CustomIngredient customIngredient) {
if (customIngredient == null) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import java.util.Optional;

import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.recipe.Ingredient;
import net.minecraft.util.Identifier;

import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient;
import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer;

public class OptionalCustomIngredientPacketCodec implements PacketCodec<RegistryByteBuf, Optional<Ingredient>> {
private final PacketCodec<RegistryByteBuf, Optional<Ingredient>> fallback;

public OptionalCustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Optional<Ingredient>> fallback) {
this.fallback = fallback;
}

@Override
public Optional<Ingredient> decode(RegistryByteBuf buf) {
int index = buf.readerIndex();

if (buf.readVarInt() != CustomIngredientPacketCodec.PACKET_MARKER) {
// Reset index for vanilla's normal deserialization logic.
buf.readerIndex(index);
return this.fallback.decode(buf);
}

Identifier type = buf.readIdentifier();
CustomIngredientSerializer<?> serializer = CustomIngredientSerializer.get(type);

if (serializer == null) {
throw new IllegalArgumentException("Cannot deserialize custom ingredient of unknown type " + type);
}

return Optional.of(serializer.getPacketCodec().decode(buf).toVanilla());
}

@Override
@SuppressWarnings("unchecked")
public void encode(RegistryByteBuf buf, Optional<Ingredient> value) {
if (value.isEmpty()) {
this.fallback.encode(buf, value);
return;
}

CustomIngredient customIngredient = value.get().getCustomIngredient();

if (CustomIngredientPacketCodec.shouldEncodeFallback(customIngredient)) {
// The client doesn't support this custom ingredient, so we send the matching stacks as a regular ingredient.
this.fallback.encode(buf, value);
return;
}

// The client supports this custom ingredient, so we send it as a custom ingredient.
buf.writeVarInt(CustomIngredientPacketCodec.PACKET_MARKER);
buf.writeIdentifier(customIngredient.getSerializer().getIdentifier());
PacketCodec<RegistryByteBuf, CustomIngredient> packetCodec = (PacketCodec<RegistryByteBuf, CustomIngredient>) customIngredient.getSerializer().getPacketCodec();
packetCodec.encode(buf, customIngredient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

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

import java.util.Optional;

import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
Expand All @@ -39,6 +41,7 @@
import net.fabricmc.fabric.api.recipe.v1.ingredient.FabricIngredient;
import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientImpl;
import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientPacketCodec;
import net.fabricmc.fabric.impl.recipe.ingredient.OptionalCustomIngredientPacketCodec;

@Mixin(Ingredient.class)
public class IngredientMixin implements FabricIngredient {
Expand All @@ -51,6 +54,30 @@ public class IngredientMixin implements FabricIngredient {
@Final
private RegistryEntryList<Item> entries;

@ModifyExpressionValue(
method = "<clinit>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/codec/PacketCodec;xmap(Ljava/util/function/Function;Ljava/util/function/Function;)Lnet/minecraft/network/codec/PacketCodec;",
ordinal = 0
)
)
private static PacketCodec<RegistryByteBuf, Ingredient> useCustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Ingredient> original) {
return new CustomIngredientPacketCodec(original);
}

@ModifyExpressionValue(
method = "<clinit>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/codec/PacketCodec;xmap(Ljava/util/function/Function;Ljava/util/function/Function;)Lnet/minecraft/network/codec/PacketCodec;",
ordinal = 1
)
)
private static PacketCodec<RegistryByteBuf, Optional<Ingredient>> useOptionalCustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Optional<Ingredient>> original) {
return new OptionalCustomIngredientPacketCodec(original);
}

@Inject(method = "<clinit>", at = @At("TAIL"), cancellable = true)
private static void injectCodec(CallbackInfo ci) {
Codec<CustomIngredient> customIngredientCodec = CustomIngredientImpl.CODEC.dispatch(
Expand All @@ -67,16 +94,14 @@ private static void injectCodec(CallbackInfo ci) {
);
}

@ModifyExpressionValue(
method = "<clinit>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/codec/PacketCodec;xmap(Ljava/util/function/Function;Ljava/util/function/Function;)Lnet/minecraft/network/codec/PacketCodec;",
ordinal = 0
)
)
private static PacketCodec<RegistryByteBuf, Ingredient> useCustomIngredientPacketCodec(PacketCodec<RegistryByteBuf, Ingredient> original) {
return new CustomIngredientPacketCodec(original);
// Targets the lambdas in the codecs which extract the entries from an ingredient.
// For custom ingredients, these lambdas will only be invoked when the client does not support this ingredient.
// In this case, use CustomIngredientImpl#getCustomMatchingItems, which as close as we can get.
@Inject(method = { "method_61673", "method_61677", "method_61680" }, at = @At("HEAD"), cancellable = true)
private static void onGetEntries(Ingredient ingredient, CallbackInfoReturnable<RegistryEntryList<Item>> cir) {
if (ingredient instanceof CustomIngredientImpl customIngredient) {
cir.setReturnValue(RegistryEntryList.of(customIngredient.getCustomMatchingItems()));
}
}

@Inject(method = "equals(Ljava/lang/Object;)Z", at = @At("HEAD"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void customIngredientMatch(CraftingRecipeInput recipeInput, World world,
if (fabric_requiresTesting) {
List<ItemStack> nonEmptyStacks = new ArrayList<>(recipeInput.getStackCount());

for (int i = 0; i < recipeInput.getStackCount(); ++i) {
for (int i = 0; i < recipeInput.size(); ++i) {
ItemStack stack = recipeInput.getStackInSlot(i);

if (!stack.isEmpty()) {
Expand Down

0 comments on commit 74b7397

Please sign in to comment.