Skip to content

Commit

Permalink
fluid crafting table recipes (#2152)
Browse files Browse the repository at this point in the history
  • Loading branch information
screret authored Oct 12, 2024
1 parent 488a37e commit 303602b
Show file tree
Hide file tree
Showing 15 changed files with 546 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void toNetwork(FriendlyByteBuf buffer, ShapedEnergyTransferRecipe recipe)
for (Ingredient ingredient : recipe.getIngredients()) {
ingredient.toNetwork(buffer);
}
buffer.writeItem(((ShapedRecipeAccessor) this).getResult());
buffer.writeItem(((ShapedRecipeAccessor) recipe).getResult());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.gregtechceu.gtceu.api.recipe;

import com.gregtechceu.gtceu.api.recipe.ingredient.FluidContainerIngredient;
import com.gregtechceu.gtceu.core.mixins.ShapedRecipeAccessor;

import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.ShapedRecipe;

import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

// TODO shapeless fluid container recipes
public class ShapedFluidContainerRecipe extends ShapedRecipe {

public static final RecipeSerializer<ShapedFluidContainerRecipe> SERIALIZER = new Serializer();

public ShapedFluidContainerRecipe(ResourceLocation id, String group, CraftingBookCategory category,
int width, int height,
NonNullList<Ingredient> recipeItems, ItemStack result,
boolean showNotification) {
super(id, group, category, width, height, recipeItems, result, showNotification);
}

public ShapedFluidContainerRecipe(ResourceLocation id, String group, CraftingBookCategory category,
int width, int height,
NonNullList<Ingredient> recipeItems, ItemStack result) {
this(id, group, category, width, height, recipeItems, result, true);
}

@Override
public @NotNull NonNullList<ItemStack> getRemainingItems(@NotNull CraftingContainer inv) {
NonNullList<ItemStack> items = NonNullList.withSize(inv.getContainerSize(), ItemStack.EMPTY);

// figure out all the fluid container ingredients' remainders.
int replacedSlot = -1;
OUTER_LOOP:
for (int x = 0; x <= inv.getWidth() - this.getWidth(); ++x) {
for (int y = 0; y <= inv.getHeight() - this.getHeight(); ++y) {
var stack = this.findFluidReplacement(inv, x, y, false);
if (stack.getFirst() != -1) {
items.set(stack.getFirst(), stack.getSecond());
replacedSlot = stack.getFirst();
break OUTER_LOOP;
}

stack = this.findFluidReplacement(inv, x, y, true);
if (stack.getFirst() != -1) {
items.set(stack.getFirst(), stack.getSecond());
replacedSlot = stack.getFirst();
break OUTER_LOOP;
}
}
}

for (int i = 0; i < items.size(); ++i) {
if (i == replacedSlot) {
continue;
}
ItemStack item = inv.getItem(i);
if (item.hasCraftingRemainingItem()) {
items.set(i, item.getCraftingRemainingItem());
}
}

return items;
}

/**
* Checks if the region of a crafting inventory is match for the recipe.
*/
private Pair<Integer, ItemStack> findFluidReplacement(CraftingContainer inv, int width, int height,
boolean mirrored) {
for (int x = 0; x < inv.getWidth(); ++x) {
for (int y = 0; y < inv.getHeight(); ++y) {
int offsetX = x - width;
int offsetY = y - height;
Ingredient ingredient = Ingredient.EMPTY;
if (offsetX >= 0 && offsetY >= 0 && offsetX < this.getWidth() && offsetY < this.getHeight()) {
if (mirrored) {
ingredient = this.getIngredients()
.get(this.getWidth() - offsetX - 1 + offsetY * this.getWidth());
} else {
ingredient = this.getIngredients().get(offsetX + offsetY * this.getWidth());
}
}

if (ingredient instanceof FluidContainerIngredient fluidContainerIngredient) {
int slot = x + y * inv.getWidth();
ItemStack stack = inv.getItem(slot);
if (fluidContainerIngredient.test(stack)) {
return Pair.of(slot, fluidContainerIngredient.getExtractedStack(stack));
}
}
}
}

return Pair.of(-1, ItemStack.EMPTY);
}

public static class Serializer implements RecipeSerializer<ShapedFluidContainerRecipe> {

@Override
public ShapedFluidContainerRecipe fromJson(ResourceLocation recipeId, JsonObject json) {
String group = GsonHelper.getAsString(json, "group", "");
CraftingBookCategory category = CraftingBookCategory.CODEC
.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC);
Map<String, Ingredient> key = ShapedRecipeAccessor.callKeyFromJson(GsonHelper.getAsJsonObject(json, "key"));
String[] pattern = ShapedRecipeAccessor
.callShrink(ShapedRecipeAccessor.callPatternFromJson(GsonHelper.getAsJsonArray(json, "pattern")));
int xSize = pattern[0].length();
int ySize = pattern.length;
NonNullList<Ingredient> dissolved = ShapedRecipeAccessor.callDissolvePattern(pattern, key, xSize, ySize);
ItemStack result = ShapedEnergyTransferRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
boolean showNotification = GsonHelper.getAsBoolean(json, "show_notification", true);
return new ShapedFluidContainerRecipe(recipeId, group, category,
xSize, ySize,
dissolved, result,
showNotification);
}

@Override
public ShapedFluidContainerRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
int xSize = buffer.readVarInt();
int ySize = buffer.readVarInt();
CraftingBookCategory category = buffer.readEnum(CraftingBookCategory.class);
String group = buffer.readUtf();
NonNullList<Ingredient> ingredients = NonNullList.withSize(xSize * ySize, Ingredient.EMPTY);
ingredients.replaceAll($ -> Ingredient.fromNetwork(buffer));
ItemStack result = buffer.readItem();
boolean showNotification = buffer.readBoolean();
return new ShapedFluidContainerRecipe(recipeId, group, category,
xSize, ySize,
ingredients, result,
showNotification);
}

@Override
public void toNetwork(FriendlyByteBuf buffer, ShapedFluidContainerRecipe recipe) {
buffer.writeVarInt(recipe.getWidth());
buffer.writeVarInt(recipe.getHeight());
buffer.writeEnum(recipe.category());
buffer.writeUtf(recipe.getGroup());
for (Ingredient ingredient : recipe.getIngredients()) {
ingredient.toNetwork(buffer);
}
buffer.writeItem(((ShapedRecipeAccessor) recipe).getResult());
buffer.writeBoolean(recipe.showNotification());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.gregtechceu.gtceu.api.recipe.ingredient;

import com.gregtechceu.gtceu.GTCEu;
import com.gregtechceu.gtceu.api.data.tag.TagUtil;
import com.gregtechceu.gtceu.utils.InfiniteFluidTransfer;

import com.lowdragmc.lowdraglib.side.fluid.*;

import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.material.Fluid;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.crafting.IIngredientSerializer;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.serialization.Codec;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.stream.Stream;

import javax.annotation.Nonnull;

public class FluidContainerIngredient extends Ingredient {

public static final ResourceLocation TYPE = GTCEu.id("fluid_container");

public static final Codec<FluidContainerIngredient> CODEC = FluidIngredient.CODEC.xmap(
FluidContainerIngredient::new, FluidContainerIngredient::getFluid);

@Getter
private final FluidIngredient fluid;

public FluidContainerIngredient(FluidIngredient fluid) {
super(Stream.empty());
this.fluid = fluid;
}

public FluidContainerIngredient(FluidStack fluidStack) {
this(FluidIngredient.of(TagUtil.createFluidTag(BuiltInRegistries.FLUID.getKey(fluidStack.getFluid()).getPath()),
fluidStack.getAmount()));
}

public FluidContainerIngredient(TagKey<Fluid> tag, int amount) {
this(FluidIngredient.of(tag, amount, null));
}

private ItemStack[] cachedStacks;

@Nonnull
@Override
public ItemStack[] getItems() {
if (cachedStacks == null)
cachedStacks = Arrays.stream(this.fluid.getStacks())
.map(stack -> stack.getFluid().getBucket().getDefaultInstance())
.filter(s -> !s.isEmpty())
.toArray(ItemStack[]::new);
return this.cachedStacks;
}

@Override
public JsonElement toJson() {
JsonObject json = new JsonObject();
json.addProperty("type", TYPE.toString());
json.add("fluid", fluid.toJson());
return json;
}

@Override
public boolean isEmpty() {
return this.fluid.isEmpty();
}

@Override
public boolean test(@Nullable ItemStack stack) {
if (stack == null || stack.isEmpty())
return false;
IFluidTransfer transfer = FluidTransferHelper.getFluidTransfer(stack);
return transfer != null && this.extractFrom(transfer, true);
}

@Override
public boolean isSimple() {
return false;
}

public ItemStack getExtractedStack(ItemStack input) {
FluidActionResult result = FluidTransferHelper.tryEmptyContainer(input,
new InfiniteFluidTransfer(1),
(int) this.fluid.getAmount(),
ForgeHooks.getCraftingPlayer(),
true);
if (result.success) {
return result.result;
}
return input;
}

public boolean extractFrom(IFluidTransfer handler, boolean simulate) {
for (int tank = 0; tank < handler.getTanks(); tank++) {
FluidStack inTank = handler.getFluidInTank(tank);
if (fluid.test(inTank)) {
FluidStack toExtract = inTank.copy(fluid.getAmount());
FluidStack extractedSim = handler.drain(toExtract, true);
if (extractedSim.getAmount() >= fluid.getAmount()) {
if (!simulate)
handler.drain(toExtract, false);
return true;
}
}
}
return false;
}

public static FluidContainerIngredient fromJson(JsonObject json) {
return SERIALIZER.parse(json);
}

public static final IIngredientSerializer<FluidContainerIngredient> SERIALIZER = new IIngredientSerializer<>() {

@Override
public @NotNull FluidContainerIngredient parse(FriendlyByteBuf buffer) {
FluidIngredient fluid = FluidIngredient.fromNetwork(buffer);
return new FluidContainerIngredient(fluid);
}

@Override
public @NotNull FluidContainerIngredient parse(JsonObject json) {
FluidIngredient fluid = FluidIngredient.fromJson(GsonHelper.getAsJsonObject(json, "fluid"));
return new FluidContainerIngredient(fluid);
}

@Override
public void write(FriendlyByteBuf buffer, FluidContainerIngredient ingredient) {
ingredient.fluid.toNetwork(buffer);
}
};
}
5 changes: 5 additions & 0 deletions src/main/java/com/gregtechceu/gtceu/common/CommonProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.gregtechceu.gtceu.api.gui.factory.GTUIEditorFactory;
import com.gregtechceu.gtceu.api.gui.factory.MachineUIFactory;
import com.gregtechceu.gtceu.api.recipe.chance.logic.ChanceLogic;
import com.gregtechceu.gtceu.api.recipe.ingredient.FluidContainerIngredient;
import com.gregtechceu.gtceu.api.recipe.ingredient.IntCircuitIngredient;
import com.gregtechceu.gtceu.api.recipe.ingredient.IntProviderIngredient;
import com.gregtechceu.gtceu.api.recipe.ingredient.SizedIngredient;
Expand Down Expand Up @@ -53,6 +54,7 @@
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.event.AddPackFindersEvent;
Expand All @@ -78,6 +80,8 @@
public class CommonProxy {

public CommonProxy() {
ForgeMod.enableMilkFluid();

// used for forge events (ClientProxy + CommonProxy)
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
eventBus.register(this);
Expand Down Expand Up @@ -229,6 +233,7 @@ public void commonSetup(FMLCommonSetupEvent event) {
CraftingHelper.register(SizedIngredient.TYPE, SizedIngredient.SERIALIZER);
CraftingHelper.register(IntCircuitIngredient.TYPE, IntCircuitIngredient.SERIALIZER);
CraftingHelper.register(IntProviderIngredient.TYPE, IntProviderIngredient.SERIALIZER);
CraftingHelper.register(FluidContainerIngredient.TYPE, FluidContainerIngredient.SERIALIZER);
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/gregtechceu/gtceu/common/data/GTFluids.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.common.ForgeMod;

import org.jetbrains.annotations.NotNull;

Expand All @@ -24,6 +25,7 @@ public class GTFluids {
public static void init() {
handleNonMaterialFluids(GTMaterials.Water, Fluids.WATER);
handleNonMaterialFluids(GTMaterials.Lava, Fluids.LAVA);
handleNonMaterialFluids(GTMaterials.Milk, ForgeMod.MILK.get());
REGISTRATE.creativeModeTab(() -> GTCreativeModeTabs.MATERIAL_FLUID);
// register fluids for materials
for (MaterialRegistry registry : GTCEuAPI.materialManager.getRegistries()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,16 +716,18 @@ public static void init() {
ModLoader.get().postEvent(new GTCEuAPI.RegisterEvent<>(GTRegistries.RECIPE_TYPES, GTRecipeType.class));
GTRegistries.RECIPE_TYPES.freeze();

GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("gt_recipe_serializer"),
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("machine"),
GTRecipeSerializer.SERIALIZER);
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("facade_cover_serializer"),
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("crafting_facade_cover"),
FacadeCoverRecipe.SERIALIZER);
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("strict_shaped_recipe_serializer"),
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("crafting_shaped_strict"),
StrictShapedRecipe.SERIALIZER);
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("shaped_energy_transfer_recipe_serializer"),
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("crafting_shaped_energy_transfer"),
ShapedEnergyTransferRecipe.SERIALIZER);
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("tool_head_replace_recipe_serializer"),
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("crafting_tool_head_replace"),
ToolHeadReplaceRecipe.SERIALIZER);
GTRegistries.register(BuiltInRegistries.RECIPE_SERIALIZER, GTCEu.id("crafting_shaped_fluid_container"),
ShapedFluidContainerRecipe.SERIALIZER);
}

public static GTRecipeType get(String name) {
Expand Down
Loading

0 comments on commit 303602b

Please sign in to comment.