分离原PatternUploadUtil类,便于对不同模块的维护

This commit is contained in:
C-H716 2025-10-26 23:22:20 +08:00
parent cfd278eb52
commit 48e8b01384
12 changed files with 844 additions and 776 deletions

View File

@ -4,7 +4,7 @@ import appeng.api.crafting.PatternDetailsHelper;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot;
import appeng.parts.encoding.EncodingMode;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.uploadPattern.MatrixUploadUtil;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import com.glodblock.github.glodium.network.packet.sync.Paras;
import net.minecraft.server.level.ServerPlayer;
@ -47,7 +47,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
}
var stack = this.encodedPatternSlot != null ? this.encodedPatternSlot.getItem() : net.minecraft.world.item.ItemStack.EMPTY;
if (stack != null && !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack)) {
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
} else {
// 槽位可能尚未同步到位继续下一 tick 重试
if (attemptsLeft > 0) {
@ -105,7 +105,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
// 为避免与 AE2 后续同步竞争切到下一 tick 执行
sp.server.execute(() -> {
try {
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
} catch (Throwable ignored) {
}
});

View File

@ -1,6 +1,6 @@
package com.extendedae_plus.mixin.ae2WTlib;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
import com.glodblock.github.extendedae.xmod.wt.ContainerUWirelessExPAT;
import com.glodblock.github.extendedae.xmod.wt.HostUWirelessExPAT;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
@ -43,7 +43,7 @@ public abstract class ContainerUWirelessExPatternTerminalMixin implements IActio
int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0));
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
var sp = (ServerPlayer) this.eap$player;
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
} catch (Throwable t) {
}
});

View File

@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.extendedae.container;
import appeng.api.util.IConfigurableObject;
import appeng.menu.guisync.GuiSync;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
import com.glodblock.github.extendedae.container.ContainerExPatternTerminal;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import com.glodblock.github.glodium.network.packet.sync.Paras;
@ -79,7 +79,7 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0));
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
var sp = (ServerPlayer) this.epp$player;
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
} catch (Throwable ignored) {
}
});

View File

@ -1,6 +1,6 @@
package com.extendedae_plus.mixin.extendedae.container;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
import com.glodblock.github.extendedae.common.me.itemhost.HostWirelessExPAT;
import com.glodblock.github.extendedae.container.ContainerWirelessExPAT;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
@ -43,7 +43,7 @@ public abstract class ContainerWirelessExPatternTerminalMixin implements IAction
int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0));
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
var sp = (ServerPlayer) this.epp$player;
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
} catch (Throwable ignored) {
}
});

View File

@ -1,7 +1,7 @@
package com.extendedae_plus.network;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
@ -36,10 +36,10 @@ public class UploadEncodedPatternToProviderC2SPacket {
// 1) providerId >= 0: 访问终端 byId 模式
// 2) providerId < 0: 索引模式由列表回退路径生成index = -1 - providerId
if (msg.providerId >= 0) {
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
} else {
int index = (int) (-1L - msg.providerId);
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
}
});
ctx.setPacketHandled(true);

View File

@ -4,7 +4,8 @@ import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.extendedae_plus.util.PatternProviderDataUtil;
import com.extendedae_plus.util.uploadPattern.PatternTerminalUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
@ -31,20 +32,20 @@ public class RequestProvidersListC2SPacket {
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
// 优先若玩家也打开了样板访问终端则用 byId 方式精确服务器ID
PatternAccessTermMenu accessMenu = ExtendedAEPatternUploadUtil.getPatternAccessMenu(player);
PatternAccessTermMenu accessMenu = PatternTerminalUtil.getPatternAccessMenu(player);
if (accessMenu != null) {
List<Long> ids = ExtendedAEPatternUploadUtil.getAllProviderIds(accessMenu);
List<Long> ids = PatternTerminalUtil.getAllProviderIds(accessMenu);
List<Long> filteredIds = new ArrayList<>();
List<String> names = new ArrayList<>();
List<Integer> slots = new ArrayList<>();
for (Long id : ids) {
if (id == null) continue;
if (!ExtendedAEPatternUploadUtil.isProviderAvailable(id, accessMenu)) continue;
int empty = ExtendedAEPatternUploadUtil.getAvailableSlots(id, accessMenu);
if (!PatternProviderDataUtil.isProviderAvailable(id, accessMenu)) continue;
int empty = PatternProviderDataUtil.getAvailableSlots(id, accessMenu);
if (empty <= 0) continue; // 只列出有空位的
filteredIds.add(id);
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu));
names.add(PatternProviderDataUtil.getProviderDisplayName(id, accessMenu));
slots.add(empty);
}
@ -53,18 +54,18 @@ public class RequestProvidersListC2SPacket {
}
// 回退基于编码终端所在网络枚举供应器负数ID编码索引encodedId = -1 - index
List<PatternContainer> containers = ExtendedAEPatternUploadUtil.listAvailableProvidersFromGrid(encMenu);
List<PatternContainer> containers = PatternTerminalUtil.listAvailableProvidersFromGrid(encMenu);
List<Long> idxIds = new ArrayList<>();
List<String> names = new ArrayList<>();
List<Integer> slots = new ArrayList<>();
for (int i = 0; i < containers.size(); i++) {
var c = containers.get(i);
if (c == null) continue;
int empty = ExtendedAEPatternUploadUtil.getAvailableSlots(c);
int empty = PatternProviderDataUtil.getAvailableSlots(c);
if (empty <= 0) continue;
long encodedId = -1L - i; // 约定负数代表按索引
idxIds.add(encodedId);
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
names.add(PatternProviderDataUtil.getProviderDisplayName(c));
slots.add(empty);
}
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);

View File

@ -0,0 +1,4 @@
package com.extendedae_plus.util;
public class AEMenUtil {
}

View File

@ -1,755 +0,0 @@
package com.extendedae_plus.util;
import appeng.api.crafting.IPatternDetails;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.core.definitions.AEItems;
import appeng.crafting.pattern.AECraftingPattern;
import appeng.crafting.pattern.AESmithingTablePattern;
import appeng.crafting.pattern.AEStonecuttingPattern;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.util.inv.FilteredInternalInventory;
import appeng.util.inv.filter.IAEItemFilter;
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ExtendedAE扩展样板管理终端专用的样板上传工具类
* 兼容ExtendedAE的ContainerExPatternTerminal和原版AE2的PatternAccessTermMenu
*/
public class ExtendedAEPatternUploadUtil {
/**
* 获取玩家当前的样板访问终端菜单支持ExtendedAE和原版AE2
*
* @param player 玩家
* @return PatternAccessTermMenu实例如果玩家没有打开则返回null
*/
public static PatternAccessTermMenu getPatternAccessMenu(ServerPlayer player) {
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) {
return false;
}
// 读取已编码槽位的物品
var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu)
.eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
sendMessage(player, "ExtendedAE Plus: 没有可上传的编码样板");
return false;
}
// 仅允许合成/锻造台/切石机图样
IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level());
if (!(details instanceof AECraftingPattern
|| details instanceof AESmithingTablePattern
|| details instanceof AEStonecuttingPattern)) {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail");
return false;
}
// 获取 AE 网络
IGridNode node = menu.getNetworkNode();
if (node == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
return false;
}
IGrid grid = node.getGrid();
if (grid == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
return false;
}
// 在尝试上传之前检查装配矩阵是否已经存在相同样板物品与NBT完全一致
if (matrixContainsPattern(grid, stack)) {
// 直接提醒并跳过上传并将同等数量的空白样板放回空白样板槽否则退回玩家背包
if (player != null) {
player.sendSystemMessage(Component.literal("ExtendedAE Plus: 装配矩阵已存在相同样板,已跳过上传并返还空白样板"));
}
try {
var accessor = (PatternEncodingTermMenuAccessor) (Object) menu;
var blankSlot = accessor.eap$getBlankPatternSlot();
ItemStack blanks = AEItems.BLANK_PATTERN.stack(stack.getCount());
if (blankSlot != null && blankSlot.mayPlace(blanks)) {
ItemStack remain = blankSlot.safeInsert(blanks);
if (!remain.isEmpty() && player != null) {
player.getInventory().placeItemBackInInventory(remain, false);
}
} else if (player != null) {
player.getInventory().placeItemBackInInventory(blanks, false);
}
} catch (Throwable t) {
if (player != null) {
// 兜底直接还给玩家背包
player.getInventory().placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(stack.getCount()), false);
}
}
// 清空编码样板槽防止再次输出
encodedSlot.set(ItemStack.EMPTY);
return false;
}
// 收集所有可用的装配矩阵图样模块内部库存并逐一尝试遵循其过滤规则
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
if (!inventories.isEmpty()) {
for (int i = 0; i < inventories.size(); i++) {
var inv = inventories.get(i);
ItemStack toInsert = stack.copy();
ItemStack remain = inv.addItems(toInsert);
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");
return true;
}
}
// 所有内部库存都无法接收 -> 尝试 capability 回退
}
// 回退尝试 Forge 能力可能为聚合图样仓同样遍历所有矩阵
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
if (!handlers.isEmpty()) {
for (int i = 0; i < handlers.size(); i++) {
var cap = handlers.get(i);
ItemStack toInsert = stack.copy();
ItemStack remain = insertIntoAnySlot(cap, toInsert);
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");
return true;
}
}
}
// 未找到可用矩阵或全部拒收
if (inventories.isEmpty() && handlers.isEmpty()) {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix");
} else {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full");
}
return false;
}
/**
* 在给定 AE Grid 中收集所有已成型且在线的装配矩阵图样模块的用于外部插入的内部库存
* 优先使用 TileAssemblerMatrixPattern#getExposedInventory仅允许插入且已带AE过滤规则
*/
private static List<InternalInventory> findAllMatrixPatternInventories(IGrid grid) {
List<InternalInventory> result = new ArrayList<>();
try {
var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class);
int idx = 0;
for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) {
if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) {
var inv = tile.getExposedInventory();
if (inv != null) {
result.add(inv);
}
}
idx++;
}
} catch (Throwable t) {
}
return result;
}
/**
* 在给定 AE Grid 中收集所有已成型的装配矩阵的聚合图样仓 IItemHandler若可用
*/
private static List<IItemHandler> findAllMatrixPatternHandlers(IGrid grid) {
List<IItemHandler> result = new ArrayList<>();
try {
Set<TileAssemblerMatrixBase> matrices = grid.getMachines(TileAssemblerMatrixBase.class);
int idx = 0;
for (TileAssemblerMatrixBase tile : matrices) {
if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) {
var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null);
if (capOpt != null) {
var handler = capOpt.orElse(null);
if (handler != null) {
result.add(handler);
}
}
}
idx++;
}
} catch (Throwable ignored) {
}
return result;
}
/**
* 尝试将整个物品栈插入到 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;
}
/**
* 检查装配矩阵所有已成型矩阵的图样仓中是否已存在与给定样板完全相同的物品含NBT
*/
private static boolean matrixContainsPattern(IGrid grid, ItemStack pattern) {
if (grid == null || pattern == null || pattern.isEmpty()) return false;
try {
// 先检查提供外部插入视图的内部库存
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
for (InternalInventory inv : inventories) {
if (inv == null) continue;
for (int i = 0; i < inv.size(); i++) {
ItemStack s = inv.getStackInSlot(i);
if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
try {
// 再检查聚合能力视图
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
for (IItemHandler h : handlers) {
if (h == null) continue;
int slots = h.getSlots();
for (int i = 0; i < slots; i++) {
ItemStack s = h.getStackInSlot(i);
if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
return false;
}
/**
* 判断给定矩阵集群中是否存在装配矩阵上传核心
* 要求至少存在 1 个即可不限制数量
* 传入任意属于该集群的 Tile Pattern/Crafter/Frame
*/
private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) {
try {
if (any == null || any.getCluster() == null) return false;
int cores = 0;
var it = any.getCluster().getBlockEntities();
while (it.hasNext()) {
var te = it.next();
if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) {
cores++;
}
}
return cores >= 1; // 至少一个即可
} catch (Throwable t) {
return false;
}
}
/**
* 检查当前菜单是否为ExtendedAE的扩展样板管理终端
*
* @param player 玩家
* @return 是否为ExtendedAE扩展终端
*/
public static boolean isExtendedAETerminal(ServerPlayer player) {
if (player == null || player.containerMenu == null) {
return false;
}
String containerClassName = player.containerMenu.getClass().getName();
return containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal");
}
/**
* 将玩家背包中的样板上传到指定的样板供应器
* 兼容ExtendedAE和原版AE2
*
* @param player 玩家
* @param playerSlotIndex 玩家背包槽位索引
* @param providerId 目标样板供应器的服务器ID
* @return 是否上传成功
*/
public static boolean uploadPatternToProvider(ServerPlayer player, int playerSlotIndex, long providerId) {
// 1. 验证玩家是否打开了样板访问终端
PatternAccessTermMenu menu = getPatternAccessMenu(player);
if (menu == null) {
sendMessage(player, "ExtendedAE Plus: 请先打开样板访问终端或扩展样板管理终端");
return false;
}
// 2. 获取玩家背包中的物品
ItemStack playerItem = player.getInventory().getItem(playerSlotIndex);
if (playerItem.isEmpty()) {
sendMessage(player, "ExtendedAE Plus: 背包槽位为空");
return false;
}
// 3. 验证是否是编码样板
if (!PatternDetailsHelper.isEncodedPattern(playerItem)) {
sendMessage(player, "ExtendedAE Plus: 该物品不是有效的编码样板");
return false;
}
// 4. 获取目标样板供应器
PatternContainer patternContainer = getPatternContainerById(menu, providerId);
if (patternContainer == null) {
sendMessage(player, "ExtendedAE Plus: 找不到指定的样板供应器 (ID: " + providerId + ")");
return false;
}
// 5. 获取样板供应器的库存
InternalInventory patternInventory = patternContainer.getTerminalPatternInventory();
if (patternInventory == null) {
sendMessage(player, "ExtendedAE Plus: 无法访问样板供应器的库存");
return false;
}
// 6. 使用AE2的标准样板过滤器进行插入
var patternFilter = new ExtendedAEPatternFilter();
var filteredInventory = new FilteredInternalInventory(patternInventory, patternFilter);
// 7. 尝试插入样板
ItemStack itemToInsert = playerItem.copy();
ItemStack remaining = filteredInventory.addItems(itemToInsert);
if (remaining.getCount() < itemToInsert.getCount()) {
// 插入成功部分或全部
int insertedCount = itemToInsert.getCount() - remaining.getCount();
playerItem.shrink(insertedCount);
if (playerItem.isEmpty()) {
player.getInventory().setItem(playerSlotIndex, ItemStack.EMPTY);
}
String terminalType = isExtendedAETerminal(player) ? "扩展样板管理终端" : "样板访问终端";
sendMessage(player, "ExtendedAE Plus: 通过" + terminalType + "成功上传 " + insertedCount + " 个样板");
return true;
} else {
sendMessage(player, "ExtendedAE Plus: 上传失败 - 样板供应器已满或样板无效");
return false;
}
}
/**
* 获取样板供应器中的空槽位数量
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单支持ExtendedAE
* @return 空槽位数量如果无法访问则返回-1
*/
public static int getAvailableSlots(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = getPatternContainerById(menu, providerId);
if (container == null) {
return -1;
}
InternalInventory inventory = container.getTerminalPatternInventory();
if (inventory == null) {
return -1;
}
int availableSlots = 0;
for (int i = 0; i < inventory.size(); i++) {
if (inventory.getStackInSlot(i).isEmpty()) {
availableSlots++;
}
}
return availableSlots;
}
/**
* 通过服务器ID获取PatternContainer
* 兼容ExtendedAE的ContainerExPatternTerminal和原版PatternAccessTermMenu
*
* @param menu 样板访问终端菜单
* @param providerId 供应器服务器ID
* @return PatternContainer实例如果不存在则返回null
*/
private static PatternContainer getPatternContainerById(PatternAccessTermMenu menu, long providerId) {
try {
// 通过反射访问byId字段ExtendedAE继承了这个字段
Field byIdField = findByIdField(menu.getClass());
if (byIdField == null) {
System.err.println("ExtendedAE Plus: 无法找到byId字段");
return null;
}
byIdField.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Long, Object> byId = (Map<Long, Object>) byIdField.get(menu);
Object containerTracker = byId.get(providerId);
if (containerTracker == null) {
return null;
}
// 从ContainerTracker中获取PatternContainer
Field containerField = findContainerField(containerTracker.getClass());
if (containerField == null) {
System.err.println("ExtendedAE Plus: 无法找到container字段");
return null;
}
containerField.setAccessible(true);
return (PatternContainer) containerField.get(containerTracker);
} catch (Exception e) {
System.err.println("ExtendedAE Plus: 无法获取PatternContainer错误: " + e.getMessage());
return null;
}
}
/**
* 在类层次结构中查找byId字段
*/
private static Field findByIdField(Class<?> clazz) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField("byId");
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
/**
* 在类层次结构中查找container字段
*/
private static Field findContainerField(Class<?> clazz) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField("container");
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
/**
* 发送消息给玩家
*
* @param player 玩家
* @param message 消息内容
*/
private static void sendMessage(ServerPlayer player, String message) {
// 静默不再向玩家左下角发送任何提示信息
// 如需恢复取消下面注释即可
// if (player != null) {
// player.sendSystemMessage(Component.literal(message));
// }
// 如果玩家为null静默忽略用于测试环境
}
/**
* ExtendedAE兼容的样板过滤器
* 使用AE2的PatternDetailsHelper进行样板验证
*/
private static class ExtendedAEPatternFilter implements IAEItemFilter {
@Override
public boolean allowExtract(InternalInventory inv, int slot, int amount) {
return true;
}
@Override
public boolean allowInsert(InternalInventory inv, int slot, ItemStack stack) {
return !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack);
}
}
/**
* 获取样板供应器的显示名称
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单
* @return 显示名称如果无法获取则返回"未知供应器"
*/
public static String getProviderDisplayName(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = getPatternContainerById(menu, providerId);
if (container == null) {
return "未知供应器";
}
try {
// 尝试获取供应器的组信息来构建显示名称
var group = container.getTerminalGroup();
if (group != null) {
// 使用 Component 序列化来保持翻译键而不是直接 getString()
// 这样客户端可以根据自己的语言设置进行翻译
return Component.Serializer.toJson(group.name());
}
} catch (Exception e) {
// 忽略异常使用默认名称
}
return "样板供应器 #" + providerId;
}
/**
* 验证样板供应器是否可用
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单
* @return 是否可用
*/
public static boolean isProviderAvailable(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = getPatternContainerById(menu, providerId);
if (container == null) {
return false;
}
// 检查是否在终端中可见
if (!container.isVisibleInTerminal()) {
return false;
}
// 检查是否连接到网络
return container.getGrid() != null;
}
/**
* 将图样编码终端的已编码图样上传到指定的样板供应器通过 providerId 定位
*/
public static boolean uploadFromEncodingMenuToProvider(ServerPlayer player, PatternEncodingTermMenu menu, long providerId) {
if (player == null || menu == null) {
return false;
}
var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu)
.eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
return false;
}
PatternAccessTermMenu accessMenu = getPatternAccessMenu(player);
if (accessMenu == null) {
return false;
}
// 先确定目标容器名称用于同名回退
String targetName = getProviderDisplayName(providerId, accessMenu);
// 构建尝试顺序先指定ID其次同名的其他ID
java.util.List<Long> tryIds = new java.util.ArrayList<>();
tryIds.add(providerId);
try {
java.util.List<Long> all = getAllProviderIds(accessMenu);
for (Long id : all) {
if (id == null || id == providerId) continue;
String name = getProviderDisplayName(id, accessMenu);
if (name != null && name.equals(targetName)) {
tryIds.add(id);
}
}
} catch (Throwable ignored) {}
// 按顺序逐个尝试插入
for (Long id : tryIds) {
PatternContainer c = getPatternContainerById(accessMenu, id);
if (c == null || !c.isVisibleInTerminal()) continue;
InternalInventory inv = c.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter());
ItemStack toInsert = stack.copy();
ItemStack remain = filtered.addItems(toInsert);
if (remain.getCount() < toInsert.getCount()) {
int inserted = toInsert.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
} else {
encodedSlot.set(stack);
}
return true;
}
}
return false;
}
/**
* 列出当前菜单中所有供应器的服务器ID原样返回 byId key 集合
*/
public static java.util.List<Long> getAllProviderIds(PatternAccessTermMenu menu) {
java.util.List<Long> result = new java.util.ArrayList<>();
if (menu == null) return result;
try {
java.lang.reflect.Field byIdField = findByIdField(menu.getClass());
if (byIdField == null) return result;
byIdField.setAccessible(true);
@SuppressWarnings("unchecked")
java.util.Map<Long, Object> byId = (java.util.Map<Long, Object>) byIdField.get(menu);
if (byId != null) {
result.addAll(byId.keySet());
}
} catch (Throwable ignored) {
}
return result;
}
/**
* 基于编码终端菜单的 AE Grid 遍历列出可在终端中可见且有空位的供应器容器
* 返回顺序稳定 grid machineClasses 顺序再按 activeMachines 迭代顺序
*/
public static List<PatternContainer> listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) {
List<PatternContainer> list = new ArrayList<>();
if (menu == null) return list;
try {
IGridNode node = menu.getNetworkNode();
if (node == null) return list;
IGrid grid = node.getGrid();
if (grid == null) return list;
for (var machineClass : grid.getMachineClasses()) {
if (PatternContainer.class.isAssignableFrom(machineClass)) {
@SuppressWarnings("unchecked")
Class<? extends PatternContainer> containerClass = (Class<? extends PatternContainer>) machineClass;
for (var container : grid.getActiveMachines(containerClass)) {
if (container == null || !container.isVisibleInTerminal()) continue;
InternalInventory inv = container.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
boolean hasEmpty = false;
for (int i = 0; i < inv.size(); i++) {
if (inv.getStackInSlot(i).isEmpty()) { hasEmpty = true; break; }
}
if (hasEmpty) list.add(container);
}
}
}
} catch (Throwable ignored) {
}
return list;
}
/** 获取供应器显示名(优先组名) */
public static String getProviderDisplayName(PatternContainer container) {
if (container == null) return "未知供应器";
try {
var group = container.getTerminalGroup();
if (group != null) {
// 使用 Component 序列化来保持翻译键而不是直接 getString()
// 这样客户端可以根据自己的语言设置进行翻译
return Component.Serializer.toJson(group.name());
}
} catch (Throwable ignored) {
}
return "样板供应器";
}
/** 计算供应器空槽位数量 */
public static int getAvailableSlots(PatternContainer container) {
if (container == null) return -1;
InternalInventory inv = container.getTerminalPatternInventory();
if (inv == null) return -1;
int available = 0;
for (int i = 0; i < inv.size(); i++) {
if (inv.getStackInSlot(i).isEmpty()) available++;
}
return available;
}
/**
* 基于索引的定向上传使用 listAvailableProvidersFromGrid(menu) 的顺序
* 将编码槽样板插入到第 index 个供应器
*/
public static boolean uploadFromEncodingMenuToProviderByIndex(ServerPlayer player, PatternEncodingTermMenu menu, int index) {
if (player == null || menu == null || index < 0) return false;
List<PatternContainer> list = listAvailableProvidersFromGrid(menu);
if (index >= list.size()) return false;
var container = list.get(index);
if (container == null) return false;
var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu)
.eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
return false;
}
// 以名称为键同名供应器依次尝试 index 指定的再同名的其他
String targetName = getProviderDisplayName(container);
java.util.List<PatternContainer> tryList = new java.util.ArrayList<>();
tryList.add(container);
try {
for (PatternContainer c : list) {
if (c == null || c == container) continue;
String name = getProviderDisplayName(c);
if (name != null && name.equals(targetName)) {
tryList.add(c);
}
}
} catch (Throwable ignored) {}
for (PatternContainer c : tryList) {
InternalInventory inv = c.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter());
ItemStack toInsert = stack.copy();
ItemStack remain = filtered.addItems(toInsert);
if (remain.getCount() < toInsert.getCount()) {
int inserted = toInsert.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
} else {
encodedSlot.set(stack);
}
return true;
}
}
return false;
}
}

View File

@ -4,8 +4,12 @@ import appeng.api.crafting.IPatternDetails;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.IGrid;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.menu.implementations.PatternAccessTermMenu;
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
import com.extendedae_plus.util.uploadPattern.PatternTerminalUtil;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -113,4 +117,112 @@ public class PatternProviderDataUtil {
}
return null;
}
/**
* 获取样板供应器中的空槽位数量
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单支持ExtendedAE
* @return 空槽位数量如果无法访问则返回-1
*/
public static int getAvailableSlots(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId);
if (container == null) {
return -1;
}
InternalInventory inventory = container.getTerminalPatternInventory();
if (inventory == null) {
return -1;
}
int availableSlots = 0;
for (int i = 0; i < inventory.size(); i++) {
if (inventory.getStackInSlot(i).isEmpty()) {
availableSlots++;
}
}
return availableSlots;
}
/**
* 获取样板供应器的显示名称
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单
* @return 显示名称如果无法获取则返回"未知供应器"
*/
public static String getProviderDisplayName(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId);
if (container == null) {
return "未知供应器";
}
try {
// 尝试获取供应器的组信息来构建显示名称
var group = container.getTerminalGroup();
if (group != null) {
// 使用 Component 序列化来保持翻译键而不是直接 getString()
// 这样客户端可以根据自己的语言设置进行翻译
return Component.Serializer.toJson(group.name());
}
} catch (Exception e) {
// 忽略异常使用默认名称
}
return "样板供应器 #" + providerId;
}
/**
* 验证样板供应器是否可用
*
* @param providerId 供应器ID
* @param menu 样板访问终端菜单
* @return 是否可用
*/
public static boolean isProviderAvailable(long providerId, PatternAccessTermMenu menu) {
PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId);
if (container == null) {
return false;
}
// 检查是否在终端中可见
if (!container.isVisibleInTerminal()) {
return false;
}
// 检查是否连接到网络
return container.getGrid() != null;
}
/** 获取供应器显示名(优先组名) */
public static String getProviderDisplayName(PatternContainer container) {
if (container == null) return "未知供应器";
try {
var group = container.getTerminalGroup();
if (group != null) {
// 使用 Component 序列化来保持翻译键而不是直接 getString()
// 这样客户端可以根据自己的语言设置进行翻译
return Component.Serializer.toJson(group.name());
}
} catch (Throwable ignored) {
}
return "样板供应器";
}
/** 计算供应器空槽位数量 */
public static int getAvailableSlots(PatternContainer container) {
if (container == null) return -1;
InternalInventory inv = container.getTerminalPatternInventory();
if (inv == null) return -1;
int available = 0;
for (int i = 0; i < inv.size(); i++) {
if (inv.getStackInSlot(i).isEmpty()) available++;
}
return available;
}
}

View File

@ -0,0 +1,279 @@
package com.extendedae_plus.util.uploadPattern;
import appeng.api.crafting.IPatternDetails;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.core.definitions.AEItems;
import appeng.crafting.pattern.AECraftingPattern;
import appeng.crafting.pattern.AESmithingTablePattern;
import appeng.crafting.pattern.AEStonecuttingPattern;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot;
import com.extendedae_plus.content.matrix.UploadCoreBlockEntity;
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 与装配矩阵 (assembler matrix) 上传 / 检查 相关的工具方法
* 保留原有逻辑与容错行为
*/
public final class MatrixUploadUtil {
private MatrixUploadUtil() {}
/**
* 发送消息给玩家
*
* @param player 玩家
* @param message 消息内容
*/
private static void sendMessage(ServerPlayer player, String message) {
// 静默不再向玩家左下角发送任何提示信息
// 如需恢复取消下面注释即可
// if (player != null) {
// player.sendSystemMessage(Component.literal(message));
// }
// 如果玩家为null静默忽略用于测试环境
}
/**
* AE2 的图样编码终端菜单上传当前已编码图样 ExtendedAE 装配矩阵仅合成图样
* 不会处理处理图样
*
* @param player 服务器玩家
* @param menu PatternEncodingTermMenu
* @return 是否成功插入矩阵
*/
public static boolean uploadFromEncodingMenuToMatrix(ServerPlayer player, PatternEncodingTermMenu menu) {
if (player == null || menu == null) {
return false;
}
// 读取已编码槽位的物品
RestrictedInputSlot encodedSlot = ((PatternEncodingTermMenuAccessor)menu).eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
return false;
}
// 仅允许合成/锻造台/切石机图样
IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level());
if (!(details instanceof AECraftingPattern
|| details instanceof AESmithingTablePattern
|| details instanceof AEStonecuttingPattern)) {
return false;
}
// 获取 AE 网络
IGridNode node = menu.getNetworkNode();
if (node == null) {
return false;
}
IGrid grid = node.getGrid();
if (grid == null) {
return false;
}
// 在尝试上传之前检查装配矩阵是否已经存在相同样板物品与NBT完全一致
if (matrixContainsPattern(grid, stack)) {
// 直接提醒并跳过上传并将同等数量的空白样板放回空白样板槽否则退回玩家背包
if (player != null) {
player.sendSystemMessage(Component.literal("ExtendedAE Plus: 装配矩阵已存在相同样板,已跳过上传并返还空白样板"));
}
try {
var accessor = (PatternEncodingTermMenuAccessor) (Object) menu;
var blankSlot = accessor.eap$getBlankPatternSlot();
ItemStack blanks = AEItems.BLANK_PATTERN.stack(stack.getCount());
if (blankSlot != null && blankSlot.mayPlace(blanks)) {
ItemStack remain = blankSlot.safeInsert(blanks);
if (!remain.isEmpty() && player != null) {
player.getInventory().placeItemBackInInventory(remain, false);
}
} else if (player != null) {
player.getInventory().placeItemBackInInventory(blanks, false);
}
} catch (Throwable t) {
if (player != null) {
// 兜底直接还给玩家背包
player.getInventory().placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(stack.getCount()), false);
}
}
// 清空编码样板槽防止再次输出
encodedSlot.set(ItemStack.EMPTY);
return false;
}
// 收集所有可用的装配矩阵图样模块内部库存并逐一尝试遵循其过滤规则
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
if (!inventories.isEmpty()) {
for (int i = 0; i < inventories.size(); i++) {
var inv = inventories.get(i);
ItemStack toInsert = stack.copy();
ItemStack remain = inv.addItems(toInsert);
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");
return true;
}
}
// 所有内部库存都无法接收 -> 尝试 capability 回退
}
// 回退尝试 Forge 能力可能为聚合图样仓同样遍历所有矩阵
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
if (!handlers.isEmpty()) {
for (int i = 0; i < handlers.size(); i++) {
var cap = handlers.get(i);
ItemStack toInsert = stack.copy();
ItemStack remain = insertIntoAnySlot(cap, toInsert);
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");
return true;
}
}
}
// 未找到可用矩阵或全部拒收
if (inventories.isEmpty() && handlers.isEmpty()) {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix");
} else {
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full");
}
return false;
}
/**
* 在给定 AE Grid 中收集所有已成型且在线的装配矩阵图样模块的用于外部插入的内部库存
* 优先使用 TileAssemblerMatrixPattern#getExposedInventory仅允许插入且已带AE过滤规则
*/
private static List<InternalInventory> findAllMatrixPatternInventories(IGrid grid) {
List<InternalInventory> result = new ArrayList<>();
try {
var tiles = grid.getMachines(TileAssemblerMatrixPattern.class);
for (TileAssemblerMatrixPattern tile : tiles) {
if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) {
var inv = tile.getExposedInventory();
if (inv != null) {
result.add(inv);
}
}
}
} catch (Throwable t) {
}
return result;
}
/**
* 在给定 AE Grid 中收集所有已成型的装配矩阵的聚合图样仓 IItemHandler若可用
*/
private static List<IItemHandler> findAllMatrixPatternHandlers(IGrid grid) {
List<IItemHandler> result = new ArrayList<>();
try {
Set<TileAssemblerMatrixBase> matrices = grid.getMachines(TileAssemblerMatrixBase.class);
for (TileAssemblerMatrixBase tile : matrices) {
if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) {
var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null);
if (capOpt != null) {
var handler = capOpt.orElse(null);
if (handler != null) {
result.add(handler);
}
}
}
}
} catch (Throwable ignored) {
}
return result;
}
/**
* 检查装配矩阵所有已成型矩阵的图样仓中是否已存在与给定样板完全相同的物品含NBT
*/
private static boolean matrixContainsPattern(IGrid grid, ItemStack pattern) {
if (grid == null || pattern == null || pattern.isEmpty()) return false;
try {
// 先检查提供外部插入视图的内部库存
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
for (InternalInventory inv : inventories) {
if (inv == null) continue;
for (int i = 0; i < inv.size(); i++) {
ItemStack s = inv.getStackInSlot(i);
if (!s.isEmpty() && ItemStack.isSameItemSameTags(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
try {
// 再检查聚合能力视图
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
for (IItemHandler h : handlers) {
if (h == null) continue;
int slots = h.getSlots();
for (int i = 0; i < slots; i++) {
ItemStack s = h.getStackInSlot(i);
if (!s.isEmpty() && ItemStack.isSameItemSameTags(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
return false;
}
/**
* 判断给定矩阵集群中是否存在装配矩阵上传核心
* 要求至少存在 1 个即可不限制数量
* 传入任意属于该集群的 Tile Pattern/Crafter/Frame
*/
private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) {
try {
if (any == null || any.getCluster() == null) return false;
int cores = 0;
var it = any.getCluster().getBlockEntities();
while (it.hasNext()) {
var te = it.next();
if (te instanceof UploadCoreBlockEntity) {
cores++;
}
}
return cores >= 1; // 至少一个即可
} catch (Throwable t) {
return false;
}
}
/**
* 尝试将整个物品栈插入到 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;
}
}

View File

@ -0,0 +1,189 @@
package com.extendedae_plus.util.uploadPattern;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.menu.me.items.PatternEncodingTermMenu;
import net.minecraft.server.level.ServerPlayer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 与反射 / 菜单PatternAccessTermMenu相关的工具方法
* 包含获取 PatternAccessTermMenu通过反射获取 byId/container列出 provider id供应器显示名等
*/
public final class PatternTerminalUtil {
private PatternTerminalUtil() {}
/**
* 检查当前菜单是否为ExtendedAE的扩展样板管理终端
*
* @param player 玩家
* @return 是否为ExtendedAE扩展终端
*/
public static boolean isExtendedAETerminal(ServerPlayer player) {
if (player == null || player.containerMenu == null) {
return false;
}
String containerClassName = player.containerMenu.getClass().getName();
return containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal");
}
/**
* 通过服务器ID获取PatternContainer
* 兼容ExtendedAE的ContainerExPatternTerminal和原版PatternAccessTermMenu
*
* @param menu 样板访问终端菜单
* @param providerId 供应器服务器ID
* @return PatternContainer实例如果不存在则返回null
*/
public static PatternContainer getPatternContainerById(PatternAccessTermMenu menu, long providerId) {
try {
// 通过反射访问byId字段ExtendedAE继承了这个字段
Field byIdField = findByIdField(menu.getClass());
if (byIdField == null) {
System.err.println("ExtendedAE Plus: 无法找到byId字段");
return null;
}
byIdField.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Long, Object> byId = (Map<Long, Object>) byIdField.get(menu);
Object containerTracker = byId.get(providerId);
if (containerTracker == null) {
return null;
}
// 从ContainerTracker中获取PatternContainer
Field containerField = findContainerField(containerTracker.getClass());
if (containerField == null) {
System.err.println("ExtendedAE Plus: 无法找到container字段");
return null;
}
containerField.setAccessible(true);
return (PatternContainer) containerField.get(containerTracker);
} catch (Exception e) {
System.err.println("ExtendedAE Plus: 无法获取PatternContainer错误: " + e.getMessage());
return null;
}
}
/**
* 在类层次结构中查找byId字段
*/
public static Field findByIdField(Class<?> clazz) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField("byId");
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
/**
* 在类层次结构中查找container字段
*/
private static Field findContainerField(Class<?> clazz) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField("container");
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
/**
* 获取玩家当前的样板访问终端菜单支持ExtendedAE和原版AE2
*
* @param player 玩家
* @return PatternAccessTermMenu实例如果玩家没有打开则返回null
*/
public static PatternAccessTermMenu getPatternAccessMenu(ServerPlayer player) {
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;
}
/**
* 列出当前菜单中所有供应器的服务器ID原样返回 byId key 集合
*/
public static java.util.List<Long> getAllProviderIds(PatternAccessTermMenu menu) {
java.util.List<Long> result = new java.util.ArrayList<>();
if (menu == null) return result;
try {
Field byIdField = PatternTerminalUtil.findByIdField(menu.getClass());
if (byIdField == null) return result;
byIdField.setAccessible(true);
@SuppressWarnings("unchecked")
java.util.Map<Long, Object> byId = (java.util.Map<Long, Object>) byIdField.get(menu);
if (byId != null) {
result.addAll(byId.keySet());
}
} catch (Throwable ignored) {
}
return result;
}
/**
* 基于编码终端菜单的 AE Grid 遍历列出可在终端中可见且有空位的供应器容器
* 返回顺序稳定 grid machineClasses 顺序再按 activeMachines 迭代顺序
*/
public static List<PatternContainer> listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) {
List<PatternContainer> list = new ArrayList<>();
if (menu == null) return list;
try {
IGridNode node = menu.getNetworkNode();
if (node == null) return list;
IGrid grid = node.getGrid();
if (grid == null) return list;
for (var machineClass : grid.getMachineClasses()) {
if (PatternContainer.class.isAssignableFrom(machineClass)) {
@SuppressWarnings("unchecked")
Class<? extends PatternContainer> containerClass = (Class<? extends PatternContainer>) machineClass;
for (var container : grid.getActiveMachines(containerClass)) {
if (container == null || !container.isVisibleInTerminal()) continue;
InternalInventory inv = container.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
boolean hasEmpty = false;
for (int i = 0; i < inv.size(); i++) {
if (inv.getStackInSlot(i).isEmpty()) { hasEmpty = true; break; }
}
if (hasEmpty) list.add(container);
}
}
}
} catch (Throwable ignored) {
}
return list;
}
}

View File

@ -0,0 +1,238 @@
package com.extendedae_plus.util.uploadPattern;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.inventories.InternalInventory;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.util.inv.FilteredInternalInventory;
import appeng.util.inv.filter.IAEItemFilter;
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
import com.extendedae_plus.util.PatternProviderDataUtil;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import java.util.List;
/**
* 与样板供应器provider上传相关的工具类
* - uploadPatternToProvider (从玩家背包上传)
* - uploadFromEncodingMenuToProvider (从编码终端上传至指定 providerId)
* - uploadFromEncodingMenuToProviderByIndex (按网格顺序 index 上传)
*
* 其中使用 PatternTerminalUtil 提供的反射/容器访问工具
*/
public final class ProviderUploadUtil {
private ProviderUploadUtil() {}
/**
* 发送消息给玩家
*
* @param player 玩家
* @param message 消息内容
*/
private static void sendMessage(ServerPlayer player, String message) {
// 静默不再向玩家左下角发送任何提示信息
// 如需恢复取消下面注释即可
// if (player != null) {
// player.sendSystemMessage(Component.literal(message));
// }
// 如果玩家为null静默忽略用于测试环境
}
/**
* 将玩家背包中的样板上传到指定的样板供应器
* 兼容ExtendedAE和原版AE2
*
* @param player 玩家
* @param playerSlotIndex 玩家背包槽位索引
* @param providerId 目标样板供应器的服务器ID
* @return 是否上传成功
*/
public static boolean uploadPatternToProvider(ServerPlayer player, int playerSlotIndex, long providerId) {
// 1. 验证玩家是否打开了样板访问终端
PatternAccessTermMenu menu = PatternTerminalUtil.getPatternAccessMenu(player);
if (menu == null) {
sendMessage(player, "ExtendedAE Plus: 请先打开样板访问终端或扩展样板管理终端");
return false;
}
// 2. 获取玩家背包中的物品
ItemStack playerItem = player.getInventory().getItem(playerSlotIndex);
if (playerItem.isEmpty()) {
sendMessage(player, "ExtendedAE Plus: 背包槽位为空");
return false;
}
// 3. 验证是否是编码样板
if (!PatternDetailsHelper.isEncodedPattern(playerItem)) {
sendMessage(player, "ExtendedAE Plus: 该物品不是有效的编码样板");
return false;
}
// 4. 获取目标样板供应器
PatternContainer patternContainer = PatternTerminalUtil.getPatternContainerById(menu, providerId);
if (patternContainer == null) {
sendMessage(player, "ExtendedAE Plus: 找不到指定的样板供应器 (ID: " + providerId + ")");
return false;
}
// 5. 获取样板供应器的库存
InternalInventory patternInventory = patternContainer.getTerminalPatternInventory();
if (patternInventory == null) {
sendMessage(player, "ExtendedAE Plus: 无法访问样板供应器的库存");
return false;
}
// 6. 使用AE2的标准样板过滤器进行插入
var patternFilter = new ExtendedAEPatternFilter();
var filteredInventory = new FilteredInternalInventory(patternInventory, patternFilter);
// 7. 尝试插入样板
ItemStack itemToInsert = playerItem.copy();
ItemStack remaining = filteredInventory.addItems(itemToInsert);
if (remaining.getCount() < itemToInsert.getCount()) {
// 插入成功部分或全部
int insertedCount = itemToInsert.getCount() - remaining.getCount();
playerItem.shrink(insertedCount);
if (playerItem.isEmpty()) {
player.getInventory().setItem(playerSlotIndex, ItemStack.EMPTY);
}
String terminalType = PatternTerminalUtil.isExtendedAETerminal(player) ? "扩展样板管理终端" : "样板访问终端";
sendMessage(player, "ExtendedAE Plus: 通过" + terminalType + "成功上传 " + insertedCount + " 个样板");
return true;
} else {
sendMessage(player, "ExtendedAE Plus: 上传失败 - 样板供应器已满或样板无效");
return false;
}
}
/**
* 将图样编码终端的已编码图样上传到指定的样板供应器通过 providerId 定位
*/
public static boolean uploadFromEncodingMenuToProvider(ServerPlayer player, PatternEncodingTermMenu menu, long providerId) {
if (player == null || menu == null) {
return false;
}
var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu)
.eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
return false;
}
PatternAccessTermMenu accessMenu = PatternTerminalUtil.getPatternAccessMenu(player);
if (accessMenu == null) {
return false;
}
// 先确定目标容器名称用于同名回退
String targetName = PatternProviderDataUtil.getProviderDisplayName(providerId, accessMenu);
// 构建尝试顺序先指定ID其次同名的其他ID
java.util.List<Long> tryIds = new java.util.ArrayList<>();
tryIds.add(providerId);
try {
java.util.List<Long> all = PatternTerminalUtil.getAllProviderIds(accessMenu);
for (Long id : all) {
if (id == null || id == providerId) continue;
String name = PatternProviderDataUtil.getProviderDisplayName(id, accessMenu);
if (name != null && name.equals(targetName)) {
tryIds.add(id);
}
}
} catch (Throwable ignored) {}
// 按顺序逐个尝试插入
for (Long id : tryIds) {
PatternContainer c = PatternTerminalUtil.getPatternContainerById(accessMenu, id);
if (c == null || !c.isVisibleInTerminal()) continue;
InternalInventory inv = c.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter());
ItemStack toInsert = stack.copy();
ItemStack remain = filtered.addItems(toInsert);
if (remain.getCount() < toInsert.getCount()) {
int inserted = toInsert.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
} else {
encodedSlot.set(stack);
}
return true;
}
}
return false;
}
/**
* 基于索引的定向上传使用 listAvailableProvidersFromGrid(menu) 的顺序
* 将编码槽样板插入到第 index 个供应器
*/
public static boolean uploadFromEncodingMenuToProviderByIndex(ServerPlayer player, PatternEncodingTermMenu menu, int index) {
if (player == null || menu == null || index < 0) return false;
List<PatternContainer> list = PatternTerminalUtil.listAvailableProvidersFromGrid(menu);
if (index >= list.size()) return false;
var container = list.get(index);
if (container == null) return false;
var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu)
.eap$getEncodedPatternSlot();
ItemStack stack = encodedSlot.getItem();
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
return false;
}
// 以名称为键同名供应器依次尝试 index 指定的再同名的其他
String targetName = PatternProviderDataUtil.getProviderDisplayName(container);
java.util.List<PatternContainer> tryList = new java.util.ArrayList<>();
tryList.add(container);
try {
for (PatternContainer c : list) {
if (c == null || c == container) continue;
String name = PatternProviderDataUtil.getProviderDisplayName(c);
if (name != null && name.equals(targetName)) {
tryList.add(c);
}
}
} catch (Throwable ignored) {}
for (PatternContainer c : tryList) {
InternalInventory inv = c.getTerminalPatternInventory();
if (inv == null || inv.size() <= 0) continue;
var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter());
ItemStack toInsert = stack.copy();
ItemStack remain = filtered.addItems(toInsert);
if (remain.getCount() < toInsert.getCount()) {
int inserted = toInsert.getCount() - remain.getCount();
stack.shrink(inserted);
if (stack.isEmpty()) {
encodedSlot.set(ItemStack.EMPTY);
} else {
encodedSlot.set(stack);
}
return true;
}
}
return false;
}
/**
* ExtendedAE兼容的样板过滤器
* 使用AE2的PatternDetailsHelper进行样板验证
*/
private static class ExtendedAEPatternFilter implements IAEItemFilter {
@Override
public boolean allowExtract(InternalInventory inv, int slot, int amount) {
return true;
}
@Override
public boolean allowInsert(InternalInventory inv, int slot, ItemStack stack) {
return !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack);
}
}
}