分离原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.me.items.PatternEncodingTermMenu;
|
||||||
import appeng.menu.slot.RestrictedInputSlot;
|
import appeng.menu.slot.RestrictedInputSlot;
|
||||||
import appeng.parts.encoding.EncodingMode;
|
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.IActionHolder;
|
||||||
import com.glodblock.github.glodium.network.packet.sync.Paras;
|
import com.glodblock.github.glodium.network.packet.sync.Paras;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
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;
|
var stack = this.encodedPatternSlot != null ? this.encodedPatternSlot.getItem() : net.minecraft.world.item.ItemStack.EMPTY;
|
||||||
if (stack != null && !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack)) {
|
if (stack != null && !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack)) {
|
||||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
|
MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
|
||||||
} else {
|
} else {
|
||||||
// 槽位可能尚未同步到位,继续下一 tick 重试
|
// 槽位可能尚未同步到位,继续下一 tick 重试
|
||||||
if (attemptsLeft > 0) {
|
if (attemptsLeft > 0) {
|
||||||
|
|
@ -105,7 +105,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
|
||||||
// 为避免与 AE2 后续同步竞争,切到下一 tick 执行
|
// 为避免与 AE2 后续同步竞争,切到下一 tick 执行
|
||||||
sp.server.execute(() -> {
|
sp.server.execute(() -> {
|
||||||
try {
|
try {
|
||||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
|
MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.extendedae_plus.mixin.ae2WTlib;
|
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.ContainerUWirelessExPAT;
|
||||||
import com.glodblock.github.extendedae.xmod.wt.HostUWirelessExPAT;
|
import com.glodblock.github.extendedae.xmod.wt.HostUWirelessExPAT;
|
||||||
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
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));
|
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));
|
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
|
||||||
var sp = (ServerPlayer) this.eap$player;
|
var sp = (ServerPlayer) this.eap$player;
|
||||||
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.extendedae.container;
|
||||||
|
|
||||||
import appeng.api.util.IConfigurableObject;
|
import appeng.api.util.IConfigurableObject;
|
||||||
import appeng.menu.guisync.GuiSync;
|
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.extendedae.container.ContainerExPatternTerminal;
|
||||||
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
||||||
import com.glodblock.github.glodium.network.packet.sync.Paras;
|
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));
|
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));
|
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
|
||||||
var sp = (ServerPlayer) this.epp$player;
|
var sp = (ServerPlayer) this.epp$player;
|
||||||
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.extendedae_plus.mixin.extendedae.container;
|
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.common.me.itemhost.HostWirelessExPAT;
|
||||||
import com.glodblock.github.extendedae.container.ContainerWirelessExPAT;
|
import com.glodblock.github.extendedae.container.ContainerWirelessExPAT;
|
||||||
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
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));
|
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));
|
long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1));
|
||||||
var sp = (ServerPlayer) this.epp$player;
|
var sp = (ServerPlayer) this.epp$player;
|
||||||
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.extendedae_plus.network;
|
package com.extendedae_plus.network;
|
||||||
|
|
||||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
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.network.FriendlyByteBuf;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
@ -36,10 +36,10 @@ public class UploadEncodedPatternToProviderC2SPacket {
|
||||||
// 1) providerId >= 0: 访问终端 byId 模式
|
// 1) providerId >= 0: 访问终端 byId 模式
|
||||||
// 2) providerId < 0: 索引模式(由列表回退路径生成),index = -1 - providerId
|
// 2) providerId < 0: 索引模式(由列表回退路径生成),index = -1 - providerId
|
||||||
if (msg.providerId >= 0) {
|
if (msg.providerId >= 0) {
|
||||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
|
ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
|
||||||
} else {
|
} else {
|
||||||
int index = (int) (-1L - msg.providerId);
|
int index = (int) (-1L - msg.providerId);
|
||||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
|
ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ctx.setPacketHandled(true);
|
ctx.setPacketHandled(true);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import appeng.helpers.patternprovider.PatternContainer;
|
||||||
import appeng.menu.implementations.PatternAccessTermMenu;
|
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||||
import com.extendedae_plus.init.ModNetwork;
|
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.network.FriendlyByteBuf;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
@ -31,20 +32,20 @@ public class RequestProvidersListC2SPacket {
|
||||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
|
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
|
||||||
|
|
||||||
// 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID)
|
// 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID)
|
||||||
PatternAccessTermMenu accessMenu = ExtendedAEPatternUploadUtil.getPatternAccessMenu(player);
|
PatternAccessTermMenu accessMenu = PatternTerminalUtil.getPatternAccessMenu(player);
|
||||||
if (accessMenu != null) {
|
if (accessMenu != null) {
|
||||||
List<Long> ids = ExtendedAEPatternUploadUtil.getAllProviderIds(accessMenu);
|
List<Long> ids = PatternTerminalUtil.getAllProviderIds(accessMenu);
|
||||||
List<Long> filteredIds = new ArrayList<>();
|
List<Long> filteredIds = new ArrayList<>();
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
List<Integer> slots = new ArrayList<>();
|
List<Integer> slots = new ArrayList<>();
|
||||||
|
|
||||||
for (Long id : ids) {
|
for (Long id : ids) {
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
if (!ExtendedAEPatternUploadUtil.isProviderAvailable(id, accessMenu)) continue;
|
if (!PatternProviderDataUtil.isProviderAvailable(id, accessMenu)) continue;
|
||||||
int empty = ExtendedAEPatternUploadUtil.getAvailableSlots(id, accessMenu);
|
int empty = PatternProviderDataUtil.getAvailableSlots(id, accessMenu);
|
||||||
if (empty <= 0) continue; // 只列出有空位的
|
if (empty <= 0) continue; // 只列出有空位的
|
||||||
filteredIds.add(id);
|
filteredIds.add(id);
|
||||||
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu));
|
names.add(PatternProviderDataUtil.getProviderDisplayName(id, accessMenu));
|
||||||
slots.add(empty);
|
slots.add(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,18 +54,18 @@ public class RequestProvidersListC2SPacket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退:基于编码终端所在网络枚举供应器,用“负数ID编码索引”:encodedId = -1 - index
|
// 回退:基于编码终端所在网络枚举供应器,用“负数ID编码索引”:encodedId = -1 - index
|
||||||
List<PatternContainer> containers = ExtendedAEPatternUploadUtil.listAvailableProvidersFromGrid(encMenu);
|
List<PatternContainer> containers = PatternTerminalUtil.listAvailableProvidersFromGrid(encMenu);
|
||||||
List<Long> idxIds = new ArrayList<>();
|
List<Long> idxIds = new ArrayList<>();
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
List<Integer> slots = new ArrayList<>();
|
List<Integer> slots = new ArrayList<>();
|
||||||
for (int i = 0; i < containers.size(); i++) {
|
for (int i = 0; i < containers.size(); i++) {
|
||||||
var c = containers.get(i);
|
var c = containers.get(i);
|
||||||
if (c == null) continue;
|
if (c == null) continue;
|
||||||
int empty = ExtendedAEPatternUploadUtil.getAvailableSlots(c);
|
int empty = PatternProviderDataUtil.getAvailableSlots(c);
|
||||||
if (empty <= 0) continue;
|
if (empty <= 0) continue;
|
||||||
long encodedId = -1L - i; // 约定:负数代表按索引
|
long encodedId = -1L - i; // 约定:负数代表按索引
|
||||||
idxIds.add(encodedId);
|
idxIds.add(encodedId);
|
||||||
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
|
names.add(PatternProviderDataUtil.getProviderDisplayName(c));
|
||||||
slots.add(empty);
|
slots.add(empty);
|
||||||
}
|
}
|
||||||
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
|
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.crafting.PatternDetailsHelper;
|
||||||
import appeng.api.inventories.InternalInventory;
|
import appeng.api.inventories.InternalInventory;
|
||||||
import appeng.api.networking.IGrid;
|
import appeng.api.networking.IGrid;
|
||||||
|
import appeng.helpers.patternprovider.PatternContainer;
|
||||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||||
|
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
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.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
@ -113,4 +117,112 @@ public class PatternProviderDataUtil {
|
||||||
}
|
}
|
||||||
return null;
|
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