feat: 加slsBoardEnabled规则作榜单总开关(服务端规则控制客户端命令)

BoardTogglePayload同步开关状态;客户端/slsboard检查enabled,未开启则拒绝。ServerMain加轮询线程检测规则变化。
This commit is contained in:
AxTps committed 2026-10-06 19:49:26 +08:00
1 parent 7366a8b244
commit 3fde7e2c1b
8 files changed
+99 -10

No files matched your search

@@ -80,4 +80,7 @@ public class SLSCarpetSettings {
@Rule(categories = {SLSA, RuleCategory.FEATURE})
public static boolean stackableVehicles = false;
@Rule(categories = {SLSA, RuleCategory.FEATURE})
public static boolean slsBoardEnabled = false;
}
@@ -44,6 +44,7 @@ public class ServerMain implements ModInitializer, CarpetExtension {
INSTANCE = this;
CarpetServer.manageExtension(this);
com.github.zly2006.carpetslsaddition.net.BoardSyncPayload.register();
com.github.zly2006.carpetslsaddition.net.BoardTogglePayload.register();
// 玩家加入时同步榜单
net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents.JOIN.register(
(handler, sender, srv) ->
@@ -60,6 +61,25 @@ public class ServerMain implements ModInitializer, CarpetExtension {
ServerMain.server = server;
com.github.zly2006.carpetslsaddition.util.BotListManager.load();
com.github.zly2006.carpetslsaddition.util.BoardSyncManager.syncToAll(server);
startBoardWatcher(server);
}
/** 每秒检测 slsBoardEnabled 规则变化并同步给客户端。 */
private void startBoardWatcher(MinecraftServer server) {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
com.github.zly2006.carpetslsaddition.util.BoardSyncManager.tick(server);
Thread.sleep(1000L);
} catch (InterruptedException e) {
return;
} catch (Throwable ex) {
LOGGER.debug("[SLSA] board watcher error: {}", ex.toString());
}
}
}, "SLSA-BoardWatcher");
t.setDaemon(true);
t.start();
}
@Override
@@ -1,6 +1,7 @@
package com.github.zly2006.carpetslsaddition.client;
import com.github.zly2006.carpetslsaddition.net.BoardSyncPayload;
import com.github.zly2006.carpetslsaddition.net.BoardTogglePayload;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
@@ -38,24 +39,27 @@ public class BoardClient implements ClientModInitializer {
context.client().execute(() -> BoardClientState.setBoard(payload.board(), entries));
});
// 接收开关状态
ClientPlayNetworking.registerGlobalReceiver(BoardTogglePayload.TYPE, (payload, context) -> {
context.client().execute(() -> BoardClientState.enabled = payload.enabled());
});
// 2) /slsboard 命令(纯客户端,非 OP 也能用)
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(
ClientCommands.literal("slsboard")
.then(ClientCommands.literal("deaths")
.executes(c -> { BoardClientState.toggle("deaths"); feedback(c.getSource()); return 1; }))
.executes(c -> run(c.getSource(), () -> BoardClientState.toggle("deaths"))))
.then(ClientCommands.literal("mined")
.executes(c -> { BoardClientState.toggle("mined"); feedback(c.getSource()); return 1; }))
.executes(c -> run(c.getSource(), () -> BoardClientState.toggle("mined"))))
.then(ClientCommands.literal("off")
.executes(c -> { BoardClientState.set(""); feedback(c.getSource()); return 1; }))
.executes(c -> run(c.getSource(), () -> BoardClientState.set(""))))
.then(ClientCommands.argument("board", StringArgumentType.word())
.suggests((c, b) -> SharedSuggestionProvider.suggest(
new String[]{"deaths", "mined", "off"}, b))
.executes(c -> {
String v = StringArgumentType.getString(c, "board");
BoardClientState.set("off".equals(v) ? "" : v);
feedback(c.getSource());
return 1;
return run(c.getSource(), () -> BoardClientState.set("off".equals(v) ? "" : v));
}))
);
});
@@ -64,8 +68,15 @@ public class BoardClient implements ClientModInitializer {
HudElementRegistry.addLast(HUD_ID, new BoardHudElement());
}
private static void feedback(FabricClientCommandSource source) {
/** 统一的命令入口:先检查服务端开关,再执行动作。 */
private static int run(FabricClientCommandSource source, Runnable action) {
if (!BoardClientState.enabled) {
source.sendError(Component.literal("[SLSA] 榜单功能未开启(服务端 /carpet slsBoardEnabled true)"));
return 0;
}
action.run();
String d = BoardClientState.displaying;
source.sendFeedback(Component.literal("[SLSA] 榜单显示:" + (d.isEmpty() ? "关闭" : d)));
return 1;
}
}
@@ -19,6 +19,9 @@ public final class BoardClientState {
/** 当前显示的榜:"" / "deaths" / "mined"。 */
public static volatile String displaying = "";
/** 服务端 Carpet 规则 slsBoardEnabled 的状态(未开启则禁止使用)。 */
public static volatile boolean enabled = false;
private static volatile List<Entry> deaths = Collections.emptyList();
private static volatile List<Entry> mined = Collections.emptyList();
@@ -0,0 +1,31 @@
package com.github.zly2006.carpetslsaddition.net;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* 服务端 → 客户端:榜单功能总开关状态(对应 Carpet 规则 {@code slsBoardEnabled})。
*
* <p>规则变化时下发;客户端收到后决定 {@code /slsboard} 是否可用。
*/
public record BoardTogglePayload(boolean enabled) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<BoardTogglePayload> TYPE =
CustomPacketPayload.createType("slsa:board_toggle");
public static final StreamCodec<RegistryFriendlyByteBuf, BoardTogglePayload> CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, BoardTogglePayload::enabled,
BoardTogglePayload::new
);
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static void register() {
net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry.clientboundPlay().register(TYPE, CODEC);
}
}
@@ -61,12 +61,18 @@ public final class BoardSyncManager {
return list;
}
/** 把两个榜单都同步给某玩家。 */
/** 把两个榜单 + 开关状态同步给某玩家。 */
public static void syncTo(ServerPlayer player) {
MinecraftServer server = ServerMain.server;
if (server == null) {
return;
}
boolean enabled = com.github.zly2006.carpetslsaddition.SLSCarpetSettings.slsBoardEnabled;
// 先发开关状态
ServerPlayNetworking.send(player, new com.github.zly2006.carpetslsaddition.net.BoardTogglePayload(enabled));
if (!enabled) {
return;
}
ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_DEATHS, buildBoard(server, BOARD_DEATHS)));
ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_MINED, buildBoard(server, BOARD_MINED)));
}
@@ -77,4 +83,15 @@ public final class BoardSyncManager {
syncTo(player);
}
}
private static volatile Boolean lastEnabled = null;
/** 由轮询线程每秒调用:规则值变化时同步给所有玩家。 */
public static void tick(MinecraftServer server) {
boolean now = com.github.zly2006.carpetslsaddition.SLSCarpetSettings.slsBoardEnabled;
if (lastEnabled == null || lastEnabled != now) {
lastEnabled = now;
syncToAll(server);
}
}
}
@@ -74,5 +74,7 @@
"carpet.slsa.block.deep_slate_core": "Deepslate Core",
"carpet.rule.stackableVehicles.name": "Stackable vehicles",
"carpet.rule.stackableVehicles.desc": "Minecarts and boats can stack up to 64, auto-merging on pickup"
"carpet.rule.stackableVehicles.desc": "Minecarts and boats can stack up to 64, auto-merging on pickup",
"carpet.rule.slsBoardEnabled.name": "Enable boards",
"carpet.rule.slsBoardEnabled.desc": "Allow players to use /slsboard to show death/mining leaderboards"
}
@@ -74,5 +74,7 @@
"carpet.slsa.block.deep_slate_core": "深板岩核心",
"carpet.rule.stackableVehicles.name": "载具可堆叠",
"carpet.rule.stackableVehicles.desc": "矿车和船的物品可以堆叠到64,拾取时自动合并"
"carpet.rule.stackableVehicles.desc": "矿车和船的物品可以堆叠到64,拾取时自动合并",
"carpet.rule.slsBoardEnabled.name": "启用榜单",
"carpet.rule.slsBoardEnabled.desc": "允许玩家用 /slsboard 显示死亡榜/挖掘榜"
}