增加编码终端上传合成样板按钮

This commit is contained in:
GaLicn 2025-08-14 00:05:17 +08:00
parent 8923fe7a62
commit 4b243461cf
8 changed files with 350 additions and 8 deletions

View File

@ -0,0 +1,72 @@
package com.extendedae_plus.mixin;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import com.glodblock.github.glodium.network.packet.sync.Paras;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;
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;
import java.util.Map;
import java.util.function.Consumer;
/**
* AE2 PatternEncodingTermMenu 增加一个通用动作持有者实现接收 EPP CGenericPacket 动作
* 注册动作 "upload_to_matrix"仅上传合成图样 ExtendedAE 装配矩阵
*/
@Mixin(PatternEncodingTermMenu.class)
public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHolder {
@Unique
private final Map<String, Consumer<Paras>> actions = createHolder();
@Unique
private Player epp$player;
// AE2 终端主构造PatternEncodingTermMenu(int id, Inventory ip, IPatternTerminalMenuHost host)
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;)V", at = @At("TAIL"), remap = false)
private void epp$ctorA(int id, net.minecraft.world.entity.player.Inventory ip, appeng.helpers.IPatternTerminalMenuHost host, CallbackInfo ci) {
this.epp$player = ip.player;
// 注册动作无参由服务端直接读 encoded 槽位
System.out.println("[EAE+][Server] Register action 'upload_to_matrix' for PatternEncodingTermMenu ctorA");
this.actions.put("upload_to_matrix", p -> {
try {
var sp = (ServerPlayer) this.epp$player;
System.out.println("[EAE+][Server] Handle action 'upload_to_matrix' from " + sp.getGameProfile().getName());
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, (PatternEncodingTermMenu) (Object) this);
} catch (Throwable t) {
System.out.println("[EAE+][Server] Exception in 'upload_to_matrix': " + t);
t.printStackTrace();
}
});
}
// AE2 另一个构造PatternEncodingTermMenu(MenuType, int, Inventory, IPatternTerminalMenuHost, boolean)
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;Z)V", at = @At("TAIL"), remap = false)
private void epp$ctorB(net.minecraft.world.inventory.MenuType<?> menuType, int id, net.minecraft.world.entity.player.Inventory ip, appeng.helpers.IPatternTerminalMenuHost host, boolean bindInventory, CallbackInfo ci) {
this.epp$player = ip.player;
System.out.println("[EAE+][Server] Register action 'upload_to_matrix' for PatternEncodingTermMenu ctorB");
this.actions.put("upload_to_matrix", p -> {
try {
var sp = (ServerPlayer) this.epp$player;
System.out.println("[EAE+][Server] Handle action 'upload_to_matrix' from " + sp.getGameProfile().getName());
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, (PatternEncodingTermMenu) (Object) this);
} catch (Throwable t) {
System.out.println("[EAE+][Server] Exception in 'upload_to_matrix': " + t);
t.printStackTrace();
}
});
}
@NotNull
@Override
public Map<String, Consumer<Paras>> getActionMap() {
return this.actions;
}
}

View File

@ -33,7 +33,7 @@ public abstract class ContainerUWirelessExPatternTerminalMixin implements IActio
private Player epp$player;
// 明确目标构造签名<init>(int, Inventory, HostUWirelessExPAT)
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/xmod/wt/HostUWirelessExPAT;)V", at = @At("TAIL"))
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/xmod/wt/HostUWirelessExPAT;)V", at = @At("TAIL"), remap = false)
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, HostUWirelessExPAT host, CallbackInfo ci) {
this.epp$player = playerInventory.player;
// 注册上传动作参数顺序必须与客户端 CGenericPacket 保持一致

View File

@ -0,0 +1,70 @@
package com.extendedae_plus.mixin;
import com.extendedae_plus.mixin.accessor.AEBaseScreenAccessor;
import appeng.client.gui.me.items.PatternEncodingTermScreen;
import appeng.client.gui.widgets.IconButton;
import appeng.client.gui.Icon;
import appeng.client.gui.style.ScreenStyle;
import appeng.menu.me.items.PatternEncodingTermMenu;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
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;
import com.glodblock.github.extendedae.network.EPPNetworkHandler;
import com.glodblock.github.glodium.network.packet.CGenericPacket;
/**
* AE2 PatternEncodingTermScreen 界面中给编码按钮旁添加一个上传到矩阵按钮
* 客户端点击后使用 ExtendedAE 的网络通道发送通用数据包到服务端由容器 Mixin 处理
*/
@Mixin(PatternEncodingTermScreen.class)
public abstract class GuiPatternEncodingTermMixin<C extends PatternEncodingTermMenu> {
@Unique
private IconButton epp$uploadButton;
// 通过 Accessor 调用 AEBaseScreen#addToLeftToolbar
// 在构造函数尾部创建按钮实例仅创建不添加
@Inject(method = "<init>", at = @At("TAIL"), remap = false)
private void epp$onInit(C menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
// 选择一个合适的图标占位AE2 暂无显式上传图标这里先用 PATTERN_ACCESS_SHOW
this.epp$uploadButton = new IconButton(b -> {
// 直接发送无参动作服务端容器会根据当前 encoded 槽位处理
System.out.println("[EAE+][Client] Click upload button -> send CGenericPacket('upload_to_matrix')");
try {
var mc = net.minecraft.client.Minecraft.getInstance();
var cm = mc.player != null ? mc.player.containerMenu : null;
System.out.println("[EAE+][Client] Current container: " + (cm == null ? "null" : cm.getClass().getName()));
if (cm instanceof com.glodblock.github.glodium.network.packet.sync.IActionHolder ah) {
var keys = ah.getActionMap().keySet();
System.out.println("[EAE+][Client] IActionHolder detected. actions=" + keys);
} else {
System.out.println("[EAE+][Client] Current container is NOT IActionHolder");
}
} catch (Throwable t) {
System.out.println("[EAE+][Client] Inspect container failed: " + t);
}
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("upload_to_matrix"));
}) {
@Override
protected Icon getIcon() {
return Icon.PATTERN_ACCESS_SHOW;
}
};
this.epp$uploadButton.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.upload_to_matrix")));
System.out.println("[EAE+][Client] Created upload button in PatternEncodingTermScreen ctor tail");
// 直接在构造尾部加入左侧工具栏避免目标类未覆写 init 导致的注入不到
((AEBaseScreenAccessor) (Object) this).epp$addToLeftToolbar(this.epp$uploadButton);
System.out.println("[EAE+][Client] Added upload button to left toolbar in ctor tail");
}
}

View File

@ -0,0 +1,12 @@
package com.extendedae_plus.mixin.accessor;
import net.minecraft.client.gui.components.Button;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import appeng.client.gui.AEBaseScreen;
@Mixin(AEBaseScreen.class)
public interface AEBaseScreenAccessor {
@Invoker(value = "addToLeftToolbar", remap = false)
<B extends Button> B epp$addToLeftToolbar(B button);
}

View File

@ -0,0 +1,12 @@
package com.extendedae_plus.mixin.accessor;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(PatternEncodingTermMenu.class)
public interface PatternEncodingTermMenuAccessor {
@Accessor("encodedPatternSlot")
RestrictedInputSlot epp$getEncodedPatternSlot();
}

View File

@ -2,6 +2,8 @@ package com.extendedae_plus.util;
import appeng.api.inventories.InternalInventory;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.util.inv.FilteredInternalInventory;
@ -12,6 +14,14 @@ import net.minecraft.network.chat.Component;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.api.crafting.IPatternDetails;
import appeng.crafting.pattern.AECraftingPattern;
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
/**
* ExtendedAE扩展样板管理终端专用的样板上传工具类
@ -29,22 +39,180 @@ public class ExtendedAEPatternUploadUtil {
if (player == null || player.containerMenu == null) {
return null;
}
// 优先检查ExtendedAE的扩展样板管理终端使用类名检查避免直接导入
String containerClassName = player.containerMenu.getClass().getName();
if (containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal")) {
// ExtendedAE的容器继承自PatternAccessTermMenu可以安全转换
return (PatternAccessTermMenu) player.containerMenu;
}
// 兼容原版AE2的样板访问终端
if (player.containerMenu instanceof PatternAccessTermMenu) {
return (PatternAccessTermMenu) player.containerMenu;
}
return null;
}
/**
* AE2 的图样编码终端菜单上传当前已编码图样 ExtendedAE 装配矩阵仅合成图样
* 不会处理处理图样
*
* @param player 服务器玩家
* @param menu PatternEncodingTermMenu
* @return 是否成功插入矩阵
*/
public static boolean uploadFromEncodingMenuToMatrix(ServerPlayer player, PatternEncodingTermMenu menu) {
if (player == null || menu == null) {
System.out.println("[EAE+][Server] uploadFromEncodingMenuToMatrix: player or menu is null");
return false;
}
// 读取已编码槽位的物品
var encodedSlot = ((com.extendedae_plus.mixin.accessor.PatternEncodingTermMenuAccessor) (Object) menu)
.epp$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
System.out.println("[EAE+][Server] Encoded slot stack: " + stack + ", count=" + stack.getCount());
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
sendMessage(player, "ExtendedAE Plus: 没有可上传的编码样板");
System.out.println("[EAE+][Server] Fail: stack empty or not encoded pattern");
return false;
}
// 仅允许合成图样
IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level());
System.out.println("[EAE+][Server] Decoded details: " + (details == null ? "null" : details.getClass().getName()));
if (!(details instanceof AECraftingPattern)) {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_not_crafting");
System.out.println("[EAE+][Server] Fail: not AECraftingPattern");
return false;
}
// 获取 AE 网络
IGridNode node = menu.getNetworkNode();
System.out.println("[EAE+][Server] Grid node: " + node);
if (node == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
System.out.println("[EAE+][Server] Fail: grid node null");
return false;
}
IGrid grid = node.getGrid();
System.out.println("[EAE+][Server] Grid: " + grid);
if (grid == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
System.out.println("[EAE+][Server] Fail: grid null");
return false;
}
// 查找已成型的装配矩阵图样模块的内部库存优先使用内部库存其带有正确过滤逻辑
InternalInventory matrixInv = findFirstMatrixPatternInventory(grid);
System.out.println("[EAE+][Server] Matrix internal inventory: " + matrixInv);
if (matrixInv == null) {
// 回退尝试 Forge 能力旧实现
IItemHandler cap = findFirstMatrixPatternHandler(grid);
System.out.println("[EAE+][Server] Fallback Matrix item handler: " + cap);
if (cap == null) {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix");
System.out.println("[EAE+][Server] Fail: no formed matrix found");
return false;
}
// 使用能力插入
ItemStack toInsert = stack.copy();
System.out.println("[EAE+][Server] Try insert via capability, count=" + toInsert.getCount());
ItemStack remain = insertIntoAnySlot(cap, toInsert);
System.out.println("[EAE+][Server] Insert remain count=" + remain.getCount());
if (remain.getCount() < stack.getCount()) {
int inserted = stack.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
}
sendMessage(player, "extendedae_plus.upload_to_matrix.success");
System.out.println("[EAE+][Server] Success via capability: inserted=" + inserted);
return true;
} else {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full");
System.out.println("[EAE+][Server] Fail via capability: inventory full or cannot accept pattern");
return false;
}
}
// 通过内部库存插入遵循其过滤规则
ItemStack toInsert = stack.copy();
System.out.println("[EAE+][Server] Try insert via internal inventory, count=" + toInsert.getCount());
ItemStack remain = matrixInv.addItems(toInsert);
System.out.println("[EAE+][Server] Internal inventory remain count=" + remain.getCount());
if (remain.getCount() < stack.getCount()) {
// 扣除插入数量
int inserted = stack.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
}
sendMessage(player, "extendedae_plus.upload_to_matrix.success");
System.out.println("[EAE+][Server] Success via internal inventory: inserted=" + inserted);
return true;
} else {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full");
System.out.println("[EAE+][Server] Fail via internal inventory: inventory full or cannot accept pattern");
return false;
}
}
/**
* 在给定 AE Grid 中查找第一台已成型且在线的装配矩阵图样模块并返回其用于外部插入的内部库存
* 优先使用 TileAssemblerMatrixPattern#getExposedInventory仅允许插入且已带AE过滤规则
*/
private static InternalInventory findFirstMatrixPatternInventory(IGrid grid) {
try {
var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class);
for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) {
if (tile != null && tile.isFormed() && tile.getMainNode().isActive()) {
var inv = tile.getExposedInventory();
if (inv != null) {
return inv;
}
}
}
} catch (Throwable t) {
System.out.println("[EAE+][Server] findFirstMatrixPatternInventory exception: " + t);
}
return null;
}
/**
* 在给定 AE Grid 中查找第一台已成型的装配矩阵并返回其聚合图样仓的 IItemHandler
*/
private static IItemHandler findFirstMatrixPatternHandler(IGrid grid) {
try {
Set<TileAssemblerMatrixBase> matrices = grid.getMachines(TileAssemblerMatrixBase.class);
for (TileAssemblerMatrixBase tile : matrices) {
if (tile != null && tile.isFormed()) {
var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null);
if (capOpt != null) {
var handler = capOpt.orElse(null);
if (handler != null) {
return handler;
}
}
}
}
} catch (Throwable ignored) {
}
return null;
}
/**
* 尝试将整个物品栈插入到 IItemHandler 的任意槽位返回剩余物品
*/
private static ItemStack insertIntoAnySlot(IItemHandler handler, ItemStack stack) {
ItemStack remaining = stack.copy();
if (handler == null || remaining.isEmpty()) return remaining;
for (int i = 0; i < handler.getSlots(); i++) {
remaining = handler.insertItem(i, remaining, false);
if (remaining.isEmpty()) break;
}
return remaining;
}
/**
* 检查当前菜单是否为ExtendedAE的扩展样板管理终端
*

View File

@ -5,5 +5,10 @@
"gui.expatternprovider.clear_selection": "取消选择",
"block.extendedae_plus.wireless_transceiver": "无线收发器",
"item.extendedae_plus.wireless_transceiver": "无线收发器",
"itemGroup.extendedae_plus.main": "扩展AE Plus"
"itemGroup.extendedae_plus.main": "扩展AE Plus",
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
"extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
"extendedae_plus.upload_to_matrix.fail_no_matrix": "未在当前网络中找到已成型的装配矩阵",
"extendedae_plus.upload_to_matrix.fail_full": "装配矩阵的样板仓已满或无法插入"
}

View File

@ -7,7 +7,9 @@
"GuiExPatternProviderMixin",
"SlotGridLayoutMixin",
"GuiExPatternTerminalMixin",
"HighlightButtonMixin"
"HighlightButtonMixin",
"GuiPatternEncodingTermMixin",
"accessor.AEBaseScreenAccessor"
],
"mixins": [
"ContainerExPatternProviderMixin",
@ -16,8 +18,9 @@
"ContainerUWirelessExPatternTerminalMixin",
"TileExPatternProviderMixin",
"PartExPatternProviderMixin",
"PatternEncodingTermMenuMixin",
"accessor.MEStorageMenuAccessor"
"ContainerPatternEncodingTermMenuMixin",
"accessor.MEStorageMenuAccessor",
"accessor.PatternEncodingTermMenuAccessor"
],
"injectors": {
"defaultRequire": 1