feat: Add Ctrl+Q quick pattern creation from JEI
Implement a new keybind (Ctrl+Q) to quickly create AE2 patterns from JEI items. Features: - Press Ctrl+Q while hovering over any item in JEI to create patterns instantly - Intelligent recipe selection (prioritizes crafting recipes) - Support Shift+Ctrl+Q to search recipes where item is used as input - JEI bookmark priority system for ingredient selection - Smart pattern placement (crafting patterns drop, processing patterns to inventory) - Consumes blank pattern from player inventory with auto-refund on failure Configuration: - ctrlQConsumeBlankPattern: Toggle blank pattern consumption (default: true) - ctrlQExtractFromNetwork: Extract patterns from AE network (default: true, WIP) Technical changes: - Add CtrlQPatternKeyHandler for client-side key handling - Add CreateCtrlQPatternC2SPacket for C2S network communication - Add RecipeFinderUtil with priority sorting - Add ModKeybindings for keybind registration - Update ModConfig with new options - Register network packet in ModNetwork - Add English translations Code quality: - Cleaned up debug logs - Removed local development configuration
This commit is contained in:
parent
5dde36b3df
commit
7c2286cd68
|
|
@ -1,8 +1,8 @@
|
||||||
# Done to increase the memory available to Gradle.
|
# Done to increase the memory available to Gradle.
|
||||||
org.gradle.jvmargs=-Xmx1G
|
org.gradle.jvmargs=-Xmx1G
|
||||||
loom.platform = forge
|
loom.platform = forge
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
# Mod properties
|
|
||||||
mod_version = 1.5.1
|
mod_version = 1.5.1
|
||||||
maven_group = com.extendedae_plus
|
maven_group = com.extendedae_plus
|
||||||
archives_name = extendedae_plus
|
archives_name = extendedae_plus
|
||||||
|
|
@ -31,3 +31,4 @@ ldlib_version=5394816
|
||||||
ie_version=5224387
|
ie_version=5224387
|
||||||
mixin_version=0.8.4
|
mixin_version=0.8.4
|
||||||
curios_version=6418456
|
curios_version=6418456
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
|
||||||
33
src/main/java/com/extendedae_plus/client/ModKeybindings.java
Normal file
33
src/main/java/com/extendedae_plus/client/ModKeybindings.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.extendedae_plus.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraftforge.client.settings.KeyConflictContext;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExtendedAE Plus 快捷键定义
|
||||||
|
*/
|
||||||
|
public final class ModKeybindings {
|
||||||
|
private ModKeybindings() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctrl+Q 快速创建样板快捷键
|
||||||
|
*/
|
||||||
|
public static final KeyMapping CREATE_PATTERN_KEY = new KeyMapping(
|
||||||
|
"key.extendedae_plus.create_pattern", // 翻译键
|
||||||
|
KeyConflictContext.GUI, // 仅在GUI中生效
|
||||||
|
InputConstants.Type.KEYSYM, // 键盘按键类型
|
||||||
|
GLFW.GLFW_KEY_Q, // Q 键
|
||||||
|
"key.categories.extendedae_plus" // 分类
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册所有快捷键
|
||||||
|
*
|
||||||
|
* @param event Forge快捷键注册事件
|
||||||
|
*/
|
||||||
|
public static void register(net.minecraftforge.client.event.RegisterKeyMappingsEvent event) {
|
||||||
|
event.register(CREATE_PATTERN_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
package com.extendedae_plus.client.event;
|
||||||
|
|
||||||
|
import appeng.api.stacks.AEItemKey;
|
||||||
|
import appeng.api.stacks.AEKey;
|
||||||
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
|
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.util.RecipeFinderUtil;
|
||||||
|
import mezz.jei.api.constants.VanillaTypes;
|
||||||
|
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||||
|
import net.minecraft.world.item.crafting.Ingredient;
|
||||||
|
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;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctrl+Q键快速创建样板事件监听器
|
||||||
|
*
|
||||||
|
* <p>监听 Ctrl+Q 组合键,自动创建样板并掉落到玩家脚下</p>
|
||||||
|
* <p>应用 JEI 书签优先级选择材料,优先选择工作台配方</p>
|
||||||
|
*/
|
||||||
|
@Mod.EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT)
|
||||||
|
public class CtrlQPatternKeyHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - CtrlQKeyHandler");
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onScreenKeyPressed(ScreenEvent.KeyPressed event) {
|
||||||
|
Screen screen = event.getScreen();
|
||||||
|
int keyCode = event.getKeyCode();
|
||||||
|
int scanCode = event.getScanCode();
|
||||||
|
|
||||||
|
// 使用 KeyMapping 检测按键(而非硬编码)
|
||||||
|
if (!ModKeybindings.CREATE_PATTERN_KEY.matches(keyCode, scanCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 Ctrl 修饰键
|
||||||
|
if (!Screen.hasControlDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JEI 必须可用
|
||||||
|
if (JeiRuntimeProxy.get() == null) {
|
||||||
|
LOGGER.warn("[CtrlQKeyHandler] JEI not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取鼠标悬浮的物品
|
||||||
|
Optional<ITypedIngredient<?>> ingredient = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||||
|
|
||||||
|
if (ingredient.isEmpty()) {
|
||||||
|
LOGGER.warn("[CtrlQKeyHandler] No ingredient under mouse");
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.player != null) {
|
||||||
|
mc.player.displayClientMessage(
|
||||||
|
Component.translatable("message.extendedae_plus.hover_item_first"),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找相关配方
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
List<Recipe<?>> recipes = RecipeFinderUtil.findRecipesByIngredient(
|
||||||
|
ingredient.get(),
|
||||||
|
mc.level
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recipes.isEmpty()) {
|
||||||
|
LOGGER.warn("[CtrlQKeyHandler] No recipes found");
|
||||||
|
if (mc.player != null) {
|
||||||
|
mc.player.displayClientMessage(
|
||||||
|
Component.translatable("message.extendedae_plus.no_recipes_found"),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动选择最佳配方(优先CraftingRecipe)
|
||||||
|
Recipe<?> selectedRecipe = RecipeFinderUtil.selectBestRecipe(recipes);
|
||||||
|
if (selectedRecipe == null) {
|
||||||
|
LOGGER.error("[CtrlQKeyHandler] selectBestRecipe returned null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCraftingPattern = selectedRecipe instanceof CraftingRecipe;
|
||||||
|
|
||||||
|
// 应用JEI书签优先级选择材料
|
||||||
|
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(selectedRecipe);
|
||||||
|
|
||||||
|
// 发送网络包到服务器
|
||||||
|
ModNetwork.CHANNEL.sendToServer(new CreateCtrlQPatternC2SPacket(
|
||||||
|
selectedRecipe.getId(),
|
||||||
|
isCraftingPattern,
|
||||||
|
selectedIngredients
|
||||||
|
));
|
||||||
|
|
||||||
|
// 消耗事件,防止传播
|
||||||
|
event.setCanceled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用JEI书签优先级选择配方材料
|
||||||
|
*
|
||||||
|
* <p>对配方的每个 Ingredient,选择 JEI 书签中优先级最高的物品</p>
|
||||||
|
* <p>如果没有在书签中,则使用配方默认的第一个物品</p>
|
||||||
|
*
|
||||||
|
* @param recipe 配方
|
||||||
|
* @return 选择的材料列表
|
||||||
|
*/
|
||||||
|
private static List<ItemStack> selectIngredientsWithJeiPriority(Recipe<?> recipe) {
|
||||||
|
// 获取JEI书签列表并构建优先级映射
|
||||||
|
List<? extends ITypedIngredient<?>> bookmarks = JeiRuntimeProxy.getBookmarkList();
|
||||||
|
Map<AEKey, Integer> priorities = new HashMap<>();
|
||||||
|
AtomicInteger index = new AtomicInteger(Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
// 构建优先级映射 (数值越小 = 优先级越高,与EncodingHelperMixin逻辑一致)
|
||||||
|
for (ITypedIngredient<?> ingredient : bookmarks) {
|
||||||
|
ingredient.getIngredient(VanillaTypes.ITEM_STACK).ifPresent(itemStack ->
|
||||||
|
priorities.put(AEItemKey.of(itemStack), index.getAndDecrement())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ItemStack> selected = new ArrayList<>();
|
||||||
|
|
||||||
|
// 对每个 ingredient 选择优先级最高的物品
|
||||||
|
for (Ingredient ingredient : recipe.getIngredients()) {
|
||||||
|
if (ingredient.isEmpty()) {
|
||||||
|
selected.add(ItemStack.EMPTY);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] items = ingredient.getItems();
|
||||||
|
if (items.length == 0) {
|
||||||
|
selected.add(ItemStack.EMPTY);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择优先级最高的 (如果都不在书签中,选第一个)
|
||||||
|
ItemStack best = items[0];
|
||||||
|
int bestPriority = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
// 检查第一个物品的优先级
|
||||||
|
AEKey firstKey = AEItemKey.of(best);
|
||||||
|
if (priorities.containsKey(firstKey)) {
|
||||||
|
bestPriority = priorities.get(firstKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历其他选项
|
||||||
|
for (int i = 1; i < items.length; i++) {
|
||||||
|
AEKey key = AEItemKey.of(items[i]);
|
||||||
|
int priority = priorities.getOrDefault(key, Integer.MAX_VALUE);
|
||||||
|
if (priority < bestPriority) {
|
||||||
|
bestPriority = priority;
|
||||||
|
best = items[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selected.add(best.copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -170,4 +170,25 @@ public final class ModConfig {
|
||||||
ConfigParsingUtils.reload();
|
ConfigParsingUtils.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Ctrl+Q 快速样板配置 ====================
|
||||||
|
|
||||||
|
@Configurable
|
||||||
|
@Configurable.Comment(value = {
|
||||||
|
"Ctrl+Q创建样板是否消耗空白样板",
|
||||||
|
"true: 从玩家背包或AE网络消耗空白样板",
|
||||||
|
"false: 不消耗空白样板,直接创建(整合包/服务器管理员可配置)"
|
||||||
|
})
|
||||||
|
@Configurable.Synchronized
|
||||||
|
public boolean ctrlQConsumeBlankPattern = true;
|
||||||
|
|
||||||
|
@Configurable
|
||||||
|
@Configurable.Comment(value = {
|
||||||
|
"Ctrl+Q创建样板是否优先从AE网络提取空白样板",
|
||||||
|
"true: 优先从AE网络提取,网络无货才从背包消耗",
|
||||||
|
"false: 仅从玩家背包消耗",
|
||||||
|
"注意:需要玩家持有或装备无线终端才能访问AE网络"
|
||||||
|
})
|
||||||
|
@Configurable.Synchronized
|
||||||
|
public boolean ctrlQExtractFromNetwork = true;
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import com.extendedae_plus.network.crafting.CraftingMonitorJumpC2SPacket;
|
||||||
import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket;
|
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.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;
|
||||||
|
|
@ -168,6 +169,12 @@ public final class ModNetwork {
|
||||||
.decoder(LabelNetworkListS2CPacket::decode)
|
.decoder(LabelNetworkListS2CPacket::decode)
|
||||||
.consumerNetworkThread(LabelNetworkListS2CPacket::handle)
|
.consumerNetworkThread(LabelNetworkListS2CPacket::handle)
|
||||||
.add();
|
.add();
|
||||||
|
|
||||||
|
CHANNEL.messageBuilder(CreateCtrlQPatternC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||||
|
.encoder(CreateCtrlQPatternC2SPacket::encode)
|
||||||
|
.decoder(CreateCtrlQPatternC2SPacket::decode)
|
||||||
|
.consumerNetworkThread(CreateCtrlQPatternC2SPacket::handle)
|
||||||
|
.add();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int nextId() { return id++; }
|
private static int nextId() { return id++; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
package com.extendedae_plus.network.pattern;
|
||||||
|
|
||||||
|
import appeng.api.crafting.PatternDetailsHelper;
|
||||||
|
import appeng.api.stacks.AEItemKey;
|
||||||
|
import appeng.api.stacks.GenericStack;
|
||||||
|
import appeng.core.definitions.AEItems;
|
||||||
|
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.entity.player.Inventory;
|
||||||
|
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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C2S: Ctrl+Q快速创建样板数据包
|
||||||
|
*
|
||||||
|
* <p>从客户端发送配方ID和选择的材料到服务器,服务器消耗空白样板并创建编码样板掉落到玩家脚下</p>
|
||||||
|
*/
|
||||||
|
public class CreateCtrlQPatternC2SPacket {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - CtrlQPattern");
|
||||||
|
|
||||||
|
private final ResourceLocation recipeId;
|
||||||
|
private final boolean isCraftingPattern;
|
||||||
|
private final List<ItemStack> selectedIngredients;
|
||||||
|
|
||||||
|
public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List<ItemStack> selectedIngredients) {
|
||||||
|
this.recipeId = recipeId;
|
||||||
|
this.isCraftingPattern = isCraftingPattern;
|
||||||
|
this.selectedIngredients = selectedIngredients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void encode(CreateCtrlQPatternC2SPacket msg, FriendlyByteBuf buf) {
|
||||||
|
buf.writeResourceLocation(msg.recipeId);
|
||||||
|
buf.writeBoolean(msg.isCraftingPattern);
|
||||||
|
buf.writeInt(msg.selectedIngredients.size());
|
||||||
|
for (ItemStack stack : msg.selectedIngredients) {
|
||||||
|
buf.writeItem(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CreateCtrlQPatternC2SPacket decode(FriendlyByteBuf buf) {
|
||||||
|
ResourceLocation recipeId = buf.readResourceLocation();
|
||||||
|
boolean isCraftingPattern = buf.readBoolean();
|
||||||
|
int count = buf.readInt();
|
||||||
|
List<ItemStack> ingredients = new ArrayList<>();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
ingredients.add(buf.readItem());
|
||||||
|
}
|
||||||
|
return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(CreateCtrlQPatternC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||||
|
var ctx = ctxSupplier.get();
|
||||||
|
ctx.enqueueWork(() -> {
|
||||||
|
ServerPlayer player = ctx.getSender();
|
||||||
|
if (player == null) {
|
||||||
|
LOGGER.warn("[CtrlQPattern] No sender found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 1. 验证配方存在
|
||||||
|
RecipeManager recipeManager = player.level().getRecipeManager();
|
||||||
|
var recipeOpt = recipeManager.byKey(msg.recipeId);
|
||||||
|
|
||||||
|
if (recipeOpt.isEmpty()) {
|
||||||
|
LOGGER.error("[CtrlQPattern] Recipe not found: {}", msg.recipeId);
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatable("message.extendedae_plus.recipe_not_found"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Recipe<?> recipe = recipeOpt.get();
|
||||||
|
|
||||||
|
// 2. 消耗空白样板
|
||||||
|
if (!consumeBlankPattern(player)) {
|
||||||
|
LOGGER.warn("[CtrlQPattern] No blank pattern found in inventory");
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatable("message.extendedae_plus.no_blank_pattern"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 创建样板
|
||||||
|
ItemStack pattern = createPattern(recipe, msg.isCraftingPattern, msg.selectedIngredients, player);
|
||||||
|
|
||||||
|
if (pattern.isEmpty()) {
|
||||||
|
LOGGER.error("[CtrlQPattern] Pattern creation failed");
|
||||||
|
// 创建失败,退还空白样板
|
||||||
|
player.getInventory().add(AEItems.BLANK_PATTERN.stack());
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatable("message.extendedae_plus.pattern_creation_failed"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 根据样板类型选择交付方式
|
||||||
|
if (msg.isCraftingPattern) {
|
||||||
|
// 合成样板:始终掉落到玩家脚下
|
||||||
|
player.drop(pattern, false);
|
||||||
|
} else {
|
||||||
|
// 处理样板:优先放入背包,满了再掉落
|
||||||
|
boolean added = player.getInventory().add(pattern);
|
||||||
|
if (added) {
|
||||||
|
} else {
|
||||||
|
player.drop(pattern, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 移除成功消息(仅失败时提示)
|
||||||
|
});
|
||||||
|
ctx.setPacketHandled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗玩家背包中的一个空白样板
|
||||||
|
*
|
||||||
|
* @param player 玩家
|
||||||
|
* @return 是否成功消耗
|
||||||
|
*/
|
||||||
|
private static boolean consumeBlankPattern(ServerPlayer player) {
|
||||||
|
Inventory inventory = player.getInventory();
|
||||||
|
|
||||||
|
// 遍历背包查找空白样板
|
||||||
|
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||||
|
ItemStack stack = inventory.getItem(i);
|
||||||
|
if (stack.is(AEItems.BLANK_PATTERN.asItem())) {
|
||||||
|
stack.shrink(1); // 消耗一个
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // 未找到
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从配方创建样板
|
||||||
|
*
|
||||||
|
* @param recipe 配方
|
||||||
|
* @param isCrafting 是否为合成样板
|
||||||
|
* @param selectedIngredients 客户端选择的材料(应用JEI优先级后)
|
||||||
|
* @param player 玩家
|
||||||
|
* @return 编码的样板物品
|
||||||
|
*/
|
||||||
|
private static ItemStack createPattern(Recipe<?> recipe, boolean isCrafting, List<ItemStack> selectedIngredients, 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()) {
|
||||||
|
inputs[i] = selectedIngredients.get(i).copy();
|
||||||
|
} else {
|
||||||
|
inputs[i] = ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备输出
|
||||||
|
ItemStack output = recipe.getResultItem(player.level().registryAccess()).copy();
|
||||||
|
|
||||||
|
// 使用 encodeCraftingPattern 创建合成样板
|
||||||
|
// 直接传递 CraftingRecipe 对象而非 RecipeHolder
|
||||||
|
ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern(
|
||||||
|
craftingRecipe,
|
||||||
|
inputs,
|
||||||
|
output,
|
||||||
|
true, // allowSubstitutes - 允许替代材料
|
||||||
|
false // allowFluidSubstitutes - 不允许流体替代
|
||||||
|
);
|
||||||
|
|
||||||
|
return encodedPattern;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ===== 处理样板创建路径 =====
|
||||||
|
|
||||||
|
List<GenericStack> inputs = new ArrayList<>();
|
||||||
|
List<GenericStack> outputs = new ArrayList<>();
|
||||||
|
|
||||||
|
// 处理输入 - 使用客户端传入的材料选择
|
||||||
|
for (ItemStack item : selectedIngredients) {
|
||||||
|
if (!item.isEmpty()) {
|
||||||
|
inputs.add(new GenericStack(
|
||||||
|
AEItemKey.of(item),
|
||||||
|
item.getCount()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理输出
|
||||||
|
ItemStack result = recipe.getResultItem(player.level().registryAccess());
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
outputs.add(new GenericStack(
|
||||||
|
AEItemKey.of(result),
|
||||||
|
result.getCount()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 encodeProcessingPattern 创建处理样板
|
||||||
|
ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern(
|
||||||
|
inputs.toArray(new GenericStack[0]),
|
||||||
|
outputs.toArray(new GenericStack[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
return encodedPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("[CtrlQPattern] Exception during pattern creation", e);
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java
Normal file
134
src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.extendedae_plus.util;
|
||||||
|
|
||||||
|
import mezz.jei.api.constants.VanillaTypes;
|
||||||
|
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||||
|
import net.minecraft.world.item.crafting.Recipe;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配方查找工具类
|
||||||
|
*
|
||||||
|
* <p>根据物品查找相关配方,优先返回工作台配方(CraftingRecipe)</p>
|
||||||
|
*/
|
||||||
|
public class RecipeFinderUtil {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据JEI物品查找相关配方
|
||||||
|
*
|
||||||
|
* @param ingredient JEI物品
|
||||||
|
* @param level 当前世界
|
||||||
|
* @return 相关配方列表
|
||||||
|
*/
|
||||||
|
public static List<Recipe<?>> findRecipesByIngredient(ITypedIngredient<?> ingredient, Level level) {
|
||||||
|
if (ingredient.getType() == VanillaTypes.ITEM_STACK) {
|
||||||
|
ItemStack stack = (ItemStack) ingredient.getIngredient();
|
||||||
|
return findRecipesByItem(stack, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.warn("[RecipeFinder] Unsupported ingredient type: {}", ingredient.getType());
|
||||||
|
// TODO: Support fluids, chemicals, and other AE2-compatible types
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据物品查找相关配方
|
||||||
|
*
|
||||||
|
* @param item 目标物品
|
||||||
|
* @param level 当前世界
|
||||||
|
* @return 配方列表
|
||||||
|
*/
|
||||||
|
private static List<Recipe<?>> findRecipesByItem(ItemStack item, Level level) {
|
||||||
|
List<Recipe<?>> results = new ArrayList<>();
|
||||||
|
int totalRecipes = level.getRecipeManager().getRecipes().size();
|
||||||
|
|
||||||
|
// 1. 查找以该物品为输出的配方
|
||||||
|
int outputMatches = 0;
|
||||||
|
for (Recipe<?> recipe : level.getRecipeManager().getRecipes()) {
|
||||||
|
if (matchesOutput(recipe, item)) {
|
||||||
|
results.add(recipe);
|
||||||
|
outputMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果按住Shift,也查找以该物品为输入的配方
|
||||||
|
if (Screen.hasShiftDown()) {
|
||||||
|
int inputMatches = 0;
|
||||||
|
for (Recipe<?> recipe : level.getRecipeManager().getRecipes()) {
|
||||||
|
if (matchesInput(recipe, item) && !results.contains(recipe)) {
|
||||||
|
results.add(recipe);
|
||||||
|
inputMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 优先级排序: CraftingRecipe优先
|
||||||
|
results.sort((r1, r2) -> {
|
||||||
|
boolean isCrafting1 = r1 instanceof CraftingRecipe;
|
||||||
|
boolean isCrafting2 = r2 instanceof CraftingRecipe;
|
||||||
|
if (isCrafting1 && !isCrafting2) return -1; // r1优先
|
||||||
|
if (!isCrafting1 && isCrafting2) return 1; // r2优先
|
||||||
|
return 0; // 保持原顺序
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择最佳配方(优先选择工作台配方)
|
||||||
|
*
|
||||||
|
* @param recipes 配方列表
|
||||||
|
* @return 最佳配方,如果列表为空返回null
|
||||||
|
*/
|
||||||
|
public static Recipe<?> selectBestRecipe(List<Recipe<?>> recipes) {
|
||||||
|
if (recipes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先返回CraftingRecipe
|
||||||
|
for (Recipe<?> recipe : recipes) {
|
||||||
|
if (recipe instanceof CraftingRecipe) {
|
||||||
|
return recipe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有工作台配方,返回第一个
|
||||||
|
return recipes.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查配方输出是否匹配目标物品
|
||||||
|
*/
|
||||||
|
private static boolean matchesOutput(Recipe<?> recipe, ItemStack target) {
|
||||||
|
try {
|
||||||
|
ItemStack result = recipe.getResultItem(null);
|
||||||
|
boolean matches = ItemStack.isSameItemSameTags(result, target);
|
||||||
|
return matches;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("[RecipeFinder] Exception in matchesOutput for recipe {}: {}", recipe.getId(), e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查配方输入是否包含目标物品
|
||||||
|
*/
|
||||||
|
private static boolean matchesInput(Recipe<?> recipe, ItemStack target) {
|
||||||
|
try {
|
||||||
|
boolean matches = recipe.getIngredients().stream()
|
||||||
|
.anyMatch(ingredient -> ingredient.test(target));
|
||||||
|
return matches;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("[RecipeFinder] Exception in matchesInput for recipe {}: {}", recipe.getId(), e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -227,5 +227,15 @@
|
||||||
"extendedae_plus.command.server_side_only": "This command must be run on server side",
|
"extendedae_plus.command.server_side_only": "This command must be run on server side",
|
||||||
"extendedae_plus.command.storage_manager_not_initialized": "InfinityStorageManager is not initialized",
|
"extendedae_plus.command.storage_manager_not_initialized": "InfinityStorageManager is not initialized",
|
||||||
"extendedae_plus.command.gave_infinity_disks": "Gave %s infinity disks",
|
"extendedae_plus.command.gave_infinity_disks": "Gave %s infinity disks",
|
||||||
"extendedae_plus.command.error": "Error: %s"
|
"extendedae_plus.command.error": "Error: %s",
|
||||||
|
|
||||||
|
"message.extendedae_plus.hover_item_first": "Please hover over an item first",
|
||||||
|
"message.extendedae_plus.no_recipes_found": "No recipes found for this item",
|
||||||
|
"message.extendedae_plus.no_blank_pattern": "No blank pattern in inventory",
|
||||||
|
"message.extendedae_plus.recipe_not_found": "Recipe not found",
|
||||||
|
"message.extendedae_plus.pattern_creation_failed": "Pattern creation failed",
|
||||||
|
"message.extendedae_plus.pattern_created": "Created pattern: %s",
|
||||||
|
|
||||||
|
"key.extendedae_plus.create_pattern": "Create Pattern from JEI",
|
||||||
|
"key.categories.extendedae_plus": "ExtendedAE Plus"
|
||||||
}
|
}
|
||||||
|
|
@ -226,5 +226,15 @@
|
||||||
"extendedae_plus.command.server_side_only": "此命令必须在服务器端执行",
|
"extendedae_plus.command.server_side_only": "此命令必须在服务器端执行",
|
||||||
"extendedae_plus.command.storage_manager_not_initialized": "InfinityStorageManager未初始化",
|
"extendedae_plus.command.storage_manager_not_initialized": "InfinityStorageManager未初始化",
|
||||||
"extendedae_plus.command.gave_infinity_disks": "已发放 %s 个无限磁盘",
|
"extendedae_plus.command.gave_infinity_disks": "已发放 %s 个无限磁盘",
|
||||||
"extendedae_plus.command.error": "错误: %s"
|
"extendedae_plus.command.error": "错误: %s",
|
||||||
|
|
||||||
|
"message.extendedae_plus.hover_item_first": "请先将鼠标悬浮在物品上",
|
||||||
|
"message.extendedae_plus.no_recipes_found": "未找到该物品的配方",
|
||||||
|
"message.extendedae_plus.no_blank_pattern": "背包中没有空白样板",
|
||||||
|
"message.extendedae_plus.recipe_not_found": "配方未找到",
|
||||||
|
"message.extendedae_plus.pattern_creation_failed": "样板创建失败",
|
||||||
|
"message.extendedae_plus.pattern_created": "已创建样板: %s",
|
||||||
|
|
||||||
|
"key.extendedae_plus.create_pattern": "从JEI创建样板",
|
||||||
|
"key.categories.extendedae_plus": "ExtendedAE Plus"
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user