无线收发器绑定ftbteams

This commit is contained in:
GaLicn 2025-10-05 13:31:26 +08:00
parent 1449df8f8b
commit 2732a9aee1
15 changed files with 761 additions and 35 deletions

View File

@ -171,6 +171,11 @@ dependencies {
//geckolib
runtimeOnly "curse.maven:geckolib-388172:7009924"
//ftbteams
runtimeOnly "curse.maven:ftb-teams-forge-404468:6930910"
runtimeOnly "curse.maven:ftb-library-forge-404465:7029003"
runtimeOnly "curse.maven:architectury-api-419699:5786327"
runtimeOnly fileTree(dir: 'libs', includes: ['*.jar'])

View File

@ -1,9 +1,11 @@
package com.extendedae_plus.ae.items;
import appeng.items.materials.UpgradeCardItem;
import com.extendedae_plus.util.WirelessTeamUtil;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -15,13 +17,20 @@ 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);
@ -38,29 +47,110 @@ public class ChannelCardItem extends UpgradeCardItem {
return tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L;
}
/**
* 设置频道卡的所有者UUID
*/
public static void setOwnerUUID(ItemStack stack, UUID ownerUUID) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
tag.putUUID(TAG_OWNER_UUID, ownerUUID);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
}
/**
* 获取频道卡的所有者UUID
*/
@Nullable
public static UUID getOwnerUUID(ItemStack stack) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
return tag.hasUUID(TAG_OWNER_UUID) ? tag.getUUID(TAG_OWNER_UUID) : null;
}
/**
* 设置团队名称用于显示
*/
public static void setTeamName(ItemStack stack, String teamName) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
tag.putString(TAG_TEAM_NAME, teamName);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
}
/**
* 获取团队名称
*/
@Nullable
public static String getTeamName(ItemStack stack) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
return tag.contains(TAG_TEAM_NAME) ? tag.getString(TAG_TEAM_NAME) : null;
}
/**
* 清除所有者信息
*/
public static void clearOwner(ItemStack stack) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
tag.remove(TAG_OWNER_UUID);
tag.remove(TAG_TEAM_NAME);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag flag) {
super.appendHoverText(stack, context, 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<ItemStack> 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);
}
}

View File

@ -0,0 +1,55 @@
package com.extendedae_plus.client;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.network.ChannelCardBindPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.network.PacketDistributor;
/**
* 频道卡客户端事件处理器
* 处理左键空气事件并发送网络包到服务端
*/
@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT)
public class ChannelCardClientHandler {
/**
* 左键空气事件仅客户端
*/
@SubscribeEvent
public static void onLeftClickEmpty(InputEvent.InteractionKeyMappingTriggered event) {
// 只处理左键空气attack模式
if (!event.isAttack()) {
return;
}
// 获取客户端玩家
Player player = Minecraft.getInstance().player;
if (player == null) {
return;
}
// 只处理潜行
if (!player.isShiftKeyDown()) {
return;
}
// 检查是否手持频道卡
InteractionHand hand = event.getHand();
ItemStack stack = player.getItemInHand(hand);
if (stack.getItem() != ModItems.CHANNEL_CARD.get()) {
return;
}
// 发送网络包到服务端
PacketDistributor.sendToServer(new ChannelCardBindPacket(hand));
}
}

View File

@ -1,12 +1,16 @@
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.ItemInteractionResult;
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;
@ -16,6 +20,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) {
@ -28,28 +35,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(), player.getName().getString());
}
}
}
@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) {
// 写入频道卡的所有者到收发器
String teamName = ChannelCardItem.getTeamName(channelCard);
te.setPlacerId(cardOwner, teamName);
player.displayClientMessage(
Component.literal("已将收发器绑定至:" + (teamName != null ? teamName : cardOwner.toString().substring(0, 8))),
true
);
} else {
// 频道卡未绑定所有者使用当前玩家
te.setPlacerId(player.getUUID(), player.getName().getString());
player.displayClientMessage(Component.literal("频道卡未绑定,已使用当前玩家"), true);
}
}
// 1.21+: 拆分为 useItemOn useWithoutItem
@Override

View File

@ -19,6 +19,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.Objects;
import java.util.UUID;
/**
* 无线收发器方块实体骨架
@ -34,6 +35,11 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements
private long frequency = 1L;
private boolean masterMode = false;
private boolean locked = false;
@Nullable
private UUID placerId; // 放置者UUID用于队伍隔离
@Nullable
private String placerName; // 放置者名称用于显示
private WirelessMasterLink masterLink;
private WirelessSlaveLink slaveLink;
@ -97,6 +103,43 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements
}
/* ===================== 公共方法(交互调用) ===================== */
/**
* 设置放置者UUID和名称在方块放置时调用
*/
public void setPlacerId(@Nullable UUID placerId, @Nullable String placerName) {
if (this.placerId != null && !this.placerId.equals(placerId)) {
// 如果所有者改变需要重新注册
if (this.masterMode) {
masterLink.onUnloadOrRemove();
} else {
slaveLink.onUnloadOrRemove();
}
}
this.placerId = placerId;
this.placerName = placerName;
this.masterLink.setPlacerId(placerId);
this.slaveLink.setPlacerId(placerId);
setChanged();
}
/**
* 仅设置UUID兼容旧代码
*/
public void setPlacerId(@Nullable UUID placerId) {
setPlacerId(placerId, null);
}
@Nullable
public UUID getPlacerId() {
return placerId;
}
@Nullable
public String getPlacerName() {
return placerName;
}
public long getFrequency() {
return frequency;
}
@ -193,6 +236,12 @@ 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 (placerName != null) {
tag.putString("placerName", placerName);
}
if (managedNode != null) {
managedNode.saveToNBT(tag);
}
@ -204,6 +253,17 @@ 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 (tag.contains("placerName")) {
this.placerName = tag.getString("placerName");
}
if (managedNode != null) {
managedNode.loadFromNBT(tag);
}

View File

@ -34,5 +34,9 @@ public class ModNetwork {
registrar.playToServer(com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket.TYPE,
com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket.STREAM_CODEC,
com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket::handle);
// 频道卡绑定
registrar.playToServer(com.extendedae_plus.network.ChannelCardBindPacket.TYPE,
com.extendedae_plus.network.ChannelCardBindPacket.STREAM_CODEC,
com.extendedae_plus.network.ChannelCardBindPacket::handle);
}
}

View File

@ -66,6 +66,39 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr
tooltip.add(Component.literal((usable ? "设备在线" : "设备离线")));
}
}
},
OWNER("wt_owner") {
@Override
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
if (data.contains("ownerName")) {
String ownerName = data.getString("ownerName");
tooltip.add(Component.translatable("extendedae_plus.tooltip.owner", ownerName));
} else if (data.hasUUID("placerId")) {
// 有placerId但没有名称显示UUID
java.util.UUID placerId = data.getUUID("placerId");
tooltip.add(Component.translatable("extendedae_plus.tooltip.owner", placerId.toString().substring(0, 8) + "..."));
} else {
// 没有所有者信息公共收发器
tooltip.add(Component.translatable("extendedae_plus.tooltip.owner.public"));
}
}
},
CHANNELS("wt_channels") {
@Override
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
if (data.contains("usedChannels") && data.contains("maxChannels")) {
int usedChannels = data.getInt("usedChannels");
int maxChannels = data.getInt("maxChannels");
// 参考AE2的显示方式
if (maxChannels <= 0) {
// 无限频道或未设置
tooltip.add(Component.translatable("extendedae_plus.tooltip.channels", usedChannels));
} else {
// 显示 "已使用/最大"
tooltip.add(Component.translatable("extendedae_plus.tooltip.channels_of", usedChannels, maxChannels));
}
}
}
};
private final ResourceLocation uid;

View File

@ -7,6 +7,7 @@ import com.extendedae_plus.wireless.IWirelessEndpoint;
import com.extendedae_plus.wireless.WirelessMasterRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import snownee.jade.api.BlockAccessor;
import snownee.jade.api.IServerDataProvider;
@ -28,6 +29,19 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
data.putLong("frequency", blockEntity.getFrequency());
data.putBoolean("masterMode", blockEntity.isMasterMode());
data.putBoolean("locked", blockEntity.isLocked());
// 添加所有者信息有FTBTeams时显示团队否则显示玩家
var placerId = blockEntity.getPlacerId();
if (placerId != null) {
data.putUUID("placerId", placerId);
var level = blockEntity.getServerLevel();
if (level != null) {
// 使用WirelessTeamUtil自动判断显示团队或玩家名称
Component ownerName = com.extendedae_plus.util.WirelessTeamUtil.getNetworkOwnerName(level, placerId);
data.putString("ownerName", ownerName.getString());
}
}
// 判断 AE 网络是否可用节点存在加入网路且网络通电
IGridNode node = blockEntity.getGridNode();
IGrid grid = node == null ? null : node.getGrid();
@ -40,11 +54,34 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
}
}
data.putBoolean("networkUsable", networkUsable);
// 添加频道使用信息参考AE2的 IUsedChannelProvider 实现
int usedChannels = 0;
int maxChannels = 0;
if (node != null && node.isActive()) {
// 遍历该节点的所有连接取使用频道数的最大值
for (var connection : node.getConnections()) {
usedChannels = Math.max(connection.getUsedChannels(), usedChannels);
}
// 获取节点的最大频道容量致密线缆为32
if (node instanceof appeng.me.GridNode gridNode) {
var channelMode = gridNode.getGrid().getPathingService().getChannelMode();
if (channelMode == appeng.api.networking.pathing.ChannelMode.INFINITE) {
maxChannels = -1; // 无限频道
} else {
maxChannels = gridNode.getMaxChannels();
}
}
}
data.putInt("usedChannels", usedChannels);
data.putInt("maxChannels", maxChannels);
// 如果是从模式查询主节点位置与维度
if (!blockEntity.isMasterMode()) {
var level = blockEntity.getServerLevel();
long freq = blockEntity.getFrequency();
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
// 使用placerId查找主节点支持队伍隔离
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq, blockEntity.getPlacerId());
if (master != null && !master.isEndpointRemoved()) {
if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) {
data.putString("customName", masterBlockEntity.getCustomName().getString());

View File

@ -0,0 +1,83 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.items.ChannelCardItem;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.util.WirelessTeamUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.UUID;
/**
* 频道卡绑定网络包
* 客户端发送到服务端用于处理左键空气的绑定/解绑操作
*/
public class ChannelCardBindPacket implements CustomPacketPayload {
public static final Type<ChannelCardBindPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "channel_card_bind"));
public static final StreamCodec<FriendlyByteBuf, ChannelCardBindPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> buf.writeEnum(pkt.hand),
buf -> new ChannelCardBindPacket(buf.readEnum(InteractionHand.class))
);
private final InteractionHand hand;
public ChannelCardBindPacket(InteractionHand hand) {
this.hand = hand;
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(final ChannelCardBindPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) {
return;
}
ItemStack stack = player.getItemInHand(msg.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
);
}
});
}
}

View File

@ -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.neoforged.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;
}
}

View File

@ -1,6 +1,9 @@
package com.extendedae_plus.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; }
@ -27,7 +36,7 @@ public class WirelessMasterLink {
}
// 频率未变的情况下也要校正注册状态
// - 当从从端切回主端registered 可能为 false需要重新注册
// - 当从"从端"切回"主端"registered 可能为 false需要重新注册
// - 当频率为 0 或端点被移除时确保处于未注册
if (frequency != 0L && !host.isEndpointRemoved()) {
if (!registered) {
@ -43,7 +52,8 @@ public class WirelessMasterLink {
public boolean register() {
ServerLevel level = host.getServerLevel();
if (level == null || frequency == 0L) return false;
boolean ok = WirelessMasterRegistry.register(level, frequency, host);
// placerId可以为null公共收发器模式
boolean ok = WirelessMasterRegistry.register(level, frequency, placerId, host);
this.registered = ok;
return ok;
}
@ -51,7 +61,8 @@ public class WirelessMasterLink {
public void unregister() {
ServerLevel level = host.getServerLevel();
if (!registered || level == null || frequency == 0L) return;
WirelessMasterRegistry.unregister(level, frequency, host);
// placerId可以为null公共收发器模式
WirelessMasterRegistry.unregister(level, frequency, placerId, host);
registered = false;
}

View File

@ -1,43 +1,68 @@
package com.extendedae_plus.wireless;
import com.extendedae_plus.config.ModConfigs;
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时同队共享没有时每个玩家独立
* 公共模式placerId为null时使用公共UUID所有人都能访问向下兼容旧版本
*/
public final class WirelessMasterRegistry {
private WirelessMasterRegistry() {}
private static final Map<Key, WeakReference<IWirelessEndpoint>> MASTERS = new HashMap<>();
/**
* 公共收发器UUID用于没有设置所有者的收发器
* 所有placerId为null的收发器都使用这个UUID实现公共访问
*/
public static final UUID PUBLIC_NETWORK_UUID = new UUID(0, 0);
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);
// 获取网络所有者UUID
// placerId为null时使用公共UUID向下兼容旧版本收发器
UUID ownerUUID = placerId != null
? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId)
: PUBLIC_NETWORK_UUID;
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) {
public static synchronized void unregister(ServerLevel level, long frequency, @Nullable UUID placerId, IWirelessEndpoint endpoint) {
if (frequency == 0L || level == null) return;
final Key key = new Key(useGlobal() ? null : level.dimension(), frequency);
UUID ownerUUID = placerId != null
? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId)
: PUBLIC_NETWORK_UUID;
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 +72,15 @@ public final class WirelessMasterRegistry {
}
}
public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency) {
public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency, @Nullable UUID placerId) {
if (frequency == 0L || level == null) return null;
final Key key = new Key(useGlobal() ? null : level.dimension(), frequency);
UUID ownerUUID = placerId != null
? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId)
: PUBLIC_NETWORK_UUID;
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 +97,12 @@ public final class WirelessMasterRegistry {
return ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get();
}
private record Key(ResourceKey<Level> dim, long freq) {
@Override public String toString() {
return (dim == null ? "*" : dim.location().toString()) + "#" + freq;
private record Key(@Nullable ResourceKey<Level> dim, long freq, UUID owner) {
@Override
public String toString() {
return (dim == null ? "*" : dim.location().toString())
+ "#" + freq
+ "@" + owner;
}
}
}

View File

@ -7,18 +7,22 @@ import appeng.me.service.helpers.ConnectionWrapper;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.util.ExtendedAELogger;
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;
@ -27,6 +31,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) {
@ -62,7 +70,8 @@ public class WirelessSlaveLink {
return;
}
IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency);
// placerId可以为null公共收发器模式
IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency, placerId);
shutdown = false;
distance = 0.0D;

View File

@ -22,6 +22,11 @@
"item.extendedae_plus.channel_card.channel": "Channel: %d",
"item.extendedae_plus.channel_card.channel.unset": "Channel: Unset",
"item.extendedae_plus.channel_card.set": "Channel set to: %d",
"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: %s",
"item.extendedae_plus.channel_card.owner.bound": "Bound to: %s",
"item.extendedae_plus.channel_card.owner.cleared": "Binding cleared",
"item.extendedae_plus.entity_speed_card": "Entity Speed Card",
"item.extendedae_plus.entity_speed_card.x2": "Entity Speed Card (x2)",
"item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
@ -103,5 +108,17 @@
"extendedae_plus.configuration.prioritizeDiskEnergy": "Prioritize FE energy from disk (requires Applied Flux)",
"extendedae_plus.configuration.state_on": "On",
"extendedae_plus.configuration.state_off": "Off"
"extendedae_plus.configuration.state_off": "Off",
"extendedae_plus.tooltip.owner": "Owner: %s",
"extendedae_plus.tooltip.owner.public": "Owner: Public",
"extendedae_plus.tooltip.channels": "Channels: %d",
"extendedae_plus.tooltip.channels_of": "Channels: %d / %d",
"extendedae_plus.tooltip.frequency": "Frequency: %d",
"extendedae_plus.tooltip.master_mode": "Mode: %s",
"extendedae_plus.tooltip.locked": "Status: %s",
"group.pattern_provider.name": "Pattern Provider",
"group.storage.name": "Storage Bus",
"group.entity_ticker.name": "Entity Ticker"
}

View File

@ -22,6 +22,11 @@
"item.extendedae_plus.channel_card.channel": "频道: %d",
"item.extendedae_plus.channel_card.channel.unset": "频道: 未设置",
"item.extendedae_plus.channel_card.set": "频道已设置为: %d",
"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": "已清除绑定",
"item.extendedae_plus.entity_speed_card": "实体加速卡",
"item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
@ -103,5 +108,17 @@
"extendedae_plus.configuration.prioritizeDiskEnergy": "优先从磁盘提取FE能量仅当Applied Flux模组存在时生效",
"extendedae_plus.configuration.state_on": "开",
"extendedae_plus.configuration.state_off": "关"
"extendedae_plus.configuration.state_off": "关",
"extendedae_plus.tooltip.owner": "所有者: %s",
"extendedae_plus.tooltip.owner.public": "所有者: 公共",
"extendedae_plus.tooltip.channels": "频道: %d",
"extendedae_plus.tooltip.channels_of": "频道: %d / %d",
"extendedae_plus.tooltip.frequency": "频率: %d",
"extendedae_plus.tooltip.master_mode": "模式: %s",
"extendedae_plus.tooltip.locked": "状态: %s",
"group.pattern_provider.name": "样板供应器",
"group.storage.name": "存储总线",
"group.entity_ticker.name": "实体加速器"
}