From c6e10a64fc633d3d2daa1566dc83f7020bf2a621 Mon Sep 17 00:00:00 2001 From: Simon B Date: Sun, 26 Nov 2023 00:48:08 +0100 Subject: [PATCH] feat: Add a new WarpPlateRecipe type for configurable shard recipes (#749) * Add a new WarpPlateRecipe type for configurable shard recipes --------- Co-authored-by: BlayTheNinth <1933180+BlayTheNinth@users.noreply.github.com> --- .../net/blay09/mods/waystones/Waystones.java | 2 + .../block/entity/WarpPlateBlockEntity.java | 77 +++++++--- .../mods/waystones/recipe/ModRecipes.java | 24 +++ .../waystones/recipe/WarpPlateRecipe.java | 144 ++++++++++++++++++ .../data/waystones/recipes/attuned_shard.json | 24 +++ 5 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 shared/src/main/java/net/blay09/mods/waystones/recipe/ModRecipes.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/recipe/WarpPlateRecipe.java create mode 100644 shared/src/main/resources/data/waystones/recipes/attuned_shard.json diff --git a/shared/src/main/java/net/blay09/mods/waystones/Waystones.java b/shared/src/main/java/net/blay09/mods/waystones/Waystones.java index c84eed53..1418a286 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/Waystones.java +++ b/shared/src/main/java/net/blay09/mods/waystones/Waystones.java @@ -9,6 +9,7 @@ import net.blay09.mods.waystones.item.ModItems; import net.blay09.mods.waystones.menu.ModMenus; import net.blay09.mods.waystones.network.ModNetworking; +import net.blay09.mods.waystones.recipe.ModRecipes; import net.blay09.mods.waystones.stats.ModStats; import net.blay09.mods.waystones.worldgen.ModWorldGen; @@ -27,5 +28,6 @@ public static void initialize() { ModItems.initialize(Balm.getItems()); ModMenus.initialize(Balm.getMenus()); ModWorldGen.initialize(Balm.getWorldGen()); + ModRecipes.initialize(Balm.getRecipes()); } } diff --git a/shared/src/main/java/net/blay09/mods/waystones/block/entity/WarpPlateBlockEntity.java b/shared/src/main/java/net/blay09/mods/waystones/block/entity/WarpPlateBlockEntity.java index 382d9cac..5d3f8a0f 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/block/entity/WarpPlateBlockEntity.java +++ b/shared/src/main/java/net/blay09/mods/waystones/block/entity/WarpPlateBlockEntity.java @@ -3,17 +3,21 @@ import net.blay09.mods.balm.api.Balm; import net.blay09.mods.balm.api.container.ImplementedContainer; import net.blay09.mods.balm.api.menu.BalmMenuProvider; +import net.blay09.mods.waystones.Waystones; import net.blay09.mods.waystones.api.*; import net.blay09.mods.waystones.block.WarpPlateBlock; import net.blay09.mods.waystones.config.WaystonesConfig; -import net.blay09.mods.waystones.menu.WarpPlateContainer; import net.blay09.mods.waystones.core.*; -import net.blay09.mods.waystones.item.ModItems; +import net.blay09.mods.waystones.menu.WarpPlateContainer; +import net.blay09.mods.waystones.recipe.ModRecipes; +import net.blay09.mods.waystones.recipe.WarpPlateRecipe; import net.blay09.mods.waystones.worldgen.namegen.NameGenerationMode; import net.blay09.mods.waystones.worldgen.namegen.NameGenerator; import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.core.NonNullList; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; @@ -31,19 +35,24 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerData; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; public class WarpPlateBlockEntity extends WaystoneBlockEntityBase implements ImplementedContainer { + private static final Logger logger = LoggerFactory.getLogger(WarpPlateBlockEntity.class); + private final WeakHashMap ticksPassedPerEntity = new WeakHashMap<>(); private final Random random = new Random(); @@ -106,7 +115,7 @@ public void initializeFromExisting(ServerLevelAccessor world, Waystone existingW completedFirstAttunement = tag != null && tag.getBoolean("CompletedFirstAttunement"); if (!completedFirstAttunement) { - initializeInventory(); + initializeInventory(world); } } @@ -123,15 +132,28 @@ public void initializeWaystone(ServerLevelAccessor world, @Nullable LivingEntity WaystoneSyncManager.sendWaystoneUpdateToAll(world.getServer(), waystone); - initializeInventory(); + initializeInventory(world); } - private void initializeInventory() { - setItem(0, new ItemStack(Items.FLINT)); - setItem(1, new ItemStack(ModItems.warpDust)); - setItem(2, new ItemStack(ModItems.warpDust)); - setItem(3, new ItemStack(ModItems.warpDust)); - setItem(4, new ItemStack(ModItems.warpDust)); + private void initializeInventory(ServerLevelAccessor levelAccessor) { + WarpPlateRecipe initializingRecipe = levelAccessor.getLevel().getRecipeManager().getAllRecipesFor(ModRecipes.warpPlateRecipeType) + .stream() + .filter(holder -> holder.id().getNamespace().equals(Waystones.MOD_ID) && holder.id().getPath().equals("attuned_shard")) + .map(RecipeHolder::value) + .findFirst() + .orElse(null); + if (initializingRecipe == null) { + logger.error("Failed to find Warp Plate recipe for initial attunement"); + completedFirstAttunement = true; + return; + } + + for (int i = 0; i < 5; i++) { + final var ingredient = initializingRecipe.getIngredients().get(i); + final var ingredientItems = ingredient.getItems(); + final var ingredientItem = ingredientItems.length > 0 ? ingredientItems[0] : ItemStack.EMPTY; + setItem(i, ingredientItem); + } } @Override @@ -198,18 +220,26 @@ private boolean isEntityOnWarpPlate(Entity entity) { } public void serverTick() { - if (isReadyForAttunement()) { + WarpPlateRecipe recipe = trySelectRecipe(); + if (recipe != null) { attunementTicks++; if (attunementTicks >= getMaxAttunementTicks()) { attunementTicks = 0; - ItemStack attunedShard = new ItemStack(ModItems.attunedShard); + ItemStack attunedShard = recipe.assemble(this, RegistryAccess.EMPTY); WaystonesAPI.setBoundWaystone(attunedShard, getWaystone()); + ItemStack centerStack = getItem(0); + if (centerStack.getCount() > 1) { + centerStack = centerStack.copyWithCount(centerStack.getCount() - 1); + if (!Minecraft.getInstance().player.getInventory().add(centerStack)) { + Minecraft.getInstance().player.drop(centerStack, false); + } + } setItem(0, attunedShard); for (int i = 1; i <= 4; i++) { getItem(i).shrink(1); } - completedFirstAttunement = true; + this.completedFirstAttunement = true; } } else { attunementTicks = 0; @@ -260,10 +290,6 @@ public void serverTick() { entry.setValue(ticksPassed + 1); } } - - if (getItem(0).getItem() != Items.FLINT) { - completedFirstAttunement = true; - } } private int getWarpPlateUseTime() { @@ -341,13 +367,14 @@ private void applyWarpPlateEffects(Entity entity) { } } - private boolean isReadyForAttunement() { - return readyForAttunement - && getItem(0).getItem() == Items.FLINT - && getItem(1).getItem() == ModItems.warpDust - && getItem(2).getItem() == ModItems.warpDust - && getItem(3).getItem() == ModItems.warpDust - && getItem(4).getItem() == ModItems.warpDust; + @Nullable + private WarpPlateRecipe trySelectRecipe() { + if (!readyForAttunement || level == null) { + return null; + } + + return level.getRecipeManager().getRecipeFor(ModRecipes.warpPlateRecipeType, this, level) + .map(RecipeHolder::value).orElse(null); } @Nullable diff --git a/shared/src/main/java/net/blay09/mods/waystones/recipe/ModRecipes.java b/shared/src/main/java/net/blay09/mods/waystones/recipe/ModRecipes.java new file mode 100644 index 00000000..35a6677f --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/recipe/ModRecipes.java @@ -0,0 +1,24 @@ +package net.blay09.mods.waystones.recipe; + +import net.blay09.mods.balm.api.recipe.BalmRecipes; +import net.blay09.mods.waystones.Waystones; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.RecipeType; + +public class ModRecipes { + + public static final String WARP_PLATE_RECIPE_GROUP = "warp_plate"; + public static final ResourceLocation WARP_PLATE_RECIPE_TYPE = new ResourceLocation(Waystones.MOD_ID, WARP_PLATE_RECIPE_GROUP); + + public static RecipeType warpPlateRecipeType; + + public static void initialize(BalmRecipes registry) { + registry.registerRecipeType(() -> warpPlateRecipeType = new RecipeType<>() { + @Override + public String toString() { + return WARP_PLATE_RECIPE_GROUP; + } + }, + WarpPlateRecipe.Serializer::new, WARP_PLATE_RECIPE_TYPE); + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/recipe/WarpPlateRecipe.java b/shared/src/main/java/net/blay09/mods/waystones/recipe/WarpPlateRecipe.java new file mode 100644 index 00000000..32a7f9b8 --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/recipe/WarpPlateRecipe.java @@ -0,0 +1,144 @@ +package net.blay09.mods.waystones.recipe; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.blay09.mods.waystones.block.ModBlocks; +import net.minecraft.core.NonNullList; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.Level; + +public class WarpPlateRecipe implements Recipe { + + private final ItemStack resultItem; + private final Ingredient primaryIngredient; + private final NonNullList secondaryIngredients; + private final NonNullList combinedIngredients; + + public WarpPlateRecipe(ItemStack resultItem, Ingredient primaryIngredient, NonNullList secondaryIngredients) { + this.resultItem = resultItem; + + this.primaryIngredient = primaryIngredient; + this.secondaryIngredients = secondaryIngredients; + + this.combinedIngredients = NonNullList.createWithCapacity(5); + this.combinedIngredients.add(primaryIngredient); + this.combinedIngredients.addAll(secondaryIngredients); + } + + @Override + public boolean matches(Container inventory, Level level) { + // Short-circuit if the primary ingredient is not present to ensure it's actually in the right slot and avoid unnecessary processing + if (!primaryIngredient.test(inventory.getItem(0))) { + return false; + } + + StackedContents stackedContents = new StackedContents(); + int foundInputs = 0; + // canCraft uses getIngredients, so we need to include the primary ingredient here as well + for (int i = 0; i < combinedIngredients.size(); i++) { + ItemStack itemStack = inventory.getItem(i); + if (!itemStack.isEmpty()) { + foundInputs++; + stackedContents.accountStack(itemStack, 1); + } + } + return foundInputs == combinedIngredients.size() && stackedContents.canCraft(this, null); + } + + @Override + public ItemStack assemble(Container inventory, RegistryAccess registryAccess) { + return this.resultItem.copy(); + } + + @Override + public ItemStack getResultItem(RegistryAccess registryAccess) { + return this.resultItem; + } + + @Override + public boolean canCraftInDimensions(int width, int height) { + return true; + } + + @Override + public NonNullList getIngredients() { + return this.combinedIngredients; + } + + @Override + public boolean isSpecial() { + return true; + } + + @Override + public String getGroup() { + return ModRecipes.WARP_PLATE_RECIPE_GROUP; + } + + @Override + public ItemStack getToastSymbol() { + return new ItemStack(ModBlocks.warpPlate); + } + + @Override + public RecipeSerializer getSerializer() { + return new Serializer(); + } + + @Override + public RecipeType getType() { + return ModRecipes.warpPlateRecipeType; + } + + static class Serializer implements RecipeSerializer { + + private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + CraftingRecipeCodecs.ITEMSTACK_OBJECT_CODEC.fieldOf("result").forGetter(recipe -> recipe.resultItem), + Ingredient.CODEC.fieldOf("primary").forGetter(recipe -> recipe.primaryIngredient), + Ingredient.CODEC.listOf().fieldOf("secondary") + .flatXmap(secondary -> { + Ingredient[] ingredients = secondary.stream().filter((ingredient) -> !ingredient.isEmpty()).toArray(Ingredient[]::new); + if (ingredients.length == 0) { + return DataResult.error(() -> "No secondary ingredients for warp plate recipe"); + } else { + return ingredients.length > 4 ? DataResult.error(() -> "Too many secondary ingredients for warp plate recipe") : DataResult.success( + NonNullList.of(Ingredient.EMPTY, ingredients)); + } + }, DataResult::success) + .forGetter(recipe -> recipe.secondaryIngredients) + ) + .apply(instance, WarpPlateRecipe::new)); + + @Override + public Codec codec() { + return CODEC; + } + + @Override + public WarpPlateRecipe fromNetwork(FriendlyByteBuf buf) { + final var resultItem = buf.readItem(); + final var primaryIngredient = Ingredient.fromNetwork(buf); + final NonNullList secondaryIngredients = NonNullList.createWithCapacity(4); + for (int i = 0; i < secondaryIngredients.size(); i++) { + secondaryIngredients.add(Ingredient.fromNetwork(buf)); + } + return new WarpPlateRecipe(resultItem, primaryIngredient, secondaryIngredients); + } + + @Override + public void toNetwork(FriendlyByteBuf buf, WarpPlateRecipe recipe) { + buf.writeItem(recipe.resultItem); + recipe.primaryIngredient.toNetwork(buf); + for (Ingredient ingredient : recipe.secondaryIngredients) { + ingredient.toNetwork(buf); + } + } + } + +} diff --git a/shared/src/main/resources/data/waystones/recipes/attuned_shard.json b/shared/src/main/resources/data/waystones/recipes/attuned_shard.json new file mode 100644 index 00000000..c49348be --- /dev/null +++ b/shared/src/main/resources/data/waystones/recipes/attuned_shard.json @@ -0,0 +1,24 @@ +{ + "type": "waystones:warp_plate", + "result": { + "item": "waystones:attuned_shard", + "count": 1 + }, + "primary": { + "item": "minecraft:flint" + }, + "secondary": [ + { + "item": "waystones:warp_dust" + }, + { + "item": "waystones:warp_dust" + }, + { + "item": "waystones:warp_dust" + }, + { + "item": "waystones:warp_dust" + } + ] +}