修修修,优化交互逻辑,支持curios中通用终端的量子卡

This commit is contained in:
GaLicn 2025-08-14 17:12:51 +08:00
parent f571790d5e
commit 8b66b3bb7f
7 changed files with 200 additions and 30 deletions

View File

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

View File

@ -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<Player, ISubMenu> 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<ItemStack> setter = (ns) -> stacks.setStackInSlot(slot, ns);
return new LocatedTerminal(st, setter, -1, null, slotId, slot);
}
}
}