Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.20.5] Add a Fluid Ingredient system as an analogue to vanilla's Ingredient #789

Merged
merged 28 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3aa6cd5
Rework fluid ingredients to work more similar to the new custom ingre…
MaxNeedsSnacks Apr 28, 2024
2517b8c
Merge branch 'refs/heads/1.20.x' into feature/fluid-ingredients
MaxNeedsSnacks Apr 28, 2024
69e7caf
Add StreamCodec for FluidIngredient, convenience of(...) methods
MaxNeedsSnacks Apr 28, 2024
3846af2
Add SizedFluidIngredient
MaxNeedsSnacks Apr 28, 2024
7dd15a5
Apply formatting
MaxNeedsSnacks Apr 28, 2024
3f78b1e
Add partial Javadoc to FluidIngredient
MaxNeedsSnacks Apr 28, 2024
9e4c4da
Add FluidIngredient#hasNoFluids, further Javadoc
MaxNeedsSnacks Apr 28, 2024
6c0ff16
Fix equality check on SingleFluidIngredient
MaxNeedsSnacks Apr 28, 2024
52ef956
Add some first tests for fluid ingredients
MaxNeedsSnacks Apr 28, 2024
7e2106d
Add more javadoc to fluid ingredients
MaxNeedsSnacks Apr 28, 2024
ff97f8f
Add more tests
MaxNeedsSnacks Apr 28, 2024
e6949fa
Change SingleFluidIngredient to take in a fluid holder instead of a s…
MaxNeedsSnacks Apr 29, 2024
7fb4f0c
Add tests for sized fluid ingredients
MaxNeedsSnacks Apr 29, 2024
cfea30f
Merge remote-tracking branch 'refs/remotes/origin/1.20.x' into featur…
MaxNeedsSnacks May 2, 2024
359a811
Merge branch 'refs/heads/1.20.x' into feature/fluid-ingredients
MaxNeedsSnacks May 6, 2024
589fec3
Update DataComponentFluidIngredient#test
MaxNeedsSnacks May 6, 2024
173300a
Address review comments (pt. 1)
MaxNeedsSnacks May 10, 2024
5a0f361
Address review comments (pt. 2)
MaxNeedsSnacks May 10, 2024
ceae8ea
Merge branch 'refs/heads/1.20.x' into feature/fluid-ingredients
MaxNeedsSnacks May 10, 2024
eb9f01e
Fix incorrectly inverted empty check
MaxNeedsSnacks May 10, 2024
b7d31db
Make hasNoFluids final
MaxNeedsSnacks May 10, 2024
15968a2
Revert "Update DataComponentFluidIngredient#test"
MaxNeedsSnacks May 10, 2024
6abe83d
Fix display stack amount in DataComponentFluidIngredient... again
MaxNeedsSnacks May 10, 2024
c95431f
Merge remote-tracking branch 'refs/remotes/origin/1.20.x' into featur…
MaxNeedsSnacks May 16, 2024
f58a13f
More javadoc for FluidIngredient
MaxNeedsSnacks May 16, 2024
13eb5dc
Add equals / hashCode to SizedFluidIngredient
MaxNeedsSnacks May 16, 2024
61ed036
FIx equals on IntersectionFluidIngredient
MaxNeedsSnacks May 16, 2024
030f607
Got trolled by spotless
MaxNeedsSnacks May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,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.network.DualStackUtils;
Expand Down Expand Up @@ -369,6 +377,15 @@ public class NeoForgeMod {
public static final DeferredHolder<IngredientType<?>, IngredientType<DifferenceIngredient>> DIFFERENCE_INGREDIENT_TYPE = INGREDIENT_TYPES.register("difference", () -> new IngredientType<>(DifferenceIngredient.CODEC));
public static final DeferredHolder<IngredientType<?>, IngredientType<IntersectionIngredient>> INTERSECTION_INGREDIENT_TYPE = INGREDIENT_TYPES.register("intersection", () -> new IngredientType<>(IntersectionIngredient.CODEC));

private static final DeferredRegister<FluidIngredientType<?>> FLUID_INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES, NeoForgeVersion.MOD_ID);
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<SingleFluidIngredient>> SINGLE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("single", () -> new FluidIngredientType<>(SingleFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<TagFluidIngredient>> TAG_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("tag", () -> new FluidIngredientType<>(TagFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<EmptyFluidIngredient>> EMPTY_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("empty", () -> new FluidIngredientType<>(EmptyFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<CompoundFluidIngredient>> COMPOUND_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("compound", () -> new FluidIngredientType<>(CompoundFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<DataComponentFluidIngredient>> DATA_COMPONENT_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("components", () -> new FluidIngredientType<>(DataComponentFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<DifferenceFluidIngredient>> DIFFERENCE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("difference", () -> new FluidIngredientType<>(DifferenceFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<IntersectionFluidIngredient>> INTERSECTION_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("intersection", () -> new FluidIngredientType<>(IntersectionFluidIngredient.CODEC));

private static final DeferredRegister<MapCodec<? extends ICondition>> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, NeoForgeVersion.MOD_ID);
public static final DeferredHolder<MapCodec<? extends ICondition>, MapCodec<AndCondition>> AND_CONDITION = CONDITION_CODECS.register("and", () -> AndCondition.CODEC);
public static final DeferredHolder<MapCodec<? extends ICondition>, MapCodec<FalseCondition>> FALSE_CONDITION = CONDITION_CODECS.register("false", () -> FalseCondition.CODEC);
Expand Down Expand Up @@ -534,7 +551,7 @@ public ResourceLocation getFlowingTexture() {
* Used in place of {@link DamageSources#magic()} for damage dealt by {@link MobEffects#POISON}.
* <p>
* May also be used by mods providing poison-like effects.
*
*
* @see {@link Tags.DamageTypes#IS_POISON}
*/
public static final ResourceKey<DamageType> POISON_DAMAGE = ResourceKey.create(Registries.DAMAGE_TYPE, new ResourceLocation(NeoForgeVersion.MOD_ID, "poison"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
* <p>Most methods in this class are adapted from {@link ItemStack}.
*/
public final class FluidStack implements MutableDataComponentHolder {
private static final Codec<Holder<Fluid>> FLUID_NON_EMPTY_CODEC = BuiltInRegistries.FLUID.holderByNameCodec().validate(holder -> {
public static final Codec<Holder<Fluid>> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.common.crafting.CompoundIngredient;
import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs;
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<CompoundFluidIngredient> CODEC = NeoForgeExtraCodecs.aliasedFieldOf(FluidIngredient.LIST_CODEC_NON_EMPTY, "children", "ingredients").xmap(CompoundFluidIngredient::new, CompoundFluidIngredient::children);

private final List<FluidIngredient> children;

public CompoundFluidIngredient(List<? extends FluidIngredient> children) {
if (children.isEmpty()) {
throw new IllegalArgumentException("Compound fluid ingredient must have at least one child");
}
this.children = List.copyOf(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<FluidIngredient> children) {
if (children.isEmpty())
return FluidIngredient.empty();
if (children.size() == 1)
return children.getFirst();

return new CompoundFluidIngredient(children);
}

public static FluidIngredient of(Stream<FluidIngredient> stream) {
return of(stream.toList());
}

@Override
public Stream<FluidStack> 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 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<FluidIngredient> children() {
return children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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.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
* {@link DataComponentFluidIngredient#isStrict() strict} or partial test on the FluidStack's components.
* <p>
* Strict ingredients will only match fluid stacks that have <b>exactly</b> 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<DataComponentFluidIngredient> 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<Fluid> fluids;
private final DataComponentPredicate components;
private final boolean strict;
private final FluidStack[] stacks;

public DataComponentFluidIngredient(HolderSet<Fluid> fluids, DataComponentPredicate components, boolean strict) {
this.fluids = fluids;
this.components = components;
this.strict = strict;
this.stacks = fluids.stream()
.map(i -> new FluidStack(i, FluidType.BUCKET_VOLUME, 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);
}
}
MaxNeedsSnacks marked this conversation as resolved.
Show resolved Hide resolved

public Stream<FluidStack> 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 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<Fluid> 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 <T> FluidIngredient of(boolean strict, DataComponentType<? super T> 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 <T> FluidIngredient of(boolean strict, Supplier<? extends DataComponentType<? super T>> 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<Fluid>... 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<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, DataComponentPredicate predicate, Holder<Fluid>... 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<Fluid> fluids) {
return new DataComponentFluidIngredient(fluids, predicate, strict);
}
}
Loading
Loading