Skip to content

Commit

Permalink
Add potion casting recipe and add recipes for filling potion bottles …
Browse files Browse the repository at this point in the history
…and tipping arrows
  • Loading branch information
KnightMiner committed Dec 27, 2022
1 parent 725e1ee commit 77c3eea
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "tconstruct:casting_table_potion",
"bottle": {
"item": "minecraft:glass_bottle"
},
"fluid": {
"tag": "forge:potion",
"amount": 250
},
"result": "minecraft:potion",
"cooling_time": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "tconstruct:casting_table_potion",
"bottle": {
"tag": "forge:bottles/splash"
},
"fluid": {
"tag": "forge:potion",
"amount": 250
},
"result": "minecraft:splash_potion",
"cooling_time": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "tconstruct:casting_table_potion",
"bottle": {
"tag": "forge:bottles/lingering"
},
"fluid": {
"tag": "forge:potion",
"amount": 250
},
"result": "minecraft:lingering_potion",
"cooling_time": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "tconstruct:casting_table_potion",
"bottle": {
"item": "minecraft:arrow"
},
"fluid": {
"tag": "forge:potion",
"amount": 25
},
"result": "minecraft:tipped_arrow",
"cooling_time": 20
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public RecipeSerializer<?> getSerializer() {
}
}

// TODO 1.19: migrate serializer to work like PotionCastingRecipe
@AllArgsConstructor
public static class Serializer<T extends ItemCastingRecipe> extends AbstractCastingRecipe.Serializer<T> {
private final IFactory<T> factory;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package slimeknights.tconstruct.library.recipe.casting;

import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.alchemy.PotionUtils;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.registries.ForgeRegistries;
import slimeknights.mantle.recipe.IMultiRecipe;
import slimeknights.mantle.recipe.helper.LoggingRecipeSerializer;
import slimeknights.mantle.recipe.ingredient.FluidIngredient;
import slimeknights.mantle.util.JsonHelper;

import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Supplier;

/**
* Recipe for casting a fluid onto an item, copying the fluid NBT to the item
*/
@RequiredArgsConstructor
public class PotionCastingRecipe implements ICastingRecipe, IMultiRecipe<DisplayCastingRecipe> {
@Getter
private final RecipeType<?> type;
@Getter
private final RecipeSerializer<?> serializer;
@Getter
private final ResourceLocation id;
@Getter
private final String group;
/** Input on the casting table, always consumed */
private final Ingredient bottle;
/** Potion ingredient, typically just the potion tag */
private final FluidIngredient fluid;
/** Potion item result, will be given the proper NBT */
private final Item result;
/** Cooling time, used for arrows */
private final int coolingTime;

private List<DisplayCastingRecipe> displayRecipes = null;

@Override
public boolean matches(ICastingContainer inv, Level level) {
return bottle.test(inv.getStack()) && fluid.test(inv.getFluid());
}

@Override
public int getFluidAmount(ICastingContainer inv) {
return fluid.getAmount(inv.getFluid());
}

@Override
public boolean isConsumed() {
return true;
}

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

@Override
public int getCoolingTime(ICastingContainer inv) {
return coolingTime;
}

@Override
public ItemStack assemble(ICastingContainer inv) {
ItemStack result = new ItemStack(this.result);
result.setTag(inv.getFluidTag());
return result;
}

@Override
public List<DisplayCastingRecipe> getRecipes() {
if (displayRecipes == null) {
// create a subrecipe for every potion variant
List<ItemStack> bottles = List.of(bottle.getItems());
displayRecipes = ForgeRegistries.POTIONS.getValues().stream()
.map(potion -> {
ItemStack result = PotionUtils.setPotion(new ItemStack(this.result), potion);
return new DisplayCastingRecipe(type, bottles, fluid.getFluids().stream()
.map(fluid -> new FluidStack(fluid.getFluid(), fluid.getAmount(), result.getTag()))
.toList(),
result, coolingTime, true);
}).toList();
}
return displayRecipes;
}


/* Recipe interface methods */

@Override
public NonNullList<Ingredient> getIngredients() {
return NonNullList.of(Ingredient.EMPTY, bottle);
}

/** @deprecated use {@link #assemble(Container)} */
@Deprecated
@Override
public ItemStack getResultItem() {
return new ItemStack(this.result);
}

@RequiredArgsConstructor
public static class Serializer extends LoggingRecipeSerializer<PotionCastingRecipe> {
private final Supplier<RecipeType<ICastingRecipe>> type;

@Override
public PotionCastingRecipe fromJson(ResourceLocation id, JsonObject json) {
String group = GsonHelper.getAsString(json, "group", "");
Ingredient bottle = Ingredient.fromJson(JsonHelper.getElement(json, "bottle"));
FluidIngredient fluid = FluidIngredient.deserialize(json, "fluid");
Item result = JsonHelper.getAsEntry(ForgeRegistries.ITEMS, json, "result");
int coolingTime = GsonHelper.getAsInt(json, "cooling_time");
return new PotionCastingRecipe(type.get(), this, id, group, bottle, fluid, result, coolingTime);
}

@Nullable
@Override
protected PotionCastingRecipe fromNetworkSafe(ResourceLocation id, FriendlyByteBuf buffer) {
String group = buffer.readUtf(Short.MAX_VALUE);
Ingredient bottle = Ingredient.fromNetwork(buffer);
FluidIngredient fluid = FluidIngredient.read(buffer);
Item result = buffer.readRegistryIdUnsafe(ForgeRegistries.ITEMS);
int coolingTime = buffer.readVarInt();
return new PotionCastingRecipe(type.get(), this, id, group, bottle, fluid, result, coolingTime);
}

@Override
protected void toNetworkSafe(FriendlyByteBuf buffer, PotionCastingRecipe recipe) {
buffer.writeUtf(recipe.group);
recipe.bottle.toNetwork(buffer);
recipe.fluid.write(buffer);
buffer.writeRegistryIdUnsafe(ForgeRegistries.ITEMS, recipe.result);
buffer.writeVarInt(recipe.coolingTime);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package slimeknights.tconstruct.library.recipe.casting;

import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.material.Fluid;
import slimeknights.mantle.recipe.data.AbstractRecipeBuilder;
import slimeknights.mantle.recipe.ingredient.FluidIngredient;
import slimeknights.tconstruct.smeltery.TinkerSmeltery;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.function.Consumer;

/**
* Builder for a potion bottle filling recipe. Takes a fluid and optional cast to create an item that copies the fluid NBT
*/
@SuppressWarnings({"WeakerAccess", "unused", "UnusedReturnValue"})
@RequiredArgsConstructor(staticName = "castingRecipe")
public class PotionCastingRecipeBuilder extends AbstractRecipeBuilder<PotionCastingRecipeBuilder> {
private final Item result;
private final PotionCastingRecipe.Serializer recipeSerializer;
private Ingredient bottle = Ingredient.EMPTY;
private FluidIngredient fluid = FluidIngredient.EMPTY;
@Setter @Accessors(chain = true)
private int coolingTime = 0;

/**
* Creates a new casting basin recipe
* @param result Recipe result
* @return Builder instance
*/
public static PotionCastingRecipeBuilder basinRecipe(ItemLike result) {
return castingRecipe(result.asItem(), TinkerSmeltery.basinPotionRecipeSerializer.get());
}

/**
* Creates a new casting table recipe
* @param result Recipe result
* @return Builder instance
*/
public static PotionCastingRecipeBuilder tableRecipe(ItemLike result) {
return castingRecipe(result.asItem(), TinkerSmeltery.tablePotionRecipeSerializer.get());
}


/* Fluids */

/**
* Sets the fluid for this recipe
* @param tagIn Tag<Fluid> instance
* @param amount amount of fluid
* @return Builder instance
*/
public PotionCastingRecipeBuilder setFluid(TagKey<Fluid> tagIn, int amount) {
return this.setFluid(FluidIngredient.of(tagIn, amount));
}

/**
* Sets the fluid ingredient
* @param fluid Fluid ingredient instance
* @return Builder instance
*/
public PotionCastingRecipeBuilder setFluid(FluidIngredient fluid) {
this.fluid = fluid;
return this;
}


/* Cast */

/**
* Sets the cast from a tag, bottles are always consumed
* @param tagIn Cast tag
* @return Builder instance
*/
public PotionCastingRecipeBuilder setBottle(TagKey<Item> tagIn) {
return this.setBottle(Ingredient.of(tagIn));
}

/**
* Sets the bottle from an item, bottles are always consumed
* @param itemIn Cast item
* @return Builder instance
*/
public PotionCastingRecipeBuilder setBottle(ItemLike itemIn) {
return this.setBottle(Ingredient.of(itemIn));
}

/**
* Sets the bottle from an ingredient, bottles are always consumed
* @param ingredient Cast ingredient
* @return Builder instance
*/
public PotionCastingRecipeBuilder setBottle(Ingredient ingredient) {
this.bottle = ingredient;
return this;
}

/**
* Builds a recipe using the registry name as the recipe name
* @param consumerIn Recipe consumer
*/
@Override
public void save(Consumer<FinishedRecipe> consumerIn) {
this.save(consumerIn, Objects.requireNonNull(this.result.getRegistryName()));
}

@Override
public void save(Consumer<FinishedRecipe> consumer, ResourceLocation id) {
if (this.fluid == FluidIngredient.EMPTY) {
throw new IllegalStateException("Casting recipes require a fluid input");
}
if (this.coolingTime < 0) {
throw new IllegalStateException("Cooling time is too low, must be at least 0");
}
ResourceLocation advancementId = this.buildOptionalAdvancement(id, "casting");
consumer.accept(new PotionCastingRecipeBuilder.Result(id, advancementId));
}

private class Result extends AbstractFinishedRecipe {
public Result(ResourceLocation ID, @Nullable ResourceLocation advancementID) {
super(ID, advancementID);
}

@Override
public RecipeSerializer<?> getType() {
return recipeSerializer;
}

@Override
public void serializeRecipeData(JsonObject json) {
if (!group.isEmpty()) {
json.addProperty("group", group);
}
if (bottle != Ingredient.EMPTY) {
json.add("bottle", bottle.toJson());
}
json.add("fluid", fluid.serialize());
json.addProperty("result", Objects.requireNonNull(result.getRegistryName()).toString());
json.addProperty("cooling_time", coolingTime);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
import slimeknights.tconstruct.common.registration.CastItemObject;
import slimeknights.tconstruct.fluids.item.EmptyPotionTransfer;
import slimeknights.tconstruct.library.recipe.FluidValues;
import slimeknights.tconstruct.library.recipe.TinkerRecipeTypes;
import slimeknights.tconstruct.library.recipe.alloying.AlloyRecipe;
import slimeknights.tconstruct.library.recipe.casting.ItemCastingRecipe;
import slimeknights.tconstruct.library.recipe.casting.PotionCastingRecipe;
import slimeknights.tconstruct.library.recipe.casting.container.ContainerFillingRecipe;
import slimeknights.tconstruct.library.recipe.casting.container.ContainerFillingRecipeSerializer;
import slimeknights.tconstruct.library.recipe.casting.material.CompositeCastingRecipe;
Expand Down Expand Up @@ -313,6 +315,8 @@ public final class TinkerSmeltery extends TinkerModule {
public static final RegistryObject<ItemCastingRecipe.Serializer<ItemCastingRecipe.Table>> tableRecipeSerializer = RECIPE_SERIALIZERS.register("casting_table", () -> new ItemCastingRecipe.Serializer<>(ItemCastingRecipe.Table::new));
public static final RegistryObject<ContainerFillingRecipeSerializer<ContainerFillingRecipe.Basin>> basinFillingRecipeSerializer = RECIPE_SERIALIZERS.register("basin_filling", () -> new ContainerFillingRecipeSerializer<>(ContainerFillingRecipe.Basin::new));
public static final RegistryObject<ContainerFillingRecipeSerializer<ContainerFillingRecipe.Table>> tableFillingRecipeSerializer = RECIPE_SERIALIZERS.register("table_filling", () -> new ContainerFillingRecipeSerializer<>(ContainerFillingRecipe.Table::new));
public static final RegistryObject<PotionCastingRecipe.Serializer> basinPotionRecipeSerializer = RECIPE_SERIALIZERS.register("casting_basin_potion", () -> new PotionCastingRecipe.Serializer(TinkerRecipeTypes.CASTING_BASIN));
public static final RegistryObject<PotionCastingRecipe.Serializer> tablePotionRecipeSerializer = RECIPE_SERIALIZERS.register("casting_table_potion", () -> new PotionCastingRecipe.Serializer(TinkerRecipeTypes.CASTING_TABLE));
// material casting
public static final RegistryObject<MaterialCastingRecipe.Serializer<MaterialCastingRecipe.Basin>> basinMaterialSerializer = RECIPE_SERIALIZERS.register("basin_casting_material", () -> new MaterialCastingRecipe.Serializer<>(MaterialCastingRecipe.Basin::new));
public static final RegistryObject<MaterialCastingRecipe.Serializer<MaterialCastingRecipe.Table>> tableMaterialSerializer = RECIPE_SERIALIZERS.register("table_casting_material", () -> new MaterialCastingRecipe.Serializer<>(MaterialCastingRecipe.Table::new));
Expand Down

0 comments on commit 77c3eea

Please sign in to comment.