This commit is contained in:
LangQi99 2026-05-30 22:44:33 +08:00 committed by GitHub
commit 8b4eb8e690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 186 additions and 30 deletions

View File

@ -105,6 +105,13 @@ public final class ModConfig {
})
public boolean patternTerminalShowSlotsDefault = true;
@Configurable
@Configurable.Comment(value = {
"样板终端默认是否合并空槽位",
"开启后只会显示到最后一个有物品的槽位之后的一个空槽位(直到占满供应器所有槽位)"
})
public boolean patternTerminalMergeEmptySlotsDefault = true;
@Configurable
@Configurable.Comment(value = {
"实体加速器能量消耗基础值"

View File

@ -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();
}

View File

@ -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<AEBaseMenu>
/* ----- eap 自有字段 ----- */
@Unique private final Map<Integer, Button> 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<AEBaseMenu>
@Unique private int lastScroll = Integer.MIN_VALUE;
@Unique private int lastRowsSize = Integer.MIN_VALUE;
@Unique private int lastVisibleRows = Integer.MIN_VALUE;
@Unique private Map<Long, Integer> 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<AEBaseMenu>
/* ----- 构造注入:创建切换按钮(只设置状态并触发一次 refresh ----- */
@Inject(method = "<init>", 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<AEBaseMenu>
}
};
// 创建合并空项按钮只切换状态并触发一次 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<AEBaseMenu>
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<AEBaseMenu>
*/
@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<Integer, HighlightButton> newHighlightBtns = new HashMap<>();
int newIndex = 0;
ArrayList<Object> newRows = new ArrayList<>();
this.eap$slotsToShowMap.clear();
@SuppressWarnings("unchecked")
ArrayList<Object> typedRows = (ArrayList<Object>) 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<Object> typedRows = (ArrayList<Object>) rows;
typedRows.set(newIndex, row);
// highlightBtns 在原实现中是放在 GroupHeaderRow 之后第一个 SlotsRow indexi+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<AEBaseMenu>
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

View File

@ -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",

View File

@ -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": "切换高级阻挡",

View File

@ -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",