diff --git a/gradle.properties b/gradle.properties index 4ed81b7..d1e9557 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # Done to increase the memory available to Gradle. org.gradle.jvmargs=-Xmx1G loom.platform = forge +org.gradle.parallel=true -# Mod properties mod_version = 1.5.1 maven_group = com.extendedae_plus archives_name = extendedae_plus @@ -31,3 +31,4 @@ ldlib_version=5394816 ie_version=5224387 mixin_version=0.8.4 curios_version=6418456 +org.gradle.parallel=true diff --git a/src/main/java/com/extendedae_plus/client/ModKeybindings.java b/src/main/java/com/extendedae_plus/client/ModKeybindings.java new file mode 100644 index 0000000..4e4cbc2 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ModKeybindings.java @@ -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); + } +} diff --git a/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java b/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java new file mode 100644 index 0000000..cfaff46 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java @@ -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键快速创建样板事件监听器 + * + *

监听 Ctrl+Q 组合键,自动创建样板并掉落到玩家脚下

+ *

应用 JEI 书签优先级选择材料,优先选择工作台配方

+ */ +@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> 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> 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 selectedIngredients = selectIngredientsWithJeiPriority(selectedRecipe); + + // 发送网络包到服务器 + ModNetwork.CHANNEL.sendToServer(new CreateCtrlQPatternC2SPacket( + selectedRecipe.getId(), + isCraftingPattern, + selectedIngredients + )); + + // 消耗事件,防止传播 + event.setCanceled(true); + } + + /** + * 应用JEI书签优先级选择配方材料 + * + *

对配方的每个 Ingredient,选择 JEI 书签中优先级最高的物品

+ *

如果没有在书签中,则使用配方默认的第一个物品

+ * + * @param recipe 配方 + * @return 选择的材料列表 + */ + private static List selectIngredientsWithJeiPriority(Recipe recipe) { + // 获取JEI书签列表并构建优先级映射 + List> bookmarks = JeiRuntimeProxy.getBookmarkList(); + Map 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 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; + } +} diff --git a/src/main/java/com/extendedae_plus/config/ModConfig.java b/src/main/java/com/extendedae_plus/config/ModConfig.java index 8877728..0b387d9 100644 --- a/src/main/java/com/extendedae_plus/config/ModConfig.java +++ b/src/main/java/com/extendedae_plus/config/ModConfig.java @@ -170,4 +170,25 @@ public final class ModConfig { 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; } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index dc20543..ebbc3d2 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -6,6 +6,7 @@ import com.extendedae_plus.network.crafting.CraftingMonitorJumpC2SPacket; import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket; import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket; 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.upload.EncodeWithShiftFlagC2SPacket; import net.minecraft.resources.ResourceLocation; @@ -168,6 +169,12 @@ public final class ModNetwork { .decoder(LabelNetworkListS2CPacket::decode) .consumerNetworkThread(LabelNetworkListS2CPacket::handle) .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++; } diff --git a/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java b/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java new file mode 100644 index 0000000..c0e2224 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java @@ -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快速创建样板数据包 + * + *

从客户端发送配方ID和选择的材料到服务器,服务器消耗空白样板并创建编码样板掉落到玩家脚下

+ */ +public class CreateCtrlQPatternC2SPacket { + private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - CtrlQPattern"); + + private final ResourceLocation recipeId; + private final boolean isCraftingPattern; + private final List selectedIngredients; + + public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List 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 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 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 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 inputs = new ArrayList<>(); + List 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; + } + } +} diff --git a/src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java b/src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java new file mode 100644 index 0000000..cc38327 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java @@ -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; + +/** + * 配方查找工具类 + * + *

根据物品查找相关配方,优先返回工作台配方(CraftingRecipe)

+ */ +public class RecipeFinderUtil { + private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder"); + + /** + * 根据JEI物品查找相关配方 + * + * @param ingredient JEI物品 + * @param level 当前世界 + * @return 相关配方列表 + */ + public static List> 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> findRecipesByItem(ItemStack item, Level level) { + List> 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> 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; + } + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 6081246..ecd0b5f 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -227,5 +227,15 @@ "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.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" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 64acdea..9a94431 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -226,5 +226,15 @@ "extendedae_plus.command.server_side_only": "此命令必须在服务器端执行", "extendedae_plus.command.storage_manager_not_initialized": "InfinityStorageManager未初始化", "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" } \ No newline at end of file