diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/CraftingPreview.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/CraftingPreview.java new file mode 100644 index 000000000..280f1377a --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/CraftingPreview.java @@ -0,0 +1,6 @@ +package com.refinedmods.refinedstorage.api.autocrafting; + +import com.refinedmods.refinedstorage.api.resource.list.ResourceList; + +public record CraftingPreview(ResourceList missing, ResourceList toTake, ResourceList toCraft) { +} diff --git a/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApi.java b/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApi.java index a96e5fb72..d43da64b9 100644 --- a/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApi.java +++ b/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApi.java @@ -7,6 +7,7 @@ import com.refinedmods.refinedstorage.api.network.energy.EnergyStorage; import com.refinedmods.refinedstorage.api.network.node.NetworkNode; import com.refinedmods.refinedstorage.api.network.security.SecurityPolicy; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.common.api.constructordestructor.ConstructorStrategyFactory; import com.refinedmods.refinedstorage.common.api.constructordestructor.DestructorStrategyFactory; @@ -46,6 +47,7 @@ import com.refinedmods.refinedstorage.common.api.wirelesstransmitter.WirelessTransmitterRangeModifier; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; @@ -203,4 +205,6 @@ EnergyStorage asBlockItemEnergyStorage( boolean canPlaceNetworkNode(ServerPlayer player, Level level, BlockPos pos, BlockState state); Optional getPattern(ItemStack stack, Level level); + + void openCraftingPreview(List resources); } diff --git a/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApiProxy.java b/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApiProxy.java index 24c147598..c68bf1cd8 100644 --- a/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApiProxy.java +++ b/refinedstorage-common-api/src/main/java/com/refinedmods/refinedstorage/common/api/RefinedStorageApiProxy.java @@ -7,6 +7,7 @@ import com.refinedmods.refinedstorage.api.network.energy.EnergyStorage; import com.refinedmods.refinedstorage.api.network.node.NetworkNode; import com.refinedmods.refinedstorage.api.network.security.SecurityPolicy; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.common.api.constructordestructor.ConstructorStrategyFactory; import com.refinedmods.refinedstorage.common.api.constructordestructor.DestructorStrategyFactory; @@ -46,6 +47,7 @@ import com.refinedmods.refinedstorage.common.api.wirelesstransmitter.WirelessTransmitterRangeModifier; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; @@ -401,6 +403,11 @@ public Optional getPattern(final ItemStack stack, final Level level) { return ensureLoaded().getPattern(stack, level); } + @Override + public void openCraftingPreview(final List resources) { + ensureLoaded().openCraftingPreview(resources); + } + private RefinedStorageApi ensureLoaded() { if (delegate == null) { throw new IllegalStateException("API not loaded yet"); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/RefinedStorageApiImpl.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/RefinedStorageApiImpl.java index 41ccefd5e..83abe2af5 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/RefinedStorageApiImpl.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/RefinedStorageApiImpl.java @@ -10,6 +10,7 @@ import com.refinedmods.refinedstorage.api.network.impl.NetworkFactory; import com.refinedmods.refinedstorage.api.network.node.NetworkNode; import com.refinedmods.refinedstorage.api.network.security.SecurityPolicy; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.common.api.RefinedStorageApi; import com.refinedmods.refinedstorage.common.api.autocrafting.PatternProviderItem; @@ -49,6 +50,7 @@ import com.refinedmods.refinedstorage.common.api.support.slotreference.SlotReferenceProvider; import com.refinedmods.refinedstorage.common.api.upgrade.UpgradeRegistry; import com.refinedmods.refinedstorage.common.api.wirelesstransmitter.WirelessTransmitterRangeModifier; +import com.refinedmods.refinedstorage.common.autocrafting.preview.CraftingPreviewScreen; import com.refinedmods.refinedstorage.common.grid.NoopGridSynchronizer; import com.refinedmods.refinedstorage.common.grid.screen.hint.GridInsertionHintsImpl; import com.refinedmods.refinedstorage.common.grid.screen.hint.ItemGridInsertionHint; @@ -99,12 +101,14 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; +import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.Item; @@ -595,4 +599,17 @@ public Optional getPattern(final ItemStack stack, final Level level) { i -> providerItem.getPattern(stack, level).orElse(null) )); } + + @Override + public void openCraftingPreview(final List resources) { + if (resources.isEmpty()) { + return; + } + final Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.screen == null || minecraft.player == null) { + return; + } + final Inventory inventory = minecraft.player.getInventory(); + minecraft.setScreen(new CraftingPreviewScreen(minecraft.screen, inventory, resources.getFirst().resource())); + } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativeContainerMenu.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativeContainerMenu.java index 9af4991ba..b17189322 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativeContainerMenu.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativeContainerMenu.java @@ -22,7 +22,7 @@ import static com.refinedmods.refinedstorage.common.autocrafting.AlternativesScreen.ALTERNATIVE_ROW_HEIGHT; import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.getTagTranslationKey; -public class AlternativeContainerMenu extends AbstractResourceContainerMenu { +class AlternativeContainerMenu extends AbstractResourceContainerMenu { private final List alternatives; private final ResourceSlot amountSlot; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativesScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativesScreen.java index e23aa3dcc..743e92a7d 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativesScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/AlternativesScreen.java @@ -493,7 +493,7 @@ protected ResourceLocation getTexture() { } @Override - protected void accept(final Double amount) { + protected boolean confirm(final Double amount) { slot.changeAmountOnClient(amount); final Set allowedAlternatives = new HashSet<>(); for (int i = 0; i < alternativeCheckboxes.size(); ++i) { @@ -502,6 +502,7 @@ protected void accept(final Double amount) { } } getMenu().sendAllowedAlternatives(allowedAlternatives); + return true; } @Override diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java index 9987552f7..f874917b5 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java @@ -78,7 +78,7 @@ public void renderImage(final Font font, final int x, final int y, final GuiGrap cycleStart = now; } if (outputText != null) { - graphics.drawString(font, outputText, x, y, requireNonNullElse(ChatFormatting.GRAY.getColor(), 15)); + graphics.drawString(font, outputText, x, y, 0xAAAAAA); } renderInputSlots(x, y + 9 + 2, graphics); renderArrow(x, y + 9 + 2, graphics); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java index 742cefdf6..e0309dec5 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java @@ -162,7 +162,7 @@ private void renderOutputText(final Font font, final int x, final int y, final G outputTexts.get(i), x, y + (i * 9), - requireNonNullElse(ChatFormatting.GRAY.getColor(), 15) + 0xAAAAAA ); } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePatternClientTooltipComponent.java index e9c4bc3cc..86fc09c17 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePatternClientTooltipComponent.java @@ -29,7 +29,7 @@ class SmithingTablePatternClientTooltipComponent implements ClientTooltipCompone @Override public void renderImage(final Font font, final int x, final int y, final GuiGraphics graphics) { - graphics.drawString(font, outputText, x, y, requireNonNullElse(ChatFormatting.GRAY.getColor(), 15)); + graphics.drawString(font, outputText, x, y, 0xAAAAAA); final int slotsY = y + 9 + 2; graphics.blitSprite(Sprites.SLOT, x, slotsY, 18, 18); final ResourceRendering rendering = RefinedStorageApi.INSTANCE.getResourceRendering(ItemResource.class); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPatternClientTooltipComponent.java index b749148dd..ddf5d2e39 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPatternClientTooltipComponent.java @@ -32,7 +32,7 @@ class StonecutterPatternClientTooltipComponent implements ClientTooltipComponent @Override public void renderImage(final Font font, final int x, final int y, final GuiGraphics graphics) { - graphics.drawString(font, outputText, x, y, requireNonNullElse(ChatFormatting.GRAY.getColor(), 15)); + graphics.drawString(font, outputText, x, y, 0xAAAAAA); graphics.blitSprite(SLOT, x, y + 9 + 2, 18, 18); final ResourceRendering rendering = RefinedStorageApi.INSTANCE.getResourceRendering(ItemResource.class); rendering.render(input, graphics, x + 1, y + 9 + 2 + 1); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreview.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreview.java new file mode 100644 index 000000000..bbb5a4794 --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreview.java @@ -0,0 +1,6 @@ +package com.refinedmods.refinedstorage.common.autocrafting.preview; + +import java.util.List; + +record CraftingPreview(boolean missing, List items) { +} diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewContainerMenu.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewContainerMenu.java new file mode 100644 index 000000000..aca99a72c --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewContainerMenu.java @@ -0,0 +1,45 @@ +package com.refinedmods.refinedstorage.common.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import com.refinedmods.refinedstorage.common.api.support.resource.ResourceContainer; +import com.refinedmods.refinedstorage.common.support.containermenu.AbstractResourceContainerMenu; +import com.refinedmods.refinedstorage.common.support.containermenu.DisabledResourceSlot; +import com.refinedmods.refinedstorage.common.support.containermenu.ResourceSlotType; +import com.refinedmods.refinedstorage.common.support.resource.ResourceContainerImpl; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +import net.minecraft.network.chat.Component; + +class CraftingPreviewContainerMenu extends AbstractResourceContainerMenu { + @Nullable + private CraftingPreview preview; + + CraftingPreviewContainerMenu(final ResourceKey resource) { + super(null, 0); + final ResourceContainer resourceContainer = ResourceContainerImpl.createForFilter(1); + resourceContainer.set(0, new ResourceAmount(resource, 1)); + addSlot(new DisabledResourceSlot( + resourceContainer, + 0, + Component.empty(), + 157, + 48, + ResourceSlotType.FILTER + )); + + final List items = new ArrayList<>(); + for (int i = 0; i < 31; ++i) { + items.add(new CraftingPreviewItem(resource, i, i % 2 == 0 ? 999 : 0, i % 2 == 0 ? 0 : 1000)); + } + preview = new CraftingPreview(true, items); + } + + @Nullable + public CraftingPreview getPreview() { + return preview; + } +} diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewItem.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewItem.java new file mode 100644 index 000000000..84ec43f5b --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewItem.java @@ -0,0 +1,6 @@ +package com.refinedmods.refinedstorage.common.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +record CraftingPreviewItem(ResourceKey resource, long available, long missing, long toCraft) { +} diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewScreen.java new file mode 100644 index 000000000..d592a0feb --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/CraftingPreviewScreen.java @@ -0,0 +1,260 @@ +package com.refinedmods.refinedstorage.common.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import com.refinedmods.refinedstorage.common.api.RefinedStorageApi; +import com.refinedmods.refinedstorage.common.api.support.resource.ResourceRendering; +import com.refinedmods.refinedstorage.common.support.amount.AbstractAmountScreen; +import com.refinedmods.refinedstorage.common.support.amount.AmountScreenConfiguration; +import com.refinedmods.refinedstorage.common.support.amount.DoubleAmountOperations; +import com.refinedmods.refinedstorage.common.support.tooltip.SmallText; +import com.refinedmods.refinedstorage.common.support.widget.ScrollbarWidget; + +import java.util.List; +import javax.annotation.Nullable; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; +import org.joml.Vector3f; + +import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.createIdentifier; +import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.createTranslation; + +public class CraftingPreviewScreen extends AbstractAmountScreen { + private static final ResourceLocation TEXTURE = createIdentifier("textures/gui/crafting_preview.png"); + private static final MutableComponent TITLE = Component.translatable("container.crafting"); + private static final MutableComponent START = createTranslation("gui", "crafting_preview.start"); + private static final MutableComponent MISSING_RESOURCES + = createTranslation("gui", "crafting_preview.start.missing_resources"); + private static final ResourceLocation ROW = createIdentifier("crafting_preview/row"); + + private static final int ROWS_VISIBLE = 4; + private static final int COLUMNS = 3; + private static final int PREVIEW_AREA_HEIGHT = 119; + + private static final int ROW_HEIGHT = 30; + private static final int ROW_WIDTH = 221; + + @Nullable + private ScrollbarWidget scrollbar; + + public CraftingPreviewScreen(final Screen parent, final Inventory playerInventory, final ResourceKey resource) { + super( + new CraftingPreviewContainerMenu(resource), + parent, + playerInventory, + TITLE, + AmountScreenConfiguration.AmountScreenConfigurationBuilder.create() + .withInitialAmount(1D) + .withIncrementsTop(1, 10, 64) + .withIncrementsTopStartPosition(new Vector3f(80, 20, 0)) + .withIncrementsBottom(-1, -10, -64) + .withIncrementsBottomStartPosition(new Vector3f(80, 71, 0)) + .withAmountFieldPosition(new Vector3f(77, 51, 0)) + .withActionButtonsStartPosition(new Vector3f(7, 222, 0)) + .withHorizontalActionButtons(true) + .withMinAmount(1D) + .withResetAmount(1D) + .withConfirmButtonText(START) + .build(), + DoubleAmountOperations.INSTANCE + ); + this.imageWidth = 254; + this.imageHeight = 249; + } + + @Override + protected void init() { + super.init(); + scrollbar = new ScrollbarWidget( + leftPos + 235, + topPos + 98, + ScrollbarWidget.Type.NORMAL, + PREVIEW_AREA_HEIGHT + ); + scrollbar.setEnabled(false); + if (confirmButton != null) { + confirmButton.active = false; + } + updatePreview(); + } + + private void updatePreview() { + if (scrollbar == null || confirmButton == null) { + return; + } + final CraftingPreview preview = getMenu().getPreview(); + if (preview == null) { + scrollbar.setEnabled(false); + scrollbar.setMaxOffset(0); + confirmButton.active = false; + return; + } + final int items = getMenu().getPreview().items().size(); + final int rows = Math.ceilDiv(items, COLUMNS) - ROWS_VISIBLE; + scrollbar.setMaxOffset(scrollbar.isSmoothScrolling() ? rows * ROW_HEIGHT : rows); + scrollbar.setEnabled(rows > 0); + confirmButton.active = !preview.missing(); + confirmButton.setTooltip(preview.missing() ? Tooltip.create(MISSING_RESOURCES) : null); + } + + @Override + public void render(final GuiGraphics graphics, final int mouseX, final int mouseY, final float partialTicks) { + super.render(graphics, mouseX, mouseY, partialTicks); + if (scrollbar != null) { + scrollbar.render(graphics, mouseX, mouseY, partialTicks); + } + } + + @Override + protected void renderBg(final GuiGraphics graphics, final float delta, final int mouseX, final int mouseY) { + super.renderBg(graphics, delta, mouseX, mouseY); + final CraftingPreview preview = getMenu().getPreview(); + if (preview == null || scrollbar == null) { + return; + } + final int x = leftPos + 8; + final int y = topPos + 98; + graphics.enableScissor(x, y, x + 221, y + PREVIEW_AREA_HEIGHT); + final List items = getMenu().getPreview().items(); + final int rows = Math.ceilDiv(items.size(), COLUMNS); + for (int i = 0; i < rows; ++i) { + final int scrollOffset = scrollbar.isSmoothScrolling() + ? (int) scrollbar.getOffset() + : (int) scrollbar.getOffset() * ROW_HEIGHT; + final int yy = y + (i * ROW_HEIGHT) - scrollOffset; + renderRow(graphics, x, yy, i, items, mouseX, mouseY); + } + graphics.disableScissor(); + } + + private void renderRow(final GuiGraphics graphics, + final int x, + final int y, + final int i, + final List items, + final double mouseX, + final double mouseY) { + if (y <= topPos + 98 - ROW_HEIGHT || y > topPos + 98 + PREVIEW_AREA_HEIGHT) { + return; + } + graphics.blitSprite(ROW, x, y, ROW_WIDTH, ROW_HEIGHT); + for (int column = i * COLUMNS; column < Math.min(i * COLUMNS + COLUMNS, items.size()); ++column) { + final CraftingPreviewItem item = items.get(column); + final int xx = x + (column % COLUMNS) * 74; + renderCell(graphics, xx, y, item, mouseX, mouseY); + } + } + + private void renderCell(final GuiGraphics graphics, + final int x, + final int y, + final CraftingPreviewItem item, + final double mouseX, + final double mouseY) { + if (item.missing() > 0) { + graphics.fill(x, y, x + 73, y + 29, 0xFFF2DEDE); + } + int xx = x + 2; + final ResourceRendering rendering = RefinedStorageApi.INSTANCE.getResourceRendering(item.resource().getClass()); + int yy = y + 7; + rendering.render(item.resource(), graphics, xx, yy); + if (isHovering(xx - leftPos, yy - topPos, 16, 16, mouseX, mouseY) + && isHoveringOverPreviewArea(mouseX, mouseY)) { + setTooltipForNextRenderPass(rendering.getTooltip(item.resource()).stream() + .map(Component::getVisualOrderText) + .toList()); + } + if (!SmallText.isSmall()) { + yy -= 2; + } + xx += 16 + 3; + if (item.missing() > 0) { + renderCellText(graphics, "missing", rendering, xx, yy, item.missing()); + yy += 7; + } + if (item.available() > 0) { + renderCellText(graphics, "available", rendering, xx, yy, item.available()); + yy += 7; + } + if (item.toCraft() > 0) { + renderCellText(graphics, "to_craft", rendering, xx, yy, item.toCraft()); + } + } + + private void renderCellText(final GuiGraphics graphics, + final String type, + final ResourceRendering rendering, + final int x, + final int y, + final long amount) { + SmallText.render( + graphics, + font, + createTranslation("gui", "crafting_preview." + type, rendering.formatAmount(amount, true)) + .getVisualOrderText(), + x, + y, + 0x404040, + false + ); + } + + @Override + public boolean mouseClicked(final double mouseX, final double mouseY, final int clickedButton) { + if (scrollbar != null && scrollbar.mouseClicked(mouseX, mouseY, clickedButton)) { + return true; + } + return super.mouseClicked(mouseX, mouseY, clickedButton); + } + + @Override + public void mouseMoved(final double mx, final double my) { + if (scrollbar != null) { + scrollbar.mouseMoved(mx, my); + } + super.mouseMoved(mx, my); + } + + @Override + public boolean mouseReleased(final double mx, final double my, final int button) { + return (scrollbar != null && scrollbar.mouseReleased(mx, my, button)) + || super.mouseReleased(mx, my, button); + } + + @Override + public boolean mouseScrolled(final double x, final double y, final double z, final double delta) { + final boolean didScrollbar = scrollbar != null + && isHoveringOverPreviewArea(x, y) + && scrollbar.mouseScrolled(x, y, z, delta); + return didScrollbar || super.mouseScrolled(x, y, z, delta); + } + + private boolean isHoveringOverPreviewArea(final double x, final double y) { + return isHovering(7, 97, 241, 121, x, y); + } + + @Override + protected ResourceLocation getTexture() { + return TEXTURE; + } + + @Override + protected void onAmountFieldChanged() { + if (amountField == null || confirmButton == null) { + return; + } + confirmButton.active = false; + final boolean valid = getAndValidateAmount().isPresent(); + amountField.setTextColor(valid ? 0xFFFFFF : 0xFF5555); + } + + @Override + protected boolean confirm(final Double amount) { + return false; + } +} diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/package-info.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/package-info.java new file mode 100644 index 000000000..b10fddc11 --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@FieldsAndMethodsAreNonnullByDefault +package com.refinedmods.refinedstorage.common.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.core.FieldsAndMethodsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/AbstractGridScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/AbstractGridScreen.java index 89ba11179..7877e55a2 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/AbstractGridScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/AbstractGridScreen.java @@ -4,6 +4,7 @@ import com.refinedmods.refinedstorage.api.grid.operations.GridInsertMode; import com.refinedmods.refinedstorage.api.grid.view.GridResource; import com.refinedmods.refinedstorage.api.grid.view.GridView; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.api.storage.tracked.TrackedResource; import com.refinedmods.refinedstorage.common.Platform; @@ -493,9 +494,17 @@ public boolean mouseClicked(final double mouseX, final double mouseY, final int final ItemStack carriedStack = getMenu().getCarried(); final PlatformGridResource resource = getCurrentGridResource(); - if (resource != null && resource.canExtract(carriedStack, getMenu().getView())) { - mouseClickedInGrid(clickedButton, resource); - return true; + if (resource != null) { + if (resource.canExtract(carriedStack, getMenu().getView()) && !hasControlDown()) { + mouseClickedInGrid(clickedButton, resource); + return true; + } else if (resource.isCraftable()) { + RefinedStorageApi.INSTANCE.openCraftingPreview(List.of(new ResourceAmount( + resource.getResourceForRecipeMods(), + 1 + ))); + return true; + } } if (isOverStorageArea((int) mouseX, (int) mouseY) diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/GridSearchBoxWidget.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/GridSearchBoxWidget.java index 94f5ac1ac..ea0928725 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/GridSearchBoxWidget.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/screen/GridSearchBoxWidget.java @@ -85,9 +85,6 @@ public void addListener(final Consumer listener) { @Override public void setValid(final boolean valid) { this.valid = valid; - setTextColor(valid - ? Objects.requireNonNullElse(ChatFormatting.WHITE.getColor(), 15) - : Objects.requireNonNullElse(ChatFormatting.RED.getColor(), 15) - ); + setTextColor(valid ? 0xFFFFFF : 0xFF5555); } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/FluidGridResource.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/FluidGridResource.java index 0ffecdb6e..77834ef9a 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/FluidGridResource.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/FluidGridResource.java @@ -70,6 +70,9 @@ private Optional tryFillFluidContainer(final ItemStack car @Override public boolean canExtract(final ItemStack carriedStack, final GridView view) { + if (getAmount(view) == 0) { + return false; + } if (carriedStack.isEmpty()) { return true; } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/ItemGridResource.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/ItemGridResource.java index 0bc1a01d1..02d11ab59 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/ItemGridResource.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/view/ItemGridResource.java @@ -80,7 +80,7 @@ public List getExtractionHints(final ItemStack carriedSt @Override public boolean canExtract(final ItemStack carriedStack, final GridView view) { - return carriedStack.isEmpty(); + return getAmount(view) > 0 && carriedStack.isEmpty(); } @Override diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/ResourceSlotRendering.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/ResourceSlotRendering.java index 4cd868c58..c0d29a830 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/ResourceSlotRendering.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/ResourceSlotRendering.java @@ -58,7 +58,7 @@ public static void renderAmount(final GuiGraphics graphics, x, y, rendering.formatAmount(amount, true), - requireNonNullElse(ChatFormatting.WHITE.getColor(), 15), + 0xFFFFFF, true ); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java index e6a0e0a26..ae47bd290 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java @@ -3,11 +3,9 @@ import com.refinedmods.refinedstorage.common.autocrafting.AlternativesScreen; import com.refinedmods.refinedstorage.common.support.AbstractBaseScreen; -import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; -import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -26,7 +24,6 @@ public abstract class AbstractAmountScreen extends AbstractBaseScreen { - private static final MutableComponent SET_TEXT = createTranslation("gui", "configure_amount.set"); private static final MutableComponent RESET_TEXT = createTranslation("gui", "configure_amount.reset"); private static final MutableComponent CANCEL_TEXT = Component.translatable("gui.cancel"); @@ -34,16 +31,16 @@ public abstract class AbstractAmountScreen configuration; private final AmountOperations amountOperations; - @Nullable - private EditBox amountField; - @Nullable - private Button confirmButton; - protected AbstractAmountScreen(final T containerMenu, @Nullable final Screen parent, final Inventory playerInventory, @@ -89,10 +86,11 @@ private void addResetButton(final int x, final int y) { } private void addConfirmButton(final int x, final int y) { - confirmButton = addRenderableWidget(Button.builder(SET_TEXT, btn -> tryConfirmAndCloseToParent()) - .pos(leftPos + x, topPos + y) - .size(ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT) - .build()); + confirmButton = addRenderableWidget( + Button.builder(configuration.getConfirmButtonText(), btn -> tryConfirmAndCloseToParent()) + .pos(leftPos + x, topPos + y) + .size(ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT) + .build()); } private void addCancelButton(final int x, final int y) { @@ -119,24 +117,26 @@ private void addAmountField() { amountField.setVisible(true); amountField.setCanLoseFocus(this instanceof AlternativesScreen); amountField.setFocused(true); - amountField.setResponder(value -> { - final boolean valid = getAndValidateAmount().isPresent(); - if (confirmButton != null) { - confirmButton.active = valid; - } else { - tryConfirm(); - } - amountField.setTextColor(valid - ? Objects.requireNonNullElse(ChatFormatting.WHITE.getColor(), 15) - : Objects.requireNonNullElse(ChatFormatting.RED.getColor(), 15) - ); - }); - amountField.setTextColor(Objects.requireNonNullElse(ChatFormatting.WHITE.getColor(), 15)); + amountField.setResponder(value -> onAmountFieldChanged()); + amountField.setTextColor(0xFFFFFF); setFocused(amountField); addRenderableWidget(amountField); } + protected void onAmountFieldChanged() { + if (amountField == null) { + return; + } + final boolean valid = getAndValidateAmount().isPresent(); + if (confirmButton != null) { + confirmButton.active = valid; + } else { + tryConfirm(); + } + amountField.setTextColor(valid ? 0xFFFFFF : 0xFF5555); + } + private void addIncrementButtons() { final Vector3f incrementsTopPos = configuration.getIncrementsTopStartPosition(); addIncrementButtons( @@ -160,7 +160,7 @@ private void addIncrementButtons(final int[] increments, final int x, final int } } - protected abstract void accept(N amount); + protected abstract boolean confirm(N amount); private Button createIncrementButton(final int x, final int y, final int increment) { final Component text = Component.literal((increment > 0 ? "+" : "") + increment); @@ -252,13 +252,14 @@ protected void reset() { } private void tryConfirm() { - getAndValidateAmount().ifPresent(this::accept); + getAndValidateAmount().ifPresent(this::confirm); } private void tryConfirmAndCloseToParent() { getAndValidateAmount().ifPresent(value -> { - accept(value); - tryCloseToParent(); + if (confirm(value)) { + tryCloseToParent(); + } }); } @@ -270,7 +271,7 @@ private boolean tryCloseToParent() { return false; } - private Optional getAndValidateAmount() { + protected final Optional getAndValidateAmount() { if (amountField == null) { return Optional.empty(); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractSingleAmountScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractSingleAmountScreen.java index 24b0c03a4..37b707b9c 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractSingleAmountScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractSingleAmountScreen.java @@ -38,8 +38,9 @@ protected AbstractSingleAmountScreen(final T containerMenu, } @Override - protected void accept(final Double amount) { + protected boolean confirm(final Double amount) { getMenu().changeAmountOnClient(amount); + return true; } @Override diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AmountScreenConfiguration.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AmountScreenConfiguration.java index 3f24f8552..b51cfa8c4 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AmountScreenConfiguration.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AmountScreenConfiguration.java @@ -2,9 +2,15 @@ import javax.annotation.Nullable; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import org.joml.Vector3f; +import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.createTranslation; + public final class AmountScreenConfiguration { + private static final MutableComponent SET_TEXT = createTranslation("gui", "configure_amount.set"); + @Nullable private final T initialAmount; private final int[] incrementsTop; @@ -16,6 +22,7 @@ public final class AmountScreenConfiguration { private final Vector3f actionButtonsStartPosition; private final boolean horizontalActionButtons; private final boolean actionButtonsEnabled; + private final Component confirmButtonText; @Nullable private final T minAmount; @Nullable @@ -33,6 +40,7 @@ private AmountScreenConfiguration(@Nullable final T initialAmount, final Vector3f actionButtonsStartPosition, final boolean horizontalActionButtons, final boolean actionButtonsEnabled, + final Component confirmButtonText, @Nullable final T minAmount, @Nullable final T maxAmount, @Nullable final T resetAmount) { @@ -46,6 +54,7 @@ private AmountScreenConfiguration(@Nullable final T initialAmount, this.actionButtonsStartPosition = actionButtonsStartPosition; this.horizontalActionButtons = horizontalActionButtons; this.actionButtonsEnabled = actionButtonsEnabled; + this.confirmButtonText = confirmButtonText; this.minAmount = minAmount; this.maxAmount = maxAmount; this.resetAmount = resetAmount; @@ -92,6 +101,10 @@ public boolean isActionButtonsEnabled() { return actionButtonsEnabled; } + public Component getConfirmButtonText() { + return confirmButtonText; + } + @Nullable public T getMinAmount() { return minAmount; @@ -118,6 +131,7 @@ public static final class AmountScreenConfigurationBuilder { private Vector3f amountFieldPosition = new Vector3f(0, 0, 0); private Vector3f actionButtonsStartPosition = new Vector3f(0, 0, 0); private boolean horizontalActionButtons = false; + private Component confirmButtonText = SET_TEXT; private boolean actionButtonsEnabled = true; @Nullable private T minAmount; @@ -182,6 +196,13 @@ public AmountScreenConfigurationBuilder withHorizontalActionButtons( return this; } + public AmountScreenConfigurationBuilder withConfirmButtonText( + final Component newConfirmButtonText + ) { + this.confirmButtonText = newConfirmButtonText; + return this; + } + public AmountScreenConfigurationBuilder withActionButtonsEnabled( final boolean newActionButtonsEnabled ) { @@ -216,6 +237,7 @@ public AmountScreenConfiguration build() { actionButtonsStartPosition, horizontalActionButtons, actionButtonsEnabled, + confirmButtonText, minAmount, maxAmount, resetAmount diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/PriorityScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/PriorityScreen.java index bf53e52dc..2e008372c 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/PriorityScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/PriorityScreen.java @@ -43,8 +43,9 @@ public PriorityScreen(final ClientProperty property, } @Override - protected void accept(final Integer amount) { + protected boolean confirm(final Integer amount) { property.setValue(amount); + return true; } @Override diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/ResourceAmountScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/ResourceAmountScreen.java index 3035986b6..104e9584a 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/ResourceAmountScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/ResourceAmountScreen.java @@ -46,8 +46,9 @@ public ResourceAmountScreen(final Screen parent, final Inventory playerInventory } @Override - protected void accept(final Double amount) { + protected boolean confirm(final Double amount) { slot.changeAmountOnClient(amount); + return true; } @Override diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/ResourceClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/ResourceClientTooltipComponent.java index ecff0219d..526fa8153 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/ResourceClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/ResourceClientTooltipComponent.java @@ -4,9 +4,6 @@ import com.refinedmods.refinedstorage.common.api.RefinedStorageApi; import com.refinedmods.refinedstorage.common.api.support.resource.ResourceRendering; -import java.util.Objects; - -import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; @@ -44,7 +41,7 @@ public void renderImage(final Font font, final int x, final int y, final GuiGrap name, x + 16 + 4, y + 4, - Objects.requireNonNullElse(ChatFormatting.GRAY.getColor(), 11184810) + 0xAAAAAA ); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/SmallText.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/SmallText.java index a4756a8c2..d84fcc952 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/SmallText.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/tooltip/SmallText.java @@ -2,6 +2,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.util.FormattedCharSequence; import org.joml.Matrix4f; @@ -50,4 +51,18 @@ public static void render(final Font font, 15728880 ); } + + public static void render(final GuiGraphics graphics, + final Font font, + final FormattedCharSequence text, + final int x, + final int y, + final int color, + final boolean dropShadow) { + final float scale = getScale(); + graphics.pose().pushPose(); + graphics.pose().scale(scale, scale, 1); + graphics.drawString(font, text, (int) (x / scale), (int) (y / scale) + 1, color, dropShadow); + graphics.pose().popPose(); + } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeDestinationClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeDestinationClientTooltipComponent.java index 1895b2dac..633044702 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeDestinationClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeDestinationClientTooltipComponent.java @@ -45,7 +45,7 @@ private void renderMapping(final Font font, mapping.destinationDisplayName().copy().withStyle(ChatFormatting.GRAY), x + 16 + 4, y + 4, - Objects.requireNonNullElse(ChatFormatting.GRAY.getColor(), 7) + 0xAAAAAA ); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeItemClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeItemClientTooltipComponent.java index 737db9d77..be000cacc 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeItemClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/upgrade/UpgradeItemClientTooltipComponent.java @@ -3,9 +3,6 @@ import com.refinedmods.refinedstorage.common.api.upgrade.UpgradeMapping; -import java.util.Objects; - -import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; @@ -36,7 +33,7 @@ public void renderImage(final Font font, final int x, final int y, final GuiGrap mapping.upgradeDisplayName(), x + 16 + 4, y + 4, - Objects.requireNonNullElse(ChatFormatting.WHITE.getColor(), 15) + 0xFFFFFF ); } } diff --git a/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json b/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json index f278e5e54..2009ba76d 100644 --- a/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json +++ b/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json @@ -206,6 +206,11 @@ "gui.refinedstorage.crafter.chained.head_help": "This crafter is the head of the chain.", "gui.refinedstorage.crafter.not_chained": "Not chained", "gui.refinedstorage.crafter.not_chained.help": "If another crafter is facing this one, they'll form a chain, allowing you to have more patterns going into a single machine.", + "gui.refinedstorage.crafting_preview.start": "Start", + "gui.refinedstorage.crafting_preview.start.missing_resources": "There are missing resources.", + "gui.refinedstorage.crafting_preview.available": "Available: %s", + "gui.refinedstorage.crafting_preview.to_craft": "To craft: %s", + "gui.refinedstorage.crafting_preview.missing": "Missing: %s", "item.refinedstorage.controller.help": "Provides the storage network with energy. Multiple are allowed in a single storage network.", "item.refinedstorage.creative_controller.help": "Provides the storage network with an infinite source of energy.", "item.refinedstorage.disk_drive.help": "Accepts storage disks to provide the storage network with storage space.", diff --git a/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/crafting_preview.png b/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/crafting_preview.png new file mode 100644 index 000000000..cbce82533 Binary files /dev/null and b/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/crafting_preview.png differ diff --git a/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/sprites/crafting_preview/row.png b/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/sprites/crafting_preview/row.png new file mode 100644 index 000000000..5a8dc29c6 Binary files /dev/null and b/refinedstorage-common/src/main/resources/assets/refinedstorage/textures/gui/sprites/crafting_preview/row.png differ