From b3e129e03c942ed24daca07b79a1ee7c6ad19ac9 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:18:57 +0800 Subject: [PATCH] =?UTF-8?q?ME=E6=8E=A5=E5=8F=A3=E4=B8=AD=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=82=B9=E5=87=BB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/init/ModNetwork.java | 6 + .../AbstractContainerScreenAccessor.java | 2 + .../ae2/client/gui/InterfaceScreenMixin.java | 137 +++++++++++++++++- .../InterfaceAdjustConfigAmountC2SPacket.java | 116 +++++++++++++++ 4 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 2f4eab5..e281d81 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -97,6 +97,12 @@ public class ModNetwork { .consumerNetworkThread(ToggleEntityTickerC2SPacket::handle) .add(); + CHANNEL.messageBuilder(InterfaceAdjustConfigAmountC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(InterfaceAdjustConfigAmountC2SPacket::encode) + .decoder(InterfaceAdjustConfigAmountC2SPacket::decode) + .consumerNetworkThread(InterfaceAdjustConfigAmountC2SPacket::handle) + .add(); + CHANNEL.messageBuilder(AdvancedBlockingSyncS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT) .encoder(AdvancedBlockingSyncS2CPacket::encode) .decoder(AdvancedBlockingSyncS2CPacket::decode) diff --git a/src/main/java/com/extendedae_plus/mixin/accessor/AbstractContainerScreenAccessor.java b/src/main/java/com/extendedae_plus/mixin/accessor/AbstractContainerScreenAccessor.java index 27b50a0..ea1eef0 100644 --- a/src/main/java/com/extendedae_plus/mixin/accessor/AbstractContainerScreenAccessor.java +++ b/src/main/java/com/extendedae_plus/mixin/accessor/AbstractContainerScreenAccessor.java @@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.accessor; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @@ -11,4 +12,5 @@ public interface AbstractContainerScreenAccessor { @Unique private int eap$lastTopPos = -1; @Unique private int eap$lastImageWidth = -1; @Unique private int eap$lastImageHeight = -1; + // 记录最近一次在 CONFIG 槽位区域内悬停到的配置槽索引, + // 以便当鼠标移到按钮上导致 hoveredSlot 为空时仍能进行操作。 + @Unique private int eap$lastConfigIndex = -1; @Inject(method = "init", at = @At("TAIL")) private void eap$addScaleButtons(CallbackInfo ci) { @@ -45,42 +52,42 @@ public abstract class InterfaceScreenMixin { // 避免重复创建 if (eap$x2Button == null) { eap$x2Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(false, 2); }, NewIcon.MULTIPLY2); eap$x2Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.multiply2"))); eap$x2Button.setVisibility(true); } if (eap$divideBy2Button == null) { eap$divideBy2Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(true, 2); }, NewIcon.DIVIDE2); eap$divideBy2Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.divide2"))); eap$divideBy2Button.setVisibility(true); } if (eap$x5Button == null) { eap$x5Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(false, 5); }, NewIcon.MULTIPLY5); eap$x5Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.multiply5"))); eap$x5Button.setVisibility(true); } if (eap$divideBy5Button == null) { eap$divideBy5Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(true, 5); }, NewIcon.DIVIDE5); eap$divideBy5Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.divide5"))); eap$divideBy5Button.setVisibility(true); } if (eap$x10Button == null) { eap$x10Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(false, 10); }, NewIcon.MULTIPLY10); eap$x10Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.multiply10"))); eap$x10Button.setVisibility(true); } if (eap$divideBy10Button == null) { eap$divideBy10Button = new ActionEPPButton((b) -> { - // 点击逻辑留空 + eap$sendAdjustForAllConfigs(true, 10); }, NewIcon.DIVIDE10); eap$divideBy10Button.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.divide10"))); eap$divideBy10Button.setVisibility(true); @@ -156,6 +163,78 @@ public abstract class InterfaceScreenMixin { eap$relayoutButtons(); try { LogUtils.getLogger().info("[EAP][InterfaceMixin] relayout due to bounds change: left={}, top={}, w={}, h={}", curLeft, curTop, curImgW, curImgH); } catch (Throwable ignored) {} } + // 每帧根据 hoveredSlot 刷新最近一次的配置槽索引 + eap$updateLastConfigFromHover(); + } + + @Unique + private void eap$sendAdjustForHoveredConfig(boolean divide, int factor) { + try { + // 仅在 InterfaceScreen 中生效 + if (!(((Object) this) instanceof InterfaceScreen)) { + return; + } + // 获取悬停槽位 + Slot hovered = ((AbstractContainerScreenAccessor) (Object) this).eap$getHoveredSlot(); + // 获取菜单与配置槽列表 + var screen = (AEBaseScreen) (Object) this; + var menu = screen.getMenu(); + if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { + return; + } + var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); + if (configSlots == null || configSlots.isEmpty()) { + return; + } + + // 优先根据 hoveredSlot 解析索引;若 hovered 为空,则回退使用最近一次的配置槽索引 + Integer slotFieldObj = null; + if (hovered != null) { + for (var s : configSlots) { + if (s == hovered) { + // 反射读取 AppEngSlot#slot + try { + var f = s.getClass().getDeclaredField("slot"); + f.setAccessible(true); + Object v = f.get(s); + if (v instanceof Integer i) { + slotFieldObj = i; + } + } catch (Throwable ignored) {} + if (slotFieldObj == null) { + // 回退:使用列表位置当作索引 + slotFieldObj = configSlots.indexOf(s); + } + break; + } + } + } + int slotField = -1; + if (slotFieldObj != null) { + slotField = slotFieldObj; + } else if (eap$lastConfigIndex >= 0 && eap$lastConfigIndex < configSlots.size()) { + slotField = eap$lastConfigIndex; + try { LogUtils.getLogger().info("[EAP][InterfaceMixin] Using last hovered config index: {}", slotField); } catch (Throwable ignored) {} + } + if (slotField < 0) { + try { LogUtils.getLogger().info("[EAP][InterfaceMixin] No hovered slot and no last config index; ignoring adjust."); } catch (Throwable ignored) {} + return; + } + + // 发送到服务端 + ModNetwork.CHANNEL.sendToServer(new InterfaceAdjustConfigAmountC2SPacket(slotField, divide, factor)); + } catch (Throwable ignored) {} + } + + @Unique + private void eap$sendAdjustForAllConfigs(boolean divide, int factor) { + try { + if (!(((Object) this) instanceof InterfaceScreen)) { + return; + } + // 直接发送 -1 表示对所有 CONFIG 槽生效 + ModNetwork.CHANNEL.sendToServer(new InterfaceAdjustConfigAmountC2SPacket(-1, divide, factor)); + } catch (Throwable ignored) {} } @Unique @@ -176,4 +255,50 @@ public abstract class InterfaceScreenMixin { if (eap$x10Button != null) { eap$x10Button.setX(bx); eap$x10Button.setY(by + spacing * 5); } } catch (Throwable ignored) {} } + + @Unique + private void eap$updateLastConfigFromHover() { + try { + if (!(((Object) this) instanceof InterfaceScreen)) { + return; + } + Slot hovered = ((AbstractContainerScreenAccessor) (Object) this).eap$getHoveredSlot(); + if (hovered == null) { + return; + } + var screen = (AEBaseScreen) (Object) this; + var menu = screen.getMenu(); + if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { + return; + } + var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); + if (configSlots == null || configSlots.isEmpty()) { + return; + } + // 在 CONFIG 槽列表中定位 hovered 对应的索引 + Integer idx = null; + for (var s : configSlots) { + if (s == hovered) { + try { + var f = s.getClass().getDeclaredField("slot"); + f.setAccessible(true); + Object v = f.get(s); + if (v instanceof Integer i) { + idx = i; + } + } catch (Throwable ignored) {} + if (idx == null) { + idx = configSlots.indexOf(s); + } + break; + } + } + if (idx != null && idx >= 0) { + if (eap$lastConfigIndex != idx) { + eap$lastConfigIndex = idx; + try { LogUtils.getLogger().info("[EAP][InterfaceMixin] lastConfigIndex updated: {}", eap$lastConfigIndex); } catch (Throwable ignored) {} + } + } + } catch (Throwable ignored) {} + } } diff --git a/src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java b/src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java new file mode 100644 index 0000000..8afc583 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java @@ -0,0 +1,116 @@ +package com.extendedae_plus.network; + +import appeng.menu.implementations.InterfaceMenu; +import appeng.menu.SlotSemantics; +import appeng.api.stacks.GenericStack; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * C2S:调整 ME 接口配置槽位(标记物品)的数量。 + * 支持按因子倍增或整除,且保持最小值为 1。 + */ +public class InterfaceAdjustConfigAmountC2SPacket { + private final int slotIndex; // 配置槽位索引(ConfigInventory 索引) + private final boolean divide; // true 表示做除法,否则做乘法 + private final int factor; // 因子:2/5/10 + + public InterfaceAdjustConfigAmountC2SPacket(int slotIndex, boolean divide, int factor) { + this.slotIndex = slotIndex; + this.divide = divide; + this.factor = factor; + } + + public static void encode(InterfaceAdjustConfigAmountC2SPacket msg, FriendlyByteBuf buf) { + buf.writeVarInt(msg.slotIndex); + buf.writeBoolean(msg.divide); + buf.writeVarInt(msg.factor); + } + + public static InterfaceAdjustConfigAmountC2SPacket decode(FriendlyByteBuf buf) { + int slot = buf.readVarInt(); + boolean div = buf.readBoolean(); + int factor = buf.readVarInt(); + return new InterfaceAdjustConfigAmountC2SPacket(slot, div, factor); + + } + + public static void handle(InterfaceAdjustConfigAmountC2SPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + ServerPlayer player = ctx.getSender(); + if (player == null) return; + if (!(player.containerMenu instanceof InterfaceMenu menu)) return; + + try { + var logic = menu.getHost().getInterfaceLogic(); + var config = logic.getConfig(); + if (msg.slotIndex == -1) { + // 对所有 CONFIG 槽位生效 + var configSlots = menu.getSlots(SlotSemantics.CONFIG); + if (configSlots == null || configSlots.isEmpty()) return; + for (var s : configSlots) { + int idx = -1; + try { + var f = s.getClass().getDeclaredField("slot"); + f.setAccessible(true); + Object v = f.get(s); + if (v instanceof Integer i) idx = i; + } catch (Throwable ignored) {} + if (idx < 0) { + idx = configSlots.indexOf(s); + } + + var st = config.getStack(idx); + if (st == null) continue; + + long current = st.amount(); + int factor = Math.max(1, msg.factor); + long next; + if (msg.divide) { + if (factor <= 1) continue; + if (current % factor != 0) continue; + next = current / factor; + if (next < 1) next = 1; + } else { + if (factor <= 1) continue; + next = current * factor; + if (next < 1) next = 1; + } + + GenericStack newStack = new GenericStack(st.what(), next); + config.setStack(idx, newStack); + } + } else { + var stack = config.getStack(msg.slotIndex); + if (stack == null) return; // 槽位无标记 + + long current = stack.amount(); + int factor = Math.max(1, msg.factor); + long next; + if (msg.divide) { + // 只能整除,且至少为 1 + if (factor <= 1) return; + if (current % factor != 0) return; // 不能整除则跳过 + next = current / factor; + if (next < 1) next = 1; + } else { + // 倍增,至少为 1 + if (factor <= 1) return; + next = current * factor; + if (next < 1) next = 1; + } + + // 应用 + GenericStack newStack = new GenericStack(stack.what(), next); + config.setStack(msg.slotIndex, newStack); + } + // 不需要显式保存:InterfaceLogic.config 的变更监听器会触发 host.saveChanges() 与计划更新 + } catch (Throwable ignored) {} + }); + ctx.setPacketHandled(true); + } +}