为管理终端增加打开机器ui按钮功能
This commit is contained in:
parent
83c52fc659
commit
b6b9c14446
|
|
@ -102,9 +102,6 @@ dependencies {
|
|||
//jec
|
||||
modCompileOnly "curse.maven:just-enough-characters-250702:6680042"
|
||||
|
||||
//mae2
|
||||
modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6342203"
|
||||
modCompileOnly "curse.maven:modern-ae2-additions-1028068:6342203"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
|
|||
loom.platform = forge
|
||||
|
||||
# Mod properties
|
||||
mod_version = 1.3.3
|
||||
mod_version = 1.3.4-beta
|
||||
maven_group = com.extendedae_plus
|
||||
archives_name = extendedae_plus
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,21 @@ import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
|||
import com.glodblock.github.glodium.network.packet.sync.Paras;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -17,11 +32,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mixin(ContainerExPatternTerminal.class)
|
||||
public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
||||
|
||||
@GuiSync(11452)
|
||||
@GuiSync(25564)
|
||||
@Unique
|
||||
public boolean eap$hidePatternSlots = false;
|
||||
|
||||
|
|
@ -41,13 +58,19 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
|||
}
|
||||
|
||||
@Unique
|
||||
private final Map<String, Consumer<Paras>> eap$actions = createHolder();
|
||||
private Map<String, Consumer<Paras>> eap$actions;
|
||||
|
||||
@Unique
|
||||
private Player epp$player;
|
||||
|
||||
@Unique
|
||||
private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus");
|
||||
|
||||
@Inject(method = "<init>*", at = @At("TAIL"))
|
||||
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, IConfigurableObject host, CallbackInfo ci) {
|
||||
if (this.eap$actions == null) {
|
||||
this.eap$actions = createHolder();
|
||||
}
|
||||
this.epp$player = playerInventory.player;
|
||||
// 注册上传动作:参数顺序必须与客户端 CGenericPacket 保持一致
|
||||
this.eap$actions.put("upload", p -> {
|
||||
|
|
@ -61,6 +84,110 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
|||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
|
||||
// 注册打开UI动作:open_ui(posLong, dimensionId, faceOrdinal?)
|
||||
this.eap$actions.put("open_ui", p -> {
|
||||
try {
|
||||
// 参数解析
|
||||
Object po = p.get(0); // BlockPos as long (BlockPos#asLong)
|
||||
Object do0 = p.get(1); // Dimension id string (e.g., minecraft:overworld)
|
||||
Object fo;
|
||||
try {
|
||||
fo = p.get(2); // Optional face ordinal
|
||||
} catch (Throwable __ignored) {
|
||||
fo = null;
|
||||
}
|
||||
|
||||
long posLong = (po instanceof Number) ? ((Number) po).longValue() : Long.parseLong(String.valueOf(po));
|
||||
String dimStr = String.valueOf(do0);
|
||||
int faceOrd = -1;
|
||||
if (fo != null) {
|
||||
faceOrd = (fo instanceof Number) ? ((Number) fo).intValue() : Integer.parseInt(String.valueOf(fo));
|
||||
}
|
||||
|
||||
BlockPos pos = BlockPos.of(posLong);
|
||||
ResourceLocation dimId = ResourceLocation.tryParse(dimStr);
|
||||
if (dimId == null) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: invalid dim '{}'", dimStr);
|
||||
return;
|
||||
}
|
||||
ResourceKey<Level> dimKey = ResourceKey.create(Registries.DIMENSION, dimId);
|
||||
|
||||
if (!(this.epp$player instanceof ServerPlayer sp)) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: not a ServerPlayer");
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = sp.server.getLevel(dimKey);
|
||||
if (level == null) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: level null for key {}", dimKey);
|
||||
return;
|
||||
}
|
||||
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: pos={}, dim={}, faceOrd={}", pos, dimKey.location(), faceOrd);
|
||||
|
||||
// 目标应为供应器所面向/连接的相邻方块,而非供应器自身
|
||||
Direction[] tries = (faceOrd >= 0 && faceOrd < Direction.values().length)
|
||||
? new Direction[]{Direction.values()[faceOrd]}
|
||||
: Direction.values();
|
||||
|
||||
// 1) 先尝试在相邻方块直接打开 MenuProvider
|
||||
for (Direction dir : tries) {
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
BlockEntity be = level.getBlockEntity(targetPos);
|
||||
if (be instanceof MenuProvider provider) {
|
||||
NetworkHooks.openScreen(sp, provider, targetPos);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: opened BE MenuProvider at {} (neighbor via {})", targetPos, dir);
|
||||
return;
|
||||
}
|
||||
var state = level.getBlockState(targetPos);
|
||||
MenuProvider provider = state.getMenuProvider(level, targetPos);
|
||||
if (provider != null) {
|
||||
NetworkHooks.openScreen(sp, provider, targetPos);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: opened State MenuProvider at {} (neighbor via {})", targetPos, dir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 兜底:为避免误触发放置/覆盖,仅在手上至少有一只手为空时,使用 BlockState.use 进行一次“徒手交互”
|
||||
boolean hasFace = (faceOrd >= 0 && faceOrd < Direction.values().length);
|
||||
boolean anyHandEmpty = sp.getMainHandItem().isEmpty() || sp.getOffhandItem().isEmpty();
|
||||
if (anyHandEmpty) {
|
||||
InteractionHand hand = sp.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (hasFace) {
|
||||
Direction dir = Direction.values()[faceOrd];
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, sp, hand, hit);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (via {}), result={}", targetPos, dir.getOpposite(), dir, r);
|
||||
} else {
|
||||
// 无朝向:优先尝试有方块实体的邻居,否则尝试实心方块邻居,各只尝试一次
|
||||
Direction chosen = null;
|
||||
for (Direction d : Direction.values()) {
|
||||
if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; }
|
||||
}
|
||||
if (chosen == null) {
|
||||
for (Direction d : Direction.values()) {
|
||||
if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; }
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
BlockPos targetPos = pos.relative(chosen);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, sp, hand, hit);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (auto via {}), result={}", targetPos, chosen.getOpposite(), chosen, r);
|
||||
} else {
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: no neighbor candidate for fallback (faceOrd<0)");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: skip fallback (hands occupied)");
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import appeng.client.gui.widgets.IconButton;
|
|||
import appeng.menu.AEBaseMenu;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
|
@ -29,9 +30,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Pseudo
|
||||
@Mixin(GuiExPatternTerminal.class)
|
||||
@Mixin(value = GuiExPatternTerminal.class, remap = false)
|
||||
public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu> {
|
||||
|
||||
@Unique
|
||||
|
|
@ -46,6 +51,14 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
private boolean eap$showSlots = false; // 默认显示槽位
|
||||
@Unique
|
||||
private long eap$currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID
|
||||
@Unique
|
||||
private final Map<Integer, Button> eap$openUIButtons = new HashMap<>();
|
||||
|
||||
@Unique
|
||||
private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus");
|
||||
|
||||
@Unique
|
||||
private boolean eap$debugLoggedOnce = false;
|
||||
@Shadow(remap = false) private AETextField searchOutField;
|
||||
@Shadow(remap = false) private AETextField searchInField;
|
||||
@Shadow(remap = false) private Set<ItemStack> matchedStack;
|
||||
|
|
@ -181,6 +194,97 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private int getIntConst(Class<?> cls, String name, int defVal) {
|
||||
try {
|
||||
var f = cls.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
return (int) f.get(null);
|
||||
} catch (Throwable t) {
|
||||
return defVal;
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$tryOpenProviderUI(int rowIndex) {
|
||||
try {
|
||||
// 使用 Accessor 获取 rows,避免取到父类导致失败
|
||||
com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc =
|
||||
(com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this;
|
||||
java.util.ArrayList<?> rows = acc.getRows();
|
||||
|
||||
// 找到该分组对应的第一个 PatternContainerRecord
|
||||
Class<?> cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class;
|
||||
var byGroupField = cls.getDeclaredField("byGroup");
|
||||
byGroupField.setAccessible(true);
|
||||
Object byGroup = byGroupField.get(this); // HashMultimap<PatternContainerGroup, PatternContainerRecord>
|
||||
|
||||
Object headerRow = rows.get(rowIndex);
|
||||
var groupField = headerRow.getClass().getDeclaredField("group");
|
||||
groupField.setAccessible(true);
|
||||
Object group = groupField.get(headerRow);
|
||||
|
||||
// 调用 byGroup.get(group),再取第一个元素
|
||||
java.util.Collection<?> containers = (java.util.Collection<?>) byGroup.getClass().getMethod("get", Object.class).invoke(byGroup, group);
|
||||
if (containers == null || containers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Object firstRecord = containers.iterator().next(); // PatternContainerRecord
|
||||
long serverId = (long) firstRecord.getClass().getMethod("getServerId").invoke(firstRecord);
|
||||
|
||||
// 通过 infoMap 获取位置信息
|
||||
var infoMapField = cls.getDeclaredField("infoMap");
|
||||
infoMapField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.HashMap<Long, Object> infoMap = (java.util.HashMap<Long, Object>) infoMapField.get(this);
|
||||
Object info = infoMap.get(serverId);
|
||||
if (info == null) {
|
||||
// 无位置信息,提示
|
||||
if (this.minecraft != null && this.minecraft.player != null) {
|
||||
this.minecraft.player.displayClientMessage(Component.literal("未找到该供应器的位置信息,无法打开UI"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// PatternProviderInfo record: pos(), face(), playerWorld()
|
||||
Object pos = info.getClass().getMethod("pos").invoke(info);
|
||||
Object face = info.getClass().getMethod("face").invoke(info); // 可能为 null(方块型供应器)
|
||||
Object playerWorld = info.getClass().getMethod("playerWorld").invoke(info);
|
||||
|
||||
long posLong = (long) pos.getClass().getMethod("asLong").invoke(pos);
|
||||
Object rl = playerWorld.getClass().getMethod("location").invoke(playerWorld); // ResourceLocation
|
||||
String dimStr = (String) rl.getClass().getMethod("toString").invoke(rl);
|
||||
int faceOrd = -1;
|
||||
if (face != null) {
|
||||
faceOrd = (int) face.getClass().getMethod("ordinal").invoke(face);
|
||||
}
|
||||
|
||||
// 发送 CGenericPacket("open_ui", [posLong, dim, face])
|
||||
try {
|
||||
Class<?> 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("open_ui", new Object[]{posLong, dimStr, faceOrd});
|
||||
|
||||
Class<?> iMessage = Class.forName("com.glodblock.github.glodium.network.packet.IMessage");
|
||||
Method sendToServer = EPPNetworkHandlerClass.getMethod("sendToServer", iMessage);
|
||||
|
||||
sendToServer.invoke(handlerInstance, packet);
|
||||
if (this.minecraft != null && this.minecraft.player != null) {
|
||||
EAP_LOGGER.debug("[EPlus] Sent open_ui packet: pos={}, dim={}, face={}", posLong, dimStr, faceOrd);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (this.minecraft != null && this.minecraft.player != null) {
|
||||
this.minecraft.player.displayClientMessage(Component.literal("❌ ExtendedAE Plus: 网络模块不可用,无法发送打开UI请求"), true);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
EAP_LOGGER.warn("[EPlus] eap$tryOpenProviderUI failed: {}", t.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置当前选择的样板供应器ID
|
||||
*/
|
||||
|
|
@ -228,6 +332,13 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
this.addToLeftToolbar(this.eap$toggleSlotsButton);
|
||||
}
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"), remap = false)
|
||||
private void eap$onInit(CallbackInfo ci) {
|
||||
// 清理旧的打开UI按钮
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "refreshList", at = @At("HEAD"), remap = false)
|
||||
private void onRefreshListStart(CallbackInfo ci) {
|
||||
// 更新按钮图标
|
||||
|
|
@ -236,6 +347,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
this.eap$showSlots ? "gui.expatternprovider.hide_slots" : "gui.expatternprovider.show_slots"
|
||||
)));
|
||||
}
|
||||
// 清理旧的打开UI按钮
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "refreshList", at = @At("TAIL"), remap = false)
|
||||
|
|
@ -342,6 +456,71 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
|
||||
@Inject(method = "drawFG", at = @At("TAIL"), remap = false)
|
||||
private void eap$afterDrawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY, CallbackInfo ci) {
|
||||
// 动态放置/创建每个组标题后的“打开UI”按钮
|
||||
try {
|
||||
// 使用 Accessor 获取必要的字段,避免反射失败
|
||||
com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc =
|
||||
(com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this;
|
||||
java.util.ArrayList<?> rows = acc.getRows();
|
||||
int currentScroll = acc.getScrollbar().getCurrentScroll();
|
||||
|
||||
// 直接引用目标类以获取其静态常量
|
||||
Class<?> cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class;
|
||||
int GUI_PADDING_X = getIntConst(cls, "GUI_PADDING_X", 22);
|
||||
int GUI_PADDING_Y = getIntConst(cls, "GUI_PADDING_Y", 6);
|
||||
int GUI_HEADER_HEIGHT = getIntConst(cls, "GUI_HEADER_HEIGHT", 51);
|
||||
int ROW_HEIGHT = getIntConst(cls, "ROW_HEIGHT", 18);
|
||||
int TEXT_MAX_WIDTH = getIntConst(cls, "TEXT_MAX_WIDTH", 155);
|
||||
|
||||
int visibleRows = acc.getVisibleRows();
|
||||
|
||||
if (!eap$debugLoggedOnce) {
|
||||
EAP_LOGGER.info("[EPlus] GuiExPatternTerminalMixin.afterDrawFG fired: rows={}, currentScroll={}, visibleRows={}",
|
||||
rows.size(), currentScroll, visibleRows);
|
||||
eap$debugLoggedOnce = true;
|
||||
}
|
||||
|
||||
// 先隐藏旧按钮,避免残留
|
||||
for (Button b : this.eap$openUIButtons.values()) {
|
||||
b.visible = false;
|
||||
}
|
||||
|
||||
int shownCount = 0;
|
||||
for (int i = 0; i < visibleRows; i++) {
|
||||
int rowIndex = currentScroll + i;
|
||||
if (rowIndex < 0 || rowIndex >= rows.size()) {
|
||||
continue;
|
||||
}
|
||||
Object row = rows.get(rowIndex);
|
||||
if (!row.getClass().getSimpleName().equals("GroupHeaderRow")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 放置按钮:位于名称文本右侧,与原类 choiceButton 锚点相邻,向右偏移 20px
|
||||
int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 40;
|
||||
int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT;
|
||||
|
||||
Button btn = eap$openUIButtons.get(rowIndex);
|
||||
if (btn == null) {
|
||||
btn = Button.builder(Component.literal("UI"), (b) -> {
|
||||
eap$tryOpenProviderUI(rowIndex);
|
||||
}).size(18, 16).build();
|
||||
btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面")));
|
||||
eap$openUIButtons.put(rowIndex, btn);
|
||||
this.addRenderableWidget(btn);
|
||||
}
|
||||
btn.setPosition(bx, by);
|
||||
btn.visible = true;
|
||||
shownCount++;
|
||||
}
|
||||
if (shownCount == 0) {
|
||||
EAP_LOGGER.debug("[EPlus] No GroupHeaderRow visible in current page (scroll={}, rows={})", currentScroll, rows.size());
|
||||
} else {
|
||||
EAP_LOGGER.debug("[EPlus] GroupHeaderRow buttons shown count: {}", shownCount);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 原有的搜索高亮逻辑
|
||||
// 仅当任一搜索框非空时绘制叠加层(与原版行为保持一致)
|
||||
boolean searchActive = (this.searchOutField != null && !this.searchOutField.getValue().isEmpty())
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user