处理书签分支完成
This commit is contained in:
parent
ab7664e4a6
commit
b87b74929e
|
|
@ -5,8 +5,10 @@ import com.extendedae_plus.client.ModKeybindings;
|
|||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import com.extendedae_plus.network.pattern.CreateCtrlQPatternC2SPacket;
|
||||
import com.extendedae_plus.network.provider.RequestProvidersListC2SPacket;
|
||||
import com.extendedae_plus.util.RecipeFinderUtil;
|
||||
import com.extendedae_plus.util.RecipeInfo;
|
||||
import com.extendedae_plus.util.uploadPattern.RecipeTypeNameConfig;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
|
|
@ -15,6 +17,7 @@ import net.minecraft.client.gui.screens.Screen;
|
|||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ScreenEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
|
@ -273,7 +276,112 @@ public class CtrlQPatternKeyHandler {
|
|||
* @param recipeBookmark 配方书签对象
|
||||
*/
|
||||
private static void handleProcessingRecipeBookmark(Object recipeBookmark) {
|
||||
System.out.println("chuli");
|
||||
try {
|
||||
var getRecipeUidMethod = recipeBookmark.getClass().getMethod("getRecipeUid");
|
||||
net.minecraft.resources.ResourceLocation recipeId =
|
||||
(net.minecraft.resources.ResourceLocation) getRecipeUidMethod.invoke(recipeBookmark);
|
||||
if (recipeId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var recipeManager = mc.level.getRecipeManager();
|
||||
var recipeOpt = recipeManager.byKey(recipeId);
|
||||
if (recipeOpt.isEmpty()) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(
|
||||
Component.translatable("message.extendedae_plus.recipe_not_found"),
|
||||
true
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Object recipeBase = null;
|
||||
try {
|
||||
var getRecipeMethod = recipeBookmark.getClass().getMethod("getRecipe");
|
||||
recipeBase = getRecipeMethod.invoke(recipeBookmark);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
setLastProcessingNameFromRecipe(recipeBase != null ? recipeBase : recipeOpt.get());
|
||||
|
||||
List<RecipeInfo> recipeInfos = RecipeFinderUtil.findRecipesByIngredient(
|
||||
JeiRuntimeProxy.getIngredientUnderMouse().orElse(null)
|
||||
);
|
||||
|
||||
if (recipeInfos.isEmpty()) {
|
||||
var getRecipeOutputMethod = recipeBookmark.getClass().getMethod("getRecipeOutput");
|
||||
Object recipeOutput = getRecipeOutputMethod.invoke(recipeBookmark);
|
||||
if (recipeOutput instanceof ITypedIngredient<?> typedIngredient) {
|
||||
recipeInfos = RecipeFinderUtil.findRecipesByIngredient(typedIngredient);
|
||||
}
|
||||
}
|
||||
|
||||
if (recipeInfos.isEmpty()) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(
|
||||
Component.translatable("message.extendedae_plus.no_recipes_found"),
|
||||
true
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeInfo matchingRecipeInfo = null;
|
||||
for (RecipeInfo info : recipeInfos) {
|
||||
if (info.getRecipe().getId().equals(recipeId)) {
|
||||
matchingRecipeInfo = info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingRecipeInfo == null) {
|
||||
matchingRecipeInfo = recipeInfos.get(0);
|
||||
}
|
||||
|
||||
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(matchingRecipeInfo);
|
||||
List<ItemStack> selectedOutputs = convertOutputsToItemStacks(matchingRecipeInfo);
|
||||
|
||||
ModNetwork.CHANNEL.sendToServer(new CreateCtrlQPatternC2SPacket(
|
||||
recipeId,
|
||||
matchingRecipeInfo.isCraftingRecipe(),
|
||||
selectedIngredients,
|
||||
selectedOutputs,
|
||||
true
|
||||
));
|
||||
|
||||
ModNetwork.CHANNEL.sendToServer(new RequestProvidersListC2SPacket());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLastProcessingNameFromRecipe(Object recipeBase) {
|
||||
String name = null;
|
||||
if (recipeBase instanceof Recipe<?> recipe) {
|
||||
name = RecipeTypeNameConfig.mapRecipeTypeToSearchKey(recipe);
|
||||
} else if (recipeBase != null
|
||||
&& "com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) {
|
||||
name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(recipeBase);
|
||||
} else if (recipeBase != null
|
||||
&& "com.gregtechceu.gtceu.integration.jei.recipe.GTRecipeWrapper".equals(recipeBase.getClass().getName())) {
|
||||
try {
|
||||
var field = recipeBase.getClass().getField("recipe");
|
||||
Object inner = field.get(recipeBase);
|
||||
name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(inner);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null || name.isBlank()) {
|
||||
name = RecipeTypeNameConfig.deriveSearchKeyFromUnknownRecipe(recipeBase);
|
||||
}
|
||||
if (name != null && !name.isBlank()) {
|
||||
RecipeTypeNameConfig.setLastProcessingName(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import net.minecraftforge.network.NetworkEvent;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S: 请求将图样编码终端的已编码样板上传到指定的样板供应器(由客户端选择)。
|
||||
* C2S: Request uploading an encoded pattern to a selected provider.
|
||||
*/
|
||||
public class UploadEncodedPatternToProviderC2SPacket {
|
||||
private final long providerId;
|
||||
|
|
@ -31,15 +31,23 @@ public class UploadEncodedPatternToProviderC2SPacket {
|
|||
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) {
|
||||
ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
|
||||
} else {
|
||||
int index = (int) (-1L - msg.providerId);
|
||||
ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
|
||||
|
||||
// Prefer pending Ctrl+Q pattern upload when present.
|
||||
if (ProviderUploadUtil.hasPendingCtrlQPattern(player)) {
|
||||
if (ProviderUploadUtil.uploadPendingCtrlQPattern(player, msg.providerId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.containerMenu instanceof PatternEncodingTermMenu menu) {
|
||||
// 1) providerId >= 0: byId mode from access terminal
|
||||
// 2) providerId < 0: index mode, index = -1 - providerId
|
||||
if (msg.providerId >= 0) {
|
||||
ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId);
|
||||
} else {
|
||||
int index = (int) (-1L - msg.providerId);
|
||||
ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
package com.extendedae_plus.network.pattern;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.extendedae_plus.util.wireless.WirelessTerminalLocator;
|
||||
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.energy.IEnergyService;
|
||||
|
|
@ -17,6 +11,8 @@ import appeng.core.definitions.AEItems;
|
|||
import appeng.items.tools.powered.WirelessCraftingTerminalItem;
|
||||
import appeng.items.tools.powered.WirelessTerminalItem;
|
||||
import appeng.me.helpers.PlayerSource;
|
||||
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
|
||||
import com.extendedae_plus.util.wireless.WirelessTerminalLocator;
|
||||
import de.mari_023.ae2wtlib.terminal.WTMenuHost;
|
||||
import de.mari_023.ae2wtlib.wut.WTDefinition;
|
||||
import de.mari_023.ae2wtlib.wut.WUTHandler;
|
||||
|
|
@ -31,24 +27,31 @@ import net.minecraft.world.item.crafting.Recipe;
|
|||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S: Ctrl+Q快速创建样板数据包
|
||||
*
|
||||
* <p>
|
||||
* 从客户端发送配方ID、选择的材料和输出到服务器,服务器消耗空白样板并创建编码样板掉落到玩家脚下</p>
|
||||
* C2S: Ctrl+Q quick-create pattern request.
|
||||
*/
|
||||
public class CreateCtrlQPatternC2SPacket {
|
||||
|
||||
private final ResourceLocation recipeId;
|
||||
private final boolean isCraftingPattern;
|
||||
private final List<ItemStack> selectedIngredients;
|
||||
private final List<ItemStack> outputs; // 输出材料(物品或包装的流体)
|
||||
private final List<ItemStack> outputs;
|
||||
private final boolean openProviderSelector;
|
||||
|
||||
public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List<ItemStack> selectedIngredients, List<ItemStack> outputs) {
|
||||
this(recipeId, isCraftingPattern, selectedIngredients, outputs, false);
|
||||
}
|
||||
|
||||
public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List<ItemStack> selectedIngredients, List<ItemStack> outputs, boolean openProviderSelector) {
|
||||
this.recipeId = recipeId;
|
||||
this.isCraftingPattern = isCraftingPattern;
|
||||
this.selectedIngredients = selectedIngredients;
|
||||
this.outputs = outputs;
|
||||
this.openProviderSelector = openProviderSelector;
|
||||
}
|
||||
|
||||
public static void encode(CreateCtrlQPatternC2SPacket msg, FriendlyByteBuf buf) {
|
||||
|
|
@ -62,22 +65,27 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
for (ItemStack stack : msg.outputs) {
|
||||
buf.writeItem(stack);
|
||||
}
|
||||
buf.writeBoolean(msg.openProviderSelector);
|
||||
}
|
||||
|
||||
public static CreateCtrlQPatternC2SPacket decode(FriendlyByteBuf buf) {
|
||||
ResourceLocation recipeId = buf.readResourceLocation();
|
||||
boolean isCraftingPattern = buf.readBoolean();
|
||||
|
||||
int ingredientCount = buf.readInt();
|
||||
List<ItemStack> ingredients = new ArrayList<>();
|
||||
for (int i = 0; i < ingredientCount; i++) {
|
||||
ingredients.add(buf.readItem());
|
||||
}
|
||||
|
||||
int outputCount = buf.readInt();
|
||||
List<ItemStack> outputs = new ArrayList<>();
|
||||
for (int i = 0; i < outputCount; i++) {
|
||||
outputs.add(buf.readItem());
|
||||
}
|
||||
return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs);
|
||||
|
||||
boolean openProviderSelector = buf.readableBytes() > 0 && buf.readBoolean();
|
||||
return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs, openProviderSelector);
|
||||
}
|
||||
|
||||
public static void handle(CreateCtrlQPatternC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
|
|
@ -88,64 +96,49 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
return;
|
||||
}
|
||||
|
||||
// 1. 验证配方存在
|
||||
RecipeManager recipeManager = player.level().getRecipeManager();
|
||||
var recipeOpt = recipeManager.byKey(msg.recipeId);
|
||||
|
||||
if (recipeOpt.isEmpty()) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable("message.extendedae_plus.recipe_not_found"),
|
||||
false
|
||||
);
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.recipe_not_found"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
Recipe<?> recipe = recipeOpt.get();
|
||||
|
||||
// 2. 消耗空白样板
|
||||
if (!consumeBlankPattern(player)) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable("message.extendedae_plus.no_blank_pattern"),
|
||||
false
|
||||
);
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.no_blank_pattern"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 创建样板
|
||||
ItemStack pattern = createPattern(recipe, msg.isCraftingPattern, msg.selectedIngredients, msg.outputs, player);
|
||||
|
||||
if (pattern.isEmpty()) {
|
||||
// 创建失败,退还空白样板
|
||||
player.getInventory().add(AEItems.BLANK_PATTERN.stack());
|
||||
player.displayClientMessage(
|
||||
Component.translatable("message.extendedae_plus.pattern_creation_failed"),
|
||||
false
|
||||
);
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.pattern_creation_failed"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.openProviderSelector) {
|
||||
String pendingId = ProviderUploadUtil.beginPendingCtrlQUpload(player, pattern);
|
||||
if (pendingId == null) {
|
||||
if (!player.getInventory().add(pattern)) {
|
||||
player.drop(pattern, false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 交付样板:优先放入背包,满了再掉落
|
||||
if (!player.getInventory().add(pattern)) {
|
||||
player.drop(pattern, false);
|
||||
}
|
||||
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 消耗空白样板:优先从AE网络提取,网络无货才从玩家背包消耗
|
||||
*
|
||||
* @param player 玩家
|
||||
* @return 是否成功消耗
|
||||
*/
|
||||
private static boolean consumeBlankPattern(ServerPlayer player) {
|
||||
// 1. 尝试从AE网络提取(需要玩家持有无线终端)
|
||||
if (tryExtractFromNetwork(player)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. 网络提取失败,从背包消耗
|
||||
Inventory inventory = player.getInventory();
|
||||
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||
ItemStack stack = inventory.getItem(i);
|
||||
|
|
@ -155,27 +148,19 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
}
|
||||
}
|
||||
|
||||
return false; // 未找到
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从AE网络提取空白样板
|
||||
*
|
||||
* @param player 玩家
|
||||
* @return 是否成功提取
|
||||
*/
|
||||
private static boolean tryExtractFromNetwork(ServerPlayer player) {
|
||||
// 定位玩家身上的无线终端
|
||||
WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player);
|
||||
ItemStack terminal = located.stack;
|
||||
if (terminal.isEmpty()) {
|
||||
return false; // 没有无线终端
|
||||
return false;
|
||||
}
|
||||
|
||||
IGrid grid;
|
||||
boolean usedWtHost;
|
||||
|
||||
// 若来自 Curios:优先通过 ae2wtlib 的 WTMenuHost 获取量子桥网络
|
||||
String curiosSlotId = located.getCuriosSlotId();
|
||||
int curiosIndex = located.getCuriosIndex();
|
||||
|
||||
|
|
@ -208,7 +193,6 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
// 非 Curios:按 AE2 原生路径处理
|
||||
WirelessCraftingTerminalItem wct = terminal.getItem() instanceof WirelessCraftingTerminalItem c ? c : null;
|
||||
WirelessTerminalItem wt = wct != null ? wct : (terminal.getItem() instanceof WirelessTerminalItem t ? t : null);
|
||||
if (wt == null) {
|
||||
|
|
@ -219,60 +203,41 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
return false;
|
||||
}
|
||||
if (!wt.hasPower(player, 0.5, terminal)) {
|
||||
return false; // 能量不足
|
||||
return false;
|
||||
}
|
||||
usedWtHost = false;
|
||||
}
|
||||
|
||||
// 从网络提取空白样板
|
||||
AEItemKey blankPatternKey = AEItemKey.of(AEItems.BLANK_PATTERN.stack());
|
||||
IEnergyService energy = grid.getEnergyService();
|
||||
MEStorage storage = grid.getStorageService().getInventory();
|
||||
|
||||
long extracted = StorageHelper.poweredExtraction(
|
||||
energy,
|
||||
storage,
|
||||
blankPatternKey,
|
||||
1, // 只提取1个
|
||||
new PlayerSource(player)
|
||||
energy,
|
||||
storage,
|
||||
blankPatternKey,
|
||||
1,
|
||||
new PlayerSource(player)
|
||||
);
|
||||
|
||||
if (extracted > 0) {
|
||||
// 提取成功,消耗无线终端能量
|
||||
if (usedWtHost) {
|
||||
// WTMenuHost 已在 drainPower 中处理能量消耗
|
||||
} else {
|
||||
// 原生 AE2 扣能
|
||||
if (!usedWtHost) {
|
||||
WirelessCraftingTerminalItem wct2 = terminal.getItem() instanceof WirelessCraftingTerminalItem c2 ? c2 : null;
|
||||
WirelessTerminalItem wt2 = wct2 != null ? wct2 : (terminal.getItem() instanceof WirelessTerminalItem t2 ? t2 : null);
|
||||
if (wt2 != null) {
|
||||
wt2.usePower(player, 0.5, terminal);
|
||||
}
|
||||
}
|
||||
// 确保写回终端(若位于 Curios 等需要显式写回的容器)
|
||||
located.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // 网络中没有空白样板
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配方创建样板(支持物品和流体)
|
||||
*
|
||||
* @param recipe 配方
|
||||
* @param isCrafting 是否为合成样板
|
||||
* @param selectedIngredients 客户端选择的材料(应用JEI优先级后,流体已包装为 GenericStack.wrapInItemStack)
|
||||
* @param selectedOutputs 客户端传递的输出材料(物品或包装的流体)
|
||||
* @param player 玩家
|
||||
* @return 编码的样板物品
|
||||
*/
|
||||
private static ItemStack createPattern(Recipe<?> recipe, boolean isCrafting, List<ItemStack> selectedIngredients, List<ItemStack> selectedOutputs, ServerPlayer player) {
|
||||
try {
|
||||
if (isCrafting && recipe instanceof CraftingRecipe craftingRecipe) {
|
||||
// ===== 合成样板创建路径 =====
|
||||
|
||||
// 准备9格工作台输入(3x3布局)
|
||||
ItemStack[] inputs = new ItemStack[9];
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (i < selectedIngredients.size()) {
|
||||
|
|
@ -282,80 +247,59 @@ public class CreateCtrlQPatternC2SPacket {
|
|||
}
|
||||
}
|
||||
|
||||
// 准备输出
|
||||
ItemStack output = recipe.getResultItem(player.level().registryAccess()).copy();
|
||||
|
||||
// 使用 encodeCraftingPattern 创建合成样板
|
||||
// 直接传递 CraftingRecipe 对象而非 RecipeHolder
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern(
|
||||
craftingRecipe,
|
||||
inputs,
|
||||
output,
|
||||
true, // allowSubstitutes - 允许替代材料
|
||||
false // allowFluidSubstitutes - 不允许流体替代
|
||||
craftingRecipe,
|
||||
inputs,
|
||||
output,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
// 添加编码玩家信息到NBT
|
||||
encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString());
|
||||
|
||||
return encodedPattern;
|
||||
|
||||
} else {
|
||||
// ===== 处理样板创建路径(支持物品和流体)=====
|
||||
|
||||
List<GenericStack> inputs = new ArrayList<>();
|
||||
List<GenericStack> outputs = new ArrayList<>();
|
||||
|
||||
// 处理输入 - 使用客户端传入的材料选择(支持流体)
|
||||
for (ItemStack item : selectedIngredients) {
|
||||
if (!item.isEmpty()) {
|
||||
// 尝试解包 GenericStack(流体会被包装在特殊的 ItemStack 中)
|
||||
GenericStack genericStack = GenericStack.unwrapItemStack(item);
|
||||
if (genericStack != null) {
|
||||
// 这是一个包装的 GenericStack(可能是流体)
|
||||
inputs.add(genericStack);
|
||||
} else {
|
||||
// 普通物品
|
||||
AEItemKey itemKey = AEItemKey.of(item);
|
||||
if (itemKey != null) {
|
||||
inputs.add(new GenericStack(itemKey, item.getCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理输出 - 使用客户端传入的输出(支持流体)
|
||||
for (ItemStack item : selectedOutputs) {
|
||||
if (!item.isEmpty()) {
|
||||
// 尝试解包 GenericStack(流体会被包装在特殊的 ItemStack 中)
|
||||
GenericStack genericStack = GenericStack.unwrapItemStack(item);
|
||||
if (genericStack != null) {
|
||||
// 这是一个包装的 GenericStack(可能是流体)
|
||||
outputs.add(genericStack);
|
||||
} else {
|
||||
// 普通物品
|
||||
AEItemKey itemKey = AEItemKey.of(item);
|
||||
if (itemKey != null) {
|
||||
outputs.add(new GenericStack(itemKey, item.getCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 encodeProcessingPattern 创建处理样板
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern(
|
||||
inputs.toArray(new GenericStack[0]),
|
||||
outputs.toArray(new GenericStack[0])
|
||||
);
|
||||
|
||||
// 添加编码玩家信息到NBT
|
||||
encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString());
|
||||
|
||||
return encodedPattern;
|
||||
}
|
||||
|
||||
List<GenericStack> inputs = new ArrayList<>();
|
||||
List<GenericStack> outputs = new ArrayList<>();
|
||||
|
||||
for (ItemStack item : selectedIngredients) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack genericStack = GenericStack.unwrapItemStack(item);
|
||||
if (genericStack != null) {
|
||||
inputs.add(genericStack);
|
||||
} else {
|
||||
AEItemKey itemKey = AEItemKey.of(item);
|
||||
if (itemKey != null) {
|
||||
inputs.add(new GenericStack(itemKey, item.getCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack item : selectedOutputs) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack genericStack = GenericStack.unwrapItemStack(item);
|
||||
if (genericStack != null) {
|
||||
outputs.add(genericStack);
|
||||
} else {
|
||||
AEItemKey itemKey = AEItemKey.of(item);
|
||||
if (itemKey != null) {
|
||||
outputs.add(new GenericStack(itemKey, item.getCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern(
|
||||
inputs.toArray(new GenericStack[0]),
|
||||
outputs.toArray(new GenericStack[0])
|
||||
);
|
||||
|
||||
encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString());
|
||||
return encodedPattern;
|
||||
} catch (Exception e) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import appeng.menu.me.items.PatternEncodingTermMenu;
|
|||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.util.PatternProviderDataUtil;
|
||||
import com.extendedae_plus.util.PatternTerminalUtil;
|
||||
import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
|
@ -29,6 +30,27 @@ public class RequestProvidersListC2SPacket {
|
|||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
// Ctrl+Q pending 模式:不依赖编码终端,直接基于玩家网络给出列表(负数索引 ID)
|
||||
if (ProviderUploadUtil.hasPendingCtrlQPattern(player)) {
|
||||
List<PatternContainer> containers = ProviderUploadUtil.listAvailableProvidersFromPlayerNetwork(player);
|
||||
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 = PatternProviderDataUtil.getAvailableSlots(c);
|
||||
if (empty <= 0) continue;
|
||||
long encodedId = -1L - i;
|
||||
idxIds.add(encodedId);
|
||||
names.add(PatternProviderDataUtil.getProviderDisplayName(c));
|
||||
slots.add(empty);
|
||||
}
|
||||
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
|
||||
|
||||
// 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID)
|
||||
|
|
|
|||
|
|
@ -158,13 +158,20 @@ public final class PatternTerminalUtil {
|
|||
* 返回顺序稳定:按 grid 的 machineClasses 顺序,再按 activeMachines 迭代顺序。
|
||||
*/
|
||||
public static List<PatternContainer> listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) {
|
||||
List<PatternContainer> list = new ArrayList<>();
|
||||
if (menu == null) return list;
|
||||
if (menu == null) return new ArrayList<>();
|
||||
try {
|
||||
IGridNode node = menu.getNetworkNode();
|
||||
if (node == null) return list;
|
||||
IGrid grid = node.getGrid();
|
||||
if (grid == null) return list;
|
||||
if (node == null) return new ArrayList<>();
|
||||
return listAvailableProvidersFromGrid(node.getGrid());
|
||||
} catch (Throwable ignored) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<PatternContainer> listAvailableProvidersFromGrid(IGrid grid) {
|
||||
List<PatternContainer> list = new ArrayList<>();
|
||||
if (grid == null) return list;
|
||||
try {
|
||||
for (var machineClass : grid.getMachineClasses()) {
|
||||
if (PatternContainer.class.isAssignableFrom(machineClass)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -175,7 +182,10 @@ public final class PatternTerminalUtil {
|
|||
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 (inv.getStackInSlot(i).isEmpty()) {
|
||||
hasEmpty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasEmpty) list.add(container);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.extendedae_plus.util.uploadPattern;
|
|||
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.inventories.InternalInventory;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.helpers.patternprovider.PatternContainer;
|
||||
import appeng.items.tools.powered.WirelessTerminalItem;
|
||||
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import appeng.util.inv.FilteredInternalInventory;
|
||||
|
|
@ -10,10 +13,16 @@ import appeng.util.inv.filter.IAEItemFilter;
|
|||
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
|
||||
import com.extendedae_plus.util.PatternProviderDataUtil;
|
||||
import com.extendedae_plus.util.PatternTerminalUtil;
|
||||
import com.extendedae_plus.util.wireless.WirelessTerminalLocator;
|
||||
import de.mari_023.ae2wtlib.terminal.WTMenuHost;
|
||||
import de.mari_023.ae2wtlib.wut.WTDefinition;
|
||||
import de.mari_023.ae2wtlib.wut.WUTHandler;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 与样板供应器(provider)上传相关的工具类:
|
||||
|
|
@ -24,6 +33,9 @@ import java.util.List;
|
|||
* 其中使用 PatternTerminalUtil 提供的反射/容器访问工具。
|
||||
*/
|
||||
public final class ProviderUploadUtil {
|
||||
private static final String PENDING_DATA_KEY = "eap_ctrlq_pending_provider_upload_id";
|
||||
private static final String PENDING_STACK_KEY = "eap_ctrlq_pending_provider_upload_stack";
|
||||
|
||||
private ProviderUploadUtil() {}
|
||||
|
||||
/**
|
||||
|
|
@ -221,6 +233,165 @@ public final class ProviderUploadUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存 Ctrl+Q 生成的待上传样板(不放入玩家背包)。
|
||||
*/
|
||||
public static String beginPendingCtrlQUpload(ServerPlayer player, ItemStack pattern) {
|
||||
if (player == null || pattern == null || pattern.isEmpty() || !PatternDetailsHelper.isEncodedPattern(pattern)) {
|
||||
return null;
|
||||
}
|
||||
clearPendingCtrlQUpload(player);
|
||||
String id = UUID.randomUUID().toString();
|
||||
player.getPersistentData().putString(PENDING_DATA_KEY, id);
|
||||
player.getPersistentData().put(PENDING_STACK_KEY, pattern.copy().save(new CompoundTag()));
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void clearPendingCtrlQUpload(ServerPlayer player) {
|
||||
if (player == null) return;
|
||||
player.getPersistentData().remove(PENDING_DATA_KEY);
|
||||
player.getPersistentData().remove(PENDING_STACK_KEY);
|
||||
}
|
||||
|
||||
public static boolean hasPendingCtrlQPattern(ServerPlayer player) {
|
||||
if (player == null) return false;
|
||||
String id = player.getPersistentData().getString(PENDING_DATA_KEY);
|
||||
if (id == null || id.isBlank()) return false;
|
||||
return !getPendingCtrlQPattern(player).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 pending Ctrl+Q 样板上传到玩家网络中的目标 provider(负数索引 ID)。
|
||||
*/
|
||||
public static boolean uploadPendingCtrlQPattern(ServerPlayer player, long providerId) {
|
||||
if (player == null) return false;
|
||||
ItemStack pending = getPendingCtrlQPattern(player);
|
||||
if (pending.isEmpty()) return false;
|
||||
|
||||
ItemStack remain = insertPatternIntoProviderFromPlayerNetwork(player, pending, providerId);
|
||||
if (remain.getCount() >= pending.getCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (remain.isEmpty()) {
|
||||
clearPendingCtrlQUpload(player);
|
||||
} else {
|
||||
player.getPersistentData().put(PENDING_STACK_KEY, remain.save(new CompoundTag()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出玩家无线终端网络中的可用 provider,顺序与负数索引上传保持一致。
|
||||
*/
|
||||
public static List<PatternContainer> listAvailableProvidersFromPlayerNetwork(ServerPlayer player) {
|
||||
IGrid grid = findPlayerGrid(player);
|
||||
return PatternTerminalUtil.listAvailableProvidersFromGrid(grid);
|
||||
}
|
||||
|
||||
private static ItemStack getPendingCtrlQPattern(ServerPlayer player) {
|
||||
if (player == null) return ItemStack.EMPTY;
|
||||
String id = player.getPersistentData().getString(PENDING_DATA_KEY);
|
||||
if (id == null || id.isBlank()) return ItemStack.EMPTY;
|
||||
|
||||
CompoundTag data = player.getPersistentData();
|
||||
if (!data.contains(PENDING_STACK_KEY)) return ItemStack.EMPTY;
|
||||
CompoundTag stackTag = data.getCompound(PENDING_STACK_KEY);
|
||||
ItemStack stack = ItemStack.of(stackTag);
|
||||
if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) {
|
||||
clearPendingCtrlQUpload(player);
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static ItemStack insertPatternIntoProviderFromPlayerNetwork(ServerPlayer player, ItemStack pattern, long providerId) {
|
||||
if (player == null || pattern == null || pattern.isEmpty() || !PatternDetailsHelper.isEncodedPattern(pattern)) {
|
||||
return pattern == null ? ItemStack.EMPTY : pattern;
|
||||
}
|
||||
|
||||
int index = decodeProviderIndex(providerId);
|
||||
if (index < 0) return pattern;
|
||||
|
||||
List<PatternContainer> providers = listAvailableProvidersFromPlayerNetwork(player);
|
||||
if (index >= providers.size()) return pattern;
|
||||
|
||||
PatternContainer target = providers.get(index);
|
||||
if (target == null) return pattern;
|
||||
|
||||
ItemStack remain = pattern.copy();
|
||||
for (PatternContainer container : buildSameNameTryList(providers, target)) {
|
||||
InternalInventory inv = container.getTerminalPatternInventory();
|
||||
if (inv == null || inv.size() <= 0) continue;
|
||||
|
||||
ItemStack nextRemain = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()).addItems(remain.copy());
|
||||
if (nextRemain.getCount() < remain.getCount()) {
|
||||
remain = nextRemain;
|
||||
if (remain.isEmpty()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
return remain;
|
||||
}
|
||||
|
||||
private static int decodeProviderIndex(long providerId) {
|
||||
if (providerId >= 0) return -1;
|
||||
long idx = -1L - providerId;
|
||||
if (idx > Integer.MAX_VALUE) return -1;
|
||||
return (int) idx;
|
||||
}
|
||||
|
||||
private static List<PatternContainer> buildSameNameTryList(List<PatternContainer> all, PatternContainer target) {
|
||||
String targetName = PatternProviderDataUtil.getProviderDisplayName(target);
|
||||
List<PatternContainer> tryList = new java.util.ArrayList<>();
|
||||
tryList.add(target);
|
||||
for (PatternContainer container : all) {
|
||||
if (container == null || container == target) continue;
|
||||
String name = PatternProviderDataUtil.getProviderDisplayName(container);
|
||||
if (name != null && name.equals(targetName)) {
|
||||
tryList.add(container);
|
||||
}
|
||||
}
|
||||
return tryList;
|
||||
}
|
||||
|
||||
private static IGrid findPlayerGrid(ServerPlayer player) {
|
||||
WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player);
|
||||
ItemStack terminal = located.stack;
|
||||
if (terminal.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String curiosSlotId = located.getCuriosSlotId();
|
||||
int curiosIndex = located.getCuriosIndex();
|
||||
|
||||
if (curiosSlotId != null && curiosIndex >= 0) {
|
||||
try {
|
||||
String current = WUTHandler.getCurrentTerminal(terminal);
|
||||
WTDefinition def = WUTHandler.wirelessTerminals.get(current);
|
||||
if (def != null) {
|
||||
WTMenuHost wtHost = def.wTMenuHostFactory().create(player, null, terminal, (p, sub) -> {});
|
||||
if (wtHost != null) {
|
||||
IGridNode node = wtHost.getActionableNode();
|
||||
if (node != null) {
|
||||
return node.getGrid();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null;
|
||||
if (wt != null) {
|
||||
return wt.getLinkedGrid(terminal, player.serverLevel(), player);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExtendedAE兼容的样板过滤器
|
||||
* 使用AE2的PatternDetailsHelper进行样板验证
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user