diff --git a/src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java b/src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java new file mode 100644 index 0000000..aace038 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java @@ -0,0 +1,101 @@ +package com.extendedae_plus.ae.wireless; + +import appeng.api.networking.GridHelper; +import appeng.api.networking.IGridNode; +import appeng.me.service.helpers.ConnectionWrapper; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +/** + * 标签无线收发器的连接器。 + * - 将 BE 的 in-world 节点连接到标签网络的虚拟节点。 + * - 不负责注册/反注册标签,只负责维持物理连接。 + */ +public class LabelLink { + private final IWirelessEndpoint host; + private final ConnectionWrapper connection = new ConnectionWrapper(null); + + @Nullable + private LabelNetworkRegistry.LabelNetwork target; + + public LabelLink(IWirelessEndpoint host) { + this.host = host; + } + + public void setTarget(@Nullable LabelNetworkRegistry.LabelNetwork target) { + this.target = target; + updateStatus(); + } + + public void clearTarget() { + setTarget(null); + } + + public boolean isConnected() { + return connection.getConnection() != null; + } + + /** + * 建议在 serverTick 或标签变化时调用。 + */ + public void updateStatus() { + if (host.isEndpointRemoved()) { + destroyConnection(); + return; + } + if (target == null) { + destroyConnection(); + return; + } + + final ServerLevel hostLevel = host.getServerLevel(); + if (hostLevel == null) { + destroyConnection(); + return; + } + + // 维度校验:未开启跨维且维度不匹配则断开 + ResourceKey targetDim = target.dim(); + if (targetDim != null && targetDim != hostLevel.dimension()) { + destroyConnection(); + return; + } + + IGridNode hostNode = host.getGridNode(); + IGridNode targetNode = target.node(); + if (hostNode == null || targetNode == null) { + destroyConnection(); + return; + } + + try { + var current = connection.getConnection(); + if (current != null) { + var a = current.a(); + var b = current.b(); + if ((a == hostNode || b == hostNode) && (a == targetNode || b == targetNode)) { + return; // 已经正确连接 + } + current.destroy(); + connection.setConnection(null); + } + connection.setConnection(GridHelper.createConnection(hostNode, targetNode)); + } catch (IllegalStateException ignore) { + destroyConnection(); + } + } + + public void onUnloadOrRemove() { + destroyConnection(); + } + + private void destroyConnection() { + var current = connection.getConnection(); + if (current != null) { + current.destroy(); + connection.setConnection(null); + } + } +} diff --git a/src/main/java/com/extendedae_plus/ae/wireless/LabelNetworkRegistry.java b/src/main/java/com/extendedae_plus/ae/wireless/LabelNetworkRegistry.java new file mode 100644 index 0000000..723b6f7 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/wireless/LabelNetworkRegistry.java @@ -0,0 +1,316 @@ +package com.extendedae_plus.ae.wireless; + +import appeng.api.networking.GridFlags; +import appeng.api.networking.GridHelper; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IInWorldGridNodeHost; +import appeng.api.networking.IManagedGridNode; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.config.ModConfig; +import com.extendedae_plus.util.wireless.WirelessTeamUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.core.registries.Registries; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * 标签无线网络注册中心(SavedData)。 + * - 负责标签到频道(频率)的分配与复用; + * - 创建/销毁虚拟节点,所有收发器连接到同一虚拟节点; + * - 记录在线端点集合,端点卸载后需调用 unregister。 + * + * UI 未实现,仅提供服务端逻辑。 + */ +public class LabelNetworkRegistry extends SavedData { + public static final String SAVE_ID = ExtendedAEPlus.MODID + "_label_networks"; + + private static final long CHANNEL_START = 1_000_000L; + + private final Map networks = new HashMap<>(); + private long nextChannel = CHANNEL_START; + + /* 入口 API */ + + public static LabelNetworkRegistry get(MinecraftServer server) { + ServerLevel level = server.getLevel(ServerLevel.OVERWORLD); + return level.getDataStorage().computeIfAbsent(LabelNetworkRegistry::load, LabelNetworkRegistry::new, SAVE_ID); + } + + public static LabelNetworkRegistry get(ServerLevel level) { + return get(level.getServer()); + } + + /** + * 规范化标签:trim + toLowerCase。 + */ + public static String normalizeLabel(String raw) { + if (raw == null) return null; + String t = raw.trim().toLowerCase(Locale.ROOT); + if (t.isEmpty()) return null; + if (t.length() > 64) { + t = t.substring(0, 64); + } + // 过滤非法字符 + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + if (!(Character.isLetterOrDigit(c) || c == '_' || c == '-')) { + return null; + } + } + return t; + } + + /** + * 注册/切换标签。 + */ + public synchronized LabelNetwork register(ServerLevel beLevel, String rawLabel, @Nullable UUID placerId, IWirelessEndpoint endpoint) { + String label = normalizeLabel(rawLabel); + if (label == null) return null; + + UUID owner = placerId == null ? WirelessMasterRegistry.PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(beLevel, placerId); + ResourceKey dimKey = ModConfig.INSTANCE.wirelessCrossDimEnable ? null : beLevel.dimension(); + Key key = new Key(dimKey, label, owner); + + LabelNetwork network = networks.get(key); + if (network == null) { + long channel = allocateChannel(); + network = new LabelNetwork(dimKey, label, owner, channel); + if (!network.ensureVirtualNode(beLevel)) { + return null; + } + networks.put(key, network); + setDirty(); + } else { + // 确保虚拟节点存在 + network.ensureVirtualNode(beLevel); + } + + network.endpoints.add(new EndpointRef(dimKey, endpoint.getBlockPos())); + setDirty(); + return network; + } + + /** + * 注销端点;若网络无端点则清理。 + */ + public synchronized void unregister(IWirelessEndpoint endpoint) { + ServerLevel level = endpoint.getServerLevel(); + if (level == null) return; + ResourceKey dimKey = ModConfig.INSTANCE.wirelessCrossDimEnable ? null : level.dimension(); + BlockPos pos = endpoint.getBlockPos(); + Iterator> it = networks.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + LabelNetwork net = entry.getValue(); + net.endpoints.removeIf(ref -> ref.matches(dimKey, pos)); + if (net.endpoints.isEmpty()) { + net.destroyVirtualNode(); + it.remove(); + setDirty(); + } + } + } + + public synchronized LabelNetwork getNetwork(ServerLevel level, String rawLabel, @Nullable UUID placerId) { + String label = normalizeLabel(rawLabel); + if (label == null) return null; + UUID owner = placerId == null ? WirelessMasterRegistry.PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(level, placerId); + ResourceKey dimKey = ModConfig.INSTANCE.wirelessCrossDimEnable ? null : level.dimension(); + Key key = new Key(dimKey, label, owner); + return networks.get(key); + } + + /* 序列化 */ + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag tag) { + tag.putLong("nextChannel", nextChannel); + ListTag list = new ListTag(); + networks.forEach((k, v) -> { + CompoundTag nbt = new CompoundTag(); + nbt.putString("label", k.label()); + if (k.dim() != null) { + nbt.putString("dim", k.dim().location().toString()); + } + nbt.putUUID("owner", k.owner()); + nbt.putLong("channel", v.channel); + nbt.put("endpoints", v.saveEndpoints()); + list.add(nbt); + }); + tag.put("networks", list); + return tag; + } + + public static LabelNetworkRegistry load(CompoundTag tag) { + LabelNetworkRegistry reg = new LabelNetworkRegistry(); + reg.nextChannel = tag.getLong("nextChannel"); + ListTag list = tag.getList("networks", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < list.size(); i++) { + CompoundTag nbt = list.getCompound(i); + String label = nbt.getString("label"); + ResourceKey dim = nbt.contains("dim") ? ResourceKey.create(Registries.DIMENSION, new ResourceLocation(nbt.getString("dim"))) : null; + UUID owner = nbt.getUUID("owner"); + long channel = nbt.getLong("channel"); + LabelNetwork net = new LabelNetwork(dim, label, owner, channel); + net.loadEndpoints(nbt.getList("endpoints", CompoundTag.TAG_COMPOUND)); + reg.networks.put(new Key(dim, label, owner), net); + } + return reg; + } + + private long allocateChannel() { + return nextChannel++; + } + + /* 内部类型 */ + + public record Key(@Nullable ResourceKey dim, String label, UUID owner) {} + + public static class LabelNetwork { + private final ResourceKey dim; // null 表示跨维共用 + private final String label; + private final UUID owner; + private final long channel; + private final Set endpoints = new HashSet<>(); + + @Nullable + private IManagedGridNode managedNode; + @Nullable + private VirtualLabelNodeHost virtualHost; + + LabelNetwork(@Nullable ResourceKey dim, String label, UUID owner, long channel) { + this.dim = dim; + this.label = label; + this.owner = owner; + this.channel = channel; + } + + public long channel() { + return channel; + } + + public ResourceKey dim() { + return dim; + } + + public @Nullable IGridNode node() { + return managedNode == null ? null : managedNode.getNode(); + } + + /** + * 确保虚拟节点存在;若已存在则复用。 + */ + public boolean ensureVirtualNode(ServerLevel level) { + if (managedNode != null && managedNode.getNode() != null) { + return true; + } + ServerLevel hostLevel = dim == null ? level.getServer().getLevel(ServerLevel.OVERWORLD) : level.getServer().getLevel(dim); + if (hostLevel == null) return false; + + // 创建虚拟节点(非 in-world) + this.virtualHost = new VirtualLabelNodeHost(); + this.managedNode = GridHelper.createManagedNode(virtualHost, NodeListener.INSTANCE); + this.virtualHost.setManagedNode(this.managedNode); + this.managedNode.setFlags(GridFlags.REQUIRE_CHANNEL); + this.managedNode.setIdlePowerUsage(0.0); + this.managedNode.setInWorldNode(false); + this.managedNode.setVisualRepresentation(com.extendedae_plus.init.ModItems.LABELED_WIRELESS_TRANSCEIVER.get().getDefaultInstance()); + this.managedNode.setTagName("label_net_" + label); + this.managedNode.create(hostLevel, null); + return true; + } + + public void destroyVirtualNode() { + if (managedNode != null) { + managedNode.destroy(); + } + managedNode = null; + virtualHost = null; + } + + public ListTag saveEndpoints() { + ListTag list = new ListTag(); + for (EndpointRef ref : endpoints) { + list.add(ref.save()); + } + return list; + } + + public void loadEndpoints(ListTag list) { + endpoints.clear(); + for (int i = 0; i < list.size(); i++) { + endpoints.add(EndpointRef.load(list.getCompound(i))); + } + } + } + + public record EndpointRef(@Nullable ResourceKey dim, BlockPos pos) { + public boolean matches(@Nullable ResourceKey currentDim, BlockPos currentPos) { + if (!Objects.equals(dim, currentDim)) return false; + return pos.equals(currentPos); + } + + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + if (dim != null) { + tag.putString("dim", dim.location().toString()); + } + tag.putLong("pos", pos.asLong()); + return tag; + } + + public static EndpointRef load(CompoundTag tag) { + ResourceKey d = tag.contains("dim") + ? ResourceKey.create(Registries.DIMENSION, new ResourceLocation(tag.getString("dim"))) + : null; + BlockPos p = BlockPos.of(tag.getLong("pos")); + return new EndpointRef(d, p); + } + } + + enum NodeListener implements IGridNodeListener { + INSTANCE; + + @Override + public void onSaveChanges(VirtualLabelNodeHost host, IGridNode node) {} + + @Override + public void onStateChanged(VirtualLabelNodeHost host, IGridNode node, IGridNodeListener.State state) {} + + @Override + public void onInWorldConnectionChanged(VirtualLabelNodeHost host, IGridNode node) {} + + @Override + public void onGridChanged(VirtualLabelNodeHost host, IGridNode node) {} + + @Override + public void onOwnerChanged(VirtualLabelNodeHost host, IGridNode node) {} + } + + /** + * 虚拟标签网络节点宿主,不在世界中放置实体。 + */ + static class VirtualLabelNodeHost implements IInWorldGridNodeHost { + private IManagedGridNode managedNode; + + void setManagedNode(IManagedGridNode managedNode) { + this.managedNode = managedNode; + } + + @Override + public @Nullable IGridNode getGridNode(@Nullable net.minecraft.core.Direction dir) { + return managedNode == null ? null : managedNode.getNode(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlock.java b/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlock.java new file mode 100644 index 0000000..51f1752 --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlock.java @@ -0,0 +1,93 @@ +package com.extendedae_plus.content.wireless; + +import com.extendedae_plus.init.ModBlockEntities; +import net.minecraft.core.BlockPos; +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.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +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.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.Nullable; + +/** + * 标签无线收发器方块(无交互 UI,占位实现)。 + * 取消所有徒手/道具调节,只允许右键打开后续 UI(当前仅占位返回 SUCCESS)。 + */ +public class LabeledWirelessTransceiverBlock extends Block implements EntityBlock { + public static final IntegerProperty STATE = IntegerProperty.create("state", 0, 5); + + public LabeledWirelessTransceiverBlock(Properties props) { + super(props); + this.registerDefaultState(this.stateDefinition.any().setValue(STATE, 5)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(STATE); + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new LabeledWirelessTransceiverBlockEntity(pos, state); + } + + @Override + 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 LabeledWirelessTransceiverBlockEntity te) { + te.setPlacerId(player.getUUID(), player.getName().getString()); + } + } + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + // UI 暂未实现,直接吞掉交互,防止旧版徒手调频。 + return InteractionResult.sidedSuccess(level.isClientSide); + } + + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + if (!state.is(newState.getBlock())) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof LabeledWirelessTransceiverBlockEntity te) { + te.onRemoved(); + } + } + super.onRemove(state, level, pos, newState, isMoving); + } + + @Override + public float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { + // 与旧收发器保持一致:锁定时降低挖掘速度 + float baseProgress = super.getDestroyProgress(state, player, level, pos); + if (level.getBlockEntity(pos) instanceof LabeledWirelessTransceiverBlockEntity te) { + if (te.isLocked()) { + return baseProgress * 0.1f; + } + } + return baseProgress; + } + + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + if (level.isClientSide) return null; + return type == ModBlockEntities.LABELED_WIRELESS_TRANSCEIVER_BE.get() + ? (lvl, pos, st, be) -> LabeledWirelessTransceiverBlockEntity.serverTick(lvl, pos, st, (LabeledWirelessTransceiverBlockEntity) be) + : null; + } +} diff --git a/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlockEntity.java b/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlockEntity.java new file mode 100644 index 0000000..3d01d58 --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/wireless/LabeledWirelessTransceiverBlockEntity.java @@ -0,0 +1,334 @@ +package com.extendedae_plus.content.wireless; + +import appeng.api.networking.GridFlags; +import appeng.api.networking.GridHelper; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IInWorldGridNodeHost; +import appeng.api.networking.IManagedGridNode; +import appeng.api.util.AECableType; +import appeng.blockentity.AEBaseBlockEntity; +import com.extendedae_plus.ae.wireless.IWirelessEndpoint; +import com.extendedae_plus.ae.wireless.LabelLink; +import com.extendedae_plus.ae.wireless.LabelNetworkRegistry; +import com.extendedae_plus.init.ModBlockEntities; +import com.extendedae_plus.init.ModItems; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.UUID; + +/** + * 标签无线收发器方块实体: + * - 无主从区分,通过标签映射频道并连接至虚拟节点; + * - 无 UI(占位),仅提供服务端逻辑与状态更新; + * - 保留频率字段用于状态显示。 + */ +public class LabeledWirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost { + + private IManagedGridNode managedNode; + + private long frequency = 0L; + @Nullable + private String labelForDisplay; + private boolean locked = false; + private boolean beingRemoved = false; + + @Nullable + private UUID placerId; + @Nullable + private String placerName; + + private final LabelLink labelLink = new LabelLink(this); + + public LabeledWirelessTransceiverBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.LABELED_WIRELESS_TRANSCEIVER_BE.get(), pos, state); + this.managedNode = GridHelper.createManagedNode(this, NodeListener.INSTANCE) + .setFlags(GridFlags.DENSE_CAPACITY); + this.managedNode.setIdlePowerUsage(1.0); + this.managedNode.setTagName("labeled_wireless_node"); + this.managedNode.setInWorldNode(true); + this.managedNode.setExposedOnSides(EnumSet.allOf(Direction.class)); + this.managedNode.setVisualRepresentation(ModItems.LABELED_WIRELESS_TRANSCEIVER.get().getDefaultInstance()); + } + + /* ===================== IInWorldGridNodeHost ===================== */ + @Override + public @Nullable IGridNode getGridNode(Direction dir) { + return getGridNode(); + } + + /* ===================== IWirelessEndpoint ===================== */ + @Override + public ServerLevel getServerLevel() { + Level lvl = super.getLevel(); + return lvl instanceof ServerLevel sl ? sl : null; + } + + @Override + public BlockPos getBlockPos() { + return this.worldPosition; + } + + @Override + public IGridNode getGridNode() { + return managedNode == null ? null : managedNode.getNode(); + } + + @Override + public boolean isEndpointRemoved() { + return super.isRemoved(); + } + + /* ===================== 公共方法 ===================== */ + + public void setPlacerId(@Nullable UUID placerId, @Nullable String placerName) { + this.placerId = placerId; + this.placerName = placerName; + setChanged(); + } + + @Nullable + public UUID getPlacerId() { + return placerId; + } + + @Nullable + public String getPlacerName() { + return placerName; + } + + public long getFrequency() { + return frequency; + } + + @Nullable + public String getLabelForDisplay() { + return labelForDisplay; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + if (this.locked == locked) return; + this.locked = locked; + setChanged(); + } + + /** + * 应用/切换标签。空或非法标签将清空并断开。 + */ + public void applyLabel(@Nullable String rawLabel) { + ServerLevel sl = getServerLevel(); + if (sl == null) return; + + // 先注销旧网络引用 + LabelNetworkRegistry.get(sl).unregister(this); + + var network = LabelNetworkRegistry.get(sl).register(sl, rawLabel, placerId, this); + if (network == null) { + clearLabel(); + return; + } + + this.labelForDisplay = rawLabel; + this.frequency = network.channel(); + this.labelLink.setTarget(network); + updateState(); + setChanged(); + } + + /** + * 清空标签并断开连接。 + */ + public void clearLabel() { + ServerLevel sl = getServerLevel(); + if (sl != null) { + LabelNetworkRegistry.get(sl).unregister(this); + } + this.labelForDisplay = null; + this.frequency = 0L; + this.labelLink.clearTarget(); + updateState(); + setChanged(); + } + + /** + * 在加载或跨区块迁移后重新获取网络并重连。 + */ + public void refreshLabel() { + ServerLevel sl = getServerLevel(); + if (sl == null) return; + if (labelForDisplay == null || labelForDisplay.isEmpty()) { + this.frequency = 0L; + this.labelLink.clearTarget(); + updateState(); + return; + } + var network = LabelNetworkRegistry.get(sl).getNetwork(sl, labelForDisplay, placerId); + if (network == null) { + this.frequency = 0L; + this.labelLink.clearTarget(); + } else { + this.frequency = network.channel(); + this.labelLink.setTarget(network); + } + updateState(); + setChanged(); + } + + public void onRemoved() { + this.beingRemoved = true; + labelLink.onUnloadOrRemove(); + ServerLevel sl = getServerLevel(); + if (sl != null) { + LabelNetworkRegistry.get(sl).unregister(this); + } + if (managedNode != null) { + managedNode.destroy(); + } + } + + /* ===================== Tick ===================== */ + public static void serverTick(Level level, BlockPos pos, BlockState state, LabeledWirelessTransceiverBlockEntity be) { + if (!(level instanceof ServerLevel)) return; + be.labelLink.updateStatus(); + be.updateState(); + } + + /** + * 根据连接状态更新方块状态。 + */ + private void updateState() { + if (this.level == null || this.level.isClientSide) return; + if (this.beingRemoved || this.isRemoved()) return; + BlockState currentState = this.getBlockState(); + if (!(currentState.getBlock() instanceof LabeledWirelessTransceiverBlock)) { + return; + } + + IGridNode node = this.getGridNode(); + int newState = 5; // 默认无连接 + + if (node != null && node.isActive()) { + int usedChannels = 0; + for (var connection : node.getConnections()) { + usedChannels = Math.max(connection.getUsedChannels(), usedChannels); + } + if (usedChannels >= 32) { + newState = 4; + } else if (usedChannels >= 24) { + newState = 3; + } else if (usedChannels >= 16) { + newState = 2; + } else if (usedChannels >= 8) { + newState = 1; + } else if (usedChannels >= 0) { + newState = 0; + } + } + + if (currentState.getValue(LabeledWirelessTransceiverBlock.STATE) != newState) { + this.level.setBlock(this.worldPosition, currentState.setValue(LabeledWirelessTransceiverBlock.STATE, newState), 3); + } + } + + /* ===================== AECableType 展示 ===================== */ + @Override + public AECableType getCableConnectionType(Direction dir) { + if (this.level == null) return AECableType.GLASS; + var adjacentPos = this.worldPosition.relative(dir); + if (!Objects.requireNonNull(this.getLevel()).hasChunkAt(adjacentPos)) return AECableType.GLASS; + var adjacentHost = GridHelper.getNodeHost(this.getLevel(), adjacentPos); + if (adjacentHost != null) { + var t = adjacentHost.getCableConnectionType(dir.getOpposite()); + if (t != null) return t; + } + return AECableType.GLASS; + } + + /* ===================== 生命周期 ===================== */ + @Override + public void onLoad() { + super.onLoad(); + ServerLevel sl = getServerLevel(); + if (sl == null) return; + GridHelper.onFirstTick(this, be -> { + be.managedNode.create(be.getLevel(), be.getBlockPos()); + be.refreshLabel(); + }); + } + + /* ===================== NBT ===================== */ + @Override + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + tag.putLong("frequency", frequency); + tag.putBoolean("locked", locked); + if (labelForDisplay != null) { + tag.putString("label", labelForDisplay); + } + if (placerId != null) { + tag.putUUID("placerId", placerId); + } + if (placerName != null) { + tag.putString("placerName", placerName); + } + if (managedNode != null) { + managedNode.saveToNBT(tag); + } + } + + @Override + public void loadTag(CompoundTag tag) { + super.loadTag(tag); + this.frequency = tag.getLong("frequency"); + this.locked = tag.getBoolean("locked"); + if (tag.contains("label")) { + this.labelForDisplay = tag.getString("label"); + } else { + this.labelForDisplay = null; + } + if (tag.hasUUID("placerId")) { + this.placerId = tag.getUUID("placerId"); + } + if (tag.contains("placerName")) { + this.placerName = tag.getString("placerName"); + } + if (managedNode != null) { + managedNode.loadFromNBT(tag); + } + } + + /* ===================== AE2 节点监听 ===================== */ + enum NodeListener implements IGridNodeListener { + INSTANCE; + @Override + public void onSaveChanges(LabeledWirelessTransceiverBlockEntity host, IGridNode node) { + host.setChanged(); + } + @Override + public void onStateChanged(LabeledWirelessTransceiverBlockEntity host, IGridNode node, State state) { + host.updateState(); + } + @Override + public void onInWorldConnectionChanged(LabeledWirelessTransceiverBlockEntity host, IGridNode node) { + host.updateState(); + } + @Override + public void onGridChanged(LabeledWirelessTransceiverBlockEntity host, IGridNode node) { + host.updateState(); + } + @Override + public void onOwnerChanged(LabeledWirelessTransceiverBlockEntity host, IGridNode node) {} + } +} diff --git a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java index 4082444..ca55796 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java +++ b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java @@ -4,6 +4,7 @@ import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.content.matrix.CrafterCorePlusBlockEntity; import com.extendedae_plus.content.matrix.PatternCorePlusBlockEntity; import com.extendedae_plus.content.matrix.SpeedCorePlusBlockEntity; +import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; import com.extendedae_plus.content.matrix.UploadCoreBlockEntity; import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity; @@ -24,6 +25,11 @@ public final class ModBlockEntities { () -> BlockEntityType.Builder.of(WirelessTransceiverBlockEntity::new, ModBlocks.WIRELESS_TRANSCEIVER.get()).build(null)); + public static final RegistryObject> LABELED_WIRELESS_TRANSCEIVER_BE = + BLOCK_ENTITY_TYPES.register("labeled_wireless_transceiver", + () -> BlockEntityType.Builder.of(LabeledWirelessTransceiverBlockEntity::new, + ModBlocks.LABELED_WIRELESS_TRANSCEIVER.get()).build(null)); + public static final RegistryObject> NETWORK_PATTERN_CONTROLLER_BE = BLOCK_ENTITY_TYPES.register("network_pattern_controller", () -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new, diff --git a/src/main/java/com/extendedae_plus/init/ModBlocks.java b/src/main/java/com/extendedae_plus/init/ModBlocks.java index 689f3d5..a2aea7e 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlocks.java +++ b/src/main/java/com/extendedae_plus/init/ModBlocks.java @@ -9,6 +9,7 @@ import com.extendedae_plus.content.matrix.CrafterCorePlusBlock; import com.extendedae_plus.content.matrix.PatternCorePlusBlock; import com.extendedae_plus.content.matrix.SpeedCorePlusBlock; import com.extendedae_plus.content.matrix.UploadCoreBlock; +import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlock; import com.extendedae_plus.content.wireless.WirelessTransceiverBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockBehaviour; @@ -32,6 +33,16 @@ public final class ModBlocks { ) ); + public static final RegistryObject LABELED_WIRELESS_TRANSCEIVER = BLOCKS.register( + "labeled_wireless_transceiver", + () -> new LabeledWirelessTransceiverBlock( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(2F, 6.0F) + .requiresCorrectToolForDrops() + ) + ); + // AE2 网络模式控制器方块 public static final RegistryObject NETWORK_PATTERN_CONTROLLER = BLOCKS.register( "network_pattern_controller", diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index 4c00151..e62218e 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -26,6 +26,11 @@ public final class ModItems { () -> new BlockItem(ModBlocks.WIRELESS_TRANSCEIVER.get(), new Item.Properties()) ); + public static final RegistryObject LABELED_WIRELESS_TRANSCEIVER = ITEMS.register( + "labeled_wireless_transceiver", + () -> new BlockItem(ModBlocks.LABELED_WIRELESS_TRANSCEIVER.get(), new Item.Properties()) + ); + public static final RegistryObject NETWORK_PATTERN_CONTROLLER = ITEMS.register( "network_pattern_controller", () -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties()) diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 234e388..80d5f34 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -150,6 +150,12 @@ public final class ModNetwork { .decoder(SetWirelessFrequencyC2SPacket::decode) .consumerNetworkThread(SetWirelessFrequencyC2SPacket::handle) .add(); + + CHANNEL.messageBuilder(LabelNetworkActionC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(LabelNetworkActionC2SPacket::encode) + .decoder(LabelNetworkActionC2SPacket::decode) + .consumerNetworkThread(LabelNetworkActionC2SPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/java/com/extendedae_plus/network/LabelNetworkActionC2SPacket.java b/src/main/java/com/extendedae_plus/network/LabelNetworkActionC2SPacket.java new file mode 100644 index 0000000..d1deba1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/LabelNetworkActionC2SPacket.java @@ -0,0 +1,61 @@ +package com.extendedae_plus.network; + +import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * 标签无线收发器的操作数据包(无UI,仅后端逻辑)。 + * 支持:设置/切换标签、删除标签、刷新重连。 + */ +public class LabelNetworkActionC2SPacket { + public enum Action { + SET, DELETE, REFRESH + } + + private final BlockPos pos; + private final String label; + private final Action action; + + public LabelNetworkActionC2SPacket(BlockPos pos, String label, Action action) { + this.pos = pos; + this.label = label; + this.action = action; + } + + public static void encode(LabelNetworkActionC2SPacket packet, FriendlyByteBuf buf) { + buf.writeBlockPos(packet.pos); + buf.writeUtf(packet.label == null ? "" : packet.label, 128); + buf.writeEnum(packet.action); + } + + public static LabelNetworkActionC2SPacket decode(FriendlyByteBuf buf) { + BlockPos pos = buf.readBlockPos(); + String label = buf.readUtf(128); + Action action = buf.readEnum(Action.class); + return new LabelNetworkActionC2SPacket(pos, label, action); + } + + public static void handle(LabelNetworkActionC2SPacket packet, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + var level = player.serverLevel(); + if (!level.hasChunkAt(packet.pos)) return; + + var be = level.getBlockEntity(packet.pos); + if (!(be instanceof LabeledWirelessTransceiverBlockEntity te)) return; + + switch (packet.action) { + case SET -> te.applyLabel(packet.label); + case DELETE -> te.clearLabel(); + case REFRESH -> te.refreshLabel(); + } + }); + ctx.get().setPacketHandled(true); + } +}