Skip to content

Commit

Permalink
Add a Fluid Ingredient system as an analogue to vanilla's Ingredient (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxNeedsSnacks authored May 18, 2024
1 parent b1c3fe0 commit 66197fe
Show file tree
Hide file tree
Showing 17 changed files with 1,481 additions and 2 deletions.
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);
}
}

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

0 comments on commit 66197fe

Please sign in to comment.