From b31ec61b54c0f22e674d3aa411003854d196c9f8 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:51:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E6=97=A0=E7=BA=BF=E6=94=B6=E5=8F=91?= =?UTF-8?q?=E5=99=A8=E7=BB=91=E5=AE=9Aftbteams?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../ae/items/ChannelCardItem.java | 142 ++++++++++- .../ae/wireless/WirelessMasterLink.java | 17 +- .../ae/wireless/WirelessMasterRegistry.java | 45 ++-- .../ae/wireless/WirelessSlaveLink.java | 14 +- .../event/ChannelCardClientHandler.java | 45 ++++ .../wireless/WirelessTransceiverBlock.java | 76 ++++-- .../WirelessTransceiverBlockEntity.java | 38 +++ .../com/extendedae_plus/init/ModNetwork.java | 6 + .../jade/WirelessTransceiverProvider.java | 3 +- .../PatternProviderLogicCompatMixin.java | 17 ++ .../InterfaceLogicChannelCardMixin.java | 4 + .../automation/IOBusPartChannelCardMixin.java | 4 + .../StorageBusPartChannelCardMixin.java | 4 + .../network/ChannelCardBindPacket.java | 77 ++++++ .../util/WirelessTeamUtil.java | 221 ++++++++++++++++++ .../assets/extendedae_plus/lang/en_us.json | 5 + .../assets/extendedae_plus/lang/zh_cn.json | 5 + 18 files changed, 687 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java create mode 100644 src/main/java/com/extendedae_plus/network/ChannelCardBindPacket.java create mode 100644 src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java diff --git a/build.gradle b/build.gradle index 9fec603..df22770 100644 --- a/build.gradle +++ b/build.gradle @@ -123,6 +123,8 @@ dependencies { modImplementation "curse.maven:configuration-444699:4710266" //ftbteams + modCompileOnly "curse.maven:ftb-teams-forge-404468:6130786" + modCompileOnly "curse.maven:ftb-library-forge-404465:6807424" modRuntimeOnly "curse.maven:ftb-teams-forge-404468:6130786" modRuntimeOnly "curse.maven:ftb-library-forge-404465:6807424" } diff --git a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java index 1d68551..ef186cd 100644 --- a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java @@ -1,8 +1,10 @@ package com.extendedae_plus.ae.items; import appeng.items.materials.UpgradeCardItem; +import com.extendedae_plus.util.WirelessTeamUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; @@ -13,13 +15,19 @@ import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.UUID; /** - * 频道卡(MVP):仅存储一个 long 类型的频道号到 NBT:"channel"。 + * 频道卡:存储频道号、所有者UUID和团队信息 + * - 右键空气:增加频道号 + * - 潜行左键空气:写入/清除玩家UUID和团队信息 + * - 潜行左键收发器:将频道卡的所有者信息写入收发器 * 继承 AE2 的 UpgradeCardItem 以复用升级卡判定与提示框架。 */ public class ChannelCardItem extends UpgradeCardItem { public static final String TAG_CHANNEL = "channel"; + public static final String TAG_OWNER_UUID = "ownerUUID"; + public static final String TAG_TEAM_NAME = "teamName"; // 用于显示 public ChannelCardItem(Item.Properties properties) { super(properties); @@ -35,29 +43,157 @@ public class ChannelCardItem extends UpgradeCardItem { return tag != null && tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L; } + /** + * 设置频道卡的所有者UUID + */ + public static void setOwnerUUID(ItemStack stack, UUID ownerUUID) { + CompoundTag tag = stack.getOrCreateTag(); + tag.putUUID(TAG_OWNER_UUID, ownerUUID); + } + + /** + * 获取频道卡的所有者UUID + */ + @Nullable + public static UUID getOwnerUUID(ItemStack stack) { + CompoundTag tag = stack.getTag(); + return tag != null && tag.hasUUID(TAG_OWNER_UUID) ? tag.getUUID(TAG_OWNER_UUID) : null; + } + + /** + * 设置团队名称(用于显示) + */ + public static void setTeamName(ItemStack stack, String teamName) { + CompoundTag tag = stack.getOrCreateTag(); + tag.putString(TAG_TEAM_NAME, teamName); + } + + /** + * 获取团队名称 + */ + @Nullable + public static String getTeamName(ItemStack stack) { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains(TAG_TEAM_NAME) ? tag.getString(TAG_TEAM_NAME) : null; + } + + /** + * 清除所有者信息 + */ + public static void clearOwner(ItemStack stack) { + CompoundTag tag = stack.getTag(); + if (tag != null) { + tag.remove(TAG_OWNER_UUID); + tag.remove(TAG_TEAM_NAME); + } + } + @Override public void appendHoverText(ItemStack stack, @Nullable Level level, List lines, TooltipFlag flag) { super.appendHoverText(stack, level, lines, flag); + + // 显示频道 long ch = getChannel(stack); if (ch == 0L) { lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset")); } else { lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch)); } + + // 显示所有者信息 + UUID ownerUUID = getOwnerUUID(stack); + String teamName = getTeamName(stack); + + if (ownerUUID != null) { + if (teamName != null && !teamName.isEmpty()) { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.team", teamName)); + } else { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.player", ownerUUID.toString().substring(0, 8))); + } + } else { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.unset")); + } } @Override public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); + if (!level.isClientSide) { long ch = getChannel(stack); - boolean dec = player.isShiftKeyDown(); - long next = dec ? Math.max(0L, ch - 1L) : ch + 1L; + long next; + + if (player.isShiftKeyDown()) { + // 潜行右键:减少频率 + next = Math.max(0L, ch - 1L); + } else { + // 普通右键:增加频率 + next = ch + 1L; + } + if (next != ch) { setChannel(stack, next); player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true); } } + return InteractionResultHolder.sidedSuccess(stack, level.isClientSide); } + + @Override + public boolean onLeftClickEntity(ItemStack stack, Player player, net.minecraft.world.entity.Entity entity) { + // 左键实体时不做任何事,避免伤害实体 + if (player.isShiftKeyDown()) { + return true; // 取消默认行为 + } + return super.onLeftClickEntity(stack, player, entity); + } + + @Override + public boolean onBlockStartBreak(ItemStack stack, net.minecraft.core.BlockPos pos, Player player) { + // 潜行左键方块时触发绑定/解绑 + if (!player.isShiftKeyDown()) { + return false; // 不拦截 + } + + if (player.level().isClientSide) { + return true; // 客户端拦截,防止破坏方块 + } + + // 服务端处理 + if (player.level() instanceof ServerLevel serverLevel) { + // 检查是否是收发器(让Block类处理) + var blockState = player.level().getBlockState(pos); + if (blockState.getBlock() instanceof com.extendedae_plus.content.wireless.WirelessTransceiverBlock) { + return false; // 不拦截,让Block类处理 + } + + // 执行绑定/解绑 + UUID currentOwner = getOwnerUUID(stack); + + if (currentOwner != null) { + // 已有所有者,清除 + clearOwner(stack); + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.cleared"), + true + ); + } else { + // 写入当前玩家的UUID和团队信息 + UUID playerUUID = player.getUUID(); + setOwnerUUID(stack, playerUUID); + + // 获取团队名称用于显示 + Component teamName = WirelessTeamUtil.getNetworkOwnerName(serverLevel, playerUUID); + setTeamName(stack, teamName.getString()); + + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.bound", teamName), + true + ); + } + } + + return true; // 拦截,防止破坏方块 + } } diff --git a/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java index 6d40cc2..98a3436 100644 --- a/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java @@ -1,6 +1,9 @@ package com.extendedae_plus.ae.wireless; import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; /** * 主收发器端逻辑:负责在频率变化/加载时向注册中心登记唯一主端,卸载时反注册。 @@ -10,10 +13,16 @@ public class WirelessMasterLink { private final IWirelessEndpoint host; private long frequency; // 0 为未设置 private boolean registered; + @Nullable + private UUID placerId; // 放置者UUID public WirelessMasterLink(IWirelessEndpoint host) { this.host = host; } + + public void setPlacerId(@Nullable UUID placerId) { + this.placerId = placerId; + } public long getFrequency() { return frequency; } @@ -42,16 +51,16 @@ public class WirelessMasterLink { public boolean register() { ServerLevel level = host.getServerLevel(); - if (level == null || frequency == 0L) return false; - boolean ok = WirelessMasterRegistry.register(level, frequency, host); + if (level == null || frequency == 0L || placerId == null) return false; + boolean ok = WirelessMasterRegistry.register(level, frequency, placerId, host); this.registered = ok; return ok; } public void unregister() { ServerLevel level = host.getServerLevel(); - if (!registered || level == null || frequency == 0L) return; - WirelessMasterRegistry.unregister(level, frequency, host); + if (!registered || level == null || frequency == 0L || placerId == null) return; + WirelessMasterRegistry.unregister(level, frequency, placerId, host); registered = false; } diff --git a/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java index 6d411a8..110d2e1 100644 --- a/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java @@ -1,43 +1,54 @@ package com.extendedae_plus.ae.wireless; import com.extendedae_plus.config.ModConfig; +import com.extendedae_plus.util.WirelessTeamUtil; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.UUID; /** - * 无线主端注册中心:按 维度 + 频率 唯一注册一个主收发器端点。 + * 无线主端注册中心:按 维度 + 频率 + 所有者 唯一注册一个主收发器端点。 * 从端通过本注册中心按频率查找主端,实现一对多连接。 + * 所有者隔离:有FTBTeams时同队共享,没有时每个玩家独立。 */ public final class WirelessMasterRegistry { private WirelessMasterRegistry() {} private static final Map> MASTERS = new HashMap<>(); - public static synchronized boolean register(ServerLevel level, long frequency, IWirelessEndpoint endpoint) { + public static synchronized boolean register(ServerLevel level, long frequency, @Nullable UUID placerId, IWirelessEndpoint endpoint) { Objects.requireNonNull(level, "level"); Objects.requireNonNull(endpoint, "endpoint"); - if (frequency == 0L) return false; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + if (frequency == 0L || placerId == null) return false; + + // 获取网络所有者UUID(FTBTeams队伍UUID或玩家UUID) + UUID ownerUUID = WirelessTeamUtil.getNetworkOwnerUUID(level, placerId); + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + cleanupIfCleared(key); var existing = MASTERS.get(key); var existingVal = existing == null ? null : existing.get(); if (existingVal != null && !existingVal.isEndpointRemoved()) { - // 同维度同频率已经有主端 + // 同维度同频率同所有者已经有主端 return false; } MASTERS.put(key, new WeakReference<>(endpoint)); return true; } - public static synchronized void unregister(ServerLevel level, long frequency, IWirelessEndpoint endpoint) { - if (frequency == 0L || level == null) return; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + public static synchronized void unregister(ServerLevel level, long frequency, @Nullable UUID placerId, IWirelessEndpoint endpoint) { + if (frequency == 0L || level == null || placerId == null) return; + + UUID ownerUUID = WirelessTeamUtil.getNetworkOwnerUUID(level, placerId); + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + var ref = MASTERS.get(key); if (ref != null) { var cur = ref.get(); @@ -47,9 +58,12 @@ public final class WirelessMasterRegistry { } } - public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency) { - if (frequency == 0L || level == null) return null; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency, @Nullable UUID placerId) { + if (frequency == 0L || level == null || placerId == null) return null; + + UUID ownerUUID = WirelessTeamUtil.getNetworkOwnerUUID(level, placerId); + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + cleanupIfCleared(key); var ref = MASTERS.get(key); return ref == null ? null : ref.get(); @@ -66,9 +80,12 @@ public final class WirelessMasterRegistry { return ModConfig.INSTANCE.wirelessCrossDimEnable; } - private record Key(ResourceKey dim, long freq) { - @Override public String toString() { - return (dim == null ? "*" : dim.location().toString()) + "#" + freq; + private record Key(@Nullable ResourceKey dim, long freq, UUID owner) { + @Override + public String toString() { + return (dim == null ? "*" : dim.location().toString()) + + "#" + freq + + "@" + owner; } } } diff --git a/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java index 8196349..da1123c 100644 --- a/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java @@ -5,18 +5,22 @@ import appeng.api.networking.IGridNode; import appeng.me.service.helpers.ConnectionWrapper; import com.extendedae_plus.config.ModConfig; import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; import java.util.Objects; +import java.util.UUID; /** * 从收发器连接器: * - 通过频率查找同维度主收发器; * - 校验距离(<= ModConfigs.WIRELESS_MAX_RANGE); - * - 动态创建/销毁 AE2 连接(GridConnection),实现“一主多从”。 + * - 动态创建/销毁 AE2 连接(GridConnection),实现"一主多从"。 */ public class WirelessSlaveLink { private final IWirelessEndpoint host; private long frequency; // 0 未设置 + @Nullable + private UUID placerId; // 放置者UUID private ConnectionWrapper connection = new ConnectionWrapper(null); private boolean shutdown = true; @@ -25,6 +29,10 @@ public class WirelessSlaveLink { public WirelessSlaveLink(IWirelessEndpoint host) { this.host = Objects.requireNonNull(host); } + + public void setPlacerId(@Nullable UUID placerId) { + this.placerId = placerId; + } public void setFrequency(long frequency) { if (this.frequency != frequency) { @@ -55,12 +63,12 @@ public class WirelessSlaveLink { return; } final ServerLevel level = host.getServerLevel(); - if (level == null || frequency == 0L) { + if (level == null || frequency == 0L || placerId == null) { destroyConnection(); return; } - IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency); + IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency, placerId); shutdown = false; distance = 0.0D; diff --git a/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java b/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java new file mode 100644 index 0000000..a952edd --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java @@ -0,0 +1,45 @@ +package com.extendedae_plus.client.event; + +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.init.ModNetwork; +import com.extendedae_plus.network.ChannelCardBindPacket; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +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 class ChannelCardClientHandler { + + /** + * 左键空气事件(仅客户端) + */ + @SubscribeEvent + public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { + Player player = event.getEntity(); + + // 只处理潜行 + if (!player.isShiftKeyDown()) { + return; + } + + // 检查是否手持频道卡 + ItemStack stack = event.getItemStack(); + if (stack.getItem() != ModItems.CHANNEL_CARD.get()) { + return; + } + + // 发送网络包到服务端 + InteractionHand hand = event.getHand(); + ModNetwork.CHANNEL.sendToServer(new ChannelCardBindPacket(hand)); + } +} + diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java index 0f0c9fc..8d2b47a 100644 --- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java +++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java @@ -1,11 +1,15 @@ package com.extendedae_plus.content.wireless; +import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.init.ModBlockEntities; +import com.extendedae_plus.init.ModItems; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -15,6 +19,9 @@ import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; public class WirelessTransceiverBlock extends Block implements EntityBlock { public WirelessTransceiverBlock(Properties props) { @@ -27,28 +34,71 @@ public class WirelessTransceiverBlock extends Block implements EntityBlock { } @Override - public void attack(BlockState state, Level level, BlockPos pos, Player player) { - // 潜行左键:减频(-1 或 -10) - if (!level.isClientSide && player.isShiftKeyDown()) { + public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + super.setPlacedBy(level, pos, state, placer, stack); + if (!level.isClientSide && placer instanceof Player player) { BlockEntity be = level.getBlockEntity(pos); if (be instanceof WirelessTransceiverBlockEntity te) { - if (te.isLocked()) { - player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true); + te.setPlacerId(player.getUUID()); + } + } + } + + @Override + public void attack(BlockState state, Level level, BlockPos pos, Player player) { + if (!level.isClientSide) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof WirelessTransceiverBlockEntity te) { + ItemStack mainHand = player.getMainHandItem(); + + // 潜行左键频道卡:写入频道卡信息到收发器 + if (player.isShiftKeyDown() && mainHand.getItem() == ModItems.CHANNEL_CARD.get()) { + handleChannelCardBinding(te, mainHand, player); super.attack(state, level, pos, player); return; } - int step = 1; - if (player.getMainHandItem().is(Items.REDSTONE_TORCH)) step = 10; - if (player.getMainHandItem().is(Items.STICK)) step = 10; - long f = te.getFrequency(); - f -= step; - if (f < 0) f = 0; - te.setFrequency(f); - player.displayClientMessage(Component.literal("频道:" + te.getFrequency()), true); + + // 潜行左键(其他物品):减频(-1 或 -10) + if (player.isShiftKeyDown()) { + if (te.isLocked()) { + player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true); + super.attack(state, level, pos, player); + return; + } + int step = 1; + if (mainHand.is(Items.REDSTONE_TORCH)) step = 10; + if (mainHand.is(Items.STICK)) step = 10; + long f = te.getFrequency(); + f -= step; + if (f < 0) f = 0; + te.setFrequency(f); + player.displayClientMessage(Component.literal("频道:" + te.getFrequency()), true); + } } } super.attack(state, level, pos, player); } + + /** + * 处理频道卡绑定到收发器 + */ + private void handleChannelCardBinding(WirelessTransceiverBlockEntity te, ItemStack channelCard, Player player) { + UUID cardOwner = ChannelCardItem.getOwnerUUID(channelCard); + + if (cardOwner != null) { + // 写入频道卡的所有者到收发器 + te.setPlacerId(cardOwner); + String teamName = ChannelCardItem.getTeamName(channelCard); + player.displayClientMessage( + Component.literal("已将收发器绑定至:" + (teamName != null ? teamName : cardOwner.toString().substring(0, 8))), + true + ); + } else { + // 频道卡未绑定所有者,使用当前玩家 + te.setPlacerId(player.getUUID()); + player.displayClientMessage(Component.literal("频道卡未绑定,已使用当前玩家"), true); + } + } @Override public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java index 6884308..1c6bd07 100644 --- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java +++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import java.util.EnumSet; import java.util.Objects; +import java.util.UUID; /** * 无线收发器方块实体(骨架): @@ -25,6 +26,7 @@ import java.util.Objects; * - 频率设置; * - 集成 AE2 节点; * - 集成无线主/从逻辑。 + * - 支持FTBTeams队伍隔离(软依赖) */ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost { @@ -33,6 +35,9 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements private long frequency = 1L; private boolean masterMode = false; private boolean locked = false; + + @Nullable + private UUID placerId; // 放置者UUID,用于队伍隔离 private WirelessMasterLink masterLink; private WirelessSlaveLink slaveLink; @@ -96,6 +101,30 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements } /* ===================== 公共方法(交互调用) ===================== */ + + /** + * 设置放置者UUID(在方块放置时调用) + */ + public void setPlacerId(@Nullable UUID placerId) { + if (this.placerId != null && !this.placerId.equals(placerId)) { + // 如果所有者改变,需要重新注册 + if (this.masterMode) { + masterLink.onUnloadOrRemove(); + } else { + slaveLink.onUnloadOrRemove(); + } + } + this.placerId = placerId; + this.masterLink.setPlacerId(placerId); + this.slaveLink.setPlacerId(placerId); + setChanged(); + } + + @Nullable + public UUID getPlacerId() { + return placerId; + } + public long getFrequency() { return frequency; } @@ -192,6 +221,9 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements tag.putLong("frequency", frequency); tag.putBoolean("master", masterMode); tag.putBoolean("locked", locked); + if (placerId != null) { + tag.putUUID("placerId", placerId); + } if (managedNode != null) { managedNode.saveToNBT(tag); } @@ -203,6 +235,12 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements this.frequency = tag.getLong("frequency"); this.masterMode = tag.getBoolean("master"); this.locked = tag.getBoolean("locked"); + + if (tag.hasUUID("placerId")) { + this.placerId = tag.getUUID("placerId"); + this.masterLink.setPlacerId(this.placerId); + this.slaveLink.setPlacerId(this.placerId); + } if (managedNode != null) { managedNode.loadFromNBT(tag); diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 666991a..2ffe168 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -125,6 +125,12 @@ public class ModNetwork { .decoder(CraftingMonitorOpenProviderC2SPacket::decode) .consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle) .add(); + + CHANNEL.messageBuilder(ChannelCardBindPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(ChannelCardBindPacket::encode) + .decoder(ChannelCardBindPacket::decode) + .consumerNetworkThread(ChannelCardBindPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java index 1578639..83213fe 100644 --- a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java +++ b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java @@ -66,7 +66,8 @@ public enum WirelessTransceiverProvider implements IServerDataProvider ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player == null) { + return; + } + + ItemStack stack = player.getItemInHand(packet.hand); + if (stack.getItem() != ModItems.CHANNEL_CARD.get()) { + return; + } + + ServerLevel level = player.serverLevel(); + UUID currentOwner = ChannelCardItem.getOwnerUUID(stack); + + if (currentOwner != null) { + // 已有所有者,清除 + ChannelCardItem.clearOwner(stack); + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.cleared"), + true + ); + } else { + // 写入当前玩家的UUID和团队信息 + UUID playerUUID = player.getUUID(); + ChannelCardItem.setOwnerUUID(stack, playerUUID); + + // 获取团队名称用于显示 + Component teamName = WirelessTeamUtil.getNetworkOwnerName(level, playerUUID); + ChannelCardItem.setTeamName(stack, teamName.getString()); + + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.bound", teamName), + true + ); + } + }); + ctx.get().setPacketHandled(true); + } +} + diff --git a/src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java b/src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java new file mode 100644 index 0000000..51401ef --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java @@ -0,0 +1,221 @@ +package com.extendedae_plus.util; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.fml.ModList; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * 无线收发器队伍工具类 + * 实现FTBTeams软依赖:有FTBTeams时使用队伍UUID,没有时使用玩家UUID + */ +public class WirelessTeamUtil { + + private static final boolean FTB_TEAMS_LOADED = ModList.get().isLoaded("ftbteams"); + + /** + * 获取用于无线网络隔离的UUID + * - 如果安装了FTBTeams且玩家在队伍中,返回队伍UUID(同队玩家共享) + * - 否则返回玩家自己的UUID(独立网络) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 网络所有者UUID + */ + public static UUID getNetworkOwnerUUID(@Nullable ServerLevel level, UUID playerUUID) { + if (playerUUID == null) { + return null; + } + + if (!FTB_TEAMS_LOADED) { + return playerUUID; + } + + if (level == null) { + return playerUUID; + } + + try { + return getTeamUUID(level, playerUUID); + } catch (Exception e) { + // 如果FTBTeams API调用失败,回退到玩家UUID + return playerUUID; + } + } + + /** + * 获取网络所有者的显示名称(用于UI显示) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 显示名称 + */ + public static Component getNetworkOwnerName(@Nullable ServerLevel level, UUID playerUUID) { + if (FTB_TEAMS_LOADED && level != null) { + try { + return getTeamName(level, playerUUID); + } catch (Exception ignored) { + } + } + + // 尝试获取玩家名称 + if (level != null) { + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + } + + return Component.literal(playerUUID.toString()); + } + + /** + * 检查网络所有者是否有效(玩家在线或队伍存在) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 是否有效 + */ + public static boolean hasNetworkOwner(@Nullable ServerLevel level, UUID playerUUID) { + if (FTB_TEAMS_LOADED && level != null) { + try { + return hasTeamOwner(level, playerUUID); + } catch (Exception ignored) { + } + } + + // 检查玩家是否在线 + if (level != null) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + return false; + } + + // ==================== FTBTeams 集成(通过反射调用避免硬依赖)==================== + + private static UUID getTeamUUID(ServerLevel level, UUID playerUUID) { + try { + // 使用FTBTeams API + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); // 静态方法,返回API实例 + + // 检查Manager是否已加载(在api实例上调用) + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + + if (!isLoaded) { + return playerUUID; + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return playerUUID; + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + var isPresent = (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + + if (isPresent) { + var team = optionalClass.getMethod("get").invoke(teamOptional); + var teamClass = team.getClass(); + return (UUID) teamClass.getMethod("getTeamId").invoke(team); + } + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + return playerUUID; + } + + private static Component getTeamName(ServerLevel level, UUID playerUUID) { + try { + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); + + // 检查Manager是否已加载 + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + if (!isLoaded) { + // Manager未加载,回退到玩家名称 + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + return Component.literal(playerUUID.toString()); + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return Component.literal(playerUUID.toString()); + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + var isPresent = (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + + if (isPresent) { + var team = optionalClass.getMethod("get").invoke(teamOptional); + var teamClass = team.getClass(); + return (Component) teamClass.getMethod("getName").invoke(team); + } + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + // 回退到玩家名称 + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + + return Component.literal(playerUUID.toString()); + } + + private static boolean hasTeamOwner(ServerLevel level, UUID playerUUID) { + try { + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); + + // 检查Manager是否已加载 + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + if (!isLoaded) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + return (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } +} + diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 392adcb..2fd413c 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -103,6 +103,11 @@ "item.extendedae_plus.channel_card.channel": "Frequency: %s", "item.extendedae_plus.channel_card.channel.unset": "Frequency: Unset", "item.extendedae_plus.channel_card.set": "Frequency set to: %s", + "item.extendedae_plus.channel_card.owner.unset": "Owner: Unbound", + "item.extendedae_plus.channel_card.owner.team": "Owner: %s", + "item.extendedae_plus.channel_card.owner.player": "Owner: Player %s", + "item.extendedae_plus.channel_card.owner.bound": "Bound to: %s", + "item.extendedae_plus.channel_card.owner.cleared": "Owner binding cleared", "group.pattern_provider.name": "Pattern Provider", "group.entity_ticker.name": "Entity Accelerator", "group.storage.name": "StorageBus" diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 5860c5d..fed6481 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -104,6 +104,11 @@ "item.extendedae_plus.channel_card.channel": "频率:%s", "item.extendedae_plus.channel_card.channel.unset": "频率:未设置", "item.extendedae_plus.channel_card.set": "已设置频率:%s", + "item.extendedae_plus.channel_card.owner.unset": "所有者:未绑定", + "item.extendedae_plus.channel_card.owner.team": "所有者:%s", + "item.extendedae_plus.channel_card.owner.player": "所有者:玩家 %s", + "item.extendedae_plus.channel_card.owner.bound": "已绑定至:%s", + "item.extendedae_plus.channel_card.owner.cleared": "已清除所有者绑定", "group.pattern_provider.name": "样板供应器", "group.entity_ticker.name": "实体加速器", "group.storage.name": "存储总线"