components, @Nullable ResourceLocation texture) {
+ super(stack, graphics, x, y, font, components);
+ this.originalTexture = texture;
+ this.texture = texture;
}
/**
- * {@return the original tooltip background's gradient end color (bottom edge)}
+ * {@return the original texture location given to the tooltip render method (may originate from {@link DataComponents#TOOLTIP_STYLE})}
*/
- public int getOriginalBackgroundEnd() {
- return originalBackground;
+ @Nullable
+ public ResourceLocation getOriginalTexture() {
+ return originalTexture;
}
/**
- * {@return the original tooltip border's gradient start color (top edge)}
+ * {@return the texture location that will be used to render the tooltip}
*/
- public int getOriginalBorderStart() {
- return originalBorderStart;
+ @Nullable
+ public ResourceLocation getTexture() {
+ return texture;
}
/**
- * {@return the original tooltip border's gradient end color (bottom edge)}
+ * Set the texture to use for the tooltip background and frame or {@code null} to use the default textures.
+ *
+ * The given {@link ResourceLocation} will be prefixed with {@code tooltip/} and suffixed with {@code _background}
+ * and {@code _frame} to determine the background and frame texture respectively
*/
- public int getOriginalBorderEnd() {
- return originalBorderEnd;
+ public void setTexture(@Nullable ResourceLocation texture) {
+ this.texture = texture;
}
}
}
diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
index 8f66013fcf..c19d9e788f 100644
--- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
+++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
@@ -52,6 +52,7 @@
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Items;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.LevelReader;
@@ -141,13 +142,16 @@
import net.neoforged.neoforge.fluids.CauldronFluidContent;
import net.neoforged.neoforge.fluids.FluidType;
import net.neoforged.neoforge.fluids.crafting.CompoundFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.CustomDisplayFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.DataComponentFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.DifferenceFluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.EmptyFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.FluidIngredientCodecs;
import net.neoforged.neoforge.fluids.crafting.FluidIngredientType;
import net.neoforged.neoforge.fluids.crafting.IntersectionFluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.SingleFluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.TagFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.SimpleFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.display.FluidSlotDisplay;
+import net.neoforged.neoforge.fluids.crafting.display.FluidStackSlotDisplay;
+import net.neoforged.neoforge.fluids.crafting.display.FluidTagSlotDisplay;
import net.neoforged.neoforge.forge.snapshots.ForgeSnapshotsMod;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.DualStackUtils;
@@ -353,6 +357,12 @@ public class NeoForgeMod {
*/
public static final Holder NOT_HOLDER_SET = HOLDER_SET_TYPES.register("not", NotHolderSet.Type::new);
+ private static final DeferredRegister> SLOT_DISPLAY_TYPES = DeferredRegister.create(Registries.SLOT_DISPLAY, NeoForgeVersion.MOD_ID);
+
+ public static final DeferredHolder, SlotDisplay.Type> FLUID_SLOT_DISPLAY = SLOT_DISPLAY_TYPES.register("fluid", () -> new SlotDisplay.Type<>(FluidSlotDisplay.MAP_CODEC, FluidSlotDisplay.STREAM_CODEC));
+ public static final DeferredHolder, SlotDisplay.Type> FLUID_STACK_SLOT_DISPLAY = SLOT_DISPLAY_TYPES.register("fluid_stack", () -> new SlotDisplay.Type<>(FluidStackSlotDisplay.MAP_CODEC, FluidStackSlotDisplay.STREAM_CODEC));
+ public static final DeferredHolder, SlotDisplay.Type> FLUID_TAG_SLOT_DISPLAY = SLOT_DISPLAY_TYPES.register("fluid_tag", () -> new SlotDisplay.Type<>(FluidTagSlotDisplay.MAP_CODEC, FluidTagSlotDisplay.STREAM_CODEC));
+
private static final DeferredRegister> INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPES, NeoForgeVersion.MOD_ID);
public static final DeferredHolder, IngredientType> COMPOUND_INGREDIENT_TYPE = INGREDIENT_TYPES.register("compound", () -> new IngredientType<>(CompoundIngredient.CODEC));
@@ -363,13 +373,12 @@ public class NeoForgeMod {
public static final DeferredHolder, IngredientType> CUSTOM_DISPLAY_INGREDIENT = INGREDIENT_TYPES.register("custom_display", () -> new IngredientType<>(CustomDisplayIngredient.CODEC));
private static final DeferredRegister> FLUID_INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES, NeoForgeVersion.MOD_ID);
- public static final DeferredHolder, FluidIngredientType> SINGLE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("single", () -> new FluidIngredientType<>(SingleFluidIngredient.CODEC));
- public static final DeferredHolder, FluidIngredientType> TAG_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("tag", () -> new FluidIngredientType<>(TagFluidIngredient.CODEC));
- public static final DeferredHolder, FluidIngredientType> EMPTY_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("empty", () -> new FluidIngredientType<>(EmptyFluidIngredient.CODEC));
+ public static final DeferredHolder, FluidIngredientType> SIMPLE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("simple", FluidIngredientCodecs::simpleType);
public static final DeferredHolder, FluidIngredientType> COMPOUND_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("compound", () -> new FluidIngredientType<>(CompoundFluidIngredient.CODEC));
public static final DeferredHolder, FluidIngredientType> DATA_COMPONENT_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("components", () -> new FluidIngredientType<>(DataComponentFluidIngredient.CODEC));
public static final DeferredHolder, FluidIngredientType> DIFFERENCE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("difference", () -> new FluidIngredientType<>(DifferenceFluidIngredient.CODEC));
public static final DeferredHolder, FluidIngredientType> INTERSECTION_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("intersection", () -> new FluidIngredientType<>(IntersectionFluidIngredient.CODEC));
+ public static final DeferredHolder, FluidIngredientType> CUSTOM_DISPLAY_FLUID_INGREDIENT = FLUID_INGREDIENT_TYPES.register("custom_display", () -> new FluidIngredientType<>(CustomDisplayFluidIngredient.CODEC, CustomDisplayFluidIngredient.STREAM_CODEC));
private static final DeferredRegister> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, NeoForgeVersion.MOD_ID);
public static final DeferredHolder, MapCodec> AND_CONDITION = CONDITION_CODECS.register("and", () -> AndCondition.CODEC);
@@ -547,6 +556,7 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) {
VANILLA_FLUID_TYPES.register(modEventBus);
ENTITY_PREDICATE_CODECS.register(modEventBus);
ITEM_SUB_PREDICATES.register(modEventBus);
+ SLOT_DISPLAY_TYPES.register(modEventBus);
INGREDIENT_TYPES.register(modEventBus);
CONDITION_CODECS.register(modEventBus);
GLOBAL_LOOT_MODIFIER_SERIALIZERS.register(modEventBus);
diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java
index b4cc31de7d..ab5cc0f8e1 100644
--- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java
+++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java
@@ -314,19 +314,6 @@ default ResourceLocation getArmorTexture(ItemStack stack, EquipmentModel.LayerTy
return null;
}
- /**
- * Called when a entity tries to play the 'swing' animation.
- *
- * @param entity The entity swinging the item.
- * @return True to cancel any further processing by {@link LivingEntity}
- * @deprecated To be replaced with hand sensitive version in 21.2
- * @see #onEntitySwing(ItemStack, LivingEntity, InteractionHand)
- */
- @Deprecated(forRemoval = true, since = "21.1")
- default boolean onEntitySwing(ItemStack stack, LivingEntity entity) {
- return false;
- }
-
/**
* Called when a entity tries to play the 'swing' animation.
*
@@ -334,7 +321,7 @@ default boolean onEntitySwing(ItemStack stack, LivingEntity entity) {
* @return True to cancel any further processing by {@link LivingEntity}
*/
default boolean onEntitySwing(ItemStack stack, LivingEntity entity, InteractionHand hand) {
- return onEntitySwing(stack, entity);
+ return false;
}
/**
diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java
index 68a254a3dc..0b18ba462f 100644
--- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java
+++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java
@@ -195,19 +195,6 @@ default boolean canDisableShield(ItemStack shield, LivingEntity entity, LivingEn
return self().getItem().canDisableShield(self(), shield, entity, attacker);
}
- /**
- * Called when a entity tries to play the 'swing' animation.
- *
- * @param entity The entity swinging the item.
- * @return True to cancel any further processing by {@link LivingEntity}
- * @deprecated To be replaced with hand sensitive version in 21.2
- * @see #onEntitySwing(LivingEntity, InteractionHand)
- */
- @Deprecated(forRemoval = true, since = "21.1")
- default boolean onEntitySwing(LivingEntity entity) {
- return self().getItem().onEntitySwing(self(), entity);
- }
-
/**
* Called when a entity tries to play the 'swing' animation.
*
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java
index f2c551a66e..09cfde9e25 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java
@@ -9,6 +9,8 @@
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.common.crafting.CompoundIngredient;
import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs;
@@ -22,7 +24,7 @@
* @see CompoundIngredient CompoundIngredient, its item equivalent
*/
public final class CompoundFluidIngredient extends FluidIngredient {
- public static final MapCodec CODEC = NeoForgeExtraCodecs.aliasedFieldOf(FluidIngredient.LIST_CODEC_NON_EMPTY, "children", "ingredients").xmap(CompoundFluidIngredient::new, CompoundFluidIngredient::children);
+ public static final MapCodec CODEC = NeoForgeExtraCodecs.aliasedFieldOf(FluidIngredient.CODEC.listOf(1, Integer.MAX_VALUE), "children", "ingredients").xmap(CompoundFluidIngredient::new, CompoundFluidIngredient::children);
private final List children;
@@ -37,8 +39,6 @@ public CompoundFluidIngredient(List extends FluidIngredient> children) {
* Creates a compound ingredient from the given list of ingredients.
*/
public static FluidIngredient of(FluidIngredient... children) {
- if (children.length == 0)
- return FluidIngredient.empty();
if (children.length == 1)
return children[0];
@@ -49,21 +49,15 @@ public static FluidIngredient of(FluidIngredient... children) {
* Creates a compound ingredient from the given list of ingredients.
*/
public static FluidIngredient of(List children) {
- if (children.isEmpty())
- return FluidIngredient.empty();
if (children.size() == 1)
return children.getFirst();
return new CompoundFluidIngredient(children);
}
- public static FluidIngredient of(Stream stream) {
- return of(stream.toList());
- }
-
@Override
- public Stream generateStacks() {
- return children.stream().flatMap(FluidIngredient::generateStacks);
+ public Stream> generateFluids() {
+ return children.stream().flatMap(FluidIngredient::generateFluids);
}
@Override
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/CustomDisplayFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/CustomDisplayFluidIngredient.java
new file mode 100644
index 0000000000..b48090e710
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CustomDisplayFluidIngredient.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting;
+
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.util.Objects;
+import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.minecraft.world.level.material.Fluid;
+import net.neoforged.neoforge.common.NeoForgeMod;
+import net.neoforged.neoforge.fluids.FluidStack;
+
+/**
+ * FluidIngredient that wraps another fluid ingredient to override its {@link SlotDisplay}.
+ */
+public final class CustomDisplayFluidIngredient extends FluidIngredient {
+ public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(
+ instance -> instance
+ .group(
+ FluidIngredient.CODEC.fieldOf("base").forGetter(CustomDisplayFluidIngredient::base),
+ SlotDisplay.CODEC.fieldOf("display").forGetter(CustomDisplayFluidIngredient::display))
+ .apply(instance, CustomDisplayFluidIngredient::new));
+
+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite(
+ FluidIngredient.STREAM_CODEC,
+ CustomDisplayFluidIngredient::base,
+ SlotDisplay.STREAM_CODEC,
+ CustomDisplayFluidIngredient::display,
+ CustomDisplayFluidIngredient::new);
+
+ private final FluidIngredient base;
+ private final SlotDisplay display;
+
+ public CustomDisplayFluidIngredient(FluidIngredient base, SlotDisplay display) {
+ this.base = base;
+ this.display = display;
+ }
+
+ public static FluidIngredient of(FluidIngredient base, SlotDisplay display) {
+ return new CustomDisplayFluidIngredient(base, display);
+ }
+
+ @Override
+ public boolean test(FluidStack stack) {
+ return base.test(stack);
+ }
+
+ @Override
+ public Stream> generateFluids() {
+ return base.generateFluids();
+ }
+
+ @Override
+ public boolean isSimple() {
+ return base.isSimple();
+ }
+
+ @Override
+ public FluidIngredientType> getType() {
+ return NeoForgeMod.CUSTOM_DISPLAY_FLUID_INGREDIENT.get();
+ }
+
+ public FluidIngredient base() {
+ return base;
+ }
+
+ @Override
+ public SlotDisplay display() {
+ return display;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof CustomDisplayFluidIngredient other &&
+ Objects.equals(this.base, other.base) &&
+ Objects.equals(this.display, other.display);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(base, display);
+ }
+
+ @Override
+ public String toString() {
+ return "CustomDisplayFluidIngredient[" +
+ "base=" + base + ", " +
+ "display=" + display + ']';
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java
index c3ebdcdaf3..a4d48dec0d 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java
@@ -20,11 +20,13 @@
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.HolderSetCodec;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.common.crafting.DataComponentIngredient;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.fluids.FluidType;
+import net.neoforged.neoforge.fluids.crafting.display.FluidStackSlotDisplay;
/**
* Fluid ingredient that matches the given set of fluids, additionally performing either a
@@ -39,7 +41,7 @@ public class DataComponentFluidIngredient extends FluidIngredient {
public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(
builder -> builder
.group(
- HolderSetCodec.create(Registries.FLUID, BuiltInRegistries.FLUID.holderByNameCodec(), false).fieldOf("fluids").forGetter(DataComponentFluidIngredient::fluids),
+ HolderSetCodec.create(Registries.FLUID, BuiltInRegistries.FLUID.holderByNameCodec(), false).fieldOf("fluids").forGetter(DataComponentFluidIngredient::fluidSet),
DataComponentPredicate.CODEC.fieldOf("components").forGetter(DataComponentFluidIngredient::components),
Codec.BOOL.optionalFieldOf("strict", false).forGetter(DataComponentFluidIngredient::isStrict))
.apply(builder, DataComponentFluidIngredient::new));
@@ -70,8 +72,15 @@ public boolean test(FluidStack stack) {
}
}
- public Stream generateStacks() {
- return Stream.of(stacks);
+ public Stream> generateFluids() {
+ return fluids.stream();
+ }
+
+ @Override
+ public SlotDisplay display() {
+ return new SlotDisplay.Composite(Stream.of(stacks)
+ .map(stack -> (SlotDisplay) new FluidStackSlotDisplay(stack))
+ .toList());
}
@Override
@@ -98,7 +107,7 @@ public boolean equals(Object obj) {
&& other.strict == this.strict;
}
- public HolderSet fluids() {
+ public HolderSet fluidSet() {
return fluids;
}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java
index fa09f3b170..9463baccf9 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java
@@ -9,6 +9,8 @@
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Objects;
import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.common.crafting.DifferenceIngredient;
import net.neoforged.neoforge.fluids.FluidStack;
@@ -23,8 +25,8 @@ public final class DifferenceFluidIngredient extends FluidIngredient {
public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(
builder -> builder
.group(
- FluidIngredient.CODEC_NON_EMPTY.fieldOf("base").forGetter(DifferenceFluidIngredient::base),
- FluidIngredient.CODEC_NON_EMPTY.fieldOf("subtracted").forGetter(DifferenceFluidIngredient::subtracted))
+ FluidIngredient.CODEC.fieldOf("base").forGetter(DifferenceFluidIngredient::base),
+ FluidIngredient.CODEC.fieldOf("subtracted").forGetter(DifferenceFluidIngredient::subtracted))
.apply(builder, DifferenceFluidIngredient::new));
private final FluidIngredient base;
private final FluidIngredient subtracted;
@@ -35,8 +37,8 @@ public DifferenceFluidIngredient(FluidIngredient base, FluidIngredient subtracte
}
@Override
- public Stream generateStacks() {
- return base.generateStacks().filter(subtracted.negate());
+ public Stream> generateFluids() {
+ return base.fluids().stream().filter(e -> !subtracted().fluids().contains(e));
}
@Override
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java
deleted file mode 100644
index d8a958c7c8..0000000000
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) NeoForged and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.fluids.crafting;
-
-import com.mojang.serialization.MapCodec;
-import java.util.stream.Stream;
-import net.neoforged.neoforge.common.NeoForgeMod;
-import net.neoforged.neoforge.fluids.FluidStack;
-
-/**
- * Singleton that represents an empty fluid ingredient.
- *
- * This is the only instance of an explicitly empty ingredient,
- * and may be used as a fallback in FluidIngredient convenience methods
- * (such as when trying to create an ingredient from an empty list).
- *
- * @see FluidIngredient#empty()
- * @see FluidIngredient#isEmpty()
- */
-public class EmptyFluidIngredient extends FluidIngredient {
- public static final EmptyFluidIngredient INSTANCE = new EmptyFluidIngredient();
-
- public static final MapCodec CODEC = MapCodec.unit(INSTANCE);
-
- private EmptyFluidIngredient() {}
-
- @Override
- public boolean test(FluidStack fluidStack) {
- return fluidStack.isEmpty();
- }
-
- @Override
- protected Stream generateStacks() {
- return Stream.empty();
- }
-
- @Override
- public boolean isSimple() {
- return true;
- }
-
- @Override
- public FluidIngredientType> getType() {
- return NeoForgeMod.EMPTY_FLUID_INGREDIENT_TYPE.get();
- }
-
- @Override
- public int hashCode() {
- return 0;
- }
-
- @Override
- public boolean equals(Object obj) {
- return this == obj;
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java
index 49f41aeb0a..c76edc65d9 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java
@@ -5,34 +5,31 @@
package net.neoforged.neoforge.fluids.crafting;
-import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
-import com.mojang.serialization.DataResult;
-import com.mojang.serialization.MapCodec;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
-import net.minecraft.core.NonNullList;
+import net.minecraft.core.HolderSet;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
-import net.minecraft.tags.TagKey;
import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.crafting.ICustomIngredient;
-import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs;
import net.neoforged.neoforge.fluids.FluidStack;
-import net.neoforged.neoforge.fluids.FluidStackLinkedSet;
+import net.neoforged.neoforge.fluids.crafting.display.FluidSlotDisplay;
import net.neoforged.neoforge.registries.NeoForgeRegistries;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* This class serves as the fluid analogue of an item {@link Ingredient},
* that is, a representation of both a {@linkplain #test predicate} to test
- * {@link FluidStack}s against, and a {@linkplain #getStacks list} of matching stacks
+ * {@link FluidStack}s against, and a {@linkplain #fluids list} of matching stacks
* for e.g. display purposes.
*
* The most common use for fluid ingredients is found in modded recipe inputs,
@@ -42,99 +39,27 @@
* you may also want to take a look at {@link SizedFluidIngredient}!
*/
public abstract class FluidIngredient implements Predicate {
- /**
- * This is a codec that is used to represent basic "single fluid" or "tag"
- * fluid ingredients directly, similar to {@link Ingredient.Value#CODEC},
- * except not using value subclasses and instead directly providing
- * the corresponding {@link FluidIngredient}.
- */
- private static final MapCodec SINGLE_OR_TAG_CODEC = singleOrTagCodec();
-
- /**
- * This is a codec that represents a single {@code FluidIngredient} in map form;
- * either dispatched by type or falling back to {@link #SINGLE_OR_TAG_CODEC}
- * if no type is specified.
- *
- * @see Ingredient#MAP_CODEC_NONEMPTY
- */
- public static final MapCodec MAP_CODEC_NONEMPTY = makeMapCodec();
- private static final Codec MAP_CODEC_CODEC = MAP_CODEC_NONEMPTY.codec();
-
- public static final Codec> LIST_CODEC = MAP_CODEC_CODEC.listOf();
- public static final Codec> LIST_CODEC_NON_EMPTY = LIST_CODEC.validate(list -> {
- if (list.isEmpty()) {
- return DataResult.error(() -> "Fluid ingredient cannot be empty, at least one item must be defined");
- }
- return DataResult.success(list);
- });
-
- /**
- * Full codec representing a fluid ingredient in all possible forms.
- *
- * Allows for arrays of fluid ingredients to be read as a {@link CompoundFluidIngredient},
- * as well as for the {@code type} field to be left out in case of a single fluid or tag ingredient.
- *
- * @see #codec(boolean)
- * @see #MAP_CODEC_NONEMPTY
- */
- public static final Codec CODEC = codec(true);
- /**
- * Same as {@link #CODEC}, except not allowing for empty ingredients ({@code []})
- * to be specified.
- *
- * @see #codec(boolean)
- */
- public static final Codec CODEC_NON_EMPTY = codec(false);
+ public static final Codec CODEC = FluidIngredientCodecs.codec();
- public static final StreamCodec STREAM_CODEC = new StreamCodec<>() {
- private static final StreamCodec DISPATCH_CODEC = ByteBufCodecs.registry(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES)
- .dispatch(FluidIngredient::getType, FluidIngredientType::streamCodec);
+ public static final StreamCodec STREAM_CODEC = FluidIngredientCodecs.streamCodec();
- private static final StreamCodec> FLUID_LIST_CODEC = FluidStack.STREAM_CODEC.apply(
- ByteBufCodecs.collection(NonNullList::createWithCapacity));
-
- @Override
- public void encode(RegistryFriendlyByteBuf buf, FluidIngredient ingredient) {
- if (ingredient.isSimple()) {
- FLUID_LIST_CODEC.encode(buf, Arrays.asList(ingredient.getStacks()));
- } else {
- buf.writeVarInt(-1);
- DISPATCH_CODEC.encode(buf, ingredient);
- }
- }
-
- @Override
- public FluidIngredient decode(RegistryFriendlyByteBuf buf) {
- var size = buf.readVarInt();
- if (size == -1) {
- return DISPATCH_CODEC.decode(buf);
- }
-
- return CompoundFluidIngredient.of(
- Stream.generate(() -> FluidStack.STREAM_CODEC.decode(buf))
- .limit(size)
- .map(FluidIngredient::single));
- }
- };
+ public static final StreamCodec> OPTIONAL_STREAM_CODEC = ByteBufCodecs.optional(STREAM_CODEC);
@Nullable
- private FluidStack[] stacks;
+ private List> fluids;
/**
- * Returns an array of fluid stacks that this ingredient accepts.
- * The fluid stacks within the returned array must not be modified by the caller!
- * {@return an array of fluid stacks this ingredient accepts}
+ * {@return a cached list of all Fluid holders that this ingredient accepts}
+ * This list is immutable and thus can and should not be modified by the caller!
*
- * @see #generateStacks()
+ * @see #generateFluids()
*/
- public final FluidStack[] getStacks() {
- if (stacks == null) {
- stacks = generateStacks()
- .collect(Collectors.toCollection(FluidStackLinkedSet::createTypeAndComponentsSet))
- .toArray(FluidStack[]::new);
+ public final List> fluids() {
+ if (fluids == null) {
+ fluids = generateFluids().toList();
}
- return stacks;
+ return fluids;
}
/**
@@ -148,28 +73,48 @@ public final FluidStack[] getStacks() {
public abstract boolean test(FluidStack fluidStack);
/**
- * Generates a stream of all fluid stacks this ingredient matches against.
+ * {@return a stream of fluids accepted by this ingredient}
*
* For compatibility reasons, implementations should follow the same guidelines
* as for custom item ingredients, i.e.:
*
- * - These stacks are generally used for display purposes, and need not be exhaustive or perfectly accurate.
+ * - Returned fluids are generally used for display purposes, and need not be exhaustive or perfectly accurate,
+ * as ingredients may additionally filter by e.g. data component values.
* - An exception is ingredients that {@linkplain #isSimple() are simple},
- * for which it is important that the returned stacks correspond exactly to all the accepted {@link Fluid}s.
- * - At least one stack should always be returned, otherwise the ingredient may be considered {@linkplain #hasNoFluids() accidentally empty}.
- * - The ingredient should try to return at least one stack with each accepted {@link Fluid}.
- * This allows mods that inspect the ingredient to figure out which stacks it might accept.
+ * for which it is important that this stream corresponds exactly all fluids accepted by {@link #test(FluidStack)}!
+ * - At least one stack should always be returned, so that the ingredient is not considered empty. Empty ingredients may invalidate recipes!
*
*
+ * Note: no caching needs to be done by the implementation, this is already handled by {@link #fluids}!
+ *
* @return a stream of all fluid stacks this ingredient accepts.
*
* Note: No guarantees are made as to the amount of the fluid,
* as FluidIngredients are generally not meant to match by amount
* and these stacks are mostly used for display.
*
- * @see ICustomIngredient#stacks()
+ * @see ICustomIngredient#items()
*/
- protected abstract Stream generateStacks();
+ @ApiStatus.OverrideOnly
+ protected abstract Stream> generateFluids();
+
+ /**
+ * {@return a slot display for this ingredient, used for display on the client-side}
+ *
+ * @implNote The default implementation just constructs a list of stacks from {@link #fluids()}.
+ * This is generally suitable for {@link #isSimple() simple} ingredients.
+ * Non-simple ingredients can either override this method to provide a more customized display,
+ * or let data pack writers use {@link CustomDisplayFluidIngredient} to override the display of an ingredient.
+ *
+ * @see Ingredient#display()
+ * @see FluidSlotDisplay
+ */
+ public SlotDisplay display() {
+ return new SlotDisplay.Composite(fluids()
+ .stream()
+ .map(FluidIngredient::displayForSingleFluid)
+ .toList());
+ }
/**
* Returns whether this fluid ingredient always requires {@linkplain #test direct stack testing}.
@@ -186,47 +131,14 @@ public final FluidStack[] getStacks() {
*/
public abstract FluidIngredientType> getType();
- /**
- * Checks if this ingredient is explicitly empty, i.e.
- * equal to {@link EmptyFluidIngredient#INSTANCE}.
- * Note: This does not return true for "accidentally empty" ingredients,
- * including compound ingredients that are explicitly constructed with no children
- * or intersection / difference ingredients that resolve to an empty set.
- *
- * @return {@code true} if this ingredient is {@link #empty()}, {@code false} otherwise
- */
- public final boolean isEmpty() {
- return this == empty();
- }
-
- /**
- * Checks if this ingredient matches no fluids, i.e. if its
- * list of {@linkplain #getStacks() matching fluids} is empty.
- *
- * Note that this method explicitly resolves the ingredient;
- * if this is not desired, you will need to check for emptiness another way!
- *
- * @return {@code true} if this ingredient matches no fluids, {@code false} otherwise
- * @see #isEmpty()
- */
- public final boolean hasNoFluids() {
- return getStacks().length == 0;
- }
-
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
- // empty
- public static FluidIngredient empty() {
- return EmptyFluidIngredient.INSTANCE;
- }
-
- // convenience methods
- public static FluidIngredient of() {
- return empty();
+ public static SlotDisplay displayForSingleFluid(Holder holder) {
+ return new FluidSlotDisplay(holder);
}
public static FluidIngredient of(FluidStack... fluids) {
@@ -237,75 +149,11 @@ public static FluidIngredient of(Fluid... fluids) {
return of(Arrays.stream(fluids));
}
- private static FluidIngredient of(Stream fluids) {
- return CompoundFluidIngredient.of(fluids.map(FluidIngredient::single));
+ public static FluidIngredient of(Stream fluids) {
+ return of(HolderSet.direct(fluids.map(Fluid::builtInRegistryHolder).toList()));
}
- public static FluidIngredient single(FluidStack stack) {
- return single(stack.getFluid());
- }
-
- public static FluidIngredient single(Fluid fluid) {
- return single(fluid.builtInRegistryHolder());
- }
-
- public static FluidIngredient single(Holder holder) {
- return new SingleFluidIngredient(holder);
- }
-
- public static FluidIngredient tag(TagKey tag) {
- return new TagFluidIngredient(tag);
- }
-
- // codecs
- private static MapCodec singleOrTagCodec() {
- return NeoForgeExtraCodecs.xor(
- SingleFluidIngredient.CODEC,
- TagFluidIngredient.CODEC).xmap(either -> either.map(id -> id, id -> id), ingredient -> {
- if (ingredient instanceof SingleFluidIngredient fluid) {
- return Either.left(fluid);
- } else if (ingredient instanceof TagFluidIngredient tag) {
- return Either.right(tag);
- }
- throw new IllegalStateException("Basic fluid ingredient should be either a fluid or a tag!");
- });
- }
-
- private static MapCodec makeMapCodec() {
- return NeoForgeExtraCodecs., FluidIngredient, FluidIngredient>dispatchMapOrElse(
- NeoForgeRegistries.FLUID_INGREDIENT_TYPES.byNameCodec(),
- FluidIngredient::getType,
- FluidIngredientType::codec,
- FluidIngredient.SINGLE_OR_TAG_CODEC).xmap(either -> either.map(id -> id, id -> id), ingredient -> {
- // prefer serializing without a type field, if possible
- if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) {
- return Either.right(ingredient);
- }
-
- return Either.left(ingredient);
- }).validate(ingredient -> {
- if (ingredient.isEmpty()) {
- return DataResult.error(() -> "Cannot serialize empty fluid ingredient using the map codec");
- }
- return DataResult.success(ingredient);
- });
- }
-
- private static Codec codec(boolean allowEmpty) {
- var listCodec = Codec.lazyInitialized(() -> allowEmpty ? LIST_CODEC : LIST_CODEC_NON_EMPTY);
- return Codec.either(listCodec, MAP_CODEC_CODEC)
- // [{...}, {...}] is turned into a CompoundFluidIngredient instance
- .xmap(either -> either.map(CompoundFluidIngredient::of, i -> i),
- ingredient -> {
- // serialize CompoundFluidIngredient instances as an array over their children
- if (ingredient instanceof CompoundFluidIngredient compound) {
- return Either.left(compound.children());
- } else if (ingredient.isEmpty()) {
- // serialize empty ingredients as []
- return Either.left(List.of());
- }
-
- return Either.right(ingredient);
- });
+ public static FluidIngredient of(HolderSet fluids) {
+ return new SimpleFluidIngredient(fluids);
}
}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientCodecs.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientCodecs.java
new file mode 100644
index 0000000000..07f3590739
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientCodecs.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting;
+
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.DynamicOps;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.MapLike;
+import com.mojang.serialization.RecordBuilder;
+import java.util.stream.Stream;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.ByteBufCodecs;
+import net.minecraft.network.codec.StreamCodec;
+import net.neoforged.neoforge.registries.NeoForgeRegistries;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public class FluidIngredientCodecs {
+ static Codec codec() {
+ return Codec.xor(
+ NeoForgeRegistries.FLUID_INGREDIENT_TYPES.byNameCodec().dispatch("neoforge:ingredient_type", FluidIngredient::getType, FluidIngredientType::codec),
+ SimpleFluidIngredient.CODEC).xmap(either -> either.map(i -> i, i -> i), ingredient -> switch (ingredient) {
+ case SimpleFluidIngredient simple -> Either.right(simple);
+ default -> Either.left(ingredient);
+ });
+ }
+
+ static StreamCodec streamCodec() {
+ return ByteBufCodecs.registry(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES)
+ .dispatch(FluidIngredient::getType, FluidIngredientType::streamCodec);
+ }
+
+ public static FluidIngredientType simpleType() {
+ MapCodec erroringMapCodec = new MapCodec<>() {
+ @Override
+ public Stream keys(DynamicOps dynamicOps) {
+ return Stream.empty();
+ }
+
+ @Override
+ public DataResult decode(DynamicOps ops, MapLike mapLike) {
+ return DataResult.error(() -> "Simple fluid ingredients cannot be decoded using map syntax!");
+ }
+
+ @Override
+ public RecordBuilder encode(SimpleFluidIngredient ingredient, DynamicOps ops, RecordBuilder builder) {
+ return builder.withErrorsFrom(DataResult.error(() -> "Simple fluid ingredients cannot be encoded using map syntax! Please use vanilla syntax (namespaced:item or #tag) instead!"));
+ }
+ };
+
+ return new FluidIngredientType<>(erroringMapCodec, SimpleFluidIngredient.CONTENTS_STREAM_CODEC);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java
index d53df462eb..d8e1729b9a 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java
@@ -11,8 +11,11 @@
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.FluidType;
/**
* FluidIngredient that matches if all child ingredients match
@@ -28,7 +31,7 @@ public IntersectionFluidIngredient(List children) {
public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(
builder -> builder
.group(
- FluidIngredient.LIST_CODEC_NON_EMPTY.fieldOf("children").forGetter(IntersectionFluidIngredient::children))
+ FluidIngredient.CODEC.listOf(1, Integer.MAX_VALUE).fieldOf("children").forGetter(IntersectionFluidIngredient::children))
.apply(builder, IntersectionFluidIngredient::new));
private final List children;
@@ -58,10 +61,10 @@ public boolean test(FluidStack stack) {
}
@Override
- public Stream generateStacks() {
+ public Stream> generateFluids() {
return children.stream()
- .flatMap(FluidIngredient::generateStacks)
- .filter(this);
+ .flatMap(child -> child.fluids().stream())
+ .filter(fluid -> test(new FluidStack(fluid, FluidType.BUCKET_VOLUME)));
}
@Override
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SimpleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SimpleFluidIngredient.java
new file mode 100644
index 0000000000..bb1f6d0829
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SimpleFluidIngredient.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting;
+
+import com.mojang.serialization.Codec;
+import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.core.HolderSet;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.ByteBufCodecs;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.resources.HolderSetCodec;
+import net.minecraft.util.ExtraCodecs;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.Fluids;
+import net.neoforged.neoforge.common.NeoForgeMod;
+import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.crafting.display.FluidTagSlotDisplay;
+
+/**
+ * Fluid ingredient that matches the fluids specified by the given {@link HolderSet}.
+ * Most commonly, this will either be a list of fluids or a fluid tag.
+ *
+ * Unlike with ingredients, this is technically an explicit "type" of fluid ingredient,
+ * though in JSON, it is still written without a type field, see {@link FluidIngredientCodecs#codec()}
+ */
+public class SimpleFluidIngredient extends FluidIngredient {
+ private static final Codec> HOLDER_SET_NO_EMPTY_FLUID = HolderSetCodec.create(
+ Registries.FLUID, FluidStack.FLUID_NON_EMPTY_CODEC, false);
+
+ static final Codec CODEC = ExtraCodecs.nonEmptyHolderSet(HOLDER_SET_NO_EMPTY_FLUID)
+ .xmap(SimpleFluidIngredient::new, SimpleFluidIngredient::fluidSet);
+
+ static final StreamCodec CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.FLUID)
+ .map(SimpleFluidIngredient::new, SimpleFluidIngredient::fluidSet);
+
+ private final HolderSet values;
+
+ public SimpleFluidIngredient(HolderSet values) {
+ values.unwrap().ifRight(list -> {
+ if (list.isEmpty()) {
+ throw new UnsupportedOperationException("Fluid ingredients can't be empty!");
+ } else if (list.contains(Fluids.EMPTY.builtInRegistryHolder())) {
+ throw new UnsupportedOperationException("Fluid ingredients can't contain the empty fluid");
+ }
+ });
+ this.values = values;
+ }
+
+ @Override
+ public boolean test(FluidStack fluidStack) {
+ return values.contains(fluidStack.getFluidHolder());
+ }
+
+ @Override
+ protected Stream> generateFluids() {
+ return values.stream();
+ }
+
+ @Override
+ public boolean isSimple() {
+ return true;
+ }
+
+ @Override
+ public FluidIngredientType> getType() {
+ return NeoForgeMod.SIMPLE_FLUID_INGREDIENT_TYPE.get();
+ }
+
+ @Override
+ public SlotDisplay display() {
+ return values.unwrapKey()
+ .map(FluidTagSlotDisplay::new)
+ .orElseGet(super::display);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.fluidSet().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ return obj instanceof SimpleFluidIngredient other && other.fluidSet().equals(this.fluidSet());
+ }
+
+ public HolderSet fluidSet() {
+ return values;
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java
deleted file mode 100644
index e2bb3ae250..0000000000
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) NeoForged and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.fluids.crafting;
-
-import com.mojang.serialization.MapCodec;
-import java.util.stream.Stream;
-import net.minecraft.core.Holder;
-import net.minecraft.world.level.material.Fluid;
-import net.minecraft.world.level.material.Fluids;
-import net.neoforged.neoforge.common.NeoForgeMod;
-import net.neoforged.neoforge.fluids.FluidStack;
-import net.neoforged.neoforge.fluids.FluidType;
-
-/**
- * Fluid ingredient that only matches the fluid of the given stack.
- *
- * Unlike with ingredients, this is an explicit "type" of fluid ingredient,
- * though it may still be written without a type field, see {@link FluidIngredient#MAP_CODEC_NONEMPTY}
- */
-public class SingleFluidIngredient extends FluidIngredient {
- public static final MapCodec CODEC = FluidStack.FLUID_NON_EMPTY_CODEC
- .xmap(SingleFluidIngredient::new, SingleFluidIngredient::fluid).fieldOf("fluid");
-
- private final Holder fluid;
-
- public SingleFluidIngredient(Holder fluid) {
- if (fluid.is(Fluids.EMPTY.builtInRegistryHolder())) {
- throw new IllegalStateException("SingleFluidIngredient must not be constructed with minecraft:empty, use FluidIngredient.empty() instead!");
- }
- this.fluid = fluid;
- }
-
- @Override
- public boolean test(FluidStack fluidStack) {
- return fluidStack.is(fluid);
- }
-
- @Override
- protected Stream generateStacks() {
- return Stream.of(new FluidStack(fluid, FluidType.BUCKET_VOLUME));
- }
-
- @Override
- public boolean isSimple() {
- return true;
- }
-
- @Override
- public FluidIngredientType> getType() {
- return NeoForgeMod.SINGLE_FLUID_INGREDIENT_TYPE.get();
- }
-
- @Override
- public int hashCode() {
- return this.fluid().value().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- return obj instanceof SingleFluidIngredient other && other.fluid.is(this.fluid);
- }
-
- public Holder fluid() {
- return fluid;
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java
index daadd12439..a87c35c690 100644
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java
@@ -8,17 +8,14 @@
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Objects;
-import java.util.stream.Stream;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
-import net.minecraft.tags.TagKey;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.fluids.FluidType;
-import org.jetbrains.annotations.Nullable;
/**
* Standard implementation for a FluidIngredient with an amount.
@@ -31,55 +28,35 @@
*/
public final class SizedFluidIngredient {
/**
- * The "flat" codec for {@link SizedFluidIngredient}.
+ * The codec for {@link SizedFluidIngredient}.
*
- * The amount is serialized inline with the rest of the ingredient, for example:
+ *
With this codec, the amount is serialized separately from the ingredient itself, for example:
*
*
{@code
* {
- * "fluid": "minecraft:water",
- * "amount": 250
- * }
- * }
- *
- *
- *
- * Compound fluid ingredients are always serialized using the map codec, i.e.
- *
- *
{@code
- * {
- * "type": "neoforge:compound",
- * "ingredients": [
- * { "fluid": "minecraft:water" },
- * { "fluid": "minecraft:milk" }
- * ],
- * "amount": 500
+ * "ingredient": "minecraft:lava",
+ * "amount": 1000
* }
* }
*
*
- */
- public static final Codec FLAT_CODEC = RecordCodecBuilder.create(instance -> instance.group(
- FluidIngredient.MAP_CODEC_NONEMPTY.forGetter(SizedFluidIngredient::ingredient),
- NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount))
- .apply(instance, SizedFluidIngredient::new));
-
- /**
- * The "nested" codec for {@link SizedFluidIngredient}.
- *
- * With this codec, the amount is always serialized separately from the ingredient itself, for example:
+ * or for custom ingredients:
*
*
{@code
* {
* "ingredient": {
- * "fluid": "minecraft:lava"
+ * "neoforge:type": "neoforge:intersection",
+ * "children": [
+ * "#example:tag1",
+ * "#example:tag2"
+ * ],
* },
- * "amount": 1000
+ * "amount": 4711
* }
* }
*/
- public static final Codec NESTED_CODEC = RecordCodecBuilder.create(instance -> instance.group(
- FluidIngredient.CODEC_NON_EMPTY.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient),
+ public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ FluidIngredient.CODEC.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient),
NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount))
.apply(instance, SizedFluidIngredient::new));
@@ -94,26 +71,9 @@ public static SizedFluidIngredient of(Fluid fluid, int amount) {
return new SizedFluidIngredient(FluidIngredient.of(fluid), amount);
}
- /**
- * Helper method to create a simple sized ingredient that matches the given fluid stack
- */
- public static SizedFluidIngredient of(FluidStack stack) {
- return new SizedFluidIngredient(FluidIngredient.single(stack), stack.getAmount());
- }
-
- /**
- * Helper method to create a simple sized ingredient that matches fluids in a tag.
- */
- public static SizedFluidIngredient of(TagKey tag, int amount) {
- return new SizedFluidIngredient(FluidIngredient.tag(tag), amount);
- }
-
private final FluidIngredient ingredient;
private final int amount;
- @Nullable
- private FluidStack[] cachedStacks;
-
public SizedFluidIngredient(FluidIngredient ingredient, int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Size must be positive");
@@ -139,20 +99,6 @@ public boolean test(FluidStack stack) {
return ingredient.test(stack) && stack.getAmount() >= amount;
}
- /**
- * Returns a list of the stacks from this {@link #ingredient}, with an updated {@link #amount}.
- *
- * @implNote the array is cached and should not be modified, just like {@link FluidIngredient#getStacks()}}.
- */
- public FluidStack[] getFluids() {
- if (cachedStacks == null) {
- cachedStacks = Stream.of(ingredient.getStacks())
- .map(s -> s.copyWithAmount(amount))
- .toArray(FluidStack[]::new);
- }
- return cachedStacks;
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java
deleted file mode 100644
index 10bbebde2b..0000000000
--- a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) NeoForged and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.fluids.crafting;
-
-import com.mojang.serialization.MapCodec;
-import java.util.stream.Stream;
-import net.minecraft.core.HolderSet;
-import net.minecraft.core.registries.BuiltInRegistries;
-import net.minecraft.core.registries.Registries;
-import net.minecraft.tags.TagKey;
-import net.minecraft.world.level.material.Fluid;
-import net.neoforged.neoforge.common.NeoForgeMod;
-import net.neoforged.neoforge.fluids.FluidStack;
-import net.neoforged.neoforge.fluids.FluidType;
-
-/**
- * Fluid ingredient that matches all fluids within the given tag.
- *
- * Unlike with ingredients, this is an explicit "type" of fluid ingredient,
- * though it may still be written without a type field, see {@link FluidIngredient#MAP_CODEC_NONEMPTY}
- */
-public class TagFluidIngredient extends FluidIngredient {
- public static final MapCodec CODEC = TagKey.codec(Registries.FLUID)
- .xmap(TagFluidIngredient::new, TagFluidIngredient::tag).fieldOf("tag");
-
- private final TagKey tag;
-
- public TagFluidIngredient(TagKey tag) {
- this.tag = tag;
- }
-
- @Override
- public boolean test(FluidStack fluidStack) {
- return fluidStack.is(tag);
- }
-
- @Override
- protected Stream generateStacks() {
- return BuiltInRegistries.FLUID.get(tag)
- .stream()
- .flatMap(HolderSet::stream)
- .map(fluid -> new FluidStack(fluid, FluidType.BUCKET_VOLUME));
- }
-
- @Override
- public boolean isSimple() {
- return true;
- }
-
- @Override
- public FluidIngredientType> getType() {
- return NeoForgeMod.TAG_FLUID_INGREDIENT_TYPE.get();
- }
-
- @Override
- public int hashCode() {
- return tag.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- return obj instanceof TagFluidIngredient tag && tag.tag.equals(this.tag);
- }
-
- public TagKey tag() {
- return tag;
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidSlotDisplay.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidSlotDisplay.java
new file mode 100644
index 0000000000..bbb9de9816
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidSlotDisplay.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.util.stream.Stream;
+import net.minecraft.core.Holder;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.ByteBufCodecs;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.resources.RegistryFixedCodec;
+import net.minecraft.util.context.ContextMap;
+import net.minecraft.world.item.crafting.display.DisplayContentsFactory;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.minecraft.world.level.material.Fluid;
+import net.neoforged.neoforge.common.NeoForgeMod;
+
+/**
+ * Slot display for a single fluid holder.
+ *
+ * Note that information on amount and data of the displayed fluid stack depends on the provided factory!
+ *
+ * @param fluid The fluid to be displayed.
+ */
+public record FluidSlotDisplay(Holder fluid) implements SlotDisplay {
+ public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(RegistryFixedCodec.create(Registries.FLUID).fieldOf("fluid").forGetter(FluidSlotDisplay::fluid))
+ .apply(instance, FluidSlotDisplay::new));
+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite(
+ ByteBufCodecs.holderRegistry(Registries.FLUID), FluidSlotDisplay::fluid, FluidSlotDisplay::new);
+
+ @Override
+ public Type type() {
+ return NeoForgeMod.FLUID_SLOT_DISPLAY.get();
+ }
+
+ @Override
+ public Stream resolve(ContextMap context, DisplayContentsFactory factory) {
+ return switch (factory) {
+ case ForFluidStacks fluids -> Stream.of(fluids.forStack(fluid));
+ default -> Stream.empty();
+ };
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackContentsFactory.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackContentsFactory.java
new file mode 100644
index 0000000000..4f4537285b
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackContentsFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.neoforged.neoforge.fluids.FluidStack;
+
+/**
+ * Base fluid stack contents factory: directly returns the stacks.
+ *
+ * Fluid equivalent of {@link SlotDisplay.ItemStackContentsFactory}.
+ */
+public class FluidStackContentsFactory implements ForFluidStacks {
+ public static final FluidStackContentsFactory INSTANCE = new FluidStackContentsFactory();
+
+ @Override
+ public FluidStack forStack(FluidStack fluid) {
+ return fluid;
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackSlotDisplay.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackSlotDisplay.java
new file mode 100644
index 0000000000..7bc734b09f
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidStackSlotDisplay.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.util.stream.Stream;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.util.context.ContextMap;
+import net.minecraft.world.item.crafting.display.DisplayContentsFactory;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.neoforged.neoforge.common.NeoForgeMod;
+import net.neoforged.neoforge.fluids.FluidStack;
+
+/**
+ * Slot display for a given fluid stack, including fluid amount and data components.
+ *
+ * @param stack The fluid stack to be displayed.
+ */
+public record FluidStackSlotDisplay(FluidStack stack) implements SlotDisplay {
+ public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(FluidStack.CODEC.fieldOf("fluid").forGetter(FluidStackSlotDisplay::stack))
+ .apply(instance, FluidStackSlotDisplay::new));
+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite(
+ FluidStack.STREAM_CODEC, FluidStackSlotDisplay::stack, FluidStackSlotDisplay::new);
+
+ @Override
+ public SlotDisplay.Type type() {
+ return NeoForgeMod.FLUID_STACK_SLOT_DISPLAY.get();
+ }
+
+ @Override
+ public Stream resolve(ContextMap context, DisplayContentsFactory factory) {
+ return switch (factory) {
+ case ForFluidStacks fluids -> Stream.of(fluids.forStack(stack));
+ default -> Stream.empty();
+ };
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else {
+ return other instanceof FluidStackSlotDisplay fluidStackDisplay
+ && FluidStack.matches(this.stack, fluidStackDisplay.stack);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidTagSlotDisplay.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidTagSlotDisplay.java
new file mode 100644
index 0000000000..7e2f258661
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/FluidTagSlotDisplay.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.util.stream.Stream;
+import net.minecraft.core.HolderLookup;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.tags.TagKey;
+import net.minecraft.util.context.ContextMap;
+import net.minecraft.world.item.crafting.display.DisplayContentsFactory;
+import net.minecraft.world.item.crafting.display.SlotDisplay;
+import net.minecraft.world.item.crafting.display.SlotDisplayContext;
+import net.minecraft.world.level.material.Fluid;
+import net.neoforged.neoforge.common.NeoForgeMod;
+
+/**
+ * Slot display that shows all fluids in a given tag.
+ *
+ * Note that information on amount and data of the displayed fluid stacks depends on the provided factory!
+ *
+ * @param tag The tag to be displayed.
+ */
+public record FluidTagSlotDisplay(TagKey tag) implements SlotDisplay {
+ public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(
+ p_379704_ -> p_379704_.group(TagKey.codec(Registries.FLUID).fieldOf("tag").forGetter(FluidTagSlotDisplay::tag))
+ .apply(p_379704_, FluidTagSlotDisplay::new));
+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite(
+ TagKey.streamCodec(Registries.FLUID), FluidTagSlotDisplay::tag, FluidTagSlotDisplay::new);
+
+ @Override
+ public SlotDisplay.Type type() {
+ return NeoForgeMod.FLUID_TAG_SLOT_DISPLAY.get();
+ }
+
+ @Override
+ public Stream resolve(ContextMap context, DisplayContentsFactory factory) {
+ if (factory instanceof ForFluidStacks fluids) {
+ HolderLookup.Provider registries = context.getOptional(SlotDisplayContext.REGISTRIES);
+ if (registries != null) {
+ return registries.lookupOrThrow(Registries.FLUID)
+ .get(this.tag)
+ .map(p_380858_ -> p_380858_.stream().map(fluids::forStack))
+ .stream()
+ .flatMap(p_380859_ -> p_380859_);
+ }
+ }
+
+ return Stream.empty();
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/ForFluidStacks.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/ForFluidStacks.java
new file mode 100644
index 0000000000..7a625d9d82
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/ForFluidStacks.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import net.minecraft.core.Holder;
+import net.minecraft.world.item.crafting.display.DisplayContentsFactory;
+import net.minecraft.world.level.material.Fluid;
+import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.FluidType;
+
+public interface ForFluidStacks extends DisplayContentsFactory {
+ /**
+ * {@return display data for the given fluid holder}
+ *
+ * @param fluid Fluid holder to display.
+ */
+ default T forStack(Holder fluid) {
+ return this.forStack(new FluidStack(fluid, FluidType.BUCKET_VOLUME));
+ }
+
+ /**
+ * {@return display data for the given fluid}
+ *
+ * @param fluid Fluid to display.
+ */
+ default T forStack(Fluid fluid) {
+ return this.forStack(fluid.builtInRegistryHolder());
+ }
+
+ /**
+ * {@return display data for the given fluid stack}
+ *
+ * @param fluid Fluid stack to display
+ */
+ T forStack(FluidStack fluid);
+}
diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/display/package-info.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/package-info.java
new file mode 100644
index 0000000000..2a27349909
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/display/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+@FieldsAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+@ParametersAreNonnullByDefault
+package net.neoforged.neoforge.fluids.crafting.display;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import net.minecraft.FieldsAreNonnullByDefault;
+import net.minecraft.MethodsReturnNonnullByDefault;
diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java
index 531b3ca0d2..efd17ee853 100644
--- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java
+++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java
@@ -151,10 +151,7 @@ public static void handle(AdvancedContainerSetDataPayload msg, IPayloadContext c
public static void handle(final ClientboundCustomSetTimePayload payload, final IPayloadContext context) {
@SuppressWarnings("resource")
final ClientLevel level = Minecraft.getInstance().level;
- level.getLevelData().setGameTime(payload.gameTime());
- level.getLevelData().setDayTime(payload.dayTime());
- // TODO porting: check whether the custom time system relies on this
- //level.getGameRules().getRule(GameRules.RULE_DAYLIGHT).set(payload.gameRule(), null);
+ level.setTimeFromServer(payload.gameTime(), payload.dayTime(), payload.gameRule());
level.setDayTimeFraction(payload.dayTimeFraction());
level.setDayTimePerTick(payload.dayTimePerTick());
}
diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks.java
index feacdbd5ba..b84ce5b79e 100644
--- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks.java
+++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks.java
@@ -48,12 +48,10 @@ public void onClear(Registry registry, boolean full) {
public void onBake(Registry registry) {
// Vanilla does this in Blocks.
- // Init cache and loot table for new blocks only (the cache init is expensive).
+ // Init cache for new blocks only (the cache init is expensive).
// State cache init cannot be done in onAdd because some of it might depend on other registries being populated in mod code.
- // Loot table init cannot be done in onAdd because the loot table supplier might depend on blocks registered later.
for (Block block : addedBlocks) {
block.getStateDefinition().getPossibleStates().forEach(BlockBehaviour.BlockStateBase::initCache);
- block.getLootTable();
}
addedBlocks.clear();
diff --git a/tests/src/junit/java/net/neoforged/neoforge/unittest/FluidIngredientTests.java b/tests/src/junit/java/net/neoforged/neoforge/unittest/FluidIngredientTests.java
new file mode 100644
index 0000000000..ab8d78ab50
--- /dev/null
+++ b/tests/src/junit/java/net/neoforged/neoforge/unittest/FluidIngredientTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.unittest;
+
+import java.util.List;
+import java.util.stream.Stream;
+import net.minecraft.core.component.DataComponentPatch;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.item.Rarity;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.level.material.Fluids;
+import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.crafting.CompoundFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.DataComponentFluidIngredient;
+import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
+import net.neoforged.testframework.junit.EphemeralTestServerProvider;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+@ExtendWith(EphemeralTestServerProvider.class)
+public class FluidIngredientTests {
+ @Test
+ void emptyIngredientFails(MinecraftServer server) {
+ Assertions.assertThatThrownBy(() -> FluidIngredient.of(Stream.empty()))
+ .withFailMessage("Empty fluid ingredient should not have been able to be constructed!")
+ .isInstanceOf(UnsupportedOperationException.class);
+ Assertions.assertThatThrownBy(() -> FluidIngredient.of(Fluids.WATER, Fluids.LAVA, Fluids.EMPTY))
+ .withFailMessage("SimpleFluidIngredient should not have been able to be constructed with empty fluid!")
+ .isInstanceOf(UnsupportedOperationException.class);
+
+ Assertions.assertThatThrownBy(() -> new CompoundFluidIngredient(List.of()))
+ .withFailMessage("Empty compound fluid ingredient should not have been able to be constructed!")
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @ParameterizedTest
+ @CsvSource({ "false", "true" })
+ void fluidIngredientComponentMatchingWorks(boolean strict, MinecraftServer server) {
+ var ingredient = DataComponentFluidIngredient.of(strict, DataComponents.RARITY, Rarity.EPIC, Fluids.WATER);
+ var stack = new FluidStack(Fluids.WATER, 1000);
+
+ Assertions.assertThat(ingredient.test(stack))
+ .withFailMessage("Fluid without custom data should not match DataComponentFluidIngredient!")
+ .isFalse();
+
+ stack.applyComponents(DataComponentPatch.builder()
+ .set(DataComponents.RARITY, Rarity.UNCOMMON)
+ .build());
+
+ Assertions.assertThat(ingredient.test(stack))
+ .withFailMessage("Fluid with incorrect data should not match DataComponentFluidIngredient!")
+ .isFalse();
+
+ stack.applyComponents(DataComponentPatch.builder()
+ .set(DataComponents.RARITY, Rarity.EPIC)
+ .build());
+
+ Assertions.assertThat(ingredient.test(stack))
+ .withFailMessage("Fluid with correct data should match DataComponentFluidIngredient!")
+ .isTrue();
+
+ var data = CustomData.EMPTY.update(tag -> tag.putFloat("abcd", 0.5F));
+ stack.set(DataComponents.CUSTOM_DATA, data);
+
+ Assertions.assertThat(ingredient.test(stack))
+ .withFailMessage("Strictness check failed for DataComponentFluidIngredient with strict: " + strict)
+ .isEqualTo(!strict);
+ }
+
+ void singleFluidIngredientIgnoresSizeAndData(MinecraftServer server) {
+ var ingredient = FluidIngredient.of(Fluids.WATER);
+
+ Assertions.assertThat(ingredient.test(new FluidStack(Fluids.WATER, 1234)))
+ .withFailMessage("Single fluid ingredient should match regardless of fluid amount!")
+ .isTrue();
+
+ Assertions.assertThat(ingredient.test(new FluidStack(Fluids.WATER.builtInRegistryHolder(), 1234, DataComponentPatch.builder().set(DataComponents.RARITY, Rarity.COMMON).build())))
+ .withFailMessage("Single fluid ingredient should match regardless of fluid data!")
+ .isTrue();
+ }
+}
diff --git a/tests/src/junit/java/net/neoforged/neoforge/unittest/IngredientTests.java b/tests/src/junit/java/net/neoforged/neoforge/unittest/IngredientTests.java
index fbe3bc9794..164ab1a686 100644
--- a/tests/src/junit/java/net/neoforged/neoforge/unittest/IngredientTests.java
+++ b/tests/src/junit/java/net/neoforged/neoforge/unittest/IngredientTests.java
@@ -7,6 +7,7 @@
import java.util.List;
import java.util.stream.Stream;
+import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.MinecraftServer;
@@ -30,12 +31,16 @@
@ExtendWith(EphemeralTestServerProvider.class)
public class IngredientTests {
+ private static List ingredientItemsAsStacks(Ingredient ingredient) {
+ return ingredient.items().stream().map(i -> i.value().getDefaultInstance()).toList();
+ }
+
@ParameterizedTest
@MethodSource("provideIngredientMatrix")
void testCompoundIngredient(Ingredient a, Ingredient b) {
final var ingredient = CompoundIngredient.of(a, b);
- Assertions.assertThat(a.stacks()).allMatch(ingredient, "first ingredient");
- Assertions.assertThat(b.stacks()).allMatch(ingredient, "second ingredient");
+ Assertions.assertThat(ingredientItemsAsStacks(a)).allMatch(ingredient, "first ingredient");
+ Assertions.assertThat(ingredientItemsAsStacks(b)).allMatch(ingredient, "second ingredient");
}
@Test
@@ -44,9 +49,9 @@ void testDifferenceIngredients(MinecraftServer server) {
final var acacia = Ingredient.of(Items.ACACIA_LOG);
final var ingredient = DifferenceIngredient.of(logs, acacia);
- Assertions.assertThat(logs.stacks())
- .filteredOn(i -> !acacia.test(i))
- .containsExactlyInAnyOrder(ingredient.stacks().toArray(ItemStack[]::new));
+ Assertions.assertThat(logs.items())
+ .filteredOn(i -> !acacia.test(i.value().getDefaultInstance()))
+ .containsExactlyInAnyOrder(ingredient.items().toArray(Holder[]::new));
}
@Test
@@ -54,7 +59,7 @@ void testIntersectionIngredient(MinecraftServer server) {
final var second = Ingredient.of(Items.BIRCH_LOG, Items.SPRUCE_LOG, Items.DISPENSER);
final var ingredient = IntersectionIngredient.of(Ingredient.of(server.registryAccess().lookupOrThrow(Registries.ITEM).getOrThrow(ItemTags.LOGS)), second);
- Assertions.assertThat(ingredient.stacks().stream().map(ItemStack::getItem).distinct())
+ Assertions.assertThat(ingredient.items().stream().map(Holder::value).distinct())
.containsExactlyInAnyOrder(Items.BIRCH_LOG, Items.SPRUCE_LOG);
}
diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java
index 974ed9377e..915c2d82b4 100644
--- a/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java
+++ b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java
@@ -5,215 +5,68 @@
package net.neoforged.neoforge.debug.fluid.crafting;
-import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
-import java.util.List;
-import net.minecraft.core.component.DataComponentMap;
-import net.minecraft.core.component.DataComponentPatch;
-import net.minecraft.core.component.DataComponents;
+import net.minecraft.Util;
+import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
-import net.minecraft.world.item.Rarity;
-import net.minecraft.world.item.component.CustomData;
-import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
+import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.common.Tags;
import net.neoforged.neoforge.fluids.FluidStack;
-import net.neoforged.neoforge.fluids.crafting.CompoundFluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.DataComponentFluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.DifferenceFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
-import net.neoforged.neoforge.fluids.crafting.IntersectionFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.SizedFluidIngredient;
import net.neoforged.testframework.annotation.ForEachTest;
import net.neoforged.testframework.annotation.TestHolder;
import net.neoforged.testframework.gametest.EmptyTemplate;
import net.neoforged.testframework.gametest.ExtendedGameTestHelper;
+// TODO(max): move rest of these to unit tests!
@ForEachTest(groups = { "fluid.crafting", "crafting.ingredient" })
public class FluidIngredientTests {
// serialization tests
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Serialization tests for empty fluid ingredients")
- static void emptyFluidIngredientSerialization(ExtendedGameTestHelper helper) {
- // analogous to IngredientTests
- // Make sure that empty ingredients serialize to []
- var emptyResult = FluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, FluidIngredient.empty());
- var emptyJson = emptyResult.resultOrPartial(error -> helper.fail("Failed to serialize empty fluid ingredient: " + error)).orElseThrow();
- helper.assertValueEqual("[]", emptyJson.toString(), "empty fluid ingredient");
-
- // Make sure that [] deserializes to an empty ingredient
- var result = FluidIngredient.CODEC.parse(JsonOps.INSTANCE, new JsonArray());
- var ingredient = result.resultOrPartial(error -> helper.fail("Failed to deserialize empty fluid ingredient: " + error)).orElseThrow();
- helper.assertTrue(ingredient.isEmpty(), "empty fluid ingredient should return true from isEmpty()");
- helper.assertTrue(ingredient.hasNoFluids(), "empty fluid ingredient should return true from hasNoFluids()");
- helper.assertValueEqual(FluidIngredient.empty(), ingredient, "empty fluid ingredient");
- helper.assertTrue(FluidIngredient.empty() == ingredient, "Reference equality with FluidIngredient.empty()");
-
- helper.succeed();
- }
-
@GameTest
@EmptyTemplate
@TestHolder(description = "Serialization tests for single fluid and tag ingredients")
static void basicFluidIngredientSerialization(ExtendedGameTestHelper helper) {
+ var registryAccess = helper.getLevel().registryAccess();
+ var ops = registryAccess.createSerializationContext(JsonOps.INSTANCE);
+
var singleFluid = FluidIngredient.of(Fluids.WATER);
- var tagFluid = FluidIngredient.tag(Tags.Fluids.WATER);
+ var tagFluid = FluidIngredient.of(BuiltInRegistries.FLUID.getOrThrow(Tags.Fluids.WATER));
// tests that the jsons for single and tag fluids do not contain a "type" field
- var singleResult = FluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, singleFluid);
+ var singleResult = FluidIngredient.CODEC.encodeStart(ops, singleFluid);
var singleJson = singleResult.resultOrPartial(error -> helper.fail("Failed to serialize single fluid ingredient: " + error)).orElseThrow();
- var tagResult = FluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, tagFluid);
+ var tagResult = FluidIngredient.CODEC.encodeStart(ops, tagFluid);
var tagJson = tagResult.resultOrPartial(error -> helper.fail("Failed to serialize tag fluid ingredient: " + error)).orElseThrow();
- helper.assertFalse(singleJson.getAsJsonObject().has("type"), "single fluid ingredient should serialize without a 'type' field");
- helper.assertFalse(tagJson.getAsJsonObject().has("type"), "tag fluid ingredient should serialize without a 'type' field");
+ helper.assertFalse(singleJson.isJsonObject(), "single fluid ingredient should not serialize as nested object!");
+ helper.assertFalse(tagJson.isJsonObject(), "tag fluid ingredient should not serialize as nested object!");
- helper.assertValueEqual(singleJson.toString(), "{\"fluid\":\"minecraft:water\"}", "serialized single fluid ingredient to match expected format!");
- helper.assertValueEqual(tagJson.toString(), "{\"tag\":\"c:water\"}", "serialized tag fluid ingredient to match expected format!");
+ helper.assertValueEqual(singleJson.getAsString(), Fluids.WATER.builtInRegistryHolder().getRegisteredName(), "serialized single fluid ingredient to match HolderSet element format!");
+ helper.assertValueEqual(tagJson.getAsString(), "#" + Tags.Fluids.WATER.location(), "serialized tag fluid ingredient to match HolderSet tag format!");
// tests that deserializing simple ingredients is reproducible and produces the desired ingredients
- var singleTwo = FluidIngredient.CODEC.parse(JsonOps.INSTANCE, singleJson)
+ var singleTwo = FluidIngredient.CODEC.parse(ops, singleJson)
.resultOrPartial(error -> helper.fail("Failed to deserialize single fluid ingredient from JSON: " + error))
.orElseThrow();
helper.assertValueEqual(singleFluid, singleTwo, "single fluid ingredient to be the same after being serialized and deserialized!");
- var tagTwo = FluidIngredient.CODEC.parse(JsonOps.INSTANCE, tagJson)
+ var tagTwo = FluidIngredient.CODEC.parse(ops, tagJson)
.resultOrPartial(error -> helper.fail("Failed to deserialize single fluid ingredient from JSON: " + error))
.orElseThrow();
helper.assertValueEqual(tagFluid, tagTwo, "tag fluid ingredient to be the same after being serialized and deserialized!");
- helper.succeed();
- }
-
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Tests that custom ingredients are not empty")
- static void testFluidIngredientEmptiness(final GameTestHelper helper) {
- FluidIngredient compoundIngredient = CompoundFluidIngredient.of(FluidIngredient.of(Fluids.WATER), FluidIngredient.tag(Tags.Fluids.LAVA));
- helper.assertFalse(compoundIngredient.isEmpty(), "CompoundFluidIngredient should not be empty");
- FluidIngredient dataComponentIngredient = DataComponentFluidIngredient.of(false, DataComponentMap.EMPTY, Fluids.WATER);
- helper.assertFalse(dataComponentIngredient.isEmpty(), "DataComponentFluidIngredient should not be empty");
- FluidIngredient differenceIngredient = DifferenceFluidIngredient.of(FluidIngredient.tag(Tags.Fluids.MILK), FluidIngredient.of(Fluids.LAVA));
- helper.assertFalse(differenceIngredient.isEmpty(), "DifferenceFluidIngredient should not be empty");
- FluidIngredient intersectionIngredient = IntersectionFluidIngredient.of(FluidIngredient.of(Fluids.WATER, Fluids.LAVA), FluidIngredient.of(Fluids.WATER));
- helper.assertFalse(intersectionIngredient.isEmpty(), "IntersectionFluidIngredient should not be empty");
- FluidIngredient emptyIngredient = FluidIngredient.empty();
- helper.assertTrue(emptyIngredient.isEmpty(), "FluidIngredient.empty() should be empty!");
-
- helper.succeed();
- }
-
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Tests that custom ingredients correctly report hasNoFluids")
- static void customFluidIngredientsHasNoFluids(final GameTestHelper helper) {
- // these can all end up accidentally empty one way or another
- FluidIngredient dataComponentIngredient = DataComponentFluidIngredient.of(false, DataComponentMap.EMPTY, new Fluid[0]);
- helper.assertFalse(dataComponentIngredient.isEmpty(), "DataComponentFluidIngredient instance should not be empty");
- helper.assertTrue(dataComponentIngredient.hasNoFluids(), "DataComponentFluidIngredient with no matching fluids should return true on hasNoFluids()");
- FluidIngredient differenceIngredient = DifferenceFluidIngredient.of(FluidIngredient.tag(Tags.Fluids.WATER), FluidIngredient.tag(Tags.Fluids.WATER));
- helper.assertFalse(differenceIngredient.isEmpty(), "DifferenceFluidIngredient instance should not be empty");
- helper.assertTrue(differenceIngredient.hasNoFluids(), "DifferenceFluidIngredient with empty difference should return true on hasNoFluids()");
- FluidIngredient intersectionIngredient = IntersectionFluidIngredient.of(FluidIngredient.of(Fluids.LAVA), FluidIngredient.of(Fluids.WATER));
- helper.assertFalse(intersectionIngredient.isEmpty(), "IntersectionFluidIngredient instance should not be empty");
- helper.assertTrue(intersectionIngredient.hasNoFluids(), "IntersectionFluidIngredient with empty intersection should return true on hasNoFluids()");
-
- // these classes have checks in place to make sure they aren't populated with empty values
- var emptyCompoundFailed = false;
- try {
- FluidIngredient compoundIngredient = new CompoundFluidIngredient(List.of());
- } catch (Exception ignored) {
- emptyCompoundFailed = true;
- }
- helper.assertTrue(emptyCompoundFailed, "Empty CompoundFluidIngredient should not have been able to be constructed!");
-
- var emptySingleFailed = false;
- try {
- FluidIngredient compoundIngredient = FluidIngredient.single(Fluids.EMPTY);
- } catch (Exception ignored) {
- emptySingleFailed = true;
- }
- helper.assertTrue(emptySingleFailed, "Empty SingleFluidIngredient should not have been able to be constructed!");
-
- helper.assertValueEqual(CompoundFluidIngredient.of(new FluidIngredient[0]), FluidIngredient.empty(), "calling CompoundFluidIngredient.of with no children to yield FluidIngredient.empty()");
-
- helper.succeed();
- }
-
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Tests that partial data matches work correctly on fluid ingredients")
- static void fluidIngredientDataPartialMatchWorks(final GameTestHelper helper) {
- var ingredient = DataComponentFluidIngredient.of(false, DataComponents.RARITY, Rarity.EPIC, Fluids.WATER);
- var stack = new FluidStack(Fluids.WATER, 1000);
-
- helper.assertFalse(ingredient.test(stack), "Fluid without custom data should not match DataComponentFluidIngredient!");
-
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.RARITY, Rarity.UNCOMMON)
- .build());
-
- helper.assertFalse(ingredient.test(stack), "Fluid with incorrect data should not match DataComponentFluidIngredient!");
-
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.RARITY, Rarity.EPIC)
- .build());
-
- helper.assertTrue(ingredient.test(stack), "Fluid with correct data should match DataComponentFluidIngredient!");
-
- var data = CustomData.EMPTY.update(tag -> tag.putFloat("abcd", helper.getLevel().random.nextFloat()));
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.CUSTOM_DATA, data)
- .build());
-
- helper.assertTrue(ingredient.test(stack), "Fluid with correct data should match partial DataComponentFluidIngredient regardless of extra data!");
-
- helper.succeed();
- }
-
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Tests that strict data matches work correctly on fluid ingredients")
- static void fluidIngredientDataStrictMatchWorks(final GameTestHelper helper) {
- var ingredient = DataComponentFluidIngredient.of(true, DataComponents.RARITY, Rarity.EPIC, Fluids.WATER);
- var stack = new FluidStack(Fluids.WATER, 1000);
-
- helper.assertFalse(ingredient.test(stack), "Fluid without custom data should not match DataComponentFluidIngredient!");
+ var nestedSimpleFailed = FluidIngredient.CODEC.parse(ops, Util.make(new JsonObject(), json1 -> {
+ json1.addProperty("neoforge:ingredient_type", NeoForgeMod.SIMPLE_FLUID_INGREDIENT_TYPE.getId().toString());
+ json1.addProperty("fluid", "minecraft:water");
+ })).isError();
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.RARITY, Rarity.UNCOMMON)
- .build());
-
- helper.assertFalse(ingredient.test(stack), "Fluid with incorrect data should not match DataComponentFluidIngredient!");
-
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.RARITY, Rarity.EPIC)
- .build());
-
- helper.assertTrue(ingredient.test(stack), "Fluid with correct data should match DataComponentFluidIngredient!");
-
- var data = CustomData.EMPTY.update(tag -> tag.putFloat("abcd", helper.getLevel().random.nextFloat()));
- stack.applyComponents(DataComponentPatch.builder()
- .set(DataComponents.CUSTOM_DATA, data)
- .build());
-
- helper.assertFalse(ingredient.test(stack), "Fluid with extra unspecified data should not match strict DataComponentFluidIngredient!");
-
- helper.succeed();
- }
-
- @GameTest
- @EmptyTemplate
- @TestHolder(description = "Tests that size and data components do not matter when matching fluid ingredients")
- static void singleFluidIngredientIgnoresSizeAndData(final GameTestHelper helper) {
- var ingredient = FluidIngredient.of(Fluids.WATER);
-
- helper.assertTrue(ingredient.test(new FluidStack(Fluids.WATER, 1234)), "Single fluid ingredient should match regardless of fluid amount!");
- helper.assertTrue(ingredient.test(new FluidStack(Fluids.WATER.builtInRegistryHolder(), 1234, DataComponentPatch.builder().set(DataComponents.RARITY, Rarity.COMMON).build())), "Single fluid ingredient should match regardless of fluid data!");
+ helper.assertTrue(nestedSimpleFailed, "Nested SimpleFluidIngredient should not have been deserialized from map!");
helper.succeed();
}
@@ -224,15 +77,10 @@ static void singleFluidIngredientIgnoresSizeAndData(final GameTestHelper helper)
static void sizedFluidIngredientSerialization(final GameTestHelper helper) {
var sized = SizedFluidIngredient.of(Fluids.WATER, 1000);
- var flatResult = SizedFluidIngredient.FLAT_CODEC.encodeStart(JsonOps.INSTANCE, sized);
- var flatJson = flatResult.resultOrPartial((error) -> helper.fail("(flat) Error while encoding SizedFluidIngredient: " + error)).orElseThrow();
-
- helper.assertValueEqual(flatJson.toString(), "{\"fluid\":\"minecraft:water\",\"amount\":1000}", "(flat) serialized SizedFluidIngredient");
-
- var nestedResult = SizedFluidIngredient.NESTED_CODEC.encodeStart(JsonOps.INSTANCE, sized);
- var nestedJson = nestedResult.resultOrPartial((error) -> helper.fail("(nested) Error while encoding SizedFluidIngredient: " + error)).orElseThrow();
+ var nestedResult = SizedFluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, sized);
+ var nestedJson = nestedResult.resultOrPartial((error) -> helper.fail("Error while encoding SizedFluidIngredient: " + error)).orElseThrow();
- helper.assertValueEqual(nestedJson.toString(), "{\"ingredient\":{\"fluid\":\"minecraft:water\"},\"amount\":1000}", "(nested) serialized SizedFluidIngredient");
+ helper.assertValueEqual(nestedJson.toString(), "{\"ingredient\":\"minecraft:water\",\"amount\":1000}", "(nested) serialized SizedFluidIngredient");
helper.succeed();
}
@@ -249,8 +97,9 @@ static void sizedFluidIngredientMatching(final GameTestHelper helper) {
helper.assertTrue(sized.test(new FluidStack(Fluids.WATER, 2)), "SizedFluidIngredient should match fluid with required amount!");
helper.assertTrue(sized.test(new FluidStack(Fluids.WATER, 3)), "SizedFluidIngredient should match fluid with more than required amount!");
- var matches = sized.getFluids();
- helper.assertTrue(matches.length == 1 && (FluidStack.matches(matches[0], new FluidStack(Fluids.WATER, 2))), "SizedFluidIngredient matches should return all matched fluids with the correct amount!");
+ // TODO(max): implement display wrapping for sized ingredients(?)
+ //var matches = sized.getFluids();
+ //helper.assertTrue(matches.length == 1 && (FluidStack.matches(matches[0], new FluidStack(Fluids.WATER, 2))), "SizedFluidIngredient matches should return all matched fluids with the correct amount!");
helper.succeed();
}
diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomTooltipTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomTooltipTest.java
index 1057944878..cb83eda472 100644
--- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomTooltipTest.java
+++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomTooltipTest.java
@@ -18,6 +18,7 @@
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
@@ -115,6 +116,8 @@ public static void onRegisterClientTooltipComponentFactories(RegisterClientToolt
}
private static class ClientEventHandler {
+ private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ID, "test");
+
@SubscribeEvent
public static void gatherTooltips(RenderTooltipEvent.GatherComponents event) {
if (event.getItemStack().getItem() == Items.STICK) {
@@ -126,12 +129,9 @@ public static void gatherTooltips(RenderTooltipEvent.GatherComponents event) {
}
@SubscribeEvent
- public static void preTooltip(RenderTooltipEvent.Color event) {
+ public static void preTooltip(RenderTooltipEvent.Texture event) {
if (event.getItemStack().getItem() == Items.APPLE) {
- event.setBackgroundStart(0xFF0000FF);
- event.setBackgroundEnd(0xFFFFFF00);
- event.setBorderStart(0xFFFF0000);
- event.setBorderEnd(0xFF000011);
+ event.setTexture(TEXTURE);
}
}
}
diff --git a/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png
new file mode 100644
index 0000000000..1f5f803bf5
Binary files /dev/null and b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png differ
diff --git a/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png.mcmeta b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png.mcmeta
new file mode 100644
index 0000000000..c0435eb685
--- /dev/null
+++ b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_background.png.mcmeta
@@ -0,0 +1,11 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "nine_slice",
+ "width": 100,
+ "height": 100,
+ "border": 9,
+ "stretch_inner": true
+ }
+ }
+}
diff --git a/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png
new file mode 100644
index 0000000000..34c5e37f1a
Binary files /dev/null and b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png differ
diff --git a/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png.mcmeta b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png.mcmeta
new file mode 100644
index 0000000000..e107d241a7
--- /dev/null
+++ b/tests/src/main/resources/assets/custom_tooltip_test/textures/gui/sprites/tooltip/test_frame.png.mcmeta
@@ -0,0 +1,11 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "nine_slice",
+ "width": 100,
+ "height": 100,
+ "border": 10,
+ "stretch_inner": true
+ }
+ }
+}