diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java index 0d7ff36cbfa..1dae34def35 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java @@ -1,7 +1,11 @@ package com.gregtechceu.gtceu.integration.ae2.machine; import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; +import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.IFilteredHandler; import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability; import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.machine.MetaMachine; @@ -13,9 +17,14 @@ import com.gregtechceu.gtceu.api.machine.fancyconfigurator.FancyTankConfigurator; import com.gregtechceu.gtceu.api.machine.feature.IDataStickInteractable; import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; +import com.gregtechceu.gtceu.api.machine.trait.IRecipeHandlerTrait; +import com.gregtechceu.gtceu.api.machine.trait.MachineTraitType; import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableRecipeHandlerTrait; +import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerGroupDistinctness; import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; import com.gregtechceu.gtceu.api.recipe.ingredient.SizedIngredient; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; @@ -25,7 +34,6 @@ import com.gregtechceu.gtceu.common.item.behavior.IntCircuitBehaviour; import com.gregtechceu.gtceu.integration.ae2.gui.widget.AETextInputButtonWidget; import com.gregtechceu.gtceu.integration.ae2.gui.widget.slot.AEPatternViewSlotWidget; -import com.gregtechceu.gtceu.integration.ae2.machine.trait.InternalSlotRecipeHandler; import com.gregtechceu.gtceu.utils.GTMath; import com.gregtechceu.gtceu.utils.ItemStackHashStrategy; @@ -43,8 +51,10 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.material.Fluid; import net.minecraftforge.common.util.INBTSerializable; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidType; @@ -62,8 +72,6 @@ import appeng.crafting.pattern.EncodedPatternItem; import appeng.crafting.pattern.ProcessingPatternItem; import appeng.helpers.patternprovider.PatternContainer; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import it.unimi.dsi.fastutil.objects.*; import lombok.Getter; import lombok.Setter; @@ -74,6 +82,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import javax.annotation.ParametersAreNonnullByDefault; @@ -119,11 +128,10 @@ public void setItemDirect(int slotIndex, ItemStack stack) { @SaveField protected final NotifiableFluidTank shareTank; - @Getter @SaveField protected final InternalSlot[] internalInventory = new InternalSlot[MAX_PATTERN_COUNT]; - private final BiMap detailsSlotMap = HashBiMap.create(MAX_PATTERN_COUNT); + private final @Nullable IPatternDetails[] patternSlotDetails = new IPatternDetails[MAX_PATTERN_COUNT]; @SyncToClient @SaveField @@ -135,23 +143,30 @@ public void setItemDirect(int slotIndex, ItemStack stack) { private final Set proxies = new ObjectOpenHashSet<>(); private final Set proxyMachines = new ReferenceOpenHashSet<>(); - @Getter - protected final InternalSlotRecipeHandler internalRecipeHandler; - @Nullable protected TickableSubscription updateSubs; + private final List workerSlots = new ArrayList<>(); + private final List<@Nullable IPatternDetails> workerPatterns = new ArrayList<>(); + private final List workerItemHandlers = new ArrayList<>(); + private final List workerFluidHandlers = new ArrayList<>(); + private final List workerHandlerLists = new ArrayList<>(); + private final @UnmodifiableView List workerHandlersView = Collections.unmodifiableList(workerHandlerLists); + public MEPatternBufferPartMachine(BlockEntityCreationInfo info) { super(info, IO.IN); patternInventory.setOnContentsChanged(() -> getSyncDataHolder().markClientSyncFieldDirty("patternInventory")); this.patternInventory.setFilter(stack -> stack.getItem() instanceof ProcessingPatternItem); + for (int i = 0; i < this.internalInventory.length; i++) { this.internalInventory[i] = new InternalSlot(); } + getMainNode().addService(ICraftingProvider.class, this); + this.shareInventory = attachTrait(new NotifiableItemStackHandler(9, IO.IN, IO.NONE)); this.shareTank = attachTrait(new NotifiableFluidTank(9, 8 * FluidType.BUCKET_VOLUME, IO.IN, IO.NONE)); - this.internalRecipeHandler = new InternalSlotRecipeHandler(this, internalInventory); + addWorkerSlot(); } @Override @@ -159,19 +174,231 @@ public void onLoad() { super.onLoad(); if (!isRemote()) { for (int i = 0; i < patternInventory.getSlots(); i++) { - var pattern = patternInventory.getStackInSlot(i); - var patternDetails = PatternDetailsHelper.decodePattern(pattern, getLevel()); - if (patternDetails != null) { - this.detailsSlotMap.put(patternDetails, this.internalInventory[i]); - } + patternSlotDetails[i] = PatternDetailsHelper.decodePattern(patternInventory.getStackInSlot(i), + getLevel()); } needPatternSync = true; } } + private void addWorkerSlot() { + int idx = workerSlots.size(); + if (idx >= MAX_PATTERN_COUNT) { + return; + } + + InternalSlot slot = internalInventory[idx]; + workerSlots.add(slot); + workerPatterns.add(null); + + WorkerItemHandler itemH = attachTrait(new WorkerItemHandler(slot, idx)); + WorkerFluidHandler fluidH = attachTrait(new WorkerFluidHandler(slot, idx)); + workerItemHandlers.add(itemH); + workerFluidHandlers.add(fluidH); + + slot.setOnContentsChanged(() -> { + itemH.notifyListeners(); + fluidH.notifyListeners(); + }); + + workerHandlerLists.add(new WorkerRHL(itemH, fluidH)); + } + + private void removeLastWorkerSlot() { + if (workerSlots.size() <= 1) { + return; + } + + int last = workerSlots.size() - 1; + InternalSlot slot = workerSlots.get(last); + + slot.refund(); + slot.setOnContentsChanged(() -> {}); + + workerSlots.remove(last); + workerPatterns.remove(last); + workerItemHandlers.remove(last); + workerFluidHandlers.remove(last); + workerHandlerLists.remove(last); + } + + public int getWorkerSlotCount() { + return workerSlots.size(); + } + + public IRecipeHandlerTrait getWorkerItemHandler(int idx) { + return workerItemHandlers.get(idx); + } + + public IRecipeHandlerTrait getWorkerFluidHandler(int idx) { + return workerFluidHandlers.get(idx); + } + + static boolean couldSlotMatchContents(InternalSlot slot, Map, List> contents) { + List itemContents = contents.get(ItemRecipeCapability.CAP); + if (itemContents != null && !slot.isItemEmpty()) { + Set itemTypes = slot.getItemTypes(); + for (Object obj : itemContents) { + if (!(obj instanceof Ingredient ing) || ing.isEmpty()) { + continue; + } + for (ItemStack stack : ing.getItems()) { + if (itemTypes.contains(stack.getItem())) { + return true; + } + } + } + } + List fluidContents = contents.get(FluidRecipeCapability.CAP); + if (fluidContents != null && !slot.isFluidEmpty()) { + Set fluidTypes = slot.getFluidTypes(); + for (Object obj : fluidContents) { + if (!(obj instanceof FluidIngredient ing) || ing.isEmpty()) { + continue; + } + for (FluidStack stack : ing.getStacks()) { + if (fluidTypes.contains(stack.getFluid())) { + return true; + } + } + } + } + return false; + } + + class WorkerRHL extends RecipeHandlerList { + + private final int workerIdx; + + WorkerRHL(WorkerItemHandler itemH, WorkerFluidHandler fluidH) { + super(IO.IN); + this.workerIdx = itemH.workerIdx; + addHandlers(getCircuitInventory(), getShareInventory(), getShareTank(), itemH, fluidH); + setGroup(RecipeHandlerGroupDistinctness.BUS_DISTINCT); + } + + @Override + public Map, List> handleRecipe(IO io, GTRecipe recipe, + Map, List> contents, + boolean simulate) { + if (workerIdx >= workerSlots.size()) { + return contents; + } + + InternalSlot slot = workerSlots.get(workerIdx); + if (slot.isItemEmpty() && slot.isFluidEmpty()) { + return contents; + } + + if (!couldSlotMatchContents(slot, contents)) { + return contents; + } + + return super.handleRecipe(io, recipe, contents, simulate); + } + + @Override + public boolean isDistinct() { + return true; + } + + @Override + public void setDistinct(boolean ignored, boolean notify) {} + } + + @Getter + class WorkerItemHandler extends NotifiableRecipeHandlerTrait { + + static final MachineTraitType TYPE = new MachineTraitType<>(WorkerItemHandler.class); + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + private final InternalSlot slot; + final int workerIdx; + private final int priority; + private final int size = 64; + private final RecipeCapability capability = ItemRecipeCapability.CAP; + private final IO handlerIO = IO.IN; + private final boolean isDistinct = true; + + WorkerItemHandler(InternalSlot slot, int idx) { + super(); + this.slot = slot; + this.workerIdx = idx; + this.priority = IFilteredHandler.HIGH + idx + 1; + } + + @Override + public @Nullable List handleRecipeInner(IO io, GTRecipe recipe, List left, + boolean simulate) { + if (io != IO.IN || slot.isItemEmpty()) { + return left; + } + return slot.handleItemInternal(left, simulate); + } + + @Override + public List getContents() { + return new ArrayList<>(slot.getItems()); + } + + @Override + public double getTotalContentAmount() { + return slot.getItems().stream().mapToLong(ItemStack::getCount).sum(); + } + } + + @Getter + class WorkerFluidHandler extends NotifiableRecipeHandlerTrait { + + static final MachineTraitType TYPE = new MachineTraitType<>(WorkerFluidHandler.class); + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + private final InternalSlot slot; + final int workerIdx; + private final int priority; + private final int size = 64; + private final RecipeCapability capability = FluidRecipeCapability.CAP; + private final IO handlerIO = IO.IN; + private final boolean isDistinct = true; + + WorkerFluidHandler(InternalSlot slot, int idx) { + super(); + this.slot = slot; + this.workerIdx = idx; + this.priority = IFilteredHandler.HIGH + idx + 1; + } + + @Override + public @Nullable List handleRecipeInner(IO io, GTRecipe recipe, List left, + boolean simulate) { + if (io != IO.IN || slot.isFluidEmpty()) { + return left; + } + return slot.handleFluidInternal(left, simulate); + } + + @Override + public List getContents() { + return new ArrayList<>(slot.getFluids()); + } + + @Override + public double getTotalContentAmount() { + return slot.getFluids().stream().mapToLong(FluidStack::getAmount).sum(); + } + } + @Override - public List getRecipeHandlers() { - return internalRecipeHandler.getSlotHandlers(); + public @UnmodifiableView List getRecipeHandlers() { + return workerHandlersView; } @Override @@ -221,18 +448,20 @@ protected void update() { public void addProxy(MEPatternBufferProxyPartMachine proxy) { proxies.add(proxy.getBlockPos()); proxyMachines.add(proxy); + addWorkerSlot(); } public void removeProxy(MEPatternBufferProxyPartMachine proxy) { proxies.remove(proxy.getBlockPos()); proxyMachines.remove(proxy); + removeLastWorkerSlot(); } @UnmodifiableView public Set getProxies() { if (proxyMachines.size() != proxies.size()) { proxyMachines.clear(); - for (var pos : proxies) { + for (BlockPos pos : proxies) { if (MetaMachine.getMachine(getLevel(), pos) instanceof MEPatternBufferProxyPartMachine proxy) { proxyMachines.add(proxy); } @@ -243,8 +472,8 @@ public Set getProxies() { private void refundAll(ClickData clickData) { if (!clickData.isRemote) { - for (InternalSlot internalSlot : internalInventory) { - internalSlot.refund(); + for (InternalSlot slot : workerSlots) { + slot.refund(); } } } @@ -253,14 +482,18 @@ private void refundAll(ClickData clickData) { public void onPatternChange(int index) { if (isRemote()) return; - // remove old if applicable - var internalInv = internalInventory[index]; - var newPattern = patternInventory.getStackInSlot(index); - var newPatternDetails = PatternDetailsHelper.decodePattern(newPattern, getLevel()); - var oldPatternDetails = detailsSlotMap.inverse().get(internalInv); - detailsSlotMap.forcePut(newPatternDetails, internalInv); - if (oldPatternDetails != null && !oldPatternDetails.equals(newPatternDetails)) { - internalInv.refund(); + IPatternDetails oldPattern = patternSlotDetails[index]; + IPatternDetails newPatternDetails = PatternDetailsHelper.decodePattern(patternInventory.getStackInSlot(index), + getLevel()); + patternSlotDetails[index] = newPatternDetails; + if (oldPattern != null && !oldPattern.equals(newPatternDetails)) { + for (int i = 0; i < workerPatterns.size(); i++) { + if (oldPattern.equals(workerPatterns.get(i))) { + workerSlots.get(i).refund(); + workerPatterns.set(i, null); + break; + } + } } needPatternSync = true; @@ -330,20 +563,43 @@ public Widget createUIWidget() { @Override public List getAvailablePatterns() { - return detailsSlotMap.keySet().stream().toList(); + ArrayList result = new ArrayList<>(MAX_PATTERN_COUNT); + for (IPatternDetails p : patternSlotDetails) { + if (p != null) result.add(p); + } + return result; } @Override public boolean pushPattern(IPatternDetails patternDetails, KeyCounter[] inputHolder) { - if (!isFormed() || !getMainNode().isActive() || !detailsSlotMap.containsKey(patternDetails) || - !checkInput(inputHolder)) { + if (!isFormed() || !getMainNode().isActive() || !checkInput(inputHolder)) { return false; } - - var slot = detailsSlotMap.get(patternDetails); - if (slot != null) { - slot.pushPattern(patternDetails, inputHolder); - return true; + boolean knownPattern = false; + for (IPatternDetails p : patternSlotDetails) { + if (patternDetails.equals(p)) { + knownPattern = true; + break; + } + } + if (!knownPattern) return false; + + for (int i = 0; i < workerSlots.size(); i++) { + InternalSlot slot = workerSlots.get(i); + boolean empty = slot.isItemEmpty() && slot.isFluidEmpty(); + if (empty) workerPatterns.set(i, null); + IPatternDetails currentPattern = workerPatterns.get(i); + + if (currentPattern == null) { + if (!empty) continue; + workerPatterns.set(i, patternDetails); + slot.pushPattern(patternDetails, inputHolder); + return true; + } + if (currentPattern.equals(patternDetails)) { + slot.pushPattern(patternDetails, inputHolder); + return true; + } } return false; } @@ -433,7 +689,7 @@ public record BufferData(Object2LongMap items, Object2LongMap(ItemStackHashStrategy.comparingAllButCount()); var fluids = new Object2LongOpenHashMap(); - for (InternalSlot slot : internalInventory) { + for (InternalSlot slot : workerSlots) { slot.itemInventory.object2LongEntrySet().fastForEach(e -> items.addTo(e.getKey(), e.getLongValue())); slot.fluidInventory.object2LongEntrySet().fastForEach(e -> fluids.addTo(e.getKey(), e.getLongValue())); } @@ -451,6 +707,8 @@ public class InternalSlot implements INBTSerializable { private final Object2LongOpenHashMap fluidInventory = new Object2LongOpenHashMap<>(); private @Nullable List itemStacks = null; private @Nullable List fluidStacks = null; + private @Nullable Set cachedItemTypes = null; + private @Nullable Set cachedFluidTypes = null; public InternalSlot() {} @@ -462,9 +720,27 @@ public boolean isFluidEmpty() { return fluidInventory.isEmpty(); } + public Set getItemTypes() { + if (cachedItemTypes == null) { + cachedItemTypes = new ReferenceOpenHashSet<>(itemInventory.size()); + itemInventory.keySet().forEach(s -> cachedItemTypes.add(s.getItem())); + } + return cachedItemTypes; + } + + public Set getFluidTypes() { + if (cachedFluidTypes == null) { + cachedFluidTypes = new ReferenceOpenHashSet<>(fluidInventory.size()); + fluidInventory.keySet().forEach(s -> cachedFluidTypes.add(s.getFluid())); + } + return cachedFluidTypes; + } + public void onContentsChanged() { itemStacks = null; fluidStacks = null; + cachedItemTypes = null; + cachedFluidTypes = null; onContentsChanged.run(); } diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferProxyPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferProxyPartMachine.java index aefb82537cd..3297a879161 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferProxyPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferProxyPartMachine.java @@ -46,7 +46,7 @@ public class MEPatternBufferProxyPartMachine extends TieredIOPartMachine impleme public MEPatternBufferProxyPartMachine(BlockEntityCreationInfo info) { super(info, GTValues.LuV, IO.IN); - proxySlotRecipeHandler = new ProxySlotRecipeHandler(this, MEPatternBufferPartMachine.MAX_PATTERN_COUNT); + proxySlotRecipeHandler = new ProxySlotRecipeHandler(this); } @Override @@ -57,6 +57,13 @@ public void onLoad() { @Override public List getRecipeHandlers() { + var buf = getBuffer(); + if (buf != null) { + proxySlotRecipeHandler.syncHandlerCount(buf.getWorkerSlotCount()); + proxySlotRecipeHandler.updateProxy(buf); + } else { + proxySlotRecipeHandler.clearProxy(); + } return proxySlotRecipeHandler.getProxySlotHandlers(); } diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/ProxySlotRecipeHandler.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/ProxySlotRecipeHandler.java index 5e5fdfa0bdc..b7d57ba0ee0 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/ProxySlotRecipeHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/ProxySlotRecipeHandler.java @@ -6,7 +6,6 @@ import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; import com.gregtechceu.gtceu.integration.ae2.machine.MEPatternBufferPartMachine; import com.gregtechceu.gtceu.integration.ae2.machine.MEPatternBufferProxyPartMachine; -import com.gregtechceu.gtceu.integration.ae2.machine.trait.InternalSlotRecipeHandler.SlotRHL; import com.gregtechceu.gtceu.utils.ISubscription; import net.minecraft.world.item.crafting.Ingredient; @@ -22,20 +21,29 @@ public final class ProxySlotRecipeHandler { @Getter private final List proxySlotHandlers; + private final MEPatternBufferProxyPartMachine machine; - public ProxySlotRecipeHandler(MEPatternBufferProxyPartMachine machine, int slots) { - proxySlotHandlers = new ArrayList<>(slots); - for (int i = 0; i < slots; ++i) { + public ProxySlotRecipeHandler(MEPatternBufferProxyPartMachine machine) { + this.machine = machine; + this.proxySlotHandlers = new ArrayList<>(); + } + + public void syncHandlerCount(int target) { + while (proxySlotHandlers.size() < target) { proxySlotHandlers.add(new ProxyRHL(machine)); } + while (proxySlotHandlers.size() > target) { + var removed = (ProxyRHL) proxySlotHandlers.remove(proxySlotHandlers.size() - 1); + removed.clearBuffer(); + } } public void updateProxy(MEPatternBufferPartMachine patternBuffer) { - var slotHandlers = patternBuffer.getInternalRecipeHandler().getSlotHandlers(); + syncHandlerCount(patternBuffer.getWorkerSlotCount()); for (int i = 0; i < proxySlotHandlers.size(); ++i) { ProxyRHL proxyRHL = (ProxyRHL) proxySlotHandlers.get(i); - SlotRHL slotRHL = (SlotRHL) slotHandlers.get(i); - proxyRHL.setBuffer(patternBuffer, slotRHL); + proxyRHL.setBuffer(patternBuffer, patternBuffer.getWorkerItemHandler(i), + patternBuffer.getWorkerFluidHandler(i)); } } @@ -64,12 +72,14 @@ public ProxyRHL(MEPatternBufferProxyPartMachine machine) { this.setGroup(RecipeHandlerGroupDistinctness.BUS_DISTINCT); } - public void setBuffer(MEPatternBufferPartMachine buffer, SlotRHL slotRHL) { + public void setBuffer(MEPatternBufferPartMachine buffer, + IRecipeHandlerTrait itemHandler, + IRecipeHandlerTrait fluidHandler) { circuit.setProxy(buffer.getCircuitInventory()); sharedItem.setProxy(buffer.getShareInventory()); sharedFluid.setProxy(buffer.getShareTank()); - slotItem.setProxy(slotRHL.getItemRecipeHandler()); - slotFluid.setProxy(slotRHL.getFluidRecipeHandler()); + slotItem.setProxy(itemHandler); + slotFluid.setProxy(fluidHandler); } public void clearBuffer() {