标签无线收发器框架
This commit is contained in:
parent
3ef75ad18d
commit
1c7291b865
101
src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java
Normal file
101
src/main/java/com/extendedae_plus/ae/wireless/LabelLink.java
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package com.extendedae_plus.ae.wireless;
|
||||
|
||||
import appeng.api.networking.GridHelper;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.me.service.helpers.ConnectionWrapper;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* 标签无线收发器的连接器。
|
||||
* - 将 BE 的 in-world 节点连接到标签网络的虚拟节点。
|
||||
* - 只维护物理连接,不负责注册/反注册标签。
|
||||
*/
|
||||
public class LabelLink {
|
||||
private final IWirelessEndpoint host;
|
||||
private final ConnectionWrapper connection = new ConnectionWrapper(null);
|
||||
|
||||
@Nullable
|
||||
private LabelNetworkRegistry.LabelNetwork target;
|
||||
|
||||
public LabelLink(IWirelessEndpoint host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public void setTarget(@Nullable LabelNetworkRegistry.LabelNetwork target) {
|
||||
this.target = target;
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
public void clearTarget() {
|
||||
setTarget(null);
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connection.getConnection() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 serverTick 或标签变化时调用。
|
||||
*/
|
||||
public void updateStatus() {
|
||||
if (host.isEndpointRemoved()) {
|
||||
destroyConnection();
|
||||
return;
|
||||
}
|
||||
if (target == null) {
|
||||
destroyConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
final ServerLevel hostLevel = host.getServerLevel();
|
||||
if (hostLevel == null) {
|
||||
destroyConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
// 维度校验:未开启跨维且维度不匹配则断开
|
||||
ResourceKey<Level> targetDim = target.dim();
|
||||
if (targetDim != null && targetDim != hostLevel.dimension()) {
|
||||
destroyConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
IGridNode hostNode = host.getGridNode();
|
||||
IGridNode targetNode = target.node();
|
||||
if (hostNode == null || targetNode == null) {
|
||||
destroyConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var current = connection.getConnection();
|
||||
if (current != null) {
|
||||
var a = current.a();
|
||||
var b = current.b();
|
||||
if ((a == hostNode || b == hostNode) && (a == targetNode || b == targetNode)) {
|
||||
return; // 已连接
|
||||
}
|
||||
current.destroy();
|
||||
connection.setConnection(null);
|
||||
}
|
||||
connection.setConnection(GridHelper.createConnection(hostNode, targetNode));
|
||||
} catch (IllegalStateException ignore) {
|
||||
destroyConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public void onUnloadOrRemove() {
|
||||
destroyConnection();
|
||||
}
|
||||
|
||||
private void destroyConnection() {
|
||||
var current = connection.getConnection();
|
||||
if (current != null) {
|
||||
current.destroy();
|
||||
connection.setConnection(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 屏幕注册)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "extendedae_plus:block/wireless_transceiver/lable_wireless_transceiver_off"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "extendedae_plus:block/wireless_transceiver/lable_wireless_transceiver_on"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "extendedae_plus:block/wirelesstransceiver/lable_wireless_transceiver_off"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 409 B |
Binary file not shown.
|
After Width: | Height: | Size: 499 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Loading…
Reference in New Issue
Block a user