From 2003f75f1f9ec7381644c08ee43fa98bf268521f Mon Sep 17 00:00:00 2001 From: DeidaraMC <117625071+DeidaraMC@users.noreply.github.com> Date: Tue, 2 Jan 2024 06:15:19 -0500 Subject: [PATCH] new scoreboard protocol number format conformance (#98) * new scoreboard protocol conformance * fix: pass number format through when reading scoreboard packet --------- Co-authored-by: mworzala --- .../src/main/java/net/minestom/demo/Main.java | 1 + .../demo/commands/SidebarCommand.java | 99 +++++++++++++++++++ .../play/ScoreboardObjectivePacket.java | 17 ++-- .../packet/server/play/UpdateScorePacket.java | 18 +--- .../server/scoreboard/Scoreboard.java | 5 +- .../minestom/server/scoreboard/Sidebar.java | 99 ++++++++++++++++--- 6 files changed, 200 insertions(+), 39 deletions(-) create mode 100644 demo/src/main/java/net/minestom/demo/commands/SidebarCommand.java diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 66d014c5b..4bf6d270a 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -64,6 +64,7 @@ public class Main { commandManager.register(new NotificationCommand()); commandManager.register(new TestCommand2()); commandManager.register(new ConfigCommand()); + commandManager.register(new SidebarCommand()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED))); diff --git a/demo/src/main/java/net/minestom/demo/commands/SidebarCommand.java b/demo/src/main/java/net/minestom/demo/commands/SidebarCommand.java new file mode 100644 index 000000000..139a4ff4e --- /dev/null +++ b/demo/src/main/java/net/minestom/demo/commands/SidebarCommand.java @@ -0,0 +1,99 @@ +package net.minestom.demo.commands; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandContext; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.condition.Conditions; +import net.minestom.server.entity.Player; +import net.minestom.server.scoreboard.Sidebar; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SidebarCommand extends Command { + private final Sidebar sidebar = new Sidebar(Component.text("DEMO").decorate(TextDecoration.BOLD)); + private int currentLine = 0; + + public SidebarCommand() { + super("sidebar"); + + addLine("BLANK ", Sidebar.NumberFormat.blank()); + addLine("STYLE ", Sidebar.NumberFormat.styled(Component.empty().decorate(TextDecoration.STRIKETHROUGH).color(NamedTextColor.GRAY))); + addLine("FIXED ", Sidebar.NumberFormat.fixed(Component.text("FIXED").color(NamedTextColor.GRAY))); + addLine("NULL ", null); + + setDefaultExecutor((source, args) -> source.sendMessage(Component.text("Unknown syntax (note: title must be quoted)"))); + setCondition(Conditions::playerOnly); + + var option = ArgumentType.Word("option").from("add-line", "remove-line", "set-title", "toggle", "update-content", "update-score"); + var content = ArgumentType.String("content").setDefaultValue(""); + var targetLine = ArgumentType.Integer("target line").setDefaultValue(-1); + + addSyntax(this::handleSidebar, option); + addSyntax(this::handleSidebar, option, content); + addSyntax(this::handleSidebar, option, content, targetLine); + } + + + private void handleSidebar(CommandSender source, CommandContext context) { + Player player = (Player) source; + String option = context.get("option"); + String content = context.get("content"); + int targetLine = context.get("target line"); + if (targetLine == -1) targetLine = currentLine; + switch (option) { + case "add-line": + addLine(content, null); + break; + case "remove-line": + removeLine(); + break; + case "set-title": + setTitle(content); + break; + case "toggle": + toggleSidebar(player); + break; + case "update-content": + updateLineContent(content, String.valueOf(targetLine)); + break; + case "update-score": + updateLineScore(Integer.parseInt(content), String.valueOf(targetLine)); + break; + } + } + + private void addLine(@NotNull String content, @Nullable Sidebar.NumberFormat numberFormat) { + if (currentLine < 16) { + sidebar.createLine(new Sidebar.ScoreboardLine(String.valueOf(currentLine), Component.text(content).color(NamedTextColor.WHITE), currentLine, numberFormat)); + currentLine++; + } + } + + private void removeLine() { + if (currentLine > 0) { + sidebar.removeLine(String.valueOf(currentLine)); + currentLine--; + } + } + + private void setTitle(@NotNull String title) { + sidebar.setTitle(Component.text(title).decorate(TextDecoration.BOLD)); + } + + private void toggleSidebar(Player player) { + if (sidebar.getViewers().contains(player)) sidebar.removeViewer(player); + else sidebar.addViewer(player); + } + + private void updateLineContent(@NotNull String content, @NotNull String lineId) { + sidebar.updateLineContent(lineId, Component.text(content).color(NamedTextColor.WHITE)); + } + + private void updateLineScore(int score, @NotNull String lineId) { + sidebar.updateLineScore(lineId, score); + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java index 7a80a6d63..7dbd48ac6 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java @@ -6,6 +6,7 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.scoreboard.Sidebar; import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,25 +19,28 @@ import static net.minestom.server.network.NetworkBuffer.*; public record ScoreboardObjectivePacket(@NotNull String objectiveName, byte mode, @Nullable Component objectiveValue, - @Nullable Type type) implements ComponentHoldingServerPacket { + @Nullable Type type, + @Nullable Sidebar.NumberFormat numberFormat) implements ComponentHoldingServerPacket { public ScoreboardObjectivePacket(@NotNull NetworkBuffer reader) { this(read(reader)); } private ScoreboardObjectivePacket(ScoreboardObjectivePacket packet) { - this(packet.objectiveName, packet.mode, packet.objectiveValue, packet.type); + this(packet.objectiveName, packet.mode, packet.objectiveValue, packet.type, packet.numberFormat); } private static ScoreboardObjectivePacket read(@NotNull NetworkBuffer reader) { - var objectiveName = reader.read(STRING); - var mode = reader.read(BYTE); + String objectiveName = reader.read(STRING); + byte mode = reader.read(BYTE); Component objectiveValue = null; Type type = null; + Sidebar.NumberFormat numberFormat = null; if (mode == 0 || mode == 2) { objectiveValue = reader.read(COMPONENT); type = Type.values()[reader.read(VAR_INT)]; + numberFormat = reader.readOptional(Sidebar.NumberFormat::new); } - return new ScoreboardObjectivePacket(objectiveName, mode, objectiveValue, type); + return new ScoreboardObjectivePacket(objectiveName, mode, objectiveValue, type, numberFormat); } @Override @@ -48,6 +52,7 @@ public record ScoreboardObjectivePacket(@NotNull String objectiveName, byte mode writer.write(COMPONENT, objectiveValue); assert type != null; writer.write(VAR_INT, type.ordinal()); + writer.writeOptional(numberFormat); } } @@ -68,7 +73,7 @@ public record ScoreboardObjectivePacket(@NotNull String objectiveName, byte mode @Override public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { return mode == 0 || mode == 2 ? new ScoreboardObjectivePacket(objectiveName, mode, - operator.apply(objectiveValue), type) : this; + operator.apply(objectiveValue), type, numberFormat) : this; } /** diff --git a/src/main/java/net/minestom/server/network/packet/server/play/UpdateScorePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/UpdateScorePacket.java index 8de4dffe7..7b0a23593 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/UpdateScorePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/UpdateScorePacket.java @@ -5,6 +5,7 @@ import net.minestom.server.network.ConnectionState; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.scoreboard.Sidebar; import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,11 +21,11 @@ public record UpdateScorePacket( @NotNull String objectiveName, int score, @Nullable Component displayName, - @Nullable NumberFormat numberFormat + @Nullable Sidebar.NumberFormat numberFormat ) implements ServerPacket { public UpdateScorePacket(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(STRING), reader.read(VAR_INT), - reader.readOptional(COMPONENT), new NumberFormat(reader)); + reader.readOptional(COMPONENT), reader.readOptional(Sidebar.NumberFormat::new)); } @Override @@ -43,17 +44,4 @@ public record UpdateScorePacket( default -> PacketUtils.invalidPacketState(getClass(), state, ConnectionState.PLAY); }; } - - public record NumberFormat() implements Writer { - - public NumberFormat(@NotNull NetworkBuffer reader) { - this(); - throw new UnsupportedOperationException("TODO"); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - throw new UnsupportedOperationException("TODO"); - } - } } diff --git a/src/main/java/net/minestom/server/scoreboard/Scoreboard.java b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java index 116253c63..ca3faecdd 100644 --- a/src/main/java/net/minestom/server/scoreboard/Scoreboard.java +++ b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java @@ -6,7 +6,6 @@ import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; -import net.minestom.server.network.packet.server.play.UpdateScorePacket; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -38,7 +37,7 @@ public interface Scoreboard extends Viewable, PacketGroupingAudience { * @return the creation objective packet */ default @NotNull ScoreboardObjectivePacket getCreationObjectivePacket(Component value, ScoreboardObjectivePacket.Type type) { - return new ScoreboardObjectivePacket(getObjectiveName(), (byte) 0, value, type); + return new ScoreboardObjectivePacket(getObjectiveName(), (byte) 0, value, type, null); } /** @@ -47,7 +46,7 @@ public interface Scoreboard extends Viewable, PacketGroupingAudience { * @return the destruction objective packet */ default @NotNull ScoreboardObjectivePacket getDestructionObjectivePacket() { - return new ScoreboardObjectivePacket(getObjectiveName(), (byte) 1, null, null); + return new ScoreboardObjectivePacket(getObjectiveName(), (byte) 1, null, null, null); } /** diff --git a/src/main/java/net/minestom/server/scoreboard/Sidebar.java b/src/main/java/net/minestom/server/scoreboard/Sidebar.java index 3b5185e85..73adccbd4 100644 --- a/src/main/java/net/minestom/server/scoreboard/Sidebar.java +++ b/src/main/java/net/minestom/server/scoreboard/Sidebar.java @@ -4,10 +4,8 @@ import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.entity.Player; -import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; -import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; -import net.minestom.server.network.packet.server.play.TeamsPacket; -import net.minestom.server.network.packet.server.play.UpdateScorePacket; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.play.*; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,7 +96,7 @@ public class Sidebar implements Scoreboard { public void setTitle(@NotNull Component title) { this.title = title; sendPacketToViewers(new ScoreboardObjectivePacket(objectiveName, (byte) 2, title, - ScoreboardObjectivePacket.Type.INTEGER)); + ScoreboardObjectivePacket.Type.INTEGER, null)); } /** @@ -260,6 +258,10 @@ public class Sidebar implements Scoreboard { * The score of the line */ private int line; + /** + * The number format of the line + */ + private NumberFormat numberFormat; private final String teamName; /** @@ -273,9 +275,14 @@ public class Sidebar implements Scoreboard { private SidebarTeam sidebarTeam; public ScoreboardLine(@NotNull String id, @NotNull Component content, int line) { + this(id, content, line, null); + } + + public ScoreboardLine(@NotNull String id, @NotNull Component content, int line, @Nullable NumberFormat numberFormat) { this.id = id; this.content = content; this.line = line; + this.numberFormat = numberFormat; this.teamName = TEAM_PREFIX + COUNTER.incrementAndGet(); } @@ -335,9 +342,8 @@ public class Sidebar implements Scoreboard { * @return a {@link UpdateScorePacket} */ private UpdateScorePacket getScoreCreationPacket(String objectiveName) { - //todo - throw new UnsupportedOperationException(); -// return new UpdateScorePacket(entityName, (byte) 0, objectiveName, line); + //TODO displayName acts as a suffix to the objective name, find way to handle elegantly + return new UpdateScorePacket(entityName, objectiveName, line, Component.empty(), numberFormat); } /** @@ -346,10 +352,8 @@ public class Sidebar implements Scoreboard { * @param objectiveName The objective name to be destroyed * @return a {@link UpdateScorePacket} */ - private UpdateScorePacket getScoreDestructionPacket(String objectiveName) { - //todo - throw new UnsupportedOperationException(); -// return new UpdateScorePacket(entityName, (byte) 1, objectiveName, 0); + private ResetScorePacket getScoreDestructionPacket(String objectiveName) { + return new ResetScorePacket(entityName, objectiveName); } /** @@ -360,9 +364,8 @@ public class Sidebar implements Scoreboard { * @return a {@link UpdateScorePacket} */ private UpdateScorePacket getLineScoreUpdatePacket(String objectiveName, int score) { - //todo - throw new UnsupportedOperationException(); -// return new UpdateScorePacket(entityName, (byte) 0, objectiveName, score); + //TODO displayName acts as a suffix to the objective name, find way to handle elegantly + return new UpdateScorePacket(entityName, objectiveName, score, Component.empty(), numberFormat); } /** @@ -466,4 +469,70 @@ public class Sidebar implements Scoreboard { this.prefix = prefix; } } + + + public static class NumberFormat implements NetworkBuffer.Writer { + private final FormatType formatType; + private final Component content; + + private NumberFormat() { + this.content = null; + this.formatType = FormatType.BLANK; + } + + private NumberFormat(@NotNull Component content, @NotNull FormatType formatType) { + this.content = content; + this.formatType = formatType; + } + + public NumberFormat(NetworkBuffer reader) { + this.formatType = FormatType.values()[reader.read(NetworkBuffer.VAR_INT)]; + if (formatType != FormatType.BLANK) this.content = reader.read(NetworkBuffer.COMPONENT); + else this.content = null; + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.VAR_INT, formatType.ordinal()); + if (formatType == FormatType.STYLED) { + assert content != null; + writer.write(NetworkBuffer.COMPONENT, content); + } + else if (formatType == FormatType.FIXED) { + assert content != null; + writer.write(NetworkBuffer.COMPONENT, content); + } + } + + /** + * A number format which has no sidebar score displayed + * + * @return a blank number format + */ + public static @NotNull NumberFormat blank() { + return new NumberFormat(); + } + + /** + * A number format which lets the sidebar scores be styled + * + * @param style a styled component + */ + public static @NotNull NumberFormat styled(@NotNull Component style) { + return new NumberFormat(style, FormatType.STYLED); + } + + /** + * A number format which lets the sidebar scores be styled with explicit text + * + * @param content the fixed component + */ + public static @NotNull NumberFormat fixed(@NotNull Component content) { + return new NumberFormat(content, FormatType.FIXED); + } + + private enum FormatType { + BLANK, STYLED, FIXED + } + } }