From 2b9e425b33c36d9e915df07eafd7172a19221371 Mon Sep 17 00:00:00 2001 From: LangQi99 <2032771946@qq.com> Date: Fri, 6 Mar 2026 22:48:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=A0=B7=E6=9D=BF=E7=AE=A1=E7=90=86=E7=BB=88=E7=AB=AF=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E7=A9=BA=E6=A0=BC=E9=80=89=E9=A1=B9=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/config/ModConfig.java | 7 + .../GuiExPatternTerminalSlotsRowAccessor.java | 20 ++ .../client/gui/GuiExPatternTerminalMixin.java | 184 +++++++++++++++--- .../assets/extendedae_plus/lang/en_us.json | 2 + .../assets/extendedae_plus/lang/zh_cn.json | 2 + .../resources/extendedae_plus.mixins.json | 1 + 6 files changed, 186 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/mixin/extendedae/accessor/GuiExPatternTerminalSlotsRowAccessor.java diff --git a/src/main/java/com/extendedae_plus/config/ModConfig.java b/src/main/java/com/extendedae_plus/config/ModConfig.java index 8877728..1c0f717 100644 --- a/src/main/java/com/extendedae_plus/config/ModConfig.java +++ b/src/main/java/com/extendedae_plus/config/ModConfig.java @@ -105,6 +105,13 @@ public final class ModConfig { }) public boolean patternTerminalShowSlotsDefault = true; + @Configurable + @Configurable.Comment(value = { + "样板终端默认是否合并空槽位", + "开启后只会显示到最后一个有物品的槽位之后的一个空槽位(直到占满供应器所有槽位)" + }) + public boolean patternTerminalMergeEmptySlotsDefault = true; + @Configurable @Configurable.Comment(value = { "实体加速器能量消耗基础值" 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..4ca5941 --- /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(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java index 786a6c7..ad61e54 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java @@ -38,6 +38,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -70,7 +72,13 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen /* ----- eap 自有字段 ----- */ @Unique private final Map openUIButtons = new HashMap<>(); @Unique private IconButton eap$toggleSlotsButton; - @Unique private boolean eap$showSlots = false; // 默认由配置初始化 + @Unique private IconButton eap$mergeEmptySlotsButton; + + @Unique private static Boolean eap$lastShowSlotsState = null; + @Unique private static Boolean eap$lastMergeEmptySlotsState = null; + + @Unique private boolean eap$showSlots = true; + @Unique private boolean eap$mergeEmptySlots = true; @Unique private long currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID // 按钮更新/缓存状态,避免每帧重建 @@ -78,6 +86,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen @Unique private int lastScroll = Integer.MIN_VALUE; @Unique private int lastRowsSize = Integer.MIN_VALUE; @Unique private int lastVisibleRows = Integer.MIN_VALUE; + @Unique private Map eap$slotsToShowMap = new HashMap<>(); public GuiExPatternTerminalMixin(AEBaseMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); @@ -182,12 +191,20 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen /* ----- 构造注入:创建切换按钮(只设置状态并触发一次 refresh) ----- */ @Inject(method = "", at = @At("TAIL"), remap = false) private void injectConstructor(CallbackInfo ci) { - // 初始化默认显示状态 - this.eap$showSlots = ModConfig.INSTANCE.patternTerminalShowSlotsDefault; + // 初始化默认显示状态(如果是第一次打开,从 Config 读取,否则使用上次的状态) + if (eap$lastShowSlotsState == null) { + eap$lastShowSlotsState = ModConfig.INSTANCE.patternTerminalShowSlotsDefault; + } + if (eap$lastMergeEmptySlotsState == null) { + eap$lastMergeEmptySlotsState = ModConfig.INSTANCE.patternTerminalMergeEmptySlotsDefault; + } + this.eap$showSlots = eap$lastShowSlotsState; + this.eap$mergeEmptySlots = eap$lastMergeEmptySlotsState; // 创建切换槽位显示的按钮(只切换状态并触发一次 refresh) this.eap$toggleSlotsButton = new IconButton((b) -> { this.eap$showSlots = !this.eap$showSlots; + eap$lastShowSlotsState = this.eap$showSlots; // 标记需要更新按钮与高亮映射 this.buttonsDirty = true; this.refreshList(); @@ -199,11 +216,28 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen } }; + // 创建合并空项按钮(只切换状态并触发一次 refresh) + this.eap$mergeEmptySlotsButton = new IconButton((b) -> { + this.eap$mergeEmptySlots = !this.eap$mergeEmptySlots; + eap$lastMergeEmptySlotsState = this.eap$mergeEmptySlots; + // 标记需要更新按钮与高亮映射 + this.buttonsDirty = true; + this.refreshList(); + this.resetScrollbar(); + }) { + @Override + protected Icon getIcon() { + return eap$mergeEmptySlots ? Icon.PATTERN_TERMINAL_NOT_FULL : Icon.PATTERN_TERMINAL_ALL; + } + }; + // 设置按钮提示文本 this.eap$toggleSlotsButton.setTooltip(Tooltip.create(Component.translatable("gui.expatternprovider.toggle_slots"))); + this.eap$mergeEmptySlotsButton.setTooltip(Tooltip.create(Component.translatable("gui.expatternprovider.merge_empty_slots"))); // 添加到左侧工具栏 this.addToLeftToolbar(this.eap$toggleSlotsButton); + this.addToLeftToolbar(this.eap$mergeEmptySlotsButton); } @Inject(method = "refreshList", at = @At("HEAD"), remap = false) @@ -214,6 +248,12 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.eap$showSlots ? "gui.expatternprovider.hide_slots" : "gui.expatternprovider.show_slots" ))); } + if (this.eap$mergeEmptySlotsButton != null) { + Component tooltip = Component.translatable( + this.eap$mergeEmptySlots ? "gui.expatternprovider.unmerge_empty_slots" : "gui.expatternprovider.merge_empty_slots" + ); + this.eap$mergeEmptySlotsButton.setTooltip(Tooltip.create(tooltip)); + } // 清理并标记需要重建 UI 按钮(但不在此处做重建) this.openUIButtons.values().forEach(this::removeWidget); this.openUIButtons.clear(); @@ -226,51 +266,104 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen */ @Inject(method = "refreshList", at = @At("TAIL"), remap = false) private void onRefreshListEnd(CallbackInfo ci) { - if (!this.eap$showSlots) { + if (!this.eap$showSlots || this.eap$mergeEmptySlots) { try { HashMap newHighlightBtns = new HashMap<>(); - int newIndex = 0; + ArrayList newRows = new ArrayList<>(); + this.eap$slotsToShowMap.clear(); + + @SuppressWarnings("unchecked") + ArrayList typedRows = (ArrayList) rows; - // 遍历 rows,保留 GroupHeaderRow 并尝试复用 highlightBtns(原 index -> 按钮) - for (int i = 0; i < rows.size(); i++) { - Object row = rows.get(i); - String className = row.getClass().getSimpleName(); + for (int i = 0; i < typedRows.size(); i++) { + Object row = typedRows.get(i); + String fullClassName = row.getClass().getName(); + System.out.println("EAP Debug: Processing Row " + i + ": " + fullClassName); - if (className.equals("GroupHeaderRow")) { - @SuppressWarnings("unchecked") - ArrayList typedRows = (ArrayList) rows; - typedRows.set(newIndex, row); - - // 原 highlightBtns 在原实现中是放在 GroupHeaderRow 之后第一个 SlotsRow 的 index(i+1) - // 尝试复用原映射中 i+1 的按钮(若存在) - if (highlightBtns.containsKey(i + 1)) { - HighlightButton button = highlightBtns.get(i + 1); - newHighlightBtns.put(newIndex, button); + if (fullClassName.endsWith(".GuiExPatternTerminal$GroupHeaderRow")) { + int headerOldIndex = i; + int headerNewIndex = newRows.size(); + newRows.add(row); + + // 逻辑:如果 !eap$showSlots,则 HighlightButton 放于 Header 位置 + if (!this.eap$showSlots && highlightBtns.containsKey(headerOldIndex + 1)) { + newHighlightBtns.put(headerNewIndex, highlightBtns.get(headerOldIndex + 1)); } + } else if (fullClassName.endsWith(".GuiExPatternTerminal$SlotsRow") && this.eap$showSlots) { + try { + GuiExPatternTerminalSlotsRowAccessor accessor = (GuiExPatternTerminalSlotsRowAccessor) row; + PatternContainerRecord container = accessor.getContainer(); + Long serverId = container.getServerId(); + + Integer slotsToShow = this.eap$slotsToShowMap.get(serverId); + if (slotsToShow == null) { + var inv = container.getInventory(); + int lastNonEmpty = -1; + for (int j = 0; j < inv.size(); j++) { + if (!inv.getStackInSlot(j).isEmpty()) { + lastNonEmpty = j; + } + } + slotsToShow = Math.min(inv.size(), lastNonEmpty + 2); + this.eap$slotsToShowMap.put(serverId, slotsToShow); + } - newIndex++; + int offset = accessor.getOffset(); + if (offset < slotsToShow) { + int availableSlots = Math.min(accessor.getSlots(), slotsToShow - offset); + int currentRowIndex = newRows.size(); + + if (availableSlots == accessor.getSlots()) { + newRows.add(row); + } else { + Object newSlotsRow = eap$createSlotsRow(container, offset, availableSlots); + if (newSlotsRow != null) { + newRows.add(newSlotsRow); + } + } + + // 如果 showSlots 为真,HighlightButton 应位于 SlotsRow 位置(通常是第一个) + if (highlightBtns.containsKey(i)) { + newHighlightBtns.put(currentRowIndex, highlightBtns.get(i)); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } else { + // Unrecognized row type, keep it just in case? Or maybe it's the superclass row type? + newRows.add(row); } - // SlotsRow:跳过,不保留 } - // 移除多余行 - while (rows.size() > newIndex) { - rows.remove(rows.size() - 1); - } - - // 更新 highlightBtns:清理旧 map 并复用 new map + typedRows.clear(); + typedRows.addAll(newRows); highlightBtns.clear(); highlightBtns.putAll(newHighlightBtns); - - // 强制刷新滚动条(一次) this.resetScrollbar(); - } catch (Exception ignored) { + } catch (Throwable e) { + e.printStackTrace(); } } // 标记按钮需要重建(因为 rows 结构可能已改变) this.buttonsDirty = true; } + /** + * 通过反射创建 AE2 的 SlotsRow record + */ + @Unique + private Object eap$createSlotsRow(PatternContainerRecord container, int offset, int slots) { + try { + Class slotsRowCls = Class.forName("com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal$SlotsRow"); + Constructor ctor = slotsRowCls.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + return ctor.newInstance(container, offset, slots); + } catch (Exception e) { + return null; + } + } + /** * drawFG 优化:仅在需要时创建/移除按钮;每帧只更新可见按钮的位置与可见性。 */ @@ -281,6 +374,37 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen int rowsSize = rows.size(); int visRows = this.visibleRows; + // Handle dynamic auto-expansion + if (this.eap$mergeEmptySlots && this.eap$showSlots) { + boolean needsRefresh = false; + for (Object row : this.rows) { + if (row.getClass().getName().endsWith(".GuiExPatternTerminal$SlotsRow")) { + GuiExPatternTerminalSlotsRowAccessor accessor = (GuiExPatternTerminalSlotsRowAccessor) row; + PatternContainerRecord container = accessor.getContainer(); + Long serverId = container.getServerId(); + Integer cachedSlots = this.eap$slotsToShowMap.get(serverId); + if (cachedSlots != null) { + var inv = container.getInventory(); + int lastNonEmpty = -1; + for (int j = 0; j < inv.size(); j++) { + if (!inv.getStackInSlot(j).isEmpty()) { + lastNonEmpty = j; + } + } + int currentSlotsToShow = Math.min(inv.size(), lastNonEmpty + 2); + if (currentSlotsToShow != cachedSlots) { + needsRefresh = true; + break; + } + } + } + } + if (needsRefresh) { + this.eap$slotsToShowMap.clear(); + net.minecraft.client.Minecraft.getInstance().execute(this::refreshList); + } + } + // 当列表或滚动或 visibleRows 发生变化时,重建或清理按钮(按需) boolean needFullUpdate = this.buttonsDirty || currentScroll != lastScroll diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index b630cd9..4c36cff 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -2,6 +2,8 @@ "gui.expatternprovider.toggle_slots": "Toggle Slots", "gui.expatternprovider.hide_slots": "Hide Slots", "gui.expatternprovider.show_slots": "Show Slots", + "gui.expatternprovider.merge_empty_slots": "Merge Empty Slots", + "gui.expatternprovider.unmerge_empty_slots": "Unmerge Empty Slots", "gui.expatternprovider.clear_selection": "Clear Selection", "gui.extendedae_plus.global.toggle_blocking": "Toggle Blocking Mode", "gui.extendedae_plus.global.toggle_adv_blocking": "Toggle Advanced Blocking", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 059a056..2710b49 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -2,6 +2,8 @@ "gui.expatternprovider.toggle_slots": "切换槽位显示", "gui.expatternprovider.hide_slots": "隐藏槽位", "gui.expatternprovider.show_slots": "显示槽位", + "gui.expatternprovider.merge_empty_slots": "合并空槽位", + "gui.expatternprovider.unmerge_empty_slots": "取消合并空槽位", "gui.expatternprovider.clear_selection": "取消选择", "gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式", "gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡", diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 640cafb..2d53c9f 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -23,6 +23,7 @@ "ae2.menu.CraftConfirmMenuGoBackMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalGroupHeaderRowAccessor", + "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", "extendedae.accessor.HighlightButtonAccessor", "extendedae.client.HighlightButtonMixin", "extendedae.client.gui.GuiExPatternProviderMixin",