标签无线收发器框架

This commit is contained in:
GaLicn 2025-12-11 18:14:17 +08:00
parent 7c9f50b96a
commit 29d07b1f90
9 changed files with 933 additions and 0 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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