基础移植
This commit is contained in:
parent
675123e8e9
commit
236a1fbe2d
39
src/main/java/com/extendedae_plus/client/ModKeybindings.java
Normal file
39
src/main/java/com/extendedae_plus/client/ModKeybindings.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.neoforged.neoforge.client.settings.KeyConflictContext;
|
||||
import net.neoforged.neoforge.client.settings.KeyModifier;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/**
|
||||
* ExtendedAE Plus 快捷键定义
|
||||
*/
|
||||
@EventBusSubscriber(modid = ExtendedAEPlus.MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||
public final class ModKeybindings {
|
||||
private ModKeybindings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ctrl+Q 快速创建样板快捷键
|
||||
*/
|
||||
public static final KeyMapping CREATE_PATTERN_KEY = new KeyMapping(
|
||||
"key.extendedae_plus.create_pattern",
|
||||
KeyConflictContext.GUI,
|
||||
KeyModifier.CONTROL,
|
||||
InputConstants.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_Q,
|
||||
"key.categories.extendedae_plus"
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void register(RegisterKeyMappingsEvent event) {
|
||||
event.register(CREATE_PATTERN_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
package com.extendedae_plus.client.event;
|
||||
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import com.extendedae_plus.client.ModKeybindings;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import com.extendedae_plus.network.CreateAndUploadPatternC2SPacket;
|
||||
import com.extendedae_plus.network.CreateCtrlQPatternC2SPacket;
|
||||
import com.extendedae_plus.network.RequestProvidersListC2SPacket;
|
||||
import com.extendedae_plus.util.RecipeFinderUtil;
|
||||
import com.extendedae_plus.util.RecipeInfo;
|
||||
import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
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.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.neoforge.client.event.ScreenEvent;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Ctrl+Q 快速创建样板事件监听器
|
||||
*/
|
||||
public final class CtrlQPatternKeyHandler {
|
||||
private CtrlQPatternKeyHandler() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onScreenKeyPressed(ScreenEvent.KeyPressed.Pre event) {
|
||||
Screen screen = event.getScreen();
|
||||
if (screen == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
int scanCode = event.getScanCode();
|
||||
if (!ModKeybindings.CREATE_PATTERN_KEY.matches(keyCode, scanCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JeiRuntimeProxy.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<?> recipeBookmark = JeiRuntimeProxy.getRecipeBookmarkUnderMouse();
|
||||
if (recipeBookmark.isPresent()) {
|
||||
handleRecipeBookmark(recipeBookmark.get());
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<ITypedIngredient<?>> ingredient = castTypedIngredient(JeiRuntimeProxy.getIngredientUnderMouse());
|
||||
if (ingredient.isEmpty()) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.translatable("message.extendedae_plus.hover_item_first"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<RecipeInfo> recipes = RecipeFinderUtil.findRecipesByIngredient(ingredient.get());
|
||||
if (recipes.isEmpty()) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.translatable("message.extendedae_plus.no_recipes_found"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeInfo selected = RecipeFinderUtil.selectBestRecipe(recipes);
|
||||
if (selected == null || selected.getRecipeId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(selected);
|
||||
List<ItemStack> selectedOutputs = convertOutputsToItemStacks(selected);
|
||||
|
||||
PacketDistributor.sendToServer(new CreateCtrlQPatternC2SPacket(
|
||||
selected.getRecipeId(),
|
||||
selected.isCraftingRecipe(),
|
||||
selectedIngredients,
|
||||
selectedOutputs
|
||||
));
|
||||
event.setCanceled(true);
|
||||
}
|
||||
|
||||
private static void handleRecipeBookmark(Object recipeBookmark) {
|
||||
if (isCraftingRecipe(recipeBookmark)) {
|
||||
handleCraftingRecipeBookmark(recipeBookmark);
|
||||
} else {
|
||||
handleProcessingRecipeBookmark(recipeBookmark);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCraftingRecipe(Object recipeBookmark) {
|
||||
try {
|
||||
var getRecipeCategoryMethod = recipeBookmark.getClass().getMethod("getRecipeCategory");
|
||||
Object recipeCategory = getRecipeCategoryMethod.invoke(recipeBookmark);
|
||||
|
||||
var getRecipeTypeMethod = recipeCategory.getClass().getMethod("getRecipeType");
|
||||
Object recipeType = getRecipeTypeMethod.invoke(recipeCategory);
|
||||
return RecipeTypes.CRAFTING.equals(recipeType)
|
||||
|| RecipeTypes.STONECUTTING.equals(recipeType)
|
||||
|| RecipeTypes.SMITHING.equals(recipeType);
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleCraftingRecipeBookmark(Object recipeBookmark) {
|
||||
try {
|
||||
ResourceLocation recipeId = getRecipeId(recipeBookmark);
|
||||
if (recipeId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
}
|
||||
var recipeOpt = mc.level.getRecipeManager().byKey(recipeId);
|
||||
if (recipeOpt.isEmpty()) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.translatable("message.extendedae_plus.recipe_not_found"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<RecipeInfo> recipeInfos = findRecipeInfosForBookmark(recipeBookmark);
|
||||
if (recipeInfos.isEmpty()) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.translatable("message.extendedae_plus.no_recipes_found"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeInfo matching = matchById(recipeInfos, recipeId);
|
||||
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(matching);
|
||||
List<ItemStack> selectedOutputs = convertOutputsToItemStacks(matching);
|
||||
|
||||
PacketDistributor.sendToServer(new CreateAndUploadPatternC2SPacket(
|
||||
recipeId,
|
||||
matching.isCraftingRecipe(),
|
||||
selectedIngredients,
|
||||
selectedOutputs
|
||||
));
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleProcessingRecipeBookmark(Object recipeBookmark) {
|
||||
try {
|
||||
ResourceLocation recipeId = getRecipeId(recipeBookmark);
|
||||
if (recipeId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
}
|
||||
var recipeOpt = mc.level.getRecipeManager().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 = findRecipeInfosForBookmark(recipeBookmark);
|
||||
if (recipeInfos.isEmpty()) {
|
||||
if (mc.player != null) {
|
||||
mc.player.displayClientMessage(Component.translatable("message.extendedae_plus.no_recipes_found"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeInfo matching = matchById(recipeInfos, recipeId);
|
||||
List<ItemStack> selectedIngredients = selectIngredientsWithJeiPriority(matching);
|
||||
List<ItemStack> selectedOutputs = convertOutputsToItemStacks(matching);
|
||||
|
||||
PacketDistributor.sendToServer(new CreateCtrlQPatternC2SPacket(
|
||||
recipeId,
|
||||
matching.isCraftingRecipe(),
|
||||
selectedIngredients,
|
||||
selectedOutputs,
|
||||
true
|
||||
));
|
||||
PacketDistributor.sendToServer(RequestProvidersListC2SPacket.INSTANCE);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<RecipeInfo> findRecipeInfosForBookmark(Object recipeBookmark) {
|
||||
Optional<ITypedIngredient<?>> hovered = castTypedIngredient(JeiRuntimeProxy.getIngredientUnderMouse());
|
||||
if (hovered.isPresent()) {
|
||||
List<RecipeInfo> infos = RecipeFinderUtil.findRecipesByIngredient(hovered.get());
|
||||
if (!infos.isEmpty()) {
|
||||
return infos;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var getRecipeOutputMethod = recipeBookmark.getClass().getMethod("getRecipeOutput");
|
||||
Object recipeOutput = getRecipeOutputMethod.invoke(recipeBookmark);
|
||||
if (recipeOutput instanceof ITypedIngredient<?> typed) {
|
||||
return RecipeFinderUtil.findRecipesByIngredient(typed);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static RecipeInfo matchById(List<RecipeInfo> recipeInfos, ResourceLocation recipeId) {
|
||||
for (RecipeInfo info : recipeInfos) {
|
||||
if (recipeId.equals(info.getRecipeId())) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return recipeInfos.get(0);
|
||||
}
|
||||
|
||||
private static ResourceLocation getRecipeId(Object recipeBookmark) {
|
||||
try {
|
||||
var getRecipeUidMethod = recipeBookmark.getClass().getMethod("getRecipeUid");
|
||||
Object recipeId = getRecipeUidMethod.invoke(recipeBookmark);
|
||||
if (recipeId instanceof ResourceLocation rl) {
|
||||
return rl;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Optional<ITypedIngredient<?>> castTypedIngredient(Optional<?> opt) {
|
||||
if (opt == null || opt.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Object value = opt.get();
|
||||
if (value instanceof ITypedIngredient<?>) {
|
||||
return (Optional<ITypedIngredient<?>>) (Optional<?>) Optional.of(value);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static void setLastProcessingNameFromRecipe(Object recipeBase) {
|
||||
String name = null;
|
||||
if (recipeBase instanceof Recipe<?> recipe) {
|
||||
name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(recipe);
|
||||
} else if (recipeBase != null
|
||||
&& "com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) {
|
||||
name = ExtendedAEPatternUploadUtil.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 = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(inner);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null || name.isBlank()) {
|
||||
name = ExtendedAEPatternUploadUtil.deriveSearchKeyFromUnknownRecipe(recipeBase);
|
||||
}
|
||||
if (name != null && !name.isBlank()) {
|
||||
ExtendedAEPatternUploadUtil.setLastProcessingName(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ItemStack> selectIngredientsWithJeiPriority(RecipeInfo recipeInfo) {
|
||||
List<?> bookmarks = JeiRuntimeProxy.getBookmarkList();
|
||||
Map<Item, Integer> priorities = new HashMap<>();
|
||||
AtomicInteger index = new AtomicInteger(Integer.MAX_VALUE);
|
||||
|
||||
for (Object obj : bookmarks) {
|
||||
if (obj instanceof ITypedIngredient<?> ingredient) {
|
||||
ingredient.getIngredient(VanillaTypes.ITEM_STACK).ifPresent(itemStack ->
|
||||
priorities.put(itemStack.getItem(), index.getAndDecrement())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return recipeInfo.selectBestInputs(priorities);
|
||||
}
|
||||
|
||||
private static List<ItemStack> convertOutputsToItemStacks(RecipeInfo recipeInfo) {
|
||||
return recipeInfo.getOutputs().stream()
|
||||
.map(genericStack -> {
|
||||
if (genericStack.what() instanceof AEItemKey itemKey) {
|
||||
return itemKey.toStack((int) genericStack.amount());
|
||||
}
|
||||
return GenericStack.wrapInItemStack(genericStack);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +22,8 @@ public class ModNetwork {
|
|||
registrar.playToServer(OpenProviderUiC2SPacket.TYPE, OpenProviderUiC2SPacket.STREAM_CODEC, OpenProviderUiC2SPacket::handle);
|
||||
registrar.playToServer(UploadEncodedPatternToProviderC2SPacket.TYPE, UploadEncodedPatternToProviderC2SPacket.STREAM_CODEC, UploadEncodedPatternToProviderC2SPacket::handle);
|
||||
registrar.playToServer(UploadInventoryPatternToProviderC2SPacket.TYPE, UploadInventoryPatternToProviderC2SPacket.STREAM_CODEC, UploadInventoryPatternToProviderC2SPacket::handle);
|
||||
registrar.playToServer(CreateCtrlQPatternC2SPacket.TYPE, CreateCtrlQPatternC2SPacket.STREAM_CODEC, CreateCtrlQPatternC2SPacket::handle);
|
||||
registrar.playToServer(CreateAndUploadPatternC2SPacket.TYPE, CreateAndUploadPatternC2SPacket.STREAM_CODEC, CreateAndUploadPatternC2SPacket::handle);
|
||||
registrar.playToServer(EncodeWithShiftFlagC2SPacket.TYPE, EncodeWithShiftFlagC2SPacket.STREAM_CODEC, EncodeWithShiftFlagC2SPacket::handle);
|
||||
// 新增:JEI 中键打开合成界面 & 无线终端拾取方块物品
|
||||
registrar.playToServer(com.extendedae_plus.network.OpenCraftFromJeiC2SPacket.TYPE,
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ public final class JeiClientBootstrap {
|
|||
public static void register() {
|
||||
net.neoforged.neoforge.common.NeoForge.EVENT_BUS.addListener(com.extendedae_plus.client.InputEvents::onMouseButtonPre);
|
||||
net.neoforged.neoforge.common.NeoForge.EVENT_BUS.addListener(com.extendedae_plus.client.InputEvents::onKeyPressedPre);
|
||||
net.neoforged.neoforge.common.NeoForge.EVENT_BUS.addListener(com.extendedae_plus.client.event.CtrlQPatternKeyHandler::onScreenKeyPressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,54 @@ public final class JeiRuntimeProxy {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取鼠标下的配方书签(RecipeBookmark),用于 Ctrl+Q 直接识别“配方书签”而非普通配料。
|
||||
*/
|
||||
public static Optional<?> getRecipeBookmarkUnderMouse() {
|
||||
Object rt = RUNTIME;
|
||||
if (rt == null) return Optional.empty();
|
||||
try {
|
||||
Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt);
|
||||
if (overlay == null) return Optional.empty();
|
||||
|
||||
Object ingredientOpt = overlay.getClass().getMethod("getIngredientUnderMouse").invoke(overlay);
|
||||
if (!(ingredientOpt instanceof Optional<?> opt) || opt.isEmpty()) return Optional.empty();
|
||||
Object hoveredIngredient = opt.get();
|
||||
|
||||
Field f = overlay.getClass().getDeclaredField("bookmarkList");
|
||||
f.setAccessible(true);
|
||||
Object bookmarkList = f.get(overlay);
|
||||
if (bookmarkList == null) return Optional.empty();
|
||||
|
||||
Object elementsObj = bookmarkList.getClass().getMethod("getElements").invoke(bookmarkList);
|
||||
if (!(elementsObj instanceof List<?> elements)) return Optional.empty();
|
||||
|
||||
for (Object element : elements) {
|
||||
if (element == null) continue;
|
||||
Object typedIngredient = null;
|
||||
try {
|
||||
typedIngredient = element.getClass().getMethod("getTypedIngredient").invoke(element);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (typedIngredient == null || !typedIngredient.equals(hoveredIngredient)) continue;
|
||||
|
||||
Object bookmarkOpt = null;
|
||||
try {
|
||||
bookmarkOpt = element.getClass().getMethod("getBookmark").invoke(element);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (bookmarkOpt instanceof Optional<?> b && b.isPresent()) {
|
||||
Object bookmark = b.get();
|
||||
if (bookmark != null && "RecipeBookmark".equals(bookmark.getClass().getSimpleName())) {
|
||||
return Optional.of(bookmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static void addBookmark(ItemStack stack) {
|
||||
Object rt = RUNTIME;
|
||||
if (rt == null || stack == null || stack.isEmpty()) return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.networking.IGrid;
|
||||
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.me.helpers.PlayerSource;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.util.uploadPattern.CtrlQPendingUploadUtil;
|
||||
import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* C2S: 创建样板并上传到装配矩阵(合成书签分支)。
|
||||
*/
|
||||
public class CreateAndUploadPatternC2SPacket implements CustomPacketPayload {
|
||||
public static final Type<CreateAndUploadPatternC2SPacket> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "create_and_upload_pattern"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, CreateAndUploadPatternC2SPacket> STREAM_CODEC = StreamCodec.of(
|
||||
(buf, pkt) -> {
|
||||
buf.writeResourceLocation(pkt.recipeId);
|
||||
buf.writeBoolean(pkt.isCraftingPattern);
|
||||
ItemStack.OPTIONAL_LIST_STREAM_CODEC.encode(buf, pkt.selectedIngredients);
|
||||
ItemStack.OPTIONAL_LIST_STREAM_CODEC.encode(buf, pkt.outputs);
|
||||
},
|
||||
buf -> {
|
||||
ResourceLocation recipeId = buf.readResourceLocation();
|
||||
boolean isCraftingPattern = buf.readBoolean();
|
||||
List<ItemStack> ingredients = ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(buf);
|
||||
List<ItemStack> outputs = ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(buf);
|
||||
return new CreateAndUploadPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs);
|
||||
}
|
||||
);
|
||||
|
||||
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 handle(final CreateAndUploadPatternC2SPacket msg, final IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (!(ctx.player() instanceof ServerPlayer player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var recipeOpt = player.level().getRecipeManager().byKey(msg.recipeId);
|
||||
if (recipeOpt.isEmpty()) {
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.recipe_not_found"), false);
|
||||
return;
|
||||
}
|
||||
RecipeHolder<?> recipeHolder = recipeOpt.get();
|
||||
|
||||
IGrid grid = CtrlQPendingUploadUtil.findPlayerGrid(player);
|
||||
if (grid == null) {
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.no_network"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!consumeBlankPattern(player, grid)) {
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.no_blank_pattern"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack pattern = createPattern(recipeHolder, 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;
|
||||
}
|
||||
|
||||
boolean uploaded = ExtendedAEPatternUploadUtil.uploadPatternToMatrix(player, pattern, grid);
|
||||
if (!uploaded) {
|
||||
refundBlankPattern(player, grid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
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(
|
||||
RecipeHolder<?> recipeHolder,
|
||||
boolean isCrafting,
|
||||
List<ItemStack> selectedIngredients,
|
||||
List<ItemStack> selectedOutputs,
|
||||
ServerPlayer player
|
||||
) {
|
||||
try {
|
||||
if (isCrafting && recipeHolder.value() instanceof CraftingRecipe) {
|
||||
@SuppressWarnings("unchecked")
|
||||
RecipeHolder<CraftingRecipe> craftingHolder = (RecipeHolder<CraftingRecipe>) (RecipeHolder<?>) recipeHolder;
|
||||
|
||||
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 = ItemStack.EMPTY;
|
||||
if (!selectedOutputs.isEmpty()) {
|
||||
output = selectedOutputs.get(0).copy();
|
||||
}
|
||||
if (output.isEmpty()) {
|
||||
output = recipeHolder.value().getResultItem(player.level().registryAccess()).copy();
|
||||
}
|
||||
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern(
|
||||
craftingHolder,
|
||||
inputs,
|
||||
output,
|
||||
true,
|
||||
false
|
||||
);
|
||||
CustomData.update(DataComponents.CUSTOM_DATA, encodedPattern, tag -> tag.putString("encodePlayer", player.getGameProfile().getName()));
|
||||
return encodedPattern;
|
||||
}
|
||||
|
||||
List<GenericStack> inputs = new ArrayList<>();
|
||||
List<GenericStack> outputs = new ArrayList<>();
|
||||
|
||||
for (ItemStack item : selectedIngredients) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack stack = GenericStack.unwrapItemStack(item);
|
||||
if (stack == null) {
|
||||
stack = GenericStack.fromItemStack(item);
|
||||
}
|
||||
if (stack != null) {
|
||||
inputs.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack item : selectedOutputs) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack stack = GenericStack.unwrapItemStack(item);
|
||||
if (stack == null) {
|
||||
stack = GenericStack.fromItemStack(item);
|
||||
}
|
||||
if (stack != null) {
|
||||
outputs.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern(inputs, outputs);
|
||||
CustomData.update(DataComponents.CUSTOM_DATA, encodedPattern, tag -> tag.putString("encodePlayer", player.getGameProfile().getName()));
|
||||
return encodedPattern;
|
||||
} catch (Throwable ignored) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.energy.IEnergyService;
|
||||
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.WirelessTerminalItem;
|
||||
import appeng.me.helpers.PlayerSource;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.util.uploadPattern.CtrlQPendingUploadUtil;
|
||||
import com.extendedae_plus.util.wireless.WirelessTerminalLocator;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
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.component.CustomData;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* C2S: Ctrl+Q 快速创建样板请求。
|
||||
*/
|
||||
public class CreateCtrlQPatternC2SPacket implements CustomPacketPayload {
|
||||
public static final Type<CreateCtrlQPatternC2SPacket> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "create_ctrlq_pattern"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, CreateCtrlQPatternC2SPacket> STREAM_CODEC = StreamCodec.of(
|
||||
(buf, pkt) -> {
|
||||
buf.writeResourceLocation(pkt.recipeId);
|
||||
buf.writeBoolean(pkt.isCraftingPattern);
|
||||
ItemStack.OPTIONAL_LIST_STREAM_CODEC.encode(buf, pkt.selectedIngredients);
|
||||
ItemStack.OPTIONAL_LIST_STREAM_CODEC.encode(buf, pkt.outputs);
|
||||
buf.writeBoolean(pkt.openProviderSelector);
|
||||
},
|
||||
buf -> {
|
||||
ResourceLocation recipeId = buf.readResourceLocation();
|
||||
boolean isCraftingPattern = buf.readBoolean();
|
||||
List<ItemStack> ingredients = ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(buf);
|
||||
List<ItemStack> outputs = ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(buf);
|
||||
|
||||
boolean openProviderSelector = buf.readableBytes() > 0 && buf.readBoolean();
|
||||
return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs, openProviderSelector);
|
||||
}
|
||||
);
|
||||
|
||||
private final ResourceLocation recipeId;
|
||||
private final boolean isCraftingPattern;
|
||||
private final List<ItemStack> selectedIngredients;
|
||||
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 handle(final CreateCtrlQPatternC2SPacket msg, final IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (!(ctx.player() instanceof ServerPlayer player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var recipeOpt = player.level().getRecipeManager().byKey(msg.recipeId);
|
||||
if (recipeOpt.isEmpty()) {
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.recipe_not_found"), false);
|
||||
return;
|
||||
}
|
||||
RecipeHolder<?> recipeHolder = recipeOpt.get();
|
||||
|
||||
if (!consumeBlankPattern(player)) {
|
||||
player.displayClientMessage(Component.translatable("message.extendedae_plus.no_blank_pattern"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack pattern = createPattern(recipeHolder, 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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.openProviderSelector) {
|
||||
String pendingId = CtrlQPendingUploadUtil.beginPendingCtrlQUpload(player, pattern);
|
||||
if (pendingId == null) {
|
||||
if (!player.getInventory().add(pattern)) {
|
||||
player.drop(pattern, false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.getInventory().add(pattern)) {
|
||||
player.drop(pattern, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
private static boolean consumeBlankPattern(ServerPlayer player) {
|
||||
if (tryExtractFromNetwork(player)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Inventory inventory = player.getInventory();
|
||||
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||
ItemStack stack = inventory.getItem(i);
|
||||
if (AEItems.BLANK_PATTERN.is(stack)) {
|
||||
stack.shrink(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean tryExtractFromNetwork(ServerPlayer player) {
|
||||
WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player);
|
||||
ItemStack terminal = located.stack;
|
||||
if (terminal.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null;
|
||||
if (wt == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IGrid grid = wt.getLinkedGrid(terminal, player.serverLevel(), null);
|
||||
if (grid == null) {
|
||||
return false;
|
||||
}
|
||||
if (!wt.hasPower(player, 0.5, terminal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnergyService energy = grid.getEnergyService();
|
||||
MEStorage storage = grid.getStorageService().getInventory();
|
||||
long extracted = StorageHelper.poweredExtraction(
|
||||
energy,
|
||||
storage,
|
||||
appeng.api.stacks.AEItemKey.of(AEItems.BLANK_PATTERN.stack()),
|
||||
1,
|
||||
new PlayerSource(player)
|
||||
);
|
||||
if (extracted > 0) {
|
||||
wt.usePower(player, 0.5, terminal);
|
||||
located.commit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ItemStack createPattern(
|
||||
RecipeHolder<?> recipeHolder,
|
||||
boolean isCrafting,
|
||||
List<ItemStack> selectedIngredients,
|
||||
List<ItemStack> selectedOutputs,
|
||||
ServerPlayer player
|
||||
) {
|
||||
try {
|
||||
if (isCrafting && recipeHolder.value() instanceof CraftingRecipe) {
|
||||
@SuppressWarnings("unchecked")
|
||||
RecipeHolder<CraftingRecipe> craftingHolder = (RecipeHolder<CraftingRecipe>) (RecipeHolder<?>) recipeHolder;
|
||||
|
||||
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 = ItemStack.EMPTY;
|
||||
if (!selectedOutputs.isEmpty()) {
|
||||
output = selectedOutputs.get(0).copy();
|
||||
}
|
||||
if (output.isEmpty()) {
|
||||
output = recipeHolder.value().getResultItem(player.level().registryAccess()).copy();
|
||||
}
|
||||
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern(
|
||||
craftingHolder,
|
||||
inputs,
|
||||
output,
|
||||
true,
|
||||
false
|
||||
);
|
||||
CustomData.update(DataComponents.CUSTOM_DATA, encodedPattern, tag -> tag.putString("encodePlayer", player.getGameProfile().getName()));
|
||||
return encodedPattern;
|
||||
}
|
||||
|
||||
List<GenericStack> inputs = new ArrayList<>();
|
||||
List<GenericStack> outputs = new ArrayList<>();
|
||||
|
||||
for (ItemStack item : selectedIngredients) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack stack = GenericStack.unwrapItemStack(item);
|
||||
if (stack == null) {
|
||||
stack = GenericStack.fromItemStack(item);
|
||||
}
|
||||
if (stack != null) {
|
||||
inputs.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack item : selectedOutputs) {
|
||||
if (!item.isEmpty()) {
|
||||
GenericStack stack = GenericStack.unwrapItemStack(item);
|
||||
if (stack == null) {
|
||||
stack = GenericStack.fromItemStack(item);
|
||||
}
|
||||
if (stack != null) {
|
||||
outputs.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern(inputs, outputs);
|
||||
CustomData.update(DataComponents.CUSTOM_DATA, encodedPattern, tag -> tag.putString("encodePlayer", player.getGameProfile().getName()));
|
||||
return encodedPattern;
|
||||
} catch (Throwable ignored) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import appeng.helpers.patternprovider.PatternContainer;
|
|||
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.util.uploadPattern.CtrlQPendingUploadUtil;
|
||||
import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
|
|
@ -32,6 +33,27 @@ public class RequestProvidersListC2SPacket implements CustomPacketPayload {
|
|||
public static void handle(final RequestProvidersListC2SPacket msg, final IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
||||
|
||||
// Ctrl+Q pending 模式:不依赖编码终端,直接基于玩家网络给出列表(负数索引 ID)
|
||||
if (CtrlQPendingUploadUtil.hasPendingCtrlQPattern(player)) {
|
||||
List<PatternContainer> containers = CtrlQPendingUploadUtil.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 = ExtendedAEPatternUploadUtil.getAvailableSlots(c);
|
||||
if (empty <= 0) continue;
|
||||
long encodedId = -1L - i;
|
||||
idxIds.add(encodedId);
|
||||
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
|
||||
slots.add(empty);
|
||||
}
|
||||
player.connection.send(new ProvidersListS2CPacket(idxIds, names, slots));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
|
||||
|
||||
// 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import com.extendedae_plus.util.uploadPattern.CtrlQPendingUploadUtil;
|
||||
import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
|
|
@ -29,6 +30,14 @@ public class UploadEncodedPatternToProviderC2SPacket implements CustomPacketPayl
|
|||
public static void handle(final UploadEncodedPatternToProviderC2SPacket msg, final IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
||||
|
||||
// 优先处理 Ctrl+Q pending 上传
|
||||
if (CtrlQPendingUploadUtil.hasPendingCtrlQPattern(player)) {
|
||||
if (CtrlQPendingUploadUtil.uploadPendingCtrlQPattern(player, msg.providerId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
|
||||
// 支持两种模式:
|
||||
// 1) providerId >= 0: 访问终端 byId 模式
|
||||
|
|
|
|||
227
src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java
Normal file
227
src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import appeng.api.stacks.AEFluidKey;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.gui.IRecipeLayoutDrawable;
|
||||
import mezz.jei.api.gui.ingredient.IRecipeSlotView;
|
||||
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
|
||||
import mezz.jei.api.helpers.IJeiHelpers;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import mezz.jei.api.neoforge.NeoForgeTypes;
|
||||
import mezz.jei.api.recipe.IFocus;
|
||||
import mezz.jei.api.recipe.IFocusFactory;
|
||||
import mezz.jei.api.recipe.IRecipeLookup;
|
||||
import mezz.jei.api.recipe.IRecipeManager;
|
||||
import mezz.jei.api.recipe.RecipeIngredientRole;
|
||||
import mezz.jei.api.recipe.RecipeType;
|
||||
import mezz.jei.api.recipe.category.IRecipeCategory;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 基于 JEI 运行时查找配方,提取输入输出槽位与数量信息。
|
||||
*/
|
||||
public final class RecipeFinderUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder");
|
||||
|
||||
private RecipeFinderUtil() {
|
||||
}
|
||||
|
||||
public static List<RecipeInfo> findRecipesByIngredient(ITypedIngredient<?> ingredient) {
|
||||
if (ingredient == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
Object runtimeObj = JeiRuntimeProxy.get();
|
||||
if (!(runtimeObj instanceof IJeiRuntime jeiRuntime)) {
|
||||
LOGGER.warn("[RecipeFinder] JEI runtime not available");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
IJeiHelpers jeiHelpers = jeiRuntime.getJeiHelpers();
|
||||
IRecipeManager recipeManager = jeiRuntime.getRecipeManager();
|
||||
IFocusFactory focusFactory = jeiHelpers.getFocusFactory();
|
||||
IFocus<?> outputFocus = focusFactory.createFocus(RecipeIngredientRole.OUTPUT, ingredient);
|
||||
List<RecipeInfo> results = new ArrayList<>();
|
||||
|
||||
// 1) Crafting recipes
|
||||
try {
|
||||
IRecipeCategory<RecipeHolder<CraftingRecipe>> category = recipeManager.getRecipeCategory(RecipeTypes.CRAFTING);
|
||||
recipeManager.createRecipeLookup(RecipeTypes.CRAFTING)
|
||||
.limitFocus(List.of(outputFocus))
|
||||
.get()
|
||||
.forEach(recipeHolder -> {
|
||||
Optional<IRecipeLayoutDrawable<Object>> layoutOpt = createLayout(recipeManager, category, recipeHolder, focusFactory);
|
||||
layoutOpt.ifPresent(layout -> {
|
||||
RecipeInfo info = extractRecipeInfo(recipeHolder, layout, true);
|
||||
if (info != null) {
|
||||
results.add(info);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
LOGGER.warn("[RecipeFinder] Error searching crafting recipes: {}", t.toString());
|
||||
}
|
||||
|
||||
// 2) Other recipe types
|
||||
try {
|
||||
jeiHelpers.getAllRecipeTypes().forEach(recipeType -> {
|
||||
if (recipeType.equals(RecipeTypes.CRAFTING)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
IRecipeCategory<?> category = recipeManager.getRecipeCategory(recipeType);
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
IRecipeLookup<Object> lookup = (IRecipeLookup) recipeManager.createRecipeLookup((RecipeType) recipeType);
|
||||
|
||||
lookup.limitFocus(List.of(outputFocus))
|
||||
.get()
|
||||
.forEach(recipeObj -> {
|
||||
Optional<IRecipeLayoutDrawable<Object>> layoutOpt = createLayout(recipeManager, category, recipeObj, focusFactory);
|
||||
layoutOpt.ifPresent(layout -> {
|
||||
RecipeInfo info = extractRecipeInfo(recipeObj, layout, false);
|
||||
if (info != null) {
|
||||
results.add(info);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
LOGGER.warn("[RecipeFinder] Error searching other recipes: {}", t.toString());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static RecipeInfo selectBestRecipe(List<RecipeInfo> recipes) {
|
||||
if (recipes == null || recipes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (RecipeInfo info : recipes) {
|
||||
if (info.isCraftingRecipe()) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return recipes.get(0);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Optional<IRecipeLayoutDrawable<Object>> createLayout(
|
||||
IRecipeManager recipeManager,
|
||||
IRecipeCategory<?> category,
|
||||
Object recipeObj,
|
||||
IFocusFactory focusFactory
|
||||
) {
|
||||
return (Optional) recipeManager.createRecipeLayoutDrawable(
|
||||
(IRecipeCategory) category,
|
||||
recipeObj,
|
||||
focusFactory.getEmptyFocusGroup()
|
||||
);
|
||||
}
|
||||
|
||||
private static RecipeInfo extractRecipeInfo(Object recipeObj, IRecipeLayoutDrawable<Object> layout, boolean isCrafting) {
|
||||
try {
|
||||
ResourceLocation recipeId = extractRecipeId(recipeObj);
|
||||
if (recipeId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IRecipeSlotsView slotsView = layout.getRecipeSlotsView();
|
||||
List<List<GenericStack>> inputs = new ArrayList<>();
|
||||
List<GenericStack> outputs = new ArrayList<>();
|
||||
|
||||
for (IRecipeSlotView slot : slotsView.getSlotViews(RecipeIngredientRole.INPUT)) {
|
||||
List<GenericStack> slotStacks = new ArrayList<>();
|
||||
for (ITypedIngredient<?> typed : slot.getAllIngredientsList()) {
|
||||
if (typed == null) {
|
||||
continue;
|
||||
}
|
||||
GenericStack stack = convertToGenericStack(typed);
|
||||
if (stack != null) {
|
||||
slotStacks.add(stack);
|
||||
}
|
||||
}
|
||||
inputs.add(slotStacks);
|
||||
}
|
||||
|
||||
for (IRecipeSlotView slot : slotsView.getSlotViews(RecipeIngredientRole.OUTPUT)) {
|
||||
for (ITypedIngredient<?> typed : slot.getAllIngredientsList()) {
|
||||
if (typed == null) {
|
||||
continue;
|
||||
}
|
||||
GenericStack stack = convertToGenericStack(typed);
|
||||
if (stack != null) {
|
||||
outputs.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RecipeInfo(recipeObj, recipeId, isCrafting, inputs, outputs);
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ResourceLocation extractRecipeId(Object recipeObj) {
|
||||
if (recipeObj == null) {
|
||||
return null;
|
||||
}
|
||||
if (recipeObj instanceof RecipeHolder<?> holder) {
|
||||
return holder.id();
|
||||
}
|
||||
try {
|
||||
var m = recipeObj.getClass().getMethod("getId");
|
||||
Object id = m.invoke(recipeObj);
|
||||
if (id instanceof ResourceLocation rl) {
|
||||
return rl;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
try {
|
||||
var m = recipeObj.getClass().getMethod("getRecipeUid");
|
||||
Object id = m.invoke(recipeObj);
|
||||
if (id instanceof ResourceLocation rl) {
|
||||
return rl;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GenericStack convertToGenericStack(ITypedIngredient<?> typedIngredient) {
|
||||
if (typedIngredient.getType() == VanillaTypes.ITEM_STACK) {
|
||||
ItemStack itemStack = (ItemStack) typedIngredient.getIngredient();
|
||||
if (!itemStack.isEmpty()) {
|
||||
AEItemKey itemKey = AEItemKey.of(itemStack);
|
||||
if (itemKey != null) {
|
||||
return new GenericStack(itemKey, itemStack.getCount());
|
||||
}
|
||||
}
|
||||
} else if (typedIngredient.getType() == NeoForgeTypes.FLUID_STACK) {
|
||||
FluidStack fluidStack = (FluidStack) typedIngredient.getIngredient();
|
||||
if (!fluidStack.isEmpty()) {
|
||||
AEFluidKey fluidKey = AEFluidKey.of(fluidStack);
|
||||
if (fluidKey != null) {
|
||||
return new GenericStack(fluidKey, fluidStack.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
98
src/main/java/com/extendedae_plus/util/RecipeInfo.java
Normal file
98
src/main/java/com/extendedae_plus/util/RecipeInfo.java
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import appeng.api.stacks.AEFluidKey;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 配方完整信息(包含配方ID、输入槽位候选项、输出)
|
||||
*/
|
||||
public class RecipeInfo {
|
||||
private final Object recipeBase;
|
||||
private final ResourceLocation recipeId;
|
||||
private final boolean craftingRecipe;
|
||||
private final List<List<GenericStack>> inputs;
|
||||
private final List<GenericStack> outputs;
|
||||
|
||||
public RecipeInfo(
|
||||
Object recipeBase,
|
||||
ResourceLocation recipeId,
|
||||
boolean craftingRecipe,
|
||||
List<List<GenericStack>> inputs,
|
||||
List<GenericStack> outputs
|
||||
) {
|
||||
this.recipeBase = recipeBase;
|
||||
this.recipeId = recipeId;
|
||||
this.craftingRecipe = craftingRecipe;
|
||||
this.inputs = inputs;
|
||||
this.outputs = outputs;
|
||||
}
|
||||
|
||||
public Object getRecipeBase() {
|
||||
return recipeBase;
|
||||
}
|
||||
|
||||
public ResourceLocation getRecipeId() {
|
||||
return recipeId;
|
||||
}
|
||||
|
||||
public boolean isCraftingRecipe() {
|
||||
return craftingRecipe;
|
||||
}
|
||||
|
||||
public List<List<GenericStack>> getInputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public List<GenericStack> getOutputs() {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
public List<ItemStack> selectBestInputs(Map<Item, Integer> bookmarkPriorities) {
|
||||
List<ItemStack> selected = new ArrayList<>();
|
||||
for (List<GenericStack> slotOptions : inputs) {
|
||||
if (slotOptions.isEmpty()) {
|
||||
selected.add(ItemStack.EMPTY);
|
||||
continue;
|
||||
}
|
||||
|
||||
GenericStack best = slotOptions.get(0);
|
||||
int bestPriority = getPriority(best, bookmarkPriorities);
|
||||
for (int i = 1; i < slotOptions.size(); i++) {
|
||||
GenericStack option = slotOptions.get(i);
|
||||
int priority = getPriority(option, bookmarkPriorities);
|
||||
if (priority < bestPriority) {
|
||||
bestPriority = priority;
|
||||
best = option;
|
||||
}
|
||||
}
|
||||
selected.add(toItemStack(best));
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private int getPriority(GenericStack stack, Map<Item, Integer> priorities) {
|
||||
if (stack.what() instanceof AEItemKey itemKey) {
|
||||
return priorities.getOrDefault(itemKey.getItem(), Integer.MAX_VALUE);
|
||||
}
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
private ItemStack toItemStack(GenericStack stack) {
|
||||
if (stack.what() instanceof AEItemKey itemKey) {
|
||||
return itemKey.toStack((int) stack.amount());
|
||||
}
|
||||
if (stack.what() instanceof AEFluidKey) {
|
||||
return GenericStack.wrapInItemStack(stack);
|
||||
}
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
package com.extendedae_plus.util.uploadPattern;
|
||||
|
||||
import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.inventories.InternalInventory;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.helpers.patternprovider.PatternContainer;
|
||||
import appeng.items.tools.powered.WirelessTerminalItem;
|
||||
import appeng.util.inv.FilteredInternalInventory;
|
||||
import appeng.util.inv.filter.IAEItemFilter;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import com.extendedae_plus.util.wireless.WirelessTerminalLocator;
|
||||
import de.mari_023.ae2wtlib.api.registration.WTDefinition;
|
||||
import de.mari_023.ae2wtlib.api.terminal.WTMenuHost;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Ctrl+Q 临时样板缓存与上传逻辑(pending provider upload)。
|
||||
*/
|
||||
public final class CtrlQPendingUploadUtil {
|
||||
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 CtrlQPendingUploadUtil() {
|
||||
}
|
||||
|
||||
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.saveOptional(player.registryAccess()));
|
||||
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();
|
||||
}
|
||||
|
||||
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.saveOptional(player.registryAccess()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<PatternContainer> listAvailableProvidersFromPlayerNetwork(ServerPlayer player) {
|
||||
return listAvailableProvidersFromGrid(findPlayerGrid(player));
|
||||
}
|
||||
|
||||
public static IGrid findPlayerGrid(ServerPlayer player) {
|
||||
WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player);
|
||||
ItemStack terminal = located.stack;
|
||||
if (terminal.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null;
|
||||
if (wt != null) {
|
||||
return wt.getLinkedGrid(terminal, player.serverLevel(), null);
|
||||
}
|
||||
|
||||
String curiosSlotId = located.getCuriosSlotId();
|
||||
int curiosIndex = located.getCuriosIndex();
|
||||
if (curiosSlotId != null && curiosIndex >= 0) {
|
||||
try {
|
||||
WTDefinition def = WTDefinition.ofOrNull(terminal);
|
||||
if (def == null) return null;
|
||||
WTMenuHost wtHost = def.wTMenuHostFactory().create(
|
||||
def.item(),
|
||||
player,
|
||||
new CuriosItemLocator(curiosSlotId, curiosIndex),
|
||||
(p, sub) -> {
|
||||
}
|
||||
);
|
||||
if (wtHost == null || wtHost.getActionableNode() == null) return null;
|
||||
return wtHost.getActionableNode().getGrid();
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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")
|
||||
Class<? extends PatternContainer> containerClass = (Class<? extends PatternContainer>) machineClass;
|
||||
for (var container : grid.getActiveMachines(containerClass)) {
|
||||
if (container == null || !container.isVisibleInTerminal()) continue;
|
||||
InternalInventory inv = container.getTerminalPatternInventory();
|
||||
if (inv == null || inv.size() <= 0) continue;
|
||||
boolean hasEmpty = false;
|
||||
for (int i = 0; i < inv.size(); i++) {
|
||||
if (inv.getStackInSlot(i).isEmpty()) {
|
||||
hasEmpty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasEmpty) list.add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
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.parseOptional(player.registryAccess(), 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 CtrlQPatternFilter()).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 = ExtendedAEPatternUploadUtil.getProviderDisplayName(target);
|
||||
List<PatternContainer> tryList = new ArrayList<>();
|
||||
tryList.add(target);
|
||||
for (PatternContainer container : all) {
|
||||
if (container == null || container == target) continue;
|
||||
String name = ExtendedAEPatternUploadUtil.getProviderDisplayName(container);
|
||||
if (name != null && name.equals(targetName)) {
|
||||
tryList.add(container);
|
||||
}
|
||||
}
|
||||
return tryList;
|
||||
}
|
||||
|
||||
private static class CtrlQPatternFilter implements IAEItemFilter {
|
||||
@Override
|
||||
public boolean allowExtract(InternalInventory inv, int slot, int amount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowInsert(InternalInventory inv, int slot, ItemStack stack) {
|
||||
return !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -517,6 +517,67 @@ public class ExtendedAEPatternUploadUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定的已编码样板直接上传到装配矩阵(不依赖编码终端槽位)。
|
||||
*
|
||||
* @param player 服务器玩家
|
||||
* @param pattern 已编码样板
|
||||
* @param grid AE 网络
|
||||
* @return 是否成功插入矩阵
|
||||
*/
|
||||
public static boolean uploadPatternToMatrix(ServerPlayer player, ItemStack pattern, IGrid grid) {
|
||||
if (player == null || grid == null || pattern == null || pattern.isEmpty() || !PatternDetailsHelper.isEncodedPattern(pattern)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IPatternDetails details = PatternDetailsHelper.decodePattern(pattern, player.level());
|
||||
if (!(details instanceof AECraftingPattern
|
||||
|| details instanceof AESmithingTablePattern
|
||||
|| details instanceof AEStonecuttingPattern)) {
|
||||
sendMessage(player, "extendedae_plus.upload_to_matrix.fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matrixContainsPattern(grid, pattern)) {
|
||||
if (player != null) {
|
||||
player.sendSystemMessage(Component.translatable("extendedae_plus.message.matrix.duplicate"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
|
||||
if (!inventories.isEmpty()) {
|
||||
for (InternalInventory inv : inventories) {
|
||||
if (inv == null) continue;
|
||||
ItemStack toInsert = pattern.copy();
|
||||
ItemStack remain = inv.addItems(toInsert);
|
||||
if (remain.getCount() < toInsert.getCount()) {
|
||||
sendMessage(player, "extendedae_plus.upload_to_matrix.success");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<?> handlers = findAllMatrixPatternHandlers(grid);
|
||||
if (!handlers.isEmpty()) {
|
||||
for (Object cap : handlers) {
|
||||
ItemStack toInsert = pattern.copy();
|
||||
ItemStack remain = insertIntoAnySlot(cap, toInsert);
|
||||
if (remain.getCount() < toInsert.getCount()) {
|
||||
sendMessage(player, "extendedae_plus.upload_to_matrix.success");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inventories.isEmpty() && handlers.isEmpty()) {
|
||||
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix");
|
||||
} else {
|
||||
sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在给定 AE Grid 中收集所有已成型且在线的装配矩阵“图样模块”的用于外部插入的内部库存。
|
||||
* 优先使用 TileAssemblerMatrixPattern#getExposedInventory(仅允许插入,且已带AE过滤规则)。
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@
|
|||
"extendedae_plus.screen.remove_mapping": "Remove Mapping",
|
||||
"extendedae_plus.screen.cn_name": "Chinese Name",
|
||||
"extendedae_plus.button.choose_provider": "Upload Pattern",
|
||||
"key.extendedae_plus.create_pattern": "Create Pattern (Ctrl+Q)",
|
||||
"key.categories.extendedae_plus": "ExtendedAE Plus",
|
||||
"message.extendedae_plus.hover_item_first": "Hover an item first.",
|
||||
"message.extendedae_plus.no_recipes_found": "No related recipes found.",
|
||||
"message.extendedae_plus.recipe_not_found": "Recipe not found.",
|
||||
"message.extendedae_plus.no_blank_pattern": "No blank pattern available.",
|
||||
"message.extendedae_plus.pattern_creation_failed": "Failed to create encoded pattern.",
|
||||
"message.extendedae_plus.no_network": "Unable to access AE network.",
|
||||
"extendedae_plus.pattern.hovertext.player": "Encoded by %s",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker": "Entity Accelerator",
|
||||
|
|
@ -231,4 +239,4 @@
|
|||
"extendedae_plus.jade.network.offline": "Offline",
|
||||
|
||||
"extendedae_plus.screen.global_controller_title": "Pattern Provider Status Controller"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@
|
|||
"extendedae_plus.screen.remove_mapping": "删除映射",
|
||||
"extendedae_plus.screen.cn_name": "中文名",
|
||||
"extendedae_plus.button.choose_provider":"上传样板",
|
||||
"key.extendedae_plus.create_pattern": "快速创建样板 (Ctrl+Q)",
|
||||
"key.categories.extendedae_plus": "扩展AE Plus",
|
||||
"message.extendedae_plus.hover_item_first": "请先将鼠标悬停在物品上。",
|
||||
"message.extendedae_plus.no_recipes_found": "未找到相关配方。",
|
||||
"message.extendedae_plus.recipe_not_found": "未找到该配方。",
|
||||
"message.extendedae_plus.no_blank_pattern": "没有可用空白样板。",
|
||||
"message.extendedae_plus.pattern_creation_failed": "创建编码样板失败。",
|
||||
"message.extendedae_plus.no_network": "无法访问 AE 网络。",
|
||||
"extendedae_plus.pattern.hovertext.player": "由 %s 编码",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker": "实体加速器",
|
||||
|
|
@ -226,4 +234,4 @@
|
|||
"extendedae_plus.jade.network.offline": "设备离线",
|
||||
|
||||
"extendedae_plus.screen.global_controller_title": "样板供应器状态控制器"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user