overhaul crafting module modes and add one that splits requests by crafts

closes #231
This commit is contained in:
Ell 2024-12-04 22:19:46 +01:00
parent ddc55003b6
commit 652b3538d2
5 changed files with 61 additions and 44 deletions

View file

@ -7,6 +7,7 @@ import de.ellpeck.prettypipes.misc.ItemFilter.IFilteredContainer;
import de.ellpeck.prettypipes.pipe.PipeBlockEntity; import de.ellpeck.prettypipes.pipe.PipeBlockEntity;
import de.ellpeck.prettypipes.pipe.containers.AbstractPipeContainer; import de.ellpeck.prettypipes.pipe.containers.AbstractPipeContainer;
import de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleContainer; import de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleContainer;
import de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleItem;
import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleContainer; import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleContainer;
import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleItem; import de.ellpeck.prettypipes.pipe.modules.modifier.FilterModifierModuleItem;
import de.ellpeck.prettypipes.pipe.modules.stacksize.StackSizeModuleItem; import de.ellpeck.prettypipes.pipe.modules.stacksize.StackSizeModuleItem;
@ -33,6 +34,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static de.ellpeck.prettypipes.misc.DirectionSelector.IDirectionContainer; import static de.ellpeck.prettypipes.misc.DirectionSelector.IDirectionContainer;
import static de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleItem.*;
public record PacketButton(BlockPos pos, int result, List<Integer> data) implements CustomPacketPayload { public record PacketButton(BlockPos pos, int result, List<Integer> data) implements CustomPacketPayload {
@ -95,15 +97,15 @@ public record PacketButton(BlockPos pos, int result, List<Integer> data) impleme
if (player.containerMenu instanceof IFilteredContainer filtered) if (player.containerMenu instanceof IFilteredContainer filtered)
filtered.getFilter().onButtonPacket(filtered, data.getFirst()); filtered.getFilter().onButtonPacket(filtered, data.getFirst());
}), }),
ENSURE_ITEM_ORDER_BUTTON((pos, data, player) -> { INSERTION_TYPE_BUTTON((pos, data, player) -> {
if (player.containerMenu instanceof CraftingModuleContainer container) { if (player.containerMenu instanceof CraftingModuleContainer container) {
container.ensureItemOrder = !container.ensureItemOrder; container.insertionType = InsertionType.values()[(container.insertionType.ordinal() + 1) % InsertionType.values().length];
container.modified = true; container.modified = true;
} }
}), }),
INSERT_SINGLES_BUTTON((pos, data, player) -> { INSERT_UNSTACKED_BUTTON((pos, data, player) -> {
if (player.containerMenu instanceof CraftingModuleContainer container) { if (player.containerMenu instanceof CraftingModuleContainer container) {
container.insertSingles = !container.insertSingles; container.insertUnstacked = !container.insertUnstacked;
container.modified = true; container.modified = true;
} }
}), }),

View file

@ -6,18 +6,15 @@ import de.ellpeck.prettypipes.pipe.containers.AbstractPipeContainer;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.items.ItemStackHandler; import net.neoforged.neoforge.items.ItemStackHandler;
import net.neoforged.neoforge.items.SlotItemHandler;
import org.jetbrains.annotations.NotNull;
public class CraftingModuleContainer extends AbstractPipeContainer<CraftingModuleItem> { public class CraftingModuleContainer extends AbstractPipeContainer<CraftingModuleItem> {
public ItemStackHandler input; public ItemStackHandler input;
public ItemStackHandler output; public ItemStackHandler output;
public boolean ensureItemOrder;
public boolean insertSingles;
public boolean emitRedstone; public boolean emitRedstone;
public CraftingModuleItem.InsertionType insertionType;
public boolean insertUnstacked;
public boolean modified; public boolean modified;
public CraftingModuleContainer(MenuType<?> type, int id, Player player, BlockPos pos, int moduleIndex) { public CraftingModuleContainer(MenuType<?> type, int id, Player player, BlockPos pos, int moduleIndex) {
@ -27,9 +24,9 @@ public class CraftingModuleContainer extends AbstractPipeContainer<CraftingModul
@Override @Override
protected void addSlots() { protected void addSlots() {
var contents = this.moduleStack.get(CraftingModuleItem.Contents.TYPE); var contents = this.moduleStack.get(CraftingModuleItem.Contents.TYPE);
this.ensureItemOrder = contents.ensureItemOrder();
this.insertSingles = contents.insertSingles();
this.emitRedstone = contents.emitRedstone(); this.emitRedstone = contents.emitRedstone();
this.insertionType = contents.insertionType();
this.insertUnstacked = contents.insertUnstacked();
this.input = Utility.copy(contents.input()); this.input = Utility.copy(contents.input());
for (var i = 0; i < this.input.getSlots(); i++) { for (var i = 0; i < this.input.getSlots(); i++) {
@ -59,7 +56,7 @@ public class CraftingModuleContainer extends AbstractPipeContainer<CraftingModul
public void removed(Player playerIn) { public void removed(Player playerIn) {
super.removed(playerIn); super.removed(playerIn);
if (this.modified) { if (this.modified) {
this.moduleStack.set(CraftingModuleItem.Contents.TYPE, new CraftingModuleItem.Contents(this.input, this.output, this.ensureItemOrder, this.insertSingles, this.emitRedstone)); this.moduleStack.set(CraftingModuleItem.Contents.TYPE, new CraftingModuleItem.Contents(this.input, this.output, this.emitRedstone, this.insertionType, this.insertUnstacked));
this.tile.setChanged(); this.tile.setChanged();
} }
} }

View file

@ -8,9 +8,11 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.Supplier; import java.util.function.Supplier;
public class CraftingModuleGui extends AbstractPipeGui<CraftingModuleContainer> { public class CraftingModuleGui extends AbstractPipeGui<CraftingModuleContainer> {
@ -28,26 +30,27 @@ public class CraftingModuleGui extends AbstractPipeGui<CraftingModuleContainer>
@Override @Override
protected void init() { protected void init() {
super.init(); super.init();
var cacheText = (Supplier<String>) () -> "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<String>) () -> "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());
var redstoneText = (Supplier<String>) () -> "info." + PrettyPipes.ID + ".emit_redstone_" + (this.menu.emitRedstone ? "on" : "off"); var redstoneText = (Supplier<String>) () -> "info." + PrettyPipes.ID + ".emit_redstone_" + (this.menu.emitRedstone ? "on" : "off");
this.addRenderableWidget(Button.builder(Component.translatable(redstoneText.get()), button -> { this.addRenderableWidget(Button.builder(Component.translatable(redstoneText.get()), button -> {
PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.EMIT_REDSTONE_BUTTON, List.of()); PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.EMIT_REDSTONE_BUTTON, List.of());
button.setMessage(Component.translatable(redstoneText.get())); button.setMessage(Component.translatable(redstoneText.get()));
}).bounds(this.leftPos + 7, this.topPos + 17 + 32 + 18 * 2 + 2, 20, 20).tooltip( }).bounds(this.leftPos + this.imageWidth - 7 - 20, this.topPos + 17 + 32 + 18 * 2 + 2, 20, 20).tooltip(
Tooltip.create(Component.translatable("info." + PrettyPipes.ID + ".emit_redstone.description").withStyle(ChatFormatting.GRAY))).build()); Tooltip.create(Component.translatable("info." + PrettyPipes.ID + ".emit_redstone.description").withStyle(ChatFormatting.GRAY))).build());
var unstackedText = (Supplier<String>) () -> "info." + PrettyPipes.ID + ".insert_unstacked_" + (this.menu.insertUnstacked ? "on" : "off");
this.addRenderableWidget(Button.builder(Component.translatable(unstackedText.get()), button -> {
PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.INSERT_UNSTACKED_BUTTON, List.of());
button.setMessage(Component.translatable(unstackedText.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_unstacked.description").withStyle(ChatFormatting.GRAY))).build());
var insertionTypeText = (Supplier<MutableComponent>) () -> Component.translatable(this.menu.insertionType.translationKey());
var insertionTypeTooltip = (Supplier<Tooltip>) () -> Tooltip.create(Component.translatable(this.menu.insertionType.translationKey() + ".description").withStyle(ChatFormatting.GRAY));
this.addRenderableWidget(Button.builder(insertionTypeText.get(), button -> {
PacketButton.sendAndExecute(this.menu.tile.getBlockPos(), PacketButton.ButtonResult.INSERTION_TYPE_BUTTON, List.of());
button.setMessage(insertionTypeText.get());
button.setTooltip(insertionTypeTooltip.get());
}).bounds(this.leftPos + 7, this.topPos + 17 + 32 + 18 * 2 + 2, 42, 20).tooltip(insertionTypeTooltip.get()).build());
} }
} }

View file

@ -3,6 +3,7 @@ package de.ellpeck.prettypipes.pipe.modules.craft;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import de.ellpeck.prettypipes.PrettyPipes;
import de.ellpeck.prettypipes.Registry; import de.ellpeck.prettypipes.Registry;
import de.ellpeck.prettypipes.Utility; import de.ellpeck.prettypipes.Utility;
import de.ellpeck.prettypipes.items.IModule; import de.ellpeck.prettypipes.items.IModule;
@ -36,7 +37,7 @@ public class CraftingModuleItem extends ModuleItem {
private final int speed; private final int speed;
public CraftingModuleItem(String name, ModuleTier tier) { 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)), false, false, false))); super(name, new Properties().component(Contents.TYPE, new Contents(new ItemStackHandler(tier.forTier(1, 4, 9)), new ItemStackHandler(tier.forTier(1, 2, 4)), false, InsertionType.ALL, false)));
this.speed = tier.forTier(20, 10, 5); this.speed = tier.forTier(20, 10, 5);
} }
@ -87,7 +88,7 @@ public class CraftingModuleItem extends ModuleItem {
var dest = tile.getAvailableDestination(Direction.values(), toRequest, true, true); var dest = tile.getAvailableDestination(Direction.values(), toRequest, true, true);
if (dest != null) { if (dest != null) {
// if we're ensuring the correct item order and the item is already on the way, don't do anything yet // if we're ensuring the correct item order and the item is already on the way, don't do anything yet
if (!module.get(Contents.TYPE).ensureItemOrder || craft.travelingIngredients.isEmpty()) { if (module.get(Contents.TYPE).insertionType != InsertionType.PER_ITEM || craft.travelingIngredients.isEmpty()) {
var equalityTypes = ItemFilter.getEqualityTypes(tile); var equalityTypes = ItemFilter.getEqualityTypes(tile);
var requested = ingredient.map(l -> { var requested = ingredient.map(l -> {
// we can ignore the return value here since we're using a lock, so we know that the item is already waiting for us there // we can ignore the return value here since we're using a lock, so we know that the item is already waiting for us there
@ -217,15 +218,15 @@ public class CraftingModuleItem extends ModuleItem {
var leftOfRequest = stack.getCount(); var leftOfRequest = stack.getCount();
var allCrafts = new ArrayList<ActiveCraft>(); var allCrafts = new ArrayList<ActiveCraft>();
// if we're ensuring item order, all items for a single recipe should be sent in order first before starting on the next one! // if we're inserting individual items or per craft, we need to split up the request into multiple crafts
for (var c = contents.ensureItemOrder ? toCraft : 1; c > 0; c--) { for (var c = contents.insertionType != InsertionType.ALL ? toCraft : 1; c > 0; c--) {
var toRequest = new ArrayList<Either<NetworkLock, ItemStack>>(); var toRequest = new ArrayList<Either<NetworkLock, ItemStack>>();
for (var i = 0; i < contents.input.getSlots(); i++) { for (var i = 0; i < contents.input.getSlots(); i++) {
var in = contents.input.getStackInSlot(i); var in = contents.input.getStackInSlot(i);
if (in.isEmpty()) if (in.isEmpty())
continue; continue;
var request = in.copy(); var request = in.copy();
if (!contents.ensureItemOrder) if (contents.insertionType == InsertionType.ALL)
request.setCount(in.getCount() * toCraft); request.setCount(in.getCount() * toCraft);
var ret = network.requestLocksAndStartCrafting(tile.getBlockPos(), items, unavailableConsumer, request, CraftingModuleItem.addDependency(dependencyChain, module), equalityTypes); var ret = network.requestLocksAndStartCrafting(tile.getBlockPos(), items, unavailableConsumer, request, CraftingModuleItem.addDependency(dependencyChain, module), equalityTypes);
for (var lock : ret.getLeft()) for (var lock : ret.getLeft())
@ -243,7 +244,7 @@ public class CraftingModuleItem extends ModuleItem {
allCrafts.add(dep); allCrafts.add(dep);
} }
} }
var crafted = contents.ensureItemOrder ? resultAmount : resultAmount * toCraft; var crafted = contents.insertionType != InsertionType.ALL ? resultAmount : resultAmount * toCraft;
// items we started craft dependencies for are ones that will be sent to us (so we're waiting for them immediately!) // items we started craft dependencies for are ones that will be sent to us (so we're waiting for them immediately!)
var activeCraft = new ActiveCraft(tile.getBlockPos(), slot, toRequest, new ArrayList<>(), destPipe, stack.copyWithCount(Math.min(crafted, leftOfRequest))); var activeCraft = new ActiveCraft(tile.getBlockPos(), slot, toRequest, new ArrayList<>(), destPipe, stack.copyWithCount(Math.min(crafted, leftOfRequest)));
tile.getActiveCrafts().add(activeCraft); tile.getActiveCrafts().add(activeCraft);
@ -272,7 +273,7 @@ public class CraftingModuleItem extends ModuleItem {
if (traveling.isEmpty()) if (traveling.isEmpty())
craft.travelingIngredients.remove(traveling); craft.travelingIngredients.remove(traveling);
if (contents.insertSingles) { if (contents.insertUnstacked) {
var handler = tile.getItemHandler(direction); var handler = tile.getItemHandler(direction);
if (handler != null) { if (handler != null) {
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
@ -316,17 +317,28 @@ public class CraftingModuleItem extends ModuleItem {
return deps; return deps;
} }
public record Contents(ItemStackHandler input, ItemStackHandler output, boolean ensureItemOrder, boolean insertSingles, boolean emitRedstone) { public record Contents(ItemStackHandler input, ItemStackHandler output, boolean emitRedstone, InsertionType insertionType, boolean insertUnstacked) {
public static final Codec<Contents> CODEC = RecordCodecBuilder.create(i -> i.group( public static final Codec<Contents> CODEC = RecordCodecBuilder.create(i -> i.group(
Utility.ITEM_STACK_HANDLER_CODEC.fieldOf("input").forGetter(d -> d.input), 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("emit_redstone", false).forGetter(d -> d.emitRedstone),
Codec.BOOL.optionalFieldOf("insert_singles", false).forGetter(d -> d.insertSingles), Codec.STRING.xmap(InsertionType::valueOf, InsertionType::name).optionalFieldOf("insertion_type", InsertionType.ALL).forGetter(d -> d.insertionType),
Codec.BOOL.optionalFieldOf("emit_redstone", false).forGetter(d -> d.emitRedstone) Codec.BOOL.optionalFieldOf("insert_unstacked", false).forGetter(d -> d.insertUnstacked)
).apply(i, Contents::new)); ).apply(i, Contents::new));
public static final DataComponentType<Contents> TYPE = DataComponentType.<Contents>builder().persistent(Contents.CODEC).cacheEncoding().build(); public static final DataComponentType<Contents> TYPE = DataComponentType.<Contents>builder().persistent(Contents.CODEC).cacheEncoding().build();
} }
public enum InsertionType {
ALL,
PER_ITEM,
PER_CRAFT;
public String translationKey() {
return "info." + PrettyPipes.ID + ".insertion_type." + this.name().toLowerCase(Locale.ROOT);
}
}
} }

View file

@ -66,15 +66,18 @@
"info.prettypipes.blacklist": "\u00A74D", "info.prettypipes.blacklist": "\u00A74D",
"info.prettypipes.whitelist.description": "Items in filter slots are allowed", "info.prettypipes.whitelist.description": "Items in filter slots are allowed",
"info.prettypipes.blacklist.description": "Items in filter slots are disallowed", "info.prettypipes.blacklist.description": "Items in filter slots are disallowed",
"info.prettypipes.insert_singles_on": "\u00A72S", "info.prettypipes.insert_unstacked_on": "\u00A72O",
"info.prettypipes.insert_singles_off": "\u00A74\u00A7mS", "info.prettypipes.insert_unstacked_off": "\u00A74\u00A7mO",
"info.prettypipes.insert_singles.description": "Whether items should be inserted one at a time, rather than as a stack\n\u00A7oRecommended for use with the Crafter", "info.prettypipes.insert_unstacked.description": "Whether items should be inserted one at a time, rather than as a stack, causing them to be spread out in some containers\n\u00A7oRecommended 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\n\u00A7oRecommended for use with the Crafter",
"info.prettypipes.emit_redstone_on": "\u00A72R", "info.prettypipes.emit_redstone_on": "\u00A72R",
"info.prettypipes.emit_redstone_off": "\u00A74\u00A7mR", "info.prettypipes.emit_redstone_off": "\u00A74\u00A7mR",
"info.prettypipes.emit_redstone.description": "Whether a redstone signal should be emitted when all crafting items have arrived\nIf the module waits for items to be inserted in order, a redstone signal is emitted for each set of items required for a single craft\n\u00A7oRecommended for use with the Crafter", "info.prettypipes.emit_redstone.description": "Whether a redstone signal should be emitted when all crafting items have arrived\nIf the module is set to insert items individually or per craft, a redstone signal is emitted for each set of items required for a single crafting operation\n\u00A7oRecommended for use with the Crafter",
"info.prettypipes.insertion_type.all": "All",
"info.prettypipes.insertion_type.all.description": "Causes the module to request all items required for the entire crafting request at once, even if the request consists of multiple crafting operations",
"info.prettypipes.insertion_type.per_item": "Singles",
"info.prettypipes.insertion_type.per_item.description": "Causes the module to request items individually so that they will be inserted in the order they appear in the input slots\n\u00A7oRecommended for use with the Crafter",
"info.prettypipes.insertion_type.per_craft": "Crafts",
"info.prettypipes.insertion_type.per_craft.description": "Causes the module to request all items for a single crafting operation at once",
"info.prettypipes.shift": "Hold Shift for info", "info.prettypipes.shift": "Hold Shift for info",
"info.prettypipes.populate": "P", "info.prettypipes.populate": "P",
"info.prettypipes.populate.description": "Populate filter slots with items from adjacent inventories", "info.prettypipes.populate.description": "Populate filter slots with items from adjacent inventories",