From 1964b224398de7f51f56035420a33301d3ec027a Mon Sep 17 00:00:00 2001 From: David Mayr Date: Sat, 17 Feb 2024 21:22:00 +0100 Subject: [PATCH] Scoreboard objective number format api (#10036) * feat: number format api Signed-off-by: David Mayr * feat: make each individual score customizable Signed-off-by: David Mayr * docs: fix incorrect descriptions Signed-off-by: David Mayr * feat: use access transformers Signed-off-by: David Mayr * feat: use adventure codecs Signed-off-by: David Mayr * test: test for matching styles Signed-off-by: David Mayr * feat: convert number formats to interfaces Signed-off-by: David Mayr * feat: add style conversion to adventure patch Signed-off-by: David Mayr * feat: use paper adventure method in PaperScoreboardFormat Signed-off-by: David Mayr * chore: rename methods to avoid a method in records Signed-off-by: David Mayr * fix: check if objective is still registered Signed-off-by: David Mayr * feat: improve style conversion Signed-off-by: David Mayr * feat: modify how the getter behaves in score Signed-off-by: David Mayr * feat: use fluent naming Signed-off-by: David Mayr * docs: add spaces before the paper comments Signed-off-by: David Mayr * chore: styling changes Signed-off-by: David Mayr * chore: make constant final Signed-off-by: David Mayr * feat: add methods for styled format instead of constants Signed-off-by: David Mayr * fix: remove incorrect getTrackedPlayers check Signed-off-by: David Mayr * docs: add . at the end of sentences Signed-off-by: David Mayr * docs: explain null behaviour Signed-off-by: David Mayr * docs: mention score creation Signed-off-by: David Mayr * rebase and fix javadoc comments * remove server implementation defaults * fix format for PaperScoreboardFormat --------- Signed-off-by: David Mayr Co-authored-by: Jake Potrebic --- patches/api/0463-add-number-format-api.patch | 229 ++++++++++++++++++ patches/server/0010-Adventure.patch | 22 +- ...oleAppender-for-console-improvements.patch | 8 +- .../server/1046-add-number-format-api.patch | 138 +++++++++++ 4 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 patches/api/0463-add-number-format-api.patch create mode 100644 patches/server/1046-add-number-format-api.patch diff --git a/patches/api/0463-add-number-format-api.patch b/patches/api/0463-add-number-format-api.patch new file mode 100644 index 000000000..6d32302ad --- /dev/null +++ b/patches/api/0463-add-number-format-api.patch @@ -0,0 +1,229 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Mayr +Date: Sat, 16 Dec 2023 10:40:29 +0100 +Subject: [PATCH] add number format api + +Signed-off-by: David Mayr + +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/BlankFormatImpl.java b/src/main/java/io/papermc/paper/scoreboard/numbers/BlankFormatImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..486da6ebe0137bb3280e8b33c8e35e309507f118 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/BlankFormatImpl.java +@@ -0,0 +1,5 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++record BlankFormatImpl() implements NumberFormat { ++ public static final BlankFormatImpl INSTANCE = new BlankFormatImpl(); ++} +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormat.java b/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..66e0569789d523076cb571fb32be78ecff74305b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormat.java +@@ -0,0 +1,19 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A scoreboard number format that replaces the score number with a chat component. ++ */ ++public interface FixedFormat extends NumberFormat, ComponentLike { ++ ++ /** ++ * The component shown instead of the number for a score ++ * ++ * @return the chat component ++ */ ++ @NotNull Component component(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormatImpl.java b/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormatImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..969bbfcdb68ffb5a207207e20e4d79621900c0f5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/FixedFormatImpl.java +@@ -0,0 +1,12 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++import net.kyori.adventure.text.Component; ++import org.jetbrains.annotations.NotNull; ++ ++record FixedFormatImpl(@NotNull Component component) implements FixedFormat { ++ ++ @Override ++ public @NotNull Component asComponent() { ++ return this.component(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/NumberFormat.java b/src/main/java/io/papermc/paper/scoreboard/numbers/NumberFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eadf637f5fc582a2af5db71274ac1f01b2a28913 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/NumberFormat.java +@@ -0,0 +1,60 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.format.Style; ++import net.kyori.adventure.text.format.StyleBuilderApplicable; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Describes a scoreboard number format that applies custom formatting to scoreboard scores. ++ */ ++public interface NumberFormat { ++ ++ /** ++ * Creates a blank scoreboard number format that removes the score number entirely. ++ * ++ * @return a blank number format ++ */ ++ static @NotNull NumberFormat blank() { ++ return BlankFormatImpl.INSTANCE; ++ } ++ ++ /** ++ * Gets an un-styled number format. ++ * ++ * @return an un-styled number format ++ */ ++ static @NotNull StyledFormat noStyle() { ++ return StyledFormatImpl.NO_STYLE; ++ } ++ ++ /** ++ * Creates a scoreboard number format that applies a custom formatting to the score number. ++ * ++ * @param style the style to apply on the number ++ * @return a styled number format ++ */ ++ static @NotNull StyledFormat styled(final @NotNull Style style) { ++ return new StyledFormatImpl(style); ++ } ++ ++ /** ++ * Creates a scoreboard number format that applies a custom formatting to the score number. ++ * ++ * @param styleBuilderApplicables the style to apply on the number ++ * @return a styled number format ++ */ ++ static @NotNull StyledFormat styled(final @NotNull StyleBuilderApplicable @NotNull... styleBuilderApplicables) { ++ return styled(Style.style(styleBuilderApplicables)); ++ } ++ ++ /** ++ * Creates a scoreboard number format that replaces the score number with a chat component. ++ * ++ * @param component the component to replace the number with ++ * @return a fixed number format ++ */ ++ static @NotNull FixedFormat fixed(final @NotNull ComponentLike component) { ++ return new FixedFormatImpl(component.asComponent()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormat.java b/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe844677d689c3afe5ff2b706d562724e4121137 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormat.java +@@ -0,0 +1,19 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++import net.kyori.adventure.text.format.Style; ++import net.kyori.adventure.text.format.StyleBuilderApplicable; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A scoreboard number format that applies a custom formatting to the score number. ++ */ ++public interface StyledFormat extends NumberFormat, StyleBuilderApplicable { ++ ++ /** ++ * The style that is being applied to the number in the score ++ * ++ * @return the style to apply ++ */ ++ @NotNull Style style(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormatImpl.java b/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormatImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0421e6c7cb32a912cf4aa281623c4311d5d1a34f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/scoreboard/numbers/StyledFormatImpl.java +@@ -0,0 +1,13 @@ ++package io.papermc.paper.scoreboard.numbers; ++ ++import net.kyori.adventure.text.format.Style; ++import org.jetbrains.annotations.NotNull; ++ ++record StyledFormatImpl(@NotNull Style style) implements StyledFormat { ++ static final StyledFormat NO_STYLE = new StyledFormatImpl(Style.empty()); ++ ++ @Override ++ public void styleApply(final Style.@NotNull Builder style) { ++ style.merge(this.style); ++ } ++} +diff --git a/src/main/java/org/bukkit/scoreboard/Objective.java b/src/main/java/org/bukkit/scoreboard/Objective.java +index bd4d84cbf220ab02f09ece97873bbf0bdf7a45ba..1750f97d2122e6e597b9549df8f6fa74bf5e2e2d 100644 +--- a/src/main/java/org/bukkit/scoreboard/Objective.java ++++ b/src/main/java/org/bukkit/scoreboard/Objective.java +@@ -195,4 +195,22 @@ public interface Objective { + */ + void setAutoUpdateDisplay(boolean autoUpdateDisplay); + // Paper end - add more score API ++ ++ // Paper start - number format api ++ /** ++ * Gets the number format for this objective's scores or null if the client default is used. ++ * ++ * @return this objective's number format, or null if the client default is used ++ * @throws IllegalStateException if this objective has been unregistered ++ */ ++ @Nullable io.papermc.paper.scoreboard.numbers.NumberFormat numberFormat(); ++ ++ /** ++ * Sets the number format for this objective's scores. ++ * ++ * @param format the number format to set, pass null to reset format to default ++ * @throws IllegalStateException if this objective has been unregistered ++ */ ++ void numberFormat(@Nullable io.papermc.paper.scoreboard.numbers.NumberFormat format); ++ // Paper end - number format api + } +diff --git a/src/main/java/org/bukkit/scoreboard/Score.java b/src/main/java/org/bukkit/scoreboard/Score.java +index 5b6f243492d55d2db0d6944dc6daca9b181551d6..fba8e475c1f1a410c44a95fcc474cce19e0f515c 100644 +--- a/src/main/java/org/bukkit/scoreboard/Score.java ++++ b/src/main/java/org/bukkit/scoreboard/Score.java +@@ -129,4 +129,26 @@ public interface Score { + */ + void customName(net.kyori.adventure.text.@Nullable Component customName); + // Paper end - add more score API ++ ++ // Paper start - number format api ++ /** ++ * Gets the number format for this score or null if the score has not been set yet ++ * or the objective's default is being used. ++ * ++ * @return this score's number format, or null if the objective's default is used or the score doesn't exist ++ * @throws IllegalStateException if the associated objective has been ++ * unregistered ++ */ ++ @Nullable io.papermc.paper.scoreboard.numbers.NumberFormat numberFormat(); ++ ++ /** ++ * Sets the number format for this score. If this score has not been set yet {@link #isScoreSet()}, it will be created ++ * ++ * @param format the number format to set, pass null to reset format to default ++ * @throws IllegalStateException if the associated objective has been ++ * unregistered ++ */ ++ void numberFormat(@Nullable io.papermc.paper.scoreboard.numbers.NumberFormat format); ++ // Paper end - number format api ++ + } diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index 631f6c96d..2ba9bdac0 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -1250,7 +1250,7 @@ new file mode 100644 index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea9d062483 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -@@ -0,0 +1,401 @@ +@@ -0,0 +1,421 @@ +package io.papermc.paper.adventure; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -1275,6 +1275,7 @@ index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.flattener.ComponentFlattener; ++import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -1285,6 +1286,7 @@ index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea +import net.kyori.adventure.translation.Translator; +import net.kyori.adventure.util.Codec; +import net.minecraft.ChatFormatting; ++import net.minecraft.Util; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; @@ -1651,6 +1653,24 @@ index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea + public static @Nullable ChatFormatting asVanilla(final TextColor color) { + return ChatFormatting.getByHexValue(color.value()); + } ++ ++ // Style ++ ++ public static net.minecraft.network.chat.Style asVanilla(Style style) { ++ Object encoded = Util.getOrThrow(AdventureCodecs.STYLE_MAP_CODEC.codec() ++ .encodeStart(net.minecraft.util.JavaOps.INSTANCE, style), IllegalStateException::new); ++ ++ return Util.getOrThrow(net.minecraft.network.chat.Style.Serializer.CODEC ++ .parse(net.minecraft.util.JavaOps.INSTANCE, encoded), IllegalStateException::new); ++ } ++ ++ public static Style asAdventure(net.minecraft.network.chat.Style style) { ++ Object encoded = Util.getOrThrow(net.minecraft.network.chat.Style.Serializer.CODEC ++ .encodeStart(net.minecraft.util.JavaOps.INSTANCE, style), IllegalStateException::new); ++ ++ return Util.getOrThrow(AdventureCodecs.STYLE_MAP_CODEC.codec() ++ .parse(net.minecraft.util.JavaOps.INSTANCE, encoded), IllegalStateException::new); ++ } +} diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java new file mode 100644 diff --git a/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch index be1dff00d..125139cc3 100644 --- a/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch +++ b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -206,18 +206,18 @@ index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a170 + +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -index 2e757cd9b01ac7eba1e4723a6e21dcea9d062483..ca80cbe422d766b3d044a5b53ce40bb7f92558e4 100644 +index 032d23ecda574ed1a3c740c16d13055f399bd6c4..cf5a4c142ed6d9ab4850373a9041bdc6af5a2a71 100644 --- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -@@ -24,6 +24,7 @@ import net.kyori.adventure.text.TranslationArgument; - import net.kyori.adventure.text.flattener.ComponentFlattener; +@@ -25,6 +25,7 @@ import net.kyori.adventure.text.flattener.ComponentFlattener; + import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -@@ -111,6 +112,7 @@ public final class PaperAdventure { +@@ -113,6 +114,7 @@ public final class PaperAdventure { public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger @Deprecated public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); diff --git a/patches/server/1046-add-number-format-api.patch b/patches/server/1046-add-number-format-api.patch new file mode 100644 index 000000000..d412b5f92 --- /dev/null +++ b/patches/server/1046-add-number-format-api.patch @@ -0,0 +1,138 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Mayr +Date: Sat, 16 Dec 2023 10:40:29 +0100 +Subject: [PATCH] add number format api + +== AT == +public net.minecraft.network.chat.numbers.FixedFormat value +public net.minecraft.network.chat.numbers.StyledFormat style + +diff --git a/src/main/java/io/papermc/paper/util/PaperScoreboardFormat.java b/src/main/java/io/papermc/paper/util/PaperScoreboardFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6064086cc76ef0df999c7057121d0ac22bd4df65 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/PaperScoreboardFormat.java +@@ -0,0 +1,38 @@ ++package io.papermc.paper.util; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.scoreboard.numbers.NumberFormat; ++ ++public final class PaperScoreboardFormat { ++ ++ private PaperScoreboardFormat() { ++ } ++ ++ public static net.minecraft.network.chat.numbers.NumberFormat asVanilla(final NumberFormat format) { ++ final net.minecraft.network.chat.numbers.NumberFormat vanilla; ++ if (format instanceof final io.papermc.paper.scoreboard.numbers.StyledFormat styled) { ++ vanilla = new net.minecraft.network.chat.numbers.StyledFormat(PaperAdventure.asVanilla(styled.style())); ++ } else if (format instanceof final io.papermc.paper.scoreboard.numbers.FixedFormat fixed) { ++ vanilla = new net.minecraft.network.chat.numbers.FixedFormat(io.papermc.paper.adventure.PaperAdventure ++ .asVanilla(fixed.component())); ++ } else if (format.equals(NumberFormat.blank())) { ++ vanilla = net.minecraft.network.chat.numbers.BlankFormat.INSTANCE; ++ } else { ++ throw new IllegalArgumentException("Unknown format type " + format.getClass()); ++ } ++ ++ return vanilla; ++ } ++ ++ public static NumberFormat asPaper(final net.minecraft.network.chat.numbers.NumberFormat vanilla) { ++ if (vanilla instanceof final net.minecraft.network.chat.numbers.StyledFormat styled) { ++ return NumberFormat.styled(PaperAdventure.asAdventure(styled.style)); ++ } else if (vanilla instanceof final net.minecraft.network.chat.numbers.FixedFormat fixed) { ++ return NumberFormat.fixed(io.papermc.paper.adventure.PaperAdventure.asAdventure(fixed.value)); ++ } else if (vanilla instanceof net.minecraft.network.chat.numbers.BlankFormat) { ++ return NumberFormat.blank(); ++ } ++ ++ throw new IllegalArgumentException("Unknown format type " + vanilla.getClass()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +index 2d3abf2a1da487ead74d698cc5ea4eb729c35c8d..1fec80c4f02aab3770c05bac8bfa2b622625e630 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +@@ -153,6 +153,34 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective + } + // Paper end + ++ // Paper start - add number format ++ @Override ++ public io.papermc.paper.scoreboard.numbers.NumberFormat numberFormat() { ++ this.checkState(); ++ ++ net.minecraft.network.chat.numbers.NumberFormat vanilla = this.objective.numberFormat(); ++ ++ if (vanilla == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.PaperScoreboardFormat.asPaper(vanilla); ++ } ++ ++ ++ @Override ++ public void numberFormat(io.papermc.paper.scoreboard.numbers.NumberFormat format) { ++ this.checkState(); ++ ++ if (format == null) { ++ this.objective.setNumberFormat(null); ++ return; ++ } ++ ++ this.objective.setNumberFormat(io.papermc.paper.util.PaperScoreboardFormat.asVanilla(format)); ++ } ++ // Paper end - add number format ++ + @Override + public void unregister() { + CraftScoreboard scoreboard = this.checkState(); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java +index 74d9c407e971804bed420370f7b684d8658eb5aa..e307e897d6e1ba4cb21883dfeaf334bfbf56cfc4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java +@@ -55,6 +55,41 @@ final class CraftScore implements Score { + this.objective.checkState().board.getOrCreatePlayerScore(this.entry, this.objective.getHandle()).set(score); + } + ++ ++ // Paper start - add number format ++ @Override ++ public io.papermc.paper.scoreboard.numbers.NumberFormat numberFormat() { ++ ReadOnlyScoreInfo scoreInfo = this.objective.checkState().board ++ .getPlayerScoreInfo(this.entry, this.objective.getHandle()); ++ ++ if (scoreInfo == null) { ++ return null; ++ } ++ ++ net.minecraft.network.chat.numbers.NumberFormat vanilla = scoreInfo.numberFormat(); ++ ++ if (vanilla == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.PaperScoreboardFormat.asPaper(vanilla); ++ } ++ ++ ++ @Override ++ public void numberFormat(io.papermc.paper.scoreboard.numbers.NumberFormat format) { ++ final net.minecraft.world.scores.ScoreAccess access = this.objective.checkState() ++ .board.getOrCreatePlayerScore(this.entry, this.objective.getHandle()); ++ ++ if (format == null) { ++ access.numberFormatOverride(null); ++ return; ++ } ++ ++ access.numberFormatOverride(io.papermc.paper.util.PaperScoreboardFormat.asVanilla(format)); ++ } ++ // Paper end - add number format ++ + @Override + public boolean isScoreSet() { + Scoreboard board = this.objective.checkState().board;