diff --git a/src/main/java/de/ellpeck/prettypipes/Registry.java b/src/main/java/de/ellpeck/prettypipes/Registry.java index b6331b7..bd074e3 100644 --- a/src/main/java/de/ellpeck/prettypipes/Registry.java +++ b/src/main/java/de/ellpeck/prettypipes/Registry.java @@ -1,5 +1,6 @@ package de.ellpeck.prettypipes; +import de.ellpeck.prettypipes.items.IModule; import de.ellpeck.prettypipes.items.ModuleItem; import de.ellpeck.prettypipes.pipe.modules.LowPriorityModuleItem; import de.ellpeck.prettypipes.pipe.modules.SpeedModuleItem; @@ -15,6 +16,9 @@ import de.ellpeck.prettypipes.pipe.modules.containers.*; import de.ellpeck.prettypipes.pipe.modules.insertion.FilterModuleContainer; import de.ellpeck.prettypipes.pipe.modules.insertion.FilterModuleGui; import de.ellpeck.prettypipes.pipe.modules.insertion.FilterModuleItem; +import de.ellpeck.prettypipes.pipe.modules.retrieval.RetrievalModuleContainer; +import de.ellpeck.prettypipes.pipe.modules.retrieval.RetrievalModuleGui; +import de.ellpeck.prettypipes.pipe.modules.retrieval.RetrievalModuleItem; import net.minecraft.block.Block; import net.minecraft.client.gui.ScreenManager; import net.minecraft.client.renderer.RenderType; @@ -68,6 +72,7 @@ public final class Registry { public static ContainerType pipeContainer; public static ContainerType extractionModuleContainer; public static ContainerType filterModuleContainer; + public static ContainerType retrievalModuleContainer; @SubscribeEvent public static void registerBlocks(RegistryEvent.Register event) { @@ -86,6 +91,7 @@ public final class Registry { registry.registerAll(createTieredModule("filter_module", FilterModuleItem::new)); registry.registerAll(createTieredModule("speed_module", SpeedModuleItem::new)); registry.registerAll(createTieredModule("low_priority_module", LowPriorityModuleItem::new)); + registry.registerAll(createTieredModule("retrieval_module", RetrievalModuleItem::new)); ForgeRegistries.BLOCKS.getValues().stream() .filter(b -> b.getRegistryName().getNamespace().equals(PrettyPipes.ID)) @@ -102,12 +108,23 @@ public final class Registry { @SubscribeEvent public static void registerContainer(RegistryEvent.Register> event) { event.getRegistry().registerAll( + // this needs to be registered manually since it doesn't send the module slot pipeContainer = (ContainerType) IForgeContainerType.create((windowId, inv, data) -> new MainPipeContainer(pipeContainer, windowId, inv.player, data.readBlockPos())).setRegistryName("pipe"), - extractionModuleContainer = (ContainerType) IForgeContainerType.create((windowId, inv, data) -> new ExtractionModuleContainer(extractionModuleContainer, windowId, inv.player, data.readBlockPos(), data.readInt())).setRegistryName("extraction_module"), - filterModuleContainer = (ContainerType) IForgeContainerType.create((windowId, inv, data) -> new FilterModuleContainer(filterModuleContainer, windowId, inv.player, data.readBlockPos(), data.readInt())).setRegistryName("filter_module") + extractionModuleContainer = createPipeContainer("extraction_module"), + filterModuleContainer = createPipeContainer("filter_module"), + retrievalModuleContainer = createPipeContainer("retrieval_module") ); } + private static > ContainerType createPipeContainer(String name) { + return (ContainerType) IForgeContainerType.create((windowId, inv, data) -> { + PipeTileEntity tile = Utility.getTileEntity(PipeTileEntity.class, inv.player.world, data.readBlockPos()); + int moduleIndex = data.readInt(); + ItemStack moduleStack = tile.modules.getStackInSlot(moduleIndex); + return ((IModule) moduleStack.getItem()).getContainer(moduleStack, tile, windowId, inv, inv.player, moduleIndex); + }).setRegistryName(name); + } + public static void setup(FMLCommonSetupEvent event) { CapabilityManager.INSTANCE.register(PipeNetwork.class, new Capability.IStorage() { @Nullable @@ -139,6 +156,7 @@ public final class Registry { ScreenManager.registerFactory(pipeContainer, MainPipeGui::new); ScreenManager.registerFactory(extractionModuleContainer, ExtractionModuleGui::new); ScreenManager.registerFactory(filterModuleContainer, FilterModuleGui::new); + ScreenManager.registerFactory(retrievalModuleContainer, RetrievalModuleGui::new); } } } diff --git a/src/main/java/de/ellpeck/prettypipes/misc/ItemFilter.java b/src/main/java/de/ellpeck/prettypipes/misc/ItemFilter.java index 6d6050d..4882418 100644 --- a/src/main/java/de/ellpeck/prettypipes/misc/ItemFilter.java +++ b/src/main/java/de/ellpeck/prettypipes/misc/ItemFilter.java @@ -4,33 +4,22 @@ import de.ellpeck.prettypipes.PrettyPipes; import de.ellpeck.prettypipes.packets.PacketButton; import de.ellpeck.prettypipes.packets.PacketHandler; import de.ellpeck.prettypipes.pipe.PipeTileEntity; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.AbstractGui; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.inventory.ContainerScreen; import net.minecraft.client.gui.widget.Widget; import net.minecraft.client.gui.widget.button.Button; import net.minecraft.client.resources.I18n; -import net.minecraft.inventory.ItemStackHelper; import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextFormatting; -import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.common.util.INBTSerializable; -import net.minecraftforge.fml.client.gui.GuiUtils; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.ItemStackHandler; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -38,8 +27,10 @@ public class ItemFilter extends ItemStackHandler { private final ItemStack stack; private final PipeTileEntity pipe; + public boolean isWhitelist; + public boolean canPopulateFromInventories; - private boolean isWhitelist; + public boolean canModifyWhitelist = true; public ItemFilter(int size, ItemStack stack, PipeTileEntity pipe) { super(size); @@ -59,12 +50,14 @@ public class ItemFilter extends ItemStackHandler { @OnlyIn(Dist.CLIENT) public List getButtons(Screen gui, int x, int y) { List buttons = new ArrayList<>(); - Supplier whitelistText = () -> I18n.format("info." + PrettyPipes.ID + "." + (this.isWhitelist ? "whitelist" : "blacklist")); - buttons.add(new Button(x, y, 70, 20, whitelistText.get(), button -> { - PacketHandler.sendToServer(new PacketButton(this.pipe.getPos(), PacketButton.ButtonResult.FILTER_CHANGE, 0)); - this.onButtonPacket(0); - button.setMessage(whitelistText.get()); - })); + if (this.canModifyWhitelist) { + Supplier whitelistText = () -> I18n.format("info." + PrettyPipes.ID + "." + (this.isWhitelist ? "whitelist" : "blacklist")); + buttons.add(new Button(x, y, 70, 20, whitelistText.get(), button -> { + PacketHandler.sendToServer(new PacketButton(this.pipe.getPos(), PacketButton.ButtonResult.FILTER_CHANGE, 0)); + this.onButtonPacket(0); + button.setMessage(whitelistText.get()); + })); + } if (this.canPopulateFromInventories) { buttons.add(new Button(x + 72, y, 70, 20, I18n.format("info." + PrettyPipes.ID + ".populate"), button -> { PacketHandler.sendToServer(new PacketButton(this.pipe.getPos(), PacketButton.ButtonResult.FILTER_CHANGE, 1)); @@ -80,9 +73,9 @@ public class ItemFilter extends ItemStackHandler { } public void onButtonPacket(int id) { - if (id == 0) { + if (id == 0 && this.canModifyWhitelist) { this.isWhitelist = !this.isWhitelist; - } else if (id == 1) { + } else if (id == 1 && this.canPopulateFromInventories) { // populate filter from inventories for (Direction direction : Direction.values()) { IItemHandler handler = this.pipe.getItemHandler(direction); diff --git a/src/main/java/de/ellpeck/prettypipes/network/NetworkLocation.java b/src/main/java/de/ellpeck/prettypipes/network/NetworkLocation.java new file mode 100644 index 0000000..0d42f8e --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/network/NetworkLocation.java @@ -0,0 +1,31 @@ +package de.ellpeck.prettypipes.network; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class NetworkLocation { + + public final BlockPos pipePos; + public final ListMultimap> items; + + public NetworkLocation(BlockPos pipePos, ListMultimap> items) { + this.pipePos = pipePos; + this.items = items; + } + + public Pair getStackLocation(ItemStack stack) { + for (Map.Entry> entry : this.items.entries()) { + if (entry.getValue().getRight().isItemEqual(stack)) + return Pair.of(entry.getKey(), entry.getValue().getLeft()); + } + return null; + } +} diff --git a/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java b/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java index 6380b7b..d457fba 100644 --- a/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java +++ b/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java @@ -1,6 +1,7 @@ package de.ellpeck.prettypipes.network; -import com.google.common.collect.Lists; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Streams; import de.ellpeck.prettypipes.PrettyPipes; import de.ellpeck.prettypipes.Registry; @@ -21,6 +22,8 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilitySerializable; import net.minecraftforge.common.util.Constants; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import org.apache.commons.lang3.tuple.Pair; import org.jgrapht.GraphPath; import org.jgrapht.ListenableGraph; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; @@ -36,7 +39,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; public class PipeNetwork implements ICapabilitySerializable, GraphListener { @@ -125,7 +127,7 @@ public class PipeNetwork implements ICapabilitySerializable, GraphL this.startProfile("find_destination"); for (BlockPos pipePos : this.getOrderedDestinations(startPipePos)) { PipeTileEntity pipe = this.getPipe(pipePos); - BlockPos dest = pipe.getAvailableDestination(stack); + BlockPos dest = pipe.getAvailableDestination(stack, false); if (dest != null) { this.endProfile(); return this.routeItemToLocation(startPipePos, startInventory, pipe.getPos(), dest, itemSupplier); @@ -165,6 +167,34 @@ public class PipeNetwork implements ICapabilitySerializable, GraphL return tile; } + public List getOrderedNetworkItems(BlockPos node) { + if (!this.isNode(node)) + return Collections.emptyList(); + this.startProfile("get_network_items"); + List info = new ArrayList<>(); + for (BlockPos dest : this.getOrderedDestinations(node)) { + PipeTileEntity pipe = this.getPipe(dest); + for (Direction dir : Direction.values()) { + IItemHandler handler = pipe.getItemHandler(dir); + if (handler == null) + continue; + ListMultimap> items = null; + for (int i = 0; i < handler.getSlots(); i++) { + ItemStack found = handler.extractItem(i, Integer.MAX_VALUE, true); + if (found.isEmpty()) + continue; + if (items == null) + items = ArrayListMultimap.create(); + items.put(dir, Pair.of(i, found)); + } + if (items != null) + info.add(new NetworkLocation(dest, items)); + } + } + this.endProfile(); + return info; + } + private void refreshNode(BlockPos pos, BlockState state) { this.startProfile("refresh_node"); this.graph.removeAllEdges(new ArrayList<>(this.graph.edgesOf(pos))); @@ -248,6 +278,7 @@ public class PipeNetwork implements ICapabilitySerializable, GraphL // sort destinations first by their priority (eg trash pipes should be last) // and then by their distance from the specified node ret = Streams.stream(new BreadthFirstIterator<>(this.graph, node)) + .filter(p -> !p.equals(node)) .sorted(Comparator.comparingInt(p -> this.getPipe(p).getPriority()).reversed().thenComparing(paths::getWeight)) .collect(Collectors.toList()); this.nodeToConnectedNodes.put(node, ret); diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/PipeTileEntity.java b/src/main/java/de/ellpeck/prettypipes/pipe/PipeTileEntity.java index a1ac00f..8d9266f 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/PipeTileEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/PipeTileEntity.java @@ -115,8 +115,8 @@ public class PipeTileEntity extends TileEntity implements INamedContainerProvide return this.getBlockState().get(PipeBlock.DIRECTIONS.get(dir)).isConnected(); } - public BlockPos getAvailableDestination(ItemStack stack) { - if (this.streamModules().anyMatch(m -> !m.getRight().canAcceptItem(m.getLeft(), this, stack))) + public BlockPos getAvailableDestination(ItemStack stack, boolean internal) { + if (!internal && this.streamModules().anyMatch(m -> !m.getRight().canAcceptItem(m.getLeft(), this, stack))) return null; for (Direction dir : Direction.values()) { IItemHandler handler = this.getItemHandler(dir); diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleContainer.java b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleContainer.java new file mode 100644 index 0000000..2a482c5 --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleContainer.java @@ -0,0 +1,39 @@ +package de.ellpeck.prettypipes.pipe.modules.retrieval; + +import de.ellpeck.prettypipes.misc.ItemFilter; +import de.ellpeck.prettypipes.misc.ItemFilter.IFilteredContainer; +import de.ellpeck.prettypipes.pipe.modules.containers.AbstractPipeContainer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.container.ContainerType; +import net.minecraft.inventory.container.Slot; +import net.minecraft.util.math.BlockPos; + +import javax.annotation.Nullable; + +public class RetrievalModuleContainer extends AbstractPipeContainer implements IFilteredContainer { + + public ItemFilter filter; + + public RetrievalModuleContainer(@Nullable ContainerType type, int id, PlayerEntity player, BlockPos pos, int moduleIndex) { + super(type, id, player, pos, moduleIndex); + } + + @Override + protected void addSlots() { + this.filter = new ItemFilter(this.module.filterSlots, this.moduleStack, this.tile); + this.filter.canModifyWhitelist = false; + for (Slot slot : this.filter.getSlots((176 - this.module.filterSlots * 18) / 2 + 1, 17 + 32)) + this.addSlot(slot); + } + + @Override + public void onContainerClosed(PlayerEntity playerIn) { + super.onContainerClosed(playerIn); + this.filter.save(); + } + + @Override + public ItemFilter getFilter() { + return this.filter; + } +} diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleGui.java b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleGui.java new file mode 100644 index 0000000..578e2db --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleGui.java @@ -0,0 +1,20 @@ +package de.ellpeck.prettypipes.pipe.modules.retrieval; + +import de.ellpeck.prettypipes.pipe.modules.containers.AbstractPipeGui; +import de.ellpeck.prettypipes.pipe.modules.extraction.ExtractionModuleContainer; +import net.minecraft.client.gui.widget.Widget; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.util.text.ITextComponent; + +public class RetrievalModuleGui extends AbstractPipeGui { + public RetrievalModuleGui(RetrievalModuleContainer screenContainer, PlayerInventory inv, ITextComponent titleIn) { + super(screenContainer, inv, titleIn); + } + + @Override + protected void init() { + super.init(); + for (Widget widget : this.container.filter.getButtons(this, this.guiLeft + 7, this.guiTop + 17 + 32 + 20)) + this.addButton(widget); + } +} diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleItem.java b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleItem.java new file mode 100644 index 0000000..2c7fe62 --- /dev/null +++ b/src/main/java/de/ellpeck/prettypipes/pipe/modules/retrieval/RetrievalModuleItem.java @@ -0,0 +1,93 @@ +package de.ellpeck.prettypipes.pipe.modules.retrieval; + +import de.ellpeck.prettypipes.Registry; +import de.ellpeck.prettypipes.items.IModule; +import de.ellpeck.prettypipes.items.ModuleItem; +import de.ellpeck.prettypipes.items.ModuleTier; +import de.ellpeck.prettypipes.misc.ItemFilter; +import de.ellpeck.prettypipes.network.NetworkLocation; +import de.ellpeck.prettypipes.network.PipeItem; +import de.ellpeck.prettypipes.network.PipeNetwork; +import de.ellpeck.prettypipes.pipe.PipeTileEntity; +import de.ellpeck.prettypipes.pipe.modules.containers.AbstractPipeContainer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.items.IItemHandler; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; + +public class RetrievalModuleItem extends ModuleItem { + private final int maxExtraction; + private final int speed; + public final int filterSlots; + + public RetrievalModuleItem(String name, ModuleTier tier) { + super(name); + this.maxExtraction = tier.forTier(1, 8, 16); + this.speed = tier.forTier(40, 20, 10); + this.filterSlots = tier.forTier(3, 6, 9); + } + + @Override + public void tick(ItemStack module, PipeTileEntity tile) { + if (tile.getWorld().getGameTime() % this.speed != 0) + return; + PipeNetwork network = PipeNetwork.get(tile.getWorld()); + List locations = null; + + ItemFilter filter = new ItemFilter(this.filterSlots, module, tile); + filter.isWhitelist = true; + // loop through filter to see which items to pull + for (int f = 0; f < filter.getSlots(); f++) { + ItemStack filtered = filter.getStackInSlot(f); + if (filtered.isEmpty()) + continue; + ItemStack copy = filtered.copy(); + copy.setCount(this.maxExtraction); + BlockPos dest = tile.getAvailableDestination(copy, true); + if (dest == null) + continue; + // loop through locations to find a location that has the item + if (locations == null) + locations = network.getOrderedNetworkItems(tile.getPos()); + for (NetworkLocation location : locations) { + Pair item = location.getStackLocation(filtered); + if (item == null) + continue; + // get that location's pipe and inventory + PipeTileEntity pipe = network.getPipe(location.pipePos); + IItemHandler handler = pipe.getItemHandler(item.getKey()); + // try to extract from that location's inventory and send the item + ItemStack stack = handler.extractItem(item.getValue(), this.maxExtraction, true); + if (network.routeItemToLocation(location.pipePos, location.pipePos.offset(item.getKey()), tile.getPos(), dest, speed -> new PipeItem(stack, speed))) { + handler.extractItem(item.getValue(), stack.getCount(), false); + return; + } + } + } + } + + @Override + public boolean canAcceptItem(ItemStack module, PipeTileEntity tile, ItemStack stack) { + return false; + } + + @Override + public boolean isCompatible(ItemStack module, PipeTileEntity tile, IModule other) { + return !(other instanceof RetrievalModuleItem); + } + + @Override + public boolean hasContainer(ItemStack module, PipeTileEntity tile) { + return true; + } + + @Override + public AbstractPipeContainer getContainer(ItemStack module, PipeTileEntity tile, int windowId, PlayerInventory inv, PlayerEntity player, int moduleIndex) { + return new RetrievalModuleContainer(Registry.retrievalModuleContainer, windowId, player, tile.getPos(), moduleIndex); + } +} diff --git a/src/main/resources/assets/prettypipes/lang/en_us.json b/src/main/resources/assets/prettypipes/lang/en_us.json index 55d344d..baa21dc 100644 --- a/src/main/resources/assets/prettypipes/lang/en_us.json +++ b/src/main/resources/assets/prettypipes/lang/en_us.json @@ -12,10 +12,14 @@ "item.prettypipes.low_low_priority_module": "Low Priority Module", "item.prettypipes.medium_low_priority_module": "Lower Priority Module", "item.prettypipes.high_low_priority_module": "Lowest Priority Module", + "item.prettypipes.low_retrieval_module": "Low Retrieval Module", + "item.prettypipes.medium_retrieval_module": "Medium Retrieval Module", + "item.prettypipes.high_retrieval_module": "High Retrieval Module", "info.prettypipes.extraction_module": "Pulls items from adjacent inventories\nFilters and pull rates vary by tier", "info.prettypipes.filter_module": "Restricts flow from pipes into adjacent inventories\nFilter amount varies by tier", "info.prettypipes.speed_module": "Increases speed of items exiting adjacent inventories\nSpeed varies by tier", "info.prettypipes.low_priority_module": "Decreases the reception priority of adjacent inventories\nLower priority means items will prefer other inventories", + "info.prettypipes.retrieval_module": "Pulls items from other inventories in the network\nFilters and pull rates vary by tier", "block.prettypipes.pipe": "Pipe", "itemGroup.prettypipes": "Pretty Pipes", "container.prettypipes.pipe": "Pipe", diff --git a/src/main/resources/assets/prettypipes/models/item/high_retrieval_module.json b/src/main/resources/assets/prettypipes/models/item/high_retrieval_module.json new file mode 100644 index 0000000..f2ae720 --- /dev/null +++ b/src/main/resources/assets/prettypipes/models/item/high_retrieval_module.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "prettypipes:item/high_retrieval_module" + } +} diff --git a/src/main/resources/assets/prettypipes/models/item/low_retrieval_module.json b/src/main/resources/assets/prettypipes/models/item/low_retrieval_module.json new file mode 100644 index 0000000..74e8a7c --- /dev/null +++ b/src/main/resources/assets/prettypipes/models/item/low_retrieval_module.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "prettypipes:item/low_retrieval_module" + } +} diff --git a/src/main/resources/assets/prettypipes/models/item/medium_retrieval_module.json b/src/main/resources/assets/prettypipes/models/item/medium_retrieval_module.json new file mode 100644 index 0000000..b7f1543 --- /dev/null +++ b/src/main/resources/assets/prettypipes/models/item/medium_retrieval_module.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "prettypipes:item/medium_retrieval_module" + } +} diff --git a/src/main/resources/assets/prettypipes/textures/item/high_retrieval_module.png b/src/main/resources/assets/prettypipes/textures/item/high_retrieval_module.png new file mode 100644 index 0000000..afe943b Binary files /dev/null and b/src/main/resources/assets/prettypipes/textures/item/high_retrieval_module.png differ diff --git a/src/main/resources/assets/prettypipes/textures/item/low_retrieval_module.png b/src/main/resources/assets/prettypipes/textures/item/low_retrieval_module.png new file mode 100644 index 0000000..92e1168 Binary files /dev/null and b/src/main/resources/assets/prettypipes/textures/item/low_retrieval_module.png differ diff --git a/src/main/resources/assets/prettypipes/textures/item/medium_retrieval_module.png b/src/main/resources/assets/prettypipes/textures/item/medium_retrieval_module.png new file mode 100644 index 0000000..bd73a62 Binary files /dev/null and b/src/main/resources/assets/prettypipes/textures/item/medium_retrieval_module.png differ