diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java b/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java
index a92c863..31da6ca 100644
--- a/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java
@@ -43,12 +43,6 @@ public class ServerMain implements ModInitializer, CarpetExtension {
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
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) ->
- com.github.zly2006.carpetslsaddition.util.BoardSyncManager.syncTo(handler.getPlayer()));
}
@Override
@@ -60,7 +54,7 @@ public class ServerMain implements ModInitializer, CarpetExtension {
public void onServerLoaded(MinecraftServer server) {
ServerMain.server = server;
com.github.zly2006.carpetslsaddition.util.BotListManager.load();
- com.github.zly2006.carpetslsaddition.util.BoardSyncManager.syncToAll(server);
+ com.github.zly2006.carpetslsaddition.util.BoardManager.ensureObjectives(server);
startBoardWatcher(server);
}
@@ -69,7 +63,7 @@ public class ServerMain implements ModInitializer, CarpetExtension {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
- com.github.zly2006.carpetslsaddition.util.BoardSyncManager.tick(server);
+ com.github.zly2006.carpetslsaddition.util.BoardManager.syncScores(server);
Thread.sleep(1000L);
} catch (InterruptedException e) {
return;
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClient.java b/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClient.java
index 38199ac..0f945bd 100644
--- a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClient.java
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClient.java
@@ -1,7 +1,5 @@
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;
@@ -9,75 +7,61 @@ import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.command.v2.ClientCommands;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
-import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
-import net.minecraft.resources.Identifier;
-
-import java.util.ArrayList;
-import java.util.List;
+import net.minecraft.world.scores.DisplaySlot;
+import net.minecraft.world.scores.Objective;
/**
- * 客户端入口:接收榜单数据、注册 /slsboard 命令、注册 HUD 渲染。
+ * 客户端入口:/slsboard 命令,控制本地计分板侧边栏显示哪个榜。
*
- *
纯客户端功能:显示哪个榜由本地状态决定,各玩家可不同步。
+ *
利用原版计分板侧边栏渲染(可靠),各玩家本地切换、互不影响。
+ * 数据由服务端从 stats 同步到 scoreboard objective(见 BoardManager)。
*/
@Environment(EnvType.CLIENT)
public class BoardClient implements ClientModInitializer {
- public static final Identifier HUD_ID = Identifier.fromNamespaceAndPath("slsa", "board_hud");
-
@Override
public void onInitializeClient() {
- // 1) 接收服务端下发的榜单数据
- ClientPlayNetworking.registerGlobalReceiver(BoardSyncPayload.TYPE, (payload, context) -> {
- List entries = new ArrayList<>();
- for (BoardSyncPayload.Entry e : payload.entries()) {
- entries.add(new BoardClientState.Entry(e.name(), e.score()));
- }
- com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.info("[SLSA-BOARD-C] recv board={} entries={}", payload.board(), entries.size());
- 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 -> run(c.getSource(), () -> BoardClientState.toggle("deaths"))))
- .then(ClientCommands.literal("mined")
- .executes(c -> run(c.getSource(), () -> BoardClientState.toggle("mined"))))
- .then(ClientCommands.literal("off")
- .executes(c -> run(c.getSource(), () -> BoardClientState.set(""))))
+ .then(ClientCommands.literal("deaths").executes(c -> show(c.getSource(), "sls_deaths")))
+ .then(ClientCommands.literal("mined").executes(c -> show(c.getSource(), "sls_mined")))
+ .then(ClientCommands.literal("off").executes(c -> { clear(c.getSource()); return 1; }))
.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");
- return run(c.getSource(), () -> BoardClientState.set("off".equals(v) ? "" : v));
+ if ("off".equals(v)) { clear(c.getSource()); return 1; }
+ return show(c.getSource(), "sls_" + v);
}))
);
});
-
- // 3) HUD 渲染
- HudElementRegistry.addLast(HUD_ID, new BoardHudElement());
}
- /** 统一的命令入口:先检查服务端开关,再执行动作。 */
- private static int run(FabricClientCommandSource source, Runnable action) {
- if (!BoardClientState.enabled) {
- source.sendError(Component.literal("[SLSA] 榜单功能未开启(服务端 /carpet slsBoardEnabled true)"));
+ private static int show(FabricClientCommandSource source, String objectiveName) {
+ var mc = source.getClient();
+ var level = mc.level;
+ if (level == null) {
return 0;
}
- action.run();
- String d = BoardClientState.displaying;
- source.sendFeedback(Component.literal("[SLSA] 榜单显示:" + (d.isEmpty() ? "关闭" : d)));
+ Objective obj = level.getScoreboard().getObjective(objectiveName);
+ if (obj == null) {
+ source.sendError(Component.literal("[SLSA] 找不到计分板 " + objectiveName + "(服务端规则未开启?)"));
+ return 0;
+ }
+ level.getScoreboard().setDisplayObjective(DisplaySlot.SIDEBAR, obj);
+ source.sendFeedback(Component.literal("[SLSA] 榜单显示:" + objectiveName));
return 1;
}
+
+ private static void clear(FabricClientCommandSource source) {
+ var level = source.getClient().level;
+ if (level != null) {
+ level.getScoreboard().setDisplayObjective(DisplaySlot.SIDEBAR, null);
+ }
+ source.sendFeedback(Component.literal("[SLSA] 榜单已关闭"));
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClientState.java b/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClientState.java
deleted file mode 100644
index 30b6610..0000000
--- a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardClientState.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.github.zly2006.carpetslsaddition.client;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * 客户端榜单状态(纯客户端,各玩家独立,不同步)。
- *
- *
- * - {@link #displaying}:当前显示哪个榜("" = 不显示)。
- * - {@code deaths}/{@code mined}:从服务端收到的排行数据缓存。
- *
- */
-public final class BoardClientState {
-
- private BoardClientState() {}
-
- /** 当前显示的榜:"" / "deaths" / "mined"。 */
- public static volatile String displaying = "";
-
- /** 服务端 Carpet 规则 slsBoardEnabled 的状态(未开启则禁止使用)。 */
- public static volatile boolean enabled = false;
-
- private static volatile List deaths = Collections.emptyList();
- private static volatile List mined = Collections.emptyList();
-
- public record Entry(String name, int score) {}
-
- public static void setBoard(String board, List entries) {
- if ("deaths".equals(board)) {
- deaths = entries;
- } else if ("mined".equals(board)) {
- mined = entries;
- }
- }
-
- public static List current() {
- return switch (displaying) {
- case "deaths" -> deaths;
- case "mined" -> mined;
- default -> Collections.emptyList();
- };
- }
-
- public static void toggle(String board) {
- displaying = displaying.equals(board) ? "" : board;
- }
-
- public static void set(String board) {
- displaying = board;
- }
-
- public static String currentTitle() {
- return switch (displaying) {
- case "deaths" -> "死亡榜";
- case "mined" -> "挖掘榜";
- default -> "";
- };
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardHudElement.java b/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardHudElement.java
deleted file mode 100644
index 87e4a17..0000000
--- a/src/main/java/com/github/zly2006/carpetslsaddition/client/BoardHudElement.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.github.zly2006.carpetslsaddition.client;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElement;
-import net.minecraft.client.DeltaTracker;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.Font;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
-import net.minecraft.network.chat.Component;
-
-import java.util.List;
-
-/**
- * 右侧排行榜 HUD:显示死亡榜/挖掘榜的 Top N。
- *
- * 纯客户端渲染,内容来自 {@link BoardClientState}(服务端同步而来)。
- */
-@Environment(EnvType.CLIENT)
-public class BoardHudElement implements HudElement {
-
- private static final int LINE_H = 10;
- private static final int PAD = 4;
- private static final int BG = 0x80000000; // 半透明黑
- private static final int TITLE_COLOR = 0xFFFF55; // 黄
- private static final int TEXT_COLOR = 0xFFFFFF; // 白
-
- @Override
- public void extractRenderState(GuiGraphicsExtractor g, DeltaTracker delta) {
- String board = BoardClientState.displaying;
- if (board == null || board.isEmpty()) {
- return;
- }
- Minecraft mc = Minecraft.getInstance();
- if (mc.player == null || mc.options.hideGui) {
- return;
- }
- List list = BoardClientState.current();
- if (list.isEmpty()) {
- return;
- }
- Font font = mc.font;
-
- // 关键:进入新的渲染层,否则画的内容不显示
- g.nextStratum();
-
- String title = BoardClientState.currentTitle();
- int maxW = font.width(title);
- for (BoardClientState.Entry e : list) {
- maxW = Math.max(maxW, font.width(line(e)));
- }
-
- int screenW = g.guiWidth();
- int screenH = g.guiHeight();
- int x = screenW - maxW - PAD * 2 - 2;
- // 背景(右侧垂直居中)
- int h = PAD * 2 + LINE_H * (list.size() + 1);
- int y = screenH / 2 - h / 2;
- g.fill(x, y, x + maxW + PAD * 2, y + h, BG);
-
- int ty = y + PAD;
- g.text(font, Component.literal(title), x + PAD, ty, TITLE_COLOR);
- ty += LINE_H;
- for (int i = 0; i < list.size(); i++) {
- BoardClientState.Entry e = list.get(i);
- g.text(font, Component.literal(line(e)), x + PAD, ty, TEXT_COLOR);
- ty += LINE_H;
- }
- }
-
- private static String line(BoardClientState.Entry e) {
- String name = e.name();
- if (name.length() > 12) name = name.substring(0, 12);
- return name + " " + e.score();
- }
-}
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardSyncPayload.java b/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardSyncPayload.java
deleted file mode 100644
index d032769..0000000
--- a/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardSyncPayload.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.github.zly2006.carpetslsaddition.net;
-
-import com.github.zly2006.carpetslsaddition.ServerMain;
-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;
-
-import java.util.List;
-
-/**
- * 服务端 → 客户端:同步排行榜数据。
- *
- * 携带某一种榜单(死亡/挖掘)的完整排行:条目为 (玩家名, 分数),已按分数降序。
- * 客户端收到后缓存,供 HUD 渲染。
- */
-public record BoardSyncPayload(String board, List entries) implements CustomPacketPayload {
-
- /** 单条排行数据。 */
- public record Entry(String name, int score) {
- public static final StreamCodec CODEC = StreamCodec.composite(
- ByteBufCodecs.STRING_UTF8, Entry::name,
- ByteBufCodecs.VAR_INT, Entry::score,
- Entry::new
- );
- }
-
- public static final CustomPacketPayload.Type TYPE =
- new CustomPacketPayload.Type<>(net.minecraft.resources.Identifier.fromNamespaceAndPath("slsa", "board_sync"));
-
- public static final StreamCodec CODEC = StreamCodec.composite(
- ByteBufCodecs.STRING_UTF8, BoardSyncPayload::board,
- Entry.CODEC.apply(ByteBufCodecs.list()), BoardSyncPayload::entries,
- BoardSyncPayload::new
- );
-
- @Override
- public CustomPacketPayload.Type extends CustomPacketPayload> type() {
- return TYPE;
- }
-
- /** 在 onInitialize 里调用,注册 payload 类型。 */
- public static void register() {
- net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry.clientboundPlay().register(TYPE, CODEC);
- ServerMain.LOGGER.info("[SLSA] registered BoardSyncPayload");
- }
-}
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardTogglePayload.java b/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardTogglePayload.java
deleted file mode 100644
index be40d41..0000000
--- a/src/main/java/com/github/zly2006/carpetslsaddition/net/BoardTogglePayload.java
+++ /dev/null
@@ -1,31 +0,0 @@
-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})。
- *
- * 规则变化时下发;客户端收到后决定 {@code /slsboard} 是否可用。
- */
-public record BoardTogglePayload(boolean enabled) implements CustomPacketPayload {
-
- public static final CustomPacketPayload.Type TYPE =
- new CustomPacketPayload.Type<>(net.minecraft.resources.Identifier.fromNamespaceAndPath("slsa", "board_toggle"));
-
- public static final StreamCodec 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);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardManager.java b/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardManager.java
index f8ad4f6..cef23d6 100644
--- a/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardManager.java
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardManager.java
@@ -4,6 +4,9 @@ import com.github.zly2006.carpetslsaddition.ServerMain;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.stats.ServerStatsCounter;
+import net.minecraft.stats.Stat;
+import net.minecraft.stats.Stats;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.ScoreAccess;
import net.minecraft.world.scores.ScoreHolder;
@@ -13,12 +16,14 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
/**
* 死亡榜 / 挖掘榜的 scoreboard 管理器(服务端)。
*
- *
- * - {@code sls_deaths}:使用原版 {@link ObjectiveCriteria#DEATH_COUNT} 判据,自动统计死亡次数。
- * - {@code sls_mined}:使用 {@link ObjectiveCriteria#DUMMY} 判据,由 {@code BoardEvents} 在破坏方块时写入。
- *
+ * 数据源是 vanilla stats,但 stats 不会自动同步给其他客户端。
+ * 因此本类定期把 stats 的聚合值写入 scoreboard objective,
+ * 客户端再用原版计分板侧边栏显示(scoreboard 会自动同步给所有客户端)。
*
- *
objective 在服务器启动时自动创建;客户端会同步到这两个 objective,用于 HUD 显示。
+ *
+ * - {@code sls_deaths}:死亡次数(DUMMY 判据,从 stats 的 {@link Stats#DEATHS} 写入)。
+ * - {@code sls_mined}:挖掘总数(DUMMY 判据,从 stats 的 {@link Stats#BLOCK_MINED} 聚合)。
+ *
*/
public final class BoardManager {
@@ -31,41 +36,54 @@ public final class BoardManager {
public static void ensureObjectives(MinecraftServer server) {
Scoreboard sb = server.getScoreboard();
if (sb.getObjective(OBJ_DEATHS) == null) {
- sb.addObjective(
- OBJ_DEATHS,
- ObjectiveCriteria.DEATH_COUNT,
- Component.literal("死亡榜"),
- ObjectiveCriteria.RenderType.INTEGER,
- true,
- null
- );
+ sb.addObjective(OBJ_DEATHS, ObjectiveCriteria.DUMMY,
+ Component.literal("死亡榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_DEATHS);
}
if (sb.getObjective(OBJ_MINED) == null) {
- sb.addObjective(
- OBJ_MINED,
- ObjectiveCriteria.DUMMY,
- Component.literal("挖掘榜"),
- ObjectiveCriteria.RenderType.INTEGER,
- true,
- null
- );
+ sb.addObjective(OBJ_MINED, ObjectiveCriteria.DUMMY,
+ Component.literal("挖掘榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_MINED);
}
}
- /** 玩家破坏一个方块 → 挖掘榜 +1。 */
- public static void onBlockMined(ServerPlayer player, int count) {
- MinecraftServer server = ServerMain.server;
- if (server == null) {
- return;
+ /** 读取某玩家的挖掘总数(遍历 stats map 中 BLOCK_MINED 类型条目)。 */
+ private static int totalMined(ServerStatsCounter counter) {
+ int sum = 0;
+ for (Stat> stat : counter.stats.keySet()) {
+ if (stat.getType() == Stats.BLOCK_MINED) {
+ sum += counter.getValue(stat);
+ }
}
- Scoreboard sb = server.getScoreboard();
- Objective obj = sb.getObjective(OBJ_MINED);
+ return sum;
+ }
+
+ /** 读取某玩家的死亡次数。 */
+ private static int totalDeaths(ServerStatsCounter counter) {
+ return counter.getValue(Stats.CUSTOM, Stats.DEATHS);
+ }
+
+ /** 把一个分数写入 objective(若不存在则创建/更新)。 */
+ private static void setScore(Scoreboard sb, Objective obj, ServerPlayer player, int value) {
if (obj == null) {
return;
}
ScoreAccess access = sb.getOrCreatePlayerScore((ScoreHolder) player, obj);
- access.add(count);
+ access.set(value);
+ }
+
+ /** 从 stats 同步所有在线玩家的分数到 scoreboard(定期调用)。 */
+ public static void syncScores(MinecraftServer server) {
+ Scoreboard sb = server.getScoreboard();
+ Objective deaths = sb.getObjective(OBJ_DEATHS);
+ Objective mined = sb.getObjective(OBJ_MINED);
+ if (deaths == null && mined == null) {
+ return;
+ }
+ for (ServerPlayer player : server.getPlayerList().getPlayers()) {
+ ServerStatsCounter counter = player.getStats();
+ setScore(sb, deaths, player, totalDeaths(counter));
+ setScore(sb, mined, player, totalMined(counter));
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardSyncManager.java b/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardSyncManager.java
deleted file mode 100644
index 284bd91..0000000
--- a/src/main/java/com/github/zly2006/carpetslsaddition/util/BoardSyncManager.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.github.zly2006.carpetslsaddition.util;
-
-import com.github.zly2006.carpetslsaddition.ServerMain;
-import com.github.zly2006.carpetslsaddition.net.BoardSyncPayload;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
-import net.minecraft.core.registries.BuiltInRegistries;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.stats.ServerStatsCounter;
-import net.minecraft.stats.Stat;
-import net.minecraft.stats.Stats;
-import net.minecraft.world.level.block.Block;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * 服务端:读取玩家原版统计(挖掘/死亡),打包排行榜发给客户端。
- *
- * 数据源是 vanilla stats({@code stats/.json}),非 scoreboard。
- * 客户端原生拿不到别人的 stats,故由服务端读取后通过网络包 {@link BoardSyncPayload} 下发。
- */
-public final class BoardSyncManager {
-
- private BoardSyncManager() {}
-
- public static final String BOARD_DEATHS = "deaths";
- public static final String BOARD_MINED = "mined";
-
- /** 排行榜最多显示条目数。 */
- public static final int TOP_N = 10;
-
- /** 读取某玩家的挖掘总数(遍历该玩家的 stats,累加 BLOCK_MINED 类型条目)。 */
- private static int totalMined(ServerStatsCounter counter) {
- int sum = 0;
- int cnt = 0;
- for (Stat> stat : counter.stats.keySet()) {
- if (stat.getType() == Stats.BLOCK_MINED) {
- cnt++;
- sum += counter.getValue(stat);
- }
- }
- com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.info("[SLSA-BOARD] totalMined: mapSize={} minedTypes={} sum={}", counter.stats.size(), cnt, sum);
- return sum;
- }
- /** 读取某玩家的死亡次数。 */
- private static int deaths(ServerStatsCounter counter) {
- return counter.getValue(Stats.CUSTOM, Stats.DEATHS);
- }
-
- /** 构建某榜单的排行数据(降序,取前 TOP_N)。 */
- public static List buildBoard(MinecraftServer server, String board) {
- List list = new ArrayList<>();
- for (ServerPlayer player : server.getPlayerList().getPlayers()) {
- ServerStatsCounter counter = player.getStats();
- int score = BOARD_MINED.equals(board) ? totalMined(counter) : deaths(counter);
- if (score > 0) {
- list.add(new BoardSyncPayload.Entry(player.getScoreboardName(), score));
- }
- }
- list.sort(Comparator.comparingInt(BoardSyncPayload.Entry::score).reversed());
- if (list.size() > TOP_N) {
- return new ArrayList<>(list.subList(0, TOP_N));
- }
- 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;
- }
- List d = buildBoard(server, BOARD_DEATHS);
- List m = buildBoard(server, BOARD_MINED);
- com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.info("[SLSA-BOARD] syncTo {} deaths={} mined={}", player.getScoreboardName(), d.size(), m.size());
- ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_DEATHS, d));
- ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_MINED, m));
- }
-
- /** 同步给所有在线玩家。 */
- public static void syncToAll(MinecraftServer server) {
- for (ServerPlayer player : server.getPlayerList().getPlayers()) {
- syncTo(player);
- }
- }
-
- private static volatile Boolean lastEnabled = null;
- private static long lastSyncTick = 0;
-
- /** 由轮询线程每秒调用:规则值变化时立即同步;否则每 5 秒定期同步一次数据。 */
- public static void tick(MinecraftServer server) {
- boolean now = com.github.zly2006.carpetslsaddition.SLSCarpetSettings.slsBoardEnabled;
- if (lastEnabled == null || lastEnabled != now) {
- lastEnabled = now;
- syncToAll(server);
- return;
- }
- if (now) {
- long tick = server.getTickCount();
- if (tick - lastSyncTick >= 100) { // 每 100 tick(5秒)同步一次数据
- lastSyncTick = tick;
- syncToAll(server);
- }
- }
- }
-}