diff --git a/.gitignore b/.gitignore index f33e946..16b0b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ out/ classes/ +source/ # Eclipse *.tmp diff --git a/build.gradle b/build.gradle index d6e3dd3..fc22b8e 100644 --- a/build.gradle +++ b/build.gradle @@ -103,12 +103,9 @@ dependencies { modCompileOnly "curse.maven:just-enough-characters-250702:6680042" } -// 忽略所有过时警告 -gradle.projectsEvaluated { - tasks.withType(JavaCompile).tap { - configureEach { - options.compilerArgs << "-Xlint:-deprecation" - } +allprojects { + tasks.withType(JavaCompile).configureEach { + options.compilerArgs << "-Xlint:-deprecation" } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/ContainerPatternEncodingTermMenuMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/ContainerPatternEncodingTermMenuMixin.java index f11413f..056f2b9 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/ContainerPatternEncodingTermMenuMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/ContainerPatternEncodingTermMenuMixin.java @@ -20,7 +20,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Map; import java.util.function.Consumer; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** * 给 AE2 的 PatternEncodingTermMenu 增加一个通用动作持有者,实现接收 EPP 的 CGenericPacket 动作。 * 注册动作 "upload_to_matrix":仅上传“合成图样”到 ExtendedAE 装配矩阵。 @@ -53,8 +52,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo eap$scheduleUploadWithRetry(sp, menu, attemptsLeft - 1); } } - } catch (Throwable t) { - LOGGER.error("Error uploading pattern to matrix", t); + } catch (Throwable ignored) { } }); } @@ -87,8 +85,10 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo return; // 仅服务器执行 } var menu = (PatternEncodingTermMenu) (Object) this; - if (menu.getMode() != EncodingMode.CRAFTING) { - return; // 只处理合成样板 + if (menu.getMode() != EncodingMode.CRAFTING + && menu.getMode() != EncodingMode.SMITHING_TABLE + && menu.getMode() != EncodingMode.STONECUTTING) { + return; // 只处理合成/锻造台/切石机样板 } if (this.encodedPatternSlot == null) { return; @@ -107,8 +107,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo } catch (Throwable ignored) { } }); - } catch (Throwable t) { - LOGGER.error("Error uploading pattern to matrix", t); + } catch (Throwable ignored) { } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/PatternAccessTermScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/PatternAccessTermScreenMixin.java new file mode 100644 index 0000000..a1a0bd4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/PatternAccessTermScreenMixin.java @@ -0,0 +1,24 @@ +package com.extendedae_plus.mixin.ae2; + +import appeng.client.gui.me.patternaccess.PatternAccessTermScreen; +import com.extendedae_plus.util.GuiUtil; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +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.CallbackInfo; + +@OnlyIn(Dist.CLIENT) +@Mixin(PatternAccessTermScreen.class) +public class PatternAccessTermScreenMixin { + // 在绘制前景的最后阶段叠加显示样板输出数量 + @Inject(method = "drawFG", at = @At("TAIL"), remap = false) + private void injectDrawCraftingAmount(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY, CallbackInfo ci) { + PatternAccessTermScreen screen = (PatternAccessTermScreen)(Object) this; + + // 调用GuiUtil的通用渲染方法 + GuiUtil.renderPatternAmounts(guiGraphics, screen); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java index 53b2e1f..e8ddd97 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java @@ -48,6 +48,27 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; */ @Mixin(value = QuartzCuttingKnifeItem.class) public abstract class QuartzCuttingKnifeItemMixin { + /** + * 清理方块名称,移除分节符号和其他格式字符 + */ + @Unique + private String eap$cleanBlockName(String name) { + if (name == null || name.isBlank()) { + return name; + } + + // 移除 Minecraft 分节符号 (§) 及其后面的字符 + name = name.replaceAll("§[0-9a-fk-or]", ""); + + // 移除多余的空白字符 + name = name.trim(); + + // 移除常见的格式字符 + name = name.replaceAll("[\\[\\](){}]", ""); + + return name; + } + @Inject(method = "use", at = @At("HEAD"), cancellable = true) private void eap$copyNameOnShiftRightClick(Level level, Player player, InteractionHand hand, CallbackInfoReturnable> cir) { @@ -69,6 +90,9 @@ public abstract class QuartzCuttingKnifeItemMixin { // 获取方块名称 String name = eap$getBlockName(level, pos, hr.getLocation()); + + // 清理名称,移除分节符号等格式字符 + name = eap$cleanBlockName(name); // 复制到剪贴板并反馈 boolean success = eap$tryCopyToClipboard(Minecraft.getInstance(), name); @@ -95,6 +119,9 @@ public abstract class QuartzCuttingKnifeItemMixin { // 获取方块名称 String name = eap$getBlockName(level, pos, context.getClickLocation()); + + // 清理名称,移除分节符号等格式字符 + name = eap$cleanBlockName(name); // 复制到剪贴板并反馈 boolean success = eap$tryCopyToClipboard(Minecraft.getInstance(), name); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenAccessor.java new file mode 100644 index 0000000..81478a4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenAccessor.java @@ -0,0 +1,23 @@ +package com.extendedae_plus.mixin.ae2.accessor; + +import appeng.client.gui.me.patternaccess.PatternAccessTermScreen; +import appeng.client.gui.widgets.Scrollbar; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.ArrayList; + +@OnlyIn(Dist.CLIENT) +@Mixin(value = PatternAccessTermScreen.class, remap = false) +public interface PatternAccessTermScreenAccessor { + @Accessor("scrollbar") + Scrollbar getScrollbar(); + + @Accessor("visibleRows") + int getVisibleRows(); + + @Accessor("rows") + ArrayList getRows(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenSlotsRowAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenSlotsRowAccessor.java new file mode 100644 index 0000000..7a1806a --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternAccessTermScreenSlotsRowAccessor.java @@ -0,0 +1,20 @@ +package com.extendedae_plus.mixin.ae2.accessor; + +import appeng.client.gui.me.patternaccess.PatternContainerRecord; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@OnlyIn(Dist.CLIENT) +@Mixin(targets = "appeng.client.gui.me.patternaccess.PatternAccessTermScreen$SlotsRow", remap = false) +public interface PatternAccessTermScreenSlotsRowAccessor { + @Accessor("container") + PatternContainerRecord getContainer(); + + @Accessor("offset") + int getOffset(); + + @Accessor("slots") + int getSlots(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index 4143ffc..fd46194 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -3,65 +3,111 @@ package com.extendedae_plus.mixin.extendedae; import appeng.api.crafting.PatternDetailsHelper; import appeng.client.gui.AEBaseScreen; import appeng.client.gui.Icon; +import appeng.client.gui.me.patternaccess.PatternContainerRecord; +import appeng.client.gui.me.patternaccess.PatternSlot; import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.AETextField; import appeng.client.gui.widgets.IconButton; +import appeng.menu.AEBaseMenu; +import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; -import com.glodblock.github.extendedae.container.ContainerExPatternTerminal; -import com.glodblock.github.extendedae.network.EPPNetworkHandler; -import com.glodblock.github.glodium.network.packet.CGenericPacket; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.renderer.Rect2i; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.Shadow; 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.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Set; + +@Pseudo @Mixin(GuiExPatternTerminal.class) -public abstract class GuiExPatternTerminalMixin extends AEBaseScreen { +public abstract class GuiExPatternTerminalMixin extends AEBaseScreen { - @Unique - private IconButton toggleSlotsButton; - - @Unique - private boolean showSlots = false; // 默认显示槽位 - - @Unique - private long currentlychooicepatterprovider = -1; // 当前选择的样板供应器ID - @Unique private static final String UPLOAD_SUCCESS_MESSAGE = "✅ ExtendedAE Plus: 样板快速上传成功!"; - @Unique private static final String UPLOAD_FAILED_MESSAGE = "❌ ExtendedAE Plus: 样板上传失败,请检查供应器状态"; - @Unique private static final String NO_PROVIDER_MESSAGE = "ExtendedAE Plus: 请先选择一个样板供应器(点击GroupHeader旁的按钮)"; + @Unique + private IconButton eap$toggleSlotsButton; + @Unique + private boolean eap$showSlots = false; // 默认显示槽位 + @Unique + private long eap$currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID + @Shadow(remap = false) private AETextField searchOutField; + @Shadow(remap = false) private AETextField searchInField; + @Shadow(remap = false) private Set matchedStack; + @Shadow(remap = false) private Set matchedProvider; - public GuiExPatternTerminalMixin(ContainerExPatternTerminal menu, Inventory playerInventory, Component title, ScreenStyle style) { + public GuiExPatternTerminalMixin(AEBaseMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } - + + @Unique + private static int eap$withAlpha(int rgb, int alpha255) { + return ((alpha255 & 0xFF) << 24) | (rgb & 0x00FFFFFF); + } + + /** + * 将 HSV 转换为 RGB(返回 0xRRGGBB,不含 alpha)。 + * h: 0.0~1.0,s: 0.0~1.0,v: 0.0~1.0 + */ + @Unique + private static int eap$hsvToRgb(float h, float s, float v) { + if (s <= 0.0f) { + int g = Math.round(v * 255.0f); + return (g << 16) | (g << 8) | g; + } + float hh = (h - (float) Math.floor(h)) * 6.0f; + int sector = (int) Math.floor(hh); + float f = hh - sector; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + float r, g, b; + switch (sector) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + default: r = v; g = p; b = q; break; + } + int ri = Math.round(r * 255.0f); + int gi = Math.round(g * 255.0f); + int bi = Math.round(b * 255.0f); + return (ri << 16) | (gi << 8) | bi; + } + /** * 获取当前选择的样板供应器ID */ @Unique public long getCurrentlyChoicePatternProvider() { - return currentlychooicepatterprovider; + return eap$currentlyChoicePatterProvider; } - + /** * 设置当前选择的样板供应器ID */ @Unique public void setCurrentlyChoicePatternProvider(long id) { - this.currentlychooicepatterprovider = id; + this.eap$currentlyChoicePatterProvider = id; } - + /** * 拦截鼠标点击事件,实现Shift+左键快速上传样板功能 */ @@ -74,22 +120,22 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen EPPNetworkHandlerClass = Class.forName("com.glodblock.github.extendedae.network.EPPNetworkHandler"); + Object handlerInstance = EPPNetworkHandlerClass.getField("INSTANCE").get(null); + + Class packetClass = Class.forName("com.glodblock.github.glodium.network.packet.CGenericPacket"); + Constructor constructor = packetClass.getConstructor(String.class, Object[].class); + Object packet = constructor.newInstance("upload", new Object[]{playerSlotIndex, eap$currentlyChoicePatterProvider}); + + Class iMessage = Class.forName("com.glodblock.github.glodium.network.packet.IMessage"); + Method sendToServer = EPPNetworkHandlerClass.getMethod("sendToServer", iMessage); + + sendToServer.invoke(handlerInstance, packet); + } catch (Throwable t) { + this.minecraft.player.displayClientMessage( + Component.literal("❌ ExtendedAE Plus: 未找到 ExtendedAE 网络支持(可能未安装或版本不兼容)"), + true + ); + } } else { this.minecraft.player.displayClientMessage( - Component.literal("❌ ExtendedAE Plus: 无效的样板物品"), - true + Component.literal("❌ ExtendedAE Plus: 无效的样板物品"), + true ); } } } - + /** * 重置当前选择的样板供应器ID */ @Unique public void resetCurrentlyChoicePatternProvider() { - this.currentlychooicepatterprovider = -1; + this.eap$currentlyChoicePatterProvider = -1; } @Inject(method = "", at = @At("TAIL"), remap = false) - private void injectConstructor(ContainerExPatternTerminal menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + private void injectConstructor(CallbackInfo ci) { // 创建切换槽位显示的按钮 - this.toggleSlotsButton = new IconButton((b) -> { - this.showSlots = !this.showSlots; // 开关状态 - + this.eap$toggleSlotsButton = new IconButton((b) -> { + this.eap$showSlots = !this.eap$showSlots; // 开关状态 + // 通过反射调用refreshList方法 - 先尝试当前类,失败后尝试父类 try { java.lang.reflect.Method refreshMethod = null; @@ -149,41 +210,40 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen rows = (java.util.ArrayList) rowsField.get(this); - + // 通过反射访问highlightBtns字段 java.lang.reflect.Field highlightBtnsField = null; try { @@ -217,22 +277,22 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen highlightBtns = (java.util.HashMap) highlightBtnsField.get(this); - + // 创建新的索引映射 java.util.HashMap newHighlightBtns = new java.util.HashMap<>(); int newIndex = 0; - + // 移除所有SlotsRow,只保留GroupHeaderRow,同时重新映射高亮按钮索引 for (int i = 0; i < rows.size(); i++) { Object row = rows.get(i); String className = row.getClass().getSimpleName(); - + if (className.equals("GroupHeaderRow")) { // 保留GroupHeaderRow,并重新映射对应的高亮按钮 @SuppressWarnings("unchecked") java.util.ArrayList typedRows = (java.util.ArrayList) rows; typedRows.set(newIndex, row); - + // 查找原来在这个位置的高亮按钮 // 原始代码中,高亮按钮的索引是在添加GroupHeaderRow之后、添加第一个SlotsRow之前设置的 // 所以按钮的索引指向的是第一个SlotsRow的位置 @@ -241,25 +301,25 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen newIndex) { rows.remove(rows.size() - 1); } - + // 更新highlightBtns highlightBtns.clear(); highlightBtns.putAll(newHighlightBtns); - + // 强制刷新滚动条 try { - java.lang.reflect.Method resetScrollbarMethod = null; + Method resetScrollbarMethod = null; try { // 先尝试在当前类中查找 resetScrollbarMethod = this.getClass().getDeclaredMethod("resetScrollbar"); @@ -271,14 +331,80 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen terminal) { - // 通过反射获取HighlightButton的serverId信息 - try { - // 获取HighlightButton的pos字段,用于标识对应的样板供应器 - var posField = HighlightButton.class.getDeclaredField("pos"); - posField.setAccessible(true); - var pos = posField.get(hb); - - if (pos != null) { - // 通过反射访问infoMap字段 - var infoMapField = GuiExPatternTerminal.class.getDeclaredField("infoMap"); - infoMapField.setAccessible(true); - @SuppressWarnings("unchecked") - var infoMap = (java.util.Map) infoMapField.get(terminal); - - // 查找对应的样板供应器ID - for (var entry : infoMap.entrySet()) { - var info = entry.getValue(); - // 通过反射调用pos()方法 - var posMethod = info.getClass().getMethod("pos"); - var infoPos = posMethod.invoke(info); - - if (pos.equals(infoPos)) { - long serverId = entry.getKey(); - - // 通过反射调用setter方法 - try { - var setMethod = terminal.getClass().getMethod("setCurrentlyChoicePatternProvider", long.class); - setMethod.invoke(terminal, serverId); - } catch (Exception ignored) { - } - break; - } - } - } - } catch (Exception ignored) { - } - } - } - } + @Inject(method = "highlight", at = @At("TAIL"), remap = false) + private static void onHighlight(Button btn, CallbackInfo ci) { + if (btn instanceof HighlightButton hb) { + var minecraft = net.minecraft.client.Minecraft.getInstance(); + if (minecraft.screen instanceof GuiExPatternTerminal terminal) { + try { + var fPos = HighlightButton.class.getDeclaredField("pos"); + fPos.setAccessible(true); + Object btnPos = fPos.get(hb); + if (btnPos == null) { + return; + } + + var fFace = HighlightButton.class.getDeclaredField("face"); + fFace.setAccessible(true); + Object btnFace = fFace.get(hb); // 允许为 null:方块形 + + var infoMapField = GuiExPatternTerminal.class.getDeclaredField("infoMap"); + infoMapField.setAccessible(true); + @SuppressWarnings("unchecked") + var infoMap = (java.util.Map) infoMapField.get(terminal); + + for (var entry : infoMap.entrySet()) { + var info = entry.getValue(); + var mPos = info.getClass().getMethod("pos"); + mPos.setAccessible(true); + Object infoPos = mPos.invoke(info); + + var mFace = info.getClass().getMethod("face"); + mFace.setAccessible(true); + Object infoFace = mFace.invoke(info); // 允许为 null:方块形 + + // 匹配规则:pos 必须相等;face 允许为 null,null 仅与 null 匹配 + boolean posEqual = Objects.equals(btnPos, infoPos); + boolean faceEqual = (btnFace == null && infoFace == null) || Objects.equals(btnFace, infoFace); + if (posEqual && faceEqual) { + long serverId = entry.getKey(); + var setMethod = terminal.getClass().getMethod("setCurrentlyChoicePatternProvider", long.class); + setMethod.setAccessible(true); + setMethod.invoke(terminal, serverId); + break; + } + } + } catch (Throwable t) { + LOGGER.warn("HighlightButton onHighlight 处理异常", t); + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalAccessor.java b/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalAccessor.java new file mode 100644 index 0000000..dc7dd9c --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalAccessor.java @@ -0,0 +1,23 @@ +package com.extendedae_plus.mixin.extendedae.accessor; + +import appeng.client.gui.widgets.Scrollbar; +import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.ArrayList; + +@OnlyIn(Dist.CLIENT) +@Mixin(value = GuiExPatternTerminal.class, remap = false) +public interface GuiExPatternTerminalAccessor { + @Accessor("scrollbar") + Scrollbar getScrollbar(); + + @Accessor("visibleRows") + int getVisibleRows(); + + @Accessor("rows") + ArrayList getRows(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalSlotsRowAccessor.java b/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalSlotsRowAccessor.java new file mode 100644 index 0000000..05e6f17 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalSlotsRowAccessor.java @@ -0,0 +1,20 @@ +package com.extendedae_plus.mixin.extendedae.accessor; + +import appeng.client.gui.me.patternaccess.PatternContainerRecord; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@OnlyIn(Dist.CLIENT) +@Mixin(targets = "com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal$SlotsRow", remap = false) +public interface GuiExPatternTerminalSlotsRowAccessor { + @Accessor("container") + PatternContainerRecord getContainer(); + + @Accessor("offset") + int getOffset(); + + @Accessor("slots") + int getSlots(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java b/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java index e09228f..39ebe7c 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java @@ -14,5 +14,6 @@ public final class ExtendedAELogger { private ExtendedAELogger() { // no instance + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } -} +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 2d48349..d78af27 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -7,6 +7,8 @@ import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; import appeng.core.definitions.AEItems; import appeng.crafting.pattern.AECraftingPattern; +import appeng.crafting.pattern.AESmithingTablePattern; +import appeng.crafting.pattern.AEStonecuttingPattern; import appeng.helpers.patternprovider.PatternContainer; import appeng.menu.implementations.PatternAccessTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu; @@ -433,10 +435,12 @@ public class ExtendedAEPatternUploadUtil { return false; } - // 仅允许“合成图样” + // 仅允许“合成/锻造台/切石机图样” IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level()); - if (!(details instanceof AECraftingPattern)) { - sendMessage(player, "extendedae_plus.upload_to_matrix.fail_not_crafting"); + if (!(details instanceof AECraftingPattern + || details instanceof AESmithingTablePattern + || details instanceof AEStonecuttingPattern)) { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail"); return false; } diff --git a/src/main/java/com/extendedae_plus/util/GuiUtil.java b/src/main/java/com/extendedae_plus/util/GuiUtil.java new file mode 100644 index 0000000..ba968c5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/GuiUtil.java @@ -0,0 +1,176 @@ +package com.extendedae_plus.util; + +import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.stacks.GenericStack; +import appeng.util.inv.AppEngInternalInventory; +import com.extendedae_plus.mixin.ae2.accessor.PatternAccessTermScreenAccessor; +import com.extendedae_plus.mixin.ae2.accessor.PatternAccessTermScreenSlotsRowAccessor; +import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; +import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; + + +/** + * GUI工具类,提供样板获取、绘制等通用功能 + */ +public class GuiUtil { + private GuiUtil() {throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");} + + /** + * 从样板中获取输出数量文本 + * + * @param pattern 样板物品 + * @return 格式化后的数量文本 + */ + public static String getPatternOutputText(ItemStack pattern) { + if (pattern.isEmpty()) { + return ""; + } + + var details = PatternDetailsHelper.decodePattern(pattern, Minecraft.getInstance().level, false); + if (details == null || details.getOutputs().length == 0) { + return ""; + } + + GenericStack out = details.getOutputs()[0]; + long amount = out.amount(); + long perUnit = out.what().getAmountPerUnit(); + if (amount <= 0 || perUnit <= 0) { + return ""; + } + + // 计算实际单位数量,支持小数 + double units = (double) amount / perUnit; + if (units <= 0) { + return ""; + } + + // 自动判断是否为流体,避免重复后缀 + String autoSuffix = ""; + if (perUnit > 1) { + // 如果每单位数量大于1,说明是流体(如1000mB = 1B) + autoSuffix = "B"; + } + return NumberFormatUtil.formatNumberWithDecimal(units) + autoSuffix; + } + + /** + * 在槽位右下角绘制数量文本 + * @param guiGraphics GUI图形上下文 + * @param font 字体 + * @param text 要绘制的文本 + * @param slotX 槽位X坐标 + * @param slotY 槽位Y坐标 + * @param scale 缩放比例 + */ + public static void drawAmountText(GuiGraphics guiGraphics, Font font, String text, int slotX, int slotY, float scale) { + if (text.isEmpty()) { + return; + } + + // 计算缩放后的字体宽度,确保右对齐 + int scaledWidth = (int)(font.width(text) * scale); + int textX = slotX + 16 - scaledWidth; + int textY = slotY + 11; // 右下角显示 + + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(0, 0, 300); // 提升 Z,确保在最上层 + guiGraphics.pose().scale(scale, scale, 1.0f); // 缩小字体 + guiGraphics.drawString(font, text, (int)(textX / scale), (int)(textY / scale), 0xFFFFFFFF, true); + guiGraphics.pose().popPose(); + } + + + /** + * 渲染样板管理终端的数量显示 + * @param guiGraphics GUI图形上下文 + * @param screen 屏幕对象 + */ + public static void renderPatternAmounts(GuiGraphics guiGraphics, Object screen) { + int scrollLevel; + int visibleRows; + ArrayList rowsList; + + if (screen instanceof PatternAccessTermScreenAccessor aeAccessor) { + var scrollbar = aeAccessor.getScrollbar(); + if (scrollbar == null) return; + scrollLevel = scrollbar.getCurrentScroll(); + visibleRows = aeAccessor.getVisibleRows(); + if (visibleRows <= 0) return; + rowsList = aeAccessor.getRows(); + if (rowsList == null || rowsList.isEmpty()) return; + } else if (screen instanceof GuiExPatternTerminalAccessor exAccessor) { + var scrollbar = exAccessor.getScrollbar(); + if (scrollbar == null) return; + scrollLevel = scrollbar.getCurrentScroll(); + visibleRows = exAccessor.getVisibleRows(); + if (visibleRows <= 0) return; + rowsList = exAccessor.getRows(); + if (rowsList == null || rowsList.isEmpty()) return; + } else { + return; + } + // 判断是否为ExtendedAE终端 + boolean isExtendedAE = screen instanceof GuiExPatternTerminalAccessor; + + // 根据终端类型使用不同的常量(与 AE2/ExtendedAE 源码保持一致) + final int SLOT_SIZE = 18; // ROW_HEIGHT == 18, SLOT_SIZE == ROW_HEIGHT + final int GUI_PADDING_X = isExtendedAE ? 22 : 8; // ExtendedAE使用22,AE2使用8 + final int SLOT_Y_OFFSET = isExtendedAE ? 34 : 0; // ExtendedAE需要额外的Y偏移 + + var font = Minecraft.getInstance().font; + + for (int i = 0; i < visibleRows; ++i) { + int rowIdx = scrollLevel + i; + if (rowIdx < 0 || rowIdx >= rowsList.size()) { + continue; + } + + Object row = rowsList.get(rowIdx); + if (row instanceof PatternAccessTermScreenSlotsRowAccessor slotsRow) { + var container = slotsRow.getContainer(); + var inventory = container.getInventory(); + drawRowAmounts(guiGraphics, font, inventory, slotsRow.getOffset(), slotsRow.getSlots(), i, SLOT_SIZE, GUI_PADDING_X, SLOT_Y_OFFSET); + continue; + } + + if (row instanceof GuiExPatternTerminalSlotsRowAccessor exSlotsRow) { + var container = exSlotsRow.getContainer(); + var inventory = container.getInventory(); + drawRowAmounts(guiGraphics, font, inventory, exSlotsRow.getOffset(), exSlotsRow.getSlots(), i, SLOT_SIZE, GUI_PADDING_X, SLOT_Y_OFFSET); + } + } + } + + private static void drawRowAmounts( + GuiGraphics guiGraphics, + Font font, + AppEngInternalInventory inventory, + int offset, + int slots, + int visibleRowIndex, + int slotSize, + int guiPaddingX, + int slotYOffset + ) { + for (int col = 0; col < slots; col++) { + int index = offset + col; + var pattern = inventory.getStackInSlot(index); + if (pattern == null || pattern.isEmpty()) { + continue; + } + String amountText = getPatternOutputText(pattern); + if (amountText.isEmpty()) { + continue; + } + int slotX = col * slotSize + guiPaddingX; + int slotY = (visibleRowIndex + 1) * slotSize + slotYOffset; + drawAmountText(guiGraphics, font, amountText, slotX, slotY, 0.6f); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java b/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java new file mode 100644 index 0000000..760b7be --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java @@ -0,0 +1,62 @@ +package com.extendedae_plus.util; + +/** + * 数字格式化工具类,提供大数字和小数的格式化功能 + */ +public class NumberFormatUtil { + private NumberFormatUtil() {throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");} + + /** + * 格式化数字,将大数字转换为k、m等格式 + * 支持小数显示,小数点后为0则不显示0 + * @param number 要格式化的数字 + * @return 格式化后的字符串 + */ + public static String formatNumber(long number) { + if (number < 1000) { + return String.valueOf(number); + } else if (number < 1000000) { + double value = number / 1000.0; + return formatDecimal(value, "k"); + } else { + double value = number / 1000000.0; + return formatDecimal(value, "m"); + } + } + + /** + * 格式化带小数的数字,支持流体等需要显示小数的场景 + * @param value 小数值 + * @return 格式化后的字符串 + */ + public static String formatNumberWithDecimal(double value) { + if (value < 1000) { + if (value == (long) value) { + return String.valueOf((long) value); + } else { + return String.format("%.1f", value).replaceAll("\\.0$", ""); + } + } else if (value < 1000000) { + return formatDecimal(value / 1000.0, "k"); + } else { + return formatDecimal(value / 1000000.0, "m"); + } + } + + /** + * 格式化小数,如果小数点后为0则不显示0 + * @param value 小数值 + * @param suffix 后缀(k、m、g等) + * @return 格式化后的字符串 + */ + private static String formatDecimal(double value, String suffix) { + // 对于接近整数的值,使用整数显示 + if (Math.abs(value - Math.round(value)) < 0.001) { + return String.valueOf(Math.round(value)) + suffix; + } else { + // 修复重复后缀问题:先格式化数字,再添加后缀 + String formatted = String.format("%.1f", value).replaceAll("\\.0$", ""); + return formatted + suffix; + } + } +} \ No newline at end of file diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 8135002..1c2ac18 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -9,6 +9,7 @@ "accessor.AbstractContainerScreenAccessor", "accessor.ScreenAccessor", "accessor.ScreenInvoker", + "ae2.PatternAccessTermScreenMixin", "ae2.PatternEncodingTermScreenMixin", "ae2.PatternProviderScreenMixin", "ae2.QuartzCuttingKnifeItemMixin", @@ -16,9 +17,13 @@ "ae2.accessor.AEBaseScreenAccessor", "ae2.accessor.AEBaseScreenInvoker", "ae2.accessor.MEStorageScreenAccessor", + "ae2.accessor.PatternAccessTermScreenAccessor", + "ae2.accessor.PatternAccessTermScreenSlotsRowAccessor", "extendedae.GuiExPatternProviderMixin", "extendedae.GuiExPatternTerminalMixin", "extendedae.HighlightButtonMixin", + "extendedae.accessor.GuiExPatternTerminalAccessor", + "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", "jei.EncodePatternTransferHandlerMixin" ], "mixins": [