From ec441b9c907d0e81b444dc29b63ca78d17edd9cd Mon Sep 17 00:00:00 2001 From: George Paton Date: Tue, 21 Oct 2025 18:22:04 +1100 Subject: [PATCH] Integrate with new EMI registry in order to support item pulling via hotkey (comin atcha with the Forge 1.20.1 version!) --- .../appeng/core/sync/BasePacketHandler.java | 5 +- .../sync/packets/PullItemToPlayerPacket.java | 54 +++++++++++++++++++ .../modules/emi/AppEngEmiPlugin.java | 1 + .../emi/EmiAeBaseScreenStackPuller.java | 33 ++++++++++++ src/main/java/appeng/menu/AEBaseMenu.java | 30 +++++++++++ .../appeng/menu/me/common/MEStorageMenu.java | 37 +++++++++++++ 6 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/main/java/appeng/core/sync/packets/PullItemToPlayerPacket.java create mode 100644 src/main/java/appeng/integration/modules/emi/EmiAeBaseScreenStackPuller.java diff --git a/src/main/java/appeng/core/sync/BasePacketHandler.java b/src/main/java/appeng/core/sync/BasePacketHandler.java index 76b54a0b3e6..3f0afcc9682 100644 --- a/src/main/java/appeng/core/sync/BasePacketHandler.java +++ b/src/main/java/appeng/core/sync/BasePacketHandler.java @@ -50,6 +50,7 @@ import appeng.core.sync.packets.NetworkStatusPacket; import appeng.core.sync.packets.PartLeftClickPacket; import appeng.core.sync.packets.PatternAccessTerminalPacket; +import appeng.core.sync.packets.PullItemToPlayerPacket; import appeng.core.sync.packets.SwapSlotsPacket; import appeng.core.sync.packets.SwitchGuisPacket; @@ -110,7 +111,9 @@ public enum PacketTypes { HOTKEY(HotkeyPacket.class, HotkeyPacket::new), - CRAFTING_JOB_STATUS(CraftingJobStatusPacket.class, CraftingJobStatusPacket::new); + CRAFTING_JOB_STATUS(CraftingJobStatusPacket.class, CraftingJobStatusPacket::new), + + PULL_ITEM(PullItemToPlayerPacket.class, PullItemToPlayerPacket::new); private final Function factory; diff --git a/src/main/java/appeng/core/sync/packets/PullItemToPlayerPacket.java b/src/main/java/appeng/core/sync/packets/PullItemToPlayerPacket.java new file mode 100644 index 00000000000..84e89f1c47c --- /dev/null +++ b/src/main/java/appeng/core/sync/packets/PullItemToPlayerPacket.java @@ -0,0 +1,54 @@ +package appeng.core.sync.packets; + +import io.netty.buffer.Unpooled; + +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +import appeng.core.sync.BasePacket; +import appeng.menu.me.common.MEStorageMenu; + +public class PullItemToPlayerPacket extends BasePacket { + + private int containerId; + private NonNullList stacks; + private long toPull; + + public PullItemToPlayerPacket(FriendlyByteBuf stream) { + containerId = stream.readInt(); + stacks = NonNullList.withSize(stream.readInt(), ItemStack.EMPTY); + for (int i = 0; i < stacks.size(); i++) { + stacks.set(i, stream.readItem()); + } + toPull = stream.readVarLong(); + } + + public PullItemToPlayerPacket(int containerId, NonNullList stacks, long toPull) { + var data = new FriendlyByteBuf(Unpooled.buffer()); + + data.writeInt(this.getPacketID()); + + data.writeInt(containerId); + data.writeInt(stacks.size()); + for (ItemStack stack : stacks) { + data.writeItem(stack); + } + data.writeVarLong(toPull); + + configureWrite(data); + } + + @Override + public void serverPacketData(ServerPlayer player) { + if (player.containerMenu instanceof MEStorageMenu aeMenu) { + if (player.containerMenu.containerId != containerId) { + return; + } + + aeMenu.transferMatchingStacksToPlayer(stacks, toPull); + } + } + +} diff --git a/src/main/java/appeng/integration/modules/emi/AppEngEmiPlugin.java b/src/main/java/appeng/integration/modules/emi/AppEngEmiPlugin.java index e7bf05457c4..5956f529b59 100644 --- a/src/main/java/appeng/integration/modules/emi/AppEngEmiPlugin.java +++ b/src/main/java/appeng/integration/modules/emi/AppEngEmiPlugin.java @@ -61,6 +61,7 @@ public void register(EmiRegistry registry) { registry.addGenericExclusionArea(new EmiAeBaseScreenExclusionZones()); registry.addGenericStackProvider(new EmiAeBaseScreenStackProvider()); registry.addGenericDragDropHandler(new EmiAeBaseScreenDragDropHandler()); + registry.addGenericStackPuller(new EmiAeBaseScreenStackPuller()); // Additional Workstations registerWorkstations(registry); diff --git a/src/main/java/appeng/integration/modules/emi/EmiAeBaseScreenStackPuller.java b/src/main/java/appeng/integration/modules/emi/EmiAeBaseScreenStackPuller.java new file mode 100644 index 00000000000..6cfe107ddd4 --- /dev/null +++ b/src/main/java/appeng/integration/modules/emi/EmiAeBaseScreenStackPuller.java @@ -0,0 +1,33 @@ +package appeng.integration.modules.emi; + +import java.util.List; + +import appeng.core.sync.network.NetworkHandler; +import appeng.core.sync.packets.PullItemToPlayerPacket; +import appeng.menu.me.common.MEStorageMenu; +import dev.emi.emi.api.EmiStackPuller; +import net.minecraft.core.NonNullList; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.PacketDistributor; + +public class EmiAeBaseScreenStackPuller implements EmiStackPuller { + + @Override + public boolean pullStack(AbstractContainerMenu menu, List stacks, long toPull) { + if (!(menu instanceof MEStorageMenu aeMenu)) { + return false; + } + + NonNullList nonNullStacks = NonNullList.withSize(stacks.size(), ItemStack.EMPTY); + for (int i = 0; i < stacks.size(); i++) { + nonNullStacks.set(i, stacks.get(i)); + } + + NetworkHandler.instance() + .sendToServer(new PullItemToPlayerPacket(menu.containerId, nonNullStacks, toPull)); + + return true; + } + +} diff --git a/src/main/java/appeng/menu/AEBaseMenu.java b/src/main/java/appeng/menu/AEBaseMenu.java index 37bf31ad9cd..90600ca9ad8 100644 --- a/src/main/java/appeng/menu/AEBaseMenu.java +++ b/src/main/java/appeng/menu/AEBaseMenu.java @@ -52,6 +52,7 @@ import appeng.api.networking.security.IActionHost; import appeng.api.networking.security.IActionSource; import appeng.api.parts.IPart; +import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEKey; import appeng.api.stacks.GenericStack; import appeng.api.upgrades.IUpgradeInventory; @@ -525,6 +526,35 @@ private boolean x(Slot clickSlot, ItemStack tis, Slot d) { return false; } + protected List getQuickMoveDestinationSlots(ItemStack stackToMove) { + // Find potential destination slots + var destinationSlots = new ArrayList(); + for (var candidateSlot : this.slots) { + if (candidateSlot.container instanceof Inventory && candidateSlot.mayPlace(stackToMove)) { + destinationSlots.add(candidateSlot); + } + } + + // Sort such that we fill existing stacks first where possible + destinationSlots.sort((a, b) -> Boolean.compare(b.hasItem(), a.hasItem())); + return destinationSlots; + } + + protected int getPlaceableAmount(Slot s, AEItemKey what) { + if (!s.mayPlace(what.toStack())) { + return 0; + } + + var currentItem = s.getItem(); + if (currentItem.isEmpty()) { + return s.getMaxStackSize(what.getReadOnlyStack()); + } else if (what.matches(currentItem)) { + return Math.max(0, s.getMaxStackSize(currentItem) - currentItem.getCount()); + } else { + return 0; + } + } + @Override public boolean stillValid(Player PlayerEntity) { if (this.isValidMenu()) { diff --git a/src/main/java/appeng/menu/me/common/MEStorageMenu.java b/src/main/java/appeng/menu/me/common/MEStorageMenu.java index 0e8eeaedce5..02b3d9d7f81 100644 --- a/src/main/java/appeng/menu/me/common/MEStorageMenu.java +++ b/src/main/java/appeng/menu/me/common/MEStorageMenu.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable; +import net.minecraft.core.NonNullList; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; @@ -668,6 +669,42 @@ private boolean moveOneStackToPlayer(AEItemKey stack) { return true; } + public void transferMatchingStacksToPlayer(NonNullList stacks, long toPull) { + for (ItemStack stack : stacks) { + AEItemKey key = AEItemKey.of(stack); + + long potentialAmount = storage.extract(key, toPull, Actionable.SIMULATE, getActionSource()); + if (potentialAmount <= 0) { + continue; + } + + var destinationSlots = getQuickMoveDestinationSlots(key.toStack()); + + for (var destinationSlot : destinationSlots) { + var amount = Math.min(toPull, getPlaceableAmount(destinationSlot, key)); + if (amount <= 0) { + continue; + } + + var extracted = StorageHelper.poweredExtraction(powerSource, storage, key, amount, getActionSource()); + if (extracted == 0) { + break; // No items available, try next matching type + } + + var currentItem = destinationSlot.getItem(); + if (!currentItem.isEmpty()) { + destinationSlot.setByPlayer(currentItem.copyWithCount(currentItem.getCount() + (int) extracted)); + } else { + destinationSlot.setByPlayer(key.toStack((int) extracted)); + } + + toPull -= extracted; + } + + if (toPull <= 0) return; + } + } + @Nullable protected final AEKey getStackBySerial(long serial) { return updateHelper.getBySerial(serial);