ME接口中按钮实现点击逻辑

This commit is contained in:
GaLicn 2025-09-18 16:18:57 +08:00
parent 224fa2a37b
commit b3e129e03c
4 changed files with 255 additions and 6 deletions

View File

@ -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)

View File

@ -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<T extends AbstractContainerMenu
@Accessor("topPos") int eap$getTopPos();
@Accessor("imageWidth") int eap$getImageWidth();
@Accessor("imageHeight") int eap$getImageHeight();
@Accessor("hoveredSlot") Slot eap$getHoveredSlot();
}

View File

@ -4,6 +4,10 @@ import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.implementations.InterfaceScreen;
import appeng.menu.AEBaseMenu;
import com.extendedae_plus.NewIcon;
import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.InterfaceAdjustConfigAmountC2SPacket;
import appeng.menu.SlotSemantics;
import net.minecraft.world.inventory.Slot;
import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor;
import com.extendedae_plus.mixin.accessor.ScreenAccessor;
import com.glodblock.github.extendedae.client.button.ActionEPPButton;
@ -34,6 +38,9 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
@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<T extends AEBaseMenu> {
// 避免重复创建
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<T extends AEBaseMenu> {
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<T extends AEBaseMenu> {
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) {}
}
}

View File

@ -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<NetworkEvent.Context> 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);
}
}