From 3aa6cd558557718fdfb45a09b7bf7614d707ba8f Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 13:52:58 +0200 Subject: [PATCH 01/23] Rework fluid ingredients to work more similar to the new custom ingredients in #793 --- .../neoforge/common/NeoForgeMod.java | 20 +- .../crafting/CompoundFluidIngredient.java | 100 ++++++++++ .../DataComponentFluidIngredient.java | 171 ++++++++++++++++++ .../crafting/DifferenceFluidIngredient.java | 80 ++++++++ .../fluids/crafting/EmptyFluidIngredient.java | 54 ++++++ .../fluids/crafting/FluidIngredient.java | 114 ++++++++++++ .../fluids/crafting/FluidIngredientType.java | 17 ++ .../crafting/IntersectionFluidIngredient.java | 95 ++++++++++ .../crafting/SingleFluidIngredient.java | 64 +++++++ .../fluids/crafting/TagFluidIngredient.java | 73 ++++++++ .../fluids/crafting/package-info.java | 13 ++ .../registries/NeoForgeRegistries.java | 3 + .../registries/NeoForgeRegistriesSetup.java | 1 + 13 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index 779a90d403..757a8ac732 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -146,6 +146,14 @@ import net.neoforged.neoforge.fluids.BaseFlowingFluid; 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.DataComponentFluidIngredient; +import net.neoforged.neoforge.fluids.crafting.DifferenceFluidIngredient; +import net.neoforged.neoforge.fluids.crafting.EmptyFluidIngredient; +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.forge.snapshots.ForgeSnapshotsMod; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.internal.versions.neoform.NeoFormVersion; @@ -371,6 +379,16 @@ public class NeoForgeMod { public static final DeferredHolder, IngredientType> DIFFERENCE_INGREDIENT_TYPE = INGREDIENT_TYPES.register("difference", () -> new IngredientType<>(DifferenceIngredient.CODEC)); public static final DeferredHolder, IngredientType> INTERSECTION_INGREDIENT_TYPE = INGREDIENT_TYPES.register("intersection", () -> new IngredientType<>(IntersectionIngredient.CODEC)); + private static final DeferredRegister> FLUID_INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES, NeoForgeVersion.MOD_ID); + // TODO: implement + 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> 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)); + 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); public static final DeferredHolder, MapCodec> FALSE_CONDITION = CONDITION_CODECS.register("false", () -> FalseCondition.CODEC); @@ -535,7 +553,7 @@ public ResourceLocation getFlowingTexture() { * Used in place of {@link DamageSources#magic()} for damage dealt by {@link MobEffects#POISON}. *

* May also be used by mods providing poison-like effects. - * + * * @see {@link Tags.DamageTypes#IS_POISON} */ public static final ResourceKey POISON_DAMAGE = ResourceKey.create(Registries.DAMAGE_TYPE, new ResourceLocation(NeoForgeVersion.MOD_ID, "poison")); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java new file mode 100644 index 0000000000..f93586781b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -0,0 +1,100 @@ +/* + * 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.List; +import java.util.Objects; +import java.util.stream.Stream; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; + +public final class CompoundFluidIngredient extends FluidIngredient { + public static final MapCodec CODEC = null; + + private final List children; + + public CompoundFluidIngredient(List children) { + if (children.isEmpty()) { + throw new IllegalArgumentException("Compound fluid ingredient must have at least one child"); + } + this.children = 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]; + + return new CompoundFluidIngredient(List.of(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); + } + + @Override + public Stream generateStacks() { + return children.stream().flatMap(FluidIngredient::generateStacks); + } + + @Override + public boolean test(FluidStack stack) { + for (var child : children) { + if (child.test(stack)) { + return true; + } + } + return false; + } + + @Override + public boolean isSimple() { + for (var child : children) { + if (!child.isSimple()) { + return false; + } + } + return true; + } + + @Override + public FluidIngredientType getType() { + return NeoForgeMod.COMPOUND_FLUID_INGREDIENT_TYPE.get(); + } + + @Override + public boolean isEmpty() { + return children.isEmpty() || children.stream().allMatch(FluidIngredient::isEmpty); + } + + @Override + public int hashCode() { + return Objects.hash(children); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + return obj instanceof CompoundFluidIngredient other && other.children.equals(this.children); + } + + public List children() { + return children; + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java new file mode 100644 index 0000000000..698a084113 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.fluids.crafting; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Stream; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.HolderSetCodec; +import net.minecraft.world.level.material.Fluid; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; + +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), + DataComponentPredicate.CODEC.fieldOf("components").forGetter(DataComponentFluidIngredient::components), + Codec.BOOL.optionalFieldOf("strict", false).forGetter(DataComponentFluidIngredient::isStrict)) + .apply(builder, DataComponentFluidIngredient::new)); + + private final HolderSet fluids; + private final DataComponentPredicate components; + private final boolean strict; + private final FluidStack[] stacks; + + public DataComponentFluidIngredient(HolderSet fluids, DataComponentPredicate components, boolean strict) { + this.fluids = fluids; + this.components = components; + this.strict = strict; + this.stacks = fluids.stream() + .map(i -> new FluidStack(i, 1, components.asPatch())) + .toArray(FluidStack[]::new); + } + + @Override + public boolean test(FluidStack stack) { + if (strict) { + for (FluidStack stack2 : this.stacks) { + if (FluidStack.isSameFluidSameComponents(stack, stack2)) return true; + } + return false; + } else { + return this.fluids.contains(stack.getFluidHolder()) && this.components.test(stack); + } + } + + public Stream generateStacks() { + return Stream.of(stacks); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public FluidIngredientType getType() { + return NeoForgeMod.DATA_COMPONENT_FLUID_INGREDIENT_TYPE.get(); + } + + @Override + public boolean isEmpty() { + return stacks.length == 0; + } + + @Override + public int hashCode() { + return Objects.hash(fluids, components, strict); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DataComponentFluidIngredient other)) return false; + return other.fluids.equals(this.fluids) + && other.components.equals(this.components) + && other.strict == this.strict; + } + + public HolderSet fluids() { + return fluids; + } + + public DataComponentPredicate components() { + return components; + } + + public boolean isStrict() { + return strict; + } + + /** + * Creates a new ingredient matching the given fluid, containing the given components + */ + public static FluidIngredient of(boolean strict, FluidStack stack) { + return of(strict, stack.getComponents(), stack.getFluid()); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, DataComponentType type, T value, Fluid... fluids) { + return of(strict, DataComponentPredicate.builder().expect(type, value).build(), fluids); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, Supplier> type, T value, Fluid... fluids) { + return of(strict, type.get(), value, fluids); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, DataComponentMap map, Fluid... fluids) { + return of(strict, DataComponentPredicate.allOf(map), fluids); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + @SafeVarargs + public static FluidIngredient of(boolean strict, DataComponentMap map, Holder... fluids) { + return of(strict, DataComponentPredicate.allOf(map), fluids); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, DataComponentMap map, HolderSet fluids) { + return of(strict, DataComponentPredicate.allOf(map), fluids); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + @SafeVarargs + public static FluidIngredient of(boolean strict, DataComponentPredicate predicate, Holder... fluids) { + return of(strict, predicate, HolderSet.direct(fluids)); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, DataComponentPredicate predicate, Fluid... fluids) { + return of(strict, predicate, HolderSet.direct(Arrays.stream(fluids).map(Fluid::builtInRegistryHolder).toList())); + } + + /** + * Creates a new ingredient matching any fluid from the list, containing the given components + */ + public static FluidIngredient of(boolean strict, DataComponentPredicate predicate, HolderSet fluids) { + return new DataComponentFluidIngredient(fluids, predicate, strict); + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java new file mode 100644 index 0000000000..c2c8d15fed --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) Forge Development LLC 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.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; + +public final class DifferenceFluidIngredient extends FluidIngredient { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder + .group( + 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; + + public DifferenceFluidIngredient(FluidIngredient base, FluidIngredient subtracted) { + this.base = base; + this.subtracted = subtracted; + } + + @Override + public Stream generateStacks() { + return base.generateStacks().filter(subtracted.negate()); + } + + @Override + public boolean test(FluidStack stack) { + return base.test(stack) && !subtracted.test(stack); + } + + @Override + public boolean isSimple() { + return base.isSimple() && subtracted.isSimple(); + } + + @Override + public FluidIngredientType getType() { + return NeoForgeMod.DIFFERENCE_FLUID_INGREDIENT_TYPE.get(); + } + + /** + * Gets the difference of the two fluid ingredients + * + * @param base Fluid ingredient that must be matched + * @param subtracted Fluid ingredient that must not be matched + * @return A fluid ingredient that matches anything contained in {@code base} that is not in {@code subtracted} + */ + public static FluidIngredient of(FluidIngredient base, FluidIngredient subtracted) { + return new DifferenceFluidIngredient(base, subtracted); + } + + public FluidIngredient base() { + return base; + } + + public FluidIngredient subtracted() { + return subtracted; + } + + @Override + public int hashCode() { + return Objects.hash(this.base, this.subtracted); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + return obj instanceof DifferenceFluidIngredient other && + other.base.equals(this.base) && other.subtracted.equals(this.subtracted); + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java new file mode 100644 index 0000000000..b32cc70633 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java @@ -0,0 +1,54 @@ +/* + * 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; + +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 boolean isEmpty() { + return true; + } + + @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 new file mode 100644 index 0000000000..5da5011b7a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -0,0 +1,114 @@ +/* + * 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.MapCodec; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +public abstract class FluidIngredient implements Predicate { + private static final MapCodec SINGLE_OR_TAG_CODEC = singleOrTagCodec(); + public static final MapCodec MAP_CODEC = makeMapCodec(); + private static final Codec MAP_CODEC_CODEC = MAP_CODEC.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); + }); + + public static final Codec CODEC = codec(true); + public static final Codec CODEC_NON_EMPTY = codec(false); + + @Nullable + private FluidStack[] stacks; + + public FluidStack[] getStacks() { + if (stacks == null) { + stacks = generateStacks().toArray(FluidStack[]::new); + } + + return stacks; + } + + @Override + public abstract boolean test(FluidStack fluidStack); + + protected abstract Stream generateStacks(); + + public abstract boolean isSimple(); + + public abstract FluidIngredientType getType(); + + public boolean isEmpty() { + // TODO: this resolves the ingredient by default, which i'm not sure is desirable? + return getStacks().length == 0; + } + + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object obj); + + // 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 -> { + if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) { + return Either.right(ingredient); + } + + return Either.left(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) + .xmap(either -> either.map(CompoundFluidIngredient::of, i -> i), + ingredient -> { + if (ingredient instanceof CompoundFluidIngredient compound) { + return Either.left(compound.children()); + } else if (ingredient.isEmpty()) { + return Either.left(List.of()); + } + + return Either.right(ingredient); + }); + } + + // empty + public static FluidIngredient empty() { + return EmptyFluidIngredient.INSTANCE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java new file mode 100644 index 0000000000..dc1fc4e50b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.fluids.crafting; + +import com.mojang.serialization.MapCodec; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; + +public record FluidIngredientType(MapCodec codec, StreamCodec streamCodec) { + public FluidIngredientType(MapCodec mapCodec) { + this(mapCodec, ByteBufCodecs.fromCodecWithRegistries(mapCodec.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 new file mode 100644 index 0000000000..2c6687dacc --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) Forge Development LLC 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.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; + +/** + * FluidIngredient that matches if all child ingredients match + */ +public final class IntersectionFluidIngredient extends FluidIngredient { + public IntersectionFluidIngredient(List children) { + if (children.isEmpty()) { + throw new IllegalArgumentException("Cannot create an IntersectionFluidIngredient with no children, use FluidIngredient.of() to create an empty ingredient"); + } + this.children = children; + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder + .group( + FluidIngredient.LIST_CODEC.fieldOf("children").forGetter(IntersectionFluidIngredient::children)) + .apply(builder, IntersectionFluidIngredient::new)); + private final List children; + + /** + * Gets an intersection fluid ingredient + * + * @param ingredients List of fluid ingredients to match + * @return FluidIngredient that only matches if all the passed ingredients match + */ + public static FluidIngredient of(FluidIngredient... ingredients) { + if (ingredients.length == 0) + throw new IllegalArgumentException("Cannot create an IntersectionFluidIngredient with no children, use FluidIngredient.of() to create an empty ingredient"); + if (ingredients.length == 1) + return ingredients[0]; + + return new IntersectionFluidIngredient(Arrays.asList(ingredients)); + } + + @Override + public boolean test(FluidStack stack) { + for (var child : children) { + if (!child.test(stack)) { + return false; + } + } + return true; + } + + @Override + public Stream generateStacks() { + return children.stream() + .flatMap(FluidIngredient::generateStacks) + .filter(this); + } + + @Override + public boolean isSimple() { + for (var child : children) { + if (!child.isSimple()) { + return false; + } + } + return true; + } + + @Override + public FluidIngredientType getType() { + return NeoForgeMod.INTERSECTION_FLUID_INGREDIENT_TYPE.get(); + } + + public List children() { + return children; + } + + @Override + public int hashCode() { + return Objects.hash(children); + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java new file mode 100644 index 0000000000..36d09942c8 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -0,0 +1,64 @@ +/* + * 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; +import net.neoforged.neoforge.fluids.FluidType; + +public class SingleFluidIngredient extends FluidIngredient { + public static final MapCodec CODEC = FluidStack.fixedAmountCodec(FluidType.BUCKET_VOLUME) + .xmap(SingleFluidIngredient::new, SingleFluidIngredient::stack).fieldOf("fluid"); + + private final FluidStack stack; + + public SingleFluidIngredient(FluidStack stack) { + this.stack = stack; + } + + @Override + public boolean test(FluidStack fluidStack) { + return FluidStack.isSameFluid(this.stack, fluidStack); + } + + @Override + protected Stream generateStacks() { + return Stream.of(stack); + } + + @Override + public boolean isSimple() { + return true; + } + + @Override + public FluidIngredientType getType() { + return NeoForgeMod.SINGLE_FLUID_INGREDIENT_TYPE.get(); + } + + @Override + public boolean isEmpty() { + // TODO: should we even allow constructing this with an empty stack? + return stack.isEmpty(); + } + + @Override + public int hashCode() { + return FluidStack.hashFluidAndComponents(stack); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + return obj instanceof SingleFluidIngredient other && other.stack.equals(this.stack); + } + + public FluidStack stack() { + return stack; + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java new file mode 100644 index 0000000000..8a63649429 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java @@ -0,0 +1,73 @@ +/* + * 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; + +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.getTag(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 boolean isEmpty() { + var opt = BuiltInRegistries.FLUID.getTag(tag); + + return opt.map(holders -> holders.size() == 0).orElse(true); + } + + @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/package-info.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java new file mode 100644 index 0000000000..e70d622cb9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/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; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java index 52e77fde19..12550df7b6 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java @@ -20,6 +20,7 @@ import net.neoforged.neoforge.common.world.BiomeModifier; import net.neoforged.neoforge.common.world.StructureModifier; import net.neoforged.neoforge.fluids.FluidType; +import net.neoforged.neoforge.fluids.crafting.FluidIngredientType; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.registries.holdersets.HolderSetType; @@ -43,6 +44,7 @@ public class NeoForgeRegistries { .defaultKey(new ResourceLocation("none")) .create(); public static final Registry> INGREDIENT_TYPES = new RegistryBuilder<>(Keys.INGREDIENT_TYPES).sync(true).create(); + public static final Registry> FLUID_INGREDIENT_TYPES = new RegistryBuilder<>(Keys.FLUID_INGREDIENT_TYPES).sync(true).create(); public static final Registry> CONDITION_SERIALIZERS = new RegistryBuilder<>(Keys.CONDITION_CODECS).create(); public static final Registry> ATTACHMENT_TYPES = new RegistryBuilder<>(Keys.ATTACHMENT_TYPES).create(); @@ -58,6 +60,7 @@ public static final class Keys { public static final ResourceKey> HOLDER_SET_TYPES = key("holder_set_type"); public static final ResourceKey> DISPLAY_CONTEXTS = key("display_contexts"); public static final ResourceKey>> INGREDIENT_TYPES = key("ingredient_serializer"); + public static final ResourceKey>> FLUID_INGREDIENT_TYPES = key("fluid_ingredient_type"); public static final ResourceKey>> CONDITION_CODECS = key("condition_codecs"); public static final ResourceKey>> ATTACHMENT_TYPES = key("attachment_types"); diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java index 1869c853fd..a71ced4fe7 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java @@ -69,6 +69,7 @@ private static void registerRegistries(NewRegistryEvent event) { event.register(NeoForgeRegistries.HOLDER_SET_TYPES); event.register(NeoForgeRegistries.DISPLAY_CONTEXTS); event.register(NeoForgeRegistries.INGREDIENT_TYPES); + event.register(NeoForgeRegistries.FLUID_INGREDIENT_TYPES); event.register(NeoForgeRegistries.CONDITION_SERIALIZERS); event.register(NeoForgeRegistries.ATTACHMENT_TYPES); } From 69e7cafafde7310ca45fe6e76686cb796933b70a Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 14:40:34 +0200 Subject: [PATCH 02/23] Add StreamCodec for FluidIngredient, convenience of(...) methods --- .../crafting/CompoundFluidIngredient.java | 13 ++- .../fluids/crafting/FluidIngredient.java | 99 ++++++++++++++++--- 2 files changed, 92 insertions(+), 20 deletions(-) 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 f93586781b..0571159759 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -6,22 +6,23 @@ package net.neoforged.neoforge.fluids.crafting; import com.mojang.serialization.MapCodec; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; + import java.util.List; import java.util.Objects; import java.util.stream.Stream; -import net.neoforged.neoforge.common.NeoForgeMod; -import net.neoforged.neoforge.fluids.FluidStack; public final class CompoundFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = null; private final List children; - public CompoundFluidIngredient(List children) { + public CompoundFluidIngredient(List children) { if (children.isEmpty()) { throw new IllegalArgumentException("Compound fluid ingredient must have at least one child"); } - this.children = children; + this.children = List.copyOf(children); } /** @@ -48,6 +49,10 @@ public static FluidIngredient of(List children) { return new CompoundFluidIngredient(children); } + public static FluidIngredient of(Stream stream) { + return of(stream.toList()); + } + @Override public Stream generateStacks() { return children.stream().flatMap(FluidIngredient::generateStacks); 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 5da5011b7a..5cfbeecb59 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -9,14 +9,23 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; -import javax.annotation.Nullable; +import net.minecraft.core.NonNullList; +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.level.material.Fluid; import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.FluidType; import net.neoforged.neoforge.registries.NeoForgeRegistries; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + public abstract class FluidIngredient implements Predicate { private static final MapCodec SINGLE_OR_TAG_CODEC = singleOrTagCodec(); public static final MapCodec MAP_CODEC = makeMapCodec(); @@ -33,6 +42,39 @@ public abstract class FluidIngredient implements Predicate { public static final Codec CODEC = codec(true); public static final Codec CODEC_NON_EMPTY = codec(false); + 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); + + 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) + ); + } + }; + @Nullable private FluidStack[] stacks; @@ -69,13 +111,13 @@ 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!"); - }); + 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() { @@ -84,12 +126,12 @@ private static MapCodec makeMapCodec() { FluidIngredient::getType, FluidIngredientType::codec, FluidIngredient.SINGLE_OR_TAG_CODEC).xmap(either -> either.map(id -> id, id -> id), ingredient -> { - if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) { - return Either.right(ingredient); - } + if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) { + return Either.right(ingredient); + } - return Either.left(ingredient); - }); + return Either.left(ingredient); + }); } private static Codec codec(boolean allowEmpty) { @@ -111,4 +153,29 @@ private static Codec codec(boolean allowEmpty) { public static FluidIngredient empty() { return EmptyFluidIngredient.INSTANCE; } + + // convenience methods + public static FluidIngredient of() { + return empty(); + } + + public static FluidIngredient of(Fluid... fluids) { + return of(Arrays.stream(fluids).map(fluid -> new FluidStack(fluid, FluidType.BUCKET_VOLUME))); + } + + public static FluidIngredient of(FluidStack... fluids) { + return of(Arrays.stream(fluids)); + } + + private static FluidIngredient of(Stream fluids) { + return CompoundFluidIngredient.of(fluids.map(FluidIngredient::single)); + } + + public static FluidIngredient single(FluidStack stack) { + return stack.isEmpty() ? empty() : new SingleFluidIngredient(stack); + } + + public static FluidIngredient tag(TagKey tag) { + return new TagFluidIngredient(tag); + } } From 3846af2dd2638b5b381754832daa673371602822 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 14:40:42 +0200 Subject: [PATCH 03/23] Add SizedFluidIngredient --- .../fluids/crafting/SizedFluidIngredient.java | 160 ++++++++++++++++++ .../fluids/crafting/package-info.java | 1 - 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java new file mode 100644 index 0000000000..56a07f39a9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java @@ -0,0 +1,160 @@ +package net.neoforged.neoforge.fluids.crafting; + +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +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; + +import java.util.stream.Stream; + +/** + * Standard implementation for a FluidIngredient with an amount. + * + *

{@link FluidIngredient}, like its item counterpart, explicitly does not perform count checks, + * so this class is used to (a) wrap a standard FluidIngredient with an amount and (b) provide a + * standard serialization format for mods to use. + * + * @see net.neoforged.neoforge.common.crafting.SizedIngredient + */ +public final class SizedFluidIngredient { + /** + * The "flat" codec for {@link SizedFluidIngredient}. + * + *

The amount is serialized inline with the rest of the ingredient, 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
+     * }
+     * }
+ *

+ */ + public static final Codec FLAT_CODEC = RecordCodecBuilder.create(instance -> instance.group( + FluidIngredient.MAP_CODEC.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: + * + *

{@code
+     * {
+     *     "ingredient": {
+     *         "fluid": "minecraft:lava"
+     *     },
+     *     "amount": 1000
+     * }
+     * }
+ */ + public static final Codec NESTED_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)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + FluidIngredient.STREAM_CODEC, + SizedFluidIngredient::ingredient, + ByteBufCodecs.VAR_INT, + SizedFluidIngredient::amount, + SizedFluidIngredient::new + ); + + 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"); + } + this.ingredient = ingredient; + this.amount = amount; + } + + public FluidIngredient ingredient() { + return ingredient; + } + + public int amount() { + return amount; + } + + /** + * Performs a size-sensitive test on the given stack. + * + * @return {@code true} if the stack matches the ingredient and has at least the required amount. + */ + 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 String toString() { + return amount + "x " + ingredient; + } +} + diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java index e70d622cb9..cf14b6e182 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: LGPL-2.1-only */ -@FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault package net.neoforged.neoforge.fluids.crafting; From 7dd15a581edaab3a7f5d0f7240e5b08fcf28d30a Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 16:47:51 +0200 Subject: [PATCH 04/23] Apply formatting --- .../crafting/CompoundFluidIngredient.java | 5 +-- .../fluids/crafting/FluidIngredient.java | 41 +++++++++---------- .../fluids/crafting/SizedFluidIngredient.java | 21 +++++----- .../fluids/crafting/package-info.java | 1 - 4 files changed, 31 insertions(+), 37 deletions(-) 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 0571159759..4a4273995e 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -6,12 +6,11 @@ package net.neoforged.neoforge.fluids.crafting; import com.mojang.serialization.MapCodec; -import net.neoforged.neoforge.common.NeoForgeMod; -import net.neoforged.neoforge.fluids.FluidStack; - import java.util.List; import java.util.Objects; import java.util.stream.Stream; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.fluids.FluidStack; public final class CompoundFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = null; 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 5cfbeecb59..adc1878fa5 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -9,6 +9,11 @@ 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.function.Predicate; +import java.util.stream.Stream; +import javax.annotation.Nullable; import net.minecraft.core.NonNullList; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -20,12 +25,6 @@ import net.neoforged.neoforge.fluids.FluidType; import net.neoforged.neoforge.registries.NeoForgeRegistries; -import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; - public abstract class FluidIngredient implements Predicate { private static final MapCodec SINGLE_OR_TAG_CODEC = singleOrTagCodec(); public static final MapCodec MAP_CODEC = makeMapCodec(); @@ -47,8 +46,7 @@ public abstract class FluidIngredient implements Predicate { .dispatch(FluidIngredient::getType, FluidIngredientType::streamCodec); private static final StreamCodec> FLUID_LIST_CODEC = FluidStack.STREAM_CODEC.apply( - ByteBufCodecs.collection(NonNullList::createWithCapacity) - ); + ByteBufCodecs.collection(NonNullList::createWithCapacity)); @Override public void encode(RegistryFriendlyByteBuf buf, FluidIngredient ingredient) { @@ -70,8 +68,7 @@ public FluidIngredient decode(RegistryFriendlyByteBuf buf) { return CompoundFluidIngredient.of( Stream.generate(() -> FluidStack.STREAM_CODEC.decode(buf)) .limit(size) - .map(FluidIngredient::single) - ); + .map(FluidIngredient::single)); } }; @@ -111,13 +108,13 @@ 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!"); - }); + 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() { @@ -126,12 +123,12 @@ private static MapCodec makeMapCodec() { FluidIngredient::getType, FluidIngredientType::codec, FluidIngredient.SINGLE_OR_TAG_CODEC).xmap(either -> either.map(id -> id, id -> id), ingredient -> { - if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) { - return Either.right(ingredient); - } + if (ingredient instanceof SingleFluidIngredient || ingredient instanceof TagFluidIngredient) { + return Either.right(ingredient); + } - return Either.left(ingredient); - }); + return Either.left(ingredient); + }); } private static Codec codec(boolean allowEmpty) { 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 56a07f39a9..6644aac63e 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java @@ -1,12 +1,13 @@ -package net.neoforged.neoforge.fluids.crafting; - /* * Copyright (c) NeoForged and contributors * SPDX-License-Identifier: LGPL-2.1-only */ +package net.neoforged.neoforge.fluids.crafting; + import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import java.util.stream.Stream; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; @@ -18,8 +19,6 @@ import net.neoforged.neoforge.fluids.FluidType; import org.jetbrains.annotations.Nullable; -import java.util.stream.Stream; - /** * Standard implementation for a FluidIngredient with an amount. * @@ -41,6 +40,7 @@ public final class SizedFluidIngredient { * "amount": 250 * } * } + * *

*

* Compound fluid ingredients are always serialized using the map codec, i.e. @@ -55,11 +55,12 @@ public final class SizedFluidIngredient { * "amount": 500 * } * } + * *

*/ public static final Codec FLAT_CODEC = RecordCodecBuilder.create(instance -> instance.group( - FluidIngredient.MAP_CODEC.forGetter(SizedFluidIngredient::ingredient), - NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount)) + FluidIngredient.MAP_CODEC.forGetter(SizedFluidIngredient::ingredient), + NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount)) .apply(instance, SizedFluidIngredient::new)); /** @@ -77,8 +78,8 @@ public final class SizedFluidIngredient { * } */ public static final Codec NESTED_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)) + FluidIngredient.CODEC.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient), + NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount)) .apply(instance, SizedFluidIngredient::new)); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( @@ -86,8 +87,7 @@ public final class SizedFluidIngredient { SizedFluidIngredient::ingredient, ByteBufCodecs.VAR_INT, SizedFluidIngredient::amount, - SizedFluidIngredient::new - ); + SizedFluidIngredient::new); public static SizedFluidIngredient of(Fluid fluid, int amount) { return new SizedFluidIngredient(FluidIngredient.of(fluid), amount); @@ -157,4 +157,3 @@ public String toString() { return amount + "x " + ingredient; } } - diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java index cf14b6e182..ee15ea38f9 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/package-info.java @@ -8,5 +8,4 @@ package net.neoforged.neoforge.fluids.crafting; import javax.annotation.ParametersAreNonnullByDefault; -import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; From 3f78b1e32268443fd82dc08bab70be4d1e5ae8c9 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 18:23:41 +0200 Subject: [PATCH 05/23] Add partial Javadoc to FluidIngredient --- .../crafting/CompoundFluidIngredient.java | 5 - .../DataComponentFluidIngredient.java | 5 - .../fluids/crafting/EmptyFluidIngredient.java | 5 - .../fluids/crafting/FluidIngredient.java | 92 ++++++++++++++++++- .../crafting/SingleFluidIngredient.java | 13 +-- .../fluids/crafting/TagFluidIngredient.java | 7 -- 6 files changed, 96 insertions(+), 31 deletions(-) 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 4a4273995e..5f459eb0fd 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -82,11 +82,6 @@ public FluidIngredientType getType() { return NeoForgeMod.COMPOUND_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean isEmpty() { - return children.isEmpty() || children.stream().allMatch(FluidIngredient::isEmpty); - } - @Override public int hashCode() { return Objects.hash(children); 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 698a084113..2ec3872feb 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -73,11 +73,6 @@ public FluidIngredientType getType() { return NeoForgeMod.DATA_COMPONENT_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean isEmpty() { - return stacks.length == 0; - } - @Override public int hashCode() { return Objects.hash(fluids, components, strict); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java index b32cc70633..f688aecef7 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java @@ -37,11 +37,6 @@ public FluidIngredientType getType() { return NeoForgeMod.EMPTY_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean isEmpty() { - return true; - } - @Override public int hashCode() { return 0; 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 adc1878fa5..66a086be4c 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -19,14 +19,30 @@ 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.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.FluidType; import net.neoforged.neoforge.registries.NeoForgeRegistries; 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 = makeMapCodec(); private static final Codec MAP_CODEC_CODEC = MAP_CODEC.codec(); @@ -38,7 +54,22 @@ public abstract class FluidIngredient implements Predicate { 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 + */ 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 StreamCodec STREAM_CODEC = new StreamCodec<>() { @@ -75,6 +106,14 @@ public FluidIngredient decode(RegistryFriendlyByteBuf buf) { @Nullable private FluidStack[] stacks; + /** + * {@return an array of fluid stacks this ingredient accepts} + *

+ * This implementation simply caches the results of dissolving the ingredient using + * {@link #generateStacks()}, and should not be overridden unless you have good reason to. + * + * @see #generateStacks() + */ public FluidStack[] getStacks() { if (stacks == null) { stacks = generateStacks().toArray(FluidStack[]::new); @@ -83,20 +122,63 @@ public FluidStack[] getStacks() { return stacks; } + /** + * Checks if a given fluid stack matches this ingredient. + * The stack must not be modified in any way. + * + * @param fluidStack the stack to test + * @return {@code true} if the stack matches, {@code false} otherwise + */ @Override public abstract boolean test(FluidStack fluidStack); + /** + * Generates a stream of all fluid stacks this ingredient matches against. + *

+ * 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.
  • + *
  • 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.
  • + *
  • 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.
  • + *
+ * + * @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#getItems() + */ protected abstract Stream generateStacks(); public abstract boolean isSimple(); + /** + * {@return The type of this fluid ingredient.} + * + *

The type must be registered to {@link NeoForgeRegistries#FLUID_INGREDIENT_TYPES}. + */ 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 boolean isEmpty() { - // TODO: this resolves the ingredient by default, which i'm not sure is desirable? - return getStacks().length == 0; + return this == empty(); } + // TODO: add empty check that includes accidentally empty ingredients + @Override public abstract int hashCode(); @@ -123,6 +205,7 @@ private static MapCodec makeMapCodec() { 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); } @@ -134,11 +217,14 @@ private static MapCodec makeMapCodec() { 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 CompoundIngredient instance .xmap(either -> either.map(CompoundFluidIngredient::of, i -> i), ingredient -> { + // serialize CompoundIngredient 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()); } @@ -169,7 +255,7 @@ private static FluidIngredient of(Stream fluids) { } public static FluidIngredient single(FluidStack stack) { - return stack.isEmpty() ? empty() : new SingleFluidIngredient(stack); + return SingleFluidIngredient.of(stack); } public static FluidIngredient tag(TagKey tag) { diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index 36d09942c8..a0d91f2cef 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -18,6 +18,9 @@ public class SingleFluidIngredient extends FluidIngredient { private final FluidStack stack; public SingleFluidIngredient(FluidStack stack) { + if (stack.isEmpty()) { + throw new IllegalStateException("SingleFluidIngredient should not be constructed with an empty stack, use FluidIngredient.empty() instead!"); + } this.stack = stack; } @@ -41,12 +44,6 @@ public FluidIngredientType getType() { return NeoForgeMod.SINGLE_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean isEmpty() { - // TODO: should we even allow constructing this with an empty stack? - return stack.isEmpty(); - } - @Override public int hashCode() { return FluidStack.hashFluidAndComponents(stack); @@ -61,4 +58,8 @@ public boolean equals(Object obj) { public FluidStack stack() { return stack; } + + public static FluidIngredient of(FluidStack stack) { + return stack.isEmpty() ? empty() : new SingleFluidIngredient(stack); + } } diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java index 8a63649429..8cf844c83e 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java @@ -49,13 +49,6 @@ public FluidIngredientType getType() { return NeoForgeMod.TAG_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean isEmpty() { - var opt = BuiltInRegistries.FLUID.getTag(tag); - - return opt.map(holders -> holders.size() == 0).orElse(true); - } - @Override public int hashCode() { return tag.hashCode(); From 9e4c4da6c98147f97cd881b50fa859700edfffa4 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 19:21:15 +0200 Subject: [PATCH 06/23] Add FluidIngredient#hasNoFluids, further Javadoc --- .../crafting/CompoundFluidIngredient.java | 5 +++++ .../DataComponentFluidIngredient.java | 5 +++++ .../fluids/crafting/EmptyFluidIngredient.java | 5 +++++ .../fluids/crafting/FluidIngredient.java | 22 ++++++++++++++++++- .../crafting/SingleFluidIngredient.java | 8 ++++--- 5 files changed, 41 insertions(+), 4 deletions(-) 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 5f459eb0fd..b8618731a5 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -87,6 +87,11 @@ public int hashCode() { return Objects.hash(children); } + @Override + public boolean hasNoFluids() { + return children.isEmpty() || children.stream().allMatch(FluidIngredient::hasNoFluids); + } + @Override public boolean equals(Object obj) { if (this == obj) return true; 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 2ec3872feb..0ccf0208a5 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -73,6 +73,11 @@ public FluidIngredientType getType() { return NeoForgeMod.DATA_COMPONENT_FLUID_INGREDIENT_TYPE.get(); } + @Override + public boolean hasNoFluids() { + return stacks.length == 0; + } + @Override public int hashCode() { return Objects.hash(fluids, components, strict); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java index f688aecef7..22fef61463 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java @@ -37,6 +37,11 @@ public FluidIngredientType getType() { return NeoForgeMod.EMPTY_FLUID_INGREDIENT_TYPE.get(); } + @Override + public boolean hasNoFluids() { + return true; + } + @Override public int hashCode() { return 0; 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 66a086be4c..5e1d331791 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -141,6 +141,7 @@ public FluidStack[] getStacks() { *

  • These stacks are generally used for display purposes, and need not be exhaustive or perfectly accurate.
  • *
  • 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.
  • * @@ -177,7 +178,26 @@ public boolean isEmpty() { return this == empty(); } - // TODO: add empty check that includes accidentally empty ingredients + /** + * Checks if this ingredient matches no fluids, i.e. either: + *
      + *
    • is equal to {@link #empty()},
    • + *
    • resolves to an empty list of fluids
    • + *
    + *

    + * Note that this method explicitly has the potential to resolve + * 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 + * @implNote If it is possible for your ingredient to return a "hint" on + * whether it is empty without resolving, you should override this method + * with a custom implementation. + * @see #isEmpty() + */ + public boolean hasNoFluids() { + return isEmpty() || getStacks().length == 0; + } @Override public abstract int hashCode(); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index a0d91f2cef..22db5ea635 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -18,9 +18,6 @@ public class SingleFluidIngredient extends FluidIngredient { private final FluidStack stack; public SingleFluidIngredient(FluidStack stack) { - if (stack.isEmpty()) { - throw new IllegalStateException("SingleFluidIngredient should not be constructed with an empty stack, use FluidIngredient.empty() instead!"); - } this.stack = stack; } @@ -44,6 +41,11 @@ public FluidIngredientType getType() { return NeoForgeMod.SINGLE_FLUID_INGREDIENT_TYPE.get(); } + @Override + public boolean hasNoFluids() { + return stack.isEmpty(); + } + @Override public int hashCode() { return FluidStack.hashFluidAndComponents(stack); From 6c0ff167be47f97207f8d7e6215a19a5646f81d8 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 21:44:07 +0200 Subject: [PATCH 07/23] Fix equality check on SingleFluidIngredient --- .../neoforge/fluids/crafting/SingleFluidIngredient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index 22db5ea635..c3f30f5057 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -54,7 +54,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { if (this == obj) return true; - return obj instanceof SingleFluidIngredient other && other.stack.equals(this.stack); + return obj instanceof SingleFluidIngredient other && FluidStack.matches(other.stack, this.stack); } public FluidStack stack() { From 52ef956585464c81c92067d39d413d60dea74e8d Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Apr 2024 22:14:51 +0200 Subject: [PATCH 08/23] Add some first tests for fluid ingredients --- .../crafting/SingleFluidIngredient.java | 3 + .../fluid/crafting/FluidIngredientTests.java | 143 ++++++++++++++++++ .../debug/fluid/crafting/package-info.java | 13 ++ 3 files changed, 159 insertions(+) create mode 100644 tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java create mode 100644 tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/package-info.java diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index c3f30f5057..b4e893df69 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -18,6 +18,9 @@ public class SingleFluidIngredient extends FluidIngredient { private final FluidStack stack; public SingleFluidIngredient(FluidStack stack) { + if (stack.isEmpty()) { + throw new IllegalStateException("SingleFluidIngredient should not be constructed with an empty stack, use FluidIngredient.empty() instead!"); + } this.stack = stack; } 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 new file mode 100644 index 0000000000..290a5249d3 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/FluidIngredientTests.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.fluid.crafting; + +import com.google.gson.JsonArray; +import com.mojang.serialization.JsonOps; +import java.util.List; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; +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.SingleFluidIngredient; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.gametest.ExtendedGameTestHelper; + +@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 singleFluid = FluidIngredient.of(Fluids.WATER); + var tagFluid = FluidIngredient.tag(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 singleJson = singleResult.resultOrPartial(error -> helper.fail("Failed to serialize single fluid ingredient: " + error)).orElseThrow(); + + var tagResult = FluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, 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.assertValueEqual(singleJson.toString(), "{\"fluid\":{\"id\":\"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!"); + + // tests that deserializing simple ingredients is reproducible and produces the desired ingredients + var singleTwo = FluidIngredient.CODEC.parse(JsonOps.INSTANCE, 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) + .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 = new SingleFluidIngredient(FluidStack.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.assertValueEqual(SingleFluidIngredient.of(FluidStack.EMPTY), FluidIngredient.empty(), "calling SingleFluidIngredient.of with empty stack to yield FluidIngredient.empty()"); + + helper.succeed(); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/package-info.java b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/package-info.java new file mode 100644 index 0000000000..5f22d8260b --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/crafting/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.debug.fluid.crafting; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; From 7e2106db233971358bd80d31ef58ae3f0deb97d9 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 29 Apr 2024 01:20:49 +0200 Subject: [PATCH 09/23] Add more javadoc to fluid ingredients --- .../fluids/crafting/CompoundFluidIngredient.java | 8 ++++++++ .../crafting/DataComponentFluidIngredient.java | 10 ++++++++++ .../fluids/crafting/DifferenceFluidIngredient.java | 7 +++++++ .../fluids/crafting/EmptyFluidIngredient.java | 10 ++++++++++ .../fluids/crafting/FluidIngredientType.java | 12 ++++++++++++ .../fluids/crafting/SingleFluidIngredient.java | 6 ++++++ .../neoforge/fluids/crafting/TagFluidIngredient.java | 6 ++++++ 7 files changed, 59 insertions(+) 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 b8618731a5..443c238022 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -10,8 +10,16 @@ import java.util.Objects; import java.util.stream.Stream; import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.common.crafting.CompoundIngredient; import net.neoforged.neoforge.fluids.FluidStack; +/** + * Fluid ingredient that matches if any of the child ingredients match. + * This type additionally represents the array notation used in + * {@linkplain FluidIngredient#CODEC} internally. + * + * @see CompoundIngredient CompoundIngredient, its item equivalent + */ public final class CompoundFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = null; 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 0ccf0208a5..78c260a895 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -22,8 +22,18 @@ import net.minecraft.resources.HolderSetCodec; 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; +/** + * Fluid ingredient that matches the given set of fluids, additionally performing either a + * {@link DataComponentFluidIngredient#isStrict() strict} or partial test on the FluidStack's components. + *

    + * Strict ingredients will only match fluid stacks that have exactly the provided components, while partial ones will + * match if the stack's components contain all required components for the {@linkplain #components input predicate}. + * + * @see DataComponentIngredient DataComponentIngredient, its item equivalent + */ public class DataComponentFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( builder -> builder 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 c2c8d15fed..199ccd4584 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java @@ -10,8 +10,15 @@ import java.util.Objects; import java.util.stream.Stream; import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.common.crafting.DifferenceIngredient; import net.neoforged.neoforge.fluids.FluidStack; +/** + * Fluid ingredient that matches the difference of two provided fluid ingredients, i.e. + * anything contained in {@code base} that is not in {@code subtracted}. + * + * @see DifferenceIngredient DifferenceIngredient, its item equivalent + */ public final class DifferenceFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( builder -> builder diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java index 22fef61463..72901bfe86 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java @@ -10,6 +10,16 @@ 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(); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java index dc1fc4e50b..eed0887ae3 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredientType.java @@ -9,7 +9,19 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.neoforged.neoforge.common.crafting.IngredientType; +/** + * This represents the "type" of a {@link FluidIngredient}, providing means of serializing + * and deserializing the ingredient over both JSON and network, using the {@link #codec} + * and {@link #streamCodec}, respectively. + *

    + * Note that the {@link #streamCodec()} is only used if {@link FluidIngredient#isSimple()} returns {@code false}, + * as otherwise its contents are synchronized directly to the network. + * + * @param The type of fluid ingredient + * @see IngredientType IngredientType, a similar class for custom item ingredients + */ public record FluidIngredientType(MapCodec codec, StreamCodec streamCodec) { public FluidIngredientType(MapCodec mapCodec) { this(mapCodec, ByteBufCodecs.fromCodecWithRegistries(mapCodec.codec())); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index b4e893df69..1b0d5eef1d 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -11,6 +11,12 @@ 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} + */ public class SingleFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = FluidStack.fixedAmountCodec(FluidType.BUCKET_VOLUME) .xmap(SingleFluidIngredient::new, SingleFluidIngredient::stack).fieldOf("fluid"); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java index 8cf844c83e..beb2be1817 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java @@ -16,6 +16,12 @@ 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} + */ public class TagFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = TagKey.codec(Registries.FLUID) .xmap(TagFluidIngredient::new, TagFluidIngredient::tag).fieldOf("tag"); From ff97f8f3cfbe46185137b6a9c69fc286e4ccc203 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 29 Apr 2024 01:48:30 +0200 Subject: [PATCH 10/23] Add more tests --- .../fluid/crafting/FluidIngredientTests.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) 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 290a5249d3..3c228e5541 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 @@ -9,8 +9,12 @@ 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.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.Tags; @@ -140,4 +144,78 @@ static void customFluidIngredientsHasNoFluids(final GameTestHelper helper) { 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!"); + + 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.succeed(); + } } From e6949fac317ebaaef30779a9f4f61157f4f06b60 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 29 Apr 2024 02:05:07 +0200 Subject: [PATCH 11/23] Change SingleFluidIngredient to take in a fluid holder instead of a stack (idk why I copied Vanilla's bad design there) --- .../fluids/crafting/FluidIngredient.java | 20 +++++++---- .../crafting/SingleFluidIngredient.java | 36 +++++++++---------- .../fluid/crafting/FluidIngredientTests.java | 6 ++-- 3 files changed, 34 insertions(+), 28 deletions(-) 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 5e1d331791..c852c50111 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -14,6 +14,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import javax.annotation.Nullable; +import net.minecraft.core.Holder; import net.minecraft.core.NonNullList; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -24,7 +25,6 @@ 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.FluidType; import net.neoforged.neoforge.registries.NeoForgeRegistries; public abstract class FluidIngredient implements Predicate { @@ -262,20 +262,28 @@ public static FluidIngredient of() { return empty(); } - public static FluidIngredient of(Fluid... fluids) { - return of(Arrays.stream(fluids).map(fluid -> new FluidStack(fluid, FluidType.BUCKET_VOLUME))); + public static FluidIngredient of(FluidStack... fluids) { + return of(Arrays.stream(fluids).map(FluidStack::getFluid)); } - public static FluidIngredient of(FluidStack... fluids) { + public static FluidIngredient of(Fluid... fluids) { return of(Arrays.stream(fluids)); } - private static FluidIngredient of(Stream fluids) { + private static FluidIngredient of(Stream fluids) { return CompoundFluidIngredient.of(fluids.map(FluidIngredient::single)); } public static FluidIngredient single(FluidStack stack) { - return SingleFluidIngredient.of(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) { diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index 1b0d5eef1d..7b7246e053 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -7,6 +7,10 @@ import com.mojang.serialization.MapCodec; import java.util.stream.Stream; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +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; @@ -18,26 +22,26 @@ * though it may still be written without a type field, see {@link FluidIngredient#MAP_CODEC} */ public class SingleFluidIngredient extends FluidIngredient { - public static final MapCodec CODEC = FluidStack.fixedAmountCodec(FluidType.BUCKET_VOLUME) - .xmap(SingleFluidIngredient::new, SingleFluidIngredient::stack).fieldOf("fluid"); + public static final MapCodec CODEC = BuiltInRegistries.FLUID.holderByNameCodec() + .xmap(SingleFluidIngredient::new, SingleFluidIngredient::fluid).fieldOf("fluid"); - private final FluidStack stack; + private final Holder fluid; - public SingleFluidIngredient(FluidStack stack) { - if (stack.isEmpty()) { - throw new IllegalStateException("SingleFluidIngredient should not be constructed with an empty stack, use FluidIngredient.empty() instead!"); + 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.stack = stack; + this.fluid = fluid; } @Override public boolean test(FluidStack fluidStack) { - return FluidStack.isSameFluid(this.stack, fluidStack); + return fluidStack.is(fluid); } @Override protected Stream generateStacks() { - return Stream.of(stack); + return Stream.of(new FluidStack(fluid, FluidType.BUCKET_VOLUME)); } @Override @@ -52,25 +56,21 @@ public FluidIngredientType getType() { @Override public boolean hasNoFluids() { - return stack.isEmpty(); + return fluid.value().isSame(Fluids.EMPTY); } @Override public int hashCode() { - return FluidStack.hashFluidAndComponents(stack); + return this.fluid().value().hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; - return obj instanceof SingleFluidIngredient other && FluidStack.matches(other.stack, this.stack); + return obj instanceof SingleFluidIngredient other && other.fluid.is(this.fluid); } - public FluidStack stack() { - return stack; - } - - public static FluidIngredient of(FluidStack stack) { - return stack.isEmpty() ? empty() : new SingleFluidIngredient(stack); + public Holder fluid() { + return fluid; } } 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 3c228e5541..fbcb5352e2 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 @@ -24,7 +24,6 @@ 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.SingleFluidIngredient; import net.neoforged.testframework.annotation.ForEachTest; import net.neoforged.testframework.annotation.TestHolder; import net.neoforged.testframework.gametest.EmptyTemplate; @@ -72,7 +71,7 @@ static void basicFluidIngredientSerialization(ExtendedGameTestHelper helper) { 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.assertValueEqual(singleJson.toString(), "{\"fluid\":{\"id\":\"minecraft:water\"}}", "serialized single fluid ingredient to match expected format!"); + 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!"); // tests that deserializing simple ingredients is reproducible and produces the desired ingredients @@ -133,14 +132,13 @@ static void customFluidIngredientsHasNoFluids(final GameTestHelper helper) { var emptySingleFailed = false; try { - FluidIngredient compoundIngredient = new SingleFluidIngredient(FluidStack.EMPTY); + 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.assertValueEqual(SingleFluidIngredient.of(FluidStack.EMPTY), FluidIngredient.empty(), "calling SingleFluidIngredient.of with empty stack to yield FluidIngredient.empty()"); helper.succeed(); } From 7fb4f0ca419c0a4fb18418af88be2e9486f24f11 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 29 Apr 2024 02:20:41 +0200 Subject: [PATCH 12/23] Add tests for sized fluid ingredients --- .../fluid/crafting/FluidIngredientTests.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) 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 fbcb5352e2..974ed9377e 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 @@ -24,6 +24,7 @@ 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; @@ -216,4 +217,41 @@ static void singleFluidIngredientIgnoresSizeAndData(final GameTestHelper helper) helper.succeed(); } + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests serialization format of sized fluid ingredients") + 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(); + + helper.assertValueEqual(nestedJson.toString(), "{\"ingredient\":{\"fluid\":\"minecraft:water\"},\"amount\":1000}", "(nested) serialized SizedFluidIngredient"); + + helper.succeed(); + } + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests matching of sized fluid ingredients") + static void sizedFluidIngredientMatching(final GameTestHelper helper) { + var sized = SizedFluidIngredient.of(Fluids.WATER, 2); + + helper.assertFalse(sized.test(new FluidStack(Fluids.LAVA, 1000)), "SizedFluidIngredient should not match incorrect fluid!"); + + helper.assertFalse(sized.test(new FluidStack(Fluids.WATER, 1)), "SizedFluidIngredient should not match fluid with less than required amount!"); + 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!"); + + helper.succeed(); + } } From 589fec3954e3701b69fe805d010465266eb6fcd6 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 7 May 2024 00:58:11 +0200 Subject: [PATCH 13/23] Update DataComponentFluidIngredient#test --- .../DataComponentFluidIngredient.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) 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 78c260a895..789e4664a9 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -24,6 +24,7 @@ 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; /** * Fluid ingredient that matches the given set of fluids, additionally performing either a @@ -31,7 +32,7 @@ *

    * Strict ingredients will only match fluid stacks that have exactly the provided components, while partial ones will * match if the stack's components contain all required components for the {@linkplain #components input predicate}. - * + * * @see DataComponentIngredient DataComponentIngredient, its item equivalent */ public class DataComponentFluidIngredient extends FluidIngredient { @@ -46,31 +47,25 @@ public class DataComponentFluidIngredient extends FluidIngredient { private final HolderSet fluids; private final DataComponentPredicate components; private final boolean strict; - private final FluidStack[] stacks; public DataComponentFluidIngredient(HolderSet fluids, DataComponentPredicate components, boolean strict) { this.fluids = fluids; this.components = components; this.strict = strict; - this.stacks = fluids.stream() - .map(i -> new FluidStack(i, 1, components.asPatch())) - .toArray(FluidStack[]::new); } @Override public boolean test(FluidStack stack) { - if (strict) { - for (FluidStack stack2 : this.stacks) { - if (FluidStack.isSameFluidSameComponents(stack, stack2)) return true; - } + if (!this.fluids.contains(stack.getFluidHolder())) { return false; - } else { - return this.fluids.contains(stack.getFluidHolder()) && this.components.test(stack); } + + return strict ? components.asPatch().equals(stack.getComponentsPatch()) + : components.test(stack); } public Stream generateStacks() { - return Stream.of(stacks); + return fluids.stream().map(i -> new FluidStack(i, FluidType.BUCKET_VOLUME, components.asPatch())); } @Override @@ -85,7 +80,7 @@ public FluidIngredientType getType() { @Override public boolean hasNoFluids() { - return stacks.length == 0; + return fluids.size() == 0; } @Override From 173300a142b7f2785eed6ed136121c8a2bf5c4ac Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 12:16:22 +0200 Subject: [PATCH 14/23] Address review comments (pt. 1) --- .../neoforge/common/NeoForgeMod.java | 1 - .../neoforged/neoforge/fluids/FluidStack.java | 2 +- .../crafting/CompoundFluidIngredient.java | 7 +- .../fluids/crafting/FluidIngredient.java | 98 ++++++++++--------- .../crafting/SingleFluidIngredient.java | 5 +- .../fluids/crafting/SizedFluidIngredient.java | 2 +- .../fluids/crafting/TagFluidIngredient.java | 2 +- 7 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index a5eab92b48..4f95f55782 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -378,7 +378,6 @@ public class NeoForgeMod { public static final DeferredHolder, IngredientType> INTERSECTION_INGREDIENT_TYPE = INGREDIENT_TYPES.register("intersection", () -> new IngredientType<>(IntersectionIngredient.CODEC)); private static final DeferredRegister> FLUID_INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES, NeoForgeVersion.MOD_ID); - // TODO: implement 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)); diff --git a/src/main/java/net/neoforged/neoforge/fluids/FluidStack.java b/src/main/java/net/neoforged/neoforge/fluids/FluidStack.java index 6209b22e03..2388ea83b8 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/FluidStack.java +++ b/src/main/java/net/neoforged/neoforge/fluids/FluidStack.java @@ -49,7 +49,7 @@ *

    Most methods in this class are adapted from {@link ItemStack}. */ public final class FluidStack implements MutableDataComponentHolder { - private static final Codec> FLUID_NON_EMPTY_CODEC = BuiltInRegistries.FLUID.holderByNameCodec().validate(holder -> { + public static final Codec> FLUID_NON_EMPTY_CODEC = BuiltInRegistries.FLUID.holderByNameCodec().validate(holder -> { return holder.is(Fluids.EMPTY.builtInRegistryHolder()) ? DataResult.error(() -> { return "Fluid must not be minecraft:empty"; }) : DataResult.success(holder); 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 443c238022..7a184761b1 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -11,6 +11,7 @@ import java.util.stream.Stream; import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.crafting.CompoundIngredient; +import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; import net.neoforged.neoforge.fluids.FluidStack; /** @@ -21,7 +22,7 @@ * @see CompoundIngredient CompoundIngredient, its item equivalent */ public final class CompoundFluidIngredient extends FluidIngredient { - public static final MapCodec CODEC = null; + public static final MapCodec CODEC = NeoForgeExtraCodecs.aliasedFieldOf(FluidIngredient.LIST_CODEC_NON_EMPTY, "children", "ingredients").xmap(CompoundFluidIngredient::new, CompoundFluidIngredient::children); private final List children; @@ -33,7 +34,7 @@ public CompoundFluidIngredient(List children) { } /** - * Creates a compound ingredient from the given list of ingredients + * Creates a compound ingredient from the given list of ingredients. */ public static FluidIngredient of(FluidIngredient... children) { if (children.length == 0) @@ -45,7 +46,7 @@ public static FluidIngredient of(FluidIngredient... children) { } /** - * Creates a compound ingredient from the given list of ingredients + * Creates a compound ingredient from the given list of ingredients. */ public static FluidIngredient of(List children) { if (children.isEmpty()) 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 c852c50111..ec246473b9 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -43,8 +43,8 @@ public abstract class FluidIngredient implements Predicate { * * @see Ingredient#MAP_CODEC_NONEMPTY */ - public static final MapCodec MAP_CODEC = makeMapCodec(); - private static final Codec MAP_CODEC_CODEC = MAP_CODEC.codec(); + 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 -> { @@ -61,7 +61,7 @@ public abstract class FluidIngredient implements Predicate { * 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 + * @see #MAP_CODEC_NONEMPTY */ public static final Codec CODEC = codec(true); /** @@ -107,14 +107,13 @@ public FluidIngredient decode(RegistryFriendlyByteBuf buf) { private FluidStack[] stacks; /** + * 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} - *

    - * This implementation simply caches the results of dissolving the ingredient using - * {@link #generateStacks()}, and should not be overridden unless you have good reason to. * * @see #generateStacks() */ - public FluidStack[] getStacks() { + public final FluidStack[] getStacks() { if (stacks == null) { stacks = generateStacks().toArray(FluidStack[]::new); } @@ -174,7 +173,7 @@ public FluidStack[] getStacks() { * * @return {@code true} if this ingredient is {@link #empty()}, {@code false} otherwise */ - public boolean isEmpty() { + public final boolean isEmpty() { return this == empty(); } @@ -196,7 +195,7 @@ public boolean isEmpty() { * @see #isEmpty() */ public boolean hasNoFluids() { - return isEmpty() || getStacks().length == 0; + return getStacks().length == 0; } @Override @@ -205,6 +204,44 @@ public boolean hasNoFluids() { @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 FluidIngredient of(FluidStack... fluids) { + return of(Arrays.stream(fluids).map(FluidStack::getFluid)); + } + + 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 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( @@ -231,6 +268,11 @@ private static MapCodec makeMapCodec() { } 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); }); } @@ -251,42 +293,4 @@ private static Codec codec(boolean allowEmpty) { return Either.right(ingredient); }); } - - // empty - public static FluidIngredient empty() { - return EmptyFluidIngredient.INSTANCE; - } - - // convenience methods - public static FluidIngredient of() { - return empty(); - } - - public static FluidIngredient of(FluidStack... fluids) { - return of(Arrays.stream(fluids).map(FluidStack::getFluid)); - } - - 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 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); - } } diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index 7b7246e053..780fc1b375 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -8,7 +8,6 @@ import com.mojang.serialization.MapCodec; import java.util.stream.Stream; import net.minecraft.core.Holder; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; import net.neoforged.neoforge.common.NeoForgeMod; @@ -19,10 +18,10 @@ * 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} + * 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 = BuiltInRegistries.FLUID.holderByNameCodec() + public static final MapCodec CODEC = FluidStack.FLUID_NON_EMPTY_CODEC .xmap(SingleFluidIngredient::new, SingleFluidIngredient::fluid).fieldOf("fluid"); private final Holder 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 6644aac63e..e16f0a6bd8 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java @@ -59,7 +59,7 @@ public final class SizedFluidIngredient { *

    */ public static final Codec FLAT_CODEC = RecordCodecBuilder.create(instance -> instance.group( - FluidIngredient.MAP_CODEC.forGetter(SizedFluidIngredient::ingredient), + FluidIngredient.MAP_CODEC_NONEMPTY.forGetter(SizedFluidIngredient::ingredient), NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount)) .apply(instance, SizedFluidIngredient::new)); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java index beb2be1817..a872dff5e3 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/TagFluidIngredient.java @@ -20,7 +20,7 @@ * 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} + * 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) From 5a0f3614b6dfbacce3d986b682779cb33ba763c3 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 12:23:26 +0200 Subject: [PATCH 15/23] Address review comments (pt. 2) --- .../neoforge/fluids/crafting/DifferenceFluidIngredient.java | 4 ++-- .../neoforge/fluids/crafting/IntersectionFluidIngredient.java | 2 +- .../neoforge/fluids/crafting/SizedFluidIngredient.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 199ccd4584..fa09f3b170 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DifferenceFluidIngredient.java @@ -23,8 +23,8 @@ public final class DifferenceFluidIngredient extends FluidIngredient { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( builder -> builder .group( - FluidIngredient.CODEC.fieldOf("base").forGetter(DifferenceFluidIngredient::base), - FluidIngredient.CODEC.fieldOf("subtracted").forGetter(DifferenceFluidIngredient::subtracted)) + FluidIngredient.CODEC_NON_EMPTY.fieldOf("base").forGetter(DifferenceFluidIngredient::base), + FluidIngredient.CODEC_NON_EMPTY.fieldOf("subtracted").forGetter(DifferenceFluidIngredient::subtracted)) .apply(builder, DifferenceFluidIngredient::new)); private final FluidIngredient base; private final FluidIngredient subtracted; 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 2c6687dacc..658e2c9f7d 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java @@ -28,7 +28,7 @@ public IntersectionFluidIngredient(List children) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( builder -> builder .group( - FluidIngredient.LIST_CODEC.fieldOf("children").forGetter(IntersectionFluidIngredient::children)) + FluidIngredient.LIST_CODEC_NON_EMPTY.fieldOf("children").forGetter(IntersectionFluidIngredient::children)) .apply(builder, IntersectionFluidIngredient::new)); private final List children; 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 e16f0a6bd8..4e2306a2c5 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java @@ -78,7 +78,7 @@ public final class SizedFluidIngredient { * } */ public static final Codec NESTED_CODEC = RecordCodecBuilder.create(instance -> instance.group( - FluidIngredient.CODEC.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient), + FluidIngredient.CODEC_NON_EMPTY.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient), NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount)) .apply(instance, SizedFluidIngredient::new)); From eb9f01eaba914032126ff685835d1a9d8d106d0e Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 12:39:40 +0200 Subject: [PATCH 16/23] Fix incorrectly inverted empty check --- .../net/neoforged/neoforge/fluids/crafting/FluidIngredient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ec246473b9..bb4eeb7089 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -269,7 +269,7 @@ private static MapCodec makeMapCodec() { return Either.left(ingredient); }).validate(ingredient -> { - if (!ingredient.isEmpty()) { + if (ingredient.isEmpty()) { return DataResult.error(() -> "Cannot serialize empty fluid ingredient using the map codec"); } return DataResult.success(ingredient); From b7d31dbd381840a0e95776064c93b1905bd2dbcd Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 12:46:10 +0200 Subject: [PATCH 17/23] Make hasNoFluids final --- .../crafting/CompoundFluidIngredient.java | 5 ----- .../crafting/DataComponentFluidIngredient.java | 5 ----- .../fluids/crafting/EmptyFluidIngredient.java | 5 ----- .../fluids/crafting/FluidIngredient.java | 17 +++++------------ .../fluids/crafting/SingleFluidIngredient.java | 5 ----- 5 files changed, 5 insertions(+), 32 deletions(-) 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 7a184761b1..f2c551a66e 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/CompoundFluidIngredient.java @@ -96,11 +96,6 @@ public int hashCode() { return Objects.hash(children); } - @Override - public boolean hasNoFluids() { - return children.isEmpty() || children.stream().allMatch(FluidIngredient::hasNoFluids); - } - @Override public boolean equals(Object obj) { if (this == obj) return true; 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 789e4664a9..d224c81f33 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -78,11 +78,6 @@ public FluidIngredientType getType() { return NeoForgeMod.DATA_COMPONENT_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean hasNoFluids() { - return fluids.size() == 0; - } - @Override public int hashCode() { return Objects.hash(fluids, components, strict); diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java index 72901bfe86..d8a958c7c8 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/EmptyFluidIngredient.java @@ -47,11 +47,6 @@ public FluidIngredientType getType() { return NeoForgeMod.EMPTY_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean hasNoFluids() { - return true; - } - @Override public int hashCode() { return 0; 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 bb4eeb7089..cfff029804 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -178,23 +178,16 @@ public final boolean isEmpty() { } /** - * Checks if this ingredient matches no fluids, i.e. either: - *

      - *
    • is equal to {@link #empty()},
    • - *
    • resolves to an empty list of fluids
    • - *
    + * Checks if this ingredient matches no fluids, i.e. if its + * list of {@linkplain #getStacks() matching fluids} is empty. *

    - * Note that this method explicitly has the potential to resolve - * the ingredient; if this is not desired, you will need to check for - * emptiness another way! + * 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 - * @implNote If it is possible for your ingredient to return a "hint" on - * whether it is empty without resolving, you should override this method - * with a custom implementation. * @see #isEmpty() */ - public boolean hasNoFluids() { + public final boolean hasNoFluids() { return getStacks().length == 0; } diff --git a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java index 780fc1b375..e2bb3ae250 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SingleFluidIngredient.java @@ -53,11 +53,6 @@ public FluidIngredientType getType() { return NeoForgeMod.SINGLE_FLUID_INGREDIENT_TYPE.get(); } - @Override - public boolean hasNoFluids() { - return fluid.value().isSame(Fluids.EMPTY); - } - @Override public int hashCode() { return this.fluid().value().hashCode(); From 15968a2cf18e18af1c489335d47b331d3f7aa860 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 12:50:31 +0200 Subject: [PATCH 18/23] Revert "Update DataComponentFluidIngredient#test" This reverts commit 589fec39 Reason: Since FluidStacks similar to their item counterparts may contain default components later down the line, simply checking if the component patch matches the patch on the stack is not enough for strict component ingredients --- .../crafting/DataComponentFluidIngredient.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 d224c81f33..3452976a56 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -24,7 +24,6 @@ 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; /** * Fluid ingredient that matches the given set of fluids, additionally performing either a @@ -47,25 +46,31 @@ public class DataComponentFluidIngredient extends FluidIngredient { private final HolderSet fluids; private final DataComponentPredicate components; private final boolean strict; + private final FluidStack[] stacks; public DataComponentFluidIngredient(HolderSet fluids, DataComponentPredicate components, boolean strict) { this.fluids = fluids; this.components = components; this.strict = strict; + this.stacks = fluids.stream() + .map(i -> new FluidStack(i, 1, components.asPatch())) + .toArray(FluidStack[]::new); } @Override public boolean test(FluidStack stack) { - if (!this.fluids.contains(stack.getFluidHolder())) { + if (strict) { + for (FluidStack stack2 : this.stacks) { + if (FluidStack.isSameFluidSameComponents(stack, stack2)) return true; + } return false; + } else { + return this.fluids.contains(stack.getFluidHolder()) && this.components.test(stack); } - - return strict ? components.asPatch().equals(stack.getComponentsPatch()) - : components.test(stack); } public Stream generateStacks() { - return fluids.stream().map(i -> new FluidStack(i, FluidType.BUCKET_VOLUME, components.asPatch())); + return Stream.of(stacks); } @Override From 6abe83d84fea0588293092e23f9f947094ddc5a0 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 10 May 2024 18:13:24 +0200 Subject: [PATCH 19/23] Fix display stack amount in DataComponentFluidIngredient... again --- .../neoforge/fluids/crafting/DataComponentFluidIngredient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3452976a56..c3ebdcdaf3 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/DataComponentFluidIngredient.java @@ -24,6 +24,7 @@ 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; /** * Fluid ingredient that matches the given set of fluids, additionally performing either a @@ -53,7 +54,7 @@ public DataComponentFluidIngredient(HolderSet fluids, DataComponentPredic this.components = components; this.strict = strict; this.stacks = fluids.stream() - .map(i -> new FluidStack(i, 1, components.asPatch())) + .map(i -> new FluidStack(i, FluidType.BUCKET_VOLUME, components.asPatch())) .toArray(FluidStack[]::new); } From f58a13f6d1fc8c4ac9eb56ba214798f2307b5461 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 16 May 2024 21:14:09 +0200 Subject: [PATCH 20/23] More javadoc for FluidIngredient --- .../fluids/crafting/FluidIngredient.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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 cfff029804..e2a2e8bd8e 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/FluidIngredient.java @@ -27,6 +27,18 @@ import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.registries.NeoForgeRegistries; +/** + * 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 + * for e.g. display purposes. + *

    + * The most common use for fluid ingredients is found in modded recipe inputs, + * for example crafting mechanics accepting a list of different fluids; + * since those mechanics even rely on a certain amount of a fluid being present, + * and fluid ingredients inherently do not hold any information with respect to fluid amount; + * 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" @@ -155,6 +167,12 @@ public final FluidStack[] getStacks() { */ protected abstract Stream generateStacks(); + /** + * Returns whether this fluid ingredient always requires {@linkplain #test direct stack testing}. + * + * @return {@code true} if this ingredient ignores NBT data when matching stacks, {@code false} otherwise + * @see ICustomIngredient#isSimple() + */ public abstract boolean isSimple(); /** @@ -272,10 +290,10 @@ private static MapCodec makeMapCodec() { 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 CompoundIngredient instance + // [{...}, {...}] is turned into a CompoundFluidIngredient instance .xmap(either -> either.map(CompoundFluidIngredient::of, i -> i), ingredient -> { - // serialize CompoundIngredient instances as an array over their children + // serialize CompoundFluidIngredient instances as an array over their children if (ingredient instanceof CompoundFluidIngredient compound) { return Either.left(compound.children()); } else if (ingredient.isEmpty()) { From 13eb5dc9f85293b7a3dff18417cf7ab5d253bc62 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 16 May 2024 21:14:17 +0200 Subject: [PATCH 21/23] Add equals / hashCode to SizedFluidIngredient --- .../fluids/crafting/SizedFluidIngredient.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 4e2306a2c5..daadd12439 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/SizedFluidIngredient.java @@ -7,6 +7,7 @@ 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; @@ -40,7 +41,7 @@ public final class SizedFluidIngredient { * "amount": 250 * } * } - * + * *

    *

    * Compound fluid ingredients are always serialized using the map codec, i.e. @@ -55,7 +56,7 @@ public final class SizedFluidIngredient { * "amount": 500 * } * } - * + * *

    */ public static final Codec FLAT_CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -152,6 +153,18 @@ public FluidStack[] getFluids() { return cachedStacks; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SizedFluidIngredient other)) return false; + return amount == other.amount && ingredient.equals(other.ingredient); + } + + @Override + public int hashCode() { + return Objects.hash(ingredient, amount); + } + @Override public String toString() { return amount + "x " + ingredient; From 61ed03695000481c002abe176595fea300170a39 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 17 May 2024 00:52:48 +0200 Subject: [PATCH 22/23] FIx equals on IntersectionFluidIngredient --- .../neoforge/fluids/crafting/IntersectionFluidIngredient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 658e2c9f7d..873da9888d 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java @@ -90,6 +90,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return this == obj; + if(this == obj) return true; + return obj instanceof IntersectionFluidIngredient other && children.equals(other.children); } } From 030f607d551ff17e1e4ccedac1a9c57ffec8633c Mon Sep 17 00:00:00 2001 From: max Date: Fri, 17 May 2024 01:03:07 +0200 Subject: [PATCH 23/23] Got trolled by spotless --- .../neoforge/fluids/crafting/IntersectionFluidIngredient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 873da9888d..d53df462eb 100644 --- a/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java +++ b/src/main/java/net/neoforged/neoforge/fluids/crafting/IntersectionFluidIngredient.java @@ -90,7 +90,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if(this == obj) return true; + if (this == obj) return true; return obj instanceof IntersectionFluidIngredient other && children.equals(other.children); } }