feat: 榜单客户端(接收包+/slsboard命令+右侧HUD)
BoardClient注册客户端入口:接收BoardSyncPayload、/slsboard deaths/mined/off切换显示、HudElementRegistry注册右侧HUD。各玩家本地状态独立,不同步。
This commit is contained in:
1 parent
f63a59495e
commit
ac6cd5c1b5
4 files changed
+200
No files matched your search
@@ -0,0 +1,69 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.client;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.net.BoardSyncPayload;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
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.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端入口:接收榜单数据、注册 /slsboard 命令、注册 HUD 渲染。
|
||||||
|
*
|
||||||
|
* <p>纯客户端功能:显示哪个榜由本地状态决定,各玩家可不同步。
|
||||||
|
*/
|
||||||
|
@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<BoardClientState.Entry> entries = new ArrayList<>();
|
||||||
|
for (BoardSyncPayload.Entry e : payload.entries()) {
|
||||||
|
entries.add(new BoardClientState.Entry(e.name(), e.score()));
|
||||||
|
}
|
||||||
|
context.client().execute(() -> BoardClientState.setBoard(payload.board(), entries));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) /slsboard 命令(纯客户端,非 OP 也能用)
|
||||||
|
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||||
|
dispatcher.register(
|
||||||
|
net.minecraft.commands.Commands.literal("slsboard")
|
||||||
|
.then(net.minecraft.commands.Commands.literal("deaths")
|
||||||
|
.executes(c -> { BoardClientState.toggle("deaths"); feedback(c.getSource()); return 1; }))
|
||||||
|
.then(net.minecraft.commands.Commands.literal("mined")
|
||||||
|
.executes(c -> { BoardClientState.toggle("mined"); feedback(c.getSource()); return 1; }))
|
||||||
|
.then(net.minecraft.commands.Commands.literal("off")
|
||||||
|
.executes(c -> { BoardClientState.set(""); feedback(c.getSource()); return 1; }))
|
||||||
|
.then(net.minecraft.commands.Commands.argument("board", StringArgumentType.word())
|
||||||
|
.suggests((c, b) -> net.minecraft.commands.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;
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3) HUD 渲染
|
||||||
|
HudElementRegistry.addLast(HUD_ID, new BoardHudElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void feedback(FabricClientCommandSource source) {
|
||||||
|
String d = BoardClientState.displaying;
|
||||||
|
source.sendFeedback(Component.literal("[SLSA] 榜单显示:" + (d.isEmpty() ? "关闭" : d)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.client;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端榜单状态(纯客户端,各玩家独立,不同步)。
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #displaying}:当前显示哪个榜("" = 不显示)。</li>
|
||||||
|
* <li>{@code deaths}/{@code mined}:从服务端收到的排行数据缓存。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class BoardClientState {
|
||||||
|
|
||||||
|
private BoardClientState() {}
|
||||||
|
|
||||||
|
/** 当前显示的榜:"" / "deaths" / "mined"。 */
|
||||||
|
public static volatile String displaying = "";
|
||||||
|
|
||||||
|
private static volatile List<Entry> deaths = Collections.emptyList();
|
||||||
|
private static volatile List<Entry> mined = Collections.emptyList();
|
||||||
|
|
||||||
|
public record Entry(String name, int score) {}
|
||||||
|
|
||||||
|
public static void setBoard(String board, List<Entry> entries) {
|
||||||
|
if ("deaths".equals(board)) {
|
||||||
|
deaths = entries;
|
||||||
|
} else if ("mined".equals(board)) {
|
||||||
|
mined = entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entry> 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 -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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。
|
||||||
|
*
|
||||||
|
* <p>纯客户端渲染,内容来自 {@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<BoardClientState.Entry> list = BoardClientState.current();
|
||||||
|
Font font = mc.font;
|
||||||
|
|
||||||
|
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 x = screenW - maxW - PAD * 2 - 2;
|
||||||
|
int y = 4;
|
||||||
|
|
||||||
|
// 背景
|
||||||
|
int h = PAD * 2 + LINE_H * (list.size() + 1);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
"com.github.zly2006.carpetslsaddition.ServerMain"
|
"com.github.zly2006.carpetslsaddition.ServerMain"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"com.github.zly2006.carpetslsaddition.client.BoardClient"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"accessWidener": "slsa.accesswidener",
|
"accessWidener": "slsa.accesswidener",
|
||||||
|
|||||||
Reference in new issue
Block a user