diff --git a/build.gradle b/build.gradle index c98809e..5016da5 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,9 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" + //curios + modRuntimeOnly "curse.maven:curios-309927:${curios_version}" + modCompileOnly "curse.maven:curios-309927:${curios_version}" // Runtime test modRuntimeOnly "curse.maven:architectury-api-419699:${architectury_version}" @@ -95,7 +98,6 @@ dependencies { modRuntimeOnly "curse.maven:projecte-226410:${projecte_version}" modRuntimeOnly "curse.maven:appliede-1009940:${appliede_version}" modRuntimeOnly "curse.maven:cloth-config-348521:5729105" - modRuntimeOnly "curse.maven:curios-309927:${curios_version}" //extendedAE modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar') diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 8c5b1e2..3d22d72 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -11,6 +11,7 @@ import com.extendedae_plus.init.ModBlocks; import com.extendedae_plus.init.ModBlockEntities; import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModCreativeTabs; +import com.extendedae_plus.network.ModNetwork; /** * ExtendedAE Plus 主mod类 @@ -43,6 +44,7 @@ public class ExtendedAEPlus { * 通用初始化设置 */ private void commonSetup(final FMLCommonSetupEvent event) { - // 现已改用 ExtendedAE 的 EPPNetworkHandler + CGenericPacket,无需自定义网络初始化 + // 注册本模组网络通道与数据包 + event.enqueueWork(ModNetwork::register); } } diff --git a/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java b/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java new file mode 100644 index 0000000..b6bb81a --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/PickFromWirelessMixin.java @@ -0,0 +1,39 @@ +package com.extendedae_plus.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.level.GameType; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; + +import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.network.PickFromWirelessC2SPacket; + + +@Mixin(Minecraft.class) +public class PickFromWirelessMixin { + @Shadow public LocalPlayer player; + @Shadow public HitResult hitResult; + + @Inject(method = "pickBlock", at = @At("HEAD"), cancellable = true) + private void extendedae_plus$pickFromAeWireless(CallbackInfo ci) { + if (this.player == null || this.hitResult == null || this.hitResult.getType() != HitResult.Type.BLOCK) { + return; + } + // 仅生存模式 + GameType type = Minecraft.getInstance().gameMode != null ? Minecraft.getInstance().gameMode.getPlayerMode() : null; + if (type == null || type.isCreative()) { + return; + } + // 发送到服务端处理 + BlockHitResult bhr = (BlockHitResult) this.hitResult; + ModNetwork.CHANNEL.sendToServer(new PickFromWirelessC2SPacket(bhr.getBlockPos(), bhr.getDirection())); + ci.cancel(); + } +} diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/network/ModNetwork.java new file mode 100644 index 0000000..397554b --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/ModNetwork.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.network; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import net.minecraftforge.network.NetworkDirection; + +import com.extendedae_plus.ExtendedAEPlus; + +public class ModNetwork { + private static final String PROTOCOL_VERSION = "1"; + public static final SimpleChannel CHANNEL = NetworkRegistry.ChannelBuilder + .named(new ResourceLocation(ExtendedAEPlus.MODID, "main")) + .networkProtocolVersion(() -> PROTOCOL_VERSION) + .clientAcceptedVersions(PROTOCOL_VERSION::equals) + .serverAcceptedVersions(PROTOCOL_VERSION::equals) + .simpleChannel(); + + private static int id = 0; + + public static void register() { + CHANNEL.messageBuilder(PickFromWirelessC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(PickFromWirelessC2SPacket::encode) + .decoder(PickFromWirelessC2SPacket::decode) + .consumerNetworkThread(PickFromWirelessC2SPacket::handle) + .add(); + } + + private static int nextId() { return id++; } +} diff --git a/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java b/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java new file mode 100644 index 0000000..4416d65 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/PickFromWirelessC2SPacket.java @@ -0,0 +1,122 @@ +package com.extendedae_plus.network; + +import java.util.function.Supplier; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraftforge.network.NetworkEvent; +import net.minecraft.network.FriendlyByteBuf; + +import appeng.api.networking.IGrid; +import appeng.api.storage.MEStorage; +import appeng.api.storage.StorageHelper; +import appeng.api.stacks.AEItemKey; +import appeng.api.networking.energy.IEnergyService; +import appeng.me.helpers.PlayerSource; +import appeng.items.tools.powered.WirelessTerminalItem; +import com.extendedae_plus.util.WirelessTerminalLocator; +import com.extendedae_plus.util.WirelessTerminalLocator.LocatedTerminal; + +public class PickFromWirelessC2SPacket { + private final BlockPos pos; + private final Direction face; + + public PickFromWirelessC2SPacket(BlockPos pos, Direction face) { + this.pos = pos; + this.face = face; + } + + public static void encode(PickFromWirelessC2SPacket msg, FriendlyByteBuf buf) { + buf.writeBlockPos(msg.pos); + buf.writeEnum(msg.face); + } + + public static PickFromWirelessC2SPacket decode(FriendlyByteBuf buf) { + BlockPos pos = buf.readBlockPos(); + Direction face = buf.readEnum(Direction.class); + return new PickFromWirelessC2SPacket(pos, face); + } + + public static void handle(PickFromWirelessC2SPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player == null || player.isCreative()) { + return; + } + ServerLevel level = player.serverLevel(); + BlockState state = level.getBlockState(msg.pos); + if (state == null || state.isAir()) { + return; + } + + // 服务端权威:定位玩家任意槽位的无线终端(含 Curios) + LocatedTerminal located = WirelessTerminalLocator.find(player); + ItemStack terminal = located.stack; + WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem w ? w : null; + if (wt == null || terminal.isEmpty()) { + return; + } + + // 校验网络与电量 + IGrid grid = wt.getLinkedGrid(terminal, level, player); + if (grid == null) { + return; + } + if (!wt.hasPower(player, 0.5, terminal)) { + return; + } + + // 计算 pick 对应的物品 + BlockHitResult bhr = new BlockHitResult(player.position(), msg.face, msg.pos, true); + ItemStack picked = state.getBlock().getCloneItemStack(state, bhr, level, msg.pos, player); + if (picked.isEmpty()) { + // 兜底用方块本身 + picked = state.getBlock().asItem().getDefaultInstance(); + } + if (picked.isEmpty()) { + return; + } + + int targetMax = picked.getMaxStackSize(); + AEItemKey targetKey = AEItemKey.of(picked); + + IEnergyService energy = grid.getEnergyService(); + MEStorage storage = grid.getStorageService().getInventory(); + + ItemStack inHand = player.getMainHandItem(); + + // 若主手有物品:尝试将其移动到玩家背包的空槽位;若没有空位则中止 + if (!inHand.isEmpty()) { + var inv = player.getInventory(); + int free = inv.getFreeSlot(); + if (free == -1) { + return; // 背包已满,不进行拉取 + } + // 将主手整组移动到空槽位 + inv.setItem(free, inHand.copy()); + inv.setItem(inv.selected, ItemStack.EMPTY); + } + + // 现在主手应为空:拉取目标物品,尽量填满一组 + int space = targetMax; // 主手为空,目标为一整组 + long extracted = StorageHelper.poweredExtraction(energy, storage, targetKey, space, new PlayerSource(player)); + if (extracted <= 0) { + return; + } + + player.getInventory().setItem(player.getInventory().selected, targetKey.toStack((int) extracted)); + wt.usePower(player, Math.max(0.5, extracted * 0.05), terminal); + // 确保写回(若位于 Curios 等需要显式写回的容器) + located.commit(); + player.containerMenu.broadcastChanges(); + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java b/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java new file mode 100644 index 0000000..d9bc695 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/WirelessTerminalLocator.java @@ -0,0 +1,82 @@ +package com.extendedae_plus.util; + +import java.util.function.Consumer; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.common.util.LazyOptional; + +import appeng.items.tools.powered.WirelessTerminalItem; + +// Curios API (软依赖) +import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; +import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; + +/** + * 定位玩家身上的无线终端: + * - 原版槽位:主手、副手、盔甲、背包 + * - 若加载了 Curios:遍历所有饰品槽 + * 返回一个可写回的结果,以便能量消耗等 NBT 变更能持久化。 + */ +public final class WirelessTerminalLocator { + private WirelessTerminalLocator() {} + + public static final class LocatedTerminal { + public final ItemStack stack; + private final Consumer setter; + + public LocatedTerminal(ItemStack stack, Consumer setter) { + this.stack = stack; + this.setter = setter; + } + + 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(); } + } + + public static LocatedTerminal find(Player player) { + if (player == null) return new LocatedTerminal(ItemStack.EMPTY, s -> {}); + + // 1) 原版槽位 + 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)); + } + } + + // 2) 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()) { + 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)); + } + } + } + } + } catch (Throwable ignored) { + // 若 Curios API 在运行时不可用或发生异常,则忽略并返回空 + } + } + + return new LocatedTerminal(ItemStack.EMPTY, s -> {}); + } +} diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index f36af20..e2041c8 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -7,7 +7,8 @@ "GuiExPatternProviderMixin", "SlotGridLayoutMixin", "GuiExPatternTerminalMixin", - "HighlightButtonMixin" + "HighlightButtonMixin", + "PickFromWirelessMixin" ], "mixins": [ "ContainerExPatternProviderMixin",