feat: 榜单同步服务端(读stats+网络包下发)
BoardSyncPayload自定义包,BoardSyncManager读原版stats(挖掘总数/死亡数)构建Top10排行。顺带补回之前漏掉的BotListManager.load()。
This commit is contained in:
1 parent
993001c4ef
commit
fba85271f4
4 files changed
+201
No files matched your search
@@ -43,6 +43,7 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
|
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
CarpetServer.manageExtension(this);
|
CarpetServer.manageExtension(this);
|
||||||
|
com.github.zly2006.carpetslsaddition.net.BoardSyncPayload.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,6 +54,8 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void onServerLoaded(MinecraftServer server) {
|
public void onServerLoaded(MinecraftServer server) {
|
||||||
ServerMain.server = server;
|
ServerMain.server = server;
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BotListManager.load();
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardSyncManager.syncToAll(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端 → 客户端:同步排行榜数据。
|
||||||
|
*
|
||||||
|
* <p>携带某一种榜单(死亡/挖掘)的完整排行:条目为 (玩家名, 分数),已按分数降序。
|
||||||
|
* 客户端收到后缓存,供 HUD 渲染。
|
||||||
|
*/
|
||||||
|
public record BoardSyncPayload(String board, List<Entry> entries) implements CustomPacketPayload {
|
||||||
|
|
||||||
|
/** 单条排行数据。 */
|
||||||
|
public record Entry(String name, int score) {
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, Entry> CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.STRING_UTF8, Entry::name,
|
||||||
|
ByteBufCodecs.VAR_INT, Entry::score,
|
||||||
|
Entry::new
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final CustomPacketPayload.Type<BoardSyncPayload> TYPE =
|
||||||
|
CustomPacketPayload.createType("slsa:board_sync");
|
||||||
|
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, BoardSyncPayload> 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.playS2C().register(TYPE, CODEC);
|
||||||
|
ServerMain.LOGGER.info("[SLSA] registered BoardSyncPayload");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.util;
|
||||||
|
|
||||||
|
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.world.scores.Objective;
|
||||||
|
import net.minecraft.world.scores.ScoreAccess;
|
||||||
|
import net.minecraft.world.scores.ScoreHolder;
|
||||||
|
import net.minecraft.world.scores.Scoreboard;
|
||||||
|
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 死亡榜 / 挖掘榜的 scoreboard 管理器(服务端)。
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code sls_deaths}:使用原版 {@link ObjectiveCriteria#DEATH_COUNT} 判据,自动统计死亡次数。</li>
|
||||||
|
* <li>{@code sls_mined}:使用 {@link ObjectiveCriteria#DUMMY} 判据,由 {@code BoardEvents} 在破坏方块时写入。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>objective 在服务器启动时自动创建;客户端会同步到这两个 objective,用于 HUD 显示。
|
||||||
|
*/
|
||||||
|
public final class BoardManager {
|
||||||
|
|
||||||
|
private BoardManager() {}
|
||||||
|
|
||||||
|
public static final String OBJ_DEATHS = "sls_deaths";
|
||||||
|
public static final String OBJ_MINED = "sls_mined";
|
||||||
|
|
||||||
|
/** 确保两个 objective 存在(服务器启动/重载时调用)。 */
|
||||||
|
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
|
||||||
|
);
|
||||||
|
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
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Scoreboard sb = server.getScoreboard();
|
||||||
|
Objective obj = sb.getObjective(OBJ_MINED);
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ScoreAccess access = sb.getOrCreatePlayerScore((ScoreHolder) player, obj);
|
||||||
|
access.add(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端:读取玩家原版统计(挖掘/死亡),打包排行榜发给客户端。
|
||||||
|
*
|
||||||
|
* <p>数据源是 vanilla stats({@code stats/<uuid>.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;
|
||||||
|
|
||||||
|
/** 读取某玩家的挖掘总数(遍历所有方块的 BLOCK_MINED 累加)。 */
|
||||||
|
private static int totalMined(ServerStatsCounter counter) {
|
||||||
|
int sum = 0;
|
||||||
|
for (Block block : BuiltInRegistries.BLOCK) {
|
||||||
|
sum += counter.getValue(Stats.BLOCK_MINED, block);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
/** 读取某玩家的死亡次数。 */
|
||||||
|
private static int deaths(ServerStatsCounter counter) {
|
||||||
|
return counter.getValue(Stats.CUSTOM, Stats.DEATHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建某榜单的排行数据(降序,取前 TOP_N)。 */
|
||||||
|
public static List<BoardSyncPayload.Entry> buildBoard(MinecraftServer server, String board) {
|
||||||
|
List<BoardSyncPayload.Entry> 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;
|
||||||
|
}
|
||||||
|
ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_DEATHS, buildBoard(server, BOARD_DEATHS)));
|
||||||
|
ServerPlayNetworking.send(player, new BoardSyncPayload(BOARD_MINED, buildBoard(server, BOARD_MINED)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步给所有在线玩家。 */
|
||||||
|
public static void syncToAll(MinecraftServer server) {
|
||||||
|
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||||
|
syncTo(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user