分离原PatternUploadUtil类,便于对不同模块的维护
This commit is contained in:
parent
cfd278eb52
commit
48e8b01384
|
|
@ -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) {
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
4
src/main/java/com/extendedae_plus/util/AEMenUtil.java
Normal file
4
src/main/java/com/extendedae_plus/util/AEMenUtil.java
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
public class AEMenUtil {
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user