getTooltipData(ItemStack saltItem) {
+ if (saltItem.getItem() instanceof AlchemicalSaltItem salt) {
+ return ImmutableList.of(salt.getSourceName(saltItem));
+ }
+
+ return ImmutableList.of();
+ }
+
+ public static MutableComponent formatSourceName(MutableComponent sourceName) {
+ return sourceName.withStyle(Style.EMPTY
+ .withColor(ChatFormatting.GREEN)
+ .withItalic(true)
+ );
+ }
+
+ public MutableComponent getSourceName(ItemStack pStack) {
+ return formatSourceName(Component.translatable(pStack.getDescriptionId() + TheurgyConstants.I18n.Item.ALCHEMICAL_SALT_SOURCE_SUFFIX));
+ }
+
+ @Override
+ public Component getName(ItemStack pStack) {
+ return Component.translatable(this.getDescriptionId(pStack), ComponentUtils.wrapInSquareBrackets(
+ this.getSourceName(pStack)
+ ));
+ }
+}
diff --git a/src/main/java/com/klikli_dev/theurgy/content/item/AlchemicalSulfurItem.java b/src/main/java/com/klikli_dev/theurgy/content/item/AlchemicalSulfurItem.java
new file mode 100644
index 000000000..42c6c29f7
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/item/AlchemicalSulfurItem.java
@@ -0,0 +1,228 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.item;
+
+import com.google.common.collect.ImmutableList;
+import com.klikli_dev.theurgy.TheurgyConstants;
+import com.klikli_dev.theurgy.content.item.render.AlchemicalSulfurBEWLR;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import com.klikli_dev.theurgy.util.LevelUtil;
+import com.klikli_dev.theurgy.util.TagUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.Util;
+import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.ComponentUtils;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.network.chat.Style;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraftforge.client.extensions.common.IClientItemExtensions;
+import net.minecraftforge.registries.ForgeRegistries;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+public class AlchemicalSulfurItem extends Item {
+ /**
+ * if true, an item model based on builtin/entity will be generated for this item.
+ * This will cause SulfurBEWLR to be used to render the item dynamically based on TheurgyConstants.Nbt.SULFUR_SOURCE_ID, instead of a json model.
+ *
+ * Note: this variable cannot be used to conditionally register an IClientItemExtensions,
+ * because the registration happens in the Item constructor, before this variable is set.
+ * However, it is also not necessary, because the render behaviour is governed by the item model anyway.
+ */
+ public boolean useAutomaticIconRendering;
+ /**
+ * if true, will use TheurgyConstants.Nbt.SULFUR_SOURCE_ID to provide a parameter to the item name component that can be accessed with %s in the language file.
+ */
+ public boolean useAutomaticNameRendering;
+ /**
+ * if true, will use TheurgyConstants.Nbt.SULFUR_SOURCE_ID to provide a parameter to the item tooltip component that can be accessed with %s in the language file.
+ */
+ public boolean provideAutomaticTooltipData;
+
+ /**
+ * If true will convert the tag to a description id and use it as source name, instead of the source stack procured for the tag.
+ * This is mainly intended for sulfurs that actually represent a tag (such as "all logs").
+ * It should not be used for sulfurs that represent a specific item where we use the tag to handle mod compat (e.g. "Tingleberry").
+ */
+ public boolean overrideTagSourceName;
+
+ /**
+ * If true will use the source name from the lang file, instead of the source stack.
+ * Additionally, this will cause overrideTagSourceName to be ignored.
+ */
+ public boolean overrideSourceName;
+
+ /**
+ * If true the LiquefactionRecipe used to craft this item will automatically generate a source id based on the ingredient, if no source id is provided in the recipe result nbt.
+ */
+ public boolean autoGenerateSourceIdInRecipe;
+
+ public AlchemicalSulfurItem(Properties pProperties) {
+ super(pProperties);
+ this.useAutomaticIconRendering = true;
+ this.useAutomaticNameRendering = true;
+ this.provideAutomaticTooltipData = true;
+ this.autoGenerateSourceIdInRecipe = true;
+ this.overrideTagSourceName = false;
+ this.overrideSourceName = false;
+ }
+
+ public static String getSourceItemId(ItemStack sulfurStack) {
+ if (!sulfurStack.hasTag()) {
+ var level = LevelUtil.getLevelWithoutContext();
+ var recipeManager = level == null ? null : level.getRecipeManager();
+ var registryAccess = level == null ? null : level.registryAccess();
+
+ if (recipeManager != null) {
+ var liquefactionRecipes = recipeManager.getAllRecipesFor(RecipeTypeRegistry.LIQUEFACTION.get()).stream().filter(r -> r.getResultItem(registryAccess) != null).toList();
+
+ var sulfurWithNbt = liquefactionRecipes.stream()
+ .filter(recipe -> recipe.getResultItem(registryAccess) != null && recipe.getResultItem(registryAccess).getItem() == sulfurStack.getItem()).findFirst().map(recipe -> recipe.getResultItem(registryAccess));
+
+ if (sulfurWithNbt.isPresent() && sulfurWithNbt.get().hasTag()) {
+ sulfurStack.setTag(sulfurWithNbt.get().getTag());
+ }
+ }
+ }
+
+ if (sulfurStack.hasTag() && sulfurStack.getTag().contains(TheurgyConstants.Nbt.SULFUR_SOURCE_ID)) {
+ return sulfurStack.getTag().getString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID);
+ }
+
+ return "";
+ }
+
+ /**
+ * Get the source item stack from the sulfur stack nbt.
+ * The source *should* be the item that was used to create the sulfur.
+ * Due to this only being used for automatic rendering and naming purposes, the source might be a different item for some reason, and in many cases could be empty.
+ */
+ public static ItemStack getSourceStack(ItemStack sulfurStack) {
+ var itemSourceId = getSourceItemId(sulfurStack); //we call this first, because it might find the source for us
+
+ //but then we do our normal checks
+ if (sulfurStack.hasTag() && sulfurStack.getTag().contains(TheurgyConstants.Nbt.SULFUR_SOURCE_ID)) {
+ ItemStack sourceStack;
+
+ if (itemSourceId.startsWith("#")) {
+ var tagId = new ResourceLocation(itemSourceId.substring(1));
+ var tag = TagKey.create(Registries.ITEM, tagId);
+ sourceStack = TagUtil.getItemStackForTag(tag);
+ } else {
+ var itemId = new ResourceLocation(itemSourceId);
+ sourceStack = new ItemStack(ForgeRegistries.ITEMS.getValue(itemId));
+ }
+
+ if (sulfurStack.getTag().contains(TheurgyConstants.Nbt.SULFUR_SOURCE_NBT))
+ sourceStack.setTag(sulfurStack.getTag().getCompound(TheurgyConstants.Nbt.SULFUR_SOURCE_NBT));
+ return sourceStack;
+ }
+
+ return ItemStack.EMPTY;
+ }
+
+ public static List getTooltipData(ItemStack sulfurStack) {
+ if (sulfurStack.getItem() instanceof AlchemicalSulfurItem sulfur && sulfur.provideAutomaticTooltipData) {
+ return ImmutableList.of(sulfur.getSourceName(sulfurStack));
+ }
+
+ return ImmutableList.of();
+ }
+
+ public static MutableComponent formatSourceName(MutableComponent sourceName) {
+ return sourceName.withStyle(Style.EMPTY
+ .withColor(ChatFormatting.GREEN)
+ .withItalic(true)
+ );
+ }
+
+ public AlchemicalSulfurItem noAuto() {
+ this.useAutomaticIconRendering = false;
+ this.useAutomaticNameRendering = false;
+ this.provideAutomaticTooltipData = false;
+ this.overrideTagSourceName = false;
+ return this;
+ }
+
+ public AlchemicalSulfurItem autoIcon(boolean value) {
+ this.useAutomaticIconRendering = value;
+ return this;
+ }
+
+ public AlchemicalSulfurItem autoName(boolean value) {
+ this.useAutomaticNameRendering = value;
+ return this;
+ }
+
+ public AlchemicalSulfurItem autoTooltip(boolean value) {
+ this.provideAutomaticTooltipData = value;
+ return this;
+ }
+
+ public AlchemicalSulfurItem overrideTagSourceName(boolean value) {
+ this.overrideTagSourceName = value;
+ return this;
+ }
+
+ public AlchemicalSulfurItem overrideSourceName(boolean value) {
+ this.overrideSourceName = value;
+ this.autoGenerateSourceIdInRecipe = !value;
+ return this;
+ }
+
+ public AlchemicalSulfurItem autoGenerateSourceIdInRecipe(boolean value) {
+ this.autoGenerateSourceIdInRecipe = value;
+ return this;
+ }
+
+ public MutableComponent getSourceName(ItemStack pStack) {
+ if (this.overrideSourceName) {
+ return formatSourceName(Component.translatable(pStack.getDescriptionId() + TheurgyConstants.I18n.Item.ALCHEMICAL_SULFUR_SOURCE_SUFFIX));
+ }
+
+ var source = getSourceStack(pStack);
+
+ if (!source.isEmpty()) {
+ var sourceId = getSourceItemId(pStack);
+ if (sourceId.startsWith("#") && this.overrideTagSourceName) {
+ var tagId = new ResourceLocation(sourceId.substring(1));
+ return formatSourceName(Component.translatable(Util.makeDescriptionId("tag", tagId)));
+ }
+
+ if (source.getHoverName() instanceof MutableComponent hoverName)
+ return formatSourceName(hoverName);
+ }
+
+ return Component.translatable(TheurgyConstants.I18n.Item.ALCHEMICAL_SULFUR_UNKNOWN_SOURCE);
+ }
+
+ @Override
+ public void initializeClient(Consumer consumer) {
+ consumer.accept(new IClientItemExtensions() {
+ @Override
+ public BlockEntityWithoutLevelRenderer getCustomRenderer() {
+ return AlchemicalSulfurBEWLR.get();
+ }
+ });
+ }
+
+ @Override
+ public Component getName(ItemStack pStack) {
+ if (this.useAutomaticNameRendering) {
+ return Component.translatable(this.getDescriptionId(pStack), ComponentUtils.wrapInSquareBrackets(
+ this.getSourceName(pStack)
+ ));
+ }
+ return super.getName(pStack);
+ }
+}
diff --git a/src/main/java/com/klikli_dev/theurgy/item/DivinationRodItem.java b/src/main/java/com/klikli_dev/theurgy/content/item/DivinationRodItem.java
similarity index 75%
rename from src/main/java/com/klikli_dev/theurgy/item/DivinationRodItem.java
rename to src/main/java/com/klikli_dev/theurgy/content/item/DivinationRodItem.java
index 8a731008b..506b846c9 100644
--- a/src/main/java/com/klikli_dev/theurgy/item/DivinationRodItem.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/item/DivinationRodItem.java
@@ -1,18 +1,19 @@
/*
- * SPDX-FileCopyrightText: 2022 klikli-dev
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
* SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.item;
+package com.klikli_dev.theurgy.content.item;
import com.klikli_dev.theurgy.TheurgyConstants;
-import com.klikli_dev.theurgy.client.scanner.ScanManager;
-import com.klikli_dev.theurgy.entity.FollowProjectile;
+import com.klikli_dev.theurgy.content.entity.FollowProjectile;
import com.klikli_dev.theurgy.network.Networking;
import com.klikli_dev.theurgy.network.messages.MessageSetDivinationResult;
+import com.klikli_dev.theurgy.registry.BlockTagRegistry;
import com.klikli_dev.theurgy.registry.SoundRegistry;
-import com.klikli_dev.theurgy.registry.TagRegistry;
+import com.klikli_dev.theurgy.scanner.ScanManager;
+import com.klikli_dev.theurgy.util.TagUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -39,7 +40,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.TierSortingRegistry;
-import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.Nullable;
@@ -72,6 +72,51 @@ public DivinationRodItem(Properties pProperties, Tier defaultTier, TagKey
this.defaultAllowAttuning = defaultAllowAttuning;
}
+ public static String getLinkedBlockId(ItemStack divinationRod) {
+ return divinationRod.getOrCreateTag().getString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID);
+ }
+
+ public static boolean hasLinkedBlock(ItemStack divinationRod) {
+ return divinationRod.hasTag() && divinationRod.getTag().contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID);
+ }
+
+ public static ItemStack getLinkedBlockStack(ItemStack divinationRod) {
+ if (hasLinkedBlock(divinationRod)) {
+ var targetId = getLinkedBlockId(divinationRod);
+ ItemStack targetStack;
+
+ if (targetId.startsWith("#")) {
+ var tagId = new ResourceLocation(targetId.substring(1));
+ var tag = TagKey.create(Registries.ITEM, tagId);
+ targetStack = TagUtil.getItemStackForTag(tag);
+ } else {
+ var itemId = new ResourceLocation(targetId);
+ targetStack = new ItemStack(ForgeRegistries.ITEMS.getValue(itemId));
+ }
+
+ return targetStack;
+ }
+
+ return ItemStack.EMPTY;
+ }
+
+ private static void scanLinkedBlock(Player player, String id, int range, int duration) {
+ var targetId = new ResourceLocation(id);
+
+ var blocks = getScanTargetsForId(targetId);
+ ScanManager.get().beginScan(player, blocks, range, duration);
+ }
+
+ private static void scanLinkedTag(Player player, String id, int range, int duration) {
+ var targetId = new ResourceLocation(id.substring(1)); //skip the #
+ var tagKey = TagKey.create(Registries.BLOCK, targetId);
+ var blocks = ForgeRegistries.BLOCKS.tags().getTag(tagKey).stream().collect(Collectors.toSet());
+
+ if (!blocks.isEmpty()) {
+ ScanManager.get().beginScan(player, blocks, range, duration);
+ }
+ }
+
public static Set getScanTargetsForId(ResourceLocation linkedBlockId) {
//First: try to get a tag for the given block.
var tagKey = TagKey.create(Registries.BLOCK, getOreTagFromBlockId(linkedBlockId));
@@ -207,7 +252,7 @@ public InteractionResultHolder use(Level level, Player player, Intera
var stack = player.getItemInHand(hand);
if (!player.isShiftKeyDown()) {
- if (stack.getOrCreateTag().contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID)) {
+ if (hasLinkedBlock(stack)) {
var tag = stack.getTag();
tag.putFloat(TheurgyConstants.Nbt.Divination.DISTANCE, SEARCHING);
player.startUsingItem(hand);
@@ -215,14 +260,17 @@ public InteractionResultHolder use(Level level, Player player, Intera
1, 1);
if (level.isClientSide) {
- var id = new ResourceLocation(stack.getTag().getString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID));
-
- var blocks = getScanTargetsForId(id);
- ScanManager.get().beginScan(player,
- blocks,
- tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_RANGE),
- tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_DURATION)
- );
+ var targetId = getLinkedBlockId(stack);
+
+ if (targetId.startsWith("#")) {
+ scanLinkedTag(player, targetId,
+ tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_RANGE),
+ tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_DURATION));
+ } else {
+ scanLinkedBlock(player, targetId,
+ tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_RANGE),
+ tag.getInt(TheurgyConstants.Nbt.Divination.SETTING_DURATION));
+ }
}
} else if (!level.isClientSide) {
player.sendSystemMessage(Component.translatable(TheurgyConstants.I18n.Message.DIVINATION_ROD_NO_LINK));
@@ -237,6 +285,7 @@ public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity enti
if (!(entityLiving instanceof Player player))
return stack;
+
if (stack.getDamageValue() >= stack.getMaxDamage()) {
//if in the last usage cycle the item was used up, we now actually break it to avoid over-use
player.broadcastBreakEvent(player.getUsedItemHand());
@@ -261,9 +310,11 @@ public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity enti
this.spawnResultParticle(result, level, player);
}
} else {
- //only hurt, but do not break -> this allows using the rod without breaking it when we just re-use a saved result.
- //we break it at the beginning of this method if we are at >= max damage.
- stack.hurt(1, player.getRandom(), null);
+ //no damage for players in creative mode
+ if (!player.getAbilities().instabuild)
+ //only hurt, but do not break -> this allows using the rod without breaking it when we just re-use a saved result.
+ //we break it at the beginning of this method if we are at >= max damage.
+ stack.hurt(1, player.getRandom(), null);
}
return stack;
}
@@ -301,19 +352,21 @@ public void releaseUsing(ItemStack stack, Level level, LivingEntity pLivingEntit
@Override
public Component getName(ItemStack pStack) {
- if (pStack.hasTag()) {
- var tag = pStack.getTag();
- if (tag.contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID)) {
- var id = new ResourceLocation(pStack.getTag().getString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID));
- var block = ForgeRegistries.BLOCKS.getValue(id);
- if (block != null) {
- //we''re not using getBlockDisplayComponent because we want custom formatting
- var blockComponent = ComponentUtils.wrapInSquareBrackets(
- block.getName().withStyle(Style.EMPTY.withColor(ChatFormatting.GREEN).withItalic(true))
- )
- .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(new ItemStack(block)))));
- return Component.translatable(this.getDescriptionId() + ".linked", blockComponent);
- }
+ if (hasLinkedBlock(pStack)) {
+ var stack = getLinkedBlockStack(pStack);
+ if (!stack.isEmpty()) {
+ var blockComponent = ComponentUtils.wrapInSquareBrackets(
+ Component.empty().append(stack.getHoverName()).withStyle(Style.EMPTY.withColor(ChatFormatting.GREEN).withItalic(true))
+ )
+ .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(stack))));
+ return Component.translatable(this.getDescriptionId() + ".linked", blockComponent);
+ } else {
+ //in case the block is not found, we indicate something went wrong
+ var blockComponent = ComponentUtils.wrapInSquareBrackets(
+ Component.translatable(TheurgyConstants.I18n.Item.DIVINATION_ROD_UNKNOWN_LINKED_BLOCK).withStyle(Style.EMPTY.withColor(ChatFormatting.RED).withItalic(true))
+ )
+ .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(stack))));
+ return Component.translatable(this.getDescriptionId() + ".linked", blockComponent);
}
}
@@ -353,32 +406,31 @@ public void verifyTagAfterLoad(CompoundTag tag) {
@Override
public void appendHoverText(ItemStack pStack, @Nullable Level pLevel, List pTooltipComponents, TooltipFlag pIsAdvanced) {
- if (pStack.hasTag()) {
- var tag = pStack.getTag();
- if (tag.contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID)) {
- var id = new ResourceLocation(pStack.getTag().getString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID));
- var block = ForgeRegistries.BLOCKS.getValue(id);
- if (block != null) {
- var blockComponent = this.getBlockDisplayComponent(block);
- pTooltipComponents.add(
- Component.translatable(
- TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_LINKED_TO,
- blockComponent
- ).withStyle(ChatFormatting.GRAY));
-
- if (tag.contains(TheurgyConstants.Nbt.Divination.POS)) {
- var pos = BlockPos.of(tag.getLong(TheurgyConstants.Nbt.Divination.POS));
- pTooltipComponents.add(Component.translatable(TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_LAST_RESULT,
- blockComponent,
- ComponentUtils.wrapInSquareBrackets(Component.literal(pos.toShortString())).withStyle(ChatFormatting.GREEN)
+ if (hasLinkedBlock(pStack)) {
+ var stack = getLinkedBlockStack(pStack);
+ if (!stack.isEmpty()) {
+ var blockComponent = Component.empty().append(stack.getHoverName())
+ .withStyle(ChatFormatting.GREEN)
+ .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(stack))));
+
+ this.getBlockDisplayComponent(stack);
+ pTooltipComponents.add(
+ Component.translatable(
+ TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_LINKED_TO,
+ blockComponent
).withStyle(ChatFormatting.GRAY));
- }
- }
- } else {
- pTooltipComponents.add(Component.translatable(TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_NO_LINK));
+ if (pStack.getTag().contains(TheurgyConstants.Nbt.Divination.POS)) {
+ var pos = BlockPos.of(pStack.getTag().getLong(TheurgyConstants.Nbt.Divination.POS));
+ pTooltipComponents.add(Component.translatable(TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_LAST_RESULT,
+ blockComponent,
+ ComponentUtils.wrapInSquareBrackets(Component.literal(pos.toShortString()).withStyle(ChatFormatting.GREEN))
+ ).withStyle(ChatFormatting.GRAY));
+ }
}
+ } else {
+ pTooltipComponents.add(Component.translatable(TheurgyConstants.I18n.Tooltip.DIVINATION_ROD_NO_LINK));
}
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced);
@@ -416,10 +468,14 @@ public float getDistance(Vec3 playerPosition, BlockPos result) {
}
protected MutableComponent getBlockDisplayComponent(Block block) {
- var displayName = block.getName();
+ return this.getBlockDisplayComponent(new ItemStack(block));
+ }
+
+ protected MutableComponent getBlockDisplayComponent(ItemStack stack) {
+ var displayName = stack.getHoverName();
return ComponentUtils.wrapInSquareBrackets(displayName)
.withStyle(ChatFormatting.GREEN)
- .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(new ItemStack(block)))));
+ .withStyle((style) -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(stack))));
}
protected void spawnResultParticle(BlockPos result, Level level, LivingEntity entity) {
@@ -443,12 +499,12 @@ public Tier getMiningTier(ItemStack stack) {
public TagKey getAllowedBlocksTag(ItemStack stack) {
var allowedBlocksTag = stack.getOrCreateTag().getString(TheurgyConstants.Nbt.Divination.SETTING_ALLOWED_BLOCKS_TAG);
- return TagRegistry.makeBlockTag(new ResourceLocation(allowedBlocksTag));
+ return BlockTagRegistry.tag(new ResourceLocation(allowedBlocksTag));
}
public TagKey getDisallowedBlocksTag(ItemStack stack) {
var disallowedBlocksTag = stack.getOrCreateTag().getString(TheurgyConstants.Nbt.Divination.SETTING_DISALLOWED_BLOCKS_TAG);
- return TagRegistry.makeBlockTag(new ResourceLocation(disallowedBlocksTag));
+ return BlockTagRegistry.tag(new ResourceLocation(disallowedBlocksTag));
}
diff --git a/src/main/java/com/klikli_dev/theurgy/client/render/SulfurBEWLR.java b/src/main/java/com/klikli_dev/theurgy/content/item/render/AlchemicalSulfurBEWLR.java
similarity index 89%
rename from src/main/java/com/klikli_dev/theurgy/client/render/SulfurBEWLR.java
rename to src/main/java/com/klikli_dev/theurgy/content/item/render/AlchemicalSulfurBEWLR.java
index eaea6fd6c..30dcf84e7 100644
--- a/src/main/java/com/klikli_dev/theurgy/client/render/SulfurBEWLR.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/item/render/AlchemicalSulfurBEWLR.java
@@ -1,13 +1,13 @@
/*
- * SPDX-FileCopyrightText: 2022 klikli-dev
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
* SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.client.render;
+package com.klikli_dev.theurgy.content.item.render;
import com.klikli_dev.theurgy.config.ClientConfig;
-import com.klikli_dev.theurgy.item.AlchemicalSulfurItem;
+import com.klikli_dev.theurgy.content.item.AlchemicalSulfurItem;
import com.klikli_dev.theurgy.registry.ItemRegistry;
import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.vertex.PoseStack;
@@ -19,18 +19,18 @@
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
-public class SulfurBEWLR extends BlockEntityWithoutLevelRenderer {
+public class AlchemicalSulfurBEWLR extends BlockEntityWithoutLevelRenderer {
- private static final SulfurBEWLR instance = new SulfurBEWLR();
+ private static final AlchemicalSulfurBEWLR instance = new AlchemicalSulfurBEWLR();
private static final ItemStack emptyJarStack = new ItemStack(ItemRegistry.EMPTY_JAR.get());
private static final ItemStack labeledEmptyJarStack = new ItemStack(ItemRegistry.EMPTY_JAR_LABELED.get());
private static final ItemStack labelStack = new ItemStack(ItemRegistry.JAR_LABEL.get());
- public SulfurBEWLR() {
+ public AlchemicalSulfurBEWLR() {
super(null, null);
}
- public static SulfurBEWLR get() {
+ public static AlchemicalSulfurBEWLR get() {
return instance;
}
@@ -98,7 +98,7 @@ public void renderLabel(ItemStack sulfurStack, ItemDisplayContext displayContext
pPoseStack.popPose();
}
- public void renderContainedItem(ItemStack sulfurStack, ItemDisplayContext displayContext, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) {
+ public void renderContainedItem(ItemStack sulfurStack, ItemDisplayContext pTransformType, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) {
ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
@@ -107,9 +107,10 @@ public void renderContainedItem(ItemStack sulfurStack, ItemDisplayContext displa
BakedModel containedModel = itemRenderer.getModel(containedStack, null, null, 0);
BakedModel labelModel = itemRenderer.getModel(labelStack, null, null, 0);
pPoseStack.pushPose();
+
//now apply the transform to the contained item to make it look right in-world -> because below we render with gui transform which would mess it up
//despite this returning a model (self in fact) it actually modifies the pose stack, hence the pushPose above!
- labelModel.applyTransform(displayContext, pPoseStack, isLeftHand(displayContext)); //reuse the label transform to simulate flat items even if the contained item is 3d
+ labelModel.applyTransform(pTransformType, pPoseStack, isLeftHand(pTransformType)); //reuse the label transform to simulate flat items even if the contained item is 3d
pPoseStack.pushPose();
@@ -122,7 +123,7 @@ public void renderContainedItem(ItemStack sulfurStack, ItemDisplayContext displa
pPoseStack.scale(0.74F, 0.74F, 0.01F); //flatten item
Lighting.setupForFlatItems(); //always render "labeled" item flat
- itemRenderer.render(containedStack, ItemDisplayContext.GUI, isLeftHand(displayContext), pPoseStack, pBuffer, pPackedLight, pPackedOverlay, containedModel);
+ itemRenderer.render(containedStack, ItemDisplayContext.GUI, isLeftHand(pTransformType), pPoseStack, pBuffer, pPackedLight, pPackedOverlay, containedModel);
//note: if we reset to 3d item light here it ignores it above and renders dark .. idk why
pPoseStack.popPose();
diff --git a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleColor.java b/src/main/java/com/klikli_dev/theurgy/content/particle/ParticleColor.java
similarity index 92%
rename from src/main/java/com/klikli_dev/theurgy/client/particle/ParticleColor.java
rename to src/main/java/com/klikli_dev/theurgy/content/particle/ParticleColor.java
index f40dcce55..37669fb57 100644
--- a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleColor.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/ParticleColor.java
@@ -1,10 +1,10 @@
/*
- * SPDX-FileCopyrightText: 2022 the original author or authors of Ars Noveau https://github.com/baileyholl/Ars-Nouveau
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
- * SPDX-License-Identifier: LGPL-3.0-only
+ * SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.client.particle;
+package com.klikli_dev.theurgy.content.particle;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.RandomSource;
@@ -90,7 +90,7 @@ public CompoundTag serialize() {
}
public String toString() {
- return "" + this.r + "," + this.g + "," + this.b;
+ return this.r + "," + this.g + "," + this.b;
}
public IntWrapper toWrapper() {
@@ -132,7 +132,7 @@ public IntWrapper(ParticleColor color) {
this.b = (int) (color.getBlue() * 255.0);
}
- public ParticleColor toParticleColor() {
+ public ParticleColor toColorParticle() {
return new ParticleColor(this.r, this.g, this.b);
}
diff --git a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleRenderTypes.java b/src/main/java/com/klikli_dev/theurgy/content/particle/ParticleRenderTypes.java
similarity index 85%
rename from src/main/java/com/klikli_dev/theurgy/client/particle/ParticleRenderTypes.java
rename to src/main/java/com/klikli_dev/theurgy/content/particle/ParticleRenderTypes.java
index 9395bd99a..a473829e8 100644
--- a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleRenderTypes.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/ParticleRenderTypes.java
@@ -1,11 +1,11 @@
/*
- * SPDX-FileCopyrightText: 2022 the original author or authors of Ars Noveau https://github.com/baileyholl/Ars-Nouveau
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
- * SPDX-License-Identifier: LGPL-3.0-only
+ * SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.client.particle;
+package com.klikli_dev.theurgy.content.particle;
import com.klikli_dev.theurgy.Theurgy;
import com.mojang.blaze3d.platform.GlStateManager;
@@ -20,8 +20,9 @@
import net.minecraft.client.renderer.texture.TextureManager;
public class ParticleRenderTypes {
- static final ParticleRenderType EMBER_RENDER = new ParticleRenderType() {
+ public static final ParticleRenderType EMBER_RENDER = new ParticleRenderType() {
@Override
+ @SuppressWarnings("deprecation")
public void begin(BufferBuilder buffer, TextureManager textureManager) {
Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer();
RenderSystem.enableBlend();
@@ -44,8 +45,9 @@ public String toString() {
}
};
- static final ParticleRenderType EMBER_RENDER_NO_MASK = new ParticleRenderType() {
+ public static final ParticleRenderType EMBER_RENDER_NO_MASK = new ParticleRenderType() {
@Override
+ @SuppressWarnings("deprecation")
public void begin(BufferBuilder buffer, TextureManager textureManager) {
RenderSystem.disableDepthTest();
RenderSystem.enableBlend();
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticle.java b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticle.java
new file mode 100644
index 000000000..f1ad1b251
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticle.java
@@ -0,0 +1,82 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+
+package com.klikli_dev.theurgy.content.particle.coloredbubble;
+
+
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.particle.ParticleRenderType;
+import net.minecraft.client.particle.SpriteSet;
+import net.minecraft.client.particle.TextureSheetParticle;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import org.joml.Math;
+
+@OnlyIn(Dist.CLIENT)
+public class ColoredBubbleParticle extends TextureSheetParticle {
+
+ private final SpriteSet sprite;
+ public float colorR;
+ public float colorG;
+ public float colorB;
+
+
+ public ColoredBubbleParticle(ClientLevel worldIn, double x, double y, double z, double vx, double vy, double vz, float r, float g, float b, SpriteSet sprite) {
+ super(worldIn, x, y, z, 0, 0, 0);
+ this.hasPhysics = false;
+
+ this.colorR = r;
+ this.colorG = g;
+ this.colorB = b;
+ if (this.colorR > 1.0) {
+ this.colorR = this.colorR / 255.0f;
+ }
+ if (this.colorG > 1.0) {
+ this.colorG = this.colorG / 255.0f;
+ }
+ if (this.colorB > 1.0) {
+ this.colorB = this.colorB / 255.0f;
+ }
+ this.setColor(this.colorR, this.colorG, this.colorB);
+
+ this.lifetime = 10;
+
+ this.quadSize = 0.05f;
+
+ this.xd = vx;
+ this.yd = vy;
+ this.zd = vz;
+
+ this.sprite = sprite;
+ this.pickSprite(this.sprite);
+ }
+
+ @Override
+ public ParticleRenderType getRenderType() {
+ return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
+ }
+
+
+ @Override
+ public void tick() {
+ super.tick();
+ float progress = (float) this.age / this.lifetime;
+ //change particle based on age progress
+ this.setAlpha(Math.lerp(progress, 1.0f, 0.75f));
+
+ //slow down y motion over time
+ this.yd *= 0.75f;
+
+ //select new visual based on updated age
+ this.setSpriteFromAge(this.sprite);
+ }
+
+ @Override
+ public boolean isAlive() {
+ return this.age < this.lifetime;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleOptions.java b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleOptions.java
new file mode 100644
index 000000000..1e504f6dc
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleOptions.java
@@ -0,0 +1,69 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.particle.coloredbubble;
+
+import com.klikli_dev.theurgy.content.particle.ParticleColor;
+import com.klikli_dev.theurgy.registry.ParticleRegistry;
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.core.particles.ParticleOptions;
+import net.minecraft.core.particles.ParticleType;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraftforge.registries.ForgeRegistries;
+
+public class ColoredBubbleParticleOptions implements ParticleOptions {
+
+ public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.FLOAT.fieldOf("r").forGetter(d -> d.color.getRed()),
+ Codec.FLOAT.fieldOf("g").forGetter(d -> d.color.getGreen()),
+ Codec.FLOAT.fieldOf("b").forGetter(d -> d.color.getBlue())
+ )
+ .apply(instance, ColoredBubbleParticleOptions::new));
+
+ @SuppressWarnings("deprecation")
+ public static final ParticleOptions.Deserializer DESERIALIZER = new ParticleOptions.Deserializer<>() {
+ @Override
+ public ColoredBubbleParticleOptions fromCommand(ParticleType type, StringReader reader) throws CommandSyntaxException {
+ reader.expect(' ');
+ return new ColoredBubbleParticleOptions(type, ParticleColor.fromString(reader.readString()));
+ }
+
+ @Override
+ public ColoredBubbleParticleOptions fromNetwork(ParticleType type, FriendlyByteBuf buffer) {
+ return new ColoredBubbleParticleOptions(type, ParticleColor.deserialize(buffer.readNbt()));
+ }
+ };
+ private final ParticleType type;
+ public ParticleColor color;
+
+ public ColoredBubbleParticleOptions(float r, float g, float b) {
+ this(ParticleRegistry.COLORED_BUBBLE_TYPE.get(), new ParticleColor(r, g, b));
+ }
+
+ public ColoredBubbleParticleOptions(ParticleType particleTypeData, ParticleColor color) {
+ this.type = particleTypeData;
+ this.color = color;
+ }
+
+
+ @Override
+ public ParticleType getType() {
+ return this.type;
+ }
+
+ @Override
+ public void writeToNetwork(FriendlyByteBuf packetBuffer) {
+ packetBuffer.writeNbt(this.color.serialize());
+ }
+
+ @Override
+ public String writeToString() {
+ return ForgeRegistries.PARTICLE_TYPES.getKey(this.type) + " " + this.color.serialize();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleProvider.java b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleProvider.java
new file mode 100644
index 000000000..50e90182a
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleProvider.java
@@ -0,0 +1,33 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+
+package com.klikli_dev.theurgy.content.particle.coloredbubble;
+
+import com.klikli_dev.theurgy.content.particle.ParticleColor;
+import com.klikli_dev.theurgy.registry.ParticleRegistry;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.particle.Particle;
+import net.minecraft.client.particle.ParticleProvider;
+import net.minecraft.client.particle.SpriteSet;
+import net.minecraft.core.particles.ParticleOptions;
+
+public class ColoredBubbleParticleProvider implements ParticleProvider {
+ private final SpriteSet spriteSet;
+
+ public ColoredBubbleParticleProvider(SpriteSet sprite) {
+ this.spriteSet = sprite;
+ }
+
+ public static ParticleOptions createOptions(ParticleColor color) {
+ return new ColoredBubbleParticleOptions(ParticleRegistry.COLORED_BUBBLE_TYPE.get(), color);
+ }
+
+ @Override
+ public Particle createParticle(ColoredBubbleParticleOptions data, ClientLevel worldIn, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) {
+ return new ColoredBubbleParticle(worldIn, x, y, z, xSpeed, ySpeed, zSpeed, data.color.getRed(), data.color.getGreen(), data.color.getBlue(), this.spriteSet);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleType.java b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleType.java
new file mode 100644
index 000000000..a62dc5451
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/coloredbubble/ColoredBubbleParticleType.java
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.particle.coloredbubble;
+
+import com.mojang.serialization.Codec;
+import net.minecraft.core.particles.ParticleType;
+
+public class ColoredBubbleParticleType extends ParticleType {
+ public ColoredBubbleParticleType() {
+ super(false, ColoredBubbleParticleOptions.DESERIALIZER);
+ }
+
+ @Override
+ public Codec codec() {
+ return ColoredBubbleParticleOptions.CODEC;
+ }
+}
diff --git a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleGlow.java b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticle.java
similarity index 87%
rename from src/main/java/com/klikli_dev/theurgy/client/particle/ParticleGlow.java
rename to src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticle.java
index 6058bab1f..b9e4c7ba6 100644
--- a/src/main/java/com/klikli_dev/theurgy/client/particle/ParticleGlow.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticle.java
@@ -1,13 +1,14 @@
/*
- * SPDX-FileCopyrightText: 2022 the original author or authors of Ars Noveau https://github.com/baileyholl/Ars-Nouveau
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
- * SPDX-License-Identifier: LGPL-3.0-only
+ * SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.client.particle;
+package com.klikli_dev.theurgy.content.particle.glow;
+import com.klikli_dev.theurgy.content.particle.ParticleRenderTypes;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
@@ -18,7 +19,7 @@
import java.util.Random;
@OnlyIn(Dist.CLIENT)
-public class ParticleGlow extends TextureSheetParticle {
+public class GlowParticle extends TextureSheetParticle {
public float colorR = 0;
public float colorG = 0;
public float colorB = 0;
@@ -27,7 +28,7 @@ public class ParticleGlow extends TextureSheetParticle {
public boolean disableDepthTest;
- public ParticleGlow(ClientLevel worldIn, double x, double y, double z, double vx, double vy, double vz, float r, float g, float b, float a, float scale, int lifetime, SpriteSet sprite, boolean disableDepthTest) {
+ public GlowParticle(ClientLevel worldIn, double x, double y, double z, double vx, double vy, double vz, float r, float g, float b, float a, float scale, int lifetime, SpriteSet sprite, boolean disableDepthTest) {
super(worldIn, x, y, z, 0, 0, 0);
this.hasPhysics = false;
diff --git a/src/main/java/com/klikli_dev/theurgy/client/particle/ColorParticleTypeData.java b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleOptions.java
similarity index 58%
rename from src/main/java/com/klikli_dev/theurgy/client/particle/ColorParticleTypeData.java
rename to src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleOptions.java
index 60b785abf..a04b90bac 100644
--- a/src/main/java/com/klikli_dev/theurgy/client/particle/ColorParticleTypeData.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleOptions.java
@@ -1,11 +1,12 @@
/*
- * SPDX-FileCopyrightText: 2022 the original author or authors of Ars Noveau https://github.com/baileyholl/Ars-Nouveau
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
- * SPDX-License-Identifier: LGPL-3.0-only
+ * SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.client.particle;
+package com.klikli_dev.theurgy.content.particle.glow;
+import com.klikli_dev.theurgy.content.particle.ParticleColor;
import com.klikli_dev.theurgy.registry.ParticleRegistry;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -21,9 +22,9 @@
* Simplified version of ElementalCraft
*/
-public class ColorParticleTypeData implements ParticleOptions {
+public class GlowParticleOptions implements ParticleOptions {
- public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.FLOAT.fieldOf("r").forGetter(d -> d.color.getRed()),
Codec.FLOAT.fieldOf("g").forGetter(d -> d.color.getGreen()),
Codec.FLOAT.fieldOf("b").forGetter(d -> d.color.getBlue()),
@@ -32,39 +33,41 @@ public class ColorParticleTypeData implements ParticleOptions {
Codec.FLOAT.fieldOf("alpha").forGetter(d -> d.alpha),
Codec.INT.fieldOf("age").forGetter(d -> d.age)
)
- .apply(instance, ColorParticleTypeData::new));
- static final Deserializer DESERIALIZER = new Deserializer<>() {
+ .apply(instance, GlowParticleOptions::new));
+
+ @SuppressWarnings("deprecation")
+ public static final Deserializer DESERIALIZER = new Deserializer<>() {
@Override
- public ColorParticleTypeData fromCommand(ParticleType type, StringReader reader) throws CommandSyntaxException {
+ public GlowParticleOptions fromCommand(ParticleType type, StringReader reader) throws CommandSyntaxException {
reader.expect(' ');
- return new ColorParticleTypeData(type, ParticleColor.fromString(reader.readString()), reader.readBoolean());
+ return new GlowParticleOptions(type, ParticleColor.fromString(reader.readString()), reader.readBoolean());
}
@Override
- public ColorParticleTypeData fromNetwork(ParticleType type, FriendlyByteBuf buffer) {
- return new ColorParticleTypeData(type, ParticleColor.deserialize(buffer.readNbt()), buffer.readBoolean());
+ public GlowParticleOptions fromNetwork(ParticleType type, FriendlyByteBuf buffer) {
+ return new GlowParticleOptions(type, ParticleColor.deserialize(buffer.readNbt()), buffer.readBoolean());
}
};
- private final ParticleType type;
+ private final ParticleType type;
public ParticleColor color;
public boolean disableDepthTest;
public float size = .25f;
public float alpha = 1.0f;
public int age = 36;
- public ColorParticleTypeData(float r, float g, float b, boolean disableDepthTest, float size, float alpha, int age) {
+ public GlowParticleOptions(float r, float g, float b, boolean disableDepthTest, float size, float alpha, int age) {
this(ParticleRegistry.GLOW_TYPE.get(), new ParticleColor(r, g, b), disableDepthTest, size, alpha, age);
}
- public ColorParticleTypeData(ParticleColor color, boolean disableDepthTest, float size, float alpha, int age) {
+ public GlowParticleOptions(ParticleColor color, boolean disableDepthTest, float size, float alpha, int age) {
this(ParticleRegistry.GLOW_TYPE.get(), color, disableDepthTest, size, alpha, age);
}
- public ColorParticleTypeData(ParticleType particleTypeData, ParticleColor color, boolean disableDepthTest) {
+ public GlowParticleOptions(ParticleType particleTypeData, ParticleColor color, boolean disableDepthTest) {
this(particleTypeData, color, disableDepthTest, 0.25f, 1.0f, 36);
}
- public ColorParticleTypeData(ParticleType particleTypeData, ParticleColor color, boolean disableDepthTest, float size, float alpha, int age) {
+ public GlowParticleOptions(ParticleType particleTypeData, ParticleColor color, boolean disableDepthTest, float size, float alpha, int age) {
this.type = particleTypeData;
this.color = color;
this.disableDepthTest = disableDepthTest;
@@ -75,7 +78,7 @@ public ColorParticleTypeData(ParticleType particleTypeDat
@Override
- public ParticleType getType() {
+ public ParticleType getType() {
return this.type;
}
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleProvider.java b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleProvider.java
new file mode 100644
index 000000000..62bfe2894
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleProvider.java
@@ -0,0 +1,45 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+
+package com.klikli_dev.theurgy.content.particle.glow;
+
+import com.klikli_dev.theurgy.content.particle.ParticleColor;
+import com.klikli_dev.theurgy.registry.ParticleRegistry;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.particle.Particle;
+import net.minecraft.client.particle.ParticleProvider;
+import net.minecraft.client.particle.SpriteSet;
+import net.minecraft.core.particles.ParticleOptions;
+
+public class GlowParticleProvider implements ParticleProvider {
+ private final SpriteSet spriteSet;
+
+ public GlowParticleProvider(SpriteSet sprite) {
+ this.spriteSet = sprite;
+ }
+
+ public static ParticleOptions createOptions(ParticleColor color) {
+ return new GlowParticleOptions(ParticleRegistry.GLOW_TYPE.get(), color, false);
+ }
+
+ public static ParticleOptions createOptions(ParticleColor color, boolean disableDepthTest) {
+ return new GlowParticleOptions(ParticleRegistry.GLOW_TYPE.get(), color, disableDepthTest, 0.25f, 0.75f, 36);
+ }
+
+ public static ParticleOptions createOptions(ParticleColor color, boolean disableDepthTest, float size, float alpha, int age) {
+ return new GlowParticleOptions(color, disableDepthTest, size, alpha, age);
+ }
+
+ public static ParticleOptions createOptions(ParticleColor color, float size, float alpha, int age) {
+ return new GlowParticleOptions(color, false, size, alpha, age);
+ }
+
+ @Override
+ public Particle createParticle(GlowParticleOptions data, ClientLevel worldIn, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) {
+ return new GlowParticle(worldIn, x, y, z, xSpeed, ySpeed, zSpeed, data.color.getRed(), data.color.getGreen(), data.color.getBlue(), data.alpha, data.size, data.age, this.spriteSet, data.disableDepthTest);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleType.java b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleType.java
new file mode 100644
index 000000000..6383de1dc
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/particle/glow/GlowParticleType.java
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.particle.glow;
+
+import com.mojang.serialization.Codec;
+import net.minecraft.core.particles.ParticleType;
+
+public class GlowParticleType extends ParticleType {
+ public GlowParticleType() {
+ super(false, GlowParticleOptions.DESERIALIZER);
+ }
+
+ @Override
+ public Codec codec() {
+ return GlowParticleOptions.CODEC;
+ }
+}
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/AccumulationRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/AccumulationRecipe.java
new file mode 100644
index 000000000..479889d19
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/AccumulationRecipe.java
@@ -0,0 +1,176 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.klikli_dev.theurgy.content.recipe.ingredient.FluidIngredient;
+import com.klikli_dev.theurgy.content.recipe.wrapper.RecipeWrapperWithFluid;
+import com.klikli_dev.theurgy.registry.ItemRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import com.klikli_dev.theurgy.util.TheurgyExtraCodecs;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.core.NonNullList;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+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 org.jetbrains.annotations.Nullable;
+
+import java.util.Optional;
+
+public class AccumulationRecipe implements Recipe {
+ public static final int DEFAULT_ACCUMULATION_TIME = 200;
+
+ public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ FluidIngredient.CODEC.fieldOf("evaporant").forGetter((r) -> r.evaporant),
+ TheurgyExtraCodecs.INGREDIENT.optionalFieldOf("solute").forGetter(r -> Optional.ofNullable(r.solute)),
+ FluidStack.CODEC.fieldOf("result").forGetter(r -> r.result),
+ Codec.INT.optionalFieldOf("accumulation_time", DEFAULT_ACCUMULATION_TIME).forGetter(r -> r.accumulationTime)
+ ).apply(instance, (evaporant, solute, result, accumulation_time) -> new AccumulationRecipe(evaporant, solute.orElse(null), result, accumulation_time))
+ );
+ /**
+ * The fluid to evaporate to obtain the result.
+ */
+ protected final FluidIngredient evaporant;
+ /**
+ * The (optional) item to dissolve in the evaporant to obtain the result.
+ */
+ @Nullable
+ protected final Ingredient solute;
+ /**
+ * The result of the recipe.
+ */
+ protected final FluidStack result;
+ protected final int accumulationTime;
+ protected ResourceLocation id;
+
+ public AccumulationRecipe(FluidIngredient evaporant, @Nullable Ingredient solute, FluidStack result, int pAccumulationTime) {
+ this.evaporant = evaporant;
+ this.solute = solute;
+ this.result = result;
+ this.accumulationTime = pAccumulationTime;
+ }
+
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ @Override
+ public ResourceLocation getId() {
+ return this.id;
+ }
+
+ @Override
+ public RecipeType> getType() {
+ return RecipeTypeRegistry.ACCUMULATION.get();
+ }
+
+ @Override
+ public boolean matches(RecipeWrapperWithFluid pContainer, Level pLevel) {
+ boolean evaporantMatches = this.evaporant.test(pContainer.getTank().getFluidInTank(0));
+ boolean soluteMatches =
+ pContainer.getItem(0).isEmpty() && !this.hasSolute() || //if recipe requires no solute and container does not have one we're ok
+ this.hasSolute() && this.solute.test(pContainer.getItem(0)); // if recipe requires solute we check if the container has it
+
+ //note: it is important that if the container HAS a solute but the recipe does not require one, we do not match -> otherwise water -> sal ammoniac recipes would always match, even if the faster water + sal ammoniac crystal -> sal ammoniac recipe is available
+
+ return soluteMatches && evaporantMatches;
+ }
+
+ @Override
+ public ItemStack assemble(RecipeWrapperWithFluid pInv, RegistryAccess registryAccess) {
+ return ItemStack.EMPTY;
+ }
+
+ public FluidStack assembleFluid(RecipeWrapperWithFluid pInv, RegistryAccess registryAccess) {
+ return this.result.copy();
+ }
+
+ @Override
+ public boolean canCraftInDimensions(int pWidth, int pHeight) {
+ return true;
+ }
+
+ @Override
+ public ItemStack getResultItem(RegistryAccess registryAccess) {
+ return ItemStack.EMPTY;
+ }
+
+ @Override
+ public NonNullList getIngredients() {
+ NonNullList nonnulllist = NonNullList.create();
+ if (this.solute != null)
+ nonnulllist.add(this.solute);
+ return nonnulllist;
+ }
+
+ @Override
+ public ItemStack getToastSymbol() {
+ return new ItemStack(ItemRegistry.SAL_AMMONIAC_ACCUMULATOR.get());
+ }
+
+ @Override
+ public RecipeSerializer> getSerializer() {
+ return RecipeSerializerRegistry.ACCUMULATION.get();
+ }
+
+ public int getAccumulationTime() {
+ return this.accumulationTime;
+ }
+
+ public FluidIngredient getEvaporant() {
+ return this.evaporant;
+ }
+
+ @Nullable
+ public Ingredient getSolute() {
+ return this.solute;
+ }
+
+ public boolean hasSolute() {
+ return this.solute != null;
+ }
+
+ public FluidStack getResult() {
+ return this.result;
+ }
+
+ public static class Serializer implements RecipeSerializer {
+
+ @Override
+ public AccumulationRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
+ var recipe = CODEC.parse(JsonOps.INSTANCE, pJson).getOrThrow(false, s -> {
+ throw new JsonParseException(s);
+ });
+ recipe.id = pRecipeId;
+ return recipe;
+ }
+
+ @Override
+ public AccumulationRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
+ var recipe = pBuffer.readJsonWithCodec(CODEC);
+ recipe.id = pRecipeId;
+ return recipe;
+ }
+
+ @Override
+ public void toNetwork(FriendlyByteBuf pBuffer, AccumulationRecipe pRecipe) {
+ pBuffer.writeJsonWithCodec(CODEC, pRecipe);
+ }
+ }
+}
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/CalcinationRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/CalcinationRecipe.java
new file mode 100644
index 000000000..050b812cd
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/CalcinationRecipe.java
@@ -0,0 +1,135 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe;
+
+import com.google.gson.JsonObject;
+import com.klikli_dev.theurgy.registry.BlockRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import net.minecraft.core.NonNullList;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+import net.minecraft.world.item.crafting.RecipeSerializer;
+import net.minecraft.world.item.crafting.RecipeType;
+import net.minecraft.world.level.Level;
+import net.minecraftforge.common.crafting.CraftingHelper;
+import net.minecraftforge.items.wrapper.RecipeWrapper;
+
+public class CalcinationRecipe implements Recipe {
+
+ public static final int DEFAULT_CALCINATION_TIME = 200;
+ protected final ResourceLocation id;
+ protected final Ingredient ingredient;
+ protected final int ingredientCount;
+ protected final ItemStack result;
+ protected final int calcinationTime;
+
+ public CalcinationRecipe(ResourceLocation pId, Ingredient pIngredient, int ingredientCount, ItemStack pResult, int calcinationTime) {
+ this.id = pId;
+ this.ingredient = pIngredient;
+ this.ingredientCount = ingredientCount;
+ this.result = pResult;
+ this.calcinationTime = calcinationTime;
+ }
+
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ public int getIngredientCount() {
+ return this.ingredientCount;
+ }
+
+ @Override
+ public ResourceLocation getId() {
+ return this.id;
+ }
+
+ @Override
+ public RecipeType> getType() {
+ return RecipeTypeRegistry.CALCINATION.get();
+ }
+
+ @Override
+ public boolean matches(RecipeWrapper pContainer, Level pLevel) {
+ var stack = pContainer.getItem(0);
+ return this.ingredient.test(stack) && stack.getCount() >= this.ingredientCount;
+ }
+
+ @Override
+ public ItemStack assemble(RecipeWrapper pInv, RegistryAccess registryAccess) {
+ return this.result.copy();
+ }
+
+ @Override
+ public boolean canCraftInDimensions(int pWidth, int pHeight) {
+ return true;
+ }
+
+ @Override
+ public ItemStack getResultItem(RegistryAccess registryAccess) {
+ return this.result;
+ }
+
+ @Override
+ public NonNullList getIngredients() {
+ NonNullList nonnulllist = NonNullList.create();
+ nonnulllist.add(this.ingredient);
+ return nonnulllist;
+ }
+
+ @Override
+ public ItemStack getToastSymbol() {
+ return new ItemStack(BlockRegistry.CALCINATION_OVEN.get());
+ }
+
+ @Override
+ public RecipeSerializer> getSerializer() {
+ return RecipeSerializerRegistry.CALCINATION.get();
+ }
+
+ public int getCalcinationTime() {
+ return this.calcinationTime;
+ }
+
+ public static class Serializer implements RecipeSerializer {
+
+ @Override
+ public CalcinationRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
+ var ingredientElement = GsonHelper.isArrayNode(pJson, "ingredient") ? GsonHelper.getAsJsonArray(pJson, "ingredient") : GsonHelper.getAsJsonObject(pJson, "ingredient");
+ var ingredient = Ingredient.fromJson(ingredientElement);
+ var ingredientCount = GsonHelper.getAsInt(pJson, "ingredient_count", 1);
+ var result = CraftingHelper.getItemStack(GsonHelper.getAsJsonObject(pJson, "result"), true, true);
+
+ var calcinationTime = GsonHelper.getAsInt(pJson, "calcination_time", DEFAULT_CALCINATION_TIME);
+ return new CalcinationRecipe(pRecipeId, ingredient, ingredientCount, result, calcinationTime);
+ }
+
+ @Override
+ public CalcinationRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
+ var ingredient = Ingredient.fromNetwork(pBuffer);
+ var ingredientCount = pBuffer.readVarInt();
+ var result = pBuffer.readItem();
+ var calcinationTime = pBuffer.readVarInt();
+ return new CalcinationRecipe(pRecipeId, ingredient, ingredientCount, result, calcinationTime);
+ }
+
+ @Override
+ public void toNetwork(FriendlyByteBuf pBuffer, CalcinationRecipe pRecipe) {
+ pRecipe.ingredient.toNetwork(pBuffer);
+ pBuffer.writeVarInt(pRecipe.ingredientCount);
+ pBuffer.writeItem(pRecipe.result);
+ pBuffer.writeVarInt(pRecipe.calcinationTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/DistillationRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/DistillationRecipe.java
new file mode 100644
index 000000000..5de57846d
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/DistillationRecipe.java
@@ -0,0 +1,139 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe;
+
+import com.google.gson.JsonObject;
+import com.klikli_dev.theurgy.registry.BlockRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import net.minecraft.core.NonNullList;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+import net.minecraft.world.item.crafting.RecipeSerializer;
+import net.minecraft.world.item.crafting.RecipeType;
+import net.minecraft.world.level.Level;
+import net.minecraftforge.common.crafting.CraftingHelper;
+import net.minecraftforge.items.wrapper.RecipeWrapper;
+
+public class DistillationRecipe implements Recipe {
+
+ public static final int DEFAULT_DISTILLATION_TIME = 200;
+ protected final ResourceLocation id;
+ protected final Ingredient ingredient;
+ protected final int ingredientCount;
+ protected final ItemStack result;
+ protected final int distillationTime;
+
+ public DistillationRecipe(ResourceLocation pId, Ingredient pIngredient, int ingredientCount, ItemStack pResult, int distillationTime) {
+ this.id = pId;
+ this.ingredient = pIngredient;
+ this.ingredientCount = ingredientCount;
+ this.result = pResult;
+ this.distillationTime = distillationTime;
+ }
+
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ @Override
+ public ResourceLocation getId() {
+ return this.id;
+ }
+
+ @Override
+ public RecipeType> getType() {
+ return RecipeTypeRegistry.DISTILLATION.get();
+ }
+
+ @Override
+ public boolean matches(RecipeWrapper pContainer, Level pLevel) {
+ var stack = pContainer.getItem(0);
+ return this.ingredient.test(stack) && stack.getCount() >= this.ingredientCount;
+ }
+
+ @Override
+ public ItemStack assemble(RecipeWrapper pInv, RegistryAccess registryAccess) {
+ return this.result.copy();
+ }
+
+ @Override
+ public boolean canCraftInDimensions(int pWidth, int pHeight) {
+ return true;
+ }
+
+ @Override
+ public ItemStack getResultItem(RegistryAccess registryAccess) {
+ return this.result;
+ }
+
+ @Override
+ public NonNullList getIngredients() {
+ NonNullList nonnulllist = NonNullList.create();
+ nonnulllist.add(this.ingredient);
+ return nonnulllist;
+ }
+
+ public Ingredient getIngredient() {
+ return this.ingredient;
+ }
+
+ public int getIngredientCount() {
+ return this.ingredientCount;
+ }
+
+ @Override
+ public ItemStack getToastSymbol() {
+ return new ItemStack(BlockRegistry.DISTILLER.get());
+ }
+
+ @Override
+ public RecipeSerializer> getSerializer() {
+ return RecipeSerializerRegistry.DISTILLATION.get();
+ }
+
+ public int getDistillationTime() {
+ return this.distillationTime;
+ }
+
+ public static class Serializer implements RecipeSerializer {
+
+ @Override
+ public DistillationRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
+ var ingredientElement = GsonHelper.isArrayNode(pJson, "ingredient") ? GsonHelper.getAsJsonArray(pJson, "ingredient") : GsonHelper.getAsJsonObject(pJson, "ingredient");
+ var ingredient = Ingredient.fromJson(ingredientElement);
+ var ingredientCount = GsonHelper.getAsInt(pJson, "ingredient_count", 1);
+ var result = CraftingHelper.getItemStack(GsonHelper.getAsJsonObject(pJson, "result"), true, true);
+
+ var distillationTime = GsonHelper.getAsInt(pJson, "distillation_time", DEFAULT_DISTILLATION_TIME);
+ return new DistillationRecipe(pRecipeId, ingredient, ingredientCount, result, distillationTime);
+ }
+
+ @Override
+ public DistillationRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
+ var ingredient = Ingredient.fromNetwork(pBuffer);
+ var ingredientCount = pBuffer.readVarInt();
+ var result = pBuffer.readItem();
+ var distillationTime = pBuffer.readVarInt();
+ return new DistillationRecipe(pRecipeId, ingredient, ingredientCount, result, distillationTime);
+ }
+
+ @Override
+ public void toNetwork(FriendlyByteBuf pBuffer, DistillationRecipe pRecipe) {
+ pRecipe.ingredient.toNetwork(pBuffer);
+ pBuffer.writeVarInt(pRecipe.ingredientCount);
+ pBuffer.writeItem(pRecipe.result);
+ pBuffer.writeVarInt(pRecipe.distillationTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/recipe/DivinationRodRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/DivinationRodRecipe.java
similarity index 64%
rename from src/main/java/com/klikli_dev/theurgy/recipe/DivinationRodRecipe.java
rename to src/main/java/com/klikli_dev/theurgy/content/recipe/DivinationRodRecipe.java
index 5d2b8cc01..518b862f2 100644
--- a/src/main/java/com/klikli_dev/theurgy/recipe/DivinationRodRecipe.java
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/DivinationRodRecipe.java
@@ -1,20 +1,22 @@
/*
- * SPDX-FileCopyrightText: 2022 klikli-dev
+ * SPDX-FileCopyrightText: 2023 klikli-dev
*
* SPDX-License-Identifier: MIT
*/
-package com.klikli_dev.theurgy.recipe;
+package com.klikli_dev.theurgy.content.recipe;
import com.google.gson.JsonObject;
import com.klikli_dev.theurgy.Theurgy;
import com.klikli_dev.theurgy.TheurgyConstants;
import com.klikli_dev.theurgy.config.ServerConfig;
-import com.klikli_dev.theurgy.registry.RecipeRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
@@ -30,8 +32,14 @@ public DivinationRodRecipe(ResourceLocation pId, String pGroup, int pWidth, int
super(pId, pGroup, CraftingBookCategory.MISC, pWidth, pHeight, pRecipeItems, pResult);
}
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ @Override
public RecipeSerializer> getSerializer() {
- return RecipeRegistry.DIVINATION_ROD.get();
+ return RecipeSerializerRegistry.DIVINATION_ROD.get();
}
@Override
@@ -46,14 +54,14 @@ public ItemStack getResultItem(RegistryAccess registryAccess) {
//this allows JEI/creative menu to show the correct linked block id effects (altered item name, tooltip, etc)
if (!resultTag.contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID)) {
- ResourceLocation sourceId = null;
+ String sourceId = null;
for (var ingredient : this.getIngredients()) {
var json = ingredient.toJson();
if (json instanceof JsonObject jsonObj && jsonObj.has("nbt")) {
var ingredientTag = CraftingHelper.getNBT(jsonObj.get("nbt"));
if (ingredientTag.contains(TheurgyConstants.Nbt.SULFUR_SOURCE_ID)) {
- sourceId = new ResourceLocation(ingredientTag.getString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID));
+ sourceId = ingredientTag.getString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID);
break;
}
}
@@ -62,9 +70,9 @@ public ItemStack getResultItem(RegistryAccess registryAccess) {
if (sourceId != null) {
var translated = this.translateToBlock(sourceId);
if (translated != null) {
- resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, translated.toString());
+ resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, translated);
} else {
- resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, sourceId.toString());
+ resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, sourceId);
}
//we also set the preview mode, to allow the assemble() method to override based on the actual input.
resultTag.putBoolean(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID_PREVIEW_MODE, true);
@@ -78,47 +86,94 @@ public ItemStack getResultItem(RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer pInv, RegistryAccess registryAccess) {
var result = this.getResultItem(registryAccess).copy();
-
var resultTag = result.getOrCreateTag();
-
//if the recipe already has a linked block, we don't need to do the translation stuff.
//if that linked block is only a preview (coming from getResultItem(), used mainly for correct display in JEI),
// we have to ignore it and translate after all
if (!resultTag.contains(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID) ||
resultTag.getBoolean(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID_PREVIEW_MODE)
) {
+
//check pInv for ingredients with sulfur source id, if so, find the appropriate block id based on it and set it on the result item
- ResourceLocation sourceId = null;
+ String sourceId = null;
for (int i = 0; i < pInv.getContainerSize(); i++) {
var stack = pInv.getItem(i);
if (stack.hasTag()) {
var tag = stack.getTag();
if (tag.contains(TheurgyConstants.Nbt.SULFUR_SOURCE_ID)) {
- sourceId = new ResourceLocation(tag.getString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID));
+ sourceId = tag.getString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID);
break;
}
}
}
if (sourceId != null) {
- var translated = this.translateToBlock(sourceId);
+ var translated = sourceId.startsWith("#") ? this.translateTagToBlock(sourceId) : this.translateToBlock(sourceId);
if (translated != null) {
- resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, translated.toString());
+ resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, translated);
} else {
- resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, sourceId.toString());
+ resultTag.putString(TheurgyConstants.Nbt.Divination.LINKED_BLOCK_ID, sourceId);
}
}
}
- return super.assemble(pInv, registryAccess);
+ return result;
}
- public ResourceLocation translateToBlock(ResourceLocation sourceId) {
+ public String translateTagToBlock(String sourceTag) {
//first we check if we have a manual override mapping
- var mapped = ServerConfig.get().recipes.sulfurSourceToBlockMapping.get().get(sourceId.toString());
+ var mapped = ServerConfig.get().recipes.sulfurSourceToBlockMapping.get().get(sourceTag);
if (mapped != null)
- return new ResourceLocation(mapped);
+ return mapped;
+
+ //if not we use generic logic to translate ingot, storage block, nugget, raw, ore, dust to (ore)block.
+ //even though likely not all of these will be used to create sulfur its good to handle them.
+
+ //examples:
+ //target is forge:ores/iron
+ //forge:ingots/iron
+ //forge:nuggets/iron
+ //forge:raw_materials/iron
+ //forge:dusts/iron
+ //forge:storage_blocks/iron
+ //forge:gems/iron
+
+ //special handling for coal items as they are none of the above
+ if (sourceTag.equals("#minecraft:coals"))
+ return "#forge:ores/coal";
+
+ var parts = sourceTag.split(":");
+ var namespace = parts[0];
+ var path = parts[1];
+ var translatedPath = path;
+ if (path.contains("ingots/")) {
+ translatedPath = path.replace("ingots/", "ores/");
+ } else if (path.contains("nuggets/")) {
+ translatedPath = path.replace("nuggets/", "ores/");
+ } else if (path.contains("raw_materials/")) {
+ translatedPath = path.replace("raw_materials/", "ores/");
+ } else if (path.contains("dusts/")) {
+ translatedPath = path.replace("dusts/", "ores/");
+ } else if (path.contains("storage_blocks/")) {
+ translatedPath = translatedPath.replace("storage_blocks/", "ores/");
+ } else if (path.contains("gems/")) {
+ translatedPath = translatedPath.replace("gems/", "ores/");
+ }
+
+ var translatedTag = new ResourceLocation(namespace.substring(1) + ":" + translatedPath);
+ if (ForgeRegistries.BLOCKS.tags().getTag(TagKey.create(Registries.BLOCK, translatedTag)).isBound())
+ return "#" + translatedTag;
+
+ Theurgy.LOGGER.warn("Could not find an appropriate block tag for sulfur source ttag: " + sourceTag + ", tried tag: #" + translatedTag);
+ return null;
+ }
+
+ public String translateToBlock(String sourceId) {
+ //first we check if we have a manual override mapping
+ var mapped = ServerConfig.get().recipes.sulfurSourceToBlockMapping.get().get(sourceId);
+ if (mapped != null)
+ return mapped;
//if not we use generic logic to translate ingot, storage block, nugget, raw, ore, dust to (ore)block.
//even though likely not all of these will be used to create sulfur its good to handle them.
@@ -131,8 +186,9 @@ public ResourceLocation translateToBlock(ResourceLocation sourceId) {
//deepslate_iron_ore
//iron_dust
//iron_block
-
- var path = sourceId.getPath();
+ var parts = sourceId.split(":");
+ var namespace = parts[0];
+ var path = parts[1];
var translatedPath = path;
if (path.contains("raw_")) {
translatedPath = path.replace("raw_", "");
@@ -152,16 +208,15 @@ public ResourceLocation translateToBlock(ResourceLocation sourceId) {
}
translatedPath = translatedPath + "_ore";
- var translated = new ResourceLocation(sourceId.getNamespace(), translatedPath);
-
- if (ForgeRegistries.BLOCKS.containsKey(translated)) {
- return translated;
+ var translatedRL = new ResourceLocation(namespace + ":" + translatedPath);
+ if (ForgeRegistries.BLOCKS.containsKey(translatedRL)) {
+ return translatedRL.toString();
}
- if (!ForgeRegistries.BLOCKS.containsKey(translated)) {
- var fallback = ForgeRegistries.BLOCKS.getKeys().stream().filter(x -> x.getPath().equals(translated.getPath())).findFirst();
+ if (!ForgeRegistries.BLOCKS.containsKey(translatedRL)) {
+ var fallback = ForgeRegistries.BLOCKS.getKeys().stream().filter(x -> x.getPath().equals(translatedRL.getPath())).findFirst();
if (fallback.isPresent()) {
- return fallback.get();
+ return fallback.get().toString();
}
}
@@ -171,18 +226,23 @@ public ResourceLocation translateToBlock(ResourceLocation sourceId) {
public static class Serializer implements RecipeSerializer {
+ @Override
public DivinationRodRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
var shapedRecipe = RecipeSerializer.SHAPED_RECIPE.fromJson(pRecipeId, pJson);
+ //we can use null here because the shaped recipe does not use the registry access
return new DivinationRodRecipe(pRecipeId, shapedRecipe.getGroup(), shapedRecipe.getWidth(), shapedRecipe.getHeight(), shapedRecipe.getIngredients(), shapedRecipe.getResultItem(RegistryAccess.EMPTY));
}
+ @Override
public DivinationRodRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
var shapedRecipe = RecipeSerializer.SHAPED_RECIPE.fromNetwork(pRecipeId, pBuffer);
+ //we can use null here because the shaped recipe does not use the registry access
return new DivinationRodRecipe(pRecipeId, shapedRecipe.getGroup(), shapedRecipe.getWidth(), shapedRecipe.getHeight(), shapedRecipe.getIngredients(), shapedRecipe.getResultItem(RegistryAccess.EMPTY));
}
+ @Override
public void toNetwork(FriendlyByteBuf pBuffer, DivinationRodRecipe pRecipe) {
RecipeSerializer.SHAPED_RECIPE.toNetwork(pBuffer, pRecipe);
}
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/IncubationRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/IncubationRecipe.java
new file mode 100644
index 000000000..abf714c76
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/IncubationRecipe.java
@@ -0,0 +1,165 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe;
+
+import com.google.gson.JsonObject;
+import com.klikli_dev.theurgy.content.recipe.result.RecipeResult;
+import com.klikli_dev.theurgy.content.recipe.wrapper.IncubatorRecipeWrapper;
+import com.klikli_dev.theurgy.registry.BlockRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import net.minecraft.core.NonNullList;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+import net.minecraft.world.item.crafting.RecipeSerializer;
+import net.minecraft.world.item.crafting.RecipeType;
+import net.minecraft.world.level.Level;
+
+public class IncubationRecipe implements Recipe {
+
+ public static final int DEFAULT_INCUBATION_TIME = 200;
+
+ protected final ResourceLocation id;
+ protected final Ingredient mercury;
+ protected final Ingredient salt;
+ protected final Ingredient sulfur;
+
+ protected final RecipeResult result;
+ protected final int incubationTime;
+
+ public IncubationRecipe(ResourceLocation pId, Ingredient mercury, Ingredient salt, Ingredient sulfur, RecipeResult pResult, int incubationTime) {
+ this.id = pId;
+ this.mercury = mercury;
+ this.salt = salt;
+ this.sulfur = sulfur;
+ this.result = pResult;
+ this.incubationTime = incubationTime;
+ }
+
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ @Override
+ public ResourceLocation getId() {
+ return this.id;
+ }
+
+ @Override
+ public RecipeType> getType() {
+ return RecipeTypeRegistry.INCUBATION.get();
+ }
+
+ @Override
+ public boolean matches(IncubatorRecipeWrapper pContainer, Level pLevel) {
+ return this.mercury.test(pContainer.getMercuryVesselInv().getStackInSlot(0)) &&
+ this.salt.test(pContainer.getSaltVesselInv().getStackInSlot(0)) &&
+ this.sulfur.test(pContainer.getSulfurVesselInv().getStackInSlot(0));
+ }
+
+ @Override
+ public ItemStack assemble(IncubatorRecipeWrapper pInv, RegistryAccess registryAccess) {
+ return this.result.getStack().copy();
+ }
+
+ @Override
+ public boolean canCraftInDimensions(int pWidth, int pHeight) {
+ return true;
+ }
+
+ @Override
+ public ItemStack getResultItem(RegistryAccess registryAccess) {
+ return this.result.getStack();
+ }
+
+ public RecipeResult getResult() {
+ return this.result;
+ }
+
+ @Override
+ public NonNullList getIngredients() {
+ NonNullList nonnulllist = NonNullList.create();
+ nonnulllist.add(this.mercury);
+ nonnulllist.add(this.salt);
+ nonnulllist.add(this.sulfur);
+ return nonnulllist;
+ }
+
+ @Override
+ public ItemStack getToastSymbol() {
+ return new ItemStack(BlockRegistry.INCUBATOR.get());
+ }
+
+ @Override
+ public RecipeSerializer> getSerializer() {
+ return RecipeSerializerRegistry.INCUBATION.get();
+ }
+
+ public int getIncubationTime() {
+ return this.incubationTime;
+ }
+
+ public Ingredient getMercury() {
+ return this.mercury;
+ }
+
+ public Ingredient getSalt() {
+ return this.salt;
+ }
+
+ public Ingredient getSulfur() {
+ return this.sulfur;
+ }
+
+ public static class Serializer implements RecipeSerializer {
+
+ @Override
+ public IncubationRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
+ var mercuryElement = GsonHelper.isArrayNode(pJson, "mercury") ?
+ GsonHelper.getAsJsonArray(pJson, "mercury") : GsonHelper.getAsJsonObject(pJson, "mercury");
+ var mercury = Ingredient.fromJson(mercuryElement);
+
+ var saltElement = GsonHelper.isArrayNode(pJson, "salt") ?
+ GsonHelper.getAsJsonArray(pJson, "salt") : GsonHelper.getAsJsonObject(pJson, "salt");
+ var salt = Ingredient.fromJson(saltElement);
+
+ var sulfurElement = GsonHelper.isArrayNode(pJson, "sulfur") ?
+ GsonHelper.getAsJsonArray(pJson, "sulfur") : GsonHelper.getAsJsonObject(pJson, "sulfur");
+ var sulfur = Ingredient.fromJson(sulfurElement);
+
+ var result = RecipeResult.fromJson(GsonHelper.getAsJsonObject(pJson, "result"));
+
+ var incubationTime = GsonHelper.getAsInt(pJson, "incubation_time", DEFAULT_INCUBATION_TIME);
+ return new IncubationRecipe(pRecipeId, mercury, salt, sulfur, result, incubationTime);
+ }
+
+ @Override
+ public IncubationRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
+ var mercury = Ingredient.fromNetwork(pBuffer);
+ var salt = Ingredient.fromNetwork(pBuffer);
+ var sulfur = Ingredient.fromNetwork(pBuffer);
+ var result = RecipeResult.fromNetwork(pBuffer);
+ var incubationTime = pBuffer.readVarInt();
+ return new IncubationRecipe(pRecipeId, mercury, salt, sulfur, result, incubationTime);
+ }
+
+ @Override
+ public void toNetwork(FriendlyByteBuf pBuffer, IncubationRecipe pRecipe) {
+ pRecipe.mercury.toNetwork(pBuffer);
+ pRecipe.salt.toNetwork(pBuffer);
+ pRecipe.sulfur.toNetwork(pBuffer);
+ pRecipe.result.toNetwork(pBuffer);
+ pBuffer.writeVarInt(pRecipe.incubationTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/LiquefactionRecipe.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/LiquefactionRecipe.java
new file mode 100644
index 000000000..93b30c22f
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/LiquefactionRecipe.java
@@ -0,0 +1,171 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe;
+
+import com.google.gson.JsonObject;
+import com.klikli_dev.theurgy.TheurgyConstants;
+import com.klikli_dev.theurgy.content.item.AlchemicalSulfurItem;
+import com.klikli_dev.theurgy.content.recipe.ingredient.FluidIngredient;
+import com.klikli_dev.theurgy.content.recipe.wrapper.RecipeWrapperWithFluid;
+import com.klikli_dev.theurgy.registry.BlockRegistry;
+import com.klikli_dev.theurgy.registry.RecipeSerializerRegistry;
+import com.klikli_dev.theurgy.registry.RecipeTypeRegistry;
+import net.minecraft.core.NonNullList;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+import net.minecraft.world.item.crafting.RecipeSerializer;
+import net.minecraft.world.item.crafting.RecipeType;
+import net.minecraft.world.level.Level;
+import net.minecraftforge.common.crafting.CraftingHelper;
+import net.minecraftforge.registries.ForgeRegistries;
+
+public class LiquefactionRecipe implements Recipe {
+
+ public static final int DEFAULT_LIQUEFACTION_TIME = 200;
+ protected final ResourceLocation id;
+ protected final Ingredient ingredient;
+ protected final FluidIngredient solvent;
+ protected final ItemStack result;
+ protected final int liquefactionTime;
+
+ public LiquefactionRecipe(ResourceLocation pId, Ingredient pIngredient, FluidIngredient pSolvent, ItemStack pResult, int liquefactionTime) {
+ this.id = pId;
+ this.ingredient = pIngredient;
+ this.solvent = pSolvent;
+ this.result = pResult;
+ this.liquefactionTime = liquefactionTime;
+ }
+
+ @Override
+ public boolean isSpecial() {
+ return true;
+ }
+
+ @Override
+ public ResourceLocation getId() {
+ return this.id;
+ }
+
+ @Override
+ public RecipeType> getType() {
+ return RecipeTypeRegistry.LIQUEFACTION.get();
+ }
+
+ @Override
+ public boolean matches(RecipeWrapperWithFluid pContainer, Level pLevel) {
+ return this.ingredient.test(pContainer.getItem(0)) && this.solvent.test(pContainer.getTank().getFluidInTank(0));
+ }
+
+ @Override
+ public ItemStack assemble(RecipeWrapperWithFluid pInv, RegistryAccess registryAccess) {
+ return this.result.copy();
+ }
+
+ @Override
+ public boolean canCraftInDimensions(int pWidth, int pHeight) {
+ return true;
+ }
+
+ @Override
+ public ItemStack getResultItem(RegistryAccess registryAccess) {
+ return this.result;
+ }
+
+ @Override
+ public NonNullList getIngredients() {
+ NonNullList nonnulllist = NonNullList.create();
+ nonnulllist.add(this.ingredient);
+ return nonnulllist;
+ }
+
+ @Override
+ public ItemStack getToastSymbol() {
+ return new ItemStack(BlockRegistry.LIQUEFACTION_CAULDRON.get());
+ }
+
+ @Override
+ public RecipeSerializer> getSerializer() {
+ return RecipeSerializerRegistry.LIQUEFACTION.get();
+ }
+
+ public int getLiquefactionTime() {
+ return this.liquefactionTime;
+ }
+
+ public FluidIngredient getSolvent() {
+ return this.solvent;
+ }
+
+ public Ingredient getIngredient() {
+ return this.ingredient;
+ }
+
+ public static class Serializer implements RecipeSerializer {
+
+ @Override
+ public LiquefactionRecipe fromJson(ResourceLocation pRecipeId, JsonObject pJson) {
+ var ingredientElement = GsonHelper.isArrayNode(pJson, "ingredient") ? GsonHelper.getAsJsonArray(pJson, "ingredient") : GsonHelper.getAsJsonObject(pJson, "ingredient");
+ var ingredient = Ingredient.fromJson(ingredientElement);
+
+ var solventElement = GsonHelper.isArrayNode(pJson, "solvent") ? GsonHelper.getAsJsonArray(pJson, "solvent") : GsonHelper.getAsJsonObject(pJson, "solvent");
+ var solvent = FluidIngredient.fromJson(solventElement);
+
+ ItemStack result = CraftingHelper.getItemStack(GsonHelper.getAsJsonObject(pJson, "result"), true, true);
+
+ int liquefactionTime = GsonHelper.getAsInt(pJson, "liquefaction_time", DEFAULT_LIQUEFACTION_TIME);
+
+ result = this.fixSourceIdIfNecessary(result, ingredient);
+
+ return new LiquefactionRecipe(pRecipeId, ingredient, solvent, result, liquefactionTime);
+ }
+
+ public ItemStack fixSourceIdIfNecessary(ItemStack resultItem, Ingredient ingredient) {
+ if (resultItem.getItem() instanceof AlchemicalSulfurItem sulfur
+ && sulfur.autoGenerateSourceIdInRecipe
+ && AlchemicalSulfurItem.getSourceStack(resultItem).isEmpty()) {
+
+ var modified = resultItem.copy();
+
+ if (ingredient.values.length > 0) {
+ if (ingredient.values[0] instanceof Ingredient.TagValue tagValue) {
+ modified.getOrCreateTag().putString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID, "#" + tagValue.tag.location());
+ return modified;
+ } else if (ingredient.values[0] instanceof Ingredient.ItemValue itemValue) {
+ modified.getOrCreateTag().putString(TheurgyConstants.Nbt.SULFUR_SOURCE_ID,
+ ForgeRegistries.ITEMS.getKey(itemValue.getItems().stream().findFirst().get().getItem()).toString());
+ return modified;
+ }
+ }
+
+ throw new IllegalArgumentException("AlchemicalSulfurItem " + resultItem + " is configured to generate source id in recipe, but Ingredient.values[0] is not a tag or item value so we don't know how to get the source.");
+ }
+ return resultItem;
+ }
+
+ @Override
+ public LiquefactionRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
+ var ingredient = Ingredient.fromNetwork(pBuffer);
+ var solvent = FluidIngredient.fromNetwork(pBuffer);
+ var result = pBuffer.readItem();
+ var liquefactionTime = pBuffer.readVarInt();
+ return new LiquefactionRecipe(pRecipeId, ingredient, solvent, result, liquefactionTime);
+ }
+
+ @Override
+ public void toNetwork(FriendlyByteBuf pBuffer, LiquefactionRecipe pRecipe) {
+ pRecipe.ingredient.toNetwork(pBuffer);
+ pRecipe.solvent.toNetwork(pBuffer); //Ingredient.toNetwork(pBuffer); should suffice here as it redirects to the serializer registry anyway
+ pBuffer.writeItem(pRecipe.result);
+ pBuffer.writeVarInt(pRecipe.liquefactionTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/klikli_dev/theurgy/content/recipe/ingredient/FluidIngredient.java b/src/main/java/com/klikli_dev/theurgy/content/recipe/ingredient/FluidIngredient.java
new file mode 100644
index 000000000..920a989ba
--- /dev/null
+++ b/src/main/java/com/klikli_dev/theurgy/content/recipe/ingredient/FluidIngredient.java
@@ -0,0 +1,312 @@
+/*
+ * SPDX-FileCopyrightText: 2023 klikli-dev
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.klikli_dev.theurgy.content.recipe.ingredient;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gson.*;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.DynamicOps;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.PrimitiveCodec;
+import it.unimi.dsi.fastutil.ints.IntList;
+import net.minecraft.core.registries.Registries;
+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.minecraft.world.level.material.Fluids;
+import net.minecraftforge.common.crafting.IIngredientSerializer;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.registries.ForgeRegistries;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+public class FluidIngredient extends Ingredient {
+
+ public static final FluidIngredient EMPTY = new FluidIngredient(Stream.empty());
+ public static final Codec CODEC = new PrimitiveCodec<>() {
+ @Override
+ public DataResult read(DynamicOps ops, T input) {
+ try {
+ return DataResult.success(FluidIngredient.fromJson(ops.convertTo(JsonOps.INSTANCE, input)));
+ } catch (JsonParseException e) {
+ return DataResult.error(() -> "Failed to parse Ingredient: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public T write(DynamicOps ops, FluidIngredient value) {
+ return JsonOps.INSTANCE.convertTo(ops, value.toJson());
+ }
+ };
+ private final Value[] values;
+
+ @Nullable
+ private FluidStack[] fluidStacks;
+
+ private int amount = -1;
+
+ protected FluidIngredient(Stream extends Value> pValues) {
+ super(Stream.empty());
+
+ this.values = pValues.toArray(Value[]::new);
+ }
+
+ public static FluidIngredient ofFluid() {
+ return EMPTY;
+ }
+
+ public static FluidIngredient ofFluid(int amount, Fluid... pItems) {
+ return ofFluid(Arrays.stream(pItems).map(f -> new FluidStack(f, amount)));
+ }
+
+ public static FluidIngredient ofFluid(FluidStack... pStacks) {
+ return ofFluid(Arrays.stream(pStacks));
+ }
+
+ public static FluidIngredient ofFluid(Stream pStacks) {
+ return fromFluidValues(pStacks.filter((p_43944_) -> {
+ return !p_43944_.isEmpty();
+ }).map(FluidValue::new));
+ }
+
+ public static FluidIngredient ofFluid(TagKey pTag, int amount) {
+ return fromFluidValues(Stream.of(new TagValue(pTag, amount)));
+ }
+
+ public static FluidIngredient fromFluidValues(Stream extends Value> pStream) {
+ var ingredient = new FluidIngredient(pStream);
+ return ingredient.isEmpty() ? EMPTY : ingredient;
+ }
+
+ public static FluidIngredient fromJson(@Nullable JsonElement pJson) {
+ if (pJson != null && !pJson.isJsonNull()) {
+ if (pJson.isJsonObject()) {
+ return fromFluidValues(Stream.of(fluidValueFromJson(pJson.getAsJsonObject())));
+ } else if (pJson.isJsonArray()) {
+ JsonArray jsonarray = pJson.getAsJsonArray();
+ if (jsonarray.size() == 0) {
+ throw new JsonSyntaxException("Fluid array cannot be empty, at least one item must be defined");
+ } else {
+ return fromFluidValues(StreamSupport.stream(jsonarray.spliterator(), false).map((p_151264_) -> {
+ return fluidValueFromJson(GsonHelper.convertToJsonObject(p_151264_, "fluid"));
+ }));
+ }
+ } else {
+ throw new JsonSyntaxException("Expected fluid to be object or array of objects");
+ }
+ } else {
+ throw new JsonSyntaxException("Item cannot be null");
+ }
+ }
+
+ public static FluidIngredient fromNetwork(FriendlyByteBuf pBuffer) {
+ var size = pBuffer.readVarInt();
+ if (size == -1)
+ throw new UnsupportedOperationException("FluidIngredients should never be serialized with size -1!");
+ return fromFluidValues(Stream.generate(() -> new FluidValue(pBuffer.readFluidStack())).limit(size));
+ }
+
+ public static FluidIngredient.Value fluidValueFromJson(JsonObject pJson) {
+ if (!pJson.has("amount")) {
+ throw new JsonParseException("A fluid ingredient entry must have an amount");
+ }
+ var amount = GsonHelper.getAsInt(pJson, "amount");
+
+ if (pJson.has("fluid") && pJson.has("tag")) {
+ throw new JsonParseException("A fluid ingredient entry is either a tag or a fluid, not both");
+ } else if (pJson.has("fluid")) {
+ var fluid = fluidFromJson(pJson);
+ return new FluidValue(new FluidStack(fluid, amount));
+ } else if (pJson.has("tag")) {
+ ResourceLocation resourcelocation = new ResourceLocation(GsonHelper.getAsString(pJson, "tag"));
+ TagKey tagkey = TagKey.create(Registries.FLUID, resourcelocation);
+ return new TagValue(tagkey, amount);
+ } else {
+ throw new JsonParseException("A fluid ingredient entry needs either a tag or a fluid");
+ }
+ }
+
+ public static Fluid fluidFromJson(JsonObject pItemObject) {
+ String s = GsonHelper.getAsString(pItemObject, "fluid");
+ var fluid = ForgeRegistries.FLUIDS.getValue(new ResourceLocation(s));
+ if (fluid == Fluids.EMPTY) {
+ throw new JsonSyntaxException("Invalid fluid: " + s);
+ } else {
+ return fluid;
+ }
+ }
+
+ @Override
+ protected void invalidate() {
+ this.invalidate();
+ this.fluidStacks = null;
+ this.amount = -1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.values.length == 0;
+ }
+
+ @Override
+ public IntList getStackingIds() {
+ return super.getStackingIds();
+ }
+
+ @Override
+ public ItemStack[] getItems() {
+ return super.getItems(); //will be empty
+ }
+
+ public FluidStack[] getFluids() {
+ if (this.fluidStacks == null) {
+ this.fluidStacks = Arrays.stream(this.values).flatMap((value) -> value.getFluids().stream()).distinct().toArray(FluidStack[]::new);
+ }
+
+ return this.fluidStacks;
+ }
+
+ public int getAmount() {
+ if (this.amount == -1) {
+ var fluids = this.getFluids();
+ this.amount = fluids.length == 0 ? 0 : Arrays.stream(this.getFluids()).max(Comparator.comparingInt(FluidStack::getAmount)).get().getAmount();
+ }
+ return this.amount;
+ }
+
+ @Override
+ public boolean test(@Nullable ItemStack pStack) {
+ return false;
+ }
+
+ @Override
+ public JsonElement toJson() {
+ if (this.values.length == 1) {
+ return this.values[0].serialize();
+ } else {
+ JsonArray jsonarray = new JsonArray();
+
+ for (Value value : this.values) {
+ jsonarray.add(value.serialize());
+ }
+
+ return jsonarray;
+ }
+ }
+
+ public boolean test(FluidStack fluidStack) {
+ if (fluidStack == null) {
+ return false;
+ } else if (this.isEmpty()) {
+ return fluidStack.isEmpty();
+ } else {
+ for (var fluid : this.getFluids()) {
+ //Fluid ingredients need to test for amounts too, hence the containsFluid
+ if (fluidStack.containsFluid(fluid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public IIngredientSerializer extends Ingredient> getSerializer() {
+ return Serializer.INSTANCE;
+ }
+
+ public interface Value {
+ Collection