为无线收发器绑定ftbteams

This commit is contained in:
GaLicn 2025-10-05 11:51:04 +08:00
parent 60c5dc7cf7
commit b31ec61b54
18 changed files with 687 additions and 38 deletions

View File

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

View File

@ -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<Component> 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<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);
}
@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; // 拦截防止破坏方块
}
}

View File

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

View File

@ -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<Key, WeakReference<IWirelessEndpoint>> 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;
// 获取网络所有者UUIDFTBTeams队伍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<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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,7 +66,8 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
if (!blockEntity.isMasterMode()) {
var level = blockEntity.getServerLevel();
long freq = blockEntity.getFrequency();
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
var placerId = blockEntity.getPlacerId(); // 获取放置者UUID
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq, placerId);
if (master != null && !master.isEndpointRemoved()) {
if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) {
data.putString("customName", masterBlockEntity.getCustomName().getString());

View File

@ -253,6 +253,11 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
for (ItemStack stack : upgrades) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack);
java.util.UUID ownerUUID = ChannelCardItem.getOwnerUUID(stack);
if (ownerUUID != null) {
// 保存ownerUUID到局部变量后面设置到link
channel |= ((long) ownerUUID.hashCode() << 32); // 临时存储
}
found = true;
break;
}
@ -274,6 +279,18 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
eap$compatLink = new WirelessSlaveLink(endpoint);
}
// 从频道卡重新读取ownerUUID并设置
java.util.UUID cardOwner = null;
if (upgrades != null) {
for (ItemStack stack : upgrades) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
cardOwner = ChannelCardItem.getOwnerUUID(stack);
channel = ChannelCardItem.getChannel(stack); // 重新读取正确的频率
break;
}
}
}
eap$compatLink.setPlacerId(cardOwner);
eap$compatLink.setFrequency(channel);
eap$compatLink.updateStatus();

View File

@ -106,10 +106,12 @@ public abstract class InterfaceLogicChannelCardMixin implements IInterfaceWirele
try {
long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false;
for (ItemStack stack : getUpgrades()) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true;
break;
}
@ -130,6 +132,8 @@ public abstract class InterfaceLogicChannelCardMixin implements IInterfaceWirele
eap$link = new WirelessSlaveLink(endpoint);
}
// 设置频道卡的所有者UUID如果有的话
eap$link.setPlacerId(ownerUUID);
eap$link.setFrequency(channel);
eap$link.updateStatus();

View File

@ -71,10 +71,12 @@ public abstract class IOBusPartChannelCardMixin implements IInterfaceWirelessLin
try {
IUpgradeInventory inv = this.getUpgrades();
long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false;
for (var stack : inv) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true;
break;
}
@ -109,6 +111,8 @@ public abstract class IOBusPartChannelCardMixin implements IInterfaceWirelessLin
Logger.EAP$LOGGER.debug("[服务端] IOBus 创建新的无线链接");
}
// 设置频道卡的所有者UUID如果有的话
eap$link.setPlacerId(ownerUUID);
eap$link.setFrequency(channel);
eap$link.updateStatus();
Logger.EAP$LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected());

View File

@ -69,10 +69,12 @@ public abstract class StorageBusPartChannelCardMixin implements IInterfaceWirele
try {
IUpgradeInventory inv = this.getUpgrades();
long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false;
for (var stack : inv) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true;
break;
}
@ -106,6 +108,8 @@ public abstract class StorageBusPartChannelCardMixin implements IInterfaceWirele
Logger.EAP$LOGGER.debug("[服务端] StorageBus 创建新的无线链接");
}
// 设置频道卡的所有者UUID如果有的话
eap$link.setPlacerId(ownerUUID);
eap$link.setFrequency(channel);
eap$link.updateStatus();
Logger.EAP$LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected());

View File

@ -0,0 +1,77 @@
package com.extendedae_plus.network;
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.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
import java.util.UUID;
import java.util.function.Supplier;
/**
* 频道卡绑定网络包
* 客户端发送到服务端用于处理左键空气的绑定/解绑操作
*/
public class ChannelCardBindPacket {
private final InteractionHand hand;
public ChannelCardBindPacket(InteractionHand hand) {
this.hand = hand;
}
public static void encode(ChannelCardBindPacket packet, FriendlyByteBuf buf) {
buf.writeEnum(packet.hand);
}
public static ChannelCardBindPacket decode(FriendlyByteBuf buf) {
return new ChannelCardBindPacket(buf.readEnum(InteractionHand.class));
}
public static void handle(ChannelCardBindPacket packet, Supplier<NetworkEvent.Context> 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);
}
}

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

View File

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

View File

@ -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": "存储总线"