From d08c8547b4a9dcff33d0e9bd2f4f67f05ef64d17 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 15:11:49 +0000 Subject: [PATCH] Show the channels of ingredients in storage terminal tooltips When all channels are shown at once, tooltips now indicate in which channel an ingredient is stored, with a quantity breakdown when it is stored in multiple channels. Crafting options indicate their channel as well. This can be disabled with the guiStorageTooltipChannels config. Closes #71 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011TkM3k6R4UTjPCxR4PCHL7 --- .../integratedterminals/GeneralConfig.java | 2 + .../TerminalStorageChannels.java | 105 ++++++++++++ ...alStorageTabIngredientComponentClient.java | 27 +++ .../slot/TerminalStorageSlotIngredient.java | 56 +++++- ...alStorageSlotIngredientCraftingOption.java | 32 ++-- .../GameTestTerminalStorageChannels.java | 160 ++++++++++++++++++ .../integratedterminals/lang/en_us.json | 3 + 7 files changed, 367 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageChannels.java create mode 100644 src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalStorageChannels.java diff --git a/src/main/java/org/cyclops/integratedterminals/GeneralConfig.java b/src/main/java/org/cyclops/integratedterminals/GeneralConfig.java index 70dd1cd2b..7452004fe 100644 --- a/src/main/java/org/cyclops/integratedterminals/GeneralConfig.java +++ b/src/main/java/org/cyclops/integratedterminals/GeneralConfig.java @@ -95,6 +95,8 @@ public class GeneralConfig extends DummyConfig { public static boolean guiStorageForceCraftingGridCenter = false; @ConfigurableProperty(category = "general", comment = "If the automatic re-sorting of the storage terminal contents should be paused while the shift key is held down.", isCommandable = true, configLocation = ModConfig.Type.CLIENT) public static boolean guiStoragePauseSortingWhileShifting = true; + @ConfigurableProperty(category = "general", comment = "If the tooltips in the storage terminal should indicate the channels in which ingredients are available when all channels are shown at once.", isCommandable = true, configLocation = ModConfig.Type.CLIENT) + public static boolean guiStorageTooltipChannels = true; public GeneralConfig() { super(IntegratedTerminals._instance, "general"); diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageChannels.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageChannels.java new file mode 100644 index 000000000..3c9c58f11 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageChannels.java @@ -0,0 +1,105 @@ +package org.cyclops.integratedterminals.core.terminalstorage; + +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.ints.Int2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2LongMap; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher; +import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent; +import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollection; +import org.cyclops.integratedterminals.api.ingredient.IIngredientComponentTerminalStorageHandler; + +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.function.IntFunction; + +/** + * Helpers for determining and displaying the channels in which ingredients are stored. + * @author rubensworks + */ +public final class TerminalStorageChannels { + + private TerminalStorageChannels() {} + + /** + * Determine the quantity of the given instance within each of the given channels. + * @param ingredientComponent The ingredient component. + * @param channels The channels to look in. + * @param channelIngredients A function to get the stored ingredients of a channel. + * @param instance The instance to look for, its quantity is ignored. + * @param The instance type. + * @param The matching condition parameter. + * @return The quantity per channel, in the order of the given channels, without channels that don't store it. + */ + public static Int2LongMap getInstanceQuantitiesPerChannel(IngredientComponent ingredientComponent, + int[] channels, + IntFunction> channelIngredients, + T instance) { + IIngredientMatcher matcher = ingredientComponent.getMatcher(); + M matchCondition = matcher.getExactMatchNoQuantityCondition(); + Int2LongMap quantities = new Int2LongLinkedOpenHashMap(); + for (int channel : channels) { + long quantity = 0; + Iterator it = channelIngredients.apply(channel).iterator(instance, matchCondition); + while (it.hasNext()) { + quantity += matcher.getQuantity(it.next()); + } + if (quantity > 0) { + quantities.put(channel, quantity); + } + } + return quantities; + } + + /** + * Create the tooltip lines that indicate in which channels an instance is stored. + * @param viewHandler The terminal storage handler of the ingredient component. + * @param instance The instance that is stored, its quantity is ignored. + * @param quantitiesPerChannel The stored quantity per channel, + * as determined by {@link #getInstanceQuantitiesPerChannel}. + * @param The instance type. + * @param The matching condition parameter. + * @return The tooltip lines, which are empty if the instance is not stored in any channel. + */ + public static List createChannelTooltipLines(IIngredientComponentTerminalStorageHandler viewHandler, + T instance, + Int2LongMap quantitiesPerChannel) { + List lines = Lists.newArrayList(); + if (quantitiesPerChannel.isEmpty()) { + return lines; + } + + if (quantitiesPerChannel.size() == 1) { + lines.add(createChannelLine(quantitiesPerChannel.keySet().iterator().nextInt())); + } else { + IIngredientMatcher matcher = viewHandler.getComponent().getMatcher(); + lines.add(Component.translatable("gui.integratedterminals.terminal_storage.tooltip.channels") + .withStyle(ChatFormatting.GRAY)); + for (Int2LongMap.Entry entry : quantitiesPerChannel.int2LongEntrySet()) { + lines.add(Component.translatable("gui.integratedterminals.terminal_storage.tooltip.channel_quantity", + formatChannel(entry.getIntKey()), + viewHandler.formatQuantity(matcher.withQuantity(instance, entry.getLongValue()))) + .withStyle(ChatFormatting.DARK_GRAY)); + } + } + + return lines; + } + + /** + * Create the tooltip line that indicates a single channel. + * @param channel A channel id. + * @return The tooltip line. + */ + public static Component createChannelLine(int channel) { + return Component.translatable("gui.integratedterminals.terminal_storage.tooltip.channel", + formatChannel(channel)).withStyle(ChatFormatting.GRAY); + } + + private static String formatChannel(int channel) { + return String.format(Locale.ROOT, "%,d", channel); + } + +} diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabIngredientComponentClient.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabIngredientComponentClient.java index c8f80b9d0..9b69fa9e6 100644 --- a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabIngredientComponentClient.java +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/TerminalStorageTabIngredientComponentClient.java @@ -78,6 +78,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -326,6 +327,32 @@ public Collection> getCraftingOptions(in return craftingOptions.get(channel); } + /** + * Determine in which channels the given instance is stored, and in which quantity. + * @param instance An instance, its quantity is ignored. + * @return The stored quantity per channel, in ascending channel order. + */ + public Int2LongMap getInstanceQuantitiesPerChannel(T instance) { + return TerminalStorageChannels.getInstanceQuantitiesPerChannel(this.ingredientComponent, getChannels(), + this::getRawUnfilteredIngredientsView, instance); + } + + /** + * Determine the channel that the given crafting option is available in. + * @param craftingOption A crafting option. + * @return A channel id, or empty if the crafting option is not available in any channel. + */ + public OptionalInt getCraftingOptionChannel(HandlerWrappedTerminalCraftingOption craftingOption) { + for (int channel : getChannels()) { + Collection> channeledCraftingOptions = getCraftingOptions(channel); + if (channeledCraftingOptions != null + && channeledCraftingOptions.stream().anyMatch(option -> option == craftingOption)) { + return OptionalInt.of(channel); + } + } + return OptionalInt.empty(); + } + /** * Called by the server when the outputs that running crafting jobs are still expected to produce have changed. * @param channel The channel the outputs were collected for. diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredient.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredient.java index 2e4c987cb..c677f4521 100644 --- a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredient.java +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredient.java @@ -9,12 +9,17 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; import org.apache.commons.lang3.tuple.Triple; +import org.cyclops.cyclopscore.helper.GuiHelpers; import org.cyclops.cyclopscore.helper.Helpers; +import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork; +import org.cyclops.integratedterminals.GeneralConfig; import org.cyclops.integratedterminals.api.ingredient.IIngredientComponentTerminalStorageHandler; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageSlot; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabClient; import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; import org.cyclops.integratedterminals.client.gui.image.Images; +import org.cyclops.integratedterminals.client.gui.tooltip.TooltipRenderHelpers; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageChannels; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutput; @@ -50,8 +55,13 @@ public void drawGuiContainerLayer(AbstractContainerScreen gui, GuiGraphics guiGr ITerminalStorageTabClient tab, int channel, @Nullable String label) { long maxQuantity = ((TerminalStorageTabIngredientComponentClient) tab).getMaxQuantity(channel); PendingCraftingJobOutput pendingCraftingJobOutput = getPendingCraftingJobOutput(tab, channel, label); + // This is called for all visible slots on every frame, + // so only determine the tooltip lines when they are actually going to be shown. + List tooltipLines = layer == ContainerScreenTerminalStorage.DrawLayer.FOREGROUND + && TooltipRenderHelpers.isHovering(gui, x, y, GuiHelpers.SLOT_SIZE_INNER, GuiHelpers.SLOT_SIZE_INNER, mouseX, mouseY) + ? createTooltipLines(pendingCraftingJobOutput, tab, channel, label) : null; ingredientComponentViewHandler.drawInstance(guiGraphics, instance, maxQuantity, label, gui, layer, partialTick, x, y, mouseX, mouseY, - createCraftingJobTooltipLines(pendingCraftingJobOutput)); + tooltipLines); drawCraftingJobOverlay(guiGraphics, layer, x, y, pendingCraftingJobOutput); } @@ -82,17 +92,49 @@ protected PendingCraftingJobOutput getPendingCraftingJobOutput(ITerminalStora : null; } - @Nullable @OnlyIn(Dist.CLIENT) - protected List createCraftingJobTooltipLines(@Nullable PendingCraftingJobOutput pendingCraftingJobOutput) { - if (pendingCraftingJobOutput == null) { - return null; - } + protected List createTooltipLines(@Nullable PendingCraftingJobOutput pendingCraftingJobOutput, + ITerminalStorageTabClient tab, int channel, @Nullable String label) { List tooltipLines = Lists.newArrayList(); - addCraftingJobTooltipLines(tooltipLines, pendingCraftingJobOutput); + if (pendingCraftingJobOutput != null) { + addCraftingJobTooltipLines(tooltipLines, pendingCraftingJobOutput); + } + addChannelTooltipLines(tooltipLines, tab, channel, label); return tooltipLines; } + /** + * Add the tooltip lines that indicate in which channels this slot's instance is available. + * + * These are only shown when all channels are shown at once, + * as the channel is already known when a single channel is shown. + * + * @param tooltipLines The tooltip lines to append to. + * @param tab The tab this slot is being rendered in. + * @param channel The channel this slot is being rendered in. + * @param label An optional label that is rendered instead of the quantity. + * Slots with such a label are not part of the storage overview, + * so they don't get a channel indication. + */ + @OnlyIn(Dist.CLIENT) + protected void addChannelTooltipLines(List tooltipLines, ITerminalStorageTabClient tab, + int channel, @Nullable String label) { + if (GeneralConfig.guiStorageTooltipChannels && label == null + && channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) { + tooltipLines.addAll(createChannelTooltipLines(tab)); + } + } + + /** + * @param tab The tab this slot is being rendered in. + * @return The tooltip lines indicating the channels in which this slot's instance is available. + */ + @OnlyIn(Dist.CLIENT) + protected List createChannelTooltipLines(ITerminalStorageTabClient tab) { + return TerminalStorageChannels.createChannelTooltipLines(getIngredientComponentViewHandler(), getInstance(), + ((TerminalStorageTabIngredientComponentClient) tab).getInstanceQuantitiesPerChannel(getInstance())); + } + @OnlyIn(Dist.CLIENT) protected void addCraftingJobTooltipLines(List tooltipLines, PendingCraftingJobOutput pendingCraftingJobOutput) { diff --git a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredientCraftingOption.java b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredientCraftingOption.java index 5bdc085be..b1084a156 100644 --- a/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredientCraftingOption.java +++ b/src/main/java/org/cyclops/integratedterminals/core/terminalstorage/slot/TerminalStorageSlotIngredientCraftingOption.java @@ -1,6 +1,5 @@ package org.cyclops.integratedterminals.core.terminalstorage.slot; -import com.google.common.collect.Lists; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; @@ -17,6 +16,7 @@ import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; import org.cyclops.integratedterminals.client.gui.tooltip.CraftingOptionIngredientsTooltip; import org.cyclops.integratedterminals.client.gui.tooltip.TooltipRenderHelpers; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageChannels; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; import org.cyclops.integratedterminals.core.terminalstorage.crafting.HandlerWrappedTerminalCraftingOption; import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutput; @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import java.util.List; +import java.util.OptionalInt; /** * An ingredient slot for a crafting option. @@ -53,23 +54,21 @@ public void drawGuiContainerLayer(AbstractContainerScreen gui, GuiGraphics guiGr drawCraftLabel(guiGraphics, x, y); } else { // This is called for all visible slots on every frame, - // so only determine the requirements when they are actually going to be shown. - List>> inputs = TooltipRenderHelpers.isHovering(gui, x, y, - GuiHelpers.SLOT_SIZE_INNER, GuiHelpers.SLOT_SIZE_INNER, mouseX, mouseY) - ? getInputs() : List.of(); + // so only determine the tooltip contents when they are actually going to be shown. + boolean hovering = TooltipRenderHelpers.isHovering(gui, x, y, + GuiHelpers.SLOT_SIZE_INNER, GuiHelpers.SLOT_SIZE_INNER, mouseX, mouseY); + List>> inputs = hovering ? getInputs() : List.of(); viewHandler.drawInstance(guiGraphics, getInstance(), maxQuantity, label, gui, layer, partialTick, x, y, mouseX, mouseY, - getTooltipLines(pendingCraftingJobOutput, inputs), + hovering ? getTooltipLines(pendingCraftingJobOutput, inputs, tab, channel, label) : null, inputs.isEmpty() ? null : new CraftingOptionIngredientsTooltip(inputs)); } drawCraftingJobOverlay(guiGraphics, layer, x, y, pendingCraftingJobOutput); } protected List getTooltipLines(@Nullable PendingCraftingJobOutput pendingCraftingJobOutput, - List>> inputs) { - List tooltipLines = Lists.newArrayList(); - if (pendingCraftingJobOutput != null) { - addCraftingJobTooltipLines(tooltipLines, pendingCraftingJobOutput); - } + List>> inputs, + ITerminalStorageTabClient tab, int channel, @Nullable String label) { + List tooltipLines = createTooltipLines(pendingCraftingJobOutput, tab, channel, label); if (!inputs.isEmpty()) { tooltipLines.add(Component.translatable("gui.integratedterminals.terminal_storage.tooltip.requirements") .withStyle(ChatFormatting.YELLOW)); @@ -77,6 +76,17 @@ protected List getTooltipLines(@Nullable PendingCraftingJobOutput return tooltipLines; } + @Override + @OnlyIn(Dist.CLIENT) + protected List createChannelTooltipLines(ITerminalStorageTabClient tab) { + // Contrary to stored ingredients, a crafting option is only available in a single channel. + OptionalInt channel = ((TerminalStorageTabIngredientComponentClient) tab) + .getCraftingOptionChannel(getCraftingOption()); + return channel.isPresent() + ? List.of(TerminalStorageChannels.createChannelLine(channel.getAsInt())) + : List.of(); + } + /** * @return The inputs that are required by this crafting option, with all their alternatives. */ diff --git a/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalStorageChannels.java b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalStorageChannels.java new file mode 100644 index 000000000..ec465bc3c --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalStorageChannels.java @@ -0,0 +1,160 @@ +package org.cyclops.integratedterminals.gametest; + +import it.unimi.dsi.fastutil.ints.Int2LongMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; +import org.cyclops.commoncapabilities.IngredientComponents; +import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollapsedCollectionMutable; +import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollection; +import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionEmpty; +import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionHelpers; +import org.cyclops.integratedterminals.Capabilities; +import org.cyclops.integratedterminals.Reference; +import org.cyclops.integratedterminals.api.ingredient.IIngredientComponentTerminalStorageHandler; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageChannels; + +import java.util.List; + +/** + * Game tests for determining the channels in which ingredients are available in the storage terminal. + * @author rubensworks + */ +@GameTestHolder(Reference.MOD_ID) +@PrefixGameTestTemplate(false) +public class GameTestTerminalStorageChannels { + + /** + * The ingredients that are stored in each channel, which are set up by each test. + */ + private final Int2ObjectMap> channelIngredients = + new Int2ObjectOpenHashMap<>(); + + private static IIngredientComponentTerminalStorageHandler getViewHandler() { + return IngredientComponents.ITEMSTACK + .getCapability(Capabilities.IngredientComponentTerminalStorageHandler.INGREDIENT) + .orElseThrow(() -> new IllegalStateException("Could not find an ingredient terminal storage handler")); + } + + private void store(int channel, ItemStack instance) { + channelIngredients.computeIfAbsent(channel, (c) -> IngredientCollectionHelpers + .createCollapsedCollection(IngredientComponents.ITEMSTACK)).add(instance); + } + + private IIngredientCollection getChannelIngredients(int channel) { + IIngredientCollection ingredients = channelIngredients.get(channel); + return ingredients == null ? new IngredientCollectionEmpty<>(IngredientComponents.ITEMSTACK) : ingredients; + } + + private Int2LongMap getQuantitiesPerChannel(int[] channels, ItemStack instance) { + return TerminalStorageChannels.getInstanceQuantitiesPerChannel(IngredientComponents.ITEMSTACK, channels, + this::getChannelIngredients, instance); + } + + private static String getTranslationKey(Component component) { + return ((TranslatableContents) component.getContents()).getKey(); + } + + private static Object[] getTranslationArgs(Component component) { + return ((TranslatableContents) component.getContents()).getArgs(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testQuantitiesWithoutChannels(GameTestHelper helper) { + helper.assertTrue(getQuantitiesPerChannel(new int[]{}, new ItemStack(Items.STONE)).isEmpty(), + "Nothing should be found without channels"); + + store(0, new ItemStack(Items.STONE, 5)); + helper.assertTrue(getQuantitiesPerChannel(new int[]{}, new ItemStack(Items.STONE)).isEmpty(), + "Nothing should be found without channels"); + + helper.succeed(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testQuantitiesInSingleChannel(GameTestHelper helper) { + store(0, new ItemStack(Items.STONE, 5)); + store(3, new ItemStack(Items.DIRT, 7)); + + Int2LongMap quantities = getQuantitiesPerChannel(new int[]{0, 3}, new ItemStack(Items.STONE, 64)); + helper.assertTrue(quantities.size() == 1, "Stone should be found in exactly one channel"); + helper.assertTrue(quantities.get(0) == 5, "5 stone should be found in channel 0, but got " + quantities.get(0)); + helper.assertTrue(!quantities.containsKey(3), "No stone should be found in channel 3"); + + helper.assertTrue(getQuantitiesPerChannel(new int[]{0, 3}, new ItemStack(Items.DIAMOND)).isEmpty(), + "An unstored item should not be found in any channel"); + + helper.succeed(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testQuantitiesInMultipleChannels(GameTestHelper helper) { + store(0, new ItemStack(Items.STONE, 5)); + store(0, new ItemStack(Items.STONE, 3)); + store(1, new ItemStack(Items.DIRT, 7)); + store(2, new ItemStack(Items.STONE, 64)); + + Int2LongMap quantities = getQuantitiesPerChannel(new int[]{0, 1, 2}, new ItemStack(Items.STONE)); + helper.assertTrue(quantities.size() == 2, "Stone should be found in exactly two channels"); + helper.assertTrue(quantities.get(0) == 8, "8 stone should be found in channel 0, but got " + quantities.get(0)); + helper.assertTrue(quantities.get(2) == 64, "64 stone should be found in channel 2, but got " + quantities.get(2)); + helper.assertTrue(quantities.keySet().toIntArray()[0] == 0, + "Channels should be ordered as they were given"); + + helper.succeed(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testTooltipLinesWithoutChannels(GameTestHelper helper) { + helper.assertTrue(TerminalStorageChannels.createChannelTooltipLines(getViewHandler(), + new ItemStack(Items.STONE), getQuantitiesPerChannel(new int[]{0}, new ItemStack(Items.STONE))) + .isEmpty(), "No tooltip lines should be shown for an unstored item"); + + helper.succeed(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testTooltipLinesForSingleChannel(GameTestHelper helper) { + store(3, new ItemStack(Items.STONE, 5)); + + List lines = TerminalStorageChannels.createChannelTooltipLines(getViewHandler(), + new ItemStack(Items.STONE), getQuantitiesPerChannel(new int[]{0, 3}, new ItemStack(Items.STONE))); + helper.assertTrue(lines.size() == 1, "A single tooltip line should be shown"); + helper.assertTrue(getTranslationKey(lines.get(0)) + .equals("gui.integratedterminals.terminal_storage.tooltip.channel"), + "The tooltip line should indicate the channel"); + helper.assertTrue(getTranslationArgs(lines.get(0))[0].equals("3"), + "The tooltip line should indicate channel 3"); + + helper.succeed(); + } + + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testTooltipLinesForMultipleChannels(GameTestHelper helper) { + store(0, new ItemStack(Items.STONE, 5)); + store(3, new ItemStack(Items.STONE, 64)); + + List lines = TerminalStorageChannels.createChannelTooltipLines(getViewHandler(), + new ItemStack(Items.STONE), getQuantitiesPerChannel(new int[]{0, 3}, new ItemStack(Items.STONE))); + helper.assertTrue(lines.size() == 3, "A tooltip line should be shown for each channel, with a header"); + helper.assertTrue(getTranslationKey(lines.get(0)) + .equals("gui.integratedterminals.terminal_storage.tooltip.channels"), + "The first tooltip line should be the header"); + helper.assertTrue(getTranslationArgs(lines.get(1))[0].equals("0") + && getTranslationArgs(lines.get(1))[1].equals("5"), + "The second tooltip line should indicate 5 in channel 0"); + helper.assertTrue(getTranslationArgs(lines.get(2))[0].equals("3") + && getTranslationArgs(lines.get(2))[1].equals("64"), + "The third tooltip line should indicate 64 in channel 3"); + + helper.succeed(); + } + +} diff --git a/src/main/resources/assets/integratedterminals/lang/en_us.json b/src/main/resources/assets/integratedterminals/lang/en_us.json index 75140886c..ad22f5470 100644 --- a/src/main/resources/assets/integratedterminals/lang/en_us.json +++ b/src/main/resources/assets/integratedterminals/lang/en_us.json @@ -31,6 +31,9 @@ "gui.integratedterminals.terminal_storage.to_craft": "To Craft: %s", "gui.integratedterminals.terminal_storage.crafting": "Crafting: %s", "gui.integratedterminals.terminal_storage.missing": "Missing: %s", + "gui.integratedterminals.terminal_storage.tooltip.channel": "Channel: %s", + "gui.integratedterminals.terminal_storage.tooltip.channels": "Channels:", + "gui.integratedterminals.terminal_storage.tooltip.channel_quantity": " %s: %s", "gui.integratedterminals.terminal_storage.tooltip.quantity": "Quantity: %s", "gui.integratedterminals.terminal_storage.sort": "Sort", "gui.integratedterminals.terminal_storage.sort.order.label": "Order: %s",