diff --git a/src/main/java/de/ellpeck/prettypipes/items/IModule.java b/src/main/java/de/ellpeck/prettypipes/items/IModule.java index 1fe5732..2c3d2fc 100644 --- a/src/main/java/de/ellpeck/prettypipes/items/IModule.java +++ b/src/main/java/de/ellpeck/prettypipes/items/IModule.java @@ -48,4 +48,7 @@ public interface IModule { ItemFilter getItemFilter(ItemStack module, PipeBlockEntity tile); DirectionSelector getDirectionSelector(ItemStack module, PipeBlockEntity tile); + + ItemStack store(ItemStack module, PipeBlockEntity tile, ItemStack stack, Direction direction); + } diff --git a/src/main/java/de/ellpeck/prettypipes/items/ModuleItem.java b/src/main/java/de/ellpeck/prettypipes/items/ModuleItem.java index f388a9a..a594164 100644 --- a/src/main/java/de/ellpeck/prettypipes/items/ModuleItem.java +++ b/src/main/java/de/ellpeck/prettypipes/items/ModuleItem.java @@ -108,4 +108,9 @@ public abstract class ModuleItem extends Item implements IModule { return null; } + @Override + public ItemStack store(ItemStack module, PipeBlockEntity tile, ItemStack stack, Direction direction) { + return stack; + } + } diff --git a/src/main/java/de/ellpeck/prettypipes/network/PipeItem.java b/src/main/java/de/ellpeck/prettypipes/network/PipeItem.java index 43bb248..2b5c5ca 100644 --- a/src/main/java/de/ellpeck/prettypipes/network/PipeItem.java +++ b/src/main/java/de/ellpeck/prettypipes/network/PipeItem.java @@ -202,13 +202,7 @@ public class PipeItem implements IPipeItem { protected ItemStack store(PipeBlockEntity currPipe) { var dir = Utility.getDirectionFromOffset(this.destInventory, this.getDestPipe()); - var connectable = currPipe.getPipeConnectable(dir); - if (connectable != null) - return connectable.insertItem(currPipe.getBlockPos(), dir, this.stack, false); - var handler = currPipe.getItemHandler(dir); - if (handler != null) - return ItemHandlerHelper.insertItemStacked(handler, this.stack, false); - return this.stack; + return currPipe.store(this.stack, dir); } protected PipeBlockEntity getNextTile(PipeBlockEntity currPipe, boolean progress) { diff --git a/src/main/java/de/ellpeck/prettypipes/packets/PacketButton.java b/src/main/java/de/ellpeck/prettypipes/packets/PacketButton.java index b9b6f9c..f7af1c6 100644 --- a/src/main/java/de/ellpeck/prettypipes/packets/PacketButton.java +++ b/src/main/java/de/ellpeck/prettypipes/packets/PacketButton.java @@ -6,6 +6,7 @@ import de.ellpeck.prettypipes.items.IModule; import de.ellpeck.prettypipes.misc.ItemFilter.IFilteredContainer; import de.ellpeck.prettypipes.pipe.PipeBlockEntity; import de.ellpeck.prettypipes.pipe.containers.AbstractPipeContainer; +import de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleContainer; import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleContainer; import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleItem; import de.ellpeck.prettypipes.pipe.modules.stacksize.StackSizeModuleItem; @@ -94,6 +95,18 @@ public record PacketButton(BlockPos pos, int result, List data) impleme if (player.containerMenu instanceof IFilteredContainer filtered) filtered.getFilter().onButtonPacket(filtered, data.getFirst()); }), + ENSURE_ITEM_ORDER_BUTTON((pos, data, player) -> { + if (player.containerMenu instanceof CraftingModuleContainer container) { + container.ensureItemOrder = !container.ensureItemOrder; + container.modified = true; + } + }), + INSERT_SINGLES_BUTTON((pos, data, player) -> { + if (player.containerMenu instanceof CraftingModuleContainer container) { + container.insertSingles = !container.insertSingles; + container.modified = true; + } + }), STACK_SIZE_MODULE_BUTTON((pos, data, player) -> { var container = (AbstractPipeContainer) player.containerMenu; var moduleData = container.moduleStack.getOrDefault(StackSizeModuleItem.Data.TYPE, StackSizeModuleItem.Data.DEFAULT); diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java index 554f458..24d088d 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java @@ -4,7 +4,6 @@ import de.ellpeck.prettypipes.PrettyPipes; import de.ellpeck.prettypipes.Registry; import de.ellpeck.prettypipes.Utility; import de.ellpeck.prettypipes.items.IModule; -import de.ellpeck.prettypipes.misc.EquatableItemStack; import de.ellpeck.prettypipes.misc.ItemFilter; import de.ellpeck.prettypipes.network.NetworkLock; import de.ellpeck.prettypipes.network.PipeNetwork; @@ -39,6 +38,7 @@ import net.neoforged.neoforge.capabilities.BlockCapability; import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.common.util.Lazy; import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.ItemHandlerHelper; import net.neoforged.neoforge.items.ItemStackHandler; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -72,7 +72,7 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC } }; // crafting module slot, ingredient request network lock - public final Queue> craftIngredientRequests = new LinkedList<>(); + public final List> craftIngredientRequests = new ArrayList<>(); // crafting module slot, destination pipe for the result, result item public final List> craftResultRequests = new ArrayList<>(); public PressurizerBlockEntity pressurizer; @@ -416,6 +416,23 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC }).filter(Objects::nonNull).collect(Collectors.toList()); } + public ItemStack store(ItemStack stack, Direction direction) { + var modules = this.streamModules().iterator(); + while (modules.hasNext()) { + var module = modules.next(); + stack = module.getRight().store(module.getLeft(), this, stack, direction); + if (stack.isEmpty()) + return stack; + } + var connectable = this.getPipeConnectable(direction); + if (connectable != null) + return connectable.insertItem(this.getBlockPos(), direction, stack, false); + var handler = this.getItemHandler(direction); + if (handler != null) + return ItemHandlerHelper.insertItemStacked(handler, stack, false); + return stack; + } + @Override public Component getDisplayName() { return Component.translatable("container." + PrettyPipes.ID + ".pipe"); diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleContainer.java b/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleContainer.java index 5083717..3136949 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleContainer.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleContainer.java @@ -6,12 +6,17 @@ import de.ellpeck.prettypipes.pipe.containers.AbstractPipeContainer; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.items.SlotItemHandler; +import org.jetbrains.annotations.NotNull; public class CraftingModuleContainer extends AbstractPipeContainer { public ItemStackHandler input; public ItemStackHandler output; + public boolean ensureItemOrder; + public boolean insertSingles; public boolean modified; public CraftingModuleContainer(MenuType type, int id, Player player, BlockPos pos, int moduleIndex) { @@ -21,6 +26,9 @@ public class CraftingModuleContainer extends AbstractPipeContainer { public CraftingModuleGui(CraftingModuleContainer screenContainer, Inventory inv, Component titleIn) { @@ -16,4 +24,23 @@ public class CraftingModuleGui extends AbstractPipeGui super.renderBg(graphics, partialTicks, mouseX, mouseY); graphics.blit(AbstractPipeGui.TEXTURE, this.leftPos + 176 / 2 - 16 / 2, this.topPos + 32 + 18 * 2, 176, 80, 16, 16); } + + @Override + protected void init() { + super.init(); + var cacheText = (Supplier) () -> "info." + PrettyPipes.ID + ".ensure_item_order_" + (this.menu.ensureItemOrder ? "on" : "off"); + this.addRenderableWidget(Button.builder(Component.translatable(cacheText.get()), button -> { + PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.ENSURE_ITEM_ORDER_BUTTON, List.of()); + button.setMessage(Component.translatable(cacheText.get())); + }).bounds(this.leftPos + this.imageWidth - 7 - 20, this.topPos + 17 + 32 + 18 * 2 + 2, 20, 20).tooltip( + Tooltip.create(Component.translatable("info." + PrettyPipes.ID + ".ensure_item_order.description").withStyle(ChatFormatting.GRAY))).build()); + + var singleText = (Supplier) () -> "info." + PrettyPipes.ID + ".insert_singles_" + (this.menu.insertSingles ? "on" : "off"); + this.addRenderableWidget(Button.builder(Component.translatable(singleText.get()), button -> { + PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.INSERT_SINGLES_BUTTON, List.of()); + button.setMessage(Component.translatable(singleText.get())); + }).bounds(this.leftPos + this.imageWidth - 7 - 20 - 22, this.topPos + 17 + 32 + 18 * 2 + 2, 20, 20).tooltip( + Tooltip.create(Component.translatable("info." + PrettyPipes.ID + ".insert_singles.description").withStyle(ChatFormatting.GRAY))).build()); + } + } diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleItem.java b/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleItem.java index d63f2d8..3652380 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleItem.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/modules/craft/CraftingModuleItem.java @@ -23,13 +23,12 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.ItemHandlerHelper; import net.neoforged.neoforge.items.ItemStackHandler; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; +import java.util.*; import java.util.function.Consumer; public class CraftingModuleItem extends ModuleItem { @@ -37,7 +36,7 @@ public class CraftingModuleItem extends ModuleItem { private final int speed; public CraftingModuleItem(String name, ModuleTier tier) { - super(name, new Properties().component(Contents.TYPE, new Contents(new ItemStackHandler(tier.forTier(1, 4, 9)), new ItemStackHandler(tier.forTier(1, 2, 4))))); + super(name, new Properties().component(Contents.TYPE, new Contents(new ItemStackHandler(tier.forTier(1, 4, 9)), new ItemStackHandler(tier.forTier(1, 2, 4)), false, false))); this.speed = tier.forTier(20, 10, 5); } @@ -75,23 +74,29 @@ public class CraftingModuleItem extends ModuleItem { // process crafting ingredient requests if (!tile.craftIngredientRequests.isEmpty()) { network.startProfile("crafting_ingredients"); - var request = tile.craftIngredientRequests.peek(); + var request = tile.craftIngredientRequests.getFirst(); if (request.getLeft() == slot) { var lock = request.getRight(); var equalityTypes = ItemFilter.getEqualityTypes(tile); var dest = tile.getAvailableDestination(Direction.values(), lock.stack, true, true); if (dest != null) { - var requestRemain = network.requestExistingItem(lock.location, tile.getBlockPos(), dest.getLeft(), lock, dest.getRight(), equalityTypes); - network.resolveNetworkLock(lock); - tile.craftIngredientRequests.remove(); + var ensureItemOrder = module.get(Contents.TYPE).ensureItemOrder; + // if we're ensuring the correct item order and the item is already on the way, don't do anything yet + if (!ensureItemOrder || network.getPipeItemsOnTheWay(dest.getLeft()).findAny().isEmpty()) { + var requestRemain = network.requestExistingItem(lock.location, tile.getBlockPos(), dest.getLeft(), lock, dest.getRight(), equalityTypes); + network.resolveNetworkLock(lock); + tile.craftIngredientRequests.remove(request); - // if we couldn't fit all items into the destination, create another request for the rest - var remain = lock.stack.copy(); - remain.shrink(dest.getRight().getCount() - requestRemain.getCount()); - if (!remain.isEmpty()) { - var remainRequest = new NetworkLock(lock.location, remain); - tile.craftIngredientRequests.add(Pair.of(slot, remainRequest)); - network.createNetworkLock(remainRequest); + // if we couldn't fit all items into the destination, create another request for the rest + var remain = lock.stack.copy(); + remain.shrink(dest.getRight().getCount() - requestRemain.getCount()); + if (!remain.isEmpty()) { + var remainRequest = new NetworkLock(lock.location, remain); + // if we're ensuring item order, we need to insert the remaining request at the start so that it gets processed first + var index = ensureItemOrder ? 0 : tile.craftResultRequests.size(); + tile.craftIngredientRequests.add(index, Pair.of(slot, remainRequest)); + network.createNetworkLock(remainRequest); + } } } } @@ -181,16 +186,20 @@ public class CraftingModuleItem extends ModuleItem { var craftableCrafts = Mth.ceil(craftableAmount / (float) resultAmount); var toCraft = Math.min(craftableCrafts, requiredCrafts); - var input = module.get(Contents.TYPE).input; - for (var i = 0; i < input.getSlots(); i++) { - var in = input.getStackInSlot(i); - if (in.isEmpty()) - continue; - var copy = in.copy(); - copy.setCount(in.getCount() * toCraft); - var ret = ItemTerminalBlockEntity.requestItemLater(tile.getLevel(), tile.getBlockPos(), items, unavailableConsumer, copy, CraftingModuleItem.addDependency(dependencyChain, module), equalityTypes); - for (var lock : ret.getLeft()) - tile.craftIngredientRequests.add(Pair.of(slot, lock)); + var contents = module.get(Contents.TYPE); + // if we're ensuring item order, all items for a single recipe should be sent in order first before starting on the next one! + for (var c = contents.ensureItemOrder ? toCraft : 1; c > 0; c--) { + for (var i = 0; i < contents.input.getSlots(); i++) { + var in = contents.input.getStackInSlot(i); + if (in.isEmpty()) + continue; + var copy = in.copy(); + if (!contents.ensureItemOrder) + copy.setCount(in.getCount() * toCraft); + var ret = ItemTerminalBlockEntity.requestItemLater(tile.getLevel(), tile.getBlockPos(), items, unavailableConsumer, copy, CraftingModuleItem.addDependency(dependencyChain, module), equalityTypes); + for (var lock : ret.getLeft()) + tile.craftIngredientRequests.add(Pair.of(slot, lock)); + } } var remain = stack.copy(); @@ -203,6 +212,22 @@ public class CraftingModuleItem extends ModuleItem { return remain; } + @Override + public ItemStack store(ItemStack module, PipeBlockEntity tile, ItemStack stack, Direction direction) { + if (module.get(Contents.TYPE).insertSingles) { + var handler = tile.getItemHandler(direction); + if (handler != null) { + while (!stack.isEmpty()) { + var remain = ItemHandlerHelper.insertItem(handler, stack.copyWithCount(1), false); + if (!remain.isEmpty()) + break; + stack.shrink(1); + } + } + } + return stack; + } + private int getResultAmountPerCraft(ItemStack module, ItemStack stack, ItemEquality... equalityTypes) { var output = module.get(Contents.TYPE).output; var resultAmount = 0; @@ -220,11 +245,13 @@ public class CraftingModuleItem extends ModuleItem { return deps; } - public record Contents(ItemStackHandler input, ItemStackHandler output) { + public record Contents(ItemStackHandler input, ItemStackHandler output, boolean ensureItemOrder, boolean insertSingles) { public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( Utility.ITEM_STACK_HANDLER_CODEC.fieldOf("input").forGetter(d -> d.input), - Utility.ITEM_STACK_HANDLER_CODEC.fieldOf("output").forGetter(d -> d.output) + Utility.ITEM_STACK_HANDLER_CODEC.fieldOf("output").forGetter(d -> d.output), + Codec.BOOL.optionalFieldOf("ensure_item_order", false).forGetter(d -> d.ensureItemOrder), + Codec.BOOL.optionalFieldOf("insert_singles", false).forGetter(d -> d.insertSingles) ).apply(i, Contents::new)); public static final DataComponentType TYPE = DataComponentType.builder().persistent(Contents.CODEC).cacheEncoding().build(); diff --git a/src/main/resources/assets/prettypipes/lang/en_us.json b/src/main/resources/assets/prettypipes/lang/en_us.json index de8fddc..1c68cc3 100644 --- a/src/main/resources/assets/prettypipes/lang/en_us.json +++ b/src/main/resources/assets/prettypipes/lang/en_us.json @@ -66,6 +66,12 @@ "info.prettypipes.blacklist": "\u00A74D", "info.prettypipes.whitelist.description": "Items in filter slots are allowed", "info.prettypipes.blacklist.description": "Items in filter slots are disallowed", + "info.prettypipes.insert_singles_on": "\u00A72S", + "info.prettypipes.insert_singles_off": "\u00A74\u00A7mS", + "info.prettypipes.insert_singles.description": "Whether items should be inserted one at a time, rather than as a stack\nRecommended for use with the Crafter", + "info.prettypipes.ensure_item_order_on": "\u00A72O", + "info.prettypipes.ensure_item_order_off": "\u00A74\u00A7mO", + "info.prettypipes.ensure_item_order.description": "Whether the module should wait for items to be inserted in the order they appear in the input slots\nRecommended for use with the Crafter", "info.prettypipes.shift": "Hold Shift for info", "info.prettypipes.populate": "P", "info.prettypipes.populate.description": "Populate filter slots with items from adjacent inventories",