增加鼠标中间拉取ae网络中物品功能,并且适配curios饰品槽

This commit is contained in:
GaLicn 2025-08-14 10:52:24 +08:00
parent 687652daf8
commit b5cb61a160
7 changed files with 281 additions and 3 deletions

View File

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

View File

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

View File

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

View File

@ -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++; }
}

View File

@ -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<NetworkEvent.Context> 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);
}
}

View File

@ -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<ItemStack> setter;
public LocatedTerminal(ItemStack stack, Consumer<ItemStack> 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 -> {});
}
}

View File

@ -7,7 +7,8 @@
"GuiExPatternProviderMixin",
"SlotGridLayoutMixin",
"GuiExPatternTerminalMixin",
"HighlightButtonMixin"
"HighlightButtonMixin",
"PickFromWirelessMixin"
],
"mixins": [
"ContainerExPatternProviderMixin",