增加jei中鼠标中建可以直接下单功能
This commit is contained in:
parent
b5cb61a160
commit
04ea2681da
|
|
@ -12,6 +12,8 @@ import com.extendedae_plus.init.ModBlockEntities;
|
|||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModCreativeTabs;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
|
||||
/**
|
||||
* ExtendedAE Plus 主mod类
|
||||
|
|
@ -45,6 +47,10 @@ public class ExtendedAEPlus {
|
|||
*/
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
// 注册本模组网络通道与数据包
|
||||
event.enqueueWork(ModNetwork::register);
|
||||
event.enqueueWork(() -> {
|
||||
ModNetwork.register();
|
||||
// 注册自定义 Curios 宿主定位器,便于将菜单宿主信息在服务端与客户端间同步
|
||||
MenuLocators.register(CuriosItemLocator.class, CuriosItemLocator::writeToPacket, CuriosItemLocator::readFromPacket);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
src/main/java/com/extendedae_plus/client/InputEvents.java
Normal file
48
src/main/java/com/extendedae_plus/client/InputEvents.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.OpenCraftFromJeiC2SPacket;
|
||||
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.integration.modules.jei.GenericEntryStackHelper;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ScreenEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||
public final class InputEvents {
|
||||
private InputEvents() {}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onMouseButtonPre(ScreenEvent.MouseButtonPressed.Pre event) {
|
||||
// 只处理中键按下
|
||||
if (event.getButton() != GLFW.GLFW_MOUSE_BUTTON_MIDDLE) return;
|
||||
|
||||
// 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY);
|
||||
if (hovered.isEmpty()) {
|
||||
hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
}
|
||||
if (hovered.isEmpty()) return;
|
||||
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed);
|
||||
if (stack == null) return;
|
||||
|
||||
// 发送到服务端,让其验证并打开 CraftAmountMenu
|
||||
ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack));
|
||||
|
||||
// 消费此次点击,避免 JEI/原版对中键的其它处理
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.extendedae_plus.integration.jei;
|
||||
|
||||
import mezz.jei.api.IModPlugin;
|
||||
import mezz.jei.api.JeiPlugin;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
|
||||
@JeiPlugin
|
||||
public class ExtendedAEJeiPlugin implements IModPlugin {
|
||||
private static final ResourceLocation UID = new ResourceLocation(ExtendedAEPlus.MODID, "jei_plugin");
|
||||
|
||||
@Override
|
||||
public ResourceLocation getPluginUid() {
|
||||
return UID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
|
||||
JeiRuntimeProxy.setRuntime(jeiRuntime);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.extendedae_plus.integration.jei;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.runtime.IBookmarkOverlay;
|
||||
import mezz.jei.api.runtime.IIngredientListOverlay;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
|
||||
/**
|
||||
* 线程安全地缓存并访问 JEI Runtime。
|
||||
*/
|
||||
public final class JeiRuntimeProxy {
|
||||
private static volatile IJeiRuntime RUNTIME;
|
||||
|
||||
private JeiRuntimeProxy() {}
|
||||
|
||||
static void setRuntime(IJeiRuntime runtime) {
|
||||
RUNTIME = runtime;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IJeiRuntime get() {
|
||||
return RUNTIME;
|
||||
}
|
||||
|
||||
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse() {
|
||||
IJeiRuntime rt = RUNTIME;
|
||||
if (rt == null) return Optional.empty();
|
||||
|
||||
IIngredientListOverlay list = rt.getIngredientListOverlay();
|
||||
if (list != null) {
|
||||
var ing = list.getIngredientUnderMouse();
|
||||
if (ing.isPresent()) return ing.map(i -> (ITypedIngredient<?>) i);
|
||||
}
|
||||
IBookmarkOverlay bm = rt.getBookmarkOverlay();
|
||||
if (bm != null) {
|
||||
var ing = bm.getIngredientUnderMouse();
|
||||
if (ing.isPresent()) return ing.map(i -> (ITypedIngredient<?>) i);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 JEI 配方界面区域内,基于屏幕坐标查询鼠标下的配料(优先物品,其次流体)。
|
||||
*/
|
||||
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse(double mouseX, double mouseY) {
|
||||
IJeiRuntime rt = RUNTIME;
|
||||
if (rt == null || rt.getRecipesGui() == null) return Optional.empty();
|
||||
|
||||
var ingredientManager = rt.getIngredientManager();
|
||||
|
||||
// 支持物品(通用且所有版本可用)。如需流体可后续按版本判断再扩展
|
||||
var item = rt.getRecipesGui().getIngredientUnderMouse(VanillaTypes.ITEM_STACK)
|
||||
.flatMap(v -> ingredientManager.createTypedIngredient(VanillaTypes.ITEM_STACK, v))
|
||||
.map(x -> (ITypedIngredient<?>) x);
|
||||
if (item.isPresent()) return Optional.of(item.get());
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.extendedae_plus.menu.locator;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import appeng.api.implementations.menuobjects.IMenuItem;
|
||||
import appeng.api.implementations.menuobjects.ItemMenuHost;
|
||||
import appeng.menu.locator.MenuLocator;
|
||||
|
||||
// Curios API (软依赖)
|
||||
import top.theillusivec4.curios.api.CuriosApi;
|
||||
import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler;
|
||||
|
||||
/**
|
||||
* 适配 Curios 槽位的自定义 MenuLocator:
|
||||
* 通过 slotId + index 在两端查找 Curios 实际物品引用,确保 NBT 变化(如耗电)能持久化。
|
||||
*/
|
||||
public record CuriosItemLocator(String slotId, int index) implements MenuLocator {
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T locate(Player player, Class<T> hostInterface) {
|
||||
try {
|
||||
var resolved = CuriosApi.getCuriosInventory(player).resolve();
|
||||
if (resolved.isPresent()) {
|
||||
var handler = resolved.get();
|
||||
ICurioStacksHandler stacksHandler = handler.getCurios().get(slotId);
|
||||
if (stacksHandler != null) {
|
||||
ItemStack it = stacksHandler.getStacks().getStackInSlot(index);
|
||||
if (!it.isEmpty() && it.getItem() instanceof IMenuItem guiItem) {
|
||||
ItemMenuHost menuHost = guiItem.getMenuHost(player, -1, it, null);
|
||||
if (hostInterface.isInstance(menuHost)) {
|
||||
return hostInterface.cast(menuHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void writeToPacket(FriendlyByteBuf buf) {
|
||||
buf.writeUtf(slotId);
|
||||
buf.writeVarInt(index);
|
||||
}
|
||||
|
||||
public static CuriosItemLocator readFromPacket(FriendlyByteBuf buf) {
|
||||
String slotId = buf.readUtf();
|
||||
int index = buf.readVarInt();
|
||||
return new CuriosItemLocator(slotId, index);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,12 @@ public class ModNetwork {
|
|||
.decoder(PickFromWirelessC2SPacket::decode)
|
||||
.consumerNetworkThread(PickFromWirelessC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(OpenCraftFromJeiC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(OpenCraftFromJeiC2SPacket::encode)
|
||||
.decoder(OpenCraftFromJeiC2SPacket::decode)
|
||||
.consumerNetworkThread(OpenCraftFromJeiC2SPacket::handle)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static int nextId() { return id++; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.items.tools.powered.WirelessTerminalItem;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import appeng.menu.me.crafting.CraftAmountMenu;
|
||||
|
||||
import com.extendedae_plus.util.WirelessTerminalLocator;
|
||||
|
||||
/**
|
||||
* C2S:从 JEI 中键点击请求打开 AE 的下单界面。
|
||||
* 负载为一个 GenericStack(物品或流体)。
|
||||
*/
|
||||
public class OpenCraftFromJeiC2SPacket {
|
||||
private final GenericStack stack;
|
||||
|
||||
public OpenCraftFromJeiC2SPacket(GenericStack stack) {
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
public static void encode(OpenCraftFromJeiC2SPacket msg, FriendlyByteBuf buf) {
|
||||
GenericStack.writeBuffer(msg.stack, buf);
|
||||
}
|
||||
|
||||
public static OpenCraftFromJeiC2SPacket decode(FriendlyByteBuf buf) {
|
||||
var gs = GenericStack.readBuffer(buf);
|
||||
return new OpenCraftFromJeiC2SPacket(gs);
|
||||
}
|
||||
|
||||
public static void handle(OpenCraftFromJeiC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
var context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null || msg.stack == null) return;
|
||||
|
||||
// 仅支持 AEKey 为可合成的种类
|
||||
AEKey what = msg.stack.what();
|
||||
|
||||
// 定位无线终端
|
||||
var located = WirelessTerminalLocator.find(player);
|
||||
if (located.isEmpty()) return;
|
||||
|
||||
if (!(located.stack.getItem() instanceof WirelessTerminalItem wt)) return;
|
||||
|
||||
// 基本前置校验:联网、电量
|
||||
IGrid grid = wt.getLinkedGrid(located.stack, player.level(), player);
|
||||
if (grid == null) return;
|
||||
if (!wt.hasPower(player, 0.5, located.stack)) return;
|
||||
|
||||
// 该 Key 是否可被网络自动合成
|
||||
var craftingService = grid.getCraftingService();
|
||||
if (!craftingService.isCraftable(what)) return;
|
||||
|
||||
var hand = located.getHand();
|
||||
int slot = located.getSlotIndex();
|
||||
if (hand != null) {
|
||||
int initial = 1;
|
||||
CraftAmountMenu.open(player, MenuLocators.forHand(player, hand), what, initial);
|
||||
} else if (slot >= 0) {
|
||||
// 直接基于物品槽位作为菜单宿主打开数量输入界面
|
||||
int initial = 1; // 初始数量,避免依赖具体 Key 的单位定义
|
||||
CraftAmountMenu.open(player, MenuLocators.forInventorySlot(slot), what, initial);
|
||||
} else {
|
||||
// Curios 槽位:使用自定义定位器携带 slotId + index 作为菜单宿主
|
||||
String curiosSlotId = located.getCuriosSlotId();
|
||||
int curiosIndex = located.getCuriosIndex();
|
||||
if (curiosSlotId != null && curiosIndex >= 0) {
|
||||
int initial = 1;
|
||||
CraftAmountMenu.open(player, new CuriosItemLocator(curiosSlotId, curiosIndex), what, initial);
|
||||
} else {
|
||||
// 未知宿主(回退忽略)
|
||||
}
|
||||
}
|
||||
});
|
||||
context.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,46 +28,90 @@ public final class WirelessTerminalLocator {
|
|||
public static final class LocatedTerminal {
|
||||
public final ItemStack stack;
|
||||
private final Consumer<ItemStack> setter;
|
||||
// 在玩家 Inventory 中的槽位索引(0..size-1)。若未知则为 -1。
|
||||
private final int slotIndex;
|
||||
// 若终端在玩家手上,则记录手别;否则为 null。
|
||||
private final net.minecraft.world.InteractionHand hand;
|
||||
// 若终端位于 Curios,则记录其槽位组 ID 与组内索引;否则 slotId 为 null,index 为 -1。
|
||||
private final String curiosSlotId;
|
||||
private final int curiosIndex;
|
||||
|
||||
public LocatedTerminal(ItemStack stack, Consumer<ItemStack> setter) {
|
||||
this(stack, setter, -1, null, null, -1);
|
||||
}
|
||||
|
||||
public LocatedTerminal(ItemStack stack, Consumer<ItemStack> setter, int slotIndex) {
|
||||
this(stack, setter, slotIndex, null, null, -1);
|
||||
}
|
||||
|
||||
public LocatedTerminal(ItemStack stack, Consumer<ItemStack> setter, int slotIndex, net.minecraft.world.InteractionHand hand) {
|
||||
this(stack, setter, slotIndex, hand, null, -1);
|
||||
}
|
||||
|
||||
public LocatedTerminal(ItemStack stack, Consumer<ItemStack> setter, int slotIndex, net.minecraft.world.InteractionHand hand, String curiosSlotId, int curiosIndex) {
|
||||
this.stack = stack;
|
||||
this.setter = setter;
|
||||
this.slotIndex = slotIndex;
|
||||
this.hand = hand;
|
||||
this.curiosSlotId = curiosSlotId;
|
||||
this.curiosIndex = curiosIndex;
|
||||
}
|
||||
|
||||
public void set(ItemStack newStack) { this.setter.accept(newStack); }
|
||||
public void commit() { this.setter.accept(this.stack); }
|
||||
public boolean isEmpty() { return this.stack == null || this.stack.isEmpty(); }
|
||||
/** 若返回 -1,说明不是从原版 Inventory 槽位中找到(比如 Curios)。 */
|
||||
public int getSlotIndex() { return this.slotIndex; }
|
||||
/** 若不为 null,说明终端在玩家手上。 */
|
||||
public net.minecraft.world.InteractionHand getHand() { return this.hand; }
|
||||
/** 若不为 null,说明终端位于 Curios 指定槽位组。 */
|
||||
public String getCuriosSlotId() { return this.curiosSlotId; }
|
||||
/** Curios 组内索引,未知时为 -1。 */
|
||||
public int getCuriosIndex() { return this.curiosIndex; }
|
||||
}
|
||||
|
||||
public static LocatedTerminal find(Player player) {
|
||||
if (player == null) return new LocatedTerminal(ItemStack.EMPTY, s -> {});
|
||||
|
||||
// 1) 原版槽位
|
||||
// 1) 先检查主手/副手
|
||||
var main = player.getMainHandItem();
|
||||
if (!main.isEmpty() && main.getItem() instanceof WirelessTerminalItem) {
|
||||
return new LocatedTerminal(main, (ns) -> player.setItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND, ns), -1, net.minecraft.world.InteractionHand.MAIN_HAND);
|
||||
}
|
||||
var off = player.getOffhandItem();
|
||||
if (!off.isEmpty() && off.getItem() instanceof WirelessTerminalItem) {
|
||||
return new LocatedTerminal(off, (ns) -> player.setItemInHand(net.minecraft.world.InteractionHand.OFF_HAND, ns), -1, net.minecraft.world.InteractionHand.OFF_HAND);
|
||||
}
|
||||
|
||||
// 2) 原版槽位
|
||||
var inv = player.getInventory();
|
||||
int size = inv.getContainerSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ItemStack st = inv.getItem(i);
|
||||
if (!st.isEmpty() && st.getItem() instanceof WirelessTerminalItem) {
|
||||
final int slot = i;
|
||||
return new LocatedTerminal(st, (ns) -> inv.setItem(slot, ns));
|
||||
return new LocatedTerminal(st, (ns) -> inv.setItem(slot, ns), slot);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Curios 饰品槽(若已加载)
|
||||
// 3) Curios 饰品槽(若已加载)
|
||||
if (ModList.get().isLoaded("curios")) {
|
||||
try {
|
||||
// Curios 1.20.x: 通过 CuriosApi.getCuriosInventory 获取 LazyOptional
|
||||
var resolved = CuriosApi.getCuriosInventory(player).resolve();
|
||||
if (resolved.isPresent()) {
|
||||
ICuriosItemHandler handler = resolved.get();
|
||||
for (ICurioStacksHandler stacksHandler : handler.getCurios().values()) {
|
||||
for (var entry : handler.getCurios().entrySet()) {
|
||||
String slotId = entry.getKey();
|
||||
ICurioStacksHandler stacksHandler = entry.getValue();
|
||||
IDynamicStackHandler stacks = stacksHandler.getStacks();
|
||||
int slots = stacks.getSlots();
|
||||
for (int i = 0; i < slots; i++) {
|
||||
ItemStack st = stacks.getStackInSlot(i);
|
||||
if (!st.isEmpty() && st.getItem() instanceof WirelessTerminalItem) {
|
||||
final int slot = i;
|
||||
return new LocatedTerminal(st, (ns) -> stacks.setStackInSlot(slot, ns));
|
||||
// 记录 Curios 槽位标识与索引,供应后续使用自定义 MenuLocator 打开菜单
|
||||
return new LocatedTerminal(st, (ns) -> stacks.setStackInSlot(slot, ns), -1, null, slotId, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +121,6 @@ public final class WirelessTerminalLocator {
|
|||
}
|
||||
}
|
||||
|
||||
return new LocatedTerminal(ItemStack.EMPTY, s -> {});
|
||||
return new LocatedTerminal(ItemStack.EMPTY, s -> {}, -1, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user