diff --git a/src/main/java/de/ellpeck/prettypipes/Utility.java b/src/main/java/de/ellpeck/prettypipes/Utility.java index ad11f85..9dd5ab2 100644 --- a/src/main/java/de/ellpeck/prettypipes/Utility.java +++ b/src/main/java/de/ellpeck/prettypipes/Utility.java @@ -122,13 +122,23 @@ public final class Utility { return ItemStack.EMPTY; } - public static ListTag serializeAll(HolderLookup.Provider provider, Collection> items) { + public static ListTag serializeAll(Collection items, Function serializer) { var list = new ListTag(); - for (INBTSerializable item : items) - list.add(item.serializeNBT(provider)); + for (var item : items) + list.add(serializer.apply(item)); return list; } + public static List deserializeAll(ListTag list, Function deserializer) { + List items = new ArrayList<>(); + for (var i = 0; i < list.size(); i++) { + var item = deserializer.apply(list.getCompound(i)); + if (item != null) + items.add(item); + } + return items; + } + public static void sendBlockEntityToClients(BlockEntity tile) { var world = (ServerLevel) tile.getLevel(); var entities = world.getChunkSource().chunkMap.getPlayers(new ChunkPos(tile.getBlockPos()), false); @@ -137,16 +147,6 @@ public final class Utility { e.connection.send(packet); } - public static > List deserializeAll(ListTag list, Function supplier) { - List items = new ArrayList<>(); - for (var i = 0; i < list.size(); i++) { - var item = supplier.apply(list.getCompound(i)); - if (item != null) - items.add(item); - } - return items; - } - public static IItemHandler getBlockItemHandler(Level world, BlockPos pos, Direction direction) { var state = world.getBlockState(pos); var block = state.getBlock(); diff --git a/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java b/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java index 1f822f6..e851c9b 100644 --- a/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java +++ b/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java @@ -118,8 +118,8 @@ public class PipeNetwork extends SavedData implements GraphListener i.serializeNBT(provider))); + nbt.put("locks", Utility.serializeAll(this.networkLocks.values(), l -> l.serializeNBT(provider))); return nbt; } @@ -283,17 +283,16 @@ public class PipeNetwork extends SavedData implements GraphListener this.getPipe(c.getLeft())).distinct().iterator(); while (craftingPipes.hasNext()) { var pipe = craftingPipes.next(); - for (var request : pipe.craftResultRequests) { - var dest = request.getMiddle(); - var stack = request.getRight(); + for (var craft : pipe.activeCrafts) { + var data = craft.getRight(); // add up all the items that should go to the same location var existing = items.stream() - .filter(s -> s.getLeft().equals(dest) && ItemEquality.compareItems(s.getRight(), stack, equalityTypes)) + .filter(s -> s.getLeft().equals(data.resultDestPipe) && ItemEquality.compareItems(s.getRight(), data.resultStackRemain, equalityTypes)) .findFirst(); if (existing.isPresent()) { - existing.get().getRight().grow(stack.getCount()); + existing.get().getRight().grow(data.resultStackRemain.getCount()); } else { - items.add(Pair.of(dest, stack.copy())); + items.add(Pair.of(data.resultDestPipe, data.resultStackRemain.copy())); } } } diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlock.java b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlock.java index 0a0e769..741af77 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlock.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlock.java @@ -270,8 +270,8 @@ public class PipeBlock extends BaseEntityBlock implements SimpleWaterloggedBlock network.onPipeChanged(pos, state); if (worldIn.getBlockEntity(pos) instanceof PipeBlockEntity pipe) { pipe.getItems().clear(); - for (var locks : pipe.craftIngredientRequests) { - for (var lock : locks.getRight()) + for (var craft : pipe.activeCrafts) { + for (var lock : craft.getRight().ingredientsToRequest) network.resolveNetworkLock(lock); } } diff --git a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java index d39f402..e7aa24a 100644 --- a/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/pipe/PipeBlockEntity.java @@ -8,6 +8,7 @@ import de.ellpeck.prettypipes.misc.ItemFilter; import de.ellpeck.prettypipes.network.NetworkLock; import de.ellpeck.prettypipes.network.PipeNetwork; import de.ellpeck.prettypipes.pipe.containers.MainPipeContainer; +import de.ellpeck.prettypipes.pipe.modules.craft.CraftingModuleItem; import de.ellpeck.prettypipes.pressurizer.PressurizerBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -71,11 +72,7 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC PipeBlockEntity.this.setChanged(); } }; - // TODO instead of having these loose lists, it would be nice to have a "ModuleData" system that allows modules to store an object of custom data on the pipe - // crafting module slot, ingredient request network locks (one list for each recipe) - public final List>> craftIngredientRequests = new ArrayList<>(); - // crafting module slot, destination pipe for the result, result item - public final List> craftResultRequests = new ArrayList<>(); + public final List> activeCrafts = new ArrayList<>(); public PressurizerBlockEntity pressurizer; public BlockState cover; public int moduleDropCheck; @@ -102,25 +99,16 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC super.saveAdditional(compound, provider); compound.put("modules", this.modules.serializeNBT(provider)); compound.putInt("module_drop_check", this.moduleDropCheck); - var requests = new ListTag(); - for (var tuple : this.craftIngredientRequests) { - var nbt = new CompoundTag(); - nbt.putInt("module_slot", tuple.getLeft()); - nbt.put("locks", Utility.serializeAll(provider, tuple.getRight())); - requests.add(nbt); - } - compound.put("craft_requests", requests); if (this.cover != null) compound.put("cover", NbtUtils.writeBlockState(this.cover)); - var results = new ListTag(); - for (var triple : this.craftResultRequests) { - var nbt = new CompoundTag(); - nbt.putInt("module_slot", triple.getLeft()); - nbt.putLong("dest_pipe", triple.getMiddle().asLong()); - nbt.put("item", triple.getRight().save(provider)); - results.add(nbt); + var crafts = new ListTag(); + for (var craft : this.activeCrafts) { + var tag = new CompoundTag(); + tag.putInt("module_slot", craft.getLeft()); + tag.put("data", craft.getRight().serializeNBT(provider)); + crafts.add(tag); } - compound.put("craft_results", results); + compound.put("active_crafts", crafts); } @Override @@ -128,22 +116,11 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC this.modules.deserializeNBT(provider, compound.getCompound("modules")); this.moduleDropCheck = compound.getInt("module_drop_check"); this.cover = compound.contains("cover") ? NbtUtils.readBlockState(this.level != null ? this.level.holderLookup(Registries.BLOCK) : BuiltInRegistries.BLOCK.asLookup(), compound.getCompound("cover")) : null; - this.craftIngredientRequests.clear(); - var requests = compound.getList("craft_requests", Tag.TAG_COMPOUND); - for (var i = 0; i < requests.size(); i++) { - var nbt = requests.getCompound(i); - this.craftIngredientRequests.add(Pair.of( - nbt.getInt("module_slot"), - Utility.deserializeAll(nbt.getList("locks", Tag.TAG_COMPOUND), c -> new NetworkLock(provider, c)))); - } - this.craftResultRequests.clear(); - var results = compound.getList("craft_results", Tag.TAG_COMPOUND); - for (var i = 0; i < results.size(); i++) { - var nbt = results.getCompound(i); - this.craftResultRequests.add(Triple.of( - nbt.getInt("module_slot"), - BlockPos.of(nbt.getLong("dest_pipe")), - ItemStack.parseOptional(provider, nbt.getCompound("item")))); + this.activeCrafts.clear(); + var crafts = compound.getList("active_crafts", Tag.TAG_COMPOUND); + for (var i = 0; i < crafts.size(); i++) { + var tag = crafts.getCompound(i); + this.activeCrafts.add(Pair.of(tag.getInt("module_slot"), new CraftingModuleItem.ActiveCraft(provider, tag.getCompound("data")))); } super.loadAdditional(compound, provider); } @@ -152,7 +129,7 @@ public class PipeBlockEntity extends BlockEntity implements MenuProvider, IPipeC public CompoundTag getUpdateTag(HolderLookup.Provider provider) { // sync pipe items on load var nbt = this.saveWithoutMetadata(provider); - nbt.put("items", Utility.serializeAll(provider, this.getItems())); + nbt.put("items", Utility.serializeAll(this.getItems(), i -> i.serializeNBT(provider))); return nbt; } 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 6db4541..e8af9fb 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 @@ -7,6 +7,7 @@ import de.ellpeck.prettypipes.Utility; import de.ellpeck.prettypipes.items.IModule; import de.ellpeck.prettypipes.items.ModuleItem; import de.ellpeck.prettypipes.items.ModuleTier; +import de.ellpeck.prettypipes.misc.EquatableItemStack; import de.ellpeck.prettypipes.misc.ItemEquality; import de.ellpeck.prettypipes.misc.ItemFilter; import de.ellpeck.prettypipes.network.NetworkLock; @@ -17,16 +18,21 @@ import de.ellpeck.prettypipes.terminal.CraftingTerminalBlockEntity; import de.ellpeck.prettypipes.terminal.ItemTerminalBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentType; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import net.minecraft.util.Mth; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.util.INBTSerializable; 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 org.jetbrains.annotations.UnknownNullability; import java.util.*; import java.util.function.Consumer; @@ -71,72 +77,66 @@ public class CraftingModuleItem extends ModuleItem { return; var slot = tile.getModuleSlot(module); var network = PipeNetwork.get(tile.getLevel()); - // process crafting ingredient requests - if (!tile.craftIngredientRequests.isEmpty()) { - network.startProfile("crafting_ingredients"); - var request = tile.craftIngredientRequests.getFirst(); - if (request.getLeft() == slot) { - var locks = request.getRight(); - var lock = locks.getFirst(); - var equalityTypes = ItemFilter.getEqualityTypes(tile); - var dest = tile.getAvailableDestination(Direction.values(), lock.stack, true, true); - if (dest != null) { - 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); - locks.remove(lock); + if (!tile.activeCrafts.isEmpty()) { + var activeCraft = tile.activeCrafts.getFirst(); + if (activeCraft.getLeft() == slot) { + var craftData = activeCraft.getRight(); - // 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 : locks.size(); - locks.add(index, remainRequest); - network.createNetworkLock(remainRequest); - } + // process crafting ingredient requests + if (!craftData.ingredientsToRequest.isEmpty()) { + network.startProfile("crafting_ingredients"); + var lock = craftData.ingredientsToRequest.getFirst(); + var equalityTypes = ItemFilter.getEqualityTypes(tile); + var dest = tile.getAvailableDestination(Direction.values(), lock.stack, true, true); + if (dest != null) { + 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); + craftData.ingredientsToRequest.remove(lock); - if (locks.isEmpty()) - tile.craftIngredientRequests.remove(request); - } - } - } - network.endProfile(); - } - // pull requested crafting results from the network once they are stored - if (!tile.craftResultRequests.isEmpty()) { - network.startProfile("crafting_results"); - var items = network.getOrderedNetworkItems(tile.getBlockPos()); - var equalityTypes = ItemFilter.getEqualityTypes(tile); - for (var request : tile.craftResultRequests) { - if (request.getLeft() == slot) { - var remain = request.getRight().copy(); - var destPipe = network.getPipe(request.getMiddle()); - if (destPipe != null) { - var dest = destPipe.getAvailableDestinationOrConnectable(remain, true, true); - if (dest == null) - continue; - for (var item : items) { - var requestRemain = network.requestExistingItem(item, request.getMiddle(), dest.getLeft(), null, dest.getRight(), equalityTypes); + var traveling = lock.stack.copy(); + traveling.shrink(requestRemain.getCount()); + craftData.travelingIngredients.add(traveling); + + // 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()) - break; - } - if (remain.getCount() != request.getRight().getCount()) { - tile.craftResultRequests.remove(request); - // if we couldn't pull everything, log a new request - if (!remain.isEmpty()) - tile.craftResultRequests.add(Triple.of(slot, request.getMiddle(), remain)); - network.endProfile(); - return; + 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 : craftData.ingredientsToRequest.size(); + craftData.ingredientsToRequest.add(index, remainRequest); + network.createNetworkLock(remainRequest); + } } } + network.endProfile(); + } + + // pull requested crafting results from the network once they are stored + if (!craftData.resultStackRemain.isEmpty()) { + network.startProfile("crafting_results"); + var items = network.getOrderedNetworkItems(tile.getBlockPos()); + var equalityTypes = ItemFilter.getEqualityTypes(tile); + var destPipe = network.getPipe(craftData.resultDestPipe); + if (destPipe != null) { + var dest = destPipe.getAvailableDestinationOrConnectable(craftData.resultStackRemain, true, true); + if (dest != null) { + for (var item : items) { + var requestRemain = network.requestExistingItem(item, craftData.resultDestPipe, dest.getLeft(), null, dest.getRight(), equalityTypes); + craftData.resultStackRemain.shrink(dest.getRight().getCount() - requestRemain.getCount()); + if (craftData.resultStackRemain.isEmpty()) { + tile.activeCrafts.remove(activeCraft); + break; + } + } + } + } + network.endProfile(); } } - network.endProfile(); } } @@ -205,28 +205,36 @@ public class CraftingModuleItem extends ModuleItem { locks.addAll(ret.getLeft()); } } - tile.craftIngredientRequests.add(Pair.of(slot, locks)); var remain = stack.copy(); remain.shrink(resultAmount * toCraft); - var result = stack.copy(); result.shrink(remain.getCount()); - tile.craftResultRequests.add(Triple.of(slot, destPipe, result)); + + var activeCraft = new ActiveCraft(locks, new ArrayList<>(), destPipe, result); + tile.activeCrafts.add(Pair.of(slot, activeCraft)); 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); + var slot = tile.getModuleSlot(module); + var equalityTypes = ItemFilter.getEqualityTypes(tile); + var matchingCraft = tile.activeCrafts.stream() + .filter(c -> c.getLeft() == slot && c.getRight().isMatchingIngredient(stack, equalityTypes)) + .findAny().orElse(null); + if (matchingCraft != null) { + matchingCraft.getRight().travelingIngredients.removeIf(s -> ItemEquality.compareItems(stack, s, equalityTypes)); + 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); + } } } } @@ -262,4 +270,50 @@ public class CraftingModuleItem extends ModuleItem { } + public static class ActiveCraft implements INBTSerializable { + + public List ingredientsToRequest; + public List travelingIngredients; + public BlockPos resultDestPipe; + public ItemStack resultStackRemain; + + public ActiveCraft(List ingredientsToRequest, List travelingIngredients, BlockPos resultDestPipe, ItemStack resultStackRemain) { + this.ingredientsToRequest = ingredientsToRequest; + this.travelingIngredients = travelingIngredients; + this.resultDestPipe = resultDestPipe; + this.resultStackRemain = resultStackRemain; + } + + public ActiveCraft(HolderLookup.Provider provider, CompoundTag tag) { + this.deserializeNBT(provider, tag); + } + + @Override + public @UnknownNullability CompoundTag serializeNBT(HolderLookup.Provider provider) { + var ret = new CompoundTag(); + ret.put("ingredients_to_request", Utility.serializeAll(this.ingredientsToRequest, n -> n.serializeNBT(provider))); + ret.put("traveling_ingredients", Utility.serializeAll(this.travelingIngredients, s -> (CompoundTag) s.save(provider, new CompoundTag()))); + ret.putLong("result_dest_pipe", this.resultDestPipe.asLong()); + ret.put("result_stack_remain", this.resultStackRemain.saveOptional(provider)); + return ret; + } + + @Override + public void deserializeNBT(HolderLookup.Provider provider, CompoundTag nbt) { + this.ingredientsToRequest = Utility.deserializeAll(nbt.getList("ingredients_to_request", Tag.TAG_COMPOUND), t -> new NetworkLock(provider, t)); + this.travelingIngredients = Utility.deserializeAll(nbt.getList("traveling_ingredients", Tag.TAG_COMPOUND), t -> ItemStack.parse(provider, t).orElseThrow()); + this.resultDestPipe = BlockPos.of(nbt.getLong("result_dest_pipe")); + this.resultStackRemain = ItemStack.parseOptional(provider, nbt.getCompound("result_stack_remain")); + } + + public boolean isMatchingIngredient(ItemStack stack, ItemEquality... equalityTypes) { + for (var traveling : this.travelingIngredients) { + if (ItemEquality.compareItems(stack, traveling, equalityTypes)) + return true; + } + return false; + } + + } + } diff --git a/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalBlockEntity.java b/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalBlockEntity.java index 92f472e..55330f1 100644 --- a/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalBlockEntity.java +++ b/src/main/java/de/ellpeck/prettypipes/terminal/ItemTerminalBlockEntity.java @@ -217,12 +217,11 @@ public class ItemTerminalBlockEntity extends BlockEntity implements IPipeConnect for (var craftable : network.getAllCraftables(pipe.getBlockPos())) { var otherPipe = network.getPipe(craftable.getLeft()); if (otherPipe != null) { - for (var locks : otherPipe.craftIngredientRequests) { - for (var lock : locks.getRight()) + for (var craft : otherPipe.activeCrafts) { + for (var lock : craft.getRight().ingredientsToRequest) network.resolveNetworkLock(lock); } - otherPipe.craftIngredientRequests.clear(); - otherPipe.craftResultRequests.clear(); + otherPipe.activeCrafts.clear(); } } var lookingPlayers = this.getLookingPlayers(); @@ -234,7 +233,7 @@ public class ItemTerminalBlockEntity extends BlockEntity implements IPipeConnect public void saveAdditional(CompoundTag compound, HolderLookup.Provider pRegistries) { super.saveAdditional(compound, pRegistries); compound.put("items", this.items.serializeNBT(pRegistries)); - compound.put("requests", Utility.serializeAll(pRegistries, this.existingRequests)); + compound.put("requests", Utility.serializeAll(this.existingRequests, i -> i.serializeNBT(pRegistries))); } @Override