为样板编码终端添加上传供应器选择按钮
This commit is contained in:
parent
2dbea6c15c
commit
92dd02ea85
|
|
@ -0,0 +1,155 @@
|
|||
package com.extendedae_plus.client.ui;
|
||||
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 简单的供应器选择弹窗。
|
||||
* 展示若干个可点击的供应器条目,点击后发送带 providerId 的上传请求。
|
||||
*/
|
||||
public class ProviderSelectScreen extends Screen {
|
||||
private final Screen parent;
|
||||
// 原始数据
|
||||
private final List<Long> ids;
|
||||
private final List<String> names;
|
||||
private final List<Integer> emptySlots;
|
||||
|
||||
// 分组后的数据(同名合并)
|
||||
private final List<Long> gIds = new ArrayList<>(); // 代表条目使用的 providerId:选择空位数最多的那个
|
||||
private final List<String> gNames = new ArrayList<>(); // 分组名(供应器名称)
|
||||
private final List<Integer> gTotalSlots = new ArrayList<>(); // 该名称下供应器空位总和
|
||||
private final List<Integer> gCount = new ArrayList<>(); // 该名称下供应器数量
|
||||
|
||||
private int page = 0;
|
||||
private static final int PAGE_SIZE = 6;
|
||||
|
||||
private final List<Button> entryButtons = new ArrayList<>();
|
||||
|
||||
public ProviderSelectScreen(Screen parent, List<Long> ids, List<String> names, List<Integer> emptySlots) {
|
||||
super(Component.translatable("extendedae_plus.screen.choose_provider.title"));
|
||||
this.parent = parent;
|
||||
this.ids = ids;
|
||||
this.names = names;
|
||||
this.emptySlots = emptySlots;
|
||||
buildGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
this.clearWidgets();
|
||||
entryButtons.clear();
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(start + PAGE_SIZE, gIds.size());
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 - 70;
|
||||
int buttonWidth = 240;
|
||||
int buttonHeight = 20;
|
||||
int gap = 5;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
int idx = i;
|
||||
String label = buildLabel(idx);
|
||||
Button btn = Button.builder(Component.literal(label), b -> onChoose(idx))
|
||||
.bounds(centerX - buttonWidth / 2, startY + (i - start) * (buttonHeight + gap), buttonWidth, buttonHeight)
|
||||
.build();
|
||||
entryButtons.add(btn);
|
||||
this.addRenderableWidget(btn);
|
||||
}
|
||||
|
||||
// 分页按钮
|
||||
int navY = startY + PAGE_SIZE * (buttonHeight + gap) + 10;
|
||||
Button prev = Button.builder(Component.literal("<"), b -> changePage(-1))
|
||||
.bounds(centerX - 60, navY, 20, 20)
|
||||
.build();
|
||||
Button next = Button.builder(Component.literal(">"), b -> changePage(1))
|
||||
.bounds(centerX + 40, navY, 20, 20)
|
||||
.build();
|
||||
prev.active = page > 0;
|
||||
next.active = (page + 1) * PAGE_SIZE < gIds.size();
|
||||
this.addRenderableWidget(prev);
|
||||
this.addRenderableWidget(next);
|
||||
|
||||
// 关闭按钮
|
||||
Button close = Button.builder(Component.translatable("gui.cancel"), b -> onClose())
|
||||
.bounds(centerX - 40, navY + 30, 80, 20)
|
||||
.build();
|
||||
this.addRenderableWidget(close);
|
||||
}
|
||||
|
||||
private void changePage(int delta) {
|
||||
int newPage = page + delta;
|
||||
if (newPage < 0) return;
|
||||
if (newPage * PAGE_SIZE >= Math.max(1, gIds.size())) return;
|
||||
page = newPage;
|
||||
init();
|
||||
}
|
||||
|
||||
private String buildLabel(int idx) {
|
||||
String name = gNames.get(idx);
|
||||
int totalSlots = gTotalSlots.get(idx);
|
||||
int count = gCount.get(idx);
|
||||
// 不显示具体 id,显示合并统计:名称(总空位)x数量
|
||||
return name + " (" + totalSlots + ") x" + count;
|
||||
}
|
||||
|
||||
private void onChoose(int idx) {
|
||||
if (idx < 0 || idx >= gIds.size()) return;
|
||||
long providerId = gIds.get(idx);
|
||||
ModNetwork.CHANNEL.sendToServer(new UploadEncodedPatternToProviderC2SPacket(providerId));
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
Minecraft.getInstance().setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void buildGroups() {
|
||||
// 使用 LinkedHashMap 保持首次出现顺序
|
||||
Map<String, Group> map = new LinkedHashMap<>();
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
String name = names.get(i);
|
||||
long id = ids.get(i);
|
||||
int slots = emptySlots.get(i);
|
||||
Group g = map.computeIfAbsent(name, k -> new Group());
|
||||
g.count++;
|
||||
g.totalSlots += Math.max(0, slots);
|
||||
// 挑选空位最多的作为代表 id;若并列,保留先到者
|
||||
if (slots > g.bestSlots) {
|
||||
g.bestSlots = slots;
|
||||
g.bestId = id;
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Group> e : map.entrySet()) {
|
||||
String name = e.getKey();
|
||||
Group g = e.getValue();
|
||||
gNames.add(name);
|
||||
gIds.add(g.bestId);
|
||||
gTotalSlots.add(g.totalSlots);
|
||||
gCount.add(g.count);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Group {
|
||||
long bestId = Long.MIN_VALUE;
|
||||
int bestSlots = Integer.MIN_VALUE;
|
||||
int totalSlots = 0;
|
||||
int count = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.extendedae_plus.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
|
||||
import appeng.client.gui.Icon;
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.me.items.PatternEncodingTermScreen;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.widgets.IconButton;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
||||
|
||||
/**
|
||||
* 在图样编码终端右侧工具栏加入一个上传按钮:
|
||||
* 点击后把当前“已编码样板”上传到任意可用的样板供应器(服务端自动选择)。
|
||||
*/
|
||||
@Mixin(AEBaseScreen.class)
|
||||
public abstract class PatternEncodingTermScreenMixin<T extends AEBaseMenu> {
|
||||
|
||||
// 使用 @Shadow 访问 AEBaseScreen 的受保护方法
|
||||
@Shadow
|
||||
protected abstract <B extends Button> B addToLeftToolbar(B button);
|
||||
|
||||
@Unique
|
||||
private boolean extendedae_plus$uploadBtnAdded = false;
|
||||
|
||||
@Inject(method = "init", at = @At("HEAD"))
|
||||
private void extendedae_plus$addUploadButton(CallbackInfo ci) {
|
||||
// 仅在图样编码终端界面中添加按钮
|
||||
if (!(((Object) this) instanceof PatternEncodingTermScreen)) {
|
||||
return;
|
||||
}
|
||||
// 幂等:避免每次 init() 都重复添加按钮
|
||||
if (extendedae_plus$uploadBtnAdded) {
|
||||
return;
|
||||
}
|
||||
var uploadBtn = new IconButton(btn -> ModNetwork.CHANNEL
|
||||
.sendToServer(new com.extendedae_plus.network.RequestProvidersListC2SPacket())) {
|
||||
@Override
|
||||
protected Icon getIcon() {
|
||||
return Icon.ARROW_UP;
|
||||
}
|
||||
};
|
||||
uploadBtn.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.button.choose_provider")));
|
||||
|
||||
// 直接调用 @Shadow 方法,避免跨包 protected 访问问题
|
||||
this.addToLeftToolbar(uploadBtn);
|
||||
extendedae_plus$uploadBtnAdded = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import net.minecraftforge.network.NetworkDirection;
|
|||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket;
|
||||
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
||||
|
||||
public class ModNetwork {
|
||||
private static final String PROTOCOL_VERSION = "1";
|
||||
|
|
@ -37,6 +38,24 @@ public class ModNetwork {
|
|||
.decoder(PullFromJeiOrCraftC2SPacket::decode)
|
||||
.consumerNetworkThread(PullFromJeiOrCraftC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(UploadEncodedPatternToProviderC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(UploadEncodedPatternToProviderC2SPacket::encode)
|
||||
.decoder(UploadEncodedPatternToProviderC2SPacket::decode)
|
||||
.consumerNetworkThread(UploadEncodedPatternToProviderC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(RequestProvidersListC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(RequestProvidersListC2SPacket::encode)
|
||||
.decoder(RequestProvidersListC2SPacket::decode)
|
||||
.consumerNetworkThread(RequestProvidersListC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(ProvidersListS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(ProvidersListS2CPacket::encode)
|
||||
.decoder(ProvidersListS2CPacket::decode)
|
||||
.consumerNetworkThread(ProvidersListS2CPacket::handle)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static int nextId() { return id++; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.extendedae_plus.client.ui.ProviderSelectScreen;
|
||||
|
||||
/**
|
||||
* S2C: 返回可见且有空位的样板供应器列表,客户端弹窗展示供用户选择。
|
||||
*/
|
||||
public class ProvidersListS2CPacket {
|
||||
private final List<Long> ids;
|
||||
private final List<String> names;
|
||||
private final List<Integer> emptySlots;
|
||||
|
||||
public ProvidersListS2CPacket(List<Long> ids, List<String> names, List<Integer> emptySlots) {
|
||||
this.ids = ids;
|
||||
this.names = names;
|
||||
this.emptySlots = emptySlots;
|
||||
}
|
||||
|
||||
public static void encode(ProvidersListS2CPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(msg.ids.size());
|
||||
for (int i = 0; i < msg.ids.size(); i++) {
|
||||
buf.writeLong(msg.ids.get(i));
|
||||
buf.writeUtf(msg.names.get(i));
|
||||
buf.writeVarInt(msg.emptySlots.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static ProvidersListS2CPacket decode(FriendlyByteBuf buf) {
|
||||
int size = buf.readVarInt();
|
||||
List<Long> ids = new ArrayList<>(size);
|
||||
List<String> names = new ArrayList<>(size);
|
||||
List<Integer> slots = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ids.add(buf.readLong());
|
||||
names.add(buf.readUtf());
|
||||
slots.add(buf.readVarInt());
|
||||
}
|
||||
return new ProvidersListS2CPacket(ids, names, slots);
|
||||
}
|
||||
|
||||
public static void handle(ProvidersListS2CPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> handleClient(msg));
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private static void handleClient(ProvidersListS2CPacket msg) {
|
||||
var mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
System.out.println("[EAE+][Client] ProvidersListS2C received, size=" + msg.ids.size());
|
||||
var current = mc.screen;
|
||||
mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||
import appeng.helpers.patternprovider.PatternContainer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
|
||||
|
||||
/**
|
||||
* C2S: 请求当前终端可见的样板供应器列表(用于弹窗选择)。
|
||||
*/
|
||||
public class RequestProvidersListC2SPacket {
|
||||
public RequestProvidersListC2SPacket() {}
|
||||
|
||||
public static void encode(RequestProvidersListC2SPacket msg, FriendlyByteBuf buf) {}
|
||||
|
||||
public static RequestProvidersListC2SPacket decode(FriendlyByteBuf buf) { return new RequestProvidersListC2SPacket(); }
|
||||
|
||||
public static void handle(RequestProvidersListC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
|
||||
System.out.println("[EAE+][Server] RequestProvidersListC2S from " + player.getGameProfile().getName());
|
||||
|
||||
// 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID)
|
||||
PatternAccessTermMenu accessMenu = ExtendedAEPatternUploadUtil.getPatternAccessMenu(player);
|
||||
if (accessMenu != null) {
|
||||
List<Long> ids = ExtendedAEPatternUploadUtil.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 (empty <= 0) continue; // 只列出有空位的
|
||||
filteredIds.add(id);
|
||||
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu));
|
||||
slots.add(empty);
|
||||
}
|
||||
|
||||
System.out.println("[EAE+][Server] Providers via accessMenu: size=" + filteredIds.size());
|
||||
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(filteredIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
|
||||
return;
|
||||
}
|
||||
|
||||
// 回退:基于编码终端所在网络枚举供应器,用“负数ID编码索引”:encodedId = -1 - index
|
||||
List<PatternContainer> containers = ExtendedAEPatternUploadUtil.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);
|
||||
if (empty <= 0) continue;
|
||||
long encodedId = -1L - i; // 约定:负数代表按索引
|
||||
idxIds.add(encodedId);
|
||||
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
|
||||
slots.add(empty);
|
||||
}
|
||||
System.out.println("[EAE+][Server] Providers via grid-fallback: size=" + idxIds.size());
|
||||
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
|
||||
|
||||
/**
|
||||
* C2S: 请求将图样编码终端的已编码样板上传到指定的样板供应器(由客户端选择)。
|
||||
*/
|
||||
public class UploadEncodedPatternToProviderC2SPacket {
|
||||
private final long providerId;
|
||||
|
||||
public UploadEncodedPatternToProviderC2SPacket(long providerId) {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public static void encode(UploadEncodedPatternToProviderC2SPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeLong(msg.providerId);
|
||||
}
|
||||
|
||||
public static UploadEncodedPatternToProviderC2SPacket decode(FriendlyByteBuf buf) {
|
||||
return new UploadEncodedPatternToProviderC2SPacket(buf.readLong());
|
||||
}
|
||||
|
||||
public static void handle(UploadEncodedPatternToProviderC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
|
||||
// 支持两种模式:
|
||||
// 1) providerId >= 0: 访问终端 byId 模式
|
||||
// 2) providerId < 0: 索引模式(由列表回退路径生成),index = -1 - providerId
|
||||
if (msg.providerId >= 0) {
|
||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
|
||||
} else {
|
||||
int index = (int) (-1L - msg.providerId);
|
||||
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,14 @@ package com.extendedae_plus.util;
|
|||
|
||||
import appeng.api.inventories.InternalInventory;
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
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 appeng.crafting.pattern.AECraftingPattern;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import appeng.util.inv.FilteredInternalInventory;
|
||||
import appeng.util.inv.filter.IAEItemFilter;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
|
@ -18,10 +22,6 @@ import java.util.Set;
|
|||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.crafting.pattern.AECraftingPattern;
|
||||
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
|
||||
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
|
@ -632,4 +632,238 @@ public class ExtendedAEPatternUploadUtil {
|
|||
return "未知终端类型";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 AE2 的图样编码终端菜单上传当前“已编码图样”至当前网络中任意可用的样板供应器。
|
||||
* 策略:
|
||||
* 1) 仅当 encoded 槽位存在有效编码样板时执行;
|
||||
* 2) 通过 menu.getNetworkNode() 获取 IGrid,遍历在线的 PatternContainer;
|
||||
* 3) 仅选择在终端中可见(isVisibleInTerminal)且库存存在空位的供应器;
|
||||
* 4) 使用 AE2 的标准 FilteredInternalInventory + Pattern 过滤器尝试插入;
|
||||
* 5) 成功后清空 encoded 槽位,返回 true;否则返回 false。
|
||||
*/
|
||||
public static boolean uploadFromEncodingMenuToAnyProvider(ServerPlayer player, PatternEncodingTermMenu menu) {
|
||||
if (player == null || menu == null) {
|
||||
return false;
|
||||
}
|
||||
// 读取已编码槽位的物品(通过 accessor)
|
||||
var encodedSlot = ((com.extendedae_plus.mixin.accessor.PatternEncodingTermMenuAccessor) (Object) menu)
|
||||
.epp$getEncodedPatternSlot();
|
||||
ItemStack stack = encodedSlot.getItem();
|
||||
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 AE 网络
|
||||
IGridNode node = menu.getNetworkNode();
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
IGrid grid = node.getGrid();
|
||||
if (grid == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历在线的 PatternContainer,寻找第一个可见且有空位的供应器
|
||||
try {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 按 AE2 样板过滤规则尝试插入
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 忽略异常以避免噪声
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图样编码终端的“已编码图样”上传到指定的样板供应器(通过 providerId 定位)。
|
||||
*/
|
||||
public static boolean uploadFromEncodingMenuToProvider(ServerPlayer player, PatternEncodingTermMenu menu, long providerId) {
|
||||
if (player == null || menu == null) {
|
||||
return false;
|
||||
}
|
||||
var encodedSlot = ((com.extendedae_plus.mixin.accessor.PatternEncodingTermMenuAccessor) (Object) menu)
|
||||
.epp$getEncodedPatternSlot();
|
||||
ItemStack stack = encodedSlot.getItem();
|
||||
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PatternAccessTermMenu accessMenu = getPatternAccessMenu(player);
|
||||
if (accessMenu == null) {
|
||||
return false;
|
||||
}
|
||||
PatternContainer container = getPatternContainerById(accessMenu, providerId);
|
||||
if (container == null || !container.isVisibleInTerminal()) {
|
||||
return false;
|
||||
}
|
||||
InternalInventory inv = container.getTerminalPatternInventory();
|
||||
if (inv == null || inv.size() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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) return group.name().getString();
|
||||
} 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 = ((com.extendedae_plus.mixin.accessor.PatternEncodingTermMenuAccessor) (Object) menu)
|
||||
.epp$getEncodedPatternSlot();
|
||||
ItemStack stack = encodedSlot.getItem();
|
||||
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InternalInventory inv = container.getTerminalPatternInventory();
|
||||
if (inv == null || inv.size() <= 0) return false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"SlotGridLayoutMixin",
|
||||
"GuiExPatternTerminalMixin",
|
||||
"HighlightButtonMixin",
|
||||
"PickFromWirelessMixin"
|
||||
"PickFromWirelessMixin",
|
||||
"PatternEncodingTermScreenMixin"
|
||||
],
|
||||
"mixins": [
|
||||
"ContainerExPatternProviderMixin",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user