标签无线收发器框架

This commit is contained in:
GaLicn 2025-12-12 13:33:51 +08:00
parent 3ef75ad18d
commit 1c7291b865
27 changed files with 1772 additions and 26 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,349 @@
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.ModConfigs;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.util.wireless.WirelessTeamUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.registries.Registries;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* 标签无线网络注册中心SavedData
* - 负责标签到频道频率的分配与复用
* - 创建/销毁虚拟节点所有收发器连接到同一虚拟节点
* - 记录在线端点集合端点卸载后需调用 unregister
*/
public class LabelNetworkRegistry extends SavedData {
public static final String SAVE_ID = ExtendedAEPlus.MODID + "_label_networks";
private static final Factory<LabelNetworkRegistry> FACTORY = new Factory<>(LabelNetworkRegistry::new, LabelNetworkRegistry::load);
private static final long CHANNEL_START = 1_000_000L;
private static final UUID PUBLIC_NETWORK_UUID = new UUID(0, 0);
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(FACTORY, SAVE_ID);
}
public record LabelNetworkSnapshot(String label, long channel) {}
public static LabelNetworkRegistry get(ServerLevel level) {
return get(level.getServer());
}
/**
* 规范化标签trim保持大小写敏感
*/
public static String normalizeLabel(String raw) {
if (raw == null) return null;
String t = raw.trim();
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 ? PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(beLevel, placerId);
ResourceKey<Level> dimKey = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get() ? 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;
}
/**
* 注销端点不自动删除网络网络的移除需显式调用 removeNetwork
*/
public synchronized void unregister(IWirelessEndpoint endpoint) {
ServerLevel level = endpoint.getServerLevel();
if (level == null) return;
ResourceKey<Level> dimKey = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get() ? null : level.dimension();
BlockPos pos = endpoint.getBlockPos();
for (LabelNetwork net : networks.values()) {
net.endpoints.removeIf(ref -> ref.matches(dimKey, pos));
}
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 ? PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(level, placerId);
ResourceKey<Level> dimKey = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get() ? null : level.dimension();
Key key = new Key(dimKey, label, owner);
return networks.get(key);
}
/**
* 显式删除一个网络销毁虚拟节点仅在 UI 删除 时调用
*/
public synchronized boolean removeNetwork(ServerLevel level, String rawLabel, @Nullable UUID placerId) {
String label = normalizeLabel(rawLabel);
if (label == null) return false;
UUID owner = placerId == null ? PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(level, placerId);
ResourceKey<Level> dimKey = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get() ? null : level.dimension();
Key key = new Key(dimKey, label, owner);
LabelNetwork net = networks.remove(key);
if (net != null) {
net.destroyVirtualNode();
setDirty();
return true;
}
return false;
}
/**
* 获取当前玩家所属网络列表按标签排序
*/
public synchronized List<LabelNetworkSnapshot> listNetworks(ServerLevel level, @Nullable UUID placerId) {
UUID owner = placerId == null ? PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(level, placerId);
ResourceKey<Level> dimKey = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get() ? null : level.dimension();
List<LabelNetworkSnapshot> list = new ArrayList<>();
for (Map.Entry<Key, LabelNetwork> entry : networks.entrySet()) {
Key key = entry.getKey();
if (!Objects.equals(key.owner(), owner)) continue;
if (!Objects.equals(key.dim(), dimKey)) continue;
list.add(new LabelNetworkSnapshot(key.label(), entry.getValue().channel()));
}
list.sort(Comparator.comparingLong(LabelNetworkSnapshot::channel));
return list;
}
/* 序列化 */
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag tag, HolderLookup.Provider registries) {
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, HolderLookup.Provider registries) {
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, ResourceLocation.parse(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() {
if (nextChannel < CHANNEL_START) {
nextChannel = CHANNEL_START;
}
long ch = nextChannel++;
return ch;
}
/* 内部类型 */
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;
this.virtualHost = new VirtualLabelNodeHost();
this.managedNode = GridHelper.createManagedNode(virtualHost, NodeListener.INSTANCE);
this.virtualHost.setManagedNode(this.managedNode);
this.managedNode.setFlags(GridFlags.DENSE_CAPACITY);
this.managedNode.setIdlePowerUsage(0.0);
this.managedNode.setInWorldNode(false);
this.managedNode.setVisualRepresentation(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 int endpointCount() {
return endpoints.size();
}
}
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, ResourceLocation.parse(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

@ -4,12 +4,11 @@ import appeng.client.render.crafting.CraftingCubeModel;
import appeng.init.client.InitScreens;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
import com.extendedae_plus.hooks.BuiltInModelHooks;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.items.materials.EntitySpeedCardItem;
import com.extendedae_plus.client.screen.LabeledWirelessTransceiverScreen;
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
import net.minecraft.client.renderer.item.ItemProperties;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
@ -28,31 +27,9 @@ public final class ClientProxy {
public static void init() {
if (REGISTERED) return;
REGISTERED = true;
// 注册 Item property用于根据 ItemStack NBT exponent 切换模型
// 注册 Item property
ItemProperties.register(ModItems.ENTITY_SPEED_CARD.get(), ExtendedAEPlus.id("mult"),
(stack, world, entity, seed) -> (float) EntitySpeedCardItem.readMultiplier(stack));
// 注册四种形成态模型为内置模型
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_4x)));
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/16x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_16x)));
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/64x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_64x)));
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/256x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_256x)));
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
}
@SubscribeEvent
@ -73,6 +50,18 @@ public final class ClientProxy {
}
);
event.register(
ModMenuTypes.LABELED_WIRELESS_TRANSCEIVER.get(),
new net.minecraft.client.gui.screens.MenuScreens.ScreenConstructor<
LabeledWirelessTransceiverMenu,
LabeledWirelessTransceiverScreen>() {
@Override
public LabeledWirelessTransceiverScreen create(LabeledWirelessTransceiverMenu menu, net.minecraft.world.entity.player.Inventory inv, net.minecraft.network.chat.Component title) {
return new LabeledWirelessTransceiverScreen(menu, inv, title);
}
}
);
/**
* 注册由 AE2 InitScreens 所需的屏幕资源映射用于内置 JSON 屏幕注册
*/

View File

@ -0,0 +1,476 @@
package com.extendedae_plus.client.screen;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
import com.extendedae_plus.network.LabelNetworkActionC2SPacket;
import com.extendedae_plus.network.LabelNetworkListC2SPacket;
import com.extendedae_plus.init.ModNetwork;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.ImageButton;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import java.util.List;
public class LabeledWirelessTransceiverScreen extends AbstractContainerScreen<LabeledWirelessTransceiverMenu> {
private static final ResourceLocation TEX = ExtendedAEPlus.id("textures/gui/lable_wireless_transceiver_gui.png");
private static final int BTN_U = 2;
private static final int BTN_V = 159;
private static final int BTN_W = 28;
private static final int BTN_H = 16;
private static final int TEX_W = 256;
private static final int TEX_H = 256;
private static final int LIST_X = 9;
private static final int LIST_Y = 27;
private static final int LIST_W = 110; // 118-9+1
private static final int LIST_H = 114; // 140-27+1
private static final int ROW_H = 11; // 10px text height + 1px 分隔
private static final int VISIBLE_ROWS = LIST_H / ROW_H; // 10
private static final int SCROLL_X = 123;
private static final int SCROLL_Y = 21;
private static final int SCROLL_W = 6;
private static final int SCROLL_H = 121; // 141-21+1
private static final int INFO_MAX_WIDTH = 116; // 信息区实际宽度(249-134+1=116)
private EditBox searchBox;
private ImageButton newBtn;
private ImageButton deleteBtn;
private ImageButton setBtn;
private ImageButton disconnectBtn;
private final BlockPos bePos;
private final List<LabelEntry> entries = new ArrayList<>();
private final List<LabelEntry> filtered = new ArrayList<>();
private int scrollOffset = 0;
private int selectedIndex = -1;
private String lastSelectedLabel = "";
private String currentLabel = "";
private String currentOwner = "";
private int onlineCount = 0;
private int usedChannels = 0;
private int maxChannels = 0;
public LabeledWirelessTransceiverScreen(LabeledWirelessTransceiverMenu menu, Inventory inv, Component title) {
super(menu, inv, title);
this.imageWidth = 256;
this.imageHeight = 156;
this.inventoryLabelY = this.imageHeight; // 不显示玩家物品栏标签
this.bePos = menu.getBlockEntityPos();
}
@Override
protected void init() {
super.init();
// 搜索框起点(134,23) 终点(249,31) => 宽116 高9取整为9
int sx = this.leftPos + 134;
int sy = this.topPos + 23;
this.searchBox = new EditBox(this.font, sx, sy, 116, 9, Component.empty());
this.searchBox.setBordered(false);
this.searchBox.setMaxLength(64);
this.searchBox.setVisible(true);
this.searchBox.setFocused(false);
this.searchBox.setResponder(s -> {
applyFilter();
});
this.addRenderableWidget(this.searchBox);
int startX = this.leftPos + 145;
int startY = this.topPos + 101;
int hGap = 30;
int vGap = 8;
int secondColX = startX + BTN_W + hGap;
int secondRowY = startY + BTN_H + vGap;
this.newBtn = new StateImageButton(startX, startY, BTN_W, BTN_H, BTN_U, BTN_V, 2, 177, 2, 195, TEX, TEX_W, TEX_H,
b -> sendSet(searchBox.getValue()), Component.translatable("gui.extendedae_plus.labeled_wireless.button.new"));
this.deleteBtn = new StateImageButton(secondColX, startY, BTN_W, BTN_H, BTN_U, BTN_V, 2, 177, 2, 195, TEX, TEX_W, TEX_H,
b -> sendDelete(), Component.translatable("gui.extendedae_plus.labeled_wireless.button.delete"));
this.setBtn = new StateImageButton(startX, secondRowY, BTN_W, BTN_H, BTN_U, BTN_V, 2, 177, 2, 195, TEX, TEX_W, TEX_H,
b -> sendSet(getSelectedLabel()), Component.translatable("gui.extendedae_plus.labeled_wireless.button.set"));
this.disconnectBtn = new StateImageButton(secondColX, secondRowY, BTN_W, BTN_H, BTN_U, BTN_V, 2, 177, 2, 195, TEX, TEX_W, TEX_H,
b -> sendDisconnect(), Component.translatable("gui.extendedae_plus.labeled_wireless.button.refresh"));
this.addRenderableWidget(this.newBtn);
this.addRenderableWidget(this.deleteBtn);
this.addRenderableWidget(this.setBtn);
this.addRenderableWidget(this.disconnectBtn);
requestList();
}
@Override
public void render(GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) {
this.renderBackground(gfx);
super.render(gfx, mouseX, mouseY, partialTicks);
drawAllButtonText(gfx);
this.renderTooltip(gfx, mouseX, mouseY);
if (this.searchBox != null) {
this.searchBox.render(gfx, mouseX, mouseY, partialTicks);
}
}
@Override
protected void renderLabels(GuiGraphics gfx, int mouseX, int mouseY) {
float titleScale = getTitleScale();
var pose = gfx.pose();
pose.pushPose();
pose.translate(8, 8, 0);
pose.scale(titleScale, titleScale, 1.0f);
gfx.drawString(this.font, this.title, 0, 0, 0x404040, false);
pose.popPose();
pose.pushPose();
pose.translate(134, 8, 0);
pose.scale(titleScale, titleScale, 1.0f);
gfx.drawString(this.font, Component.translatable("gui.extendedae_plus.labeled_wireless.info"), 0, 0, 0x404040, false);
pose.popPose();
}
@Override
protected void renderBg(GuiGraphics gfx, float partialTicks, int mouseX, int mouseY) {
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
gfx.blit(TEX, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight);
// 占位绘制列表和信息区内的内容框线
// 标签列表区域
gfx.fill(this.leftPos + 9, this.topPos + 27, this.leftPos + 118 + 1, this.topPos + 140 + 1, 0x20FFFFFF);
// 滚动条区域
gfx.fill(this.leftPos + 123, this.topPos + 21, this.leftPos + 128 + 1, this.topPos + 141 + 1, 0x20000000);
// 当前收发器信息区域
gfx.fill(this.leftPos + 134, this.topPos + 41, this.leftPos + 249 + 1, this.topPos + 92 + 1, 0x10FFFFFF);
renderList(gfx);
renderScrollBar(gfx);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (this.searchBox != null && this.searchBox.mouseClicked(mouseX, mouseY, button)) {
setFocused(this.searchBox);
return true;
}
if (isMouseInList(mouseX, mouseY)) {
int localY = (int) mouseY - (this.topPos + LIST_Y);
int row = localY / ROW_H;
int idx = scrollOffset + row;
if (idx >= 0 && idx < filtered.size()) {
selectedIndex = idx;
lastSelectedLabel = filtered.get(idx).label();
}
return true;
}
if (isMouseInScrollbar(mouseX, mouseY)) {
updateScrollByMouse((int) mouseY);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (this.searchBox != null && this.searchBox.keyPressed(keyCode, scanCode, modifiers)) {
return true;
}
return super.keyPressed(keyCode, scanCode, modifiers);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
if (isMouseInList(mouseX, mouseY) || isMouseInScrollbar(mouseX, mouseY)) {
int maxOffset = Math.max(0, filtered.size() - VISIBLE_ROWS);
scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset - (int) Math.signum(delta)));
return true;
}
return super.mouseScrolled(mouseX, mouseY, delta);
}
private void renderList(GuiGraphics gfx) {
int baseX = this.leftPos + LIST_X;
int baseY = this.topPos + LIST_Y;
for (int row = 0; row < VISIBLE_ROWS; row++) {
int idx = scrollOffset + row;
if (idx >= filtered.size()) break;
int y = baseY + row * ROW_H;
if (idx == selectedIndex) {
gfx.fill(baseX, y, baseX + LIST_W, y + ROW_H, 0x40FFFFFF);
}
LabelEntry e = filtered.get(idx);
String text = this.font.plainSubstrByWidth(e.label(), LIST_W - 2);
int ty = y + (ROW_H - this.font.lineHeight) / 2;
gfx.drawString(this.font, text, baseX + 2, ty, 0x404040, false);
}
// 信息显示
int infoX = this.leftPos + 134;
int infoY = this.topPos + 41;
float infoScale = getInfoScale();
String labelLine = Component.translatable("gui.extendedae_plus.labeled_wireless.current_label").getString() + ": " + (currentLabel == null || currentLabel.isEmpty() ? "-" : currentLabel);
String ownerLine = Component.translatable("gui.extendedae_plus.labeled_wireless.current_owner").getString() + ": " + (currentOwner == null || currentOwner.isEmpty() ? Component.translatable("extendedae_plus.jade.owner.public").getString() : currentOwner);
String onlineLine = Component.translatable("gui.extendedae_plus.labeled_wireless.online_count").getString() + ": " + onlineCount;
Component channelComp = maxChannels <= 0
? Component.translatable("extendedae_plus.jade.channels", usedChannels)
: Component.translatable("extendedae_plus.jade.channels_of", usedChannels, maxChannels);
drawInfoLine(gfx, labelLine, infoX, infoY, infoScale);
drawInfoLine(gfx, ownerLine, infoX, infoY + 12, infoScale);
drawInfoLine(gfx, onlineLine, infoX, infoY + 24, infoScale);
drawInfoLine(gfx, channelComp.getString(), infoX, infoY + 36, infoScale);
}
private void renderScrollBar(GuiGraphics gfx) {
int total = filtered.size();
if (total <= VISIBLE_ROWS) {
// 画静态条
gfx.fill(this.leftPos + SCROLL_X, this.topPos + SCROLL_Y, this.leftPos + SCROLL_X + SCROLL_W, this.topPos + SCROLL_Y + SCROLL_H, 0x20000000);
return;
}
int maxOffset = total - VISIBLE_ROWS;
int trackX1 = this.leftPos + SCROLL_X;
int trackY1 = this.topPos + SCROLL_Y;
int trackY2 = trackY1 + SCROLL_H;
gfx.fill(trackX1, trackY1, trackX1 + SCROLL_W, trackY2, 0x20000000);
int knobH = Math.max(10, (int) ((double) VISIBLE_ROWS / total * SCROLL_H));
int knobY = trackY1 + (int) ((SCROLL_H - knobH) * (scrollOffset / (double) maxOffset));
gfx.fill(trackX1, knobY, trackX1 + SCROLL_W, knobY + knobH, 0x80FFFFFF);
}
private boolean isMouseInList(double mouseX, double mouseY) {
return mouseX >= this.leftPos + LIST_X && mouseX < this.leftPos + LIST_X + LIST_W
&& mouseY >= this.topPos + LIST_Y && mouseY < this.topPos + LIST_Y + LIST_H;
}
private boolean isMouseInScrollbar(double mouseX, double mouseY) {
return mouseX >= this.leftPos + SCROLL_X && mouseX < this.leftPos + SCROLL_X + SCROLL_W
&& mouseY >= this.topPos + SCROLL_Y && mouseY < this.topPos + SCROLL_Y + SCROLL_H;
}
private void updateScrollByMouse(int mouseY) {
int total = filtered.size();
if (total <= VISIBLE_ROWS) return;
int maxOffset = total - VISIBLE_ROWS;
int relativeY = mouseY - (this.topPos + SCROLL_Y);
relativeY = Math.max(0, Math.min(SCROLL_H, relativeY));
int knobH = Math.max(10, (int) ((double) VISIBLE_ROWS / total * SCROLL_H));
double ratio = (relativeY - knobH / 2.0) / (double) (SCROLL_H - knobH);
ratio = Math.max(0.0, Math.min(1.0, ratio));
scrollOffset = (int) Math.round(ratio * maxOffset);
}
private void applyFilter() {
String prevSelected = lastSelectedLabel;
String q = searchBox.getValue() == null ? "" : searchBox.getValue().trim();
filtered.clear();
if (q.isEmpty()) {
filtered.addAll(entries);
} else {
for (LabelEntry e : entries) {
if (e.label().contains(q)) {
filtered.add(e);
}
}
}
scrollOffset = 0;
selectedIndex = -1;
if (prevSelected != null && !prevSelected.isEmpty()) {
for (int i = 0; i < filtered.size(); i++) {
if (filtered.get(i).label().equals(prevSelected)) {
selectedIndex = i;
ensureSelectionVisible();
break;
}
}
}
}
private void requestList() {
ModNetwork.CHANNEL.sendToServer(new LabelNetworkListC2SPacket(bePos));
}
private void sendSet(String label) {
if (label == null) label = "";
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, label, LabelNetworkActionC2SPacket.Action.SET));
this.lastSelectedLabel = label;
this.searchBox.setValue("");
requestList();
}
private void sendDelete() {
String label = getSelectedLabel();
if (label == null || label.isEmpty()) {
label = searchBox.getValue();
}
if (label == null) label = "";
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, label, LabelNetworkActionC2SPacket.Action.DELETE));
this.lastSelectedLabel = "";
requestList();
}
private void sendDisconnect() {
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, "", LabelNetworkActionC2SPacket.Action.DISCONNECT));
this.lastSelectedLabel = "";
requestList();
}
private String getSelectedLabel() {
if (selectedIndex >= 0 && selectedIndex < filtered.size()) {
return filtered.get(selectedIndex).label();
}
return "";
}
public void updateList(List<LabelNetworkRegistry.LabelNetworkSnapshot> list, String currentLabel, String ownerName, int usedChannels, int maxChannels, int onlineCount) {
String prevSelected = getSelectedLabel();
this.entries.clear();
for (LabelNetworkRegistry.LabelNetworkSnapshot s : list) {
this.entries.add(new LabelEntry(s.label(), s.channel()));
}
this.currentLabel = currentLabel == null ? "" : currentLabel;
this.currentOwner = ownerName == null ? "" : ownerName;
this.onlineCount = onlineCount;
this.usedChannels = usedChannels;
this.maxChannels = maxChannels;
if (prevSelected != null && !prevSelected.isEmpty()) {
this.lastSelectedLabel = prevSelected;
} else if (this.currentLabel != null && !this.currentLabel.isEmpty()) {
this.lastSelectedLabel = this.currentLabel;
} else {
this.lastSelectedLabel = "";
}
applyFilter();
}
public boolean isFor(BlockPos pos) {
return this.bePos.equals(pos);
}
private record LabelEntry(String label, long channel) {}
private void drawAllButtonText(GuiGraphics gfx) {
// 按钮文本24px 内居中避免溢出放在 super.render 之后确保绘制在按钮纹理之上
int startX = this.leftPos + 145;
int startY = this.topPos + 101;
int hGap = 30;
int vGap = 8;
int secondColX = startX + BTN_W + hGap;
int secondRowY = startY + BTN_H + vGap;
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.new"), startX, startY);
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.delete"), secondColX, startY);
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.set"), startX, secondRowY);
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.refresh"), secondColX, secondRowY);
}
private void drawButtonText(GuiGraphics gfx, Component text, int x, int y) {
String s = this.font.plainSubstrByWidth(text.getString(), BTN_W - 4);
int tx = x + (BTN_W - this.font.width(s)) / 2;
int ty = y + (BTN_H - this.font.lineHeight) / 2 + 1;
gfx.drawString(this.font, s, tx, ty, 0xFFFFFF, false);
}
private void ensureSelectionVisible() {
if (selectedIndex < 0) return;
int maxOffset = Math.max(0, filtered.size() - VISIBLE_ROWS);
int targetRow = selectedIndex;
if (targetRow < scrollOffset) {
scrollOffset = targetRow;
} else if (targetRow >= scrollOffset + VISIBLE_ROWS) {
scrollOffset = Math.min(maxOffset, targetRow - VISIBLE_ROWS + 1);
}
}
private String trimInfo(String text, float scale) {
if (text == null) return "";
int maxWidth = (int) (INFO_MAX_WIDTH / Math.max(0.0001f, scale));
return this.font.plainSubstrByWidth(text, maxWidth);
}
private void drawInfoLine(GuiGraphics gfx, String text, int x, int y, float scale) {
String trimmed = trimInfo(text, scale);
var pose = gfx.pose();
pose.pushPose();
pose.translate(x, y, 0);
pose.scale(scale, scale, 1.0f);
gfx.drawString(this.font, trimmed, 0, 0, 0x404040, false);
pose.popPose();
}
private boolean isEnglish() {
Minecraft mc = Minecraft.getInstance();
var lang = mc.getLanguageManager().getSelected();
return lang != null && lang.equalsIgnoreCase("en_us");
}
private float getInfoScale() {
return isEnglish() ? 0.75f : 1.0f;
}
private float getTitleScale() {
return isEnglish() ? 0.75f : 1.0f;
}
private static class StateImageButton extends ImageButton {
private final ResourceLocation tex;
private final int texW;
private final int texH;
private final int baseU;
private final int baseV;
private final int hoverU;
private final int hoverV;
private final int pressU;
private final int pressV;
private boolean pressedVisual = false;
public StateImageButton(int x, int y, int w, int h, int baseU, int baseV, int hoverU, int hoverV, int pressU, int pressV,
ResourceLocation tex, int texW, int texH, OnPress onPress, Component tooltip) {
super(x, y, w, h, baseU, baseV, 0, tex, texW, texH, onPress, tooltip);
this.tex = tex;
this.texW = texW;
this.texH = texH;
this.baseU = baseU;
this.baseV = baseV;
this.hoverU = hoverU;
this.hoverV = hoverV;
this.pressU = pressU;
this.pressV = pressV;
}
@Override
public void renderWidget(GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) {
boolean hovered = this.isMouseOver(mouseX, mouseY);
boolean pressed = pressedVisual || (hovered && GLFW.glfwGetMouseButton(Minecraft.getInstance().getWindow().getWindow(), GLFW.GLFW_MOUSE_BUTTON_1) == GLFW.GLFW_PRESS);
int u = baseU;
int v = baseV;
if (pressed) {
u = pressU;
v = pressV;
} else if (hovered) {
u = hoverU;
v = hoverV;
}
gfx.blit(tex, this.getX(), this.getY(), u, v, this.width, this.height, texW, texH);
}
@Override
public void onClick(double mouseX, double mouseY) {
this.pressedVisual = true;
super.onClick(mouseX, mouseY);
this.setFocused(false);
}
@Override
public void onRelease(double mouseX, double mouseY) {
this.pressedVisual = false;
super.onRelease(mouseX, mouseY);
this.setFocused(false);
}
}
}

View File

@ -0,0 +1,92 @@
package com.extendedae_plus.content.wireless;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
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.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.neoforged.neoforge.network.MenuHooks;
import org.jetbrains.annotations.Nullable;
import com.extendedae_plus.init.ModBlockEntities;
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
/**
* 标签无线收发器方块
*/
public class LabeledWirelessTransceiverBlock extends Block implements EntityBlock {
public static final BooleanProperty STATE = BooleanProperty.create("state");
public LabeledWirelessTransceiverBlock(Properties props) {
super(props);
this.registerDefaultState(this.stateDefinition.any().setValue(STATE, Boolean.FALSE));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(STATE);
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
return this.defaultBlockState();
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new LabeledWirelessTransceiverBlockEntity(pos, state);
}
@Nullable
@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;
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
if (level.isClientSide) {
return InteractionResult.SUCCESS;
}
BlockEntity be = level.getBlockEntity(pos);
if (!(be instanceof LabeledWirelessTransceiverBlockEntity te)) {
return InteractionResult.PASS;
}
MenuHooks.openMenu((net.minecraft.server.level.ServerPlayer) player, te, pos);
return InteractionResult.CONSUME;
}
@Override
protected ItemInteractionResult useItemOn(net.minecraft.world.item.ItemStack heldItem, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
// 委托空手交互逻辑统一入口
InteractionResult r = this.useWithoutItem(state, level, pos, player, hit);
return r.consumesAction() ? ItemInteractionResult.SUCCESS : ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
@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);
}
}

View File

@ -0,0 +1,311 @@
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 com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
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;
public class LabeledWirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost, MenuProvider {
private IManagedGridNode managedNode;
private long frequency = 0L;
@Nullable
private String labelForDisplay;
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());
}
@Override
public @Nullable IGridNode getGridNode(Direction dir) {
return getGridNode();
}
@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();
}
@Override
public Component getDisplayName() {
return Component.translatable("block.extendedae_plus.labeled_wireless_transceiver");
}
@Override
public @Nullable AbstractContainerMenu createMenu(int id, Inventory inv, Player player) {
return new LabeledWirelessTransceiverMenu(id, inv, this.worldPosition);
}
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 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(boolean ensureRegister) {
ServerLevel sl = getServerLevel();
if (sl == null) return;
if (labelForDisplay == null || labelForDisplay.isEmpty()) {
this.frequency = 0L;
this.labelLink.clearTarget();
updateState();
return;
}
var registry = LabelNetworkRegistry.get(sl);
var network = registry.getNetwork(sl, labelForDisplay, placerId);
if (network == null && ensureRegister) {
network = registry.register(sl, labelForDisplay, placerId, this);
}
if (network == null) {
this.frequency = 0L;
this.labelLink.clearTarget();
} else {
network.ensureVirtualNode(sl);
this.frequency = network.channel();
this.labelLink.setTarget(network);
}
updateState();
setChanged();
}
public void refreshLabel() {
refreshLabel(false);
}
public void onRemoved() {
this.beingRemoved = true;
labelLink.onUnloadOrRemove();
ServerLevel sl = getServerLevel();
if (sl != null) {
LabelNetworkRegistry.get(sl).unregister(this);
}
if (managedNode != null) {
managedNode.destroy();
}
}
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();
boolean online = false;
if (node != null && node.isActive()) {
try {
var grid = node.getGrid();
online = grid != null && grid.getEnergyService().isNetworkPowered();
} catch (Throwable ignored) {
online = false;
}
}
if (currentState.getValue(LabeledWirelessTransceiverBlock.STATE) != online) {
this.level.setBlock(this.worldPosition, currentState.setValue(LabeledWirelessTransceiverBlock.STATE, online), 3);
}
}
@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(true);
be.labelLink.updateStatus();
be.updateState();
});
}
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
tag.putLong("frequency", frequency);
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, HolderLookup.Provider registries) {
super.loadTag(tag, registries);
this.frequency = tag.getLong("frequency");
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);
}
}
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

@ -5,6 +5,7 @@ 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.WirelessTransceiverBlockEntity;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
import com.extendedae_plus.content.matrix.UploadCoreBlockEntity;
import appeng.blockentity.crafting.CraftingBlockEntity;
@ -25,6 +26,11 @@ public final class ModBlockEntities {
() -> BlockEntityType.Builder.of(WirelessTransceiverBlockEntity::new,
ModBlocks.WIRELESS_TRANSCEIVER.get()).build(null));
public static final DeferredHolder<BlockEntityType<?>, 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 DeferredHolder<BlockEntityType<?>, BlockEntityType<NetworkPatternControllerBlockEntity>> NETWORK_PATTERN_CONTROLLER_BE =
BLOCK_ENTITY_TYPES.register("network_pattern_controller",
() -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new,

View File

@ -6,6 +6,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 com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
import appeng.block.crafting.CraftingUnitBlock;
@ -32,6 +33,16 @@ public final class ModBlocks {
)
);
public static final DeferredBlock<Block> LABELED_WIRELESS_TRANSCEIVER = BLOCKS.register(
"labeled_wireless_transceiver",
() -> new LabeledWirelessTransceiverBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.METAL)
.strength(1.5F, 6.0F)
.requiresCorrectToolForDrops()
)
);
// AE2 网络模式控制器方块
public static final DeferredBlock<Block> NETWORK_PATTERN_CONTROLLER = BLOCKS.register(
"network_pattern_controller",

View File

@ -20,6 +20,7 @@ public final class ModCreativeTabs {
.displayItems((params, output) -> {
List.of(
ModItems.WIRELESS_TRANSCEIVER.get().getDefaultInstance(),
ModItems.LABELED_WIRELESS_TRANSCEIVER.get().getDefaultInstance(),
ModItems.NETWORK_PATTERN_CONTROLLER.get().getDefaultInstance(),
ModItems.ACCELERATOR_4x.get().getDefaultInstance(),
ModItems.ACCELERATOR_16x.get().getDefaultInstance(),

View File

@ -18,6 +18,10 @@ public final class ModItems {
"wireless_transceiver",
() -> new BlockItem(ModBlocks.WIRELESS_TRANSCEIVER.get(), new Item.Properties())
);
public static final DeferredItem<Item> LABELED_WIRELESS_TRANSCEIVER = ITEMS.register(
"labeled_wireless_transceiver",
() -> new BlockItem(ModBlocks.LABELED_WIRELESS_TRANSCEIVER.get(), new Item.Properties())
);
// Crafting Accelerators
public static final DeferredItem<Item> ACCELERATOR_4x = ITEMS.register(
"4x_crafting_accelerator",

View File

@ -5,6 +5,7 @@ import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.inventory.MenuType;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
@ -21,6 +22,10 @@ public final class ModMenuTypes {
MENUS.register("network_pattern_controller",
() -> IMenuTypeExtension.create(NetworkPatternControllerMenu::new));
public static final DeferredHolder<MenuType<?>, MenuType<LabeledWirelessTransceiverMenu>> LABELED_WIRELESS_TRANSCEIVER =
MENUS.register("labeled_wireless_transceiver",
() -> IMenuTypeExtension.create(LabeledWirelessTransceiverMenu::new));
public static final DeferredHolder<MenuType<?>, MenuType<EntitySpeedTickerMenu>> ENTITY_TICKER_MENU =
MENUS.register("entity_speed_ticker",
() -> MenuTypeBuilder

View File

@ -42,6 +42,17 @@ public class ModNetwork {
com.extendedae_plus.network.SetWirelessFrequencyC2SPacket.STREAM_CODEC,
com.extendedae_plus.network.SetWirelessFrequencyC2SPacket::handle);
// 标签无线收发器请求列表 / 操作 / 列表下发
registrar.playToServer(com.extendedae_plus.network.LabelNetworkListC2SPacket.TYPE,
com.extendedae_plus.network.LabelNetworkListC2SPacket.STREAM_CODEC,
com.extendedae_plus.network.LabelNetworkListC2SPacket::handle);
registrar.playToServer(com.extendedae_plus.network.LabelNetworkActionC2SPacket.TYPE,
com.extendedae_plus.network.LabelNetworkActionC2SPacket.STREAM_CODEC,
com.extendedae_plus.network.LabelNetworkActionC2SPacket::handle);
registrar.playToClient(com.extendedae_plus.network.LabelNetworkListS2CPacket.TYPE,
com.extendedae_plus.network.LabelNetworkListS2CPacket.STREAM_CODEC,
com.extendedae_plus.network.LabelNetworkListS2CPacket::handle);
registrar.playToServer(EAPConfigButtonPacket.TYPE, EAPConfigButtonPacket.STREAM_CODEC, EAPConfigButtonPacket::handleOnServer);
}
}

View File

@ -0,0 +1,68 @@
package com.extendedae_plus.integration.jade;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
import com.extendedae_plus.util.wireless.WirelessTeamUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import snownee.jade.api.BlockAccessor;
import snownee.jade.api.IServerDataProvider;
/**
* 标签无线收发器服务端数据同步无主从
*/
public enum LabeledWirelessTransceiverProvider implements IServerDataProvider<BlockAccessor> {
INSTANCE;
private static final ResourceLocation UID = new ResourceLocation("extendedae_plus", "labeled_wireless_info");
@Override
public ResourceLocation getUid() {
return UID;
}
@Override
public void appendServerData(CompoundTag data, BlockAccessor accessor) {
if (!(accessor.getBlockEntity() instanceof LabeledWirelessTransceiverBlockEntity be)) return;
String label = be.getLabelForDisplay();
if (label != null) {
data.putString("label", label);
}
// 所有者
var placerId = be.getPlacerId();
if (placerId != null && be.getServerLevel() != null) {
data.putUUID("placerId", placerId);
data.putString("ownerName", WirelessTeamUtil.getNetworkOwnerName(be.getServerLevel(), placerId).getString());
}
IGridNode node = be.getGridNode();
IGrid grid = node == null ? null : node.getGrid();
boolean networkUsable = false;
int usedChannels = 0;
int maxChannels = 0;
if (node != null && node.isActive()) {
for (var connection : node.getConnections()) {
usedChannels = Math.max(connection.getUsedChannels(), usedChannels);
}
if (node instanceof appeng.me.GridNode gridNode) {
var channelMode = gridNode.getGrid().getPathingService().getChannelMode();
if (channelMode == appeng.api.networking.pathing.ChannelMode.INFINITE) {
maxChannels = -1;
} else {
maxChannels = gridNode.getMaxChannels();
}
}
}
if (grid != null) {
try {
networkUsable = grid.getEnergyService().isNetworkPowered();
} catch (Throwable ignored) {
networkUsable = false;
}
}
data.putInt("usedChannels", usedChannels);
data.putInt("maxChannels", maxChannels);
data.putBoolean("networkUsable", networkUsable);
}
}

View File

@ -2,6 +2,12 @@ package com.extendedae_plus.integration.jade;
import com.extendedae_plus.content.wireless.WirelessTransceiverBlock;
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlock;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
import com.extendedae_plus.integration.jade.LabeledWirelessTransceiverProvider;
import com.extendedae_plus.integration.jade.LabeledWirelessTransceiverComponents;
import com.extendedae_plus.integration.jade.WirelessTransceiverProvider;
import com.extendedae_plus.integration.jade.WirelessTransceiverJadePluginComponents;
import snownee.jade.api.IWailaClientRegistration;
import snownee.jade.api.IWailaCommonRegistration;
import snownee.jade.api.IWailaPlugin;
@ -14,6 +20,7 @@ public class WirelessTransceiverJadePlugin implements IWailaPlugin {
public void register(IWailaCommonRegistration registration) {
// 注册服务端数据提供者用于同步数据
registration.registerBlockDataProvider(WirelessTransceiverProvider.INSTANCE, WirelessTransceiverBlockEntity.class);
registration.registerBlockDataProvider(LabeledWirelessTransceiverProvider.INSTANCE, LabeledWirelessTransceiverBlockEntity.class);
}
@Override
@ -22,5 +29,6 @@ public class WirelessTransceiverJadePlugin implements IWailaPlugin {
for (var component : WirelessTransceiverJadePluginComponents.values()) {
registration.registerBlockComponent(component, WirelessTransceiverBlock.class);
}
registration.registerBlockComponent(LabeledWirelessTransceiverComponents.LABEL_AND_CHANNEL, LabeledWirelessTransceiverBlock.class);
}
}

View File

@ -0,0 +1,41 @@
package com.extendedae_plus.menu;
import com.extendedae_plus.init.ModMenuTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
/**
* 标签无线收发器菜单无物品槽仅用于打开客户端界面
*/
public class LabeledWirelessTransceiverMenu extends AbstractContainerMenu {
private final BlockPos bePos;
public LabeledWirelessTransceiverMenu(int id, Inventory inv, BlockPos bePos) {
super(ModMenuTypes.LABELED_WIRELESS_TRANSCEIVER.get(), id);
this.bePos = bePos;
}
public LabeledWirelessTransceiverMenu(int id, Inventory inv, RegistryFriendlyByteBuf buf) {
this(id, inv, buf.readBlockPos());
}
public BlockPos getBlockEntityPos() {
return bePos;
}
@Override
public boolean stillValid(Player player) {
return player.level() != null
&& player.level().getBlockEntity(bePos) != null
&& player.distanceToSqr(bePos.getX() + 0.5, bePos.getY() + 0.5, bePos.getZ() + 0.5) <= 64;
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
return ItemStack.EMPTY;
}
}

View File

@ -0,0 +1,57 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S标签无线收发器操作设置/删除/断开
*/
public record LabelNetworkActionC2SPacket(BlockPos pos, String label, Action action) implements CustomPacketPayload {
public enum Action { SET, DELETE, DISCONNECT }
public static final Type<LabelNetworkActionC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "label_network_action"));
public static final StreamCodec<RegistryFriendlyByteBuf, LabelNetworkActionC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeBlockPos(pkt.pos);
buf.writeUtf(pkt.label == null ? "" : pkt.label, 128);
buf.writeEnum(pkt.action);
},
buf -> new LabelNetworkActionC2SPacket(buf.readBlockPos(), buf.readUtf(128), buf.readEnum(Action.class))
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(LabelNetworkActionC2SPacket packet, IPayloadContext ctx) {
ctx.enqueueWork(() -> {
var player = ctx.player();
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 -> {
String target = (packet.label == null || packet.label.isEmpty()) ? te.getLabelForDisplay() : packet.label;
if (target != null && !target.isEmpty()) {
LabelNetworkRegistry.get(level).removeNetwork(level, target, te.getPlacerId());
}
te.clearLabel();
}
case DISCONNECT -> te.clearLabel();
}
});
}
}

View File

@ -0,0 +1,73 @@
package com.extendedae_plus.network;
import appeng.api.networking.pathing.ChannelMode;
import appeng.me.GridNode;
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
import com.extendedae_plus.util.wireless.WirelessTeamUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S请求标签网络列表及当前标签信息
*/
public record LabelNetworkListC2SPacket(BlockPos pos) implements CustomPacketPayload {
public static final Type<LabelNetworkListC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "label_network_list"));
public static final StreamCodec<RegistryFriendlyByteBuf, LabelNetworkListC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> buf.writeBlockPos(pkt.pos),
buf -> new LabelNetworkListC2SPacket(buf.readBlockPos())
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(LabelNetworkListC2SPacket pkt, IPayloadContext ctx) {
ctx.enqueueWork(() -> {
var player = ctx.player();
if (player == null) return;
var level = player.serverLevel();
if (!level.hasChunkAt(pkt.pos)) return;
var be = level.getBlockEntity(pkt.pos);
if (!(be instanceof LabeledWirelessTransceiverBlockEntity te)) return;
var list = LabelNetworkRegistry.get(level).listNetworks(level, te.getPlacerId());
String currentLabel = te.getLabelForDisplay();
String ownerName = te.getPlacerId() != null ? WirelessTeamUtil.getNetworkOwnerName(level, te.getPlacerId()).getString() : "";
int onlineCount = 0;
if (currentLabel != null && !currentLabel.isEmpty()) {
var network = LabelNetworkRegistry.get(level).getNetwork(level, currentLabel, te.getPlacerId());
if (network != null) {
onlineCount = network.endpointCount();
}
}
int usedChannels = 0;
int maxChannels = 0;
var node = te.getGridNode();
if (node != null && node.isActive()) {
for (var connection : node.getConnections()) {
usedChannels = Math.max(connection.getUsedChannels(), usedChannels);
}
if (node instanceof GridNode gridNode) {
var channelMode = gridNode.getGrid().getPathingService().getChannelMode();
if (channelMode == ChannelMode.INFINITE) {
maxChannels = -1;
} else {
maxChannels = gridNode.getMaxChannels();
}
}
}
ctx.reply(new LabelNetworkListS2CPacket(pkt.pos, list, currentLabel == null ? "" : currentLabel, ownerName, usedChannels, maxChannels, onlineCount));
});
}
}

View File

@ -0,0 +1,79 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
import com.extendedae_plus.client.screen.LabeledWirelessTransceiverScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.ArrayList;
import java.util.List;
/**
* S2C标签网络列表下发
*/
public record LabelNetworkListS2CPacket(BlockPos pos,
List<LabelNetworkRegistry.LabelNetworkSnapshot> list,
String currentLabel,
String ownerName,
int usedChannels,
int maxChannels,
int onlineCount) implements CustomPacketPayload {
public static final Type<LabelNetworkListS2CPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "label_network_list_s2c"));
public static final StreamCodec<RegistryFriendlyByteBuf, LabelNetworkListS2CPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeBlockPos(pkt.pos);
buf.writeUtf(pkt.currentLabel == null ? "" : pkt.currentLabel, 128);
buf.writeUtf(pkt.ownerName == null ? "" : pkt.ownerName, 128);
buf.writeVarInt(pkt.usedChannels);
buf.writeVarInt(pkt.maxChannels);
buf.writeVarInt(pkt.onlineCount);
buf.writeVarInt(pkt.list.size());
for (var s : pkt.list) {
buf.writeUtf(s.label(), 128);
buf.writeLong(s.channel());
}
},
buf -> {
BlockPos pos = buf.readBlockPos();
String curLabel = buf.readUtf(128);
String ownerName = buf.readUtf(128);
int usedChannels = buf.readVarInt();
int maxChannels = buf.readVarInt();
int onlineCount = buf.readVarInt();
int size = buf.readVarInt();
List<LabelNetworkRegistry.LabelNetworkSnapshot> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
String label = buf.readUtf(128);
long channel = buf.readLong();
list.add(new LabelNetworkRegistry.LabelNetworkSnapshot(label, channel));
}
return new LabelNetworkListS2CPacket(pos, list, curLabel, ownerName, usedChannels, maxChannels, onlineCount);
}
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void handle(LabelNetworkListS2CPacket pkt, IPayloadContext ctx) {
ctx.enqueueWork(() -> handleClient(pkt));
}
@OnlyIn(Dist.CLIENT)
private static void handleClient(LabelNetworkListS2CPacket pkt) {
Minecraft mc = Minecraft.getInstance();
if (mc.screen instanceof LabeledWirelessTransceiverScreen screen && screen.isFor(pkt.pos)) {
screen.updateList(pkt.list, pkt.currentLabel, pkt.ownerName, pkt.usedChannels, pkt.maxChannels, pkt.onlineCount);
}
}
}

View File

@ -0,0 +1,10 @@
{
"variants": {
"state=false": {
"model": "extendedae_plus:block/wirelesstransceiver/lable_wireless_transceiver_off"
},
"state=true": {
"model": "extendedae_plus:block/wirelesstransceiver/lable_wireless_transceiver_on"
}
}
}

View File

@ -117,6 +117,23 @@
"extendedae_plus.configuration.state_on": "On",
"extendedae_plus.configuration.state_off": "Off",
"block.extendedae_plus.labeled_wireless_transceiver": "Labeled Wireless Transceiver",
"item.extendedae_plus.labeled_wireless_transceiver": "Labeled Wireless Transceiver",
"gui.extendedae_plus.labeled_wireless.info": "Transceiver Info",
"gui.extendedae_plus.labeled_wireless.button.new": "New Label",
"gui.extendedae_plus.labeled_wireless.button.delete": "Delete",
"gui.extendedae_plus.labeled_wireless.button.set": "Set Current",
"gui.extendedae_plus.labeled_wireless.button.refresh": "Disconnect",
"gui.extendedae_plus.labeled_wireless.current_label": "Current Label",
"gui.extendedae_plus.labeled_wireless.current_owner": "Owner",
"gui.extendedae_plus.labeled_wireless.online_count": "Online",
"extendedae_plus.jade.label": "Label: %s",
"extendedae_plus.jade.owner.public": "Owner: Public",
"extendedae_plus.jade.channels": "Channels: %s",
"extendedae_plus.jade.channels_of": "Channels: %s/%s",
"extendedae_plus.tooltip.owner": "Owner: %s",
"extendedae_plus.tooltip.owner.public": "Owner: Public",
"extendedae_plus.tooltip.channels": "Channels: %d",

View File

@ -114,6 +114,26 @@
"extendedae_plus.configuration.state_on": "开",
"extendedae_plus.configuration.state_off": "关",
"block.extendedae_plus.labeled_wireless_transceiver": "标签无线收发器",
"item.extendedae_plus.labeled_wireless_transceiver": "标签无线收发器",
"gui.extendedae_plus.labeled_wireless.info": "收发器信息",
"gui.extendedae_plus.labeled_wireless.button.new": "新建标签",
"gui.extendedae_plus.labeled_wireless.button.delete": "删除",
"gui.extendedae_plus.labeled_wireless.button.set": "设为当前",
"gui.extendedae_plus.labeled_wireless.button.refresh": "断开连接",
"gui.extendedae_plus.labeled_wireless.current_label": "当前标签",
"gui.extendedae_plus.labeled_wireless.current_owner": "所有者",
"gui.extendedae_plus.labeled_wireless.online_count": "在线数",
"extendedae_plus.jade.label": "标签: %s",
"extendedae_plus.jade.owner": "所有者: %s",
"extendedae_plus.jade.owner.public": "所有者: 公共",
"extendedae_plus.jade.channels": "频道: %s",
"extendedae_plus.jade.channels_of": "频道: %s/%s",
"extendedae_plus.jade.online": "设备在线",
"extendedae_plus.jade.offline": "设备离线",
"extendedae_plus.tooltip.owner": "所有者: %s",
"extendedae_plus.tooltip.owner.public": "所有者: 公共",
"extendedae_plus.tooltip.channels": "频道: %d",

View File

@ -0,0 +1,7 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "extendedae_plus:block/wireless_transceiver/lable_wireless_transceiver_off"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "extendedae_plus:block/wireless_transceiver/lable_wireless_transceiver_on"
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "extendedae_plus:block/wirelesstransceiver/lable_wireless_transceiver_off"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB