diff --git a/gradle.properties b/gradle.properties index 5799383..62a0082 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.2.1 +mod_version = 1.2.1-fix maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/menu/host/CuriosWTMenuHost.java b/src/main/java/com/extendedae_plus/menu/host/CuriosWTMenuHost.java new file mode 100644 index 0000000..80775ee --- /dev/null +++ b/src/main/java/com/extendedae_plus/menu/host/CuriosWTMenuHost.java @@ -0,0 +1,61 @@ +package com.extendedae_plus.menu.host; + +import java.util.function.BiConsumer; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; + +import appeng.menu.ISubMenu; +import de.mari_023.ae2wtlib.terminal.WTMenuHost; + +// Curios API +import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; + +/** + * 针对 Curios 槽位的 ae2wtlib WTMenuHost 适配器: + * - 复用 wtlib 的量子卡跨维/跨距逻辑(rangeCheck/isQuantumLinked)。 + * - 覆写槽位校验与回写,改为使用 Curios 实际槽位,避免 wtlib 的 Trinkets 平台判断失效。 + */ +public class CuriosWTMenuHost extends WTMenuHost { + private final ICurioStacksHandler curiosHandler; + private final int curiosIndex; + + public CuriosWTMenuHost(Player player, + @Nullable Integer inventorySlot, + ItemStack is, + ICurioStacksHandler curiosHandler, + int curiosIndex, + BiConsumer returnToMainMenu) { + super(player, inventorySlot, is, returnToMainMenu); + this.curiosHandler = curiosHandler; + this.curiosIndex = curiosIndex; + // 初始化内部库存(含奇点槽),以便量子桥判定能够读取到频率等 NBT + try { + super.readFromNbt(); + } catch (Throwable ignored) { + } + } + + @Override + protected boolean ensureItemStillInSlot() { + try { + ItemStack cur = curiosHandler.getStacks().getStackInSlot(curiosIndex); + return !cur.isEmpty(); + } catch (Throwable ignored) { + return false; + } + } + + @Override + public boolean onBroadcastChanges(AbstractContainerMenu menu) { + try { + ItemStack current = getItemStack(); + curiosHandler.getStacks().setStackInSlot(curiosIndex, current); + } catch (Throwable ignored) { + } + return super.onBroadcastChanges(menu); + } +} diff --git a/src/main/java/com/extendedae_plus/menu/locator/CuriosItemLocator.java b/src/main/java/com/extendedae_plus/menu/locator/CuriosItemLocator.java index 1d3a145..2988cd8 100644 --- a/src/main/java/com/extendedae_plus/menu/locator/CuriosItemLocator.java +++ b/src/main/java/com/extendedae_plus/menu/locator/CuriosItemLocator.java @@ -15,6 +15,12 @@ import appeng.menu.me.common.MEStorageMenu; import appeng.menu.locator.MenuLocator; import com.extendedae_plus.menu.host.CuriosWirelessTerminalMenuHost; +// ae2wtlib +import de.mari_023.ae2wtlib.wut.WUTHandler; +import de.mari_023.ae2wtlib.wut.WTDefinition; +import de.mari_023.ae2wtlib.terminal.WTMenuHost; +import com.extendedae_plus.menu.host.CuriosWTMenuHost; + // Curios API (软依赖) import top.theillusivec4.curios.api.CuriosApi; import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; @@ -35,8 +41,26 @@ public record CuriosItemLocator(String slotId, int index) implements MenuLocator if (stacksHandler != null) { ItemStack it = stacksHandler.getStacks().getStackInSlot(index); if (!it.isEmpty()) { + // 1) ae2wtlib: 优先构造 WTMenuHost 以启用量子卡的跨维/跨距逻辑 + String current = WUTHandler.getCurrentTerminal(it); + WTDefinition def = WUTHandler.wirelessTerminals.get(current); + if (def != null) { + WTMenuHost wtHost = new CuriosWTMenuHost( + player, + null, + it, + stacksHandler, + index, + (p, sub) -> MenuOpener.open(MEStorageMenu.WIRELESS_TYPE, p, this) + ); + if (hostInterface.isInstance(wtHost)) { + return hostInterface.cast(wtHost); + } + } + + // 2) 回退:AE2 原生无线终端 if (it.getItem() instanceof WirelessTerminalItem) { - // 为 Curios 构建一个带回写能力的宿主 + // 首选:为 CraftAmountMenu 等需要网络/能量上下文的菜单提供 WirelessTerminalMenuHost WirelessTerminalMenuHost host = new CuriosWirelessTerminalMenuHost( player, it, diff --git a/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java b/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java index 99d41a4..526fd42 100644 --- a/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java @@ -12,9 +12,12 @@ import net.minecraft.world.level.GameType; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.HitResult; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; import com.extendedae_plus.network.ModNetwork; import com.extendedae_plus.network.PickFromWirelessC2SPacket; +// no client-side WCT gating; server will check presence (including Curios) @Mixin(Minecraft.class) @@ -32,8 +35,35 @@ public class PickFromWirelessMixin { if (type == null || type.isCreative()) { return; } - // 发送到服务端处理 + // 若背包已有该物品,让原版逻辑处理(将该物品切换到主手) BlockHitResult bhr = (BlockHitResult) this.hitResult; + var level = Minecraft.getInstance().level; + if (level != null) { + try { + BlockState state = level.getBlockState(bhr.getBlockPos()); + if (state != null && !state.isAir()) { + ItemStack picked = state.getBlock().getCloneItemStack(state, bhr, level, bhr.getBlockPos(), this.player); + if (picked.isEmpty()) { + picked = state.getBlock().asItem().getDefaultInstance(); + } + if (!picked.isEmpty()) { + // 若主手已拿同一物品(含标签),则仍然走 AE 拉取逻辑进行补充/合并 + if (!ItemStack.isSameItemSameTags(picked, this.player.getMainHandItem())) { + int slot = this.player.getInventory().findSlotMatchingItem(picked); + if (slot != -1) { + return; // 交给原版 pickBlock 处理 + } + } + } + } + } catch (Throwable t) { + // 若其它模组导致 getCloneItemStack 出异常,放弃拦截,保持原版行为,确保健壮性 + return; + } + } + + // 不在客户端检查是否持有无线合成终端,由服务端权威校验(含 Curios 支持),以避免整合包环境下的软依赖与槽位问题 + // 背包没有:发送到服务端处理(从 AE2 网络拉取)并拦截原版 Vec3 loc = bhr.getLocation(); ModNetwork.CHANNEL.sendToServer(new PickFromWirelessC2SPacket(bhr.getBlockPos(), bhr.getDirection(), loc)); ci.cancel(); diff --git a/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java b/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java index 93dd70d..627ced4 100644 --- a/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java @@ -50,6 +50,17 @@ public class OpenCraftFromJeiC2SPacket { var located = WirelessTerminalLocator.find(player); if (located.isEmpty()) return; + // 若为 Curios 槽位:跳过 AE2 基类的距离/电量前置校验,直接打开数量界面, + // 让菜单与宿主(WirelessTerminalMenuHost)以及 ae2wtlib 自身处理量子卡跨维/跨距逻辑。 + 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); + return; + } + + // 非 Curios(主手/副手/背包)仍按原先流程做前置校验,保持行为一致。 if (!(located.stack.getItem() instanceof WirelessTerminalItem wt)) return; // 基本前置校验:联网、电量 @@ -71,15 +82,7 @@ public class OpenCraftFromJeiC2SPacket { 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); diff --git a/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java b/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java index 9e9ccea..02986d7 100644 --- a/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java @@ -21,6 +21,11 @@ import appeng.api.stacks.AEItemKey; import appeng.api.networking.energy.IEnergyService; import appeng.me.helpers.PlayerSource; import appeng.items.tools.powered.WirelessCraftingTerminalItem; +import appeng.items.tools.powered.WirelessTerminalItem; +// ae2wtlib +import de.mari_023.ae2wtlib.wut.WUTHandler; +import de.mari_023.ae2wtlib.wut.WTDefinition; +import de.mari_023.ae2wtlib.terminal.WTMenuHost; import com.extendedae_plus.util.WirelessTerminalLocator; import com.extendedae_plus.util.WirelessTerminalLocator.LocatedTerminal; @@ -68,18 +73,56 @@ public class PickFromWirelessC2SPacket { // 服务端权威:定位玩家任意槽位的无线终端(含 Curios) LocatedTerminal located = WirelessTerminalLocator.find(player); ItemStack terminal = located.stack; - WirelessCraftingTerminalItem wt = terminal.getItem() instanceof WirelessCraftingTerminalItem w ? w : null; - if (wt == null || terminal.isEmpty()) { + if (terminal.isEmpty()) { return; } - // 校验网络与电量 - IGrid grid = wt.getLinkedGrid(terminal, level, player); - if (grid == null) { - return; - } - if (!wt.hasPower(player, 0.5, terminal)) { - return; + IGrid grid; + boolean usedWtHost = false; + // 若来自 Curios:优先通过 ae2wtlib 的 WTMenuHost 获取量子桥网络,绕过距离限制 + String curiosSlotId = located.getCuriosSlotId(); + int curiosIndex = located.getCuriosIndex(); + WTMenuHost wtHost = null; + if (curiosSlotId != null && curiosIndex >= 0) { + String current = WUTHandler.getCurrentTerminal(terminal); + WTDefinition def = WUTHandler.wirelessTerminals.get(current); + if (def != null) { + wtHost = def.wTMenuHostFactory().create(player, null, terminal, (p, sub) -> {}); + if (wtHost != null) { + var node = wtHost.getActionableNode(); + if (node != null) { + grid = node.getGrid(); + if (grid == null) { + return; + } + // 通过 WTMenuHost 的电力处理以兼容量子卡补能 + if (!wtHost.drainPower()) { + return; + } + usedWtHost = true; + } else { + return; + } + } else { + return; + } + } else { + return; + } + } else { + // 非 Curios:按 AE2 原生路径处理 + WirelessCraftingTerminalItem wct = terminal.getItem() instanceof WirelessCraftingTerminalItem c ? c : null; + WirelessTerminalItem wt = wct != null ? wct : (terminal.getItem() instanceof WirelessTerminalItem t ? t : null); + if (wt == null) { + return; + } + grid = wt.getLinkedGrid(terminal, level, player); + if (grid == null) { + return; + } + if (!wt.hasPower(player, 0.5, terminal)) { + return; + } } // 计算 pick 对应的物品:使用客户端实际命中位置,保证多部件方块(AE2 CableBus/部件)能返回正确克隆物品 @@ -150,7 +193,16 @@ public class PickFromWirelessC2SPacket { inv.setItem(free, targetKey.toStack((int) extracted)); } - wt.usePower(player, Math.max(0.5, extracted * 0.05), terminal); + if (usedWtHost) { + // WTMenuHost 已在 drainPower 中处理能量消耗/回充,此处不重复扣除 + } else { + // 原生 AE2 扣能 + WirelessCraftingTerminalItem wct2 = terminal.getItem() instanceof WirelessCraftingTerminalItem c2 ? c2 : null; + WirelessTerminalItem wt2 = wct2 != null ? wct2 : (terminal.getItem() instanceof WirelessTerminalItem t2 ? t2 : null); + if (wt2 != null) { + wt2.usePower(player, Math.max(0.5, extracted * 0.05), terminal); + } + } // 确保写回(若位于 Curios 等需要显式写回的容器) located.commit(); player.containerMenu.broadcastChanges(); diff --git a/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java b/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java index b88c91d..e93a62d 100644 --- a/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java +++ b/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java @@ -9,12 +9,13 @@ import net.minecraftforge.fml.ModList; import net.minecraftforge.common.util.LazyOptional; import appeng.items.tools.powered.WirelessCraftingTerminalItem; +import appeng.items.tools.powered.WirelessTerminalItem; // Curios API (软依赖) import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; -import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; /** * 定位玩家身上的无线终端: @@ -75,11 +76,11 @@ public final class WirelessTerminalLocator { // 1) 先检查主手/副手 var main = player.getMainHandItem(); - if (!main.isEmpty() && main.getItem() instanceof WirelessCraftingTerminalItem) { + if (!main.isEmpty() && (main.getItem() instanceof WirelessCraftingTerminalItem || 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 WirelessCraftingTerminalItem) { + if (!off.isEmpty() && (off.getItem() instanceof WirelessCraftingTerminalItem || 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); } @@ -88,7 +89,7 @@ public final class WirelessTerminalLocator { int size = inv.getContainerSize(); for (int i = 0; i < size; i++) { ItemStack st = inv.getItem(i); - if (!st.isEmpty() && st.getItem() instanceof WirelessCraftingTerminalItem) { + if (!st.isEmpty() && (st.getItem() instanceof WirelessCraftingTerminalItem || st.getItem() instanceof WirelessTerminalItem)) { final int slot = i; return new LocatedTerminal(st, (ns) -> inv.setItem(slot, ns), slot); } @@ -97,7 +98,6 @@ public final class WirelessTerminalLocator { // 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(); @@ -108,10 +108,10 @@ public final class WirelessTerminalLocator { int slots = stacks.getSlots(); for (int i = 0; i < slots; i++) { ItemStack st = stacks.getStackInSlot(i); - if (!st.isEmpty() && st.getItem() instanceof WirelessCraftingTerminalItem) { + if (!st.isEmpty() && (st.getItem() instanceof WirelessCraftingTerminalItem || st.getItem() instanceof WirelessTerminalItem)) { final int slot = i; - // 记录 Curios 槽位标识与索引,供应后续使用自定义 MenuLocator 打开菜单 - return new LocatedTerminal(st, (ns) -> stacks.setStackInSlot(slot, ns), -1, null, slotId, slot); + java.util.function.Consumer setter = (ns) -> stacks.setStackInSlot(slot, ns); + return new LocatedTerminal(st, setter, -1, null, slotId, slot); } } }