快捷从EMI配方树界面编写/上传样板

This commit is contained in:
XianYuFish001 2025-09-25 13:30:19 +08:00
parent be3bc1dcf8
commit 7fe6d63c03
8 changed files with 324 additions and 3 deletions

View File

@ -15,7 +15,7 @@ loader_version_range=[1,)
mod_id=extendedae_plus mod_id=extendedae_plus
mod_name=ExtendedAE-Plus mod_name=ExtendedAE-Plus
mod_license=LGPL-3.0-or-later mod_license=LGPL-3.0-or-later
mod_version=1.0.0fix mod_version=1.0.1
mod_group_id=com.extendedae_plus mod_group_id=com.extendedae_plus
mod_authors=GaLicn, XianYuFish001 mod_authors=GaLicn, XianYuFish001
mod_description= mod_description=

View File

@ -7,7 +7,6 @@ import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.init.*; import com.extendedae_plus.init.*;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.util.storage.InfinityStorageManager; import com.extendedae_plus.util.storage.InfinityStorageManager;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;

View File

@ -1,6 +1,7 @@
package com.extendedae_plus.network; package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.network.*;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
public class ModNetwork { public class ModNetwork {
@ -39,5 +40,8 @@ public class ModNetwork {
registrar.playToServer(C2SPacketTargetKeyTriggered.TYPE, registrar.playToServer(C2SPacketTargetKeyTriggered.TYPE,
C2SPacketTargetKeyTriggered.STREAM_CODEC, C2SPacketTargetKeyTriggered.STREAM_CODEC,
C2SPacketTargetKeyTriggered::handle); C2SPacketTargetKeyTriggered::handle);
registrar.playToServer(C2SPacketStoneCuttingID.TYPE,
C2SPacketStoneCuttingID.STREAM_CODEC,
C2SPacketStoneCuttingID::handle);
} }
} }

View File

@ -0,0 +1,96 @@
package com.extendedae_plus.integration.emi;
import appeng.api.stacks.GenericStack;
import appeng.integration.modules.emi.EmiStackHelper;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.bom.FoldState;
import dev.emi.emi.bom.MaterialNode;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class HandlerBoMRecipes {
public static HashMap<ResourceLocation, GenericStack> collectInputs(MaterialNode parentNode) {
var recipe = parentNode.recipe;
if (parentNode.state != FoldState.EXPANDED) return new HashMap<>();
if (parentNode.children == null) return new HashMap<>();
HashMap<ResourceLocation, GenericStack> results = new HashMap<>();
var type = recipe.getBackingRecipe().value().getType();
parentNode.children.forEach(child -> {
EmiStack stack;
if (child.recipe != null && type != RecipeType.STONECUTTING)
stack = child.recipe.getOutputs().getFirst().getEmiStacks().getFirst();
else stack = child.ingredient.getEmiStacks().getFirst();
recipe.getInputs().forEach(input -> {
if (input.getEmiStacks().contains(stack)) {
stack.setAmount(input.getEmiStacks().getFirst().getAmount());
GenericStack genericStack = EmiStackHelper.toGenericStack(stack);
if (genericStack == null) return;
results.put(input.getEmiStacks().getFirst().getId(), genericStack);
}
});
});
return results;
}
// public static void handle(C2SPacketStoneCuttingID packet, ServerPlayer player) {
// if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
//// RecipeHolder<?> recipeHolder = player.level().getRecipeManager()
//// .byKey(packet.recipeID()).orElse(null);
// EmiRecipe recipe = EmiApi.getRecipeManager()
// .getRecipe(packet.recipeID());
//
// if (recipe == null) return;
//
// try {
// menu.clear();
// List<List<GenericStack>> modifiedInputs = updateRecipe(recipe, packet.batches(), packet.selectedStacks());
//
// // 遍历原配方的每个输入
// for (EmiIngredient originalInput : recipe.getInputs()) {
// if (!originalInput.getEmiStacks().isEmpty()) {
// EmiStack firstStack = originalInput.getEmiStacks().getFirst();
// ResourceLocation stackId = firstStack.getId();
//
// if (packet.selectedStacks().containsKey(stackId))
// modifiedInputs.add(List.of(
// packet.selectedStacks().getOrDefault(stackId, EmiStackHelper.toGenericStack(firstStack))));
// }
// }
//
// // 获取输出保持不变
// List<GenericStack> outputs = EmiStackHelper.ofOutputs(recipe);
//
// // 编码修改后的配方
//// EncodingHelper.encodeProcessingRecipe(menu, modifiedInputs, outputs);
// } catch (Exception e) {
// ExtendedAEPlus.LOGGER.debug("encode failed:", e);
// }
// }
public static List<List<GenericStack>> updateRecipe(EmiRecipe original, long batches,
HashMap<ResourceLocation, GenericStack> selectedInputs) {
List<List<GenericStack>> modifiedInputs = new ArrayList<>();
EmiStackHelper.ofInputs(original).forEach(stacks -> {
if (!stacks.isEmpty()) {
GenericStack originStack = stacks.getFirst();
ResourceLocation stackId = originStack.what().getId();
if (selectedInputs.containsKey(stackId))
modifiedInputs.add(List.of(
selectedInputs.getOrDefault(stackId, originStack)));
} else modifiedInputs.add(List.of());
});
return modifiedInputs;
}
}

View File

@ -0,0 +1,100 @@
package com.extendedae_plus.integration.emi;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.GenericStack;
import appeng.core.network.ServerboundPacket;
import appeng.core.network.serverbound.InventoryActionPacket;
import appeng.helpers.InventoryAction;
import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.parts.encoding.EncodingMode;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType;
import net.neoforged.neoforge.network.PacketDistributor;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Predicate;
public class PatternFillingHelper {
public static void encodeCraftingRecipe(PatternEncodingTermMenu menu,
@Nullable RecipeHolder<?> recipe,
List<List<GenericStack>> genericIngredients,
Predicate<ItemStack> visiblePredicate) {
if (recipe != null && recipe.value().getType().equals(RecipeType.STONECUTTING)) {
menu.setMode(EncodingMode.STONECUTTING);
menu.setStonecuttingRecipeId(recipe.id());
} else if (recipe != null && recipe.value().getType().equals(RecipeType.SMITHING))
menu.setMode(EncodingMode.SMITHING_TABLE);
else menu.setMode(EncodingMode.CRAFTING);
var encodedInputs = NonNullList.withSize(menu.getCraftingGridSlots().length, ItemStack.EMPTY);
for (int slot = 0; slot < genericIngredients.size(); slot++) {
var genericIngredient = genericIngredients.get(slot);
if (genericIngredient.isEmpty()) continue;
GenericStack selectedStack = null;
for (var stack : genericIngredient) {
if (stack.what() instanceof AEItemKey itemKey) {
ItemStack itemStack = itemKey.toStack();
if (visiblePredicate.test(itemStack)) {
selectedStack = stack;
break;
}
}
}
if (selectedStack == null) selectedStack = genericIngredient.getFirst();
if (selectedStack.what() instanceof AEItemKey itemKey)
encodedInputs.set(slot, itemKey.toStack());
else encodedInputs.set(slot, GenericStack.wrapInItemStack(selectedStack.what(), 1));
}
for (int i = 0; i < encodedInputs.size(); i++) {
ItemStack encodedInput = encodedInputs.get(i);
ServerboundPacket message = new InventoryActionPacket(
InventoryAction.SET_FILTER, menu.getCraftingGridSlots()[i].index, encodedInput);
PacketDistributor.sendToServer(message);
}
for (var outputSlot : menu.getProcessingOutputSlots()) {
ServerboundPacket message = new InventoryActionPacket(
InventoryAction.SET_FILTER, outputSlot.index, ItemStack.EMPTY);
PacketDistributor.sendToServer(message);
}
}
public static void encodeProcessingRecipe(PatternEncodingTermMenu menu, List<List<GenericStack>> genericIngredients,
List<GenericStack> genericResults) {
menu.setMode(EncodingMode.PROCESSING);
for (int i = 0; i < menu.getProcessingInputSlots().length; i++) {
ItemStack stackToSet = ItemStack.EMPTY;
if (i < genericIngredients.size() && !genericIngredients.get(i).isEmpty()) {
GenericStack selectedStack = genericIngredients.get(i).getFirst();
stackToSet = GenericStack.wrapInItemStack(selectedStack);
}
ServerboundPacket message = new InventoryActionPacket(
InventoryAction.SET_FILTER, menu.getProcessingInputSlots()[i].index, stackToSet);
PacketDistributor.sendToServer(message);
}
for (int i = 0; i < menu.getProcessingOutputSlots().length; i++) {
ItemStack stackToSet = ItemStack.EMPTY;
if (i < genericResults.size()) {
GenericStack resultStack = genericResults.get(i);
stackToSet = GenericStack.wrapInItemStack(resultStack);
}
ServerboundPacket message = new InventoryActionPacket(
InventoryAction.SET_FILTER, menu.getProcessingOutputSlots()[i].index, stackToSet);
PacketDistributor.sendToServer(message);
}
}
}

View File

@ -0,0 +1,90 @@
package com.extendedae_plus.mixin.emi;
import appeng.api.stacks.GenericStack;
import appeng.integration.modules.emi.EmiStackHelper;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.integration.emi.HandlerBoMRecipes;
import com.extendedae_plus.integration.emi.PatternFillingHelper;
import com.extendedae_plus.network.RequestProvidersListC2SPacket;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.bom.BoM;
import dev.emi.emi.bom.MaterialNode;
import dev.emi.emi.screen.BoMScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeType;
import net.neoforged.neoforge.network.PacketDistributor;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Mixin(BoMScreen.class)
public abstract class MixinBoMScreen {
@Inject(method = "mouseClicked", at = @At("HEAD"))
private void onMouseClick(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
if (!(button == GLFW.GLFW_MOUSE_BUTTON_MIDDLE && Screen.hasControlDown())) return;
try {
var self = (BoMScreen)(Object) this;
Object hover = self.getHoveredStack((int) mouseX, (int) mouseY);
HashMap<ResourceLocation, GenericStack> selectedInputs;
// 救救孩子😭😭😭为什么private内部类套内部类aaa
// Mixins你们好啊, 我是reflection大王, 我要来破坏代码兼容性了😈😈😈
// 你知道吗: 其实以前这里套了两层reflection😋😋😋
if (hover != null) {
Field nodeField = hover.getClass().getDeclaredField("node");
nodeField.setAccessible(true);
Object nodeObject = nodeField.get(hover);
MaterialNode node = (MaterialNode) nodeObject;
if (node.recipe != null) {
if (Minecraft.getInstance().player.containerMenu instanceof PatternEncodingTermMenu menu) {
boolean isCraftingRecipe = false;
if (node.recipe.getBackingRecipe() != null) {
var type = node.recipe.getBackingRecipe().value().getType();
if (type == RecipeType.CRAFTING || type == RecipeType.STONECUTTING || type == RecipeType.SMITHING)
isCraftingRecipe = true;
}
menu.clear();
if (isCraftingRecipe) {
PatternFillingHelper.encodeCraftingRecipe(menu,
node.recipe.getBackingRecipe(),
HandlerBoMRecipes.updateRecipe(node.recipe, BoM.tree.batches,
HandlerBoMRecipes.collectInputs(node)),
stack -> true);
menu.encode();
} else {
EmiStack nodeStack = node.ingredient.getEmiStacks().getFirst();
List<EmiStack> outputs = new ArrayList<>(node.recipe.getOutputs());
outputs.remove(nodeStack);
outputs.addFirst(nodeStack);
PatternFillingHelper.encodeProcessingRecipe(menu,
HandlerBoMRecipes.updateRecipe(node.recipe, BoM.tree.batches,
HandlerBoMRecipes.collectInputs(node)),
outputs.stream().map(EmiStackHelper::toGenericStack).toList());
menu.encode();
String name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(node.recipe.getBackingRecipe().value());
if (!(name == null || name.isBlank()))
ExtendedAEPatternUploadUtil.setLastProcessingName(name);
PacketDistributor.sendToServer(RequestProvidersListC2SPacket.INSTANCE);
}
}
}
}
} catch (Exception ignored) {}
}
}

View File

@ -0,0 +1,31 @@
package com.extendedae_plus.network;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.ExtendedAEPlus;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadContext;
public record C2SPacketStoneCuttingID(ResourceLocation recipeID) implements CustomPacketPayload {
public static final Type<C2SPacketStoneCuttingID> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "bom_pattern_encoding"));
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static final StreamCodec<RegistryFriendlyByteBuf, C2SPacketStoneCuttingID> STREAM_CODEC = StreamCodec.composite(
ResourceLocation.STREAM_CODEC, C2SPacketStoneCuttingID::recipeID,
C2SPacketStoneCuttingID::new);
public static void handle(C2SPacketStoneCuttingID packet, IPayloadContext context) {
if (context.player().level().isClientSide) return;
context.enqueueWork(() -> {
if (!(context.player().containerMenu instanceof PatternEncodingTermMenu menu)) return;
menu.setStonecuttingRecipeId(packet.recipeID);
});
}
}

View File

@ -48,6 +48,7 @@
"ae2.client.gui.PatternProviderScreenMixin", "ae2.client.gui.PatternProviderScreenMixin",
"ae2.client.gui.SlotGridLayoutMixin", "ae2.client.gui.SlotGridLayoutMixin",
"ae2.menu.CraftConfirmMenuGoBackMixin", "ae2.menu.CraftConfirmMenuGoBackMixin",
"emi.MixinBoMScreen",
"emi.MixinEncodingHelper", "emi.MixinEncodingHelper",
"extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalAccessor",
"extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor",