合成书签分支完成

This commit is contained in:
GaLi 2026-02-28 16:58:40 +08:00
parent 974c09be78
commit ab7664e4a6
4 changed files with 463 additions and 1 deletions

View File

@ -161,7 +161,110 @@ public class CtrlQPatternKeyHandler {
* @param recipeBookmark 配方书签对象 * @param recipeBookmark 配方书签对象
*/ */
private static void handleCraftingRecipeBookmark(Object recipeBookmark) { private static void handleCraftingRecipeBookmark(Object recipeBookmark) {
System.out.println("hecheng"); try {
// 1. 获取配方ID
var getRecipeUidMethod = recipeBookmark.getClass().getMethod("getRecipeUid");
net.minecraft.resources.ResourceLocation recipeId =
(net.minecraft.resources.ResourceLocation) getRecipeUidMethod.invoke(recipeBookmark);
if (recipeId == null) {
return;
}
// 2. 获取Minecraft实例
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) {
return;
}
// 3. 验证配方存在
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;
}
// 4. 从JEI获取配方的详细信息输入输出槽位
// 使用RecipeBookmark的getRecipe方法获取配方对象
var getRecipeMethod = recipeBookmark.getClass().getMethod("getRecipe");
Object recipe = getRecipeMethod.invoke(recipeBookmark);
if (recipe == null) {
return;
}
// 5. 通过JEI的RecipeManager获取配方布局信息
mezz.jei.api.runtime.IJeiRuntime jeiRuntime = JeiRuntimeProxy.get();
if (jeiRuntime == null) {
return;
}
// 6. 获取配方类别
var getRecipeCategoryMethod = recipeBookmark.getClass().getMethod("getRecipeCategory");
Object recipeCategory = getRecipeCategoryMethod.invoke(recipeBookmark);
// 7. 创建RecipeInfo来应用JEI书签优先级
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 mezz.jei.api.ingredients.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;
}
// 8. 找到匹配的RecipeInfo
RecipeInfo matchingRecipeInfo = null;
for (RecipeInfo info : recipeInfos) {
if (info.getRecipe().getId().equals(recipeId)) {
matchingRecipeInfo = info;
break;
}
}
if (matchingRecipeInfo == null) {
matchingRecipeInfo = recipeInfos.get(0);
}
// 9. 应用JEI书签优先级选择材料
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(matchingRecipeInfo);
// 10. 获取输出材料
List<ItemStack> selectedOutputs = convertOutputsToItemStacks(matchingRecipeInfo);
// 11. 发送网络包创建样板并上传到装配矩阵
ModNetwork.CHANNEL.sendToServer(new com.extendedae_plus.network.pattern.CreateAndUploadPatternC2SPacket(
recipeId,
matchingRecipeInfo.isCraftingRecipe(),
selectedIngredients,
selectedOutputs
));
} catch (Exception e) {
e.printStackTrace();
}
} }
/** /**

View File

@ -7,6 +7,7 @@ import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket
import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket; import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket;
import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket; import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket;
import com.extendedae_plus.network.pattern.CreateCtrlQPatternC2SPacket; import com.extendedae_plus.network.pattern.CreateCtrlQPatternC2SPacket;
import com.extendedae_plus.network.pattern.CreateAndUploadPatternC2SPacket;
import com.extendedae_plus.network.provider.*; import com.extendedae_plus.network.provider.*;
import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket; import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -175,6 +176,12 @@ public final class ModNetwork {
.decoder(CreateCtrlQPatternC2SPacket::decode) .decoder(CreateCtrlQPatternC2SPacket::decode)
.consumerNetworkThread(CreateCtrlQPatternC2SPacket::handle) .consumerNetworkThread(CreateCtrlQPatternC2SPacket::handle)
.add(); .add();
CHANNEL.messageBuilder(CreateAndUploadPatternC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
.encoder(CreateAndUploadPatternC2SPacket::encode)
.decoder(CreateAndUploadPatternC2SPacket::decode)
.consumerNetworkThread(CreateAndUploadPatternC2SPacket::handle)
.add();
} }
private static int nextId() { return id++; } private static int nextId() { return id++; }

View File

@ -0,0 +1,297 @@
package com.extendedae_plus.network.pattern;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.api.networking.energy.IEnergyService;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.GenericStack;
import appeng.api.storage.MEStorage;
import appeng.api.storage.StorageHelper;
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.MatrixUploadUtil;
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.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
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: 创建样板并上传到装配矩阵
*
* <p>从客户端发送配方ID选择的材料和输出到服务器服务器消耗空白样板并创建编码样板然后直接上传到装配矩阵</p>
*/
public class CreateAndUploadPatternC2SPacket {
private final ResourceLocation recipeId;
private final boolean isCraftingPattern;
private final List<ItemStack> selectedIngredients;
private final List<ItemStack> outputs;
public CreateAndUploadPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List<ItemStack> selectedIngredients, List<ItemStack> outputs) {
this.recipeId = recipeId;
this.isCraftingPattern = isCraftingPattern;
this.selectedIngredients = selectedIngredients;
this.outputs = outputs;
}
public static void encode(CreateAndUploadPatternC2SPacket msg, FriendlyByteBuf buf) {
buf.writeResourceLocation(msg.recipeId);
buf.writeBoolean(msg.isCraftingPattern);
buf.writeInt(msg.selectedIngredients.size());
for (ItemStack stack : msg.selectedIngredients) {
buf.writeItem(stack);
}
buf.writeInt(msg.outputs.size());
for (ItemStack stack : msg.outputs) {
buf.writeItem(stack);
}
}
public static CreateAndUploadPatternC2SPacket 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 CreateAndUploadPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs);
}
public static void handle(CreateAndUploadPatternC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
if (player == null) {
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
);
return;
}
Recipe<?> recipe = recipeOpt.get();
// 2. 获取AE网络
IGrid grid = getPlayerGrid(player);
if (grid == null) {
player.displayClientMessage(
Component.translatable("message.extendedae_plus.no_network"),
false
);
return;
}
// 3. 消耗空白样板
if (!consumeBlankPattern(player, grid)) {
player.displayClientMessage(
Component.translatable("message.extendedae_plus.no_blank_pattern"),
false
);
return;
}
// 4. 创建样板
ItemStack pattern = createPattern(recipe, msg.isCraftingPattern, msg.selectedIngredients, msg.outputs, player);
if (pattern.isEmpty()) {
// 创建失败退还空白样板到网络
refundBlankPattern(player, grid);
player.displayClientMessage(
Component.translatable("message.extendedae_plus.pattern_creation_failed"),
false
);
return;
}
// 5. 上传样板到装配矩阵
boolean uploaded = MatrixUploadUtil.uploadPatternToMatrix(player, pattern, grid);
if (!uploaded) {
// 上传失败退还空白样板到网络
refundBlankPattern(player, grid);
}
});
ctx.setPacketHandled(true);
}
/**
* 获取玩家的AE网络
*/
private static IGrid getPlayerGrid(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 e) {
return null;
}
} else {
WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null;
if (wt != null) {
return wt.getLinkedGrid(terminal, player.serverLevel(), player);
}
}
return null;
}
/**
* 消耗空白样板优先从AE网络提取
*/
private static boolean consumeBlankPattern(ServerPlayer player, IGrid grid) {
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,
new PlayerSource(player)
);
return extracted > 0;
}
/**
* 退还空白样板到网络
*/
private static void refundBlankPattern(ServerPlayer player, IGrid grid) {
AEItemKey blankPatternKey = AEItemKey.of(AEItems.BLANK_PATTERN.stack());
IEnergyService energy = grid.getEnergyService();
MEStorage storage = grid.getStorageService().getInventory();
StorageHelper.poweredInsert(
energy,
storage,
blankPatternKey,
1,
new PlayerSource(player)
);
}
/**
* 从配方创建样板
*/
private static ItemStack createPattern(Recipe<?> recipe, boolean isCrafting, List<ItemStack> selectedIngredients, List<ItemStack> selectedOutputs, ServerPlayer player) {
try {
if (isCrafting && recipe instanceof CraftingRecipe craftingRecipe) {
// 合成样板
ItemStack[] inputs = new ItemStack[9];
for (int i = 0; i < 9; i++) {
if (i < selectedIngredients.size()) {
inputs[i] = selectedIngredients.get(i).copy();
} else {
inputs[i] = ItemStack.EMPTY;
}
}
ItemStack output = recipe.getResultItem(player.level().registryAccess()).copy();
ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern(
craftingRecipe,
inputs,
output,
true,
false
);
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 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;
}
}
}

View File

@ -87,6 +87,61 @@ public final class MatrixUploadUtil {
} }
} }
} }
/**
* 直接上传已创建的样板到装配矩阵不从菜单读取
*
* @param player 服务器玩家
* @param pattern 已编码的样板
* @param grid AE网络
* @return 是否上传成功
*/
public static boolean uploadPatternToMatrix(ServerPlayer player, ItemStack pattern, IGrid grid) {
if (player == null || pattern.isEmpty() || grid == null) {
return false;
}
// 验证是否为已编码的样板
if (!PatternDetailsHelper.isEncodedPattern(pattern)) {
return false;
}
// 仅允许"合成/锻造台/切石机样板"
IPatternDetails details = PatternDetailsHelper.decodePattern(pattern, player.level());
if (!(details instanceof AECraftingPattern
|| details instanceof AESmithingTablePattern
|| details instanceof AEStonecuttingPattern)) {
return false;
}
ItemStack toInsert = pattern.copy();
// 收集所有可用的装配矩阵图样模块内部库存并逐一尝试遵循其过滤规则
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
// 在尝试上传之前检查装配矩阵是否已经存在相同样板物品与NBT完全一致
if (matrixContainsPattern(inventories, pattern)) {
// 直接提醒并跳过上传
sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.repetition"));
return false;
}
// 尝试插入
for (InternalInventory inv : inventories) {
if (inv == null) continue;
ItemStack remain = inv.addItems(toInsert);
if (remain.getCount() < pattern.getCount()) {
// 上传成功
sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.success"));
return true;
}
}
// 所有矩阵都满了
sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.full"));
return false;
}
/** /**
* 在给定 AE Grid 中收集所有已成型且在线的装配矩阵样板核心的用于外部插入的内部库存 * 在给定 AE Grid 中收集所有已成型且在线的装配矩阵样板核心的用于外部插入的内部库存