Merge pull request #1 from C-H716/feature/gtceu-recipe-type-copy

添加对gtceu多模式机器复制支持
This commit is contained in:
GaLicn 2025-08-20 13:13:28 +08:00 committed by GitHub
commit 552c53400c
3 changed files with 155 additions and 96 deletions

View File

@ -56,50 +56,40 @@ dependencies {
mappings loom.officialMojangMappings()
forge "net.minecraftforge:forge:${forge_version}"
//exendedae前置
modImplementation "curse.maven:glodium-957920:${glodium_version}"
//extendedAE
modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar')
//ae2
modImplementation "appeng:appliedenergistics2-forge:${ae2_version}"
modImplementation "org.appliedenergistics:guideme:${guideme_version}"
modCompileOnly "curse.maven:applied-energistics-2-wireless-terminals-459929:${wireless_terminals_version}"
modCompileOnly "mezz.jei:jei-${minecraft_version}-forge:${jei_version}"
annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor"
modCompileOnly "curse.maven:applied-flux-965012:${applied_flux_version}"
modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}"
modCompileOnly "curse.maven:jade-324717:${jade_version}"
// REI
modCompileOnly "me.shedaniel.cloth:basic-math:${basic_math_version}"
modCompileOnly "curse.maven:architectury-api-419699:${architectury_version}"
modCompileOnly "me.shedaniel:RoughlyEnoughItems-forge:${rei_version}"
modCompileOnly "me.shedaniel.cloth:cloth-config-forge:${cloth_config_version}"
// ProjectE
modCompileOnly "curse.maven:projecte-226410:${projecte_version}"
modCompileOnly "curse.maven:appliede-1009940:${appliede_version}"
// GregTech
modCompileOnly "curse.maven:gregtechceu-modern-890405:${gregtech_version}"
modCompileOnly "curse.maven:ldlib-626676:${ldlib_version}"
// Immersive Engineering
modCompileOnly "curse.maven:immersive-engineering-231951:${ie_version}"
annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor"
//curios
modRuntimeOnly "curse.maven:curios-309927:${curios_version}"
modCompileOnly "curse.maven:curios-309927:${curios_version}"
// Runtime test
modRuntimeOnly "curse.maven:curios-309927:${curios_version}"
modRuntimeOnly "curse.maven:jade-324717:${jade_version}"
modRuntimeOnly "curse.maven:architectury-api-419699:${architectury_version}"
modRuntimeOnly "curse.maven:applied-energistics-2-wireless-terminals-459929:${wireless_terminals_version}"
modRuntimeOnly "mezz.jei:jei-${minecraft_version}-forge:${jei_version}"
modRuntimeOnly "curse.maven:jade-324717:${jade_version}"
modRuntimeOnly "curse.maven:mega-cells-622112:${mega_cells_version}"
modRuntimeOnly "curse.maven:projecte-226410:${projecte_version}"
modRuntimeOnly "curse.maven:appliede-1009940:${appliede_version}"
modRuntimeOnly "curse.maven:cloth-config-348521:5729105"
//extendedAE
modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar')
//jec
modCompileOnly "curse.maven:just-enough-characters-250702:6680042"
modRuntimeOnly "curse.maven:just-enough-characters-250702:6680042"

View File

@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
loom.platform = forge
# Mod properties
mod_version = 1.3.1
mod_version = 1.3.1-fix1
maven_group = com.extendedae_plus
archives_name = extendedae_plus

View File

@ -1,35 +1,46 @@
package com.extendedae_plus.mixin.ae2;
import appeng.api.parts.IPartHost;
import appeng.api.parts.SelectedPart;
import appeng.items.tools.quartz.QuartzCuttingKnifeItem;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.Nameable;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import appeng.api.parts.IPartHost;
import appeng.api.parts.SelectedPart;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fml.ModList;
import org.lwjgl.glfw.GLFW;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* AE2 石英切割刀添加潜行 + 右键 指向世界中的方块/部件如线缆复制其名称到剪贴板
*
* <p>
* 设计要点参考原类 QuartzCuttingKnifeItem 的写法
* - 原本右键会打开 QuartzKnifeMenu我们在客户端潜行且命中方块时优先拦截并复制名称
* - 名称优先取方块实体的自定义名若实现 Nameable否则使用方块显示名
@ -37,6 +48,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
*/
@Mixin(value = QuartzCuttingKnifeItem.class)
public abstract class QuartzCuttingKnifeItemMixin {
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAEPlus");
@Inject(method = "use", at = @At("HEAD"), cancellable = true)
private void eap$copyNameOnShiftRightClick(Level level, Player player, InteractionHand hand,
@ -55,37 +67,17 @@ public abstract class QuartzCuttingKnifeItemMixin {
}
var pos = bhr.getBlockPos();
var state = level.getBlockState(pos);
if (state == null || state.isAir()) {
return;
}
String name = null;
BlockEntity be = level.getBlockEntity(pos);
// 优先若为 AE2 线缆总线选择命中的具体 Part 名称
if (be instanceof IPartHost partHost) {
SelectedPart sel = partHost.selectPartWorld(bhr.getLocation());
if (sel.part != null) {
var stack = new net.minecraft.world.item.ItemStack(sel.part.getPartItem());
name = stack.getHoverName().getString();
} else if (sel.facade != null) {
var stack = sel.facade.getItemStack();
if (!stack.isEmpty()) {
name = stack.getHoverName().getString();
}
}
}
if (be instanceof Nameable nm) {
var custom = nm.getCustomName();
if (custom != null) {
name = custom.getString();
}
}
if (name == null || name.isBlank()) {
name = state.getBlock().getName().getString();
}
boolean ok = tryCopyToClipboard(mc, name);
player.displayClientMessage(Component.literal(ok
? ("已复制方块名: " + name)
if (state == null || state.isAir()) return;
// 获取方块名称
String name = eap$getBlockName(level, pos, hr.getLocation());
// 复制到剪贴板并反馈
boolean success = eap$tryCopyToClipboard(Minecraft.getInstance(), name);
player.displayClientMessage(Component.literal(success
? ("已复制方块/部件名: " + name)
: "复制失败:整合包可能限制剪贴板或未聚焦窗口"), true);
// 拦截默认行为不再打开刀具界面
ItemStack held = player.getItemInHand(hand);
cir.setReturnValue(new InteractionResultHolder<>(InteractionResult.SUCCESS, held));
@ -101,40 +93,111 @@ public abstract class QuartzCuttingKnifeItemMixin {
}
var pos = context.getClickedPos();
var state = level.getBlockState(pos);
if (state == null || state.isAir()) {
return;
if (state.isAir()) return;
// 获取方块名称
String name = eap$getBlockName(level, pos, context.getClickLocation());
// 复制到剪贴板并反馈
boolean success = eap$tryCopyToClipboard(Minecraft.getInstance(), name);
player.displayClientMessage(Component.literal(success
? ("已复制方块/部件名: " + name)
: "复制失败:整合包可能限制剪贴板或未聚焦窗口"), true);
// 拦截默认行为
cir.setReturnValue(InteractionResult.SUCCESS);
}
/**
* 获取方块或部件的名称优先级自定义名称 > AE2 部件 > GregTech 配方翻译 > 方块名称
*/
@Unique
private String eap$getBlockName(Level level, BlockPos pos, Vec3 clickLocation) {
BlockEntity blockEntity = level.getBlockEntity(pos);
BlockState state = level.getBlockState(pos);
// 1. 自定义名称
if (blockEntity instanceof Nameable nameable && nameable.getCustomName() != null) {
return nameable.getCustomName().getString();
}
String name = null;
BlockEntity be = level.getBlockEntity(pos);
// 优先若为 AE2 线缆总线选择命中的具体 Part 名称使用点击位置的世界坐标
if (be instanceof IPartHost partHost) {
// RenderBlockOutlineHook 相同基于世界坐标选取部件
SelectedPart sel = partHost.selectPartWorld(context.getClickLocation());
// 2. AE2 部件
String ae2Name = eap$handleAE2Block(blockEntity, clickLocation);
if (ae2Name != null && !ae2Name.isBlank()) {
return ae2Name;
}
// 3. GregTech CEu 配方翻译
if (ModList.get().isLoaded("gtceu")) {
String gtceuName = eap$handleGTCEuBlock(blockEntity);
if (gtceuName != null && !gtceuName.isBlank()) {
return gtceuName;
}
}
// 4. 方块名称
return state.getBlock().getName().getString();
}
/**
* 处理 GregTech CEu 方块获取配方翻译名
*/
@Unique
private String eap$handleGTCEuBlock(BlockEntity blockEntity) {
try {
// 动态加载 GTCEu
Class<?> metaMachineBlockEntityClass = Class.forName("com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity");
Class<?> workableElectricMultiblockMachineClass = Class.forName("com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine");
if (metaMachineBlockEntityClass.isInstance(blockEntity)) {
// 获取 metaMachine 字段
Field metaMachineField = metaMachineBlockEntityClass.getDeclaredField("metaMachine");
metaMachineField.setAccessible(true);
Object metaMachine = metaMachineField.get(blockEntity);
if (workableElectricMultiblockMachineClass.isInstance(metaMachine)) {
// 调用 getRecipeType 方法
Method getRecipeTypeMethod = workableElectricMultiblockMachineClass.getMethod("getRecipeType");
getRecipeTypeMethod.setAccessible(true);
Object recipeType = getRecipeTypeMethod.invoke(metaMachine);
if (recipeType != null) {
// 调用 toString 方法获取配方名
String recipeName = recipeType.toString().replace("gtceu:", "");
String translationKey = "gtceu." + recipeName; // e.g., gtceu.cracker
// 客户端使用 I18n
return I18n.get(translationKey, recipeName); // e.g., 裂化机
}
}
}
} catch (ClassNotFoundException e) {
LOGGER.info("GregTech CEu 类未找到,跳过配方翻译处理");
return null; // GTCEu 不可用
} catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
LOGGER.error("处理 GTCEu 配方翻译失败: {}", e.getMessage());
return null; // 反射失败
}
return null; // GTCEu 方块实体
}
/**
* 处理 AE2 方块获取部件或外观名称
*/
@Unique
private String eap$handleAE2Block(BlockEntity blockEntity, Vec3 clickLocation) {
if (blockEntity instanceof IPartHost partHost) {
SelectedPart sel = partHost.selectPartWorld(clickLocation);
if (sel.part != null) {
var stack = new net.minecraft.world.item.ItemStack(sel.part.getPartItem());
name = stack.getHoverName().getString();
ItemStack stack = new ItemStack(sel.part.getPartItem());
return stack.getHoverName().getString();
} else if (sel.facade != null) {
var stack = sel.facade.getItemStack();
ItemStack stack = sel.facade.getItemStack();
if (!stack.isEmpty()) {
name = stack.getHoverName().getString();
return stack.getHoverName().getString();
}
}
}
if (be instanceof Nameable nm) {
var custom = nm.getCustomName();
if (custom != null) {
name = custom.getString();
}
}
if (name == null || name.isBlank()) {
name = state.getBlock().getName().getString();
}
boolean ok = tryCopyToClipboard(Minecraft.getInstance(), name);
player.displayClientMessage(Component.literal(ok
? ("已复制方块/部件名: " + name)
: "复制失败:整合包可能限制剪贴板或未聚焦窗口"), true);
// 拦截默认行为
cir.setReturnValue(InteractionResult.SUCCESS);
return null;
}
/**
@ -143,27 +206,33 @@ public abstract class QuartzCuttingKnifeItemMixin {
* 2) GLFW.glfwSetClipboardString(Window handle)
* 3) AWT 系统剪贴板可能在某些整合包/无头环境不可用
*/
private static boolean tryCopyToClipboard(Minecraft mc, String text) {
if (text == null) return false;
@Unique
private static boolean eap$tryCopyToClipboard(Minecraft mc, String text) {
if (text == null || text.isBlank()) return false;
// 确保在游戏主线程执行
if (!mc.isSameThread()) {
AtomicBoolean result = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
mc.execute(() -> {
try {
result.set(doCopy(mc, text));
result.set(eap$doCopy(mc, text));
} finally {
latch.countDown();
}
});
try { latch.await(); } catch (InterruptedException ignored) {}
try {
latch.await();
} catch (InterruptedException e) {
LOGGER.error("剪贴板复制线程中断: {}", e.getMessage());
}
return result.get();
} else {
return doCopy(mc, text);
return eap$doCopy(mc, text);
}
}
private static boolean doCopy(Minecraft mc, String text) {
@Unique
private static boolean eap$doCopy(Minecraft mc, String text) {
try {
mc.keyboardHandler.setClipboard(text);
return true;