ME接口倍增按钮添加

This commit is contained in:
GaLicn 2025-09-18 23:34:16 +08:00
parent 98e97f1103
commit 48709c6ce8
6 changed files with 407 additions and 0 deletions

View File

@ -25,6 +25,15 @@ public final class ModCapabilities {
(be, ctx) -> (IInWorldGridNodeHost) be (be, ctx) -> (IInWorldGridNodeHost) be
); );
// 并行处理单元CraftingUnitBlock -> CraftingBlockEntity 实现了 IInWorldGridNodeHost
// 未注册该能力时AE 电缆通过 GridHelper.getNodeHost(...) 无法发现节点导致节点不入网
// 方块虽然能成型并提供并行度 getMainNode().isOnline() false从而显示设备离线
event.registerBlockEntity(
AECapabilities.IN_WORLD_GRID_NODE_HOST,
ModBlockEntities.EPLUS_CRAFTING_UNIT_BE.get(),
(be, ctx) -> (IInWorldGridNodeHost) be
);
// 如果还有其他实现了 IInWorldGridNodeHost 的方块实体也在这里一并注册 // 如果还有其他实现了 IInWorldGridNodeHost 的方块实体也在这里一并注册
// event.registerBlockEntity(AECapabilities.IN_WORLD_GRID_NODE_HOST, ModBlockEntities.NETWORK_PATTERN_CONTROLLER_BE.get(), (be, ctx) -> (IInWorldGridNodeHost) be); // event.registerBlockEntity(AECapabilities.IN_WORLD_GRID_NODE_HOST, ModBlockEntities.NETWORK_PATTERN_CONTROLLER_BE.get(), (be, ctx) -> (IInWorldGridNodeHost) be);
} }

View File

@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.accessor;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
@ -11,4 +12,5 @@ public interface AbstractContainerScreenAccessor<T extends AbstractContainerMenu
@Accessor("topPos") int eap$getTopPos(); @Accessor("topPos") int eap$getTopPos();
@Accessor("imageWidth") int eap$getImageWidth(); @Accessor("imageWidth") int eap$getImageWidth();
@Accessor("imageHeight") int eap$getImageHeight(); @Accessor("imageHeight") int eap$getImageHeight();
@Accessor("hoveredSlot") Slot eap$getHoveredSlot();
} }

View File

@ -0,0 +1,280 @@
package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.implementations.InterfaceScreen;
import appeng.menu.AEBaseMenu;
import appeng.menu.SlotSemantics;
import com.extendedae_plus.NewIcon;
import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor;
import com.extendedae_plus.mixin.accessor.ScreenAccessor;
import com.extendedae_plus.network.InterfaceAdjustConfigAmountC2SPacket;
import com.glodblock.github.extendedae.client.button.ActionEPPButton;
import com.mojang.logging.LogUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.world.inventory.Slot;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* AE2 ME 接口界面注入倍增/除法按钮x2/÷2x5/÷5x10/÷10
* 点击时通过 NeoForge 自定义负载发送到服务端调整配置数量
*/
@Mixin(value = AEBaseScreen.class, remap = false)
public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
@Unique private ActionEPPButton eap$x2Button;
@Unique private ActionEPPButton eap$divideBy2Button;
@Unique private ActionEPPButton eap$x5Button;
@Unique private ActionEPPButton eap$divideBy5Button;
@Unique private ActionEPPButton eap$x10Button;
@Unique private ActionEPPButton eap$divideBy10Button;
@Unique private int eap$lastLeftPos = -1;
@Unique private int eap$lastTopPos = -1;
@Unique private int eap$lastImageWidth = -1;
@Unique private int eap$lastImageHeight = -1;
// 最近一次在 CONFIG 区域悬停的配置槽索引
@Unique private int eap$lastConfigIndex = -1;
@Inject(method = "init", at = @At("TAIL"))
private void eap$addScaleButtons(CallbackInfo ci) {
if (!eap$isSupportedInterfaceScreen()) {
return;
}
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] init tail reached, preparing scale buttons."); } catch (Throwable ignored) {}
if (eap$x2Button == null) {
eap$x2Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(false, 2), NewIcon.MULTIPLY2);
eap$x2Button.setTooltip(null);
eap$x2Button.setVisibility(true);
}
if (eap$divideBy2Button == null) {
eap$divideBy2Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(true, 2), NewIcon.DIVIDE2);
eap$divideBy2Button.setTooltip(null);
eap$divideBy2Button.setVisibility(true);
}
if (eap$x5Button == null) {
eap$x5Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(false, 5), NewIcon.MULTIPLY5);
eap$x5Button.setTooltip(null);
eap$x5Button.setVisibility(true);
}
if (eap$divideBy5Button == null) {
eap$divideBy5Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(true, 5), NewIcon.DIVIDE5);
eap$divideBy5Button.setTooltip(null);
eap$divideBy5Button.setVisibility(true);
}
if (eap$x10Button == null) {
eap$x10Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(false, 10), NewIcon.MULTIPLY10);
eap$x10Button.setTooltip(null);
eap$x10Button.setVisibility(true);
}
if (eap$divideBy10Button == null) {
eap$divideBy10Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(true, 10), NewIcon.DIVIDE10);
eap$divideBy10Button.setTooltip(null);
eap$divideBy10Button.setVisibility(true);
}
// 注册到渲染与交互列表
var accessor = (ScreenAccessor) (Object) this;
if (!accessor.eap$getRenderables().contains(eap$divideBy2Button)) accessor.eap$getRenderables().add(eap$divideBy2Button);
if (!accessor.eap$getChildren().contains(eap$divideBy2Button)) accessor.eap$getChildren().add(eap$divideBy2Button);
if (!accessor.eap$getRenderables().contains(eap$x2Button)) accessor.eap$getRenderables().add(eap$x2Button);
if (!accessor.eap$getChildren().contains(eap$x2Button)) accessor.eap$getChildren().add(eap$x2Button);
if (!accessor.eap$getRenderables().contains(eap$divideBy5Button)) accessor.eap$getRenderables().add(eap$divideBy5Button);
if (!accessor.eap$getChildren().contains(eap$divideBy5Button)) accessor.eap$getChildren().add(eap$divideBy5Button);
if (!accessor.eap$getRenderables().contains(eap$x5Button)) accessor.eap$getRenderables().add(eap$x5Button);
if (!accessor.eap$getChildren().contains(eap$x5Button)) accessor.eap$getChildren().add(eap$x5Button);
if (!accessor.eap$getRenderables().contains(eap$divideBy10Button)) accessor.eap$getRenderables().add(eap$divideBy10Button);
if (!accessor.eap$getChildren().contains(eap$divideBy10Button)) accessor.eap$getChildren().add(eap$divideBy10Button);
if (!accessor.eap$getRenderables().contains(eap$x10Button)) accessor.eap$getRenderables().add(eap$x10Button);
if (!accessor.eap$getChildren().contains(eap$x10Button)) accessor.eap$getChildren().add(eap$x10Button);
eap$relayoutButtons();
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] buttons added and laid out."); } catch (Throwable ignored) {}
}
@Inject(method = "containerTick", at = @At("TAIL"))
private void eap$ensureButtons(CallbackInfo ci) {
if (!eap$isSupportedInterfaceScreen()) {
return;
}
var accessor = (ScreenAccessor) (Object) this;
if (eap$divideBy2Button != null && !accessor.eap$getRenderables().contains(eap$divideBy2Button)) {
accessor.eap$getRenderables().add(eap$divideBy2Button);
accessor.eap$getChildren().add(eap$divideBy2Button);
}
if (eap$x2Button != null && !accessor.eap$getRenderables().contains(eap$x2Button)) {
accessor.eap$getRenderables().add(eap$x2Button);
accessor.eap$getChildren().add(eap$x2Button);
}
if (eap$divideBy5Button != null && !accessor.eap$getRenderables().contains(eap$divideBy5Button)) {
accessor.eap$getRenderables().add(eap$divideBy5Button);
accessor.eap$getChildren().add(eap$divideBy5Button);
}
if (eap$x5Button != null && !accessor.eap$getRenderables().contains(eap$x5Button)) {
accessor.eap$getRenderables().add(eap$x5Button);
accessor.eap$getChildren().add(eap$x5Button);
}
if (eap$divideBy10Button != null && !accessor.eap$getRenderables().contains(eap$divideBy10Button)) {
accessor.eap$getRenderables().add(eap$divideBy10Button);
accessor.eap$getChildren().add(eap$divideBy10Button);
}
if (eap$x10Button != null && !accessor.eap$getRenderables().contains(eap$x10Button)) {
accessor.eap$getRenderables().add(eap$x10Button);
accessor.eap$getChildren().add(eap$x10Button);
}
int curLeft = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getLeftPos();
int curTop = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
int curImgW = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
int curImgH = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageHeight();
if (curLeft != eap$lastLeftPos || curTop != eap$lastTopPos || curImgW != eap$lastImageWidth || curImgH != eap$lastImageHeight) {
eap$lastLeftPos = curLeft;
eap$lastTopPos = curTop;
eap$lastImageWidth = curImgW;
eap$lastImageHeight = curImgH;
eap$relayoutButtons();
}
eap$updateLastConfigFromHover();
}
@Unique
private void eap$sendAdjustForHoveredConfig(boolean divide, int factor) {
try {
if (!eap$isSupportedInterfaceScreen()) {
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;
}
Integer slotFieldObj = null;
if (hovered != 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) {
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;
}
if (slotField < 0) {
return;
}
var conn = Minecraft.getInstance().getConnection();
if (conn != null) conn.send(new InterfaceAdjustConfigAmountC2SPacket(slotField, divide, factor));
} catch (Throwable ignored) {}
}
@Unique
private void eap$sendAdjustForAllConfigs(boolean divide, int factor) {
try {
if (!eap$isSupportedInterfaceScreen()) {
return;
}
var conn = Minecraft.getInstance().getConnection();
if (conn != null) conn.send(new InterfaceAdjustConfigAmountC2SPacket(-1, divide, factor));
} catch (Throwable ignored) {}
}
@Unique
private boolean eap$isSupportedInterfaceScreen() {
if (((Object) this) instanceof InterfaceScreen) {
return true;
}
try {
String cn = ((Object) this).getClass().getName();
if ("com.glodblock.github.extendedae.client.gui.GuiExInterface".equals(cn)) {
return true;
}
} catch (Throwable ignored) {}
return false;
}
@Unique
private void eap$relayoutButtons() {
try {
int leftPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getLeftPos();
int topPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
int imageWidth = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
int bx = leftPos + imageWidth + 1;
int by = topPos + 20;
int spacing = 22;
if (eap$divideBy2Button != null) { eap$divideBy2Button.setX(bx); eap$divideBy2Button.setY(by); }
if (eap$x2Button != null) { eap$x2Button.setX(bx); eap$x2Button.setY(by + spacing); }
if (eap$divideBy5Button != null) { eap$divideBy5Button.setX(bx); eap$divideBy5Button.setY(by + spacing * 2); }
if (eap$x5Button != null) { eap$x5Button.setX(bx); eap$x5Button.setY(by + spacing * 3); }
if (eap$divideBy10Button != null) { eap$divideBy10Button.setX(bx); eap$divideBy10Button.setY(by + spacing * 4); }
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;
}
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;
}
}
} catch (Throwable ignored) {}
}
}

View File

@ -0,0 +1,114 @@
package com.extendedae_plus.network;
import appeng.menu.implementations.InterfaceMenu;
import appeng.api.stacks.GenericStack;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S调整 ME 接口配置槽位(标记物品)的数量
* 支持按因子倍增或整除且保持最小值为 1
*/
public class InterfaceAdjustConfigAmountC2SPacket implements CustomPacketPayload {
public static final Type<InterfaceAdjustConfigAmountC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "interface_adjust_config_amount"));
public static final StreamCodec<FriendlyByteBuf, InterfaceAdjustConfigAmountC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeVarInt(pkt.slotIndex);
buf.writeBoolean(pkt.divide);
buf.writeVarInt(pkt.factor);
},
buf -> new InterfaceAdjustConfigAmountC2SPacket(buf.readVarInt(), buf.readBoolean(), buf.readVarInt())
);
private final int slotIndex; // 配置槽位索引-1 表示全部
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;
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(final InterfaceAdjustConfigAmountC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
// 支持 AE2 原版接口和 ExtendedAE 扩展接口若存在
InterfaceMenu menu = null;
com.glodblock.github.extendedae.container.ContainerExInterface exMenu = null;
if (player.containerMenu instanceof InterfaceMenu im) {
menu = im;
} else if (player.containerMenu instanceof com.glodblock.github.extendedae.container.ContainerExInterface cem) {
exMenu = cem;
} else {
return;
}
try {
var logic = (menu != null ? menu.getHost() : exMenu.getHost()).getInterfaceLogic();
var config = logic.getConfig();
if (msg.slotIndex == -1) {
// 对所有配置槽统一生效
int size = config.size();
for (int idx = 0; idx < size; idx++) {
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 的变更监听器会触发保存与计划更新
} catch (Throwable ignored) {}
});
}
}

View File

@ -10,6 +10,7 @@ public class ModNetwork {
registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle); registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle);
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle); registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle); registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);
registrar.playToServer(InterfaceAdjustConfigAmountC2SPacket.TYPE, InterfaceAdjustConfigAmountC2SPacket.STREAM_CODEC, InterfaceAdjustConfigAmountC2SPacket::handle);
registrar.playToClient(SetPatternHighlightS2CPacket.TYPE, SetPatternHighlightS2CPacket.STREAM_CODEC, SetPatternHighlightS2CPacket::handle); registrar.playToClient(SetPatternHighlightS2CPacket.TYPE, SetPatternHighlightS2CPacket.STREAM_CODEC, SetPatternHighlightS2CPacket::handle);
registrar.playToClient(AdvancedBlockingSyncS2CPacket.TYPE, AdvancedBlockingSyncS2CPacket.STREAM_CODEC, AdvancedBlockingSyncS2CPacket::handle); registrar.playToClient(AdvancedBlockingSyncS2CPacket.TYPE, AdvancedBlockingSyncS2CPacket.STREAM_CODEC, AdvancedBlockingSyncS2CPacket::handle);
registrar.playToClient(ProvidersListS2CPacket.TYPE, ProvidersListS2CPacket.STREAM_CODEC, ProvidersListS2CPacket::handle); registrar.playToClient(ProvidersListS2CPacket.TYPE, ProvidersListS2CPacket.STREAM_CODEC, ProvidersListS2CPacket::handle);

View File

@ -41,6 +41,7 @@
"ae2.accessor.MEStorageScreenAccessor", "ae2.accessor.MEStorageScreenAccessor",
"ae2.accessor.PatternAccessTermScreenAccessor", "ae2.accessor.PatternAccessTermScreenAccessor",
"ae2.accessor.PatternAccessTermScreenSlotsRowAccessor", "ae2.accessor.PatternAccessTermScreenSlotsRowAccessor",
"ae2.client.gui.InterfaceScreenMixin",
"ae2.client.gui.AEBaseScreenMixin", "ae2.client.gui.AEBaseScreenMixin",
"ae2.client.gui.PatternEncodingTermScreenMixin", "ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.PatternProviderCloseMixin", "ae2.client.gui.PatternProviderCloseMixin",