标签无线收发器框架
This commit is contained in:
parent
7c9f50b96a
commit
29d07b1f90
101
src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java
Normal file
101
src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java
Normal file
|
|
@ -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<Level> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Key, LabelNetwork> 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<Level> 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<Level> dimKey = ModConfig.INSTANCE.wirelessCrossDimEnable ? null : level.dimension();
|
||||
BlockPos pos = endpoint.getBlockPos();
|
||||
Iterator<Map.Entry<Key, LabelNetwork>> 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<Level> 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<Level> 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<Level> dim, String label, UUID owner) {}
|
||||
|
||||
public static class LabelNetwork {
|
||||
private final ResourceKey<Level> dim; // null 表示跨维共用
|
||||
private final String label;
|
||||
private final UUID owner;
|
||||
private final long channel;
|
||||
private final Set<EndpointRef> endpoints = new HashSet<>();
|
||||
|
||||
@Nullable
|
||||
private IManagedGridNode managedNode;
|
||||
@Nullable
|
||||
private VirtualLabelNodeHost virtualHost;
|
||||
|
||||
LabelNetwork(@Nullable ResourceKey<Level> 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<Level> 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<Level> dim, BlockPos pos) {
|
||||
public boolean matches(@Nullable ResourceKey<Level> 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<Level> 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<VirtualLabelNodeHost> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Block, BlockState> 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 <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<LabeledWirelessTransceiverBlockEntity> {
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BlockEntityType<LabeledWirelessTransceiverBlockEntity>> 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<BlockEntityType<NetworkPatternControllerBlockEntity>> NETWORK_PATTERN_CONTROLLER_BE =
|
||||
BLOCK_ENTITY_TYPES.register("network_pattern_controller",
|
||||
() -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new,
|
||||
|
|
|
|||
|
|
@ -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<Block> 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<Block> NETWORK_PATTERN_CONTROLLER = BLOCKS.register(
|
||||
"network_pattern_controller",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ public final class ModItems {
|
|||
() -> new BlockItem(ModBlocks.WIRELESS_TRANSCEIVER.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<Item> LABELED_WIRELESS_TRANSCEIVER = ITEMS.register(
|
||||
"labeled_wireless_transceiver",
|
||||
() -> new BlockItem(ModBlocks.LABELED_WIRELESS_TRANSCEIVER.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<Item> NETWORK_PATTERN_CONTROLLER = ITEMS.register(
|
||||
"network_pattern_controller",
|
||||
() -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties())
|
||||
|
|
|
|||
|
|
@ -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++; }
|
||||
|
|
|
|||
|
|
@ -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<NetworkEvent.Context> 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user