Merge branch 'master' into feature/autoPattern
This commit is contained in:
commit
e21e404b6d
|
|
@ -102,6 +102,7 @@ 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"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.List;
|
|||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 参照 MAE2 的 DynamicCraftingCubeModelProvider,实现形成态光照模型。
|
||||
* 形成态光照模型。
|
||||
*/
|
||||
public class EPlusCraftingCubeModelProvider
|
||||
extends AbstractCraftingUnitModelProvider<EPlusCraftingUnitType> {
|
||||
|
|
@ -29,13 +29,13 @@ public class EPlusCraftingCubeModelProvider
|
|||
public static final ChunkRenderTypeSet CUTOUT = ChunkRenderTypeSet.of(RenderType.cutout());
|
||||
private static final List<Material> MATERIALS = new ArrayList<>();
|
||||
|
||||
// 与 MAE2 一致:将环形边框与基础发光底图放在本模组命名空间
|
||||
//将环形边框与基础发光底图放在本模组命名空间
|
||||
protected static final Material RING_CORNER = texture(ExtendedAEPlus.MODID, "ring_corner");
|
||||
protected static final Material RING_SIDE_HOR = texture(ExtendedAEPlus.MODID, "ring_side_hor");
|
||||
protected static final Material RING_SIDE_VER = texture(ExtendedAEPlus.MODID, "ring_side_ver");
|
||||
protected static final Material LIGHT_BASE = texture(ExtendedAEPlus.MODID, "light_base");
|
||||
|
||||
// 我们自己的亮面贴图(formed 时使用)
|
||||
// 亮面贴图(formed 时使用)
|
||||
protected static final Material ACCELERATOR_4X_LIGHT = texture(ExtendedAEPlus.MODID,
|
||||
"4x_accelerator_light");
|
||||
protected static final Material ACCELERATOR_16X_LIGHT = texture(ExtendedAEPlus.MODID,
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
package com.extendedae_plus.mixin.accessor;
|
||||
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.narration.NarratableEntry;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface ScreenInvoker {
|
||||
@Invoker("addRenderableWidget")
|
||||
<W extends GuiEventListener & Renderable & NarratableEntry> W eap$invokeAddRenderableWidget(W widget);
|
||||
}
|
||||
|
|
@ -2,21 +2,29 @@ package com.extendedae_plus.mixin.ae2;
|
|||
|
||||
import appeng.client.Point;
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.StackWithBounds;
|
||||
import appeng.client.gui.me.crafting.CraftingCPUScreen;
|
||||
import appeng.client.gui.TextOverride;
|
||||
import appeng.client.gui.style.PaletteColor;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.style.Text;
|
||||
import appeng.client.gui.style.TextAlignment;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.menu.slot.AppEngSlot;
|
||||
import com.extendedae_plus.api.ExPatternPageAccessor;
|
||||
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
|
||||
import com.extendedae_plus.util.GuiUtil;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -38,6 +46,74 @@ public abstract class AEBaseScreenMixin {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+左键,
|
||||
* 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
private void eap$craftingCpuShiftLeftClick(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 仅处理 CraftingCPUScreen 实例
|
||||
Object self = this;
|
||||
if (!(self instanceof CraftingCPUScreen<?> screen)) {
|
||||
return;
|
||||
}
|
||||
// 仅在 Shift + 左键 时触发
|
||||
if (button != 0 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY);
|
||||
if (hovered == null || hovered.stack() == null) {
|
||||
return;
|
||||
}
|
||||
AEKey key = hovered.stack().what();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+右键,
|
||||
* 读取鼠标下的 AEKey 并发送 CraftingMonitorOpenProviderC2SPacket(打开样板供应器UI)。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
private void eap$craftingCpuShiftRightClick(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 仅处理 CraftingCPUScreen 实例
|
||||
Object self = this;
|
||||
if (!(self instanceof CraftingCPUScreen<?> screen)) {
|
||||
return;
|
||||
}
|
||||
// 仅在 Shift + 右键 时触发
|
||||
if (button != 1 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY);
|
||||
if (hovered == null || hovered.stack() == null) {
|
||||
return;
|
||||
}
|
||||
AEKey key = hovered.stack().what();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送(打开供应器UI)
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorOpenProviderC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int eap$getIntField(Object self, String name, int def) {
|
||||
Class<?> c = self.getClass();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -10,13 +10,22 @@ import appeng.client.gui.widgets.AETextField;
|
|||
import appeng.client.gui.widgets.IconButton;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.OpenProviderUiC2SPacket;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.Minecraft;
|
||||
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 net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
@ -29,9 +38,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)
|
||||
public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu> {
|
||||
|
||||
@Unique
|
||||
|
|
@ -46,6 +59,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;
|
||||
|
|
@ -109,8 +130,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
|
||||
/**
|
||||
* 拦截鼠标点击事件,实现Shift+左键快速上传样板功能
|
||||
* 注意:某些整合包的 ExtendedAE 版本不在该类中覆写 mouseClicked,此处设置 require=0 以防止注入失败导致崩溃。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true, require = 0)
|
||||
private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 检查是否是左键点击 + Shift键
|
||||
if (button == 0 && hasShiftDown()) {
|
||||
|
|
@ -181,6 +203,86 @@ 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);
|
||||
|
||||
// 避免对 MC 类进行反射,使用强制类型转换后直接调用方法(由 Forge 运行时重映射保证)
|
||||
long posLong = ((BlockPos) pos).asLong();
|
||||
String dimStr = ((ResourceKey<Level>) playerWorld).location().toString();
|
||||
int faceOrd = -1;
|
||||
if (face != null) {
|
||||
faceOrd = ((Direction) face).ordinal();
|
||||
}
|
||||
|
||||
// 发送我们自己的 C2S 包:OpenProviderUiC2SPacket
|
||||
try {
|
||||
ModNetwork.CHANNEL.sendToServer(new OpenProviderUiC2SPacket(
|
||||
posLong,
|
||||
new ResourceLocation(dimStr),
|
||||
faceOrd
|
||||
));
|
||||
} catch (Throwable t) {
|
||||
// 静默失败:不提示玩家
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 静默失败:不输出日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置当前选择的样板供应器ID
|
||||
*/
|
||||
|
|
@ -228,6 +330,70 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
this.addToLeftToolbar(this.eap$toggleSlotsButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理屏幕缩放(resize)后按钮位置未更新的问题:
|
||||
* - 清理并移除现有的“打开UI”按钮
|
||||
* - 尝试重置滚动条并刷新列表
|
||||
* 缩放后的下一帧,drawFG 会基于新的 leftPos/topPos 重建与定位按钮
|
||||
*/
|
||||
@Inject(method = "resize", at = @At("TAIL"), remap = false, require = 0)
|
||||
private void eap$onResize(Minecraft mc, int width, int height, CallbackInfo ci) {
|
||||
try {
|
||||
// 移除并清理按钮,避免旧位置残留
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
|
||||
// 重置一次滚动条,避免可见行/偏移在缩放后与 UI 尺寸不一致
|
||||
try {
|
||||
Method resetScrollbarMethod = null;
|
||||
try {
|
||||
resetScrollbarMethod = this.getClass().getDeclaredMethod("resetScrollbar");
|
||||
} catch (NoSuchMethodException e1) {
|
||||
try {
|
||||
resetScrollbarMethod = this.getClass().getSuperclass().getDeclaredMethod("resetScrollbar");
|
||||
} catch (NoSuchMethodException e2) {
|
||||
resetScrollbarMethod = null;
|
||||
}
|
||||
}
|
||||
if (resetScrollbarMethod != null) {
|
||||
resetScrollbarMethod.setAccessible(true);
|
||||
resetScrollbarMethod.invoke(this);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 刷新列表,使 rows/visibleRows 立即以新尺寸重算
|
||||
try {
|
||||
Method refreshMethod = null;
|
||||
try {
|
||||
refreshMethod = this.getClass().getDeclaredMethod("refreshList");
|
||||
} catch (NoSuchMethodException e1) {
|
||||
try {
|
||||
refreshMethod = this.getClass().getSuperclass().getDeclaredMethod("refreshList");
|
||||
} catch (NoSuchMethodException e2) {
|
||||
refreshMethod = null;
|
||||
}
|
||||
}
|
||||
if (refreshMethod != null) {
|
||||
refreshMethod.setAccessible(true);
|
||||
refreshMethod.invoke(this);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 下次绘制重新输出一次调试行,便于确认缩放后的 rows/scroll
|
||||
this.eap$debugLoggedOnce = false;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"), remap = false, require = 0)
|
||||
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 +402,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 +511,63 @@ 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();
|
||||
|
||||
// 生产环境移除调试日志
|
||||
|
||||
// 先隐藏旧按钮,避免残留
|
||||
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 - 3;
|
||||
|
||||
Button btn = eap$openUIButtons.get(rowIndex);
|
||||
if (btn == null) {
|
||||
btn = Button.builder(Component.literal("UI"), (b) -> {
|
||||
eap$tryOpenProviderUI(rowIndex);
|
||||
}).size(14, 12).build();
|
||||
btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面")));
|
||||
eap$openUIButtons.put(rowIndex, btn);
|
||||
this.addRenderableWidget(btn);
|
||||
}
|
||||
btn.setPosition(bx, by);
|
||||
btn.visible = true;
|
||||
shownCount++;
|
||||
}
|
||||
// 生产环境移除调试日志
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 原有的搜索高亮逻辑
|
||||
// 仅当任一搜索框非空时绘制叠加层(与原版行为保持一致)
|
||||
boolean searchActive = (this.searchOutField != null && !this.searchOutField.getValue().isEmpty())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.crafting.ICraftingProvider;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.me.service.CraftingService;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。
|
||||
* 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器,
|
||||
* 尝试打开其目标机器的 GUI。
|
||||
*/
|
||||
public class CraftingMonitorJumpC2SPacket {
|
||||
private final AEKey what;
|
||||
|
||||
public CraftingMonitorJumpC2SPacket(AEKey what) {
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
public static void encode(CraftingMonitorJumpC2SPacket msg, FriendlyByteBuf buf) {
|
||||
AEKey.writeKey(buf, msg.what);
|
||||
}
|
||||
|
||||
public static CraftingMonitorJumpC2SPacket decode(FriendlyByteBuf buf) {
|
||||
AEKey key = AEKey.readKey(buf);
|
||||
return new CraftingMonitorJumpC2SPacket(key);
|
||||
}
|
||||
|
||||
public static void handle(CraftingMonitorJumpC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName());
|
||||
|
||||
// 必须在 CraftingCPU 界面内
|
||||
if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) {
|
||||
LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过菜单 target(可能是 BlockEntity/Part/ItemHost)按 IActionHost 获取 Grid
|
||||
IGrid grid = null;
|
||||
Object target = ((appeng.menu.AEBaseMenu) menu).getTarget();
|
||||
if (target instanceof IActionHost host && host.getActionableNode() != null) {
|
||||
grid = host.getActionableNode().getGrid();
|
||||
}
|
||||
if (grid == null) {
|
||||
LogUtils.getLogger().info("EAP[S]: grid is null, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
var cs = grid.getCraftingService();
|
||||
if (!(cs instanceof CraftingService craftingService)) {
|
||||
LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) 根据 AEKey 找到可能的样板(pattern)
|
||||
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what);
|
||||
LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what);
|
||||
if (patterns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 遍历提供该样板的 Provider,优先 PatternProviderLogic
|
||||
for (var pattern : patterns) {
|
||||
var providers = craftingService.getProviders(pattern);
|
||||
int providerCount = 0;
|
||||
for (var provider : providers) {
|
||||
providerCount++;
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP[S]: provider class={}", provider.getClass().getName());
|
||||
} catch (Throwable ignored) {}
|
||||
if (provider instanceof PatternProviderLogic ppl) {
|
||||
// 使用 accessor 获取 host(受保护字段通过 accessor 访问)
|
||||
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
|
||||
if (host == null) continue;
|
||||
var pbe = host.getBlockEntity();
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
|
||||
// 尝试对邻居打开 GUI(复用 OpenProviderUiC2SPacket 的策略)
|
||||
for (Direction dir : host.getTargets()) {
|
||||
BlockPos targetPos = pbe.getBlockPos().relative(dir);
|
||||
var tbe = serverLevel.getBlockEntity(targetPos);
|
||||
if (tbe instanceof MenuProvider provider1) {
|
||||
LogUtils.getLogger().info("EAP[S]: open screen via MenuProvider at {}", targetPos);
|
||||
NetworkHooks.openScreen(player, provider1, targetPos);
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
var tstate = serverLevel.getBlockState(targetPos);
|
||||
var provider2 = tstate.getMenuProvider(serverLevel, targetPos);
|
||||
if (provider2 != null) {
|
||||
LogUtils.getLogger().info("EAP[S]: open screen via state.getMenuProvider at {}", targetPos);
|
||||
NetworkHooks.openScreen(player, provider2, targetPos);
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:若无 MenuProvider,始终模拟一次右键(优先有方块实体的一面)
|
||||
InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.MAIN_HAND;
|
||||
Direction chosen = null;
|
||||
for (Direction d : host.getTargets()) {
|
||||
if (serverLevel.getBlockEntity(pbe.getBlockPos().relative(d)) != null) { chosen = d; break; }
|
||||
}
|
||||
if (chosen == null) {
|
||||
for (Direction d : host.getTargets()) {
|
||||
if (!serverLevel.getBlockState(pbe.getBlockPos().relative(d)).isAir()) { chosen = d; break; }
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
BlockPos targetPos = pbe.getBlockPos().relative(chosen);
|
||||
var state2 = serverLevel.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(serverLevel, player, hand, hit);
|
||||
LogUtils.getLogger().info("EAP[S]: simulated use on {}, face={}, result={}", targetPos, chosen, r);
|
||||
if (r.consumesAction()) {
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.getLogger().info("EAP[S]: providers count for one pattern: {}", providerCount);
|
||||
}
|
||||
LogUtils.getLogger().info("EAP[S]: no target opened for key={}", msg.what);
|
||||
});
|
||||
context.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.me.service.CraftingService;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.me.crafting.CraftingCPUMenu;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。
|
||||
* 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器,
|
||||
* 打开该供应器自身的 UI(不是目标机器的 UI)。
|
||||
*/
|
||||
public class CraftingMonitorOpenProviderC2SPacket {
|
||||
private final AEKey what;
|
||||
|
||||
public CraftingMonitorOpenProviderC2SPacket(AEKey what) {
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
public static void encode(CraftingMonitorOpenProviderC2SPacket msg, FriendlyByteBuf buf) {
|
||||
AEKey.writeKey(buf, msg.what);
|
||||
}
|
||||
|
||||
public static CraftingMonitorOpenProviderC2SPacket decode(FriendlyByteBuf buf) {
|
||||
AEKey key = AEKey.readKey(buf);
|
||||
return new CraftingMonitorOpenProviderC2SPacket(key);
|
||||
}
|
||||
|
||||
public static void handle(CraftingMonitorOpenProviderC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorOpenProviderC2SPacket key={} from {}", msg.what, player.getGameProfile().getName());
|
||||
|
||||
// 必须在 CraftingCPU 界面内
|
||||
if (!(player.containerMenu instanceof CraftingCPUMenu menu)) {
|
||||
LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过菜单的 target(可能是 BlockEntity/Part/ItemHost),按 IActionHost 获取 Grid
|
||||
IGrid grid = null;
|
||||
Object target = ((AEBaseMenu) menu).getTarget();
|
||||
if (target instanceof IActionHost host && host.getActionableNode() != null) {
|
||||
grid = host.getActionableNode().getGrid();
|
||||
}
|
||||
if (grid == null) {
|
||||
LogUtils.getLogger().info("EAP[S]: grid is null, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
var cs = grid.getCraftingService();
|
||||
if (!(cs instanceof CraftingService craftingService)) {
|
||||
LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) 根据 AEKey 找到可能的样板(pattern)
|
||||
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what);
|
||||
LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what);
|
||||
if (patterns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 遍历提供该样板的 Provider,定位 PatternProviderLogic
|
||||
for (var pattern : patterns) {
|
||||
var providers = craftingService.getProviders(pattern);
|
||||
for (var provider : providers) {
|
||||
if (provider instanceof PatternProviderLogic ppl) {
|
||||
// accessor 获取 host
|
||||
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
|
||||
if (host == null) continue;
|
||||
var pbe = host.getBlockEntity();
|
||||
if (pbe == null) continue;
|
||||
// 在服务端上下文中执行,pbe 仅用于构造菜单定位器
|
||||
|
||||
// 直接打开供应器自身的 UI(调用 Host 默认方法)
|
||||
try {
|
||||
// 部件与方块实体分别选择定位器
|
||||
if (host instanceof appeng.parts.AEBasePart part) {
|
||||
host.openMenu(player, MenuLocators.forPart(part));
|
||||
} else {
|
||||
host.openMenu(player, MenuLocators.forBlockEntity(pbe));
|
||||
}
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
LogUtils.getLogger().error("EAP[S]: open provider UI failed at {}", pbe.getBlockPos(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: no provider UI opened for key={}", msg.what);
|
||||
});
|
||||
context.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,12 @@ public class ModNetwork {
|
|||
private static int id = 0;
|
||||
|
||||
public static void register() {
|
||||
CHANNEL.messageBuilder(OpenProviderUiC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(OpenProviderUiC2SPacket::encode)
|
||||
.decoder(OpenProviderUiC2SPacket::decode)
|
||||
.consumerNetworkThread(OpenProviderUiC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(PickFromWirelessC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(PickFromWirelessC2SPacket::encode)
|
||||
.decoder(PickFromWirelessC2SPacket::decode)
|
||||
|
|
@ -65,6 +71,18 @@ public class ModNetwork {
|
|||
.decoder(AdvancedBlockingSyncS2CPacket::decode)
|
||||
.consumerNetworkThread(AdvancedBlockingSyncS2CPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(CraftingMonitorJumpC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(CraftingMonitorJumpC2SPacket::encode)
|
||||
.decoder(CraftingMonitorJumpC2SPacket::decode)
|
||||
.consumerNetworkThread(CraftingMonitorJumpC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(CraftingMonitorOpenProviderC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(CraftingMonitorOpenProviderC2SPacket::encode)
|
||||
.decoder(CraftingMonitorOpenProviderC2SPacket::decode)
|
||||
.consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static int nextId() { return id++; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OpenProviderUiC2SPacket {
|
||||
private final long posLong;
|
||||
private final ResourceLocation dimId;
|
||||
private final int faceOrd; // 目前保留,若目标需要可用
|
||||
|
||||
public OpenProviderUiC2SPacket(long posLong, ResourceLocation dimId, int faceOrd) {
|
||||
this.posLong = posLong;
|
||||
this.dimId = dimId;
|
||||
this.faceOrd = faceOrd;
|
||||
}
|
||||
|
||||
public static void encode(OpenProviderUiC2SPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeLong(msg.posLong);
|
||||
buf.writeResourceLocation(msg.dimId);
|
||||
buf.writeVarInt(msg.faceOrd);
|
||||
}
|
||||
|
||||
public static OpenProviderUiC2SPacket decode(FriendlyByteBuf buf) {
|
||||
long posLong = buf.readLong();
|
||||
ResourceLocation dimId = buf.readResourceLocation();
|
||||
int faceOrd = buf.readVarInt();
|
||||
return new OpenProviderUiC2SPacket(posLong, dimId, faceOrd);
|
||||
|
||||
}
|
||||
|
||||
public static void handle(OpenProviderUiC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
|
||||
// 校验维度与方块
|
||||
ResourceKey<Level> levelKey = ResourceKey.create(Registries.DIMENSION, msg.dimId);
|
||||
ServerLevel level = player.server.getLevel(levelKey);
|
||||
if (level == null) {
|
||||
return; // 无效维度
|
||||
}
|
||||
|
||||
BlockPos pos = BlockPos.of(msg.posLong);
|
||||
if (!level.isLoaded(pos)) {
|
||||
return; // 区块未加载
|
||||
}
|
||||
|
||||
var be = level.getBlockEntity(pos);
|
||||
var stateAtPos = level.getBlockState(pos);
|
||||
|
||||
// 目标通常是供应器所面对/连接的“相邻方块”,优先尝试邻居
|
||||
Direction[] tries = (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length)
|
||||
? new Direction[]{Direction.values()[msg.faceOrd]}
|
||||
: Direction.values();
|
||||
|
||||
for (Direction dir : tries) {
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
BlockEntity tbe = level.getBlockEntity(targetPos);
|
||||
if (tbe instanceof MenuProvider provider) {
|
||||
NetworkHooks.openScreen(player, provider, targetPos);
|
||||
return;
|
||||
}
|
||||
var tstate = level.getBlockState(targetPos);
|
||||
MenuProvider provider2 = tstate.getMenuProvider(level, targetPos);
|
||||
if (provider2 != null) {
|
||||
NetworkHooks.openScreen(player, provider2, targetPos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果邻居也未提供 MenuProvider,则兜底:尽量模拟一次徒手右键相邻方块
|
||||
boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty();
|
||||
if (anyHandEmpty) {
|
||||
InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length) {
|
||||
Direction dir = Direction.values()[msg.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, player, hand, hit);
|
||||
if (r.consumesAction()) {
|
||||
return;
|
||||
}
|
||||
} 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, player, hand, hit);
|
||||
if (r.consumesAction()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 无可选邻居
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 双手占用则跳过兜底交互
|
||||
}
|
||||
|
||||
context.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
"PickFromWirelessMixin",
|
||||
"accessor.AbstractContainerScreenAccessor",
|
||||
"accessor.ScreenAccessor",
|
||||
"accessor.ScreenInvoker",
|
||||
"ae2.AEBaseScreenMixin",
|
||||
"ae2.PatternEncodingTermScreenMixin",
|
||||
"ae2.PatternProviderScreenMixin",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user