Skip to content

Commit 91a64fd

Browse files
authored
Support FluidIngredients in machine inputs (#999)
1 parent 3203c82 commit 91a64fd

File tree

12 files changed

+183
-26
lines changed

12 files changed

+183
-26
lines changed

docs/ADDING_RECIPES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ ServerEvents.recipes(event => {
2828
// add all the inputs and outputs:
2929
.itemIn("64x #minecraft:logs_that_burn")
3030
.itemOut("64x minecraft:charcoal")
31-
.fluidIn("modern_industrialization:oxygen", 1000)
32-
.fluidOut("modern_industrialization:creosote", 5000)
31+
.fluidIn("1000x modern_industrialization:oxygen")
32+
.fluidOut("5000x modern_industrialization:creosote")
3333
})
3434
```
3535

src/client/java/aztech/modern_industrialization/compat/viewer/abstraction/ViewerCategory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import net.minecraft.world.item.crafting.Ingredient;
3939
import net.minecraft.world.item.crafting.RecipeManager;
4040
import net.minecraft.world.level.ItemLike;
41+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
4142
import org.jetbrains.annotations.Nullable;
4243

4344
/**
@@ -111,6 +112,8 @@ public interface SlotBuilder {
111112

112113
SlotBuilder fluid(FluidVariant fluid, long amount, float probability);
113114

115+
SlotBuilder fluid(FluidIngredient ingredient, long amount, float probability);
116+
114117
default SlotBuilder item(ItemStack stack) {
115118
return item(stack, 1);
116119
}

src/client/java/aztech/modern_industrialization/compat/viewer/impl/emi/ViewerCategoryEmi.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@
3939
import java.util.ArrayList;
4040
import java.util.List;
4141
import java.util.function.Consumer;
42+
import java.util.stream.Stream;
4243
import net.minecraft.client.Minecraft;
4344
import net.minecraft.client.gui.GuiGraphics;
4445
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
4546
import net.minecraft.network.chat.Component;
4647
import net.minecraft.resources.ResourceLocation;
4748
import net.minecraft.world.item.ItemStack;
4849
import net.minecraft.world.item.crafting.Ingredient;
50+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
4951
import org.jetbrains.annotations.Nullable;
5052

5153
class ViewerCategoryEmi<D> extends EmiRecipeCategory {
@@ -165,6 +167,19 @@ public ViewerCategory.SlotBuilder fluid(FluidVariant fluid, long amount, float p
165167
return this;
166168
}
167169

170+
@Override
171+
public ViewerCategory.SlotBuilder fluid(FluidIngredient ingredient, long amount, float probability) {
172+
isFluid = true;
173+
hasBackground = false;
174+
ing = EmiIngredient.of(
175+
Stream.of(ingredient.getStacks())
176+
.map(fs -> EmiStack.of(fs.getFluid(), fs.getComponentsPatch()))
177+
.toList(),
178+
amount);
179+
processProbability(probability);
180+
return this;
181+
}
182+
168183
private void processProbability(float probability) {
169184
if (probability == 0) {
170185
markCatalyst();

src/client/java/aztech/modern_industrialization/compat/viewer/impl/jei/ViewerCategoryJei.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import net.minecraft.world.item.ItemStack;
5252
import net.minecraft.world.item.crafting.Ingredient;
5353
import net.neoforged.neoforge.fluids.FluidType;
54+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
5455
import org.jetbrains.annotations.Nullable;
5556

5657
class ViewerCategoryJei<D> extends AbstractRecipeCategory<D> {
@@ -135,6 +136,18 @@ public ViewerCategory.SlotBuilder fluid(FluidVariant fluid, long amount, float p
135136
return this;
136137
}
137138

139+
@Override
140+
public ViewerCategory.SlotBuilder fluid(FluidIngredient ingredient, long amount, float probability) {
141+
for (var fs : ingredient.getStacks()) {
142+
slotBuilder.addFluidStack(fs.getFluid(), amount, fs.getComponentsPatch());
143+
}
144+
// This call displays the full sprite (instead of JEI's partial rendering)
145+
slotBuilder.setFluidRenderer(1, false, 16, 16);
146+
addProbability(slotBuilder, probability);
147+
slotBuilder.setBackground(fluidSlot, -1, -1);
148+
return this;
149+
}
150+
138151
private ViewerCategory.SlotBuilder items(List<ItemStack> stacks, float probability) {
139152
slotBuilder.addItemStacks(stacks);
140153
addProbability(slotBuilder, probability);

src/client/java/aztech/modern_industrialization/compat/viewer/impl/rei/ViewerCategoryRei.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import net.minecraft.resources.ResourceLocation;
5353
import net.minecraft.world.item.ItemStack;
5454
import net.minecraft.world.item.crafting.Ingredient;
55+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
5556
import org.jetbrains.annotations.Nullable;
5657

5758
class ViewerCategoryRei<D> implements DisplayCategory<ViewerCategoryRei<D>.ViewerDisplay> {
@@ -173,6 +174,16 @@ public ViewerCategory.SlotBuilder fluid(FluidVariant fluid, long amount, float p
173174
return this;
174175
}
175176

177+
@Override
178+
public ViewerCategory.SlotBuilder fluid(FluidIngredient ingredient, long amount, float probability) {
179+
isFluid = true;
180+
hasBackground = false;
181+
for (var fs : ingredient.getStacks()) {
182+
ing.add(ReiSlotUtil.createFluidEntryStack(FluidVariant.of(fs), amount, probability, input));
183+
}
184+
return this;
185+
}
186+
176187
private ViewerCategory.SlotBuilder items(List<ItemStack> stacks, float probability) {
177188
for (var stack : stacks) {
178189
ing.add(EntryStacks.of(stack).tooltip(ReiSlotUtil.getProbabilitySetting(probability, input)));

src/client/java/aztech/modern_industrialization/compat/viewer/usage/MachineCategory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ private void addFluidInputs(LayoutBuilder builder, MachineRecipe recipe) {
150150

151151
if (i < recipe.fluidInputs.size()) {
152152
var input = recipe.fluidInputs.get(i);
153-
slot.fluid(FluidVariant.of(input.fluid()), input.amount(), input.probability());
153+
slot.fluid(input.fluid(), input.amount(), input.probability());
154154
} else {
155155
slot.variant(FluidVariant.blank());
156156
}

src/main/java/aztech/modern_industrialization/compat/kubejs/recipe/FluidInputComponent.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import aztech.modern_industrialization.MI;
2727
import aztech.modern_industrialization.machines.recipe.MachineRecipe;
28+
import dev.latvian.mods.kubejs.core.RegistryObjectKJS;
2829
import dev.latvian.mods.kubejs.fluid.FluidWrapper;
2930
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
3031
import dev.latvian.mods.kubejs.recipe.component.SimpleRecipeComponent;
@@ -33,8 +34,6 @@
3334
import dev.latvian.mods.kubejs.recipe.match.ReplacementMatchInfo;
3435
import dev.latvian.mods.kubejs.util.RegistryAccessContainer;
3536
import dev.latvian.mods.rhino.Context;
36-
import net.minecraft.world.level.material.Fluids;
37-
import net.neoforged.neoforge.fluids.FluidStack;
3837

3938
public class FluidInputComponent extends SimpleRecipeComponent<MachineRecipe.FluidInput> {
4039
public static final FluidInputComponent FLUID_INPUT = new FluidInputComponent();
@@ -45,35 +44,35 @@ public FluidInputComponent() {
4544

4645
@Override
4746
public MachineRecipe.FluidInput wrap(Context cx, KubeRecipe recipe, Object from) {
48-
var fs = FluidWrapper.wrap(RegistryAccessContainer.of(cx), from);
49-
return new MachineRecipe.FluidInput(fs.getFluid(), fs.getAmount(), 1);
47+
var fs = FluidWrapper.wrapSizedIngredient(RegistryAccessContainer.of(cx), from);
48+
return new MachineRecipe.FluidInput(fs.ingredient(), fs.amount(), 1);
5049
}
5150

5251
@Override
5352
public boolean matches(Context cx, KubeRecipe recipe, MachineRecipe.FluidInput value, ReplacementMatchInfo match) {
54-
return match.match() instanceof FluidMatch m && m.matches(cx, new FluidStack(value.fluid(), 1), match.exact());
53+
return match.match() instanceof FluidMatch m && m.matches(cx, value.fluid(), match.exact());
5554
}
5655

5756
@Override
5857
public MachineRecipe.FluidInput replace(Context cx, KubeRecipe recipe, MachineRecipe.FluidInput original, ReplacementMatchInfo match,
5958
Object with) {
6059
if (matches(cx, recipe, original, match)) {
61-
var fs = FluidWrapper.wrap(RegistryAccessContainer.of(cx), with);
62-
return new MachineRecipe.FluidInput(fs.getFluid(), original.amount(), original.probability());
60+
var fi = FluidWrapper.wrapIngredient(RegistryAccessContainer.of(cx), with);
61+
return new MachineRecipe.FluidInput(fi, original.amount(), original.probability());
6362
} else {
6463
return original;
6564
}
6665
}
6766

6867
@Override
6968
public boolean isEmpty(MachineRecipe.FluidInput value) {
70-
return value.amount() <= 0 || value.fluid().isSame(Fluids.EMPTY);
69+
return value.amount() <= 0 || value.fluid().isEmpty();
7170
}
7271

7372
@Override
7473
public void buildUniqueId(UniqueIdBuilder builder, MachineRecipe.FluidInput value) {
7574
if (!isEmpty(value)) {
76-
builder.append(value.fluid().builtInRegistryHolder().getKey().location());
75+
builder.append(((RegistryObjectKJS) value.fluid().getStacks()[0].getFluid()).kjs$getIdLocation());
7776
}
7877
}
7978
}

src/main/java/aztech/modern_industrialization/compat/kubejs/recipe/MachineKubeRecipe.java

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,23 @@
2727
import aztech.modern_industrialization.machines.recipe.MachineRecipe;
2828
import aztech.modern_industrialization.machines.recipe.condition.MachineProcessCondition;
2929
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.item.ItemVariant;
30+
import com.mojang.logging.LogUtils;
3031
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
3132
import dev.latvian.mods.kubejs.recipe.RecipeKey;
3233
import dev.latvian.mods.kubejs.recipe.schema.KubeRecipeFactory;
3334
import java.util.ArrayList;
3435
import java.util.List;
36+
import net.minecraft.core.registries.BuiltInRegistries;
3537
import net.minecraft.world.item.ItemStack;
3638
import net.minecraft.world.level.material.Fluid;
3739
import net.neoforged.neoforge.common.crafting.SizedIngredient;
40+
import net.neoforged.neoforge.fluids.FluidStack;
41+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
42+
import net.neoforged.neoforge.fluids.crafting.SizedFluidIngredient;
43+
import org.slf4j.Logger;
3844

3945
public class MachineKubeRecipe extends KubeRecipe implements ProcessConditionHelper {
46+
private static final Logger LOGGER = LogUtils.getLogger();
4047
public static final KubeRecipeFactory FACTORY = new KubeRecipeFactory(MI.id("machine"), MachineKubeRecipe.class, MachineKubeRecipe::new);
4148

4249
private <T> MachineKubeRecipe addToList(RecipeKey<List<T>> key, T element) {
@@ -64,19 +71,73 @@ public MachineKubeRecipe itemOut(ItemStack output, float chance) {
6471
return addToList(MachineRecipeSchema.ITEM_OUTPUTS, new MachineRecipe.ItemOutput(ItemVariant.of(output), output.getCount(), chance));
6572
}
6673

67-
public MachineKubeRecipe fluidIn(Fluid fluid, long mbs) {
68-
return fluidIn(fluid, mbs, 1F);
74+
public MachineKubeRecipe fluidIn(SizedFluidIngredient ingredient) {
75+
return fluidInInternal(ingredient, 1F); // skip chance == 1 check
6976
}
7077

71-
public MachineKubeRecipe fluidIn(Fluid fluid, long mbs, float probability) {
72-
return addToList(MachineRecipeSchema.FLUID_INPUTS, new MachineRecipe.FluidInput(fluid, mbs, probability));
78+
public MachineKubeRecipe fluidIn(SizedFluidIngredient ingredient, float chance) {
79+
// TODO: remove this mess once we don't need to worry about backwards compat anymore.
80+
if ((ingredient.amount() == 1 || ingredient.amount() == 1000) && chance > 1) {
81+
LOGGER.warn("fluidIn with separate fluid and amount is deprecated. Use fluidIn(\"%dx %s\") notation instead.".formatted(
82+
(int) chance,
83+
BuiltInRegistries.FLUID.getKey(ingredient.ingredient().getStacks()[0].getFluid())));
84+
return fluidIn(new SizedFluidIngredient(ingredient.ingredient(), (int) chance));
85+
} else if (ingredient.amount() == 1000 && chance == 1) {
86+
throw new IllegalArgumentException(
87+
"fluidIn(\"some_fluid\", 1) is ambiguous. Use either fluidIn(\"1x some_fluid\") or fluidIn(\"some_fluid\") depending on what you meant.");
88+
}
89+
90+
return fluidInInternal(ingredient, chance);
91+
}
92+
93+
private MachineKubeRecipe fluidInInternal(SizedFluidIngredient ingredient, float chance) {
94+
if (chance > 1) {
95+
throw new IllegalArgumentException("Fluid input chance must be between 0 and 1. It is " + chance);
96+
}
97+
return addToList(MachineRecipeSchema.FLUID_INPUTS, new MachineRecipe.FluidInput(ingredient.ingredient(), ingredient.amount(), chance));
98+
}
99+
100+
public MachineKubeRecipe fluidOut(FluidStack output) {
101+
return fluidOutInternal(output, 1F); // skip chance == 1 check
102+
}
103+
104+
public MachineKubeRecipe fluidOut(FluidStack output, float chance) {
105+
// TODO: remove this mess once we don't need to worry about backwards compat anymore.
106+
if ((output.getAmount() == 1 || output.getAmount() == 1000) && chance > 1) {
107+
LOGGER.warn("fluidOut with separate fluid and amount is deprecated. Use fluidOut(\"%dx %s\") notation instead.".formatted(
108+
(int) chance,
109+
BuiltInRegistries.FLUID.getKey(output.getFluid())));
110+
return fluidOut(output.copyWithAmount((int) chance));
111+
} else if (output.getAmount() == 1000 && chance == 1) {
112+
throw new IllegalArgumentException(
113+
"fluidOut(\"some_fluid\", 1) is ambiguous. Use either fluidOut(\"1x some_fluid\") or fluidOut(\"some_fluid\") depending on what you meant.");
114+
}
115+
116+
return fluidOutInternal(output, chance);
117+
}
118+
119+
private MachineKubeRecipe fluidOutInternal(FluidStack output, float chance) {
120+
if (!output.isComponentsPatchEmpty()) {
121+
throw new IllegalArgumentException("FluidStack components are not supported in machine recipe outputs.");
122+
}
123+
return addToList(MachineRecipeSchema.FLUID_OUTPUTS, new MachineRecipe.FluidOutput(output.getFluid(), output.getAmount(), chance));
73124
}
74125

75-
public MachineKubeRecipe fluidOut(Fluid fluid, long mbs) {
76-
return fluidOut(fluid, mbs, 1F);
126+
@Deprecated(forRemoval = true)
127+
public MachineKubeRecipe fluidIn(FluidIngredient fluid, long mbs, float probability) {
128+
LOGGER.warn("fluidIn with separate fluid and amount is deprecated. Use fluidIn(\"%dx %s\", %f) notation instead.".formatted(
129+
mbs,
130+
BuiltInRegistries.FLUID.getKey(fluid.getStacks()[0].getFluid()),
131+
probability));
132+
return fluidIn(new SizedFluidIngredient(fluid, (int) mbs), probability);
77133
}
78134

135+
@Deprecated(forRemoval = true)
79136
public MachineKubeRecipe fluidOut(Fluid fluid, long mbs, float probability) {
137+
LOGGER.warn("fluidOut with separate fluid and amount is deprecated. Use fluidOut(\"%dx %s\", %f) notation instead.".formatted(
138+
mbs,
139+
BuiltInRegistries.FLUID.getKey(fluid),
140+
probability));
80141
return addToList(MachineRecipeSchema.FLUID_OUTPUTS, new MachineRecipe.FluidOutput(fluid, mbs, probability));
81142
}
82143

src/main/java/aztech/modern_industrialization/machines/components/CrafterComponent.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
import net.minecraft.world.item.ItemStack;
5959
import net.minecraft.world.item.crafting.RecipeHolder;
6060
import net.minecraft.world.level.material.Fluid;
61+
import net.neoforged.neoforge.fluids.FluidStack;
62+
import net.neoforged.neoforge.fluids.FluidUtil;
63+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
6164
import org.jetbrains.annotations.Nullable;
6265

6366
public class CrafterComponent implements IComponent.ServerOnly, CrafterAccess {
@@ -474,7 +477,7 @@ protected boolean takeFluidInputs(MachineRecipe recipe, boolean simulate) {
474477
}
475478
long remainingAmount = input.amount();
476479
for (ConfigurableFluidStack stack : stacks) {
477-
if (stack.getResource().equals(FluidVariant.of(input.fluid()))) {
480+
if (fluidIngredientMatch(stack.getResource(), input.fluid())) {
478481
long taken = Math.min(remainingAmount, stack.getAmount());
479482
if (taken > 0 && !simulate) {
480483
behavior.getStatsOrDummy().addUsedFluids(stack.getResource().getFluid(), taken);
@@ -491,6 +494,17 @@ protected boolean takeFluidInputs(MachineRecipe recipe, boolean simulate) {
491494
return ok;
492495
}
493496

497+
private boolean fluidIngredientMatch(FluidVariant resource, FluidIngredient ingredient) {
498+
if (ingredient.isSimple()) {
499+
for (var stack : ingredient.getStacks()) {
500+
return resource.equals(FluidVariant.of(stack.getFluid()));
501+
}
502+
return false;
503+
} else {
504+
return ingredient.test(resource.toStack(1));
505+
}
506+
}
507+
494508
protected boolean putItemOutputs(MachineRecipe recipe, boolean simulate, boolean toggleLock) {
495509
List<ConfigurableItemStack> baseList = inventory.getItemOutputs();
496510
List<ConfigurableItemStack> stacks = simulate ? ConfigurableItemStack.copyList(baseList) : baseList;
@@ -682,10 +696,38 @@ public void lockRecipe(ResourceLocation recipeId, net.minecraft.world.entity.pla
682696
// FLUID INPUTS
683697
outer: for (MachineRecipe.FluidInput input : recipe.value().fluidInputs) {
684698
for (ConfigurableFluidStack stack : this.inventory.getFluidInputs()) {
685-
if (stack.isLockedTo(input.fluid()))
699+
if (stack.getLockedInstance() != null && input.fluid().test(new FluidStack(stack.getLockedInstance(), 1)))
686700
continue outer;
687701
}
688-
AbstractConfigurableStack.playerLockNoOverride(input.fluid(), this.inventory.getFluidInputs());
702+
Fluid targetFluid = null;
703+
// Find the first match in the player inventory
704+
for (int i = 0; i < inventory.getContainerSize(); i++) {
705+
var playerStack = FluidUtil.getFluidContained(inventory.getItem(i)).orElse(FluidStack.EMPTY);
706+
if (!playerStack.isEmpty() && input.fluid().test(new FluidStack(playerStack.getFluid(), 1))) {
707+
targetFluid = playerStack.getFluid();
708+
break;
709+
}
710+
}
711+
if (targetFluid == null) {
712+
// Find the first match that is an item from MI
713+
for (Fluid fluid : input.getInputFluids()) {
714+
ResourceLocation id = BuiltInRegistries.FLUID.getKey(fluid);
715+
if (id.getNamespace().equals(MI.ID)) {
716+
targetFluid = fluid;
717+
break;
718+
}
719+
}
720+
}
721+
if (targetFluid == null) {
722+
// If there is only one value in the tag, pick that one
723+
if (input.getInputFluids().size() == 1) {
724+
targetFluid = input.getInputFluids().get(0);
725+
}
726+
}
727+
728+
if (targetFluid != null) {
729+
AbstractConfigurableStack.playerLockNoOverride(targetFluid, this.inventory.getFluidInputs());
730+
}
689731
}
690732
// FLUID OUTPUTS
691733
outer: for (MachineRecipe.FluidOutput output : recipe.value().fluidOutputs) {

src/main/java/aztech/modern_industrialization/machines/recipe/MIRecipeJson.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import net.minecraft.world.item.crafting.ShapedRecipe;
3838
import net.minecraft.world.level.ItemLike;
3939
import net.minecraft.world.level.material.Fluid;
40+
import net.neoforged.neoforge.fluids.crafting.FluidIngredient;
4041

4142
@SuppressWarnings({ "FieldCanBeLocal", "unused", "MismatchedQueryAndUpdateOfCollection" })
4243
public class MIRecipeJson<T extends MIRecipeJson<?>> {
@@ -149,7 +150,7 @@ public T addFluidInput(Fluid fluid, int amount, float probability) {
149150
if (id.equals(BuiltInRegistries.FLUID.getDefaultKey())) {
150151
throw new RuntimeException("Could not find id for fluid " + fluid);
151152
}
152-
recipe.fluidInputs.add(new MachineRecipe.FluidInput(fluid, amount, probability));
153+
recipe.fluidInputs.add(new MachineRecipe.FluidInput(FluidIngredient.of(fluid), amount, probability));
153154
return (T) this;
154155
}
155156

0 commit comments

Comments
 (0)